Skip to content

Upgrade PySide6 multi-camera GUI#38

Open
C-Achard wants to merge 175 commits intomasterfrom
cy/pre-release-fixes-2.0
Open

Upgrade PySide6 multi-camera GUI#38
C-Achard wants to merge 175 commits intomasterfrom
cy/pre-release-fixes-2.0

Conversation

@C-Achard
Copy link

@C-Achard C-Achard commented Jan 30, 2026

Finalizing the PySide6+multi-camera GUI

Further refinement of GUI added in #35 by @arturoptophys

Features

  • Full project file tree refactor for more granularity and ease of use
  • Typed configs
  • Improved settings UX/UI Works as is, delayed
  • Theme/UI alignment with DLC, icons, colors, etc
  • Improved camera loading UX and validation
  • Many fixes to OpenCV backend
  • Finish config refactor
  • Fixes all issues in Pre-release fixes & improvements checklist #37
  • Fixes all issues in Feedback thread for PySide6 GUI #40
  • Figure out a proper multi-platform, multi-device OpenCV backend design
  • Make processor socket design a bit safer by default
    • Added proper warnings about executing custom code, leaving this up to user responsibiity

Also tweaks error handling, UI and UX.

Additional TODOs

Related

arturoptophys and others added 30 commits October 21, 2025 11:22
…modern-python-and-pyqt6

Add Basler and GenTL camera backends for modular capture
…camera-functionality

Rework layout and camera handling controls
…r integration

- Implemented `get_device_count` method in `GenTLCameraBackend` to retrieve the number of GenTL devices detected.
- Added `max_devices` configuration option in `CameraSettings` to limit device probing.
- Introduced `BoundingBoxSettings` for bounding box visualization, integrated into the main GUI.
- Enhanced `DLCLiveProcessor` to accept a processor instance during configuration.
- Updated GUI to support processor selection and auto-recording based on processor commands.
- Refactored camera properties handling and removed deprecated advanced properties editor.
- Improved error handling and logging for processor connections and recording states.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
C-Achard and others added 28 commits February 12, 2026 15:05
Add a visual "dirty" state for the Apply Settings button to make unapplied camera config edits more visible. Introduces _set_apply_dirty to toggle button text, icon and tooltip, and a _mark_dirty handler that is connected to various input signals (valueChanged, currentIndexChanged, stateChanged). Ensure the dirty/state is cleared when populating settings and after a successful apply, and unify/replace previous ad-hoc enable calls so the button reliably reflects pending edits.
Basler backend: simplify and harden exposure/gain configuration by only applying positive values, turning off auto modes when available, and logging failures as warnings. Harden stream lifecycle for previews by stopping grabbing if needed, creating the ImageFormatConverter before StartGrabbing, forcing MaxNumBuffer, starting grabbing reliably and logging grab state; also wrap StopGrabbing on shutdown to ignore errors.

Camera config UI: refine property merge and UI behavior — avoid reloading the form after merge, safely refresh camera labels by guarding null lists and blocking signals, ignore redundant selection events, and add explicit preview restart/start helpers (_restart_preview_for_camera and _start_preview_with_camera). Ensure previews never use fast_start mode and improve logging around selection and preview startup.
Avoid unwanted UI-driven side-effects when programmatically updating or selecting cameras. Add suppression flags to skip selection handlers/ actions during updates and preview startup, and wrap preview start/stop in a try/finally to restore suppression. Block widget signals while loading camera settings to prevent spurious change events, add exposure/gain to the form field groups, and skip probing when a preview is active. Also add a few diagnostic logs to help trace selection/preview behavior.
Improve camera dialog test fixture to show the dialog, yield it to tests, and perform a robust teardown: stop preview, reject/close the dialog, and wait for loader/scan worker threads and preview state to clear. Also add an autouse fixture that monkeypatches QMessageBox methods to raise on any unexpected modal dialog, preventing tests from hanging due to blocking message boxes. Small formatting/inline cleanup of MultiCameraSettings initialization included.
Ensure the probe worker is cleanly stopped when shutting down: call request_cancel(), wait (up to 1500ms), and clear the _probe_worker reference to avoid dangling threads. Also make a copy of the QImage created from the frame buffer (QImage(...).copy()) so the Qt image doesn't reference the underlying frame memory, preventing use-after-free crashes or visual corruption.
Camera dialog: tighten preview lifecycle and disabled-control handling. Introduces a preview-starting flag, commits pending edits before starting preview, consolidates loader/start/stop flow, and treats exposure/gain as 0 when their controls are disabled so they won't trigger unnecessary restarts. Also fixes various preview UI/loader state transitions and button states during async start/stop.

Tests: large refactor of test fixtures to provide deterministic fake backends and DLCLive doubles. Adds reusable test backend helpers (make_backend_class, temp_backend, register_fake_backend_session), a stable fake_backend_factory, and simplifies GUI autouse patches. Expands and reorganizes unit and end-to-end camera-config tests to cover preview start/stop, restart/no-restart semantics, scan cancellation, duplicate/max-camera guards, commit-on-select/OK behavior, crop validation, and backend capability handling. Updates gui conftest to use the new fake backend and DLCLive test doubles.
Stabilize GUI tests by making backend discovery and capabilities deterministic. Add _select_backend_for_active_cam helper to ensure combo/backend coherence during tests. Replace global fake/opencv mixing with a patch_detect_cameras fixture (staticmethod) and use a stable 'fake' backend in E2E fixtures. Inline CountingBackend in relevant tests and monkeypatch CameraFactory.create as staticmethod to avoid hardware access. In unit tests, patch backend_capabilities for predictable enable/disable state and switch assertions to FPS (supported) instead of gain. Misc: minor test cleanups, docstring tweaks, and staticmethod adjustments for slow scan/loader helpers.
* Enhance Aravis backend device discovery & rebind

Add device enumeration and rebinding utilities to the Aravis backend: implement quick_ping, discover_devices, rebind_settings, _arv_snapshot_devices and _safe_str to allow probing and best-effort rebinds without opening cameras. Update open() to record and refresh device identity (device_id, physical id, vendor/model/serial, label) into CameraSettings properties and compute a higher-quality label from the opened camera. Change namespace key usage from camera_id to device_id and import CameraSettings and DetectedCamera to support the new APIs.

* Add fake camera SDKs and contract tests

Introduce comprehensive test scaffolding for camera backends: add fake SDK implementations and patching fixtures in tests/cameras/backends/conftest.py (FakeAravis, FakePylon, FakeHarvester and helpers to monkeypatch aravis/pypylon/harvesters). Add backend-agnostic contract tests in tests/cameras/backends/test_generic_contracts.py to validate backend capability shapes, availability reporting, safe discovery, create/close semantics, optional accelerator warnings, and a GUI identity helper. These changes enable running backend contract tests in CI without real SDKs and help ensure consistent backend behavior.

* Enhance fake GenTL/Aravis test fixtures

Make test backend mocks more robust for SDK-less unit tests: add numpy import and a stricter Aravis availability check (require gi and Aravis typelib), expose HARVESTERS_AVAILABLE, and refine force_pypylon_unavailable to set pylon=None. Replace the simple FakeHarvester with a richer fake GenTL implementation (device info adapter, node/node_map, image acquirer, payload/components, timeout exception) that supports create()/create_image_acquirer(), start/stop/fetch and realistic buffer payloads. Patch GenTLCameraBackend to avoid CTI file searching during tests. Also remove pytest.mark.integration markers from many aravis tests so they run as unit tests.
* Adjust coverage pragmas and omit entries

Rework test-coverage annotations and update .coveragerc. Move # pragma: no cover/cover markers inline on classes in dlc_processor_socket.py (OneEuroFilter and example processor classes) to more precisely control coverage. Comment out the explicit omit list in .coveragerc so those backend shims are no longer excluded there. No functional behavior changed; these edits only affect test coverage reporting and file trailing-newline cleanup.

* Add ScrubSpinBox tests and mouse helpers

Add comprehensive tests for ScrubSpinBox and ScrubDoubleSpinBox behavior, including fixtures, event helpers, and modifier handling. Introduces _send_mouse, _press, _move, and _release helpers to send QMouseEvent directly (reduces flakiness), plus spin/dspin fixtures configured for scrubbing. Tests cover initialization defaults, tooltip instruction handling, scrubbing threshold and accumulation, Shift/Ctrl modifier effects, rounding/coercion behavior for integer and double spinboxes, and ensuring coercion avoids zero step. Also updates imports and adds small test section headers for clarity.

* Update pre-commit config and add hooks

Bump pre-commit-hooks to v6.0.0 and add the check-toml hook; bump ruff-pre-commit to v0.15.0. Add pyproject-fmt (v2.15.2) and validate-pyproject (v0.25) repos to format and validate pyproject.toml files. These changes add TOML checks and pyproject formatting/validation and update tooling versions.

* Include tests and enable branch coverage

Update coverage configuration in pyproject.toml: add "tests" to run.source and enable branch coverage (branch = true); remove tests omission from the run omit list and add an omit rule under [tool.coverage.report] to exclude tests/* from generated reports. This collects branch metrics and includes tests in coverage analysis while keeping tests out of the final report output.
* Extract camera loaders; refactor preview UI state

Move camera worker and preview state logic into a new module (dlclivegui/gui/camera_loaders.py) and refactor the CameraConfigDialog to use it. Added DetectCamerasWorker, CameraProbeWorker, CameraLoadWorker, PreviewState enum and PreviewSession dataclass to centralize loader/backend/timer intent. Removed the embedded worker classes from camera_config_dialog.py and replaced multiple booleans/flags with a single PreviewSession, an epoch-based signal invalidation mechanism, coalesced preview restarts, and unified scan/preview UI syncing. Also adjusted loader signal handlers to be epoch-aware, simplified start/stop flows, and made small docstring/comment tweaks in basler_backend.py regarding fast_start.

* Refactor camera config into package

Move camera configuration code into a dedicated gui/camera_config package and extract UI construction into a new ui_blocks module. Renamed camera_config_dialog.py and camera_loaders.py to camera_config/camera_config_dialog.py and camera_config/loaders.py respectively, replaced the large _setup_ui implementation with setup_camera_config_dialog_ui(dlg) from ui_blocks.py, and added ui_blocks.py to build the dialog UI. Updated relative imports in __init__.py, main_window.py, and affected modules/tests to the new paths. This refactor improves modularity and keeps the dialog file focused on logic rather than bulky widget construction.

* Refactor camera config dialog & add identity utils

Move camera identity utilities into cameras.factory (apply_detected_identity, camera_identity_key) and add CameraSettings.check_diff for concise settings diffs. Major refactor of CameraConfigDialog: reorganize UI/state helpers, probe/preview lifecycle, auto-apply pending edits, improved scan/probe cancellation and dialog reject handling, duplicate-camera checks, add reorder/add/remove safety, and split preview helpers into a new preview module; also remove unused cv2 import. These changes centralize identity handling, improve preview & probe UX, and add safer settings application and logging. Tests updated to match the new contracts.

* Centralize camera dialog cleanup and fixes

Add a unified _on_close_cleanup and closeEvent to ensure preview/worker shutdown and UI reset on dialog close/cancel. Make cleanup idempotent with a _cleanup_done guard, shorten worker wait times to reduce UI freeze, and defensively reset scan UI widgets. Connect scan cancel button handler. Initialize _multi_camera_settings fallback, tighten type annotations, and note eventFilter UI assignments. Refactor form/apply flow to build a new CameraSettings model, compute diffs, replace the working camera entry and update the active list item (apply now returns a truthy value). Call _reconcile_fps_from_backend when loading settings, remove exc_info from a loader error log, and invoke cleanup before accepting the dialog. These changes improve robustness during shutdown and reduce UI hangs.

* Refactor preview helpers into controller

Move preview state and session datatypes into preview.py and refactor low-level image ops into MultiCameraController static methods. preview.py now delegates rotation, crop, resize and pixmap conversion to MultiCameraController (with proper type hints and QTimer usage). loaders.py removes duplicate PreviewState/PreviewSession definitions and cleans imports. camera_config_dialog uses getattr to safely read backend.actual_fps and updates imports to match the refactor. MultiCameraController gains apply_rotation/apply_crop/apply_resize/ensure_color_* and to_display_pixmap utilities (default: no upscale when resizing).

* Update tests to use PreviewState and dialog._preview

Adapt camera config GUI tests to the refactored preview API: import PreviewState and replace checks against legacy dialog attributes (_loader, _preview_active, _preview_backend, _preview_timer) with dialog._preview.loader, dialog._preview.state (using PreviewState.*), dialog._preview.backend, and dialog._preview.timer. Update waitUntil conditions and assertions across test_cam_dialog_e2e.py to reflect the new preview state machine and object structure.
Improve stability of test_max_cameras_prevented by waiting for the scan to finish before injecting results. Removed the backend='fake' remark from the docstring and added qtbot.waitUntil(lambda: not d._is_scan_running(), timeout=1000) and d._on_scan_finished() around the simulated scan result to ensure the dialog is in a stable state before interacting with UI widgets.
Introduce a new color_dropdowns module to provide colormap and bbox-color QComboBox helpers (gradient swatches, matplotlib registry integration, and enum-based BGR swatches). Refactor layouts.make_two_field_row (renamed and enhanced) to support flexible key/value pairs, styling, and optional spacing, and add enable_combo_shrink_to_current to size combos to their current item. Integrate these into the main window: wire a Visualization group with colormap and bbox color controls, use the new helpers to populate and manage combo state, add _on_colormap_changed, and update bbox color handling. Also update ui_blocks to use make_two_field_row, tweak several UI labels/rows, comment out the PYLON emulation env var, and change several CameraConfigDialog log statements from INFO to DEBUG for less noisy logging.
Introduce a ShrinkCurrentWidePopupComboBox and ComboSizing to make combobox controls shrink to the current selection while the popup widens to fit the longest item. Add factory helpers (make_colormap_combo, make_bbox_color_combo) that create/populate colormap and bbox-color combos with sizing, icons, tooltips and safer Matplotlib handling. Rename/refactor colormap helpers (list_colormap_names, _safe_mpl_colormaps_registry), improve gradient icon creation, and preserve editable behavior if Matplotlib is unavailable. Update main_window to use the new factories, pass sizing and icon options, and adjust key widths for layout consistency.
Add graceful SIGINT handling for the Qt app by installing a signal handler that closes the main window and starting a small QTimer keepalive so Python can process signals while the event loop runs. The timer is stored on QApplication as _sig_timer and cleaned up on aboutToQuit. In the GUI, replace plain QComboBox instances with color_ui.ShrinkCurrentWidePopupComboBox for processor and camera controls, and layout them together using lyts.make_two_field_row to produce a compact, stable row and avoid shifting.
Import the logging module and emit an informational log when a keyboard interrupt triggers _request_quit inside _maybe_allow_keyboard_interrupt. This adds visibility for debugging application shutdowns without changing existing close behavior.
Replace the previous side-panel layout with a QDockWidget-based controls panel to allow docking/undocking and prevent UI shifting. Extract stats layout into _build_stats_layout and enable selectable stats text. Add sizing/shrink options and placeholder for processor and camera combo boxes and call update_shrink_width at key points so combo widths adapt. Add controls toggle to the View menu, set dock features/options, and give the dock a stable objectName for state saving. Also stop the display timer on shutdown and perform minor UI/layout cleanups and refactors (imports and button/preview layout adjustments).
Allow the left controls dock to be closed independently by adding QDockWidget.DockWidgetClosable to its features. Hide the docked title bar by applying a transparent stylesheet to the controls dock to improve visual integration. Also add a separator in the View menu before the Appearance submenu
Prevent the Controls dock from being closed by the user (keep it movable/floatable) and replace the previous toggleViewAction with an explicit, checkable "Show controls" QAction in the View menu. The new action is synchronized with the dock's visibility (action toggled -> dock visibility; dock visibilityChanged -> action checked). Also minor reordering/cleanup of Appearance menu setup and comments.
Improve camera config loading UX and improve Basler backend
…-backend"

This reverts commit 03af146, reversing
changes made to fe62907.
@C-Achard C-Achard linked an issue Feb 16, 2026 that may be closed by this pull request
15 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pre-release checklist

4 participants