Skip to content

Commit fbe55e5

Browse files
committed
Add trigger config dialog GUI test coverage
Introduce a new `test_trigger_config.py` suite for camera trigger configuration. The tests cover backend UI profiles, backend namespace normalization, dialog field visibility/enabling by role and backend, loading existing trigger settings, and accept-path payload serialization for off/external/master roles across GenTL and Basler behavior.
1 parent 6b64dff commit fbe55e5

1 file changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
# tests/gui/camera_config/test_trigger_config_dialog.py
2+
from __future__ import annotations
3+
4+
import pytest
5+
6+
from dlclivegui.config import CameraSettings
7+
from dlclivegui.gui.camera_config.trigger_config_dialog import (
8+
TriggerConfigDialog,
9+
_backend_namespace,
10+
trigger_ui_profile_for_backend,
11+
)
12+
13+
14+
class TestTriggerUiProfiles:
15+
@pytest.mark.parametrize(
16+
("backend", "supports_input", "supports_master", "show_strobe", "show_line"),
17+
[
18+
("gentl", True, True, True, True),
19+
("basler", True, True, False, True),
20+
("opencv", False, False, False, False),
21+
("fake", False, False, False, False),
22+
],
23+
)
24+
def test_profile_capabilities_by_backend(
25+
self,
26+
backend: str,
27+
supports_input: bool,
28+
supports_master: bool,
29+
show_strobe: bool,
30+
show_line: bool,
31+
):
32+
profile = trigger_ui_profile_for_backend(backend)
33+
34+
assert profile.supports_input is supports_input
35+
assert profile.supports_master is supports_master
36+
assert profile.show_strobe_fields is show_strobe
37+
assert profile.show_line_output_fields is show_line
38+
39+
def test_profile_backend_is_case_insensitive(self):
40+
upper = trigger_ui_profile_for_backend("GeNtL")
41+
lower = trigger_ui_profile_for_backend("gentl")
42+
43+
assert upper == lower
44+
45+
46+
class TestBackendNamespace:
47+
def test_backend_namespace_creates_backend_dict(self):
48+
cam = CameraSettings(backend="gentl", index=0, properties={})
49+
50+
ns = _backend_namespace(cam)
51+
52+
assert ns == {}
53+
assert cam.properties == {"gentl": {}}
54+
55+
def test_backend_namespace_replaces_non_dict_properties(self):
56+
cam = CameraSettings(backend="gentl", index=0, properties={})
57+
cam.properties = None
58+
59+
ns = _backend_namespace(cam)
60+
ns["trigger"] = {"role": "external"}
61+
62+
assert cam.properties == {"gentl": {"trigger": {"role": "external"}}}
63+
64+
def test_backend_namespace_replaces_non_dict_namespace(self):
65+
cam = CameraSettings(backend="gentl", index=0, properties={"gentl": "bad"})
66+
67+
ns = _backend_namespace(cam)
68+
69+
assert ns == {}
70+
assert cam.properties == {"gentl": {}}
71+
72+
73+
class TestTriggerConfigDialogPresentation:
74+
@pytest.mark.gui
75+
def test_unknown_backend_exposes_only_off_role_and_disables_trigger_fields(self, qtbot):
76+
cam = CameraSettings(backend="opencv", index=0, properties={})
77+
dlg = TriggerConfigDialog(cam)
78+
qtbot.addWidget(dlg)
79+
80+
roles = [dlg.role_combo.itemData(i) for i in range(dlg.role_combo.count())]
81+
82+
assert roles == ["off"]
83+
assert not dlg.selector_edit.isVisible()
84+
assert not dlg.source_combo.isVisible()
85+
assert not dlg.activation_combo.isVisible()
86+
assert not dlg.output_line_edit.isVisible()
87+
assert not dlg.strobe_polarity_combo.isVisible()
88+
89+
@pytest.mark.gui
90+
def test_gentl_profile_shows_input_master_line_and_strobe_fields(self, qtbot):
91+
cam = CameraSettings(backend="gentl", index=0, properties={})
92+
dlg = TriggerConfigDialog(cam)
93+
qtbot.addWidget(dlg)
94+
95+
roles = [dlg.role_combo.itemData(i) for i in range(dlg.role_combo.count())]
96+
97+
assert roles == ["off", "external", "follower", "master"]
98+
assert not dlg.selector_edit.isHidden()
99+
assert not dlg.source_combo.isHidden()
100+
assert not dlg.activation_combo.isHidden()
101+
assert not dlg.output_line_edit.isHidden()
102+
assert not dlg.output_source_edit.isHidden()
103+
assert not dlg.strobe_polarity_combo.isHidden()
104+
assert not dlg.strobe_operation_combo.isHidden()
105+
assert not dlg.strobe_duration_spin.isHidden()
106+
assert not dlg.strobe_delay_spin.isHidden()
107+
108+
@pytest.mark.gui
109+
def test_basler_profile_shows_line_fields_but_hides_strobe_fields(self, qtbot):
110+
cam = CameraSettings(backend="basler", index=0, properties={})
111+
dlg = TriggerConfigDialog(cam)
112+
qtbot.addWidget(dlg)
113+
114+
roles = [dlg.role_combo.itemData(i) for i in range(dlg.role_combo.count())]
115+
116+
assert roles == ["off", "external", "follower", "master"]
117+
assert not dlg.output_line_edit.isHidden()
118+
assert not dlg.output_source_edit.isHidden()
119+
assert dlg.strobe_polarity_combo.isHidden()
120+
assert dlg.strobe_operation_combo.isHidden()
121+
assert dlg.strobe_duration_spin.isHidden()
122+
assert dlg.strobe_delay_spin.isHidden()
123+
124+
@pytest.mark.gui
125+
def test_role_changes_enable_input_and_output_fields(self, qtbot):
126+
cam = CameraSettings(backend="gentl", index=0, properties={})
127+
dlg = TriggerConfigDialog(cam)
128+
qtbot.addWidget(dlg)
129+
130+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("off"))
131+
assert not dlg.selector_edit.isEnabled()
132+
assert not dlg.source_combo.isEnabled()
133+
assert not dlg.output_line_edit.isEnabled()
134+
135+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("external"))
136+
assert dlg.selector_edit.isEnabled()
137+
assert dlg.source_combo.isEnabled()
138+
assert dlg.activation_combo.isEnabled()
139+
assert not dlg.output_line_edit.isEnabled()
140+
141+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("master"))
142+
assert not dlg.selector_edit.isEnabled()
143+
assert not dlg.source_combo.isEnabled()
144+
assert dlg.output_line_edit.isEnabled()
145+
assert dlg.output_source_edit.isEnabled()
146+
assert dlg.strobe_polarity_combo.isEnabled()
147+
assert dlg.strobe_delay_spin.isEnabled()
148+
149+
@pytest.mark.gui
150+
def test_fixed_duration_enables_strobe_duration_only_for_master(self, qtbot):
151+
cam = CameraSettings(backend="gentl", index=0, properties={})
152+
dlg = TriggerConfigDialog(cam)
153+
qtbot.addWidget(dlg)
154+
155+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("master"))
156+
dlg.strobe_operation_combo.setCurrentIndex(dlg.strobe_operation_combo.findData("FixedDuration"))
157+
158+
assert dlg.strobe_duration_spin.isEnabled()
159+
160+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("external"))
161+
162+
assert not dlg.strobe_duration_spin.isEnabled()
163+
164+
165+
class TestTriggerConfigDialogModelRoundtrip:
166+
@pytest.mark.gui
167+
def test_loads_existing_trigger_settings(self, qtbot):
168+
cam = CameraSettings(
169+
backend="gentl",
170+
index=0,
171+
properties={
172+
"gentl": {
173+
"trigger": {
174+
"role": "external",
175+
"selector": "FrameStart",
176+
"source": "Line1",
177+
"activation": "FallingEdge",
178+
"timeout": 0.25,
179+
"strict": True,
180+
}
181+
}
182+
},
183+
)
184+
185+
dlg = TriggerConfigDialog(cam)
186+
qtbot.addWidget(dlg)
187+
188+
assert dlg.role_combo.currentData() == "external"
189+
assert dlg.selector_edit.text() == "FrameStart"
190+
assert dlg.source_combo.currentText() == "Line1"
191+
assert dlg.activation_combo.currentData() == "FallingEdge"
192+
assert dlg.timeout_spin.value() == pytest.approx(0.25)
193+
assert dlg.strict_checkbox.isChecked()
194+
195+
@pytest.mark.gui
196+
def test_accept_external_writes_trigger_payload_to_backend_namespace(self, qtbot):
197+
cam = CameraSettings(backend="gentl", index=0, properties={})
198+
dlg = TriggerConfigDialog(cam)
199+
qtbot.addWidget(dlg)
200+
201+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("external"))
202+
dlg.selector_edit.setText("FrameStart")
203+
dlg.source_combo.setCurrentText("Line1")
204+
dlg.activation_combo.setCurrentIndex(dlg.activation_combo.findData("FallingEdge"))
205+
dlg.timeout_spin.setValue(0.5)
206+
dlg.strict_checkbox.setChecked(True)
207+
208+
with qtbot.waitSignal(dlg.accepted, timeout=1000):
209+
dlg._accept()
210+
211+
trigger = dlg.camera_settings.properties["gentl"]["trigger"]
212+
213+
assert trigger["role"] == "external"
214+
assert trigger["selector"] == "FrameStart"
215+
assert trigger["source"] == "Line1"
216+
assert trigger["activation"] == "FallingEdge"
217+
assert trigger["timeout"] == pytest.approx(0.5)
218+
assert trigger["strict"] is True
219+
220+
@pytest.mark.gui
221+
def test_accept_off_clears_timeout(self, qtbot):
222+
cam = CameraSettings(
223+
backend="gentl",
224+
index=0,
225+
properties={"gentl": {"trigger": {"role": "external", "timeout": 2.0}}},
226+
)
227+
dlg = TriggerConfigDialog(cam)
228+
qtbot.addWidget(dlg)
229+
230+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("off"))
231+
dlg.timeout_spin.setValue(1.25)
232+
233+
with qtbot.waitSignal(dlg.accepted, timeout=1000):
234+
dlg._accept()
235+
236+
trigger = dlg.camera_settings.properties["gentl"]["trigger"]
237+
238+
assert trigger["role"] == "off"
239+
assert trigger.get("timeout") is None
240+
241+
@pytest.mark.gui
242+
def test_accept_master_gentl_includes_strobe_values(self, qtbot):
243+
cam = CameraSettings(backend="gentl", index=0, properties={})
244+
dlg = TriggerConfigDialog(cam)
245+
qtbot.addWidget(dlg)
246+
247+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("master"))
248+
dlg.output_line_edit.setText("Line2")
249+
dlg.output_source_edit.setText("ExposureActive")
250+
dlg.strobe_polarity_combo.setCurrentIndex(dlg.strobe_polarity_combo.findData("ActiveLow"))
251+
dlg.strobe_operation_combo.setCurrentIndex(dlg.strobe_operation_combo.findData("FixedDuration"))
252+
dlg.strobe_duration_spin.setValue(1200)
253+
dlg.strobe_delay_spin.setValue(300)
254+
255+
with qtbot.waitSignal(dlg.accepted, timeout=1000):
256+
dlg._accept()
257+
258+
trigger = dlg.camera_settings.properties["gentl"]["trigger"]
259+
260+
assert trigger["role"] == "master"
261+
assert trigger["output_line"] == "Line2"
262+
assert trigger["output_source"] == "ExposureActive"
263+
assert trigger["strobe_polarity"] == "ActiveLow"
264+
assert trigger["strobe_operation"] == "FixedDuration"
265+
assert trigger["strobe_duration"] == 1200
266+
assert trigger["strobe_delay"] == 300
267+
268+
@pytest.mark.gui
269+
def test_accept_master_basler_does_not_add_strobe_values(self, qtbot):
270+
cam = CameraSettings(backend="basler", index=0, properties={})
271+
dlg = TriggerConfigDialog(cam)
272+
qtbot.addWidget(dlg)
273+
274+
dlg.role_combo.setCurrentIndex(dlg.role_combo.findData("master"))
275+
dlg.strobe_duration_spin.setValue(1200)
276+
dlg.strobe_delay_spin.setValue(300)
277+
278+
with qtbot.waitSignal(dlg.accepted, timeout=1000):
279+
dlg._accept()
280+
281+
trigger = dlg.camera_settings.properties["basler"]["trigger"]
282+
283+
assert trigger["role"] == "master"
284+
assert "strobe_duration" not in trigger
285+
assert "strobe_delay" not in trigger
286+
assert "strobe_polarity" not in trigger
287+
assert "strobe_operation" not in trigger

0 commit comments

Comments
 (0)