realsense-viewer: adapt depth color-map ruler to visible range (RSDEV-14227) - #15664
remibettan merged 6 commits into
Conversation
RSDEV-14227: on close-range D401 scenes the color-map ruler kept
labelling 0-4 m because calculate_ruler_max_distance snapped the
mean+1.5σ estimate up to a hardcoded 4 m grid. Replace it with a
percentile-driven bounds pair (p05..p95 with headroom) fed through
an asymmetric EMA (fast expand, slow contract) and snapped to a
"nice" step (0.05/0.1/0.25/0.5/1/2/5/…) only when the smoothed
value crosses an asymmetric deadband — expand at half a step, shrink
at one and a half — so a bound near a tick edge doesn't oscillate.
Ruler labels are drawn only at nice-step multiples that fall inside
the range (no forced min/max, no rounded-off duplicate at the top),
capped at five ticks; decimals derive from the step so 0.5 shows as
"0.5" and 5 shows as "5". draw_color_ruler now takes {min,max} and
maps depths accordingly, clipping pixels that fall outside the
window.
Right-clicking the ruler button opens a small popover with the mode
selector (Auto / Fixed 0-4 m legacy / Fixed custom) and, for the
custom mode, min/max inputs. All three settings persist through
config_file, so the legacy 0-4 m ruler remains one click away.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sysrsbuild-gh-agentic
left a comment
There was a problem hiding this comment.
Auto-generated review by rs-agentic bot
Overall the approach is clean — reusing the existing distances vector, asymmetric EMA hysteresis, and the per-stream depth_ruler_state are all solid choices. A few items flagged below.
Follow-ups on the previous ruler commit, all from a self code-review: - Cache the p95-p05 span before mutating raw_lo/raw_hi so the ruler gets symmetric 5% headroom on both ends; before, the upper padding rode the already-shrunk lower endpoint and grew slightly larger every frame of steady input. - Merge nice_step_for_range's hardcoded threshold table and draw_color_ruler's step_ladder array into one k_step_ladder constant that both consume. Reformulate nice_step_for_range as "coarsest step with <=10 grid cells across the range" — verified to match the previous thresholds through 20 m and extrapolate sensibly beyond (100 m now uses a 10 m step instead of 5 m). - Drop the const qualifier on calculate_ruler_bounds — it writes ruler_state through the non-const stream_model&, so the const claim was misleading. Also take distances by value and std::move at the call site, eliminating the per-frame copy. - Introduce k_min_ruler_gap in stream-model.h so the load path, the popover, and the fixed_user branch in calculate_ruler_bounds all enforce the same 0.1 m minimum span between user min and max. - Validate the persisted ruler_range_mode against static_cast<int>(ruler_range_mode::fixed_user) instead of the magic 2, so adding a new mode won't silently rewrite it to auto. - Remove the ruler_was_shown local left behind from an earlier iteration of the popover code. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bot review on the previous commit flagged the ImGui popover id as a std::string reconstructed every render frame. Switch to a static const char* — same value, zero per-frame cost. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Please share once you have the changes discussed
|
…mals
Post-review feedback on the previous ruler commits:
1. The "Fixed 0-4 m (legacy)" radio has been asked to go — Auto covers
the same case for anyone who really wants a 0-4 m bar, they can pin
it via Fixed custom range. The enum entry is removed; the numeric
value 1 is left commented out so any config that had it set silently
falls back to Auto via the load-path validation.
2. In narrow depth scenes the label picker could pick a snap-step that
gave only one tick — the reported case was seeing just "5" floating
at the middle of the bar. Two changes:
- Split the step ladder in two. k_snap_ladder stays as it was
(finest 0.05 m) so the smoothing deadband is not defeated by
sub-cm jitter. k_label_ladder extends down to 0.001/0.002/0.005/
0.01/0.02 for label picking only — labels can safely be finer
than the snap grid.
- Walk k_label_ladder fine→coarse, take the smallest step with
tick_count <= 6. If that step yields fewer than 3 labels, fall
back one entry (finer) so a ~1 cm ruler shows sub-cm labels
instead of a single lonely tick.
- Decimals now derive from the picked step: >=1 -> 0, >=0.1 -> 1,
>=0.01 -> 2, smaller -> 3. A ruler over [4.99, 5.01] labels as
4.990, 4.995, 5.000, 5.005, 5.010.
- Add an overlap guard: skip a tick whose glyph would collide with
the previously placed one (font_size + 2 px). Prevents labels
from stacking on short bars, which was the likely cause of the
"only 5 visible" symptom.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@Nir-Az — both landed in
The label picker used a single "nice step" ladder starting at 0.05 m and capped at ≤5 labels. On a narrow ruler that could pick a step whose only tick multiple inside the range was one number in the middle (the reported "only 5" case). Fix in
The right-click popover on the color-map ruler button had three modes: Auto, Fixed 0-4 m (legacy), and Fixed custom range. The legacy option is gone — Auto covers what it was for, and Fixed custom can pin
|
Nir's feedback: a fixed ruler range saved on a D455 shouldn't follow you to a D435 or a D585. Switch the three ruler settings to the same per-device layout the post_processing entries already use: viewer_model.ruler.<device name>.<sensor name>.range_mode viewer_model.ruler.<device name>.<sensor name>.fixed_min viewer_model.ruler.<device name>.<sensor name>.fixed_max - Removed the ruler load from stream_model's ctor (still loads the global show_map_ruler there — that stays a viewer-wide preference). - Compute the key root in begin_stream once the subdevice is attached; store it on the stream_model. - Popover writes route through the per-device root and no-op if the root is empty (defensive; a depth stream always has one after begin_stream). - Reseed the smoothing state after loading so switching between two cameras doesn't inherit the previous scene's bounds. The old flat keys (viewer_model.ruler_range_mode / _fixed_min / _fixed_max) are no longer read or written; existing configs with those set are silently ignored, and each SKU starts from the Auto default until the user pins a custom range. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
sysrsbuild-gh-agentic
left a comment
There was a problem hiding this comment.
🤖 [rs-agentic] BUG — shared popup ID in multi-stream mode
static const char* const popup_id = "##ColorMapRulerPopup" is the same literal for every stream_model instance. When two depth streams are visible simultaneously (e.g. two devices), both ruler buttons share this ImGui popup ID inside the same window. Whichever stream_model::draw_stream_footer runs last in the frame "owns" the popup; right-clicking either button opens that instance's popup and mutates the other stream's ruler_mode/ruler_fixed_* — wrong stream gets its settings changed.
Fix: make the ID unique per stream, e.g.:
// use the stream_model address as a discriminator
const std::string popup_id = std::string("##ColorMapRulerPopup") + std::to_string(reinterpret_cast<uintptr_t>(this));or wrap the whole ruler block in ImGui::PushID(this) / ImGui::PopID() and use a bare "##ColorMapRulerPopup" — ImGui scopes it automatically.
Bot review flagged a real collision: with two visible depth streams (two devices), both ruler buttons share the same "##Color map" ID and both popovers share "##ColorMapRulerPopup". ImGui matches OpenPopup and BeginPopup by ID within the current stack, so right-clicking one button would open the OTHER stream's popover and mutate its ruler_mode / ruler_fixed_* — wrong stream gets the settings changed. Wrap the entire depth-stream ruler block in PushID(this)/PopID() so both the button ID and the popup ID hash with the stream_model pointer. No functional change with a single stream; multi-device setups now correctly target the clicked tile. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tracked by: RSDEV-14227
Overview
On close-range D401 scenes the color-map ruler kept labelling
0-4 meven when everything visible was at 30-50 cm. The oldcalculate_ruler_max_distancewas data-driven in principle (mean + 1.5·σ of visible depths), but then snapped up to the next 4 m grid multiple, so any scene with mean+1.5·σ ≤ 4 m always displayed exactly0..4 m. The bottom of the ruler was a hardcoded"0"regardless of the actual minimum depth.This change makes the ruler read the actual measured range, on a fine step ladder, without introducing SKU-specific behavior. The colored strip already sampled a subset of pixels (
skip_pixels_factor = 30) to color the bar — the new bounds computation reuses that samedistancesvector, so per-frame cost stays effectively unchanged.What changed
common/viewer.cpp, newcalculate_ruler_bounds): p05 / p95 of visible-depth samples with 5% headroom, snapped to a "nice" step from{0.05, 0.10, 0.25, 0.5, 1, 2, 5, 10, 20, 50, 100} m. Replacescalculate_ruler_max_distanceand its hardcoded 4 m grid.draw_color_ruler): the ruler now takes{min, max}instead of a single length. Labels come only from nice-step multiples inside the range — no forced min/max, no rounded-off duplicate at the top. Capped at 5 total ticks. Decimals derived from the step (≥1 → 0,≥0.1 → 1, else2), consistent across all labels. Depth-to-y clips pixels that fall outside the current window.common/stream-model.cpp): mode selector with three options, persisted throughconfig_file:common/stream-model.h): newruler_range_modeenum anddepth_ruler_state(EMA + snapped bounds) live onstream_model, so each depth stream keeps its own smoothing.common/device-model.h):viewer_model.ruler_range_mode,viewer_model.ruler_fixed_min,viewer_model.ruler_fixed_max.Not in scope
Test plan
0..0.5 mor0..0.25 mwindow; labels are0.25and0.5(or similar) rather than0-4.0..5 mwindow with labels1, 2, 3, 4, 5.🤖 Generated with Claude Code