Engineering Guides · Module 07
Timestamps in ROS 2 and message_filters
Use exact matching only when stamps are really identical; otherwise use approximate matching and choose the slop from the allowed timing error.
Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.
1. Engineering problem
Fusing two sensor streams needs pairs of messages from the same time. Real sensors never produce identical stamps unless they are triggered together. The synchroniser must decide how close is close enough.
2. When is this needed?
- Matching camera and LiDAR messages.
- When a synchronised callback never fires.
- When fused results jitter because pairs are poorly aligned.
3. Selection and design decisions
From the Jazzy message_filters documentation: TimeSynchronizer passes messages only when all channels have messages with the same timestamp; the policies for the Synchronizer are ExactTime (identical stamps required), ApproximateEpsilonTime (identical within an epsilon) and ApproximateTime (an adaptive algorithm matching by stamp); TimeSequencer passes messages in timestamp order after a queue delay (C++ only: it is not implemented in Python); Cache keeps the last N messages for lookup by time. Messages need a header.
| Policy | Matches when | Use for |
|---|---|---|
| ExactTime | Stamps identical | Triggered sensors; stereo pairs from one driver |
| ApproximateEpsilonTime | Within an epsilon | Almost-synchronised sources |
| ApproximateTime | Adaptive best match within slop | Independent sensors at different rates |
| Cache + lookup | Choose the nearest or interpolate on demand | One slow stream, one fast stream |
4. Advantages and disadvantages
- Small slop gives better alignment and fewer pairs; large slop gives more pairs with misaligned data.
- Queue size must hold messages long enough for the slowest sensor’s latency.
- Interpolating the faster stream to the slower stream’s stamp can beat pairing.
5. Requirements and constraints
- The slop is derived from the maximum allowed timing error (speed × slop below the spatial tolerance).
- The queue size covers the latency difference between streams.
- Stamps are capture times (see the hardware versus software guide).
6. Architecture
lidar ─┐
├─▶ ApproximateTime(slop) ─▶ callback(lidar_msg, camera_msg)
camera ─┘7. Implementation
- Decide the allowed timing error and set the slop below it.
- Choose the queue size from the latency difference and rates.
- Use ApproximateTime for independent sensors; ExactTime only for triggered ones.
- Log the stamp difference of each matched pair and watch for drift.
- Count unmatched messages.
def approx_sync(a, b, slop):
out, used = [], set()
for ta in a:
best = min(((abs(ta - tb), j) for j, tb in enumerate(b) if j not in used), default=None)
if best and best[0] <= slop:
used.add(best[1]); out.append((ta, b[best[1]]))
return out
lidar = [0.00, 0.10, 0.20, 0.30]
camera = [0.013, 0.118, 0.215, 0.331]
for slop in (0.0, 0.02, 0.05):
pairs = approx_sync(lidar, camera, slop)
print(f"slop {slop}: {len(pairs)} pairs", [(round(x, 3), round(y, 3), round((y - x) * 1000)) for x, y in pairs])Printed (checked by the site’s tests):
slop 0.0: 0 pairs []
slop 0.02: 3 pairs [(0.0, 0.013, 13), (0.1, 0.118, 18), (0.2, 0.215, 15)]
slop 0.05: 4 pairs [(0.0, 0.013, 13), (0.1, 0.118, 18), (0.2, 0.215, 15), (0.3, 0.331, 31)]Reading it: with the stamps offset by 13 to 31 ms, an exact match (slop 0) finds nothing, 20 ms finds three of four pairs, and 50 ms finds all four but with a 31 ms misalignment in the last pair. A simple greedy matcher stands in for the adaptive ApproximateTime algorithm, so the numbers illustrate the trade-off and are not the library’s output. The stamps are illustrative.
8. Real examples
- Camera–LiDAR fusion: an ApproximateTime synchroniser with a slop of 20 ms and a queue of 10.
9. Failure scenarios
- ExactTime used for independent sensors: the callback never fires.
- A huge slop that matches frames 100 ms apart.
- A queue too small for a delayed stream, so matches are dropped silently.
10. Troubleshooting
- Print the stamp differences of each pair.
- Check that both streams have header stamps from the same clock.
- Increase the queue size if messages are dropped.
11. Validation
- The example prints the lines shown (executed by the site tests).
- The distribution of stamp differences of matched pairs is below the requirement.
12. Engineering checklist
13. Further references
- ROS 2 message_filters documentation (Jazzy branch): doc/index.rst, read 2026-11-06.