refactor(deps): make supervision an optional dependency#1090
refactor(deps): make supervision an optional dependency#1090everglow01 wants to merge 4 commits into
Conversation
Move supervision out of core dependencies into the train/onnx/tflite/visual extras, defer all supervision imports (lazy + TYPE_CHECKING), and add a import_supervision() helper that raises a friendly install hint when it is missing. predict() still returns sv.Detections; the native-types return change is deferred to a follow-up PR pending maintainer sign-off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR makes supervision an optional dependency by moving it out of core pyproject.toml dependencies and introducing a centralized lazy import helper (import_supervision()) that provides a user-friendly install hint when supervision isn’t available. This reduces the baseline install footprint for users who don’t need sv.Detections/annotation utilities, while preserving the existing public return types.
Changes:
- Moved
supervisionfrom required dependencies into relevant extras (train,onnx,tflite,visual) and updated supervision-dependent code paths to import lazily. - Added
rfdetr.utilities.optional_imports.import_supervision()and wired it into inference/export/visualization/dataset utilities. - Updated tests to (a) validate the helper behavior and (b) skip supervision-dependent test modules when
supervisionis not installed.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/utilities/test_optional_imports.py | Adds coverage for the new import_supervision() helper. |
| tests/models/test_predict.py | Skips predict tests when supervision is unavailable. |
| tests/export/test_tflite_inference.py | Skips TFLite inference tests when supervision is unavailable. |
| tests/datasets/test_synthetic.py | Skips synthetic dataset tests when supervision is unavailable. |
| src/rfdetr/visualize/data.py | Switches eager supervision import to import_supervision(). |
| src/rfdetr/utilities/optional_imports.py | Introduces a shared optional-import helper with a friendly error message. |
| src/rfdetr/export/_tflite/inference.py | Defers supervision import until detections creation. |
| src/rfdetr/export/_onnx/inference.py | Defers supervision import until detections creation. |
| src/rfdetr/detr.py | Makes predict() use import_supervision() and documents ImportError. |
| src/rfdetr/datasets/yolo.py | Makes to_detections() use import_supervision(). |
| src/rfdetr/datasets/synthetic.py | Defers supervision usage to runtime (incl. colors) via import_supervision(). |
| src/rfdetr/datasets/save_grids.py | Defers supervision usage to runtime via import_supervision(). |
| pyproject.toml | Removes supervision from core deps and adds it to relevant extras. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is ❌ Your project check has failed because the head coverage (77%) is below the target coverage (95%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## develop #1090 +/- ##
=======================================
Coverage 77% 77%
=======================================
Files 102 103 +1
Lines 9104 9125 +21
=======================================
+ Hits 7037 7056 +19
- Misses 2067 2069 +2 🚀 New features to boost your workflow:
|
scipy is imported on the core import path (models/matcher.py uses scipy.optimize.linear_sum_assignment), but was only declared in the [train] extra. It was satisfied transitively via supervision, which roboflow#1074 moved out of core deps — breaking `import rfdetr` for any install without supervision (e.g. the [plus]-only Integration Tests job). - Add scipy to core dependencies; drop the now-redundant [train] entry. - Integration Tests install [plus,visual] so supervision is present for the predict() call in tests/try_instantiate_all_models.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Hi @Borda 👋 — thanks for the autofix commit, I've rebased on top of it. PR1 status (this PR):
I think this is ready for review. Question on the follow-up (PR2): the second half of #1074 — returning native Python types when |
| def import_supervision() -> ModuleType: | ||
| """Import the optional ``supervision`` package, raising a friendly hint if it is missing. | ||
|
|
||
| ``supervision`` is an optional dependency: it is required for the ``Detections`` return type of | ||
| inference helpers and for annotation/visualization utilities, but is not installed by the core | ||
| ``rfdetr`` package. This helper defers the import to call time so the rest of the package remains | ||
| usable without it, and turns a bare ``ModuleNotFoundError`` into an actionable installation hint. | ||
|
|
||
| Returns: | ||
| The imported ``supervision`` module. | ||
|
|
||
| Raises: | ||
| ImportError: If ``supervision`` is not installed. | ||
| """ | ||
| try: | ||
| import supervision as sv | ||
| except ModuleNotFoundError as exc: | ||
| if exc.name != "supervision": | ||
| raise | ||
| raise ImportError( | ||
| "This feature requires the 'supervision' package. Install it with " | ||
| "`pip install supervision` (also bundled in the rfdetr[onnx], rfdetr[tflite], " | ||
| "rfdetr[train], and rfdetr[visual] extras)." | ||
| ) from exc | ||
| return cast(ModuleType, sv) |
There was a problem hiding this comment.
Thanks! I'd prefer to leave this un-memoized:
- Python already caches the module in
sys.modules, so after the first callimport supervisionis just a dict lookup + name binding (microseconds). The helper is also called once perpredict()/_run_inference(), not per-sample, so there's no hot-loop cost in practice. functools.lru_cachewould cache the successful module object and breaktests/utilities/test_optional_imports.py::test_raises_with_install_hint_when_missing, which relies onmonkeypatch.setitem(sys.modules, "supervision", None)to force the missing-package path after a prior successful import. Working around that (acache_clear()in the test) adds complexity for a non-issue.
Happy to revisit if profiling ever shows this on a hot path.
| - name: 🚀 Install Packages (plus + visual extras) | ||
| timeout-minutes: 5 | ||
| # Install PyTorch CPU-only first (UV_TORCH_BACKEND=cpu works with 'uv pip') | ||
| run: uv pip install -e .[plus] | ||
| # [visual] pulls in supervision, required by the predict() call in | ||
| # tests/try_instantiate_all_models.py (supervision is optional since #1074). | ||
| run: uv pip install -e .[plus,visual] |
There was a problem hiding this comment.
Good catch, quoted it. The install line now uses uv pip install -e ".[plus,visual]", matching the style already used in ci-tests-cpu.yml (".[train,cli,visual,kornia]").
Quote `".[plus,visual]"` in the Integration Tests install step to avoid any accidental glob expansion and match the style already used in ci-tests-cpu.yml. Addresses a Copilot review nit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| # Available colors for synthetic dataset generation (resolved to sv.Color at use time) | ||
| SYNTHETIC_COLORS = ["red", "green", "blue"] |
| Tuple of ``(image_with_shape, polygon)`` where ``polygon`` is a flat list ``[x1, y1, x2, y2, …]`` suitable for | ||
| the COCO ``segmentation`` field. Returns an empty polygon list for unknown shape names. | ||
| """ | ||
| sv = import_supervision() |
| Each grid is a 3x3 JPEG containing up to 9 images from a single batch, with bounding boxes and class labels | ||
| drawn on top. | ||
| """ | ||
| sv = import_supervision() |
| """ | ||
| from PIL import Image as PILImage | ||
|
|
||
| sv = import_supervision() |
|
Yes but some contexts of lazy import can be upgraded again after the release of Python 3.15. Python 3.15 adds the significant feature, and usage is following: lazy import supervision as svMaybe you can try it in the future?😂 |
We still need to support wide range of python versions... |
What does this PR do?
Moves
supervisionfrom a required core dependency to an optional one, sousers who only train or run inference aren't forced to pull in
supervision(andits transitive
opencv) when they don't need annotation orDetectionsoutput.Concretely:
pyproject.toml: removedsupervisionfrom coredependencies; added it tothe extras whose code actually uses it —
train,onnx,tflite,visual(the same duplication pattern already used for
peft,polygraphy,onnx). CIinstalls
[train,visual], so the test matrix is unaffected.supervisioneagerly(
export/_onnx/inference,export/_tflite/inference,datasets/save_grids,datasets/synthetic) now import it lazily, withTYPE_CHECKINGguards forannotations.
rfdetr/utilities/optional_imports.py::import_supervision()raises a friendly
ImportErrorwith an install hint instead of a crypticModuleNotFoundError, reused everywheresupervisionis needed (incl.predict(),yolo.to_detections,visualize.data).predict()still returnssv.Detections | list[sv.Detections]— no public APIchange. The only behavioral difference is for a bare
pip install rfdetrfollowed by
predict(): it now raises a clearImportErrortelling the user toinstall
supervision, instead of relying on it being implicitly present. Thismatches the issue's "clear error messages" acceptance criterion.
This is the safe half of #1074. The other half — returning native dict/list
when
supervisionis absent, plusto_supervision()conversion helpers andreworked doc examples — changes the documented public return type and is
intentionally left to a follow-up PR pending maintainer sign-off on the API
direction.
Related Issue(s): Part of #1074 (intentionally not
Closes, since thenative-types half is deferred).
Type of Change
supervisionoptional. No public APIchange; one narrow behavioral nuance (bare-install
predict()now raises a clearImportError) is described above.Testing
Test details:
tests/utilities/test_optional_imports.pycoveringimport_supervision():returns the module when present, and raises an
ImportErrorcontaining thepip install supervisionhint when absent (simulated viasys.modules).tests/models/test_predict,tests/export/test_tflite_inference,tests/datasets/test_synthetic) withpytest.importorskip("supervision").pytest src/ tests/ -m "not gpu" …): 2022 passed, 43 skipped.(Local-only errors were COCO-download fixture timeouts in
tests/benchmarks/test_inference_coco.py, unrelated to this change.)import rfdetrsucceeds withsupervisionblocked, and thatsupervision-producing paths raise the friendly
ImportError.Checklist
updates are deferred to the follow-up PR together with the native-types change
Additional Context
pre-commit run --all-filespasses clean (ruff, mypy, docformatter, licenseheaders, etc.).
supervisionacross thetrain/onnx/tflite/visualextras keepseach functional install working out of the box and avoids any CI workflow change.
Open to a dedicated
[supervision]/[detections]extra instead if you prefer.