Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
b9a8878
refactor(workflows): decontaminate inference imports
grzegorz-roboflow Sep 8, 2026
1410e2f
CI
grzegorz-roboflow Sep 8, 2026
98fc192
refactor(workflows): relocate Roboflow-platform blocks to an in-repo …
grzegorz-roboflow Sep 9, 2026
935a05a
fix(workflows): round-1 review fixes for the Roboflow plugin relocation
grzegorz-roboflow Sep 9, 2026
f6cd30d
refactor(workflows): replace ModelEndpointType with a local string co…
grzegorz-roboflow Sep 9, 2026
efdb9fb
refactor(workflows): fix cache key collision in endpoint_type coercion
grzegorz-roboflow Sep 9, 2026
f7ea335
feat(workflows): add the RoboflowPlatformClient and WorkspaceResolver…
grzegorz-roboflow Sep 9, 2026
2710b86
refactor(workflows): route Roboflow-proxied requests through the plat…
grzegorz-roboflow Sep 9, 2026
cdad099
fix(workflows): address Task 9.4 review findings in the platform-clie…
grzegorz-roboflow Sep 9, 2026
ea0a770
refactor(workflows): inject Roboflow API headers and wrap_url into SA…
grzegorz-roboflow Sep 9, 2026
354ba91
fix(workflows): revert unrelated black reformat in sam3 video test
grzegorz-roboflow Sep 9, 2026
0e3022f
refactor(workflows): inject the workspace and inner-workflow spec res…
grzegorz-roboflow Sep 9, 2026
ea26764
fix(workflows): collect dynamic block definitions once per compile
grzegorz-roboflow Sep 9, 2026
58508a9
refactor(workflows): relocate the Roboflow API exception classes into…
grzegorz-roboflow Sep 9, 2026
071af7f
fix(workflows): remove orphaned typing.Optional import from exception…
grzegorz-roboflow Sep 10, 2026
4468905
refactor(workflows): swap the cache default now that every root injec…
grzegorz-roboflow Sep 10, 2026
631ce20
refactor(workflows): schema DTO, merge_window_segments and the AR mod…
grzegorz-roboflow Sep 10, 2026
818569f
test(workflows): cross-check the isolation probe against the lint bas…
grzegorz-roboflow Sep 10, 2026
0d9b9dd
fix(workflows): validate-before-write in the remaining Phase 9 codemo…
grzegorz-roboflow Sep 10, 2026
5e06273
feat(workflows): add ExecutionObserver port with a null default
grzegorz-roboflow Sep 10, 2026
df477f5
feat(workflows): add workflows-local block duration channel
grzegorz-roboflow Sep 10, 2026
8cbeed9
feat(server): add UsageTrackingExecutionObserver for workflows
grzegorz-roboflow Sep 10, 2026
92fcd3e
feat(server): bind the workflows execution observer at every composit…
grzegorz-roboflow Sep 10, 2026
805830e
refactor(workflows): route engine billing and tracing through Executi…
grzegorz-roboflow Sep 10, 2026
fe25da6
refactor(workflows): observe custom-python block execution through th…
grzegorz-roboflow Sep 10, 2026
fe9595e
refactor(workflows): observe SAM2/SAM3 video model calls through the …
grzegorz-roboflow Sep 10, 2026
dad60d6
test(workflows): pin the Phase 6 row removals and the removed usage k…
grzegorz-roboflow Sep 10, 2026
7c25163
refactor(workflows): drop dead image_utils import from qwen_vlm/v1
grzegorz-roboflow Sep 10, 2026
98a6163
feat(workflows): vendor pure image encode/decode helpers
grzegorz-roboflow Sep 10, 2026
c8e457d
feat(workflows): add ImageCodec port with a refusing, lock-guarded re…
grzegorz-roboflow Sep 10, 2026
65cf21d
feat(workflows): add ImageCodec delegates to utils.images
grzegorz-roboflow Sep 10, 2026
cfab21c
feat(server): guarded ImageCodec adapter and single-owner local-file …
grzegorz-roboflow Sep 10, 2026
3840204
test(server): pin the backslash and redirect-cap parity assertions to…
grzegorz-roboflow Sep 10, 2026
b3ef19d
feat(workflows): inject the image codec into the runtime-input deseri…
grzegorz-roboflow Sep 10, 2026
783a9a0
feat(server): bind one image codec to both injection paths at every root
grzegorz-roboflow Sep 10, 2026
7b7879e
refactor(workflows): route image loading through the ImageCodec port
grzegorz-roboflow Sep 10, 2026
f7fd617
fix(workflows): gate the icon alpha-recovery reload behind the image …
grzegorz-roboflow Sep 10, 2026
1da288b
chore(workflows): add the inference.core.env import manifest generator
grzegorz-roboflow Sep 10, 2026
4a12d00
feat(workflows): add WorkflowsConfiguration, its standalone default a…
grzegorz-roboflow Sep 10, 2026
2e7309f
feat(server): build the Workflows configuration from env and wire it …
grzegorz-roboflow Sep 10, 2026
9120692
test(server): count only hop requests in the redirect-cap parity test
grzegorz-roboflow Sep 11, 2026
c8093cc
refactor(workflows): loader reads the injected configuration, not inf…
grzegorz-roboflow Sep 11, 2026
d2dd0c9
refactor(workflows): engine reads the injected configuration and refu…
grzegorz-roboflow Sep 11, 2026
455409a
refactor(workflows): modal executor reads the injected configuration
grzegorz-roboflow Sep 11, 2026
4a9e55d
refactor(workflows): dynamic-block scaffolding reads the injected con…
grzegorz-roboflow Sep 11, 2026
d6c22c1
refactor(workflows): repoint 139 block files at the Workflows configu…
grzegorz-roboflow Sep 11, 2026
8a25a64
test(workflows): the isolation probe installs a standalone WorkflowsC…
grzegorz-roboflow Sep 11, 2026
a8be951
docs(server): state the exact install-ordering invariant for the Work…
grzegorz-roboflow Sep 11, 2026
58e7942
refactor(workflows): first-class model metadata + stream-pipeline por…
grzegorz-roboflow Sep 11, 2026
3fa14a8
refactor(workflows): duck-type response normalisation instead of impo…
grzegorz-roboflow Sep 11, 2026
b464920
refactor(workflows): plain-argument load_core_model; drop annotation-…
grzegorz-roboflow Sep 11, 2026
e343023
refactor(workflows): own the segmentation prediction classes (server …
grzegorz-roboflow Sep 11, 2026
f784139
docs(workflows): name the dropped Sam3 3D response type at _format_re…
grzegorz-roboflow Sep 11, 2026
31964e9
test(stream): teach the fake model manager the stream-pipeline port m…
grzegorz-roboflow Sep 11, 2026
cc41202
refactor(workflows): own ActionRecognitionPrediction, server re-expor…
grzegorz-roboflow Sep 11, 2026
35618e1
test(http): count only the image fetch in the bound-codec allow-list …
grzegorz-roboflow Sep 11, 2026
ee62f94
feat(server): ModelsProvider adapter bound at the four workflow compo…
grzegorz-roboflow Sep 11, 2026
26cb501
test: GET-only request assertions in codec-binding tests; entities pu…
grzegorz-roboflow Sep 11, 2026
7cb4843
refactor(workflows): detection/classification/keypoints/semantic-seg …
grzegorz-roboflow Sep 11, 2026
4f52dcc
test(workflows): block→adapter requests match the pre-port construction
grzegorz-roboflow Sep 11, 2026
c0c5f77
test(workflows): multi-label v2/v3 forward the separate confidence ke…
grzegorz-roboflow Sep 11, 2026
458e0e7
refactor(workflows): instance segmentation runs through the plain-arg…
grzegorz-roboflow Sep 11, 2026
fcb7ed1
test: stream-pipeline tests drive v3 through the real ModelsProvider …
grzegorz-roboflow Sep 11, 2026
0ec4aaf
test(workflows): pin explicit-None forwarding on a non-None request d…
grzegorz-roboflow Sep 11, 2026
82febf1
refactor(workflows): LMM, depth and moondream2 blocks run through the…
grzegorz-roboflow Sep 11, 2026
9b222ab
test(workflows): parity cases compare model_fields_set; thinking-with…
grzegorz-roboflow Sep 11, 2026
b205c1a
refactor(workflows): clip and perception-encoder blocks run through t…
grzegorz-roboflow Sep 11, 2026
ef0575e
test(http): codec-binding tests count only this image's fetches
grzegorz-roboflow Sep 11, 2026
7864a37
test(workflows): clip parity cases compare model_fields_set; text-sub…
grzegorz-roboflow Sep 11, 2026
d8e1f2d
refactor(workflows): OCR and YOLO-World blocks run through the plain-…
grzegorz-roboflow Sep 11, 2026
e781cd5
test: PP-OCR omitted-stage sentinel coverage; tensor-block parity case
grzegorz-roboflow Sep 11, 2026
5758cd7
refactor(workflows): own the SAM prompt classes; the server re-export…
grzegorz-roboflow Sep 11, 2026
f5ef475
test(workflows): SAM prompt move — nested-dict coercion, schema descr…
grzegorz-roboflow Sep 11, 2026
c2e5348
refactor(workflows): SAM blocks run through the plain-argument port
grzegorz-roboflow Sep 11, 2026
4ce13b3
refactor(workflows): drop infer_from_request_sync from the ModelsProv…
grzegorz-roboflow Sep 11, 2026
295a86e
test(workflows): isolation probe is green; drop the strict xfail wrapper
grzegorz-roboflow Sep 11, 2026
3a0c969
fix(server): SAM2 prompt revival keeps box and points together; 3D-te…
grzegorz-roboflow Sep 11, 2026
194a965
docs(workflows): the ModelsProvider port is implemented by the server…
grzegorz-roboflow Sep 11, 2026
a70b4b9
fix tests
grzegorz-roboflow Sep 14, 2026
11ce624
fix test
grzegorz-roboflow Sep 14, 2026
d084272
tests
grzegorz-roboflow Sep 14, 2026
740aa45
tests
grzegorz-roboflow Sep 14, 2026
636d95a
Remove plan scripts
grzegorz-roboflow Sep 14, 2026
4c81d98
Fix server bindings for direct workflow execution
grzegorz-roboflow Sep 14, 2026
5f92d7c
Remove redundant workflow decontamination tests
grzegorz-roboflow Sep 14, 2026
4e2cc8a
Preserve ModelManager compatibility in Workflows
grzegorz-roboflow Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion .github/workflows/unit_tests_inference_x86.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools
pip install -r requirements/_requirements.txt -r requirements/requirements.cpu.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt -r requirements/requirements.http.txt -r requirements/requirements.transformers.txt
# Composition-root tests also exercise the CLI entry points.
pip install -r requirements/requirements.cli.txt -r requirements/_requirements.txt -r requirements/requirements.cpu.txt -r requirements/requirements.sdk.http.txt -r requirements/requirements.test.unit.txt -r requirements/requirements.http.txt -r requirements/requirements.transformers.txt
pip install --no-deps ./inference_models
- name: 🧪 Unit Tests of Inference
timeout-minutes: 30
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit_tests_workflows_x86.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ jobs:
run: python build_scripts/download_fonts.py
- name: 🧪 Unit Tests of Workflows
timeout-minutes: 30
run: python -m pytest tests/workflows/unit_tests
run: python -m pytest tests/workflows/unit_tests tests/workflows/isolation
61 changes: 51 additions & 10 deletions development/stream_interface/benchmark_engine_throughput.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
If the tensor run now matches (or beats) the numpy run here, while the live pipeline
showed tensor at half the FPS, the gap is the per-frame numpy->GPU conversion.
"""

import argparse
import os
import sys
Expand All @@ -42,6 +43,22 @@
MAX_ACTIVE_MODELS,
WORKFLOWS_IMAGE_TENSOR_DEVICE,
)
from inference.core.interfaces.roboflow_platform_client import (
install_workflows_platform_bindings,
)
from inference.core.interfaces.workflows_configuration import (
server_workflows_configuration,
)
from inference.core.interfaces.workflows_execution_observer import (
UsageTrackingExecutionObserver,
)
from inference.core.interfaces.workflows_image_codec import bind_image_codec
from inference.core.interfaces.workflows_models_provider import (
ModelManagerModelsProvider,
)
from inference.core.interfaces.workflows_step_error_handlers import (
resolve_step_error_handler,
)
from inference.core.managers.base import ModelManager
from inference.core.managers.decorators.fixed_size_cache import WithFixedSizeCache
from inference.core.registries.roboflow import RoboflowModelRegistry
Expand Down Expand Up @@ -113,7 +130,9 @@ def parse_args() -> argparse.Namespace:
def build_model_manager() -> ModelManager:
# Same construction as tests/workflows/integration_tests/conftest.py::model_manager.
registry = RoboflowModelRegistry(ROBOFLOW_MODEL_TYPES)
return WithFixedSizeCache(ModelManager(model_registry=registry), max_size=MAX_ACTIVE_MODELS)
return WithFixedSizeCache(
ModelManager(model_registry=registry), max_size=MAX_ACTIVE_MODELS
)


def decode_frames(video_path: str, count: int) -> List[np.ndarray]:
Expand All @@ -139,7 +158,10 @@ def to_gpu_tensor(bgr_hwc: np.ndarray, device: torch.device) -> torch.Tensor:


def _cuda_sync_if_needed() -> None:
if ENABLE_TENSOR_DATA_REPRESENTATION and WORKFLOWS_IMAGE_TENSOR_DEVICE.type == "cuda":
if (
ENABLE_TENSOR_DATA_REPRESENTATION
and WORKFLOWS_IMAGE_TENSOR_DEVICE.type == "cuda"
):
torch.cuda.synchronize()


Expand Down Expand Up @@ -167,7 +189,10 @@ def main() -> None:
f"WORKFLOWS_IMAGE_TENSOR_DEVICE = {WORKFLOWS_IMAGE_TENSOR_DEVICE} | "
f"ENABLE_TENSOR_DATA_REPRESENTATION = {ENABLE_TENSOR_DATA_REPRESENTATION}"
)
if ENABLE_TENSOR_DATA_REPRESENTATION and WORKFLOWS_IMAGE_TENSOR_DEVICE.type != "cuda":
if (
ENABLE_TENSOR_DATA_REPRESENTATION
and WORKFLOWS_IMAGE_TENSOR_DEVICE.type != "cuda"
):
print(
"WARNING: tensor mode is on but the tensor device is not CUDA — frames live on "
f"'{WORKFLOWS_IMAGE_TENSOR_DEVICE}', so the model will still copy to GPU itself."
Expand All @@ -193,14 +218,26 @@ def main() -> None:
)

model_manager = build_model_manager()
# The server services a direct caller must bind; `examples/
# run_perspective_correction.py` documents what each one buys. The observer
# is billed work the engine used to do internally, so it belongs inside the
# measured loop, as it was before.
init_parameters = {
"workflows_core.model_manager": ModelManagerModelsProvider(model_manager),
"workflows_core.api_key": args.api_key,
"workflows_core.step_execution_mode": StepExecutionMode.LOCAL,
"workflows_core.execution_observer": UsageTrackingExecutionObserver(),
}
install_workflows_platform_bindings(init_parameters)
bind_image_codec(init_parameters)
init_parameters.setdefault(
"workflows_core.configuration", server_workflows_configuration()
)
engine = ExecutionEngine.init(
Comment thread
grzegorz-roboflow marked this conversation as resolved.
workflow_definition=workflow,
init_parameters={
"workflows_core.model_manager": model_manager,
"workflows_core.api_key": args.api_key,
"workflows_core.step_execution_mode": StepExecutionMode.LOCAL,
},
init_parameters=init_parameters,
workflow_id=args.workflow_id,
step_error_handler=resolve_step_error_handler(),
)

extra_params = {"model_id": args.model_id} if args.model_id else {}
Expand Down Expand Up @@ -237,13 +274,17 @@ def run_once(i: int):
runs_per_s = args.engine_runs / elapsed if elapsed > 0 else 0.0
frames_per_s = total_frames / elapsed if elapsed > 0 else 0.0
print("\n=== Summary ===")
print(f"data representation : {'TENSOR' if ENABLE_TENSOR_DATA_REPRESENTATION else 'NUMPY'}")
print(
f"data representation : {'TENSOR' if ENABLE_TENSOR_DATA_REPRESENTATION else 'NUMPY'}"
)
print(f"pre-decoded frames : {n}")
print(f"batch size : {batch_size}")
print(f"engine runs (timed) : {args.engine_runs}")
print(f"frames processed : {total_frames}")
print(f"elapsed : {elapsed:.3f} s")
print(f"throughput : {frames_per_s:.1f} frames/s ({runs_per_s:.1f} runs/s)")
print(
f"throughput : {frames_per_s:.1f} frames/s ({runs_per_s:.1f} runs/s)"
)
print(f"per-run latency : {elapsed / args.engine_runs * 1000:.3f} ms")
print(f"per-frame latency : {elapsed / total_frames * 1000:.3f} ms")

Expand Down
91 changes: 86 additions & 5 deletions examples/run_perspective_correction.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,59 @@
"""Perspective-correction demo, and the reference for what a direct Python
caller may bind.

Two supported wirings, permanent. Pick either:

1. Pass a raw `ModelManager` (or `ModelManagerDecorator`) as
`workflows_core.model_manager`. `ExecutionEngine.init` invokes the
manager's `__workflows_bind__` hook, which installs the historical server
bindings (platform client / cache / workspace / inner-workflow resolver,
image codec, usage-tracking observer, process configuration and the
server's step-error handler). Explicit keys the caller already set
survive.

2. Pass an explicit `ModelManagerModelsProvider` (or any other
`ModelsProvider` implementation) as `workflows_core.model_manager` and
bind the rest of the services yourself. Suits standalone / custom-host
integrations that keep provider object identity.

This applies to passing a manager INTO the engine, not to direct
construction of individual workflow block classes. The script below uses
form (2); form (1) works without any of the extra `workflows_core.*` calls
made here.

The Execution Engine takes the server's capabilities as explicit
`workflows_core.*` init parameters instead of reaching for them itself. Its
standalone defaults refuse or no-op whatever is missing; the services below
restore them for a caller who is wiring things by hand:

* `install_workflows_platform_bindings` - Roboflow-managed VLM and notification
proxy calls, workflows referenced by ID, workspace identity for authenticated
Modal execution, and the shared cache behind sink cooldown/dedup. Unbound, the
offline platform client raises and the cache is process-local.
* `bind_image_codec` - `{"type": "file"}` / `{"type": "url"}` and serialized
numpy inputs, plus the later re-load of a stored image reference; the default
codec refuses them. Call it AFTER merging overrides, so input deserialization
and reference loading share one codec and one SSRF/local-file policy.
* `UsageTrackingExecutionObserver()` - workflow and custom-block usage plus the
run's tracing spans; the default observer records nothing.
* `resolve_step_error_handler()` - the server's error classification; the
engine's own default is the mapping-free legacy handler.
* `server_workflows_configuration()` - the object the rest of the process uses,
so a mis-wire is reported instead of silently diverging.

The usage categories are separate scopes, not substitutes: `request` (one HTTP
handler call), `workflows` (one observed engine run, with workflow identity, FPS
and preview), `workflow_block` (one custom-Python block execution) and `model`
(the existing model-level accounting). Server entry points keep their existing
layers; a direct caller gets the workflow and block scopes only from the
observer above. Leaving the observer out on purpose stays supported - execution
still runs, without workflow/custom-block collection.

An offline platform client is not "no network": blocks calling a third party
with the user's own provider key, and the REMOTE branches that go through
`inference_sdk`, keep working on their own terms.
"""

import argparse
import os
from pathlib import Path
Expand All @@ -6,6 +62,22 @@
import numpy as np
import supervision as sv

from inference.core.interfaces.roboflow_platform_client import (
install_workflows_platform_bindings,
)
from inference.core.interfaces.workflows_configuration import (
server_workflows_configuration,
)
from inference.core.interfaces.workflows_execution_observer import (
UsageTrackingExecutionObserver,
)
from inference.core.interfaces.workflows_image_codec import bind_image_codec
from inference.core.interfaces.workflows_models_provider import (
ModelManagerModelsProvider,
)
from inference.core.interfaces.workflows_step_error_handlers import (
resolve_step_error_handler,
)
from inference.core.managers.base import ModelManager
from inference.core.registries.roboflow import RoboflowModelRegistry
from inference.core.workflows.core_steps.common.entities import StepExecutionMode
Expand Down Expand Up @@ -224,13 +296,22 @@ def parse_args() -> argparse.Namespace:
model_registry = RoboflowModelRegistry(ROBOFLOW_MODEL_TYPES)
model_manager = ModelManager(model_registry=model_registry)

init_parameters = {
"workflows_core.model_manager": ModelManagerModelsProvider(model_manager),
"workflows_core.api_key": os.getenv("ROBOFLOW_API_KEY"),
"workflows_core.step_execution_mode": StepExecutionMode.LOCAL,
"workflows_core.execution_observer": UsageTrackingExecutionObserver(),
}
install_workflows_platform_bindings(init_parameters)
bind_image_codec(init_parameters)
init_parameters.setdefault(
"workflows_core.configuration", server_workflows_configuration()
)

execution_engine = ExecutionEngine.init(
workflow_definition=WORKFLOW_DEFINITION,
init_parameters={
"workflows_core.model_manager": model_manager,
"workflows_core.api_key": os.getenv("ROBOFLOW_API_KEY"),
"workflows_core.step_execution_mode": StepExecutionMode.LOCAL,
},
init_parameters=init_parameters,
step_error_handler=resolve_step_error_handler(),
)

result = execution_engine.run(
Expand Down
20 changes: 20 additions & 0 deletions inference/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,29 @@
from packaging import version as packaging_version

from inference.core.env import DISABLE_VERSION_CHECK, VERSION_CHECK_MODE

# Hand the Workflows module its configuration before anything can READ it.
# The invariant: `install_workflows_configuration()` runs before any import
# of `inference.core.workflows.environment` (the constants facade) or any
# other configuration-consuming workflows module, so `core_steps/loader.py`'s
# import-time tensor branches and every facade constant see the server's
# values. A few configuration-independent workflows modules are already on
# the bootstrap path above this point (`inference.core.env` ->
# `utils/environment.py` -> `core/exceptions.py` ->
# `workflows/prototypes/platform_errors.py`, and the builder's own import of
# `workflows/configuration.py`); they must stay configuration-independent -
# none of them may import the facade. `inference.core.interfaces
# .workflows_configuration` imports only `inference.core.env` (already fully
# imported above) and `inference.core.workflows.configuration`, so this adds
# no import weight.
from inference.core.interfaces.workflows_configuration import (
install_workflows_configuration,
)
from inference.core.logger import logger
from inference.core.version import __version__

install_workflows_configuration()

latest_release = None
last_checked = 0
cache_duration = 86400 # 24 hours
Expand Down
76 changes: 8 additions & 68 deletions inference/core/entities/requests/sam2.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from typing import Any, List, Optional, Tuple, Union
from typing import Any, List, Optional, Union

from pydantic import BaseModel, Field, root_validator, validator
from pydantic import Field, root_validator, validator

from inference.core.entities.requests.inference import (
BaseRequest,
InferenceRequestImage,
)
from inference.core.env import SAM2_VERSION_ID
from inference.core.workflows.core_steps.models.foundation.segment_anything_common.prompts import ( # noqa: F401
Box,
Point,
Sam2Prompt,
Sam2PromptSet,
)


class Sam2InferenceRequest(BaseRequest):
Expand Down Expand Up @@ -56,72 +62,6 @@ class Sam2EmbeddingRequest(Sam2InferenceRequest):
)


class Box(BaseModel):
x: float
y: float
width: float
height: float


class Point(BaseModel):
x: float
y: float
positive: bool

def to_hashable(self) -> Tuple[float, float, bool]:
return (self.x, self.y, self.positive)


class Sam2Prompt(BaseModel):
box: Optional[Box] = Field(default=None)
points: Optional[List[Point]] = Field(default=None)

def num_points(self) -> int:
return len(self.points or [])


class Sam2PromptSet(BaseModel):
prompts: Optional[List[Sam2Prompt]] = Field(
default=None,
description="An optional list of prompts for masks to predict. Each prompt can include a bounding box and / or a set of postive or negative points",
)

def num_points(self) -> int:
if not self.prompts:
return 0
return sum(prompt.num_points() for prompt in self.prompts)

def to_sam2_inputs(self):
if self.prompts is None:
return {"point_coords": None, "point_labels": None, "box": None}
return_dict = {"point_coords": [], "point_labels": [], "box": []}
for prompt in self.prompts:
if prompt.box is not None:
x1 = prompt.box.x - prompt.box.width / 2
y1 = prompt.box.y - prompt.box.height / 2
x2 = prompt.box.x + prompt.box.width / 2
y2 = prompt.box.y + prompt.box.height / 2
return_dict["box"].append([x1, y1, x2, y2])
if prompt.points is not None:
return_dict["point_coords"].append(
list([point.x, point.y] for point in prompt.points)
)
return_dict["point_labels"].append(
list(int(point.positive) for point in prompt.points)
)
else:
return_dict["point_coords"].append([])
return_dict["point_labels"].append([])

if not any(return_dict["point_coords"]):
return_dict["point_coords"] = None
if not any(return_dict["point_labels"]):
return_dict["point_labels"] = None

return_dict = {k: v if v else None for k, v in return_dict.items()}
return return_dict


class Sam2SegmentationRequest(Sam2InferenceRequest):
"""SAM segmentation request.

Expand Down
Loading
Loading