Conversation
bufferSegmentation stored each TileObservation's age field with
cloud.header.stamp.seconds(), but SemanticSegmentationLayer::updateBounds
purges observations using node->now().seconds() (wall-clock). When the
sensor publishes clouds with stamps that lag wall-clock by more than
tile_map_decay_time, every observation is purged on the very first
updateBounds cycle after it was buffered, leaving the layer's costmap_
empty and the master grid permanently FREE_SPACE despite the layer
visibly having entries in temporal_tile_map_.
Reproduced on Jetson Orin + ZED X (HD1080 @ 15 fps, NEURAL_LIGHT depth):
the published cloud header.stamp lagged wall-clock by ~4.35 s. With
tile_map_decay_time = 1.5 s (the kiwicampus default) the layer purge
treated every fresh observation as 4.35 s old > 1.5 s decay -> all
purged. Symptoms:
- bufferSegmentation entry: tile_map_size=0 -> push 11 obs ->
tile_map_size=11 (correct)
- 67 ms later, next bufferSegmentation entry: tile_map_size=0 again
- layer's purge fires once per cycle and erases everything since the
age check (now - stored_stamp) > decay_time always evaluates true
The fix is one line: stamp the observation with clock_->now().seconds()
inside bufferSegmentation, so both purge call sites operate in the same
wall-clock domain. Observations are 0 s old when stored, decay_time
behaves as documented, and the master grid receives lethal cells.
Verified 2026-04-29 on the same Jetson + ZED X setup that exhibited the
empty-master-grid bug. After this patch /local_costmap/costmap_raw
shows ~10 LETHAL cells whenever the perception node detects lane
pixels, with inflation falloff visible in RViz.
Without this patch, PR3 (raytrace-clear) is necessary but not sufficient
to keep cells fresh in the master grid -- the upstream marking loop
still needs a non-empty temporal_tile_map_ when updateBounds runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SegmentationBuffer::bufferSegmentationstores each observation's age field withcloud.header.stamp.seconds(), butSemanticSegmentationLayer::updateBoundspurges observations usingnode->now().seconds()(wall-clock). When the sensor publishes clouds with stamps that lag wall-clock by more thantile_map_decay_time, every observation is purged on the very firstupdateBoundscycle after it was buffered. Result: the layer'scostmap_array stays empty, the master grid never receives any LETHAL cells, and the plugin produces no costmap output despitetemporal_tile_map_having entries every cycle.Reproduction
Reproduced on Jetson Orin + Stereolabs ZED X (HD1080 @ 15 fps, NEURAL_LIGHT depth, organized point cloud, ROS 2 Humble). The published cloud
header.stamplagged wall-clock by ~4.35 s — apparent ZED publish-pipeline latency. Withtile_map_decay_time: 1.5(default), the layer's purge treated every fresh observation as 4.35 s old > 1.5 s decay → all purged on every layer cycle.Symptom in our setup:
bufferSegmentationenters at 14.9 Hz (matches sensor rate); pushes 10–13 observations per frame;temporal_tile_map_->size() = 10–13at end of function.bufferSegmentationenters andtemporal_tile_map_->size() = 0.updateBoundsruns at the local_costmap update rate; on each call the tile-iteration loop sees zero tiles, so nothing gets written into the layer'scostmap_, andupdateWithMaxpropagates nothing to the master grid.We confirmed empirically with throttled
RCLCPP_INFOlogs:current_time - cloud_time_seconds = 4.35 s, confirming the dual-clock bug.Fix
One line in
src/segmentation_buffer.cpp:clock_is already a member ofSegmentationBuffer(set fromnode->get_clock()at construction), and is the same clock the layer'supdateBoundsuses fornode->now(). Both purge sites then operate in the same wall-clock domain, observations are 0 s old when stored, anddecay_timeworks as documented.Verification
After the patch on the same Jetson + ZED X setup:
/local_costmap/front/tile_mapsize/local_costmap/costmap_rawLETHAL countLane stripes detected by an HSV pipeline now appear as LETHAL cells in the costmap with the
inflation_layerhalo around them, exactly where the camera sees them on the ground.Notes
cloud_time_secondsfrom the buffer instead ofnode->now(). Decided against it because (a)node->now()is the natural cadence for a layer running in the costmap update loop, (b) using stale buffer time on the layer side would prevent purging when the buffer stops receiving data (which is when you most want stale cells to age out).