Repository navigation
usb: add MinZ embedded filter (Improved Close Range Depth) on D555 - demo - #15176
Conversation
Nir-Az
left a comment
There was a problem hiding this comment.
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 ) ); |
There was a problem hiding this comment.
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:
- Log without errno detail:
"MinZ get_xu(0x14) failed before set". - Have the UVC wrapper return/throw a platform-native error string.
This comment was auto-generated by the rs-agentic bot
There was a problem hiding this comment.
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 ) ); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Fixed in 198b9cf - dropped strerror(errno) from the set_xu failure message.
| { | ||
| auto ep = _ep.lock(); | ||
| if( ! ep ) | ||
| return 0.f; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ) ); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Fixed in 198b9cf - dropped strerror(errno) from the query get_xu failure message as well.
| { | ||
| public: | ||
| explicit d500_minz_embedded_filter( std::weak_ptr< uvc_sensor > raw_depth_ep ); | ||
| virtual ~d500_minz_embedded_filter() = default; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Fixed in 198b9cf - removed the redundant virtual ~d500_minz_embedded_filter() = default; (inherited from minz_embedded_filter).
| 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; } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Fixed in 198b9cf - dropped the redundant inline keyword on the in-class get_type() definition.
| // 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 ) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
…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
|
Regarding the top-level note about the missing |
…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
d607463 to
903888c
Compare
| // _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 ) |
There was a problem hiding this comment.
Move to relevant class, not in base. Can be a feature (implementing feature_interface) registered by relevant devices
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
| # test:donotrun:!nightly | ||
| # test:device D555 | ||
|
|
||
| import pyrealsense2 as rs |
There was a problem hiding this comment.
Why can't we test DDS and USB together? Should test using public interface
There was a problem hiding this comment.
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:!ddsmarker; 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.
There was a problem hiding this comment.
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..
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 oftest.closureblocks;pytest.raisesinstead oftest.check_throws;pytest_checkfor 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) checkrs.option.X in get_supported_options()andpytest.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(...)andpytest.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.
…p virtual inheritance; refactor close-range registration into feature_interface; switch _embedded_filters to vector; trim comments; name dppc_ctl wire constants
| 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 ) ); |
There was a problem hiding this comment.
Passed in as weak_ptr but creates another shared_ptr inside. Better to just pass shared_ptr as parameter
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
I think we can pass only the depth_sensor and get the raw sensor internally. Possible?
There was a problem hiding this comment.
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.
…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
| // 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 >( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
✅ 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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
✅ 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.
…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() ); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@AikidoSec ignore: nullptr checked upstream
There was a problem hiding this comment.
✅ Based on your feedback, we ignored this issue because of the following reason:
nullptr checked upstream
…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
…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
3dae619 to
5bf6cf0
Compare
…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
…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
5bf6cf0 to
47e7108
Compare
…demo, toggle only
…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
…the rationale instead
… 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>
6473627 to
b2b2ed2
Compare
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]
Why "demo"
The current D555 firmware honors only the
enablefield of the 38-bytedppc_ctlXU 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 indoc/minz-usb-demo-design.md§7.Summary
minz_xu_optionclass (src/ds/d500/d500-minz-embedded-filter.{h,cpp}) wraps the 38-bytedppc_ctlpayload on Depth XU selector0x14.set()doesGET_CUR→ mutateparams[0](enable) →SET_CUR, preserving the FW's current ratio/shift/threshold defensively.query()returnsparams[0]as a float.d500_minz_embedded_filterconcrete (minz_embedded_filterbase from the DDS branch) registers a singleRS2_OPTION_EMBEDDED_FILTER_ENABLEDoption backed byminz_xu_option.d500_depth_sensorgets an embedded-filters container +add_embedded_filter()+get_supported_embedded_filters()override — mirrors thedds_depth_sensor_proxyshape so the viewer'squery_embedded_filters()picks it up.d500_device::create_depth_deviceinstantiates the filter on D555 only. Important:create_depth_deviceruns befored500_device::init()populates_pid, so the PID gate reads directly from theall_device_infosparameter (same pattern asd400-device.cpp:509).RS2_EMBEDDED_FILTER_TYPE_MINZenum and theRS2_OPTION_EMBEDDED_FILTER_ENABLEDoption 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 into-string.cpp.Files touched
src/ds/d500/d500-minz-embedded-filter.{h,cpp}— newsrc/ds/d500/d500-device.{h,cpp}—d500_depth_sensorembedded-filters container + D555 registrationsrc/ds/d500/CMakeLists.txt— register new sourcesunit-tests/live/d500/test-usb-minz.py— new pytest (markerstest:device D555,test:donotrun:!nightly; noddscontext)Test plan
unit-tests/live/d500/test-usb-minz.pyrun 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 theembedded_filtersmetadata 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.get_embedded_filter(rs.embedded_filter_type.improved_close_range_depth)), single-option enumeration, range[0,1]step1, default0, set/get round-trip on XU 0x14, invalid-value rejection on2.0and-1.0.What is NOT in this PR
Per
doc/minz-usb-demo-design.md§6, deferred until FW supports per-parameter XU:rs2_set_minz_control/STMinZControlC API (spec §2.6)D555_PIDonly)