Add UI to configure Trigger mode#79
Draft
C-Achard wants to merge 27 commits into
Draft
Conversation
Add generic hardware-trigger support for GenTL cameras and expose trigger settings in CameraSettings. - Introduce CameraTriggerSettings model (config.py) with role/input/output/timeout/strict and helpers to coerce and serialize. - Allow CameraSettings to read/write backend-specific trigger options. - GenTLCameraBackend: import and use trigger settings, apply trigger timeout override, persist actual trigger config, and mark hardware_trigger capability as BEST_EFFORT. - Implement trigger configuration paths: off, external/follower (input), and master (output). Add helper methods (_set_enum_node, _node, _node_symbolics, _trigger_attr, _trigger_to_dict) to safely interact with GenICam nodes. - Improve lifecycle handling: configure trigger after other settings, restore a safe non-triggering state on stop, and provide waits_for_hardware_trigger property and clearer timeout messages when waiting for hardware triggers. - Add DEFAULT_CAPABILITIES entry for hardware_trigger in base defaults. These changes enable safer and configurable hardware-trigger operation for GenTL backends and improve error reporting and shutdown behavior.
Populate gentl trigger defaults on save and allow saving configs with empty DLCLive model path. Add _with_camera_defaults_for_save to ensure CameraTriggerSettings are present for gentl cameras and thread through allow_empty_model_path in _dlc_settings_from_ui/_current_config so configs can be saved without a model while preserving existing DLC fields. Improve multi-camera runtime robustness: treat TimeoutError from hardware-trigger backends as an expected 'no trigger' event (don't count as a camera failure) and add _trigger_role_from_settings/_camera_start_priority helpers. Start active cameras sorted by trigger role so trigger-waiting (external/follower) devices are armed before masters. Also extend DLCLive configuration error handling to include RuntimeError.
Enhance Qt app signal handling to support SIGTERM and SIGBREAK, and make Ctrl+C shutdown more robust. Adds a quitting flag and a two-stage interrupt: first interrupt triggers window close and schedules app.quit, second interrupt forces immediate exit (os._exit(130)). Parents the keepalive QTimer to the QApplication, sets a 100ms interval, and safely stops any previous timer to avoid duplicates. Adds logging and exception handling around window close and timer cleanup, and uses QTimer.singleShot to ensure Qt leaves its event loop even if closeEvent cleanup is asynchronous.
Add a comprehensive test suite for GenTL hardware trigger handling (tests/cameras/backends/test_gentl_trigger.py). Tests cover trigger roles (off/external/follower/master), selector/source/activation settings, strict vs non-strict behavior for invalid sources, master output configuration, alias mapping, timeout handling and error messaging, and persistence of trigger_actual for debugging. Update the test conftest fake node map (tests/cameras/backends/conftest.py) to include Trigger* and Line* nodes (AcquisitionMode, TriggerSelector, TriggerMode, TriggerSource, TriggerActivation, LineSelector, LineMode, LineSource) so the tests can exercise trigger and GPIO-related configuration.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds camera-level hardware trigger configuration to the camera configuration dialog, focused on GenTL-backed cameras and preview restart behavior when trigger settings change.
Changes:
- Adds a new
TriggerConfigDialogfor editing trigger role, input/output lines, activation, timeout, and strict mode. - Adds a “Trigger Settings…” UI button and trigger role indicators in active camera labels.
- Initializes default GenTL trigger settings and restarts previews when trigger configuration changes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
dlclivegui/gui/camera_config/ui_blocks.py |
Adds the trigger settings button to the camera settings form. |
dlclivegui/gui/camera_config/trigger_config_dialog.py |
Implements the trigger configuration dialog and persistence into camera backend properties. |
dlclivegui/gui/camera_config/camera_config_dialog.py |
Wires trigger settings into the dialog, labels, defaults, and preview restart logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Use the user-configured camera order for display/tiling and ensure tiling geometry matches displayed frames. Changes: - GUI: populate the inference camera dropdown from active cameras in the configured order and only add entries for cameras that are actually running. - MultiCameraController: store the user display order, derive startup order by sorting for trigger safety (followers/external first, master last), emit frames/timestamps in display order (with any unexpected IDs appended deterministically), clear display order on stop, and handle no-active-cameras early. - Utils: compute_tiling_geometry now uses the frames' display order (insertion order) rather than sorted keys so tile dimensions and overlays align with the tiled frame; updated docstrings to reflect this. These changes ensure consistent tiling, overlay transforms, and UI behavior that follow the user's configured ordering.
Update display tests to assert that tiling and tile computations preserve frame insertion/display order (no longer sorting by camera ID) and add coverage for tile offsets, scaling, and tiled frame content. Add a suite of unit tests for MultiCameraController utilities and behavior: get_camera_id, trigger role aliasing, camera start priority, preserving user display order on start, frame_ready emission order, clearing display order on stop, hardware trigger timeouts (non-fatal), and non-trigger timeouts (fatal). Also import newly-tested helper functions from multi_camera_controller.
Patch tests/gui/test_app_entrypoint.py to monkeypatch appmod._maybe_allow_keyboard_interrupt with a MagicMock in both test_main_with_splash and test_main_without_splash. This prevents the real interrupt-handling helper from running during GUI tests and avoids side effects on global keyboard/signal handling.
GenTLCameraBackend: add handling for a 'strict' flag when parsing GenTL trigger config so invalid configs raise in strict mode but fall back with a warning otherwise. Preserve the original exception when raising, and improve the warning text to mention strict mode. Return whether LineSelector was actually set and skip configuring trigger output if selection failed to avoid driving an unintended GPIO line. DLCLiveMainWindow: introduce _with_camera_trigger_defaults_for_save to ensure gentl trigger defaults are stored per camera, refactor _with_camera_defaults_for_save to apply that per-camera, and adjust _current_config to use the first camera with defaults applied. This ensures trigger settings are persisted and validated consistently.
Add with_save_defaults helpers to CameraSettings and MultiCameraSettings and use them when serializing ApplicationSettings so gentl "trigger" defaults are applied to saved configs. Remove duplicated camera-default helper logic from DLCLiveMainWindow and simplify _current_config to rely on model methods. Add unit tests to verify gentl trigger defaults are included for top-level and multi-camera cases.
Remove sorting when building available_ids so the original frame key order is preserved. The DLC camera selection relies on the first active camera in frame_data.frames; using list(...) keeps the insertion/order semantics (dict order) instead of reordering keys with sorted(). This avoids unintended changes to camera priority caused by alphabetical/numeric sorting.
Initialize an existing DLC config (falling back to DEFAULT_CONFIG if unset) and use its model_copy(update=...) to return settings. This replaces explicit field-by-field construction so only model_path (and model_type when set) are changed, preserving other DLC options and avoiding duplication.
Add a warning log when setting GenTL TriggerMode to 'On' fails in non-strict mode. Previously the code only raised an exception in strict mode; now it emits a warning to inform users that the trigger mode may not be correctly configured when continuing without strict enforcement.
Make GenTLCameraBackend trigger configuration more robust and adjust tests. - Use the waits_for_hardware_trigger property when converting GenTL timeouts to user-facing errors instead of inferring from the role string. - Read and record the configured trigger role early in _configure_trigger_input. - Check results when setting TriggerSelector, TriggerSource and TriggerActivation (selector_ok, source_ok, activation_ok). If selector/source routing fails in non-strict mode, disable the trigger, reset internal trigger state and log a warning to avoid arming the camera on a previous/default input line. If activation fails, warn and continue using the camera default. - If enabling TriggerMode=On fails, disable the trigger and reset internal state instead of only warning. - Validate LineMode and LineSource when configuring master output and log if configuration is incomplete. Tests updated to reflect safety changes: - Expect waits_for_hardware_trigger to be set for external input configuration. - Rename and change a non-strict invalid-source test to assert the trigger is disabled, waits_for_hardware_trigger is False, and trigger_actual is persisted as off. - Add a new test ensuring an invalid selector in non-strict mode disables the trigger and persists the off state. These changes prevent the camera from being left armed on an unintended/default input if routing nodes could not be applied, improving safety and predictability.
Introduce a MAX_HARDWARE_TRIGGER_FETCH_TIMEOUT and cap Harvester.fetch() timeouts when the trigger role waits for external hardware (roles: external, follower) to keep individual fetch calls short and allow prompt shutdown. Preserve legacy behavior for non-waiting roles (e.g. master). Remove a noisy Trigger input LOG.info call. Update tests to expect the capped fetch timeout, verify the original requested timeout is still persisted in trigger_actual, add a test that master mode is not capped, and make test resource cleanup more robust (use try/finally around open/close).
Make SingleCameraWorker sleep calls interruptible by using self._stop_event.wait() instead of time.sleep(), and add a small _trigger_timeout_delay to allow early exit during trigger/wait cycles. This prevents the worker from being unresponsive to stop requests during retry and trigger waits. Also simplify ApplicationSettings.to_dict() to always start from self.camera.with_save_defaults() and stop overriding it with active multi-camera selection, ensuring consistent camera settings are saved.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Add resolution logic for trigger source: implement _resolve_trigger_source to map the model-level default 'auto' to the first supported camera line (Line0/Line1/Line2/Any), return whether the resolved value is supported, and log/warn or raise depending on strict mode. Integrate this into _configure_pixel_format/_configure trigger flow so TriggerSource is only set if resolution succeeded. Change CameraTriggerSettings default source to 'auto' and add a field validator to coerce common aliases (e.g. 'default', 'automatic', 'device', 'camera') to 'auto'. Update tests and fixtures: adjust fake node default TriggerSource to Line1, update expectations, and add unit tests for auto-selection behavior and strict-mode error. This makes trigger configuration more robust across cameras with differing GenICam enum symbolics.
Introduce a TriggerConfigDialog to edit per-camera hardware trigger settings (CameraTriggerSettings). The dialog provides fields for role, selector, source, activation, output line/source, timeout and strict mode, and persists settings into camera.properties[<backend>]['trigger']. Also add a "Trigger Settings…" button to the camera settings UI (disabled by default) with tooltip and icon; wiring to open the dialog can be connected elsewhere.
Introduce support for per-camera hardware trigger configuration in the camera config dialog. Imports TriggerConfigDialog and CameraTriggerSettings, wires the trigger settings button to open a modal, and adds _open_trigger_settings_dialog to commit edits, show the dialog, apply updates, and restart the preview if needed. Adds helpers: _ensure_default_trigger_config to initialize gentl.trigger defaults, _trigger_role_for_label to show trigger role in camera list labels, and _trigger_dict_for_cam to compare trigger settings when deciding to restart previews. Also integrates the hardware trigger field into the settings summary and ensures new/loaded cameras get a default trigger config.
Replace direct checks of self._preview.state == PreviewState.ACTIVE with self._is_preview_live() in camera_config_dialog to centralize preview-active logic and ensure consistent behavior when restarting the preview after trigger or camera setting changes. In trigger_config_dialog, only set payload["timeout"] for external/follower roles when timeout > 0, and explicitly set payload["timeout"] = None when role == "off" to clear any stale timeout when disabling the trigger.
Allow pending preview restarts to proceed when the preview is ACTIVE (previously only IDLE) and return early to avoid double UI sync in camera_config_dialog.py. Also wrap CameraTriggerSettings loading in a try/except and fall back to a default instance when parsing fails, making trigger_config_dialog.py tolerant of malformed or missing trigger data.
279a952 to
1780065
Compare
Make trigger dialog friendlier and more robust: recommend using 'auto' for trigger source and switch default/fallback source from "Line0" to "auto", update source placeholder and info/timeout tooltips, and import QMessageBox. Wrap CameraTriggerSettings.from_any in a try/except to show a critical error dialog on failure and abort apply. Store trigger settings via trigger.to_properties() instead of model_dump(exclude_none=True). These changes provide clearer defaults and better error feedback when applying trigger settings.
Introduce WorkerTimingStats (utils/stats.py) to collect simple timing counters (per-named section totals, frame/timeouts/errors) and periodically emit debug logs. Integrate it into SingleCameraWorker: create a timing instance (configurable via new SINGLE_CAMERA_WORKER_DO_LOG_TIMING in config.py), wrap backend.read and frame emit calls with timed sections, and call note_frame/note_timeout/note_error + maybe_log to accumulate and flush stats. Also update imports accordingly and use a 1s default log interval.
Introduce MULTI_CAMERA_WORKER_DO_LOG_TIMING and disable single-worker timing by default (SINGLE_CAMERA_WORKER_DO_LOG_TIMING=false). Add per-camera WorkerTimingStats storage and a _timing_for_camera factory that respects the new config. Wrap _on_frame_captured processing in timed sections (total, apply_transforms, update_latest), call note_frame/maybe_log per camera, and emit frames as before. Also adjust SingleCameraWorker timing labels from GenTL.* to Single.* for clearer logs.
Introduce a human-readable representation for CameraSettings by adding a pretty() method and overriding __str__ and __repr__. The pretty output formats key fields (name, index, backend, enabled, fps, size, exposure, gain, rotation) and displays a readable crop region (showing 'none' or coordinate range with 'edge' fallbacks). This aids debugging and logging without changing existing validation or behavior.
ae2664b to
662313a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request adds comprehensive support for configuring hardware trigger and synchronization settings for individual cameras in the camera configuration dialog. It introduces a new
TriggerConfigDialogfor editing per-camera trigger settings, updates the UI to display trigger roles, and ensures that trigger changes are properly handled and reflected in the camera preview and configuration.Key changes include:
Hardware Trigger Configuration Support:
TriggerConfigDialog(trigger_config_dialog.py) that allows users to configure per-camera hardware trigger and synchronization settings, such as role, selector, source, activation, output line, output source, timeout, and strict mode. The dialog updates the camera's trigger configuration in a structured and user-friendly way.trigger_settings_btn) and connecting it to open the dialog for the selected camera. [1] [2] [3]UI and Usability Improvements:
Configuration and Preview Handling:
applyutility to support the new trigger settings button, maintaining consistent UI state management.These changes collectively provide robust and user-friendly management of hardware trigger and synchronization settings for cameras in the application.