From 216a02752378354f9be1af371daa017274aee750 Mon Sep 17 00:00:00 2001 From: Aaron Brown Date: Thu, 17 Oct 2024 11:33:15 +1300 Subject: [PATCH 01/44] Remove excess foreach_uvc_device in v4l_uvc_device constructor --- src/linux/backend-v4l2.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/linux/backend-v4l2.cpp b/src/linux/backend-v4l2.cpp index d4147a7d51..c293babdf2 100644 --- a/src/linux/backend-v4l2.cpp +++ b/src/linux/backend-v4l2.cpp @@ -1205,7 +1205,10 @@ namespace librealsense } v4l_uvc_device::v4l_uvc_device(const uvc_device_info& info, bool use_memory_map) - : _name(""), _info(), + : _name(info.id), + _device_path(info.device_path), + _device_usb_spec(info.conn_spec), + _info(info), _is_capturing(false), _is_alive(true), _is_started(false), @@ -1217,19 +1220,6 @@ namespace librealsense _buf_dispatch(use_memory_map), _frame_drop_monitor(DEFAULT_KPI_FRAME_DROPS_PERCENTAGE) { - foreach_uvc_device([&info, this](const uvc_device_info& i, const std::string& name) - { - if (i == info) - { - _name = name; - _info = i; - _device_path = i.device_path; - _device_usb_spec = i.conn_spec; - } - }); - if (_name == "") - throw linux_backend_exception("device is no longer connected!"); - _named_mtx = std::unique_ptr(new named_mutex(_name, 5000)); } From 1708e79c534e87dcf0f867cdc76ca43ae30e06ef Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Thu, 31 Oct 2024 09:58:09 +0200 Subject: [PATCH 02/44] remove libusb leftovers --- third-party/libusb/CMakeLists-remove.txt | 96 ------------------------ 1 file changed, 96 deletions(-) delete mode 100644 third-party/libusb/CMakeLists-remove.txt diff --git a/third-party/libusb/CMakeLists-remove.txt b/third-party/libusb/CMakeLists-remove.txt deleted file mode 100644 index 69fb41c2cd..0000000000 --- a/third-party/libusb/CMakeLists-remove.txt +++ /dev/null @@ -1,96 +0,0 @@ -# our current cmake version is 3.8 -cmake_minimum_required(VERSION 3.8) - -project(usb) - - set(LIBUSB_C - libusb/core.c - libusb/descriptor.c - libusb/hotplug.c - libusb/io.c - libusb/strerror.c - libusb/sync.c - ) - -if(WIN32) - LIST(APPEND LIBUSB_C - libusb/os/threads_windows.c - libusb/os/poll_windows.c - libusb/os/windows_winusb.c - libusb/os/windows_nt_common.c - libusb/os/windows_usbdk.c - ) -elseif (APPLE) - LIST(APPEND LIBUSB_C - libusb/os/poll_posix.c - libusb/os/threads_posix.c - libusb/os/darwin_usb.c - ) -elseif(ANDROID) - LIST(APPEND LIBUSB_C - libusb/os/linux_usbfs.c - libusb/os/poll_posix.c - libusb/os/threads_posix.c - libusb/os/linux_netlink.c - ) -else() - LIST(APPEND LIBUSB_C - libusb/os/linux_usbfs.c - libusb/os/poll_posix.c - libusb/os/threads_posix.c - libusb/os/linux_udev.c - ) -endif() - -set(LIBUSB_H - libusb/libusb.h -) - -include_directories( - libusb - libusb/os -) - -add_library(usb STATIC ${LIBUSB_C} ${LIBUSB_H}) - -if(WIN32) - set_target_properties (usb PROPERTIES - FOLDER "3rd Party" - ) - include_directories(msvc) - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO - CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /utf-8") -endif() - -if(ANDROID) - include_directories(android) -endif() - -if(APPLE) - find_library(corefoundation_lib CoreFoundation) - find_library(iokit_lib IOKit) - target_include_directories(usb PRIVATE XCode) - TARGET_LINK_LIBRARIES(usb objc ${corefoundation_lib} ${iokit_lib}) -endif() - -if((NOT APPLE) AND (NOT ANDROID) AND (NOT WIN32)) - TARGET_LINK_LIBRARIES(usb udev) -endif() - -#set_target_properties(usb PROPERTIES PREFIX "") - -install(TARGETS ${PROJECT_NAME} - EXPORT realsense2Targets - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin - ) From 576e4e465c128202a2b7ba303fd56bb5cc686d1f Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Thu, 31 Oct 2024 12:29:04 +0200 Subject: [PATCH 03/44] bump version to 2.57.0.0 --- include/librealsense2/rs.h | 2 +- package.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/librealsense2/rs.h b/include/librealsense2/rs.h index 8a5cd517a2..d6059c85c1 100644 --- a/include/librealsense2/rs.h +++ b/include/librealsense2/rs.h @@ -24,7 +24,7 @@ extern "C" { #include "h/rs_sensor.h" #define RS2_API_MAJOR_VERSION 2 -#define RS2_API_MINOR_VERSION 56 +#define RS2_API_MINOR_VERSION 57 #define RS2_API_PATCH_VERSION 0 #define RS2_API_BUILD_VERSION 0 diff --git a/package.xml b/package.xml index 1f5b5a93fe..6e69a69193 100644 --- a/package.xml +++ b/package.xml @@ -6,7 +6,7 @@ librealsense2 - 2.56.0 + 2.57.0 Library for controlling and capturing data from the Intel(R) RealSense(TM) D400 devices. From bac49efc8547422a4d4299cda4439b789cd506eb Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Sun, 3 Nov 2024 09:44:10 +0200 Subject: [PATCH 04/44] update get_gvd --- src/ds/d500/d500-device.cpp | 20 ++++++++++++++++---- src/ds/d500/d500-device.h | 2 ++ src/hw-monitor.cpp | 31 +++++++++++++++++++++++++++++-- src/hw-monitor.h | 5 ++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/ds/d500/d500-device.cpp b/src/ds/d500/d500-device.cpp index b88393d107..9778971fc5 100644 --- a/src/ds/d500/d500-device.cpp +++ b/src/ds/d500/d500-device.cpp @@ -382,7 +382,8 @@ namespace librealsense _depth_stream(new stream(RS2_STREAM_DEPTH)), _left_ir_stream(new stream(RS2_STREAM_INFRARED, 1)), _right_ir_stream(new stream(RS2_STREAM_INFRARED, 2)), - _color_stream(nullptr) + _color_stream(nullptr), + _hw_monitor_response(std::make_shared()) { _depth_device_idx = add_sensor( create_depth_device( dev_info->get_context(), dev_info->get_group().uvc_devices ) ); @@ -407,13 +408,13 @@ namespace librealsense _hw_monitor = std::make_shared( std::make_shared( std::make_shared( *raw_sensor, depth_xu, DS5_HWMONITOR ), - raw_sensor), std::make_shared()); + raw_sensor), _hw_monitor_response); } else { _hw_monitor = std::make_shared< hw_monitor_extended_buffers >( std::make_shared< locked_transfer >( get_backend()->create_usb_device( group.usb_devices.front() ), - raw_sensor ), std::make_shared()); + raw_sensor ), _hw_monitor_response); } _ds_device_common = std::make_shared(this, _hw_monitor); @@ -455,8 +456,19 @@ namespace librealsense bool advanced_mode = false; bool usb_modality = true; group_multiple_fw_calls(depth_sensor, [&]() { + + // D500 device can get enumerated before the whole HW in the camera is ready. + // Since GVD gather all information from all the HW, it might need some more time to finish all hand shakes. + // on this case it will return HW_NOT_READY error code. + // Note: D500 error codes list is different than D400. + + const std::set< int32_t > gvd_retry_errors{ _hw_monitor_response->HW_NOT_READY }; + + _hw_monitor->get_gvd( gvd_buff.size(), + gvd_buff.data(), + ds::fw_cmd::GVD, + &gvd_retry_errors ); - _hw_monitor->get_gvd(gvd_buff.size(), gvd_buff.data(), ds::fw_cmd::GVD); get_gvd_details(gvd_buff, &gvd_parsed_fields); _device_capabilities = ds_caps::CAP_ACTIVE_PROJECTOR | ds_caps::CAP_RGB_SENSOR | ds_caps::CAP_IMU_SENSOR | diff --git a/src/ds/d500/d500-device.h b/src/ds/d500/d500-device.h index a0c14ea43c..a3dba10e59 100644 --- a/src/ds/d500/d500-device.h +++ b/src/ds/d500/d500-device.h @@ -51,6 +51,8 @@ namespace librealsense d500_device( std::shared_ptr< const d500_info > const & ); + std::shared_ptr _hw_monitor_response; + std::vector send_receive_raw_data(const std::vector& input) override; std::vector build_command(uint32_t opcode, diff --git a/src/hw-monitor.cpp b/src/hw-monitor.cpp index aa6dce3681..6a01b540a2 100644 --- a/src/hw-monitor.cpp +++ b/src/hw-monitor.cpp @@ -2,6 +2,7 @@ // Copyright(c) 2015 Intel Corporation. All Rights Reserved. #include "hw-monitor.h" #include "types.h" +#include #include #include #include @@ -226,10 +227,36 @@ namespace librealsense } - void hw_monitor::get_gvd(size_t sz, unsigned char* gvd, uint8_t gvd_cmd) const + void hw_monitor::get_gvd( size_t sz, + unsigned char * gvd, + uint8_t gvd_cmd, + const std::set< int32_t > * retry_error_codes ) const { command command(gvd_cmd); - auto data = send(command); + hwmon_response_type response; + auto data = send( command, &response ); + // If we get an error code that match to the error code defined as require retry, + // we will retry the command until it succeed or we reach a timeout + bool should_retry = retry_error_codes && retry_error_codes->find( response ) != retry_error_codes->end(); + if( should_retry ) + { + constexpr size_t RETRIES = 50; + for( int i = 0; i < RETRIES; ++i ) + { + LOG_WARNING( "GVD not ready - retrying GET_GVD command" ); + std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); + data = send( command, &response ); + if( response == _hwmon_response->success_value() ) + break; + // If we failed after 'RETRIES' retries or it is less `RETRIES` and the error + // code is not in the retry list than , raise an exception + if( i >= ( RETRIES - 1 ) || retry_error_codes->find( response ) == retry_error_codes->end() ) + throw io_exception( rsutils::string::from() + << "error in querying GVD, error:" + << _hwmon_response->hwmon_error2str( response ) ); + + } + } auto minSize = std::min(sz, data.size()); std::memcpy( gvd, data.data(), minSize ); } diff --git a/src/hw-monitor.h b/src/hw-monitor.h index 3f99198ba3..bc0b5221da 100644 --- a/src/hw-monitor.h +++ b/src/hw-monitor.h @@ -176,7 +176,10 @@ namespace librealsense uint8_t const * data = nullptr, size_t dataLength = 0); - void get_gvd(size_t sz, unsigned char* gvd, uint8_t gvd_cmd) const; + void get_gvd( size_t sz, + unsigned char * gvd, + uint8_t gvd_cmd, + const std::set< int32_t > * retry_error_codes = nullptr ) const; template std::string get_firmware_version_string( const std::vector< uint8_t > & buff, From 634410817e48a80437861780d37435c7fcdc3fed Mon Sep 17 00:00:00 2001 From: ohadmeir Date: Sun, 3 Nov 2024 12:19:31 +0200 Subject: [PATCH 05/44] Fix DDS stream tagging --- src/dds/rs-dds-device-proxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dds/rs-dds-device-proxy.cpp b/src/dds/rs-dds-device-proxy.cpp index 10503106f5..94bd86e0ae 100644 --- a/src/dds/rs-dds-device-proxy.cpp +++ b/src/dds/rs-dds-device-proxy.cpp @@ -556,9 +556,9 @@ void dds_device_proxy::tag_default_profile_of_stream( // Superset = picked up in pipeline with enable_all_stream() config // We want color and depth to be default, and add infrared as superset int tag = PROFILE_TAG_SUPERSET; - if( stream->type_string() == "color" || stream->type_string() == "depth" ) + if( strcmp( stream->type_string(), "color" ) == 0 || strcmp( stream->type_string(), "depth" ) == 0 ) tag |= PROFILE_TAG_DEFAULT; - else if( stream->type_string() != "infrared" || profile->get_stream_index() >= 2 ) + else if( strcmp( stream->type_string(), "ir" ) != 0 || profile->get_stream_index() >= 2 ) return; // leave untagged profile->tag_profile( tag ); } From fb6299e6c22df32f5f49ec0c7c73795c95cb7dab Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:02:40 +0200 Subject: [PATCH 06/44] add get_opcode_string to sdk --- include/librealsense2/rs.h | 2 ++ src/core/debug.h | 2 ++ src/ds/d400/d400-device.cpp | 5 +++++ src/ds/d400/d400-device.h | 1 + src/ds/d500/d500-device.cpp | 5 +++++ src/ds/d500/d500-device.h | 1 + src/realsense.def | 1 + src/rs.cpp | 10 ++++++++++ 8 files changed, 27 insertions(+) diff --git a/include/librealsense2/rs.h b/include/librealsense2/rs.h index d6059c85c1..b767a2225d 100644 --- a/include/librealsense2/rs.h +++ b/include/librealsense2/rs.h @@ -136,6 +136,8 @@ float rs2_depth_frame_get_distance(const rs2_frame* frame_ref, int x, int y, rs2 */ rs2_time_t rs2_get_time( rs2_error** error); +void rs2_hw_monitor_get_opcode_string(int opcode, char* buffer, size_t buffer_size,rs2_device* device, rs2_error** error); + #ifdef __cplusplus } #endif diff --git a/src/core/debug.h b/src/core/debug.h index 1d5956e4aa..5d232b068d 100644 --- a/src/core/debug.h +++ b/src/core/debug.h @@ -4,6 +4,7 @@ #include "extension.h" #include +#include namespace librealsense { @@ -18,6 +19,7 @@ namespace librealsense uint32_t param4 = 0, uint8_t const * data = nullptr, size_t dataLength = 0) const = 0; + virtual std::string get_opcode_string(int opcode) const = 0; }; MAP_EXTENSION(RS2_EXTENSION_DEBUG, librealsense::debug_interface); diff --git a/src/ds/d400/d400-device.cpp b/src/ds/d400/d400-device.cpp index 5018b15992..d63f190bf4 100644 --- a/src/ds/d400/d400-device.cpp +++ b/src/ds/d400/d400-device.cpp @@ -148,6 +148,11 @@ namespace librealsense return result; } + std::string d400_device::get_opcode_string(int opcode) const + { + return ds::d400_hwmon_response().hwmon_error2str(opcode); + } + class d400_depth_sensor : public synthetic_sensor , public video_sensor_interface diff --git a/src/ds/d400/d400-device.h b/src/ds/d400/d400-device.h index 0816b54d03..03de43540e 100644 --- a/src/ds/d400/d400-device.h +++ b/src/ds/d400/d400-device.h @@ -66,6 +66,7 @@ namespace librealsense std::vector backup_flash( rs2_update_progress_callback_sptr callback) override; void update_flash(const std::vector& image, rs2_update_progress_callback_sptr callback, int update_mode) override; bool check_fw_compatibility(const std::vector& image) const override; + std::string get_opcode_string(int opcode) const override; protected: std::shared_ptr _ds_device_common; diff --git a/src/ds/d500/d500-device.cpp b/src/ds/d500/d500-device.cpp index 9778971fc5..d456ab0123 100644 --- a/src/ds/d500/d500-device.cpp +++ b/src/ds/d500/d500-device.cpp @@ -116,6 +116,11 @@ namespace librealsense throw not_implemented_exception("D500 device does not support unsigned FW update"); } + std::string d500_device::get_opcode_string(int opcode) const + { + return _hw_monitor_response->hwmon_error2str(opcode); + } + class d500_depth_sensor : public synthetic_sensor, public video_sensor_interface, public depth_stereo_sensor, public roi_sensor_base { public: diff --git a/src/ds/d500/d500-device.h b/src/ds/d500/d500-device.h index a3dba10e59..3679e16f5c 100644 --- a/src/ds/d500/d500-device.h +++ b/src/ds/d500/d500-device.h @@ -72,6 +72,7 @@ namespace librealsense std::vector backup_flash( rs2_update_progress_callback_sptr callback ) override; void update_flash(const std::vector& image, rs2_update_progress_callback_sptr callback, int update_mode) override; bool check_fw_compatibility( const std::vector& image ) const override { return true; }; + std::string get_opcode_string(int opcode) const override; protected: std::shared_ptr _ds_device_common; diff --git a/src/realsense.def b/src/realsense.def index 06f4507611..607307aaf6 100644 --- a/src/realsense.def +++ b/src/realsense.def @@ -438,3 +438,4 @@ EXPORTS rs2_project_color_pixel_to_depth_pixel rs2_get_calibration_config rs2_set_calibration_config + rs2_hw_monitor_get_opcode_string \ No newline at end of file diff --git a/src/rs.cpp b/src/rs.cpp index ba1dd7649c..514c2e0b2b 100644 --- a/src/rs.cpp +++ b/src/rs.cpp @@ -4452,3 +4452,13 @@ void rs2_set_calibration_config( auto_calib->set_calibration_config(calibration_config_json_str); } HANDLE_EXCEPTIONS_AND_RETURN(, device, calibration_config_json_str) +void rs2_hw_monitor_get_opcode_string(int opcode, char* buffer, size_t buffer_size, + rs2_device* device, + rs2_error** error) BEGIN_API_CALL +{ + VALIDATE_NOT_NULL(device); + auto device_interface = VALIDATE_INTERFACE(device->device, librealsense::debug_interface); + strncpy(buffer, device_interface->get_opcode_string(opcode).c_str(), buffer_size); + //return device_interface->get_opcode_string(opcode).c_str(); // TODO: ask Nir and implement this function on d400/d500 if needed +} +HANDLE_EXCEPTIONS_AND_RETURN(, device) \ No newline at end of file From 026f44bcfb477cc3c9e6c27568202bd5fdf10273 Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:16:19 +0300 Subject: [PATCH 07/44] python script to create html from lrs_options --- CMake/lrs_options.cmake | 6 ++ doc/build-flags.css | 91 +++++++++++++++++++++++ doc/build-flags.ico | Bin 0 -> 178298 bytes doc/lrs_options-to-html.py | 144 +++++++++++++++++++++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 doc/build-flags.css create mode 100644 doc/build-flags.ico create mode 100644 doc/lrs_options-to-html.py diff --git a/CMake/lrs_options.cmake b/CMake/lrs_options.cmake index cc2b21de13..07d3dae62b 100644 --- a/CMake/lrs_options.cmake +++ b/CMake/lrs_options.cmake @@ -48,3 +48,9 @@ option(BUILD_PC_STITCHING "Build pointcloud-stitching example" OFF) option(BUILD_WITH_DDS "Access camera devices through DDS topics (requires CMake 3.16.3)" OFF) option(BUILD_RS2_ALL "Build realsense2-all static bundle containing all realsense libraries (with BUILD_SHARED_LIBS=OFF)" ON) + +## This file is also being used to generate our build flags document at https://intelrealsense.github.io/librealsense/build-flags-docs/build-flags.html +## Formatting notes for this file: +## Options are listed as: | [comment] | +## regular comments should be ABOVE their relevent option +## use double # for comments that should not show in the options doc diff --git a/doc/build-flags.css b/doc/build-flags.css new file mode 100644 index 0000000000..57e78b2593 --- /dev/null +++ b/doc/build-flags.css @@ -0,0 +1,91 @@ +/* This file is used in lrs_options-to-html, creating the html build-flags.html file we use to keep + the flags updated */ +body { + font-family: 'Roboto', Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 0; +} + +.container { + width: 80%; + margin: 0 auto; + padding: 30px; +} + +h1 { + text-align: center; + margin-bottom: 20px; +} + +h2 { + text-align: center; + margin-bottom: 30px; +} + +table { + width: 100%; + border-collapse: collapse; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); + border-radius: 8px; + overflow: visible; +} + +th, td { + padding: 16px; + text-align: left; + border: 1px solid #ddd; +} + +th { + background-color: #5873e0; + color: white; +} + +tr:hover { + background-color: #f0f0f0; +} + +code { + padding: 2px 4px; + border-radius: 4px; + font-family: 'Roboto Mono', monospace; +} +/* --- Tooltip --- */ +.tooltip { + position: relative; + display: block; +} + +.tooltip .tooltip-text { + visibility: hidden; + /*width: 120px;*/ + background-color: #555; + color: #fff; + text-align: center; + padding: 5%; + border-radius: 6px; + position: absolute; + z-index: 1; + bottom: 125%; + left: 50%; + margin-left: -60px; + opacity: 0; + transition: opacity 0.3s; +} + +.tooltip .tooltip-text::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: #555 transparent transparent transparent; +} + +.tooltip:hover .tooltip-text { + visibility: visible; + opacity: 1; +} diff --git a/doc/build-flags.ico b/doc/build-flags.ico new file mode 100644 index 0000000000000000000000000000000000000000..a98c8ab74db02bb17001bf9593c22d4e2310ea44 GIT binary patch literal 178298 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') + val = val.replace('OFF', 'OFF') + + # if more complicated conditions are being used, this can be disabled - + # if we see an option more than once at add_row, just set its value to be 'Environment dependant' + # this should handle if/elseif/else structures of cmake + if in_cond: + # text will say Environment dependant, and if hovered will show the condition + val = f'Environment dependant{val}
' + elif in_elseif: + # append condition to tooltip + val = table_rows[option][2].replace("
", f',
{val}
') + + elif in_else: + # append otherwise to tooltip + val = table_rows[option][2].replace("
", f',
{val} otherwise') + + return val + + +def add_row(option, description, value): + if option in table_rows: + # update value if option exists + table_rows[option][2] = value + else: + # add to table new option + table_rows[option] = [option, description, value] + + +with open('CMake/lrs_options.cmake', 'r') as file: + lines = file.readlines() + +table_rows = {} +in_cond = False +in_elseif = False +in_else = False +current_condition = None +current_comment = "" + +for line in lines: + if line.strip().startswith(('option(', 'set(')): + line = ' '.join(line.split()) # remove consecutive spaces + parts = line.strip().split(' ') + if line.strip().startswith('option('): + option = parts[0].strip('option(') + value = parts[-1].strip(")") # the last word in line is the value (ON/OFF by default) + + # concatenate rest of the line - expected to be just the description in quotes + description = ' '.join(parts[1:-1]).strip('"') + else: + option = parts[0].strip('set(') + value = parts[1] + value = f'{value}' + # parts[2] is expected to be 'CACHE' + vartype = parts[3] # INT, STRING ... + + # concatenate rest of the line - expected to be just the description in quotes + description = ' '.join(parts[4:-1]).strip('"') + f" (type {vartype})" + + if current_comment: + description += " " + current_comment + "" + current_comment = "" + + if in_cond or in_elseif: + value = f'{value} if {current_condition}' + + value = add_style(option, value) + add_row(option, description, value) + elif line.startswith('if'): + parts = line.strip().split('(', 1) + condition = parts[1][:-1] # remove last ")" - part of the 'if' + current_condition = condition + in_cond = True + elif line.startswith('elseif'): + parts = line.strip().split('(', 1) + condition = parts[1][:-1] # remove last ")" - part of the 'if' + current_condition = condition + in_cond = False + in_elseif = True + elif line.startswith('else'): + in_cond = False + in_elseif = False + in_else = True + elif line.startswith('endif'): + current_condition = None + in_cond = False + in_else = False + elif line.startswith('##'): + continue # ignore internal comments + elif line.startswith('#'): + current_comment += line.strip('# \n') + + +def format_dict_values(): + return ''.join( + f'\n ' + f'\n\t\n\t {option}'f'\n\t' + f'\n\t\n\t {description}\n\t' + f'\n\t\n\t {value}\n\t' + f'\n ' + for option, description, value in table_rows.values()) + +def get_sdk_version(): + version_info = {} + with open('include/librealsense2/rs.h', 'r') as file: + lines = file.readlines() + + for line in lines: + if line.startswith('#define RS2_API_MAJOR_VERSION'): + version_info['major'] = line.split()[2] + elif line.startswith('#define RS2_API_MINOR_VERSION'): + version_info['minor'] = line.split()[2] + elif line.startswith('#define RS2_API_PATCH_VERSION'): + version_info['patch'] = line.split()[2] + return 'Version ' + '.'.join(version_info.values()) + +html = f''' + + + Build Customization Flags + + + + +

+ + +''' + +with open('doc/build-flags.html', 'w') as file: + file.write(html) + print("build-flags.html generated") From defc911aeda845499d4013e70dc34f73bba88396 Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:44:38 +0200 Subject: [PATCH 08/44] use get_opcode_string to show error string --- common/output-model.cpp | 12 ++++++++++-- include/librealsense2/hpp/rs_device.hpp | 8 ++++++++ src/dds/rs-dds-device-proxy.cpp | 17 +++++++++++++++++ src/dds/rs-dds-device-proxy.h | 1 + src/rs.cpp | 4 ++-- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/common/output-model.cpp b/common/output-model.cpp index 937478a8b3..e1cd38e465 100644 --- a/common/output-model.cpp +++ b/common/output-model.cpp @@ -893,6 +893,7 @@ void output_model::add_log(rs2_log_severity severity, std::string filename, int void output_model::run_command(std::string command, device_models_list & device_models) { + std::string opcode_error_as_string = ""; try { if (to_lower(command) == "clear") @@ -990,7 +991,11 @@ void output_model::run_command(std::string command, device_models_list & device_ { found = true; auto res = dbg.send_and_receive_raw_data(buffer); - + if (res.data()) + { + int8_t opcode = *res.data(); + opcode_error_as_string = dbg.get_opcode_string(opcode); + } std::string response = rsutils::string::from() << "\n" << terminal_parser.parse_response(to_lower(command), res); add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response); } @@ -1006,7 +1011,10 @@ void output_model::run_command(std::string command, device_models_list & device_ } catch(const std::exception& ex) { - add_log( RS2_LOG_SEVERITY_ERROR, __FILE__, __LINE__, ex.what() ); + std::string foo = rsutils::string::from() << ex.what(); + if (opcode_error_as_string != "") + foo = rsutils::string::from() << foo << " (" << opcode_error_as_string << ")"; + add_log( RS2_LOG_SEVERITY_ERROR, __FILE__, __LINE__, foo); } } diff --git a/include/librealsense2/hpp/rs_device.hpp b/include/librealsense2/hpp/rs_device.hpp index 4068dc5a53..bc7af22454 100644 --- a/include/librealsense2/hpp/rs_device.hpp +++ b/include/librealsense2/hpp/rs_device.hpp @@ -1042,6 +1042,14 @@ namespace rs2 return results; } + + std::string get_opcode_string(int opcode) + { + rs2_error* e = nullptr; + char buffer[1024]; + rs2_hw_monitor_get_opcode_string(opcode, buffer, sizeof(buffer), _dev.get(), &e); + return std::string(buffer); + } }; class device_list diff --git a/src/dds/rs-dds-device-proxy.cpp b/src/dds/rs-dds-device-proxy.cpp index 94bd86e0ae..bc01659712 100644 --- a/src/dds/rs-dds-device-proxy.cpp +++ b/src/dds/rs-dds-device-proxy.cpp @@ -28,6 +28,7 @@ #include #include +#include using rsutils::json; @@ -578,6 +579,22 @@ void dds_device_proxy::hardware_reset() _dds_dev->send_control( control, &reply ); } +std::string dds_device_proxy::get_opcode_string(int opcode) const +{ + std::string product_line = get_info(RS2_CAMERA_INFO_PRODUCT_LINE); + if (product_line.find("D400") != std::string::npos) + { + // d400 device + return ds::d400_hwmon_response().hwmon_error2str(opcode); + } + else if (product_line.find("D500") != std::string::npos) + { + // d500 device + return ds::d500_hwmon_response().hwmon_error2str(opcode); + } + return ""; +} + std::vector< uint8_t > dds_device_proxy::send_receive_raw_data( const std::vector< uint8_t > & input ) { diff --git a/src/dds/rs-dds-device-proxy.h b/src/dds/rs-dds-device-proxy.h index f442b1f5b4..797cbf4e9a 100644 --- a/src/dds/rs-dds-device-proxy.h +++ b/src/dds/rs-dds-device-proxy.h @@ -84,6 +84,7 @@ class dds_device_proxy uint32_t param4 = 0, uint8_t const * data = nullptr, size_t dataLength = 0 ) const override; + std::string get_opcode_string(int opcode) const override; // updatable: unsigned, non-recovery-mode private: diff --git a/src/rs.cpp b/src/rs.cpp index 514c2e0b2b..5078ece181 100644 --- a/src/rs.cpp +++ b/src/rs.cpp @@ -4452,6 +4452,7 @@ void rs2_set_calibration_config( auto_calib->set_calibration_config(calibration_config_json_str); } HANDLE_EXCEPTIONS_AND_RETURN(, device, calibration_config_json_str) + void rs2_hw_monitor_get_opcode_string(int opcode, char* buffer, size_t buffer_size, rs2_device* device, rs2_error** error) BEGIN_API_CALL @@ -4459,6 +4460,5 @@ void rs2_hw_monitor_get_opcode_string(int opcode, char* buffer, size_t buffer_si VALIDATE_NOT_NULL(device); auto device_interface = VALIDATE_INTERFACE(device->device, librealsense::debug_interface); strncpy(buffer, device_interface->get_opcode_string(opcode).c_str(), buffer_size); - //return device_interface->get_opcode_string(opcode).c_str(); // TODO: ask Nir and implement this function on d400/d500 if needed } -HANDLE_EXCEPTIONS_AND_RETURN(, device) \ No newline at end of file +HANDLE_EXCEPTIONS_AND_RETURN(, device) From 180a91b9e49b128a3e82d74c7e234cd902156d95 Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:55:14 +0200 Subject: [PATCH 09/44] renaming --- common/output-model.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/output-model.cpp b/common/output-model.cpp index e1cd38e465..9ebe9b473a 100644 --- a/common/output-model.cpp +++ b/common/output-model.cpp @@ -1011,10 +1011,10 @@ void output_model::run_command(std::string command, device_models_list & device_ } catch(const std::exception& ex) { - std::string foo = rsutils::string::from() << ex.what(); + std::string error_string = rsutils::string::from() << ex.what(); if (opcode_error_as_string != "") - foo = rsutils::string::from() << foo << " (" << opcode_error_as_string << ")"; - add_log( RS2_LOG_SEVERITY_ERROR, __FILE__, __LINE__, foo); + error_string = rsutils::string::from() << error_string << " (" << opcode_error_as_string << ")"; + add_log( RS2_LOG_SEVERITY_ERROR, __FILE__, __LINE__, error_string); } } From 1b2ef8a00f241d33581867fae787f12e9c4e9ebf Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:52:54 +0200 Subject: [PATCH 10/44] move script, add GHA check and exception throw --- .github/workflows/static_analysis.yaml | 12 ++++++++++++ CMake/lrs_options.cmake | 12 ++++++------ doc/build-flags.ico | Bin 178298 -> 4117 bytes {doc => scripts}/lrs_options-to-html.py | 4 +++- 4 files changed, 21 insertions(+), 7 deletions(-) rename {doc => scripts}/lrs_options-to-html.py (96%) diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index b5840c5ab4..a811f1a0a9 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -194,3 +194,15 @@ jobs: echo "Error - The minimal CMake version required for LibRS is ${EXPECTED_CMAKE_MAJOR_VER}.${EXPECTED_CMAKE_MINOR_VER} but on this build the minimal CMake version that works is $CURRENT_CMAKE_MAJOR_VER.$CURRENT_CMAKE_MINOR_VER" exit 1 fi + + build_flags_docs: + name: "Generate build-flags.html" + timeout-minutes: 10 + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 #v3 + + - name: Build docs + run: | + python3 scripts/lrs_options-to-html.py diff --git a/CMake/lrs_options.cmake b/CMake/lrs_options.cmake index 07d3dae62b..316add802e 100644 --- a/CMake/lrs_options.cmake +++ b/CMake/lrs_options.cmake @@ -1,3 +1,9 @@ +## This file is also being used to generate our build flags document at https://intelrealsense.github.io/librealsense/build-flags-docs/build-flags.html +## Formatting notes for this file: +## Options are listed as: | [comment] | +## regular comments should be ABOVE their relevent option +## use double # for comments that should not show in the options doc + option(ENABLE_CCACHE "Build with ccache." ON) option(BUILD_WITH_CUDA "Enable CUDA" OFF) option(BUILD_GLSL_EXTENSIONS "Build GLSL extensions API" ON) @@ -48,9 +54,3 @@ option(BUILD_PC_STITCHING "Build pointcloud-stitching example" OFF) option(BUILD_WITH_DDS "Access camera devices through DDS topics (requires CMake 3.16.3)" OFF) option(BUILD_RS2_ALL "Build realsense2-all static bundle containing all realsense libraries (with BUILD_SHARED_LIBS=OFF)" ON) - -## This file is also being used to generate our build flags document at https://intelrealsense.github.io/librealsense/build-flags-docs/build-flags.html -## Formatting notes for this file: -## Options are listed as: | [comment] | -## regular comments should be ABOVE their relevent option -## use double # for comments that should not show in the options doc diff --git a/doc/build-flags.ico b/doc/build-flags.ico index a98c8ab74db02bb17001bf9593c22d4e2310ea44..ff97f560e56eea9c47bcd11610ddef91d55dd26f 100644 GIT binary patch literal 4117 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 literal 178298 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 Date: Mon, 11 Nov 2024 11:59:38 +0200 Subject: [PATCH 11/44] update comment --- scripts/lrs_options-to-html.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/lrs_options-to-html.py b/scripts/lrs_options-to-html.py index 629ac99437..e203acc826 100644 --- a/scripts/lrs_options-to-html.py +++ b/scripts/lrs_options-to-html.py @@ -91,8 +91,9 @@ def add_row(option, description, value): continue # ignore internal comments elif line.startswith('#'): current_comment += line.strip('# \n') - elif line.strip(): # if we've reached a non-empty line, throw an error - raise Exception(f"{i, line} cannot be handled") + elif line.strip(): + # if we reach a line that doesn't match the pattern, throw an error as it's not handled (shouldn't happen) + raise Exception(f"{i, line} not handled") def format_dict_values(): From eba3fd808481a26b708ef8c4db3856b662bb80e4 Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:38:44 +0200 Subject: [PATCH 12/44] get version number as a param --- scripts/lrs_options-to-html.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/lrs_options-to-html.py b/scripts/lrs_options-to-html.py index e203acc826..03ff168f59 100644 --- a/scripts/lrs_options-to-html.py +++ b/scripts/lrs_options-to-html.py @@ -1,3 +1,6 @@ +import sys + + def add_style(option, val): # colorize ON and OFF keywords val = val.replace('ON', 'ON') @@ -106,18 +109,14 @@ def format_dict_values(): for option, description, value in table_rows.values()) def get_sdk_version(): - version_info = {} - with open('include/librealsense2/rs.h', 'r') as file: - lines = file.readlines() - - for line in lines: - if line.startswith('#define RS2_API_MAJOR_VERSION'): - version_info['major'] = line.split()[2] - elif line.startswith('#define RS2_API_MINOR_VERSION'): - version_info['minor'] = line.split()[2] - elif line.startswith('#define RS2_API_PATCH_VERSION'): - version_info['patch'] = line.split()[2] - return 'Version ' + '.'.join(version_info.values()) + if len(sys.argv) > 1: + # the script can get the version number as a parameter + version = sys.argv[1] + return f'Version {version}' + else: + # if no parameter provided - no version number will be included + return "" + html = f''' From ea836e6358f218ce4875489e0a58de679437e882 Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:42:12 +0200 Subject: [PATCH 13/44] remove response size limitation --- src/rs.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rs.cpp b/src/rs.cpp index 5078ece181..95cc32a915 100644 --- a/src/rs.cpp +++ b/src/rs.cpp @@ -4013,7 +4013,9 @@ rs2_raw_data_buffer* rs2_terminal_parse_response(rs2_terminal_parser* terminal_p VALIDATE_NOT_NULL(command); VALIDATE_NOT_NULL(response); VALIDATE_LE(size_of_command, 1000); //bufer shall be less than 1000 bytes or similar - VALIDATE_LE(size_of_response, 5000);//bufer shall be less than 5000 bytes or similar + + // some commands may return a longer length as a response + //VALIDATE_LE(size_of_response, 5000);//bufer shall be less than 5000 bytes or similar std::string command_string; From 87d431a56cb3c3134f570880def906139362e2da Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Tue, 12 Nov 2024 16:43:42 +0200 Subject: [PATCH 14/44] remove log print for cmds with empty response --- common/output-model.cpp | 3 ++- src/terminal-parser.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/common/output-model.cpp b/common/output-model.cpp index 9ebe9b473a..b8506e8307 100644 --- a/common/output-model.cpp +++ b/common/output-model.cpp @@ -997,7 +997,8 @@ void output_model::run_command(std::string command, device_models_list & device_ opcode_error_as_string = dbg.get_opcode_string(opcode); } std::string response = rsutils::string::from() << "\n" << terminal_parser.parse_response(to_lower(command), res); - add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response); + if (response != "\n") + add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response); } } diff --git a/src/terminal-parser.cpp b/src/terminal-parser.cpp index 63a55ec7ce..0e272457ea 100644 --- a/src/terminal-parser.cpp +++ b/src/terminal-parser.cpp @@ -61,10 +61,11 @@ namespace librealsense data_vec.insert(data_vec.begin(), data.begin(), data.end()); return data_vec; } - else + else if (std::all_of(response.begin() + 1, response.end(), [](int x) { return x == 0; })) // if the response contains only the opcode, the response is empty { - return response; + return {}; } + return response; } vector terminal_parser::build_raw_command_data(const command_from_xml& command, const vector& params) const From 40a90a83bd836e7c654f122c3a80a7572f12fbdd Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Wed, 13 Nov 2024 15:06:02 +0200 Subject: [PATCH 15/44] support empty flash enumeration --- src/ds/d500/d500-device.cpp | 14 ++++++++++++-- src/ds/d500/d500-private.cpp | 32 +++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/ds/d500/d500-device.cpp b/src/ds/d500/d500-device.cpp index d456ab0123..85379bdb41 100644 --- a/src/ds/d500/d500-device.cpp +++ b/src/ds/d500/d500-device.cpp @@ -286,8 +286,18 @@ namespace librealsense float d500_device::get_stereo_baseline_mm() const // to be d500 adapted { using namespace ds; - auto table = check_calib(*_coefficients_table_raw); - return fabs(table->baseline); + float baseline = 100.0f; // so we will have a non zero value if cannot read from table + try + { + auto table = check_calib(*_coefficients_table_raw); + baseline = fabs(table->baseline); + } + catch( const std::exception &e ) + { + LOG_ERROR("Failed reading stereo baseline, using a default value --> " << e.what() ); + } + + return baseline; } std::vector d500_device::get_d500_raw_calibration_table(ds::d500_calibration_table_id table_id) const // to be d500 adapted diff --git a/src/ds/d500/d500-private.cpp b/src/ds/d500/d500-private.cpp index fb597f8d0e..dadaf447e4 100644 --- a/src/ds/d500/d500-private.cpp +++ b/src/ds/d500/d500-private.cpp @@ -43,18 +43,32 @@ namespace librealsense rs2_intrinsics get_d500_intrinsic_by_resolution(const vector& raw_data, d500_calibration_table_id table_id, uint32_t width, uint32_t height, bool is_symmetrization_enabled) { - switch (table_id) - { - case d500_calibration_table_id::depth_calibration_id: + + if (!raw_data.empty()) { - return get_d500_depth_intrinsic_by_resolution(raw_data, width, height, is_symmetrization_enabled); + switch (table_id) + { + case d500_calibration_table_id::depth_calibration_id: + { + return get_d500_depth_intrinsic_by_resolution(raw_data, width, height, is_symmetrization_enabled); + } + case d500_calibration_table_id::rgb_calibration_id: + { + return get_d500_color_intrinsic_by_resolution(raw_data, width, height); + } + default: + throw invalid_value_exception(rsutils::string::from() << "Parsing Calibration table type " << static_cast(table_id) << " is not supported"); + } } - case d500_calibration_table_id::rgb_calibration_id: + else // In case we got an empty table we will run with default values { - return get_d500_color_intrinsic_by_resolution(raw_data, width, height); - } - default: - throw invalid_value_exception(rsutils::string::from() << "Parsing Calibration table type " << static_cast(table_id) << " is not supported"); + LOG_ERROR("cannot read intrinsic values, setting defaults"); + rs2_intrinsics intrinsics = {0}; + intrinsics.height = height; + intrinsics.width = width; + intrinsics.ppx = intrinsics.fx = width / 2.f; + intrinsics.ppy = intrinsics.fy = height / 2.f; + return intrinsics; } } From ed7afed2891d5678166643692108473018101047 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Thu, 7 Nov 2024 17:23:58 +0200 Subject: [PATCH 16/44] rotate frames --- common/subdevice-model.cpp | 3 + include/librealsense2/h/rs_option.h | 1 + include/librealsense2/h/rs_processing.h | 6 + include/librealsense2/h/rs_types.h | 1 + include/librealsense2/hpp/rs_processing.hpp | 36 ++++ src/proc/CMakeLists.txt | 2 + src/proc/rotation-filter.cpp | 214 ++++++++++++++++++++ src/proc/rotation-filter.h | 41 ++++ src/realsense.def | 1 + src/rs.cpp | 10 + src/sensor.cpp | 2 + 11 files changed, 317 insertions(+) create mode 100644 src/proc/rotation-filter.cpp create mode 100644 src/proc/rotation-filter.h diff --git a/common/subdevice-model.cpp b/common/subdevice-model.cpp index 3a7914f6cb..734a1d65b9 100644 --- a/common/subdevice-model.cpp +++ b/common/subdevice-model.cpp @@ -215,6 +215,9 @@ namespace rs2 model->enable(false); } + if( shared_filter->is< rotation_filter >() ) + model->enable( false ); + if (shared_filter->is()) { if (s->supports(RS2_CAMERA_INFO_PRODUCT_ID)) diff --git a/include/librealsense2/h/rs_option.h b/include/librealsense2/h/rs_option.h index 10d03fe2e8..80c30bc853 100644 --- a/include/librealsense2/h/rs_option.h +++ b/include/librealsense2/h/rs_option.h @@ -125,6 +125,7 @@ extern "C" { RS2_OPTION_SOC_PVT_TEMPERATURE, /**< Temperature of PVT SOC */ RS2_OPTION_GYRO_SENSITIVITY,/**< Control of the gyro sensitivity level, see rs2_gyro_sensitivity for values */ RS2_OPTION_REGION_OF_INTEREST,/**< The rectangular area used from the streaming profile */ + RS2_OPTION_ROTATION,/**Rotates frames*/ RS2_OPTION_COUNT /**< Number of enumeration values. Not a valid input: intended to be used in for-loops. */ } rs2_option; diff --git a/include/librealsense2/h/rs_processing.h b/include/librealsense2/h/rs_processing.h index 745b72c54d..e13e1484a8 100644 --- a/include/librealsense2/h/rs_processing.h +++ b/include/librealsense2/h/rs_processing.h @@ -224,6 +224,12 @@ rs2_processing_block* rs2_create_align(rs2_stream align_to, rs2_error** error); */ rs2_processing_block* rs2_create_decimation_filter_block(rs2_error** error); +/** + * Creates post-processing filter block. This block accepts frames and applies rotation filter + * \param[out] error if non-null, receives any error that occurs during this call, otherwise, errors are ignored + */ +rs2_processing_block * rs2_create_rotation_filter_block( rs2_error ** error ); + /** * Creates Depth post-processing filter block. This block accepts depth frames, applies temporal filter * \param[out] error if non-null, receives any error that occurs during this call, otherwise, errors are ignored diff --git a/include/librealsense2/h/rs_types.h b/include/librealsense2/h/rs_types.h index 775fb5dd83..5543fa00fe 100644 --- a/include/librealsense2/h/rs_types.h +++ b/include/librealsense2/h/rs_types.h @@ -161,6 +161,7 @@ typedef enum rs2_extension RS2_EXTENSION_SOFTWARE_DEVICE, RS2_EXTENSION_SOFTWARE_SENSOR, RS2_EXTENSION_DECIMATION_FILTER, + RS2_EXTENSION_ROTATION_FILTER, RS2_EXTENSION_THRESHOLD_FILTER, RS2_EXTENSION_DISPARITY_FILTER, RS2_EXTENSION_SPATIAL_FILTER, diff --git a/include/librealsense2/hpp/rs_processing.hpp b/include/librealsense2/hpp/rs_processing.hpp index 30a2181c50..be2cc812c1 100644 --- a/include/librealsense2/hpp/rs_processing.hpp +++ b/include/librealsense2/hpp/rs_processing.hpp @@ -851,6 +851,42 @@ namespace rs2 } }; + class rotation_filter : public filter + { + public: + /** + * Create rotation filter + * Rotation filter performs rotation of the frames + */ + rotation_filter() + : filter( init(), 1 ) + { + } + + rotation_filter( filter f ) + : filter( f ) + { + rs2_error * e = nullptr; + if( ! rs2_is_processing_block_extendable_to( f.get(), RS2_EXTENSION_ROTATION_FILTER, &e ) && ! e ) + { + _block.reset(); + } + error::handle( e ); + } + + private: + friend class context; + + std::shared_ptr< rs2_processing_block > init() + { + rs2_error * e = nullptr; + auto block = std::shared_ptr< rs2_processing_block >( rs2_create_rotation_filter_block( &e ), + rs2_delete_processing_block ); + error::handle( e ); + return block; + } + }; + class temporal_filter : public filter { public: diff --git a/src/proc/CMakeLists.txt b/src/proc/CMakeLists.txt index 1d37afe0af..35784d6991 100644 --- a/src/proc/CMakeLists.txt +++ b/src/proc/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(${LRS_TARGET} "${CMAKE_CURRENT_LIST_DIR}/synthetic-stream.cpp" "${CMAKE_CURRENT_LIST_DIR}/syncer-processing-block.cpp" "${CMAKE_CURRENT_LIST_DIR}/decimation-filter.cpp" + "${CMAKE_CURRENT_LIST_DIR}/rotation-filter.cpp" "${CMAKE_CURRENT_LIST_DIR}/spatial-filter.cpp" "${CMAKE_CURRENT_LIST_DIR}/temporal-filter.cpp" "${CMAKE_CURRENT_LIST_DIR}/hdr-merge.cpp" @@ -49,6 +50,7 @@ target_sources(${LRS_TARGET} "${CMAKE_CURRENT_LIST_DIR}/occlusion-filter.h" "${CMAKE_CURRENT_LIST_DIR}/synthetic-stream.h" "${CMAKE_CURRENT_LIST_DIR}/decimation-filter.h" + "${CMAKE_CURRENT_LIST_DIR}/rotation-filter.h" "${CMAKE_CURRENT_LIST_DIR}/spatial-filter.h" "${CMAKE_CURRENT_LIST_DIR}/temporal-filter.h" "${CMAKE_CURRENT_LIST_DIR}/hdr-merge.h" diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp new file mode 100644 index 0000000000..35a1dbb92c --- /dev/null +++ b/src/proc/rotation-filter.cpp @@ -0,0 +1,214 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#include +#include "option.h" +#include "stream.h" +#include "core/video.h" +#include "proc/rotation-filter.h" + +namespace librealsense { + + const int rotation_min_val = -90; + const int rotation_max_val = 180; + const int rotation_default_val = 0; + const int rotation_step = 90; + + rotation_filter::rotation_filter() : + stream_filter_processing_block("Rotation Filter"), + _control_val(rotation_default_val), + _real_width(0), + _real_height(0), + _rotated_width(0), + _rotated_height(0) + { + auto rotation_control = std::make_shared< ptr_option< int > >( + rotation_min_val, + rotation_max_val, + rotation_step, + rotation_default_val, + &_control_val, "Rotation scale"); + + auto weak_rotation_control = std::weak_ptr< ptr_option< int > >( rotation_control ); + rotation_control->on_set( + [this, weak_rotation_control]( float val ) + { + auto strong_rotation_control = weak_rotation_control.lock(); + if(!strong_rotation_control) return; + + std::lock_guard lock(_mutex); + + if( ! strong_rotation_control->is_valid( val ) ) + throw invalid_value_exception( rsutils::string::from() + << "Unsupported rotation scale " << val << " is out of range." ); + + _value = val; + + }); + + register_option( RS2_OPTION_ROTATION, rotation_control ); + } + + rs2::frame rotation_filter::process_frame(const rs2::frame_source& source, const rs2::frame& f) + { + if( _value == rotation_default_val ) + return f; + + auto src = f.as(); + rs2::stream_profile profile = f.get_profile(); + _target_stream_profile = profile; + + if( _value == 90 || _value == -90 ) + { + _rotated_width = src.get_height(); + _rotated_height = src.get_width(); + } + else if( _value == 180 ) + { + _rotated_width = src.get_width(); + _rotated_height = src.get_height(); + } + auto bpp = src.get_bytes_per_pixel(); + update_output_profile( f ); + + rs2_stream type = profile.stream_type(); + rs2_extension tgt_type; + if (type == RS2_STREAM_COLOR || type == RS2_STREAM_INFRARED) + tgt_type = RS2_EXTENSION_VIDEO_FRAME; + else + tgt_type = f.is() ? RS2_EXTENSION_DISPARITY_FRAME : RS2_EXTENSION_DEPTH_FRAME; + + + if (auto tgt = prepare_target_frame(f, source, tgt_type)) + { + int rotated_width = ( _value == 90 || _value == -90 ) ? src.get_height() : src.get_width(); + int rotated_height = ( _value == 90 || _value == -90 ) ? src.get_width() : src.get_height(); + + switch( bpp ) + { + case 1: { + rotate_depth< 1 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), + static_cast< const uint8_t * >( src.get_data() ), + rotated_height, + rotated_width ); + auto temp = static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ); + break; + } + + case 2: { + rotate_depth< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), + static_cast< const uint8_t * >( src.get_data() ), + rotated_height, + rotated_width ); + auto temp = static_cast< uint16_t * >( const_cast< void * >( tgt.get_data() ) ); + break; + } + + default: + LOG_ERROR( "Rotation transform does not support format: " + + std::string( rs2_format_to_string( tgt.get_profile().format() ) ) ); + } + return tgt; + } + return f; + } + + void rotation_filter::update_output_profile(const rs2::frame& f) + { + _source_stream_profile = f.get_profile(); + + _target_stream_profile = _source_stream_profile.clone( _source_stream_profile.stream_type(), + _source_stream_profile.stream_index(), + _source_stream_profile.format() ); + + + auto src_vspi = dynamic_cast< video_stream_profile_interface * >( _source_stream_profile.get()->profile ); + if( ! src_vspi ) + throw std::runtime_error( "Stream profile interface is not video stream profile interface" ); + + auto tgt_vspi = dynamic_cast< video_stream_profile_interface * >( _target_stream_profile.get()->profile ); + if( ! tgt_vspi ) + throw std::runtime_error( "Profile is not video stream profile" ); + + rs2_intrinsics src_intrin = src_vspi->get_intrinsics(); + rs2_intrinsics tgt_intrin = tgt_vspi->get_intrinsics(); + + // Adjust width and height based on the rotation angle + if( _value == 90 || _value == -90 ) // 90 or -90 degrees rotation + { + _rotated_width = src_intrin.height; + _rotated_height = src_intrin.width; + tgt_intrin.fx = src_intrin.fy; + tgt_intrin.fy = src_intrin.fx; + tgt_intrin.ppx = src_intrin.ppy; + tgt_intrin.ppy = src_intrin.ppx; + } + else if( _value == 180 ) // 180 degrees rotation + { + _rotated_width = src_intrin.width; + _rotated_height = src_intrin.height; + tgt_intrin = src_intrin; + } + else { throw std::invalid_argument( "Unsupported rotation angle" ); } + + tgt_intrin.width = _rotated_width; + tgt_intrin.height = _rotated_height; + + tgt_vspi->set_intrinsics( [tgt_intrin]() { return tgt_intrin; } ); + tgt_vspi->set_dims( _rotated_width, _rotated_height ); + } + + rs2::frame rotation_filter::prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type) + { + auto vf = f.as(); + auto ret = source.allocate_video_frame(_target_stream_profile, f, + vf.get_bytes_per_pixel(), + _rotated_width, + _rotated_height, + _rotated_width * vf.get_bytes_per_pixel(), + tgt_type); + + return ret; + } + + template< size_t SIZE > + void rotation_filter::rotate_depth( uint8_t * const out, const uint8_t * source, + int width, int height) + { + auto width_out = ( _value == 90 || _value == -90 ) ? height : width; + auto height_out = ( _value == 90 || _value == -90 ) ? width : height; + + for( int i = 0; i < height; ++i ) + { + for( int j = 0; j < width; ++j ) + { + auto src_index = ( i * width + j ) * SIZE; + size_t out_index; + + if( _value == 90 ) + { + // 90-degree rotation + out_index = ( j * width_out + ( width_out - i - 1 ) ) * SIZE; + } + else if( _value == -90 ) + { + // -90-degree rotation + out_index = ( ( height_out - j - 1 ) * width_out + i ) * SIZE; + } + else if( _value == 180 ) + { + // 180-degree rotation + out_index = ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; + } + else + { + throw std::invalid_argument( "Unsupported rotation angle" ); + } + + std::memcpy( &out[out_index], &source[src_index], SIZE ); + } + } + } + +} + diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h new file mode 100644 index 0000000000..4d96fed9b2 --- /dev/null +++ b/src/proc/rotation-filter.h @@ -0,0 +1,41 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#pragma once + +#include "../include/librealsense2/hpp/rs_frame.hpp" +#include "../include/librealsense2/hpp/rs_processing.hpp" +#include "proc/synthetic-stream.h" + +namespace librealsense +{ + + class rotation_filter : public stream_filter_processing_block + { + public: + rotation_filter(); + + protected: + rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); + + template< size_t SIZE > + void rotate_depth( uint8_t * const out, const uint8_t * source, int width, int height ); + + rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; + + private: + void update_output_profile(const rs2::frame& f); + + + int _control_val; + rs2::stream_profile _source_stream_profile; + rs2::stream_profile _target_stream_profile; + uint16_t _real_width; + uint16_t _real_height; + uint16_t _rotated_width; + uint16_t _rotated_height; + + int _value; + }; + MAP_EXTENSION( RS2_EXTENSION_ROTATION_FILTER, librealsense::rotation_filter ); + } diff --git a/src/realsense.def b/src/realsense.def index 607307aaf6..e1e7a9b79d 100644 --- a/src/realsense.def +++ b/src/realsense.def @@ -211,6 +211,7 @@ EXPORTS rs2_create_threshold rs2_create_units_transform rs2_create_decimation_filter_block + rs2_create_rotation_filter_block rs2_create_temporal_filter_block rs2_create_spatial_filter_block rs2_create_hole_filling_filter_block diff --git a/src/rs.cpp b/src/rs.cpp index 5078ece181..07e88abdb4 100644 --- a/src/rs.cpp +++ b/src/rs.cpp @@ -30,6 +30,7 @@ #include "proc/disparity-transform.h" #include "proc/syncer-processing-block.h" #include "proc/decimation-filter.h" +#include "proc/rotation-filter.h" #include "proc/spatial-filter.h" #include "proc/hole-filling-filter.h" #include "proc/color-formats-converter.h" @@ -1944,6 +1945,7 @@ int rs2_is_processing_block_extendable_to(const rs2_processing_block* f, rs2_ext switch (extension_type) { case RS2_EXTENSION_DECIMATION_FILTER: return VALIDATE_INTERFACE_NO_THROW((processing_block_interface*)(f->block.get()), librealsense::decimation_filter) != nullptr; + case RS2_EXTENSION_ROTATION_FILTER: return VALIDATE_INTERFACE_NO_THROW((processing_block_interface*)(f->block.get()), librealsense::rotation_filter) != nullptr; case RS2_EXTENSION_THRESHOLD_FILTER: return VALIDATE_INTERFACE_NO_THROW((processing_block_interface*)(f->block.get()), librealsense::threshold) != nullptr; case RS2_EXTENSION_DISPARITY_FILTER: return VALIDATE_INTERFACE_NO_THROW((processing_block_interface*)(f->block.get()), librealsense::disparity_transform) != nullptr; case RS2_EXTENSION_SPATIAL_FILTER: return VALIDATE_INTERFACE_NO_THROW((processing_block_interface*)(f->block.get()), librealsense::spatial_filter) != nullptr; @@ -2779,6 +2781,14 @@ rs2_processing_block* rs2_create_decimation_filter_block(rs2_error** error) BEGI } NOARGS_HANDLE_EXCEPTIONS_AND_RETURN(nullptr) +rs2_processing_block * rs2_create_rotation_filter_block( rs2_error ** error ) BEGIN_API_CALL +{ + auto block = std::make_shared< librealsense::rotation_filter >(); + + return new rs2_processing_block{ block }; +} +NOARGS_HANDLE_EXCEPTIONS_AND_RETURN( nullptr ) + rs2_processing_block* rs2_create_temporal_filter_block(rs2_error** error) BEGIN_API_CALL { auto block = std::make_shared(); diff --git a/src/sensor.cpp b/src/sensor.cpp index f051cb2af4..2b18cd0985 100644 --- a/src/sensor.cpp +++ b/src/sensor.cpp @@ -10,6 +10,7 @@ #include "image.h" #include "proc/synthetic-stream.h" #include "proc/decimation-filter.h" +#include "proc/rotation-filter.h" #include "global_timestamp_reader.h" #include "device-calibration.h" #include "core/notification.h" @@ -262,6 +263,7 @@ void log_callback_end( uint32_t fps, dec->get_option(RS2_OPTION_STREAM_FORMAT_FILTER).set(RS2_FORMAT_Z16); res.push_back(dec); } + res.push_back( std::make_shared< rotation_filter >() ); return res; } From b9bf02dc5a55826e4af6aa4adad9ecd232cb8497 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Thu, 7 Nov 2024 17:55:20 +0200 Subject: [PATCH 17/44] convert _value to float --- src/proc/rotation-filter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 4d96fed9b2..7b4083191a 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -35,7 +35,7 @@ namespace librealsense uint16_t _rotated_width; uint16_t _rotated_height; - int _value; + float _value; }; MAP_EXTENSION( RS2_EXTENSION_ROTATION_FILTER, librealsense::rotation_filter ); } From 0f673a35bf1735824ad666d96dbb003de3786ef1 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Thu, 7 Nov 2024 18:42:22 +0200 Subject: [PATCH 18/44] add case rotation_filter --- src/to-string.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/to-string.cpp b/src/to-string.cpp index 16f1dad3da..bd814bb91b 100644 --- a/src/to-string.cpp +++ b/src/to-string.cpp @@ -313,6 +313,7 @@ const char * get_string( rs2_extension value ) CASE( MAX_USABLE_RANGE_SENSOR ) CASE( DEBUG_STREAM_SENSOR ) CASE( CALIBRATION_CHANGE_DEVICE ) + CASE( ROTATION_FILTER ) default: assert( ! is_valid( value ) ); return UNKNOWN_VALUE; From e025cb79660502d4f50aaafb02149981306b6bad Mon Sep 17 00:00:00 2001 From: noacoohen Date: Sun, 10 Nov 2024 14:57:19 +0200 Subject: [PATCH 19/44] fix PR review comments --- src/proc/rotation-filter.cpp | 2 -- src/proc/rotation-filter.h | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 35a1dbb92c..33eb8e2643 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -91,7 +91,6 @@ namespace librealsense { static_cast< const uint8_t * >( src.get_data() ), rotated_height, rotated_width ); - auto temp = static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ); break; } @@ -100,7 +99,6 @@ namespace librealsense { static_cast< const uint8_t * >( src.get_data() ), rotated_height, rotated_width ); - auto temp = static_cast< uint16_t * >( const_cast< void * >( tgt.get_data() ) ); break; } diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 7b4083191a..2433bc463e 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -3,8 +3,8 @@ #pragma once -#include "../include/librealsense2/hpp/rs_frame.hpp" -#include "../include/librealsense2/hpp/rs_processing.hpp" +#include +#include #include "proc/synthetic-stream.h" namespace librealsense @@ -26,15 +26,13 @@ namespace librealsense private: void update_output_profile(const rs2::frame& f); - - int _control_val; + int _control_val; rs2::stream_profile _source_stream_profile; rs2::stream_profile _target_stream_profile; uint16_t _real_width; uint16_t _real_height; uint16_t _rotated_width; uint16_t _rotated_height; - float _value; }; MAP_EXTENSION( RS2_EXTENSION_ROTATION_FILTER, librealsense::rotation_filter ); From 195c58b3a933ee43e8d3c92138332e5d7003a4d4 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Mon, 11 Nov 2024 11:41:00 +0200 Subject: [PATCH 20/44] fix PR comments --- src/proc/rotation-filter.cpp | 48 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 33eb8e2643..5849f572c7 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -173,36 +173,34 @@ namespace librealsense { void rotation_filter::rotate_depth( uint8_t * const out, const uint8_t * source, int width, int height) { - auto width_out = ( _value == 90 || _value == -90 ) ? height : width; - auto height_out = ( _value == 90 || _value == -90 ) ? width : height; + if( _value != 90 && _value != -90 && _value != 180 ) + { + throw std::invalid_argument( "Invalid rotation angle. Only 90, -90, and 180 degrees are supported." ); + } + + int width_out = ( _value == 90 || _value == -90 ) ? height : width; + int height_out = ( _value == 90 || _value == -90 ) ? width : height; + + auto compute_out_index = [&]( int i, int j ) + { + switch( static_cast< int >( _value ) ) + { + case 90: + return ( j * width_out + ( width_out - i - 1 ) ) * SIZE; + case -90: + return ( ( height_out - j - 1 ) * width_out + i ) * SIZE; + case 180: + return ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; + } + return size_t( 0 ); // Will not be reached due to angle validation + }; for( int i = 0; i < height; ++i ) { for( int j = 0; j < width; ++j ) { - auto src_index = ( i * width + j ) * SIZE; - size_t out_index; - - if( _value == 90 ) - { - // 90-degree rotation - out_index = ( j * width_out + ( width_out - i - 1 ) ) * SIZE; - } - else if( _value == -90 ) - { - // -90-degree rotation - out_index = ( ( height_out - j - 1 ) * width_out + i ) * SIZE; - } - else if( _value == 180 ) - { - // 180-degree rotation - out_index = ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; - } - else - { - throw std::invalid_argument( "Unsupported rotation angle" ); - } - + size_t src_index = ( i * width + j ) * SIZE; + size_t out_index = compute_out_index( i, j ); std::memcpy( &out[out_index], &source[src_index], SIZE ); } } From 5098156d57baf33d211fcfb4f2e29c3fc8c40305 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Mon, 11 Nov 2024 15:51:28 +0200 Subject: [PATCH 21/44] fix rotation only for depth frames --- src/proc/rotation-filter.cpp | 71 +++++++++++++++++++++--------------- src/to-string.cpp | 1 + 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 5849f572c7..ba4f69120f 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -54,8 +54,27 @@ namespace librealsense { if( _value == rotation_default_val ) return f; - auto src = f.as(); rs2::stream_profile profile = f.get_profile(); + rs2_stream type = profile.stream_type(); + rs2_extension tgt_type; + if( type == RS2_STREAM_INFRARED ) + { + tgt_type = RS2_EXTENSION_VIDEO_FRAME; + } + else if( f.is< rs2::disparity_frame >() ) + { + tgt_type = RS2_EXTENSION_DISPARITY_FRAME; + } + else if( f.is< rs2::depth_frame >() ) + { + tgt_type = RS2_EXTENSION_DEPTH_FRAME; + } + else + { + return f; + } + + auto src = f.as< rs2::video_frame >(); _target_stream_profile = profile; if( _value == 90 || _value == -90 ) @@ -70,20 +89,11 @@ namespace librealsense { } auto bpp = src.get_bytes_per_pixel(); update_output_profile( f ); - - rs2_stream type = profile.stream_type(); - rs2_extension tgt_type; - if (type == RS2_STREAM_COLOR || type == RS2_STREAM_INFRARED) - tgt_type = RS2_EXTENSION_VIDEO_FRAME; - else - tgt_type = f.is() ? RS2_EXTENSION_DISPARITY_FRAME : RS2_EXTENSION_DEPTH_FRAME; - - if (auto tgt = prepare_target_frame(f, source, tgt_type)) { int rotated_width = ( _value == 90 || _value == -90 ) ? src.get_height() : src.get_width(); int rotated_height = ( _value == 90 || _value == -90 ) ? src.get_width() : src.get_height(); - + switch( bpp ) { case 1: { @@ -93,7 +103,7 @@ namespace librealsense { rotated_width ); break; } - + case 2: { rotate_depth< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), static_cast< const uint8_t * >( src.get_data() ), @@ -101,7 +111,7 @@ namespace librealsense { rotated_width ); break; } - + default: LOG_ERROR( "Rotation transform does not support format: " + std::string( rs2_format_to_string( tgt.get_profile().format() ) ) ); @@ -170,8 +180,7 @@ namespace librealsense { } template< size_t SIZE > - void rotation_filter::rotate_depth( uint8_t * const out, const uint8_t * source, - int width, int height) + void rotation_filter::rotate_depth( uint8_t * const out, const uint8_t * source, int width, int height ) { if( _value != 90 && _value != -90 && _value != 180 ) { @@ -181,30 +190,32 @@ namespace librealsense { int width_out = ( _value == 90 || _value == -90 ) ? height : width; int height_out = ( _value == 90 || _value == -90 ) ? width : height; - auto compute_out_index = [&]( int i, int j ) - { - switch( static_cast< int >( _value ) ) - { - case 90: - return ( j * width_out + ( width_out - i - 1 ) ) * SIZE; - case -90: - return ( ( height_out - j - 1 ) * width_out + i ) * SIZE; - case 180: - return ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; - } - return size_t( 0 ); // Will not be reached due to angle validation - }; - for( int i = 0; i < height; ++i ) { for( int j = 0; j < width; ++j ) { size_t src_index = ( i * width + j ) * SIZE; - size_t out_index = compute_out_index( i, j ); + size_t out_index; + + if( _value == 90 ) + { + out_index = ( j * width_out + ( width_out - i - 1 ) ) * SIZE; + } + else if( _value == -90 ) + { + out_index = ( ( height_out - j - 1 ) * width_out + i ) * SIZE; + } + else + { // 180 degrees + out_index = ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; + } + std::memcpy( &out[out_index], &source[src_index], SIZE ); } } + } + } diff --git a/src/to-string.cpp b/src/to-string.cpp index bd814bb91b..7bc1d95bf4 100644 --- a/src/to-string.cpp +++ b/src/to-string.cpp @@ -460,6 +460,7 @@ std::string const & get_string_( rs2_option value ) CASE( OHM_TEMPERATURE ) CASE( SOC_PVT_TEMPERATURE ) CASE( GYRO_SENSITIVITY ) + CASE( ROTATION ) arr[RS2_OPTION_REGION_OF_INTEREST] = "Region of Interest"; #undef CASE return arr; From d08520f497f04267d04d33597beb30e639cca1b0 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Wed, 13 Nov 2024 10:53:23 +0200 Subject: [PATCH 22/44] override should process to filter frames for rotate filter --- common/subdevice-model.cpp | 2 - include/librealsense2/hpp/rs_processing.hpp | 6 ++ src/proc/rotation-filter.cpp | 86 +++++++++++++++------ src/proc/rotation-filter.h | 19 +++-- src/sensor.cpp | 7 +- 5 files changed, 87 insertions(+), 33 deletions(-) diff --git a/common/subdevice-model.cpp b/common/subdevice-model.cpp index 734a1d65b9..b4dbf10ce5 100644 --- a/common/subdevice-model.cpp +++ b/common/subdevice-model.cpp @@ -194,8 +194,6 @@ namespace rs2 } catch (...) {} - auto filters = s->get_recommended_filters(); - for (auto&& f : s->get_recommended_filters()) { auto shared_filter = std::make_shared(f); diff --git a/include/librealsense2/hpp/rs_processing.hpp b/include/librealsense2/hpp/rs_processing.hpp index be2cc812c1..3344a54860 100644 --- a/include/librealsense2/hpp/rs_processing.hpp +++ b/include/librealsense2/hpp/rs_processing.hpp @@ -863,6 +863,12 @@ namespace rs2 { } + rotation_filter( float value ) + : filter( init(), 1 ) + { + set_option( RS2_OPTION_ROTATION, value ); + } + rotation_filter( filter f ) : filter( f ) { diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index ba4f69120f..d8fbaf0b55 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -14,8 +14,46 @@ namespace librealsense { const int rotation_default_val = 0; const int rotation_step = 90; - rotation_filter::rotation_filter() : - stream_filter_processing_block("Rotation Filter"), + + rotation_filter::rotation_filter() + : stream_filter_processing_block( "Rotation Filter" ) + , _streams_to_rotate() + , _control_val( rotation_default_val ) + , _real_width( 0 ) + , _real_height( 0 ) + , _rotated_width( 0 ) + , _rotated_height( 0 ) + { + auto rotation_control = std::make_shared< ptr_option< int > >( rotation_min_val, + rotation_max_val, + rotation_step, + rotation_default_val, + &_control_val, + "Rotation angle" ); + + auto weak_rotation_control = std::weak_ptr< ptr_option< int > >( rotation_control ); + rotation_control->on_set( + [this, weak_rotation_control]( float val ) + { + auto strong_rotation_control = weak_rotation_control.lock(); + if( ! strong_rotation_control ) + return; + + std::lock_guard< std::mutex > lock( _mutex ); + + if( ! strong_rotation_control->is_valid( val ) ) + throw invalid_value_exception( rsutils::string::from() + << "Unsupported rotation scale " << val << " is out of range." ); + + _value = val; + } ); + + register_option( RS2_OPTION_ROTATION, rotation_control ); + } + + rotation_filter::rotation_filter( std::vector< stream_filter > streams_to_rotate ): + stream_filter_processing_block("Rotation Filter"), + _streams_to_rotate(streams_to_rotate), _control_val(rotation_default_val), _real_width(0), _real_height(0), @@ -27,7 +65,7 @@ namespace librealsense { rotation_max_val, rotation_step, rotation_default_val, - &_control_val, "Rotation scale"); + &_control_val, "Rotation angle"); auto weak_rotation_control = std::weak_ptr< ptr_option< int > >( rotation_control ); rotation_control->on_set( @@ -54,27 +92,8 @@ namespace librealsense { if( _value == rotation_default_val ) return f; - rs2::stream_profile profile = f.get_profile(); - rs2_stream type = profile.stream_type(); - rs2_extension tgt_type; - if( type == RS2_STREAM_INFRARED ) - { - tgt_type = RS2_EXTENSION_VIDEO_FRAME; - } - else if( f.is< rs2::disparity_frame >() ) - { - tgt_type = RS2_EXTENSION_DISPARITY_FRAME; - } - else if( f.is< rs2::depth_frame >() ) - { - tgt_type = RS2_EXTENSION_DEPTH_FRAME; - } - else - { - return f; - } - auto src = f.as< rs2::video_frame >(); + rs2::stream_profile profile = f.get_profile(); _target_stream_profile = profile; if( _value == 90 || _value == -90 ) @@ -89,6 +108,14 @@ namespace librealsense { } auto bpp = src.get_bytes_per_pixel(); update_output_profile( f ); + + rs2_stream type = profile.stream_type(); + rs2_extension tgt_type; + if( type == RS2_STREAM_COLOR || type == RS2_STREAM_INFRARED ) + tgt_type = RS2_EXTENSION_VIDEO_FRAME; + else + tgt_type = f.is< rs2::disparity_frame >() ? RS2_EXTENSION_DISPARITY_FRAME : RS2_EXTENSION_DEPTH_FRAME; + if (auto tgt = prepare_target_frame(f, source, tgt_type)) { int rotated_width = ( _value == 90 || _value == -90 ) ? src.get_height() : src.get_width(); @@ -215,6 +242,19 @@ namespace librealsense { } } + + bool rotation_filter::should_process( const rs2::frame & frame ) + { + if( ! frame || frame.is< rs2::frameset >() ) + return false; + auto profile = frame.get_profile(); + for( auto stream : _streams_to_rotate ) + { + if( stream.match( frame ) ) + return true; + } + return false; + } } diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 2433bc463e..4e326920b9 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -15,6 +15,8 @@ namespace librealsense public: rotation_filter(); + rotation_filter( std::vector< stream_filter > streams_to_rotate ); + protected: rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); @@ -23,16 +25,19 @@ namespace librealsense rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; + bool should_process( const rs2::frame & frame ) override; + private: void update_output_profile(const rs2::frame& f); - int _control_val; - rs2::stream_profile _source_stream_profile; - rs2::stream_profile _target_stream_profile; - uint16_t _real_width; - uint16_t _real_height; - uint16_t _rotated_width; - uint16_t _rotated_height; + std::vector< stream_filter > _streams_to_rotate; + int _control_val; + rs2::stream_profile _source_stream_profile; + rs2::stream_profile _target_stream_profile; + uint16_t _real_width; + uint16_t _real_height; + uint16_t _rotated_width; + uint16_t _rotated_height; float _value; }; MAP_EXTENSION( RS2_EXTENSION_ROTATION_FILTER, librealsense::rotation_filter ); diff --git a/src/sensor.cpp b/src/sensor.cpp index 2b18cd0985..ef97f8d7da 100644 --- a/src/sensor.cpp +++ b/src/sensor.cpp @@ -263,7 +263,12 @@ void log_callback_end( uint32_t fps, dec->get_option(RS2_OPTION_STREAM_FORMAT_FILTER).set(RS2_FORMAT_Z16); res.push_back(dec); } - res.push_back( std::make_shared< rotation_filter >() ); + std::vector streams_to_rotate; + stream_filter depth_filter( RS2_STREAM_DEPTH, RS2_FORMAT_Z16, -1); + streams_to_rotate.push_back( depth_filter ); + stream_filter ir_filter( RS2_STREAM_INFRARED, RS2_FORMAT_Y8, -1 ); + streams_to_rotate.push_back( ir_filter ); + res.push_back( std::make_shared< rotation_filter >( streams_to_rotate ) ); return res; } From 2b3c978f803584866bfdf07077b953050e4fcb38 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Wed, 13 Nov 2024 16:08:37 +0200 Subject: [PATCH 23/44] add rotation filter to rs-post-processing example and remove override of should process --- .../post-processing/rs-post-processing.cpp | 33 ++++++-- src/proc/rotation-filter.cpp | 81 +++++-------------- src/proc/rotation-filter.h | 4 - src/sensor.cpp | 7 +- 4 files changed, 50 insertions(+), 75 deletions(-) diff --git a/examples/post-processing/rs-post-processing.cpp b/examples/post-processing/rs-post-processing.cpp index 96355e7ef1..da787dfa49 100644 --- a/examples/post-processing/rs-post-processing.cpp +++ b/examples/post-processing/rs-post-processing.cpp @@ -71,6 +71,7 @@ int main(int argc, char * argv[]) try // Declare filters rs2::decimation_filter dec_filter; // Decimation - reduces depth frame density + rs2::rotation_filter rot_filter; // Rotation - rotates frames rs2::threshold_filter thr_filter; // Threshold - removes values outside recommended range rs2::spatial_filter spat_filter; // Spatial - edge-preserving spatial smoothing rs2::temporal_filter temp_filter; // Temporal - reduces temporal noise @@ -85,6 +86,7 @@ int main(int argc, char * argv[]) try // The following order of emplacement will dictate the orders in which filters are applied filters.emplace_back("Decimate", dec_filter); + filters.emplace_back( "Rotate", rot_filter ); filters.emplace_back("Threshold", thr_filter); filters.emplace_back(disparity_filter_name, depth_to_disparity); filters.emplace_back("Spatial", spat_filter); @@ -115,11 +117,12 @@ int main(int argc, char * argv[]) try /* Apply filters. The implemented flow of the filters pipeline is in the following order: 1. apply decimation filter - 2. apply threshold filter - 3. transform the scene into disparity domain - 4. apply spatial filter - 5. apply temporal filter - 6. revert the results back (if step Disparity filter was applied + 2. apply rotation filter + 3. apply threshold filter + 4. transform the scene into disparity domain + 5. apply spatial filter + 6. apply temporal filter + 7. revert the results back (if step Disparity filter was applied to depth domain (each post processing block is optional and can be applied independantly). */ bool revert_disparity = false; @@ -269,11 +272,29 @@ void render_ui(float w, float h, std::vector& filters) ImGui::Checkbox(filter.filter_name.c_str(), &tmp_value); filter.is_enabled = tmp_value; ImGui::PopStyleColor(); + + + if( filter.filter_name == "Rotate" ) // Combo box specifically for the rotation filter + { + offset_y += elements_margin; + ImGui::PushItemWidth( w / 4 ); + ImGui::SetCursorPos( { offset_x, offset_y } ); + static const char * rotation_modes[] = { "0", "90", "180", "270" }; + static int current_rotation_mode = 0; + if( ImGui::Combo( "Rotation Angle", ¤t_rotation_mode, rotation_modes, 4 ) ) + { + float rotation_value = std::stof( rotation_modes[current_rotation_mode] ); + filter.supported_options[RS2_OPTION_ROTATION].value = rotation_value; - if (filter.supported_options.size() == 0) + // Set the filter's option using the new value + filter.filter.set_option( RS2_OPTION_ROTATION, rotation_value ); + } + } + if( filter.supported_options.size() == 0 ) { offset_y += elements_margin; } + // Draw a slider for each of the filter's options for (auto& option_slider_pair : filter.supported_options) { diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index d8fbaf0b55..6fe28f6a97 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -23,6 +23,7 @@ namespace librealsense { , _real_height( 0 ) , _rotated_width( 0 ) , _rotated_height( 0 ) + , _value( 0 ) { auto rotation_control = std::make_shared< ptr_option< int > >( rotation_min_val, rotation_max_val, @@ -51,49 +52,32 @@ namespace librealsense { register_option( RS2_OPTION_ROTATION, rotation_control ); } - rotation_filter::rotation_filter( std::vector< stream_filter > streams_to_rotate ): - stream_filter_processing_block("Rotation Filter"), - _streams_to_rotate(streams_to_rotate), - _control_val(rotation_default_val), - _real_width(0), - _real_height(0), - _rotated_width(0), - _rotated_height(0) - { - auto rotation_control = std::make_shared< ptr_option< int > >( - rotation_min_val, - rotation_max_val, - rotation_step, - rotation_default_val, - &_control_val, "Rotation angle"); - - auto weak_rotation_control = std::weak_ptr< ptr_option< int > >( rotation_control ); - rotation_control->on_set( - [this, weak_rotation_control]( float val ) - { - auto strong_rotation_control = weak_rotation_control.lock(); - if(!strong_rotation_control) return; - - std::lock_guard lock(_mutex); - - if( ! strong_rotation_control->is_valid( val ) ) - throw invalid_value_exception( rsutils::string::from() - << "Unsupported rotation scale " << val << " is out of range." ); - - _value = val; - - }); - - register_option( RS2_OPTION_ROTATION, rotation_control ); - } - rs2::frame rotation_filter::process_frame(const rs2::frame_source& source, const rs2::frame& f) { if( _value == rotation_default_val ) return f; - auto src = f.as< rs2::video_frame >(); rs2::stream_profile profile = f.get_profile(); + rs2_stream type = profile.stream_type(); + rs2_extension tgt_type; + if( type == RS2_STREAM_INFRARED ) + { + tgt_type = RS2_EXTENSION_VIDEO_FRAME; + } + else if( f.is< rs2::disparity_frame >() ) + { + tgt_type = RS2_EXTENSION_DISPARITY_FRAME; + } + else if( f.is< rs2::depth_frame >() ) + { + tgt_type = RS2_EXTENSION_DEPTH_FRAME; + } + else + { + return f; + } + + auto src = f.as< rs2::video_frame >(); _target_stream_profile = profile; if( _value == 90 || _value == -90 ) @@ -109,13 +93,6 @@ namespace librealsense { auto bpp = src.get_bytes_per_pixel(); update_output_profile( f ); - rs2_stream type = profile.stream_type(); - rs2_extension tgt_type; - if( type == RS2_STREAM_COLOR || type == RS2_STREAM_INFRARED ) - tgt_type = RS2_EXTENSION_VIDEO_FRAME; - else - tgt_type = f.is< rs2::disparity_frame >() ? RS2_EXTENSION_DISPARITY_FRAME : RS2_EXTENSION_DEPTH_FRAME; - if (auto tgt = prepare_target_frame(f, source, tgt_type)) { int rotated_width = ( _value == 90 || _value == -90 ) ? src.get_height() : src.get_width(); @@ -242,20 +219,6 @@ namespace librealsense { } } - - bool rotation_filter::should_process( const rs2::frame & frame ) - { - if( ! frame || frame.is< rs2::frameset >() ) - return false; - auto profile = frame.get_profile(); - for( auto stream : _streams_to_rotate ) - { - if( stream.match( frame ) ) - return true; - } - return false; - } - - + } diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 4e326920b9..fa841b2d58 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -15,8 +15,6 @@ namespace librealsense public: rotation_filter(); - rotation_filter( std::vector< stream_filter > streams_to_rotate ); - protected: rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); @@ -25,8 +23,6 @@ namespace librealsense rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; - bool should_process( const rs2::frame & frame ) override; - private: void update_output_profile(const rs2::frame& f); diff --git a/src/sensor.cpp b/src/sensor.cpp index ef97f8d7da..686a4dead6 100644 --- a/src/sensor.cpp +++ b/src/sensor.cpp @@ -263,12 +263,7 @@ void log_callback_end( uint32_t fps, dec->get_option(RS2_OPTION_STREAM_FORMAT_FILTER).set(RS2_FORMAT_Z16); res.push_back(dec); } - std::vector streams_to_rotate; - stream_filter depth_filter( RS2_STREAM_DEPTH, RS2_FORMAT_Z16, -1); - streams_to_rotate.push_back( depth_filter ); - stream_filter ir_filter( RS2_STREAM_INFRARED, RS2_FORMAT_Y8, -1 ); - streams_to_rotate.push_back( ir_filter ); - res.push_back( std::make_shared< rotation_filter >( streams_to_rotate ) ); + res.push_back( std::make_shared< rotation_filter >( )); return res; } From 524f519d548565362b9f6d6aaa3a1e15d16d38d7 Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Wed, 13 Nov 2024 18:40:06 +0200 Subject: [PATCH 24/44] commets improvements --- src/ds/d500/d500-device.cpp | 2 +- src/ds/d500/d500-private.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ds/d500/d500-device.cpp b/src/ds/d500/d500-device.cpp index 85379bdb41..843585f891 100644 --- a/src/ds/d500/d500-device.cpp +++ b/src/ds/d500/d500-device.cpp @@ -294,7 +294,7 @@ namespace librealsense } catch( const std::exception &e ) { - LOG_ERROR("Failed reading stereo baseline, using a default value --> " << e.what() ); + LOG_ERROR("Failed reading stereo baseline, using default value --> " << e.what() ); } return baseline; diff --git a/src/ds/d500/d500-private.cpp b/src/ds/d500/d500-private.cpp index dadaf447e4..bd23e70c4f 100644 --- a/src/ds/d500/d500-private.cpp +++ b/src/ds/d500/d500-private.cpp @@ -62,7 +62,7 @@ namespace librealsense } else // In case we got an empty table we will run with default values { - LOG_ERROR("cannot read intrinsic values, setting defaults"); + LOG_ERROR("cannot read intrinsic values, using default values"); rs2_intrinsics intrinsics = {0}; intrinsics.height = height; intrinsics.width = width; From c25e183f37554eaf4c38f0fa24445ce34616be2c Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Thu, 14 Nov 2024 11:22:26 +0200 Subject: [PATCH 25/44] add depth table related sensor to the comments --- src/ds/d500/d500-private.cpp | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ds/d500/d500-private.cpp b/src/ds/d500/d500-private.cpp index bd23e70c4f..0dfb39977d 100644 --- a/src/ds/d500/d500-private.cpp +++ b/src/ds/d500/d500-private.cpp @@ -43,33 +43,33 @@ namespace librealsense rs2_intrinsics get_d500_intrinsic_by_resolution(const vector& raw_data, d500_calibration_table_id table_id, uint32_t width, uint32_t height, bool is_symmetrization_enabled) { - - if (!raw_data.empty()) + switch (table_id) { - switch (table_id) - { - case d500_calibration_table_id::depth_calibration_id: - { + case d500_calibration_table_id::depth_calibration_id: + { + if ( !raw_data.empty() ) return get_d500_depth_intrinsic_by_resolution(raw_data, width, height, is_symmetrization_enabled); - } - case d500_calibration_table_id::rgb_calibration_id: - { - return get_d500_color_intrinsic_by_resolution(raw_data, width, height); - } - default: - throw invalid_value_exception(rsutils::string::from() << "Parsing Calibration table type " << static_cast(table_id) << " is not supported"); - } + else + LOG_ERROR("Cannot read depth table intrinsic values, using default values"); } - else // In case we got an empty table we will run with default values + case d500_calibration_table_id::rgb_calibration_id: { - LOG_ERROR("cannot read intrinsic values, using default values"); - rs2_intrinsics intrinsics = {0}; - intrinsics.height = height; - intrinsics.width = width; - intrinsics.ppx = intrinsics.fx = width / 2.f; - intrinsics.ppy = intrinsics.fy = height / 2.f; - return intrinsics; + if ( !raw_data.empty() ) + return get_d500_color_intrinsic_by_resolution(raw_data, width, height); + else + LOG_ERROR("Cannot read color table intrinsic values, using default values"); } + default: + throw invalid_value_exception(rsutils::string::from() << "Parsing Calibration table type " << static_cast(table_id) << " is not supported"); + } + + // If we got here, the table is empty so continue with default values + rs2_intrinsics intrinsics = {0}; + intrinsics.height = height; + intrinsics.width = width; + intrinsics.ppx = intrinsics.fx = width / 2.f; + intrinsics.ppy = intrinsics.fy = height / 2.f; + return intrinsics; } // Algorithm prepared by Oscar Pelc in matlab: From c0dc22ee816ec7036ad9c117550f906a2411e175 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Thu, 14 Nov 2024 12:01:43 +0200 Subject: [PATCH 26/44] add unit test --- .../post-processing/test-rotation-filter.py | 133 ++++++++++++++++++ wrappers/python/pyrs_processing.cpp | 3 + 2 files changed, 136 insertions(+) create mode 100644 unit-tests/post-processing/test-rotation-filter.py diff --git a/unit-tests/post-processing/test-rotation-filter.py b/unit-tests/post-processing/test-rotation-filter.py new file mode 100644 index 0000000000..47f405e8ad --- /dev/null +++ b/unit-tests/post-processing/test-rotation-filter.py @@ -0,0 +1,133 @@ +# License: Apache 2.0. See LICENSE file in root directory. +# Copyright(c) 2024 Intel Corporation. All Rights Reserved. + +#temporary fix to prevent the test from running on Win_SH_Py_DDS_CI +#test:donotrun:dds +#test:donotrun:!nightly + +from rspy import test, repo +import pyrealsense2 as rs +import numpy as np + +# Parameters for frame creation and depth intrinsics +input_res_x = 640 +input_res_y = 480 +focal_length = 600 +depth_units = 0.001 +stereo_baseline_mm = 50 +frames = 5 # Number of frames to process + + +# Function to create depth intrinsics directly +def create_depth_intrinsics(): + depth_intrinsics = rs.intrinsics() + depth_intrinsics.width = input_res_x + depth_intrinsics.height = input_res_y + depth_intrinsics.ppx = input_res_x / 2.0 + depth_intrinsics.ppy = input_res_y / 2.0 + depth_intrinsics.fx = focal_length + depth_intrinsics.fy = focal_length + depth_intrinsics.model = rs.distortion.brown_conrady + depth_intrinsics.coeffs = [0, 0, 0, 0, 0] + return depth_intrinsics + + +# Function to create a video stream with specified parameters +def create_video_stream(depth_intrinsics): + vs = rs.video_stream() + vs.type = rs.stream.depth + vs.index = 0 + vs.uid = 0 + vs.width = input_res_x + vs.height = input_res_y + vs.fps = 30 + vs.bpp = 2 + vs.fmt = rs.format.z16 + vs.intrinsics = depth_intrinsics + return vs + + +# Function to create a synthetic frame +def create_frame(depth_stream_profile, index): + frame = rs.software_video_frame() + data = np.arange(input_res_x * input_res_y, dtype=np.uint16).reshape((input_res_y, input_res_x)) + frame.pixels = data.tobytes() + frame.bpp = 2 + frame.stride = input_res_x * 2 + frame.timestamp = index * 33 + frame.domain = rs.timestamp_domain.system_time + frame.frame_number = index + frame.profile = depth_stream_profile.as_video_stream_profile() + frame.depth_units = depth_units + return frame + + +# Function to validate rotated results based on the angle +def validate_rotation_results(filtered_frame, angle): + rotated_height = filtered_frame.profile.as_video_stream_profile().height() + rotated_width = filtered_frame.profile.as_video_stream_profile().width() + + # Reshape the rotated data according to its actual dimensions + rotated_data = np.frombuffer(filtered_frame.get_data(), dtype=np.uint16).reshape((rotated_height, rotated_width)) + + # Original data for comparison + original_data = np.arange(input_res_x * input_res_y, dtype=np.uint16).reshape((input_res_y, input_res_x)) + + # Determine the expected rotated result based on the angle + if angle == 90: + expected_data = np.rot90(original_data, k=-1) # Clockwise + elif angle == 180: + expected_data = np.rot90(original_data, k=2) # 180 degrees + elif angle == -90: + expected_data = np.rot90(original_data, k=1) # Counterclockwise + + # Convert numpy arrays to lists before comparison + rotated_list = rotated_data.flatten().tolist() + expected_list = expected_data.flatten().tolist() + + # Compare the flattened lists + test.check_equal_lists(rotated_list, expected_list) + + +################################################################################################ +with test.closure("Test rotation filter"): + # Set up software device and depth sensor + sw_dev = rs.software_device() + depth_sensor = sw_dev.add_sensor("Depth") + + # Initialize intrinsics and video stream profile + depth_intrinsics = create_depth_intrinsics() + vs = create_video_stream(depth_intrinsics) + depth_stream_profile = depth_sensor.add_video_stream(vs) + + sync = rs.syncer() + + # Define rotation angles to test + rotation_angles = [90, 180, -90] + for angle in rotation_angles: + rotation_filter = rs.rotation_filter() + rotation_filter.set_option(rs.option.rotation, angle) + + # Start depth sensor + depth_sensor.open(depth_stream_profile) + depth_sensor.start(sync) + + for i in range(frames): + # Create and process each frame + frame = create_frame(depth_stream_profile, i) + depth_sensor.on_video_frame(frame) + + # Wait for frames and apply rotation filter + fset = sync.wait_for_frames() + depth = fset.first_or_default(rs.stream.depth) + filtered_depth = rotation_filter.process(depth) + + # Validate rotated frame results + validate_rotation_results(filtered_depth, angle) + + # Stop and close the sensor after each angle test + depth_sensor.stop() + depth_sensor.close() + +################################################################################################ +test.print_results_and_exit() \ No newline at end of file diff --git a/wrappers/python/pyrs_processing.cpp b/wrappers/python/pyrs_processing.cpp index 29dd9db7b4..29b6066c18 100644 --- a/wrappers/python/pyrs_processing.cpp +++ b/wrappers/python/pyrs_processing.cpp @@ -157,6 +157,9 @@ void init_processing(py::module &m) { decimation_filter.def(py::init<>()) .def(py::init(), "magnitude"_a); + py::class_< rs2::rotation_filter, rs2::filter > rotation_filter(m, "rotation_filter","Performs rotation of frames." ); + rotation_filter.def( py::init<>() ).def( py::init< float >(), "value"_a ); + py::class_ temporal_filter(m, "temporal_filter", "Temporal filter smooths the image by calculating multiple frames " "with alpha and delta settings. Alpha defines the weight of current frame, and delta defines the" "threshold for edge classification and preserving."); From 52de0e30b94e1427bf8e4216ce6636af25adb372 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Thu, 14 Nov 2024 15:20:34 +0200 Subject: [PATCH 27/44] add rotation filter to python --- src/media/ros/ros_writer.cpp | 2 ++ src/rscore-pp-block-factory.cpp | 3 +++ wrappers/python/pyrs_processing.cpp | 1 + 3 files changed, 6 insertions(+) diff --git a/src/media/ros/ros_writer.cpp b/src/media/ros/ros_writer.cpp index beb836715b..c55d38f7da 100644 --- a/src/media/ros/ros_writer.cpp +++ b/src/media/ros/ros_writer.cpp @@ -2,6 +2,7 @@ // Copyright(c) 2019 Intel Corporation. All Rights Reserved. #include "proc/decimation-filter.h" +#include "proc/rotation-filter.h" #include "proc/threshold.h" #include "proc/disparity-transform.h" #include "proc/spatial-filter.h" @@ -513,6 +514,7 @@ namespace librealsense RETURN_IF_EXTENSION(block, RS2_EXTENSION_HOLE_FILLING_FILTER); RETURN_IF_EXTENSION(block, RS2_EXTENSION_HDR_MERGE); RETURN_IF_EXTENSION(block, RS2_EXTENSION_SEQUENCE_ID_FILTER); + RETURN_IF_EXTENSION(block, RS2_EXTENSION_ROTATION_FILTER); #undef RETURN_IF_EXTENSION diff --git a/src/rscore-pp-block-factory.cpp b/src/rscore-pp-block-factory.cpp index 679bcf63e0..d09fd7978b 100644 --- a/src/rscore-pp-block-factory.cpp +++ b/src/rscore-pp-block-factory.cpp @@ -4,6 +4,7 @@ #include "rscore-pp-block-factory.h" #include "proc/decimation-filter.h" +#include "proc/rotation-filter.h" #include "proc/disparity-transform.h" #include "proc/hdr-merge.h" #include "proc/hole-filling-filter.h" @@ -27,6 +28,8 @@ rscore_pp_block_factory::create_pp_block( std::string const & name, rsutils::jso if( rsutils::string::nocase_equal( name, "Decimation Filter" ) ) return std::make_shared< decimation_filter >(); + if( rsutils::string::nocase_equal( name, "Rotation Filter" ) ) + return std::make_shared< rotation_filter >(); if( rsutils::string::nocase_equal( name, "HDR Merge" ) ) // and Hdr Merge return std::make_shared< hdr_merge >(); if( rsutils::string::nocase_equal( name, "Filter By Sequence id" ) // name diff --git a/wrappers/python/pyrs_processing.cpp b/wrappers/python/pyrs_processing.cpp index 29b6066c18..5dd80f2f0a 100644 --- a/wrappers/python/pyrs_processing.cpp +++ b/wrappers/python/pyrs_processing.cpp @@ -65,6 +65,7 @@ void init_processing(py::module &m) { return new rs2::filter(filter_function, queue_size); }), "filter_function"_a, "queue_size"_a = 1) .def(BIND_DOWNCAST(filter, decimation_filter)) + .def( BIND_DOWNCAST( filter, rotation_filter ) ) .def(BIND_DOWNCAST(filter, disparity_transform)) .def(BIND_DOWNCAST(filter, hole_filling_filter)) .def(BIND_DOWNCAST(filter, spatial_filter)) From 855774ee5741c3cb18581f73f9647b9b37cb609c Mon Sep 17 00:00:00 2001 From: Nir Azkiel Date: Sun, 17 Nov 2024 10:23:50 +0200 Subject: [PATCH 28/44] fix coverity issues --- src/ds/d500/d500-private.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ds/d500/d500-private.cpp b/src/ds/d500/d500-private.cpp index 0dfb39977d..425b663877 100644 --- a/src/ds/d500/d500-private.cpp +++ b/src/ds/d500/d500-private.cpp @@ -51,6 +51,7 @@ namespace librealsense return get_d500_depth_intrinsic_by_resolution(raw_data, width, height, is_symmetrization_enabled); else LOG_ERROR("Cannot read depth table intrinsic values, using default values"); + break; } case d500_calibration_table_id::rgb_calibration_id: { @@ -58,6 +59,7 @@ namespace librealsense return get_d500_color_intrinsic_by_resolution(raw_data, width, height); else LOG_ERROR("Cannot read color table intrinsic values, using default values"); + break; } default: throw invalid_value_exception(rsutils::string::from() << "Parsing Calibration table type " << static_cast(table_id) << " is not supported"); From 7ac1dfaecb5efcdb0ed327425acfd8b05f35465e Mon Sep 17 00:00:00 2001 From: ohadmeir Date: Sun, 17 Nov 2024 11:15:40 +0200 Subject: [PATCH 29/44] rs-enumerate-devices returns failure if no device found (detect no device in scripts) --- tools/enumerate-devices/rs-enumerate-devices.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/enumerate-devices/rs-enumerate-devices.cpp b/tools/enumerate-devices/rs-enumerate-devices.cpp index ffcd152983..54dd737f24 100644 --- a/tools/enumerate-devices/rs-enumerate-devices.cpp +++ b/tools/enumerate-devices/rs-enumerate-devices.cpp @@ -393,7 +393,7 @@ int main(int argc, char** argv) try if( !device_count ) { cout << "No device detected. Is it plugged in?\n"; - return EXIT_SUCCESS; + return EXIT_FAILURE; } if (short_view || compact_view) From fec7a5dfdf2391b93b9487e007bedb83d170c558 Mon Sep 17 00:00:00 2001 From: ohadmeir Date: Sun, 17 Nov 2024 11:31:04 +0200 Subject: [PATCH 30/44] SDK should not throw if DDS devices are not ready in time --- src/dds/rsdds-device-factory.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dds/rsdds-device-factory.cpp b/src/dds/rsdds-device-factory.cpp index 0338443dbb..a2fc144ab2 100644 --- a/src/dds/rsdds-device-factory.cpp +++ b/src/dds/rsdds-device-factory.cpp @@ -51,8 +51,15 @@ class rsdds_watcher_singleton _device_watcher->on_device_added( [this]( std::shared_ptr< realdds::dds_device > const & dev ) { - dev->wait_until_ready(); // make sure handshake is complete - _callbacks.raise( dev, true ); + try + { + dev->wait_until_ready(); // make sure handshake is complete, might throw + _callbacks.raise( dev, true ); + } + catch (std::runtime_error e) + { + LOG_ERROR( "Discovered DDS device failed to be ready within timeout" << e.what() ); + } } ); _device_watcher->on_device_removed( [this]( std::shared_ptr< realdds::dds_device > const & dev ) From 9da74c77882f416ca56f1738b315982353e887d9 Mon Sep 17 00:00:00 2001 From: ohadmeir Date: Thu, 7 Nov 2024 09:32:48 +0200 Subject: [PATCH 31/44] Identify D555 by name, not pid --- common/d500-on-chip-calib.cpp | 3 ++- common/device-model.cpp | 6 +++--- src/ds/d500/d500-auto-calibration.cpp | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/common/d500-on-chip-calib.cpp b/common/d500-on-chip-calib.cpp index 5f36a508c6..59b7c29b73 100644 --- a/common/d500-on-chip-calib.cpp +++ b/common/d500-on-chip-calib.cpp @@ -88,7 +88,8 @@ namespace rs2 auto depth_sensor = _sub->s->as (); // disabling the depth visual preset change for D555 - not needed - if (get_device_pid() != "0B56" && get_device_pid() != "DDS") + std::string dev_name = _dev.supports( RS2_CAMERA_INFO_NAME ) ? _dev.get_info( RS2_CAMERA_INFO_NAME ) : ""; + if( dev_name.find( "D555" ) == std::string::npos ) { // set depth preset as default preset set_option_if_needed(depth_sensor, RS2_OPTION_VISUAL_PRESET, 1); diff --git a/common/device-model.cpp b/common/device-model.cpp index 319425eda3..72d37d2bb6 100644 --- a/common/device-model.cpp +++ b/common/device-model.cpp @@ -3351,10 +3351,10 @@ namespace rs2 { bool is_d555 = false; - if( dev.supports( RS2_CAMERA_INFO_PRODUCT_ID ) ) + if( dev.supports( RS2_CAMERA_INFO_NAME ) ) { - auto pid_str = std::string( dev.get_info( RS2_CAMERA_INFO_PRODUCT_ID ) ); - if( pid_str == "0B56" || pid_str == "DDS" ) + auto dev_name = std::string( dev.get_info( RS2_CAMERA_INFO_NAME ) ); + if( dev_name.find( "D555" ) != std::string::npos ) is_d555 = true; } diff --git a/src/ds/d500/d500-auto-calibration.cpp b/src/ds/d500/d500-auto-calibration.cpp index d461d2be9b..d53d303752 100644 --- a/src/ds/d500/d500-auto-calibration.cpp +++ b/src/ds/d500/d500-auto-calibration.cpp @@ -95,8 +95,8 @@ namespace librealsense { bool is_d555 = false; auto dev = As< device >( _debug_dev ); - std::string pid_str = dev ? dev->get_info( RS2_CAMERA_INFO_PRODUCT_ID ) : ""; - if( pid_str == "0B56" || pid_str == "DDS" ) + std::string dev_name = dev ? dev->get_info( RS2_CAMERA_INFO_NAME ) : ""; + if( dev_name.find( "D555" ) != std::string::npos ) is_d555 = true; if( is_d555 ) From 85d26a6674d19d3c40e4ae79868ebf9963df2e6d Mon Sep 17 00:00:00 2001 From: noacoohen Date: Sun, 17 Nov 2024 15:19:49 +0200 Subject: [PATCH 32/44] add step to struct filter_slider_ui in rs-post-processing example --- .../post-processing/rs-post-processing.cpp | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/examples/post-processing/rs-post-processing.cpp b/examples/post-processing/rs-post-processing.cpp index da787dfa49..fac5ccb246 100644 --- a/examples/post-processing/rs-post-processing.cpp +++ b/examples/post-processing/rs-post-processing.cpp @@ -22,6 +22,7 @@ struct filter_slider_ui std::string description; bool is_int; float value; + float step; rs2::option_range range; bool render(const float3& location, bool enabled); @@ -86,7 +87,7 @@ int main(int argc, char * argv[]) try // The following order of emplacement will dictate the orders in which filters are applied filters.emplace_back("Decimate", dec_filter); - filters.emplace_back( "Rotate", rot_filter ); + filters.emplace_back("Rotate", rot_filter); filters.emplace_back("Threshold", thr_filter); filters.emplace_back(disparity_filter_name, depth_to_disparity); filters.emplace_back("Spatial", spat_filter); @@ -273,23 +274,6 @@ void render_ui(float w, float h, std::vector& filters) filter.is_enabled = tmp_value; ImGui::PopStyleColor(); - - if( filter.filter_name == "Rotate" ) // Combo box specifically for the rotation filter - { - offset_y += elements_margin; - ImGui::PushItemWidth( w / 4 ); - ImGui::SetCursorPos( { offset_x, offset_y } ); - static const char * rotation_modes[] = { "0", "90", "180", "270" }; - static int current_rotation_mode = 0; - if( ImGui::Combo( "Rotation Angle", ¤t_rotation_mode, rotation_modes, 4 ) ) - { - float rotation_value = std::stof( rotation_modes[current_rotation_mode] ); - filter.supported_options[RS2_OPTION_ROTATION].value = rotation_value; - - // Set the filter's option using the new value - filter.filter.set_option( RS2_OPTION_ROTATION, rotation_value ); - } - } if( filter.supported_options.size() == 0 ) { offset_y += elements_margin; @@ -337,11 +321,21 @@ bool filter_slider_ui::render(const float3& location, bool enabled) { int value_as_int = static_cast(value); value_changed = ImGui::SliderInt(("##" + name).c_str(), &value_as_int, static_cast(range.min), static_cast(range.max), "%.0f"); + if( step > 1 ) + { + value_as_int = static_cast< int >( range.min ) + + ( ( value_as_int - static_cast< int >( range.min ) ) / static_cast< int >( step ) ) + * static_cast< int >( step ); + } value = static_cast(value_as_int); } else { value_changed = ImGui::SliderFloat(("##" + name).c_str(), &value, range.min, range.max, "%.3f", 1.0f); + if( step > 0.0f ) + { + value = range.min + round( ( value - range.min ) / step ) * step; + } } ImGui::PopItemWidth(); @@ -377,12 +371,13 @@ filter_options::filter_options(const std::string name, rs2::filter& flt) : filter(flt), is_enabled(true) { - const std::array possible_filter_options = { + const std::array possible_filter_options = { RS2_OPTION_FILTER_MAGNITUDE, RS2_OPTION_FILTER_SMOOTH_ALPHA, RS2_OPTION_MIN_DISTANCE, RS2_OPTION_MAX_DISTANCE, - RS2_OPTION_FILTER_SMOOTH_DELTA + RS2_OPTION_FILTER_SMOOTH_DELTA, + RS2_OPTION_ROTATION }; //Go over each filter option and create a slider for it @@ -399,6 +394,7 @@ filter_options::filter_options(const std::string name, rs2::filter& flt) : supported_options[opt].name = name + "_" + opt_name; std::string prefix = "Filter "; supported_options[opt].label = opt_name; + supported_options[opt].step = range.step; } } } From 59c8039413d15169dc59d52119849c0dc6aed4af Mon Sep 17 00:00:00 2001 From: noacoohen Date: Sun, 17 Nov 2024 15:21:16 +0200 Subject: [PATCH 33/44] fix PR comments --- unit-tests/post-processing/test-rotation-filter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/unit-tests/post-processing/test-rotation-filter.py b/unit-tests/post-processing/test-rotation-filter.py index 47f405e8ad..32582d953a 100644 --- a/unit-tests/post-processing/test-rotation-filter.py +++ b/unit-tests/post-processing/test-rotation-filter.py @@ -3,7 +3,6 @@ #temporary fix to prevent the test from running on Win_SH_Py_DDS_CI #test:donotrun:dds -#test:donotrun:!nightly from rspy import test, repo import pyrealsense2 as rs From 8b36e44c25b4de773568b42bcbf3312f1534d0fe Mon Sep 17 00:00:00 2001 From: Avia Avraham <145359432+AviaAv@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:17:11 +0200 Subject: [PATCH 34/44] return success message for some commands --- common/output-model.cpp | 3 +-- src/terminal-parser.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/output-model.cpp b/common/output-model.cpp index b8506e8307..9ebe9b473a 100644 --- a/common/output-model.cpp +++ b/common/output-model.cpp @@ -997,8 +997,7 @@ void output_model::run_command(std::string command, device_models_list & device_ opcode_error_as_string = dbg.get_opcode_string(opcode); } std::string response = rsutils::string::from() << "\n" << terminal_parser.parse_response(to_lower(command), res); - if (response != "\n") - add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response); + add_log(RS2_LOG_SEVERITY_INFO, __FILE__, 0, response); } } diff --git a/src/terminal-parser.cpp b/src/terminal-parser.cpp index 0e272457ea..942a937ca3 100644 --- a/src/terminal-parser.cpp +++ b/src/terminal-parser.cpp @@ -61,9 +61,11 @@ namespace librealsense data_vec.insert(data_vec.begin(), data.begin(), data.end()); return data_vec; } - else if (std::all_of(response.begin() + 1, response.end(), [](int x) { return x == 0; })) // if the response contains only the opcode, the response is empty + else if (response.size() == 4) // if the response contains only the opcode, there's no response other than the opcode, return success message { - return {}; + unsigned opcode = response[0]; + auto str = (rsutils::string::from() << "Command succeeded, returned 0x" << hex << opcode).str(); + return std::vector(str.begin(), str.end()); } return response; } From 58db2aad416862f6b7fb6135a82d42cbca9aa75a Mon Sep 17 00:00:00 2001 From: noacoohen Date: Sun, 17 Nov 2024 17:28:36 +0200 Subject: [PATCH 35/44] replace syncder with frame_queue --- unit-tests/post-processing/test-rotation-filter.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/unit-tests/post-processing/test-rotation-filter.py b/unit-tests/post-processing/test-rotation-filter.py index 32582d953a..8b515c7f73 100644 --- a/unit-tests/post-processing/test-rotation-filter.py +++ b/unit-tests/post-processing/test-rotation-filter.py @@ -99,7 +99,7 @@ def validate_rotation_results(filtered_frame, angle): vs = create_video_stream(depth_intrinsics) depth_stream_profile = depth_sensor.add_video_stream(vs) - sync = rs.syncer() + frame_queue = rs.frame_queue(15) # Define rotation angles to test rotation_angles = [90, 180, -90] @@ -109,7 +109,7 @@ def validate_rotation_results(filtered_frame, angle): # Start depth sensor depth_sensor.open(depth_stream_profile) - depth_sensor.start(sync) + depth_sensor.start(frame_queue) for i in range(frames): # Create and process each frame @@ -117,9 +117,8 @@ def validate_rotation_results(filtered_frame, angle): depth_sensor.on_video_frame(frame) # Wait for frames and apply rotation filter - fset = sync.wait_for_frames() - depth = fset.first_or_default(rs.stream.depth) - filtered_depth = rotation_filter.process(depth) + depth_frame = frame_queue.wait_for_frame() + filtered_depth = rotation_filter.process(depth_frame) # Validate rotated frame results validate_rotation_results(filtered_depth, angle) From a5ab5ec2d03dbfc9b3131e9d0bbd3cd4f11cf3e4 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 17 Nov 2024 15:45:49 +0200 Subject: [PATCH 36/44] adding read and write data to bagfile --- src/media/ros/ros_file_format.h | 1 + src/media/ros/ros_reader.cpp | 18 ++++++++++++++++++ src/media/ros/ros_writer.cpp | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/media/ros/ros_file_format.h b/src/media/ros/ros_file_format.h index 11c9c64a07..24a7c3a6c2 100644 --- a/src/media/ros/ros_file_format.h +++ b/src/media/ros/ros_file_format.h @@ -417,6 +417,7 @@ namespace librealsense case RS2_STREAM_GYRO: case RS2_STREAM_ACCEL: + case RS2_STREAM_MOTION: return ros_imu_type_str(); case RS2_STREAM_POSE: diff --git a/src/media/ros/ros_reader.cpp b/src/media/ros/ros_reader.cpp index ce5df08463..99b4125bd6 100644 --- a/src/media/ros/ros_reader.cpp +++ b/src/media/ros/ros_reader.cpp @@ -518,6 +518,24 @@ namespace librealsense data[2] = static_cast(msg->angular_velocity.z); LOG_DEBUG("RS2_STREAM_GYRO " << motion_frame); } + if (stream_id.stream_type == RS2_STREAM_MOTION) + { + auto data = reinterpret_cast(motion_frame->data.data()); + // orientation part + data[0] = static_cast(msg->orientation.x); + data[1] = static_cast(msg->orientation.y); + data[2] = static_cast(msg->orientation.z); + data[3] = static_cast(msg->orientation.w); + // ACCEL part + data[4] = static_cast(msg->linear_acceleration.x); + data[5] = static_cast(msg->linear_acceleration.y); + data[6] = static_cast(msg->linear_acceleration.z); + // GYRO part + data[7] = static_cast(msg->angular_velocity.x); + data[8] = static_cast(msg->angular_velocity.y); + data[9] = static_cast(msg->angular_velocity.z); + LOG_DEBUG("RS2_STREAM_MOTION " << motion_frame); + } else { throw io_exception( rsutils::string::from() << "Unsupported stream type " << stream_id.stream_type ); diff --git a/src/media/ros/ros_writer.cpp b/src/media/ros/ros_writer.cpp index c55d38f7da..d07c887e54 100644 --- a/src/media/ros/ros_writer.cpp +++ b/src/media/ros/ros_writer.cpp @@ -227,6 +227,23 @@ namespace librealsense imu_msg.angular_velocity.y = data_ptr[1]; imu_msg.angular_velocity.z = data_ptr[2]; } + else if (stream_id.stream_type == RS2_STREAM_MOTION) + { + const double* data_double = reinterpret_cast(data_ptr); + // orientation part + imu_msg.orientation.x = data_double[0]; + imu_msg.orientation.y = data_double[1]; + imu_msg.orientation.z = data_double[2]; + imu_msg.orientation.w = data_double[3]; + // ACCEL part + imu_msg.linear_acceleration.x = data_double[4]; + imu_msg.linear_acceleration.y = data_double[5]; + imu_msg.linear_acceleration.z = data_double[6]; + // GYRO part + imu_msg.angular_velocity.x = data_double[7]; + imu_msg.angular_velocity.y = data_double[8]; + imu_msg.angular_velocity.z = data_double[9]; + } else { throw io_exception("Unsupported stream type for a motion frame"); From ddd994bc4b12a460382fde04be69367b1d8e5335 Mon Sep 17 00:00:00 2001 From: Remi Bettan Date: Mon, 18 Nov 2024 12:15:58 +0200 Subject: [PATCH 37/44] enabling record and playback --- src/media/ros/ros_reader.cpp | 26 ++++++++++++++------------ src/media/ros/ros_writer.cpp | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/media/ros/ros_reader.cpp b/src/media/ros/ros_reader.cpp index 99b4125bd6..6c6e6e0642 100644 --- a/src/media/ros/ros_reader.cpp +++ b/src/media/ros/ros_reader.cpp @@ -486,9 +486,11 @@ namespace librealsense get_frame_metadata(m_file, info_topic, stream_id, motion_data, additional_data); } + size_t size_of_imu_data = (stream_id.stream_type == RS2_STREAM_MOTION) ? sizeof(rs2_combined_motion) : 3 * sizeof(float); + frame_interface * frame = m_frame_source->alloc_frame( { stream_id.stream_type, stream_id.stream_index, RS2_EXTENSION_MOTION_FRAME }, - 3 * sizeof( float ), + size_of_imu_data, std::move( additional_data ), true ); if (frame == nullptr) @@ -522,18 +524,18 @@ namespace librealsense { auto data = reinterpret_cast(motion_frame->data.data()); // orientation part - data[0] = static_cast(msg->orientation.x); - data[1] = static_cast(msg->orientation.y); - data[2] = static_cast(msg->orientation.z); - data[3] = static_cast(msg->orientation.w); - // ACCEL part - data[4] = static_cast(msg->linear_acceleration.x); - data[5] = static_cast(msg->linear_acceleration.y); - data[6] = static_cast(msg->linear_acceleration.z); + data[0] = msg->orientation.x; + data[1] = msg->orientation.y; + data[2] = msg->orientation.z; + data[3] = msg->orientation.w; // GYRO part - data[7] = static_cast(msg->angular_velocity.x); - data[8] = static_cast(msg->angular_velocity.y); - data[9] = static_cast(msg->angular_velocity.z); + data[4] = msg->angular_velocity.x; + data[5] = msg->angular_velocity.y; + data[6] = msg->angular_velocity.z; + // ACCEL part + data[7] = msg->linear_acceleration.x; + data[8] = msg->linear_acceleration.y; + data[9] = msg->linear_acceleration.z; LOG_DEBUG("RS2_STREAM_MOTION " << motion_frame); } else diff --git a/src/media/ros/ros_writer.cpp b/src/media/ros/ros_writer.cpp index d07c887e54..1c31701eda 100644 --- a/src/media/ros/ros_writer.cpp +++ b/src/media/ros/ros_writer.cpp @@ -229,20 +229,20 @@ namespace librealsense } else if (stream_id.stream_type == RS2_STREAM_MOTION) { - const double* data_double = reinterpret_cast(data_ptr); + auto data_imu = *reinterpret_cast(frame.frame->get_frame_data()); // orientation part - imu_msg.orientation.x = data_double[0]; - imu_msg.orientation.y = data_double[1]; - imu_msg.orientation.z = data_double[2]; - imu_msg.orientation.w = data_double[3]; - // ACCEL part - imu_msg.linear_acceleration.x = data_double[4]; - imu_msg.linear_acceleration.y = data_double[5]; - imu_msg.linear_acceleration.z = data_double[6]; + imu_msg.orientation.x = data_imu.orientation.x; + imu_msg.orientation.y = data_imu.orientation.y; + imu_msg.orientation.z = data_imu.orientation.z; + imu_msg.orientation.w = data_imu.orientation.w; // GYRO part - imu_msg.angular_velocity.x = data_double[7]; - imu_msg.angular_velocity.y = data_double[8]; - imu_msg.angular_velocity.z = data_double[9]; + imu_msg.angular_velocity.x = data_imu.angular_velocity.x; + imu_msg.angular_velocity.y = data_imu.angular_velocity.y; + imu_msg.angular_velocity.z = data_imu.angular_velocity.z; + // ACCEL part + imu_msg.linear_acceleration.x = data_imu.linear_acceleration.x; + imu_msg.linear_acceleration.y = data_imu.linear_acceleration.y; + imu_msg.linear_acceleration.z = data_imu.linear_acceleration.z; } else { From 36336ff2ab1974b2de3790097616dfdcf20cd4b1 Mon Sep 17 00:00:00 2001 From: Remi Bettan Date: Mon, 18 Nov 2024 14:24:36 +0200 Subject: [PATCH 38/44] semantic change --- src/media/ros/ros_reader.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/media/ros/ros_reader.cpp b/src/media/ros/ros_reader.cpp index 6c6e6e0642..299b233f1f 100644 --- a/src/media/ros/ros_reader.cpp +++ b/src/media/ros/ros_reader.cpp @@ -522,20 +522,20 @@ namespace librealsense } if (stream_id.stream_type == RS2_STREAM_MOTION) { - auto data = reinterpret_cast(motion_frame->data.data()); + auto data = reinterpret_cast(motion_frame->data.data()); // orientation part - data[0] = msg->orientation.x; - data[1] = msg->orientation.y; - data[2] = msg->orientation.z; - data[3] = msg->orientation.w; + data->orientation.x = msg->orientation.x; + data->orientation.y = msg->orientation.y; + data->orientation.z = msg->orientation.z; + data->orientation.w = msg->orientation.w; // GYRO part - data[4] = msg->angular_velocity.x; - data[5] = msg->angular_velocity.y; - data[6] = msg->angular_velocity.z; + data->angular_velocity.x = msg->angular_velocity.x; + data->angular_velocity.y = msg->angular_velocity.y; + data->angular_velocity.z = msg->angular_velocity.z; // ACCEL part - data[7] = msg->linear_acceleration.x; - data[8] = msg->linear_acceleration.y; - data[9] = msg->linear_acceleration.z; + data->linear_acceleration.x = msg->linear_acceleration.x; + data->linear_acceleration.y = msg->linear_acceleration.y; + data->linear_acceleration.z = msg->linear_acceleration.z; LOG_DEBUG("RS2_STREAM_MOTION " << motion_frame); } else From 82b51b1ee591945aafa0d8fa06cfada7b7066366 Mon Sep 17 00:00:00 2001 From: Remi Bettan Date: Tue, 19 Nov 2024 09:56:55 +0200 Subject: [PATCH 39/44] bug correction --- src/media/ros/ros_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/media/ros/ros_reader.cpp b/src/media/ros/ros_reader.cpp index 299b233f1f..b98d2ec5e8 100644 --- a/src/media/ros/ros_reader.cpp +++ b/src/media/ros/ros_reader.cpp @@ -520,7 +520,7 @@ namespace librealsense data[2] = static_cast(msg->angular_velocity.z); LOG_DEBUG("RS2_STREAM_GYRO " << motion_frame); } - if (stream_id.stream_type == RS2_STREAM_MOTION) + else if (stream_id.stream_type == RS2_STREAM_MOTION) { auto data = reinterpret_cast(motion_frame->data.data()); // orientation part From c424a87a14b3f72a4b49f45bc530c13259535408 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Tue, 19 Nov 2024 11:47:50 +0200 Subject: [PATCH 40/44] fix coverity issue --- src/proc/rotation-filter.cpp | 28 ++++++++++------------------ src/proc/rotation-filter.h | 2 +- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 6fe28f6a97..1f894b1a60 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -95,24 +95,17 @@ namespace librealsense { if (auto tgt = prepare_target_frame(f, source, tgt_type)) { - int rotated_width = ( _value == 90 || _value == -90 ) ? src.get_height() : src.get_width(); - int rotated_height = ( _value == 90 || _value == -90 ) ? src.get_width() : src.get_height(); - switch( bpp ) { case 1: { - rotate_depth< 1 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), - static_cast< const uint8_t * >( src.get_data() ), - rotated_height, - rotated_width ); + rotate_frame< 1 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), + static_cast< const uint8_t * >( src.get_data() )); break; } case 2: { - rotate_depth< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), - static_cast< const uint8_t * >( src.get_data() ), - rotated_height, - rotated_width ); + rotate_frame< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), + static_cast< const uint8_t * >( src.get_data() ) ); break; } @@ -184,21 +177,21 @@ namespace librealsense { } template< size_t SIZE > - void rotation_filter::rotate_depth( uint8_t * const out, const uint8_t * source, int width, int height ) + void rotation_filter::rotate_frame( uint8_t * const out, const uint8_t * source ) { if( _value != 90 && _value != -90 && _value != 180 ) { throw std::invalid_argument( "Invalid rotation angle. Only 90, -90, and 180 degrees are supported." ); } - int width_out = ( _value == 90 || _value == -90 ) ? height : width; - int height_out = ( _value == 90 || _value == -90 ) ? width : height; + int width_out = ( _value == 90 || _value == -90 ) ? _rotated_width : _rotated_height; + int height_out = ( _value == 90 || _value == -90 ) ? _rotated_height : _rotated_width; - for( int i = 0; i < height; ++i ) + for( int i = 0; i < _rotated_width; ++i ) { - for( int j = 0; j < width; ++j ) + for( int j = 0; j < _rotated_height; ++j ) { - size_t src_index = ( i * width + j ) * SIZE; + size_t src_index = ( i * _rotated_height + j ) * SIZE; size_t out_index; if( _value == 90 ) @@ -217,7 +210,6 @@ namespace librealsense { std::memcpy( &out[out_index], &source[src_index], SIZE ); } } - } } diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index fa841b2d58..3030fc314b 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -19,7 +19,7 @@ namespace librealsense rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); template< size_t SIZE > - void rotate_depth( uint8_t * const out, const uint8_t * source, int width, int height ); + void rotate_frame( uint8_t * const out, const uint8_t * source ); rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; From da07459665b0266b97feb064ae7e57526aa470bf Mon Sep 17 00:00:00 2001 From: noacoohen Date: Tue, 19 Nov 2024 15:56:39 +0200 Subject: [PATCH 41/44] fix PR comments --- src/proc/rotation-filter.cpp | 23 +++++++++++++++-------- src/proc/rotation-filter.h | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 1f894b1a60..ead0e5d068 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -99,13 +99,17 @@ namespace librealsense { { case 1: { rotate_frame< 1 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), - static_cast< const uint8_t * >( src.get_data() )); + static_cast< const uint8_t * >( src.get_data() ), + src.get_height(), + src.get_width() ); break; } case 2: { rotate_frame< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), - static_cast< const uint8_t * >( src.get_data() ) ); + static_cast< const uint8_t * >( src.get_data() ) , + src.get_height(), + src.get_width() ); break; } @@ -177,21 +181,24 @@ namespace librealsense { } template< size_t SIZE > - void rotation_filter::rotate_frame( uint8_t * const out, const uint8_t * source ) + void rotation_filter::rotate_frame( uint8_t * const out, const uint8_t * source, int height, int width ) { if( _value != 90 && _value != -90 && _value != 180 ) { throw std::invalid_argument( "Invalid rotation angle. Only 90, -90, and 180 degrees are supported." ); } - int width_out = ( _value == 90 || _value == -90 ) ? _rotated_width : _rotated_height; - int height_out = ( _value == 90 || _value == -90 ) ? _rotated_height : _rotated_width; + int width_in = ( _value == 90 || _value == -90 ) ? height : width; //else rotation angle is 180 + int height_in = ( _value == 90 || _value == -90 ) ? width : height; //else rotation angle is 180 - for( int i = 0; i < _rotated_width; ++i ) + int width_out = ( _value == 90 || _value == -90 ) ? _rotated_width : _rotated_height; //else rotation angle is 180 + int height_out = ( _value == 90 || _value == -90 ) ? _rotated_height : _rotated_width; //else rotation angle is 180 + + for( int i = 0; i < width_in; ++i ) { - for( int j = 0; j < _rotated_height; ++j ) + for( int j = 0; j < height_in; ++j ) { - size_t src_index = ( i * _rotated_height + j ) * SIZE; + size_t src_index = ( i * height_in + j ) * SIZE; size_t out_index; if( _value == 90 ) diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 3030fc314b..31bf65c99d 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -19,7 +19,7 @@ namespace librealsense rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); template< size_t SIZE > - void rotate_frame( uint8_t * const out, const uint8_t * source ); + void rotate_frame( uint8_t * const out, const uint8_t * source, int height, int width ); rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; From 6354e60fd9fd18bfb9a6450a6d85fd36a8fb9a50 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Tue, 19 Nov 2024 17:00:29 +0200 Subject: [PATCH 42/44] fix PR comments --- src/proc/rotation-filter.cpp | 40 +++++++++++++++++++----------------- src/proc/rotation-filter.h | 2 +- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index ead0e5d068..cb9e1976d2 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -100,16 +100,16 @@ namespace librealsense { case 1: { rotate_frame< 1 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), static_cast< const uint8_t * >( src.get_data() ), - src.get_height(), - src.get_width() ); + src.get_width(), + src.get_height() ); break; } case 2: { rotate_frame< 2 >( static_cast< uint8_t * >( const_cast< void * >( tgt.get_data() ) ), static_cast< const uint8_t * >( src.get_data() ) , - src.get_height(), - src.get_width() ); + src.get_width(), + src.get_height()); break; } @@ -181,39 +181,41 @@ namespace librealsense { } template< size_t SIZE > - void rotation_filter::rotate_frame( uint8_t * const out, const uint8_t * source, int height, int width ) + void rotation_filter::rotate_frame( uint8_t * const out, const uint8_t * source, int width, int height ) { if( _value != 90 && _value != -90 && _value != 180 ) { throw std::invalid_argument( "Invalid rotation angle. Only 90, -90, and 180 degrees are supported." ); } - int width_in = ( _value == 90 || _value == -90 ) ? height : width; //else rotation angle is 180 - int height_in = ( _value == 90 || _value == -90 ) ? width : height; //else rotation angle is 180 + // Define output dimensions + int width_out = ( _value == 180 ) ? width : height; + int height_out = ( _value == 180 ) ? height : width; - int width_out = ( _value == 90 || _value == -90 ) ? _rotated_width : _rotated_height; //else rotation angle is 180 - int height_out = ( _value == 90 || _value == -90 ) ? _rotated_height : _rotated_width; //else rotation angle is 180 - - for( int i = 0; i < width_in; ++i ) + // Perform rotation + for( int i = 0; i < height; ++i ) { - for( int j = 0; j < height_in; ++j ) + for( int j = 0; j < width; ++j ) { - size_t src_index = ( i * height_in + j ) * SIZE; - size_t out_index; + // Calculate source index + size_t src_index = ( i * width + j ) * SIZE; + // Determine output index based on rotation angle + size_t out_index; if( _value == 90 ) { - out_index = ( j * width_out + ( width_out - i - 1 ) ) * SIZE; + out_index = ( j * height + ( height - i - 1 ) ) * SIZE; } else if( _value == -90 ) { - out_index = ( ( height_out - j - 1 ) * width_out + i ) * SIZE; + out_index = ( ( width - j - 1 ) * height + i ) * SIZE; } - else - { // 180 degrees - out_index = ( ( height_out - i - 1 ) * width_out + ( width_out - j - 1 ) ) * SIZE; + else // 180 degrees + { + out_index = ( ( height - i - 1 ) * width + ( width - j - 1 ) ) * SIZE; } + // Copy pixel data from source to destination std::memcpy( &out[out_index], &source[src_index], SIZE ); } } diff --git a/src/proc/rotation-filter.h b/src/proc/rotation-filter.h index 31bf65c99d..f5a542e21d 100644 --- a/src/proc/rotation-filter.h +++ b/src/proc/rotation-filter.h @@ -19,7 +19,7 @@ namespace librealsense rs2::frame prepare_target_frame(const rs2::frame& f, const rs2::frame_source& source, rs2_extension tgt_type); template< size_t SIZE > - void rotate_frame( uint8_t * const out, const uint8_t * source, int height, int width ); + void rotate_frame( uint8_t * const out, const uint8_t * source, int width, int height ); rs2::frame process_frame(const rs2::frame_source& source, const rs2::frame& f) override; From 6174446659103aad5edb0f3fbf9167dae5c3d4aa Mon Sep 17 00:00:00 2001 From: noacoohen Date: Tue, 19 Nov 2024 17:05:30 +0200 Subject: [PATCH 43/44] add comment --- src/proc/rotation-filter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index cb9e1976d2..55782f47b5 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -189,8 +189,8 @@ namespace librealsense { } // Define output dimensions - int width_out = ( _value == 180 ) ? width : height; - int height_out = ( _value == 180 ) ? height : width; + int width_out = ( _value == 180 ) ? width : height; // else 90 or -90 degrees rotation + int height_out = ( _value == 180 ) ? height : width; // else 90 or -90 degrees rotation // Perform rotation for( int i = 0; i < height; ++i ) From 9e51a1a44f0a147ed6f9975250a405614b0e5a21 Mon Sep 17 00:00:00 2001 From: noacoohen Date: Tue, 19 Nov 2024 17:08:25 +0200 Subject: [PATCH 44/44] fix PR comments --- src/proc/rotation-filter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/proc/rotation-filter.cpp b/src/proc/rotation-filter.cpp index 55782f47b5..c3654280ca 100644 --- a/src/proc/rotation-filter.cpp +++ b/src/proc/rotation-filter.cpp @@ -189,8 +189,8 @@ namespace librealsense { } // Define output dimensions - int width_out = ( _value == 180 ) ? width : height; // else 90 or -90 degrees rotation - int height_out = ( _value == 180 ) ? height : width; // else 90 or -90 degrees rotation + int width_out = ( _value == 90 || _value == -90 ) ? height : width; // rotate by 180 will keep the values as is + int height_out = ( _value == 90 || _value == -90 ) ? width : height; // rotate by 180 will keep the values as is // Perform rotation for( int i = 0; i < height; ++i )