Skip to content

refactor(deps): make supervision an optional dependency#1090

Open
everglow01 wants to merge 4 commits into
roboflow:developfrom
everglow01:refactor/1074-optional-supervision
Open

refactor(deps): make supervision an optional dependency#1090
everglow01 wants to merge 4 commits into
roboflow:developfrom
everglow01:refactor/1074-optional-supervision

Conversation

@everglow01

Copy link
Copy Markdown

What does this PR do?

Moves supervision from a required core dependency to an optional one, so
users who only train or run inference aren't forced to pull in supervision (and
its transitive opencv) when they don't need annotation or Detections output.

Concretely:

  • pyproject.toml: removed supervision from core dependencies; added it to
    the extras whose code actually uses it — train, onnx, tflite, visual
    (the same duplication pattern already used for peft, polygraphy, onnx). CI
    installs [train,visual], so the test matrix is unaffected.
  • Deferred imports: the four modules that imported supervision eagerly
    (export/_onnx/inference, export/_tflite/inference, datasets/save_grids,
    datasets/synthetic) now import it lazily, with TYPE_CHECKING guards for
    annotations.
  • New helper rfdetr/utilities/optional_imports.py::import_supervision()
    raises a friendly ImportError with an install hint instead of a cryptic
    ModuleNotFoundError, reused everywhere supervision is needed (incl.
    predict(), yolo.to_detections, visualize.data).

predict() still returns sv.Detections | list[sv.Detections]no public API
change
. The only behavioral difference is for a bare pip install rfdetr
followed by predict(): it now raises a clear ImportError telling the user to
install supervision, instead of relying on it being implicitly present. This
matches the issue's "clear error messages" acceptance criterion.

This is the safe half of #1074. The other half — returning native dict/list
when supervision is absent, plus to_supervision() conversion helpers and
reworked 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 the
native-types half is deferred).

Type of Change

  • Other: Dependency/packaging change — makes supervision optional. No public API
    change; one narrow behavioral nuance (bare-install predict() now raises a clear
    ImportError) is described above.

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

  • Added tests/utilities/test_optional_imports.py covering import_supervision():
    returns the module when present, and raises an ImportError containing the
    pip install supervision hint when absent (simulated via sys.modules).
  • Guarded the three supervision-dependent test modules (tests/models/test_predict,
    tests/export/test_tflite_inference, tests/datasets/test_synthetic) with
    pytest.importorskip("supervision").
  • Full CPU suite (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.)
  • Manually verified import rfdetr succeeds with supervision blocked, and that
    supervision-producing paths raise the friendly ImportError.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable) — doc/example
    updates are deferred to the follow-up PR together with the native-types change

Additional Context

  • pre-commit run --all-files passes clean (ruff, mypy, docformatter, license
    headers, etc.).
  • Duplicating supervision across the train/onnx/tflite/visual extras keeps
    each functional install working out of the box and avoids any CI workflow change.
    Open to a dedicated [supervision]/[detections] extra instead if you prefer.

  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>
@CLAassistant

CLAassistant commented Jun 4, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@Borda Borda changed the title refactor(deps): make supervision an optional dependency (#1074) refactor(deps): make supervision an optional dependency Jun 6, 2026
@Borda Borda requested a review from Copilot June 6, 2026 16:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 supervision from 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 supervision is 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.

Comment thread src/rfdetr/utilities/optional_imports.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Jun 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 77%. Comparing base (680b586) to head (94d59c1).

❌ 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:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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>
@everglow01

Copy link
Copy Markdown
Author

Hi @Borda 👋 — thanks for the autofix commit, I've rebased on top of it.

PR1 status (this PR):

  • supervision is now optional: lazy imports + a friendly ImportError. predict() still returns sv.Detections, so no public API change.
  • Fixed the failing Validate model instantiation job: scipy is used in models/matcher.py on the core import path but was only satisfied transitively via supervision. Moved scipy into core dependencies, and the integration job now installs [plus,visual] so supervision is present for the predict() check. Bare pip install rfdetr imports cleanly again.

I think this is ready for review.

Question on the follow-up (PR2): the second half of #1074 — returning native Python types when supervision is absent (plus a to_supervision() helper) — changes the documented return type, so I'd like your guidance before implementing. Preferred shape: (a) keep returning sv.Detections when installed, native dataclass only when absent (backwards-compatible union), or (b) a unified native type with .to_supervision()? Happy to open a separate issue to discuss if that's easier.

@everglow01 everglow01 requested a review from Copilot June 7, 2026 01:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment on lines +12 to +36
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)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'd prefer to leave this un-memoized:

  1. Python already caches the module in sys.modules, so after the first call import supervision is just a dict lookup + name binding (microseconds). The helper is also called once per predict() / _run_inference(), not per-sample, so there's no hot-loop cost in practice.
  2. functools.lru_cache would cache the successful module object and break tests/utilities/test_optional_imports.py::test_raises_with_install_hint_when_missing, which relies on monkeypatch.setitem(sys.modules, "supervision", None) to force the missing-package path after a prior successful import. Working around that (a cache_clear() in the test) adds complexity for a non-issue.

Happy to revisit if profiling ever shows this on a hot path.

Comment thread .github/workflows/ci-integrations.yml Outdated
Comment on lines +40 to +45
- 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]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@everglow01 everglow01 requested a review from Copilot June 7, 2026 01:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Comment on lines +121 to +122
# 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()
@Anawaert

Anawaert commented Jun 8, 2026

Copy link
Copy Markdown

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 sv

Maybe you can try it in the future?😂

@Borda

Borda commented Jun 8, 2026

Copy link
Copy Markdown
Member

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 sv

Maybe you can try it in the future?😂

We still need to support wide range of python versions...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants