Skip to content

Commit 126c1c3

Browse files
committed
Add GenTL telemetry and frame-rate debugging
Add helpers and telemetry to better read and debug GenTL/GenICam cameras. - New debug helper _debug_frame_rate_nodes to log many common frame-rate/exposure/throughput nodes. - Added robust node accessors: _node_value, _node_float, _node_str to safely read various GenICam node types and try multiple node names. - Enhance frame-rate configuration to log before/after values when enabling rate control and when setting AcquisitionFrameRate/AcquisitionFrameRateAbs; record any accepted frame-rate as _actual_fps. - After starting acquisition, attempt to read telemetry and run FPS debug logging; warn (but continue) if telemetry read fails. - Improve _read_telemetry to prefer resulting frame-rate nodes over requested ones, and to populate actual_fps, actual_exposure, actual_gain and other useful properties (pixel format, throughput, resolution, etc.) into the settings namespace for GUI/debugging. These changes make frame-rate/exposure behavior more observable and more tolerant across different camera implementations.
1 parent 0d13a4c commit 126c1c3

1 file changed

Lines changed: 155 additions & 13 deletions

File tree

dlclivegui/cameras/backends/gentl_backend.py

Lines changed: 155 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,15 @@ def open(self) -> None:
558558

559559
self._acquirer.start()
560560

561+
try:
562+
self._read_telemetry(node_map)
563+
self._debug_frame_rate_nodes(node_map, context="after starting acquisition")
564+
except Exception:
565+
LOG.warning(
566+
"Failed to read telemetry after starting acquisition; some 'actual' values may be missing.",
567+
exc_info=True,
568+
)
569+
561570
LOG.debug(
562571
"Opened GenTL camera index=%s serial=%s label=%s",
563572
selected_index,
@@ -1126,6 +1135,48 @@ def _node_symbolics(node) -> list[str]:
11261135
except Exception:
11271136
return []
11281137

1138+
@staticmethod
1139+
def _node_value(node_map, name: str, default=None):
1140+
"""Best-effort read of a GenICam node value."""
1141+
try:
1142+
node = getattr(node_map, name)
1143+
except Exception:
1144+
return default
1145+
1146+
try:
1147+
return node.value
1148+
except Exception:
1149+
return default
1150+
1151+
@classmethod
1152+
def _node_float(cls, node_map, *names: str) -> float | None:
1153+
"""Return the first positive float value from a list of GenICam node names."""
1154+
for name in names:
1155+
value = cls._node_value(node_map, name, None)
1156+
try:
1157+
fvalue = float(value)
1158+
except Exception:
1159+
continue
1160+
1161+
if fvalue > 0:
1162+
return fvalue
1163+
1164+
return None
1165+
1166+
@classmethod
1167+
def _node_str(cls, node_map, *names: str) -> str | None:
1168+
"""Return the first non-empty string value from a list of GenICam node names."""
1169+
for name in names:
1170+
value = cls._node_value(node_map, name, None)
1171+
if value is None:
1172+
continue
1173+
1174+
text = str(value).strip()
1175+
if text:
1176+
return text
1177+
1178+
return None
1179+
11291180
def _set_enum_node(self, node_map, name: str, value: str, *, strict: bool = False) -> bool:
11301181
node = self._node(node_map, name)
11311182
if node is None:
@@ -1605,21 +1656,48 @@ def _configure_frame_rate(self, node_map) -> None:
16051656
return
16061657

16071658
target = float(self.settings.fps)
1659+
LOG.info("Configuring GenTL frame rate: requested %.3f FPS", target)
1660+
16081661
for attr in ("AcquisitionFrameRateEnable", "AcquisitionFrameRateControlEnable"):
16091662
try:
1610-
getattr(node_map, attr).value = True
1663+
node = getattr(node_map, attr)
1664+
before = getattr(node, "value", None)
1665+
node.value = True
1666+
after = getattr(node, "value", None)
1667+
LOG.info("Enabled GenTL %s: before=%r after=%r", attr, before, after)
16111668
break
16121669
except Exception:
16131670
pass
16141671

1615-
for attr in ("AcquisitionFrameRate", "ResultingFrameRate", "AcquisitionFrameRateAbs"):
1672+
for attr in ("AcquisitionFrameRate", "AcquisitionFrameRateAbs"):
16161673
try:
1617-
getattr(node_map, attr).value = target
1674+
node = getattr(node_map, attr)
1675+
before = getattr(node, "value", None)
1676+
node.value = target
1677+
after = getattr(node, "value", None)
1678+
1679+
LOG.info(
1680+
"Set GenTL %s: before=%r requested=%.3f after=%r",
1681+
attr,
1682+
before,
1683+
target,
1684+
after,
1685+
)
1686+
1687+
try:
1688+
accepted = float(after)
1689+
if accepted > 0:
1690+
self._actual_fps = accepted
1691+
except Exception:
1692+
pass
1693+
16181694
return
1695+
16191696
except AttributeError:
16201697
continue
16211698
except Exception as e:
16221699
LOG.warning("Failed to set frame rate via %s: %s", attr, e)
1700+
16231701
LOG.warning("Could not set frame rate to %s FPS", target)
16241702

16251703
def _read_telemetry(self, node_map) -> None:
@@ -1629,20 +1707,84 @@ def _read_telemetry(self, node_map) -> None:
16291707
except Exception:
16301708
pass
16311709

1632-
try:
1633-
self._actual_fps = float(node_map.ResultingFrameRate.value)
1634-
except Exception:
1635-
self._actual_fps = None
1710+
# Prefer true/resulting frame-rate readback nodes.
1711+
resulting_fps = self._node_float(
1712+
node_map,
1713+
"AcquisitionResultingFrameRate",
1714+
"ResultingFrameRate",
1715+
"AcquisitionFrameRateResulting",
1716+
"DeviceFrameRate",
1717+
)
16361718

1637-
try:
1638-
self._actual_exposure = float(node_map.ExposureTime.value)
1639-
except Exception:
1640-
self._actual_exposure = None
1719+
# Fallback to requested/accepted frame-rate nodes only if no resulting node exists.
1720+
requested_fps = self._node_float(
1721+
node_map,
1722+
"AcquisitionFrameRate",
1723+
"AcquisitionFrameRateAbs",
1724+
)
1725+
1726+
if resulting_fps is not None:
1727+
self._actual_fps = resulting_fps
1728+
elif requested_fps is not None:
1729+
self._actual_fps = requested_fps
16411730

1731+
exposure = self._node_float(
1732+
node_map,
1733+
"ExposureTime",
1734+
"ExposureTimeAbs",
1735+
"Exposure",
1736+
)
1737+
if exposure is not None:
1738+
self._actual_exposure = exposure
1739+
1740+
gain = self._node_float(
1741+
node_map,
1742+
"Gain",
1743+
"GainRaw",
1744+
)
1745+
if gain is not None:
1746+
self._actual_gain = gain
1747+
1748+
# Persist useful telemetry into properties["gentl"] for GUI/debugging.
16421749
try:
1643-
self._actual_gain = float(node_map.Gain.value)
1750+
ns = self._ensure_settings_ns()
1751+
1752+
if self._actual_width and self._actual_height:
1753+
ns["actual_resolution"] = [int(self._actual_width), int(self._actual_height)]
1754+
1755+
if self._actual_fps is not None:
1756+
ns["actual_fps"] = float(self._actual_fps)
1757+
1758+
if resulting_fps is not None:
1759+
ns["actual_resulting_frame_rate"] = float(resulting_fps)
1760+
1761+
if requested_fps is not None:
1762+
ns["actual_acquisition_frame_rate"] = float(requested_fps)
1763+
1764+
if self._actual_exposure is not None:
1765+
ns["actual_exposure"] = float(self._actual_exposure)
1766+
1767+
if self._actual_gain is not None:
1768+
ns["actual_gain"] = float(self._actual_gain)
1769+
1770+
exposure_auto = self._node_str(node_map, "ExposureAuto")
1771+
if exposure_auto is not None:
1772+
ns["actual_exposure_auto"] = exposure_auto
1773+
1774+
throughput = self._node_float(node_map, "DeviceLinkThroughputLimit")
1775+
if throughput is not None:
1776+
ns["actual_device_link_throughput_limit"] = float(throughput)
1777+
1778+
throughput_mode = self._node_str(node_map, "DeviceLinkThroughputLimitMode")
1779+
if throughput_mode is not None:
1780+
ns["actual_device_link_throughput_limit_mode"] = throughput_mode
1781+
1782+
pixel_format = self._node_str(node_map, "PixelFormat")
1783+
if pixel_format is not None:
1784+
ns["actual_pixel_format"] = pixel_format
1785+
16441786
except Exception:
1645-
self._actual_gain = None
1787+
pass
16461788

16471789
# ------------------------------------------------------------------
16481790
# Frame conversion / local helpers

0 commit comments

Comments
 (0)