Skip to content

Commit 002c957

Browse files
committed
Add trigger settings dialog and UI button
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.
1 parent a2eca8e commit 002c957

2 files changed

Lines changed: 182 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# dlclivegui/gui/camera_config/trigger_config_dialog.py
2+
from __future__ import annotations
3+
4+
from PySide6.QtWidgets import (
5+
QCheckBox,
6+
QComboBox,
7+
QDialog,
8+
QDialogButtonBox,
9+
QDoubleSpinBox,
10+
QFormLayout,
11+
QGroupBox,
12+
QLabel,
13+
QLineEdit,
14+
QVBoxLayout,
15+
QWidget,
16+
)
17+
18+
from ...config import CameraSettings, CameraTriggerSettings
19+
20+
21+
def _backend_namespace(cam: CameraSettings) -> dict:
22+
backend = (cam.backend or "").lower()
23+
if not isinstance(cam.properties, dict):
24+
cam.properties = {}
25+
ns = cam.properties.setdefault(backend, {})
26+
if not isinstance(ns, dict):
27+
ns = {}
28+
cam.properties[backend] = ns
29+
return ns
30+
31+
32+
class TriggerConfigDialog(QDialog):
33+
"""Small dialog for editing per-camera hardware trigger settings."""
34+
35+
def __init__(self, cam: CameraSettings, parent: QWidget | None = None):
36+
super().__init__(parent)
37+
self.setWindowTitle("Trigger Settings")
38+
self.setMinimumWidth(420)
39+
40+
self._cam = cam.model_copy(deep=True)
41+
42+
ns = _backend_namespace(self._cam)
43+
self._trigger = CameraTriggerSettings.from_any(ns.get("trigger"))
44+
45+
self._setup_ui()
46+
self._load_from_trigger(self._trigger)
47+
self._sync_role_ui()
48+
49+
@property
50+
def camera_settings(self) -> CameraSettings:
51+
return self._cam
52+
53+
def _setup_ui(self) -> None:
54+
root = QVBoxLayout(self)
55+
56+
info = QLabel(
57+
"Configure hardware trigger settings for this camera.\n"
58+
"Unsupported fields are ignored by the backend unless strict mode is enabled."
59+
)
60+
info.setWordWrap(True)
61+
root.addWidget(info)
62+
63+
group = QGroupBox("Hardware Trigger")
64+
form = QFormLayout(group)
65+
66+
self.role_combo = QComboBox()
67+
self.role_combo.addItem("Off / Free-run", "off")
68+
self.role_combo.addItem("External trigger", "external")
69+
self.role_combo.addItem("Follower", "follower")
70+
self.role_combo.addItem("Master", "master")
71+
form.addRow("Role:", self.role_combo)
72+
73+
self.selector_edit = QLineEdit()
74+
self.selector_edit.setPlaceholderText("FrameStart")
75+
form.addRow("Trigger selector:", self.selector_edit)
76+
77+
self.source_edit = QLineEdit()
78+
self.source_edit.setPlaceholderText("Line0")
79+
form.addRow("Trigger source:", self.source_edit)
80+
81+
self.activation_combo = QComboBox()
82+
for value in ("RisingEdge", "FallingEdge", "AnyEdge", "LevelHigh", "LevelLow"):
83+
self.activation_combo.addItem(value, value)
84+
form.addRow("Activation:", self.activation_combo)
85+
86+
self.output_line_edit = QLineEdit()
87+
self.output_line_edit.setPlaceholderText("Line2")
88+
form.addRow("Output line:", self.output_line_edit)
89+
90+
self.output_source_edit = QLineEdit()
91+
self.output_source_edit.setPlaceholderText("ExposureActive")
92+
form.addRow("Output source:", self.output_source_edit)
93+
94+
self.timeout_spin = QDoubleSpinBox()
95+
self.timeout_spin.setRange(0.0, 3600.0)
96+
self.timeout_spin.setDecimals(3)
97+
self.timeout_spin.setSingleStep(0.1)
98+
self.timeout_spin.setSpecialValueText("Default")
99+
self.timeout_spin.setToolTip(
100+
"Fetch poll timeout in seconds. For triggered cameras, 0.2–0.5s is usually responsive."
101+
)
102+
form.addRow("Read timeout:", self.timeout_spin)
103+
104+
self.strict_checkbox = QCheckBox("Strict mode")
105+
self.strict_checkbox.setToolTip("If enabled, missing/unsupported GenICam trigger nodes fail camera open.")
106+
form.addRow(self.strict_checkbox)
107+
108+
root.addWidget(group)
109+
110+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
111+
buttons.accepted.connect(self._accept)
112+
buttons.rejected.connect(self.reject)
113+
root.addWidget(buttons)
114+
115+
self.role_combo.currentIndexChanged.connect(self._sync_role_ui)
116+
117+
def _load_from_trigger(self, trigger: CameraTriggerSettings) -> None:
118+
role = str(getattr(trigger, "role", "off") or "off").lower()
119+
idx = self.role_combo.findData(role)
120+
self.role_combo.setCurrentIndex(idx if idx >= 0 else 0)
121+
122+
self.selector_edit.setText(str(getattr(trigger, "selector", "FrameStart") or "FrameStart"))
123+
self.source_edit.setText(str(getattr(trigger, "source", "Line0") or "Line0"))
124+
125+
activation = str(getattr(trigger, "activation", "RisingEdge") or "RisingEdge")
126+
idx = self.activation_combo.findData(activation)
127+
self.activation_combo.setCurrentIndex(idx if idx >= 0 else 0)
128+
129+
self.output_line_edit.setText(str(getattr(trigger, "output_line", "Line2") or "Line2"))
130+
self.output_source_edit.setText(str(getattr(trigger, "output_source", "ExposureActive") or "ExposureActive"))
131+
132+
timeout = getattr(trigger, "timeout", None)
133+
self.timeout_spin.setValue(float(timeout) if timeout else 0.0)
134+
135+
self.strict_checkbox.setChecked(bool(getattr(trigger, "strict", False)))
136+
137+
def _sync_role_ui(self) -> None:
138+
role = str(self.role_combo.currentData() or "off")
139+
140+
input_enabled = role in {"external", "follower"}
141+
output_enabled = role == "master"
142+
143+
self.selector_edit.setEnabled(input_enabled)
144+
self.source_edit.setEnabled(input_enabled)
145+
self.activation_combo.setEnabled(input_enabled)
146+
147+
self.output_line_edit.setEnabled(output_enabled)
148+
self.output_source_edit.setEnabled(output_enabled)
149+
150+
# Timeout is mostly useful for external/follower, but harmless for any role.
151+
self.timeout_spin.setEnabled(role in {"external", "follower"})
152+
153+
def _accept(self) -> None:
154+
role = str(self.role_combo.currentData() or "off")
155+
156+
payload = {
157+
"role": role,
158+
"selector": self.selector_edit.text().strip() or "FrameStart",
159+
"source": self.source_edit.text().strip() or "Line0",
160+
"activation": str(self.activation_combo.currentData() or "RisingEdge"),
161+
"output_line": self.output_line_edit.text().strip() or "Line2",
162+
"output_source": self.output_source_edit.text().strip() or "ExposureActive",
163+
"strict": bool(self.strict_checkbox.isChecked()),
164+
}
165+
166+
timeout = float(self.timeout_spin.value())
167+
if timeout > 0:
168+
payload["timeout"] = timeout
169+
170+
trigger = CameraTriggerSettings.from_any(payload)
171+
172+
ns = _backend_namespace(self._cam)
173+
ns["trigger"] = trigger.model_dump(exclude_none=True)
174+
175+
self.accept()

dlclivegui/gui/camera_config/ui_blocks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ def build_settings_group(dlg: CameraConfigDialog) -> QGroupBox:
354354

355355
dlg.settings_form.addRow("Crop:", crop_widget)
356356

357+
# --- Trigger settings button ---
358+
dlg.trigger_settings_btn = QPushButton("Trigger Settings…")
359+
dlg.trigger_settings_btn.setIcon(dlg.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogDetailedView))
360+
dlg.trigger_settings_btn.setEnabled(False)
361+
dlg.trigger_settings_btn.setToolTip("Configure hardware trigger / GPIO sync settings for this camera.")
362+
dlg.settings_form.addRow("Sync:", dlg.trigger_settings_btn)
363+
357364
# Apply/Reset buttons row
358365
dlg.apply_settings_btn = QPushButton("Apply Settings")
359366
dlg.apply_settings_btn.setIcon(dlg.style().standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton))

0 commit comments

Comments
 (0)