diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d914983..3fbb7700f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ endif(NOT CMAKE_BUILD_TYPE) cmake_minimum_required(VERSION 3.5) -project(metavision VERSION 4.6.0) +project(metavision VERSION 4.6.1) set(PROJECT_VERSION_SUFFIX "") if(PROJECT_VERSION_SUFFIX STREQUAL "") @@ -26,6 +26,8 @@ else(PROJECT_VERSION_SUFFIX STREQUAL "") set(PROJECT_VERSION_FULL "${PROJECT_VERSION}-${PROJECT_VERSION_SUFFIX}") endif(PROJECT_VERSION_SUFFIX STREQUAL "") +include(CTest) + # Set output directory for build targets set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib/ CACHE PATH "Output directory of libraries.") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib/ CACHE PATH "Output directory of all executables.") @@ -227,7 +229,6 @@ if (GENERATE_DOC) endif (GENERATE_DOC) # Tests -include(CTest) if (BUILD_TESTING) # Gtest find_package(GTest CONFIG) @@ -456,6 +457,9 @@ add_subdirectory(utils/scripts) # Add open archive task after modules, hal and plugins that might affect it include(create_metavision_open_archive) +# Create metavision-get-started archive +include(create_metavision_get_started_archive) + ################################################### CPack for debian packages include(deb_packages) diff --git a/README.md b/README.md index aa2ff55b7..236909559 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ python3 -m pip install "numba==0.56.3" "profilehooks==1.12.0" "pytorch_lightning ### Compilation - 1. Retrieve the code: `git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.0`. + 1. Retrieve the code: `git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.1`. (If you choose to download an archive of OpenEB from GitHub rather than cloning the repository, you need to ensure that you select a ``Full.Source.Code.*`` archive instead of using the automatically generated ``Source.Code.*`` archives. This is because the latter do not include @@ -324,7 +324,7 @@ python -m pip install "numba==0.56.3" "profilehooks==1.12.0" "pytorch_lightning= First, retrieve the codebase: ```bash -git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.0 +git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.1 ``` Note that if you choose to download an archive of OpenEB from GitHub rather than cloning the repository, @@ -356,7 +356,7 @@ or you can deploy the OpenEB files (applications, samples, libraries etc.) in a * Option 2 - deploying in a directory of your choice - * To deploy SDK Pro in the default folder (`C:\Program Files\Prophesee`), execute this command + * To deploy OpenEB in the default folder (`C:\Program Files\Prophesee`), execute this command (your console should be launched as an administrator): ```bash diff --git a/cmake/custom_functions/add_library_version_header.cmake b/cmake/custom_functions/add_library_version_header.cmake index cdafa235c..6e9801dd7 100644 --- a/cmake/custom_functions/add_library_version_header.cmake +++ b/cmake/custom_functions/add_library_version_header.cmake @@ -8,8 +8,8 @@ # See the License for the specific language governing permissions and limitations under the License. set(GIT_BRANCH "main") -set(GIT_COMMIT_ID "95ccd2597aa7a8f8f44fd6ee405c4f3a8f9eda24") -set(GIT_COMMIT_DATE "2024-05-21 15:27:14 +0200") +set(GIT_COMMIT_ID "4bade2b1aeb57e36ba22a68e4270470d9d4d8715") +set(GIT_COMMIT_DATE "2024-06-14 10:34:56 +0000") find_program(GIT_SCM git DOC "Git version control" HINTS "C:\\Program Files\\Git\\bin\\") diff --git a/cmake/custom_targets/README_metavision_open.md b/cmake/custom_targets/README_metavision_open.md index aa2ff55b7..236909559 100644 --- a/cmake/custom_targets/README_metavision_open.md +++ b/cmake/custom_targets/README_metavision_open.md @@ -133,7 +133,7 @@ python3 -m pip install "numba==0.56.3" "profilehooks==1.12.0" "pytorch_lightning ### Compilation - 1. Retrieve the code: `git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.0`. + 1. Retrieve the code: `git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.1`. (If you choose to download an archive of OpenEB from GitHub rather than cloning the repository, you need to ensure that you select a ``Full.Source.Code.*`` archive instead of using the automatically generated ``Source.Code.*`` archives. This is because the latter do not include @@ -324,7 +324,7 @@ python -m pip install "numba==0.56.3" "profilehooks==1.12.0" "pytorch_lightning= First, retrieve the codebase: ```bash -git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.0 +git clone https://github.com/prophesee-ai/openeb.git --branch 4.6.1 ``` Note that if you choose to download an archive of OpenEB from GitHub rather than cloning the repository, @@ -356,7 +356,7 @@ or you can deploy the OpenEB files (applications, samples, libraries etc.) in a * Option 2 - deploying in a directory of your choice - * To deploy SDK Pro in the default folder (`C:\Program Files\Prophesee`), execute this command + * To deploy OpenEB in the default folder (`C:\Program Files\Prophesee`), execute this command (your console should be launched as an administrator): ```bash diff --git a/cmake/custom_targets/create_metavision_get_started_archive.cmake b/cmake/custom_targets/create_metavision_get_started_archive.cmake new file mode 100644 index 000000000..040123044 --- /dev/null +++ b/cmake/custom_targets/create_metavision_get_started_archive.cmake @@ -0,0 +1,27 @@ +# Copyright (c) Prophesee S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +set(output_metavision_get_started_archive_dir_path "${GENERATE_FILES_DIRECTORY}/metavision_get_started_archive") + +add_custom_target(create_metavision_get_started_archive_folder + COMMAND ${CMAKE_COMMAND} + -DPROJECT_SOURCE_DIR="${PROJECT_SOURCE_DIR}" + -DOUTPUT_DIR="${output_metavision_get_started_archive_dir_path}" + -DCMAKE_MODULE_PATH="${CMAKE_MODULE_PATH}" + -DGENERATE_FILES_DIRECTORY="${GENERATE_FILES_DIRECTORY}" + -P ${CMAKE_CURRENT_LIST_DIR}/create_metavision_get_started_archive_folder.cmake +) + +set(output_metavision_get_started_archive_path "${GENERATE_FILES_DIRECTORY}/metavision_get_started_${PROJECT_VERSION_FULL}.tar") + +add_custom_target(create_metavision_get_started_archive + COMMAND ${CMAKE_COMMAND} -E chdir ${output_metavision_get_started_archive_dir_path} ${CMAKE_COMMAND} -E tar cvf ${output_metavision_get_started_archive_path} . + COMMAND ${CMAKE_COMMAND} -E echo "File ${output_metavision_get_started_archive_path} generated" +) +add_dependencies(create_metavision_get_started_archive create_metavision_get_started_archive_folder) diff --git a/cmake/custom_targets/create_metavision_get_started_archive_folder.cmake b/cmake/custom_targets/create_metavision_get_started_archive_folder.cmake new file mode 100644 index 000000000..dab4e0d71 --- /dev/null +++ b/cmake/custom_targets/create_metavision_get_started_archive_folder.cmake @@ -0,0 +1,82 @@ +# Copyright (c) Prophesee S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +# Create directory where we will add the files needed to compile the open source offer +file(REMOVE_RECURSE "${OUTPUT_DIR}") +file(MAKE_DIRECTORY "${OUTPUT_DIR}") + +# For some reason, CMAKE_MODULE_PATH passed by create_metavision_open_archive +# has spaces instead of semicolumns +string(REPLACE " " ";" CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}") + +include(overridden_cmake_functions) + +set(MV_GET_STARTED_FILES images README.md) +list_transform_prepend (MV_GET_STARTED_FILES metavision-get-started/) + +# Add the files and folders needed to compile open : +foreach (file_or_dir ${MV_GET_STARTED_FILES}) + if (EXISTS "${PROJECT_SOURCE_DIR}/${file_or_dir}") + get_filename_component(dest "${OUTPUT_DIR}/${file_or_dir}" DIRECTORY) + file(COPY "${PROJECT_SOURCE_DIR}/${file_or_dir}" + DESTINATION "${dest}" + PATTERN __pycache__ EXCLUDE + ) + endif () +endforeach(file_or_dir) + +set(METAVISION_GET_STARTED_FOLDER "${OUTPUT_DIR}/metavision-get-started/") + +function(copy_cpp_sample path) + set(sample_path ${PROJECT_SOURCE_DIR}/${path}) + if (EXISTS ${sample_path}) + get_filename_component(dest_folder_name ${sample_path} NAME) + set(dest_path "${METAVISION_GET_STARTED_FOLDER}") + file(COPY "${sample_path}" + DESTINATION "${dest_path}" + ) + set(cmakelist_file ${dest_path}${dest_folder_name}/CMakeLists.txt) + file (REMOVE ${cmakelist_file}) + file (RENAME ${cmakelist_file}.install ${cmakelist_file}) + else() + message(FATAL_ERROR "The requested sample does not exist: ${sample_path}") + endif () +endfunction() + +function(copy_python_sample path) + set(sample_path ${PROJECT_SOURCE_DIR}/${path}) + if (EXISTS ${sample_path}) + get_filename_component(dest_folder_name "${sample_path}" NAME) + set(dest_path "${METAVISION_GET_STARTED_FOLDER}") + file(COPY "${sample_path}" + DESTINATION "${dest_path}" + PATTERN __pycache__ EXCLUDE + ) + file (REMOVE ${dest_path}${dest_folder_name}/CMakeLists.txt) + else() + message(FATAL_ERROR "The requested sample does not exist: ${sample_path}") + endif () +endfunction() + +# Add some Metavision Python samples to the metavision-get-started folder +copy_python_sample("sdk/modules/core/python/samples/metavision_time_surface") +copy_python_sample("sdk/modules/ml/python_extended/samples/flow_inference") + +# Add some Metavision C++ samples to the metavision-get-started folder +copy_cpp_sample("sdk/modules/core/cpp/samples/metavision_time_surface") +copy_cpp_sample("sdk/modules/core/cpp/samples/metavision_dummy_radar") + +# Copy license files +set(LICENSE_FILES "licensing/LICENSE_METAVISION_SDK" "licensing/LICENSE_OPEN") +foreach (file ${LICENSE_FILES}) + file(COPY "${PROJECT_SOURCE_DIR}/${file}" + DESTINATION "${METAVISION_GET_STARTED_FOLDER}/licensing" + ) +endforeach(file) + diff --git a/hal/cpp/src/device/device_discovery.cpp b/hal/cpp/src/device/device_discovery.cpp index 445c01670..a8233fe80 100644 --- a/hal/cpp/src/device/device_discovery.cpp +++ b/hal/cpp/src/device/device_discovery.cpp @@ -171,7 +171,7 @@ void common_log_plugin_error(const Metavision::Plugin &plugin, const std::string } void log_plugin_error(const Metavision::Plugin &plugin, const std::string &discovery_name, - const Metavision::HalException &e) { + const Metavision::BaseException &e) { common_log_plugin_error(plugin, discovery_name); MV_HAL_LOG_ERROR() << "Failed with exception:"; MV_HAL_LOG_ERROR() << e.what(); @@ -408,7 +408,7 @@ std::unique_ptr DeviceDiscovery::open(const std::string &input_serial, c log_plugin_error(plugin, camera_discovery.get_name(), e); } catch (const HalConnectionException &e) { log_plugin_error(plugin, camera_discovery.get_name(), e); - throw e; + throw; } catch (const std::exception &e) { log_plugin_error(plugin, camera_discovery.get_name(), e); } catch (...) { log_plugin_error(plugin, camera_discovery.get_name()); } @@ -461,7 +461,7 @@ std::unique_ptr DeviceDiscovery::open_raw_file(const std::string &raw_fi } catch (const HalException &e) { MV_HAL_LOG_ERROR() << Log::no_space << "While opening RAW file '" << raw_file << "':" << std::endl; - throw e; + throw; } return device; @@ -567,7 +567,7 @@ std::unique_ptr DeviceDiscovery::open_stream(std::unique_ptr libusb_ctx, - const std::shared_ptr &board_cmd) : - board_cmd(board_cmd), libusb_ctx(libusb_ctx) {} - std::shared_ptr board_cmd; - std::shared_ptr libusb_ctx; - }; - virtual CameraDiscovery::SerialList list() override; virtual CameraDiscovery::SystemList list_available_sources() override; virtual bool discover(Metavision::DeviceBuilder &device_builder, const std::string &serial, @@ -49,7 +41,6 @@ class TzCameraDiscovery : public Metavision::CameraDiscovery { private: std::vector> list_boards() const; - std::shared_ptr libusb_ctx; std::unique_ptr builder; // By default, nothing is supported, because we want boards to be ignored by the plugins that can manage it, so that diff --git a/hal_psee_plugins/include/boards/v4l2/v4l2_device.h b/hal_psee_plugins/include/boards/v4l2/v4l2_device.h index 8162c0132..b9867802d 100644 --- a/hal_psee_plugins/include/boards/v4l2/v4l2_device.h +++ b/hal_psee_plugins/include/boards/v4l2/v4l2_device.h @@ -12,14 +12,14 @@ #ifndef METAVISION_HAL_PSEE_PLUGINS_V4L2_DEVICE_H #define METAVISION_HAL_PSEE_PLUGINS_V4L2_DEVICE_H +#include +#include +#include #include -#include #include -#include "metavision/hal/facilities/i_hw_identification.h" #include "metavision/hal/facilities/i_camera_synchronization.h" -#include "metavision/hal/utils/camera_discovery.h" #include "metavision/hal/utils/device_control.h" namespace Metavision { diff --git a/hal_psee_plugins/psee_hw_layer_headers/include/metavision/psee_hw_layer/boards/utils/psee_libusb.h b/hal_psee_plugins/psee_hw_layer_headers/include/metavision/psee_hw_layer/boards/utils/psee_libusb.h index d71b87e34..0e53bafe7 100644 --- a/hal_psee_plugins/psee_hw_layer_headers/include/metavision/psee_hw_layer/boards/utils/psee_libusb.h +++ b/hal_psee_plugins/psee_hw_layer_headers/include/metavision/psee_hw_layer/boards/utils/psee_libusb.h @@ -85,6 +85,8 @@ class LibUSBDevice { void interrupt_transfer(unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout); + void force_release(); + friend void libusb_fill_control_transfer(struct libusb_transfer *transfer, std::shared_ptr dev, unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) { diff --git a/hal_psee_plugins/src/boards/treuzell/tz_camera_discovery.cpp b/hal_psee_plugins/src/boards/treuzell/tz_camera_discovery.cpp index fb1d63269..16bd2aa4c 100644 --- a/hal_psee_plugins/src/boards/treuzell/tz_camera_discovery.cpp +++ b/hal_psee_plugins/src/boards/treuzell/tz_camera_discovery.cpp @@ -23,10 +23,11 @@ namespace Metavision { -TzCameraDiscovery::TzCameraDiscovery() : - libusb_ctx(std::make_shared()), builder(std::make_unique()) {} +TzCameraDiscovery::TzCameraDiscovery() : builder(std::make_unique()) {} -std::vector> TzCameraDiscovery::list_boards() const { +std::vector> + TzCameraDiscovery::list_boards() const { + std::shared_ptr libusb_ctx = std::make_shared(); std::vector> boards; libusb_device **devs; diff --git a/hal_psee_plugins/src/boards/treuzell/tz_libusb_board_command.cpp b/hal_psee_plugins/src/boards/treuzell/tz_libusb_board_command.cpp index ef530f4e2..6a6cba343 100644 --- a/hal_psee_plugins/src/boards/treuzell/tz_libusb_board_command.cpp +++ b/hal_psee_plugins/src/boards/treuzell/tz_libusb_board_command.cpp @@ -229,7 +229,7 @@ std::string TzLibUSBBoardCommand::get_serial() { if (e.code().value() == TZ_TOO_SHORT) ostr << req.get32(0); else - throw e; + throw; } ostr << std::dec; return ostr.str(); @@ -273,7 +273,7 @@ unsigned int TzLibUSBBoardCommand::get_device_count() { } catch (std::system_error &e) { if (!quirks.ignore_size_on_device_prop_answer || (e.code().value() != TZ_SIZE_MISMATCH)) { // if quirk is enabled and error is SIZE_MISMATCH, ignore it - throw e; + throw; } } return req.get32(0); @@ -294,7 +294,7 @@ std::vector TzLibUSBBoardCommand::read_device_register(uint32_t device int err = req.get32(2); throw std::system_error(err, std::generic_category()); } else { - throw e; + throw; } } if (req.get32(0) != device) @@ -333,7 +333,7 @@ void TzLibUSBBoardCommand::write_device_register(uint32_t device, uint32_t addre int err = req.get32(2); throw std::system_error(err, std::generic_category()); } else { - throw e; + throw; } } if (req.get32(0) != device) diff --git a/hal_psee_plugins/src/boards/utils/psee_libusb.cpp b/hal_psee_plugins/src/boards/utils/psee_libusb.cpp index edcc012a3..fbbff15c6 100644 --- a/hal_psee_plugins/src/boards/utils/psee_libusb.cpp +++ b/hal_psee_plugins/src/boards/utils/psee_libusb.cpp @@ -54,7 +54,9 @@ LibUSBDevice::LibUSBDevice(std::shared_ptr libusb_ctx, uint16_t v } LibUSBDevice::~LibUSBDevice() { - libusb_close(dev_handle_); + if (dev_handle_) { + libusb_close(dev_handle_); + } } libusb_context *LibUSBDevice::ctx() { @@ -62,59 +64,107 @@ libusb_context *LibUSBDevice::ctx() { } libusb_device *LibUSBDevice::get_device() { + if (!dev_handle_) { + return nullptr; + } return libusb_get_device(dev_handle_); } int LibUSBDevice::get_configuration(int *config) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_get_configuration(dev_handle_, config); } int LibUSBDevice::set_configuration(int configuration) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_set_configuration(dev_handle_, configuration); } int LibUSBDevice::claim_interface(int interface_number) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_claim_interface(dev_handle_, interface_number); } int LibUSBDevice::release_interface(int interface_number) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_release_interface(dev_handle_, interface_number); } int LibUSBDevice::set_interface_alt_setting(int interface_number, int alternate_setting) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_set_interface_alt_setting(dev_handle_, interface_number, alternate_setting); } int LibUSBDevice::clear_halt(unsigned char endpoint) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_clear_halt(dev_handle_, endpoint); } int LibUSBDevice::reset_device() { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_reset_device(dev_handle_); } +void LibUSBDevice::force_release() { + reset_device(); + dev_handle_ = nullptr; +} + int LibUSBDevice::kernel_driver_active(int interface_number) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_kernel_driver_active(dev_handle_, interface_number); } int LibUSBDevice::detach_kernel_driver(int interface_number) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_detach_kernel_driver(dev_handle_, interface_number); } int LibUSBDevice::attach_kernel_driver(int interface_number) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_attach_kernel_driver(dev_handle_, interface_number); } int LibUSBDevice::set_auto_detach_kernel_driver(int enable) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_set_auto_detach_kernel_driver(dev_handle_, enable); } int LibUSBDevice::get_string_descriptor_ascii(uint8_t desc_index, unsigned char *data, int length) { + if (!dev_handle_) { + return LIBUSB_ERROR_NO_DEVICE; + } return libusb_get_string_descriptor_ascii(dev_handle_, desc_index, data, length); } void LibUSBDevice::control_transfer(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { + if (!dev_handle_) { + throw HalConnectionException(LIBUSB_ERROR_NO_DEVICE, libusb_error_category()); + } + int res; res = libusb_control_transfer(dev_handle_, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); if (res < 0) { @@ -124,6 +174,10 @@ void LibUSBDevice::control_transfer(uint8_t bmRequestType, uint8_t bRequest, uin void LibUSBDevice::bulk_transfer(unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout) { + if (!dev_handle_) { + throw HalConnectionException(LIBUSB_ERROR_NO_DEVICE, libusb_error_category()); + } + int res; res = libusb_bulk_transfer(dev_handle_, endpoint, data, length, transferred, timeout); if (res < 0) { @@ -133,6 +187,10 @@ void LibUSBDevice::bulk_transfer(unsigned char endpoint, unsigned char *data, in void LibUSBDevice::interrupt_transfer(unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout) { + if (!dev_handle_) { + throw HalConnectionException(LIBUSB_ERROR_NO_DEVICE, libusb_error_category()); + } + int res; res = libusb_interrupt_transfer(dev_handle_, endpoint, data, length, transferred, timeout); if (res < 0) { diff --git a/hal_psee_plugins/src/boards/utils/psee_libusb_data_transfer.cpp b/hal_psee_plugins/src/boards/utils/psee_libusb_data_transfer.cpp index cdf3e95c9..dc83dd06f 100644 --- a/hal_psee_plugins/src/boards/utils/psee_libusb_data_transfer.cpp +++ b/hal_psee_plugins/src/boards/utils/psee_libusb_data_transfer.cpp @@ -71,6 +71,7 @@ class PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback { void start(); void stop(); + void cancel(); private: bool proceed_async_bulk(struct libusb_transfer *transfer); @@ -122,12 +123,28 @@ void PseeLibUSBDataTransfer::start_impl(BufferPtr buffer) { void PseeLibUSBDataTransfer::run_impl() { MV_HAL_LOG_TRACE() << "poll thread running"; - while (!should_stop() && active_bulks_transfers_ > 0) { - // Ensure we'll have sufficient space to handle all transfers on the next iteration - get_buffer_pool().arrange(async_transfer_num_, packet_size_); + try { + while (!should_stop() && active_bulks_transfers_ > 0) { + // Ensure we'll have sufficient space to handle all transfers on the next iteration + get_buffer_pool().arrange(async_transfer_num_, packet_size_); - struct timeval tv = {0, 1}; - libusb_handle_events_timeout_completed(dev_->ctx(), &tv, nullptr); + struct timeval tv = {0, 1}; + libusb_handle_events_timeout_completed(dev_->ctx(), &tv, nullptr); + } + } catch (const HalConnectionException &e) { + if (e.code().value() == LIBUSB_ERROR_NO_DEVICE || e.code().value() == LIBUSB_TRANSFER_NO_DEVICE) { + for (auto &t : vtransfer_) { + t->cancel(); + } + } + + if (dev_) { + // When the device gets disconnected, closing the libusb_device_handle will hang + // Force the object to forget about the handle so we don't try to close it when it gets destroyed + dev_->force_release(); + } + release_async_transfers(); + throw; } MV_HAL_LOG_TRACE() << "poll thread shutting down"; @@ -194,7 +211,7 @@ bool PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::proceed_async_bulk(l assert(transfer == transfer_); assert(transfer->buffer == buf_->data()); - if (stop_) { + if (stop_ || transfer->status == LIBUSB_TRANSFER_CANCELLED) { return false; } @@ -206,7 +223,6 @@ bool PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::proceed_async_bulk(l MV_HAL_LOG_ERROR() << libusb_error_name(transfer->status); if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { throw HalConnectionException(transfer->status, libusb_error_category()); - return false; } } int r = submit_transfer(transfer); @@ -245,11 +261,16 @@ bool PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::proceed_async_bulk(l void PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::start() { std::lock_guard lock(transfer_mutex_); // avoid concurrent access to stop + if (!transfer_) { + return; + } + submitted_transfer_ = true; int r = submit_transfer(transfer_); if (r != 0) { MV_HAL_LOG_ERROR() << "Submit error in start"; MV_HAL_LOG_ERROR() << libusb_error_name(r); + submitted_transfer_ = false; throw HalConnectionException(r, libusb_error_category()); } @@ -266,6 +287,15 @@ void PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::stop() { --libusb_data_transfer_.active_bulks_transfers_; } +void PseeLibUSBDataTransfer::UserParamForAsyncBulkCallback::cancel() { + stop(); + while (libusb_cancel_transfer(transfer_) != LIBUSB_ERROR_NOT_FOUND) { + struct timeval tv = {0, 1}; + libusb_handle_events_timeout_completed(dev_->ctx(), &tv, nullptr); + } + submitted_transfer_ = false; +} + void PseeLibUSBDataTransfer::prepare_async_bulk_transfer(libusb_transfer *transfer, unsigned char *buf, int packet_size, libusb_transfer_cb_fn async_bulk_cb, void *user_data, unsigned int timeout) { @@ -298,7 +328,6 @@ int PseeLibUSBDataTransfer::submit_transfer(libusb_transfer *transfer) { int r = libusb_submit_transfer(transfer); if (r < 0) { MV_HAL_LOG_ERROR() << "USB Submit Error"; - throw HalConnectionException(r, libusb_error_category()); } return r; } diff --git a/hal_psee_plugins/src/utils/tz_device_control.cpp b/hal_psee_plugins/src/utils/tz_device_control.cpp index 8dda1022d..a5d0cc072 100644 --- a/hal_psee_plugins/src/utils/tz_device_control.cpp +++ b/hal_psee_plugins/src/utils/tz_device_control.cpp @@ -14,6 +14,7 @@ #include "metavision/psee_hw_layer/utils/tz_device_control.h" #include "metavision/psee_hw_layer/devices/treuzell/tz_device.h" #include "metavision/psee_hw_layer/devices/treuzell/tz_main_device.h" +#include "metavision/hal/utils/hal_connection_exception.h" #include "metavision/hal/utils/hal_exception.h" #include "utils/psee_hal_plugin_error_code.h" #include "metavision/hal/utils/hal_log.h" @@ -75,8 +76,14 @@ void TzDeviceControl::stop() { return; // Stop only the main device, the others are always running for (auto dev = devices_.rbegin(); dev != devices_.rend(); dev++) - if (auto main_dev = dynamic_cast((*dev).get())) - (*dev).get()->stop(); + if (auto main_dev = dynamic_cast((*dev).get())) { + try { + (*dev).get()->stop(); + } catch (const HalConnectionException &e) { + MV_HAL_LOG_WARNING() << "Failed to properly stop TzDevice due do connection error"; + MV_HAL_LOG_WARNING() << e.what(); + } + } streaming_ = false; } diff --git a/metavision_open_4.6.0.tar b/metavision_open_4.6.0.tar deleted file mode 100644 index 3554aa5a3..000000000 Binary files a/metavision_open_4.6.0.tar and /dev/null differ diff --git a/sdk/modules/base/cpp/include/metavision/sdk/base/utils/error_utils.h b/sdk/modules/base/cpp/include/metavision/sdk/base/utils/error_utils.h index 1b31949f5..be69abfc8 100644 --- a/sdk/modules/base/cpp/include/metavision/sdk/base/utils/error_utils.h +++ b/sdk/modules/base/cpp/include/metavision/sdk/base/utils/error_utils.h @@ -49,6 +49,15 @@ class BaseException : public std::system_error { BaseException(int error_code, const std::error_category &ecat, const std::string &what_arg) : BaseException(error_code, error_code, ecat, what_arg) {} + /// @brief Copy constructor + BaseException(const BaseException &other) : + std::system_error(other) { + msg_ = other.msg_; + } + + /// @brief Destructor + virtual ~BaseException() {} + /// @brief Returns the explanatory string /// @return Pointer to a null-terminated string with explanatory information virtual const char *what() const noexcept override { diff --git a/sdk/modules/core/cpp/samples/CMakeLists.txt b/sdk/modules/core/cpp/samples/CMakeLists.txt index 61c11e986..51bb5d824 100644 --- a/sdk/modules/core/cpp/samples/CMakeLists.txt +++ b/sdk/modules/core/cpp/samples/CMakeLists.txt @@ -15,6 +15,7 @@ endif () if (NOT ANDROID) add_subdirectory(metavision_composed_viewer) add_subdirectory(metavision_csv_viewer) + add_subdirectory(metavision_dummy_radar) add_subdirectory(metavision_event_frame_generation) add_subdirectory(metavision_event_frame_gpu_loading) add_subdirectory(metavision_file_to_video) diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt b/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt new file mode 100644 index 000000000..ad7ed6a30 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (c) Prophesee S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +set (sample metavision_dummy_radar) +set (common_libraries + MetavisionSDK::core + MetavisionSDK::driver + MetavisionSDK::ui + Boost::program_options + opencv_core + opencv_imgproc + opencv_imgcodecs) + +add_executable(${sample} ${sample}.cpp activity_monitoring.cpp radar_viewer.cpp) +target_link_libraries(${sample} PRIVATE ${common_libraries}) + +install(FILES ${sample}.cpp activity_monitoring.cpp radar_viewer.cpp activity_monitoring.h radar_viewer.h + DESTINATION share/metavision/sdk/core/cpp_samples/${sample} + COMPONENT metavision-sdk-core-samples +) + +install(FILES CMakeLists.txt.install + RENAME CMakeLists.txt + DESTINATION share/metavision/sdk/core/cpp_samples/${sample} + COMPONENT metavision-sdk-core-samples +) diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt.install b/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt.install new file mode 100644 index 000000000..cb7af9c78 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/CMakeLists.txt.install @@ -0,0 +1,30 @@ +# Copyright (c) Prophesee S.A. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and limitations under the License. + +project(metavision_dummy_radar) + +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_CXX_STANDARD 17) + +find_package(MetavisionSDK COMPONENTS core driver ui REQUIRED) +find_package(Boost COMPONENTS program_options REQUIRED) +find_package(OpenCV COMPONENTS core highgui imgproc REQUIRED) + +set (sample metavision_dummy_radar) +add_executable(${sample} ${sample}.cpp activity_monitoring.cpp radar_viewer.cpp) +target_link_libraries(${sample} + MetavisionSDK::base + MetavisionSDK::core + MetavisionSDK::driver + MetavisionSDK::ui + Boost::program_options + opencv_core + opencv_highgui + opencv_imgproc) diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.cpp b/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.cpp new file mode 100644 index 000000000..5905001b9 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.cpp @@ -0,0 +1,30 @@ +/********************************************************************************************************************** + * Copyright (c) Prophesee S.A. * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and limitations under the License. * + **********************************************************************************************************************/ + +#include "activity_monitoring.h" + +ActivityMonitor::ActivityMonitor(const Config &conf, int sensor_width) : + conf_(conf), bin_width_(sensor_width / conf.n_bins) { + histogram_.resize(conf.n_bins); +} + +void ActivityMonitor::reset() { + std::fill(histogram_.begin(), histogram_.end(), 0); +} + +void ActivityMonitor::get_ev_rate_per_bin(std::vector &ev_rate_per_bin) const { + ev_rate_per_bin.clear(); + ev_rate_per_bin.reserve(histogram_.size()); + for (uint32_t i = 0; i < histogram_.size(); ++i) { + const float ev_rate_bin = histogram_[i] / (conf_.accumulation_time * 1e-6f); + ev_rate_per_bin.emplace_back(ev_rate_bin); + } +} diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.h b/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.h new file mode 100644 index 000000000..8757f253c --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/activity_monitoring.h @@ -0,0 +1,73 @@ +/********************************************************************************************************************** + * Copyright (c) Prophesee S.A. * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and limitations under the License. * + **********************************************************************************************************************/ + +#ifndef ACTIVITY_MONITORING_H +#define ACTIVITY_MONITORING_H + +#include +#include + +#include + +/// @brief Class to monitor the event rate along the X dimension of the sensor +/// It counts the events falling in each bin and finally computes the event rate +/// from the accumulation time +class ActivityMonitor { +public: + struct Config { + uint8_t n_bins = 10; ///< Number of bins to compute event rate for + Metavision::timestamp accumulation_time = 50000; ///< Accumulation time to measure event rate + }; + + /// @brief Constructor + /// @param conf Monitoring parameters + /// @param sensor_width Sensor width (in pixels) + ActivityMonitor(const Config &conf, int sensor_width); + + /// @brief Destructor + ~ActivityMonitor() {} + + /// @brief Increments the bins associated to each event + /// @tparam InputIt Input event iterator type, works for iterators over + /// containers of @ref EventCD + /// @param[in] begin Iterator pointing to the first event in the stream + /// @param[in] end Iterator pointing to the past-the-end element in the stream + template + void process_events(InputIt begin, InputIt end); + + /// @brief Resets the bins counters to 0 + void reset(); + + /// @brief Retrieves the event rate of each bin + /// @param[out] ev_rate_per_bin The event rate for each bin + void get_ev_rate_per_bin(std::vector &ev_rate_per_bin) const; + +private: + const Config conf_; + const int bin_width_; + + std::vector histogram_; +}; + +template +void ActivityMonitor::process_events(InputIt begin, InputIt end) { + if (begin == end) { + return; + } + + for (auto it = begin; it != end; ++it) { + const uint32_t id_bin = it->x / bin_width_; + if (id_bin < conf_.n_bins) + histogram_[id_bin]++; + } +} + +#endif // ACTIVITY_MONITORING_H diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/metavision_dummy_radar.cpp b/sdk/modules/core/cpp/samples/metavision_dummy_radar/metavision_dummy_radar.cpp new file mode 100644 index 000000000..f3141d266 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/metavision_dummy_radar.cpp @@ -0,0 +1,199 @@ +/********************************************************************************************************************** + * Copyright (c) Prophesee S.A. * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and limitations under the License. * + **********************************************************************************************************************/ + +// Example of using Metavision SDK API for building a passive radar for moving +// objects +// +// It simply counts all events falling within vertical bins (groups of sensor columns) of the sensor (the number of +// bins being chosen by the user). Then, the event rate of each bin is computed from the event count and the selected +// period. The maximum event rate is found and if it falls within the operational event rate boundaries, it is assumed +// to correspond to the object of interest. +// To estimate a relative "distance", it is assumed that the closer the object, the more events will be generated +// (filling more the field of view). Thus, the "distance" to the camera is inversely proportional to the event rate +// of the bin with more events. +// Finally, the histogram is transformed into a "radar"-like shape to get better insight on the location of the target. + +#include +#include + +// Basic utils for camera streaming +#include +#include +#include +#include + +// More advanced classes for event processing +#include +#include + +#include "activity_monitoring.h" +#include "radar_viewer.h" + +namespace po = boost::program_options; + +struct Params { + std::string serial; + std::string cam_config_path; + std::string cam_calibration_path; + std::string event_file_path; + uint32_t delta_ts; + uint32_t stc_threshold; + uint16_t nbins; + float min_ev_rate; + float max_ev_rate; + float camera_fov; +}; + +int main(int argc, char *argv[]) { + /// [PROGRAM INPUTS] + Params parameters; + + const std::string short_program_desc( + "Example of using Metavision SDK API for processing events and visualizing some" + "spatial representation of events.\n"); + po::options_description options_desc("Options"); + + // clang-format off + options_desc.add_options() + ("help,h", "Produce help message.") + ("input-event-file,i", po::value(¶meters.event_file_path), "Path to input event file (RAW or HDF5). If not specified, the camera live stream is used.") + ("input-camera-config,j", po::value(¶meters.cam_config_path), "Path to a JSON file containing camera config settings to restore a camera state. Only works for live cameras.") + ("accumulation-time,a", po::value(¶meters.delta_ts)->default_value(50000), "Accumulation time for which to display the radar plot.") + ("min-ev-rate", po::value(¶meters.min_ev_rate)->default_value(1e5f), "Minimum event rate per bin.") + ("max-ev-rate", po::value(¶meters.max_ev_rate)->default_value(3e6f), "Maximum event rate per bin.") + ("cam-fov-deg", po::value(¶meters.camera_fov)->default_value(90.f), "Camera lateral FOV (in degrees).") + ("nbins", po::value(¶meters.nbins)->default_value(8), "Number of bins describing the FOV.") + ; + // clang-format on + + po::variables_map vm; + try { + po::store(po::command_line_parser(argc, argv).options(options_desc).run(), vm); + po::notify(vm); + } catch (po::error &e) { + MV_LOG_ERROR() << short_program_desc; + MV_LOG_ERROR() << options_desc; + MV_LOG_ERROR() << "Parsing error:" << e.what(); + return 1; + } + + if (vm.count("help")) { + MV_LOG_INFO() << short_program_desc; + MV_LOG_INFO() << options_desc; + return 0; + } + + if (parameters.nbins == 0) { + MV_LOG_ERROR() << "The number of bins should be strictly positive."; + return 2; + } + + /// [CAMERA INITIALIZATION] + // If the filename is set, then read from the file + Metavision::Camera camera; + if (parameters.event_file_path.empty()) { + camera = Metavision::Camera::from_first_available(); + if (!parameters.cam_config_path.empty()) + camera.load(parameters.cam_config_path); + } else { + const auto cam_config = Metavision::FileConfigHints().real_time_playback(true); + camera = Metavision::Camera::from_file(parameters.event_file_path, cam_config); + } + + /// [ALGO INITIALIZATION] + // Get camera resolution + const int camera_width = camera.geometry().width(); + const int camera_height = camera.geometry().height(); + + ActivityMonitor::Config config; + config.n_bins = parameters.nbins; + config.accumulation_time = parameters.delta_ts; + ActivityMonitor activity_monitor(config, camera_width); + + RadarViewer::Config conf; + conf.n_bins_x = parameters.nbins; + conf.min_ev_rate = parameters.min_ev_rate; + conf.max_ev_rate = parameters.max_ev_rate; + conf.lateral_fov = parameters.camera_fov * M_PI / 180.f; + RadarViewer radar_plot(conf, camera_width, camera_height); + + /// [DISPLAY WINDOW INITIALIZATION] + + // To render the frames, we create a window using the Window class of the UI module + Metavision::Window window("Radar viewer", 2 * camera_width, camera_height, Metavision::BaseWindow::RenderMode::BGR); + + cv::Mat disp_frame(cv::Size(camera_width * 2, camera_height), CV_8UC3); + + auto frame_gen = Metavision::OnDemandFrameGenerationAlgorithm(camera_width, camera_height, parameters.delta_ts); + + // We set a callback on the windows to close it when the Escape or Q key is + // pressed + window.set_keyboard_callback( + [&window, &disp_frame](Metavision::UIKeyEvent key, int scancode, Metavision::UIAction action, int mods) { + if (action == Metavision::UIAction::RELEASE && + (key == Metavision::UIKeyEvent::KEY_ESCAPE || key == Metavision::UIKeyEvent::KEY_Q)) { + window.set_close_flag(); + } + if (action == Metavision::UIAction::RELEASE && (key == Metavision::UIKeyEvent::KEY_R)) { + cv::imwrite("radar.png", disp_frame); + } + }); + + /// [PROCESSING DESCRIPTION] + cv::Rect evts_roi(0, 0, camera_width, camera_height); + cv::Rect radar_roi(camera_width, 0, camera_width, camera_height); + + cv::Mat radar_frame = disp_frame(radar_roi); + cv::Mat evts_frame = disp_frame(evts_roi); + + // Initialize the slicer which is gonna slice the events into same duration + // buffers of events + std::vector ev_rate_per_bin; + using Slicer = Metavision::EventBufferReslicerAlgorithm; + Slicer slicer( + [&](Slicer::ConditionStatus, Metavision::timestamp ts, std::size_t) { + activity_monitor.get_ev_rate_per_bin(ev_rate_per_bin); + radar_plot.compute_view(ev_rate_per_bin, radar_frame); + frame_gen.generate(ts, evts_frame, false); + window.show(disp_frame); + activity_monitor.reset(); + }, + Slicer::Condition::make_n_us(parameters.delta_ts)); + + // Update the frame to be displayed as well as the event histogram used for the radar plot using a callback on CD + // events + camera.cd().add_callback([&](const Metavision::EventCD *ev_begin, const Metavision::EventCD *ev_end) { + slicer.process_events(ev_begin, ev_end, [&](const Metavision::EventCD *begin, const Metavision::EventCD *end) { + if (begin == end) + return; + + frame_gen.process_events(begin, end); + activity_monitor.process_events(begin, end); + }); + }); + + // Start the camera + camera.start(); + + // Keep running until the camera is off, the recording is finished or the + // escape key was pressed + while (camera.is_running() && !window.should_close()) { + // We poll events (keyboard, mouse etc.) from the system with a 20ms sleep + // to avoid using 100% of a CPU's core and we push them into the window + // where the callback on the escape key will ask the windows to close + static constexpr std::int64_t kSleepPeriodMs = 20; + Metavision::EventLoop::poll_and_dispatch(kSleepPeriodMs); + } + + camera.stop(); + + return 0; +} diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.cpp b/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.cpp new file mode 100644 index 000000000..a667f75a5 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.cpp @@ -0,0 +1,122 @@ +/********************************************************************************************************************** + * Copyright (c) Prophesee S.A. * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and limitations under the License. * + **********************************************************************************************************************/ + +#include +#include + +#include "radar_viewer.h" + +RadarViewer::RadarViewer(const Config &conf, int sensor_width, int sensor_height) : + conf_(conf), + width_(sensor_width), + height_(sensor_height), + bin_width_(width_ / conf_.n_bins_x), + bin_height_(height_ / conf_.n_bins_y), + lateral_fov_(conf.lateral_fov) { + initialize_linear_grid(); + compute_radar_maps(); + tmp_bin_heights_.resize(conf_.n_bins_x); +} + +void RadarViewer::compute_view(std::vector &histo, cv::Mat &radar) { + const cv::Size s(width_, height_); + + auto max_val = std::max_element(histo.cbegin(), histo.cend()); + if (*max_val == 0) + return; + + const int i = std::distance(histo.cbegin(), max_val); + const float ev_rate_bin = histo[i]; + const bool draw_max = (ev_rate_bin >= conf_.min_ev_rate && ev_rate_bin < conf_.max_ev_rate); + if (draw_max) { + const float ratio = (ev_rate_bin - conf_.min_ev_rate) / (conf_.max_ev_rate - conf_.min_ev_rate); + const int max_val_y = static_cast(ratio * conf_.n_bins_y) * bin_height_; + tmp_bin_heights_[i] = max_val_y; + cv::rectangle(linear_bins_, cv::Point(i * bin_width_, max_val_y), + cv::Point((i + 1) * bin_width_ - 1, height_ - 1), cv::Scalar(255, 255, 255)); + } + cv::remap(linear_bins_, radar, mapx_, mapy_, cv::INTER_NEAREST, cv::BORDER_CONSTANT, 0); + + if (draw_max) { + // Draw back initial rectangle grid + const int max_val_y = tmp_bin_heights_[i]; + cv::rectangle(linear_bins_, cv::Point(i * bin_width_, max_val_y), + cv::Point((i + 1) * bin_width_ - 1, height_ - 1), cv::Scalar(0, 0, 0)); + + reset_grid(); + } +} + +void RadarViewer::compute_radar_maps() { + const cv::Size img_size(width_, height_); + + // Initialize remapping maps with polar data + cv::Mat tmp_mapx_(img_size, CV_32FC1); + cv::Mat tmp_mapy_(img_size, CV_32FC1); + tmp_mapx_.setTo(-1.f); + tmp_mapy_.setTo(-1.f); + + const float x_c = width_ / 2.f; + const float y_c = height_; + const float width_f = static_cast(width_); + for (int i = 0; i < height_; ++i) { + for (int j = 0; j < width_; ++j) { + const float theta = (j / width_f - 0.5f) * lateral_fov_; + const int x_pol = static_cast(std::round(x_c + (height_ - i) * sin(theta))); + const int y_pol = static_cast(std::round(y_c - (height_ - i) * cos(theta))); + if (x_pol >= 0 && x_pol < width_ && y_pol >= 0 && y_pol < height_) { + // Some pixels will be written several times if the cast to int provides + // same value (in particular for neighboring pixels + tmp_mapx_.at(y_pol, x_pol) = j; + tmp_mapy_.at(y_pol, x_pol) = i; + } + } + } + + // Reset grid lines coordinates which may have been overridden by neighboring pixels + cv::Mat grid(img_size, CV_8UC1, cv::Scalar(0)); + for (int i = 0; i < conf_.n_bins_x; ++i) { + for (int k = 0; k < conf_.n_bins_y; ++k) { + cv::rectangle(grid, cv::Point(i * bin_width_, k * bin_height_), + cv::Point((i + 1) * bin_width_ - 1, (k + 1) * bin_height_), cv::Scalar(255)); + } + } + for (int i = 0; i < height_; ++i) { + for (int j = 0; j < width_; ++j) { + if (grid.at(i, j)) { + const float theta = (j / width_f - 0.5f) * lateral_fov_; + const int x_pol = static_cast(std::round(x_c + (height_ - i) * sin(theta))); + const int y_pol = static_cast(std::round(y_c - (height_ - i) * cos(theta))); + if (x_pol >= 0 && x_pol < width_ && y_pol >= 0 && y_pol < height_) { + tmp_mapx_.at(y_pol, x_pol) = j; + tmp_mapy_.at(y_pol, x_pol) = i; + } + } + } + } + cv::convertMaps(tmp_mapx_, tmp_mapy_, mapx_, mapy_, CV_16SC2, true); +} + +void RadarViewer::initialize_linear_grid() { + linear_bins_.create(height_, width_, CV_8UC3); + linear_bins_.setTo(0); + + reset_grid(); +} + +void RadarViewer::reset_grid() { + for (int i = 0; i < conf_.n_bins_x; ++i) { + for (int k = 0; k < conf_.n_bins_y; ++k) { + cv::rectangle(linear_bins_, cv::Point(i * bin_width_, k * bin_height_), + cv::Point((i + 1) * bin_width_ - 1, (k + 1) * bin_height_), cv::Scalar(0, 255, 0)); + } + } +} diff --git a/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.h b/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.h new file mode 100644 index 000000000..e8c4f7689 --- /dev/null +++ b/sdk/modules/core/cpp/samples/metavision_dummy_radar/radar_viewer.h @@ -0,0 +1,57 @@ +/********************************************************************************************************************** + * Copyright (c) Prophesee S.A. * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed * + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and limitations under the License. * + **********************************************************************************************************************/ + +#ifndef RADAR_VIEWER_H +#define RADAR_VIEWER_H + +#include + +/// @brief Display class to show a "radar"-like view of the most prominent data +/// visible in the event stream +class RadarViewer { +public: + struct Config { + std::uint8_t n_bins_x = 10; ///< Number of bins in the X direction + std::uint8_t n_bins_y = 5; ///< Number of bins in the Y direction + float lateral_fov = M_PI / 2.f; ///< Field of view in the X direction + float min_ev_rate = 40e3f; ///< Minimum event rate computed in a bin for it to be displayed + float max_ev_rate = 100e6f; ///< Maximum event rate computed in a bin for it to be displayed + }; + + /// @brief Constructor + /// @param conf Display parameters + /// @param sensor_width Sensor width in pixels + /// @param sensor_height Sensor height in pixels + RadarViewer(const Config &conf, int sensor_width, int sensor_height); + + /// @brief Destructor + ~RadarViewer() {} + + /// @brief Computes the radar view from the event rate vector + /// @param[in] histo Vector containing the event rate for each input bin + /// @param[out] radar Image of the radar view + void compute_view(std::vector &histo, cv::Mat &radar); + +private: + void compute_radar_maps(); + void initialize_linear_grid(); + void reset_grid(); + + const Config conf_; + const int width_, height_; + const int bin_width_, bin_height_; + const float lateral_fov_; + cv::Mat mapx_, mapy_; + cv::Mat linear_bins_; + std::vector tmp_bin_heights_; +}; + +#endif // RADAR_VIEWER_H diff --git a/sdk/modules/core/cpp/samples/metavision_time_surface/metavision_time_surface.cpp b/sdk/modules/core/cpp/samples/metavision_time_surface/metavision_time_surface.cpp index b911ed638..a9155b61e 100644 --- a/sdk/modules/core/cpp/samples/metavision_time_surface/metavision_time_surface.cpp +++ b/sdk/modules/core/cpp/samples/metavision_time_surface/metavision_time_surface.cpp @@ -9,15 +9,19 @@ * See the License for the specific language governing permissions and limitations under the License. * **********************************************************************************************************************/ -// Example of using Metavision SDK Core API for visualizing Time Surface of events +// Example of using Metavision SDK API for visualizing Time Surface of events #include -#include -#include -#include + +// Basic utils for camera streaming #include -#include +#include #include +#include + +// More advanced classes for event processing +#include +#include namespace po = boost::program_options; @@ -34,10 +38,9 @@ int main(int argc, char *argv[]) { // clang-format off options_desc.add_options() ("help,h", "Produce help message.") - ("serial,s", po::value(&serial),"Serial ID of the camera. This flag is incompatible with flag '--input-event-file'.") ("input-event-file,i", po::value(&event_file_path), "Path to input event file (RAW or HDF5). If not specified, the camera live stream is used.") ("input-camera-config,j", po::value(&cam_config_path), "Path to a JSON file containing camera config settings to restore a camera state. Only works for live cameras.") - ("accumulation-time,a", po::value(&delta_ts)->default_value(10000), "Accumulation time for which to display the Time Surface.") + ("accumulation-time,a", po::value(&delta_ts)->default_value(50000), "Accumulation time for which to display the Time Surface.") ; // clang-format on @@ -58,68 +61,32 @@ int main(int argc, char *argv[]) { return 0; } + // If the filename is set, then read from the file Metavision::Camera camera; - - // if the filename is set, then read from the file - if (!event_file_path.empty()) { - if (!serial.empty()) { - MV_LOG_ERROR() << "Options --serial and --input-event-file are not compatible."; - return 1; - } - - try { - camera = - Metavision::Camera::from_file(event_file_path, Metavision::FileConfigHints().real_time_playback(true)); - - } catch (Metavision::CameraException &e) { - MV_LOG_ERROR() << e.what(); - return 2; - } - // otherwise, set the input source to a camera - } else { - try { - if (!serial.empty()) { - camera = Metavision::Camera::from_serial(serial); - } else { - camera = Metavision::Camera::from_first_available(); - } - - if (!cam_config_path.empty()) { + try { + if (event_file_path.empty()) { + camera = Metavision::Camera::from_first_available(); + if (!cam_config_path.empty()) camera.load(cam_config_path); - } - } catch (Metavision::CameraException &e) { - MV_LOG_ERROR() << e.what(); - return 3; + } else { + camera = Metavision::Camera::from_file(event_file_path, Metavision::FileConfigHints().real_time_playback(true)); } + } catch (const Metavision::CameraException &e) { + MV_LOG_ERROR() << e.what(); + return 2; } - // get camera resolution - int camera_width = camera.geometry().width(); - int camera_height = camera.geometry().height(); - - // create a MostRecentTimestampBuffer to store the last timestamp of each pixel and initialize all elements to zero - Metavision::MostRecentTimestampBuffer time_surface(camera_height, camera_width, 1); - time_surface.set_to(0); - - // we use a mutex to control concurrent accesses to the time surface - std::mutex frame_mutex; - // create a variable where to store the latest timestamp - Metavision::timestamp last_time = 0; - // update the time surface using a callback on CD events - camera.cd().add_callback([&time_surface, &frame_mutex, &last_time](const Metavision::EventCD *ev_begin, - const Metavision::EventCD *ev_end) { - for (auto it = ev_begin; it != ev_end; ++it) { - std::unique_lock lock(frame_mutex); - time_surface.at(it->y, it->x) = it->t; - last_time = it->t; - } - }); + // Get camera resolution + const int camera_width = camera.geometry().width(); + const int camera_height = camera.geometry().height(); - // to render the frames, we create a window using the Window class of the UI module + // To render the frames, we create a window using the Window class of the UI + // module Metavision::Window window("Metavision Time Surface", camera_width, camera_height, Metavision::BaseWindow::RenderMode::BGR); - // we set a callback on the windows to close it when the Escape or Q key is pressed + // We set a callback on the windows to close it when the Escape or Q key is + // pressed window.set_keyboard_callback( [&window](Metavision::UIKeyEvent key, int scancode, Metavision::UIAction action, int mods) { if (action == Metavision::UIAction::RELEASE && @@ -128,27 +95,53 @@ int main(int argc, char *argv[]) { } }); - // create cv::Mat to store the time surface and the heatmap + // Create a MostRecentTimestampBuffer to store the last timestamp of each + // pixel and initialize all elements to zero + Metavision::MostRecentTimestampBuffer time_surface(camera_height, camera_width, 1); + time_surface.set_to(0); + // Create a variable to store the latest timestamp + Metavision::timestamp last_time = 0; + // Create cv::Mat to store the time surface and the heatmap + + // Initialize the slicer which is gonna slice the events into same duration + // buffers of events cv::Mat heatmap, time_surface_gray; + using Slicer = Metavision::EventBufferReslicerAlgorithm; + Slicer slicer( + [&](Slicer::ConditionStatus, Metavision::timestamp, std::size_t) { + // Generate the time surface from MostRecentTimestampBuffer + time_surface.generate_img_time_surface(last_time, delta_ts, time_surface_gray); + // Apply a colormap to the time surface and display the new frame + cv::applyColorMap(time_surface_gray, heatmap, cv::COLORMAP_JET); + window.show(heatmap); + }, + Slicer::Condition::make_n_us(delta_ts)); + + // Update the time surface using a callback on CD events + camera.cd().add_callback([&](const Metavision::EventCD *ev_begin, const Metavision::EventCD *ev_end) { + slicer.process_events(ev_begin, ev_end, [&](const Metavision::EventCD *begin, const Metavision::EventCD *end) { + if (begin == end) + return; + + last_time = std::prev(end)->t; + for (auto it = begin; it != end; ++it) + time_surface.at(it->y, it->x) = it->t; + }); + }); - // start the camera + // Start the camera camera.start(); - // keep running until the camera is off, the recording is finished or the escape key was pressed + // Keep running until the recording is finished, the escape or 'q' key was pressed, or the window was closed while (camera.is_running() && !window.should_close()) { - if (!time_surface.empty()) { - std::unique_lock lock(frame_mutex); - // generate the time surface from MostRecentTimestampBuffer - time_surface.generate_img_time_surface(last_time, delta_ts, time_surface_gray); - // apply a colormap to the time surface and display the new frame - cv::applyColorMap(time_surface_gray, heatmap, cv::COLORMAP_JET); - window.show(heatmap); - } - // we poll events (keyboard, mouse etc.) from the system with a 20ms sleep to avoid using 100% of a CPU's core - // and we push them into the window where the callback on the escape key will ask the windows to close + // We poll events (keyboard, mouse etc.) from the system with a 20ms sleep + // to avoid using 100% of a CPU's core and we push them into the window + // where the callback on the escape key will ask the windows to close static constexpr std::int64_t kSleepPeriodMs = 20; Metavision::EventLoop::poll_and_dispatch(kSleepPeriodMs); } camera.stop(); + + return 0; } diff --git a/sdk/modules/driver/cpp/3rdparty/hdf5_ecf b/sdk/modules/driver/cpp/3rdparty/hdf5_ecf index d3235ecf1..8735ce716 160000 --- a/sdk/modules/driver/cpp/3rdparty/hdf5_ecf +++ b/sdk/modules/driver/cpp/3rdparty/hdf5_ecf @@ -1 +1 @@ -Subproject commit d3235ecf12497d57b0de80e1d989804ab2cf97a9 +Subproject commit 8735ce7161a637cacc5f0affef48129a13ca5a3f diff --git a/sdk/modules/driver/cpp/include/metavision/sdk/driver/camera_error_code.h b/sdk/modules/driver/cpp/include/metavision/sdk/driver/camera_error_code.h index 2bdd1259c..2bf077f39 100644 --- a/sdk/modules/driver/cpp/include/metavision/sdk/driver/camera_error_code.h +++ b/sdk/modules/driver/cpp/include/metavision/sdk/driver/camera_error_code.h @@ -61,6 +61,9 @@ enum Enum : CameraErrorCodeType { WrongExtension = InvalidArgument | 0x3, /// Could not open file CouldNotOpenFile = InvalidArgument | 0x4, + + /// Errors related to connectivity + ConnectionError = CameraError | 0x4000, }; } diff --git a/sdk/modules/driver/cpp/samples/metavision_viewer/metavision_viewer.cpp b/sdk/modules/driver/cpp/samples/metavision_viewer/metavision_viewer.cpp index 3ea6dc6ed..7588e09eb 100644 --- a/sdk/modules/driver/cpp/samples/metavision_viewer/metavision_viewer.cpp +++ b/sdk/modules/driver/cpp/samples/metavision_viewer/metavision_viewer.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include static const int ESCAPE = 27; @@ -141,7 +142,7 @@ int main(int argc, char *argv[]) { std::string out_cam_config_path; std::vector roi; - bool do_retry = false; + std::atomic do_retry = false; const std::string short_program_desc( "Simple viewer to stream events from an event file or a device, using the SDK driver API.\n"); @@ -245,7 +246,14 @@ int main(int argc, char *argv[]) { } camera_is_opened = true; - } catch (Metavision::CameraException &e) { MV_LOG_ERROR() << e.what(); } + } catch (Metavision::CameraException &e) { + MV_LOG_ERROR() << e.what(); + if (e.code().value() == Metavision::CameraErrorCode::ConnectionError) { + do_retry = true; + MV_LOG_INFO() << "Trying to reopen camera..."; + continue; + } + } } // With the HAL device corresponding to the camera object (file or live camera), we can try to get a facility @@ -274,6 +282,9 @@ int main(int argc, char *argv[]) { // Add runtime error callback camera.add_runtime_error_callback([&do_retry](const Metavision::CameraException &e) { + if (e.code().value() == Metavision::CameraErrorCode::ConnectionError) { + MV_LOG_ERROR() << "Lost connection with the device. Please try replugging the device"; + } MV_LOG_ERROR() << e.what(); do_retry = true; }); @@ -339,7 +350,16 @@ int main(int argc, char *argv[]) { }); // Start the camera streaming - camera.start(); + try { + camera.start(); + } catch (const Metavision::CameraException &e) { + MV_LOG_ERROR() << e.what(); + if (e.code().value() == Metavision::CameraErrorCode::ConnectionError) { + do_retry = true; + MV_LOG_INFO() << "Trying to reopen camera..."; + continue; + } + } bool recording = false; bool osc_available = false; @@ -524,7 +544,11 @@ int main(int argc, char *argv[]) { } // Stop the camera streaming, optional, the destructor will automatically do it - camera.stop(); + try { + camera.stop(); + } catch (const Metavision::CameraException &e) { + MV_LOG_ERROR() << e.what(); + } cd_frame_generator.stop(); } while (!signal_caught && do_retry); diff --git a/sdk/modules/driver/cpp/src/camera.cpp b/sdk/modules/driver/cpp/src/camera.cpp index 2de3893d7..a1cb28221 100644 --- a/sdk/modules/driver/cpp/src/camera.cpp +++ b/sdk/modules/driver/cpp/src/camera.cpp @@ -12,6 +12,7 @@ #include #include "metavision/hal/device/device_discovery.h" +#include "metavision/hal/utils/hal_connection_exception.h" #include "metavision/sdk/driver/internal/camera_internal.h" #include "metavision/sdk/driver/internal/camera_live_internal.h" #include "metavision/sdk/driver/internal/camera_offline_generic_internal.h" @@ -104,7 +105,7 @@ bool Camera::Private::start() { // notifies the thread that it can start running run_thread_cond_.notify_one(); - while (!camera_is_started_) {} + while (!camera_is_started_ && is_running_) {} return true; } @@ -148,7 +149,7 @@ bool Camera::Private::start_recording(const std::string &file_path) { try { ret = start_recording_impl(file_path); } catch (CameraException &e) { - throw e; + throw; } catch (...) { throw CameraException(CameraErrorCode::CouldNotOpenFile, "Could not open file '" + file_path + @@ -361,6 +362,17 @@ void Camera::Private::load(std::istream &is) { throw CameraException(CameraErrorCode::CameraNotInitialized); } +void Camera::Private::propagate_runtime_error(const CameraException &e) { + std::map callbacks; + { + std::unique_lock lock(cbs_mutex_); + callbacks = runtime_error_callback_map_; + } + for (auto &&p : callbacks) { + p.second(e); + } +} + void Camera::Private::run() { check_initialization(); @@ -374,7 +386,15 @@ void Camera::Private::run() { // notifies that this thread can now be stopped if needed run_thread_cond_.notify_one(); - start_impl(); + try { + start_impl(); + } catch (const HalConnectionException &e) { + const CameraException camera_error = + CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + propagate_runtime_error(camera_error); + set_is_running(false); + return; + } camera_is_started_ = true; while (is_running_) { @@ -382,15 +402,15 @@ void Camera::Private::run() { if (!process_impl()) { break; } - } catch (std::exception &e) { - std::map callbacks; - { - std::unique_lock lock(cbs_mutex_); - callbacks = runtime_error_callback_map_; - } - for (auto &&p : callbacks) { - p.second(CameraException(CameraErrorCode::RuntimeError, std::string("Unexpected error : ") + e.what())); - } + } catch (const HalConnectionException &e) { + const CameraException camera_error = + CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + propagate_runtime_error(camera_error); + break; + } catch (const std::exception &e) { + const CameraException camera_error = + CameraException(CameraErrorCode::RuntimeError, std::string("Unexpected error : ") + e.what()); + propagate_runtime_error(camera_error); break; } } @@ -489,27 +509,51 @@ Camera::Camera(Private *pimpl) : pimpl_(pimpl) { } Camera Camera::from_first_available() { - return Camera(new detail::LivePrivate()); + try { + return Camera(new detail::LivePrivate()); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_first_available(const DeviceConfig &config) { - return Camera(new detail::LivePrivate(&config)); + try { + return Camera(new detail::LivePrivate(&config)); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_source(OnlineSourceType input_source_type, uint32_t source_index) { - return Camera(new detail::LivePrivate(input_source_type, source_index)); + try { + return Camera(new detail::LivePrivate(input_source_type, source_index)); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_source(OnlineSourceType input_source_type, const DeviceConfig &config, uint32_t source_index) { - return Camera(new detail::LivePrivate(input_source_type, source_index, &config)); + try { + return Camera(new detail::LivePrivate(input_source_type, source_index, &config)); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_serial(const std::string &serial) { - return Camera(new detail::LivePrivate(serial)); + try { + return Camera(new detail::LivePrivate(serial)); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_serial(const std::string &serial, const DeviceConfig &config) { - return Camera(new detail::LivePrivate(serial, &config)); + try { + return Camera(new detail::LivePrivate(serial, &config)); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } Camera Camera::from_file(const std::string &file_path, const FileConfigHints &hints) { @@ -615,7 +659,11 @@ const CameraGeneration &Camera::generation() const { } bool Camera::start() { - return pimpl_->start(); + try { + return pimpl_->start(); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } bool Camera::is_running() { @@ -623,7 +671,11 @@ bool Camera::is_running() { } bool Camera::stop() { - return pimpl_->stop(); + try { + return pimpl_->stop(); + } catch (const HalConnectionException &e) { + throw CameraException(CameraErrorCode::ConnectionError, std::string("Connection error: ") + e.what()); + } } bool Camera::start_recording(const std::string &file_path) { diff --git a/sdk/modules/driver/cpp/src/camera_offline_generic.cpp b/sdk/modules/driver/cpp/src/camera_offline_generic.cpp index d5e30a5cd..b69f52bf5 100644 --- a/sdk/modules/driver/cpp/src/camera_offline_generic.cpp +++ b/sdk/modules/driver/cpp/src/camera_offline_generic.cpp @@ -49,7 +49,7 @@ OfflineGenericPrivate::OfflineGenericPrivate(const std::string &file_path, const file_reader_ = std::make_unique(file_path); } } catch (CameraException &e) { - throw e; + throw; } catch (...) { throw CameraException(CameraErrorCode::CouldNotOpenFile); } diff --git a/sdk/modules/driver/cpp/src/include/metavision/sdk/driver/internal/camera_internal.h b/sdk/modules/driver/cpp/src/include/metavision/sdk/driver/internal/camera_internal.h index 12765806a..781aefe6b 100644 --- a/sdk/modules/driver/cpp/src/include/metavision/sdk/driver/internal/camera_internal.h +++ b/sdk/modules/driver/cpp/src/include/metavision/sdk/driver/internal/camera_internal.h @@ -84,6 +84,8 @@ class Camera::Private { void check_initialization() const; + void propagate_runtime_error(const CameraException &e); + Camera *pub_ptr_ = nullptr; detail::Config config_;