Skip to content

minZ feature integration into libRS viewer#15027

Open
alexkunin-gh wants to merge 5 commits intorealsenseai:developmentfrom
alexkunin-gh:akunin/minz
Open

minZ feature integration into libRS viewer#15027
alexkunin-gh wants to merge 5 commits intorealsenseai:developmentfrom
alexkunin-gh:akunin/minz

Conversation

@alexkunin-gh
Copy link
Copy Markdown
Contributor

minZ feature integration into libRS viewer

@alexkunin-gh alexkunin-gh requested review from Nir-Az and Copilot May 6, 2026 07:41
}
else if( !res_values.empty() )
{
auto& res = res_values[ui.selected_res_id];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing res_values[ui.selected_res_id] without validating ui.selected_res_id may index out-of-bounds. Check that ui.selected_res_id is within [0, res_values.size()) before using it.

Details

✨ AI Reasoning
​A newly added availability predicate captures this and reads res_values[ui.selected_res_id] after only checking res_values.empty(). If ui.selected_res_id is out-of-range (negative or >= res_values.size()) this will index past the vector bounds and can cause a segmentation fault. The code elsewhere sets selected_res_id but it can be modified elsewhere (user/UI) or be uninitialized at times; accessing without validating the index increases crash risk.

🔧 How do I fix it?
Add null checks before dereferencing pointers, validate array bounds before access, avoid using pointers after free/delete, don't write to string literals, and prefer smart pointers in modern C++.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Copy link
Copy Markdown
Collaborator

@Nir-Az Nir-Az left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generated by rs-agentic

Branch naming: akunin/minz uses a username prefix, which violates the convention documented in .github/copilot-instructions.md (rule: short descriptive name, no username prefix). Please rename to something like minz-viewer-integration.

Comment thread common/device-model.cpp Outdated
}
catch (...)
{
if( !pb_available ) ImGui::EndDisabled();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generated by rs-agentic

Potential double EndDisabled() in the catch block — ImGui stack imbalance.

The normal code path already calls ImGui::EndDisabled() inside the if( !pb_available ) block a few lines above (around line 3083). If an exception is thrown after that call but still inside the try (e.g. from ImGui::PopStyleColor(5) or ImGui::PopFont()), the catch block here will call EndDisabled() a second time, leaving ImGui's internal disabled-state stack unbalanced and causing hard-to-debug rendering glitches.

Consider tracking whether EndDisabled has already been called with a local flag, or use an RAII guard:

struct DisabledGuard {
    bool active;
    DisabledGuard( bool a ) : active(a) { if (active) ImGui::BeginDisabled(true); }
    ~DisabledGuard() { if (active) ImGui::EndDisabled(); }
};
DisabledGuard dg( !pb_available );

This eliminates both the explicit BeginDisabled/EndDisabled calls and the duplicate-call hazard.

Comment thread common/min-z-depth-improver.cpp Outdated
{
_depth_mm_buf.resize( w * h );
for( int i = 0; i < w * h; ++i )
_depth_mm_buf[i] = static_cast< uint16_t >( raw[i] * scale_mm );
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generated by rs-agentic

Silent uint16_t overflow when scale_mm > 1.0.

raw[i] * scale_mm is computed in float and then truncated to uint16_t with no bounds check. When depth_units > 0.001 (e.g. depth_units = 0.01scale_mm = 10), any pixel value above 6553 will wrap around silently, corrupting the depth map passed to DepthRangeImprover.

Add a clamp before the cast:

_depth_mm_buf[i] = static_cast<uint16_t>(
    std::min( raw[i] * scale_mm, static_cast<float>( std::numeric_limits<uint16_t>::max() ) ) );


// get_data() returns const void* even for newly-allocated frames;
// exclusive ownership from allocate_video_frame above makes the write safe.
auto * dst = reinterpret_cast< uint16_t * >( const_cast< void * >( new_frame.get_data() ) );
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generated by rs-agentic

const_cast to write into a freshly-allocated frame is a fragile API-contract violation.

rs2::frame::get_data() returns const void*. Stripping const via const_cast relies on an undocumented internal guarantee that the buffer is actually mutable. While the comment is correct that exclusive ownership makes it currently safe, a future rs2 change (e.g., marking the buffer read-only in debug/ASAN builds, or pooled-frame reuse policy) could cause a silent write to a shared buffer or a protection fault.

A cleaner approach: build the converted values into _out_buf (already done) and then memcpy from _out_buf.data() using the existing std::memcpy path — which is already in place for the is_1mm branch. Alternatively, request a writable-frame accessor from the rs2 API owners.

{
// Safe to capture this: the lambda lives in model which lives in post_processing,
// a member of this subdevice_model — so the lambda cannot outlive its owner.
model->available = [this]()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generated by rs-agentic

Raw this capture in a long-lived lambda stored in a shared_ptr.

The comment documents the current lifetime guarantee, but model is stored in a shared_ptr<processing_block_model> inside post_processing. If that shared_ptr is ever copied or moved outside its current owner (e.g. into a callback, cache, or async task in a future refactor), this will dangle without any compile-time or runtime warning.

A more robust pattern — if subdevice_model inherits from std::enable_shared_from_this — is:

auto weak_self = weak_from_this();
model->available = [weak_self]()
{
    auto self = weak_self.lock();
    if( !self ) return false;
    // use self-> instead of this->
    ...
};

If enable_shared_from_this is not available here, at minimum add a static_assert or comment referencing the specific invariant that must hold.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR integrates an optional “MinZ” depth-improvement processing block into the RealSense Viewer, intended for Jetson/aarch64 builds that have the external rs-enhanced-depth package installed.

Changes:

  • Adds an aarch64-only CMake option (BUILD_WITH_MINZ) to locate and link rs-enhanced-depth headers/libs and enable BUILD_WITH_MINZ compilation.
  • Introduces a viewer-side MinZ adapter (min_z_depth_improver) and an rs2::filter wrapper (minz_filter) and registers it in the per-sensor post-processing chain.
  • Extends the post-processing UI model to support “availability” predicates and disabled-tooltips, and uses them to gate MinZ based on active streams/resolution.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tools/realsense-viewer/CMakeLists.txt Adds aarch64-only MinZ build option, finds headers/libs under MINZ_ROOT, and links the external library.
common/CMakeLists.txt Adds MinZ adapter/filter sources to the viewer common source list.
common/min-z-depth-improver.h Declares a viewer-side adapter around the external MinZ/DepthRangeImprover implementation.
common/min-z-depth-improver.cpp Implements frameset handling, lazy init from calibration, and depth replacement in framesets.
common/minz-filter.h Adds an rs2::filter wrapper to insert MinZ into the existing post-processing chain.
common/subdevice-model.cpp Registers the MinZ processing block and provides UI availability gating/tooltips.
common/processing-block-model.h Adds optional availability predicate + tooltip to the processing block UI model.
common/device-model.cpp Disables the processing-block toggle in the UI when processing_block_model::is_available() is false and shows tooltip when hovered.

Comment thread common/min-z-depth-improver.cpp Outdated

try
{
_impl = std::make_unique< rs_depth::DepthRangeImprover >( cal );
Comment on lines +33 to +40
int w = ir_left.get_width();
int h = ir_left.get_height();
if( ! _impl || w != _init_width || h != _init_height )
if( ! init( ir_left, ir_right ) )
return f;

auto new_depth = run( fs, ir_left, ir_right, depth, src );
if( ! new_depth )
Comment on lines +200 to +230
// Resolution check — VGA (640x480) minimum
if( ui.is_multiple_resolutions )
{
// Per-stream resolutions: check depth and IR independently
auto check = [&]( rs2_stream stream ) {
auto it = ui.selected_stream_to_res.find( stream );
if( it == ui.selected_stream_to_res.end() ) return false;
return it->second.first >= 640 && it->second.second >= 480;
};
if( !check( RS2_STREAM_DEPTH ) || !check( RS2_STREAM_INFRARED ) )
return false;
}
else if( !res_values.empty() )
{
auto& res = res_values[ui.selected_res_id];
if( res.first < 640 || res.second < 480 )
return false;
}

bool depth = false, ir1 = false, ir2 = false;
for( auto& p : profiles )
{
auto it = stream_enabled.find( p.unique_id() );
if( it == stream_enabled.end() || !it->second ) continue;
if( p.stream_type() == RS2_STREAM_DEPTH ) depth = true;
else if( p.stream_type() == RS2_STREAM_INFRARED && p.stream_index() == 1 ) ir1 = true;
else if( p.stream_type() == RS2_STREAM_INFRARED && p.stream_index() == 2 ) ir2 = true;
}
return depth && ir1 && ir2;
};
model->unavailable_tooltip = "Depth, IR Left/Right streams have to be enabled at VGA or higher resolution";
Comment thread common/device-model.cpp Outdated
Comment on lines 3007 to 3095
@@ -3075,11 +3078,20 @@ namespace rs2
}
}

if( !pb_available )
{
ImGui::EndDisabled();
if( ImGui::IsItemHovered( ImGuiHoveredFlags_AllowWhenDisabled )
&& !pb->unavailable_tooltip.empty() )
RsImGui::CustomTooltip( "%s", pb->unavailable_tooltip.c_str() );
}

ImGui::PopStyleColor(5);
ImGui::PopFont();
}
catch (...)
{
if( !pb_available ) ImGui::EndDisabled();
ImGui::PopStyleColor(5);
Comment thread tools/realsense-viewer/CMakeLists.txt Outdated
set(RS_VIEWER_LIBS ${GTK3_LIBRARIES} Threads::Threads realsense2-gl)

if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
option(BUILD_WITH_MINZ "Enable MinZ depth improvement (Jetson only, requires rs-enhanced-depth deb)" OFF)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread tools/realsense-viewer/CMakeLists.txt Outdated
Comment thread tools/realsense-viewer/CMakeLists.txt Outdated

set(RS_VIEWER_LIBS ${GTK3_LIBRARIES} Threads::Threads realsense2-gl)

if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we take all of this section and place it in a seperate .cmake file that we import and use here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but then it'll be different design vs OpenVino and the rest

Comment thread tools/realsense-viewer/CMakeLists.txt
Comment thread CMake/lrs_options.cmake
@Nir-Az
Copy link
Copy Markdown
Collaborator

Nir-Az commented May 6, 2026

@alexkunin-gh how do we test this works?
Should we deploy the library on libci machines?
If so, who will update it?
Lets speak

throw new InvalidOperationException($"Could not find a sensor matching {extension}");
}

/// <summary>Returns the first depth sensor on the device.</summary>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple one-line sensor helper methods (e.g., FirstDepthSensor, FirstColorSensor, FirstMotionSensor, FirstFisheyeSensor) duplicate the same thin wrapper pattern. Consider consolidating to a single parameterized method or remove redundant wrappers.

Details

✨ AI Reasoning
​The change adds several one-line wrapper methods that all follow the same pattern: each returns First() or a typed wrapper of that call. These methods are introduced together in the same file and are highly similar; this is a case of trivial repetition of identical logic across multiple small methods. Consolidation (for example, exposing a single method with an Extension parameter or using a single typed factory) could reduce maintenance surface. The duplication was introduced by this PR and appears in the same file as the additions.

🔧 How do I fix it?
Delete extra code. Extract repeated code sequences into reusable functions or methods. Use loops or data structures to eliminate repetitive patterns.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment thread common/minz-filter.h Outdated

public:
minz_filter()
: rs2::filter( [this]( rs2::frame f, rs2::frame_source & src )
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lambda in minz_filter ctor captures 'this' and dereferences member _improver later; if the callback is invoked after minz_filter is destroyed (or before construction completes), the captured 'this' will be dangling and dereferencing it can segfault. Avoid capturing 'this' into a callback stored in base class or ensure the callback cannot be invoked beyond the object's lifetime.

Details

✨ AI Reasoning
​A constructor passes a lambda that captures 'this' to the base rs2::filter constructor. The lambda later dereferences member _improver via that captured this. If the stored callback is invoked after the minz_filter object has been destroyed (or before the derived ctor completed), the captured this becomes dangling and dereferencing _improver will cause a segmentation fault. The issue was introduced by adding minz_filter with a lambda capture of 'this' in the base initializer. This is a classic use-after-free/dangling pointer pattern that can crash at runtime.

🔧 How do I fix it?
Add null checks before dereferencing pointers, validate array bounds before access, avoid using pointers after free/delete, don't write to string literals, and prefer smart pointers in modern C++.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants