@@ -185,6 +185,89 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
185185 "hardware_trigger" : SupportLevel .BEST_EFFORT ,
186186 }
187187
188+ def _debug_trigger_nodes (self , node_map , * , context : str = "" ) -> None :
189+ names = (
190+ "TriggerMode" ,
191+ "TriggerSelector" ,
192+ "TriggerSource" ,
193+ "TriggerActivation" ,
194+ "AcquisitionMode" ,
195+ # Generic line nodes, if available.
196+ "LineSelector" ,
197+ "LineMode" ,
198+ "LineSource" ,
199+ # TIS 37U / DMK 37BUX287 strobe/output nodes.
200+ "GPIn" ,
201+ "GPOut" ,
202+ "StrobeEnable" ,
203+ "StrobePolarity" ,
204+ "StrobeOperation" ,
205+ "StrobeDuration" ,
206+ "StrobeDelay" ,
207+ )
208+
209+ label = f"GenTL trigger debug { context } " .strip ()
210+
211+ for name in names :
212+ node = self ._node (node_map , name )
213+ if node is None :
214+ continue
215+
216+ value = self ._node_value (node_map , name , None )
217+
218+ extras = []
219+
220+ symbolics = self ._node_symbolics (node )
221+ if symbolics :
222+ extras .append (f"symbolics={ symbolics } " )
223+
224+ for attr in ("access_mode" , "is_writable" , "is_readable" ):
225+ try :
226+ extras .append (f"{ attr } ={ getattr (node , attr )} " )
227+ except Exception :
228+ pass
229+
230+ LOG .debug ("%s: %s=%r %s" , label , name , value , " " .join (extras ))
231+
232+ def _debug_frame_rate_nodes (self , node_map , * , context : str = "" ) -> None :
233+ names = (
234+ "AcquisitionFrameRateEnable" ,
235+ "AcquisitionFrameRateControlEnable" ,
236+ "AcquisitionFrameRate" ,
237+ "AcquisitionFrameRateAbs" ,
238+ "AcquisitionResultingFrameRate" ,
239+ "ResultingFrameRate" ,
240+ "AcquisitionFrameRateResulting" ,
241+ "DeviceFrameRate" ,
242+ "ExposureAuto" ,
243+ "ExposureTime" ,
244+ "ExposureTimeAbs" ,
245+ "DeviceLinkThroughputLimit" ,
246+ "DeviceLinkThroughputLimitMode" ,
247+ "PayloadSize" ,
248+ "Width" ,
249+ "Height" ,
250+ "PixelFormat" ,
251+ )
252+
253+ label = f"GenTL FPS debug { context } " .strip ()
254+
255+ for name in names :
256+ node = self ._node (node_map , name )
257+ if node is None :
258+ continue
259+
260+ value = self ._node_value (node_map , name , None )
261+
262+ extras = []
263+ for attr in ("min" , "max" , "inc" ):
264+ try :
265+ extras .append (f"{ attr } ={ getattr (node , attr )} " )
266+ except Exception :
267+ pass
268+
269+ LOG .debug ("%s: %s=%r %s" , label , name , value , " " .join (extras ))
270+
188271 # ------------------------------------------------------------------
189272 # Discovery
190273 # ------------------------------------------------------------------
@@ -462,6 +545,7 @@ def open(self) -> None:
462545
463546 self ._configure_trigger (node_map )
464547 self ._apply_effective_fetch_timeout ()
548+ self ._debug_trigger_nodes (node_map , context = "after configuration before acquisition" )
465549
466550 actual_trigger = self ._trigger_to_dict (self ._trigger )
467551 actual_trigger ["fetch_timeout" ] = self ._timeout
@@ -1015,6 +1099,32 @@ def _node(node_map, name: str):
10151099 except Exception :
10161100 return None
10171101
1102+ @staticmethod
1103+ def _node_value (node_map , name : str , default = None ):
1104+ """Best-effort read of a GenICam node value.
1105+
1106+ Debug helpers must not make open() fail just because a value cannot be read.
1107+ Harvesters-style fake/test nodes usually expose `.value`; some SDK-style
1108+ nodes may expose `GetValue()`.
1109+ """
1110+ node = GenTLCameraBackend ._node (node_map , name )
1111+ if node is None :
1112+ return default
1113+
1114+ try :
1115+ return node .value
1116+ except Exception :
1117+ pass
1118+
1119+ try :
1120+ getter = getattr (node , "GetValue" , None )
1121+ if getter is not None :
1122+ return getter ()
1123+ except Exception :
1124+ pass
1125+
1126+ return default
1127+
10181128 @staticmethod
10191129 def _node_symbolics (node ) -> list [str ]:
10201130 try :
@@ -1214,16 +1324,35 @@ def _configure_trigger_off(self, node_map, *, strict: bool = False) -> None:
12141324 def _configure_trigger_input (self , node_map , cfg , * , strict : bool = False ) -> None :
12151325 role = str (self ._trigger_attr (cfg , "role" , "external" ) or "external" ).strip ().lower ()
12161326 selector = str (self ._trigger_attr (cfg , "selector" , "FrameStart" ) or "FrameStart" )
1217- source = str (self ._trigger_attr (cfg , "source" , "Line0" ) or "Line0" )
12181327 activation = str (self ._trigger_attr (cfg , "activation" , "RisingEdge" ) or "RisingEdge" )
1328+ source = str (self ._trigger_attr (cfg , "source" , "auto" ) or "auto" ).strip ()
12191329
12201330 # Disable trigger while changing trigger-related nodes.
12211331 self ._set_enum_node (node_map , "TriggerMode" , "Off" , strict = False )
12221332
12231333 selector_ok = self ._set_enum_node (node_map , "TriggerSelector" , selector , strict = strict )
1224- source , source_resolved = self ._resolve_trigger_source (node_map , source , strict = strict )
1225- source_ok = source_resolved and self ._set_enum_node (node_map , "TriggerSource" , source , strict = strict )
1226- activation_ok = self ._set_enum_node (node_map , "TriggerActivation" , activation , strict = strict )
1334+
1335+ resolved_source , source_supported = self ._resolve_trigger_source (
1336+ node_map ,
1337+ source ,
1338+ strict = strict ,
1339+ )
1340+
1341+ source_ok = False
1342+ if source_supported :
1343+ source_ok = self ._set_enum_node (
1344+ node_map ,
1345+ "TriggerSource" ,
1346+ resolved_source ,
1347+ strict = strict ,
1348+ )
1349+
1350+ activation_ok = self ._set_enum_node (
1351+ node_map ,
1352+ "TriggerActivation" ,
1353+ activation ,
1354+ strict = False ,
1355+ )
12271356
12281357 # TriggerSelector and TriggerSource are required routing nodes.
12291358 # If either failed in non-strict mode, do not arm TriggerMode=On.
@@ -1232,12 +1361,13 @@ def _configure_trigger_input(self, node_map, cfg, *, strict: bool = False) -> No
12321361 LOG .warning (
12331362 "Could not apply GenTL trigger input routing "
12341363 "(selector_ok=%s, source_ok=%s); disabling trigger. "
1235- "requested role=%s selector=%s source=%s activation=%s" ,
1364+ "requested role=%s selector=%s source=%s resolved_source=%s activation=%s" ,
12361365 selector_ok ,
12371366 source_ok ,
12381367 role ,
12391368 selector ,
12401369 source ,
1370+ resolved_source ,
12411371 activation ,
12421372 )
12431373 self ._configure_trigger_off (node_map , strict = False )
@@ -1259,55 +1389,154 @@ def _configure_trigger_input(self, node_map, cfg, *, strict: bool = False) -> No
12591389 return
12601390
12611391 LOG .info (
1262- "GenTL trigger input configured: role=%s selector=%s source=%s activation=%s activation_ok=%s" ,
1392+ "GenTL trigger input configured: role=%s selector=%s source_requested=%s "
1393+ "source=%s activation=%s selector_ok=%s source_ok=%s activation_ok=%s" ,
12631394 role ,
12641395 selector ,
12651396 source ,
1397+ resolved_source ,
12661398 activation ,
1399+ selector_ok ,
1400+ source_ok ,
12671401 activation_ok ,
12681402 )
12691403
12701404 def _configure_trigger_master (self , node_map , cfg , * , strict : bool = False ) -> None :
1405+ """Configure this camera as a free-running master that emits STROBE_OUT pulses.
1406+
1407+ For DMK 37BUX287 / TIS 37U series, the physical output is controlled by
1408+ StrobeEnable/StrobePolarity/StrobeOperation rather than SFNC LineSelector/
1409+ LineMode/LineSource nodes.
1410+ """
12711411 output_line = str (self ._trigger_attr (cfg , "output_line" , "Line2" ) or "Line2" )
12721412 output_source = str (self ._trigger_attr (cfg , "output_source" , "ExposureActive" ) or "ExposureActive" )
12731413
1274- # Master camera runs freerun and exposes an output signal.
1414+ # Optional extra fields if present in trigger dict/model.
1415+ strobe_polarity = str (self ._trigger_attr (cfg , "strobe_polarity" , "ActiveHigh" ) or "ActiveHigh" )
1416+ strobe_operation = str (self ._trigger_attr (cfg , "strobe_operation" , "Exposure" ) or "Exposure" )
1417+ strobe_duration = self ._trigger_attr (cfg , "strobe_duration" , None )
1418+ strobe_delay = self ._trigger_attr (cfg , "strobe_delay" , None )
1419+
1420+ # Master camera should be free-running.
12751421 self ._configure_trigger_off (node_map , strict = False )
12761422
1277- line_selected = self ._set_enum_node (
1278- node_map ,
1279- "LineSelector" ,
1280- output_line ,
1281- strict = strict ,
1282- )
1423+ # ------------------------------------------------------------------
1424+ # Preferred path for The Imaging Source 37U / DMK 37BUX287:
1425+ # StrobeEnable, StrobePolarity, StrobeOperation, StrobeDuration, StrobeDelay
1426+ # ------------------------------------------------------------------
1427+ strobe_enable_node = self ._node (node_map , "StrobeEnable" )
1428+
1429+ if strobe_enable_node is not None :
1430+ # Disable first while changing parameters.
1431+ self ._set_enum_node (node_map , "StrobeEnable" , "Off" , strict = False )
1432+
1433+ polarity_ok = self ._set_enum_node (
1434+ node_map ,
1435+ "StrobePolarity" ,
1436+ strobe_polarity ,
1437+ strict = False ,
1438+ )
12831439
1284- # In non-strict mode, do not continue configuring output behavior if the
1285- # requested line could not be selected. Otherwise we may accidentally drive
1286- # whichever GPIO line the camera had selected previously/defaulted to.
1287- if not line_selected :
1288- LOG .warning (
1289- "Could not select GenTL output line '%s'; skipping trigger output configuration." ,
1290- output_line ,
1440+ operation_ok = self ._set_enum_node (
1441+ node_map ,
1442+ "StrobeOperation" ,
1443+ strobe_operation ,
1444+ strict = False ,
12911445 )
1292- return
12931446
1294- mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1295- source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1447+ if strobe_duration is not None :
1448+ try :
1449+ node = self ._node (node_map , "StrobeDuration" )
1450+ if node is not None :
1451+ node .value = int (strobe_duration )
1452+ LOG .info ("Configured GenTL StrobeDuration=%s" , int (strobe_duration ))
1453+ except Exception as exc :
1454+ if strict :
1455+ raise RuntimeError (f"Failed to set StrobeDuration={ strobe_duration } : { exc } " ) from exc
1456+ LOG .warning ("Failed to set StrobeDuration=%s: %s" , strobe_duration , exc )
1457+
1458+ if strobe_delay is not None :
1459+ try :
1460+ node = self ._node (node_map , "StrobeDelay" )
1461+ if node is not None :
1462+ node .value = int (strobe_delay )
1463+ LOG .info ("Configured GenTL StrobeDelay=%s" , int (strobe_delay ))
1464+ except Exception as exc :
1465+ if strict :
1466+ raise RuntimeError (f"Failed to set StrobeDelay={ strobe_delay } : { exc } " ) from exc
1467+ LOG .warning ("Failed to set StrobeDelay=%s: %s" , strobe_delay , exc )
1468+
1469+ enable_ok = self ._set_enum_node (
1470+ node_map ,
1471+ "StrobeEnable" ,
1472+ "On" ,
1473+ strict = strict ,
1474+ )
1475+
1476+ if enable_ok :
1477+ LOG .info (
1478+ "GenTL trigger master configured via Strobe*: "
1479+ "StrobeEnable=On StrobePolarity=%s polarity_ok=%s "
1480+ "StrobeOperation=%s operation_ok=%s" ,
1481+ strobe_polarity ,
1482+ polarity_ok ,
1483+ strobe_operation ,
1484+ operation_ok ,
1485+ )
1486+ return
1487+
1488+ if strict :
1489+ raise RuntimeError ("Could not enable GenTL StrobeEnable=On" )
12961490
1297- if not (mode_ok and source_ok ):
12981491 LOG .warning (
1299- "GenTL trigger master output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1300- mode_ok ,
1301- source_ok ,
1492+ "StrobeEnable node exists but could not be enabled; falling back to generic Line* output configuration."
13021493 )
1303- return
13041494
1305- LOG .info (
1306- "GenTL trigger master configured: output_line=%s output_source=%s" ,
1307- output_line ,
1308- output_source ,
1495+ # ------------------------------------------------------------------
1496+ # Generic SFNC fallback for cameras that expose LineSelector/LineMode/LineSource.
1497+ # ------------------------------------------------------------------
1498+ line_selector = self ._node (node_map , "LineSelector" )
1499+ if line_selector is not None :
1500+ line_selected = self ._set_enum_node (
1501+ node_map ,
1502+ "LineSelector" ,
1503+ output_line ,
1504+ strict = strict ,
1505+ )
1506+
1507+ if not line_selected :
1508+ LOG .warning (
1509+ "Could not select GenTL output line '%s'; skipping Line* output configuration." ,
1510+ output_line ,
1511+ )
1512+ else :
1513+ mode_ok = self ._set_enum_node (node_map , "LineMode" , "Output" , strict = strict )
1514+ source_ok = self ._set_enum_node (node_map , "LineSource" , output_source , strict = strict )
1515+
1516+ if mode_ok and source_ok :
1517+ LOG .info (
1518+ "GenTL trigger master configured via Line*: output_line=%s output_source=%s" ,
1519+ output_line ,
1520+ output_source ,
1521+ )
1522+ return
1523+
1524+ LOG .warning (
1525+ "GenTL Line* trigger output configuration incomplete (LineMode ok=%s, LineSource ok=%s)." ,
1526+ mode_ok ,
1527+ source_ok ,
1528+ )
1529+
1530+ msg = (
1531+ "Could not configure GenTL trigger master output. "
1532+ "No supported Strobe* or Line* output path was successfully configured."
13091533 )
13101534
1535+ if strict :
1536+ raise RuntimeError (msg )
1537+
1538+ LOG .warning (msg )
1539+
13111540 def _restore_trigger_idle (self , node_map ) -> None :
13121541 """Best-effort restore to a safe non-triggering state after acquisition stops.
13131542
0 commit comments