Skip to content

Commit 8efc1b5

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 4e83f8c commit 8efc1b5

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
@@ -58,7 +58,7 @@ def __init__(self, settings):
5858
self._fast_start: bool = bool(self.ns.get("fast_start", False))
5959
self._retrieve_timeout_ms: int = 100 # default; may be overridden by trigger settings
6060
self._timestamp_tick_frequency_hz: float | None = None
61-
self._last_frame_timestamp_metadata: FrameTimestampMetadata | None = None
61+
self._timestamp_tick_frequency_source: str | None = None
6262

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

161-
@property
162-
def last_frame_timestamp_metadata(self) -> FrameTimestampMetadata | None:
163-
return self._last_frame_timestamp_metadata
164-
165161
@property
166162
def recommended_preserve_mono(self) -> bool | None:
167163
if not self._camera_pixel_format:
@@ -185,7 +181,7 @@ def static_capabilities(cls) -> dict[str, SupportLevel]:
185181
"stable_identity": SupportLevel.SUPPORTED,
186182
"hardware_trigger": SupportLevel.BEST_EFFORT,
187183
"preserve_mono": SupportLevel.SUPPORTED,
188-
"hardware_frame_timestamps": SupportLevel.SUPPORTED,
184+
"hardware_frame_timestamps": SupportLevel.BEST_EFFORT,
189185
}
190186
)
191187
return caps
@@ -678,10 +674,22 @@ def open(self) -> None:
678674
node = getattr(self._camera, "GevTimestampTickFrequency", None)
679675
if node is not None and node.IsReadable():
680676
self._timestamp_tick_frequency_hz = float(node.GetValue())
681-
LOG.info("[Basler] timestamp tick frequency: %.3f Hz", self._timestamp_tick_frequency_hz)
677+
self._timestamp_tick_frequency_source = "GevTimestampTickFrequency"
678+
LOG.info(
679+
"[Basler] timestamp tick frequency: %.3f Hz from GevTimestampTickFrequency",
680+
self._timestamp_tick_frequency_hz,
681+
)
682682
except Exception:
683683
LOG.debug("[Basler] Could not read GevTimestampTickFrequency", exc_info=True)
684684

685+
if not self._timestamp_tick_frequency_hz or self._timestamp_tick_frequency_hz <= 0:
686+
self._timestamp_tick_frequency_hz = 1_000_000_000.0
687+
self._timestamp_tick_frequency_source = "assumed_default_1ghz"
688+
LOG.info(
689+
"[Basler] timestamp tick frequency unavailable; assuming %.3f Hz",
690+
self._timestamp_tick_frequency_hz,
691+
)
692+
685693
# Persist stable identity into namespace
686694
try:
687695
serial = device.GetSerialNumber()
@@ -701,6 +709,10 @@ def _make_timestamp_metadata(self, grab_result) -> FrameTimestampMetadata | None
701709
except Exception:
702710
return None
703711

712+
if ticks == 0:
713+
# Basler returns 0 if the timestamp is not available (e.g. for some GigE cameras)
714+
return None
715+
704716
freq = getattr(self, "_timestamp_tick_frequency_hz", None)
705717
seconds = ticks / freq if freq and freq > 0 else None
706718

@@ -715,6 +727,9 @@ def _make_timestamp_metadata(self, grab_result) -> FrameTimestampMetadata | None
715727
tick_frequency_hz=freq,
716728
timebase="Basler camera timestamp counter",
717729
kind="camera_clock",
730+
extra={
731+
"tick_frequency_source": self._timestamp_tick_frequency_source,
732+
},
718733
)
719734

720735
def read(self) -> CapturedFrame:
@@ -749,7 +764,6 @@ def read(self) -> CapturedFrame:
749764
with self._timing.measure("Basler.timestamp"):
750765
software_timestamp = time.time()
751766
timestamp_metadata = self._make_timestamp_metadata(grab_result)
752-
self._last_frame_timestamp_metadata = timestamp_metadata
753767

754768
if not self._logged_first_frame:
755769
self._logged_first_frame = True

‎dlclivegui/services/video_recorder.py‎

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

0 commit comments

Comments
 (0)