Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions .github/workflows/publish.pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ on:
release:
types: [created]
workflow_dispatch:
inputs:
package:
description: "Build all root packages or only the SDK"
type: choice
default: all
options:
- all
- sdk
publish:
description: "Publish built packages (false builds only)"
type: boolean
default: false

permissions:
contents: read
Expand All @@ -25,10 +37,37 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: 🛞 Create Wheels
# Packaging tools are small; avoid restoring/saving the runtime pip cache.
env:
PACKAGE_SELECTION: ${{ github.event_name == 'workflow_dispatch' && inputs.package || 'all' }}
run: |
make create_pypi_wheels
- name: 🚀 Publish to PyPi
if [ "$PACKAGE_SELECTION" = "sdk" ]; then
python -m pip install "setuptools>=83.0.0" "wheel>=0.38.1,<=0.45.1"
make create_sdk_wheel
else
make create_pypi_wheels
fi
- name: 🧹 Separate SDK publication
# Keep SDK publication in this existing trusted workflow. Its isolated
# build can precede Workflows without resolving the consumer lock.
run: |
mkdir -p sdk-dist
mv dist/pypi/inference_sdk-*.whl sdk-dist/
- name: 📤 Upload built wheels
uses: actions/upload-artifact@v4
with:
name: inference-wheels
path: |
dist/pypi/*.whl
sdk-dist/*.whl
- name: 🚀 Publish SDK to PyPI
if: ${{ github.event_name == 'release' || inputs.publish == true }}
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: sdk-dist/
# A dependency bootstrap may publish the SDK before the root release.
skip-existing: true
- name: 🚀 Publish root packages to PyPI
if: ${{ github.event_name == 'release' || (inputs.publish == true && inputs.package != 'sdk') }}
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/pypi/
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: style check_code_quality download_fonts create_workflows_wheel create_streamvision_wheel create_models_wheel create_isolation_wheels create_pypi_wheels
.PHONY: style check_code_quality download_fonts create_workflows_wheel create_streamvision_wheel create_models_wheel create_isolation_wheels create_pypi_wheels create_sdk_wheel

serve:
SKIP_CODEGEN=1 python -m zensical serve
Expand Down Expand Up @@ -100,7 +100,7 @@ create_wheels: create_workflows_wheel create_models_wheel create_streamvision_wh
rm -rf build/*
python .release/pypi/inference.cli.setup.py bdist_wheel

create_wheels_for_gpu_notebook: create_workflows_wheel create_streamvision_wheel
create_wheels_for_gpu_notebook: create_workflows_wheel create_models_wheel create_streamvision_wheel
python -m pip install --upgrade pip
python -m pip install setuptools wheel twine requests
rm -rf build/*
Expand All @@ -112,6 +112,10 @@ create_wheels_for_gpu_notebook: create_workflows_wheel create_streamvision_wheel
rm -rf build/*
python .release/pypi/inference.cli.setup.py bdist_wheel

create_sdk_wheel:
rm -rf build/*
${PYTHON} .release/pypi/inference.sdk.setup.py bdist_wheel --dist-dir dist/pypi

create_inference_cli_whl:
${PYTHON} -m pip install --upgrade pip
${PYTHON} -m pip install setuptools wheel requests
Expand Down
2 changes: 1 addition & 1 deletion docker/dockerfiles/Dockerfile.onnx.cpu
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ARG TARGETPLATFORM
WORKDIR /build
COPY . .
RUN make create_wheels
RUN pip3 install --find-links dist/ dist/roboflow_workflows*.whl dist/streamvision*.whl dist/inference_cli*.whl dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl "setuptools~=84.2.1"
RUN pip3 install --find-links dist/ dist/roboflow_workflows*.whl dist/inference_models*.whl dist/streamvision*.whl dist/inference_cli*.whl dist/inference_core*.whl dist/inference_cpu*.whl dist/inference_sdk*.whl "setuptools~=84.2.1"
RUN pip3 install "inference-models[model-blob-cache]"

RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then pip3 install -r requirements/requirements.vino.txt; rm -rf ~/.cache/pip; fi
Expand Down
1 change: 1 addition & 0 deletions docker/dockerfiles/Dockerfile.onnx.gpu
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ RUN ln -sf /usr/bin/python3 /usr/bin/python
RUN /bin/make create_wheels_for_gpu_notebook && \
python3 -m pip install --no-cache-dir --find-links dist/ \
dist/roboflow_workflows*.whl \
dist/inference_models*.whl \
dist/streamvision*.whl \
dist/inference_cli*.whl \
dist/inference_core*.whl \
Expand Down
198 changes: 198 additions & 0 deletions docs/fine-tuned/rfdetr-obb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Native RF-DETR oriented object detection

This implementation adds the `oriented-object-detection` task for an exported
RF-DETR OBB model. It does not register the artifact as ordinary object detection.

## Artifact and geometry contract

The producer registers a standard RF-DETR ONNX package containing `weights.onnx`,
`class_names.txt`, and `inference_config.json`. The loader generates
`model_config.json` for its cache and offline loading. The registry identity is
architecture `rfdetr`, task `oriented-object-detection`, backend `onnx`, with model
variant `rfdetr-obb-preview-2xl`. The registry also accepts the producer's
`obb-detection` task alias; responses use `oriented-object-detection`.

The model metadata response supplies `orientedBoxMetadata` with a `classHeading`
map and nonnegative `classHeadingRevision`, taken from the model's frozen dataset
version. This field is optional for other tasks and required by the native OBB
loader. It follows the model through cached and offline loads. The runtime does
not infer class heading capability from the graph's heading head.

The graph emits raw `dets[B,Q,5]`, `labels[B,Q,C]`, and `heading[B,Q,4]` before
selection. `dets` contains normalized center and ordered side lengths, followed by
a clockwise-radian angle. `labels` contains class logits; output zero is background.
`class_names.txt` identifies output columns, and the package's standard
`class_names_operations` removes the background class. Response class IDs are
zero-based indices after that remapping, rather than COCO category IDs. Frozen
class heading policy is matched by class name. Zero-area padded query slots are
discarded before selection. Nonfinite graph values and missing heading outputs
are errors.

For a directed class, heading side `k` adds `k*pi/2` to the raw angle and swaps
width/height iff `k` is odd. This preserves the exact footprint and designates the
predicted front. Undirected classes retain the raw ordered rectangle axis.
No independent front vector is persisted.

The package declares letterbox resizing, RGB padding 114, division by 255 and
ImageNet normalization. For an image of width `w`, height `h` and square model
input size `S`, the runtime resizes to `round(w*S/max(w,h))` by
`round(h*S/max(w,h))`, with each dimension at least one pixel. Pillow bilinear
resizing and integer centered padding match the producer's training transform.

Rounding can make the realized width and height ratios differ. The inverse maps
centers using their respective ratios and ordered sides using the mean of those
two ratios, matching the training-target transform; angles remain unchanged.
The producer's current postprocessing uses a continuous max-side inverse.
Comparison with that postprocessing and validation against trained weights remain
pending. Roboflow static crop, grayscale and contrast preprocessing from
`inference_config.json` runs before model normalization. The inverse includes the
static-crop offset. Returned geometry remains floating point in original
input-image coordinates.

## Response and SDK

`POST /infer/oriented_object_detection` accepts the shared image/confidence request
fields. Each prediction contains `obb: [cx, cy, width, height, theta]`, `class_id`,
`class`, `confidence`, `heading`, and `detection_id`. The response includes
`task_type: "oriented-object-detection"` and frozen `class_heading` semantics.
Detection IDs use a fresh namespace for each input result, so repeated calls and
separate models produce distinct IDs. IDs remain stable through Workflow transport.
Bounding AABBs and corners are compatibility views derived from native geometry.

The current Inference SDK in API v1 mode routes the native task to this endpoint. It disables
client resizing for this task so a second rounded resize cannot alter the native
shape before the server's declared preprocessing. Select API v1 explicitly when connecting to a hosted URL that otherwise defaults to the legacy API v0 client; API v0 client downsizing is not part of the advertised native path.

## Workflows

`roboflow_core/roboflow_oriented_object_detection_model@v1` produces the separate
`oriented_object_detection_prediction` kind. Numeric confidence, class filtering,
row selection, row combination, dynamic crop offsets, original-coordinate output,
serialization, and `roboflow_core/oriented_box_visualization@v1` retain the ordered
footprint and class front. The visualization highlights the native front edge for
directed classes. NumPy and tensor carriers retain the same semantic fields.

Inside Workflows, ordered corners carry geometry/front through coordinate changes.
A general affine/perspective warp can produce a quadrilateral: serialization then
emits `geometry_type: "quadrilateral"` and ordered `corners`, without inventing an
OBB fit. Such results require an explicit rectangle-fit policy before use as native
rectangle annotations. Isotropic scaling and crop offsets preserve native tuples
without integer rounding.

The existing Detections Merge block fits a union AABB and is not compatible with
this native kind. Row combination concatenates detections without refitting them.
Query-language `DetectionsShift` and `DetectionsOffset` are also excluded and reject
native inputs at execution, because their geometry operations only update AABBs.
Opt-in `roboflow_core/detections_consensus@v2` compares native footprints using
rotated IoU and retains the highest-confidence contributor of the selected class,
including its ordered geometry, front and identity. It reuses v1 voting and class
selection, with configured confidence aggregation. Its geometry overlap code ports
Agis Kounelis's [PR #2420](https://github.com/roboflow/inference/pull/2420) at
`ee7fe5288255df1c8401877feda3ddaca0118eef`. Frozen class policies survive consensus
and combination, including empty outputs; incompatible policies or mixed tasks
raise errors. Ordinary tensor inputs remain unsupported by v2; v1 retains its
existing behavior.
Dataset writeback and annotation acceptance need the platform's native adapters and
workspace admission checks; ordinary AABB upload paths are not advertised for OBB.

## Validation and deployment matrix

| Target | Implementation and validation |
| --- | --- |
| Local CPU ONNX model package | Implemented; synthetic three-output ONNX fixtures use the standard producer package files and frozen class metadata; pinned producer letterbox pixels and target geometry tested across five image shapes |
| Server request consumer | Implemented; metadata retrieval, AutoModel loading, cache/offline reloads, native request lifecycle and original-coordinate response tested locally with synthetic fixtures |
| Current Inference SDK | Native route exercised through the request adapter and SDK with a synthetic ONNX fixture; its response passes native preview and evaluation parsers; no hosted request executed |
| Local Workflow model, transport and visualization | Implemented; NumPy/tensor semantic and coordinate tests |
| Query-language `DetectionsShift` / `DetectionsOffset` | Excluded; native NumPy/tensor inputs reject before geometry mutation |
| Hosted production serving | Requires compatible package releases, trained artifact registration and serving deployment; no hosted/cloud run claimed |
| CUDA, TensorRT, CoreML, browser inferencejs | Excluded from this initial backend registration |
| Legacy `roboflow-python` `version.model` | Excluded; current Inference SDK support does not add legacy task dispatch |

Synthetic fixtures establish package and transport compatibility; they do not
establish trained-model accuracy or an end-to-end platform training run. No model
package release, cloud/GPU run, deployment or feature activation is part of these
local validation results. Existing authorized model reads remain independent
of the platform's default-off gate for new OBB authoring/jobs.

## Package and worker release gate

This feature is pending release. The registry versions below are forward
requirements, not evidence of a published or deployed native provider:

| Distribution | Required version | Consumer |
| --- | --- | --- |
| `inference-sdk` | `1.8.4` | Remote native task dispatch in Workflows and model evaluation |
| `inference-models` | `0.39.8` | Native RF-DETR ONNX registry/runtime |
| `roboflow-workflows` | `0.2.16` | Canonical native response entities, blocks and consensus v2 |
| `inference`, `inference-cpu`, `inference-gpu`, `inference-core` | `1.8.4` | HTTP server and model adapters |

### Build validation before release

Build candidate packages from the same reviewed checkout:

```bash
make create_sdk_wheel
(cd inference_models && uv build)
make download_fonts
(cd workflows && uv build --wheel --out-dir ../dist)
```

These commands produce local distributions without publishing them. Same-checkout
wheels can validate source compatibility and package isolation in a disposable
environment. They do not establish registry availability or make a committed
registry lock valid. The existing SDK and Models workflows also support manual
build-only validation; publishing is a separate release operation.

### Dependency order and release gates

SDK and Models changes must merge before their normal, separately approved
releases. Consumer pins and registry locks require those published dependencies.
With the existing registry-lock gates, this requires a merge boundary between the
SDK/Models changes and the Workflows/server consumer changes; the combined change
cannot satisfy that order by publishing from an unmerged branch. This dependency
order is required for release; no package publication or deployment is established
by the local build checks above.

1. Merge the SDK and Models changes, then release SDK `1.8.4` and Models `0.39.8`
through their existing release workflows after separate release approval.
Verify the registry wheels' metadata and contents against the merged source.
2. Once those registry releases are verified, update the consumer pins and run
`cd workflows && uv lock`. Verify the genuine generated registry lock and a
fresh constrained installation before merging the consumer changes. Use the
same-checkout Workflows wheel when testing its unpublished version; that
installed-wheel check is separate from the registry-lock check. Update the
Streamvision registry lock if its dependency graph requires it, and verify
which Workflows release it actually selects.
The Models project lock must also pass
`cd inference_models && uv lock --check`. Local wheel overrides, fabricated
hashes, and vendored wheels do not satisfy the registry-lock gate.
3. After the consumer changes merge, release Workflows `0.2.16` through its
existing workflow. Verify the registry wheel contains
`core_steps/common/oriented_entities.py`, and verify clean ordinary server
imports with the released dependency set. Workflows must be available before
root distributions requiring its exact version become consumable.
4. Release the root `1.8.4` packages after their consumer changes merge and build
server images. `make create_wheels` builds the root release set, including the
SDK; the root workflow handles the SDK upload separately. Full CPU/GPU builds
explicitly install their checkout Models wheel; slim builds resolve the new
registry pin. Verify installed model registration and a CPU ONNX fixture in
actual deployed images and US/EU targets.
5. Deploy compatible serving and queue workers before enabling platform native
producers. Native custom Auto Label jobs use direct HTTP and require no queue
SDK/Supervision upgrade. Unavailable endpoints or non-native responses fail the
job without an ordinary detection fallback. Native Workflow Auto Label
admission is a separate unsupported path.

Before publication, a PyPI project owner must verify the project's Trusted
Publisher settings. The expected repository is `roboflow/inference`, with
`publish.pypi.yml` for SDK/root distributions,
`publish.pypi.inference_exp.yml` for Models, and
`publish.pypi.roboflow_workflows.yml` for Workflows. The workflow filename is part
of the trusted identity, and any configured environment or protection must match.
Private publisher settings have not been verified by the local checks. See
[PyPI's publisher setup](https://docs.pypi.org/trusted-publishers/adding-a-publisher/).

Python Supervision's native parser/NMM feature is independent. Its development
version is outside the platform's existing `<0.31.0` cap; the server/Workflows
native carrier constructs compatible `Detections` directly and does not require
that upgrade. Existing ordinary/legacy task discriminators remain valid.
30 changes: 29 additions & 1 deletion inference/core/entities/requests/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,16 +431,44 @@ class LMMInferenceRequest(CVInferenceRequest):


def request_from_type(model_type, request_dict):
"""Uses original request id"""
"""Reconstruct a task-specific request, preserving its identifier.

Args:
model_type: Canonical model task type.
request_dict: Serialized inference request.

Returns:
The request model for the specified task.

Raises:
ValueError: If the task type is unsupported.
"""
if model_type == "classification":
request = ClassificationInferenceRequest(**request_dict)
elif model_type == "instance-segmentation":
request = InstanceSegmentationInferenceRequest(**request_dict)
elif model_type == "object-detection":
request = ObjectDetectionInferenceRequest(**request_dict)
elif model_type == "oriented-object-detection":
request = OrientedObjectDetectionInferenceRequest(**request_dict)
elif model_type == "semantic-segmentation":
request = SemanticSegmentationInferenceRequest(**request_dict)
else:
raise ValueError(f"Unknown task type {model_type}")
request.id = request_dict.get("id", request.id)
return request


class OrientedObjectDetectionInferenceRequest(ObjectDetectionInferenceRequest):
"""Run a native OBB model using numeric confidence and shared image fields."""

confidence: float = Field(
default=0.5,
ge=0,
le=1,
description="Minimum sigmoid class confidence for native OBB.",
)
disable_active_learning: bool = Field(
default=True,
description="Native OBB writeback requires a compatible platform adapter.",
)
6 changes: 6 additions & 0 deletions inference/core/entities/responses/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from uuid import uuid4

from pydantic import BaseModel, Field, ValidationError, field_serializer
from roboflow_workflows.core_steps.common.oriented_entities import (
OrientedObjectDetectionInferenceResponse,
OrientedObjectDetectionPrediction,
)

from inference.core.workflows.core_steps.common.inference_response_entities import ( # noqa: F401
CvInferenceResponse,
Expand Down Expand Up @@ -454,6 +458,8 @@ def response_from_type(model_type, response_dict):
return InstanceSegmentationInferenceResponse(**response_dict)
elif model_type == "semantic-segmentation":
return SemanticSegmentationInferenceResponse(**response_dict)
elif model_type == "oriented-object-detection":
return OrientedObjectDetectionInferenceResponse(**response_dict)
elif model_type == "object-detection":
return ObjectDetectionInferenceResponse(**response_dict)
else:
Expand Down
Loading
Loading