Skip to content

usb: add MinZ embedded filter (Improved Close Range Depth) on D555 - demo - #15176

Merged
remibettan merged 10 commits into
realsenseai:developmentfrom
remibettan:lrs-embedded-minz-usb-demo
Jun 14, 2026
Merged

remibettan merged 10 commits into
realsenseai:developmentfrom
remibettan:lrs-embedded-minz-usb-demo

Conversation

@remibettan

Copy link
Copy Markdown
Contributor

Overview: Add MinZ (HDRD near-range depth) embedded filter support for D555 over USB — toggle only (demo). Surfaces in the viewer's Embedded Filters panel as "Improved Close Range Depth" with a single Enable checkbox, matching the DDS UI shape.

Tracked on [RSDEV-11827]

Target branch: This PR targets the integration branch embedded_minZ (same one as the DDS PR #15174). The integration branch will be promoted to development once the MinZ FW/SW work is fully aligned across teams.

Based on: the DDS implementation in PR #15174 (commits already on embedded_minZ). This PR adds the USB transport path on top, re-using the public-API surface (RS2_EMBEDDED_FILTER_TYPE_MINZ, RS2_OPTION_EMBEDDED_FILTER_ENABLED) and the "Improved Close Range Depth" UI label introduced there.

Why "demo"

The current D555 firmware honors only the enable field of the 38-byte dppc_ctl XU payload (spec §2.2); the other three parameters (downscale_ratio, disparity_shift, threshold) can be set on the wire but are ignored. Exposing the full 4-parameter API over USB before FW supports it would be misleading. This branch therefore implements a deliberately reduced toggle-only surface — same UI shape as the DDS path, just with one option instead of four. The full multi-parameter promotion is described in doc/minz-usb-demo-design.md §7.

Summary

  • New minz_xu_option class (src/ds/d500/d500-minz-embedded-filter.{h,cpp}) wraps the 38-byte dppc_ctl payload on Depth XU selector 0x14. set() does GET_CUR → mutate params[0] (enable) → SET_CUR, preserving the FW's current ratio/shift/threshold defensively. query() returns params[0] as a float.
  • New d500_minz_embedded_filter concrete (minz_embedded_filter base from the DDS branch) registers a single RS2_OPTION_EMBEDDED_FILTER_ENABLED option backed by minz_xu_option.
  • d500_depth_sensor gets an embedded-filters container + add_embedded_filter() + get_supported_embedded_filters() override — mirrors the dds_depth_sensor_proxy shape so the viewer's query_embedded_filters() picks it up.
  • d500_device::create_depth_device instantiates the filter on D555 only. Important: create_depth_device runs before d500_device::init() populates _pid, so the PID gate reads directly from the all_device_infos parameter (same pattern as d400-device.cpp:509).
  • Zero public-API additions — re-uses the RS2_EMBEDDED_FILTER_TYPE_MINZ enum and the RS2_OPTION_EMBEDDED_FILTER_ENABLED option that the DDS PR dds: add MinZ embedded filter (Improved Close Range Depth) for D555 #15174 already declared, plus the "Improved Close Range Depth" UI label already overridden in to-string.cpp.

Files touched

  • src/ds/d500/d500-minz-embedded-filter.{h,cpp} — new
  • src/ds/d500/d500-device.{h,cpp} — d500_depth_sensor embedded-filters container + D555 registration
  • src/ds/d500/CMakeLists.txt — register new sources
  • unit-tests/live/d500/test-usb-minz.py — new pytest (markers test:device D555, test:donotrun:!nightly; no dds context)

Test plan

  • Built realsense-viewer locally on Windows (VS 2022, x64); link clean, no warnings on the new sources.
  • Manually verified on D555 over USB: the Embedded Filters panel under the Stereo Module now shows "Improved Close Range Depth" with one Enable checkbox.
  • New pytest unit-tests/live/d500/test-usb-minz.py run locally against a live D555 over USB. Result: 4 of 5 test cases pass, 1 fails; 14 of 16 assertions pass. The remaining failure is firmware-side:
    • MinZ embedded filter metadata member — no depth frame carries the embedded_filters metadata key over USB, so the metadata-gated callback never fires (10s timer expires). Different shape from the DDS failure (DDS frames do carry the key, just with bit 5 = 0). Pending FW team response.
  • Host plumbing checks all green: filter discovery (get_embedded_filter(rs.embedded_filter_type.improved_close_range_depth)), single-option enumeration, range [0,1] step 1, default 0, set/get round-trip on XU 0x14, invalid-value rejection on 2.0 and -1.0.

What is NOT in this PR

Per doc/minz-usb-demo-design.md §6, deferred until FW supports per-parameter XU:

  • The full 4-parameter rs2_set_minz_control / STMinZControl C API (spec §2.6)
  • USB exposure of downscale_ratio / disparity_shift / threshold
  • D585 inclusion (spec §6 marks D585 as "Planned"; demo gates on D555_PID only)

@Nir-Az Nir-Az left a comment

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.

Automated Code Review — rs-agentic bot

Reviewed the USB MinZ embedded filter implementation for D555. Overall the structure is clean and the intent is well-documented. Several issues were flagged below — mix of bugs, enhancements, and test safety concerns.

Missing artifact: doc/minz-usb-demo-design.md is referenced in code comments and the PR description (§7) but is not included in this PR. Without it, the design rationale and promotion path for the full multi-parameter API are undocumented in the repo. Please add the file or update the comments to reference an external ticket/wiki.

⚠️ This comment was auto-generated by the rs-agentic bot

sizeof( payload ) ) )
{
throw invalid_value_exception( rsutils::string::from()
<< "MinZ get_xu(0x14) failed before set; errno=" << strerror( errno ) );

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.

BUG — strerror(errno) is unreliable here

errno is not guaranteed to be set by UVC XU calls. On Windows the UVC stack uses HRESULT/GetLastError(), not errno, so this will almost always print a misleading or stale errno value (e.g. 0 / "Success"). On Linux, errno may be set by the underlying xioctl inside backend-v4l2, but there is a TOCTOU risk inside a lambda.

The existing ds-options.cpp has the same pattern, but it is still a latent bug. Prefer:

  1. Log without errno detail: "MinZ get_xu(0x14) failed before set".
  2. Have the UVC wrapper return/throw a platform-native error string.

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - dropped the errno/strerror from the message (kept just MinZ get_xu(0x14) failed before set). Applied to the other two occurrences in the same file as well.

sizeof( payload ) ) )
{
throw invalid_value_exception( rsutils::string::from()
<< "MinZ set_xu(0x14) failed; errno=" << strerror( errno ) );

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.

BUG — same strerror(errno) reliability issue as the get_xu failure above

errno is not guaranteed to reflect the UVC set_xu failure, especially on Windows. The error message will likely be misleading.

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - dropped strerror(errno) from the set_xu failure message.

{
auto ep = _ep.lock();
if( ! ep )
return 0.f;

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.

BUG — Inconsistency: silent 0.f return vs. throw in set()

set() (line 54) throws invalid_value_exception when the endpoint has been destroyed (_ep.lock() returns null). query() silently returns 0.f in the same situation. This can hide a use-after-destroy scenario: a caller may interpret 0.f as "filter is disabled" when the sensor is actually gone.

Suggestion:

if( !ep )
    throw invalid_value_exception( "MinZ: depth sensor not alive for query" );

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - query() now throws invalid_value_exception("MinZ: depth sensor not alive for query") when _ep.lock() returns null, mirroring the set() behavior. Use-after-destroy is no longer silently reported as 'filter disabled'.

sizeof( payload ) ) )
{
throw invalid_value_exception( rsutils::string::from()
<< "MinZ get_xu(0x14) failed; errno=" << strerror( errno ) );

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.

BUG — third occurrence of strerror(errno) reliability issue

Same concern as line 72 — errno is not reliably set after UVC XU operations. See comment at line 72.

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - dropped strerror(errno) from the query get_xu failure message as well.

Comment thread src/ds/d500/d500-minz-embedded-filter.h Outdated
{
public:
explicit d500_minz_embedded_filter( std::weak_ptr< uvc_sensor > raw_depth_ep );
virtual ~d500_minz_embedded_filter() = default;

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.

Enhancement — redundant virtual destructor

minz_embedded_filter (the base class in src/proc/minz-embedded-filter.h) already declares virtual ~minz_embedded_filter() = default;. Redeclaring it here adds noise without effect. Simply remove this line — the virtual-ness is inherited.

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - removed the redundant virtual ~d500_minz_embedded_filter() = default; (inherited from minz_embedded_filter).

Comment thread src/ds/d500/d500-minz-embedded-filter.h Outdated
explicit d500_minz_embedded_filter( std::weak_ptr< uvc_sensor > raw_depth_ep );
virtual ~d500_minz_embedded_filter() = default;

inline rs2_embedded_filter_type get_type() const override { return RS2_EMBEDDED_FILTER_TYPE_MINZ; }

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.

Enhancement — redundant inline

Methods defined inside a class body are implicitly inline per the C++ standard (C++14 [dcl.fct.spec]). The inline keyword here adds noise without effect. Consistent with the other inline method on line 29 in the same file (get_range()) which correctly omits it.

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - dropped the redundant inline keyword on the in-class get_type() definition.

Comment thread src/ds/d500/d500-device.cpp Outdated
// so we expose just the toggle. See doc/minz-usb-demo-design.md.
// NOTE: this->get_pid() is not populated yet during create_depth_device() -
// _pid is assigned in d500_device::init() which runs AFTER. Read it from the device-info parameter.
if( ! all_device_infos.empty() && all_device_infos.front().pid == ds::D555_PID )

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.

Potential Issue — PID check on all_device_infos.front() may not be the right interface entry

all_device_infos contains entries for all UVC interfaces of the device (MI 0 = Depth, MI 3 = RGB, etc.). The rest of create_depth_device() consistently uses filter_by_mi(all_device_infos, 0).front() before reading interface-specific fields. Here we read .pid from the unfiltered list's first element, which works because pid is the same across MI entries — but it is inconsistent with the surrounding code style and fragile if the ordering changes.

Suggestion for consistency:

auto depth_devs = filter_by_mi( all_device_infos, 0 );
if( !depth_devs.empty() && depth_devs.front().pid == ds::D555_PID )
{
    depth_ep->add_embedded_filter( std::make_shared< d500_minz_embedded_filter >( raw_depth_ep ) );
}

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - now reads the PID from filter_by_mi(all_device_infos, 0).front().pid (the depth MI=0 interface entry), matching the convention used elsewhere in this file.


def stream_and_check_minz_filter():
global waiting_for_test
depth_sensor.open(depth_profile)

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.

BUG — sensor not closed on exception (resource leak)

stream_and_check_minz_filter() calls depth_sensor.open() then depth_sensor.start() but has no try/finally guard. If depth_sensor.start() raises (e.g., device in wrong state), depth_sensor.close() is never called, leaving the sensor open for the subsequent test closures and causing them to fail for unrelated reasons.

Suggestion:

def stream_and_check_minz_filter():
    global waiting_for_test
    depth_sensor.open(depth_profile)
    try:
        depth_sensor.start(minz_check_callback)
        wait_for_frames_timer.start()
        waiting_for_test = True
        while waiting_for_test and not wait_for_frames_timer.has_expired():
            time.sleep(0.5)
        test.check(not wait_for_frames_timer.has_expired())
        depth_sensor.stop()
    finally:
        depth_sensor.close()

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - stream_and_check_minz_filter() now wraps the streaming in nested try/finally blocks so stop() and close() always run even if start() raises.

Comment thread unit-tests/live/d500/test-usb-minz.py Outdated
and p.stream_type() == rs.stream.depth
and p.format() == rs.format.z16
and p.as_video_stream_profile().width() == 640
and p.as_video_stream_profile().height() == 360)

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.

Potential Issue — bare next() raises StopIteration with no diagnostic

If the connected D555 does not advertise a 640x360@30fps Z16 depth profile, next(p for p in ...) will raise a bare StopIteration that gives no hint about which profile was missing. The test:device D555 tag guarantees the device, but not a specific profile list.

Suggestion:

depth_profile = next(
    (p for p in depth_sensor.profiles
     if p.fps() == 30
     and p.stream_type() == rs.stream.depth
     and p.format() == rs.format.z16
     and p.as_video_stream_profile().width() == 640
     and p.as_video_stream_profile().height() == 360),
    None)
if not test.check(depth_profile is not None,
                  "640x360@30fps Z16 depth profile not found — aborting"):
    test.print_results_and_exit()

This comment was auto-generated by the rs-agentic bot

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.

Fixed in 198b9cf - next(...) now uses a default of None and we explicitly log.f(...) a clear message naming the missing profile (640x360@30fps Z16) before the test would crash on a bare StopIteration.

remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 9, 2026
…r(errno), throw on null _ep in query, use filter_by_mi for D555 PID gate, drop redundant virtual/inline, harden pytest with try/finally + profile-lookup diagnostic
@remibettan

Copy link
Copy Markdown
Contributor Author

Regarding the top-level note about the missing doc/minz-usb-demo-design.md: per project convention in this branch, design docs are kept locally (untracked) and not committed alongside the implementation. Updated the code comment in d500-device.cpp in 198b9cf to reference PR #15176 instead of the file. The promotion path to the full multi-parameter API is summarized in the PR description itself (What is NOT in this PR section).

@remibettan
remibettan requested a review from OhadMeir June 9, 2026 12:03
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 9, 2026
…r(errno), throw on null _ep in query, use filter_by_mi for D555 PID gate, drop redundant virtual/inline, harden pytest with try/finally + profile-lookup diagnostic
@remibettan
remibettan force-pushed the lrs-embedded-minz-usb-demo branch from d607463 to 903888c Compare June 9, 2026 14:18
@remibettan
remibettan changed the base branch from embedded_minZ to development June 9, 2026 14:18
Comment thread src/proc/close-range-embedded-filter.h Resolved
Comment thread src/ds/d500/d500-device.h Resolved
Comment thread src/ds/d500/d500-device.cpp Resolved
Comment thread src/ds/d500/d500-device.cpp Outdated
// _pid is assigned in d500_device::init() which runs AFTER. Read the PID from the
// depth (MI=0) interface entry directly, matching the convention used elsewhere here.
auto depth_infos = filter_by_mi( all_device_infos, 0 );
if( ! depth_infos.empty() && depth_infos.front().pid == ds::D555_PID )

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move to relevant class, not in base. Can be a feature (implementing feature_interface) registered by relevant devices

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.

Done in 4041473 - moved the registration into a new close_range_filter_feature (src/ds/features/close-range-filter-feature.{h,cpp}) following the existing feature_interface pattern (alongside amplitude_factor_feature, auto_exposure_roi_feature, etc.). Registered conditionally in d500_device::register_features() via get_pid() == ds::D555_PID.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still registered in base class (d500_device) should be registered by supporting devices only (rsd555_device) and for D555 case should also be registered only if FW version supports (greater than X)

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.

Fixed in 3759316 - moved the registration out of d500_device::register_features() into the d555_device constructor (src/ds/d500/d500-factory.cpp). Added the FW gate: registered only when d500_device::_fw_version >= firmware_version("7.58.39807.10573"). The base d500_device no longer knows about close-range; D585/D585S devices won't see the feature, and a D555 on older FW gets nothing.

Comment thread src/ds/d500/d500-device.cpp Resolved
# test:donotrun:!nightly
# test:device D555

import pyrealsense2 as rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why can't we test DDS and USB together? Should test using public interface

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.

Conceptually yes - testing against the public embedded_filter interface (get_embedded_filter, get_supported_options, set/get on whatever options are present, metadata bit) would work for both transports. The behaviour differs only in:

  • DDS connection -> 4 options exposed (Enable, Downscale Ratio, Disparity Shift, Threshold)
  • USB connection -> 1 option exposed (Enable, demo only)
  • DDS test needs the test:donotrun:!dds marker; USB test must not have it

A unified test could discriminate via RS2_CAMERA_INFO_CONNECTION_TYPE at runtime and assert the right option count + run the appropriate subset. Trade-off: the conditional obscures what's actually being tested per environment, and the two test markers cleanly route each one to its CI lane today.

For this demo PR I'd keep them separate; once the USB path gains the full 4-option surface and the two converge in scope, the connection-type-discriminator merge becomes the right call. Happy to merge now if you'd prefer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think a unified test is better. Note - new test should be implemented as pytest tests.
Can skip non-relevant tests based on connection type/FW version/etc..

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.

Acknowledged - I agree a unified pytest with skips based on connection type and FW version is the right long-term shape. Two practical considerations for this PR: (1) the existing test-dds-embedded-filters.py (decimation/temporal/close-range when DDS lands all 4 options) and the new test-usb-close-range.py (USB toggle only) are structurally different enough that merging them right now would be a sizeable test refactor on top of the production-code changes already in flight. (2) Both files use the rspy + pytest harness used elsewhere in unit-tests/live/d500/ - so they ARE pytest tests already. I'd like to keep them separate in this PR and open a follow-up to unify them once the USB path has parity with DDS (full 4-option surface). Acceptable?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pytest tests should start with pytest prefix and don't use "# test:...".
They are both related and should be merged in this PR. We don't know if and when USB will support the extra controls.
Consider checking by get_supported_options and testing only available options. Downside - might succeed when expected options are not present.

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.

Done in 3dae619 - merged the two tests into unit-tests/live/d500/pytest-close-range.py and deleted test-dds-close-range.py and test-usb-close-range.py. Highlights:

  • Pytest-native: test_* functions instead of test.closure blocks; pytest.raises instead of test.check_throws; pytest_check for soft assertions where appropriate.
  • Parametrized on device: pytestmark = [pytest.mark.device_each("D555", "D585"), pytest.mark.context("nightly")]. No # test: directives anywhere.
  • Dynamic option discovery: the option-specific tests (downscale_ratio, disparity_shift, threshold) check rs.option.X in get_supported_options() and pytest.skip() when the option isn't exposed on the current transport. So USB (1-option) runs only the Enable + metadata tests, DDS (4-option) runs everything.
  • FW gating via filter presence: instead of an explicit FW version check, the test calls get_embedded_filter(...) and pytest.skip() if the filter isn't there (production code only registers it when FW supports it). One source of truth for the FW gate, no duplication between production and test.
  • Metadata test is parametrized over enable_value in [0, 1] so we get two sub-tests in the report instead of one combined check.

remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 10, 2026
…p virtual inheritance; refactor close-range registration into feature_interface; switch _embedded_filters to vector; trim comments; name dppc_ctl wire constants
@remibettan
remibettan requested a review from OhadMeir June 10, 2026 10:01
close_range_filter_feature::close_range_filter_feature( d500_depth_sensor & depth_sensor,
std::weak_ptr< uvc_sensor > raw_depth_ep )
{
depth_sensor.add_embedded_filter( std::make_shared< d500_close_range_embedded_filter >( raw_depth_ep ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Passed in as weak_ptr but creates another shared_ptr inside. Better to just pass shared_ptr as parameter

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.

Fixed in 3759316 - feature ctor now takes std::shared_ptr< uvc_sensor > const & directly. The chain is now shared_ptr (from get_raw_depth_sensor()) through the feature to the filter ctor (which still stores as weak_ptr internally - matches the existing uvc_xu_option<T> pattern).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we can pass only the depth_sensor and get the raw sensor internally. Possible?

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.

Done in 3dae619 - feature ctor now takes only d500_depth_sensor &. The raw uvc_sensor is derived inside via dynamic_pointer_cast<uvc_sensor>(depth_sensor.get_raw_sensor()). Both call sites (d555_device, rs_d585_device) updated accordingly.

remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 10, 2026
…ure registration from d500_device base to d555_device; gate on FW >= 7.58.39807.10573; pass uvc_sensor as shared_ptr through feature ctor
Comment thread src/ds/d500/d500-factory.cpp Outdated
// Improved Close Range Depth - D555 only, USB toggle gated on FW support.
if( d500_device::_fw_version >= firmware_version( "7.58.39807.10573" ) )
{
register_feature( std::make_shared< close_range_filter_feature >(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Passing d500_device::get_raw_depth_sensor() directly to the feature may propagate a null shared_ptr; validate the returned shared_ptr before constructing/ registering the feature.

Details

✨ AI Reasoning
​The code registers the close_range_filter_feature by passing d500_device::get_raw_depth_sensor() (a shared_ptr<uvc_sensor>) into close_range_filter_feature. A shared_ptr can be empty; the feature immediately forwards this pointer to construct a d500_close_range_embedded_filter without validating it. If get_raw_depth_sensor() returns nullptr, downstream code (inside d500_close_range_embedded_filter or its use of the uvc_sensor) may dereference the null shared_ptr, causing a segmentation fault. The change introduced the new feature registration and the unvalidated propagation of the raw endpoint pointer, increasing risk compared to prior code.

🔧 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
Contributor Author

Choose a reason for hiding this comment

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

@AikidoSec ignore: matches existing pattern in this PR's other registrations and elsewhere in d500-factory.cpp; the duplication is small and intentional (different per-PID FW gate versions), and the null check is unnecessary because get_raw_depth_sensor() always returns a valid sensor at this point in construction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Based on your feedback, we ignored this issue because of the following reason:

matches existing pattern in this PR's other registrations and elsewhere in d500-factory.cpp; the duplication is small and intentional (different per-PID FW gate versions), and the null check is unnecessary because get_raw_depth_sensor() always returns a valid sensor at this point in construction.

{
ds_advanced_mode_base::initialize_advanced_mode( this );

// Improved Close Range Depth - USB toggle gated on FW support.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicate feature-registration block added in a constructor; extract common registration helper to avoid repeating firmware-gated register_feature logic.

Details

✨ AI Reasoning
​Two newly added conditional blocks perform the same business action: check firmware version and call register_feature with a close_range_filter_feature constructed from a depth sensor and raw depth sensor. This repeats non-trivial feature-registration logic in multiple constructors, increasing maintenance burden and risk of divergence.

🔧 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

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.

@AikidoSec ignore: matches existing pattern in this PR's other registrations and elsewhere in d500-factory.cpp; the duplication is small and intentional (different per-PID FW gate versions), and the null check is unnecessary because get_raw_depth_sensor() always returns a valid sensor at this point in construction.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Based on your feedback, we ignored this issue because of the following reason:

matches existing pattern in this PR's other registrations and elsewhere in d500-factory.cpp; the duplication is small and intentional (different per-PID FW gate versions), and the null check is unnecessary because get_raw_depth_sensor() always returns a valid sensor at this point in construction.

remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 11, 2026
…nto pytest-close-range.py with dynamic option discovery; simplify close_range_filter_feature ctor to take only d500_depth_sensor

close_range_filter_feature::close_range_filter_feature( d500_depth_sensor & depth_sensor )
{
auto raw_depth_ep = std::dynamic_pointer_cast< uvc_sensor >( depth_sensor.get_raw_sensor() );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dynamic_pointer_cast result 'raw_depth_ep' is used without null check; if get_raw_sensor() isn't a uvc_sensor the shared_ptr will be null and downstream use may dereference it. Add a null check before passing to d500_close_range_embedded_filter.

Details

✨ AI Reasoning
​The new feature constructor performs a dynamic_pointer_cast from depth_sensor.get_raw_sensor() to a shared_ptr<uvc_sensor> and immediately passes the result into d500_close_range_embedded_filter without checking for null. If get_raw_sensor() doesn't hold a uvc_sensor, dynamic_pointer_cast returns nullptr; if d500_close_range_embedded_filter's constructor dereferences the pointer (likely, to access the UVC endpoint), this will cause a segmentation fault. The code introduced the cast and the unconditional use of its result in the new feature implementation, so this is an introduced 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
Contributor Author

Choose a reason for hiding this comment

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

@AikidoSec ignore: nullptr checked upstream

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Based on your feedback, we ignored this issue because of the following reason:

nullptr checked upstream

remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…r(errno), throw on null _ep in query, use filter_by_mi for D555 PID gate, drop redundant virtual/inline, harden pytest with try/finally + profile-lookup diagnostic
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…p virtual inheritance; refactor close-range registration into feature_interface; switch _embedded_filters to vector; trim comments; name dppc_ctl wire constants
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…ure registration from d500_device base to d555_device; gate on FW >= 7.58.39807.10573; pass uvc_sensor as shared_ptr through feature ctor
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…nto pytest-close-range.py with dynamic option discovery; simplify close_range_filter_feature ctor to take only d500_depth_sensor
@remibettan
remibettan force-pushed the lrs-embedded-minz-usb-demo branch from 3dae619 to 5bf6cf0 Compare June 14, 2026 06:54
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…r(errno), throw on null _ep in query, use filter_by_mi for D555 PID gate, drop redundant virtual/inline, harden pytest with try/finally + profile-lookup diagnostic
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…p virtual inheritance; refactor close-range registration into feature_interface; switch _embedded_filters to vector; trim comments; name dppc_ctl wire constants
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…ure registration from d500_device base to d555_device; gate on FW >= 7.58.39807.10573; pass uvc_sensor as shared_ptr through feature ctor
remibettan added a commit to remibettan/librealsense that referenced this pull request Jun 14, 2026
…nto pytest-close-range.py with dynamic option discovery; simplify close_range_filter_feature ctor to take only d500_depth_sensor
@remibettan
remibettan force-pushed the lrs-embedded-minz-usb-demo branch from 5bf6cf0 to 47e7108 Compare June 14, 2026 07:51
remibettan and others added 10 commits June 14, 2026 14:22
…r(errno), throw on null _ep in query, use filter_by_mi for D555 PID gate, drop redundant virtual/inline, harden pytest with try/finally + profile-lookup diagnostic
… includes, class names, test variables, CMakeLists
…p virtual inheritance; refactor close-range registration into feature_interface; switch _embedded_filters to vector; trim comments; name dppc_ctl wire constants
…ure registration from d500_device base to d555_device; gate on FW >= 7.58.39807.10573; pass uvc_sensor as shared_ptr through feature ctor
…nto pytest-close-range.py with dynamic option discovery; simplify close_range_filter_feature ctor to take only d500_depth_sensor
…construction); flip option_range default to 1; update pytest expectation
The streaming check was already disabled because the SDK-level
default-on path calls set(1) at ctor time. Aikido flagged the
commented-out lines as an accidental commit; remove them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@remibettan
remibettan force-pushed the lrs-embedded-minz-usb-demo branch from 6473627 to b2b2ed2 Compare June 14, 2026 11:22
@remibettan
remibettan requested a review from OhadMeir June 14, 2026 11:23

@OhadMeir OhadMeir left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great PR

@remibettan
remibettan merged commit 85262bc into realsenseai:development Jun 14, 2026
30 checks passed
@remibettan
remibettan deleted the lrs-embedded-minz-usb-demo branch July 8, 2026 12:05
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