Skip to content

Commit 358b37b

Browse files
committed
Harden Basler timestamp metadata handling
Improve Basler hardware timestamp robustness by treating support as best-effort, recording the tick-frequency source, falling back to an assumed 1 GHz clock when frequency is unavailable, and ignoring zero-value camera timestamps as missing data. The frame timestamp metadata now includes the frequency source in `extra`, and unused last-frame timestamp state was removed. Video recorder metadata also drops the legacy top-level `timestamps` field in favor of the structured `timestamp_sources` schema.
1 parent 7528465 commit 358b37b

2 files changed

Lines changed: 23 additions & 11 deletions

File tree

dlclivegui/cameras/backends/basler_backend.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(self, settings):
5757
self._fast_start: bool = bool(self.ns.get("fast_start", False))
5858
self._retrieve_timeout_ms: int = 100 # default; may be overridden by trigger settings
5959
self._timestamp_tick_frequency_hz: float | None = None
60-
self._last_frame_timestamp_metadata: FrameTimestampMetadata | None = None
60+
self._timestamp_tick_frequency_source: str | None = None
6161

6262
# ---- Trigger settings ----
6363
raw_trigger = self.ns.get("trigger", self._props.get("trigger"))
@@ -157,10 +157,6 @@ def actual_output_format(self) -> str | None:
157157
return None
158158
return "Mono8" if self._should_output_mono() else "BGR8"
159159

160-
@property
161-
def last_frame_timestamp_metadata(self) -> FrameTimestampMetadata | None:
162-
return self._last_frame_timestamp_metadata
163-
164160
@property
165161
def recommended_preserve_mono(self) -> bool | None:
166162
if not self._camera_pixel_format:
@@ -184,7 +180,7 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
184180
"stable_identity": SupportLevel.SUPPORTED,
185181
"hardware_trigger": SupportLevel.BEST_EFFORT,
186182
"preserve_mono": SupportLevel.SUPPORTED,
187-
"hardware_frame_timestamps": SupportLevel.SUPPORTED,
183+
"hardware_frame_timestamps": SupportLevel.BEST_EFFORT,
188184
}
189185
)
190186
return caps
@@ -667,10 +663,22 @@ def open(self) -> None:
667663
node = getattr(self._camera, "GevTimestampTickFrequency", None)
668664
if node is not None and node.IsReadable():
669665
self._timestamp_tick_frequency_hz = float(node.GetValue())
670-
LOG.info("[Basler] timestamp tick frequency: %.3f Hz", self._timestamp_tick_frequency_hz)
666+
self._timestamp_tick_frequency_source = "GevTimestampTickFrequency"
667+
LOG.info(
668+
"[Basler] timestamp tick frequency: %.3f Hz from GevTimestampTickFrequency",
669+
self._timestamp_tick_frequency_hz,
670+
)
671671
except Exception:
672672
LOG.debug("[Basler] Could not read GevTimestampTickFrequency", exc_info=True)
673673

674+
if not self._timestamp_tick_frequency_hz or self._timestamp_tick_frequency_hz <= 0:
675+
self._timestamp_tick_frequency_hz = 1_000_000_000.0
676+
self._timestamp_tick_frequency_source = "assumed_default_1ghz"
677+
LOG.info(
678+
"[Basler] timestamp tick frequency unavailable; assuming %.3f Hz",
679+
self._timestamp_tick_frequency_hz,
680+
)
681+
674682
# Persist stable identity into namespace
675683
try:
676684
serial = device.GetSerialNumber()
@@ -690,6 +698,10 @@ def _make_timestamp_metadata(self, grab_result) -> FrameTimestampMetadata | None
690698
except Exception:
691699
return None
692700

701+
if ticks == 0:
702+
# Basler returns 0 if the timestamp is not available (e.g. for some GigE cameras)
703+
return None
704+
693705
freq = getattr(self, "_timestamp_tick_frequency_hz", None)
694706
seconds = ticks / freq if freq and freq > 0 else None
695707

@@ -704,6 +716,9 @@ def _make_timestamp_metadata(self, grab_result) -> FrameTimestampMetadata | None
704716
tick_frequency_hz=freq,
705717
timebase="Basler camera timestamp counter",
706718
kind="camera_clock",
719+
extra={
720+
"tick_frequency_source": self._timestamp_tick_frequency_source,
721+
},
707722
)
708723

709724
def read(self) -> CapturedFrame:
@@ -738,7 +753,6 @@ def read(self) -> CapturedFrame:
738753
with self._timing.measure("Basler.timestamp"):
739754
software_timestamp = time.time()
740755
timestamp_metadata = self._make_timestamp_metadata(grab_result)
741-
self._last_frame_timestamp_metadata = timestamp_metadata
742756

743757
if not self._logged_first_frame:
744758
self._logged_first_frame = True

dlclivegui/services/video_recorder.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -548,9 +548,7 @@ def _save_timestamps(self) -> None:
548548
"schema_version": 2,
549549
"video_file": str(self._output.name),
550550
"num_frames": len(frame_timestamps),
551-
# Backward-compatible host/software timestamp list.
552-
"timestamps": software_timestamps,
553-
# New descriptive schema.
551+
# "timestamps": software_timestamps,
554552
"timestamp_sources": {
555553
"software_timestamp": {
556554
"source": "host_time.time",

0 commit comments

Comments
 (0)