Skip to content

Commit ab2598e

Browse files
committed
Improve GenTL pixel format selection and conversion
Add explicit lists of color and mono GenTL pixel formats and default pixel_format to "auto" with normalization. Rewrite _configure_pixel_format to handle missing PixelFormat node, choose a suitable format when "auto" is requested (prefer color formats, then mono, then first available), warn and fallback when a requested format is unavailable, and persist the selected format. Update frame postprocessing to use the normalized pixel format for proper Bayer demosaicing (BayerRG/GB/GR/BG) and correct RGB->BGR conversion while leaving BGR8 native. Minor logging message tweaks and added defensive checks to improve robustness.
1 parent b2e53dc commit ab2598e

1 file changed

Lines changed: 81 additions & 13 deletions

File tree

dlclivegui/cameras/backends/gentl_backend.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ class GenTLCameraBackend(CameraBackend):
5454
r"C:\Program Files\The Imaging Source Europe GmbH\TIS Camera SDK\bin\win64_x64\*.cti",
5555
r"C:\Program Files (x86)\The Imaging Source Europe GmbH\TIS Grabber\bin\win64_x64\*.cti",
5656
)
57+
_COLOR_PIXEL_FORMATS: ClassVar[tuple[str, ...]] = (
58+
"BGR8",
59+
"RGB8",
60+
"BayerRG8",
61+
"BayerGB8",
62+
"BayerGR8",
63+
"BayerBG8",
64+
)
65+
_MONO_PIXEL_FORMATS: ClassVar[tuple[str, ...]] = (
66+
"Mono8",
67+
"Mono10",
68+
"Mono12",
69+
"Mono16",
70+
)
5771

5872
# Source marker stored in properties["gentl"]["cti_files_source"].
5973
# auto: persisted by auto-discovery; may be stale and can fall back.
@@ -77,7 +91,8 @@ def __init__(self, settings):
7791
self._device_id: str | None = str(raw_device_id).strip() if raw_device_id else None
7892
self._serial_number: str | None = self._serial_from_identity(self._device_id, legacy_serial)
7993

80-
self._pixel_format: str = ns.get("pixel_format") or props.get("pixel_format", "Mono8")
94+
self._pixel_format: str = ns.get("pixel_format") or props.get("pixel_format", "auto")
95+
self._pixel_format = str(self._pixel_format).strip()
8196
self._rotate: int = int(ns.get("rotate", props.get("rotate", 0))) % 360
8297
self._crop: tuple[int, int, int, int] | None = self._parse_crop(ns.get("crop", props.get("crop")))
8398

@@ -913,17 +928,56 @@ def _create_acquirer(self, serial: str | None, index: int):
913928

914929
def _configure_pixel_format(self, node_map) -> None:
915930
try:
916-
if self._pixel_format in node_map.PixelFormat.symbolics:
917-
node_map.PixelFormat.value = self._pixel_format
918-
actual = node_map.PixelFormat.value
919-
if actual != self._pixel_format:
920-
LOG.warning("Pixel format mismatch: requested '%s', got '%s'", self._pixel_format, actual)
931+
pixel_format_node = getattr(node_map, "PixelFormat", None)
932+
if pixel_format_node is None:
933+
return
934+
935+
available = list(getattr(pixel_format_node, "symbolics", []) or [])
936+
if not available:
937+
return
938+
939+
requested = str(self._pixel_format or "auto").strip()
940+
941+
if requested.lower() == "auto":
942+
selected = None
943+
944+
for fmt in self._COLOR_PIXEL_FORMATS:
945+
if fmt in available:
946+
selected = fmt
947+
break
948+
949+
if selected is None:
950+
for fmt in self._MONO_PIXEL_FORMATS:
951+
if fmt in available:
952+
selected = fmt
953+
break
954+
955+
if selected is None:
956+
selected = available[0]
957+
921958
else:
922-
LOG.warning(
923-
"Pixel format '%s' not in available formats: %s", self._pixel_format, node_map.PixelFormat.symbolics
924-
)
959+
selected = requested
960+
if selected not in available:
961+
LOG.warning(
962+
"Pixel format '%s' not available. Available formats: %s. Falling back to auto.",
963+
selected,
964+
available,
965+
)
966+
selected = None
967+
for fmt in self._COLOR_PIXEL_FORMATS + self._MONO_PIXEL_FORMATS:
968+
if fmt in available:
969+
selected = fmt
970+
break
971+
if selected is None:
972+
selected = available[0]
973+
974+
pixel_format_node.value = selected
975+
self._pixel_format = str(pixel_format_node.value)
976+
977+
LOG.debug("GenTL pixel format selected: %s", self._pixel_format)
978+
925979
except Exception as e:
926-
LOG.warning("Failed to set pixel format '%s': %s", self._pixel_format, e)
980+
LOG.warning("Failed to configure pixel format '%s': %s", self._pixel_format, e)
927981

928982
def _configure_trigger(self, node_map) -> None:
929983
try:
@@ -1056,10 +1110,24 @@ def _convert_frame(self, frame: np.ndarray) -> np.ndarray:
10561110
scale = 255.0 / max_val if max_val > 0.0 else 1.0
10571111
frame = np.clip(frame * scale, 0, 255).astype(np.uint8)
10581112

1113+
fmt = str(self._pixel_format or "").strip()
1114+
10591115
if frame.ndim == 2:
1060-
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
1061-
elif frame.ndim == 3 and frame.shape[2] == 3 and self._pixel_format == "RGB8":
1062-
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
1116+
if fmt == "BayerRG8":
1117+
frame = cv2.cvtColor(frame, cv2.COLOR_BayerRG2BGR)
1118+
elif fmt == "BayerGB8":
1119+
frame = cv2.cvtColor(frame, cv2.COLOR_BayerGB2BGR)
1120+
elif fmt == "BayerGR8":
1121+
frame = cv2.cvtColor(frame, cv2.COLOR_BayerGR2BGR)
1122+
elif fmt == "BayerBG8":
1123+
frame = cv2.cvtColor(frame, cv2.COLOR_BayerBG2BGR)
1124+
else:
1125+
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
1126+
1127+
elif frame.ndim == 3 and frame.shape[2] == 3:
1128+
if fmt == "RGB8":
1129+
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
1130+
# BGR8 is already OpenCV-native.
10631131

10641132
if self._crop is not None:
10651133
top, bottom, left, right = (int(v) for v in self._crop)

0 commit comments

Comments
 (0)