feat(utils): add TkImageWindow and switch to opencv-python-headless#2320
feat(utils): add TkImageWindow and switch to opencv-python-headless#2320Borda wants to merge 27 commits into
TkImageWindow and switch to opencv-python-headless#2320Conversation
Borda
commented
Jun 14, 2026
- 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>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #2320 +/- ##
========================================
Coverage 83% 83%
========================================
Files 69 70 +1
Lines 9616 9757 +141
========================================
+ Hits 7941 8060 +119
- Misses 1675 1697 +22 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 toopencv-python-headless. - Migrated runnable examples away from
cv2.imshow/cv2.waitKeytosv.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. |
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>
…into drop/cv2-gui
…into drop/cv2-gui
…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>
…into drop/cv2-gui
- 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>
- [resolve #3] add Added: sv.TkImageWindow entry with key differences from cv2 (wait_key return type, mouse signature, left-button-only) - [resolve #2] replace 'resolver conflict' with accurate 'silent co-install with filesystem collision' — pip installs both wheels silently; whichever ran last wins; advise explicit uninstall - [resolve #8] add python3-tk system package install instructions (apt-get / brew) in TkImageWindow entry --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
- [resolve #7] add test_wait_key_returns_none_when_no_window — tests fix #6 (early return when no window and queue empty) - add test_wait_key_returns_none_when_closed_mid_wait — tests interaction of fix #5 + fix #9 (close mid-wait clears queue, wait_key returns None) - add test_close_clears_key_queue — tests fix #5 (_key_queue cleared on close) - collapse two structurally-identical show() error-path tests into one parametrized test (PT006-compliant tuple first arg) --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
|
@Borda I took a quick look at the code. I agree, and I like this direction. Let me know when it'll be ready. |
- Add _on_configure binding that rescales stored image to new window dims - Add _update_display() to separate frame rendering from show() plumbing - Add _fit_image() helper: aspect-ratio fit or free-form stretch - Expose keep_aspect_ratio param on TkImageWindow (default True) - Add 16 tests: TestFitImage, TestTkImageWindowUpdateDisplay, TestTkImageWindowOnConfigure --- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
|
This is expected as it refers to the contribution of this PR |
# Conflicts: # examples/time_in_zone/inference_stream_example.py # examples/time_in_zone/rfdetr_stream_example.py # examples/time_in_zone/ultralytics_stream_example.py