Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions dlclivegui/cameras/backends/basler_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def _configure_frame_rate(self) -> None:

fps = self._positive_float(getattr(self.settings, "fps", 0.0))
if fps is None:
LOG.info("[Basler] FPS: auto/free-run, not forcing AcquisitionFrameRate")
LOG.debug("[Basler] FPS: auto/free-run, not forcing AcquisitionFrameRate")
return

enable = self._feature("AcquisitionFrameRateEnable")
Expand All @@ -454,7 +454,7 @@ def _configure_frame_rate(self) -> None:
try:
min_v = rate.GetMin()
max_v = rate.GetMax()
LOG.info("[Basler] AcquisitionFrameRate range: min=%s max=%s requested=%s", min_v, max_v, fps)
LOG.debug("[Basler] AcquisitionFrameRate range: min=%s max=%s requested=%s", min_v, max_v, fps)
except Exception:
pass

Expand Down Expand Up @@ -485,7 +485,7 @@ def _configure_frame_rate(self) -> None:
if feature is not None:
readbacks[name] = self._feature_value(feature, None)

LOG.info("[Basler] FPS readback requested=%s values=%s", fps, readbacks)
LOG.debug("[Basler] Readback requested=%s values=%s", fps, readbacks)

try:
self._actual_fps = float(readbacks.get("AcquisitionFrameRate"))
Expand All @@ -510,14 +510,14 @@ def _configure_converter(self) -> None:

if self._should_output_mono():
self._converter.OutputPixelFormat = pylon.PixelType_Mono8
LOG.info(
LOG.debug(
"[Basler] Converter configured for Mono8 output (camera PixelFormat=%s preserve_mono=%s)",
camera_pixel_format,
self._preserve_mono,
)
else:
self._converter.OutputPixelFormat = pylon.PixelType_BGR8packed
LOG.info(
LOG.debug(
"[Basler] Converter configured for BGR8 output (camera PixelFormat=%s preserve_mono=%s)",
camera_pixel_format,
self._preserve_mono,
Expand Down Expand Up @@ -548,7 +548,7 @@ def open(self) -> None:
self._camera.ExposureTime.SetValue(float(self.settings.exposure))
if hasattr(self._camera, "ExposureTimeAbs"):
self._camera.ExposureTimeAbs.SetValue(float(self.settings.exposure))
LOG.info("[Basler] Exposure set to %s us (auto off)", self.settings.exposure)
LOG.debug("[Basler] Exposure set to %s us (auto off)", self.settings.exposure)
except Exception as exc:
LOG.warning("[Basler] Failed to set exposure: %s", exc)

Expand All @@ -558,7 +558,7 @@ def open(self) -> None:
if hasattr(self._camera, "GainAuto"):
self._camera.GainAuto.SetValue("Off")
self._camera.Gain.SetValue(float(self.settings.gain))
LOG.info("[Basler] Gain set to %s dB (auto off)", self.settings.gain)
LOG.debug("[Basler] Gain set to %s dB (auto off)", self.settings.gain)
except Exception as exc:
LOG.warning("[Basler] Failed to set gain: %s", exc)

Expand Down Expand Up @@ -638,15 +638,15 @@ def open(self) -> None:
# pylon.GrabStrategy_LatestImageOnly,
pylon.GrabStrategy_OneByOne,
)
LOG.info(
LOG.debug(
"[Basler] grabbing=%s max_buffers=%s",
self._camera.IsGrabbing(),
self._camera.MaxNumBuffer.GetValue() if hasattr(self._camera, "MaxNumBuffer") else "N/A",
)
else:
LOG.debug("Fast-start probe: skipping StartGrabbing and converter")

LOG.info(
LOG.debug(
"[Basler] open device_id=%s index=%s fast_start=%s requested=(%sx%s @ %s fps exp=%s gain=%s)",
getattr(self, "_device_id", None),
getattr(self.settings, "index", None),
Expand Down Expand Up @@ -756,7 +756,7 @@ def read(self) -> CapturedFrame:

if not self._logged_first_frame:
self._logged_first_frame = True
LOG.info(
LOG.debug(
"[Basler] first frame device_id=%s shape=%s dtype=%s nbytes=%.2f MB "
"camera_pixel_format=%s output_format=%s preserve_mono=%s",
self._device_id,
Expand Down Expand Up @@ -803,7 +803,7 @@ def read(self) -> CapturedFrame:
raise RuntimeError("Failed to retrieve image from Basler camera.") from exc

def close(self) -> None:
LOG.info(
LOG.debug(
"[Basler] close called camera_exists=%s grabbing=%s open=%s",
self._camera is not None,
bool(self._camera and self._camera.IsGrabbing()),
Expand Down Expand Up @@ -1164,7 +1164,7 @@ def _configure_trigger_input(self, cfg, *, strict: bool = False) -> None:
self._trigger = CameraTriggerSettings()
return

LOG.info(
LOG.debug(
"Basler trigger input configured: role=%s selector=%s source=%s activation=%s "
"selector_ok=%s source_ok=%s activation_ok=%s",
role,
Expand Down Expand Up @@ -1229,7 +1229,7 @@ def _configure_trigger_master(self, cfg, *, strict: bool = False) -> None:
source_ok = self._set_enum_feature("LineSource", output_source, strict=strict)

if mode_ok and source_ok:
LOG.info(
LOG.debug(
"Basler trigger master configured via Line*: output_line=%s output_source=%s",
output_line,
output_source,
Expand Down
72 changes: 61 additions & 11 deletions dlclivegui/cameras/backends/gentl_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ def __init__(self, settings):
ns = {}

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

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

@property
def recommended_preserve_mono(self) -> bool | None:
if not self._camera_pixel_format:
return None
return self._is_camera_mono()

@property
def actual_output_format(self) -> str | None:
"""Current GenTL backend emits OpenCV-native BGR uint8 frames."""
return self._actual_output_format or "BGR8"
"""Backend output frame format emitted to the app, e.g. 'Mono8' or 'BGR8'."""
if self._actual_output_format:
return self._actual_output_format
if not self._camera_pixel_format:
return None
return "Mono8" if self._should_output_mono() else "BGR8"

@classmethod
def is_available(cls) -> bool:
Expand All @@ -203,6 +215,7 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
"device_discovery": SupportLevel.SUPPORTED,
"stable_identity": SupportLevel.SUPPORTED,
"hardware_trigger": SupportLevel.BEST_EFFORT,
"preserve_mono": SupportLevel.SUPPORTED,
}

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

def _is_camera_mono(self) -> bool:
fmt = str(self._camera_pixel_format or self._pixel_format or "").strip()
return fmt.startswith("Mono")

def _should_output_mono(self) -> bool:
return bool(self._preserve_mono and self._is_camera_mono())

@staticmethod
def _output_format_for_frame(frame: np.ndarray) -> str:
if frame.ndim == 2:
Expand Down Expand Up @@ -654,6 +674,25 @@ def read(self) -> CapturedFrame:
except Exception:
pass
self._actual_output_format = self._output_format_for_frame(frame)
try:
ns = self._ensure_settings_ns()
ns["actual_output_format"] = self._actual_output_format
ns["preserve_mono"] = self._preserve_mono
except Exception:
pass
if not self._logged_first_frame:
self._logged_first_frame = True
LOG.info(
"[GenTL] first frame device_id=%s shape=%s dtype=%s nbytes=%.2f MB "
"camera_pixel_format=%s output_format=%s preserve_mono=%s",
self._device_id,
frame.shape,
frame.dtype,
frame.nbytes / (1024 * 1024),
self._camera_pixel_format,
self.actual_output_format,
self._preserve_mono,
)

return CapturedFrame(
frame=frame,
Expand Down Expand Up @@ -1275,7 +1314,7 @@ def _resolve_trigger_source(self, node_map, requested: str, *, strict: bool) ->
if requested.lower() == "auto":
for candidate in ("Line0", "Line1", "Line2", "Any"):
if candidate in available:
LOG.info(
LOG.debug(
"GenTL TriggerSource auto-selected '%s'. Available: %s",
candidate,
available,
Expand Down Expand Up @@ -1346,6 +1385,14 @@ def _configure_pixel_format(self, node_map) -> None:
pixel_format_node.value = selected
self._pixel_format = str(pixel_format_node.value)
self._camera_pixel_format = self._pixel_format
try:
ns = self._ensure_settings_ns()
ns["actual_pixel_format"] = self._camera_pixel_format
ns["detected_pixel_format"] = self._camera_pixel_format
ns["actual_output_format"] = self.actual_output_format
ns["preserve_mono"] = self._preserve_mono
except Exception:
pass

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

Expand Down Expand Up @@ -1447,7 +1494,7 @@ def _configure_trigger_input(self, node_map, cfg, *, strict: bool = False) -> No
self._trigger = CameraTriggerSettings()
return

LOG.info(
LOG.debug(
"GenTL trigger input configured: role=%s selector=%s source_requested=%s "
"source=%s activation=%s selector_ok=%s source_ok=%s activation_ok=%s",
role,
Expand Down Expand Up @@ -1508,7 +1555,7 @@ def _configure_trigger_master(self, node_map, cfg, *, strict: bool = False) -> N
node = self._node(node_map, "StrobeDuration")
if node is not None:
node.value = int(strobe_duration)
LOG.info("Configured GenTL StrobeDuration=%s", int(strobe_duration))
LOG.debug("Configured GenTL StrobeDuration=%s", int(strobe_duration))
except Exception as exc:
if strict:
raise RuntimeError(f"Failed to set StrobeDuration={strobe_duration}: {exc}") from exc
Expand All @@ -1519,7 +1566,7 @@ def _configure_trigger_master(self, node_map, cfg, *, strict: bool = False) -> N
node = self._node(node_map, "StrobeDelay")
if node is not None:
node.value = int(strobe_delay)
LOG.info("Configured GenTL StrobeDelay=%s", int(strobe_delay))
LOG.debug("Configured GenTL StrobeDelay=%s", int(strobe_delay))
except Exception as exc:
if strict:
raise RuntimeError(f"Failed to set StrobeDelay={strobe_delay}: {exc}") from exc
Expand All @@ -1533,7 +1580,7 @@ def _configure_trigger_master(self, node_map, cfg, *, strict: bool = False) -> N
)

if enable_ok:
LOG.info(
LOG.debug(
"GenTL trigger master configured via Strobe*: "
"StrobeEnable=On StrobePolarity=%s polarity_ok=%s "
"StrobeOperation=%s operation_ok=%s",
Expand Down Expand Up @@ -1573,7 +1620,7 @@ def _configure_trigger_master(self, node_map, cfg, *, strict: bool = False) -> N
source_ok = self._set_enum_node(node_map, "LineSource", output_source, strict=strict)

if mode_ok and source_ok:
LOG.info(
LOG.debug(
"GenTL trigger master configured via Line*: output_line=%s output_source=%s",
output_line,
output_source,
Expand Down Expand Up @@ -1692,15 +1739,15 @@ def _configure_frame_rate(self, node_map) -> None:
return

target = float(self.settings.fps)
LOG.info("Configuring GenTL frame rate: requested %.3f FPS", target)
LOG.debug("Configuring GenTL frame rate: requested %.3f FPS", target)

for attr in ("AcquisitionFrameRateEnable", "AcquisitionFrameRateControlEnable"):
try:
node = getattr(node_map, attr)
before = getattr(node, "value", None)
node.value = True
after = getattr(node, "value", None)
LOG.info("Enabled GenTL %s: before=%r after=%r", attr, before, after)
LOG.debug("Enabled GenTL %s: before=%r after=%r", attr, before, after)
break
except Exception:
pass
Expand All @@ -1712,7 +1759,7 @@ def _configure_frame_rate(self, node_map) -> None:
node.value = target
after = getattr(node, "value", None)

LOG.info(
LOG.debug(
"Set GenTL %s: before=%r requested=%.3f after=%r",
attr,
before,
Expand Down Expand Up @@ -1851,6 +1898,9 @@ def _convert_frame(self, frame: np.ndarray) -> np.ndarray:
frame = cv2.cvtColor(frame, cv2.COLOR_BayerGR2BGR)
elif fmt == "BayerBG8":
frame = cv2.cvtColor(frame, cv2.COLOR_BayerBG2BGR)
elif self._should_output_mono():
# Keep Mono* cameras as 2D uint8 frames when explicitly requested.
pass
else:
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

Expand Down
8 changes: 6 additions & 2 deletions dlclivegui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@
# Global settings
## GUI
GUI_MAX_DISPLAY_FPS: float = 30.0
## Recording
ALLOWED_VIDEO_CONTAINERS: set[str] = {"mp4", "avi", "mov"}
DEFAULT_RECORDING_CONTAINER: str = "mp4"


## Debug
### Timing logs
SINGLE_CAMERA_WORKER_DO_LOG_TIMING: bool = False
MULTI_CAMERA_WORKER_DO_LOG_TIMING: bool = False
REC_DO_LOG_TIMING: bool = False
DLC_DO_LOG_TIMING: bool = True
Comment thread
C-Achard marked this conversation as resolved.
# MAIN_WINDOW_DO_LOG_TIMING: bool = False
Comment on lines 30 to 34
#### Backends
BASLER_DO_LOG_TIMING: bool = False
Expand Down Expand Up @@ -512,7 +516,7 @@ class RecordingSettings(BaseModel):
enabled: bool = False
directory: str = Field(default_factory=lambda: str(Path.home() / "Videos" / "deeplabcut-live"))
filename: str = "session.mp4"
container: Literal["mp4", "avi", "mov"] = "mp4"
container: Literal["mp4", "avi", "mov"] = DEFAULT_RECORDING_CONTAINER
codec: str = "libx264"
crf: int = Field(default=23, ge=0, le=51)
fast_encoding: bool = False
Expand Down Expand Up @@ -554,7 +558,7 @@ def writegear_options(self, fps: float | None) -> dict[str, Any]:
crf_value = int(self.crf) if self.crf is not None else 23

opts: dict[str, Any] = {
"-input_framerate": f"{fps_value:.6f}",
"-input_framerate": float(fps_value),
"-vcodec": codec_value,
"-crf": str(crf_value),
}
Expand Down
5 changes: 3 additions & 2 deletions dlclivegui/gui/camera_config/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from PySide6.QtCore import QTimer

from ...services.camera_controller import SingleCameraWorker
from ...services.multi_camera_controller import MultiCameraController

if TYPE_CHECKING:
Expand Down Expand Up @@ -56,7 +57,7 @@ class PreviewSession:


def apply_rotation(frame, rotation):
return MultiCameraController.apply_rotation(frame, rotation)
return SingleCameraWorker.apply_rotation(frame, rotation)


def apply_crop(frame, x0, y0, x1, y1):
Expand All @@ -66,7 +67,7 @@ def apply_crop(frame, x0, y0, x1, y1):
x1 = max(x0, min(x1, w))
y1 = max(y0, min(y1, h))

return MultiCameraController.apply_crop(frame, (x0, y0, x1, y1))
return SingleCameraWorker.apply_crop(frame, (x0, y0, x1, y1))


def resize_to_fit(frame, max_w=400, max_h=300):
Expand Down
Loading