Cameras & sensorsTroubleshooting

Camera frame timestamp vs system time: why clocks differ

Frame timestamps vs system time: the camera counts ticks from its boot clock, so they never match. Read the tick frequency and epoch, then latch one offset.

LumaSpec Vision8 min read Share on LinkedIn

On this page 10 sections
  1. 01Why the two numbers can never match
  2. 02Read the unit, origin and anchor from the camera
  3. 03Worked example: decode the two printed values
  4. 04Map both clocks onto one time base
  5. 05Three tests before you trust the mapping
  6. 06Other epochs you will meet
  7. 07Rolling shutter: the timestamp belongs to row 0
  8. 08Keep the mapping honest across a shift
  9. 09What this means when you pick hardware
  10. 10Questions

You print the frame timestamp next to the host clock and get 13,905,354,715,900 beside 1,729,090,310.31. Nothing is broken: the first value counts ticks of the camera's own hardware counter, the second counts seconds since 1970-01-01 UTC. The units differ and the origins differ, so subtracting one from the other produces a number with no meaning. Work through this once and the pairing behaves: read the camera clock's unit and origin, decode both printed values, latch a single offset that puts every frame on the host time base, and run three tests that prove the mapping. The same method correlates frames with PLC events, log entries or a second camera.

Why the two numbers can never match

The frame timestamp is a free-running counter driven by the camera's own crystal. It counts up only, ignores leap seconds, daylight saving and NTP corrections, and starts from an origin of its own: power-up, or zero after a reset command. time.time() returns seconds since 1970-01-01 UTC on the host wall clock, which NTP steps back and forth. Two counters with different units and origins do not subtract. When a mapped time looks wrong, the cause is one of these five, roughly in order of how often it is the culprit:

  1. Unit. The value is ticks, not seconds. The same digits, 13,905,354,715,900, mean 13,905.35 s at 1 ns per tick but 92,702.36 s at 150 MHz. Test: read the tick frequency before interpreting anything.
  2. Epoch. The origin is camera boot or the last reset, not 1970. Test: power-cycle the camera. A boot-relative counter restarts near zero; Unix time would survive.
  3. Anchor. The value is latched when exposure starts, on some models when exposure ends, never when your callback runs.
  4. Arrival. A GigE or USB3 frame reaches your callback tens of milliseconds after capture. The host arrival time is a different measurement; see the buffer timestamp latency guide.
  5. Drift, reset or wrap. The two crystals drift apart by milliseconds per hour, a reset zeroes the counter, and narrow counters wrap.

Read the unit, origin and anchor from the camera

On GigE Vision cameras the feature GevTimestampTickFrequency reports the counter rate in ticks per second (Hz). A camera reporting 150,000,000 has ticks of 1/150,000,000 s ≈ 6.67 ns. Non-GigE cameras expose an equivalent clock-frequency control or document a fixed rate. The value is model-specific, so read it instead of assuming nanoseconds.

The origin follows the same rule: the counter runs from power-up, and the documented TimestampReset control sets it back to zero. GenICam SFNC defines the frame timestamp as the instant exposure starts; some models latch at exposure end instead. How that value travels into your buffer is the subject of the chunk data timestamp guide.

Worked example: decode the two printed values

Take the pair from the introduction. Host value first: subtract the 2024-01-01 00:00:00 UTC anchor of 1,704,067,200 s. 1,729,090,310 - 1,704,067,200 = 25,023,110 s, which is 289 days plus 53,510 s, and 53,510 s = 14 h 51 min 50 s. The log line was written on 16 October 2024 at 14:51:50 UTC.

The camera value stays ambiguous until the tick rate is read. Two plausible readings of the same 13,905,354,715,900:

Assumed tick rate Seconds since boot Uptime that implies
1 GHz (1 ns/tick) 13,905.35 s 3 h 51 min 45.4 s
150 MHz (6.67 ns/tick) 92,702.36 s 1 day 1 h 45 min 2.4 s

Both are believable uptimes, which is the point. The naive subtraction 1,729,090,310.31 - 13,905.35 = 1,729,076,404.96 decodes to 11:00:05 UTC on the same day: plausible-looking, and wrong by exactly the uptime you misread. Read the frequency first; never guess the unit.

Map both clocks onto one time base

One offset latches the counters together. Read the camera counter and the host monotonic clock at the same instant, scale ticks to seconds, and the difference is your offset. Every frame then maps with one addition:

t_cam_s = ticks / tick_frequency offset = host_mono_now_s - ticks_now / tick_frequency t_host_s = ticks / tick_frequency + offset

Worked through: at latch, the host monotonic clock reads 5,000.000 s and the camera counter reads 13,905,354,715,900 at 1 GHz, i.e. 13,905.3547159 s. The offset is 5,000.000 - 13,905.3547159 = -8,905.3547159 s. A frame with 13,906,354,715,900 ticks maps to 13,906.3547159 - 8,905.3547159 = 5,001.0000000 s, exactly 1.0000000 s after the latch, as timed.

In Python the pattern is four lines. The feature name comes from the camera; the variable names are illustrative:

import time

tick_frequency = 150_000_000    # from GevTimestampTickFrequency, Hz (illustrative value)
ticks_now = 13_905_354_715_900  # int, camera counter read now
host_now = time.monotonic()     # float, seconds on the host monotonic clock

offset = host_now - ticks_now / tick_frequency  # float, seconds

def frame_time(ticks: int) -> float:
    return ticks / tick_frequency + offset

Latch against the monotonic clock, not time.time(): NTP can step wall time backwards, and a step corrupts the offset. The two crystals then drift apart at the tolerance of the camera's crystal, typically ±20-50 ppm. That is 1.2-3 ms per minute (50 ppm × 60 s = 3 ms) and up to 180 ms per hour (50 ppm × 3,600 s = 0.18 s). Re-latch every 60 s and clock error stays at or below 3 ms; if 0.1 s accuracy is enough, an hourly re-latch does.

Three tests before you trust the mapping

  1. Round trip. Acquire two frames 1,000 ms apart, paced by the host timer. The mapped delta must read 1.000 s within your drift budget; with a fresh offset and 6.67 ns resolution, an error above single-digit milliseconds is a bug, not clock physics.
  2. Hardware event. Pulse the camera's trigger line from an output the host also time-stamps. The mapped timestamp must land at trigger-plus-latency, and repeated pulses should scatter by microseconds — wired trigger jitter, well under a millisecond.
  3. Cross-camera. Fire two cameras from one trigger. Their mapped times must agree within wiring skew. A camera that reads consistently late by about one exposure duration has an anchor mismatch, exposure end instead of start, not a clock fault.

Other epochs you will meet

Clock Unit Origin
Unix time, time.time() 1 s 1970-01-01 00:00 UTC
Windows FILETIME 100 ns 1601-01-01 UTC
Camera counter tick, rate from GevTimestampTickFrequency boot or last reset
IEEE 1588 (PTP) time 1 ns the profile's epoch, commonly 1970-01-01

FILETIME catches people because the scale is 100 ns: 132,702,865,451,985,472 × 100 ns = 13,270,286,545.2 s after 1601-01-01, which is 9 July 2021. Convert by dividing by 10^7, never by 10^9.

PTP is the clean exit: a camera disciplined to a network grandmaster reports genuine epoch time and the offset step disappears. When it misbehaves, the camera PTP timestamps guide lists the checks that restore Unix time. Host runtimes carry their own traps: real-time controllers keep a separate clock domain from their Windows machines, which is why real-time timestamp strings can differ from the Windows host at the same instant.

Rolling shutter: the timestamp belongs to row 0

On a rolling-shutter sensor the latched instant is the start of row 0's exposure, and row N is exposed roughly N × t_row later. Say a 4,000-row sensor reads out in 20 ms: the per-row time is 20 ms / 4,000 = 5 µs, so the last row sees the scene about 20 ms after the timestamp. Part of an apparent clock error is simply where the object sat in the frame. If exposure changes frame to frame under auto exposure, the anchor moves with it; read the real exposure per frame from metadata and correct, as in the frame metadata exposure guide.

Keep the mapping honest across a shift

  • Re-latch the offset at a fixed cadence: 60 s for millisecond-class work, per the drift numbers above.
  • Log every timestamp reset and power cycle. Each starts a new time base and invalidates stored offsets.
  • Mind counter width. 64 bits at 1 GHz wraps after about 584 years (2^64 × 1 ns = 1.845 × 10^10 s), so wrap is rarely the bug; a 32-bit counter at 150 MHz wraps every 28.6 s. The clock rollover and monotonic mapping guide shows the reconstruction.
  • Keep the latch, the conversion and the drift check in one tested module, not scattered through the application.

What this means when you pick hardware

The decision: never compare the raw numbers. Read GevTimestampTickFrequency, latch one offset against the host monotonic clock, re-latch every 60 s for millisecond work, and prove the mapping with the three tests above. GigE Vision cameras report the tick rate directly; models with PTP or a hardware trigger input remove most of the mapping work in multi-camera cells, where every camera needs the same time base.

CamerasArea scan cameras

GigE, USB3, 10GigE and CoaXPress

1,287 models · from $149.00

Shortlist with the camera finder; more timing guides sit on the cameras topic page.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

If the timing options look like a maze, contact the applications team and bring your drift budget.

Questions

How do I measure the tick frequency if my camera does not publish it?

Latch the counter twice exactly 60.000 s apart on the host timer. Ticks elapsed divided by 60.000 s gives ticks per second; a register read takes a few milliseconds at most, so the estimate lands within about a hundred ppm. Cross-check against any documented value before trusting it.

Does the frame timestamp mark exposure start or exposure end?

Start, per the SFNC definition given in the body above, but the anchor is firmware-specific: several models latch at exposure end instead, and rolling-shutter designs anchor row 0. Test yours: fire a trigger you time-stamp on the host and repeat it with a 10 ms exposure. An end-anchored camera maps 10 ms late; a start-anchored one does not move.

Can I skip the offset and just set the camera clock to Unix time?

Usually no. A plain counter only resets to zero, so a written start value still leaves a boot-relative origin and you still need an offset latch. Cameras with IEEE 1588 PTP or a writable wall-clock feature are the exception: they keep genuine epoch time, and the offset step drops out entirely.

How accurate can the timestamp mapping get?

Budget drift plus latch latency. With ±20-50 ppm crystals and a 60 s re-latch, clock error stays at or below 3 ms; reading the counter over GigE adds a few milliseconds of uncertainty. For microsecond-class timing, use PTP or latch against a hardware trigger line instead of software reads.

Why did my frame timestamps jump backwards after a camera restart?

The counter restarted: the origin is power-up, so after a reboot the counter runs from near zero while your stored offset is stale. Treat every power cycle or reset command as a new time base: discard old offsets and re-latch before the first mapped frame. Log boot events so gaps in the mapping are visible.