Skip to content

feat(utils): add TkImageWindow to unblock switch to opencv-python-headless#2320

Merged
Borda merged 47 commits into
developfrom
drop/cv2-gui
Jul 15, 2026
Merged

feat(utils): add TkImageWindow to unblock switch to opencv-python-headless#2320
Borda merged 47 commits into
developfrom
drop/cv2-gui

Conversation

@Borda

@Borda Borda commented Jun 14, 2026

Copy link
Copy Markdown
Member
  • Add sv.TkImageWindow: tkinter+pillow desktop window replacing cv2.imshow/waitKey/destroyAllWindows under headless OpenCV; supports BGR/grayscale/BGRA frames, key polling, mouse callbacks, context manager
  • Switch pyproject.toml from opencv-python to opencv-python-headless (breaking: cv2.imshow/waitKey/namedWindow no longer provided transitively; restore with pip install opencv-python)
  • Migrate all 17 runnable examples from cv2.imshow/waitKey/destroyAllWindows to sv.TkImageWindow
  • Add docstring with webcam ownership pattern to VideoInfo.from_video_path and get_video_frames_generator
  • Add breaking-change CHANGELOG entry and two FAQ entries covering headless and webcam usage
  • Add 18 tests for TkImageWindow covering show(), wait_key(), close(), context manager, mouse callbacks, _bgr_to_pil

- Add sv.TkImageWindow: tkinter+pillow desktop window replacing cv2.imshow/waitKey/destroyAllWindows under headless OpenCV; supports BGR/grayscale/BGRA frames, key polling, mouse callbacks, context manager
- Switch pyproject.toml from opencv-python to opencv-python-headless (breaking: cv2.imshow/waitKey/namedWindow no longer provided transitively; restore with pip install opencv-python)
- Migrate all 17 runnable examples from cv2.imshow/waitKey/destroyAllWindows to sv.TkImageWindow
- Add docstring with webcam ownership pattern to VideoInfo.from_video_path and get_video_frames_generator
- Add breaking-change CHANGELOG entry and two FAQ entries covering headless and webcam usage
- Add 18 tests for TkImageWindow covering show(), wait_key(), close(), context manager, mouse callbacks, _bgr_to_pil

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 14, 2026 23:42
@Borda Borda requested a review from SkalskiP as a code owner June 14, 2026 23:42
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.29282% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 87%. Comparing base (d5cadf5) to head (e903d68).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop   #2320    +/-   ##
========================================
- Coverage       87%     87%    -0%     
========================================
  Files           71      72     +1     
  Lines        10485   10679   +194     
========================================
+ Hits          9098    9264   +166     
- Misses        1387    1415    +28     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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 introduces a tkinter+Pillow-based image display utility (sv.TkImageWindow) so runnable examples (and user code) can display frames without relying on OpenCV HighGUI, and switches the core dependency from opencv-python to opencv-python-headless to avoid pulling GUI libraries transitively.

Changes:

  • Added sv.TkImageWindow (plus _bgr_to_pil) and exported it from the top-level package.
  • Switched pyproject.toml (and lockfile) dependency to opencv-python-headless.
  • Migrated runnable examples away from cv2.imshow/cv2.waitKey to sv.TkImageWindow, added tests, and documented the breaking change in docs.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
uv.lock Updates locked dependencies reflecting the move to opencv-python-headless.
pyproject.toml Switches runtime dependency from opencv-python to opencv-python-headless.
src/supervision/utils/image_window.py Adds TkImageWindow implementation and BGR/BGRA/grayscale conversion helper.
src/supervision/init.py Exports TkImageWindow at the package top level.
src/supervision/utils/video.py Adds/updates VideoInfo.from_video_path docstring content.
tests/utils/test_image_window.py Adds unit tests for TkImageWindow and _bgr_to_pil.
docs/faq.md Adds FAQ entries covering headless OpenCV GUI removal and webcam guidance.
docs/changelog.md Adds breaking-change entry describing the OpenCV wheel switch and mitigation.
examples/traffic_analysis/ultralytics_example.py Replaces cv2.imshow/waitKey usage with TkImageWindow.
examples/traffic_analysis/inference_example.py Replaces cv2.imshow/waitKey usage with TkImageWindow.
examples/time_in_zone/ultralytics_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI in streaming callback.
examples/time_in_zone/ultralytics_naive_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/time_in_zone/ultralytics_file_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/time_in_zone/scripts/draw_zones.py Migrates interactive zone drawing from OpenCV HighGUI to TkImageWindow mouse+key handling.
examples/time_in_zone/rfdetr_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI in streaming callback.
examples/time_in_zone/rfdetr_naive_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/time_in_zone/rfdetr_file_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/time_in_zone/inference_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI in streaming callback.
examples/time_in_zone/inference_naive_stream_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/time_in_zone/inference_file_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/speed_estimation/yolo_nas_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/speed_estimation/ultralytics_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/speed_estimation/inference_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/count_people_in_zone/ultralytics_example.py Uses TkImageWindow instead of OpenCV HighGUI.
examples/count_people_in_zone/inference_example.py Uses TkImageWindow instead of OpenCV HighGUI.

Comment thread src/supervision/utils/image_window.py
Comment thread src/supervision/utils/image_window.py
Comment thread src/supervision/utils/image_window.py Outdated
Comment thread docs/changelog.md Outdated
Comment thread src/supervision/utils/video.py
Comment thread docs/faq.md Outdated
Borda and others added 15 commits June 15, 2026 02:17
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
…failure

patch("PIL.ImageTk.PhotoImage") resolved via pkgutil.resolve_name on
Python 3.12+, which calls getattr(PIL, "ImageTk") — fails on headless
runners without python3-tk. Replace with patch.dict("sys.modules",
{"PIL.ImageTk": fake_imagetk}) which injects the mock before the
from PIL import ImageTk call inside show(), avoiding the import
entirely.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
InferencePipeline calls on_prediction() from a worker thread; tkinter
requires all Tk calls from the thread that created Tk() root — macOS
enforces this strictly (crash), Linux is undefined behavior. Revert
inference_stream_example, rfdetr_stream_example, and
ultralytics_stream_example to cv2.imshow/waitKey (requires
opencv-python, not headless).

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ption docs

- Replace 'Drop-in replacement' with 'Functional replacement' — wait_key
  returns str not int, mouse callback signature differs from cv2, only
  left-button events captured
- Add 'Differences from cv2' section documenting all three breaking
  behavioural differences for migrating callers
- Correct documented failure mode for headless environments: raises
  AttributeError (PIL.ImageTk missing) not TclError when python3-tk absent;
  distinguish that from display-unavailable case (TclError)

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
PR description promised a webcam/VideoCapture note on this function but
the docstring was left without guidance. Add a Note section showing the
cv2.VideoCapture pattern with explicit release in a finally block for
callers wanting live camera access.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
TkImageWindow is public API (exported in __all__) but had no docs page.
Add docs/utils/image_window.md and wire it into mkdocs.yml under Utils.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

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 26 out of 27 changed files in this pull request and generated 5 comments.

Comment thread src/supervision/utils/image_window.py Outdated
Comment thread src/supervision/utils/image_window.py
Comment thread examples/time_in_zone/ultralytics_stream_example.py Outdated
Comment thread examples/time_in_zone/rfdetr_stream_example.py Outdated
Comment thread examples/time_in_zone/inference_stream_example.py Outdated
Borda and others added 2 commits June 16, 2026 14:23
- Replace bare `list[np.ndarray]` with `list[npt.NDArray[np.floating]]`
  in _merge_obb_corners signature — fixes mypy missing-type-args error
  introduced by #2312 OBB NMM merge

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
… race

- [resolve #4] fix docstring: show() raises ModuleNotFoundError (not
  AttributeError) when python3-tk absent; add apt-get/brew install hint
- [resolve #5] clear _key_queue in _reset_window_refs() so stale
  keypresses from a previous session don't fire after close()+show()
- [resolve #6] wait_key() returns None immediately when _root is None
  and queue is empty — matches cv2.waitKey() contract (no ghost window)
- [resolve #9] re-check _key_queue before wait_variable in
  _wait_for_key_or_close() to close the _on_key arrival race window

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
@SkalskiP

SkalskiP commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

General question: what happens when someone installs supervision together with inference or ultralytics in the same Python environment? Which OpenCV package gets installed, one or both? If both are installed, which one is used at runtime? Should we have any recommendations for users who run into this setup?

Comment thread examples/time_in_zone/inference_stream_example.py
Comment thread examples/time_in_zone/scripts/draw_zones.py
Comment thread src/supervision/utils/image_window.py Outdated
Comment thread src/supervision/utils/image_window.py Outdated
Comment thread src/supervision/utils/image_window.py
Comment thread docs/changelog.md Outdated
Comment thread docs/changelog.md Outdated
Comment thread docs/faq.md
Borda and others added 10 commits July 9, 2026 14:24
…xports

[resolve group] PR #2320 — item 2

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
…llow; drop banner comments

Bug: _on_mouse forwarded raw label-widget event.x/event.y unscaled while
_fit_image scales+letterboxes the displayed frame, so mouse coordinates
drifted from image-pixel space after any window resize (user-confirmed).
Fix stores display scale/offset at render time and inverts the transform
in _on_mouse, clamped to the original image bounds.

Also extends sv.cv2_to_pillow to support grayscale and BGRA inputs (it
previously only handled 3-channel BGR) and makes _bgr_to_pil delegate to
it instead of duplicating the conversion logic. Drops four decorative
section-separator comment blocks per reviewer request.

[resolve group] PR #2320 — items 1, 2, 5, 8

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
…ing admonition

Updates the TkImageWindow -> ImageWindow rename mention. Deduplicates the
repeated pip uninstall/install snippet under "How to restore GUI support"
and "Co-installation warning" into a single cross-referenced block, and
reframes the opencv-python-headless breaking-change note as a mkdocs
!!! warning admonition instead of a regular bullet so it is not mistaken
for a routine change.

[resolve group] PR #2320 — items 2, 6, 7

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…1 section

The 0.29.1 tag is already released; filing this PR's changelog entries
there falsely implied the feature shipped in a past release. Moved the
ImageWindow "Added" bullet and the opencv-python-headless breaking-change
warning into the Unreleased section instead.

Also adds test coverage flagged by the QA gate: an end-to-end
resize-then-click path (_on_configure -> _on_mouse) and mouse-coordinate
mapping under keep_aspect_ratio=False (stretched mode), both previously
untested.

[resolve] PR #2320 — Step 9 QA gate fixes

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
- Add ImageWindow.is_open so display loops can stop cleanly when the Tk window is closed.\n\n- Update examples, docs, and FAQ guidance to use the new window-state check and clarify OpenCV wheel selection.\n\n- Tighten grayscale crop tests and cap opencv-python-headless below 5 for compatibility.

---

Co-authored-by: Codex <codex@openai.com>
@Borda Borda requested a review from SkalskiP July 10, 2026 15:51

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 27 out of 28 changed files in this pull request and generated 3 comments.

Comment thread src/supervision/utils/image_window.py
Comment thread src/supervision/utils/image_window.py Outdated
Comment thread src/supervision/__init__.py
Borda and others added 2 commits July 10, 2026 18:08
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment thread pyproject.toml Outdated
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
@Borda Borda changed the title feat(utils): add TkImageWindow and switch to opencv-python-headless feat(utils): add TkImageWindow to unblock switch to opencv-python-headless Jul 14, 2026
@Borda

Borda commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@SkalskiP so holding on the switch to opencv-python-headless yet and adding just the GUI

Borda and others added 4 commits July 15, 2026 11:25
docs/changelog.md and docs/faq.md described opencv-python-headless as
already the declared dependency ("supervision now depends on..."), and
docs/faq.md added a full FAQ entry on restoring GUI support from
headless — but pyproject.toml still pins opencv-python (the switch was
reverted in cfd1f69 and dropped from this PR's scope).

- Replace the false "Breaking change" block in changelog.md with a
  forward-looking "Planned: OpenCV dependency change" note, and drop
  the "under opencv-python-headless" framing from the sv.ImageWindow
  Added entry.
- Remove the "why does cv2.imshow stop working" FAQ entry — its
  premise doesn't hold and won't until a future PR performs the actual
  dependency change.
- Fix examples/time_in_zone/README.md's "with the default headless
  dependency" line to match the unchanged opencv-python pin.

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
The "Planned: OpenCV dependency change" admonition announced a future
dependency removal with no scheduled PR behind it — this PR doesn't
touch pyproject.toml's opencv-python pin, and the change it referenced
belongs to a separate, not-yet-started multi-PR effort. A changelog
records shipped changes, not unscheduled roadmap items.

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
cv2.VideoCapture lives in OpenCV's videoio module, not highgui, so it
works under opencv-python-headless too. Verified empirically in a
clean opencv-python-headless-only venv.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
@Borda Borda merged commit 16814ac into develop Jul 15, 2026
26 checks passed
@Borda Borda deleted the drop/cv2-gui branch July 15, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants