Skip to content

Commit e4f22df

Browse files
committed
Add mono-preserving output mode to GenTL
Adds a `preserve_mono` option to the GenTL camera backend so Mono pixel formats can remain 2D Mono8 output instead of always converting to BGR. The backend now reports preserve-mono capability, exposes recommended/actual output format based on camera format, and persists detected pixel/output format metadata in settings. It also logs first-frame format details to make runtime format behavior easier to inspect.
1 parent 035a974 commit e4f22df

1 file changed

Lines changed: 52 additions & 2 deletions

File tree

dlclivegui/cameras/backends/gentl_backend.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def __init__(self, settings):
9090
ns = {}
9191

9292
self._fast_start: bool = bool(ns.get("fast_start", False))
93+
self._preserve_mono: bool = bool(getattr(settings, "preserve_mono", False) or ns.get("preserve_mono", False))
94+
self._logged_first_frame: bool = False
9395

9496
raw_device_id = ns.get("device_id") or props.get("device_id")
9597
legacy_serial = ns.get("serial_number") or ns.get("serial") or props.get("serial_number") or props.get("serial")
@@ -184,10 +186,20 @@ def actual_pixel_format(self) -> str | None:
184186
"""Camera/native pixel format selected on the GenICam PixelFormat node."""
185187
return self._camera_pixel_format or (self._pixel_format if self._pixel_format != "auto" else None)
186188

189+
@property
190+
def recommended_preserve_mono(self) -> bool | None:
191+
if not self._camera_pixel_format:
192+
return None
193+
return self._is_camera_mono()
194+
187195
@property
188196
def actual_output_format(self) -> str | None:
189-
"""Current GenTL backend emits OpenCV-native BGR uint8 frames."""
190-
return self._actual_output_format or "BGR8"
197+
"""Backend output frame format emitted to the app, e.g. 'Mono8' or 'BGR8'."""
198+
if self._actual_output_format:
199+
return self._actual_output_format
200+
if not self._camera_pixel_format:
201+
return None
202+
return "Mono8" if self._should_output_mono() else "BGR8"
191203

192204
@classmethod
193205
def is_available(cls) -> bool:
@@ -203,6 +215,7 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
203215
"device_discovery": SupportLevel.SUPPORTED,
204216
"stable_identity": SupportLevel.SUPPORTED,
205217
"hardware_trigger": SupportLevel.BEST_EFFORT,
218+
"preserve_mono": SupportLevel.SUPPORTED,
206219
}
207220

208221
def _debug_trigger_nodes(self, node_map, *, context: str = "") -> None:
@@ -600,6 +613,13 @@ def waits_for_hardware_trigger(self) -> bool:
600613
role = str(self._trigger_attr(getattr(self, "_trigger", None), "role", "off") or "off").lower()
601614
return role in {"external", "follower"}
602615

616+
def _is_camera_mono(self) -> bool:
617+
fmt = str(self._camera_pixel_format or self._pixel_format or "").strip()
618+
return fmt.startswith("Mono")
619+
620+
def _should_output_mono(self) -> bool:
621+
return bool(self._preserve_mono and self._is_camera_mono())
622+
603623
@staticmethod
604624
def _output_format_for_frame(frame: np.ndarray) -> str:
605625
if frame.ndim == 2:
@@ -654,6 +674,25 @@ def read(self) -> CapturedFrame:
654674
except Exception:
655675
pass
656676
self._actual_output_format = self._output_format_for_frame(frame)
677+
try:
678+
ns = self._ensure_settings_ns()
679+
ns["actual_output_format"] = self._actual_output_format
680+
ns["preserve_mono"] = self._preserve_mono
681+
except Exception:
682+
pass
683+
if not self._logged_first_frame:
684+
self._logged_first_frame = True
685+
LOG.info(
686+
"[GenTL] first frame device_id=%s shape=%s dtype=%s nbytes=%.2f MB "
687+
"camera_pixel_format=%s output_format=%s preserve_mono=%s",
688+
self._device_id,
689+
frame.shape,
690+
frame.dtype,
691+
frame.nbytes / (1024 * 1024),
692+
self._camera_pixel_format,
693+
self.actual_output_format,
694+
self._preserve_mono,
695+
)
657696

658697
return CapturedFrame(
659698
frame=frame,
@@ -1346,6 +1385,14 @@ def _configure_pixel_format(self, node_map) -> None:
13461385
pixel_format_node.value = selected
13471386
self._pixel_format = str(pixel_format_node.value)
13481387
self._camera_pixel_format = self._pixel_format
1388+
try:
1389+
ns = self._ensure_settings_ns()
1390+
ns["actual_pixel_format"] = self._camera_pixel_format
1391+
ns["detected_pixel_format"] = self._camera_pixel_format
1392+
ns["actual_output_format"] = self.actual_output_format
1393+
ns["preserve_mono"] = self._preserve_mono
1394+
except Exception:
1395+
pass
13491396

13501397
LOG.debug("GenTL pixel format selected: %s", self._pixel_format)
13511398

@@ -1851,6 +1898,9 @@ def _convert_frame(self, frame: np.ndarray) -> np.ndarray:
18511898
frame = cv2.cvtColor(frame, cv2.COLOR_BayerGR2BGR)
18521899
elif fmt == "BayerBG8":
18531900
frame = cv2.cvtColor(frame, cv2.COLOR_BayerBG2BGR)
1901+
elif self._should_output_mono():
1902+
# Keep Mono* cameras as 2D uint8 frames when explicitly requested.
1903+
pass
18541904
else:
18551905
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
18561906

0 commit comments

Comments
 (0)