diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..d46dabf3 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,59 @@ +Checks: '*, + -llvmlibc-*, -altera-*, -fuchsia-*, + -modernize-use-trailing-return-type, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -readability-magic-numbers, + -misc-const-correctness, + -llvm-header-guard, + -hicpp-uppercase-literal-suffix, + -readability-uppercase-literal-suffix, + -readability-else-after-return, + -llvm-else-after-return, + -bugprone-easily-swappable-parameters, + -google-readability-todo, + -abseil-*, + -android-cloexec-open' +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-length.IgnoredVariableNames + value: 'i|j|k|w|h|x|y|z|it' + - key: readability-identifier-length.IgnoredLoopCounterNames + value: 'i|j|k|w|h|x|y|z|it' + - key: readability-identifier-length.IgnoredParameterNames + value: 'w|h|x|y|z' + - key: performance-unnecessary-value-param.AllowedTypes + value: 'Eigen::Ref|sync_ptr' + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.MemberCase + value: lower_case + - key: readability-identifier-naming.ProtectedMemberSuffix + value: _ + - key: readability-identifier-naming.PrivateMemberSuffix + value: _ + - key: readability-identifier-naming.ClassMethodCase + value: lower_case + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.TemplateParameterCase + value: CamelCase + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.GlobalConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.FunctionCase + value: lower_case \ No newline at end of file diff --git a/.github/workflows/build_test.yaml b/.github/workflows/build_test.yaml index 07275e7f..f7df15ea 100644 --- a/.github/workflows/build_test.yaml +++ b/.github/workflows/build_test.yaml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: install deps - run: sudo apt install build-essential cmake libjsoncpp-dev libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libglfw3-dev libglew-dev libspdlog-dev libflatbuffers-dev libgtest-dev clang-format + run: sudo apt install build-essential cmake libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libpng-dev libglfw3-dev libflatbuffers-dev libgtest-dev clang-format - name: cpp-lint run: ./clang-linting.sh - name: install python @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: install deps - run: sudo apt install build-essential cmake libjsoncpp-dev libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libglfw3-dev libglew-dev libspdlog-dev libflatbuffers-dev libgtest-dev clang-format + run: sudo apt install build-essential cmake libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libpng-dev libglfw3-dev libflatbuffers-dev libgtest-dev clang-format - name: cmake configure run: cmake . -B build -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON - name: cmake build @@ -38,7 +38,7 @@ jobs: - name: install python run: sudo apt install python3 python3-pip - name: install deps - run: sudo apt install build-essential cmake libjsoncpp-dev libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libglfw3-dev libglew-dev libspdlog-dev libflatbuffers-dev + run: sudo apt install build-essential cmake libeigen3-dev libcurl4-openssl-dev libtins-dev libpcap-dev libpng-dev libglfw3-dev libflatbuffers-dev - name: install python-deps run: pip3 install pytest pytest-xdist - name: build python @@ -53,9 +53,9 @@ jobs: uses: johnwason/vcpkg-action@v6 id: vcpkg with: - pkgs: jsoncpp eigen3 curl libtins glfw3 glew spdlog libpng flatbuffers gtest + pkgs: eigen3 curl libtins glfw3 libpng flatbuffers gtest triplet: x64-windows - revision: 2024.04.26 + revision: 2024.11.16 token: ${{ github.token }} github-binarycache: true - name: cmake configure @@ -74,9 +74,9 @@ jobs: uses: johnwason/vcpkg-action@v6 id: vcpkg with: - pkgs: jsoncpp eigen3 curl libtins glfw3 glew spdlog libpng flatbuffers gtest + pkgs: eigen3 curl libtins glfw3 libpng flatbuffers gtest triplet: x64-windows - revision: 2024.04.26 + revision: 2024.11.16 token: ${{ github.token }} github-binarycache: true - name: install python-deps @@ -92,7 +92,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: install deps - run: brew install cmake pkg-config jsoncpp eigen curl libtins glfw glew spdlog flatbuffers googletest + run: brew install cmake pkg-config eigen curl libtins glfw flatbuffers googletest - name: cmake configure run: cmake . -B build -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON - name: cmake build @@ -104,7 +104,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: install deps - run: brew install cmake pkg-config jsoncpp eigen curl libtins glfw glew spdlog flatbuffers + run: brew install cmake pkg-config eigen curl libtins glfw flatbuffers - name: install python-deps run: python3.12 -m pip install pytest pytest-xdist --break-system-packages - name: build python diff --git a/.gitignore b/.gitignore index 1d30d7a6..70a27769 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ CTestTestfile.cmake generated rules.ninja *.a -**/_build \ No newline at end of file +**/_build +clang-tidy-output.{txt,json} \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index f4ca0429..c03bdda8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "sdk-extensions"] path = sdk-extensions url = ../sdk-extensions.git - branch = master + branch = master \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3430909e..d8355d25 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,77 @@ Changelog ========= -**Important: as of 0.13.0, the SDK is no longer compatible with firmware versions older than 2.1.0.** +Important announcements +----------------------- +* As of 0.13.0, the SDK is no longer compatible with firmware versions older than 2.1.0. +* Official SDK support for firmware versions 2.2 and 2.3 will end at the end of June, 2025. +* Update vcpkg ref of build to 2024.11.16 + +[20250117] [0.14.0] +====================== + +ouster_client/C++ SDK +--------------------- + +* Jsoncpp fully removed for jsoncons +* [BREAKING] All the HTTP endpoint methods in the ``SensorHttpImp`` class now return a ``std::string`` instead of a ``Json::Value`` object. The result can be parsed with any json parser. +* Add CMake logic for packaging c++ sdk in binary format when ``-DBUILD_SHARED_LIBRARY=ON`` is enabled. + +ouster_client/Python SDK +------------------------ +* Add a new command ``localize`` to perform localization and tracking within a SLAM-generated map of a given site. +* Add ``LidarScan.sensor_info`` to store the relevant ``SensorInfo`` for each scan +* [BREAKING] Deprecated ``ScanBatcher::operator()(const uint8_t*, uint64_t, LidarScan&)`` for ``ScanBatcher::operator()(const LidarPacket&, LidarScan&)`` +* [BREAKING] Disabled ``OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32`` by default to help avoid ABI mismatches +* [BREAKING] Changed ``SensorClient`` ``get_packet`` API to return packet in the ``ClientEvent`` rather than through reference parameters +* Updated to Kiss ICP 1.1.0 version +* [BUGFIX] Fixed OSF failing to load scans saved in 4096 * 5 mode +* [BUGFIX] Fixed Python ``client.transform`` and ``client.dewarp`` methods returning incorrect results due to ignoring column layout of input data +* Refactored logging to remove spdlog API exposure +* Vendored spdlog in third party dependencies +* [BREAKING] Change ``sensor_info.sn`` type from string to uint64_t +* Support additional array types and formats for Cloud.set_xyz +* Added new ``mask``, ``reduce`` and ``clip`` ScanSource operations to the SDK CLI and API +* The ``clip`` command can now specify which fields to be applied to and accepts unit +* Add relevant methods from ``packet_format`` to ``LidarPacket`` and ``ImuPacket`` classes +* Add ``format`` to each ``Packet`` object with the relevant packet format +* Tolerate off-by-1-byte for bag files recorded using an older version of the ouster-ros driver +* Fix yaw axis to zero in the get_rot_matrix_to_align_to_gravity function +* [BUGFIX] Fix the ``-c`` option in ouster-cli ``config`` command to set config from file + +ouster_cli +---------- +* Add ``ouster-cli source SENSOR set_static_ip`` command to set sensor static IPs. +* Add ``ouster-cli source SENSOR diagnostics`` command to download sensor diagnostic dumps. +* [BREAKING] Merge the handling of ``--extrinsics-file`` ouster-cli option into ``--extrinsics`` option. +* [BREAKING] ouster-cli ``--extrinsics`` option requires adding double quotes for space separated values. +* ouster-cli ``--extrinsics`` option now accepts ``identity`` as a keyword for overrideng sensor extriniscs with identity. +* ouster-cli ``--extrinsics`` option now accepts the following additional formats besides the ``16`` numbers array format: + * ``--extrinsics X,Y,Z,R,PY`` for position + euler angles. + * ``--extrinsics X,Y,Z,QX,QY,QZ,QW`` for position + quaternion. +* Add cursor-driven AOI selection feature to 2d images in ouster-cli ``viz`` command. + +ouster_osf +---------- +* Introduce ``ouster::osf::AsyncWriter`` to offload saving ``LidarScan`` as OSF to a background thread, improving CLI performance when saving OSF files. +* Add the ``ouster::osf::Encoder`` type, which allows parameterizing the OSF compression level. +* Change the default OSF PNG encoder compression level to 1 from 4. +* [BREAKING] ``ouster.sdk.osf`` no longer exports lower-level OSF API classes (such as ``osf.Reader``.) +* ``ouster::osf::Writer::save`` now throws if the resolution of a ``LidarScan`` being saved doesn't match what is specified in sensor info/metadata. +* [BUGFIX] Fix incorrect ``OsfScanSource`` data when reading from an OSF file containing empty or missing streams. + +ouster_viz +---------- +* ``SimpleViz`` now drops frames when necessary to keep up with a live data source (i.e. sensor.) +* Add a map origin axis and label. +* Invoke frame buffer resize handlers added to ``PointViz`` when GLFW's window resize event fires. +* [BREAKING] Change ``PointViz::window_coordinates_to_image_pixel`` so that it always returns a pixel location (even outside the image), which can be useful in some situations. +* [BUGFIX] On screen display frame number starts at zero instead of one. +* [BUGFIX] ``LidarScanViz`` now only creates view modes for PIXEL fields. +* [BUGFIX] Use the last valid column pose as a ``LidarScan``'s origin, instead of the first. +* [BUGFIX] Limit number of keyboard shortcuts to toggle sensors from CTRL+1 to CTRL+9. +* [BUGFIX] Fix a key shortcut help rendering issue and improve consistency of key shortcut help. + [20241017] [0.13.1] ====================== @@ -101,7 +171,7 @@ ouster_viz * [BREAKING] removed ``bool update_on_input()`` and ``update_on_input(bool)`` methods from ``PointViz``. * [BUGFIX] SimpleViz throws a 'generator already executing' exception. -ouster_cli +ouster-cli ---------- * Add support for reading and writing ROS1 and ROS2 bag files. @@ -449,9 +519,7 @@ ouster_client ------------- * Add ``LidarScan.pose`` with poses per column -* Add ``_client.IndexedPcapReader`` and ``_client.PcapIndex`` to enable random - pcap file access by frame number - +* Add ``_client.IndexedPcapReader`` and ``_client.PcapIndex`` to enable random pcap file access by frame number * [BREAKING] remove ``ouster::Imu`` object * [BREAKING] change the return type of ``ouster::packet_format::frame_id`` from ``uint16_t`` to ``uint32_t`` * [BREAKING] remove methods ``px_range``, ``px_reflectivity``, ``px_signal``, and ``px_ambient`` from ``ouster::packet_format`` @@ -1073,3 +1141,4 @@ Fixed - fixed clipping issues with parallel projection - fixed point coloring issues in z-color mode - improved visualizer performance + diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ba18416..480b06a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,26 +13,28 @@ include(DefaultBuildType) include(VcpkgEnv) # ==== Project Name ==== -project(ouster-sdk VERSION 0.13.1) +project(ouster-sdk VERSION 0.14.0) # generate version header -set(OusterSDK_VERSION_STRING 0.13.1) +set(OusterSDK_VERSION_STRING 0.14.0) include(VersionGen) # ==== Options ==== option(CMAKE_POSITION_INDEPENDENT_CODE "Build position independent code." ON) -option(BUILD_SHARED_LIBS "Build shared libraries." OFF) option(BUILD_PCAP "Build pcap utils." ON) option(BUILD_OSF "Build Ouster OSF library." ON) option(BUILD_VIZ "Build Ouster visualizer." ON) +option(BUILD_PYTHON_MODULE "Build python module (should not use this except in special instances)." OFF) option(BUILD_TESTING "Build tests" OFF) option(BUILD_EXAMPLES "Build C++ examples" OFF) -option(OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32 "Eigen max aligned bytes." ON) +option(OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32 "Eigen max aligned bytes." OFF) +option(BUILD_SHARED_LIBRARY "Build shared Library." OFF) +option(BUILD_DEBIAN_FOR_GITHUB "Build debian for github ci" OFF) # when building as a top-level project if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - + set(OUSTER_TOP_LEVEL ON) if(NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -43,13 +45,15 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) endif() if(MSVC) - add_compile_options(/W2) + add_compile_options(/W2 /bigobj /Zf) add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN) else() add_compile_options(-Wall -Wextra) endif() include(CTest) +else() + set(OUSTER_TOP_LEVEL OFF) endif() # === Subdirectories === @@ -69,16 +73,26 @@ endif() if(BUILD_VIZ) add_subdirectory(ouster_viz) + add_subdirectory(thirdparty/glad) endif() if(BUILD_EXAMPLES) add_subdirectory(examples) endif() +if(BUILD_SHARED_LIBRARY) + add_subdirectory(ouster_library) +endif() + if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) add_subdirectory(tests) endif() +if(BUILD_PYTHON_MODULE) + set(OUSTER_SDK_PATH "${CMAKE_CURRENT_LIST_DIR}" CACHE STRING "SDK source directory") + add_subdirectory(python) +endif() + # ==== Packaging ==== set(CPACK_PACKAGE_CONTACT "oss@ouster.io") set(CPACK_PACKAGE_VENDOR "Ouster") @@ -88,18 +102,30 @@ set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.rst") set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/ouster") set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_PROCESSOR}) -set(CPACK_PACKAGE_NAME ouster_sdk) -set(CPACK_GENERATOR "DEB;TGZ") + +if(BUILD_DEBIAN_FOR_GITHUB) + set(CPACK_DEBIAN_PACKAGE_NAME ouster-sdk) + set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + set(CPACK_DEBIAN_PACKAGE_DEPENDS + "libeigen3-dev, libtins-dev, libglfw3-dev, libpng-dev, libflatbuffers-dev") +endif() + +if(OUSTER_LIBRARY_NAME) + set(CPACK_PACKAGE_NAME "${OUSTER_LIBRARY_NAME}") +else() + set(CPACK_PACKAGE_NAME "ouster-sdk") +endif() +set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CMAKE_BUILD_TYPE}-${CPACK_PACKAGE_VERSION}-${CMAKE_HOST_SYSTEM_NAME}-${CMAKE_SYSTEM_VERSION}-${CMAKE_SYSTEM_PROCESSOR}") + +set(CPACK_GENERATOR "TGZ;ZIP") +if(BUILD_DEBIAN_FOR_GITHUB) + set(CPACK_GENERATOR "DEB;${CPACK_GENERATOR}") +endif() # source packages set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") set(CPACK_SOURCE_IGNORE_FILES /.git /dist) -# deb options -set(CPACK_DEBIAN_PACKAGE_NAME ouster-sdk) -set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) -set(CPACK_DEBIAN_PACKAGE_DEPENDS - "libjsoncpp-dev, libeigen3-dev, libtins-dev, libglfw3-dev, libglew-dev, libspdlog-dev, libpng-dev, libflatbuffers-dev") include(CPack) # ==== Install ==== @@ -117,14 +143,22 @@ install(EXPORT ouster-sdk-targets configure_file(cmake/OusterSDKConfig.cmake.in OusterSDKConfig.cmake @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/OusterSDKConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/OusterSDKConfigVersion.cmake" + "cmake/Coverage.cmake" + "cmake/VcpkgEnv.cmake" DESTINATION lib/cmake/OusterSDK) -if(BUILD_PCAP) - # Install the findpcap cmake file for install targets. - # For some reason it only works if you rename it to PcapConfig.cmake - install(FILES "cmake/FindPcap.cmake" - DESTINATION lib/cmake/OusterSDK - RENAME PcapConfig.cmake) -endif() +install(FILES "cmake/FindEigen3.cmake" + DESTINATION lib/cmake/OusterSDK) +install(FILES "cmake/Findlibtins.cmake" + DESTINATION lib/cmake/OusterSDK) +install(FILES "cmake/FindCURL.cmake" + DESTINATION lib/cmake/OusterSDK) +install(FILES "cmake/Findglfw3.cmake" + DESTINATION lib/cmake/OusterSDK) +install(FILES "cmake/FindPcap.cmake" + DESTINATION lib/cmake/OusterSDK) +install(FILES "cmake/FindFlatbuffers.cmake" + DESTINATION lib/cmake/OusterSDK) + install(FILES LICENSE LICENSE-bin DESTINATION share) diff --git a/CMakeSettings.json b/CMakeSettings.json index dcf61699..78517021 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -12,7 +12,7 @@ "inheritEnvironments": [ "msvc_x64_x64" ], "variables": [ { - "name": "BUILD_EXAMPLES", + "name": "VCPKG_MANIFEST_MODE", "value": "True", "type": "BOOL" } @@ -30,7 +30,7 @@ "ctestCommandArgs": "", "variables": [ { - "name": "BUILD_EXAMPLES", + "name": "VCPKG_MANIFEST_MODE", "value": "True", "type": "BOOL" } diff --git a/LICENSE b/LICENSE index d6e599d0..827d523a 100644 --- a/LICENSE +++ b/LICENSE @@ -11,10 +11,52 @@ Files: include/optional-lite/* Copyright: 2014-2021 Martin Moene License: BSL-1.0 -Files: include/jsoncons* -Copyright Daniel Parker 2013 - 2020 +Files: thirdparty/include/jsoncons* +Copyright: Daniel Parker 2013 - 2020 License: BSL-1.0 +Files: thirdparty/spdlog/* +Copyright: 2016 Gabi Melman. +License: MIT + +Files: thirdparty/glad/* +Copyright: The Khronos Group, Public Domain +License: Apache-2.0 + +Files: thirdparty/glad/include/khrplatform.h +Copyright: 2008-2018 The Khronos Group Inc. +License: MIT + +Files: thirdparty/spdlog/fmt/* +Copyright: 2012 - present, Victor Zverovich and {fmt} contributors +Availability: https://github.com/fmtlib/fmt/ +License: MIT derivative + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + License: BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -64,4 +106,74 @@ License: BSL-1.0 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. \ No newline at end of file + DEALINGS IN THE SOFTWARE. + +License: Apache-2.0 + Apache License - Version 2.0, January 2004 + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + You must give any other recipients of the Work or Derivative Works a copy of this License; and + You must cause any modified files to carry prominent notices stating that You changed the files; and + You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and/or associated documentation files (the + "Materials"), to deal in the Materials without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Materials, and to + permit persons to whom the Materials are furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Materials. + + THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. diff --git a/LICENSE-bin b/LICENSE-bin index 6a0d9f8b..da219f25 100644 --- a/LICENSE-bin +++ b/LICENSE-bin @@ -7,70 +7,11 @@ Description: compiled-in header-only library Availability: https://gitlab.com/libeigen/eigen License: MPL2 -Name: glew -Description: statically linked -Availability: https://github.com/nigels-com/glew -License: BSD 3-clause - The OpenGL Extension Wrangler Library - Copyright (C) 2002-2007, Milan Ikits - Copyright (C) 2002-2007, Marcelo E. Magallon - Copyright (C) 2002, Lev Povalahev - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * The name of the author may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - THE POSSIBILITY OF SUCH DAMAGE. - Name: glfw Description: statically linked Availability: https://github.com/glfw/glfw License: zlib -Name: jsoncpp -Description: statically linked -Availability: https://github.com/open-source-parsers/jsoncpp -License: MIT - Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, copy, - modify, merge, publish, distribute, sublicense, and/or sell copies - of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - Name: jsoncons Description: statically linked vendored header only library Availability: https://github.com/danielaparker/jsoncons/ @@ -234,30 +175,6 @@ License: Zlib License 3. This notice may not be removed or altered from any source distribution. -Name: glad -Description: statically linked -Availability: https://github.com/Dav1dde/glad/ -License: MIT - Copyright (c) 2013-2021 David Herberth - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Name: curl Description: statically linked Availability: https://github.com/curl/curl @@ -284,7 +201,7 @@ License: Curl License in this Software without prior written authorization of the copyright holder. Name: spdlog -Description: statically linked +Description: statically linked vendored header only library Availability: https://github.com/gabime/spdlog License: MIT Copyright (c) 2016 Gabi Melman. @@ -311,6 +228,37 @@ License: MIT This software depends on the fmt lib (MIT License), and users must comply to its license: https://github.com/fmtlib/fmt/blob/master/LICENSE.rst +Name: fmt +Description: statically linked vendored header only library +Availability: https://github.com/fmtlib/fmt/ +License: MIT derivative + Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. Name: flatbuffers Description: statically linked @@ -528,4 +476,4 @@ subject to the following restrictions: not be misrepresented as being the original software. 3. This Copyright notice may not be removed or altered from any - source or altered source distribution. \ No newline at end of file + source or altered source distribution. diff --git a/Security.md b/Security.md index 49be5e11..daf444a6 100644 --- a/Security.md +++ b/Security.md @@ -34,6 +34,6 @@ Impact of the issue, including how an attacker might exploit the issue We prefer all communications to be in English. ### Response time -You should receive a response within 24 hours. +You should receive a response within 3 business days. If for some reason you do not, please follow up via email to ensure we received your original message. \ No newline at end of file diff --git a/_clang-tidy.py b/_clang-tidy.py new file mode 100644 index 00000000..0934376d --- /dev/null +++ b/_clang-tidy.py @@ -0,0 +1,195 @@ +import argparse +import re +import shutil +import glob +import os +import subprocess +from multiprocessing import Pool, cpu_count +import time +import json +import sys + +class ClangTidy: + class ClangTidyEntry: + def __init__(self, path, line_number, column_number, + msg_level, msg, name, split_names): + self.path = path + self.line_number = line_number + self.column_number = column_number + self.msg_level = msg_level + self.msg = msg + if name: + if split_names: + self.name = name.split(',') + else: + self.name = [name] + else: + self.name = "" + + def __str__(self): + return str(self.__dict__) + + def __eq__(self, other): + return (self.path == other.path and self.line_number == other.line_number and + self.column_number == other.column_number and self.msg_level == other.msg_level and + self.msg == other.msg and self.name == other.name) + + def __hash__(self): + return hash((self.path, self.line_number, self.column_number, + self.msg_level, self.msg, tuple(self.name))) + + + def __init__(self, clang_tidy_bin, paths, threads, compile_commands, clang_tidy_config, build_dir, split_names): + self._clang_tidy_bin = clang_tidy_bin + self._files = [] + self._split_names = split_names + for item in paths: + if os.path.isdir(item): + self._files.extend(glob.glob(item + "/*.h")) + self._files.extend(glob.glob(item + "/*.cpp")) + self._files.extend(glob.glob(item + "/**/*.h")) + self._files.extend(glob.glob(item + "/**/*.cpp")) + else: + self._files.append(item) + self._threads = threads + self._compile_commands = compile_commands + self._clang_tidy_config = clang_tidy_config + self._build_dir = build_dir + self._clang_message_regex = re.compile(r"^(?P.+):(?P\d+):(?P\d+): " + r"(?P\S+): (?P.*?)( \[(?P.*)\])?$") + self._entries = set() + self._count = 0 + + def _tidy_thread(self, file_path): + clang_tidy_args = [self._clang_tidy_bin, + "-extra-arg=-ferror-limit=0", + "-p", self._compile_commands, + f"--config-file={self._clang_tidy_config}", + file_path + ] + return subprocess.run(clang_tidy_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout + + def _flush_entry(self, current_result): + entry = self.ClangTidyEntry(current_result.group('path'), current_result.group('line_number'), + current_result.group('column_number'), current_result.group('msg_level'), + current_result.group('msg'), current_result.group('name'), self._split_names) + self._entries.add(entry) + self._count += 1 + + def run_tidy(self, raw_output_file=None, json_output_file=None, json_summary_output_file=None, progress_bar=True, quiet=True): + if progress_bar: + try: + from tqdm import tqdm + except: + print("tqdm not found, cant display progress bar. please install tqdm") + progress_bar = False + _pool = Pool(processes=self._threads) + results = [_pool.apply_async(self._tidy_thread, args=(item, )) for item in self._files] + print("Processing Files") + if raw_output_file: + if os.path.exists(raw_output_file): + os.remove(raw_output_file) + bar = None + if progress_bar: + bar = tqdm(total=len(self._files)) + while len(results) > 0: + if progress_bar: + bar.refresh() + time.sleep(1) + for item in results: + if item.ready(): + results.remove(item) + raw_output = item.get().decode('utf-8') + if raw_output_file: + with open(raw_output_file, 'a') as f: + f.write(raw_output) + for line in raw_output.split("\n"): + result = self._clang_message_regex.match(line) + if result: + self._flush_entry(result) + if progress_bar: + bar.update() + bar.refresh() + break + if progress_bar: + bar.clear() + bar.close() + temp_json = json.dumps([x.__dict__ for x in list(self._entries)], indent=4) + if not quiet: + print("Raw Output:") + with open(raw_output_file, 'r') as f: + print(f.read()) + print(f"Number of Unique entries: {len(self._entries)}") + print(f"Number of Entries Encountered: {self._count}") + if json_output_file: + with open(json_output_file, 'w') as f: + f.write(temp_json) + buckets = {} + for item in list(self._entries): + for name in item.name: + if name not in buckets: + buckets[name] = 0 + buckets[name] += 1 + + issue_categories = dict(sorted(buckets.items(), key=lambda item: item[1], reverse=True)) + summary = { + "unique_entries": len(self._entries), + "total_entries": self._count, + "issue_categories": issue_categories + } + if json_summary_output_file: + with open(json_summary_output_file, 'w') as f: + f.write(json.dumps(summary, indent=4)) + + print("Most Frequent Issue Categories:") + print(issue_categories) + print ("Summary") + print(summary) + + return self._entries + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Run Clang Tidy') + parser.add_argument("--clang-tidy-bin", + help='Path to clang-tidy binary', + required=True, + type=str) + parser.add_argument("-j", "--threads-to-use", + help='Number Of Threads To Use', + default=cpu_count(), + type=int) + parser.add_argument("--compile-commands-json", + help='Path to compile_commands.json file', + required=True, + type=str) + parser.add_argument("--clang-tidy-config", + help='Path to .clang-tidy file', + required=True, + type=str) + parser.add_argument("--build-dir", + help='Path to the build directory', + required=True, + type=str) + parser.add_argument('-p', "--paths", nargs='+', + help='Paths to Scan', + required=True) + parser.add_argument("--json-output", type=str, default=None, + help='Json Output File') + parser.add_argument("--json-summary-output", type=str, default=None, + help='Json Summary Output File') + parser.add_argument("--raw-output", type=str, default=None, + help='Raw Output File') + + parser.add_argument("--split-names", default=False, action='store_true', + help='Split the names into their own buckets') + + parser.add_argument("--quiet", default=True, action='store_true', + help='Do not emit stdout messages') + + args = parser.parse_args() + + tidy = ClangTidy(args.clang_tidy_bin, args.paths, + args.threads_to_use, args.compile_commands_json, + args.clang_tidy_config, args.build_dir, args.split_names) + + tidy.run_tidy(args.raw_output, args.json_output, args.json_summary_output, sys.stdout.isatty(), args.quiet) diff --git a/check_exports.py b/check_exports.py new file mode 100644 index 00000000..b86f4387 --- /dev/null +++ b/check_exports.py @@ -0,0 +1,255 @@ +import sys +import clang.cindex +import glob +import os +import atexit +import shutil +import subprocess +import tempfile +import argparse +import time +from multiprocessing import Pool, cpu_count + +def find_annotation(node): + for child in node.get_children(): + if child.kind == clang.cindex.CursorKind.ANNOTATE_ATTR: + return child.spelling + +CLASS_TEMPLATE_METHOD = "" + +def process_ast(node): + wrong_annotation = [] + correct_annotation = [] + missing_annotation = [] + initial_queue = [node] + ouster_queue = [] + ast_type_match = { + clang.cindex.CursorKind.CLASS_DECL: "OUSTER_API_CLASS", + clang.cindex.CursorKind.CONSTRUCTOR: "OUSTER_API_FUNCTION", + clang.cindex.CursorKind.DESTRUCTOR: "OUSTER_API_FUNCTION", + clang.cindex.CursorKind.CXX_METHOD: "OUSTER_API_FUNCTION", + clang.cindex.CursorKind.STRUCT_DECL: "OUSTER_API_CLASS", + clang.cindex.CursorKind.FUNCTION_DECL: "OUSTER_API_FUNCTION", + + } + ouster_api_ignore = "OUSTER_API_IGNORE" + while len(initial_queue) > 0: + current_node = initial_queue.pop(0) + if current_node.kind == clang.cindex.CursorKind.NAMESPACE and current_node.spelling == "ouster": + ouster_queue.append(current_node) + else: + initial_queue.extend([x for x in current_node.get_children()]) + + while len(ouster_queue) > 0: + current_node = ouster_queue.pop(0) + kind = current_node.kind + + in_class = current_node.lexical_parent and (current_node.lexical_parent.kind == clang.cindex.CursorKind.CLASS_DECL or + current_node.lexical_parent.kind == clang.cindex.CursorKind.STRUCT_DECL) + in_class_template = current_node.lexical_parent and current_node.lexical_parent.kind == clang.cindex.CursorKind.CLASS_TEMPLATE + + # make sure it actually is a class method if template + if kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE: + if in_class or in_class_template: + kind = CLASS_TEMPLATE_METHOD + if current_node.access_specifier != clang.cindex.AccessSpecifier.PUBLIC: + continue + + if (kind == clang.cindex.CursorKind.CXX_METHOD or + kind == clang.cindex.CursorKind.CONSTRUCTOR or + kind == clang.cindex.CursorKind.DESTRUCTOR) and in_class_template: + kind = CLASS_TEMPLATE_METHOD + if current_node.access_specifier != clang.cindex.AccessSpecifier.PUBLIC: + if kind == clang.cindex.CursorKind.CONSTRUCTOR: + continue + if kind == clang.cindex.CursorKind.CXX_METHOD: + continue + if kind == clang.cindex.CursorKind.STRUCT_DECL: + continue + if kind == clang.cindex.CursorKind.FUNCTION_DECL: + continue + if kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE: + if len([x for x in current_node.get_children()]) == 0: + continue + + if kind == clang.cindex.CursorKind.FUNCTION_DECL and in_class: + continue + + if kind == clang.cindex.CursorKind.CLASS_DECL: + if len([x for x in current_node.get_children()]) == 0: + continue + + if kind == clang.cindex.CursorKind.CLASS_TEMPLATE: + template_count = 0 + total_count = 0 + for item in current_node.get_children(): + if item.kind == clang.cindex.CursorKind.TEMPLATE_TYPE_PARAMETER: + template_count += 1 + if item.kind == clang.cindex.CursorKind.TEMPLATE_NON_TYPE_PARAMETER: + template_count += 1 + total_count += 1 + if total_count == template_count: + continue + + if kind == clang.cindex.CursorKind.STRUCT_DECL: + if len([x for x in current_node.get_children()]) == 0: + continue + + if kind in ast_type_match: + annotation = find_annotation(current_node) + if annotation: + if annotation == ouster_api_ignore: + continue + if annotation != ast_type_match[kind]: + wrong_annotation.append((current_node, ast_type_match[kind], annotation)) + else: + correct_annotation.append((current_node, ast_type_match[kind], annotation)) + else: + missing_annotation.append((current_node, ast_type_match[kind], annotation)) + ouster_queue.extend([x for x in current_node.get_children()]) + + wrong_annotation.sort(key=lambda x: str(list(x)[0].location.file)) + wrong_annotation = [item_to_string(*x) for x in wrong_annotation] + correct_annotation.sort(key=lambda x: str(list(x)[0].location.file)) + correct_annotation = [item_to_string(*x) for x in correct_annotation] + missing_annotation.sort(key=lambda x: str(list(x)[0].location.file)) + missing_annotation = [item_to_string(*x) for x in missing_annotation] + + return (wrong_annotation, correct_annotation, missing_annotation) + + +def get_full_name(item): + output = "" + stack = [item.displayname] + filename = item.location.file + current_node = item.lexical_parent + while current_node is not None and current_node.spelling != filename: + stack.append(current_node.spelling) + current_node = current_node.lexical_parent + + return "::".join(reversed(stack[:-1])) + + +def item_to_string(item, expected, actual): + return f"\t{item.location.file}:[{item.location.line}:{item.location.column}] {get_full_name(item)} Kind: {item.kind}: Expected: {expected} Actual: {actual}" + + +def process(file_to_check, tempdir): + index = clang.cindex.Index.create() + token_dict = {} + compdb = clang.cindex.CompilationDatabase.fromDirectory(tempdir) + commands = compdb.getCompileCommands(file_to_check) + + file_args = ["-D OUSTER_CHECK_EXPORTS=1"] + for command in commands: + for argument in command.arguments: + file_args.append(argument) + tu = index.parse(file_to_check, file_args[:-2]) + wrong_annotation, correct_annotation, missing_annotation = process_ast(tu.cursor) + + return missing_annotation, wrong_annotation, file_to_check + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + prog='check_exports.py', + description='Check the linker exports for the project') + + + parser.add_argument('--vcpkg-toolchain', default=None) + parser.add_argument('--vcpkg-triplet', default=None) + parser.add_argument("-j", "--threads-to-use", + help='Number Of Threads To Use', + default=cpu_count(), + type=int) + parser.add_argument('-v', '--verbose', action='store_true', default=False) + + args = parser.parse_args() + + vcpkg_toolchain = args.vcpkg_toolchain + vcpkg_triplet = args.vcpkg_triplet + threads = args.threads_to_use + verbose = args.verbose + + wrong = [] + missing = [] + source_dir = os.path.dirname(os.path.abspath(__file__)) + files = [] + files.extend(glob.glob(f"{source_dir}/ouster_client/include/ouster/*.h")) + files.extend(glob.glob(f"{source_dir}/ouster_osf/include/ouster/osf/*.h")) + files.extend(glob.glob(f"{source_dir}/ouster_pcap/include/ouster/*.h")) + files.extend(glob.glob(f"{source_dir}/ouster_viz/include/ouster/*.h")) + + tempdir = tempfile.mkdtemp() + def cleanup(): + shutil.rmtree(tempdir) + + atexit.register(cleanup) + + + if vcpkg_toolchain and vcpkg_triplet: + subprocess.check_output(['cmake', source_dir, + f"-DCMAKE_TOOLCHAIN_FILE={vcpkg_toolchain}", + f"-DVCPKG_DEFAULT_TRIPLET={vcpkg_triplet}", + "-DVCPKG_MANIFEST_MODE=OFF"], + cwd=tempdir) + else: + subprocess.check_output(['cmake', '-B', tempdir, source_dir], cwd=tempdir) + + pool = Pool(processes=threads) + + progress_bar = sys.stdout.isatty() + bar = None + if progress_bar: + try: + from tqdm import tqdm + except: + print("tqdm not found, cant display progress bar. please install tqdm") + progress_bar = False + if progress_bar: + bar = tqdm(total=len(files)) + + results = [pool.apply_async(process, args=(file_to_check, tempdir)) + for file_to_check in files] + + missing = [] + wrong = [] + files_complete = [] + while len(results) > 0: + if progress_bar: + bar.refresh() + time.sleep(0.1) + for item in results: + if item.ready(): + results.remove(item) + missing_annotation, wrong_annotation, file_to_check = item.get() + missing.extend(missing_annotation) + wrong.extend(wrong_annotation) + files_complete.append(file_to_check) + if progress_bar: + if verbose: + tqdm.write(f"Finished File: {file_to_check}") + tqdm.write(f"Remaining Files:") + for remaining in list(set(files) - set(files_complete)): + tqdm.write(f" {remaining}") + bar.update() + bar.refresh() + break + if progress_bar: + bar.clear() + bar.close() + issues = False + if len(wrong) > 0: + issues = True + print(f"Wrong:") + for item in set(wrong): + print(item) + if len(missing) > 0: + issues = True + print(f"Missing:") + for item in set(missing): + print(item) + + if issues: + sys.exit(1) + else: + print("No issues detected") diff --git a/clang-linting.sh b/clang-linting.sh index 349c2126..21c02ccb 100755 --- a/clang-linting.sh +++ b/clang-linting.sh @@ -49,4 +49,4 @@ if [ -z "$OUTPUT" ]; then else run_command exit 1; -fi \ No newline at end of file +fi diff --git a/clang-tidy.sh b/clang-tidy.sh new file mode 100755 index 00000000..5d4c4663 --- /dev/null +++ b/clang-tidy.sh @@ -0,0 +1,82 @@ +#! /bin/bash +set -e +# Variables +BUILD_DIR=$(mktemp -d) +SOURCE_DIR=$(cd $(dirname $0) && pwd) +CURRENT_DIR=$PWD +CLANG_TIDY_THREADS=${CLANG_TIDY_THREADS:-$(nproc)} + +# Check For Tools +which clang-tidy 2>&1 > /dev/null +if [ $? -ne 0 ]; +then + echo "ERROR: clang-tidy needs to be installed to run this tool" + exit 1 +else + clang-tidy --help | grep "config-file" 2>&1 > /dev/null + if [ $? -ne 0 ]; + then + echo "ERROR: clang-tidy needs to be updated, missing --config-file cli support" + exit 2 + fi +fi + +which python3 2>&1 > /dev/null +if [ $? -ne 0 ]; +then + echo "ERROR: python3 needs to be installed to run this tool" + exit 3 +fi + +which cmake 2>&1 > /dev/null +if [ $? -ne 0 ]; +then + echo "ERROR: cmake needs to be installed to run this tool" + exit 4 +fi + +python3 -c "import pybind11" 2>&1 > /dev/null +if [ $? -ne 0 ]; +then + echo "ERROR: pybind11 (via pip) to be installed to run this tool" + echo "ERROR: run python3 -m pip install pybind11" + exit 5 +fi +python3 -c "import tqdm" 2>&1 > /dev/null +if [ $? -ne 0 ]; +then + echo "ERROR: tqdm (via pip) to be installed to run this tool" + echo "ERROR: run python3 -m pip install tqdm" + exit 6 +fi + +# Register an on exit cleanup function to delete the temporary +# directory. +cleanup() { + rm -rf "$BUILD_DIR" +} +trap cleanup EXIT + +# Map out the files to be checked, if no files are specified on the command line +# include all of the source files +FILES="$SOURCE_DIR/ouster_client $SOURCE_DIR/ouster_pcap $SOURCE_DIR/ouster_osf $SOURCE_DIR/ouster_viz $SOURCE_DIR/python $SOURCE_DIR/examples" +if [ "$#" -ne 0 ]; then + FILES="" + for item in "$@" + do + # Grab the realpath of the files before we change directories + FILES="$FILES $(realpath $item)" + done +fi + +# Enter the temporary build directory +pushd $BUILD_DIR > /dev/null + +# Generate the compile_commands.json file for the tidy step +cmake $SOURCE_DIR -DBUILD_EXAMPLES=ON -DBUILD_PCAP=ON -DBUILD_OSF=ON -DBUILD_VIZ=ON -DBUILD_PYTHON_MODULE=ON -DSKIP_SDK_FIND=ON -DBUILD_TESTING=ON -DPYTHON_EXECUTABLE=$(which python3) +cmake --build . --target ouster_generate_header cpp_gen + +# Run tidy +python3 $SOURCE_DIR/_clang-tidy.py --clang-tidy-bin $(which clang-tidy) --compile-commands-json $PWD/compile_commands.json --clang-tidy-config $SOURCE_DIR/.clang-tidy -j $CLANG_TIDY_THREADS --build-dir $PWD --json-output $CURRENT_DIR/clang-tidy-output.json --raw-output $CURRENT_DIR/clang-tidy-output.txt --paths $FILES --json-summary-output $CURRENT_DIR/clang-tidy-output-summary.json + +popd > /dev/null \ No newline at end of file diff --git a/cmake/FindCURL.cmake b/cmake/FindCURL.cmake index 0c3d0591..69b70708 100644 --- a/cmake/FindCURL.cmake +++ b/cmake/FindCURL.cmake @@ -2,12 +2,16 @@ # Note: curl from VCPKG appears to completely ignore curl find modules despite # CMAKE_MODULE_PATH settings -include(FindPackageHandleStandardArgs) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) +endif() # Prefer package config if it exists -find_package(CURL CONFIG QUIET) +find_package(CURL CONFIG QUIET NO_CMAKE_FIND_ROOT_PATH) if(CURL_FOUND) - find_package_handle_standard_args(CURL CONFIG_MODE) + if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(CURL CONFIG_MODE) + endif() return() endif() @@ -32,9 +36,11 @@ find_library(CURL_LIBRARIES NAMES mark_as_advanced(CURL_LIBRARIES) # Check that we have everything that we need -find_package_handle_standard_args(CURL - REQUIRED_VARS CURL_INCLUDE_DIRS CURL_LIBRARIES - VERSION_VAR CURL_VERSION_STRING) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(CURL + REQUIRED_VARS CURL_INCLUDE_DIRS CURL_LIBRARIES + VERSION_VAR CURL_VERSION_STRING) +endif() if(NOT TARGET CURL::libcurl) add_library(CURL::libcurl UNKNOWN IMPORTED) diff --git a/cmake/FindEigen3.cmake b/cmake/FindEigen3.cmake index 8ba8cbba..1988b86e 100644 --- a/cmake/FindEigen3.cmake +++ b/cmake/FindEigen3.cmake @@ -1,10 +1,14 @@ # Define forwards-compatible imported target for old platforms (Ubuntu 16.04) -include(FindPackageHandleStandardArgs) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) +endif() -find_package(Eigen3 CONFIG QUIET) +find_package(Eigen3 CONFIG QUIET NO_CMAKE_FIND_ROOT_PATH) -find_package_handle_standard_args(Eigen3 CONFIG_MODE) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(Eigen3 CONFIG_MODE) +endif() if(NOT TARGET Eigen3::Eigen) add_library(Eigen3::Eigen INTERFACE IMPORTED) diff --git a/cmake/FindFlatbuffers.cmake b/cmake/FindFlatbuffers.cmake new file mode 100644 index 00000000..767da664 --- /dev/null +++ b/cmake/FindFlatbuffers.cmake @@ -0,0 +1,59 @@ +# Find flatbuffers and set flatc +# Set OUSTER_SKIP_FIND_PACKAGE_STANDARD to FALSE on APPLE +# When set to TRUE, there are: flatbuffers/flatbuffers.h not found errors +if(APPLE) + set(OUSTER_SKIP_FIND_PACKAGE_STANDARD FALSE) +else() + set(OUSTER_SKIP_FIND_PACKAGE_STANDARD TRUE) +endif() + +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) +endif() + +find_package(Flatbuffers CONFIG REQUIRED QUIET NO_CMAKE_FIND_ROOT_PATH) + +if(NOT TARGET flatbuffers::flatbuffers) + add_library(flatbuffers::flatbuffers INTERFACE IMPORTED) + set_target_properties(flatbuffers::flatbuffers PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${FLATBUFFERS_LIBRARY}" + ) + message(STATUS "FlatBuffers found: ${FLATBUFFERS_INCLUDE_DIR}") +endif() + +# Flatbuffers flatc resolution and different search name 'flatbuffers` with Conan +# NOTE2[pb]: 200221007: We changed Conan cmake package to look to `flatbuffers` +# because it started failing out of blue :idk:scream: will see. +if(NOT CONAN_EXPORTED) + if(NOT DEFINED FLATBUFFERS_FLATC_EXECUTABLE) + set(FLATBUFFERS_FLATC_EXECUTABLE flatbuffers::flatc) + endif() + message(STATUS "FLATBUFFERS_FLATC_EXECUTABLE found: ${FLATBUFFERS_FLATC_EXECUTABLE}" ) +else() + if(WIN32) + set(FLATBUFFERS_FLATC_EXECUTABLE flatc.exe) + else() + set(FLATBUFFERS_FLATC_EXECUTABLE flatc) + endif() + message(STATUS "flatbuffers found: ${Flatbuffers_DIR}" ) +endif() + + +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + # Find flatc executable + if(NOT DEFINED FLATBUFFERS_FLATC_EXECUTABLE) + if(WIN32) + find_program(FLATBUFFERS_FLATC_EXECUTABLE NAMES flatc.exe) + else() + find_program(FLATBUFFERS_FLATC_EXECUTABLE NAMES flatc) + endif() + endif() + + # Find the FlatBuffers include directory + find_path(FLATBUFFERS_INCLUDE_DIR NAMES flatbuffers/flatbuffers.h) + find_package_handle_standard_args( + Flatbuffers + DEFAULT_MSG FLATBUFFERS_FLATC_EXECUTABLE FLATBUFFERS_INCLUDE_DIR + ) +endif() \ No newline at end of file diff --git a/cmake/FindGTest.cmake b/cmake/FindGTest.cmake index 7929834f..ffecb646 100644 --- a/cmake/FindGTest.cmake +++ b/cmake/FindGTest.cmake @@ -1,5 +1,4 @@ # Attempt to deal with gtest cmake differences across platforms - function(find_gtest) # use system find module in this scope set(CMAKE_MODULE_PATH "") diff --git a/cmake/FindPcap.cmake b/cmake/FindPcap.cmake index b7c91f66..aa572ee1 100644 --- a/cmake/FindPcap.cmake +++ b/cmake/FindPcap.cmake @@ -22,11 +22,12 @@ if(NOT TARGET libpcap::libpcap) IMPORTED_LOCATION "${PCAP_LIBRARY}") endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Pcap - REQUIRED_VARS PCAP_LIBRARY PCAP_INCLUDE_DIR - VERSION_VAR PCAP_VERSION_STRING) - +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Pcap + REQUIRED_VARS PCAP_LIBRARY PCAP_INCLUDE_DIR + VERSION_VAR PCAP_VERSION_STRING) +endif() mark_as_advanced( PCAP_INCLUDE_DIR PCAP_LIBRARY) diff --git a/cmake/Findglfw3.cmake b/cmake/Findglfw3.cmake index cb473cff..8e61da89 100644 --- a/cmake/Findglfw3.cmake +++ b/cmake/Findglfw3.cmake @@ -1,7 +1,9 @@ # For ROS/catkin compatibility only. Set variables expected by catkin to allow exporting GLFW3 in # the DEPENDS clause of the catkin_package() macro. -include(FindPackageHandleStandardArgs) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) +endif() find_package(glfw3 CONFIG REQUIRED) @@ -11,4 +13,6 @@ if(TARGET glfw) get_target_property(GLFW3_INCLUDE_DIRS glfw INTERFACE_INCLUDE_DIRECTORIES) endif() -find_package_handle_standard_args(glfw3 CONFIG_MODE) +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(glfw3 CONFIG_MODE) +endif() diff --git a/cmake/Findjsoncpp.cmake b/cmake/Findjsoncpp.cmake deleted file mode 100644 index 967e7326..00000000 --- a/cmake/Findjsoncpp.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# Targets defined for jsoncpp differ a good amount between distributions -include(FindPackageHandleStandardArgs) - -# Recent jsoncpp cmake config fails if called twice -if(NOT jsoncpp_FOUND) - find_package(jsoncpp CONFIG QUIET) -endif() - -# This target exists on ubuntu / debian. For some reason debian bullseye doesn't -# ship a static lib / define the jsoncpp_lib_static target. -if(jsoncpp_FOUND AND TARGET jsoncpp_lib) - find_package_handle_standard_args(jsoncpp CONFIG_MODE) - return() -endif() - -# With vcpkg, only a static lib is available so create a target for compatibility -if(jsoncpp_FOUND AND TARGET jsoncpp_static) - add_library(jsoncpp_lib INTERFACE) - target_link_libraries(jsoncpp_lib INTERFACE jsoncpp_static) - install(TARGETS jsoncpp_lib EXPORT ouster-sdk-targets) - find_package_handle_standard_args(jsoncpp CONFIG_MODE) - return() -endif() - -# Fall back to find_library with hints from pkgconfig -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_JSONCPP QUIET jsoncpp) - if(PC_JSONCPP_FOUND) - set(jsoncpp_VERSION_STRING ${PC_JSONCPP_VERSION}) - endif() -endif() - -find_library(jsoncpp_LIBRARY NAMES libjsoncpp.so libjsoncpp.dylib - PATHS ${PC_JSONCPP_LIBDIR} ${PC_JSONCPP_LIBRARY_DIRS}) -find_library(jsoncpp_STATIC_LIBRARY NAMES libjsoncpp.a - PATHS ${PC_JSONCPP_LIBDIR} ${PC_JSONCPP_LIBRARY_DIRS}) - -find_path(jsoncpp_INCLUDE_DIR - NAMES json/json.h - PATHS ${PC_JSONCPP_INCLUDE_DIRS}) - -find_package_handle_standard_args(jsoncpp - REQUIRED_VARS jsoncpp_LIBRARY jsoncpp_INCLUDE_DIR - VERSION_VAR jsoncpp_VERSION_STRING) - -if(jsoncpp_FOUND) - add_library(jsoncpp_lib_static STATIC IMPORTED) - set_target_properties(jsoncpp_lib_static PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${jsoncpp_INCLUDE_DIR} - IMPORTED_LOCATION ${jsoncpp_STATIC_LIBRARY}) - - add_library(jsoncpp_lib SHARED IMPORTED) - set_target_properties(jsoncpp_lib PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES ${jsoncpp_INCLUDE_DIR} - IMPORTED_LOCATION ${jsoncpp_LIBRARY}) -endif() diff --git a/cmake/Findlibtins.cmake b/cmake/Findlibtins.cmake index 55c87d38..a45a992a 100644 --- a/cmake/Findlibtins.cmake +++ b/cmake/Findlibtins.cmake @@ -8,8 +8,9 @@ # Try to find a cmake config compatible with vcpkg and fall back to defining # targets using pkgconfig. -include(FindPackageHandleStandardArgs) - +if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + include(FindPackageHandleStandardArgs) +endif() # Prefer config, if found find_package(libtins CONFIG QUIET) if(libtins_FOUND AND TARGET tins) @@ -24,7 +25,9 @@ if(libtins_FOUND AND TARGET tins) set_target_properties(tins PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBTINS_INCLUDE_DIRS}) - find_package_handle_standard_args(libtins CONFIG_MODE) + if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(libtins CONFIG_MODE) + endif() else() # Fall back to find_library with hints from pkgconfig find_package(PkgConfig QUIET) @@ -43,9 +46,12 @@ else() NAMES tins/tins.h PATHS ${PC_TINS_INCLUDE_DIRS}) - find_package_handle_standard_args(libtins - REQUIRED_VARS LIBTINS_LIBRARIES LIBTINS_INCLUDE_DIRS - VERSION_VAR LIBTINS_VERSION) + if(NOT OUSTER_SKIP_FIND_PACKAGE_STANDARD) + find_package_handle_standard_args(libtins + REQUIRED_VARS LIBTINS_LIBRARIES LIBTINS_INCLUDE_DIRS + VERSION_VAR LIBTINS_VERSION) + endif() + endif() # LIBTINS_LIBRARIES is set, add target with a name compatible with the conan diff --git a/cmake/OusterSDKConfig.cmake.in b/cmake/OusterSDKConfig.cmake.in index 136243bf..c55688c4 100644 --- a/cmake/OusterSDKConfig.cmake.in +++ b/cmake/OusterSDKConfig.cmake.in @@ -1,38 +1,94 @@ +# configure vcpkg from environment variables, if present +include("${CMAKE_CURRENT_LIST_DIR}/VcpkgEnv.cmake") + +cmake_minimum_required(VERSION 3.12) + message(STATUS "Found OusterSDK: ${CMAKE_CURRENT_LIST_FILE}") include(CMakeFindDependencyMacro) -# ouster_client dependencies -find_dependency(Eigen3) -find_dependency(jsoncpp) -find_dependency(CURL) -find_dependency(spdlog) +get_filename_component(OUSTER_LINK_DIRS "${CMAKE_CURRENT_LIST_DIR}/../../" ABSOLUTE) +get_filename_component(OUSTER_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/../../../include/" ABSOLUTE) + +set(BUILD_SHARED_LIBRARY "@BUILD_SHARED_LIBRARY@") +set(OUSTER_SDK_COMPONENT_STATIC FALSE) +set(OUSTER_SDK_COMPONENT_SHARED FALSE) +set(OUSTER_SDK_COMPONENT_NUMBER 0) +foreach(component ${OusterSDK_FIND_COMPONENTS}) + set(OUSTER_SDK_COMPONENT_FOUND FALSE) + if("${component}" STREQUAL "Static") + set(OUSTER_SDK_COMPONENT_FOUND TRUE) + set(OUSTER_SDK_COMPONENT_STATIC TRUE) + elseif("${component}" STREQUAL "Shared" AND BUILD_SHARED_LIBRARY) + set(OUSTER_SDK_COMPONENT_FOUND TRUE) + set(OUSTER_SDK_COMPONENT_SHARED TRUE) + endif() + if(OUSTER_SDK_COMPONENT_FOUND) + math(EXPR OUSTER_SDK_COMPONENT_NUMBER "${OUSTER_SDK_COMPONENT_NUMBER} + 1") + else() + message(FATAL_ERROR "Unknown OusterSDK Component: ${component}") + endif() +endforeach() +if(OUSTER_SDK_COMPONENT_NUMBER EQUAL 0) + set(OUSTER_SDK_COMPONENT_STATIC TRUE) + set(OUSTER_SDK_COMPONENT_SHARED TRUE) +endif() +include("${CMAKE_CURRENT_LIST_DIR}/Coverage.cmake") + +if(OUSTER_SDK_COMPONENT_STATIC) + # ouster_client dependencies + SET(OUSTER_SKIP_FIND_PACKAGE_STANDARD TRUE) + include("${CMAKE_CURRENT_LIST_DIR}/FindEigen3.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/Findlibtins.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/FindPcap.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/FindCURL.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/Findglfw3.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/FindFlatbuffers.cmake") + SET(OUSTER_SKIP_FIND_PACKAGE_STANDARD FALSE) -# ouster_osf dependencies -if(@BUILD_OSF@) + find_package(Threads REQUIRED) find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) - find_package(Flatbuffers NAMES Flatbuffers FlatBuffers) + include("${CMAKE_CURRENT_LIST_DIR}/OusterSDKTargets.cmake") endif() -# viz dependencies -if(@BUILD_VIZ@) - set(OpenGL_GL_PREFERENCE GLVND) - find_dependency(OpenGL) - find_dependency(glfw3) +if(OUSTER_SDK_COMPONENT_SHARED) + SET(OUSTER_SKIP_FIND_PACKAGE_STANDARD TRUE) + include("${CMAKE_CURRENT_LIST_DIR}/FindEigen3.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/FindFlatbuffers.cmake") + SET(OUSTER_SKIP_FIND_PACKAGE_STANDARD FALSE) + + add_library(shared_library SHARED IMPORTED) + # find shared library + find_library(OUSTER_SHARED_LIBRARY shared_library REQUIRED + HINTS "${OUSTER_LINK_DIRS}" + ) + if(NOT OUSTER_SHARED_LIBRARY) + message(FATAL_ERROR "FAILED FINDING LIBRARY: ${OUSTER_SHARED_LIBRARY}") + endif() - if(@OUSTER_VIZ_USE_GLAD@) - find_dependency(glad) + message("OusterSDK::shared_library: \"${OUSTER_SHARED_LIBRARY}\"") + if(WIN32) + set_target_properties(shared_library PROPERTIES + IMPORTED_IMPLIB ${OUSTER_SHARED_LIBRARY} + ) else() - find_dependency(GLEW) + set_target_properties(shared_library PROPERTIES + IMPORTED_LOCATION ${OUSTER_SHARED_LIBRARY} + ) endif() -endif() + get_target_property(target_location shared_library LOCATION) + message("OusterSDK::shared_library target_location: \"${target_location}\"") -if(@BUILD_PCAP@) - # make libtins dependency optional; on debian distros, libtins doesn't include - # a config module and sdk targets will just include paths in that case - find_package(libtins QUIET) - find_package(Pcap REQUIRED HINTS ${CMAKE_CURRENT_LIST_DIR}) + set_target_properties(shared_library PROPERTIES + IMPORTED_GLOBAL TRUE + ) + target_link_libraries(shared_library INTERFACE Eigen3::Eigen) + set_property(TARGET shared_library APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS BUILD_SHARED_LIBS_IMPORT) + target_include_directories(shared_library INTERFACE + ${OUSTER_INCLUDE_DIRS} + ${OUSTER_INCLUDE_DIRS}/optional-lite + ${OUSTER_INCLUDE_DIRS}/fb_generated + ) + add_library(OusterSDK::shared_library ALIAS shared_library) endif() - -include("${CMAKE_CURRENT_LIST_DIR}/OusterSDKTargets.cmake") diff --git a/conan/test_package/CMakeLists.txt b/conan/test_package/CMakeLists.txt deleted file mode 100644 index dacc3d41..00000000 --- a/conan/test_package/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0) - -project(PackageTest CXX) - -find_package(OusterSDK REQUIRED) - -add_subdirectory(../../examples examples) diff --git a/conan/test_package/conanfile.py b/conan/test_package/conanfile.py deleted file mode 100644 index 34f29785..00000000 --- a/conan/test_package/conanfile.py +++ /dev/null @@ -1,39 +0,0 @@ -import os - -from conans import ConanFile, CMake, tools - - -class OusterSDKTestConan(ConanFile): - settings = "os", "compiler", "build_type", "arch" - generators = "cmake_paths", "cmake_find_package" - - def build(self): - cmake = CMake(self) - # Current dir is "test_package/build/" and CMakeLists.txt is - # in "test_package" - cmake.definitions["BUILD_OSF"] = True - cmake.definitions[ - "CMAKE_PROJECT_PackageTest_INCLUDE"] = os.path.join( - self.build_folder, "conan_paths.cmake") - cmake.configure() - cmake.build() - - def imports(self): - self.copy("*.dll", dst="bin", src="bin") - self.copy("*.dylib*", dst="bin", src="lib") - self.copy('*.so*', dst='bin', src='lib') - - def test(self): - if not tools.cross_building(self): - os.chdir("examples") - # on Windows VS puts execulables under `./Release` or `./Debug` folders - exec_path = f"{os.sep}{self.settings.build_type}" if self.settings.os == "Windows" else "" - self.run(f".{exec_path}{os.sep}client_example") - - # List files + ldd for verbosity - if self.settings.os == "Linux": - self.run(f"ls -al .{exec_path}{os.sep}") - self.run(f"ldd .{exec_path}{os.sep}osf_reader_example") - - # Smoke test OSF lib - self.run(f".{exec_path}{os.sep}osf_reader_example") diff --git a/conanfile.py b/conanfile.py deleted file mode 100644 index f6e92e11..00000000 --- a/conanfile.py +++ /dev/null @@ -1,156 +0,0 @@ -import os -import re -from conan import ConanFile -from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps -from conan.tools.files import collect_libs, load -from conan.tools.scm import Git - - -class ousterSdkRecipe(ConanFile): - name = "ouster_sdk" - package_type = "library" - license = "BSD 3-Clause License" - author = "Ouster, Inc." - url = "https://github.com/ouster-lidar/ouster-sdk" - description = "Ouster SDK - tools for working with Ouster Lidars" - topics = ("lidar", "driver", "hardware", "point cloud", "3d", "robotics", "automotive") - settings = "os", "compiler", "build_type", "arch" - - options = { - "build_viz": [True, False], - "build_pcap": [True, False], - "build_osf": [True, False], - "shared": [True, False], - "fPIC": [True, False], - "ensure_cpp17": [True, False], - "eigen_max_align_bytes": [True, False], - } - default_options = { - "build_viz": False, - "build_pcap": False, - "build_osf": False, - "shared": False, - "fPIC": True, - "ensure_cpp17": False, - "eigen_max_align_bytes": False, - } - - exports_sources = [ - "cmake/*", - "conan/*", - "ouster_client/*", - "ouster_pcap/*", - "ouster_osf/*", - "ouster_viz/*", - "tests/*", - "thirdparty/*", - "CMakeLists.txt", - "CMakeSettings.json", - "LICENSE", - "LICENSE-bin", - "README.rst" - ] - - # https://docs.conan.io/2/reference/conanfile/methods/set_version.html - def set_version(self): - content = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) - version = re.search("set\(OusterSDK_VERSION_STRING (.*)\)", content).group(1) - self.version = version.strip() - - def config_options(self): - if self.settings.os == "Windows": - self.options.rm_safe("fPIC") - - def configure(self): - if self.options.shared: - self.options.rm_safe("fPIC") - - def requirements(self): - # not required directly here but because boost and openssl pulling - # slightly different versions of zlib we need to set it - # here explicitly - self.requires("zlib/1.3") - - # Since Eigen is a header only library, and the SDK includes Eigen - # headers in its headers, we must set transitive_headers=True so that - # packages consuming the SDK will also have access to the Eigen headers. - self.requires("eigen/3.4.0", transitive_headers=True) - self.requires("jsoncpp/1.9.5") - self.requires("spdlog/1.12.0") - self.requires("fmt/9.1.0", override=True) - self.requires("libcurl/7.86.0") - - if self.options.build_pcap: - self.requires("libtins/4.3") - - if self.options.build_osf: - self.requires("flatbuffers/23.5.26") - self.requires("libpng/1.6.39") - - if self.options.build_viz: - self.requires("glad/0.1.34") - if self.settings.os != "Windows": - self.requires("xorg/system") - self.requires("glfw/3.3.6") - # maybe needed for cpp examples, but not for the lib - # self.requires("tclap/1.2.4") - - def build_requirements(self): - if self.options.build_osf: - self.build_requires("flatbuffers/") - - def layout(self): - cmake_layout(self) - - def generate(self): - deps = CMakeDeps(self) - deps.generate() - tc = CMakeToolchain(self) - tc.variables["BUILD_VIZ"] = self.options.build_viz - tc.variables["BUILD_PCAP"] = self.options.build_pcap - tc.variables["BUILD_OSF"] = self.options.build_osf - tc.variables[ - "OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32" - ] = self.options.eigen_max_align_bytes - tc.variables["BUILD_SHARED_LIBS"] = True if self.options.shared else False - tc.variables["CMAKE_POSITION_INDEPENDENT_CODE"] = ( - True if "fPIC" in self.options and self.options.fPIC else False - ) - - # we use this option until we remove nonstd::optional from SDK codebase (soon) - if self.options.ensure_cpp17: - tc.variables["CMAKE_CXX_STANDARD"] = 17 - - tc.generate() - - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() - - def package(self): - cmake = CMake(self) - cmake.install() - - def package_info(self): - self.cpp_info.libs = collect_libs(self) - self.cpp_info.includedirs = [ - "include", - "include/optional-lite" - ] - self.cpp_info.build_modules["cmake_find_package"].append( - "lib/cmake/OusterSDK/OusterSDKConfig.cmake" - ) - - self.cpp_info.set_property( - "cmake_build_modules", - [os.path.join("lib", "cmake", "OusterSDK", "OusterSDKConfig.cmake")], - ) - self.cpp_info.set_property("cmake_file_name", "OusterSDK") - self.cpp_info.set_property("cmake_target_name", "OusterSDK") - - # TODO: to remove in conan v2 once cmake_find_package* generators removed - self.cpp_info.filenames["cmake_find_package"] = "OusterSDK" - self.cpp_info.filenames["cmake_find_package_multi"] = "OusterSDK" - self.cpp_info.names["cmake_find_package"] = "OusterSDK" - self.cpp_info.names["cmake_find_package_multi"] = "OusterSDK" diff --git a/docs/Doxyfile b/docs/Doxyfile index 6f301465..cf2971ce 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -2167,7 +2167,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2207,7 +2207,9 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = "OUSTER_API_FUNCTION= " \ + "OUSTER_API_CLASS= " \ + "OUSTER_API_IGNORE= " # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/docs/cli/clip-sessions.rst b/docs/cli/clip-sessions.rst index 43346054..fa20158a 100644 --- a/docs/cli/clip-sessions.rst +++ b/docs/cli/clip-sessions.rst @@ -1,5 +1,5 @@ -Clip Point clouds in the Ouster-CLI -================================= +Clip Point clouds values in Ouster-CLI +====================================== .. _ouster-cli-clip: @@ -7,9 +7,9 @@ Clip Point clouds in the Ouster-CLI Clip Command ------------ -The clip command clips the lidar scan by the given range and streams down the modified lidar -scan to the subsequent commands (like slam, viz, save, etc.). The position of the clip command -in the ouster-cli command chain makes a difference as it only affects the commands that follow it. +The ``clip`` command can be used to limit the range of values of different scan fields. The outcome of this command +can then be consumed by other downstream commands operation (like slam, viz, save, etc.). The position of the ``clip`` +command in the ouster-cli command chain makes a difference as it only affects operations that come afterwords. To explore the parameters you can use with the clip command, you can use the --help flag: @@ -26,32 +26,25 @@ with the visualizer on, run the following command: .. code:: bash - ouster-cli source / clip --min-range 20 --max-range 50 viz save clipped.pcap + ouster-cli source / clip RANGE,RANGE2 20m:50m viz save clipped.pcap -Remember, the clip command only affects the commands after it. In the following example, the +Remember, the ``clip`` command only affects the commands after it. In the following example, the viz command runs before the clip command, which means the point cloud modification won't be reflected in the visualizer but will affect the subsequent save command and the saved PCAP file: .. code:: bash - ouster-cli source / viz clip --min-range 20 --max-range 50 save clipped_2.pcap + ouster-cli source / viz clip RANGE,RANGE2 20m:50m save clipped_2.pcap -In addition to the ``min-range`` and ``max-range`` parameters, the ``clip`` command also includes the -``percent-range`` parameter. This parameter discards points with ranges greater than a specified percentile -in each lidar scan, helping to filter out noise points. - -.. code:: bash - - ouster-cli source / clip --percent-range 99 viz Combined with SLAM Command -------------------------- -The ``slam`` command also has ``min-range`` and ``max-range`` parameters. When the clip command is -used after the ``slam`` command, the ``clip`` command will, by default, use the range settings specified -in the slam command. However, you can explicitly pass in the range settings to the ``clip`` command to -apply different ranges to the clip operation. +The ``slam`` command has ``min-range`` and ``max-range`` parameters. When the clip command is used after +the ``slam`` command, the ``clip`` command will, by default, use the range settings specified in the slam +command. However, you can explicitly pass in the range settings to the ``clip`` command to apply different +ranges to the clip operation. Note that the range settings in the ``slam`` command only affect the point cloud within the SLAM algorithm. The slam range settings will not modify the lidar scan and will not affect the other following commands. @@ -64,8 +57,8 @@ Experiment with the following commands using a pre-recorded PCAP or OSF file: .. code:: bash - ouster-cli source slam clip --min-range 20 --max-range 50 viz save clipped_3.ply - ouster-cli source slam --min-range 10 --max-range 100 clip --min-range 20 --max-range 50 viz save clipped_4.ply + ouster-cli source slam clip RANGE,RANGE2 20m:50m viz save clipped_3.ply + ouster-cli source slam --min-range 10 --max-range 100 clip RANGE,RANGE2 20m:50m viz save clipped_4.ply You can view the output PLY files using the open source software `CloudCompare`_ For more details about the slam command, refer to the :ref:`SLAM Command ` diff --git a/docs/cli/common-use-cases.rst b/docs/cli/common-use-cases.rst index e7ae5a73..f8094330 100644 --- a/docs/cli/common-use-cases.rst +++ b/docs/cli/common-use-cases.rst @@ -126,3 +126,42 @@ metadata json file with the same name as PCAP FILE by default, but you can speci .. code:: bash $ ouster-cli source viz + + +Masking Lidar Data +++++++++++++++++++ + +The following command applies an image mask ``MASK-IMAGE`` to the ``RANGE`` data field of incoming +scans: + +.. code:: bash + + $ ouster-cli source mask --fields RANGE viz + + +The ``MASK-IMAGE`` is expected to be of composed solely of black and white pixels; black pixels +represent the pixels that will be zeroed out and white pixels represent areas of the RANGE that +will stay intact. The ``MASK-IMAGE`` is expected to have the same size of the streamed LidarScans. +If not the command will scale the mask image to the same size as the incoming LidarScans. +The ``MASK-IMAGE`` will be applied to all sensors in case of a multi-sensor dataset. + + +Reduce Beam Count ++++++++++++++++++ +Use the ``reduce`` command to reduce the lower vertical resolution or beam count of stream LidarScans. +For example, let's assume you have an OS-1-128 Ouster sensor which has 128 beams, using the following +command you can reduce the effective vertical resolution of the sensor to 32: + +.. code:: bash + + $ ouster-cli source reduce --fields 32 viz + + +The reduced LidarScans will applied to the rest of the chain, that means if you chain a ``save`` command +afterwords the generated file will have LidarScans with 32 beams only. +One thing to note is that the beams are sampled uniformally across the original beam count. + +.. note:: + + Currently, the ``reduce`` command can't occure more than once in the ouster-cli command chain and needs + to be the very first command after the ``source`` args. diff --git a/docs/cli/getting-started.rst b/docs/cli/getting-started.rst index 02fddb61..033aca42 100644 --- a/docs/cli/getting-started.rst +++ b/docs/cli/getting-started.rst @@ -110,9 +110,12 @@ commands also have subcommands that further extend or specify what * Sensors and files * ``viz`` - visualizes data in a 3D point cloud viewer. + * ``slice`` - use the slice command to extract a subset of lidar frames from a source by specifing a frame index + interval or time duration. + * ``clip`` - the clip command can be used to limit the range of values of scan fields. + * ``mask`` - use the mask command to mask certain pixel from the lidar frames. + * ``reduce`` - use the reduce command to downsample the vertical resolution of any ouster Lidar. * ``slam`` - computes trajectories by determining the change in pose between lidar frames. - * ``slice`` - reads a subset of lidar frames from the source using counts or time duration. - * ``clip`` - restrict the minimum or maximum range of lidar measurements in the source data. * ``stats`` - calculates statistics from the source data. * ``metadata`` - displays the metadata (e.g. sensor information) associated with the source data. * ``save`` - saves the source data, optionally converting to a new format. diff --git a/docs/cli/mapping-sessions.rst b/docs/cli/mapping-sessions.rst index ec156971..ceac9224 100644 --- a/docs/cli/mapping-sessions.rst +++ b/docs/cli/mapping-sessions.rst @@ -131,7 +131,7 @@ file to a PLY file and keep only the point within 20 to 80 meters range you can .. code:: bash - ouster-cli source sample.osf clip --min-range 20 --max-range 80 save clipped_output.ply + ouster-cli source sample.osf clip RANGE,RANGE2 20:80 save clipped_output.ply More details about the clip command usage can be found in the :ref:`Clip Command ` @@ -256,6 +256,50 @@ displaying the preview of the lidar trajectory: palette) +Localize Command +---------------- +Starting with SDK 0.14.0 the ouster-cli has a new command ``localize`` which allows users to load a +map of a given site, then use it to query the position and orientation of the sensor starting from +a known position. Here is an example: + +.. code:: bash + + ouster-cli source localize viz + +Once this command is invoked the viz will load the given map and display it in the background (flattened +by default) while simultaneously streaming the lidar data from the ``SOURCE_URL`` and updating the +position of the sensor relative to the map origin as show in the image below: + +.. figure:: /images/localization-map-main.png + + +.. note:: + + Currently ply is the only supported format for the localization map. + +This above example works fine when the input source begins from the same place as the origin of the map, +however, in many situations this isn't the case. In the case of wanting to start from a different +starting point than the map origin, the user could do that using the following: + +.. code:: bash + + ouster-cli source --initial-pose PX,PY,PZ,R,P,Y localize viz + +This would set the initial pose of the input source with respect to the map origin, where PX,PY,PZ +represent the position and R,P,Y represent orientation in Euler angles (roll, pitch, yaw) +specified in degrees. + +As part of the localization feature the ``viz`` command was extended to give control the visuals of the +localization map. All these options start with the ``--global-map`` prefix. For example, it is possible +to show the localization map in its 3D form without flattening by passing the option +``--global-map-flatten False`` to the ``viz`` command. With this the command becomes like this: + +.. code:: bash + + ouster-cli source localize viz --global-map-flatten False + +To see the full list of available options to modify the map visuals check the ``viz`` help menu + .. _Networking Guide: https://static.ouster.dev/sensor-docs/image_route1/image_route3/networking_guide/networking_guide.html .. _CloudCompare: https://www.cloudcompare.org/ diff --git a/docs/conf.py b/docs/conf.py index da7c6b62..965a359b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -186,7 +186,7 @@ def do_doxygen_generate_xml(app): raise SystemError( "Expects 'doxygen' command on the PATH to generate C++ docs") - print("===== Generating Doxygen XML ======") + print("====== Generating Doxygen XML ======") # Prepare temp Doxyfile with resolved param values temp_doxy_file_dir = tempfile.mkdtemp() diff --git a/docs/cpp/building.rst b/docs/cpp/building.rst index 5e353e01..edcfc77c 100644 --- a/docs/cpp/building.rst +++ b/docs/cpp/building.rst @@ -5,7 +5,7 @@ Building the C++ Client from Source =================================== Building the example code requires a compiler supporting C++14 and CMake 3.1 or newer and the -jsoncpp, Eigen3, and tins libraries with headers installed on the system. The sample visualizer also +Eigen3 and tins libraries with headers installed on the system. The sample visualizer also requires the GLFW3 and GLEW libraries. The C++ example code is available `on the Ouster Github @@ -18,9 +18,9 @@ To install build dependencies on Ubuntu:20.04+, run: .. code:: console - $ sudo apt install build-essential cmake libjsoncpp-dev libeigen3-dev libcurl4-openssl-dev \ - libtins-dev libpcap-dev libglfw3-dev libglew-dev libspdlog-dev \ - libpng-dev libflatbuffers-dev + $ sudo apt install build-essential cmake libeigen3-dev libcurl4-openssl-dev \ + libtins-dev libpcap-dev libglfw3-dev libpng-dev \ + libflatbuffers-dev You may also install curl with a different ssl backend, for example libcurl4-gnutls-dev or libcurl4-nss-dev. @@ -29,9 +29,9 @@ On macOS, install XCode and `homebrew `_ and run: .. code:: console - $ brew install cmake pkg-config jsoncpp eigen curl libtins glfw glew spdlog libpng flatbuffers + $ brew install cmake pkg-config eigen curl libtins glfw libpng flatbuffers -To build run the following commands: +To build on macOS and Ubuntu:20.04+ run the following commands: .. code:: console @@ -51,7 +51,7 @@ defaults: -DBUILD_OSF=OFF # Do not build OSF lib -DBUILD_EXAMPLES=ON # Build C++ examples -DBUILD_TESTING=ON # Build tests - -DBUILD_SHARED_LIBS=ON # Build shared instead of static libraries + -DBUILD_SHARED_LIBRARY=ON # Build the shared library and binary artifacts Building on Windows @@ -82,7 +82,7 @@ You should be able to install dependencies with .. code:: powershell - PS > .\vcpkg.exe install --triplet x64-windows jsoncpp eigen3 curl libtins glfw3 glew spdlog libpng flatbuffers + PS > .\vcpkg.exe install --triplet x64-windows eigen3 curl libtins glfw3 libpng flatbuffers After these steps are complete, you should be able to open, build and run the ``ouster-sdk`` project using Visual Studio: @@ -125,3 +125,14 @@ supply ``""``, an empty string, to utilize automatic detection. On Windows, you may need to allow the client/visualizer through the Windows firewall to receive sensor data. + +Building Against The Library +============================ + +Navigate to ``examples`` under the ouster-sdk source directory, which should contain several linux +example folders building against the sdk library. To run each use the example.bash script. + +1. compiled_in_linking_example - Compile ouster_sdk as a sub-project under a larger codebase. +2. static_linking_example - Use installed static libs of ouster_sdk under a larger codebase. +3. shared_linking_example - Use installed shared libs of ouster_sdk under a larger codebase. + diff --git a/docs/cpp/examples/index.rst b/docs/cpp/examples/index.rst index 1423a9cb..63ad7e2e 100644 --- a/docs/cpp/examples/index.rst +++ b/docs/cpp/examples/index.rst @@ -6,3 +6,4 @@ CPP Examples :caption: CPP Examples Simple Examples + Linking Examples \ No newline at end of file diff --git a/docs/cpp/examples/linking_examples.rst b/docs/cpp/examples/linking_examples.rst new file mode 100644 index 00000000..737bba9f --- /dev/null +++ b/docs/cpp/examples/linking_examples.rst @@ -0,0 +1,30 @@ +=========================== +C++ Shared Library Examples +=========================== + +Navigate to examples under the ouster-sdk source directory, which should contain several linux example folders building against the sdk library. +To run each use the example.bash script. + +1. `Compiled-In Linking Example `_ + +2. `Shared Linking Example `_ + +3. `Static Linking Example `_ + + +Compiled In Linking example +--------------------------- + +:: + .. include:: ../../../examples/compiled_in_linking_example/readme.rst + +Shared Linking example +----------------------- +:: + .. include:: ../../../examples/shared_linking_example/readme.rst + +Static Linking example +----------------------- + +:: + .. include:: ../../../examples/static_linking_example/readme.rst \ No newline at end of file diff --git a/docs/cpp/ouster_osf/meta_lidar_sensor.rst b/docs/cpp/ouster_osf/meta_lidar_sensor.rst index 8235690a..9adb4e1f 100644 --- a/docs/cpp/ouster_osf/meta_lidar_sensor.rst +++ b/docs/cpp/ouster_osf/meta_lidar_sensor.rst @@ -2,9 +2,6 @@ meta_lidar_sensor.h =================== -.. doxygenclass:: ouster::osf::LidarSensor - :members: - .. doxygengroup:: OSFTraitsLidarSensor :members: :content-only: diff --git a/docs/cpp/ouster_osf/meta_streaming_info.rst b/docs/cpp/ouster_osf/meta_streaming_info.rst index e5148b33..6c441670 100644 --- a/docs/cpp/ouster_osf/meta_streaming_info.rst +++ b/docs/cpp/ouster_osf/meta_streaming_info.rst @@ -9,19 +9,3 @@ ChunkInfo .. doxygenfunction:: ouster::osf::to_string(const ChunkInfo& chunk_info) -StreamStats ------------ -.. doxygenstruct:: ouster::osf::StreamStats - :members: - -.. doxygenfunction:: ouster::osf::to_string(const StreamStats& stream_stats) - -Streaming Info --------------- -.. doxygenclass:: ouster::osf::StreamingInfo - :members: - -.. doxygengroup:: OSFTraitsStreamingInfo - :members: - :content-only: - diff --git a/docs/cpp/ouster_osf/metadata.rst b/docs/cpp/ouster_osf/metadata.rst index 6310df2f..2b492d64 100644 --- a/docs/cpp/ouster_osf/metadata.rst +++ b/docs/cpp/ouster_osf/metadata.rst @@ -11,9 +11,6 @@ Public API .. doxygenclass:: ouster::osf::MetadataEntryRef :members: -.. doxygenclass:: ouster::osf::MetadataStore - :members: - .. doxygenfunction:: ouster::osf::metadata_pointer_as diff --git a/docs/cpp/ouster_osf/stream_lidar_scan.rst b/docs/cpp/ouster_osf/stream_lidar_scan.rst index a1e21ca7..0f40f3c7 100644 --- a/docs/cpp/ouster_osf/stream_lidar_scan.rst +++ b/docs/cpp/ouster_osf/stream_lidar_scan.rst @@ -9,7 +9,4 @@ stream_lidar_scan.h :members: :content-only: -.. doxygenclass:: ouster::osf::LidarScanStream - :members: - .. doxygenfunction:: ouster::osf::slice_with_cast diff --git a/docs/cpp/ouster_pcap/os_pcap.rst b/docs/cpp/ouster_pcap/os_pcap.rst index 0cac0fdb..d8becf58 100644 --- a/docs/cpp/ouster_pcap/os_pcap.rst +++ b/docs/cpp/ouster_pcap/os_pcap.rst @@ -13,13 +13,6 @@ Packet Info .. doxygenfunction:: ouster::sensor_utils::operator<<(std::ostream& stream_in, const packet_info& data) -Handles -======= - -.. doxygenstruct:: ouster::sensor_utils::record_handle - -.. doxygenstruct:: ouster::sensor_utils::playback_handle - Functions ========= diff --git a/docs/images/localization-map-main.png b/docs/images/localization-map-main.png new file mode 100644 index 00000000..f60d369c Binary files /dev/null and b/docs/images/localization-map-main.png differ diff --git a/docs/index.rst b/docs/index.rst index 7333ca16..3b106032 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,7 @@ Migrating from 20220927/0.5.1 to 20230114/0.7.1 Migrating from 20230114/0.7.1 to 20230403/0.8.1 Migration from 20231031/0.10.0 to 20240423/0.11.0 + Migration from 20241004/0.13.1 to 20250113/0.14.0 .. FAQ diff --git a/docs/migration/migration-20241004-20250113.rst b/docs/migration/migration-20241004-20250113.rst new file mode 100644 index 00000000..5203452e --- /dev/null +++ b/docs/migration/migration-20241004-20250113.rst @@ -0,0 +1,58 @@ +================================================= +Migration from 20241004/0.13.0 to 20250117/0.14.0 +================================================= + +The OusterSDK 0.14.0 release brings a few breaking changes. Here we summarize how to migrate +from 0.13.0 to 0.14.0 + +ouster_client c++ module changes +++++++++++++++++++++++++++++++++ +The ``SensorClient`` ``get_packet`` signature was changed. + +Before, this method was used as follows: + +.. code:: c++ + + ouster::sensor::SensorClient sensor_client(sensors); // sensors is a std::vector + + auto lidar_packet = sensor::LidarPacket(); + auto imu_packet = sensor::ImuPacket(); + + while(true) { + auto client_event = sensor_client.get_packet(lidar_packet, imu_packet, 0.1); + + if (client_event.type == ouster::sensor::ClientEvent::LidarPacket) { + // do something with lidar_packet here + } else if (client_event.type == ouster::sensor::ClientEvent::ImuPacket) { + // do something with imu_packet here + } + } + +This interface would have to be broken every time a new packet type is added. For that reason, +this was changed and the same logic would be written now as follows: + +.. code:: c++ + + ouster::sensor::SensorClient sensor_client(sensors); // sensors is a std::vector + + while(true) { + auto client_event = sensor_client.get_packet(0.1); + + if (client_event.type == ouster::sensor::ClientEvent::Packet) { + if (client_event.packet().type() == ouster::sensor::PacketType::Lidar) { + auto& lidar_packet = client_event.packet().as(); + // do something with lidar_packet here + } else if (client_event.packet().type() == ouster::sensor::PacketType::Imu) { + auto& imu_packet = static_cast(client_event.packet()); + // do something with imu_packet here + } + } + + if (client_event.type == ouster::sensor::ClientEvent::PollTimeout) { + // handle timeout here + } + } + + +Now, ``LidarPacket`` and ``ImuPacket`` provide convenient methods that return the parsed fields that +are specific to each packet type. diff --git a/docs/overview.rst b/docs/overview.rst index 6517443b..48627fd5 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -41,6 +41,10 @@ The Ouster SDK currently provides backwards compatibility with any FW 2.0 and la releases. Older SDK versions are not, however, generally forward-compatible with later FW releases, e.g., the SDK version 20210608 (ouster-sdk 0.2.0) is not compatible with FW 3.0. +.. important:: + + Official SDK support for firmware versions 2.2 and 2.3 will end at the end of June, 2025. + .. note:: Please note that compatibility does not indicate that upgrading between SDK versions can happen @@ -51,6 +55,7 @@ The following table indicates the compatibility of each released SDK version and ===================================== ======= ======= ======= ======= ======= ======= ======= ======= SDK Tag (Release) / Python SDK FW 2.0 FW 2.1 FW 2.2 FW 2.3 FW 2.4 FW 2.5 FW 3.0 FW 3.1 ===================================== ======= ======= ======= ======= ======= ======= ======= ======= +C++ SDK 20250117 / Python SDK 0.14.0 no **yes** **yes** **yes** **yes** **yes** **yes** **yes** C++ SDK 20240703 / Python SDK 0.13.1 no **yes** **yes** **yes** **yes** **yes** **yes** **yes** C++ SDK 20240703 / Python SDK 0.13.0 no **yes** **yes** **yes** **yes** **yes** **yes** **yes** C++ SDK 20240703 / Python SDK 0.12.0 **yes** **yes** **yes** **yes** **yes** **yes** **yes** **yes** diff --git a/docs/python/api/osf.rst b/docs/python/api/osf.rst index 2daf9b05..f4db98d7 100644 --- a/docs/python/api/osf.rst +++ b/docs/python/api/osf.rst @@ -13,39 +13,6 @@ Module :mod:`ouster.sdk.osf` Low-Level API ------------- -Reading -^^^^^^^ - -``Reader`` for OSF files -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: Reader - :members: - :undoc-members: - - -``MessageRef`` wrapper for a `message` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: MessageRef - :members: - :undoc-members: - -``MetadataStore`` for `metadata entries` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: MetadataStore - :members: - :undoc-members: - -``MetadataEntry`` base class for all metadata -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: MetadataEntry - :members: - :undoc-members: - - Writing OSF files ^^^^^^^^^^^^^^^^^ @@ -57,42 +24,6 @@ Writing OSF files :undoc-members: -Common `metadata entries` -^^^^^^^^^^^^^^^^^^^^^^^^^ - -``LidarSensor`` Ouster sensor metadata (i.e. ``client.SensorInfo``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: LidarSensor - :members: - :undoc-members: - -``StreamStats`` statistics per stream -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: StreamStats - :members: - :undoc-members: - -``StreamingInfo`` stream statistics -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: StreamingInfo - :members: - :undoc-members: - - -Common `streams` -^^^^^^^^^^^^^^^^ - -``LidarScanStream`` stream -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: LidarScanStream - :members: - :undoc-members: - - High-Level API -------------- diff --git a/docs/python/devel.rst b/docs/python/devel.rst index 334f26b6..5c9464ba 100644 --- a/docs/python/devel.rst +++ b/docs/python/devel.rst @@ -13,14 +13,11 @@ Building the Python SDK from source requires several dependencies: - `cmake `_ >= 3.5 - `eigen `_ >= 3.3 - `curl `_ >= 7.58 -- `jsoncpp `_ >= 1.7 - `libtins `_ >= 3.4 - `libpcap `_ - `libpng `_ >= 1.6 - `flatbuffers `_ >= 1.1 - `libglfw3 `_ >= 3.2 -- `libglew `_ >= 2.1 or `glad `_ -- `spdlog `_ >= 1.9 - `python `_ >= 3.8 (with headers and development libraries) - `pybind11 `_ >= 2.0 @@ -34,16 +31,15 @@ On supported Debian-based Linux systems, you can install all build dependencies .. code:: console $ sudo apt install build-essential cmake \ - libeigen3-dev libjsoncpp-dev libtins-dev libpcap-dev \ + libeigen3-dev libtins-dev libpcap-dev \ python3-dev python3-pip libcurl4-openssl-dev \ - libglfw3-dev libglew-dev libspdlog-dev \ - libpng-dev libflatbuffers-dev + libglfw3-dev libpng-dev libflatbuffers-dev On macOS >= 11, using Homebrew, you should be able to run: .. code:: console - $ brew install cmake eigen curl jsoncpp libtins python3 glfw glew spdlog libpng flatbuffers + $ brew install cmake eigen curl libtins python3 glfw libpng flatbuffers After you have the system dependencies, you can build the SDK with: @@ -80,7 +76,7 @@ package manager and run: .. code:: powershell - PS > vcpkg install --triplet=x64-windows curl eigen3 jsoncpp libtins glfw3 glad[gl-api-33] spdlog libpng flatbuffers + PS > vcpkg install --triplet=x64-windows curl eigen3 libtins glfw3 glad[gl-api-33] libpng flatbuffers The currently tested vcpkg tag is ``2024.04.26``. After that, using a developer powershell prompt: @@ -108,7 +104,7 @@ The currently tested vcpkg tag is ``2024.04.26``. After that, using a developer # or just install directly (virtualenv recommended) PS > py -m pip install "$env:OUSTER_SDK_PATH\python" -See the top-level README in the `Ouster Example repository`_ for more details on setting up a +See the top-level README in the `Ouster SDK repository`_ for more details on setting up a development environment on Windows. .. _vcpkg: https://github.com/microsoft/vcpkg/blob/master/README.md diff --git a/docs/python/examples/osf-examples.rst b/docs/python/examples/osf-examples.rst index db59e78a..b0be5cd6 100644 --- a/docs/python/examples/osf-examples.rst +++ b/docs/python/examples/osf-examples.rst @@ -35,18 +35,14 @@ Every example is wrapped into a CLI and available for quick tests by running Ouster Python SDK OSF examples. The EXAMPLE must be one of: read-scans - read-messages - split-scans slice-scans - get-lidar-streams get-sensors-info - check-layout -For example to execute the ``get-lidar-streams`` example you can run: +For example to execute the ``read-scans`` example you can run: .. code:: bash - $ python3 -m ouster.sdk.examples.osf get-lidar-streams + $ python3 -m ouster.sdk.examples.osf read-scans Read Lidar Scans with ``osf.Scans`` @@ -87,47 +83,6 @@ case it's of type ``osf.LidarSensor``): :dedent: -Read All Messages with ``osf.Reader`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -With ``osf.Reader``, you can use ``reader.messages()`` iterator to read messages in ``timestamp`` -order. - -.. literalinclude:: /../python/src/ouster/sdk/examples/osf.py - :start-after: [doc-stag-osf-read-all-messages] - :end-before: [doc-etag-osf-read-all-messages] - :dedent: - - -Checking Chunks Layout via ``osf.StreamingInfo`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Building on top of an example from above we can check for stream -statistics information from ``osf.StreamingInfo``: - -.. literalinclude:: /../python/src/ouster/sdk/examples/osf.py - :start-after: [doc-stag-osf-check-layout] - :end-before: [doc-etag-osf-check-layout] - :dedent: - -For more information about ``osf.StreamingInfo`` metadata entry please refer to [RFC 0018]_. - - -Get Lidar Scan streams info via ``osf.LidarScanStream`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Every message in an OSF belongs to a stream of a particular type (i.e. ``osf.LidarScanStream``, -``osf.LidarImuStream``, etc.). Streams information stored as **metadata entry** within -``osf.Reader.meta_store`` object that can be read and decoded in various ways. Below is an example -of how we can check parameters of an available LidarScan streams (``osf.LidarScanStream``) by -checking the metadata entries: - -.. literalinclude:: /../python/src/ouster/sdk/examples/osf.py - :start-after: [doc-stag-osf-get-lidar-streams] - :end-before: [doc-etag-osf-get-lidar-streams] - :dedent: - - Write Lidar Scan with sliced fields with ``osf.Writer`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -145,16 +100,4 @@ A general scheme of writing scans to the OSF with Writer: .. literalinclude:: /../python/src/ouster/sdk/examples/osf.py :start-after: [doc-stag-osf-slice-scans] :end-before: [doc-etag-osf-slice-scans] - :dedent: - - -Split Lidar Scan stream into multiple files -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Another example of using ``osf.Writer`` that we will see is the splitting of Lidar Scan stream from -one OSF file into 2 files. - -.. literalinclude:: /../python/src/ouster/sdk/examples/osf.py - :start-after: [doc-stag-osf-split-scans] - :end-before: [doc-etag-osf-split-scans] - :dedent: + :dedent: \ No newline at end of file diff --git a/docs/python/examples/udp-packets.rst b/docs/python/examples/udp-packets.rst index 54853769..b8de8bff 100644 --- a/docs/python/examples/udp-packets.rst +++ b/docs/python/examples/udp-packets.rst @@ -13,11 +13,7 @@ Let's make a :py:class:`.PacketMultiSource` from our sample data using :py:class .. code:: python - from ouster.sdk import pcap - with open(metadata_path, 'r') as f: - metadata = client.SensorInfo(f.read()) - - source = pcap.PcapMultiPacketReader(pcap_path, metadatas=[metadata]) + source = pcap.PcapMultiPacketReader(pcap_path).single_source(0) Now we can read packets from ``source`` with the following code: diff --git a/docs/python/quickstart.rst b/docs/python/quickstart.rst index e14ad9c8..367745e9 100644 --- a/docs/python/quickstart.rst +++ b/docs/python/quickstart.rst @@ -282,6 +282,20 @@ following snippet shows few examples to demonstrate this capability: >>> print("source3 length:", len(source3)) # Should print 2 +Additional ScanSource operators +------------------------------- +besides the indexing and slicing capabilities, the ``ScanSource`` API also supports the following operators: + + * ``clip(fields, lower, upper)``: generates a ``ScanSource`` that limits the range of values of scan fields to [lower, upper]. + * ``reduce(beams)``: returns a downsampled version of the main ``ScanSource`` (vertical resolution only as set by the beams). + * ``mask(fields, mask_image)``: creates a ``ScanSource`` that has the pixels of specified fields masked by the mask_image. + +All of these operations can be invoked directly through the ``ScanSource`` object and yield another ``ScanSource`` allowing +the user to cascade these operations. For example, one could execute the following command chain in one line: +``source.clip(["RANGE"], 0, 1000)[0:10].reduce(16)`` which essentially means to clip any ``RANGE`` value that exceed 1000 +taking a subset of 10 frames from the main dataset and have the vertical resolution of the sensscans reduced to 16 beams. + + Using the client API ==================== diff --git a/docs/python/slam-api-example.rst b/docs/python/slam-api-example.rst index f1c8de3b..c002400b 100644 --- a/docs/python/slam-api-example.rst +++ b/docs/python/slam-api-example.rst @@ -55,7 +55,7 @@ between consecutive scans SLAM with Visualizer and Accumulated Scans -========================================= +========================================== Visualizers and Accumulated Scans are also available for monitoring the performance of the algorithm, as well as for demonstration and feedback purposes. diff --git a/docs/versions.json b/docs/versions.json index 699d8ef7..64052684 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1,4 +1,11 @@ [ + { + "version": "0.13.1", + "tags": { + "sdkx": "release-0.13.1", + "sdk": "release-0.13.1" + } + }, { "version": "0.13.0", "tags": { diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4ea04b7a..f0e4282d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(Threads REQUIRED) + add_executable(client_example client_example.cpp) target_link_libraries(client_example PRIVATE OusterSDK::ouster_client) diff --git a/examples/client_packet_example.cpp b/examples/client_packet_example.cpp index 07ad45bb..23dff014 100644 --- a/examples/client_packet_example.cpp +++ b/examples/client_packet_example.cpp @@ -103,27 +103,27 @@ int main(int argc, char* argv[]) { */ std::cerr << "Capturing points... "; - // buffer to store raw packet data - auto lidar_packet = sensor::LidarPacket(); - auto imu_packet = sensor::ImuPacket(); - while (true) { - auto ev = client.get_packet(lidar_packet, imu_packet, 0.1); - if (ev.type == ouster::sensor::ClientEvent::LidarPacket) { - if (count[ev.source] == N_SCANS) continue; - // batcher will return "true" when the current scan is complete - if (batch_to_scan[ev.source](lidar_packet, - scans[ev.source][count[ev.source]])) { - // retry until we receive a full set of valid measurements - // (accounting for azimuth_window settings if any) - if (scans[ev.source][count[ev.source]].complete( - client.get_sensor_info()[ev.source] - .format.column_window)) { - count[ev.source]++; + auto ev = client.get_packet(0.1); + if (ev.type == ouster::sensor::ClientEvent::Packet) { + if (ev.packet().type() == ouster::sensor::PacketType::Lidar) { + auto& lidar_packet = + static_cast(ev.packet()); + if (count[ev.source] == N_SCANS) continue; + // batcher will return "true" when the current scan is complete + if (batch_to_scan[ev.source]( + lidar_packet, scans[ev.source][count[ev.source]])) { + // retry until we receive a full set of valid measurements + // (accounting for azimuth_window settings if any) + if (scans[ev.source][count[ev.source]].complete( + client.get_sensor_info()[ev.source] + .format.column_window)) { + count[ev.source]++; + } } + } else if (ev.packet().type() == ouster::sensor::PacketType::Imu) { + // got an IMU packet } - } else if (ev.type == ouster::sensor::ClientEvent::ImuPacket) { - // got an IMU packet } if (ev.type == ouster::sensor::ClientEvent::Error) { diff --git a/examples/linking_example/CMakeLists.txt b/examples/compiled_in_linking_example/CMakeLists.txt similarity index 58% rename from examples/linking_example/CMakeLists.txt rename to examples/compiled_in_linking_example/CMakeLists.txt index e65254f5..c5352026 100644 --- a/examples/linking_example/CMakeLists.txt +++ b/examples/compiled_in_linking_example/CMakeLists.txt @@ -1,11 +1,10 @@ cmake_minimum_required(VERSION 3.21) project(pcap_test) -find_package(OusterSDK REQUIRED) +add_subdirectory(sdk) add_executable(pcap_test main.cpp) target_link_libraries(pcap_test - OusterSDK::ouster_client - OusterSDK::ouster_pcap + OusterSDK::ouster_client OusterSDK::ouster_pcap ) diff --git a/examples/compiled_in_linking_example/Dockerfile b/examples/compiled_in_linking_example/Dockerfile new file mode 100644 index 00000000..c8b96b5b --- /dev/null +++ b/examples/compiled_in_linking_example/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + libeigen3-dev \ + libtins-dev \ + libpcap-dev \ + libcurl4-openssl-dev \ + git \ + build-essential \ + cmake \ + zlib1g \ + zlib1g-dev \ + libglfw3-dev \ + libpng-dev \ + libflatbuffers-dev + +ENV WORKSPACE=/root +COPY . $WORKSPACE/sdk/ +COPY tests/pcaps/OS-2-32-U0_v2.0.0_1024x10.pcap examples/compiled_in_linking_example/CMakeLists.txt \ + examples/compiled_in_linking_example/main.cpp $WORKSPACE/ + +RUN cd $WORKSPACE && \ + cmake -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF . && \ + cmake --build . --parallel 4 + +CMD $WORKSPACE/pcap_test /root/OS-2-32-U0_v2.0.0_1024x10.pcap diff --git a/examples/linking_example/example.bash b/examples/compiled_in_linking_example/example.bash similarity index 88% rename from examples/linking_example/example.bash rename to examples/compiled_in_linking_example/example.bash index 2f9492ce..46e6c987 100755 --- a/examples/linking_example/example.bash +++ b/examples/compiled_in_linking_example/example.bash @@ -12,4 +12,4 @@ cd $baseDir docker build -f $currentDir/Dockerfile --iidfile=$tempDir/iid . -docker run $(cat $tempDir/iid) +docker run --rm $(cat $tempDir/iid) diff --git a/examples/linking_example/main.cpp b/examples/compiled_in_linking_example/main.cpp similarity index 100% rename from examples/linking_example/main.cpp rename to examples/compiled_in_linking_example/main.cpp diff --git a/examples/compiled_in_linking_example/readme.rst b/examples/compiled_in_linking_example/readme.rst new file mode 100644 index 00000000..af32b049 --- /dev/null +++ b/examples/compiled_in_linking_example/readme.rst @@ -0,0 +1,15 @@ +This is a simple example on how to build and link a cpp project with the ouster_pcap +library. + +Bash script to run the example in a container: + +.. literalinclude:: ../../../examples/compiled_in_linking_example/example.bash + :language: bash + :dedent: + +Dockerfile: + +.. literalinclude:: ../../../examples/compiled_in_linking_example/Dockerfile + :language: docker + :dedent: + diff --git a/examples/linking_example/readme.rst b/examples/linking_example/readme.rst deleted file mode 100644 index 6d57f6c8..00000000 --- a/examples/linking_example/readme.rst +++ /dev/null @@ -1,10 +0,0 @@ -============================ -Simple ouster_pcap Example -============================ - -This is a simple example on how to build and link a cpp project with the ouster_pcap -library. - -.. code-block:: bash - ./example.bash - diff --git a/examples/shared_linking_example/CMakeLists.txt b/examples/shared_linking_example/CMakeLists.txt new file mode 100644 index 00000000..031c2a2e --- /dev/null +++ b/examples/shared_linking_example/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.21) +project(pcap_test) + +find_package(OusterSDK REQUIRED COMPONENTS Shared) + +add_executable(pcap_test main.cpp) + +target_link_libraries(pcap_test + OusterSDK::shared_library +) diff --git a/examples/shared_linking_example/Dockerfile b/examples/shared_linking_example/Dockerfile new file mode 100644 index 00000000..8761c15e --- /dev/null +++ b/examples/shared_linking_example/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + git build-essential cmake \ + curl zip unzip tar pkg-config \ + python3 libxinerama-dev libxcursor-dev \ + xorg-dev libglu1-mesa-dev flex bison \ + libeigen3-dev + +ENV WORKSPACE=/root +ENV INSTALL_DIR="/usr/local" +RUN mkdir -p /opt/vcpkg && cd /opt && git clone https://github.com/microsoft/vcpkg.git \ + && cd vcpkg && ./bootstrap-vcpkg.sh && ./vcpkg install "curl[core]" libtins \ + glfw3 "glad[gl-api-33]" libpng flatbuffers zlib gtest openssl +COPY . $WORKSPACE/ +RUN cd $WORKSPACE && \ + cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DBUILD_EXAMPLES=OFF \ + -DBUILD_SHARED_LIBRARY=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \ + -DCMAKE_TOOLCHAIN_FILE=/opt/vcpkg/scripts/buildsystems/vcpkg.cmake . &&\ + cmake --build . --parallel 4 --target install + +FROM ubuntu:22.04 + +RUN apt-get update && apt-get install -y \ + libeigen3-dev \ + libflatbuffers-dev \ + git \ + build-essential \ + cmake + +ENV WORKSPACE=/root +ENV INSTALL_DIR="/usr/local" + +COPY --from=0 $INSTALL_DIR $INSTALL_DIR +COPY tests/pcaps/OS-2-32-U0_v2.0.0_1024x10.pcap examples/shared_linking_example/CMakeLists.txt \ + examples/shared_linking_example/main.cpp $WORKSPACE/ + +RUN export CMAKE_PREFIX_PATH="$INSTALL_DIR" &&\ + mkdir -p $WORKSPACE/build &&\ + cd $WORKSPACE/build &&\ + cmake $WORKSPACE && cmake --build . --parallel 4 + +CMD $WORKSPACE/build/pcap_test /root/OS-2-32-U0_v2.0.0_1024x10.pcap diff --git a/examples/shared_linking_example/example.bash b/examples/shared_linking_example/example.bash new file mode 100755 index 00000000..46e6c987 --- /dev/null +++ b/examples/shared_linking_example/example.bash @@ -0,0 +1,15 @@ +#! /bin/bash + +set -e +currentDir="$(cd $(dirname $0) && pwd)" +baseDir=$currentDir/../.. +tempDir="$(mktemp -d)" + +trap 'rm -rf $tempDir' EXIT +trap 'echo \*\*\* ERROR on line: $LINENO exit_code: $?' ERR + +cd $baseDir + +docker build -f $currentDir/Dockerfile --iidfile=$tempDir/iid . + +docker run --rm $(cat $tempDir/iid) diff --git a/examples/shared_linking_example/main.cpp b/examples/shared_linking_example/main.cpp new file mode 100644 index 00000000..2243ee3b --- /dev/null +++ b/examples/shared_linking_example/main.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +#include +#include + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "\n\nUsage: pcap_test " << std::endl; + + return argc == 1 ? EXIT_SUCCESS : EXIT_FAILURE; + } + const std::string pcap_file = argv[1]; + auto stream_info = ouster::sensor_utils::get_stream_info(pcap_file); + std::cout << *stream_info << std::endl; +} diff --git a/examples/shared_linking_example/readme.rst b/examples/shared_linking_example/readme.rst new file mode 100644 index 00000000..b2a80336 --- /dev/null +++ b/examples/shared_linking_example/readme.rst @@ -0,0 +1,15 @@ +This is a simple example on how to build a shared library and link a cpp project with the ouster_pcap +library. + +Bash script to run the example in a container: + +.. literalinclude:: ../../../examples/shared_linking_example/example.bash + :language: bash + :dedent: + +Dockerfile: + +.. literalinclude:: ../../../examples/shared_linking_example/Dockerfile + :language: docker + :dedent: + diff --git a/examples/static_linking_example/CMakeLists.txt b/examples/static_linking_example/CMakeLists.txt new file mode 100644 index 00000000..8ad55654 --- /dev/null +++ b/examples/static_linking_example/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.21) +project(pcap_test) + +find_package(OusterSDK REQUIRED COMPONENTS Static) + +add_executable(pcap_test main.cpp) + +target_link_libraries(pcap_test + OusterSDK::ouster_client OusterSDK::ouster_pcap +) diff --git a/examples/linking_example/Dockerfile b/examples/static_linking_example/Dockerfile similarity index 57% rename from examples/linking_example/Dockerfile rename to examples/static_linking_example/Dockerfile index 19a6809a..007f2b68 100644 --- a/examples/linking_example/Dockerfile +++ b/examples/static_linking_example/Dockerfile @@ -2,45 +2,51 @@ FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ libeigen3-dev \ - libjsoncpp-dev \ libtins-dev \ libpcap-dev \ libcurl4-openssl-dev \ git \ build-essential \ cmake \ - libspdlog-dev + zlib1g \ + zlib1g-dev \ + libglfw3-dev \ + libpng-dev \ + libflatbuffers-dev ENV WORKSPACE=/root ENV INSTALL_DIR="/usr/local" COPY . $WORKSPACE/ RUN cd $WORKSPACE && \ - cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DBUILD_PCAP=ON -DBUILD_OSF=OFF -DBUILD_VIZ=OFF -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF . && \ - cmake --build . --target install + cmake -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DBUILD_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF . && \ + cmake --build . --parallel 4 --target install FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \ libeigen3-dev \ - libjsoncpp-dev \ libtins-dev \ libpcap-dev \ libcurl4-openssl-dev \ git \ build-essential \ cmake \ - libspdlog-dev + libglfw3-dev \ + zlib1g \ + zlib1g-dev \ + libpng-dev \ + libflatbuffers-dev ENV WORKSPACE=/root ENV INSTALL_DIR="/usr/local" COPY --from=0 $INSTALL_DIR $INSTALL_DIR -COPY tests/pcaps/OS-2-32-U0_v2.0.0_1024x10.pcap examples/linking_example/CMakeLists.txt \ - examples/linking_example/main.cpp $WORKSPACE/ +COPY tests/pcaps/OS-2-32-U0_v2.0.0_1024x10.pcap examples/static_linking_example/CMakeLists.txt \ + examples/static_linking_example/main.cpp $WORKSPACE/ RUN export CMAKE_PREFIX_PATH="$INSTALL_DIR" &&\ mkdir -p $WORKSPACE/build &&\ cd $WORKSPACE/build &&\ - cmake $WORKSPACE && cmake --build . + cmake $WORKSPACE && cmake --build . --parallel 4 CMD $WORKSPACE/build/pcap_test /root/OS-2-32-U0_v2.0.0_1024x10.pcap diff --git a/examples/static_linking_example/example.bash b/examples/static_linking_example/example.bash new file mode 100755 index 00000000..46e6c987 --- /dev/null +++ b/examples/static_linking_example/example.bash @@ -0,0 +1,15 @@ +#! /bin/bash + +set -e +currentDir="$(cd $(dirname $0) && pwd)" +baseDir=$currentDir/../.. +tempDir="$(mktemp -d)" + +trap 'rm -rf $tempDir' EXIT +trap 'echo \*\*\* ERROR on line: $LINENO exit_code: $?' ERR + +cd $baseDir + +docker build -f $currentDir/Dockerfile --iidfile=$tempDir/iid . + +docker run --rm $(cat $tempDir/iid) diff --git a/examples/static_linking_example/main.cpp b/examples/static_linking_example/main.cpp new file mode 100644 index 00000000..2243ee3b --- /dev/null +++ b/examples/static_linking_example/main.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +#include +#include + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "\n\nUsage: pcap_test " << std::endl; + + return argc == 1 ? EXIT_SUCCESS : EXIT_FAILURE; + } + const std::string pcap_file = argv[1]; + auto stream_info = ouster::sensor_utils::get_stream_info(pcap_file); + std::cout << *stream_info << std::endl; +} diff --git a/examples/static_linking_example/readme.rst b/examples/static_linking_example/readme.rst new file mode 100644 index 00000000..130c363a --- /dev/null +++ b/examples/static_linking_example/readme.rst @@ -0,0 +1,15 @@ +This is a simple example on how to build a static shared library and link a cpp project with the ouster_pcap +library. + +Bash script to run the example in a container: + +.. literalinclude:: ../../../examples/static_linking_example/example.bash + :language: bash + :dedent: + +Dockerfile: + +.. literalinclude:: ../../../examples/static_linking_example/Dockerfile + :language: docker + :dedent: + diff --git a/examples/viz_events_example.cpp b/examples/viz_events_example.cpp index febaba31..37e123ef 100644 --- a/examples/viz_events_example.cpp +++ b/examples/viz_events_example.cpp @@ -24,12 +24,14 @@ constexpr int h = 8; float img_data[w * h]; auto img = std::make_shared(); +// returns true if the provided window coordinates were within the image bool set_pixel_from_window_coordinates(ouster::viz::PointViz& viz, const WindowCtx& ctx, double x, double y) { auto pixel = img->window_coordinates_to_image_pixel(ctx, x, y); - if (pixel) { - img_data[pixel->first + pixel->second * w] = 1.0; + if (pixel.first >= 0 && pixel.first < w && pixel.second >= 0 && + pixel.second < h) { + img_data[pixel.first + pixel.second * w] = 1.0; img->set_image(w, h, img_data); viz.update(); return false; diff --git a/ouster_client/CMakeLists.txt b/ouster_client/CMakeLists.txt index 304cceae..824ad5d2 100644 --- a/ouster_client/CMakeLists.txt +++ b/ouster_client/CMakeLists.txt @@ -1,24 +1,32 @@ # ==== Requirements ==== find_package(Eigen3 REQUIRED) -find_package(jsoncpp REQUIRED) find_package(CURL REQUIRED) -find_package(spdlog REQUIRED) +find_package(Threads REQUIRED) + include(Coverage) # ==== Libraries ==== -add_library(ouster_client src/client.cpp src/types.cpp src/sensor_info.cpp src/netcompat.cpp src/lidar_scan.cpp +add_library(ouster_client STATIC src/client.cpp src/types.cpp src/sensor_info.cpp src/netcompat.cpp src/lidar_scan.cpp src/image_processing.cpp src/parsing.cpp src/sensor_client.cpp src/sensor_http.cpp src/sensor_http_imp.cpp src/sensor_scan_source.cpp - src/sensor_tcp_imp.cpp src/logging.cpp src/field.cpp src/profile_extension.cpp src/util.cpp src/metadata.cpp) + src/sensor_tcp_imp.cpp src/logging.cpp src/field.cpp src/profile_extension.cpp src/metadata.cpp src/packet.cpp) target_link_libraries(ouster_client PUBLIC Eigen3::Eigen - jsoncpp_lib $ - spdlog::spdlog + Threads::Threads + $ PRIVATE - CURL::libcurl) + $ + ) target_compile_definitions(ouster_client PRIVATE EIGEN_MPL2_ONLY) +if(BUILD_SHARED_LIBRARY) + target_compile_definitions(ouster_client PRIVATE BUILD_SHARED_LIBS_EXPORT) +endif() +set_property(TARGET ouster_client PROPERTY POSITION_INDEPENDENT_CODE ON) +if(BUILD_SHARED_LIBRARY) + set_target_properties(ouster_client PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() CodeCoverageFunctionality(ouster_client) add_library(OusterSDK::ouster_client ALIAS ouster_client) @@ -42,9 +50,9 @@ target_include_directories(ouster_client SYSTEM # ==== Install ==== install(TARGETS ouster_client EXPORT ouster-sdk-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include) -install(DIRECTORY include/ouster include/optional-lite DESTINATION include) +install(DIRECTORY include/ouster include/optional-lite + DESTINATION include +) diff --git a/ouster_client/include/ouster/array_view.h b/ouster_client/include/ouster/array_view.h index bc8d044f..7598a1a4 100644 --- a/ouster_client/include/ouster/array_view.h +++ b/ouster_client/include/ouster/array_view.h @@ -13,6 +13,7 @@ #include "ouster/impl/cuda_macros.h" #include "ouster/impl/idx_range.h" +#include "ouster/visibility.h" namespace ouster { diff --git a/ouster_client/include/ouster/client.h b/ouster_client/include/ouster/client.h index 63c4552b..b75ef748 100644 --- a/ouster_client/include/ouster/client.h +++ b/ouster_client/include/ouster/client.h @@ -14,13 +14,15 @@ #include #include "ouster/defaults.h" +#include "ouster/packet.h" #include "ouster/types.h" #include "ouster/version.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor { -struct client; +struct OUSTER_API_CLASS client; /** Returned by poll_client. */ enum client_state { @@ -57,6 +59,7 @@ const util::version min_version = {1, 12, 0, "", "", "", ""}; * * @return true on success, otherwise false. */ +OUSTER_API_FUNCTION bool init_logger(const std::string& log_level, const std::string& log_file_path = "", bool rotating = false, int max_size_in_bytes = 0, int max_files = 0); @@ -74,6 +77,7 @@ bool init_logger(const std::string& log_level, * * @return pointer owning the resources associated with the connection. */ +OUSTER_API_FUNCTION std::shared_ptr init_client(const std::string& hostname, int lidar_port, int imu_port); @@ -96,6 +100,7 @@ std::shared_ptr init_client(const std::string& hostname, int lidar_port, * * @return pointer owning the resources associated with the connection. */ +OUSTER_API_FUNCTION std::shared_ptr init_client( const std::string& hostname, const std::string& udp_dest_host, lidar_mode ld_mode = MODE_UNSPEC, timestamp_mode ts_mode = TIME_FROM_UNSPEC, @@ -123,6 +128,7 @@ std::shared_ptr init_client( * the sensor, otherwise only the port values within the config object will be * used and the rest will be ignored. */ +OUSTER_API_FUNCTION std::shared_ptr mtp_init_client( const std::string& hostname, const sensor_config& config, const std::string& mtp_dest_host, bool main, @@ -144,6 +150,7 @@ std::shared_ptr mtp_init_client( * LIDAR_DATA) is true if lidar data is ready to read, and (s & IMU_DATA) is * true if imu data is ready to read. */ +OUSTER_API_FUNCTION client_state poll_client(const client& cli, int timeout_sec = 1); /** @@ -156,6 +163,7 @@ client_state poll_client(const client& cli, int timeout_sec = 1); * * @return true if a lidar packet was successfully read. */ +OUSTER_API_FUNCTION bool read_lidar_packet(const client& cli, uint8_t* buf, const packet_format& pf); @@ -169,6 +177,7 @@ bool read_lidar_packet(const client& cli, uint8_t* buf, * * @return true if a lidar packet was successfully read. */ +OUSTER_API_FUNCTION bool read_lidar_packet(const client& cli, uint8_t* buf, size_t bytes); /** @@ -181,6 +190,7 @@ bool read_lidar_packet(const client& cli, uint8_t* buf, size_t bytes); * * @return true if a lidar packet was successfully read. */ +OUSTER_API_FUNCTION bool read_lidar_packet(const client& cli, LidarPacket& packet); /** @@ -193,6 +203,7 @@ bool read_lidar_packet(const client& cli, LidarPacket& packet); * * @return true if a lidar packet was successfully read. */ +OUSTER_API_FUNCTION bool read_imu_packet(const client& cli, uint8_t* buf, size_t bytes); /** @@ -205,6 +216,7 @@ bool read_imu_packet(const client& cli, uint8_t* buf, size_t bytes); * * @return true if an imu packet was successfully read. */ +OUSTER_API_FUNCTION bool read_imu_packet(const client& cli, uint8_t* buf, const packet_format& pf); /** @@ -217,6 +229,7 @@ bool read_imu_packet(const client& cli, uint8_t* buf, const packet_format& pf); * * @return true if an imu packet was successfully read. */ +OUSTER_API_FUNCTION bool read_imu_packet(const client& cli, ImuPacket& packet); /** @@ -235,6 +248,7 @@ bool read_imu_packet(const client& cli, ImuPacket& packet); * * @return a text blob of metadata parseable into a sensor_info struct. */ +OUSTER_API_FUNCTION std::string get_metadata(client& cli, int timeout_sec = LONG_HTTP_REQUEST_TIMEOUT_SECONDS); @@ -251,6 +265,7 @@ std::string get_metadata(client& cli, * * @return true if sensor config successfully populated. */ +OUSTER_API_FUNCTION bool get_config(const std::string& hostname, sensor_config& config, bool active = true, int timeout_sec = LONG_HTTP_REQUEST_TIMEOUT_SECONDS); @@ -281,6 +296,7 @@ enum config_flags : uint8_t { * * @return true if config params successfuly set on sensor. */ +OUSTER_API_FUNCTION bool set_config(const std::string& hostname, const sensor_config& config, uint8_t config_flags = 0, int timeout_sec = LONG_HTTP_REQUEST_TIMEOUT_SECONDS); @@ -292,6 +308,7 @@ bool set_config(const std::string& hostname, const sensor_config& config, * * @return the port number. */ +OUSTER_API_FUNCTION int get_lidar_port(const client& cli); /** @@ -301,6 +318,7 @@ int get_lidar_port(const client& cli); * * @return the port number. */ +OUSTER_API_FUNCTION int get_imu_port(const client& cli); /** @@ -310,6 +328,7 @@ int get_imu_port(const client& cli); * * @return true if addr is in multicast range. */ +OUSTER_API_FUNCTION bool in_multicast(const std::string& addr); } // namespace sensor diff --git a/ouster_client/include/ouster/field.h b/ouster_client/include/ouster/field.h index 3cf9a121..4b1d3bbe 100644 --- a/ouster_client/include/ouster/field.h +++ b/ouster_client/include/ouster/field.h @@ -17,6 +17,7 @@ #include "ouster/array_view.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { @@ -29,6 +30,7 @@ namespace impl { * * @return vector of stride offsets */ +OUSTER_API_FUNCTION std::vector calculate_strides(const std::vector& shape); } // namespace impl @@ -39,24 +41,24 @@ namespace impl { // clang-format off template int type_size() { return sizeof(T); } -template <> int type_size(); +template <> OUSTER_API_FUNCTION int type_size(); template size_t type_hash() { return typeid(T).hash_code(); } -template <> size_t type_hash(); +template <> OUSTER_API_FUNCTION size_t type_hash(); template ChanFieldType type_cft() { return ChanFieldType::UNREGISTERED; } -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); -template <> ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); +template <> OUSTER_API_FUNCTION ChanFieldType type_cft(); // clang-format on @@ -68,7 +70,7 @@ template <> ChanFieldType type_cft(); * Unlike FieldType this fully describes a Field's dimensions rather than * abstract away the lidar width and height or packet count. */ -struct FieldDescriptor { +struct OUSTER_API_CLASS FieldDescriptor { /** * type hash of the described field */ @@ -79,6 +81,7 @@ struct FieldDescriptor { * * @return type size in bytes */ + OUSTER_API_FUNCTION size_t bytes() const; // TODO: ideally we need something like llvm::SmallVector here -- Tim T. @@ -117,6 +120,7 @@ struct FieldDescriptor { * * @return size in elements */ + OUSTER_API_FUNCTION size_t size() const; /** @@ -125,8 +129,10 @@ struct FieldDescriptor { * * @return ChanFieldType */ + OUSTER_API_FUNCTION sensor::ChanFieldType tag() const; + OUSTER_API_FUNCTION bool operator==(const FieldDescriptor& other) const noexcept { return type == other.type && shape == other.shape && strides == other.strides && element_size == other.element_size; @@ -137,6 +143,7 @@ struct FieldDescriptor { * * @param[in,out] other Handle to swapped FieldDescriptor. */ + OUSTER_API_FUNCTION void swap(FieldDescriptor& other); /** @@ -160,6 +167,7 @@ struct FieldDescriptor { * @param[in] other A constant of type FieldDescriptor. * @return true if compatible, otherwise false. */ + OUSTER_API_FUNCTION bool is_type_compatible(const FieldDescriptor& other) const noexcept; /** @@ -167,6 +175,7 @@ struct FieldDescriptor { * * @return number of dimensions. */ + OUSTER_API_FUNCTION size_t ndim() const noexcept; // Factory functions @@ -262,13 +271,14 @@ auto fd_array(sensor::ChanFieldType tag, Args&&... args) -> FieldDescriptor { * Non-owning wrapper over a memory pointer that allows for type safe * conversion to typed pointer, eigen array or ArrayView */ -class FieldView { +class OUSTER_API_CLASS FieldView { protected: void* ptr_; FieldDescriptor desc_; public: /** Default constructor for empty FieldView */ + OUSTER_API_FUNCTION FieldView() noexcept; /** @@ -277,6 +287,7 @@ class FieldView { * @param[in] ptr Memory pointer. * @param[in] desc Field descriptor. */ + OUSTER_API_FUNCTION FieldView(void* ptr, const FieldDescriptor& desc); /** @@ -298,7 +309,10 @@ class FieldView { const T* get() const { if (!desc_.eligible_type()) { throw std::invalid_argument( - "FieldView: ineligible dereference type"); + "FieldView: ineligible dereference type for field of element " + "type " + + ouster::sensor::to_string(desc_.tag()) + + ". Dereference type must match or be void."); } return reinterpret_cast(ptr_); } @@ -336,7 +350,9 @@ class FieldView { if (desc_.ndim() != Dim) { throw std::invalid_argument( "FieldView: ArrayView conversion failed due to dimension " - "mismatch"); + "mismatch. Expected " + + std::to_string(desc_.ndim()) + " got " + std::to_string(Dim) + + " dimensions."); } return ArrayView(get(), desc_.shape, desc_.strides); @@ -353,7 +369,9 @@ class FieldView { if (desc_.ndim() != Dim) { throw std::invalid_argument( "FieldView: ArrayView conversion failed due to dimension " - "mismatch"); + "mismatch. Expected " + + std::to_string(desc_.ndim()) + " got " + std::to_string(Dim) + + " dimensions."); } return ConstArrayView(get(), desc_.shape, desc_.strides); @@ -370,7 +388,9 @@ class FieldView { if (desc_.ndim() != 2) { throw std::invalid_argument( "Field: Eigen array conversion failed due to dimension " - "mismatch"); + "mismatch. Underlying data has " + + std::to_string(desc_.ndim()) + + " dimensions but must have 2 dimensions."); } if (sparse()) { @@ -393,7 +413,9 @@ class FieldView { if (desc_.ndim() != 2) { throw std::invalid_argument( "Field: Eigen array conversion failed due to dimension " - "mismatch"); + "mismatch. Underlying data has " + + std::to_string(desc_.ndim()) + + " dimensions but must have 2 dimensions."); } if (sparse()) { @@ -416,7 +438,9 @@ class FieldView { if (desc_.ndim() != 1) { throw std::invalid_argument( "Field: Eigen array conversion failed due to dimension " - "mismatch"); + "mismatch. Underlying data has " + + std::to_string(desc_.ndim()) + + " dimensions but must have 1 dimensions."); } if (sparse()) { @@ -439,7 +463,9 @@ class FieldView { if (desc_.ndim() != 1) { throw std::invalid_argument( "Field: Eigen array conversion failed due to dimension " - "mismatch"); + "mismatch. Underlying data has " + + std::to_string(desc_.ndim()) + + " dimensions but must have 1 dimensions."); } if (sparse()) { @@ -457,6 +483,7 @@ class FieldView { * * @return true if FieldView is owning a resource */ + OUSTER_API_FUNCTION explicit operator bool() const noexcept; /** @@ -533,9 +560,13 @@ class FieldView { "FieldView: cannot reshape sparse views"); } - if (impl::product{}(dims...) != size()) { + auto requested_size = impl::product{}(dims...); + if (requested_size != size()) { throw std::invalid_argument( - "ArrayView: cannot reshape due to size mismatch"); + "ArrayView: cannot reshape due to size mismatch. Requested " + "dimensions had " + + std::to_string(requested_size) + " but we have " + + std::to_string(size()) + " elements."); } auto new_desc = FieldDescriptor{}; @@ -551,6 +582,7 @@ class FieldView { * * @return size in bytes */ + OUSTER_API_FUNCTION size_t bytes() const noexcept; /** @@ -558,6 +590,7 @@ class FieldView { * * @return size in elements */ + OUSTER_API_FUNCTION size_t size() const; /** @@ -567,6 +600,7 @@ class FieldView { * * @return true if matched, otherwise false */ + OUSTER_API_FUNCTION bool matches(const FieldDescriptor& d) const noexcept; /** @@ -574,6 +608,7 @@ class FieldView { * * @return FieldDescriptor */ + OUSTER_API_FUNCTION const FieldDescriptor& desc() const; /** @@ -583,6 +618,7 @@ class FieldView { * * @return vector of dimensions */ + OUSTER_API_FUNCTION const std::vector& shape() const; /** @@ -590,6 +626,7 @@ class FieldView { * * @return ChanFieldType */ + OUSTER_API_FUNCTION sensor::ChanFieldType tag() const; /** @@ -597,6 +634,7 @@ class FieldView { * * @return true if sparse */ + OUSTER_API_FUNCTION bool sparse() const; }; @@ -634,6 +672,7 @@ enum class FieldClass { * * @return string representation of the FieldClass, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(FieldClass f); /** @@ -642,15 +681,17 @@ std::string to_string(FieldClass f); * * For usage examples, check unit tests */ -class Field : public FieldView { +class OUSTER_API_CLASS Field : public FieldView { protected: FieldClass class_; public: /** Default constructor, representing invalid Field */ + OUSTER_API_FUNCTION Field() noexcept; /** Field destructor */ + OUSTER_API_FUNCTION ~Field(); /** @@ -659,6 +700,7 @@ class Field : public FieldView { * @param[in] desc FieldDescriptor * @param[in] field_class FieldClass */ + OUSTER_API_FUNCTION Field(const FieldDescriptor& desc, FieldClass field_class = {}); /** @@ -666,6 +708,7 @@ class Field : public FieldView { * * @param[in] other Field to copy */ + OUSTER_API_FUNCTION Field(const Field& other); /** @@ -673,6 +716,7 @@ class Field : public FieldView { * * @param[in] other Field to copy */ + OUSTER_API_FUNCTION Field& operator=(const Field& other); /** @@ -680,6 +724,7 @@ class Field : public FieldView { * * @param[in] other Field to steal resource from */ + OUSTER_API_FUNCTION Field(Field&& other) noexcept; /** @@ -687,6 +732,7 @@ class Field : public FieldView { * * @param[in] other Field to steal resource from */ + OUSTER_API_FUNCTION Field& operator=(Field&& other) noexcept; /** @@ -694,6 +740,7 @@ class Field : public FieldView { * * @return FieldClass */ + OUSTER_API_FUNCTION FieldClass field_class() const; /** @@ -701,6 +748,7 @@ class Field : public FieldView { * * @param[in] other Field to swap resources with */ + OUSTER_API_FUNCTION void swap(Field& other) noexcept; /** @@ -708,6 +756,7 @@ class Field : public FieldView { * * @return true if type, shape and memory contents are equal to other */ + OUSTER_API_FUNCTION bool operator==(const Field& other) const; }; @@ -725,6 +774,7 @@ class Field : public FieldView { * * @return reinterpreted view of uint8_t, uint16_t, uint32_t or uint64_t type */ +OUSTER_API_FUNCTION FieldView uint_view(const FieldView& other); } // namespace ouster @@ -737,6 +787,7 @@ namespace std { * @param[in] a Field to swap with b * @param[in] b Field to swap with a */ +OUSTER_API_FUNCTION void swap(ouster::Field& a, ouster::Field& b); } // namespace std diff --git a/ouster_client/include/ouster/image_processing.h b/ouster_client/include/ouster/image_processing.h index a222e556..3b7441af 100644 --- a/ouster_client/include/ouster/image_processing.h +++ b/ouster_client/include/ouster/image_processing.h @@ -11,12 +11,13 @@ #include #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace viz { /** Adjusts brightness to between 0 and 1. */ -class AutoExposure { +class OUSTER_API_CLASS AutoExposure { const double lo_percentile, hi_percentile; // percentiles used for scaling const int ae_update_every; @@ -33,6 +34,7 @@ class AutoExposure { public: /** Default constructor using default percentile and update values. */ + OUSTER_API_FUNCTION AutoExposure(); /** @@ -40,6 +42,7 @@ class AutoExposure { * * @param[in] update_every update every this number of frames. */ + OUSTER_API_FUNCTION AutoExposure(int update_every); /** @@ -49,6 +52,7 @@ class AutoExposure { * @param[in] hi_percentile high percentile to use for adjustment. * @param[in] update_every update every this number of frames. */ + OUSTER_API_FUNCTION AutoExposure(double lo_percentile, double hi_percentile, int update_every); /** @@ -60,6 +64,7 @@ class AutoExposure { * @param[in] image Reference to the image, modified in place. * @param[in] update_state Update lo/hi percentiles if true. */ + OUSTER_API_FUNCTION void operator()(Eigen::Ref> image, bool update_state = true); /** @@ -71,6 +76,7 @@ class AutoExposure { * @param[in] image Reference to the image, modified in place. * @param[in] update_state Update lo/hi percentiles if true. */ + OUSTER_API_FUNCTION void operator()(Eigen::Ref> image, bool update_state = true); }; @@ -79,7 +85,7 @@ class AutoExposure { * thereby correcting subtle horizontal line artifacts in images, especially the * ambient image. */ -class BeamUniformityCorrector { +class OUSTER_API_CLASS BeamUniformityCorrector { private: int counter = 0; Eigen::ArrayXd dark_count; @@ -95,6 +101,7 @@ class BeamUniformityCorrector { * @param[in] image Reference to the image, modified in-place. * @param[in] update_state Update dark counts if true. */ + OUSTER_API_FUNCTION void operator()(Eigen::Ref> image, bool update_state = true); /** @@ -104,6 +111,7 @@ class BeamUniformityCorrector { * @param[in] image Reference to the image, modified in-place. * @param[in] update_state Update dark counts if true. */ + OUSTER_API_FUNCTION void operator()(Eigen::Ref> image, bool update_state = true); }; } // namespace viz diff --git a/ouster_client/include/ouster/impl/client_poller.h b/ouster_client/include/ouster/impl/client_poller.h index 28a9221e..e7052d4f 100644 --- a/ouster_client/include/ouster/impl/client_poller.h +++ b/ouster_client/include/ouster/impl/client_poller.h @@ -5,6 +5,9 @@ #pragma once +#include "ouster/client.h" +#include "ouster/visibility.h" + namespace ouster { namespace sensor { namespace impl { @@ -12,11 +15,12 @@ namespace impl { /** * Poller used in multiclient scenarios */ -struct client_poller; +struct OUSTER_API_CLASS client_poller; /** * produces uninitialized poller */ +OUSTER_API_FUNCTION std::shared_ptr make_poller(); /** @@ -24,6 +28,7 @@ std::shared_ptr make_poller(); * * @param[in] poller client_poller to reset */ +OUSTER_API_FUNCTION void reset_poll(client_poller& poller); /** @@ -32,6 +37,7 @@ void reset_poll(client_poller& poller); * @param[in] poller client_poller * @param[in] cli client to watch */ +OUSTER_API_FUNCTION void set_poll(client_poller& poller, const client& cli); /** @@ -42,6 +48,7 @@ void set_poll(client_poller& poller, const client& cli); * * @return -1 for error, 0 for timeout, otherwise number of messages received */ +OUSTER_API_FUNCTION int poll(client_poller& poller, int timeout_sec = 1); /** @@ -52,6 +59,7 @@ int poll(client_poller& poller, int timeout_sec = 1); * @return client_state which is one of CLIENT_ERROR or EXIT on error, * otherwise returning TIMEOUT if no error occurred */ +OUSTER_API_FUNCTION client_state get_error(const client_poller& poller); /** @@ -63,6 +71,7 @@ client_state get_error(const client_poller& poller); * @return client_state comprising of either LIDAR_DATA or IMU_DATA, or TIMEOUT * if no data was received */ +OUSTER_API_FUNCTION client_state get_poll(const client_poller& poller, const client& cli); } // namespace impl diff --git a/ouster_client/include/ouster/impl/idx_range.h b/ouster_client/include/ouster/impl/idx_range.h index ad14823d..011c626b 100644 --- a/ouster_client/include/ouster/impl/idx_range.h +++ b/ouster_client/include/ouster/impl/idx_range.h @@ -10,11 +10,16 @@ #include #include "ouster/impl/cuda_macros.h" - +#include "ouster/visibility.h" namespace ouster { namespace impl { -struct idx_range { +struct OUSTER_API_CLASS idx_range { + OUSTER_API_FUNCTION + idx_range(){}; + OUSTER_API_FUNCTION + idx_range(int start, int end) : start(start), end(end){}; + int start = 0; int end = 0; }; @@ -50,7 +55,9 @@ OSDK_FN int range_or_idx(T idx) { return idx; } -inline OSDK_FN int range_or_idx(idx_range e) { return e.start; } +inline OSDK_FN OUSTER_API_FUNCTION int range_or_idx(idx_range e) { + return e.start; +} template OSDK_FN std::enable_if_t::v, T> range_replace_dim(T dim, U) { @@ -148,8 +155,10 @@ OSDK_FN_HOST std::vector range_args_reshape(const std::vector& shape, } // namespace impl OSDK_FN +OUSTER_API_FUNCTION inline impl::idx_range keep() { return {0, 0}; } OSDK_FN +OUSTER_API_FUNCTION inline impl::idx_range keep(int start, int end) { return {start, end}; } } // namespace ouster diff --git a/ouster_client/include/ouster/impl/lidar_scan_impl.h b/ouster_client/include/ouster/impl/lidar_scan_impl.h index 56dbabdf..49f6cd47 100644 --- a/ouster_client/include/ouster/impl/lidar_scan_impl.h +++ b/ouster_client/include/ouster/impl/lidar_scan_impl.h @@ -13,6 +13,7 @@ #include "ouster/impl/packet_writer.h" #include "ouster/lidar_scan.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace impl { @@ -23,52 +24,52 @@ template struct FieldTag; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::UINT8; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::UINT16; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::UINT32; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::UINT64; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::INT8; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::INT16; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::INT32; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::INT64; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::FLOAT32; }; template <> -struct FieldTag { +struct OUSTER_API_CLASS FieldTag { static constexpr ChanFieldType tag = ChanFieldType::FLOAT64; }; @@ -209,17 +210,20 @@ void visit_field(SCAN&& ls, const std::string& name, OP&& op, Args&&... args) { std::forward(args)...); } +// clang-format off /* * Call a generic operation op(f, Args...) for each field of the lidar scan * with type parameter T having the correct field type */ template -[[deprecated("Use either ls.fields() or foreach_channel_field instead")]] void -foreach_field(SCAN&& ls, OP&& op, Args&&... args) { +[[deprecated("Use either ls.fields() or foreach_channel_field instead")]] + +void foreach_field(SCAN&& ls, OP&& op, Args&&... args) { for (const auto& ft : ls) visit_field(std::forward(ls), ft.first, std::forward(op), ft.first, std::forward(args)...); } +// clang-format on /* * Call a generic operation op(f, Args...) for each parsed channel field of @@ -237,7 +241,7 @@ void foreach_channel_field(SCAN&& ls, const sensor::packet_format& pf, OP&& op, } // Read LidarScan field and cast to the destination -struct read_and_cast { +struct OUSTER_API_CLASS read_and_cast { template void operator()(Eigen::Ref> src, Eigen::Ref> dest) { dest = src.template cast(); @@ -258,7 +262,7 @@ struct read_and_cast { // Copy fields from `ls_source` LidarScan to `field_dest` img with casting // to the img_t type of `field_dest`. -struct copy_and_cast { +struct OUSTER_API_CLASS copy_and_cast { template void operator()(Eigen::Ref> field_dest, const LidarScan& ls_source, const std::string& ls_source_field) { @@ -269,7 +273,7 @@ struct copy_and_cast { /** * Zeros fields in LidarScans */ -struct zero_field { +struct OUSTER_API_CLASS zero_field { /** * Zeros the field dest. * @@ -288,6 +292,7 @@ struct zero_field { * @param[in] pf packet format * @param[in] ls lidar scan to check for RAW_HEADERS field presence. */ +OUSTER_API_FUNCTION bool raw_headers_enabled(const sensor::packet_format& pf, const LidarScan& ls); /** @@ -321,7 +326,8 @@ void scan_to_packets(const LidarScan& ls, auto frame_id = ls.frame_id; LidarPacket packet(pw.lidar_packet_size); - + // TODO: avoid this copy if we combine packet_writer and packet_format + packet.format = std::make_shared(pw); for (size_t p_id = 0; p_id < total_packets; ++p_id) { uint8_t* lidar_buf = packet.buf.data(); std::memset(packet.buf.data(), 0, packet.buf.size()); diff --git a/ouster_client/include/ouster/impl/logging.h b/ouster_client/include/ouster/impl/logging.h index 4ae9f197..7b339cc8 100644 --- a/ouster_client/include/ouster/impl/logging.h +++ b/ouster_client/include/ouster/impl/logging.h @@ -1,34 +1,120 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ + #pragma once -#include +#include +#include namespace ouster { namespace sensor { namespace impl { class Logger { + private: + struct format_builder; + std::string finalize_format_builder( + std::shared_ptr); + struct internal_logger; + public: - static Logger& instance(); + Logger(); + ~Logger(); - spdlog::logger& get_logger(); + enum LOG_LEVEL { + LOG_TRACE, + LOG_DEBUG, + LOG_INFO, + LOG_WARN, + LOG_ERROR, + LOG_CRITICAL + }; + + static Logger& instance(); - void configure_generic_sink(spdlog::sink_ptr sink, - const std::string& log_level); bool configure_stdout_sink(const std::string& log_level); bool configure_file_sink(const std::string& log_level, const std::string& log_file_path, bool rotating, int max_size_in_bytes, int max_files); + template + void trace(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_TRACE, format_string, + std::forward(args)...); + }; + + template + void debug(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_DEBUG, format_string, + std::forward(args)...); + }; + + template + void info(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_INFO, format_string, + std::forward(args)...); + }; + + template + void warn(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_WARN, format_string, + std::forward(args)...); + }; + + template + void error(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_ERROR, format_string, + std::forward(args)...); + }; + + template + void critical(const std::string& format_string, Args&&... args) { + return log(LOG_LEVEL::LOG_CRITICAL, format_string, + std::forward(args)...); + }; + + template + void log(LOG_LEVEL level, const std::string& format_string, + Args&&... args) { + std::shared_ptr builder = + make_builder(format_string); + process_args(builder, std::forward(args)...); + return log(level, finalize_format_builder(builder)); + } + + void log(LOG_LEVEL level, const std::string& msg); + private: - Logger(); + // Base Case + template + void process_args(std::shared_ptr builder, + T next_arg) { + process_arg(builder, next_arg); + } + + // Recursive Case + template + void process_args(std::shared_ptr builder, + T next_arg, Args&&... args) { + process_arg(builder, next_arg); + process_args(builder, std::forward(args)...); + } + + template + void process_arg(std::shared_ptr builder, T data); + + std::shared_ptr make_builder( + const std::string& format_string); static const std::string logger_name; - std::unique_ptr logger_; + std::unique_ptr internal_logger_; }; } // namespace impl -spdlog::logger& logger(); +ouster::sensor::impl::Logger& logger(); } // namespace sensor } // namespace ouster diff --git a/ouster_client/include/ouster/impl/netcompat.h b/ouster_client/include/ouster/impl/netcompat.h index c5d90a06..d78952b7 100644 --- a/ouster_client/include/ouster/impl/netcompat.h +++ b/ouster_client/include/ouster/impl/netcompat.h @@ -21,6 +21,13 @@ #include #define ssize_t SSIZE_T #endif +/** + * Windows for some reason globally defines BAUD_9600 and BAUD_115200 + * which causes issues when you try and use those inside of something + * like an enum. Undefine BAUD_9600 and BAUD_115200 coming from windows. + */ +#undef BAUD_9600 +#undef BAUD_115200 #else // --------- Compiling on *nix --------- @@ -41,15 +48,6 @@ #endif #define SOCKET_ERROR -1 - -/** - * Windows for some reason globally defines BAUD_9600 and BAUD_115200 - * which causes issues when you try and use those inside of something - * like an enum. Undefine BAUD_9600 and BAUD_115200 coming from windows. - */ -#undef BAUD_9600 -#undef BAUD_115200 - #endif // --------- End Platform Differentiation Block --------- namespace ouster { diff --git a/ouster_client/include/ouster/impl/packet_writer.h b/ouster_client/include/ouster/impl/packet_writer.h index e64d7b80..2f82bfbc 100644 --- a/ouster_client/include/ouster/impl/packet_writer.h +++ b/ouster_client/include/ouster/impl/packet_writer.h @@ -6,6 +6,7 @@ #pragma once #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor { @@ -14,28 +15,44 @@ namespace impl { /** * Writing counterpart to packet_format, used for packet generation */ -class packet_writer : public packet_format { +class OUSTER_API_CLASS packet_writer : public packet_format { public: using packet_format::packet_format; // construct from packet format - packet_writer(const packet_format& pf) : packet_format(pf) {} + OUSTER_API_FUNCTION + packet_writer(const packet_format& pf); + OUSTER_API_FUNCTION uint8_t* nth_col(int n, uint8_t* lidar_buf) const; + OUSTER_API_FUNCTION uint8_t* nth_px(int n, uint8_t* col_buf) const; + OUSTER_API_FUNCTION uint8_t* footer(uint8_t* lidar_buf) const; + OUSTER_API_FUNCTION void set_alert_flags(uint8_t* lidar_buf, uint8_t alert_flags) const; + OUSTER_API_FUNCTION void set_col_status(uint8_t* col_buf, uint32_t status) const; + OUSTER_API_FUNCTION void set_col_timestamp(uint8_t* col_buf, uint64_t ts) const; + OUSTER_API_FUNCTION void set_col_measurement_id(uint8_t* col_buf, uint16_t m_id) const; + OUSTER_API_FUNCTION void set_frame_id(uint8_t* lidar_buf, uint32_t frame_id) const; + OUSTER_API_FUNCTION void set_init_id(uint8_t* lidar_buf, uint32_t init_id) const; - void set_packet_type(uint8_t* lidar_buf, uint16_t packet_type) const; + OUSTER_API_FUNCTION + void set_packet_type(uint8_t* packet_buf, uint16_t packet_type) const; + OUSTER_API_FUNCTION void set_prod_sn(uint8_t* lidar_buf, uint64_t sn) const; + OUSTER_API_FUNCTION void set_shot_limiting(uint8_t* lidar_buf, uint8_t status) const; + OUSTER_API_FUNCTION void set_shot_limiting_countdown(uint8_t* lidar_buf, uint8_t shot_limiting_countdown) const; + OUSTER_API_FUNCTION void set_shutdown(uint8_t* lidar_buf, uint8_t status) const; + OUSTER_API_FUNCTION void set_shutdown_countdown(uint8_t* lidar_buf, uint8_t shutdown_countdown) const; diff --git a/ouster_client/include/ouster/impl/profile_extension.h b/ouster_client/include/ouster/impl/profile_extension.h index a405788a..d3467198 100644 --- a/ouster_client/include/ouster/impl/profile_extension.h +++ b/ouster_client/include/ouster/impl/profile_extension.h @@ -10,12 +10,13 @@ #include #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor { namespace impl { -struct FieldInfo { +struct OUSTER_API_CLASS FieldInfo { ChanFieldType ty_tag; size_t offset; uint64_t mask; @@ -24,6 +25,7 @@ struct FieldInfo { } // namespace impl +OUSTER_API_FUNCTION void add_custom_profile( int profile_nr, const std::string& name, const std::vector>& fields, diff --git a/ouster_client/include/ouster/impl/ring_buffer.h b/ouster_client/include/ouster/impl/ring_buffer.h index 7a35b4e3..30e9ccfb 100644 --- a/ouster_client/include/ouster/impl/ring_buffer.h +++ b/ouster_client/include/ouster/impl/ring_buffer.h @@ -8,9 +8,13 @@ #include #include #include +#include +#include #include #include +#include "ouster/visibility.h" + namespace ouster { namespace sensor { namespace impl { @@ -47,6 +51,7 @@ class RingBuffer { std::atomic r_idx_, w_idx_; std::vector bufs_; + OUSTER_API_IGNORE size_t _capacity() const { return bufs_.size(); } public: diff --git a/ouster_client/include/ouster/impl/threadsafe_queue.h b/ouster_client/include/ouster/impl/threadsafe_queue.h new file mode 100644 index 00000000..204d5071 --- /dev/null +++ b/ouster_client/include/ouster/impl/threadsafe_queue.h @@ -0,0 +1,104 @@ +/** + * Copyright(c) 2024, Ouster, Inc. + * All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include + +#include "nonstd/optional.hpp" + +/** + * @brief a threadsafe queue. + * + * This is a blocking threadsafe queue class: pushing blocks forever until there + * is room in the queue for a new item; popping blocks until an item is + * available or the queue is "shut down" by another thread, causing all threads + * waiting for a pop() to receive a nonstd::nullopt. + * + * This queue also requires that items be movable. + */ +template +class ThreadsafeQueue { + std::queue queue_{}; + std::mutex mutex_{}; + std::condition_variable empty_condition_{}; + std::condition_variable full_condition_{}; + bool shutdown_{false}; + size_t capacity_{}; + + public: + /** + * Instantiates a ThreadsafeQueue with the given capacity. + */ + ThreadsafeQueue(size_t capacity) : capacity_(capacity) {} + + /** + * Adds an item to the queue, blocking until there is room for the item. + * + * @throws std::logic_error if the queue has been shut down. + */ + void push(T&& t) { + std::unique_lock lock(mutex_); + + if (shutdown_) { + throw std::logic_error("queue is shutdown"); + } + + // Block until there is room for the item. + while (queue_.size() >= capacity_) { + full_condition_.wait(lock); + } + queue_.push(std::move(t)); + + // Wake up a thread waiting to pop. + empty_condition_.notify_one(); + } + + /** + * Returns the oldest item in the queue, + * or std::nullopt if there are no more items and the queue is shut down. + * + * This method blocks the calling thread indefinitely, + * until either an item is available or shutdown() is called from another + * thread, after which std::nullopt is returned. + * + * @throws std::logic_error if it would return a nonstd::nullopt + * but the queue is not shut down (a contradiction.) + */ + nonstd::optional pop() { + std::unique_lock lock(mutex_); + + // Wait for the queue to have an item in it, or to be shutdown. + while (queue_.empty() && !shutdown_) { + empty_condition_.wait(lock); + } + + if (queue_.empty()) { + if (!shutdown_) { + throw std::logic_error("queue is empty but not shut down"); + } + return nonstd::nullopt; + } + + auto front = std::move(queue_.front()); + queue_.pop(); + + // Wake up a thread waiting to push. + full_condition_.notify_one(); + return front; + } + + /** + * Shuts down the queue, waking up any threads that have called "pop". + */ + void shutdown() { + std::unique_lock lock(mutex_); + shutdown_ = true; + empty_condition_.notify_all(); + } +}; diff --git a/ouster_client/include/ouster/lidar_scan.h b/ouster_client/include/ouster/lidar_scan.h index a18c6818..b6f92250 100644 --- a/ouster_client/include/ouster/lidar_scan.h +++ b/ouster_client/include/ouster/lidar_scan.h @@ -11,19 +11,20 @@ #include #include #include -#include #include #include "ouster/defaults.h" #include "ouster/field.h" +#include "ouster/packet.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { /* * Description for a field that we desire in a lidar scan */ -struct FieldType { +struct OUSTER_API_CLASS FieldType { std::string name; ///< Name of the field sensor::ChanFieldType element_type; ///< Type of field elements std::vector extra_dims; ///< Additional dimensions of the field @@ -34,6 +35,7 @@ struct FieldType { /** * Initialize a default FieldType with no name. */ + OUSTER_API_FUNCTION FieldType(); /** @@ -46,6 +48,7 @@ struct FieldType { * @param[in] class_ Category of field, determins the first dimensions of the field */ + OUSTER_API_FUNCTION FieldType(const std::string& name_, sensor::ChanFieldType element_type_, const std::vector extra_dims_ = {}, FieldClass class_ = FieldClass::PIXEL_FIELD); @@ -57,6 +60,7 @@ struct FieldType { * * @return if the name is "less than" the provided FieldType */ + OUSTER_API_FUNCTION inline bool operator<(const FieldType& other) const { return name < other.name; } @@ -69,6 +73,7 @@ struct FieldType { * * @return string representation of the FieldType. */ +OUSTER_API_FUNCTION std::string to_string(const FieldType& field_type); /** @@ -79,6 +84,7 @@ std::string to_string(const FieldType& field_type); * * @return if a == b. */ +OUSTER_API_FUNCTION bool operator==(const FieldType& a, const FieldType& b); /** @@ -89,6 +95,7 @@ bool operator==(const FieldType& a, const FieldType& b); * * @return if a != b. */ +OUSTER_API_FUNCTION bool operator!=(const FieldType& a, const FieldType& b); /** @@ -107,7 +114,7 @@ using LidarScanFieldTypes = std::vector; * to a single measurement in time. Use the destagger() function to create an * image where columns correspond to a single azimuth angle. */ -class LidarScan { +class OUSTER_API_CLASS LidarScan { public: template using Header = Eigen::Array; ///< Header typedef @@ -190,7 +197,13 @@ class LidarScan { */ int64_t frame_id{-1}; + /** + * The associated sensor info for this lidar scan + */ + std::shared_ptr sensor_info; + /** The default constructor creates an invalid 0 x 0 scan. */ + OUSTER_API_FUNCTION LidarScan(); /** @@ -203,6 +216,7 @@ class LidarScan { * Note, the number of columns per packet is set to the default * (DEFAULT_COLUMNS_PER_PACKET). */ + OUSTER_API_FUNCTION LidarScan(size_t w, size_t h); /** @@ -211,8 +225,18 @@ class LidarScan { * * @param[in] sensor_info description of sensor to create scan for */ + OUSTER_API_FUNCTION LidarScan(const sensor::sensor_info& sensor_info); + /** + * Initialize a scan with fields configured as default for the provided + * SensorInfo's configuration + * + * @param[in] sensor_info a shared_ptr to the sensor_info object + */ + OUSTER_API_FUNCTION + LidarScan(std::shared_ptr sensor_info); + /** * Initialize a scan with the default fields for a particular udp profile. * @@ -223,6 +247,7 @@ class LidarScan { * @param[in] columns_per_packet The number of columns per packet, * this argument is optional. */ + OUSTER_API_FUNCTION LidarScan(size_t w, size_t h, sensor::UDPProfileLidar profile, size_t columns_per_packet = DEFAULT_COLUMNS_PER_PACKET); @@ -249,6 +274,7 @@ class LidarScan { * * @param[in] other The other lidar scan to initialize from. */ + OUSTER_API_FUNCTION LidarScan(const LidarScan& other); /** @@ -260,9 +286,11 @@ class LidarScan { * @param[in] other The other lidar scan to initialize from. * @param[in] fields Fields to have in new lidar scan. */ + OUSTER_API_FUNCTION LidarScan(const LidarScan& other, const LidarScanFieldTypes& fields); /** @copydoc LidarScan(const LidarScan& other) */ + OUSTER_API_FUNCTION LidarScan(LidarScan&& other); /** @@ -270,6 +298,7 @@ class LidarScan { * * @param[in] other The lidar scan to copy from. */ + OUSTER_API_FUNCTION LidarScan& operator=(const LidarScan& other); /** @@ -277,11 +306,13 @@ class LidarScan { * * @param[in] other The lidar scan to copy from. */ + OUSTER_API_FUNCTION LidarScan& operator=(LidarScan&& other); /** * Lidar scan destructor. */ + OUSTER_API_FUNCTION ~LidarScan(); /** @@ -289,6 +320,7 @@ class LidarScan { * * @return true if sensor is shot limiting */ + OUSTER_API_FUNCTION sensor::ShotLimitingStatus shot_limiting() const; /** @@ -296,6 +328,7 @@ class LidarScan { * * @return true if sensor is in thermal shutdown state. */ + OUSTER_API_FUNCTION sensor::ThermalShutdownStatus thermal_shutdown() const; /** @@ -337,11 +370,13 @@ class LidarScan { /** * @copydoc ClientLidarScanFieldString */ + OUSTER_API_FUNCTION Field& field(const std::string& name); /** * @copydoc ClientLidarScanFieldString */ + OUSTER_API_FUNCTION const Field& field(const std::string& name) const; /** @@ -351,6 +386,7 @@ class LidarScan { * * @return true if the lidar scan has the field, else false */ + OUSTER_API_FUNCTION bool has_field(const std::string& name) const; /** @@ -366,6 +402,7 @@ class LidarScan { * * @return field */ + OUSTER_API_FUNCTION Field& add_field(const std::string& name, FieldDescriptor d, FieldClass field_class = FieldClass::PIXEL_FIELD); @@ -378,6 +415,7 @@ class LidarScan { * * @return field The value of the field added. */ + OUSTER_API_FUNCTION Field& add_field(const FieldType& type); /** @@ -389,6 +427,7 @@ class LidarScan { * * @return field The deleted field. */ + OUSTER_API_FUNCTION Field del_field(const std::string& name); /** @@ -398,6 +437,7 @@ class LidarScan { * * @return the type associated with the field. */ + OUSTER_API_FUNCTION FieldType field_type(const std::string& name) const; /** @@ -405,6 +445,7 @@ class LidarScan { * * @return The type associated with every field in the scan. */ + OUSTER_API_FUNCTION LidarScanFieldTypes field_types() const; /** @@ -412,9 +453,11 @@ class LidarScan { * * @return The unordered map of field type and field. */ + OUSTER_API_FUNCTION std::unordered_map& fields(); /** @copydoc fields() */ + OUSTER_API_FUNCTION const std::unordered_map& fields() const; /** @@ -422,11 +465,13 @@ class LidarScan { * * @return a view of timestamp as a w-element vector. */ + OUSTER_API_FUNCTION Eigen::Ref> timestamp(); /** * @copydoc timestamp() */ + OUSTER_API_FUNCTION Eigen::Ref> timestamp() const; /** @@ -435,6 +480,7 @@ class LidarScan { * @return a view of timestamp as a vector with w / columns-per-packet * elements. */ + OUSTER_API_FUNCTION Eigen::Ref> packet_timestamp(); /** @@ -443,6 +489,7 @@ class LidarScan { * @return a view of timestamp as a vector with w / columns-per-packet * elements. */ + OUSTER_API_FUNCTION Eigen::Ref> packet_timestamp() const; /** @@ -451,6 +498,7 @@ class LidarScan { * @return a view of timestamp as a vector with w / columns-per-packet * elements. */ + OUSTER_API_FUNCTION Eigen::Ref> alert_flags(); /** @@ -459,6 +507,7 @@ class LidarScan { * @return a view of timestamp as a vector with w / columns-per-packet * elements. */ + OUSTER_API_FUNCTION Eigen::Ref> alert_flags() const; /** @@ -466,6 +515,7 @@ class LidarScan { * * @return the first valid packet timestamp, 0 if none available */ + OUSTER_API_FUNCTION uint64_t get_first_valid_packet_timestamp() const; /** @@ -473,6 +523,7 @@ class LidarScan { * * @return the first valid column timestamp, 0 if none available */ + OUSTER_API_FUNCTION uint64_t get_first_valid_column_timestamp() const; /** @@ -480,9 +531,11 @@ class LidarScan { * * @return a view of measurement ids as a w-element vector. */ + OUSTER_API_FUNCTION Eigen::Ref> measurement_id(); /** @copydoc measurement_id() */ + OUSTER_API_FUNCTION Eigen::Ref> measurement_id() const; /** @@ -490,9 +543,11 @@ class LidarScan { * * @return a view of measurement statuses as a w-element vector. */ + OUSTER_API_FUNCTION Eigen::Ref> status(); /** @copydoc status() */ + OUSTER_API_FUNCTION Eigen::Ref> status() const; /** @@ -500,9 +555,11 @@ class LidarScan { * ArrayView3 in order to access as 3d * @return 3d field of homogenous pose matrices, shaped (w, 4, 4). */ + OUSTER_API_FUNCTION Field& pose(); /** @copydoc pose() */ + OUSTER_API_FUNCTION const Field& pose() const; /** @@ -510,6 +567,7 @@ class LidarScan { * @param[in] window The column window to use for validity assessment * @return whether all columns within given column window were valid */ + OUSTER_API_FUNCTION bool complete(sensor::ColumnWindow window) const; /** @@ -517,11 +575,42 @@ class LidarScan { * * @return the number of packets */ + OUSTER_API_FUNCTION size_t packet_count() const; - friend bool operator==(const LidarScan& a, const LidarScan& b); + /** + * Equality for scans. + * + * @param[in] other The other scan to compare to. + * + * @return if a == b. + */ + OUSTER_API_FUNCTION + bool equals(const LidarScan& other) const; }; +/** + * Equality for LidarScan. + * + * @param[in] a The first scan to compare. + * @param[in] b The second scan to compare. + * + * @return if a == b. + */ +OUSTER_API_FUNCTION +bool operator==(const LidarScan& a, const LidarScan& b); + +/** + * Inequality for LidarScan. + * + * @param[in] a The first scan to compare. + * @param[in] b The second scan to compare. + * + * @return if a != b. + */ +OUSTER_API_FUNCTION +bool operator!=(const LidarScan& a, const LidarScan& b); + /** * Get string representation of lidar scan field types. * @@ -529,6 +618,7 @@ class LidarScan { * * @return string representation of the lidar scan field types. */ +OUSTER_API_FUNCTION std::string to_string(const LidarScanFieldTypes& field_types); /** @@ -538,6 +628,7 @@ std::string to_string(const LidarScanFieldTypes& field_types); * * @return The lidar scan field types */ +OUSTER_API_FUNCTION LidarScanFieldTypes get_field_types(sensor::UDPProfileLidar udp_profile_lidar); /** @@ -547,6 +638,7 @@ LidarScanFieldTypes get_field_types(sensor::UDPProfileLidar udp_profile_lidar); * * @return The lidar scan field types */ +OUSTER_API_FUNCTION LidarScanFieldTypes get_field_types(const sensor::sensor_info& info); /** @@ -556,6 +648,7 @@ LidarScanFieldTypes get_field_types(const sensor::sensor_info& info); * * @return string representation of the lidar scan. */ +OUSTER_API_FUNCTION std::string to_string(const LidarScan& ls); /** \defgroup ouster_client_lidar_scan_operators Ouster Client lidar_scan.h @@ -563,31 +656,10 @@ std::string to_string(const LidarScan& ls); * @{ */ -/** - * Equality for scans. - * - * @param[in] a The first scan to compare. - * @param[in] b The second scan to compare. - * - * @return if a == b. - */ -bool operator==(const LidarScan& a, const LidarScan& b); - -/** - * NOT Equality for scans. - * - * @param[in] a The first scan to compare. - * @param[in] b The second scan to compare. - * - * @return if a != b. - */ -inline bool operator!=(const LidarScan& a, const LidarScan& b) { - return !(a == b); -} /** @}*/ /** Lookup table of beam directions and offsets. */ -struct XYZLut { +struct OUSTER_API_CLASS XYZLut { LidarScan::Points direction; ///< Lookup table of beam directions LidarScan::Points offset; ///< Lookup table of beam offsets }; @@ -619,6 +691,7 @@ struct XYZLut { * * @return xyz direction and offset vectors for each point in the lidar scan. */ +OUSTER_API_FUNCTION XYZLut make_xyz_lut(size_t w, size_t h, double range_unit, const mat4d& beam_to_lidar_transform, const mat4d& transform, @@ -638,6 +711,7 @@ XYZLut make_xyz_lut(size_t w, size_t h, double range_unit, * * @return xyz direction and offset vectors for each point in the lidar scan. */ +OUSTER_API_FUNCTION XYZLut make_xyz_lut(const sensor::sensor_info& sensor, bool use_extrinsics); /** \defgroup ouster_client_lidar_scan_cartesian Ouster Client lidar_scan.h @@ -653,6 +727,7 @@ XYZLut make_xyz_lut(const sensor::sensor_info& sensor, bool use_extrinsics); * @return Cartesian points where ith row is a 3D point which corresponds * to ith pixel in LidarScan where i = row * w + col. */ +OUSTER_API_FUNCTION LidarScan::Points cartesian(const LidarScan& scan, const XYZLut& lut); /** @@ -665,6 +740,7 @@ LidarScan::Points cartesian(const LidarScan& scan, const XYZLut& lut); * @return Cartesian points where ith row is a 3D point which corresponds * to ith pixel in LidarScan where i = row * w + col. */ +OUSTER_API_FUNCTION LidarScan::Points cartesian(const Eigen::Ref>& range, const XYZLut& lut); /** @}*/ @@ -718,22 +794,27 @@ inline img_t stagger(const Eigen::Ref>& img, * Make a function that batches a single scan (revolution) of data to a * LidarScan. */ -class ScanBatcher { +class OUSTER_API_CLASS ScanBatcher { size_t w; size_t h; uint16_t next_valid_m_id; uint16_t next_headers_m_id; uint16_t next_valid_packet_id; - std::vector cache; - uint64_t cache_packet_ts; + sensor::LidarPacket cache; bool cached_packet = false; int64_t finished_scan_id = -1; size_t expected_packets; size_t batched_packets = 0; + std::shared_ptr sensor_info; + + void parse_by_col(const uint8_t* packet_buf, LidarScan& ls); + void parse_by_block(const uint8_t* packet_buf, LidarScan& ls); + + void cache_packet(const sensor::LidarPacket& packet); + void batch_cached_packet(LidarScan& ls); - void _parse_by_col(const uint8_t* packet_buf, LidarScan& ls); - void _parse_by_block(const uint8_t* packet_buf, LidarScan& ls); - void finalize_scan(LidarScan& ls, bool raw_headers); + bool check_scan_complete(const LidarScan& ls) const; + void finalize_scan(LidarScan& ls); public: sensor::packet_format pf; ///< The packet format object used for decoding @@ -747,6 +828,7 @@ class ScanBatcher { * 2048. * @param[in] pf expected format of the incoming packets used for parsing. */ + OUSTER_API_FUNCTION ScanBatcher(size_t w, const sensor::packet_format& pf); // clang-format on @@ -755,8 +837,11 @@ class ScanBatcher { * * @param[in] info sensor metadata returned from the client. */ + OUSTER_API_FUNCTION ScanBatcher(const sensor::sensor_info& info); + // clang-format off + /** * Add a packet to the scan. * @deprecated this method is deprecated in favor of one that accepts a @@ -767,32 +852,40 @@ class ScanBatcher { * * @return true when the provided lidar scan is ready to use. */ + // clang-format off [[deprecated( - "Use ScanBatcher::operator(LidarPacket&, LidarScan&) instead.")]] bool - operator()(const uint8_t* packet_buf, LidarScan& ls); + "Use ScanBatcher::operator(const LidarPacket&, LidarScan&) instead.")]] + OUSTER_API_FUNCTION + bool operator()(const uint8_t* packet_buf, LidarScan& ls); /** * Add a packet to the scan. * - * @param[in] packet a LidarPacket. + * @param[in] packet_buf a buffer containing raw bytes from a lidar packet. + * @param[in] packet_ts timestamp of the packet (usually HOST time on + * receive). * @param[in] ls lidar scan to populate. * * @return true when the provided lidar scan is ready to use. */ - bool operator()(const ouster::sensor::LidarPacket& packet, LidarScan& ls); + [[deprecated( + "Use ScanBatcher::operator(const LidarPacket&, LidarScan&) instead.")]] + OUSTER_API_FUNCTION + bool operator()(const uint8_t* packet_buf, uint64_t packet_ts, + LidarScan& ls); + + // clang-format on /** * Add a packet to the scan. * - * @param[in] packet_buf a buffer containing raw bytes from a lidar packet. - * @param[in] packet_ts timestamp of the packet (usually HOST time on - * receive). + * @param[in] packet a LidarPacket. * @param[in] ls lidar scan to populate. * * @return true when the provided lidar scan is ready to use. */ - bool operator()(const uint8_t* packet_buf, uint64_t packet_ts, - LidarScan& ls); + OUSTER_API_FUNCTION + bool operator()(const ouster::sensor::LidarPacket& packet, LidarScan& ls); }; namespace pose_util { @@ -811,7 +904,7 @@ typedef Eigen::Matrix Pose; * @param[in] poses A Eigen matrix of shape (W, 16) representing W 4x4 * transformation matrices. Each row is a flattened 4x4 pose matrix */ - +OUSTER_API_FUNCTION void dewarp(Eigen::Ref dewarped, const Eigen::Ref points, const Eigen::Ref poses); @@ -827,7 +920,7 @@ void dewarp(Eigen::Ref dewarped, const Eigen::Ref points, * where the same number of points are transformed by each corresponding pose * matrix. */ - +OUSTER_API_FUNCTION Points dewarp(const Eigen::Ref points, const Eigen::Ref poses); @@ -844,7 +937,7 @@ Points dewarp(const Eigen::Ref points, * @param[in] pose A vector of 16 elements representing a flattened 4x4 * transformation matrix. */ - +OUSTER_API_FUNCTION void transform(Eigen::Ref transformed, const Eigen::Ref points, const Eigen::Ref pose); @@ -862,7 +955,7 @@ void transform(Eigen::Ref transformed, * @return A matrix of shape (N, 3) containing the transformed 3D points, * where each point is rotated and translated by the given pose. */ - +OUSTER_API_FUNCTION Points transform(const Eigen::Ref points, const Eigen::Ref pose); diff --git a/ouster_client/include/ouster/metadata.h b/ouster_client/include/ouster/metadata.h index 976e14a2..82c1360a 100644 --- a/ouster_client/include/ouster/metadata.h +++ b/ouster_client/include/ouster/metadata.h @@ -14,18 +14,19 @@ #include "nonstd/optional.hpp" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { /** * Class for representing metadata issues. */ -struct ValidatorIssues { +struct OUSTER_API_CLASS ValidatorIssues { public: /** * Subclass for recording validator issues */ - class ValidatorEntry { + class OUSTER_API_CLASS ValidatorEntry { public: /** * Construct a validator issue entry. @@ -33,13 +34,24 @@ struct ValidatorIssues { * @param[in] path The json path associated with the issue. * @param[in] msg The specific issue. */ + OUSTER_API_FUNCTION ValidatorEntry(const std::string& path, const std::string& msg); + /** + * Construct a validator issue entry from another validator + * issue entry. + * + * @param[in] other The other validator entry to copy from. + */ + OUSTER_API_FUNCTION + ValidatorEntry(const ValidatorEntry& other); + /** * Return the string representation of the validation issue. * * @return the string representation of the validation issue. */ + OUSTER_API_FUNCTION std::string to_string() const; /** @@ -47,6 +59,7 @@ struct ValidatorIssues { * * @return the json path associated with the issue. */ + OUSTER_API_FUNCTION const std::string& get_path() const; /** @@ -54,6 +67,7 @@ struct ValidatorIssues { * * @return the specific issue. */ + OUSTER_API_FUNCTION const std::string& get_msg() const; protected: @@ -65,18 +79,41 @@ struct ValidatorIssues { * Convenience alias for the issue list */ using EntryList = std::vector; + + /** + * Return the string representation of all of the validation issues. + * + * @return the string representation of all of the validation issues. + */ + OUSTER_API_FUNCTION + std::string to_string() const; + EntryList information; ///< Validation issues at the information level EntryList warning; ///< Validation issues at the warning level EntryList critical; ///< Validation issues at the critical level }; +/** + * Return the string representation of all of the validation issues in an + * EntryList. + * + * @param[in] list The EntryList to get the string representation for. + * + * @return the string representation of all of the validation issues in an + * EntryList. + */ +OUSTER_API_FUNCTION +std::string to_string(const ValidatorIssues::EntryList& list); + /** * Parse and validate a metadata stream. * * @param[in] json_data The metadata data. - * @param[in] issues The issues that occured during parsing. + * @param[out] issues The issues that occured during parsing. + * * @return If parsing was successful(no critical issues) */ +OUSTER_API_FUNCTION bool parse_and_validate_metadata(const std::string& json_data, ValidatorIssues& issues); @@ -86,11 +123,48 @@ bool parse_and_validate_metadata(const std::string& json_data, * @param[in] json_data The metadata data. * @param[in] sensor_info The optional sensor_info to populate. * @param[in] issues The issues that occurred during parsing. + * * @return If parsing was successful(no critical issues) */ +OUSTER_API_FUNCTION bool parse_and_validate_metadata( const std::string& json_data, nonstd::optional& sensor_info, ValidatorIssues& issues); +/** + * Parse config text blob from the sensor into a sensor_config struct. + * + * All fields are optional, and will only be set if found. + * + * @throw runtime_error if the text is not valid json. + * + * @param[in] config a text blob given by get_config from client.h. + * @param[out] sensor_config The optional sensor config object, if parsing + * issues occur, will be empty. + * + * @return If parsing was successful(no critical issues) + */ +OUSTER_API_FUNCTION +bool parse_and_validate_config(const std::string& config, + ouster::sensor::sensor_config& sensor_config); + +/** + * Parse config text blob from the sensor into a sensor_config struct. + * + * All fields are optional, and will only be set if found. + * + * @throw runtime_error if the text is not valid json. + * + * @param[in] config a text blob given by get_config from client.h. + * @param[out] sensor_config The optional sensor config object, if parsing + * issues occur, will be empty. + * @param[out] issues The specific issues parsing the sensor config + * + * @return If parsing was successful(no critical issues) + */ +OUSTER_API_FUNCTION +bool parse_and_validate_config(const std::string& config, + ouster::sensor::sensor_config& sensor_config, + ValidatorIssues& issues); }; // namespace ouster diff --git a/ouster_client/include/ouster/packet.h b/ouster_client/include/ouster/packet.h new file mode 100644 index 00000000..8ee761da --- /dev/null +++ b/ouster_client/include/ouster/packet.h @@ -0,0 +1,354 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + * + * @file + * @brief Ouster client packet datatypes + */ + +#pragma once + +#include +#include + +#include "types.h" + +namespace ouster { +namespace sensor { + +/// Types of Ouster packets that can come from a sensor +enum class PacketType { + Unknown, ///< Packet type could not or has not been determined + Lidar, ///< Pointcloud data packet + Imu ///< IMU data packet +}; + +/// Encapsulate a packet buffer and attributes associated with it. +struct OUSTER_API_CLASS Packet { + private: + /// Type of the packet + PacketType type_; + + public: + /// Timestamp in nanoseconds of packet capture + uint64_t host_timestamp; + + /// Packet data + std::vector buf; + + /// packet_format associated with this packet + std::shared_ptr format; + + /// Returns the type of the packet + /// @return the packet type + OUSTER_API_FUNCTION PacketType type() const { return type_; } + + protected: + /// Construct an empty packet with a given type + Packet(PacketType type ///< [in] type packet type (imu or lidar) + ); + + /// Construct a packet with given type and a pre-allocated size + Packet(PacketType type, ///< [in] type packet type (imu or lidar) + int size ///< [in] size in bytes to allocate + ); + + public: + /// Attempt to cast the packet to the desired concrete PacketType. + /// @throw runtime_error if packet is not that of that type + /// @tparam Type Type of packet to cast to. Either LidarPacker or ImuPacket. + /// @return reference to the packet as the desired type + template + Type& as() { + if (type() != Type::MyType) { + throw std::runtime_error("Tried to cast packet to incorrect type."); + } + return static_cast(*this); + } +}; + +/// Reasons for failure of packet validation. +enum class PacketValidationFailure { + NONE = 0, ///< No validation errors were found + PACKET_SIZE = 1, ///< The packet size does not match the expected size + ID = 2 ///< The prod_sn or init_id does not match the metadata +}; + +/// Validate a packet buffer against a given type. +/// +/// @param[in] info The sensor info to try to check the buffer against. +/// @param[in] format The packet format to try to check the buffer against. +/// @param[in] buf The packet buffer to validate. +/// @param[in] buf_size The size of the packet buffer. +/// @param[in] type Optional type of packet to try and validate as. Unknown will +/// try and guess the packet type +/// @return Result of the validation +PacketValidationFailure validate_packet( + const sensor_info& info, const ouster::sensor::packet_format& format, + const uint8_t* buf, uint64_t buf_size, + PacketType type = PacketType::Unknown); + +/// Encapsulate a lidar packet buffer and attributes associated with it. +struct OUSTER_API_CLASS LidarPacket : public Packet { + using Packet::Packet; + + /// PacketType enum for this packet type + const static PacketType MyType = PacketType::Lidar; + + /// Construct a new empty Lidar Packet + OUSTER_API_FUNCTION LidarPacket(); + + /// Construct a new Lidar packet with a pre-allocated size + OUSTER_API_FUNCTION LidarPacket( + int size ///< [in] size in bytes to allocate + ); + + /// Validates that the packet matches the expected format and metadata. + /// @return a PacketValdationFailure with either NONE or a failure reason. + OUSTER_API_FUNCTION PacketValidationFailure validate( + const sensor_info& info, ///< [in] expected sensor_metadata + const ouster::sensor::packet_format& + format ///< [in] expected packet_format + ) const; + + /// Validates that the packet matches the expected format and metadata. + /// @return a PacketValdationFailure with either NONE or a failure reason. + OUSTER_API_FUNCTION PacketValidationFailure validate( + const sensor_info& info ///< [in] expected sensor_metadata + ) const; + + /// Get pointer to the nth column in the packet. + /// @return pointer to the nth column in this packet + OUSTER_API_FUNCTION inline auto nth_col(int n ///< [in] which column + ) const { + return format->nth_col(n, buf.data()); + } + + /// Get pointer to the nth pixel of a column buffer + /// @return pointer to the nth pixel of a column buffer + OUSTER_API_FUNCTION inline auto nth_px( + int n, ///< [in] which pixel + const uint8_t* col_buf ///< [in] pointer to column data + ) const { + return format->nth_px(n, col_buf); + } + + /// Read column timestamp from a column buffer + /// @return column timestamp + OUSTER_API_FUNCTION inline auto col_timestamp( + const uint8_t* col_buf ///< [in] pointer to column data + ) const { + return format->col_timestamp(col_buf); + } + + /// Read column measurement id from a column buffer + /// @return column measurement id + OUSTER_API_FUNCTION inline auto col_measurement_id( + const uint8_t* col_buf ///< [in] pointer to column data + ) const { + return format->col_measurement_id(col_buf); + } + + /// Read column status from a column buffer + /// @return column status + OUSTER_API_FUNCTION inline auto col_status( + const uint8_t* col_buf ///< [in] pointer to column data + ) const { + return format->col_status(col_buf); + } + + /// Copy the specified channel field out of a packet measurement block. + /// + /// @tparam T T should be a numeric type large enough to store + /// values of the specified field. Otherwise, data will be truncated. + /// + /// @param[in] col_buf a measurement block pointer returned by `nth_col()`. + /// @param[in] f the channel field to copy. + /// @param[out] dst destination array of size pixels_per_column * + /// dst_stride. + /// @param[in] dst_stride stride for writing to the destination array. + template + void col_field(const uint8_t* col_buf, const std::string& f, T* dst, + int dst_stride = 1) const { + format->col_field(col_buf, f, dst, dst_stride); + } + + /// Read the packet type from the packet header. + /// @return packet type + OUSTER_API_FUNCTION inline auto packet_type() const { + return format->packet_type(buf.data()); + } + + /// Read the frame id from the packet header. + /// @return frame id + OUSTER_API_FUNCTION inline auto frame_id() const { + return format->frame_id(buf.data()); + } + + /// Read the init id from the packet header. + /// @return init id + OUSTER_API_FUNCTION inline auto init_id() const { + return format->init_id(buf.data()); + } + + /// Read the product serial number from the packet header. + /// @return product serial number + OUSTER_API_FUNCTION inline auto prod_sn() const { + return format->prod_sn(buf.data()); + } + + /// Read the alert flags from the packet header. + /// @return alert flags + OUSTER_API_FUNCTION inline auto alert_flags() const { + return format->alert_flags(buf.data()); + } + + /// Read the thermal shutdown countdown from the packet header. + /// @return thermal shutdown countdown + OUSTER_API_FUNCTION inline auto countdown_thermal_shutdown() const { + return format->countdown_thermal_shutdown(buf.data()); + } + + /// Read the shot limiting countdown from the packet header. + /// @return shot limiting countdown + OUSTER_API_FUNCTION inline auto countdown_shot_limiting() const { + return format->countdown_shot_limiting(buf.data()); + } + + /// Read the thermal shutdown state from the packet header. + /// @return thermal shutdown state + OUSTER_API_FUNCTION inline auto thermal_shutdown() const { + return format->thermal_shutdown(buf.data()); + } + + /// Read the shot limiting state from the packet header. + /// @return shot limiting state + OUSTER_API_FUNCTION inline auto shot_limiting() const { + return format->shot_limiting(buf.data()); + } + + /// Get a pointer to the packet footer. + /// @return pointer to packet footer of lidar buffer, can be nullptr if + /// packet format doesn't have packet footer. + OUSTER_API_FUNCTION inline auto footer() const { + return format->footer(buf.data()); + } + + /// Return the CRC contained in the packet footer if present. + /// @return crc contained in the packet if present + OUSTER_API_FUNCTION inline auto crc() const { + return format->crc(buf.data()); + } + + /// Calculate the CRC for the given packet data. + /// @return calculated crc of the packet + OUSTER_API_FUNCTION inline auto calculate_crc() const { + return format->calculate_crc(buf.data()); + } + + /// Returns maximum available size of parsing block usable with block_field + /// @return if packet format does not allow for block parsing, returns 0 + OUSTER_API_FUNCTION inline auto block_parsable() const { + return format->block_parsable(); + } + + /// Copy the specified channel field out of a packet measurement block. + /// Faster traversal than col_field, but has to copy the entire packet all + /// at once. + /// + /// @tparam T T should be a numeric type large enough to store + /// values of the specified field. Otherwise, data will be truncated. + template + void block_field( + Eigen::Ref> field, ///< [out] destination eigen array + const std::string& f ///< [in] the channel field to copy + ) const { + format->block_field(field, f, buf.data()); + } +}; + +/// Encapsulate an imu packet buffer and attributes associated with it. +struct OUSTER_API_CLASS ImuPacket : public Packet { + using Packet::Packet; + + /// PacketType enum for this packet type + const static PacketType MyType = PacketType::Imu; + + /// Construct a new empty Imu Packet + OUSTER_API_FUNCTION ImuPacket(); + + /// Construct a new Imu packet with a pre-allocated size + OUSTER_API_FUNCTION ImuPacket(int size ///< [in] size in bytes to allocate + ); + + /// Validates that the packet matches the expected format and metadata. + /// @return a PacketValdationFailure with either NONE or a failure reason. + OUSTER_API_FUNCTION PacketValidationFailure validate( + const sensor_info& info, ///< [in] expected sensor_metadata + const ouster::sensor::packet_format& + format ///< [in] expected packet_format + ) const; + + /// Validates that the packet matches the expected format and metadata. + /// @return a PacketValdationFailure with either NONE or a failure reason. + OUSTER_API_FUNCTION PacketValidationFailure validate( + const sensor_info& info ///< [in] expected sensor_metadata + ) const; + + /// Read system timestamp from the packet. + /// @return system timestamp + OUSTER_API_FUNCTION inline auto sys_ts() const { + return format->imu_sys_ts(buf.data()); + } + + /// Read accelerometer timestamp from the packet. + /// @return accelerometer timestamp + OUSTER_API_FUNCTION inline auto accel_ts() const { + return format->imu_accel_ts(buf.data()); + } + + /// Read gyroscope timestamp from the packet. + /// @return gyroscope timestamp + OUSTER_API_FUNCTION inline auto gyro_ts() const { + return format->imu_gyro_ts(buf.data()); + } + + /// Read acceleration in X direction from the packet. + /// @return acceleration in the X direction + OUSTER_API_FUNCTION inline auto la_x() const { + return format->imu_la_x(buf.data()); + } + + /// Read acceleration in Y direction from the packet. + /// @return acceleration in the Y direction + OUSTER_API_FUNCTION inline auto la_y() const { + return format->imu_la_y(buf.data()); + } + + /// Read acceleration in Z direction from the packet. + /// @return acceleration in the Z direction + OUSTER_API_FUNCTION inline auto la_z() const { + return format->imu_la_z(buf.data()); + } + + /// Read angular velocity on the X axis from the packet. + /// @return angular velocity on the X axis + OUSTER_API_FUNCTION inline auto av_x() const { + return format->imu_av_x(buf.data()); + } + + /// Read angular velocity on the Y axis from the packet. + /// @return angular velocity on the Y axis + OUSTER_API_FUNCTION inline auto av_y() const { + return format->imu_av_y(buf.data()); + } + + /// Read angular velocity on the Z axis from the packet. + /// @return angular velocity on the Z axis + OUSTER_API_FUNCTION inline auto av_z() const { + return format->imu_av_z(buf.data()); + } +}; + +} // namespace sensor +} // namespace ouster diff --git a/ouster_client/include/ouster/sensor_client.h b/ouster_client/include/ouster/sensor_client.h index fb533ddc..5802c4db 100644 --- a/ouster_client/include/ouster/sensor_client.h +++ b/ouster_client/include/ouster/sensor_client.h @@ -23,14 +23,22 @@ #include "ouster/impl/netcompat.h" #include "ouster/impl/ring_buffer.h" #include "ouster/lidar_scan.h" +#include "ouster/packet.h" #include "ouster/sensor_http.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor { /// Struct that describes a result from retriving a packet from SensorClient -struct ClientEvent { +class SensorClient; +class OUSTER_API_CLASS ClientEvent { + friend class SensorClient; + + public: + OUSTER_API_FUNCTION ClientEvent(); + /// Types of events that can occur enum EventType { Error, ///< An error occurred in the SensorClient, it may no longer @@ -38,39 +46,52 @@ struct ClientEvent { Exit, ///< The client has been closed and will not return any more /// packets. PollTimeout, ///< get_packet has timed out waiting for an event/packet - ImuPacket, ///< An IMU packet from a sensor - LidarPacket ///< A Lidar packet from a sensor + Packet, ///< An packet from a sensor }; int source; ///< negative if not applicable to a source, like PollTimeout ///< or Error EventType type; ///< The type of event that occurred. + + OUSTER_API_FUNCTION inline ouster::sensor::Packet& packet() { + return *packet_; + } + + private: + ouster::sensor::Packet* packet_; + ClientEvent(ouster::sensor::Packet* packet, int src, EventType tpe) + : source{src}, type{tpe}, packet_{packet} {} }; /// Class that indicates a sensor and its desired configuration -class Sensor { +class OUSTER_API_CLASS Sensor { public: /// Construct a sensor descriptor with the given hostname and desired config + OUSTER_API_FUNCTION Sensor(const std::string& hostname, ///< [in] sensor hostname const sensor_config& config ///< [in] desired sensor configuration ); /// Queries the sensor metadata. /// @return the parsed sensor_info object containing the metadata. + OUSTER_API_FUNCTION sensor_info fetch_metadata( int timeout = 10 ///< [in] timeout for the request in seconds ) const; /// Get a SensorHttp client for this sensor. /// @return the SensorHttp client + OUSTER_API_FUNCTION std::shared_ptr http_client() const; /// Get the desired config of this sensor. /// @return the desired config + OUSTER_API_FUNCTION inline const sensor_config& desired_config() const { return config_; } /// Get the hostname of this sensor. /// @return the sensor hostname + OUSTER_API_FUNCTION inline const std::string& hostname() const { return hostname_; } private: @@ -81,10 +102,11 @@ class Sensor { }; /// An interface to configure and retrieve packets from one or multiple lidars -class SensorClient { +class OUSTER_API_CLASS SensorClient { public: /// Build a sensor client to retrieve packets for the provided sensors. /// Configures the sensors if necessary according to their desired configs. + OUSTER_API_FUNCTION SensorClient( const std::vector& sensors, ///< [in] sensors to connect to double config_timeout_sec = 45, ///< [in] timeout for sensor config @@ -96,6 +118,7 @@ class SensorClient { /// Build a sensor client to retrieve packets for the provided sensors. /// If provided, uses the provided metadata for each sensor rather /// configuring and retrieving them from each sensor. + OUSTER_API_FUNCTION SensorClient( const std::vector& sensors, ///< [in] sensors to connect to const std::vector& @@ -108,49 +131,60 @@ class SensorClient { ); /// Destruct the sensor client + OUSTER_API_FUNCTION ~SensorClient(); /// Retrieve a packet from the sensor with a given timeout. - /// timeout_sec of 0 = return instantly, timeout_sec < 0 = wait forever + /// timeout_sec of 0 = return immediately, timeout_sec < 0 = wait forever /// Important: may return a timeout event if the underlying condition var /// experiences a spurious wakeup. /// @return a ClientEvent representing the result of the call + OUSTER_API_FUNCTION ClientEvent get_packet( - LidarPacket& lp, ///< [out] output LidarPacket if received - ImuPacket& ip, ///< [out] output ImuPacket if received double timeout_sec ///< [in] timeout in seconds to wait for a packet ); /// Get the sensor_infos for each connected sensor /// @return the sensor_infos for each connected sensor + OUSTER_API_FUNCTION inline const std::vector& get_sensor_info() { return sensor_info_; } /// Get the number of packets dropped due to buffer overflow. /// @return the number of dropped packets + OUSTER_API_FUNCTION uint64_t dropped_packets(); /// Flush the internal packet buffer (if enabled) + OUSTER_API_FUNCTION void flush(); /// Shut down the client, closing any sockets and threads + OUSTER_API_FUNCTION void close(); /// Get the number of packets in the internal buffer. /// @return the number of packets in the internal buffer + OUSTER_API_FUNCTION size_t buffer_size(); private: - struct BufferEvent { - ClientEvent event; + struct OUSTER_API_IGNORE InternalEvent { + int source; + PacketType packet_type; + ClientEvent::EventType event_type; + }; + + struct OUSTER_API_IGNORE BufferEvent { + InternalEvent event; uint64_t timestamp; std::vector data; }; std::vector sensor_info_; std::vector sockets_; - std::vector formats_; + std::vector> formats_; bool do_buffer_ = false; uint64_t dropped_packets_ = 0; @@ -159,15 +193,19 @@ class SensorClient { std::thread buffer_thread_; std::deque buffer_; - struct Addr { + std::vector staging_buffer; + ImuPacket imu_packet_; + LidarPacket lidar_packet_; + + struct OUSTER_API_IGNORE Addr { uint32_t ipv4; uint8_t ipv6[16]; uint8_t ipv6_4[16]; }; std::vector addresses_; - ClientEvent get_packet_internal(std::vector& data, uint64_t& ts, - double timeout_sec); + InternalEvent get_packet_internal(std::vector& data, uint64_t& ts, + double timeout_sec); /// Start a background thread to do buffering if requested void start_buffer_thread(double buffer_time ///< [in] time in seconds diff --git a/ouster_client/include/ouster/sensor_http.h b/ouster_client/include/ouster/sensor_http.h index d92dd525..4c49596e 100644 --- a/ouster_client/include/ouster/sensor_http.h +++ b/ouster_client/include/ouster/sensor_http.h @@ -9,12 +9,14 @@ #pragma once -#include #include +#include #include #include +#include "ouster/visibility.h" + namespace ouster { namespace sensor { namespace util { @@ -22,7 +24,7 @@ namespace util { /** * Result for get_user_data_and_policy on SensorHttp */ -struct UserDataAndPolicy { +struct OUSTER_API_CLASS UserDataAndPolicy { bool keep_on_config_delete; std::string data; }; @@ -30,7 +32,7 @@ struct UserDataAndPolicy { /** * An interface to communicate with Ouster sensors via http requests */ -class SensorHttp { +class OUSTER_API_CLASS SensorHttp { ouster::util::version version_; std::string hostname_; @@ -44,6 +46,7 @@ class SensorHttp { /** * Deconstruct the sensor http interface. */ + OUSTER_API_FUNCTION virtual ~SensorHttp() = default; /** @@ -51,6 +54,7 @@ class SensorHttp { * * @return returns the sensor FW version */ + OUSTER_API_FUNCTION inline const ouster::util::version& firmware_version() const { return version_; } @@ -60,6 +64,7 @@ class SensorHttp { * * @return returns the sensor FW version */ + OUSTER_API_FUNCTION inline const std::string& hostname() const { return hostname_; } /** @@ -67,9 +72,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return returns a Json object of the sensor metadata. + * @return returns a json string of the sensor metadata. */ - virtual Json::Value metadata( + OUSTER_API_FUNCTION + virtual std::string metadata( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -77,9 +83,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return returns a Json object representing the sensor_info. + * @return returns a json string representing the sensor_info. */ - virtual Json::Value sensor_info( + OUSTER_API_FUNCTION + virtual std::string sensor_info( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -88,8 +95,9 @@ class SensorHttp { * @param[in] active if true retrieve active, otherwise get staged configs. * @param[in] timeout_sec The timeout for the request in seconds. * - * @return a string represnting the active or staged config + * @return a string representing the active or staged config */ + OUSTER_API_FUNCTION virtual std::string get_config_params( bool active, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -102,6 +110,7 @@ class SensorHttp { * @param[in] value the new value to set for the selected configuration. * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void set_config_param( const std::string& key, const std::string& value, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -111,9 +120,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return active configuration parameters set on the sensor + * @return json string of active configuration parameters set on the sensor */ - virtual Json::Value active_config_params( + OUSTER_API_FUNCTION + virtual std::string active_config_params( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -121,9 +131,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return staged configuration parameters set on the sensor + * @return json string of staged configuration parameters set on the sensor */ - virtual Json::Value staged_config_params( + OUSTER_API_FUNCTION + virtual std::string staged_config_params( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -131,6 +142,7 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void set_udp_dest_auto( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -139,9 +151,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return beam_intrinsics retrieved from sensor + * @return json string of beam_intrinsics retrieved from sensor */ - virtual Json::Value beam_intrinsics( + OUSTER_API_FUNCTION + virtual std::string beam_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -149,9 +162,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return imu_intrinsics received from sensor + * @return json string of imu_intrinsics received from sensor */ - virtual Json::Value imu_intrinsics( + OUSTER_API_FUNCTION + virtual std::string imu_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -159,9 +173,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return lidar_intrinsics retrieved from sensor + * @return json string of lidar_intrinsics retrieved from sensor */ - virtual Json::Value lidar_intrinsics( + OUSTER_API_FUNCTION + virtual std::string lidar_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -169,9 +184,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return lidar_data_format received from sensor + * @return json string of lidar_data_format received from sensor */ - virtual Json::Value lidar_data_format( + OUSTER_API_FUNCTION + virtual std::string lidar_data_format( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -179,9 +195,10 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. * - * @return calibration status received from sensor + * @return json string ofcalibration status received from sensor */ - virtual Json::Value calibration_status( + OUSTER_API_FUNCTION + virtual std::string calibration_status( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; /** @@ -189,6 +206,7 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void reinitialize( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -197,6 +215,7 @@ class SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void save_config_params( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -207,6 +226,7 @@ class SensorHttp { * * @return user data retrieved from sensor */ + OUSTER_API_FUNCTION virtual std::string get_user_data( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -217,6 +237,7 @@ class SensorHttp { * * @return user data and policy setting retrieved from the sensor */ + OUSTER_API_FUNCTION virtual UserDataAndPolicy get_user_data_and_policy( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -228,6 +249,7 @@ class SensorHttp { configuration is deleted from the sensor * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void set_user_data( const std::string& data, bool keep_on_config_delete = true, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; @@ -240,17 +262,50 @@ class SensorHttp { * * @return a JSON string containing sensor IP address information. */ + OUSTER_API_FUNCTION virtual std::string network( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; + /** + * Set's the sensor's static IP address. + * + * @param[in] ip_address The static IP to set on the sensor. + * @param[in] timeout_sec The timeout for the request in seconds. + */ + OUSTER_API_FUNCTION + virtual void set_static_ip( + const std::string& ip_address, + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; + + /** + * Deletes any static IP address stored on the sensor. + * + * @param[in] timeout_sec The timeout for the request in seconds. + */ + OUSTER_API_FUNCTION + virtual void delete_static_ip( + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; + /** * Deletes the user data stored on the sensor. * * @param[in] timeout_sec The timeout for the request in seconds. */ + OUSTER_API_FUNCTION virtual void delete_user_data( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; + /** + * Downloads diagnostics data in zip format from the sensor. + * + * @param[in] timeout_sec The timeout for the request in seconds. + * + * @return diagnostics dump file contents + */ + OUSTER_API_FUNCTION + virtual std::vector diagnostics_dump( + int timeout_sec = LONG_HTTP_REQUEST_TIMEOUT_SECONDS) const = 0; + /** * Retrieves sensor firmware version information as a string. * @@ -260,6 +315,7 @@ class SensorHttp { * * @return firmware version string from sensor */ + OUSTER_API_FUNCTION static std::string firmware_version_string( const std::string& hostname, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS); @@ -273,6 +329,7 @@ class SensorHttp { * * @return parsed firmware version from sensor */ + OUSTER_API_FUNCTION static ouster::util::version firmware_version( const std::string& hostname, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS); @@ -286,6 +343,7 @@ class SensorHttp { * * @return a version specific implementation of the SensorHTTP instance */ + OUSTER_API_FUNCTION static std::unique_ptr create( const std::string& hostname, int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS); diff --git a/ouster_client/include/ouster/sensor_scan_source.h b/ouster_client/include/ouster/sensor_scan_source.h index 2743b2aa..7dede75c 100644 --- a/ouster_client/include/ouster/sensor_scan_source.h +++ b/ouster_client/include/ouster/sensor_scan_source.h @@ -16,15 +16,17 @@ #include #include "ouster/sensor_client.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor { /// Provides a simple API for configuring sensors and retreiving LidarScans from /// them -class SensorScanSource { +class OUSTER_API_CLASS SensorScanSource { public: /// Construct a SensorScanSource to connect to the listed sensors + OUSTER_API_FUNCTION SensorScanSource( const std::vector& sensors, ///< [in] sensors to connect to double config_timeout = @@ -38,6 +40,7 @@ class SensorScanSource { /// Construct a SensorScanSource to connect to the listed sensors /// If infos are provided, they are used instead of configuring the sensors /// and retrieving the sensor info from them. + OUSTER_API_FUNCTION SensorScanSource( const std::vector& sensors, ///< [in] sensors to connect to const std::vector& @@ -53,6 +56,7 @@ class SensorScanSource { /// Construct a SensorScanSource to connect to the listed sensors /// If infos are provided, they are used instead of configuring the sensors /// and retrieving the sensor info from them. + OUSTER_API_FUNCTION SensorScanSource( const std::vector& sensors, ///< [in] sensors to connect to const std::vector& @@ -69,19 +73,23 @@ class SensorScanSource { ); /// Destruct the SensorScanSource + OUSTER_API_FUNCTION ~SensorScanSource(); /// Get the sensor_infos for each connected sensor /// @return the sensor_infos for each connected sensor + OUSTER_API_FUNCTION inline const std::vector& get_sensor_info() { return client_.get_sensor_info(); } /// Flush any buffered scans. + OUSTER_API_FUNCTION void flush(); /// Get the number of scans that were dropped due to buffer overflow. /// @return the number of dropped scans + OUSTER_API_FUNCTION inline uint64_t dropped_scans() { std::unique_lock lock(buffer_mutex_); return dropped_scans_; @@ -89,6 +97,7 @@ class SensorScanSource { /// Get the number of packets that had an id verification error /// @return the number of errors + OUSTER_API_FUNCTION inline uint64_t id_error_count() { return id_error_count_; } /// Retrieves a scan from the queue or waits up to timeout_sec until one is @@ -97,11 +106,13 @@ class SensorScanSource { /// experiences a spurious wakeup. /// @return the resulting lidar scan with the idx of the producing sensor /// if no result, the returned scan will be nullptr + OUSTER_API_FUNCTION std::pair> get_scan( double timeout_sec = 0.0 /// [in] timeout for retrieving a scan ); /// Shut down the scan source, closing any sockets and threads + OUSTER_API_FUNCTION void close(); private: diff --git a/ouster_client/include/ouster/types.h b/ouster_client/include/ouster/types.h index c97c10a1..dce0e13e 100644 --- a/ouster_client/include/ouster/types.h +++ b/ouster_client/include/ouster/types.h @@ -19,8 +19,8 @@ #include #include -#include "json/json.h" #include "nonstd/optional.hpp" +#include "ouster/visibility.h" #include "version.h" namespace ouster { @@ -153,6 +153,26 @@ enum Polarity { POLARITY_ACTIVE_HIGH ///< ACTIVE_HIGH }; +#if defined(_WIN32) +#if defined(BAUD_9600) +/** + * @note On windows platforms, the windows headers do a global define on + * BAUD_9600 which causes issues with defines in types.h. Netcompat.h must + * be included after every other header file to avoid this issue. This #error + * is included to notify people of the issue. + */ +#undef BAUD_9600 +#endif +#if defined(BAUD_115200) +/** + * @note On windows platforms, the windows headers do a global define on + * BAUD_115200 which causes issues with defines in types.h. Netcompat.h must + * be included after every other header file to avoid this issue. This #error + * is included to notify people of the issue. + */ +#undef BAUD_115200 +#endif +#endif /** * Baud rate the sensor attempts for NMEA UART input $GPRMC messages * See sensor docs for more details. @@ -261,7 +281,7 @@ using ColumnWindow = std::pair; /** * Struct for sensor configuration parameters. */ -struct sensor_config { +struct OUSTER_API_CLASS sensor_config { optional udp_dest; ///< The destination address for the ///< lidar/imu data to be sent to optional udp_port_lidar; ///< The destination port for the lidar @@ -415,7 +435,7 @@ struct sensor_config { }; /** Stores data format information. */ -struct data_format { +struct OUSTER_API_CLASS data_format { uint32_t pixels_per_column; ///< pixels per column uint32_t columns_per_packet; ///< columns per packet uint32_t @@ -429,13 +449,13 @@ struct data_format { }; /** Stores from-sensor calibration information */ -struct calibration_status { +struct OUSTER_API_CLASS calibration_status { optional reflectivity_status; optional reflectivity_timestamp; }; /** Stores parsed information about the prod line */ -class product_info { +class OUSTER_API_CLASS product_info { public: /** * The original full product line string. @@ -472,12 +492,14 @@ class product_info { * the product_info class from. * @return The new product_info class. */ + OUSTER_API_FUNCTION static product_info create_product_info(std::string product_info_string); /** * Default constructor for product_info that * sets everything to blank. */ + OUSTER_API_FUNCTION product_info(); protected: @@ -493,6 +515,7 @@ class product_info { * * @internal */ + OUSTER_API_FUNCTION product_info(std::string product_info_string, std::string form_factor, bool short_range, std::string beam_config, int beam_count); }; @@ -505,6 +528,7 @@ class product_info { * * @return lhs == rhs */ +OUSTER_API_FUNCTION bool operator==(const product_info& lhs, const product_info& rhs); /** @@ -515,6 +539,7 @@ bool operator==(const product_info& lhs, const product_info& rhs); * * @return lhs != rhs */ +OUSTER_API_FUNCTION bool operator!=(const product_info& lhs, const product_info& rhs); /** @@ -524,12 +549,25 @@ bool operator!=(const product_info& lhs, const product_info& rhs); * * @return string representation of the product info. */ +OUSTER_API_FUNCTION std::string to_string(const product_info& info); -/** Stores parsed information from metadata and */ -struct sensor_info { +/** + * Stores parsed information from metadata. + */ +class OUSTER_API_CLASS sensor_info { + public: + OUSTER_API_FUNCTION + sensor_info(const sensor_info&) = default; + OUSTER_API_FUNCTION + sensor_info(sensor_info&&) = default; + OUSTER_API_FUNCTION + ~sensor_info() = default; + OUSTER_API_FUNCTION + sensor_info& operator=(const ouster::sensor::sensor_info&) = default; + // clang-format off - std::string sn{}; ///< sensor serial number corresponding to prod_sn in + uint64_t sn{}; ///< sensor serial number corresponding to prod_sn in ///< metadata.json std::string fw_rev{}; ///< fw revision corresponding to build_rev in metadata.json @@ -566,15 +604,22 @@ struct sensor_info { /* Constructor from metadata */ [[deprecated("skip_beam_validation does not do anything anymore")]] - explicit sensor_info(const std::string& metadata, bool skip_beam_validation); + OUSTER_API_FUNCTION + explicit sensor_info(const std::string& metadata, bool skip_beam_validation); + OUSTER_API_FUNCTION explicit sensor_info(const std::string& metadata); /* Empty constructor -- keep for */ + OUSTER_API_FUNCTION sensor_info(); - /* Return an updated version of the metadata string reflecting any + /** Return an updated version of the metadata string reflecting any * changes to the sensor_info. - * Errors out if changes are incompatible but does not check for validity */ + * Errors out if changes are incompatible but does not check for validity + * + * @return json serialized version of this object + */ + OUSTER_API_FUNCTION std::string to_json_string() const; /** @@ -582,10 +627,13 @@ struct sensor_info { * * @return sensor version info */ + OUSTER_API_FUNCTION ouster::util::version get_version() const; + OUSTER_API_FUNCTION product_info get_product_info() const; + OUSTER_API_FUNCTION bool has_fields_equal(const sensor_info& other) const; /** @@ -593,6 +641,7 @@ struct sensor_info { * * @return width of a frame. */ + OUSTER_API_FUNCTION auto w() const -> decltype(format.columns_per_frame); ///< returns the width of a frame (equivalent to format.columns_per_frame) /** @@ -600,6 +649,7 @@ struct sensor_info { * * @return height of a frame. */ + OUSTER_API_FUNCTION auto h() const -> decltype(format.pixels_per_column); ///< returns the height of a frame (equivalent to format.pixels_per_column) private: @@ -615,6 +665,7 @@ struct sensor_info { * * @return lhs == rhs */ +OUSTER_API_FUNCTION bool operator==(const data_format& lhs, const data_format& rhs); /** @@ -625,6 +676,7 @@ bool operator==(const data_format& lhs, const data_format& rhs); * * @return lhs != rhs */ +OUSTER_API_FUNCTION bool operator!=(const data_format& lhs, const data_format& rhs); /** @@ -635,6 +687,7 @@ bool operator!=(const data_format& lhs, const data_format& rhs); * * @return lhs == rhs */ +OUSTER_API_FUNCTION bool operator==(const sensor_info& lhs, const sensor_info& rhs); /** @@ -645,6 +698,7 @@ bool operator==(const sensor_info& lhs, const sensor_info& rhs); * * @return lhs != rhs */ +OUSTER_API_FUNCTION bool operator!=(const sensor_info& lhs, const sensor_info& rhs); /** @@ -655,6 +709,7 @@ bool operator!=(const sensor_info& lhs, const sensor_info& rhs); * * @return lhs == rhs */ +OUSTER_API_FUNCTION bool operator==(const sensor_config& lhs, const sensor_config& rhs); /** @@ -665,6 +720,7 @@ bool operator==(const sensor_config& lhs, const sensor_config& rhs); * * @return lhs != rhs */ +OUSTER_API_FUNCTION bool operator!=(const sensor_config& lhs, const sensor_config& rhs); /** @@ -673,6 +729,7 @@ bool operator!=(const sensor_config& lhs, const sensor_config& rhs); * @param[in] lhs The first object to compare. * @param[out] rhs The second object to compare. */ +OUSTER_API_FUNCTION bool operator==(const calibration_status& lhs, const calibration_status& rhs); /** @@ -681,6 +738,7 @@ bool operator==(const calibration_status& lhs, const calibration_status& rhs); * @param[in] lhs The first object to compare. * @param[out] rhs The second object to compare. */ +OUSTER_API_FUNCTION bool operator!=(const calibration_status& lhs, const calibration_status& rhs); /** @@ -690,6 +748,7 @@ bool operator!=(const calibration_status& lhs, const calibration_status& rhs); * * @return default sensor_info for the OS1-64. */ +OUSTER_API_FUNCTION sensor_info default_sensor_info(lidar_mode mode); /** @@ -699,6 +758,7 @@ sensor_info default_sensor_info(lidar_mode mode); * * @return string representation of the lidar mode, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(lidar_mode mode); /** @@ -708,6 +768,7 @@ std::string to_string(lidar_mode mode); * * @return lidar mode corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION lidar_mode lidar_mode_of_string(const std::string& s); /** @@ -717,6 +778,7 @@ lidar_mode lidar_mode_of_string(const std::string& s); * * @return number of columns per rotation for the mode. */ +OUSTER_API_FUNCTION uint32_t n_cols_of_lidar_mode(lidar_mode mode); /** @@ -726,6 +788,7 @@ uint32_t n_cols_of_lidar_mode(lidar_mode mode); * * @return lidar rotation frequency in Hz. */ +OUSTER_API_FUNCTION int frequency_of_lidar_mode(lidar_mode mode); /** @@ -735,6 +798,7 @@ int frequency_of_lidar_mode(lidar_mode mode); * * @return string representation of the timestamp mode, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(timestamp_mode mode); /** @@ -744,6 +808,7 @@ std::string to_string(timestamp_mode mode); * * @return timestamp mode corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION timestamp_mode timestamp_mode_of_string(const std::string& s); /** @@ -753,6 +818,7 @@ timestamp_mode timestamp_mode_of_string(const std::string& s); * * @return string representation of the operating mode, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(OperatingMode mode); /** @@ -762,6 +828,7 @@ std::string to_string(OperatingMode mode); * * @return operating mode corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION optional operating_mode_of_string(const std::string& s); /** @@ -771,6 +838,7 @@ optional operating_mode_of_string(const std::string& s); * * @return string representation of the multipurpose io mode, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(MultipurposeIOMode mode); /** @@ -780,6 +848,7 @@ std::string to_string(MultipurposeIOMode mode); * * @return multipurpose io mode corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION optional multipurpose_io_mode_of_string( const std::string& s); @@ -790,6 +859,7 @@ optional multipurpose_io_mode_of_string( * * @return string representation of the polarity, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(Polarity polarity); /** @@ -799,6 +869,7 @@ std::string to_string(Polarity polarity); * * @return polarity corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION optional polarity_of_string(const std::string& s); /** @@ -808,6 +879,7 @@ optional polarity_of_string(const std::string& s); * * @return string representation of the NMEA baud rate, or "UNKNOWN". */ +OUSTER_API_FUNCTION std::string to_string(NMEABaudRate rate); /** @@ -817,6 +889,7 @@ std::string to_string(NMEABaudRate rate); * * @return nmea baud rate corresponding to the string, or 0 on error. */ +OUSTER_API_FUNCTION optional nmea_baud_rate_of_string(const std::string& s); /** @@ -827,6 +900,7 @@ representation. of * * @return string representation of the azimuth window. */ +OUSTER_API_FUNCTION std::string to_string(AzimuthWindow azimuth_window); /** @@ -836,6 +910,7 @@ std::string to_string(AzimuthWindow azimuth_window); * * @return string representation of the lidar profile. */ +OUSTER_API_FUNCTION std::string to_string(UDPProfileLidar profile); /** @@ -845,6 +920,7 @@ std::string to_string(UDPProfileLidar profile); * * @return lidar profile corresponding to the string, or nullopt on error. */ +OUSTER_API_FUNCTION optional udp_profile_lidar_of_string(const std::string& s); /** @@ -854,6 +930,7 @@ optional udp_profile_lidar_of_string(const std::string& s); * * @return string representation of the lidar profile. */ +OUSTER_API_FUNCTION std::string to_string(UDPProfileIMU profile); /** @@ -863,6 +940,7 @@ std::string to_string(UDPProfileIMU profile); * * @return imu profile corresponding to the string, or nullopt on error. */ +OUSTER_API_FUNCTION optional udp_profile_imu_of_string(const std::string& s); /** @@ -872,6 +950,7 @@ optional udp_profile_imu_of_string(const std::string& s); * * @return full scale range corresponding to the string, or nullopt on error. */ +OUSTER_API_FUNCTION optional full_scale_range_of_string(const std::string& s); /** @@ -881,6 +960,7 @@ optional full_scale_range_of_string(const std::string& s); * * @return return order corresponding to the string, or nullopt on error. */ +OUSTER_API_FUNCTION optional return_order_of_string(const std::string& s); /** @@ -891,6 +971,7 @@ optional return_order_of_string(const std::string& s); * * @return string representation of the return order. */ +OUSTER_API_FUNCTION std::string to_string(ReturnOrder return_order); /** @@ -901,6 +982,7 @@ std::string to_string(ReturnOrder return_order); * * @return string representation of the full scale range. */ +OUSTER_API_FUNCTION std::string to_string(FullScaleRange full_scale_range); /** @@ -911,6 +993,7 @@ std::string to_string(FullScaleRange full_scale_range); * * @return string representation of the shot limiting status. */ +OUSTER_API_FUNCTION std::string to_string(ShotLimitingStatus shot_limiting_status); /** @@ -921,6 +1004,7 @@ std::string to_string(ShotLimitingStatus shot_limiting_status); * * @return string representation of thermal shutdown status. */ +OUSTER_API_FUNCTION std::string to_string(ThermalShutdownStatus thermal_shutdown_status); /** @@ -928,6 +1012,7 @@ std::string to_string(ThermalShutdownStatus thermal_shutdown_status); * * @param[in] signal_multiplier Signal multiplier value. */ +OUSTER_API_FUNCTION void check_signal_multiplier(const double signal_multiplier); /** @@ -942,9 +1027,11 @@ void check_signal_multiplier(const double signal_multiplier); * * @return a sensor_info struct populated with a subset of the metadata. */ +OUSTER_API_FUNCTION sensor_info metadata_from_json(const std::string& json_file, bool skip_beam_validation = false); +// clang-format off /** * String representation of the sensor_info. All fields included. NOT equivalent * or interchangeable with metadata from sensor. @@ -953,10 +1040,11 @@ sensor_info metadata_from_json(const std::string& json_file, * * @return a debug string in json format */ -// clang-format off [[deprecated("This is a debug function. Use original_string() or " - "updated_metadata_string()")]] std::string -to_string(const sensor_info& info); + "updated_metadata_string()")]] +OUSTER_API_FUNCTION +std::string to_string(const sensor_info& info); + // clang-format on /** @@ -971,7 +1059,10 @@ to_string(const sensor_info& info); * @return a sensor_config struct populated with the sensor config. * parameters. */ -sensor_config parse_config(const std::string& config); +[[deprecated( + "Please switch to using parse_and_validate_config")]] OUSTER_API_FUNCTION + sensor_config + parse_config(const std::string& config); /** * Get a string representation of sensor config. Only set fields will be @@ -981,6 +1072,7 @@ sensor_config parse_config(const std::string& config); * * @return a json sensor config string. */ +OUSTER_API_FUNCTION std::string to_string(const sensor_config& config); /** @@ -991,7 +1083,7 @@ std::string to_string(const sensor_config& config); * * @return string representation of sensor calibration. */ - +OUSTER_API_FUNCTION std::string to_string(const calibration_status& cal); /** @@ -999,8 +1091,10 @@ std::string to_string(const calibration_status& cal); * * @return client version string */ +OUSTER_API_FUNCTION std::string client_version(); +// clang-format off /** * Get version information from the metadata. * @@ -1008,10 +1102,10 @@ std::string client_version(); * * @return version corresponding to the string, or invalid_version on error. */ -[[deprecated("Use sensor_info::get_version() instead")]] ouster::util::version -firmware_version_from_metadata(const std::string& metadata); +[[deprecated("Use sensor_info::get_version() instead")]] +OUSTER_API_FUNCTION +ouster::util::version firmware_version_from_metadata(const std::string& metadata); -// clang-format off typedef const char* cf_type; /** * @namespace ChanField @@ -1041,11 +1135,16 @@ namespace ChanField { static constexpr cf_type RAW32_WORD3 = "RAW32_WORD3"; ///< raw word access to packet for dev use static constexpr cf_type RAW32_WORD4 = "RAW32_WORD4"; ///< raw word access to packet for dev use }; -// clang-format on +// clang-format on /** * Types of channel fields. */ +#if defined(VOID) +#define OUSTER_REMOVED_VOID +#pragma push_macro("VOID") +#undef VOID +#endif enum ChanFieldType { VOID = 0, UINT8 = 1, @@ -1060,6 +1159,10 @@ enum ChanFieldType { FLOAT64 = 10, UNREGISTERED = 100 }; +#if defined(OUSTER_REMOVED_VOID) +#pragma pop_macro("VOID") +#undef OUSTER_REMOVED_VOID +#endif /** * Get the size of the ChanFieldType in bytes. @@ -1068,6 +1171,7 @@ enum ChanFieldType { * * @return size of the field type in bytes */ +OUSTER_API_FUNCTION size_t field_type_size(ChanFieldType ft); /** @@ -1077,6 +1181,7 @@ size_t field_type_size(ChanFieldType ft); * * @return 64 bit mask */ +OUSTER_API_FUNCTION uint64_t field_type_mask(ChanFieldType ft); /** @@ -1086,6 +1191,7 @@ uint64_t field_type_mask(ChanFieldType ft); * * @return string representation of the channel field type. */ +OUSTER_API_FUNCTION std::string to_string(ChanFieldType ft); /** @@ -1101,7 +1207,7 @@ std::string to_string(ChanFieldType ft); * Use imu_la_{x,y,z} to access the acceleration in the corresponding * direction. Use imu_av_{x,y,z} to read the angular velocity. */ -class packet_format { +class OUSTER_API_CLASS packet_format { protected: struct Impl; std::shared_ptr impl_; @@ -1109,9 +1215,11 @@ class packet_format { std::vector> field_types_; public: + OUSTER_API_FUNCTION packet_format(UDPProfileLidar udp_profile_lidar, size_t pixels_per_column, size_t columns_per_packet); + OUSTER_API_FUNCTION packet_format( const sensor_info& info); //< create packet_format from sensor_info @@ -1141,6 +1249,7 @@ class packet_format { * * @return the packet type. */ + OUSTER_API_FUNCTION uint16_t packet_type(const uint8_t* lidar_buf) const; /** @@ -1150,6 +1259,7 @@ class packet_format { * * @return the frame id. */ + OUSTER_API_FUNCTION uint32_t frame_id(const uint8_t* lidar_buf) const; /** @@ -1159,6 +1269,7 @@ class packet_format { * * @return the init id. */ + OUSTER_API_FUNCTION uint32_t init_id(const uint8_t* lidar_buf) const; /** @@ -1168,6 +1279,7 @@ class packet_format { * * @return the serial number. */ + OUSTER_API_FUNCTION uint64_t prod_sn(const uint8_t* lidar_buf) const; /** @@ -1177,6 +1289,7 @@ class packet_format { * * @return the alert flags byte. */ + OUSTER_API_FUNCTION uint8_t alert_flags(const uint8_t* lidar_buf) const; /** @@ -1186,6 +1299,7 @@ class packet_format { * * @return the thermal shutdown countdown. */ + OUSTER_API_FUNCTION uint16_t countdown_thermal_shutdown(const uint8_t* lidar_buf) const; /** @@ -1195,6 +1309,7 @@ class packet_format { * * @return the shot limiting countdown. */ + OUSTER_API_FUNCTION uint16_t countdown_shot_limiting(const uint8_t* lidar_buf) const; /** @@ -1204,6 +1319,7 @@ class packet_format { * * @return the thermal shutdown status */ + OUSTER_API_FUNCTION uint8_t thermal_shutdown(const uint8_t* lidar_buf) const; /** @@ -1213,6 +1329,7 @@ class packet_format { * * @return the shot limiting status */ + OUSTER_API_FUNCTION uint8_t shot_limiting(const uint8_t* lidar_buf) const; /** @@ -1223,6 +1340,7 @@ class packet_format { * @return a type tag specifying the bitwidth of the requested field or * ChannelFieldType::VOID if it is not supported by the packet format. */ + OUSTER_API_FUNCTION ChanFieldType field_type(const std::string& f) const; /** @@ -1232,6 +1350,7 @@ class packet_format { * packets. * */ + OUSTER_API_FUNCTION FieldIter begin() const; /** @@ -1240,6 +1359,7 @@ class packet_format { * @return Iterator pointing to the last element in the field type of * packets. */ + OUSTER_API_FUNCTION FieldIter end() const; /** @@ -1250,6 +1370,7 @@ class packet_format { * @return pointer to packet footer of lidar buffer, can be nullptr if * packet format doesn't have packet footer. */ + OUSTER_API_FUNCTION const uint8_t* footer(const uint8_t* lidar_buf) const; // Measurement block accessors @@ -1261,6 +1382,7 @@ class packet_format { * * @return pointer to nth column of lidar buffer. */ + OUSTER_API_FUNCTION const uint8_t* nth_col(int n, const uint8_t* lidar_buf) const; /** @@ -1270,6 +1392,7 @@ class packet_format { * * @return column timestamp. */ + OUSTER_API_FUNCTION uint64_t col_timestamp(const uint8_t* col_buf) const; /** @@ -1279,6 +1402,7 @@ class packet_format { * * @return column measurement id. */ + OUSTER_API_FUNCTION uint16_t col_measurement_id(const uint8_t* col_buf) const; /** @@ -1288,7 +1412,10 @@ class packet_format { * * @return column status. */ + OUSTER_API_FUNCTION uint32_t col_status(const uint8_t* col_buf) const; + + // clang-format off /** * @brief Encodes the column value. * @@ -1301,11 +1428,16 @@ class packet_format { * * @return Encoded column value. */ - [[deprecated("Use col_measurement_id instead")]] uint32_t col_encoder( + [[deprecated("Use col_measurement_id instead")]] + OUSTER_API_FUNCTION + uint32_t col_encoder( const uint8_t* col_buf) const; ///< @deprecated Encoder count is deprecated as it is redundant ///< with measurement id, barring a multiplication factor which ///< varies by lidar mode. Use col_measurement_id instead + // clang-format on + + // clang-format off /** * @brief Retrieves the current frame id * @@ -1318,8 +1450,11 @@ class packet_format { * * @return The current frame id. */ - [[deprecated("Use frame_id instead")]] uint16_t col_frame_id( + [[deprecated("Use frame_id instead")]] + OUSTER_API_FUNCTION + uint16_t col_frame_id( const uint8_t* col_buf) const; ///< @deprecated Use frame_id instead + // clang-format on /** * Copy the specified channel field out of a packet measurement block. @@ -1341,6 +1476,7 @@ class packet_format { * * @return if packet format does not allow for block parsing, returns 0 */ + OUSTER_API_FUNCTION int block_parsable() const; /** @@ -1368,6 +1504,7 @@ class packet_format { * * @return pointer to nth pixel of a column buffer. */ + OUSTER_API_FUNCTION const uint8_t* nth_px(int n, const uint8_t* col_buf) const; // IMU packet accessors @@ -1377,6 +1514,7 @@ class packet_format { * * @return sys ts from imu pacet buffer. */ + OUSTER_API_FUNCTION uint64_t imu_sys_ts(const uint8_t* imu_buf) const; /** @@ -1386,6 +1524,7 @@ class packet_format { * * @return acceleration ts from imu packet buffer. */ + OUSTER_API_FUNCTION uint64_t imu_accel_ts(const uint8_t* imu_buf) const; /** @@ -1395,6 +1534,7 @@ class packet_format { * * @return gyro ts from imu packet buffer. */ + OUSTER_API_FUNCTION uint64_t imu_gyro_ts(const uint8_t* imu_buf) const; /** @@ -1404,6 +1544,7 @@ class packet_format { * * @return acceleration in x. */ + OUSTER_API_FUNCTION float imu_la_x(const uint8_t* imu_buf) const; /** @@ -1413,6 +1554,7 @@ class packet_format { * * @return acceleration in y. */ + OUSTER_API_FUNCTION float imu_la_y(const uint8_t* imu_buf) const; /** @@ -1422,6 +1564,7 @@ class packet_format { * * @return acceleration in z. */ + OUSTER_API_FUNCTION float imu_la_z(const uint8_t* imu_buf) const; /** @@ -1431,6 +1574,7 @@ class packet_format { * * @return angular velocity in x. */ + OUSTER_API_FUNCTION float imu_av_x(const uint8_t* imu_buf) const; /** @@ -1440,6 +1584,7 @@ class packet_format { * * @return angular velocity in y. */ + OUSTER_API_FUNCTION float imu_av_y(const uint8_t* imu_buf) const; /** @@ -1449,6 +1594,7 @@ class packet_format { * * @return angular velocity in z. */ + OUSTER_API_FUNCTION float imu_av_z(const uint8_t* imu_buf) const; /** @@ -1458,6 +1604,7 @@ class packet_format { * * @return mask of possible values */ + OUSTER_API_FUNCTION uint64_t field_value_mask(const std::string& f) const; /** @@ -1467,6 +1614,7 @@ class packet_format { * * @return number of bits */ + OUSTER_API_FUNCTION int field_bitness(const std::string& f) const; /** @@ -1476,6 +1624,7 @@ class packet_format { * * @return crc contained in the packet if present */ + OUSTER_API_FUNCTION optional crc(const uint8_t* lidar_buf) const; /** @@ -1485,6 +1634,7 @@ class packet_format { * * @return calculated crc of the packet */ + OUSTER_API_FUNCTION uint64_t calculate_crc(const uint8_t* lidar_buf) const; }; @@ -1499,6 +1649,7 @@ class packet_format { * * @return a packet_format suitable for parsing UDP packets sent by the sensor. */ +OUSTER_API_FUNCTION const packet_format& get_format(const sensor_info& info); /** @@ -1512,99 +1663,11 @@ const packet_format& get_format(const sensor_info& info); * * @return a packet_format suitable for parsing UDP packets sent by the sensor. */ +OUSTER_API_FUNCTION const packet_format& get_format(UDPProfileLidar udp_profile_lidar, size_t pixels_per_column, size_t columns_per_packet); -/** - * Encapsulate a packet buffer and attributes associated with it. - */ -struct Packet { - uint64_t host_timestamp; - std::vector buf; - - Packet(int size = 65536) : host_timestamp{0} { - // this is necessary due to how client works - it may read size() + 1 - // bytes into the packet in case of rogue packet coming through - buf.reserve(size + 1); - buf.resize(size, 0); - } - - template - PacketType& as() { - return static_cast(*this); - } -}; - -/** - * Reasons for failure of packet validation. - */ -enum class PacketValidationFailure { - NONE = 0, ///< No validation errors were found - PACKET_SIZE = 1, ///< The packet size does not match the expected size - ID = 2 ///< The prod_sn or init_id does not match the metadata -}; - -/** - * Enum for packet validation types. - */ -enum class PacketValidationType { - LIDAR, ///< Validate as if the buffer was a lidar buffer - IMU, ///< Validate as if the buffer was an imu buffer - GUESS_TYPE ///< Try to guess the type and validate as that -}; - -/** - * Validate a packet buffer against a given type. - * - * @param[in] info The sensor info to try to check the buffer against. - * @param[in] format The packet format to try to check the buffer against. - * @param[in] buf The packet buffer to validate. - * @param[in] buf_size The size of the packet buffer. - * @param[in] type Optional type of packet to try and validate as. - * @return Result of the validation - */ -PacketValidationFailure validate_packet( - const sensor_info& info, const packet_format& format, const uint8_t* buf, - uint64_t buf_size, - PacketValidationType type = PacketValidationType::GUESS_TYPE); - -/** - * Encapsulate a lidar packet buffer and attributes associated with it. - */ -struct LidarPacket : public Packet { - using Packet::Packet; - - /** - * Validates that the packet matches the expected format and metadata. - * - * @param[in] info expected sensor metadata - * @param[in] format expected packet_format - * - * @return a PacketValdationFailure with either NONE or a failure reason. - */ - PacketValidationFailure validate(const sensor_info& info, - const packet_format& format); -}; - -/** - * Encapsulate an imu packet buffer and attributes associated with it. - */ -struct ImuPacket : public Packet { - using Packet::Packet; - - /** - * Validates that the packet matches the expected format and metadata. - * - * @param[in] info expected sensor metadata - * @param[in] format expected packet_format - * - * @return a PacketValdationFailure with either NONE or a failure reason. - */ - PacketValidationFailure validate(const sensor_info& info, - const packet_format& format); -}; - namespace impl { /** Maximum number of allowed lidar profiles */ @@ -1613,4 +1676,8 @@ constexpr int MAX_NUM_PROFILES = 32; } // namespace impl } // namespace sensor +/** + * The type to represent json data in string form. + */ +typedef std::string json_string; } // namespace ouster diff --git a/ouster_client/include/ouster/util.h b/ouster_client/include/ouster/util.h deleted file mode 100644 index b31117f0..00000000 --- a/ouster_client/include/ouster/util.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2023, Ouster, Inc. - * All rights reserved. - */ - -#include - -/* Helper function which produces a combination of the two roots, with - * any value in root_orig that exists in root_new replaced with the root_new - * value. The vector changed indicates each field changed in the result - * from the original - */ - -namespace ouster { - -Json::Value combined(const Json::Value& root_orig, const Json::Value& root_new, - std::vector& changed); - -} diff --git a/ouster_client/include/ouster/version.h b/ouster_client/include/ouster/version.h index d7bc62e6..2ee54c4b 100644 --- a/ouster_client/include/ouster/version.h +++ b/ouster_client/include/ouster/version.h @@ -5,19 +5,21 @@ * @file * @brief Simple version struct */ +#pragma once #include #include -#pragma once +#include "ouster/visibility.h" namespace ouster { namespace util { -struct version { - uint16_t major; ///< Major version number - uint16_t minor; ///< Minor version number - uint16_t patch; ///< Patch(or revision) version number +struct OUSTER_API_CLASS version { + uint16_t major; ///< Major version number + uint16_t minor; ///< Minor version number + uint16_t patch; ///< Patch(or revision) version number + std::string stage; ///< Release stage name, if present. std::string machine; ///< Machine name, if present. std::string prerelease; ///< Prerelease name (e.g. rc1), if present. @@ -37,6 +39,7 @@ const version invalid_version = {0, 0, 0, "", "", "", ""}; * * @return If the versions are the same. */ +OUSTER_API_FUNCTION inline bool operator==(const version& u, const version& v) { return u.major == v.major && u.minor == v.minor && u.patch == v.patch && u.stage == v.stage && u.machine == v.machine && u.build == v.build && @@ -51,6 +54,7 @@ inline bool operator==(const version& u, const version& v) { * * @return If the first version is less than the second version. */ +OUSTER_API_FUNCTION inline bool operator<(const version& u, const version& v) { return (u.major < v.major) || (u.major == v.major && u.minor < v.minor) || (u.major == v.major && u.minor == v.minor && u.patch < v.patch); @@ -64,6 +68,7 @@ inline bool operator<(const version& u, const version& v) { * * @return If the first version is less than or equal to the second version. */ +OUSTER_API_FUNCTION inline bool operator<=(const version& u, const version& v) { return u < v || u == v; } @@ -76,6 +81,7 @@ inline bool operator<=(const version& u, const version& v) { * * @return If the versions are not the same. */ +OUSTER_API_FUNCTION inline bool operator!=(const version& u, const version& v) { return !(u == v); } /** @@ -86,6 +92,7 @@ inline bool operator!=(const version& u, const version& v) { return !(u == v); } * * @return If the first version is greater than or equal to the second version. */ +OUSTER_API_FUNCTION inline bool operator>=(const version& u, const version& v) { return !(u < v); } /** @@ -96,6 +103,7 @@ inline bool operator>=(const version& u, const version& v) { return !(u < v); } * * @return If the first version is greater than the second version. */ +OUSTER_API_FUNCTION inline bool operator>(const version& u, const version& v) { return !(u <= v); } /** @}*/ @@ -108,6 +116,7 @@ inline bool operator>(const version& u, const version& v) { return !(u <= v); } * * @return version corresponding to the string, or invalid_version on error. */ +OUSTER_API_FUNCTION version version_from_string(const std::string& ver); } // namespace util diff --git a/ouster_client/include/ouster/visibility.h b/ouster_client/include/ouster/visibility.h new file mode 100644 index 00000000..2c8555bb --- /dev/null +++ b/ouster_client/include/ouster/visibility.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ + +#pragma once + +/** + * Turn off formatting to better organise the ifdefs with indents, + * otherwise the code is hard to read. + */ +// clang-format off + +/** + * Defines for the check_exports.py script to be able to analyze + * missing attributes. + */ +#if defined OUSTER_CHECK_EXPORTS + #define OUSTER_API_FUNCTION __attribute__((annotate("OUSTER_API_FUNCTION"))) + #define OUSTER_API_CLASS __attribute__((annotate("OUSTER_API_CLASS"))) + #define OUSTER_API_IGNORE __attribute__((annotate("OUSTER_API_IGNORE"))) + #define OUSTER_API_DEFINES + +/** + * We are not running the check_exports.py script. + */ +#else + + /** + * Tag to use for items that the check_exports.py script stubbornly insists needs + * an attribute. For some reason clang returns public visibility for some protected + * members of classes. + */ + #define OUSTER_API_IGNORE + + /** + * Currently compiling for windows. + */ + #if defined _WIN32 + + /** + * Currently compiling the DLL for exports. + */ + #ifdef BUILD_SHARED_LIBS_EXPORT + #define OUSTER_API_FUNCTION __declspec( dllexport ) + #define OUSTER_API_CLASS + #define OUSTER_API_DEFINES + + /** + * Currently compiling the code requiring the ouster + * shared library. + */ + #elif defined(BUILD_SHARED_LIBS_IMPORT) + #define OUSTER_API_FUNCTION __declspec( dllimport ) + #define OUSTER_API_CLASS + #define OUSTER_API_DEFINES + #endif + + /** + * Currently compiling for linux or macos. + */ + #else + + /** + * Only add for linux and macos, and not emscripten. + */ + #ifndef __EMSCRIPTEN__ + #define _OUSTER_API_UNIX_ATTR __attribute__((visibility("default"))) + #define OUSTER_API_FUNCTION _OUSTER_API_UNIX_ATTR + #define OUSTER_API_CLASS _OUSTER_API_UNIX_ATTR + #define OUSTER_API_DEFINES + #endif + #endif +#endif + +/** + * Check to make sure we defined all of the defines, if not + * define them as empty. Emscripten usage also falls into this + * ifndef. + */ +#ifndef OUSTER_API_DEFINES + #define OUSTER_API_FUNCTION + #define OUSTER_API_CLASS + #define OUSTER_API_DEFINES +#endif +// clang-format on diff --git a/ouster_client/src/client.cpp b/ouster_client/src/client.cpp index 76299472..6398c68b 100644 --- a/ouster_client/src/client.cpp +++ b/ouster_client/src/client.cpp @@ -5,8 +5,6 @@ #include "ouster/client.h" -#include - #include #include #include @@ -15,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -26,6 +27,7 @@ #include "ouster/impl/client_poller.h" #include "ouster/impl/logging.h" #include "ouster/impl/netcompat.h" +#include "ouster/metadata.h" #include "ouster/sensor_http.h" #include "ouster/types.h" @@ -48,7 +50,7 @@ struct client { }; // defined in types.cpp -Json::Value config_to_json(const sensor_config& config); +jsoncons::json config_to_json(const sensor_config& config); // default udp receive buffer size on windows is very low -- use 1MB const int RCVBUF_SIZE = 1024 * 1024; @@ -87,7 +89,8 @@ SOCKET mtp_data_socket(int port, const std::vector& udp_dest_hosts, if (preferred_af == AF_INET6 && setsockopt(sock_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&off, sizeof(off))) { - logger().warn("udp setsockopt(): {}", impl::socket_get_error()); + logger().warn("udp setsockopt(IPV6_V6ONLY): {}", + impl::socket_get_error()); impl::socket_close(sock_fd); continue; } @@ -134,7 +137,8 @@ SOCKET mtp_data_socket(int port, const std::vector& udp_dest_hosts, if (setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq))) { - logger().warn("udp setsockopt(): {}", impl::socket_get_error()); + logger().warn("udp setsockopt(IP_ADD_MEMBERSHIP): {}", + impl::socket_get_error()); impl::socket_close(sock_fd); continue; } @@ -149,7 +153,8 @@ SOCKET mtp_data_socket(int port, const std::vector& udp_dest_hosts, if (setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF, (char*)&RCVBUF_SIZE, sizeof(RCVBUF_SIZE))) { - logger().warn("udp setsockopt(): {}", impl::socket_get_error()); + logger().warn("udp setsockopt(SO_RCVBUF): {}", + impl::socket_get_error()); impl::socket_close(sock_fd); continue; } @@ -164,7 +169,7 @@ SOCKET mtp_data_socket(int port, const std::vector& udp_dest_hosts, SOCKET udp_data_socket(int port) { return mtp_data_socket(port, {}); } -Json::Value collect_metadata(SensorHttp& sensor_http, int timeout_sec) { +jsoncons::json collect_metadata(SensorHttp& sensor_http, int timeout_sec) { // Note, this function throws std::runtime_error if // 1. the metadata couldn't be retrieved // 2. the sensor is in the INITIALIZING state when timeout is reached @@ -179,7 +184,10 @@ Json::Value collect_metadata(SensorHttp& sensor_http, int timeout_sec) { "A timeout occurred while waiting for the sensor to " "initialize."); } - status = sensor_http.sensor_info(timeout_sec)["status"].asString(); + + status = jsoncons::json::parse( + sensor_http.sensor_info(timeout_sec))["status"] + .as(); if (status != "INITIALIZING") { break; } @@ -197,7 +205,8 @@ Json::Value collect_metadata(SensorHttp& sensor_http, int timeout_sec) { } try { - auto metadata = sensor_http.metadata(timeout_sec); + auto metadata = + jsoncons::json::parse(sensor_http.metadata(timeout_sec)); metadata["ouster-sdk"]["client_version"] = client_version(); metadata["ouster-sdk"]["output_source"] = "collect_metadata"; @@ -235,7 +244,10 @@ bool get_config(SensorHttp& sensor_http, sensor_config& config, bool active = true, int timeout_sec = LONG_HTTP_REQUEST_TIMEOUT_SECONDS) { auto res = sensor_http.get_config_params(active, timeout_sec); - config = parse_config(res); + ValidatorIssues issues; + if (!parse_and_validate_config(res, config, issues)) { + throw std::runtime_error(to_string(issues.critical)); + } return true; } @@ -248,17 +260,18 @@ bool get_config(const std::string& hostname, sensor_config& config, bool active, bool set_config(SensorHttp& sensor_http, const sensor_config& config, uint8_t config_flags, int timeout_sec) { // reset staged config to avoid spurious errors - auto config_params = sensor_http.active_config_params(timeout_sec); - Json::Value config_params_copy = config_params; + jsoncons::json config_params = + jsoncons::json::parse(sensor_http.active_config_params(timeout_sec)); + jsoncons::json config_params_copy = config_params; // set all desired config parameters - Json::Value config_json = config_to_json(config); - for (const auto& key : config_json.getMemberNames()) { - config_params[key] = config_json[key]; + jsoncons::json config_json = config_to_json(config); + for (const auto& it : config_json.object_range()) { + config_params[it.key()] = it.value(); } - if (config_json.isMember("operating_mode") && - config_params.isMember("auto_start_flag")) { + if (config_json.contains("operating_mode") && + config_params.contains("auto_start_flag")) { // we're setting operating mode and this sensor has a FW with // auto_start_flag config_params["auto_start_flag"] = @@ -268,12 +281,13 @@ bool set_config(SensorHttp& sensor_http, const sensor_config& config, // Signal multiplier changed from int to double for FW 3.0/2.5+, with // corresponding change to config.signal_multiplier. // Change values 1, 2, 3 back to ints to support older FWs - if (config_json.isMember("signal_multiplier")) { - check_signal_multiplier(config_params["signal_multiplier"].asDouble()); - if (config_params["signal_multiplier"].asDouble() != 0.25 && - config_params["signal_multiplier"].asDouble() != 0.5) { + if (config_json.contains("signal_multiplier")) { + check_signal_multiplier( + config_params["signal_multiplier"].as()); + if (config_params["signal_multiplier"].as() != 0.25 && + config_params["signal_multiplier"].as() != 0.5) { config_params["signal_multiplier"] = - config_params["signal_multiplier"].asInt(); + config_params["signal_multiplier"].as(); } } @@ -289,15 +303,16 @@ bool set_config(SensorHttp& sensor_http, const sensor_config& config, "UDP_DEST_AUTO flag set but provided config has udp_dest"); sensor_http.set_udp_dest_auto(timeout_sec); - auto staged = sensor_http.staged_config_params(timeout_sec); + auto staged = jsoncons::json::parse( + sensor_http.staged_config_params(timeout_sec)); // now we set config_params according to the staged udp_dest from the // sensor - if (staged.isMember("udp_ip")) { // means the FW version carries udp_ip - config_params["udp_ip"] = staged["udp_ip"]; - config_params["udp_dest"] = staged["udp_ip"]; + if (staged.contains("udp_ip")) { // means the FW version carries udp_ip + config_params["udp_ip"] = staged["udp_ip"].as(); + config_params["udp_dest"] = staged["udp_ip"].as(); } else { // don't need to worry about udp_ip - config_params["udp_dest"] = staged["udp_dest"]; + config_params["udp_dest"] = staged["udp_dest"].as(); } } @@ -306,11 +321,10 @@ bool set_config(SensorHttp& sensor_http, const sensor_config& config, // introduced after the verison of FW the sensor is on if (config_flags & CONFIG_FORCE_REINIT || config_params_copy != config_params) { - Json::StreamWriterBuilder builder; - builder["indentation"] = ""; // send full string -- depends on older FWs not rejecting a blob even // when it contains unknown keys - auto config_params_str = Json::writeString(builder, config_params); + std::string config_params_str; + config_params.dump(config_params_str); sensor_http.set_config_param(".", config_params_str, timeout_sec); // reinitialize to make all staged parameters effective sensor_http.reinitialize(timeout_sec); @@ -334,20 +348,16 @@ std::string get_metadata(client& cli, int timeout_sec) { // Note, this function calls functions that throw std::runtime_error // on timeout. auto sensor_http = SensorHttp::create(cli.hostname, timeout_sec); - Json::Value meta; + std::string meta; try { - meta = collect_metadata(*sensor_http, timeout_sec); + auto temp_data = collect_metadata(*sensor_http, timeout_sec); + temp_data.dump(meta); } catch (const std::exception& e) { logger().warn(std::string("Unable to retrieve sensor metadata: ") + e.what()); throw; } - - Json::StreamWriterBuilder builder; - builder["enableYAMLCompatibility"] = true; - builder["precision"] = 6; - builder["indentation"] = " "; - return Json::writeString(builder, meta); + return meta; } bool init_logger(const std::string& log_level, const std::string& log_file_path, @@ -413,9 +423,10 @@ std::shared_ptr init_client(const std::string& hostname, // will block until no longer INITIALIZING auto meta = collect_metadata(*sensor_http, timeout_sec); // check for sensor error states - auto status = meta["sensor_info"]["status"].asString(); - if (status == "ERROR" || status == "UNCONFIGURED") + std::string status = meta["sensor_info"]["status"].as(); + if (status == "ERROR" || status == "UNCONFIGURED") { return std::shared_ptr(); + } } catch (const std::runtime_error& e) { // log error message logger().error("init_client(): {}", e.what()); @@ -465,7 +476,7 @@ std::shared_ptr mtp_init_client(const std::string& hostname, // will block until no longer INITIALIZING auto meta = collect_metadata(*sensor_http, timeout_sec); // check for sensor error states - auto status = meta["sensor_info"]["status"].asString(); + auto status = meta["sensor_info"]["status"].as(); if (status == "ERROR" || status == "UNCONFIGURED") return std::shared_ptr(); } catch (const std::runtime_error& e) { diff --git a/ouster_client/src/lidar_scan.cpp b/ouster_client/src/lidar_scan.cpp index e62286c4..a69dbfdc 100644 --- a/ouster_client/src/lidar_scan.cpp +++ b/ouster_client/src/lidar_scan.cpp @@ -17,6 +17,7 @@ #include "ouster/impl/logging.h" #include "ouster/strings.h" #include "ouster/types.h" +#include "ouster/visibility.h" #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -109,7 +110,7 @@ static const Table fusa_two_word_slots{{ {sensor::ChanField::FLAGS2, ChanFieldType::UINT8}, }}; -struct DefaultFieldsEntry { +struct OUSTER_API_CLASS DefaultFieldsEntry { const std::pair* fields; size_t n_fields; }; @@ -201,6 +202,14 @@ static FieldDescriptor get_field_type_descriptor(const LidarScan& scan, LidarScan::LidarScan(const sensor::sensor_info& info) : LidarScan{info.format.columns_per_frame, info.format.pixels_per_column, info.format.udp_profile_lidar, info.format.columns_per_packet} { + sensor_info = std::make_shared(info); +} + +LidarScan::LidarScan(std::shared_ptr info) + : LidarScan{info->format.columns_per_frame, info->format.pixels_per_column, + info->format.udp_profile_lidar, + info->format.columns_per_packet} { + sensor_info = info; } // specify sensor:: namespace for doxygen matching @@ -247,7 +256,8 @@ LidarScan::LidarScan(const LidarScan& ls_src, h(ls_src.h), columns_per_packet_(ls_src.columns_per_packet_), frame_status(ls_src.frame_status), - frame_id(ls_src.frame_id) { + frame_id(ls_src.frame_id), + sensor_info(ls_src.sensor_info) { for (const auto& ft : field_types) { const std::string& name = ft.name; FieldDescriptor dst_desc = get_field_type_descriptor(*this, ft); @@ -302,10 +312,20 @@ sensor::ThermalShutdownStatus LidarScan::thermal_shutdown() const { frame_status_shifts::FRAME_STATUS_THERMAL_SHUTDOWN_SHIFT); } -Field& LidarScan::field(const std::string& name) { return fields().at(name); } +Field& LidarScan::field(const std::string& name) { + try { + return fields().at(name); + } catch (std::out_of_range& e) { + throw std::out_of_range("Field '" + name + "' not found in LidarScan."); + } +} const Field& LidarScan::field(const std::string& name) const { - return fields().at(name); + try { + return fields().at(name); + } catch (std::out_of_range& e) { + throw std::out_of_range("Field '" + name + "' not found in LidarScan."); + } } bool LidarScan::has_field(const std::string& name) const { @@ -544,15 +564,19 @@ bool LidarScan::complete(sensor::ColumnWindow window) const { size_t LidarScan::packet_count() const { return packet_count_; } -bool operator==(const LidarScan& a, const LidarScan& b) { - return a.frame_id == b.frame_id && a.w == b.w && a.h == b.h && - a.frame_status == b.frame_status && - a.measurement_id_ == b.measurement_id_ && - a.timestamp_ == b.timestamp_ && - a.packet_timestamp_ == b.packet_timestamp_ && a.pose() == b.pose() && - a.fields() == b.fields(); +bool LidarScan::equals(const LidarScan& other) const { + return frame_id == other.frame_id && w == other.w && h == other.h && + frame_status == other.frame_status && + measurement_id_ == other.measurement_id_ && + timestamp_ == other.timestamp_ && + packet_timestamp_ == other.packet_timestamp_ && + pose() == other.pose() && fields() == other.fields(); } +bool operator==(const LidarScan& a, const LidarScan& b) { return a.equals(b); } + +bool operator!=(const LidarScan& a, const LidarScan& b) { return !(a == b); } + LidarScanFieldTypes LidarScan::field_types() const { LidarScanFieldTypes ft; for (const auto& kv : fields()) { @@ -771,8 +795,7 @@ ScanBatcher::ScanBatcher(size_t w, const sensor::packet_format& pf) h(pf.pixels_per_column), next_valid_m_id(0), next_headers_m_id(0), - cache(pf.lidar_packet_size), - cache_packet_ts(0), + cache(0), pf(pf) { if (pf.columns_per_packet == 0) throw std::invalid_argument("unexpected columns_per_packet: 0"); @@ -806,6 +829,7 @@ ScanBatcher::ScanBatcher(const sensor::sensor_info& info) expected_packets = end_packet - start_packet + 1; } + sensor_info = std::make_shared(info); } namespace { @@ -911,7 +935,7 @@ struct pack_raw_headers_col { } // namespace -void ScanBatcher::_parse_by_col(const uint8_t* packet_buf, LidarScan& ls) { +void ScanBatcher::parse_by_col(const uint8_t* packet_buf, LidarScan& ls) { const bool raw_headers = impl::raw_headers_enabled(pf, ls); for (int icol = 0; icol < pf.columns_per_packet; icol++) { const uint8_t* col_buf = pf.nth_col(icol, packet_buf); @@ -972,7 +996,7 @@ struct parse_field_block { } }; -void ScanBatcher::_parse_by_block(const uint8_t* packet_buf, LidarScan& ls) { +void ScanBatcher::parse_by_block(const uint8_t* packet_buf, LidarScan& ls) { // zero out missing columns if we jumped forward const uint16_t first_m_id = pf.col_measurement_id(pf.nth_col(0, packet_buf)); @@ -1013,26 +1037,8 @@ void ScanBatcher::_parse_by_block(const uint8_t* packet_buf, LidarScan& ls) { } } -bool ScanBatcher::operator()(const uint8_t* packet_buf, LidarScan& ls) { - return this->operator()(packet_buf, 0, ls); -} - bool ScanBatcher::operator()(const ouster::sensor::LidarPacket& packet, LidarScan& ls) { - return (*this)(packet.buf.data(), packet.host_timestamp, ls); -} - -void ScanBatcher::finalize_scan(LidarScan& ls, bool raw_headers) { - impl::foreach_channel_field(ls, pf, zero_field_cols{}, next_valid_m_id, w); - - if (raw_headers) { - impl::visit_field(ls, sensor::ChanField::RAW_HEADERS, zero_field_cols{}, - "", next_headers_m_id, w); - } -} - -bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, - LidarScan& ls) { if (ls.w != w || ls.h != h) throw std::invalid_argument("unexpected scan dimensions"); if (static_cast(ls.packet_timestamp().rows()) != @@ -1040,12 +1046,10 @@ bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, throw std::invalid_argument("unexpected scan columns_per_packet: " + std::to_string(pf.columns_per_packet)); - // process cached packet and packet ts - if (cached_packet) { - cached_packet = false; - this->operator()(cache.data(), cache_packet_ts, ls); - } + // process cached packet and packet ts, if present + batch_cached_packet(ls); + const uint8_t* packet_buf = packet.buf.data(); const int64_t f_id = pf.frame_id(packet_buf); const bool raw_headers = impl::raw_headers_enabled(pf, ls); @@ -1079,19 +1083,17 @@ bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, // given scan. ls.shutdown_countdown = pf.countdown_thermal_shutdown(packet_buf); ls.shot_limiting_countdown = pf.countdown_shot_limiting(packet_buf); + ls.sensor_info = sensor_info; } else if (ls.frame_id == ((f_id + 1) % (static_cast(pf.max_frame_id) + 1))) { // drop reordered packets from the previous frame return false; } else if (ls.frame_id != f_id) { // got a packet from a new frame, release the old one - finished_scan_id = ls.frame_id; - finalize_scan(ls, raw_headers); + finalize_scan(ls); // store packet buf and ts data to the cache for later processing - std::memcpy(cache.data(), packet_buf, cache.size()); - cache_packet_ts = packet_ts; - cached_packet = true; + cache_packet(packet); return true; } @@ -1102,7 +1104,7 @@ bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, const uint16_t packet_id = pf.col_measurement_id(col0_buf) / pf.columns_per_packet; if (packet_id < ls.packet_timestamp().rows()) { - ls.packet_timestamp()[packet_id] = packet_ts; + ls.packet_timestamp()[packet_id] = packet.host_timestamp; ls.alert_flags()[packet_id] = pf.alert_flags(packet_buf); } @@ -1121,22 +1123,63 @@ bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, } if (pf.block_parsable() && happy_packet && !raw_headers) { - _parse_by_block(packet_buf, ls); + parse_by_block(packet_buf, ls); } else { - _parse_by_col(packet_buf, ls); + parse_by_col(packet_buf, ls); } // if we have enough packets and are packet-complete release the scan - if (batched_packets >= expected_packets && - (size_t)ls.packet_timestamp().count() == expected_packets) { - finished_scan_id = f_id; - finalize_scan(ls, raw_headers); + if (check_scan_complete(ls)) { + finalize_scan(ls); return true; } return false; } +void ScanBatcher::cache_packet(const sensor::LidarPacket& packet) { + cache = packet; + cached_packet = true; +} + +void ScanBatcher::batch_cached_packet(LidarScan& ls) { + if (cached_packet) { + cached_packet = false; + this->operator()(cache.as(), ls); + } +} + +bool ScanBatcher::check_scan_complete(const LidarScan& ls) const { + return batched_packets >= expected_packets && + static_cast(ls.packet_timestamp().count()) == + expected_packets; +} + +void ScanBatcher::finalize_scan(LidarScan& ls) { + impl::foreach_channel_field(ls, pf, zero_field_cols{}, next_valid_m_id, w); + + if (impl::raw_headers_enabled(pf, ls)) { + impl::visit_field(ls, sensor::ChanField::RAW_HEADERS, zero_field_cols{}, + "", next_headers_m_id, w); + } + + finished_scan_id = ls.frame_id; +} + +bool ScanBatcher::operator()(const uint8_t* packet_buf, LidarScan& ls) { + return this->operator()(packet_buf, 0, ls); +} + +bool ScanBatcher::operator()(const uint8_t* packet_buf, uint64_t packet_ts, + LidarScan& ls) { + // backwards compatibility method + // induces an extra copy but this is a deprecated method + sensor::LidarPacket packet(pf.lidar_packet_size); + packet.host_timestamp = packet_ts; + std::memcpy(packet.buf.data(), packet_buf, packet.buf.size()); + return this->operator()(packet, ls); +} + FieldType::FieldType() {} FieldType::FieldType(const std::string& name_, diff --git a/ouster_client/src/logging.cpp b/ouster_client/src/logging.cpp index 16ea6a87..179e3b35 100644 --- a/ouster_client/src/logging.cpp +++ b/ouster_client/src/logging.cpp @@ -1,45 +1,56 @@ #include "ouster/impl/logging.h" +#include #include #include #include #include #include +#include #include +#include using namespace ouster::sensor::impl; const std::string Logger::logger_name{"ouster::sensor"}; +struct Logger::internal_logger { + std::unique_ptr logger_; + + internal_logger(const std::string& logger_name) + : logger_(std::make_unique( + logger_name, std::make_shared())) { + } + + void configure_generic_sink(spdlog::sink_ptr sink, + const std::string& log_level) { + // replace the logger sink with the new sink + logger_->sinks() = {sink}; + + auto ll = spdlog::level::from_str(log_level); + + logger_->set_level(ll); + logger_->flush_on(ll); + } + + ~internal_logger() {} +}; + Logger& Logger::instance() { static Logger logger_singleton; return logger_singleton; } -spdlog::logger& Logger::get_logger() { return *logger_; } - Logger::Logger() - : logger_(std::make_unique( - logger_name, std::make_shared())) { - logger_->set_level(spdlog::level::info); - logger_->flush_on(spdlog::level::info); -} - -void Logger::configure_generic_sink(spdlog::sink_ptr sink, - const std::string& log_level) { - // replace the logger sink with the new sink - logger_->sinks() = {sink}; - - auto ll = spdlog::level::from_str(log_level); - - logger_->set_level(ll); - logger_->flush_on(ll); + : internal_logger_(std::make_unique(logger_name)) { + internal_logger_->logger_->set_level(spdlog::level::info); + internal_logger_->logger_->flush_on(spdlog::level::info); } bool Logger::configure_stdout_sink(const std::string& log_level) { try { - configure_generic_sink( + internal_logger_->configure_generic_sink( std::make_shared(), log_level); } catch (const spdlog::spdlog_ex& ex) { std::cerr << logger_name << " init_logger failed: " << ex.what() @@ -64,7 +75,7 @@ bool Logger::configure_file_sink(const std::string& log_level, file_sink = std::make_shared( log_file_path, true); } - configure_generic_sink(file_sink, log_level); + internal_logger_->configure_generic_sink(file_sink, log_level); } catch (const spdlog::spdlog_ex& ex) { std::cerr << logger_name << " init_logger failed: " << ex.what() << std::endl; @@ -74,8 +85,98 @@ bool Logger::configure_file_sink(const std::string& log_level, return true; } +std::shared_ptr Logger::make_builder( + const std::string& format_string) { + return std::make_shared(format_string); +} + +struct Logger::format_builder { + ~format_builder() = default; + format_builder(const std::string& format_string) + : format_string(format_string){}; + + const std::string& format_string; + fmt::dynamic_format_arg_store store; +}; + +Logger::~Logger() {} + +std::string Logger::finalize_format_builder( + std::shared_ptr builder) { + return fmt::vformat(builder->format_string, builder->store); +} + +void Logger::log(LOG_LEVEL level, const std::string& msg) { + spdlog::level::level_enum level_out = spdlog::level::debug; + + switch (level) { + case Logger::LOG_LEVEL::LOG_TRACE: + level_out = spdlog::level::trace; + break; + case Logger::LOG_LEVEL::LOG_DEBUG: + level_out = spdlog::level::debug; + break; + case Logger::LOG_LEVEL::LOG_INFO: + level_out = spdlog::level::info; + break; + case Logger::LOG_LEVEL::LOG_WARN: + level_out = spdlog::level::warn; + break; + case Logger::LOG_LEVEL::LOG_ERROR: + level_out = spdlog::level::err; + break; + case Logger::LOG_LEVEL::LOG_CRITICAL: + level_out = spdlog::level::critical; + break; + }; + + internal_logger_->logger_->log(level_out, msg); +} + +#define LOGGER_PROCESS_ARG(SINGLE_LOGGER_PROCESS_ARG_type) \ + template void Logger::process_arg( \ + std::shared_ptr builder, \ + SINGLE_LOGGER_PROCESS_ARG_type data); + +LOGGER_PROCESS_ARG(std::string); +LOGGER_PROCESS_ARG(std::string&); +LOGGER_PROCESS_ARG(const std::string&); + +LOGGER_PROCESS_ARG(char*); +LOGGER_PROCESS_ARG(const char*); + +LOGGER_PROCESS_ARG(int8_t); +LOGGER_PROCESS_ARG(int16_t); +LOGGER_PROCESS_ARG(int32_t); +LOGGER_PROCESS_ARG(int64_t); +#if defined(__EMSCRIPTEN__) || defined(__APPLE__) || defined(WIN32) +LOGGER_PROCESS_ARG(long); +#endif + +LOGGER_PROCESS_ARG(uint8_t); +LOGGER_PROCESS_ARG(uint16_t); +LOGGER_PROCESS_ARG(uint32_t); +LOGGER_PROCESS_ARG(uint64_t); +#if defined(__EMSCRIPTEN__) || defined(__APPLE__) || defined(WIN32) +LOGGER_PROCESS_ARG(unsigned long); +#endif + +LOGGER_PROCESS_ARG(float); +LOGGER_PROCESS_ARG(double); +LOGGER_PROCESS_ARG(long double); + +LOGGER_PROCESS_ARG(bool); + +template +void Logger::process_arg(std::shared_ptr builder, + T data) { + builder->store.push_back(data); +} + namespace ouster { namespace sensor { -spdlog::logger& logger() { return Logger::instance().get_logger(); } +ouster::sensor::impl::Logger& logger() { + return ouster::sensor::impl::Logger::instance(); +} } // namespace sensor } // namespace ouster diff --git a/ouster_client/src/metadata.cpp b/ouster_client/src/metadata.cpp index 322a3330..75203a6f 100644 --- a/ouster_client/src/metadata.cpp +++ b/ouster_client/src/metadata.cpp @@ -8,6 +8,7 @@ #include "ouster/metadata.h" +#include #include #include #include @@ -63,6 +64,9 @@ ValidatorIssues::ValidatorEntry::ValidatorEntry(const std::string& path, const std::string& msg) : path(path), msg(msg) {} +ValidatorIssues::ValidatorEntry::ValidatorEntry(const ValidatorEntry& other) + : path(other.path), msg(other.msg) {} + std::string ValidatorIssues::ValidatorEntry::to_string() const { std::stringstream errorMessage; errorMessage << path << ": "; @@ -74,58 +78,57 @@ std::string ValidatorIssues::ValidatorEntry::to_string() const { const std::string& ValidatorIssues::ValidatorEntry::get_path() const { return path; } + const std::string& ValidatorIssues::ValidatorEntry::get_msg() const { return msg; } +std::string to_string(const ValidatorIssues::EntryList& list) { + std::stringstream output_string; + for (auto it : list) { + output_string << it.to_string() << std::endl; + } + return output_string.str(); +} + +std::string ValidatorIssues::to_string() const { + std::stringstream output_string; + if (critical.size() > 0) { + output_string << "Critical Issues:" << std::endl; + output_string << ouster::to_string(critical); + } + if (warning.size() > 0) { + output_string << "Warning Issues:" << std::endl; + output_string << ouster::to_string(warning); + } + if (information.size() > 0) { + output_string << "Information Issues:" << std::endl; + output_string << ouster::to_string(information); + } + return output_string.str(); +} + class MetadataImpl { - public: + protected: /** * Internal class for parsing and validating metadata. * * @param[in] root The root of the json object to parse and validate. * @param[out] result The resulting metadata parsed and validated. */ - MetadataImpl(const jsoncons::json& root, - ouster::sensor::sensor_info& sensor_info, - ValidatorIssues& issues) + MetadataImpl(const jsoncons::json& root, ValidatorIssues& issues) : root(root), - sensor_info(sensor_info), issues(issues), have_prod_line(false), prod_line_string("$.sensor_info.prod_line"), have_lidar_mode(false), lidar_mode_string("$.config_params.lidar_mode"), have_pixels_per_column(false), - pixels_per_column_string("$.lidar_data_format.pixels_per_column") { - parse_and_validate_sensor_info(); - parse_and_validate_config_params(); - // parse_and_validate_sensor_info must be run before - // parse_and_validate_data_format - // due to requirements on prod_line - // parse_and_validate_config_params must be run before - // parse_and_validate_data_format - // due to requirements on lidar_mode - parse_and_validate_data_format(); - parse_and_validate_calibration_status(); - // parse_and_validate_sensor_info must be run before - // parse_and_validate_data_format - // due to requirements on prod_line - // parse_and_validate_config_params must be run before - // parse_and_validate_data_format due to requirements on lidar_mode - // parse_and_validate_data_format must be run before - // parse_and_validate_intrinsics due to requirements on - // pixels_per_column - parse_and_validate_intrinsics(); - parse_and_validate_misc(); - } + pixels_per_column_string("$.lidar_data_format.pixels_per_column") {} - protected: // Data const jsoncons::json& root; ///< The json root - - ouster::sensor::sensor_info& sensor_info; ///< The output sensor info - ValidatorIssues& issues; ///< The validation output + ValidatorIssues& issues; ///< The validation output /** * Variable to keep track of the status of the prodline. @@ -182,9 +185,10 @@ class MetadataImpl { const std::string& cause_item, const std::string explanation = "") { std::stringstream errorMessage; - errorMessage << "Item \"" << item_skipped << "\" Skipped" - << " Due to failures with \"" << cause_item << "\" " - << explanation; + errorMessage << "Validation step for path: \"" << item_skipped + << "\" skipped" + << " due to failures validating path: \"" << cause_item + << "\"." << explanation; auto entry = ValidatorIssues::ValidatorEntry(item_skipped, errorMessage.str()); @@ -225,7 +229,7 @@ class MetadataImpl { */ void default_message(const std::string& path) { auto entry = ValidatorIssues::ValidatorEntry( - path, "Item not found, using defaults"); + path, "Metadata entry not found (" + path + "), using defaults"); issues.information.push_back(entry); } @@ -257,7 +261,8 @@ class MetadataImpl { if (zeros == data.size()) { std::stringstream errorMessage; - errorMessage << "Expected at least some non-zero values in path"; + errorMessage + << "Expected at least some non-zero values in metadata array"; auto entry = ValidatorIssues::ValidatorEntry(path, errorMessage.str()); @@ -287,7 +292,7 @@ class MetadataImpl { } else { std::stringstream errorMessage; errorMessage << "String that was expected to contain data" - << " was empty."; + << " was empty"; auto entry = ValidatorIssues::ValidatorEntry(path, errorMessage.str()); @@ -376,6 +381,8 @@ class MetadataImpl { if (value.is() || (relaxed_number_verification && value.is_number() && std::is_arithmetic::value)) { + // Warning: ran into a really weird argument swapping issue here + // we think it was related to ABI issues. output = value.as(); bool temp_result = verification_callback(severity, path, value.as()); @@ -396,7 +403,7 @@ class MetadataImpl { } } else { std::stringstream errorMessage; - errorMessage << "Expected One Item In Query, " + errorMessage << "Expected One Item In Data, " << "Number Of Items: " << value_array.size() << " Values: \"" << value_array << "\""; auto entry = @@ -529,8 +536,8 @@ class MetadataImpl { bool result = (index == matches && matches > 0); if (verify_count > 0 && matches != verify_count) { std::stringstream errorMessage; - errorMessage << "Invalid array, got " << index << " items, " - << matches << " matching items," + errorMessage << "Invalid metadata array, got " << index + << " items, " << matches << " matching items," << " was expecting " << verify_count << " matching items"; severity.push_back( @@ -586,8 +593,8 @@ class MetadataImpl { bool result = (index == matches && matches > 0); if (verify_count > 0 && matches != verify_count) { std::stringstream errorMessage; - errorMessage << "Invalid array, got " << index << " items, " - << matches << " matching items," + errorMessage << "Invalid metadata array, got " << index + << " items, " << matches << " matching items," << " was expecting " << verify_count << " matching items"; severity.push_back( @@ -625,7 +632,7 @@ class MetadataImpl { output = f(data); } catch (std::exception& e) { std::stringstream errorMessage; - errorMessage << "Failed To Parse: " << data + errorMessage << "Failed To Parse Enum: " << data << " Error Message: \"" << e.what() << "\""; severity.push_back( ValidatorIssues::ValidatorEntry(path, errorMessage.str())); @@ -694,7 +701,7 @@ class MetadataImpl { verify_string_not_empty)) { std::istringstream date_data(data); std::tm t = {}; - date_data.imbue(std::locale("en_US.utf-8")); + date_data.imbue(std::locale("C")); date_data >> std::get_time(&t, date_format.c_str()); if (date_data.fail()) { std::stringstream errorMessage; @@ -744,7 +751,8 @@ class MetadataImpl { } // Sections - void parse_and_validate_sensor_info() { + void parse_and_validate_sensor_info( + ouster::sensor::sensor_info& sensor_info) { parse_and_validate_datetime(issues.information, "$.sensor_info.build_date", "%Y-%m-%dT%TZ", sensor_info.build_date); @@ -774,82 +782,106 @@ class MetadataImpl { parse_and_validate_item(issues.information, "$.sensor_info.prod_pn", sensor_info.prod_pn, verify_string_not_empty); + std::string sn_string; parse_and_validate_item(issues.information, "$.sensor_info.prod_sn", - sensor_info.sn, verify_string_not_empty); + sn_string, verify_string_not_empty); + char* end; + sensor_info.sn = std::strtoull(sn_string.c_str(), &end, 10); + const char* expected_end = sn_string.c_str() + sn_string.length(); + if (end != expected_end) { + std::stringstream errorMessage; + errorMessage << "prod_sn not a valid integer string: \""; + errorMessage << sn_string << "\""; + + auto entry = ValidatorIssues::ValidatorEntry( + "$.sensor_info.prod_sn", errorMessage.str()); + issues.information.push_back(entry); + } parse_and_validate_item(issues.information, "$.sensor_info.status", sensor_info.status, verify_string_not_empty); } - void parse_and_validate_config_params() { + void parse_and_validate_config_params( + ouster::sensor::sensor_config& config) { std::vector azimuth_window_data; if (parse_and_validate_item( issues.information, "$.config_params.azimuth_window.*", azimuth_window_data, 2, make_verify_in_bounds(0, 360000))) { - sensor_info.config.azimuth_window = {azimuth_window_data[0], - azimuth_window_data[1]}; + config.azimuth_window = {azimuth_window_data[0], + azimuth_window_data[1]}; } parse_and_validate_item(issues.information, "$.config_params.columns_per_packet", - sensor_info.config.columns_per_packet); + config.columns_per_packet); if (parse_and_validate_enum( - issues.information, lidar_mode_string, - sensor_info.config.lidar_mode, sensor::lidar_mode_of_string)) { + issues.information, lidar_mode_string, config.lidar_mode, + sensor::lidar_mode_of_string)) { have_lidar_mode = true; } parse_and_validate_enum( issues.information, "$.config_params.multipurpose_io_mode", - sensor_info.config.multipurpose_io_mode, + config.multipurpose_io_mode, ouster::sensor::multipurpose_io_mode_of_string); parse_and_validate_enum( issues.information, "$.config_params.nmea_baud_rate", - sensor_info.config.nmea_baud_rate, - ouster::sensor::nmea_baud_rate_of_string); + config.nmea_baud_rate, ouster::sensor::nmea_baud_rate_of_string); uint64_t nmea_ignore_valid_char; if (parse_and_validate_item(issues.information, "$.config_params.nmea_ignore_valid_char", nmea_ignore_valid_char)) { - sensor_info.config.nmea_ignore_valid_char = - (nmea_ignore_valid_char != 0); + config.nmea_ignore_valid_char = (nmea_ignore_valid_char != 0); } parse_and_validate_enum( issues.information, "$.config_params.nmea_in_polarity", - sensor_info.config.nmea_in_polarity, - ouster::sensor::polarity_of_string); + config.nmea_in_polarity, ouster::sensor::polarity_of_string); parse_and_validate_item(issues.information, "$.config_params.nmea_leap_seconds", - sensor_info.config.nmea_leap_seconds, true); + config.nmea_leap_seconds, true); const std::string operating_mode_string = "$.config_params.operating_mode"; if (!parse_and_validate_enum( issues.information, operating_mode_string, - sensor_info.config.operating_mode, + config.operating_mode, ouster::sensor::operating_mode_of_string)) { const std::string auto_start_flag_string = "$.config_params.auto_start_flag"; bool auto_start_flag; + int auto_start_int; + std::string auto_start_flag_deprecation = + "Please note that auto_start_flag has been deprecated in " + "favor " + "of operating_mode. Will set operating_mode " + "appropriately..."; if (parse_and_validate_item(issues.information, auto_start_flag_string, auto_start_flag)) { auto entry = ValidatorIssues::ValidatorEntry( - auto_start_flag_string, - "Please note that auto_start_flag has been deprecated in " - "favor " - "of operating_mode. Will set operating_mode " - "appropriately..."); + auto_start_flag_string, auto_start_flag_deprecation); issues.information.push_back(entry); - sensor_info.config.operating_mode = - auto_start_flag ? sensor::OPERATING_NORMAL - : sensor::OPERATING_STANDBY; + config.operating_mode = auto_start_flag + ? sensor::OPERATING_NORMAL + : sensor::OPERATING_STANDBY; + } else if (parse_and_validate_item(issues.information, + auto_start_flag_string, + auto_start_int, true)) { + auto entry = ValidatorIssues::ValidatorEntry( + auto_start_flag_string, auto_start_flag_deprecation); + issues.information.push_back(entry); + auto_start_flag = (auto_start_int != 0); + config.operating_mode = auto_start_flag + ? sensor::OPERATING_NORMAL + : sensor::OPERATING_STANDBY; + } else { default_message(operating_mode_string); } @@ -857,108 +889,100 @@ class MetadataImpl { parse_and_validate_item(issues.information, "$.config_params.phase_lock_enable", - sensor_info.config.phase_lock_enable); + config.phase_lock_enable); parse_and_validate_item(issues.information, "$.config_params.phase_lock_offset", - sensor_info.config.phase_lock_offset, true); + config.phase_lock_offset, true); const std::string signal_multiplier_string = "$.config_params.signal_multiplier"; - if (parse_and_validate_item( - issues.information, signal_multiplier_string, - sensor_info.config.signal_multiplier, true)) { + if (parse_and_validate_item(issues.information, + signal_multiplier_string, + config.signal_multiplier, true)) { try { ouster::sensor::check_signal_multiplier( - *sensor_info.config.signal_multiplier); + *config.signal_multiplier); } catch (std::runtime_error& e) { auto entry = ValidatorIssues::ValidatorEntry( signal_multiplier_string, e.what()); - issues.information.push_back(entry); + issues.critical.push_back(entry); } } parse_and_validate_enum( issues.information, "$.config_params.sync_pulse_in_polarity", - sensor_info.config.sync_pulse_in_polarity, - ouster::sensor::polarity_of_string); + config.sync_pulse_in_polarity, ouster::sensor::polarity_of_string); parse_and_validate_item(issues.information, "$.config_params.sync_pulse_out_angle", - sensor_info.config.sync_pulse_out_angle, + config.sync_pulse_out_angle, make_verify_in_bounds(0, 360), true); - parse_and_validate_item( - issues.information, "$.config_params.sync_pulse_out_frequency", - sensor_info.config.sync_pulse_out_frequency, true); + parse_and_validate_item(issues.information, + "$.config_params.sync_pulse_out_frequency", + config.sync_pulse_out_frequency, true); parse_and_validate_enum( issues.information, "$.config_params.sync_pulse_out_polarity", - sensor_info.config.sync_pulse_out_polarity, - ouster::sensor::polarity_of_string); + config.sync_pulse_out_polarity, ouster::sensor::polarity_of_string); - parse_and_validate_item( - issues.information, "$.config_params.sync_pulse_out_pulse_width", - sensor_info.config.sync_pulse_out_pulse_width, true); + parse_and_validate_item(issues.information, + "$.config_params.sync_pulse_out_pulse_width", + config.sync_pulse_out_pulse_width, true); parse_and_validate_enum( issues.information, "$.config_params.timestamp_mode", - sensor_info.config.timestamp_mode, - ouster::sensor::timestamp_mode_of_string); + config.timestamp_mode, ouster::sensor::timestamp_mode_of_string); if (!parse_and_validate_item( - issues.information, "$.config_params.udp_dest", - sensor_info.config.udp_dest, verify_string_not_empty)) { - parse_and_validate_item( - issues.information, "$.config_params.udp_ip", - sensor_info.config.udp_dest, verify_string_not_empty); + issues.information, "$.config_params.udp_dest", config.udp_dest, + verify_string_not_empty)) { + parse_and_validate_item(issues.information, + "$.config_params.udp_ip", config.udp_dest, + verify_string_not_empty); } - parse_and_validate_item(issues.information, - "$.config_params.udp_port_imu", - sensor_info.config.udp_port_imu, - make_verify_in_bounds(0, 65535)); + parse_and_validate_item( + issues.information, "$.config_params.udp_port_imu", + config.udp_port_imu, make_verify_in_bounds(0, 65535)); - parse_and_validate_item(issues.information, - "$.config_params.udp_port_lidar", - sensor_info.config.udp_port_lidar, - make_verify_in_bounds(0, 65535)); + parse_and_validate_item( + issues.information, "$.config_params.udp_port_lidar", + config.udp_port_lidar, make_verify_in_bounds(0, 65535)); parse_and_validate_enum( issues.information, "$.config_params.udp_profile_imu", - sensor_info.config.udp_profile_imu, - ouster::sensor::udp_profile_imu_of_string); + config.udp_profile_imu, ouster::sensor::udp_profile_imu_of_string); parse_and_validate_enum( issues.information, "$.config_params.udp_profile_lidar", - sensor_info.config.udp_profile_lidar, + config.udp_profile_lidar, ouster::sensor::udp_profile_lidar_of_string); parse_and_validate_enum( - issues.information, "$.config_params.gyro_fsr", - sensor_info.config.gyro_fsr, + issues.information, "$.config_params.gyro_fsr", config.gyro_fsr, ouster::sensor::full_scale_range_of_string); parse_and_validate_enum( - issues.information, "$.config_params.accel_fsr", - sensor_info.config.accel_fsr, + issues.information, "$.config_params.accel_fsr", config.accel_fsr, ouster::sensor::full_scale_range_of_string); parse_and_validate_enum( issues.information, "$.config_params.return_order", - sensor_info.config.return_order, - ouster::sensor::return_order_of_string); + config.return_order, ouster::sensor::return_order_of_string); parse_and_validate_item(issues.information, "$.config_params.min_range_threshold_cm", - sensor_info.config.min_range_threshold_cm); + config.min_range_threshold_cm); } // parse_and_validate_sensor_info must be run before // parse_and_validate_data_format due to requirements on prod_line // parse_and_validate_config_params must be run before // parse_and_validate_data_format due to requirements on lidar_mode - void parse_and_validate_data_format() { + void parse_and_validate_data_format( + ouster::sensor::sensor_info& sensor_info) { if (have_lidar_mode) { // lidar mode is present, create default data format sensor_info.format = ouster::sensor::default_data_format( @@ -992,9 +1016,10 @@ class MetadataImpl { } else { // lidar mode not present but columns_per_frame available, // nothing to match - skipped_due_to_item(issues.information, - columns_per_frame_string, - lidar_mode_string); + skipped_due_to_item( + issues.information, columns_per_frame_string, + lidar_mode_string, + "Lidar mode not found, can't verify columns per frame"); } } else { // need either lidar mode or columns_per_frame @@ -1013,7 +1038,8 @@ class MetadataImpl { if (!columns_per_frame_success) { skipped_due_to_item(issues.information, column_window_string, columns_per_frame_string, - "Couldnt verify bounds on column window data"); + "Columns per frame not found, can't verify " + "numeric bounds on column window"); } auto column_window_callback = [&](ValidatorIssues::EntryList& severity, const std::string& path, @@ -1087,7 +1113,9 @@ class MetadataImpl { } else { skipped_due_to_item(issues.information, pixel_shift_string + ".length()", - prod_line_string); + prod_line_string, + "Product line not found, can't verify size of " + "pixel shift array"); } parse_and_validate_item(issues.information, pixel_shift_string, sensor_info.format.pixel_shift_by_row, @@ -1118,13 +1146,15 @@ class MetadataImpl { frequency_of_lidar_mode(*sensor_info.config.lidar_mode); default_message(fps_string); } else { - skipped_due_to_item(issues.information, fps_string, - lidar_mode_string); + skipped_due_to_item( + issues.information, fps_string, lidar_mode_string, + "Lidar mode not found, can't verify FPS value"); } } } - void parse_and_validate_calibration_status() { + void parse_and_validate_calibration_status( + ouster::sensor::sensor_info& sensor_info) { parse_and_validate_datetime( issues.information, "$.calibration_status.reflectivity.timestamp", "%Y-%m-%dT%T", sensor_info.cal.reflectivity_timestamp); @@ -1195,7 +1225,7 @@ class MetadataImpl { } if (count != height) { auto entry = ValidatorIssues::ValidatorEntry( - path, "Each sub-array must have " + + path, "Each beam angle sub-array must have " + std::to_string(height) + " elements."); issues.critical.push_back(entry); return; @@ -1217,7 +1247,7 @@ class MetadataImpl { if (is_doubles || is_arrays) { if (angles.size() != width) { auto entry = ValidatorIssues::ValidatorEntry( - path, "Must have " + std::to_string(width) + + path, "Must have beam angle " + std::to_string(width) + " elements. Had " + std::to_string(angles.size()) + " elements."); issues.critical.push_back(entry); @@ -1226,7 +1256,7 @@ class MetadataImpl { } else { // zero size auto entry = ValidatorIssues::ValidatorEntry( - path, "Cannot be empty array."); + path, "Cannot be empty beam angle array."); issues.critical.push_back(entry); return; } @@ -1235,7 +1265,8 @@ class MetadataImpl { verify_all_not_zero(issues.warning, path, output); } else { // error - auto entry = ValidatorIssues::ValidatorEntry(path, "Missing."); + auto entry = + ValidatorIssues::ValidatorEntry(path, "Missing beam angle."); issues.warning.push_back(entry); } } @@ -1246,7 +1277,8 @@ class MetadataImpl { // parse_and_validate_data_format due to requirements on lidar_mode // parse_and_validate_data_format must be run before // parse_and_validate_intrinsics due to requirements on pixels_per_column - void parse_and_validate_intrinsics() { + void parse_and_validate_intrinsics( + ouster::sensor::sensor_info& sensor_info) { std::vector imu_intrinsics_data; const std::string imu_intrinsics_string = "$.imu_intrinsics.imu_to_sensor_transform.*"; @@ -1313,7 +1345,7 @@ class MetadataImpl { } } - void parse_and_validate_misc() { + void parse_and_validate_misc(ouster::sensor::sensor_info& sensor_info) { std::vector extrinsic_data; const std::string extrinsic_string = "$.'ouster-sdk'.extrinsic.*"; if (parse_and_validate_item(issues.information, @@ -1330,6 +1362,44 @@ class MetadataImpl { } }; +class SensorInfoImpl : public MetadataImpl { + public: + SensorInfoImpl(const jsoncons::json& root, + ouster::sensor::sensor_info& sensor_info, + ValidatorIssues& issues) + : MetadataImpl(root, issues) { + parse_and_validate_sensor_info(sensor_info); + parse_and_validate_config_params(sensor_info.config); + // parse_and_validate_sensor_info must be run before + // parse_and_validate_data_format + // due to requirements on prod_line + // parse_and_validate_config_params must be run before + // parse_and_validate_data_format + // due to requirements on lidar_mode + parse_and_validate_data_format(sensor_info); + parse_and_validate_calibration_status(sensor_info); + // parse_and_validate_sensor_info must be run before + // parse_and_validate_data_format + // due to requirements on prod_line + // parse_and_validate_config_params must be run before + // parse_and_validate_data_format due to requirements on lidar_mode + // parse_and_validate_data_format must be run before + // parse_and_validate_intrinsics due to requirements on + // pixels_per_column + parse_and_validate_intrinsics(sensor_info); + parse_and_validate_misc(sensor_info); + } +}; + +class ConfigImpl : public MetadataImpl { + public: + ConfigImpl(const jsoncons::json& root, + ouster::sensor::sensor_config& config, ValidatorIssues& issues) + : MetadataImpl(root, issues) { + parse_and_validate_config_params(config); + } +}; + const std::map nonlegacy_metadata_fields = { {"sensor_info", true}, {"beam_intrinsics", true}, {"imu_intrinsics", true}, {"lidar_intrinsics", true}, @@ -1355,8 +1425,9 @@ bool is_new_format(const jsoncons::json& root) { return nonlegacy_fields_present == nonlegacy_metadata_fields.size(); } -jsoncons::json convert_legacy_to_nonlegacy(jsoncons::json& root) { +jsoncons::json convert_legacy_to_nonlegacy(const jsoncons::json& root) { jsoncons::json result; + std::vector skip; // just convert to non-legacy and run the non-legacy parse const std::vector config_fields{ @@ -1377,57 +1448,58 @@ jsoncons::json convert_legacy_to_nonlegacy(jsoncons::json& root) { if (root.contains("lidar_to_sensor_transform")) { result["lidar_intrinsics"]["lidar_to_sensor_transform"] = root.at("lidar_to_sensor_transform"); - root.erase("lidar_to_sensor_transform"); + skip.push_back("lidar_to_sensor_transform"); } if (root.contains("imu_to_sensor_transform")) { result["imu_intrinsics"]["imu_to_sensor_transform"] = root.at("imu_to_sensor_transform"); - root.erase("imu_to_sensor_transform"); + skip.push_back("imu_to_sensor_transform"); } + if (root.contains("data_format")) { result["lidar_data_format"] = root.at("data_format"); - root.erase("data_format"); + skip.push_back("data_format"); } if (root.contains("client_version")) { result["ouster-sdk"]["client_version"] = root.at("client_version"); - root.erase("client_version"); + skip.push_back("client_version"); } for (const auto& field : config_fields) { if (root.contains(field)) { result["config_params"][field] = root.at(field); - root.erase(field); + skip.push_back(field); } } for (const auto& field : beam_intrinsics_fields) { if (root.contains(field)) { result["beam_intrinsics"][field] = root.at(field); - root.erase(field); + skip.push_back(field); } } for (const auto& field : sensor_info_fields) { if (root.contains(field)) { result["sensor_info"][field] = root.at(field); - root.erase(field); + skip.push_back(field); } } for (const auto& it : root.object_range()) { - result[it.key()] = it.value(); + if (std::find(skip.begin(), skip.end(), it.key()) == skip.end()) { + result[it.key()] = it.value(); + } } return result; } -bool parse_and_validate_metadata(const std::string& json_data, +bool parse_and_validate_metadata(const jsoncons::json& root, ouster::sensor::sensor_info& sensor_info, ValidatorIssues& issues) { - auto root = jsoncons::json::parse(json_data); - size_t nonlegacy_fields_present = 0; std::vector missing_fields; for (const auto& field_pair : nonlegacy_metadata_fields) { @@ -1442,9 +1514,12 @@ bool parse_and_validate_metadata(const std::string& json_data, } if (nonlegacy_fields_present != nonlegacy_metadata_fields.size()) { - root = convert_legacy_to_nonlegacy(root); + SensorInfoImpl impl(convert_legacy_to_nonlegacy(root), sensor_info, + issues); + } else { + SensorInfoImpl impl(root, sensor_info, issues); } - MetadataImpl impl(root, sensor_info, issues); + if (nonlegacy_fields_present > 0 && nonlegacy_fields_present < nonlegacy_metadata_fields.size()) { for (auto it : missing_fields) { @@ -1473,6 +1548,13 @@ bool parse_and_validate_metadata(const std::string& json_data, return issues.critical.size() == 0; } +bool parse_and_validate_metadata(const std::string& json_data, + ouster::sensor::sensor_info& sensor_info, + ValidatorIssues& issues) { + jsoncons::json data = jsoncons::json::parse(json_data); + return parse_and_validate_metadata(data, sensor_info, issues); +} + bool parse_and_validate_metadata(const std::string& json_data, ValidatorIssues& issues) { nonstd::optional sensor_info; @@ -1495,4 +1577,31 @@ bool parse_and_validate_metadata( return result; } + +bool parse_and_validate_config(const std::string& json_data, + ouster::sensor::sensor_config& config_out, + ValidatorIssues& issues) { + jsoncons::json root; + root["config_params"] = jsoncons::json::parse(json_data); + + ConfigImpl impl(root, config_out, issues); + + return issues.critical.size() == 0; +} + +bool parse_and_validate_config(const std::string& json_data, + ouster::sensor::sensor_config& sensor_config) { + ValidatorIssues issues; + return parse_and_validate_config(json_data, sensor_config, issues); +} + +namespace sensor { +sensor_config parse_config(const std::string& config) { + ouster::sensor::sensor_config sensor_config; + parse_and_validate_config(config, sensor_config); + + return sensor_config; +} +} // namespace sensor + }; // namespace ouster diff --git a/ouster_client/src/packet.cpp b/ouster_client/src/packet.cpp new file mode 100644 index 00000000..c41fdef8 --- /dev/null +++ b/ouster_client/src/packet.cpp @@ -0,0 +1,84 @@ +#include "ouster/packet.h" + +namespace ouster { +namespace sensor { +Packet::Packet(PacketType type) : type_(type), host_timestamp{0} {} + +Packet::Packet(PacketType type, int size) : type_(type), host_timestamp{0} { + // this is necessary due to how client works - it may read size() + 1 + // bytes into the packet in case of rogue packet coming through + buf.reserve(size + 1); + buf.resize(size, 0); +} + +PacketValidationFailure validate_packet( + const sensor_info& info, const ouster::sensor::packet_format& format, + const uint8_t* buf, uint64_t buf_size, PacketType type) { + // Check if we need to guess the type + if (type == PacketType::Unknown) { + if (buf_size == format.imu_packet_size) { + type = PacketType::Imu; + } else { + type = PacketType::Lidar; + } + } + + if (type == PacketType::Lidar) { + if (buf_size != format.lidar_packet_size) { + return PacketValidationFailure::PACKET_SIZE; + } + + auto init_id = format.init_id(buf); + if (info.init_id != 0 && init_id != 0 && init_id != info.init_id) { + return PacketValidationFailure::ID; + } + + if (info.sn != 0) { + uint64_t p_sn = format.prod_sn(buf); + if ((p_sn != 0) && (p_sn != info.sn)) { + return PacketValidationFailure::ID; + } + } + return PacketValidationFailure::NONE; + } else if (type == PacketType::Imu) { + if (buf_size != format.imu_packet_size) { + return PacketValidationFailure::PACKET_SIZE; + } + return PacketValidationFailure::NONE; + } + return PacketValidationFailure::NONE; +}; + +LidarPacket::LidarPacket() : Packet{MyType} {} + +LidarPacket::LidarPacket(int size) : Packet{MyType, size} {} + +PacketValidationFailure LidarPacket::validate( + const sensor_info& info, + const ouster::sensor::packet_format& format) const { + return validate_packet(info, format, buf.data(), buf.size(), + PacketType::Lidar); +} + +PacketValidationFailure LidarPacket::validate(const sensor_info& info) const { + return validate_packet(info, *format, buf.data(), buf.size(), + PacketType::Lidar); +} + +ImuPacket::ImuPacket() : Packet{MyType} {} + +ImuPacket::ImuPacket(int size) : Packet{MyType, size} {} + +PacketValidationFailure ImuPacket::validate( + const sensor_info& info, + const ouster::sensor::packet_format& format) const { + return validate_packet(info, format, buf.data(), buf.size(), + PacketType::Imu); +} + +PacketValidationFailure ImuPacket::validate(const sensor_info& info) const { + return validate_packet(info, *format, buf.data(), buf.size(), + PacketType::Imu); +} +} // namespace sensor +} // namespace ouster diff --git a/ouster_client/src/parsing.cpp b/ouster_client/src/parsing.cpp index cd28c37a..1ea9e37d 100644 --- a/ouster_client/src/parsing.cpp +++ b/ouster_client/src/parsing.cpp @@ -153,6 +153,7 @@ static int count_set_bits(uint64_t value) { return count; }; +OUSTER_API_FUNCTION uint64_t get_value_mask(const FieldInfo& f) { uint64_t type_mask = sensor::field_type_mask(f.ty_tag); @@ -166,6 +167,7 @@ uint64_t get_value_mask(const FieldInfo& f) { return mask; } +OUSTER_API_FUNCTION int get_bitness(const FieldInfo& f) { return count_set_bits(get_value_mask(f)); } @@ -251,20 +253,26 @@ static const Table fusa_two_word_pixel_info{{ {ChanField::RAW32_WORD2, field_info(32, 32)}, }}; -Table profiles{{ - {UDPProfileLidar::PROFILE_LIDAR_LEGACY, - {legacy_field_info.data(), legacy_field_info.size(), 12}}, - {UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16_DUAL, - {dual_field_info.data(), dual_field_info.size(), 16}}, - {UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16, - {single_field_info.data(), single_field_info.size(), 12}}, - {UDPProfileLidar::PROFILE_RNG15_RFL8_NIR8, - {lb_field_info.data(), lb_field_info.size(), 4}}, - {UDPProfileLidar::PROFILE_FIVE_WORD_PIXEL, - {five_word_pixel_info.data(), five_word_pixel_info.size(), 20}}, - {UDPProfileLidar::PROFILE_FUSA_RNG15_RFL8_NIR8_DUAL, - {fusa_two_word_pixel_info.data(), fusa_two_word_pixel_info.size(), 8}}, -}}; +Table OUSTER_API_FUNCTION + profiles{{ + {UDPProfileLidar::PROFILE_LIDAR_LEGACY, + {legacy_field_info.data(), legacy_field_info.size(), 12}}, + {UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16_DUAL, + {dual_field_info.data(), dual_field_info.size(), 16}}, + {UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16, + {single_field_info.data(), single_field_info.size(), 12}}, + {UDPProfileLidar::PROFILE_RNG15_RFL8_NIR8, + {lb_field_info.data(), lb_field_info.size(), 4}}, + {UDPProfileLidar::PROFILE_FIVE_WORD_PIXEL, + {five_word_pixel_info.data(), five_word_pixel_info.size(), 20}}, + {UDPProfileLidar::PROFILE_FUSA_RNG15_RFL8_NIR8_DUAL, + {fusa_two_word_pixel_info.data(), fusa_two_word_pixel_info.size(), 8}}, + }}; + +OUSTER_API_FUNCTION Table +get_profiles() { + return profiles; +} static const ProfileEntry& lookup_profile_entry(UDPProfileLidar profile) { auto end = profiles.end(); @@ -819,6 +827,7 @@ int packet_format::field_bitness(const std::string& i) const { /* packet_writer implementation */ namespace impl { +packet_writer::packet_writer(const packet_format& pf) : packet_format(pf) {} uint8_t* packet_writer::nth_col(int n, uint8_t* lidar_buf) const { return const_cast(packet_format::nth_col(n, lidar_buf)); @@ -853,9 +862,9 @@ void packet_writer::set_init_id(uint8_t* lidar_buf, uint32_t init_id) const { impl_->init_id_info.set(lidar_buf, init_id); } -void packet_writer::set_packet_type(uint8_t* lidar_buf, +void packet_writer::set_packet_type(uint8_t* packet_buf, uint16_t packet_type) const { - impl_->packet_type_info.set(lidar_buf, packet_type); + impl_->packet_type_info.set(packet_buf, packet_type); } void packet_writer::set_prod_sn(uint8_t* lidar_buf, uint64_t sn) const { diff --git a/ouster_client/src/sensor_client.cpp b/ouster_client/src/sensor_client.cpp index 2991586f..99dcf193 100644 --- a/ouster_client/src/sensor_client.cpp +++ b/ouster_client/src/sensor_client.cpp @@ -5,19 +5,26 @@ #include "ouster/sensor_client.h" +#include + #include "ouster/defaults.h" #include "ouster/impl/logging.h" +#include "ouster/metadata.h" using ouster::sensor::impl::Logger; using ouster::sensor::util::SensorHttp; namespace ouster { +bool parse_and_validate_metadata(const jsoncons::json& json_data, + ouster::sensor::sensor_info& sensor_info, + ValidatorIssues& issues); namespace sensor { // External imports of internal methods SOCKET udp_data_socket(int port); int32_t get_sock_port(SOCKET sock_fd); -Json::Value collect_metadata(SensorHttp& sensor_http, int timeout_sec); +jsoncons::json collect_metadata(SensorHttp& sensor_http, int timeout_sec); + SOCKET mtp_data_socket(int port, const std::vector& udp_dest_hosts, const std::string& mtp_dest_host = ""); bool set_config(SensorHttp& sensor_http, const sensor_config& config, @@ -47,8 +54,16 @@ Sensor::Sensor(const std::string& hostname, const sensor_config& config) : hostname_(hostname), config_(config) {} sensor_info Sensor::fetch_metadata(int timeout) const { - Json::FastWriter writer; - return sensor_info(writer.write(collect_metadata(*http_client(), timeout))); + auto data = collect_metadata(*http_client(), timeout); + + sensor_info result; + ValidatorIssues issues; + ouster::parse_and_validate_metadata(data, result, issues); + if (issues.critical.size() > 0) { + throw std::runtime_error(to_string(issues.critical)); + } + + return result; } std::shared_ptr Sensor::http_client() const { @@ -226,7 +241,7 @@ SensorClient::SensorClient(const std::vector& sensors, for (const auto& info : sensor_info_) { ports[info.config.udp_port_lidar.value()] = true; ports[info.config.udp_port_imu.value()] = true; - formats_.push_back(packet_format(info)); + formats_.push_back(std::make_shared(info)); } // now open sockets @@ -262,8 +277,8 @@ void SensorClient::start_buffer_thread(double buffer_time) { const uint64_t buffer_ns = buffer_time * 1000000000.0; while (do_buffer_) { uint64_t ts; - ClientEvent ev = get_packet_internal(data, ts, 0.01); - if (ev.type == ClientEvent::PollTimeout) { + InternalEvent ev = get_packet_internal(data, ts, 0.01); + if (ev.event_type == ClientEvent::PollTimeout) { continue; } // Enqueue received packets @@ -273,6 +288,8 @@ void SensorClient::start_buffer_thread(double buffer_time) { be.event = ev; be.timestamp = ts; std::swap(be.data, data); + be.data.shrink_to_fit(); // can save a lot of memory, though + // does force a copy buffer_.push_back(std::move(be)); // Discard old buffered packets if our consumer couldn't keep up @@ -317,16 +334,16 @@ size_t SensorClient::buffer_size() { return 0; } -ClientEvent SensorClient::get_packet_internal(std::vector& data, - uint64_t& ts, - double timeout_sec) { +SensorClient::InternalEvent SensorClient::get_packet_internal( + std::vector& data, uint64_t& ts, double timeout_sec) { if (sockets_.size() == 0) { auto now = std::chrono::system_clock::now(); auto now_ts = std::chrono::duration_cast( now.time_since_epoch()) .count(); ts = now_ts; - return {-1, ClientEvent::Exit}; // someone called us while shut down + return {-1, PacketType::Unknown, + ClientEvent::Exit}; // someone called us while shut down } // setup poll SOCKET max_fd = 0; @@ -349,19 +366,18 @@ ClientEvent SensorClient::get_packet_internal(std::vector& data, now.time_since_epoch()) .count(); if (ret == 0) { - return {-1, ClientEvent::PollTimeout}; + return {-1, PacketType::Unknown, ClientEvent::PollTimeout}; } else if (ret < 0) { - return {-1, ClientEvent::Error}; + return {-1, PacketType::Unknown, ClientEvent::Error}; } struct sockaddr_storage from_addr; socklen_t addr_len = sizeof(from_addr); - char buffer[65535]; // this isnt great, but otherwise have to reserve this - // much in every packet + data.resize(65535); // need enough room for maximum possible packet size for (auto sock : sockets_) { if (!FD_ISSET(sock, &fds)) continue; - auto size = recvfrom(sock, buffer, 65535, 0, + auto size = recvfrom(sock, (char*)data.data(), 65535, 0, (struct sockaddr*)&from_addr, &addr_len); if (size <= 0) continue; // this is unexpected @@ -387,33 +403,30 @@ ClientEvent SensorClient::get_packet_internal(std::vector& data, } if (source == -1) { // if we got a random packet, just say we got nothing - return {-1, ClientEvent::PollTimeout}; + return {-1, PacketType::Unknown, ClientEvent::PollTimeout}; } // detect packet type by size - const int imu_size = formats_[source].imu_packet_size; + const int imu_size = formats_[source]->imu_packet_size; if (size > imu_size) { data.resize(size); - memcpy(data.data(), buffer, size); - return {(int)source, ClientEvent::LidarPacket}; + return {source, PacketType::Lidar, ClientEvent::Packet}; } else if (size == imu_size) { data.resize(size); - memcpy(data.data(), buffer, size); - return {(int)source, ClientEvent::ImuPacket}; + return {source, PacketType::Imu, ClientEvent::Packet}; } else { // The sensor returned an invalid packet size, say we got nothing - return {-1, ClientEvent::PollTimeout}; + return {-1, PacketType::Unknown, ClientEvent::PollTimeout}; } } - return {-1, ClientEvent::Error}; // this shouldnt happen + return {-1, PacketType::Unknown, + ClientEvent::Error}; // this shouldnt happen } -ClientEvent SensorClient::get_packet(LidarPacket& lp, ImuPacket& ip, - double timeout_sec) { +ClientEvent SensorClient::get_packet(double timeout_sec) { // poll on all our sockets - ClientEvent ev; + InternalEvent ev; uint64_t ts; - std::vector data; if (do_buffer_) { std::unique_lock lock(buffer_mutex_); // if the buffer if empty, wait for a new event @@ -423,28 +436,38 @@ ClientEvent SensorClient::get_packet(LidarPacket& lp, ImuPacket& ip, // check for timeout or for spurious wakeup of "wait_for" // by checking whether the buffer is empty if (res == std::cv_status::timeout || buffer_.empty()) { - return {-1, ClientEvent::PollTimeout}; + return ClientEvent(0, -1, ClientEvent::PollTimeout); } } // dequeue auto& buf = buffer_.front(); ev = buf.event; ts = buf.timestamp; - std::swap(data, buf.data); + std::swap(staging_buffer, buf.data); buffer_.pop_front(); lock.unlock(); // unlock asap } else { - ev = get_packet_internal(data, ts, timeout_sec); + ev = get_packet_internal(staging_buffer, ts, timeout_sec); } - if (ev.type == ClientEvent::LidarPacket) { - lp.host_timestamp = ts; - std::swap(data, lp.buf); - } else if (ev.type == ClientEvent::ImuPacket) { - ip.host_timestamp = ts; - std::swap(data, ip.buf); + ClientEvent rev; + rev.source = ev.source; + rev.type = ev.event_type; + if (ev.event_type == ClientEvent::Packet) { + if (ev.packet_type == PacketType::Imu) { + rev.packet_ = &imu_packet_; + } else if (ev.packet_type == PacketType::Lidar) { + rev.packet_ = &lidar_packet_; + } else { + throw; // Should never happen, but who knows + } + rev.packet_->host_timestamp = ts; + rev.packet_->format = formats_[ev.source]; + std::swap(rev.packet_->buf, staging_buffer); + } else { + rev.packet_ = 0; } - return ev; // todo finish + return rev; } uint64_t SensorClient::dropped_packets() { @@ -452,5 +475,7 @@ uint64_t SensorClient::dropped_packets() { return dropped_packets_; } +ClientEvent::ClientEvent() {} + } // namespace sensor } // namespace ouster diff --git a/ouster_client/src/sensor_http.cpp b/ouster_client/src/sensor_http.cpp index 8d8358f2..e32fa67e 100644 --- a/ouster_client/src/sensor_http.cpp +++ b/ouster_client/src/sensor_http.cpp @@ -1,41 +1,33 @@ #include "ouster/sensor_http.h" +#include #include #include "curl_client.h" #include "sensor_http_imp.h" #include "sensor_tcp_imp.h" -using std::string; - using namespace ouster::util; using namespace ouster::sensor; using namespace ouster::sensor::util; using namespace ouster::sensor::impl; -string SensorHttp::firmware_version_string(const string& hostname, - int timeout_sec) { +std::string SensorHttp::firmware_version_string(const std::string& hostname, + int timeout_sec) { auto http_client = std::make_unique(hostname); auto fwjson = http_client->get("api/v1/system/firmware", timeout_sec); - Json::Value root{}; - Json::CharReaderBuilder builder{}; - std::string errors{}; - std::stringstream ss{fwjson}; - - if (!Json::parseFromStream(builder, ss, &root, &errors)) - throw std::runtime_error{ - "Errors parsing firmware for firmware_version_string: " + errors}; - - return root["fw"].asString(); + // This will exception out on bad parse + return jsoncons::json::parse(fwjson)["fw"].as(); } -version SensorHttp::firmware_version(const string& hostname, int timeout_sec) { +version SensorHttp::firmware_version(const std::string& hostname, + int timeout_sec) { auto result = firmware_version_string(hostname, timeout_sec); return ouster::util::version_from_string(result); } -std::unique_ptr SensorHttp::create(const string& hostname, +std::unique_ptr SensorHttp::create(const std::string& hostname, int timeout_sec) { auto fw = firmware_version(hostname, timeout_sec); diff --git a/ouster_client/src/sensor_http_imp.cpp b/ouster_client/src/sensor_http_imp.cpp index 36f4ce13..ba3bdb0e 100644 --- a/ouster_client/src/sensor_http_imp.cpp +++ b/ouster_client/src/sensor_http_imp.cpp @@ -1,31 +1,38 @@ #include "sensor_http_imp.h" +#include +#include + #include "curl_client.h" +#include "ouster/types.h" using ouster::sensor::util::UserDataAndPolicy; -using std::string; + using namespace ouster::sensor::impl; -SensorHttpImp::SensorHttpImp(const string& hostname) +SensorHttpImp::SensorHttpImp(const std::string& hostname) : http_client(std::make_unique(hostname)) {} SensorHttpImp::~SensorHttpImp() = default; -Json::Value SensorHttpImp::metadata(int timeout_sec) const { - return get_json("api/v1/sensor/metadata", timeout_sec); +std::string SensorHttpImp::metadata(int timeout_sec) const { + return get("api/v1/sensor/metadata", timeout_sec); } -Json::Value SensorHttpImp::sensor_info(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/sensor_info", timeout_sec); +std::string SensorHttpImp::sensor_info(int timeout_sec) const { + return get("api/v1/sensor/metadata/sensor_info", timeout_sec); } -string SensorHttpImp::get_config_params(bool active, int timeout_sec) const { +std::string SensorHttpImp::get_config_params(bool active, + int timeout_sec) const { auto config_type = active ? "active" : "staged"; - return get(string("api/v1/sensor/cmd/get_config_param?args=") + config_type, - timeout_sec); + return get( + std::string("api/v1/sensor/cmd/get_config_param?args=") + config_type, + timeout_sec); } -void SensorHttpImp::set_config_param(const string& key, const string& value, +void SensorHttpImp::set_config_param(const std::string& key, + const std::string& value, int timeout_sec) const { auto encoded_value = http_client->encode(value); // encode config params auto url = @@ -33,38 +40,36 @@ void SensorHttpImp::set_config_param(const string& key, const string& value, execute(url, "\"set_config_param\"", timeout_sec); } -Json::Value SensorHttpImp::active_config_params(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_config_param?args=active", - timeout_sec); +std::string SensorHttpImp::active_config_params(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_config_param?args=active", timeout_sec); } -Json::Value SensorHttpImp::staged_config_params(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_config_param?args=staged", - timeout_sec); +std::string SensorHttpImp::staged_config_params(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_config_param?args=staged", timeout_sec); } void SensorHttpImp::set_udp_dest_auto(int timeout_sec) const { execute("api/v1/sensor/cmd/set_udp_dest_auto", "{}", timeout_sec); } -Json::Value SensorHttpImp::beam_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/beam_intrinsics", timeout_sec); +std::string SensorHttpImp::beam_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/metadata/beam_intrinsics", timeout_sec); } -Json::Value SensorHttpImp::imu_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/imu_intrinsics", timeout_sec); +std::string SensorHttpImp::imu_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/metadata/imu_intrinsics", timeout_sec); } -Json::Value SensorHttpImp::lidar_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/lidar_intrinsics", timeout_sec); +std::string SensorHttpImp::lidar_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/metadata/lidar_intrinsics", timeout_sec); } -Json::Value SensorHttpImp::lidar_data_format(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/lidar_data_format", timeout_sec); +std::string SensorHttpImp::lidar_data_format(int timeout_sec) const { + return get("api/v1/sensor/metadata/lidar_data_format", timeout_sec); } -Json::Value SensorHttpImp::calibration_status(int timeout_sec) const { - return get_json("api/v1/sensor/metadata/calibration_status", timeout_sec); +std::string SensorHttpImp::calibration_status(int timeout_sec) const { + return get("api/v1/sensor/metadata/calibration_status", timeout_sec); } std::string SensorHttpImp::network(int timeout_sec) const { @@ -81,23 +86,36 @@ void SensorHttpImp::save_config_params(int timeout_sec) const { } std::string SensorHttpImp::get_user_data(int timeout_sec) const { - return get_json("api/v1/user/data", timeout_sec).asString(); + // the API returns a jsonified string (quoted and escaped), parse it and + // return the actual string value to the user + return jsoncons::json::parse(get("api/v1/user/data", timeout_sec)) + .as(); } UserDataAndPolicy SensorHttpImp::get_user_data_and_policy( int timeout_sec) const { - auto json = get_json("api/v1/user/data?include_metadata=true", timeout_sec); - return {json["policy"].asString() != "clear_on_config_delete", - json["value"].asString()}; + std::string url = "api/v1/user/data?include_metadata=true"; + auto json_data = get(url, timeout_sec); + jsoncons::json root; + + try { + root = jsoncons::json::parse(json_data); + return {root["policy"].as() != "clear_on_config_delete", + root["value"].as()}; + } catch (const jsoncons::ser_error& e) { + throw std::runtime_error("Http response parse failed! url: " + url + + " Error: " + e.what()); + } } void SensorHttpImp::set_user_data(const std::string& data, bool keep_on_config_delete, int timeout_sec) const { - Json::StreamWriterBuilder wbuilder; - std::string json = Json::writeString(wbuilder, Json::Value(data)); + std::string json; + auto temp_data = jsoncons::json(data); + temp_data.dump(json); http_client->put( - string("api/v1/user/data") + + std::string("api/v1/user/data") + (keep_on_config_delete ? "?policy=keep_on_config_delete" : ""), json, timeout_sec); } @@ -106,22 +124,12 @@ void SensorHttpImp::delete_user_data(int timeout_sec) const { http_client->del("api/v1/user/data", timeout_sec); } -string SensorHttpImp::get(const string& url, int timeout_sec) const { +std::string SensorHttpImp::get(const std::string& url, int timeout_sec) const { return http_client->get(url, timeout_sec); } -Json::Value SensorHttpImp::get_json(const string& url, int timeout_sec) const { - Json::CharReaderBuilder builder; - auto reader = std::unique_ptr{builder.newCharReader()}; - Json::Value root; - auto result = get(url, timeout_sec); - if (!reader->parse(result.c_str(), result.c_str() + result.size(), &root, - nullptr)) - throw std::runtime_error("SensorHttpImp::get_json failed! url: " + url); - return root; -} - -void SensorHttpImp::execute(const string& url, const string& validation, +void SensorHttpImp::execute(const std::string& url, + const std::string& validation, int timeout_sec) const { auto result = get(url, timeout_sec); if (result != validation) @@ -130,7 +138,24 @@ void SensorHttpImp::execute(const string& url, const string& validation, validation + "]"); } -SensorHttpImp_2_2::SensorHttpImp_2_2(const string& hostname) +void SensorHttpImp::set_static_ip(const std::string& ip_address, + int timeout_sec) const { + std::string json; + auto temp_data = jsoncons::json(ip_address); + temp_data.dump(json); + http_client->put("api/v1/system/network/ipv4/override", json, timeout_sec); +} + +void SensorHttpImp::delete_static_ip(int timeout_sec) const { + http_client->del("api/v1/system/network/ipv4/override", timeout_sec); +} + +std::vector SensorHttpImp::diagnostics_dump(int timeout_sec) const { + std::string str = http_client->get("/api/v1/diagnostics/dump", timeout_sec); + return std::vector(str.begin(), str.end()); +} + +SensorHttpImp_2_2::SensorHttpImp_2_2(const std::string& hostname) : SensorHttpImp_2_4_or_3(hostname) {} void SensorHttpImp_2_2::set_udp_dest_auto(int timeout_sec) const { @@ -138,53 +163,59 @@ void SensorHttpImp_2_2::set_udp_dest_auto(int timeout_sec) const { "\"set_config_param\"", timeout_sec); } -SensorHttpImp_2_1::SensorHttpImp_2_1(const string& hostname) +SensorHttpImp_2_1::SensorHttpImp_2_1(const std::string& hostname) : SensorHttpImp_2_2(hostname) {} -Json::Value SensorHttpImp_2_1::metadata(int timeout_sec) const { - Json::Value root; - root["sensor_info"] = sensor_info(timeout_sec); - root["beam_intrinsics"] = beam_intrinsics(timeout_sec); - root["imu_intrinsics"] = imu_intrinsics(timeout_sec); - root["lidar_intrinsics"] = lidar_intrinsics(timeout_sec); - root["lidar_data_format"] = lidar_data_format(timeout_sec); - root["calibration_status"] = calibration_status(timeout_sec); +std::string SensorHttpImp_2_1::metadata(int timeout_sec) const { + jsoncons::json root; + try { + root["sensor_info"] = jsoncons::json::parse(sensor_info(timeout_sec)); + root["beam_intrinsics"] = + jsoncons::json::parse(beam_intrinsics(timeout_sec)); + root["imu_intrinsics"] = + jsoncons::json::parse(imu_intrinsics(timeout_sec)); + root["lidar_intrinsics"] = + jsoncons::json::parse(lidar_intrinsics(timeout_sec)); + root["lidar_data_format"] = + jsoncons::json::parse(lidar_data_format(timeout_sec)); + root["calibration_status"] = + jsoncons::json::parse(calibration_status(timeout_sec)); + root["config_params"] = + jsoncons::json::parse(get_config_params(true, timeout_sec)); + } catch (const jsoncons::ser_error& e) { + throw std::runtime_error(e.what()); + } - Json::CharReaderBuilder builder; - auto reader = std::unique_ptr{builder.newCharReader()}; - Json::Value node; - auto res = get_config_params(true, timeout_sec); - auto parse_success = - reader->parse(res.c_str(), res.c_str() + res.size(), &node, nullptr); - root["config_params"] = parse_success ? node : res; - return root; + std::string result; + root.dump(result); + return result; } -Json::Value SensorHttpImp_2_1::sensor_info(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_sensor_info", timeout_sec); +std::string SensorHttpImp_2_1::sensor_info(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_sensor_info", timeout_sec); } -Json::Value SensorHttpImp_2_1::beam_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_beam_intrinsics", timeout_sec); +std::string SensorHttpImp_2_1::beam_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_beam_intrinsics", timeout_sec); } -Json::Value SensorHttpImp_2_1::imu_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_imu_intrinsics", timeout_sec); +std::string SensorHttpImp_2_1::imu_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_imu_intrinsics", timeout_sec); } -Json::Value SensorHttpImp_2_1::lidar_intrinsics(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_lidar_intrinsics", timeout_sec); +std::string SensorHttpImp_2_1::lidar_intrinsics(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_lidar_intrinsics", timeout_sec); } -Json::Value SensorHttpImp_2_1::lidar_data_format(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_lidar_data_format", timeout_sec); +std::string SensorHttpImp_2_1::lidar_data_format(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_lidar_data_format", timeout_sec); } -Json::Value SensorHttpImp_2_1::calibration_status(int timeout_sec) const { - return get_json("api/v1/sensor/cmd/get_calibration_status", timeout_sec); +std::string SensorHttpImp_2_1::calibration_status(int timeout_sec) const { + return get("api/v1/sensor/cmd/get_calibration_status", timeout_sec); } -SensorHttpImp_2_4_or_3::SensorHttpImp_2_4_or_3(const string& hostname) +SensorHttpImp_2_4_or_3::SensorHttpImp_2_4_or_3(const std::string& hostname) : SensorHttpImp(hostname) {} std::string SensorHttpImp_2_4_or_3::get_user_data(int /*timeout_sec*/) const { diff --git a/ouster_client/src/sensor_http_imp.h b/ouster_client/src/sensor_http_imp.h index c755edd9..ddbe7a40 100644 --- a/ouster_client/src/sensor_http_imp.h +++ b/ouster_client/src/sensor_http_imp.h @@ -11,6 +11,7 @@ #include "http_client.h" #include "ouster/sensor_http.h" +#include "ouster/types.h" namespace ouster { namespace sensor { @@ -38,7 +39,7 @@ class SensorHttpImp : public util::SensorHttp { * * @return returns a Json object of the sensor metadata. */ - Json::Value metadata( + std::string metadata( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** @@ -46,7 +47,7 @@ class SensorHttpImp : public util::SensorHttp { * * @return returns a Json object representing the sensor_info. */ - Json::Value sensor_info( + std::string sensor_info( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** @@ -55,7 +56,7 @@ class SensorHttpImp : public util::SensorHttp { * @param[in] active if true retrieve active, otherwise get staged configs. * @param[in] timeout_sec The timeout for the request in seconds. * - * @return a string represnting the active or staged config + * @return a string representing the active or staged config */ std::string get_config_params( bool active, @@ -76,13 +77,13 @@ class SensorHttpImp : public util::SensorHttp { /** * Retrieves the active configuration on the sensor */ - Json::Value active_config_params( + std::string active_config_params( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves the staged configuration on the sensor */ - Json::Value staged_config_params( + std::string staged_config_params( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** @@ -94,31 +95,31 @@ class SensorHttpImp : public util::SensorHttp { /** * Retrieves beam intrinsics of the sensor. */ - Json::Value beam_intrinsics( + std::string beam_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves imu intrinsics of the sensor. */ - Json::Value imu_intrinsics( + std::string imu_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves lidar intrinsics of the sensor. */ - Json::Value lidar_intrinsics( + std::string lidar_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves lidar data format. */ - Json::Value lidar_data_format( + std::string lidar_data_format( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Gets the calibaration status of the sensor. */ - Json::Value calibration_status( + std::string calibration_status( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** @@ -169,11 +170,19 @@ class SensorHttpImp : public util::SensorHttp { std::string network( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + void set_static_ip( + const std::string& ip_address, + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + + void delete_static_ip( + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + + std::vector diagnostics_dump( + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + protected: std::string get(const std::string& url, int timeout_sec) const; - Json::Value get_json(const std::string& url, int timeout_sec) const; - void execute(const std::string& url, const std::string& validation, int timeout_sec) const; @@ -235,47 +244,47 @@ class SensorHttpImp_2_1 : public SensorHttpImp_2_2 { /** * Queries the sensor metadata. * - * @return returns a Json object of the sensor metadata. + * @return returns a Json string of the sensor metadata. */ - Json::Value metadata( + std::string metadata( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Queries the sensor_info. * - * @return returns a Json object representing the sensor_info. + * @return returns a Json string representing the sensor_info. */ - Json::Value sensor_info( + std::string sensor_info( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves beam intrinsics of the sensor. */ - Json::Value beam_intrinsics( + std::string beam_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves imu intrinsics of the sensor. */ - Json::Value imu_intrinsics( + std::string imu_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves lidar intrinsics of the sensor. */ - Json::Value lidar_intrinsics( + std::string lidar_intrinsics( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Retrieves lidar data format. */ - Json::Value lidar_data_format( + std::string lidar_data_format( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; /** * Gets the calibaration status of the sensor. */ - Json::Value calibration_status( + std::string calibration_status( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; }; diff --git a/ouster_client/src/sensor_info.cpp b/ouster_client/src/sensor_info.cpp index 4b9ea832..0356c14f 100644 --- a/ouster_client/src/sensor_info.cpp +++ b/ouster_client/src/sensor_info.cpp @@ -3,12 +3,13 @@ * All rights reserved. */ -#include - #include #include #include #include +#include +#include +#include #include #include #include @@ -19,7 +20,6 @@ #include "ouster/impl/logging.h" #include "ouster/metadata.h" #include "ouster/types.h" -#include "ouster/util.h" #include "ouster/version.h" namespace ouster { @@ -50,10 +50,8 @@ extern data_format default_data_format(lidar_mode mode); extern double default_lidar_origin_to_beam_origin(std::string prod_line); extern mat4d default_beam_to_lidar_transform(std::string prod_line); extern calibration_status default_calibration_status(); -extern sensor_config parse_config(const Json::Value& root); -extern data_format parse_data_format(const Json::Value& root); -extern Json::Value cal_to_json(const calibration_status& cal); -extern Json::Value config_to_json(const sensor_config& config); +extern jsoncons::json cal_to_json(const calibration_status& cal); +extern jsoncons::json config_to_json(const sensor_config& config); /* Equality operators and functions */ @@ -95,7 +93,7 @@ auto sensor_info::h() const -> decltype(format.pixels_per_column) { sensor_info default_sensor_info(lidar_mode mode) { auto info = sensor_info(); - info.sn = "000000000000"; + info.sn = 0; info.fw_rev = "UNKNOWN"; info.prod_line = "OS-1-64"; @@ -105,7 +103,7 @@ sensor_info default_sensor_info(lidar_mode mode) { info.beam_altitude_angles = gen1_altitude_angles; info.lidar_origin_to_beam_origin_mm = default_lidar_origin_to_beam_origin(info.prod_line); - info.beam_to_lidar_transform = + info.beam_to_lidar_transform = default_beam_to_lidar_transform(info.prod_line); info.imu_to_sensor_transform = default_imu_to_sensor_transform; info.lidar_to_sensor_transform = default_lidar_to_sensor_transform; @@ -182,8 +180,6 @@ sensor_info::sensor_info() { } sensor_info::sensor_info(const std::string& metadata) { - Json::Value root{}; - Json::CharReaderBuilder builder{}; ValidatorIssues issues; if (metadata.size() > 0) { @@ -201,122 +197,129 @@ sensor_info::sensor_info(const std::string& metadata) { throw std::runtime_error("ERROR: empty metadata passed in"); } } + sensor_info::sensor_info(const std::string& metadata, bool /*skip_beam_validation*/) : sensor_info(metadata) {} -void mat4d_to_json(Json::Value& val, mat4d mat) { +void mat4d_to_json(jsoncons::json& val, mat4d mat) { for (size_t i = 0; i < 4; i++) { for (size_t j = 0; j < 4; j++) { - val.append(mat(i, j)); + val.emplace_back(mat(i, j)); } } } -/* DO NOT make public - internal logic use only - * Powers outputting a sensor_info to a nested json resembling non-legacy - * metadata - */ -Json::Value info_to_nested_json(const sensor_info& info) { - Json::Value result{}; - - result["sensor_info"]["build_date"] = info.build_date; - result["sensor_info"]["build_rev"] = info.fw_rev; - result["sensor_info"]["image_rev"] = info.image_rev; - result["sensor_info"]["initialization_id"] = info.init_id; - result["sensor_info"]["prod_line"] = info.prod_line; - result["sensor_info"]["prod_pn"] = info.prod_pn; - result["sensor_info"]["prod_sn"] = info.sn; - result["sensor_info"]["status"] = info.status; +std::string sensor_info::to_json_string() const { + jsoncons::json result; + + result["sensor_info"]["build_date"] = build_date; + result["sensor_info"]["build_rev"] = fw_rev; + result["sensor_info"]["image_rev"] = image_rev; + result["sensor_info"]["initialization_id"] = init_id; + result["sensor_info"]["prod_line"] = prod_line; + result["sensor_info"]["prod_pn"] = prod_pn; + result["sensor_info"]["prod_sn"] = std::to_string(sn); + result["sensor_info"]["status"] = status; // data_format - result["lidar_data_format"]["pixels_per_column"] = - info.format.pixels_per_column; + result["lidar_data_format"]["pixels_per_column"] = format.pixels_per_column; result["lidar_data_format"]["columns_per_packet"] = - info.format.columns_per_packet; - result["lidar_data_format"]["columns_per_frame"] = - info.format.columns_per_frame; - result["lidar_data_format"]["fps"] = info.format.fps; - result["lidar_data_format"]["column_window"].append( - info.format.column_window.first); - result["lidar_data_format"]["column_window"].append( - info.format.column_window.second); + format.columns_per_packet; + result["lidar_data_format"]["columns_per_frame"] = format.columns_per_frame; + result["lidar_data_format"]["fps"] = format.fps; + result["lidar_data_format"]["column_window"] = + jsoncons::json(jsoncons::json_array_arg); + result["lidar_data_format"]["column_window"].emplace_back( + format.column_window.first); + result["lidar_data_format"]["column_window"].emplace_back( + format.column_window.second); result["lidar_data_format"]["udp_profile_lidar"] = - to_string(info.format.udp_profile_lidar); + to_string(format.udp_profile_lidar); result["lidar_data_format"]["udp_profile_imu"] = - to_string(info.format.udp_profile_imu); + to_string(format.udp_profile_imu); - for (auto i : info.format.pixel_shift_by_row) - result["lidar_data_format"]["pixel_shift_by_row"].append(i); + result["lidar_data_format"]["pixel_shift_by_row"] = + jsoncons::json(jsoncons::json_array_arg); + for (auto i : format.pixel_shift_by_row) + result["lidar_data_format"]["pixel_shift_by_row"].emplace_back(i); // beam intrinsics // + result["beam_intrinsics"] = jsoncons::json(); + result["beam_intrinsics"]["beam_to_lidar_transform"] = + jsoncons::json(jsoncons::json_array_arg); mat4d_to_json(result["beam_intrinsics"]["beam_to_lidar_transform"], - info.beam_to_lidar_transform); + beam_to_lidar_transform); result["beam_intrinsics"]["lidar_origin_to_beam_origin_mm"] = - info.lidar_origin_to_beam_origin_mm; + lidar_origin_to_beam_origin_mm; - if (info.beam_azimuth_angles.size() == info.format.pixels_per_column) { + result["beam_intrinsics"]["beam_azimuth_angles"] = + jsoncons::json(jsoncons::json_array_arg); + result["beam_intrinsics"]["beam_altitude_angles"] = + jsoncons::json(jsoncons::json_array_arg); + if (beam_azimuth_angles.size() == format.pixels_per_column) { // OS sensor path - for (auto angle : info.beam_azimuth_angles) - result["beam_intrinsics"]["beam_azimuth_angles"].append(angle); - for (auto angle : info.beam_altitude_angles) - result["beam_intrinsics"]["beam_altitude_angles"].append(angle); + for (auto angle : beam_azimuth_angles) + result["beam_intrinsics"]["beam_azimuth_angles"].emplace_back( + angle); + for (auto angle : beam_altitude_angles) + result["beam_intrinsics"]["beam_altitude_angles"].emplace_back( + angle); } else { // DF sensor path int j = 0; - for (size_t i = 0; i < info.beam_azimuth_angles.size(); i++) { - int col_index_within_row = i % info.format.columns_per_frame; - if (col_index_within_row == 0) { // start new array - result["beam_intrinsics"]["beam_azimuth_angles"].append( - Json::Value(Json::arrayValue)); + for (size_t i = 0; i < beam_azimuth_angles.size(); i++) { + int col_index_within_row = i % format.columns_per_frame; + if (col_index_within_row == 0) { + result["beam_intrinsics"]["beam_azimuth_angles"].emplace_back( + jsoncons::json(jsoncons::json_array_arg)); j++; } - result["beam_intrinsics"]["beam_azimuth_angles"][j - 1].append( - info.beam_azimuth_angles[i]); + result["beam_intrinsics"]["beam_azimuth_angles"][j - 1] + .emplace_back(beam_azimuth_angles[i]); } j = 0; - for (size_t i = 0; i < info.beam_altitude_angles.size(); i++) { - int col_index_within_row = i % info.format.columns_per_frame; - if (col_index_within_row == 0) { // start new array - result["beam_intrinsics"]["beam_altitude_angles"].append( - Json::Value(Json::arrayValue)); + for (size_t i = 0; i < beam_altitude_angles.size(); i++) { + int col_index_within_row = i % format.columns_per_frame; + if (col_index_within_row == 0) { + result["beam_intrinsics"]["beam_altitude_angles"].emplace_back( + jsoncons::json(jsoncons::json_array_arg)); j++; } - result["beam_intrinsics"]["beam_altitude_angles"][j - 1].append( - info.beam_altitude_angles[i]); + result["beam_intrinsics"]["beam_altitude_angles"][j - 1] + .emplace_back(beam_altitude_angles[i]); } } + result["calibration_status"] = cal_to_json(cal); - result["calibration_status"] = cal_to_json(info.cal); + result["config_params"] = config_to_json(config); - result["config_params"] = config_to_json(info.config); - - result["user_data"] = info.user_data; + result["user_data"] = user_data; + result["imu_intrinsics"] = jsoncons::json(); + result["imu_intrinsics"]["imu_to_sensor_transform"] = + jsoncons::json(jsoncons::json_array_arg); mat4d_to_json(result["imu_intrinsics"]["imu_to_sensor_transform"], - info.imu_to_sensor_transform); + imu_to_sensor_transform); + result["lidar_intrinsics"] = jsoncons::json(); + result["lidar_intrinsics"]["lidar_to_sensor_transform"] = + jsoncons::json(jsoncons::json_array_arg); mat4d_to_json(result["lidar_intrinsics"]["lidar_to_sensor_transform"], - info.lidar_to_sensor_transform); - - mat4d_to_json(result["ouster-sdk"]["extrinsic"], info.extrinsic); + lidar_to_sensor_transform); - return result; -} - -std::string sensor_info::to_json_string() const { - Json::Value result = info_to_nested_json(*this); + result["ouster-sdk"] = jsoncons::json(); + result["ouster-sdk"]["extrinsic"] = + jsoncons::json(jsoncons::json_array_arg); + mat4d_to_json(result["ouster-sdk"]["extrinsic"], extrinsic); result["ouster-sdk"]["output_source"] = "sensor_info_to_string"; result["ouster-sdk"]["client_version"] = client_version(); - - Json::StreamWriterBuilder write_builder; - write_builder["enableYAMLCompatibility"] = true; - write_builder["precision"] = 6; - write_builder["indentation"] = " "; - return Json::writeString(write_builder, result); + std::string out; + result.dump(out); + return out; } ouster::util::version sensor_info::get_version() const { @@ -346,17 +349,6 @@ sensor_info metadata_from_json(const std::string& json_file, std::string to_string(const sensor_info& info) { return info.to_json_string(); } -std::string get_firmware_version(const Json::Value& metadata_root) { - auto fw_ver = std::string{}; - if (metadata_root["sensor_info"].isObject()) { - if (metadata_root["sensor_info"].isMember("image_rev")) { - // image_rev is preferred over build_rev - fw_ver = metadata_root["sensor_info"]["image_rev"].asString(); - } - } - return fw_ver; -} - ouster::util::version firmware_version_from_metadata( const std::string& metadata) { if (metadata.empty()) { @@ -364,23 +356,16 @@ ouster::util::version firmware_version_from_metadata( "firmware_version_from_metadata metadata empty!"); } - Json::Value root{}; - Json::CharReaderBuilder builder{}; - std::string errors{}; - std::stringstream ss{metadata}; - - if (!Json::parseFromStream(builder, ss, &root, &errors)) - throw std::runtime_error{ - "Errors parsing metadata for parse_metadata: " + errors}; - - auto fw_ver = get_firmware_version(root); - if (fw_ver.empty()) { - throw std::runtime_error( + jsoncons::json value_array = jsoncons::jsonpath::json_query( + jsoncons::json::parse(metadata), "$.sensor_info.image_rev"); + if (value_array.size() == 1) { + return ouster::util::version_from_string( + value_array[0].as()); + } else { + throw std::invalid_argument( "firmware_version_from_metadata failed to deduce version info from " "metadata!"); } - - return ouster::util::version_from_string(fw_ver); } } // namespace sensor diff --git a/ouster_client/src/sensor_scan_source.cpp b/ouster_client/src/sensor_scan_source.cpp index 23d7cc10..f56a7f90 100644 --- a/ouster_client/src/sensor_scan_source.cpp +++ b/ouster_client/src/sensor_scan_source.cpp @@ -54,11 +54,8 @@ SensorScanSource::SensorScanSource( run_thread_ = true; batcher_thread_ = std::thread([this, queue_size, soft_id_check]() { - LidarPacket lp; - ImuPacket ip; std::vector> scans; std::vector batchers; - std::vector pfs; auto infos = get_sensor_info(); for (size_t i = 0; i < infos.size(); i++) { const auto& info = infos[i]; @@ -68,24 +65,22 @@ SensorScanSource::SensorScanSource( scans.push_back(std::make_unique( w, h, fields_[i].begin(), fields_[i].end(), info.format.columns_per_packet)); - pfs.push_back(packet_format(info)); } while (run_thread_) { - auto p = client_.get_packet(lp, ip, 0.05); - if (p.type == ClientEvent::LidarPacket) { - const auto& pf = pfs[p.source]; + auto p = client_.get_packet(0.05); + if (p.type == ClientEvent::Packet && + p.packet().type() == PacketType::Lidar) { const auto& info = infos[p.source]; - auto result = lp.validate(info, pf); + const auto& lp = static_cast(p.packet()); + auto result = lp.validate(info); if (result == PacketValidationFailure::ID) { id_error_count_++; if (!soft_id_check) { - auto init_id = pf.init_id(lp.buf.data()); - auto prod_sn = pf.prod_sn(lp.buf.data()); logger().warn( "Metadata init_id/sn does not match: expected by " "metadata - {}/{}, but got from packet buffer - " "{}/{}", - info.init_id, info.sn, init_id, prod_sn); + info.init_id, info.sn, lp.init_id(), lp.prod_sn()); continue; } } diff --git a/ouster_client/src/sensor_tcp_imp.cpp b/ouster_client/src/sensor_tcp_imp.cpp index b1c20866..f7093c4b 100644 --- a/ouster_client/src/sensor_tcp_imp.cpp +++ b/ouster_client/src/sensor_tcp_imp.cpp @@ -7,43 +7,54 @@ #include #include +#include +#include #include #include "ouster/impl/logging.h" using ouster::sensor::util::UserDataAndPolicy; -using std::string; using namespace ouster::sensor::impl; -SensorTcpImp::SensorTcpImp(const string& hostname) +SensorTcpImp::SensorTcpImp(const std::string& hostname) : socket_handle(cfg_socket(hostname.c_str())), read_buf(std::unique_ptr{new char[MAX_RESULT_LENGTH + 1]}) {} SensorTcpImp::~SensorTcpImp() { socket_close(socket_handle); } -Json::Value SensorTcpImp::metadata(int timeout_sec) const { - Json::Value root; - root["sensor_info"] = sensor_info(timeout_sec); - root["beam_intrinsics"] = beam_intrinsics(timeout_sec); - root["imu_intrinsics"] = imu_intrinsics(timeout_sec); - root["lidar_intrinsics"] = lidar_intrinsics(timeout_sec); - root["lidar_data_format"] = lidar_data_format(timeout_sec); - root["calibration_status"] = calibration_status(timeout_sec); - Json::CharReaderBuilder builder; - auto reader = std::unique_ptr{builder.newCharReader()}; +std::string SensorTcpImp::metadata(int timeout_sec) const { auto res = get_config_params(true, timeout_sec); - Json::Value node; - auto parse_success = - reader->parse(res.c_str(), res.c_str() + res.size(), &node, nullptr); - root["config_params"] = parse_success ? node : res; - return root; -} - -Json::Value SensorTcpImp::sensor_info(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_sensor_info"}); -} + jsoncons::json config_params; + bool parse_success = false; + try { + config_params = jsoncons::json::parse(res); + parse_success = true; + } catch (jsoncons::ser_error&) { + } -string SensorTcpImp::get_config_params(bool active, int /*timeout_sec*/) const { + jsoncons::json root; + root["sensor_info"] = jsoncons::json::parse(sensor_info(timeout_sec)); + root["beam_intrinsics"] = + jsoncons::json::parse(beam_intrinsics(timeout_sec)); + root["imu_intrinsics"] = jsoncons::json::parse(imu_intrinsics(timeout_sec)); + root["lidar_intrinsics"] = + jsoncons::json::parse(lidar_intrinsics(timeout_sec)); + root["lidar_data_format"] = + jsoncons::json::parse(lidar_data_format(timeout_sec)); + root["calibration_status"] = + jsoncons::json::parse(calibration_status(timeout_sec)); + root["config_params"] = (parse_success) ? config_params : res; + std::string result; + root.dump(result); + return result; +} + +std::string SensorTcpImp::sensor_info(int /*timeout_sec*/) const { + return tcp_cmd({"get_sensor_info"}); +} + +std::string SensorTcpImp::get_config_params(bool active, + int /*timeout_sec*/) const { auto config_type = active ? "active" : "staged"; return tcp_cmd({"get_config_param", config_type}); } @@ -59,42 +70,43 @@ std::string rtrim(const std::string& s) { } } // namespace -void SensorTcpImp::set_config_param(const string& key, const string& value, +void SensorTcpImp::set_config_param(const std::string& key, + const std::string& value, int /*timeout_sec*/) const { tcp_cmd_with_validation({"set_config_param", key, rtrim(value)}, "set_config_param"); } -Json::Value SensorTcpImp::active_config_params(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_config_param", "active"}); +std::string SensorTcpImp::active_config_params(int /*timeout_sec*/) const { + return tcp_cmd({"get_config_param", "active"}); } -Json::Value SensorTcpImp::staged_config_params(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_config_param", "staged"}); +std::string SensorTcpImp::staged_config_params(int /*timeout_sec*/) const { + return tcp_cmd({"get_config_param", "staged"}); } void SensorTcpImp::set_udp_dest_auto(int /*timeout_sec*/) const { tcp_cmd_with_validation({"set_udp_dest_auto"}, "set_udp_dest_auto"); } -Json::Value SensorTcpImp::beam_intrinsics(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_beam_intrinsics"}); +std::string SensorTcpImp::beam_intrinsics(int /*timeout_sec*/) const { + return tcp_cmd({"get_beam_intrinsics"}); } -Json::Value SensorTcpImp::imu_intrinsics(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_imu_intrinsics"}); +std::string SensorTcpImp::imu_intrinsics(int /*timeout_sec*/) const { + return tcp_cmd({"get_imu_intrinsics"}); } -Json::Value SensorTcpImp::lidar_intrinsics(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_lidar_intrinsics"}); +std::string SensorTcpImp::lidar_intrinsics(int /*timeout_sec*/) const { + return tcp_cmd({"get_lidar_intrinsics"}); } -Json::Value SensorTcpImp::lidar_data_format(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_lidar_data_format"}, false); +std::string SensorTcpImp::lidar_data_format(int /*timeout_sec*/) const { + return tcp_cmd({"get_lidar_data_format"}); } -Json::Value SensorTcpImp::calibration_status(int /*timeout_sec*/) const { - return tcp_cmd_json({"get_calibration_status"}, false); +std::string SensorTcpImp::calibration_status(int /*timeout_sec*/) const { + return tcp_cmd({"get_calibration_status"}); } void SensorTcpImp::reinitialize(int /*timeout_sec*/) const { @@ -130,6 +142,22 @@ std::string SensorTcpImp::network(int /*timeout_sec*/) const { "This endpoint is not supported on this FW version"); } +void SensorTcpImp::set_static_ip(const std::string& /*ip_address*/, + int /*timeout_sec*/) const { + throw std::runtime_error( + "This endpoint is not supported on this FW version"); +} + +void SensorTcpImp::delete_static_ip(int /*timeout_sec*/) const { + throw std::runtime_error( + "This endpoint is not supported on this FW version"); +} + +std::vector SensorTcpImp::diagnostics_dump(int /*timeout_sec*/) const { + throw std::runtime_error( + "This endpoint is not supported on this FW version"); +} + SOCKET SensorTcpImp::cfg_socket(const char* addr) { struct addrinfo hints, *info_start, *ai; @@ -185,11 +213,12 @@ SOCKET SensorTcpImp::cfg_socket(const char* addr) { return sock_fd; } -string SensorTcpImp::tcp_cmd(const std::vector& cmd_tokens) const { +std::string SensorTcpImp::tcp_cmd( + const std::vector& cmd_tokens) const { std::stringstream ss; for (const auto& token : cmd_tokens) ss << token << " "; ss << "\n"; - string cmd = ss.str(); + std::string cmd = ss.str(); ssize_t len = send(socket_handle, cmd.c_str(), cmd.length(), 0); if (len != (ssize_t)cmd.length()) { @@ -213,24 +242,9 @@ string SensorTcpImp::tcp_cmd(const std::vector& cmd_tokens) const { return res; } -Json::Value SensorTcpImp::tcp_cmd_json(const std::vector& cmd_tokens, - bool exception_on_parse_errors) const { - Json::CharReaderBuilder builder; - auto reader = std::unique_ptr{builder.newCharReader()}; - Json::Value root; - auto result = tcp_cmd(cmd_tokens); - auto success = reader->parse(result.c_str(), result.c_str() + result.size(), - &root, nullptr); - if (success) return root; - if (!exception_on_parse_errors) return result; - - throw std::runtime_error( - "SensorTcp::tcp_cmd_json failed for " + cmd_tokens[0] + - " command. returned json string [" + result + "] couldn't be parsed ["); -} - void SensorTcpImp::tcp_cmd_with_validation( - const std::vector& cmd_tokens, const string& validation) const { + const std::vector& cmd_tokens, + const std::string& validation) const { auto result = tcp_cmd(cmd_tokens); if (result != validation) { throw std::runtime_error("SensorTcp::tcp_cmd failed: " + cmd_tokens[0] + diff --git a/ouster_client/src/sensor_tcp_imp.h b/ouster_client/src/sensor_tcp_imp.h index 725585f2..d228a52e 100644 --- a/ouster_client/src/sensor_tcp_imp.h +++ b/ouster_client/src/sensor_tcp_imp.h @@ -9,8 +9,17 @@ #pragma once -#include "ouster/impl/netcompat.h" #include "ouster/sensor_http.h" +#include "ouster/types.h" + +/** + * @note On windows platforms, the windows headers do a global define on + * BAUD_9600 which causes issues with defines in types.h. Netcompat.h must + * be included after every other header file to avoid this issue. + */ +// clang-format off +#include "ouster/impl/netcompat.h" +// clang-format on namespace ouster { namespace sensor { @@ -44,7 +53,7 @@ class SensorTcpImp : public util::SensorHttp { * * @return returns a Json object of the sensor metadata. */ - Json::Value metadata(int timeout_sec = 1) const override; + std::string metadata(int timeout_sec = 1) const override; /** * Queries the sensor_info. @@ -53,7 +62,7 @@ class SensorTcpImp : public util::SensorHttp { * * @return returns a Json object representing the sensor_info. */ - Json::Value sensor_info(int timeout_sec = 1) const override; + std::string sensor_info(int timeout_sec = 1) const override; /** * Queries active/staged configuration on the sensor @@ -61,7 +70,7 @@ class SensorTcpImp : public util::SensorHttp { * @param[in] active if true retrieve active, otherwise get staged configs. * @param[in] timeout_sec The timeout for the request in seconds. * - * @return a string represnting the active or staged config + * @return a string representing the active or staged config */ std::string get_config_params(bool active, int timeout_sec = 1) const override; @@ -82,14 +91,14 @@ class SensorTcpImp : public util::SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value active_config_params(int timeout_sec = 1) const override; + std::string active_config_params(int timeout_sec = 1) const override; /** * Retrieves the staged configuration on the sensor * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value staged_config_params(int timeout_sec = 1) const override; + std::string staged_config_params(int timeout_sec = 1) const override; /** * Enables automatic assignment of udp destination ports. @@ -103,35 +112,35 @@ class SensorTcpImp : public util::SensorHttp { * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value beam_intrinsics(int timeout_sec = 1) const override; + std::string beam_intrinsics(int timeout_sec = 1) const override; /** * Retrieves imu intrinsics of the sensor. * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value imu_intrinsics(int timeout_sec = 1) const override; + std::string imu_intrinsics(int timeout_sec = 1) const override; /** * Retrieves lidar intrinsics of the sensor. * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value lidar_intrinsics(int timeout_sec = 1) const override; + std::string lidar_intrinsics(int timeout_sec = 1) const override; /** * Retrieves lidar data format. * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value lidar_data_format(int timeout_sec = 1) const override; + std::string lidar_data_format(int timeout_sec = 1) const override; /** * Gets the calibaration status of the sensor. * * @param[in] timeout_sec The timeout for the request in seconds. */ - Json::Value calibration_status(int timeout_sec = 1) const override; + std::string calibration_status(int timeout_sec = 1) const override; /** * Restarts the sensor applying all staged configurations. @@ -192,6 +201,16 @@ class SensorTcpImp : public util::SensorHttp { std::string network( int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + void set_static_ip( + const std::string& ip_address, + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + + void delete_static_ip( + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + + std::vector diagnostics_dump( + int timeout_sec = SHORT_HTTP_REQUEST_TIMEOUT_SECONDS) const override; + private: SOCKET cfg_socket(const char* addr); @@ -200,9 +219,6 @@ class SensorTcpImp : public util::SensorHttp { void tcp_cmd_with_validation(const std::vector& cmd_tokens, const std::string& validation) const; - Json::Value tcp_cmd_json(const std::vector& cmd_tokens, - bool exception_on_parse_errors = true) const; - private: SOCKET socket_handle; std::unique_ptr read_buf; diff --git a/ouster_client/src/types.cpp b/ouster_client/src/types.cpp index 10bdcfe6..41c298b2 100644 --- a/ouster_client/src/types.cpp +++ b/ouster_client/src/types.cpp @@ -5,13 +5,13 @@ #include "ouster/types.h" -#include - #include #include #include #include #include +#include +#include #include #include #include @@ -70,8 +70,15 @@ extern const Table polarity_strings{ {{POLARITY_ACTIVE_LOW, "ACTIVE_LOW"}, {POLARITY_ACTIVE_HIGH, "ACTIVE_HIGH"}}}; +#if defined(BAUD_9600) +#undef BAUD_9600 +#endif +#if defined(BAUD_115200) +#undef BAUD_115200 +#endif extern const Table nmea_baud_rate_strings{ - {{BAUD_9600, "BAUD_9600"}, {BAUD_115200, "BAUD_115200"}}}; + {{NMEABaudRate::BAUD_9600, "BAUD_9600"}, + {NMEABaudRate::BAUD_115200, "BAUD_115200"}}}; Table udp_profile_lidar_strings{ { @@ -383,6 +390,11 @@ std::string to_string(AzimuthWindow azimuth_window) { return ss.str(); } +#if defined(VOID) +#define OUSTER_REMOVED_VOID +#pragma push_macro("VOID") +#undef VOID +#endif std::string to_string(ChanFieldType ft) { switch (ft) { case sensor::ChanFieldType::VOID: @@ -411,6 +423,10 @@ std::string to_string(ChanFieldType ft) { return "UNKNOWN"; } } +#if defined(OUSTER_REMOVED_VOID) +#pragma pop_macro("VOID") +#undef OUSTER_REMOVED_VOID +#endif size_t field_type_size(ChanFieldType ft) { switch (ft) { @@ -500,89 +516,8 @@ void check_signal_multiplier(const double signal_multiplier) { } } -data_format parse_data_format(const Json::Value& root) { - const std::vector data_format_required_fields{ - "pixels_per_column", "columns_per_packet", "columns_per_frame"}; - - for (const auto& field : data_format_required_fields) { - if (!root.isMember(field)) { - throw std::runtime_error{ - "Metadata field data_format must include field: " + field}; - } - } - data_format format; - - format.pixels_per_column = root["pixels_per_column"].asInt(); - format.columns_per_packet = root["columns_per_packet"].asInt(); - format.columns_per_frame = root["columns_per_frame"].asInt(); - - if (root.isMember("pixel_shift_by_row")) { - if (root["pixel_shift_by_row"].size() != format.pixels_per_column) { - throw std::runtime_error{"Unexpected number of pixel_shift_by_row"}; - } - - for (const auto& v : root["pixel_shift_by_row"]) - format.pixel_shift_by_row.push_back(v.asInt()); - } else { - // DF path - format.pixel_shift_by_row.assign(format.pixels_per_column, 0); - } - - if (root.isMember("column_window")) { - if (root["column_window"].size() != 2) { - throw std::runtime_error{"Unexpected size of column_window tuple"}; - } - format.column_window.first = root["column_window"][0].asInt(); - format.column_window.second = root["column_window"][1].asInt(); - } else { - logger().warn( - "No column window found. Using default column window (full)"); - format.column_window = default_column_window(format.columns_per_frame); - } - - if (root.isMember("udp_profile_lidar")) { - // initializing directly triggers -Wmaybe-uninitialized - // GCC 8.3.1 - optional profile{nullopt}; - profile = - udp_profile_lidar_of_string(root["udp_profile_lidar"].asString()); - if (profile) { - format.udp_profile_lidar = profile.value(); - } else { - throw std::runtime_error{"Unexpected udp lidar profile: " + - root["udp_profile_lidar"].asString()}; - } - } else { - logger().warn("No lidar profile found. Using LEGACY lidar profile"); - format.udp_profile_lidar = PROFILE_LIDAR_LEGACY; - } - - if (root.isMember("udp_profile_imu")) { - optional profile{nullopt}; - profile = udp_profile_imu_of_string(root["udp_profile_imu"].asString()); - if (profile) { - format.udp_profile_imu = profile.value(); - } else { - throw std::runtime_error{"Unexpected udp imu profile"}; - } - } else { - logger().warn("No imu profile found. Using LEGACY imu profile"); - format.udp_profile_imu = PROFILE_IMU_LEGACY; - } - - if (root.isMember("fps")) { - format.fps = root["fps"].asInt(); - } else { - // logger().warn("No fps found. Trying to use one from lidar mode (or - // 0)"); - format.fps = 0; - } - - return format; -} - -Json::Value cal_to_json(const calibration_status& cal) { - Json::Value root{Json::objectValue}; +jsoncons::json cal_to_json(const calibration_status& cal) { + jsoncons::json root; if (cal.reflectivity_status) { root["reflectivity"]["valid"] = cal.reflectivity_status.value(); @@ -595,225 +530,49 @@ Json::Value cal_to_json(const calibration_status& cal) { } std::string to_string(const calibration_status& cal) { - Json::Value root = cal_to_json(cal); - - Json::StreamWriterBuilder builder; - builder["enableYAMLCompatibility"] = true; - builder["precision"] = 6; - builder["indentation"] = " "; - return Json::writeString(builder, root); + auto root = cal_to_json(cal); + std::string out; + root.dump(out); + return out; } -sensor_config parse_config(const Json::Value& root) { - sensor_config config{}; - - if (!root["udp_dest"].empty()) { - config.udp_dest = root["udp_dest"].asString(); - } else if (!root["udp_ip"].empty()) { - // deprecated params from FW 1.13. Set FW 2.0+ configs appropriately - config.udp_dest = root["udp_ip"].asString(); - logger().warn( - "Please note that udp_ip has been deprecated in favor " - "of udp_dest. Will set udp_dest appropriately..."); - } - - if (!root["udp_port_lidar"].empty()) - config.udp_port_lidar = root["udp_port_lidar"].asInt(); - if (!root["udp_port_imu"].empty()) - config.udp_port_imu = root["udp_port_imu"].asInt(); - if (!root["timestamp_mode"].empty()) - config.timestamp_mode = - timestamp_mode_of_string(root["timestamp_mode"].asString()); - if (!root["lidar_mode"].empty()) - config.lidar_mode = lidar_mode_of_string(root["lidar_mode"].asString()); - - if (!root["azimuth_window"].empty()) - config.azimuth_window = - std::make_pair(root["azimuth_window"][0].asInt(), - root["azimuth_window"][1].asInt()); - - if (!root["signal_multiplier"].empty()) { - double signal_multiplier = root["signal_multiplier"].asDouble(); - check_signal_multiplier(signal_multiplier); - config.signal_multiplier = signal_multiplier; - } - - if (!root["operating_mode"].empty()) { - auto operating_mode = - operating_mode_of_string(root["operating_mode"].asString()); - if (operating_mode) { - config.operating_mode = operating_mode; - } else { - throw std::runtime_error{"Unexpected Operating Mode"}; - } - } else if (!root["auto_start_flag"].empty()) { - logger().warn( - "Please note that auto_start_flag has been deprecated in favor " - "of operating_mode. Will set operating_mode appropriately..."); - config.operating_mode = root["auto_start_flag"].asBool() - ? sensor::OPERATING_NORMAL - : sensor::OPERATING_STANDBY; - } +jsoncons::json config_to_json(const sensor_config& config) { + jsoncons::json root; - if (!root["multipurpose_io_mode"].empty()) { - auto multipurpose_io_mode = multipurpose_io_mode_of_string( - root["multipurpose_io_mode"].asString()); - if (multipurpose_io_mode) { - config.multipurpose_io_mode = multipurpose_io_mode; - } else { - throw std::runtime_error{"Unexpected Multipurpose IO Mode"}; - } - } - if (!root["sync_pulse_out_angle"].empty()) - config.sync_pulse_out_angle = root["sync_pulse_out_angle"].asInt(); - if (!root["sync_pulse_out_pulse_width"].empty()) - config.sync_pulse_out_pulse_width = - root["sync_pulse_out_pulse_width"].asInt(); - - if (!root["nmea_in_polarity"].empty()) { - auto nmea_in_polarity = - polarity_of_string(root["nmea_in_polarity"].asString()); - if (nmea_in_polarity) { - config.nmea_in_polarity = nmea_in_polarity; - } else { - throw std::runtime_error{"Unexpected NMEA Input Polarity"}; - } - } - if (!root["nmea_baud_rate"].empty()) { - auto nmea_baud_rate = - nmea_baud_rate_of_string(root["nmea_baud_rate"].asString()); - if (nmea_baud_rate) { - config.nmea_baud_rate = nmea_baud_rate; - } else { - throw std::runtime_error{"Unexpected NMEA Baud Rate"}; - } - } - if (!root["nmea_ignore_valid_char"].empty()) - config.nmea_ignore_valid_char = root["nmea_ignore_valid_char"].asBool(); - if (!root["nmea_leap_seconds"].empty()) - config.nmea_leap_seconds = root["nmea_leap_seconds"].asInt(); - - if (!root["sync_pulse_in_polarity"].empty()) { - auto sync_pulse_in_polarity = - polarity_of_string(root["sync_pulse_in_polarity"].asString()); - if (sync_pulse_in_polarity) { - config.sync_pulse_in_polarity = sync_pulse_in_polarity; - } else { - throw std::runtime_error{"Unexpected Sync Pulse Input Polarity"}; - } + if (config.udp_dest) { + root["udp_dest"] = config.udp_dest.value(); } - if (!root["sync_pulse_out_polarity"].empty()) { - auto sync_pulse_out_polarity = - polarity_of_string(root["sync_pulse_out_polarity"].asString()); - if (sync_pulse_out_polarity) { - config.sync_pulse_out_polarity = sync_pulse_out_polarity; - } else { - throw std::runtime_error{"Unexpected Sync Pulse Output Polarity"}; - } - } - if (!root["sync_pulse_out_frequency"].empty()) - config.sync_pulse_out_frequency = - root["sync_pulse_out_frequency"].asInt(); - - if (!root["phase_lock_enable"].empty()) - config.phase_lock_enable = - root["phase_lock_enable"].asString() == "true" ? true : false; - - if (!root["phase_lock_offset"].empty()) - config.phase_lock_offset = root["phase_lock_offset"].asInt(); - - if (!root["columns_per_packet"].empty()) - config.columns_per_packet = root["columns_per_packet"].asInt(); - - // udp_profiles - if (!root["udp_profile_lidar"].empty()) - config.udp_profile_lidar = - udp_profile_lidar_of_string(root["udp_profile_lidar"].asString()); - if (!root["udp_profile_imu"].empty()) - config.udp_profile_imu = - udp_profile_imu_of_string(root["udp_profile_imu"].asString()); - - // Firmware 3.1 and higher options - if (!root["gyro_fsr"].empty()) { - auto gyro_fsr = full_scale_range_of_string(root["gyro_fsr"].asString()); - if (gyro_fsr) { - config.gyro_fsr = gyro_fsr; - } else { - throw std::runtime_error{"Unexpected Gyro FSR"}; - } - } - - if (!root["accel_fsr"].empty()) { - auto accel_fsr = - full_scale_range_of_string(root["accel_fsr"].asString()); - if (accel_fsr) { - config.accel_fsr = accel_fsr; - } else { - throw std::runtime_error{"Unexpected Accel FSR"}; - } + if (config.udp_port_lidar) { + root["udp_port_lidar"] = config.udp_port_lidar.value(); } - if (!root["return_order"].empty()) { - auto return_order = - return_order_of_string(root["return_order"].asString()); - if (return_order) { - config.return_order = return_order; - } else { - throw std::runtime_error{"Unexpected Return Order"}; - } + if (config.udp_port_imu) { + root["udp_port_imu"] = config.udp_port_imu.value(); } - if (!root["min_range_threshold_cm"].empty()) - config.min_range_threshold_cm = root["min_range_threshold_cm"].asInt(); - - return config; -} - -sensor_config parse_config(const std::string& config) { - Json::Value root{}; - Json::CharReaderBuilder builder{}; - std::string errors{}; - std::stringstream ss{config}; - - if (config.size()) { - if (!Json::parseFromStream(builder, ss, &root, &errors)) { - throw std::runtime_error{errors}; - } - } - - return parse_config(root); -} - -Json::Value config_to_json(const sensor_config& config) { - Json::Value root{Json::objectValue}; - - if (config.udp_dest) root["udp_dest"] = config.udp_dest.value(); - - if (config.udp_port_lidar) - root["udp_port_lidar"] = config.udp_port_lidar.value(); - - if (config.udp_port_imu) root["udp_port_imu"] = config.udp_port_imu.value(); - - if (config.timestamp_mode) + if (config.timestamp_mode) { root["timestamp_mode"] = to_string(config.timestamp_mode.value()); + } - if (config.lidar_mode) + if (config.lidar_mode) { root["lidar_mode"] = to_string(config.lidar_mode.value()); + } if (config.operating_mode) { auto mode = config.operating_mode.value(); root["operating_mode"] = to_string(mode); } - if (config.multipurpose_io_mode) + if (config.multipurpose_io_mode) { root["multipurpose_io_mode"] = to_string(config.multipurpose_io_mode.value()); + } if (config.azimuth_window) { - Json::Value azimuth_window; - azimuth_window.append(config.azimuth_window.value().first); - azimuth_window.append(config.azimuth_window.value().second); + jsoncons::json azimuth_window(jsoncons::json_array_arg); + azimuth_window.emplace_back(config.azimuth_window.value().first); + azimuth_window.emplace_back(config.azimuth_window.value().second); root["azimuth_window"] = azimuth_window; } @@ -823,136 +582,97 @@ Json::Value config_to_json(const sensor_config& config) { (config.signal_multiplier == 0.5)) { root["signal_multiplier"] = config.signal_multiplier.value(); } else { - // jsoncpp < 1.7.7 strips 0s off of exact representation - // so 2.0 becomes 2 - // Work around by always casting to int before writing out to json int signal_multiplier_int = int(config.signal_multiplier.value()); root["signal_multiplier"] = signal_multiplier_int; } } - if (config.sync_pulse_out_angle) + if (config.sync_pulse_out_angle) { root["sync_pulse_out_angle"] = config.sync_pulse_out_angle.value(); + } - if (config.sync_pulse_out_pulse_width) + if (config.sync_pulse_out_pulse_width) { root["sync_pulse_out_pulse_width"] = config.sync_pulse_out_pulse_width.value(); + } - if (config.nmea_in_polarity) + if (config.nmea_in_polarity) { root["nmea_in_polarity"] = to_string(config.nmea_in_polarity.value()); + } - if (config.nmea_baud_rate) + if (config.nmea_baud_rate) { root["nmea_baud_rate"] = to_string(config.nmea_baud_rate.value()); + } - if (config.nmea_ignore_valid_char) + if (config.nmea_ignore_valid_char) { root["nmea_ignore_valid_char"] = config.nmea_ignore_valid_char.value() ? 1 : 0; + } - if (config.nmea_leap_seconds) + if (config.nmea_leap_seconds) { root["nmea_leap_seconds"] = config.nmea_leap_seconds.value(); + } - if (config.sync_pulse_in_polarity) + if (config.sync_pulse_in_polarity) { root["sync_pulse_in_polarity"] = to_string(config.sync_pulse_in_polarity.value()); + } - if (config.sync_pulse_out_polarity) + if (config.sync_pulse_out_polarity) { root["sync_pulse_out_polarity"] = to_string(config.sync_pulse_out_polarity.value()); + } - if (config.sync_pulse_out_frequency) + if (config.sync_pulse_out_frequency) { root["sync_pulse_out_frequency"] = config.sync_pulse_out_frequency.value(); + } - if (config.phase_lock_enable) + if (config.phase_lock_enable) { root["phase_lock_enable"] = config.phase_lock_enable.value(); + } - if (config.phase_lock_offset) + if (config.phase_lock_offset) { root["phase_lock_offset"] = config.phase_lock_offset.value(); + } - if (config.columns_per_packet) + if (config.columns_per_packet) { root["columns_per_packet"] = config.columns_per_packet.value(); + } - if (config.udp_profile_lidar) + if (config.udp_profile_lidar) { root["udp_profile_lidar"] = to_string(config.udp_profile_lidar.value()); + } - if (config.udp_profile_imu) + if (config.udp_profile_imu) { root["udp_profile_imu"] = to_string(config.udp_profile_imu.value()); + } // Firmware 3.1 and higher options - if (config.gyro_fsr) root["gyro_fsr"] = to_string(config.gyro_fsr.value()); + if (config.gyro_fsr) { + root["gyro_fsr"] = to_string(config.gyro_fsr.value()); + } - if (config.accel_fsr) + if (config.accel_fsr) { root["accel_fsr"] = to_string(config.accel_fsr.value()); + } - if (config.min_range_threshold_cm) + if (config.min_range_threshold_cm) { root["min_range_threshold_cm"] = config.min_range_threshold_cm.value(); + } - if (config.return_order) + if (config.return_order) { root["return_order"] = to_string(config.return_order.value()); + } return root; } std::string to_string(const sensor_config& config) { - Json::Value root = config_to_json(config); - - Json::StreamWriterBuilder builder; - builder["enableYAMLCompatibility"] = true; - builder["precision"] = 6; - builder["indentation"] = " "; - return Json::writeString(builder, root); -} - -PacketValidationFailure validate_packet(const sensor_info& info, - const packet_format& format, - const uint8_t* buf, uint64_t buf_size, - PacketValidationType type) { - // Check if we need to guess the type - if (type == PacketValidationType::GUESS_TYPE) { - if (buf_size == format.imu_packet_size) { - type = PacketValidationType::IMU; - } else { - type = PacketValidationType::LIDAR; - } - } - - if (type == PacketValidationType::LIDAR) { - if (buf_size != format.lidar_packet_size) { - return PacketValidationFailure::PACKET_SIZE; - } - - auto init_id = format.init_id(buf); - if (info.init_id != 0 && init_id != 0 && init_id != info.init_id) { - return PacketValidationFailure::ID; - } - - if (info.sn.length() > 0) { - uint64_t p_sn = format.prod_sn(buf); - uint64_t m_sn = std::stoull(info.sn); - if ((p_sn != 0) && (p_sn != m_sn)) { - return PacketValidationFailure::ID; - } - } - return PacketValidationFailure::NONE; - } else if (type == PacketValidationType::IMU) { - if (buf_size != format.imu_packet_size) { - return PacketValidationFailure::PACKET_SIZE; - } - return PacketValidationFailure::NONE; - } - return PacketValidationFailure::NONE; -}; - -PacketValidationFailure LidarPacket::validate(const sensor_info& info, - const packet_format& format) { - return validate_packet(info, format, buf.data(), buf.size(), - PacketValidationType::LIDAR); -} - -PacketValidationFailure ImuPacket::validate(const sensor_info& info, - const packet_format& format) { - return validate_packet(info, format, buf.data(), buf.size(), - PacketValidationType::IMU); + jsoncons::json root = config_to_json(config); + std::string out; + root.dump(out); + return out; } product_info product_info::create_product_info( diff --git a/ouster_client/src/util.cpp b/ouster_client/src/util.cpp deleted file mode 100644 index d7a6aff8..00000000 --- a/ouster_client/src/util.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2023, Ouster, Inc. - * All rights reserved. - */ - -#include "ouster/util.h" - -#include - -namespace ouster { - -void combined_helper(const Json::Value& root_orig, const Json::Value& root_new, - Json::Value& result, std::string changed_str, - std::vector& changed) { - // Iterate over new root - any - for (Json::Value::const_iterator itr = root_new.begin(); - itr != root_new.end(); itr++) { - std::string key = itr.key().asString(); - std::string new_changed_str = - (changed_str == "") ? key : changed_str + "." + key; - if (root_orig.isMember(key) && root_new[key].isObject() && - root_orig[key].isObject()) { - // need to crawl to collect changed_str output - combined_helper(root_orig[key], root_new[key], result[key], - new_changed_str, changed); - } else { - // CASE: root_orig does not have key OR - // CASE: key is leaf OR - // CASE: key is not matched as leaf/object in root_new vs root_orig - - bool change = false; - - // Unfortunately we have to handle the Ints and Doubles because 0 is - // different from 0.0 in output - // TODO - find differnt way so we don't have to do this - if (!root_orig.isMember(key) || root_new[key].isObject()) { - change = true; - } else if ((root_new[key].isString() || root_new[key].isBool()) && - (root_orig[key] != root_new[key])) { - change = true; - } else if (root_new[key].isIntegral() && - (root_orig[key].asInt() != root_new[key].asInt())) { - change = true; - } else if (root_new[key].isDouble() && (root_orig[key].asDouble() != - root_new[key].asDouble())) { - change = true; - } else if (root_new[key].isArray()) { - // NOTE: this does not handle the array of arrays that DF - // currently has - for (size_t i = 0; i < root_new[key].size(); i++) { - if (root_new[key][(int)i].asDouble() != - root_orig[key][(int)i].asDouble()) { - change = true; - continue; - } - } - } - - if (change) { - result[key] = root_new[key]; - changed.push_back(new_changed_str); - } - } - } -} // namespace ouster - -Json::Value combined(const Json::Value& root_orig, const Json::Value& root_new, - std::vector& changed) { - Json::Value result{}; - - // make sure changed is empty to start - changed.clear(); - - // set result equal to root_orig to capture all keys in root_orig - result = root_orig; - - combined_helper(root_orig, root_new, result, "", changed); - return result; -} - -} // namespace ouster diff --git a/ouster_library/CMakeLists.txt b/ouster_library/CMakeLists.txt new file mode 100644 index 00000000..f036c87e --- /dev/null +++ b/ouster_library/CMakeLists.txt @@ -0,0 +1,67 @@ +find_package(Eigen3 REQUIRED) + +function(ouster_library_common _TARGET) + message("Building Library: ${_TARGET}") + target_link_libraries(${_TARGET} + PUBLIC + Eigen3::Eigen) + target_include_directories(${_TARGET} SYSTEM + PUBLIC + $) + get_target_property(CLIENT_INCLUDE_DIRS ouster_client INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${_TARGET} + PUBLIC + $ + $ + $) + set_property(TARGET ${_TARGET} PROPERTY POSITION_INDEPENDENT_CODE ON) + set_target_properties(${_TARGET} PROPERTIES CXX_VISIBILITY_PRESET hidden) + target_link_libraries(${_TARGET} PRIVATE ouster_client) + message("Building Library: ${_TARGET}: Adding ouster_client") + if(BUILD_PCAP) + target_link_libraries(${_TARGET} PRIVATE ouster_pcap) + get_target_property(_INCLUDE_DIRS ouster_pcap INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${_TARGET} + PUBLIC + $) + message("Building Library: ${_TARGET}: Adding ouster_pcap") + endif() + if(BUILD_OSF) + target_link_libraries(${_TARGET} PRIVATE ouster_osf) + get_target_property(_INCLUDE_DIRS ouster_osf INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${_TARGET} + PUBLIC + $) + message("Building Library: ${_TARGET}: Adding ouster_osf") + endif() + if(BUILD_VIZ) + target_link_libraries(${_TARGET} PRIVATE ouster_viz) + get_target_property(_INCLUDE_DIRS ouster_viz INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${_TARGET} + PUBLIC + $) + message("Building Library: ${_TARGET}: Adding ouster_viz") + endif() + + add_library(OusterSDK::${_TARGET} ALIAS ${_TARGET}) +endfunction() + +set(OUSTER_LIBRARY_OBJECTS "") +if(BUILD_SHARED_LIBRARY) + # FIGURE OUT HOW TO SKIP VIZ OSF AND PCAP HERE + add_library(shared_library SHARED + $ + $ + $ + $) + ouster_library_common(shared_library) + target_compile_definitions(shared_library PRIVATE BUILD_SHARED_LIBS_EXPORT) +endif() + +if(BUILD_SHARED_LIBRARY) + install(TARGETS shared_library + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION lib + INCLUDES DESTINATION include) +endif() diff --git a/ouster_library/empty.cpp b/ouster_library/empty.cpp new file mode 100644 index 00000000..e69de29b diff --git a/ouster_osf/CMakeLists.txt b/ouster_osf/CMakeLists.txt index 564f870d..f422a4af 100644 --- a/ouster_osf/CMakeLists.txt +++ b/ouster_osf/CMakeLists.txt @@ -10,8 +10,7 @@ option(OUSTER_OSF_NO_THREADING "Don't use threads, useful for WASM targets" OFF) find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(Eigen3 REQUIRED) -find_package(jsoncpp REQUIRED) -find_package(spdlog REQUIRED) +find_package(Threads) include(Coverage) # TODO: Extract to a separate FindFlatbuffers cmake file @@ -20,20 +19,11 @@ include(Coverage) # because it started failing out of blue :idk:scream: will see. if(NOT CONAN_EXPORTED) find_package(Flatbuffers REQUIRED) - if(NOT DEFINED FLATBUFFERS_FLATC_EXECUTABLE) - set(FLATBUFFERS_FLATC_EXECUTABLE flatbuffers::flatc) - endif() - message(STATUS "Flatbuffers found: ${Flatbuffers_DIR}" ) else() find_package(flatbuffers REQUIRED) - if(WIN32) - set(FLATBUFFERS_FLATC_EXECUTABLE flatc.exe) - else() - set(FLATBUFFERS_FLATC_EXECUTABLE flatc) - endif() - message(STATUS "flatbuffers found: ${Flatbuffers_DIR}" ) endif() + # TODO[pb]: Move to flatbuffers 2.0 and check do we still need this??? # Using this link lib search method so to get shared .so library and not # static in Debian systems. But it correctly find static lib in vcpkg/manylinux @@ -100,13 +90,21 @@ add_library(ouster_osf STATIC src/compat_ops.cpp src/file.cpp src/reader.cpp src/operations.cpp - src/json_utils.cpp src/fb_utils.cpp src/writer.cpp + src/async_writer.cpp + src/png_lidarscan_encoder.cpp ) - +set_property(TARGET ouster_osf PROPERTY POSITION_INDEPENDENT_CODE ON) +if(BUILD_SHARED_LIBRARY) + set_target_properties(ouster_osf PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() CodeCoverageFunctionality(ouster_osf) +if(BUILD_SHARED_LIBRARY) + target_compile_definitions(ouster_osf PRIVATE BUILD_SHARED_LIBS_EXPORT) +endif() + if (OUSTER_OSF_NO_MMAP) target_compile_definitions(ouster_osf PRIVATE OUSTER_OSF_NO_MMAP) endif() @@ -116,14 +114,21 @@ if (OUSTER_OSF_NO_THREADING) endif() # Include Flatbuffers generated C++ headers +target_include_directories(ouster_osf INTERFACE ${FLATBUFFERS_INCLUDE_DIR}) + target_include_directories(ouster_osf PUBLIC $ $ + PRIVATE + $ ) target_link_libraries(ouster_osf PUBLIC - OusterSDK::ouster_client OusterSDK::ouster_pcap PNG::PNG - flatbuffers::flatbuffers ZLIB::ZLIB jsoncpp_lib + OusterSDK::ouster_client + OusterSDK::ouster_pcap + PRIVATE + PNG::PNG + flatbuffers::flatbuffers ZLIB::ZLIB ) target_include_directories(ouster_osf PUBLIC $ @@ -132,7 +137,6 @@ target_include_directories(ouster_osf PUBLIC add_dependencies(ouster_osf cpp_gen) add_library(OusterSDK::ouster_osf ALIAS ouster_osf) - # Check if ouster_client compiled with -mavx2 option and add those to ouster_osf # If we are not matching -mavx2 compile flag Eigen lib functions might crash with # SegFault and double free/memory corruption errors... diff --git a/ouster_osf/include/ouster/osf/async_writer.h b/ouster_osf/include/ouster/osf/async_writer.h new file mode 100644 index 00000000..65783476 --- /dev/null +++ b/ouster_osf/include/ouster/osf/async_writer.h @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "ouster/impl/threadsafe_queue.h" +#include "ouster/osf/osf_encoder.h" +#include "ouster/osf/writer.h" + +namespace ouster { +namespace osf { + +/** + * %OSF AsyncWriter wraps osf::Writer so that saving occurs in a background + * thread. Calls to save() return a std::future instead of void to enable + * propagating exceptions from the save thread. + */ +class OUSTER_API_CLASS AsyncWriter { + public: + /** + * @param[in] filename The filename to output to. + * @param[in] info The sensor info vector to use for a multi stream OSF + * file. + * @param[in] fields_to_write The fields from scans to actually save into + * the OSF. If not provided uses the fields from + * the first saved lidar scan for this sensor. + * This parameter is optional. + * @param[in] chunk_size The chunksize to use for the OSF file, this + * parameter is optional. + * @param[in] encoder An optional Encoder instance for configuring how the + * Writer should encode the OSF. + */ + OUSTER_API_FUNCTION + AsyncWriter(const std::string& filename, + const std::vector& info, + const std::vector& fields_to_write = + std::vector(), + uint32_t chunk_size = 0, + std::shared_ptr encoder = nullptr); + + /** + * Save a single scan to the specified stream_index in an OSF + * file. + * + * The concept of the stream_index is related to the sensor_info vector. + * Consider the following: + @code{.cpp} + sensor_info info1; // The first sensor in this OSF file + sensor_info info2; // The second sensor in this OSF file + sensor_info info3; // The third sensor in this OSF file + + Writer output = Writer(filename, {info1, info2, info3}); + + LidarScan scan = RANDOM_SCAN_HERE; + + // To save the LidarScan of scan to the first sensor, you would do the + // following + output.save(0, scan); + + // To save the LidarScan of scan to the second sensor, you would do the + // following + output.save(1, scan); + + // To save the LidarScan of scan to the third sensor, you would do the + // following + output.save(2, scan); + @endcode + * + * @throws std::logic_error Will throw exception on writer being closed. + * @throws std::logic_error ///< Will throw exception on + * ///< out of bound stream_index. + * + * @param[in] stream_index The index of the corrosponding sensor_info to + * use. + * @param[in] scan The scan to save. + * @return a future, which can propagate exceptions that may have occurred + * in the background from the save thread. + */ + OUSTER_API_FUNCTION + std::future save(uint32_t stream_index, const LidarScan& scan); + + /** + * Save a single scan with the specified timestamp to the + * specified stream_index in an OSF file. + * + * @throws std::logic_error Will throw exception on writer being closed. + * @throws std::logic_error ///< Will throw exception on + * ///< out of bound stream_index. + * + * @param[in] stream_index The index of the corrosponding sensor_info to + * use. + * @param[in] scan The scan to save. + * @param[in] timestamp Receive timestamp to index this scan with. + * @return a future, which can propagate exceptions that may have occurred + * in the background from the save thread. + */ + OUSTER_API_FUNCTION + std::future save(uint32_t stream_index, const LidarScan& scan, + ouster::osf::ts_t timestamp); + + /** + * Save multiple scans to the OSF file. + * + * The concept of the stream_index is related to the sensor_info vector. + * Consider the following: + @code{.cpp} + sensor_info info1; // The first sensor in this OSF file + sensor_info info2; // The second sensor in this OSF file + sensor_info info3; // The third sensor in this OSF file + + Writer output = Writer(filename, {info1, info2, info3}); + + LidarScan sensor1_scan = RANDOM_SCAN_HERE; + LidarScan sensor2_scan = RANDOM_SCAN_HERE; + LidarScan sensor3_scan = RANDOM_SCAN_HERE; + + // To save the scans matched appropriately to their sensors, you would do + // the following + output.save({sensor1_scan, sensor2_scan, sensor3_scan}); + @endcode + * + * + * @throws std::logic_error Will throw exception on writer being closed + * + * @param[in] scans The vector of scans to save. + * @return a vector of futures, which can propagate exceptions that may have + * occurred in the background from the save thread. + */ + OUSTER_API_FUNCTION + std::vector> save(const std::vector& scans); + + /** + * Finish file with a proper metadata object, and header. + * This method blocks until all remaining tasks generated by save() have + * been finalized. + */ + OUSTER_API_FUNCTION + void close(); + + private: + /** + * Encapsulates everything that's needed to encode and save the provided + * lidar scan into the OSF. + * + */ + struct OUSTER_API_IGNORE LidarScanMessage { + int stream_index_; + ouster::osf::ts_t timestamp_; + ouster::LidarScan lidar_scan_; + std::promise promise_; + + // Note - this constructor deliberately copies the LidarScan because it + // could be modified in a different thread. + OUSTER_API_IGNORE + LidarScanMessage(int stream_index, const ouster::osf::ts_t& timestamp, + const ouster::LidarScan& lidar_scan, + std::promise& promise) + : stream_index_(stream_index), + timestamp_(timestamp), + lidar_scan_(lidar_scan), + promise_(std::move(promise)) {} + LidarScanMessage(const LidarScanMessage&) = delete; + OUSTER_API_IGNORE + LidarScanMessage(LidarScanMessage&&) = default; + }; + + Writer writer_; + /** + * Internal job queue, used to keep the save function from blocking the + * calling thread for the duration of encoding. + */ + ThreadsafeQueue save_queue_; + std::thread save_thread_; + std::mutex stream_mutex_; + + /** + * A runnable used to handle writes in the thread 'save_thread_'. + */ + void save_thread_method(); + + /** + * Exception propagated from the save thread. + */ + std::exception_ptr save_exception_; +}; + +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/include/ouster/osf/basics.h b/ouster_osf/include/ouster/osf/basics.h index b3a8205a..e4416ff5 100644 --- a/ouster_osf/include/ouster/osf/basics.h +++ b/ouster_osf/include/ouster/osf/basics.h @@ -13,6 +13,7 @@ #include "metadata_generated.h" #include "ouster/lidar_scan.h" #include "ouster/types.h" +#include "ouster/visibility.h" // OSF basic types for LidarSensor and LidarScan/Imu Streams #include "os_sensor/common_generated.h" @@ -63,6 +64,7 @@ enum ChunksLayout { * @param[in] chunks_layout The data to get the string representation format * @return The string representation */ +OUSTER_API_FUNCTION std::string to_string(ChunksLayout chunks_layout); /** @@ -71,6 +73,7 @@ std::string to_string(ChunksLayout chunks_layout); * @param[in] s The String Representation of ChunksLayout * @return The corrosponding ChunksLayout object */ +OUSTER_API_FUNCTION ChunksLayout chunks_layout_of_string(const std::string& s); // stable common types mapped to ouster::osf @@ -94,6 +97,7 @@ static constexpr uint32_t FLATBUFFERS_PREFIX_LENGTH = 4; * @param[in] status The data to get the string representation format * @return The string representation */ +OUSTER_API_FUNCTION std::string to_string(const HEADER_STATUS status); /** @@ -104,6 +108,7 @@ std::string to_string(const HEADER_STATUS status); * @param[in] max_show_count The number of bytes to dump. This arg is optional. * @return The string representation */ +OUSTER_API_FUNCTION std::string to_string(const uint8_t* buf, const size_t count, const size_t max_show_count = 0); @@ -114,6 +119,7 @@ std::string to_string(const uint8_t* buf, const size_t count, * @param[in] filename The file to read. * @return The text of the file specified. */ +OUSTER_API_FUNCTION std::string read_text_file(const std::string& filename); /** @@ -122,6 +128,7 @@ std::string read_text_file(const std::string& filename); * @param[in] buf Pointer to Flatbuffers buffer stared with prefixed size * @return the size recovered from the stored prefix size */ +OUSTER_API_FUNCTION uint32_t get_prefixed_size(const uint8_t* buf); /** @@ -130,6 +137,7 @@ uint32_t get_prefixed_size(const uint8_t* buf); * @param[in] buf Pointer to Flatbuffers buffer stared with prefixed size * @return the calculated size of the block */ +OUSTER_API_FUNCTION uint32_t get_block_size(const uint8_t* buf); /** @@ -144,6 +152,7 @@ uint32_t get_block_size(const uint8_t* buf); * if first prefixed size bytes are broken. * @return true if CRC field is correct, false otherwise */ +OUSTER_API_FUNCTION bool check_prefixed_size_block_crc( const uint8_t* buf, const uint32_t max_size = std::numeric_limits::max()); diff --git a/ouster_osf/include/ouster/osf/crc32.h b/ouster_osf/include/ouster/osf/crc32.h index 45367b03..715ce6e8 100644 --- a/ouster_osf/include/ouster/osf/crc32.h +++ b/ouster_osf/include/ouster/osf/crc32.h @@ -12,6 +12,8 @@ #include +#include "ouster/visibility.h" + namespace ouster { namespace osf { @@ -31,6 +33,7 @@ const uint32_t CRC_BYTES_SIZE = 4; * @param[in] size Size of the buffer in bytes. * @return CRC32 value */ +OUSTER_API_FUNCTION uint32_t crc32(const uint8_t* buf, uint32_t size); /** @@ -44,6 +47,7 @@ uint32_t crc32(const uint8_t* buf, uint32_t size); * @param[in] size Size of the buffer in bytes. * @return CRC32 value */ +OUSTER_API_FUNCTION uint32_t crc32(uint32_t initial_crc, const uint8_t* buf, uint32_t size); } // namespace osf diff --git a/ouster_osf/include/ouster/osf/file.h b/ouster_osf/include/ouster/osf/file.h index ca6aab21..d4d9503a 100644 --- a/ouster_osf/include/ouster/osf/file.h +++ b/ouster_osf/include/ouster/osf/file.h @@ -12,6 +12,7 @@ #include #include "ouster/osf/basics.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -41,11 +42,12 @@ using ChunkBuffer = std::vector; * Interface to abstract the way of how we handle file system read/write * operations. */ -class OsfFile { +class OUSTER_API_CLASS OsfFile { public: /** * Default constructor, sets most data to nullptr and 0. */ + OUSTER_API_FUNCTION explicit OsfFile(); /** @@ -55,12 +57,14 @@ class OsfFile { * @param[in] filename The OSF file to open * @param[in] mode The mode to open the file in, this argument is optional. */ + OUSTER_API_FUNCTION explicit OsfFile(const std::string& filename, OpenMode mode = OpenMode::READ); /** * Cleans up any filebuffers/memory mapping. */ + OUSTER_API_FUNCTION ~OsfFile(); // Header Info @@ -70,6 +74,7 @@ class OsfFile { * * @return The size of the OSF file in bytes. */ + OUSTER_API_FUNCTION uint64_t size() const; /** @@ -77,6 +82,7 @@ class OsfFile { * * @return The filename of the open OSF file. */ + OUSTER_API_FUNCTION std::string filename() const; /** @@ -84,6 +90,7 @@ class OsfFile { * * @return The version of the OSF file. */ + OUSTER_API_FUNCTION OSF_VERSION version(); /** @@ -94,6 +101,7 @@ class OsfFile { * * @return Offset to the metadata in bytes */ + OUSTER_API_FUNCTION uint64_t metadata_offset(); /** @@ -104,6 +112,7 @@ class OsfFile { * * @return Offset to the chunks in bytes */ + OUSTER_API_FUNCTION uint64_t chunks_offset(); /** @@ -111,6 +120,7 @@ class OsfFile { * * @return If the header, session, and file_info blocks are valid. */ + OUSTER_API_FUNCTION bool valid(); /** @@ -119,6 +129,7 @@ class OsfFile { * * @return If the OSF file is good or not. */ + OUSTER_API_FUNCTION bool good() const; // Convenience operators @@ -129,6 +140,7 @@ class OsfFile { * * @return If the OSF file is good or not, negated. */ + OUSTER_API_FUNCTION bool operator!() const; /** @@ -138,6 +150,7 @@ class OsfFile { * * @return If the OSF file is good or not. */ + OUSTER_API_FUNCTION explicit operator bool() const; /** @@ -145,6 +158,7 @@ class OsfFile { * * @return The current offset in the OSF file. */ + OUSTER_API_FUNCTION uint64_t offset() const; /** @@ -157,6 +171,7 @@ class OsfFile { * @param[in] pos position in the file * @return A reference to `this` object. */ + OUSTER_API_FUNCTION OsfFile& seek(uint64_t pos); /** @@ -173,6 +188,7 @@ class OsfFile { * @param[in] count The number of bytes to write to buf. * @return A reference to `this` object. */ + OUSTER_API_FUNCTION OsfFile& read(uint8_t* buf, const uint64_t count); /** @@ -180,6 +196,7 @@ class OsfFile { * * @return Is the OSF file memory mapped or not. */ + OUSTER_API_FUNCTION bool is_memory_mapped() const; /** @@ -194,6 +211,7 @@ class OsfFile { * argument is optional. * @return The pointer to the OSF file. */ + OUSTER_API_FUNCTION const uint8_t* buf(const uint64_t offset = 0) const; /** @@ -203,6 +221,7 @@ class OsfFile { * and Reader. * @sa ouster::osf::MessageRef, ouster::osf::Reader */ + OUSTER_API_FUNCTION void close(); /** @@ -210,17 +229,20 @@ class OsfFile { * * @return The string representation of OsfFile */ + OUSTER_API_FUNCTION std::string to_string(); /** * Copy policy: * Don't allow the copying of the file handler */ + OUSTER_API_FUNCTION OsfFile(const OsfFile&) = delete; /** * @copydoc OsfFile::OsfFile(const OsfFile&) */ + OUSTER_API_FUNCTION OsfFile& operator=(const OsfFile&) = delete; /** @@ -230,11 +252,13 @@ class OsfFile { * * @param[in] other The OSF file to move */ + OUSTER_API_FUNCTION OsfFile(OsfFile&& other); /** * @copydoc OsfFile::OsfFile(OsfFile&& other) */ + OUSTER_API_FUNCTION OsfFile& operator=(OsfFile&& other); /** @@ -245,6 +269,7 @@ class OsfFile { * @param[in] offset The offset to read the chunk from. * @return Shared pointer to the chunk. nullptr if osf file is bad */ + OUSTER_API_FUNCTION std::shared_ptr read_chunk(uint64_t offset); /** @@ -252,6 +277,7 @@ class OsfFile { * * @return Pointer to the header chunk. nullptr if filestream is bad. */ + OUSTER_API_FUNCTION uint8_t* get_header_chunk_ptr(); /** @@ -259,6 +285,7 @@ class OsfFile { * * @return Pointer to the metadata chunk. nullptr if filestream is bad. */ + OUSTER_API_FUNCTION uint8_t* get_metadata_chunk_ptr(); private: diff --git a/ouster_osf/include/ouster/osf/layout_streaming.h b/ouster_osf/include/ouster/osf/layout_streaming.h index 9555c871..8c2e2133 100644 --- a/ouster_osf/include/ouster/osf/layout_streaming.h +++ b/ouster_osf/include/ouster/osf/layout_streaming.h @@ -10,6 +10,7 @@ #include "ouster/osf/meta_streaming_info.h" #include "ouster/osf/writer.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -39,12 +40,13 @@ constexpr uint32_t STREAMING_DEFAULT_CHUNK_SIZE = * possible). However if a single message size is bigger than specified * `chunk_size` it's still recorded. */ -class StreamingLayoutCW : public ChunksWriter { +class OUSTER_API_CLASS StreamingLayoutCW : public ChunksWriter { public: /** * @param[in] writer Writer object for use when writing messages * @param[in] chunk_size The chunk size to use, this arg is optional. */ + OUSTER_API_FUNCTION StreamingLayoutCW(Writer& writer, uint32_t chunk_size = STREAMING_DEFAULT_CHUNK_SIZE); @@ -53,6 +55,7 @@ class StreamingLayoutCW : public ChunksWriter { * * @throws std::logic_error Exception on inconsistent timestamps. */ + OUSTER_API_FUNCTION void save_message(const uint32_t stream_id, const ts_t receive_ts, const ts_t sensor_ts, const std::vector& buf) override; @@ -60,11 +63,13 @@ class StreamingLayoutCW : public ChunksWriter { /** * @copydoc ChunksWriter::finish */ + OUSTER_API_FUNCTION void finish() override; /** * @copydoc ChunksWriter::chunk_size */ + OUSTER_API_FUNCTION uint32_t chunk_size() const override; private: diff --git a/ouster_osf/include/ouster/osf/lidarscan_encoder.h b/ouster_osf/include/ouster/osf/lidarscan_encoder.h new file mode 100644 index 00000000..198da3d6 --- /dev/null +++ b/ouster_osf/include/ouster/osf/lidarscan_encoder.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ +#pragma once + +#include + +#include "ouster/lidar_scan.h" +#include "ouster/types.h" +#include "ouster/visibility.h" + +namespace ouster { +namespace osf { + +// Encoded single PNG buffer +using ScanChannelData = std::vector; + +// Encoded PNG buffers +using ScanData = std::vector; + +class OUSTER_API_CLASS LidarScanEncoder { + public: + OUSTER_API_FUNCTION + virtual ~LidarScanEncoder() = default; + + private: + // TODO these methods do essentially the same thing and should be + // deduplicated. Standard fields are stored in destaggered form, but this + // should be a detail specific to and hidden by the encoder. + + // This method is for standard destaggered fields. + virtual bool fieldEncode(const LidarScan& lidar_scan, + const ouster::FieldType& field_type, + const std::vector& px_offset, + ScanData& scan_data, size_t scan_idx) const = 0; + + // This method is for custom fields. + virtual ScanChannelData encodeField(const ouster::Field& field) const = 0; + friend class LidarScanStream; +}; + +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/include/ouster/osf/meta_extrinsics.h b/ouster_osf/include/ouster/osf/meta_extrinsics.h index 8f896064..3b28e1a1 100644 --- a/ouster_osf/include/ouster/osf/meta_extrinsics.h +++ b/ouster_osf/include/ouster/osf/meta_extrinsics.h @@ -13,6 +13,7 @@ #include "ouster/osf/metadata.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -26,7 +27,7 @@ namespace osf { * Flat Buffer Reference: * fb/os_sensor/extrinsics.fbs */ -class Extrinsics : public MetadataEntryHelper { +class OUSTER_API_CLASS Extrinsics : public MetadataEntryHelper { public: /** * @param[in] extrinsics ///< The extrinsic matrix to store @@ -39,6 +40,7 @@ class Extrinsics : public MetadataEntryHelper { * ///< system of records or just name the source * ///< originator of the extrinsics information. */ + OUSTER_API_FUNCTION explicit Extrinsics(const mat4d& extrinsics, uint32_t ref_meta_id = 0, const std::string& name = ""); @@ -47,6 +49,7 @@ class Extrinsics : public MetadataEntryHelper { * * @return The eigen extrinsics matrix. */ + OUSTER_API_FUNCTION const mat4d& extrinsics() const; /** @@ -54,6 +57,7 @@ class Extrinsics : public MetadataEntryHelper { * * @return The extrinsics name. */ + OUSTER_API_FUNCTION const std::string& name() const; /** @@ -61,11 +65,13 @@ class Extrinsics : public MetadataEntryHelper { * * @return The reference metadata id. */ + OUSTER_API_FUNCTION uint32_t ref_meta_id() const; /** * @copydoc MetadataEntry::buffer */ + OUSTER_API_FUNCTION std::vector buffer() const final; /** @@ -78,6 +84,7 @@ class Extrinsics : public MetadataEntryHelper { * @param[in] buf The byte vector to construct an Extrinsics object from. * @return The new Extrinsics cast as a MetadataEntry */ + OUSTER_API_FUNCTION static std::unique_ptr from_buffer( const std::vector& buf); @@ -88,6 +95,7 @@ class Extrinsics : public MetadataEntryHelper { * * @return The string representation for the Extrinsics object. */ + OUSTER_API_FUNCTION std::string repr() const override; private: @@ -123,12 +131,13 @@ class Extrinsics : public MetadataEntryHelper { * @ingroup OSFTraitsExtrinsics */ template <> -struct MetadataTraits { +struct OUSTER_API_CLASS MetadataTraits { /** * Return the OSF type string. * * @return The OSF type string "ouster/v1/os_sensor/Extrinsics". */ + OUSTER_API_FUNCTION static const std::string type() { return "ouster/v1/os_sensor/Extrinsics"; } }; diff --git a/ouster_osf/include/ouster/osf/meta_lidar_sensor.h b/ouster_osf/include/ouster/osf/meta_lidar_sensor.h index 698929d1..65ee3820 100644 --- a/ouster_osf/include/ouster/osf/meta_lidar_sensor.h +++ b/ouster_osf/include/ouster/osf/meta_lidar_sensor.h @@ -13,6 +13,7 @@ #include "ouster/osf/metadata.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -26,19 +27,21 @@ namespace osf { * Flat Buffer Reference: * fb/os_sensor/lidar_sensor.fbs */ -class LidarSensor : public MetadataEntryHelper { +class OUSTER_API_CLASS LidarSensor : public MetadataEntryHelper { using sensor_info = ouster::sensor::sensor_info; public: /** * @param[in] si Initialize the LidarSensor with a sensor_info object. */ + OUSTER_API_FUNCTION explicit LidarSensor(const sensor_info& si); /** * @param[in] sensor_metadata Initialize the LidarSensor with a json string * representation of the sensor_info object. */ + OUSTER_API_FUNCTION explicit LidarSensor(const std::string& sensor_metadata); /** @@ -46,6 +49,7 @@ class LidarSensor : public MetadataEntryHelper { * * @return The sensor_info associated with the LidarSensor. */ + OUSTER_API_FUNCTION const sensor_info& info() const; /** @@ -55,11 +59,13 @@ class LidarSensor : public MetadataEntryHelper { * @return ///< The json string representation of the * ///< sensor_info object. */ + OUSTER_API_FUNCTION const std::string& metadata() const; /** * @copydoc MetadataEntry::buffer */ + OUSTER_API_FUNCTION std::vector buffer() const final; /** @@ -72,6 +78,7 @@ class LidarSensor : public MetadataEntryHelper { * @param[in] buf The raw flatbuffer byte vector to initialize from. * @return The new LidarSensor cast as a MetadataEntry */ + OUSTER_API_FUNCTION static std::unique_ptr from_buffer( const std::vector& buf); @@ -82,6 +89,7 @@ class LidarSensor : public MetadataEntryHelper { * * @return The string representation for the LidarSensor object. */ + OUSTER_API_FUNCTION std::string repr() const override; /** @@ -91,6 +99,7 @@ class LidarSensor : public MetadataEntryHelper { * * @copydoc LidarSensor::repr */ + OUSTER_API_FUNCTION std::string to_string() const override; private: @@ -113,12 +122,13 @@ class LidarSensor : public MetadataEntryHelper { * @ingroup OSFTraitsLidarSensor */ template <> -struct MetadataTraits { +struct OUSTER_API_CLASS MetadataTraits { /** * Return the OSF type string. * * @return The OSF type string "ouster/v1/os_sensor/LidarSensor". */ + OUSTER_API_FUNCTION static const std::string type() { return "ouster/v1/os_sensor/LidarSensor"; } diff --git a/ouster_osf/include/ouster/osf/meta_streaming_info.h b/ouster_osf/include/ouster/osf/meta_streaming_info.h index fe651718..47a61689 100644 --- a/ouster_osf/include/ouster/osf/meta_streaming_info.h +++ b/ouster_osf/include/ouster/osf/meta_streaming_info.h @@ -13,6 +13,7 @@ #include "ouster/osf/metadata.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -23,7 +24,7 @@ namespace osf { * Flat Buffer Reference: * fb/streaming/streaming_info.fbs :: ChunkInfo */ -struct ChunkInfo { +struct OUSTER_API_CLASS ChunkInfo { /** * The offset in the flatbuffer where * the chunk is located. @@ -56,7 +57,7 @@ struct ChunkInfo { * Flat Buffer Reference: * fb/streaming/streaming_info.fbs :: StreamStats */ -struct StreamStats { +struct OUSTER_API_CLASS StreamStats { /** * The specific stream the chunk is associated with. * @@ -116,6 +117,7 @@ struct StreamStats { /** * Default constructor, sets everthing to 0. */ + OUSTER_API_FUNCTION StreamStats() = default; /** @@ -127,6 +129,7 @@ struct StreamStats { * @param[in] sensor_ts Add to the sensor timestamps. * @param[in] msg_size Set the average message size to the specified value. */ + OUSTER_API_FUNCTION StreamStats(uint32_t s_id, ts_t receive_ts, ts_t sensor_ts, uint32_t msg_size); @@ -138,6 +141,7 @@ struct StreamStats { * @param[in] sensor_ts Add another sensor timestamp * @param[in] msg_size Add another message size and calculate the average. */ + OUSTER_API_FUNCTION void update(ts_t receive_ts, ts_t sensor_ts, uint32_t msg_size); }; @@ -148,6 +152,7 @@ struct StreamStats { * * @return The string representation for a ChunkInfo object. */ +OUSTER_API_FUNCTION std::string to_string(const ChunkInfo& chunk_info); /** @@ -157,6 +162,7 @@ std::string to_string(const ChunkInfo& chunk_info); * * @return The string representation for a StreamStats object. */ +OUSTER_API_FUNCTION std::string to_string(const StreamStats& stream_stats); /** @@ -168,8 +174,10 @@ std::string to_string(const StreamStats& stream_stats); * Flat Buffer Reference: * fb/streaming/streaming_info.fbs :: StreamingInfo */ -class StreamingInfo : public MetadataEntryHelper { +class OUSTER_API_CLASS StreamingInfo + : public MetadataEntryHelper { public: + OUSTER_API_FUNCTION StreamingInfo() {} /** @@ -182,6 +190,7 @@ class StreamingInfo : public MetadataEntryHelper { * to be used to generate a * stream_id/StreamStats map. */ + OUSTER_API_FUNCTION StreamingInfo( const std::vector>& chunks_info, const std::vector>& stream_stats); @@ -190,6 +199,7 @@ class StreamingInfo : public MetadataEntryHelper { * @param[in] chunks_info ///< Map containing stream_id/ChunkInfo data. * @param[in] stream_stats ///< Map containing stream_id/StreamStats data. */ + OUSTER_API_FUNCTION StreamingInfo(const std::map& chunks_info, const std::map& stream_stats); @@ -198,6 +208,7 @@ class StreamingInfo : public MetadataEntryHelper { * * @return The chunk_info map. stream_id/ChunkInfo data. */ + OUSTER_API_FUNCTION std::map& chunks_info(); /** @@ -205,11 +216,13 @@ class StreamingInfo : public MetadataEntryHelper { * * @return The stream stat map. stream_id/StreamStats data. */ + OUSTER_API_FUNCTION std::map& stream_stats(); /** * @copydoc MetadataEntry::buffer */ + OUSTER_API_FUNCTION std::vector buffer() const override final; /** @@ -222,6 +235,7 @@ class StreamingInfo : public MetadataEntryHelper { * @param[in] buf The raw flatbuffer byte vector to initialize from. * @return The new StreamingInfo cast as a MetadataEntry */ + OUSTER_API_FUNCTION static std::unique_ptr from_buffer( const std::vector& buf); @@ -232,6 +246,7 @@ class StreamingInfo : public MetadataEntryHelper { * * @return The string representation for the LidarSensor object. */ + OUSTER_API_FUNCTION std::string repr() const override; private: @@ -259,12 +274,13 @@ class StreamingInfo : public MetadataEntryHelper { * @ingroup OSFTraitsStreamingInfo */ template <> -struct MetadataTraits { +struct OUSTER_API_CLASS MetadataTraits { /** * Return the OSF type string. * * @return The OSF type string "ouster/v1/streaming/StreamingInfo". */ + OUSTER_API_FUNCTION static const std::string type() { return "ouster/v1/streaming/StreamingInfo"; } diff --git a/ouster_osf/include/ouster/osf/metadata.h b/ouster_osf/include/ouster/osf/metadata.h index 1f9a35f3..fdd15a77 100644 --- a/ouster_osf/include/ouster/osf/metadata.h +++ b/ouster_osf/include/ouster/osf/metadata.h @@ -19,6 +19,7 @@ #include "flatbuffers/flatbuffers.h" #include "ouster/osf/basics.h" +#include "ouster/visibility.h" /// @todo fix api docs in this file /// @todo add equality operators @@ -94,7 +95,7 @@ inline const std::string metadata_type() { * from_buffer(buf, type) function. * */ -class MetadataEntry { +class OUSTER_API_CLASS MetadataEntry { public: /** * Function type to recover metadata object from buffer. @@ -106,6 +107,7 @@ class MetadataEntry { * @return Type of the metadata, used to identify the object type in * serialized OSF and as key in deserialization registry */ + OUSTER_API_FUNCTION virtual std::string type() const = 0; /** @@ -114,6 +116,7 @@ class MetadataEntry { * NOTE: Introduced as a convenience/(HACK?) to simpler reconstruct * and cast dynamic objects from MetadataEntryRef */ + OUSTER_API_FUNCTION virtual std::string static_type() const = 0; /** @@ -122,6 +125,7 @@ class MetadataEntry { * * @return Should return a clone of the current MetadataEntry */ + OUSTER_API_FUNCTION virtual std::unique_ptr clone() const = 0; /** @@ -130,6 +134,7 @@ class MetadataEntry { * * @return The byte vector representation of the metadata. */ + OUSTER_API_FUNCTION virtual std::vector buffer() const = 0; /** @@ -140,6 +145,7 @@ class MetadataEntry { * @param[in] type_str The type string from the derived type. * @return A new object of the derived type cast as a MetadataEntry */ + OUSTER_API_FUNCTION static std::unique_ptr from_buffer( const std::vector& buf, const std::string type_str); @@ -149,6 +155,7 @@ class MetadataEntry { * * @return The string representation for the internal metadata object. */ + OUSTER_API_FUNCTION virtual std::string repr() const; /** @@ -158,6 +165,7 @@ class MetadataEntry { * * @return The string representation of the whole metadata entry. */ + OUSTER_API_FUNCTION virtual std::string to_string() const; /** @@ -166,6 +174,7 @@ class MetadataEntry { * * @param[in] id The unique id to set. */ + OUSTER_API_FUNCTION void setId(uint32_t id); /** @@ -176,6 +185,7 @@ class MetadataEntry { * * @return The unique id of this object. */ + OUSTER_API_FUNCTION uint32_t id() const; /** @@ -220,6 +230,7 @@ class MetadataEntry { * @param[in] fbb The flatbuffer builder to use to make the entry. * @return An offset into a flatbuffer for the new entry. */ + OUSTER_API_FUNCTION flatbuffers::Offset make_entry( flatbuffers::FlatBufferBuilder& fbb) const; @@ -230,8 +241,10 @@ class MetadataEntry { * * @return The static registry used to register metadata types. */ + OUSTER_API_FUNCTION static std::map& get_registry(); + OUSTER_API_FUNCTION virtual ~MetadataEntry() = default; private: @@ -264,6 +277,7 @@ std::shared_ptr metadata_pointer_as( }; /** @internal */ +OUSTER_API_FUNCTION void RegisterMetadata_internal_error_function_(std::string error); /** @@ -438,7 +452,7 @@ class MetadataEntryHelper : public MetadataEntry, * as() OR metadata_pointer_as() - using the * specified derived metadata class. */ -class MetadataEntryRef : public MetadataEntry { +class OUSTER_API_CLASS MetadataEntryRef : public MetadataEntry { public: /** * Creates the metadata reference from Flatbuffers v2::MetadataEntry buffer. @@ -446,6 +460,7 @@ class MetadataEntryRef : public MetadataEntry { * * @param[in] buf The buffer to create the MetadataEntryRef from. */ + OUSTER_API_FUNCTION explicit MetadataEntryRef(const uint8_t* buf); /** @@ -453,11 +468,13 @@ class MetadataEntryRef : public MetadataEntry { * * @return The type of the MetadataEntry. */ + OUSTER_API_FUNCTION std::string type() const override; /** * @copydoc type() */ + OUSTER_API_FUNCTION std::string static_type() const override; /** @@ -465,6 +482,7 @@ class MetadataEntryRef : public MetadataEntry { * * @return The cloned MetadataEntry object. */ + OUSTER_API_FUNCTION std::unique_ptr clone() const override; /** @@ -472,6 +490,7 @@ class MetadataEntryRef : public MetadataEntry { * * @return The raw underlying byte vector. */ + OUSTER_API_FUNCTION std::vector buffer() const final; /** @@ -481,6 +500,7 @@ class MetadataEntryRef : public MetadataEntry { * * @return The reconstructed object. */ + OUSTER_API_FUNCTION std::unique_ptr as_type() const; private: @@ -514,6 +534,7 @@ struct MetadataTraits { * * @return The type string "impl/MetadataEntryRef". */ + OUSTER_API_FUNCTION static const std::string type() { return "impl/MetadataEntryRef"; } }; @@ -525,7 +546,7 @@ struct MetadataTraits { * * Also can serialize itself to Flatbuffers collection of metadata. */ -class MetadataStore { +class OUSTER_API_CLASS MetadataStore { /** * Metadata id to MetadataEntry map. */ @@ -542,11 +563,13 @@ class MetadataStore { * * @return The metadata id of the entry added. */ + OUSTER_API_FUNCTION uint32_t add(MetadataEntry&& entry); /** * @copydoc add(MetadataEntry&& entry) */ + OUSTER_API_FUNCTION uint32_t add(MetadataEntry& entry); /** @@ -611,6 +634,7 @@ class MetadataStore { * @param[in] metadata_id The id to try and return the associated entry. * @return The MetadataEntry. */ + OUSTER_API_FUNCTION std::shared_ptr get(const uint32_t metadata_id) const { auto it = metadata_entries_.find(metadata_id); if (it == metadata_entries_.end()) return nullptr; @@ -642,6 +666,7 @@ class MetadataStore { * * @return The number of MetadataEntry objects. */ + OUSTER_API_FUNCTION size_t size() const; /** @@ -649,6 +674,7 @@ class MetadataStore { * * @return The entire map of MetadataEnty objects. */ + OUSTER_API_FUNCTION const MetadataEntriesMap& entries() const; /** @@ -658,6 +684,7 @@ class MetadataStore { * @param[in] fbb The flatbuffer builder to use. * @return The resulting serialized byte vector. */ + OUSTER_API_FUNCTION std::vector> make_entries(flatbuffers::FlatBufferBuilder& fbb) const; diff --git a/ouster_osf/include/ouster/osf/operations.h b/ouster_osf/include/ouster/osf/operations.h index 12468567..550952d4 100644 --- a/ouster_osf/include/ouster/osf/operations.h +++ b/ouster_osf/include/ouster/osf/operations.h @@ -13,6 +13,7 @@ #include "ouster/osf/basics.h" #include "ouster/osf/metadata.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -25,6 +26,7 @@ namespace osf { * metas) * @return JSON formatted string of the OSF metadata + header */ +OUSTER_API_FUNCTION std::string dump_metadata(const std::string& file, bool full = true); /** @@ -35,6 +37,7 @@ std::string dump_metadata(const std::string& file, bool full = true); * @param[in] with_decoding decode known messages (used to time a * reading + decoding together) */ +OUSTER_API_FUNCTION void parse_and_print(const std::string& file, bool with_decoding = false); /** @@ -44,6 +47,7 @@ void parse_and_print(const std::string& file, bool with_decoding = false); * @param[in] backup_file_name The path to store the metadata blob backup. * @return The number of the bytes written to the backup file. */ +OUSTER_API_FUNCTION int64_t backup_osf_file_metablob(const std::string& osf_file_name, const std::string& backup_file_name); @@ -54,6 +58,7 @@ int64_t backup_osf_file_metablob(const std::string& osf_file_name, * @param[in] backup_file_name The path to the metadata blob backup. * @return The number of the bytes written to the OSF file. */ +OUSTER_API_FUNCTION int64_t restore_osf_file_metablob(const std::string& osf_file_name, const std::string& backup_file_name); @@ -61,9 +66,10 @@ int64_t restore_osf_file_metablob(const std::string& osf_file_name, * Modify an OSF files sensor_info metadata. * * @param[in] file_name The OSF file to modify. - * @param[in] new_metadata The new metadata for the OSF file + * @param[out] new_metadata The new metadata for the OSF file * @return The number of the bytes written to the OSF file. */ +OUSTER_API_FUNCTION int64_t osf_file_modify_metadata( const std::string& file_name, const std::vector& new_metadata); diff --git a/ouster_osf/include/ouster/osf/osf_encoder.h b/ouster_osf/include/ouster/osf/osf_encoder.h new file mode 100644 index 00000000..440e0849 --- /dev/null +++ b/ouster_osf/include/ouster/osf/osf_encoder.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ +#pragma once + +#include "ouster/osf/lidarscan_encoder.h" +#include "ouster/visibility.h" + +namespace ouster { +namespace osf { + +/** + * @brief used to configure the osf::Writer class. + * + * Right now it only contains a shared ptr to a LidarScanEncoder, + * but in the future it may contain other items to allow parts of the OSF + * encoding to vary independently. + */ +class OUSTER_API_CLASS Encoder { + public: + OUSTER_API_FUNCTION + Encoder(const std::shared_ptr& lidar_scan_encoder) + : lidar_scan_encoder_{lidar_scan_encoder} {} + + OUSTER_API_FUNCTION + LidarScanEncoder& lidar_scan_encoder() const { + return *lidar_scan_encoder_; + } + + private: + std::shared_ptr lidar_scan_encoder_; +}; + +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/include/ouster/osf/png_lidarscan_encoder.h b/ouster_osf/include/ouster/osf/png_lidarscan_encoder.h new file mode 100644 index 00000000..b9b89f0a --- /dev/null +++ b/ouster_osf/include/ouster/osf/png_lidarscan_encoder.h @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ +#pragma once + +#include "ouster/lidar_scan.h" +#include "ouster/osf/lidarscan_encoder.h" +#include "ouster/visibility.h" + +namespace ouster { +namespace osf { + +/** + * Effect of png_set_compression(comp level): + * - (no png out): 2s, n/a + * - comp level 1: 39s, 648M (60% speedup vs default, 10% size increase) + * - comp level 2: 38s, 643M + * - comp level 3: 45s, 639M + * - comp level 4: 48s, 590M (47% speedup vs default, <1% size increase) + * - comp level 5: 61s, 589M + * - libpng default: 98s, 586M + * - comp level 9: 328s, 580M + * + * @todo investigate other zlib options + */ +static constexpr int DEFAULT_PNG_OSF_ZLIB_COMPRESSION_LEVEL = 1; + +class OUSTER_API_CLASS PngLidarScanEncoder + : public ouster::osf::LidarScanEncoder { + public: + OUSTER_API_FUNCTION + PngLidarScanEncoder(int compression_amount) + : compression_amount_{compression_amount} {} + + // TODO these methods do essentially the same thing and should be + // deduplicated. Standard fields are stored in destaggered form, but this + // should be a detail specific to and hidden by the encoder. + + // This method is for standard destaggered fields. + // FIXME[tws] method should be private, but "friend class/FRIEND_TEST" for + // the unit test isn't working for some reason + OUSTER_API_IGNORE + bool fieldEncode(const LidarScan& lidar_scan, + const ouster::FieldType& field_type, + const std::vector& px_offset, ScanData& scan_data, + size_t scan_idx) const override; + + // This method is for custom fields. + // FIXME[tws] method should be private, but "friend class/FRIEND_TEST" for + // the unit test isn't working for some reason + OUSTER_API_IGNORE + ScanChannelData encodeField(const ouster::Field& field) const override; + + private: + int compression_amount_{DEFAULT_PNG_OSF_ZLIB_COMPRESSION_LEVEL}; + + /** + * @defgroup OSFPngEncode8 Encoding Functionality. + * Encode img object into a 8 bit, Gray, PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img The image object to encode. + * @return false (0) if operation is successful, true (1) if error occured + */ + + /** + * @copydoc OSFPngEncode8 + * @param[in] px_offset Pixel shift per row used to destagger img data. + */ + template + bool encode8bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img, + const std::vector& px_offset) const; + + /** @copydoc OSFPngEncode8 */ + template + bool encode8bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img) const; + + /** + * Encode img object into a 16 bit, Gray, PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img The image object to encode. + * @param[in] px_offset Pixel shift per row used to destagger img data. + * @return false (0) if operation is successful, true (1) if error occured + */ + template + bool encode16bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img, + const std::vector& px_offset) const; + + /** + * Encode 2D image of a typical lidar scan field channel into a 16 bit, + * Gray, PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img The image object to encode. + * @return false (0) if operation is successful, true (1) if error occured + */ + template + bool encode16bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img) const; + + /** + * @defgroup OSFPngEncode32 Encoding Functionality. + * Encode 2D image of a typical lidar scan field channel into a 32 bit, + * RGBA, PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img 2D image or a single LidarScan field data. + * @return false (0) if operation is successful, true (1) if error occured + */ + + /** + * @copydoc OSFPngEncode32 + * @param[in] px_offset Pixel shift per row used to destagger img data. + */ + template + bool encode32bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img, + const std::vector& px_offset) const; + + /** @copydoc OSFPngEncode32 */ + template + bool encode32bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img) const; + + /** + * @defgroup OSFPngEncode24 Encoding Functionality. + * Encode 2D image of a typical lidar scan field channel into a 24 bit, RGB, + * PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img 2D image or a single LidarScan field data. + * @return false (0) if operation is successful, true (1) if error occured + */ + + /** + * @copydoc OSFPngEncode24 + * @param[in] px_offset Pixel shift per row used to destagger img data. + */ + template + bool encode24bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img, + const std::vector& px_offset) const; + + /** @copydoc OSFPngEncode24 */ + template + bool encode24bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img) const; + + /** + * @defgroup OSFPngEncode64 Encoding Functionality. + * Encode 2D image of a typical lidar scan field channel into a 64 bit, + * RGBA, PNG buffer. + * + * @tparam T The type to use for the output array. + * + * @param[out] res_buf The output buffer with a single encoded PNG. + * @param[in] img 2D image or a single LidarScan field data. + * @return false (0) if operation is successful, true (1) if error occured + */ + + /** + * @copydoc OSFPngEncode64 + * @param[in] px_offset Pixel shift per row used to destagger img data. + */ + template + bool encode64bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img, + const std::vector& px_offset) const; + + /** @copydoc OSFPngEncode64 */ + template + bool encode64bitImage(ScanChannelData& res_buf, + const Eigen::Ref>& img) const; +}; +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/include/ouster/osf/reader.h b/ouster_osf/include/ouster/osf/reader.h index 0d9bcfa2..aa4270c6 100644 --- a/ouster_osf/include/ouster/osf/reader.h +++ b/ouster_osf/include/ouster/osf/reader.h @@ -14,6 +14,7 @@ #include "ouster/osf/file.h" #include "ouster/osf/metadata.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -37,7 +38,7 @@ enum class ChunkValidity { * This struct is partially mapped to the Flat Buffer data. * Flat Buffer Reference: fb/metadata.fbs :: ChunkOffset */ -struct ChunkState { +struct OUSTER_API_CLASS ChunkState { /** * The current chunk's offset from the begining of the chunks section. * @@ -84,7 +85,7 @@ struct ChunkState { * * This struct is partially mapped to the Flat Buffer data. */ -struct ChunkInfoNode { +struct OUSTER_API_CLASS ChunkInfoNode { /** * The chunk offset from the begining of the chunks section. * @@ -134,7 +135,7 @@ struct ChunkInfoNode { /** * Chunks state map. Validity info and next offset. */ -class ChunksPile { +class OUSTER_API_CLASS ChunksPile { public: /** * stream_id to offset map. @@ -145,6 +146,7 @@ class ChunksPile { /** * Default blank constructor. */ + OUSTER_API_FUNCTION ChunksPile(); /** @@ -154,6 +156,7 @@ class ChunksPile { * @param[in] start_ts The first timestamp in the chunk. * @param[in] end_ts The first timestamp in the chunk. */ + OUSTER_API_FUNCTION void add(uint64_t offset, ts_t start_ts, ts_t end_ts); /** @@ -162,6 +165,7 @@ class ChunksPile { * @param[in] offset The offset to return the chunk for. * @return The chunk if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkState* get(uint64_t offset); /** @@ -171,6 +175,7 @@ class ChunksPile { * @param[in] stream_id The stream_id associated. * @param[in] message_count The number of messages. */ + OUSTER_API_FUNCTION void add_info(uint64_t offset, uint32_t stream_id, uint32_t message_count); /** @@ -179,6 +184,7 @@ class ChunksPile { * @param[in] offset The offset to return the streaming info for. * @return The streaming info if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkInfoNode* get_info(uint64_t offset); /** @@ -188,6 +194,7 @@ class ChunksPile { * @param[in] message_idx The specific message index to look for. * @return The streaming info if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkInfoNode* get_info_by_message_idx(uint32_t stream_id, uint32_t message_idx); @@ -198,6 +205,7 @@ class ChunksPile { * @param[in] ts The lower bound for the chunk. * @return The chunk if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkState* get_by_lower_bound_ts(uint32_t stream_id, const ts_t ts); /** @@ -206,6 +214,7 @@ class ChunksPile { * @param[in] offset The offset to return the next chunk for. * @return The chunk if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkState* next(uint64_t offset); /** @@ -214,6 +223,7 @@ class ChunksPile { * @param[in] offset The offset to return the next chunk for. * @return The chunk if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkState* next_by_stream(uint64_t offset); /** @@ -221,6 +231,7 @@ class ChunksPile { * * @return The chunk if found, or nullptr. */ + OUSTER_API_FUNCTION ChunkState* first(); /** @@ -228,6 +239,7 @@ class ChunksPile { * * @return The size of the chunk pile. */ + OUSTER_API_FUNCTION size_t size() const; /** @@ -235,6 +247,7 @@ class ChunksPile { * * @return If there is a message index. */ + OUSTER_API_FUNCTION bool has_message_idx() const; /** @@ -242,6 +255,7 @@ class ChunksPile { * * @return The stream_id to chunk offset map. */ + OUSTER_API_FUNCTION StreamChunksMap& stream_chunks(); /** @@ -250,6 +264,7 @@ class ChunksPile { * @throws std::logic_error exception on non increasing timestamps. * @throws std::logic_error exception on non existent info. */ + OUSTER_API_FUNCTION void link_stream_chunks(); private: @@ -276,6 +291,7 @@ class ChunksPile { * @param[in] chunk_state The data to get the string representation for * @return The string representation */ +OUSTER_API_FUNCTION std::string to_string(const ChunkState& chunk_state); /** @@ -284,6 +300,7 @@ std::string to_string(const ChunkState& chunk_state); * @param[in] chunk_info The data to get the string representation format * @return The string representation */ +OUSTER_API_FUNCTION std::string to_string(const ChunkInfoNode& chunk_info); // Forward Decls @@ -299,7 +316,7 @@ class MessagesStreamingRange; /** * Chunk forward iterator in order of offset. */ -struct ChunksIter { +struct OUSTER_API_CLASS ChunksIter { using iterator_category = std::forward_iterator_tag; using value_type = const ChunkRef; using difference_type = std::ptrdiff_t; @@ -309,6 +326,7 @@ struct ChunksIter { /** * Default construction that zeros out member variables. */ + OUSTER_API_FUNCTION ChunksIter(); /** @@ -316,6 +334,7 @@ struct ChunksIter { * * @param[in] other The other ChunksIter object to initalize from. */ + OUSTER_API_FUNCTION ChunksIter(const ChunksIter& other); /** @@ -323,6 +342,7 @@ struct ChunksIter { * * @param[in] other The other ChunksIter to assign to this. */ + OUSTER_API_FUNCTION ChunksIter& operator=(const ChunksIter& other) = default; /** @@ -332,6 +352,7 @@ struct ChunksIter { * * @return The ChunkRef object associated with this ChunksIter object. */ + OUSTER_API_FUNCTION const ChunkRef operator*() const; /** @@ -339,6 +360,7 @@ struct ChunksIter { * * @return The ChunkRef pointer associated with this ChunksIter object. */ + OUSTER_API_FUNCTION const std::unique_ptr operator->() const; /** @@ -346,6 +368,7 @@ struct ChunksIter { * * @return The current ChunksIter object. */ + OUSTER_API_FUNCTION ChunksIter& operator++(); /** @@ -354,6 +377,7 @@ struct ChunksIter { * @param[in] other The other object to compare. * @return Whether the two ChunksIter objects are the same. */ + OUSTER_API_FUNCTION bool operator==(const ChunksIter& other) const; /** @@ -363,6 +387,7 @@ struct ChunksIter { * @param[in] other The other object to compare. * @return Whether the two ChunksIter objects are not the same. */ + OUSTER_API_FUNCTION bool operator!=(const ChunksIter& other) const; /** @@ -370,6 +395,7 @@ struct ChunksIter { * * @return The string representation */ + OUSTER_API_FUNCTION std::string to_string() const; private: @@ -420,13 +446,14 @@ struct ChunksIter { /** * std iterator class for iterating through chunks. */ -class ChunksRange { +class OUSTER_API_CLASS ChunksRange { public: /** * Begin function for std iterator support. * * @return A ChunksIter object for iteration. */ + OUSTER_API_FUNCTION ChunksIter begin() const; /** @@ -435,6 +462,7 @@ class ChunksRange { * @return A ChunksIter object for signifying * the end of iteration. */ + OUSTER_API_FUNCTION ChunksIter end() const; /** @@ -442,6 +470,7 @@ class ChunksRange { * * @return The string representation. */ + OUSTER_API_FUNCTION std::string to_string() const; private: @@ -477,13 +506,14 @@ class ChunksRange { * * @todo Add filtered reads, and other nice things... */ -class Reader { +class OUSTER_API_CLASS Reader { public: /** * Creates reader from %OSF file resource. * * @param[in] osf_file The OsfFile object to use to read from. */ + OUSTER_API_FUNCTION Reader(OsfFile& osf_file); /** @@ -491,6 +521,7 @@ class Reader { * * @param[in] file The OSF file path to read from. */ + OUSTER_API_FUNCTION Reader(const std::string& file); /** @@ -502,6 +533,7 @@ class Reader { * @return The MessageStreamingRange object to iterate * through the messages. */ + OUSTER_API_FUNCTION MessagesStreamingRange messages(); /** @@ -511,18 +543,21 @@ class Reader { * @param[in] end_ts Specify the end of the timestamps that * should be iterated through. */ + OUSTER_API_FUNCTION MessagesStreamingRange messages(const ts_t start_ts, const ts_t end_ts); /** * @copydoc messages() * @param[in] stream_ids Filter the message iteration to specific streams. */ + OUSTER_API_FUNCTION MessagesStreamingRange messages(const std::vector& stream_ids); /** * @copydoc messages(const ts_t start_ts, const ts_t end_ts) * @param[in] stream_ids Filter the message iteration to specific streams. */ + OUSTER_API_FUNCTION MessagesStreamingRange messages(const std::vector& stream_ids, const ts_t start_ts, const ts_t end_ts); @@ -540,6 +575,7 @@ class Reader { * @return message timestamp that corresponds to the message_idx in the * stream_id */ + OUSTER_API_FUNCTION nonstd::optional ts_by_message_idx(uint32_t stream_id, uint32_t message_idx); @@ -553,6 +589,7 @@ class Reader { * @return Whether OSF contains the message counts that are needed for * ``ts_by_message_idx()`` */ + OUSTER_API_FUNCTION bool has_message_idx() const; /** @@ -561,6 +598,7 @@ class Reader { * * @return Whether OSF contains the message timestamp index */ + OUSTER_API_FUNCTION bool has_timestamp_idx() const; /** @@ -570,6 +608,7 @@ class Reader { * * @return The iterator to valid chunks only. */ + OUSTER_API_FUNCTION ChunksRange chunks(); /** @@ -577,6 +616,7 @@ class Reader { * * @return The metadata id. */ + OUSTER_API_FUNCTION std::string metadata_id() const; /** @@ -584,6 +624,7 @@ class Reader { * * @return The lowest timestamp in the ChunksIter. */ + OUSTER_API_FUNCTION ts_t start_ts() const; /** @@ -591,6 +632,7 @@ class Reader { * * @return The highest timestamp in the ChunksIter. */ + OUSTER_API_FUNCTION ts_t end_ts() const; /** @@ -598,6 +640,7 @@ class Reader { * * @return All of the metadata entries as a MetadataStore. */ + OUSTER_API_FUNCTION const MetadataStore& meta_store() const; /** @@ -606,6 +649,7 @@ class Reader { * * @return The chunks can be read by stream and timestamps are sane. */ + OUSTER_API_FUNCTION bool has_stream_info() const; private: @@ -676,7 +720,7 @@ class Reader { * and reconstructs underlying data to the corresponding object type given * the Stream type. */ -class MessageRef { +class OUSTER_API_CLASS MessageRef { public: using ts_t = osf::ts_t; @@ -688,6 +732,7 @@ class MessageRef { * @param[in] meta_provider The metadata store that is used in types * reconstruction */ + OUSTER_API_FUNCTION MessageRef(const uint8_t* buf, const MetadataStore& meta_provider); /** @@ -699,6 +744,7 @@ class MessageRef { * reconstruction * @param[in,out] chunk_buf The pre-existing chunk buffer to use. */ + OUSTER_API_FUNCTION MessageRef(const uint8_t* buf, const MetadataStore& meta_provider, std::shared_ptr> chunk_buf); @@ -707,6 +753,7 @@ class MessageRef { * * @return The message stream id. */ + OUSTER_API_FUNCTION uint32_t id() const; /** @@ -714,6 +761,7 @@ class MessageRef { * * @return The timestamp of the message. */ + OUSTER_API_FUNCTION ts_t ts() const; /// @todo [pb] Type of the stored data (meta of the stream?) @@ -724,6 +772,7 @@ class MessageRef { * * @return The pointer to the underlying data. */ + OUSTER_API_FUNCTION const uint8_t* buf() const; /** @@ -731,6 +780,7 @@ class MessageRef { * * @return The string representation of a MessageRef. */ + OUSTER_API_FUNCTION std::string to_string() const; /** @@ -751,6 +801,7 @@ class MessageRef { * @param[in] type_str The data type in string form to check against. * @return If the current MessageRef is of type type_str. */ + OUSTER_API_FUNCTION bool is(const std::string& type_str) const; /** @@ -788,6 +839,7 @@ class MessageRef { * * @return Return the underlying raw message byte vector. */ + OUSTER_API_FUNCTION std::vector buffer() const; /** @@ -796,6 +848,7 @@ class MessageRef { * @param[in] other The other MessageRef to check against. * @return If the two MessageRefs are equal. */ + OUSTER_API_FUNCTION bool operator==(const MessageRef& other) const; /** @@ -804,6 +857,7 @@ class MessageRef { * @param[in] other The other MessageRef to check against. * @return If the two MessageRefs are not equal. */ + OUSTER_API_FUNCTION bool operator!=(const MessageRef& other) const; private: @@ -828,11 +882,12 @@ class MessageRef { * messages reading routines. It expects that Chunk was "verified" before * creating a ChunkRef. */ -class ChunkRef { +class OUSTER_API_CLASS ChunkRef { public: /** * Default ChunkRef constructor that just zeros the internal fields. */ + OUSTER_API_FUNCTION ChunkRef(); /** @@ -840,6 +895,7 @@ class ChunkRef { * chunk. * @param[in] reader The reader object to use for reading. */ + OUSTER_API_FUNCTION ChunkRef(const uint64_t offset, Reader* reader); /** @@ -848,6 +904,7 @@ class ChunkRef { * @param[in] other The other ChunkRef to check against. * @return If the two ChunkRef are equal. */ + OUSTER_API_FUNCTION bool operator==(const ChunkRef& other) const; /** @@ -856,6 +913,7 @@ class ChunkRef { * @param[in] other The other ChunkRef to check against. * @return If the two ChunkRef are not equal. */ + OUSTER_API_FUNCTION bool operator!=(const ChunkRef& other) const; /** @@ -865,11 +923,13 @@ class ChunkRef { * * @return The ChunkState associated with this ChunkRef. */ + OUSTER_API_FUNCTION ChunkState* state(); /** * @copydoc state() */ + OUSTER_API_FUNCTION const ChunkState* state() const; /** @@ -879,11 +939,13 @@ class ChunkRef { * * @return The ChunkInfoNode associated with this ChunkRef. */ + OUSTER_API_FUNCTION ChunkInfoNode* info(); /** * @copydoc info() */ + OUSTER_API_FUNCTION const ChunkInfoNode* info() const; /** @@ -891,6 +953,7 @@ class ChunkRef { * * @return A MessagesChunkIter object for iteration. */ + OUSTER_API_FUNCTION MessagesChunkIter begin() const; /** @@ -899,6 +962,7 @@ class ChunkRef { * @return A MessagesChunkIter object for signifying * the end of iteration. */ + OUSTER_API_FUNCTION MessagesChunkIter end() const; /** @@ -909,6 +973,7 @@ class ChunkRef { * @param[in] msg_idx The message index to get. * @return The resulting message. */ + OUSTER_API_FUNCTION const MessageRef operator[](size_t msg_idx) const; /** @@ -920,6 +985,7 @@ class ChunkRef { * @return The resulting message smart pointer, * returns nullptr if non existent. */ + OUSTER_API_FUNCTION std::unique_ptr messages(size_t msg_idx) const; /** @@ -927,6 +993,7 @@ class ChunkRef { * * @return The string representation of a ChunkRef. */ + OUSTER_API_FUNCTION std::string to_string() const; /** @@ -934,6 +1001,7 @@ class ChunkRef { * * @return The chunk offset in the larger flatbuffer array. */ + OUSTER_API_FUNCTION uint64_t offset() const; /** @@ -943,6 +1011,7 @@ class ChunkRef { * @relates state * @return starting timestamp in the received chunk */ + OUSTER_API_FUNCTION ts_t start_ts() const; /** @@ -952,6 +1021,7 @@ class ChunkRef { * @relates state * @return last timestamp in the received chunk */ + OUSTER_API_FUNCTION ts_t end_ts() const; /** @@ -960,6 +1030,7 @@ class ChunkRef { * @return The summation of the sizes of the chunks messages, * 0 on chunk invalidity. */ + OUSTER_API_FUNCTION size_t size() const; /** @@ -967,6 +1038,7 @@ class ChunkRef { * * @return The validity of the chunk. */ + OUSTER_API_FUNCTION bool valid() const; private: @@ -998,7 +1070,7 @@ class ChunkRef { * Convenient iterator class to go over all of the * messages in a chunk. */ -struct MessagesChunkIter { +struct OUSTER_API_CLASS MessagesChunkIter { using iterator_category = std::forward_iterator_tag; using value_type = const MessageRef; using difference_type = std::ptrdiff_t; @@ -1009,6 +1081,7 @@ struct MessagesChunkIter { * Default MessagesChunkIter constructor that just zeros * the internal fields. */ + OUSTER_API_FUNCTION MessagesChunkIter(); /** @@ -1017,6 +1090,7 @@ struct MessagesChunkIter { * * @param[in] other The other MessagesChunkIter to initalize from. */ + OUSTER_API_FUNCTION MessagesChunkIter(const MessagesChunkIter& other); /** @@ -1024,6 +1098,7 @@ struct MessagesChunkIter { * * @param[in] other The other MessageChunkIter to assign to. */ + OUSTER_API_FUNCTION MessagesChunkIter& operator=(const MessagesChunkIter& other) = default; /** @@ -1031,6 +1106,7 @@ struct MessagesChunkIter { * * @return The current ChunkRef value. */ + OUSTER_API_FUNCTION const MessageRef operator*() const; /** @@ -1038,6 +1114,7 @@ struct MessagesChunkIter { * * @return The current ChunkRef smart pointer. */ + OUSTER_API_FUNCTION std::unique_ptr operator->() const; /** @@ -1045,11 +1122,13 @@ struct MessagesChunkIter { * * @return *this */ + OUSTER_API_FUNCTION MessagesChunkIter& operator++(); /** * @copydoc operator++() */ + OUSTER_API_FUNCTION MessagesChunkIter operator++(int); /** @@ -1057,11 +1136,13 @@ struct MessagesChunkIter { * * @return *this */ + OUSTER_API_FUNCTION MessagesChunkIter& operator--(); /** * @copydoc operator--() */ + OUSTER_API_FUNCTION MessagesChunkIter operator--(int); /** @@ -1070,6 +1151,7 @@ struct MessagesChunkIter { * @param[in] other The other MessagesChunkIter to check against. * @return If the two MessagesChunkIter are equal. */ + OUSTER_API_FUNCTION bool operator==(const MessagesChunkIter& other) const; /** @@ -1078,6 +1160,7 @@ struct MessagesChunkIter { * @param[in] other The other MessagesChunkIter to check against. * @return If the two MessagesChunkIter are not equal. */ + OUSTER_API_FUNCTION bool operator!=(const MessagesChunkIter& other) const; /** @@ -1085,6 +1168,7 @@ struct MessagesChunkIter { * * @return The string representation of a MessagesChunkIter. */ + OUSTER_API_FUNCTION std::string to_string() const; private: @@ -1121,13 +1205,14 @@ struct MessagesChunkIter { friend class ChunkRef; }; // MessagesChunkIter -class MessagesStreamingRange { +class OUSTER_API_CLASS MessagesStreamingRange { public: /** * Begin function for std iterator support. * * @return A MessagesStreamingIter object for iteration. */ + OUSTER_API_FUNCTION MessagesStreamingIter begin() const; /** @@ -1136,6 +1221,7 @@ class MessagesStreamingRange { * @return A MessagesStreamingIter object for signifying * the end of iteration. */ + OUSTER_API_FUNCTION MessagesStreamingIter end() const; /** @@ -1143,6 +1229,7 @@ class MessagesStreamingRange { * * @return The string representation of a MessagesStreamingRange. */ + OUSTER_API_FUNCTION std::string to_string() const; private: @@ -1186,7 +1273,7 @@ class MessagesStreamingRange { * Iterator over all messages in Streaming Layout order for specified * timestamp range. */ -struct MessagesStreamingIter { +struct OUSTER_API_CLASS MessagesStreamingIter { using iterator_category = std::forward_iterator_tag; using value_type = const MessageRef; using difference_type = std::ptrdiff_t; @@ -1198,7 +1285,7 @@ struct MessagesStreamingIter { /** * Comparison struct used for determining which chunk is greater. */ - struct greater_chunk_type { + struct OUSTER_API_CLASS greater_chunk_type { /** * Comparison operator used for determining if the first is greater * than the second. The comparison is based on the timestamps. @@ -1207,6 +1294,7 @@ struct MessagesStreamingIter { * @param[in] b The second chunk to compare. * @return If the first chunk is greater than the second chunk. */ + OUSTER_API_FUNCTION bool operator()(const opened_chunk_type& a, const opened_chunk_type& b); }; @@ -1214,6 +1302,7 @@ struct MessagesStreamingIter { * Default MessagesStreamingIter constructor that just zeros * the internal fields. */ + OUSTER_API_FUNCTION MessagesStreamingIter(); /** @@ -1222,6 +1311,7 @@ struct MessagesStreamingIter { * * @param[in] other The other MessagesStreamingIter to initalize from. */ + OUSTER_API_FUNCTION MessagesStreamingIter(const MessagesStreamingIter& other); /** @@ -1229,6 +1319,7 @@ struct MessagesStreamingIter { * * @param[in] other The other MessagesStreamingIter to assign to. */ + OUSTER_API_FUNCTION MessagesStreamingIter& operator=(const MessagesStreamingIter& other) = default; @@ -1237,6 +1328,7 @@ struct MessagesStreamingIter { * * @return The current MessageRef value. */ + OUSTER_API_FUNCTION const MessageRef operator*() const; /** @@ -1244,6 +1336,7 @@ struct MessagesStreamingIter { * * @return The current MessageRef smart pointer. */ + OUSTER_API_FUNCTION std::unique_ptr operator->() const; /** @@ -1251,11 +1344,13 @@ struct MessagesStreamingIter { * * @return *this */ + OUSTER_API_FUNCTION MessagesStreamingIter& operator++(); /** * @copydoc operator++() */ + OUSTER_API_FUNCTION MessagesStreamingIter operator++(int); /** @@ -1264,6 +1359,7 @@ struct MessagesStreamingIter { * @param[in] other The other MessagesStreamingIter to check against. * @return If the two MessagesStreamingIter are equal. */ + OUSTER_API_FUNCTION bool operator==(const MessagesStreamingIter& other) const; /** @@ -1272,6 +1368,7 @@ struct MessagesStreamingIter { * @param[in] other The other MessagesStreamingIter to check against. * @return If the two MessagesStreamingIter are not equal. */ + OUSTER_API_FUNCTION bool operator!=(const MessagesStreamingIter& other) const; /** @@ -1279,6 +1376,7 @@ struct MessagesStreamingIter { * * @return The string representation of a MessagesStreamingIter. */ + OUSTER_API_FUNCTION std::string to_string() const; private: diff --git a/ouster_osf/include/ouster/osf/stream_lidar_scan.h b/ouster_osf/include/ouster/osf/stream_lidar_scan.h index 9d101a41..a56d2ced 100644 --- a/ouster_osf/include/ouster/osf/stream_lidar_scan.h +++ b/ouster_osf/include/ouster/osf/stream_lidar_scan.h @@ -12,6 +12,7 @@ #include "ouster/osf/meta_lidar_sensor.h" #include "ouster/osf/metadata.h" #include "ouster/osf/writer.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -27,6 +28,7 @@ namespace osf { * @param[in] field_types The field types to cast the LidarScan to. * @return a copy of `ls_src` with transformed fields. */ +OUSTER_API_FUNCTION LidarScan slice_with_cast(const LidarScan& ls_src, const ouster::LidarScanFieldTypes& field_types); @@ -40,13 +42,15 @@ LidarScan slice_with_cast(const LidarScan& ls_src, * Flat Buffer Reference: * fb/os_sensor/lidar_scan_stream.fbs */ -class LidarScanStreamMeta : public MetadataEntryHelper { +class OUSTER_API_CLASS LidarScanStreamMeta + : public MetadataEntryHelper { public: /** * @param[in] sensor_meta_id Reference to LidarSensor metadata that * describes the sensor configuration. * @param[in] field_types LidarScan fields specs, this argument is optional. */ + OUSTER_API_FUNCTION LidarScanStreamMeta(const uint32_t sensor_meta_id, const ouster::LidarScanFieldTypes field_types = {}); @@ -55,11 +59,13 @@ class LidarScanStreamMeta : public MetadataEntryHelper { * * @return The sensor meta id. */ + OUSTER_API_FUNCTION uint32_t sensor_meta_id() const; /** * @copydoc MetadataEntry::buffer */ + OUSTER_API_FUNCTION std::vector buffer() const final; /** @@ -72,6 +78,7 @@ class LidarScanStreamMeta : public MetadataEntryHelper { * @param[in] buf The raw flatbuffer byte vector to initialize from. * @return The new LidarScanStreamMeta cast as a MetadataEntry */ + OUSTER_API_FUNCTION static std::unique_ptr from_buffer( const std::vector& buf); @@ -82,6 +89,7 @@ class LidarScanStreamMeta : public MetadataEntryHelper { * * @return The string representation for the LidarScanStreamMeta object. */ + OUSTER_API_FUNCTION std::string repr() const override; private: @@ -110,12 +118,13 @@ class LidarScanStreamMeta : public MetadataEntryHelper { * @ingroup OSFTraitsLidarScanStreamMeta */ template <> -struct MetadataTraits { +struct OUSTER_API_CLASS MetadataTraits { /** * Return the OSF type string. * * @return The OSF type string "ouster/v1/os_sensor/LidarScanStream". */ + OUSTER_API_FUNCTION static const std::string type() { return "ouster/v1/os_sensor/LidarScanStream"; } @@ -130,7 +139,9 @@ struct MetadataTraits { * Flatbuffer definition file: * fb/os_sensor/lidar_scan_stream.fbs */ -class LidarScanStream : public MessageStream { +class OUSTER_API_CLASS LidarScanStream + : public MessageStream { + protected: friend class Writer; friend class MessageRef; @@ -153,6 +164,32 @@ class LidarScanStream : public MessageStream { void save(const ouster::osf::ts_t receive_ts, const ouster::osf::ts_t sensor_ts, const obj_type& lidar_scan); + flatbuffers::Offset create_osf_field( + flatbuffers::FlatBufferBuilder& fbb, const std::string& name, + const Field& f) const; + + flatbuffers::Offset create_lidar_scan_msg( + flatbuffers::FlatBufferBuilder& fbb, const LidarScan& lidar_scan, + const ouster::sensor::sensor_info& info, + const ouster::LidarScanFieldTypes meta_field_types) const; + + void fieldEncodeMulti(const LidarScan& lidar_scan, + const LidarScanFieldTypes& field_types, + const std::vector& px_offset, + ScanData& scan_data, + const std::vector& scan_idxs) const; + + ScanData scanEncodeFieldsSingleThread( + const LidarScan& lidar_scan, const std::vector& px_offset, + const LidarScanFieldTypes& field_types) const; + + ScanData scanEncodeFields(const LidarScan& lidar_scan, + const std::vector& px_offset, + const LidarScanFieldTypes& field_types) const; + + ScanData scanEncode(const LidarScan& lidar_scan, + const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) const; /** * Encode/serialize the object to the buffer of bytes. * @@ -187,6 +224,7 @@ class LidarScanStream : public MessageStream { * @param[in] sensor_meta_id The sensor to use. * @param[in] field_types LidarScan fields specs, this argument is optional. */ + OUSTER_API_FUNCTION LidarScanStream(Token key, Writer& writer, const uint32_t sensor_meta_id, const ouster::LidarScanFieldTypes& field_types = {}); @@ -196,7 +234,7 @@ class LidarScanStream : public MessageStream { * * @return The concrete metadata type. */ - + OUSTER_API_FUNCTION const meta_type& meta() const { return meta_; }; private: diff --git a/ouster_osf/include/ouster/osf/writer.h b/ouster_osf/include/ouster/osf/writer.h index d400cca6..0e4ebb02 100644 --- a/ouster_osf/include/ouster/osf/writer.h +++ b/ouster_osf/include/ouster/osf/writer.h @@ -9,8 +9,11 @@ #include +#include "ouster/lidar_scan.h" #include "ouster/osf/basics.h" #include "ouster/osf/metadata.h" +#include "ouster/osf/osf_encoder.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -21,7 +24,7 @@ class LidarScanStream; * Chunks writing strategy that decides when and how exactly write chunks * to a file. See RFC 0018 for Standard and Streaming Layout description. */ -class ChunksWriter { +class OUSTER_API_CLASS ChunksWriter { public: /** * Save a message to a specified stream. @@ -31,6 +34,7 @@ class ChunksWriter { * @param[in] sensor_ts The sensor timestamp for the messages. * @param[in] buf A vector of message buffers to record. */ + OUSTER_API_FUNCTION virtual void save_message(const uint32_t stream_id, const ts_t receive_ts, const ts_t sensor_ts, const std::vector& buf) = 0; @@ -38,6 +42,7 @@ class ChunksWriter { /** * Finish the process of saving messages and write out the stream stats. */ + OUSTER_API_FUNCTION virtual void finish() = 0; /** @@ -45,26 +50,33 @@ class ChunksWriter { * * @return the chunk size */ + OUSTER_API_FUNCTION virtual uint32_t chunk_size() const = 0; /** * Default deconstructor. */ + OUSTER_API_FUNCTION virtual ~ChunksWriter() = default; }; /** - * %OSF Writer provides the base universal interface to store the collection - * of metadata entries, streams and corresponding objects. + * @class Writer + * @brief OSF Writer provides the base universal interface to store the + * collection of metadata entries, streams, and corresponding objects. Detailed + * description of the class follows here. + * */ -class Writer { +class OUSTER_API_CLASS Writer { friend class StreamingLayoutCW; + friend class AsyncWriter; // TODO[tws] provide access to necessary + // internals through public iface public: /** * @throws std::runtime_error Exception on file writing issues. * - * @param[in] file_name The filename of the output OSF file. + * @param[in] file_name The file name of the output OSF file. * @param[in] chunk_size The chunk size in bytes to use for the OSF file. * This argument is optional, and if not provided the default value of * 2MB is used. If the current chunk being written exceeds @@ -76,25 +88,29 @@ class Writer { * index entries. A more granular index allows for more precise * seeking at the slight expense of a larger file. */ + OUSTER_API_FUNCTION Writer(const std::string& file_name, uint32_t chunk_size = 0); /** - * @param[in] filename The filename to output to. + * @param[in] filename The file name to output to. * @param[in] info The sensor info to use for a single stream OSF file. - * @param[in] chunk_size The chunksize to use for the OSF file, this - * parameter is optional. * @param[in] fields_to_write The fields from scans to actually save into * the OSF. If not provided uses the fields from * the first saved lidar scan for this sensor. * This parameter is optional. + * @param[in] chunk_size The chunksize to use for the OSF file, this + * parameter is optional. + * @param[in] encoder An optional Encoder instance for configuring how the + * Writer should encode the OSF. */ + OUSTER_API_FUNCTION Writer(const std::string& filename, const ouster::sensor::sensor_info& info, const std::vector& fields_to_write = std::vector(), - uint32_t chunk_size = 0); + uint32_t chunk_size = 0, std::shared_ptr encoder = nullptr); /** - * @param[in] filename The filename to output to. + * @param[in] filename The file name to output to. * @param[in] info The sensor info vector to use for a multi stream OSF * file. * @param[in] chunk_size The chunksize to use for the OSF file, this @@ -103,12 +119,15 @@ class Writer { * the OSF. If not provided uses the fields from * the first saved lidar scan for this sensor. * This parameter is optional. + * @param[in] encoder An optional Encoder instance for configuring how the + * Writer should encode the OSF. */ + OUSTER_API_FUNCTION Writer(const std::string& filename, const std::vector& info, const std::vector& fields_to_write = std::vector(), - uint32_t chunk_size = 0); + uint32_t chunk_size = 0, std::shared_ptr encoder = nullptr); /** * Add metadata to the OSF file. @@ -133,11 +152,13 @@ class Writer { * * @return The corresponding lidar id of the metadata entry */ + OUSTER_API_FUNCTION uint32_t add_metadata(MetadataEntry&& entry); /** * @copydoc add_metadata(MetadataEntry&& entry) */ + OUSTER_API_FUNCTION uint32_t add_metadata(MetadataEntry& entry); /** @@ -151,6 +172,7 @@ class Writer { /** * @copydoc OSFGetMetadataGroup */ + OUSTER_API_FUNCTION std::shared_ptr get_metadata( const uint32_t metadata_id) const; @@ -161,7 +183,7 @@ class Writer { */ template std::shared_ptr get_metadata( - const uint32_t metadata_id) const { + uint32_t metadata_id) const { return meta_store_.get(metadata_id); } @@ -194,6 +216,7 @@ class Writer { * @param[in] sensor_ts The sensor timestamp to use for the message. * @param[in] buf The message to save in the form of a byte vector. */ + OUSTER_API_FUNCTION void save_message(const uint32_t stream_id, const ts_t receive_ts, const ts_t sensor_ts, const std::vector& buf); @@ -209,12 +232,15 @@ class Writer { * * @return The stream index for the newly added sensor. */ + OUSTER_API_FUNCTION uint32_t add_sensor(const ouster::sensor::sensor_info& info, const std::vector& fields_to_write = std::vector()); /** - * Save a single scan to the specified stream_index in an OSF file. + * Save a single scan to the specified stream_index in an OSF + * file. + * * The concept of the stream_index is related to the sensor_info vector. * Consider the following: @code{.cpp} @@ -247,11 +273,12 @@ class Writer { * use. * @param[in] scan The scan to save. */ + OUSTER_API_FUNCTION void save(uint32_t stream_index, const LidarScan& scan); /** - * Save a single scan to the specified stream_index in an OSF file indexed - * with the provided timestamp. + * Save a single scan with the specified timestamp to the + * specified stream_index in an OSF file. * * @throws std::logic_error Will throw exception on writer being closed. * @throws std::logic_error ///< Will throw exception on @@ -262,11 +289,13 @@ class Writer { * @param[in] scan The scan to save. * @param[in] timestamp Receive timestamp to index this scan with. */ + OUSTER_API_FUNCTION void save(uint32_t stream_index, const LidarScan& scan, - const ouster::osf::ts_t timestamp); + ouster::osf::ts_t timestamp); /** - * Save multiple scans in an OSF file. + * Save multiple scans to the OSF file. + * * The concept of the stream_index is related to the sensor_info vector. * Consider the following: @code{.cpp} @@ -290,6 +319,7 @@ class Writer { * * @param[in] scans The vector of scans to save. */ + OUSTER_API_FUNCTION void save(const std::vector& scans); /** @@ -298,6 +328,7 @@ class Writer { * * @return The flatbuffer metadata entries. */ + OUSTER_API_FUNCTION const MetadataStore& meta_store() const; /** @@ -305,6 +336,7 @@ class Writer { * * @return The metadata id label. */ + OUSTER_API_FUNCTION const std::string& metadata_id() const; /** @@ -312,13 +344,15 @@ class Writer { * * @param[in] id The metadata id label to set. */ + OUSTER_API_FUNCTION void set_metadata_id(const std::string& id); /** * Return the filename for the OSF file. * - * @return The filename for the OSF file. + * @return The file name for the OSF file. */ + OUSTER_API_FUNCTION const std::string& filename() const; /** @@ -328,6 +362,7 @@ class Writer { * * @return The chunks layout of the OSF file. */ + OUSTER_API_FUNCTION ChunksLayout chunks_layout() const; /** @@ -335,6 +370,7 @@ class Writer { * * @return The chunk size for the OSF file. */ + OUSTER_API_FUNCTION uint32_t chunk_size() const; /** @@ -353,6 +389,7 @@ class Writer { * * @return The sensor info vector. */ + OUSTER_API_FUNCTION const std::vector& sensor_info() const; /** @@ -374,6 +411,7 @@ class Writer { * @param[in] stream_index The sensor info to return. * @return The correct sensor info. */ + OUSTER_API_FUNCTION const ouster::sensor::sensor_info sensor_info(int stream_index) const; /** @@ -381,11 +419,15 @@ class Writer { * * @return The sensor_info count. */ + OUSTER_API_FUNCTION uint32_t sensor_info_count() const; /** * Finish file with a proper metadata object, and header. + * This method blocks until all remaining tasks generated by save() have + * been finalized. */ + OUSTER_API_FUNCTION void close(); /** @@ -393,34 +435,52 @@ class Writer { * * @return If the writer is closed or not. */ + OUSTER_API_FUNCTION inline bool is_closed() const { return finished_; } + /** + * Returns the Encoder object used by this Writer. + * + * @return the Encoder. + */ + OUSTER_API_FUNCTION + Encoder& encoder() const { return *encoder_; } + /** * @relates close */ + OUSTER_API_FUNCTION ~Writer(); /** * Disallow copying and moving. */ + OUSTER_API_FUNCTION Writer(const Writer&) = delete; /** * Disallow copying and moving. */ + OUSTER_API_FUNCTION Writer& operator=(const Writer&) = delete; /** * Disallow copying and moving. */ + OUSTER_API_FUNCTION Writer(Writer&&) = delete; /** * Disallow copying and moving. */ + OUSTER_API_FUNCTION Writer& operator=(Writer&&) = delete; private: + /** + * A runnable used to handle writes in the thread 'save_thread_'. + */ + void save_thread_method(); /** * Helper to construct the Metadata OSF Block at the end of writing. * This function takes the metadata entries from the metadata store @@ -454,7 +514,7 @@ class Writer { * @param[in] size The size of the buffer to append. * @return The number of bytes writen to the OSF file. */ - uint64_t append(const uint8_t* buf, const uint64_t size); + uint64_t append(const uint8_t* buf, uint64_t size); /** * Save a specified chunk to the OSF file. @@ -466,13 +526,13 @@ class Writer { * @param[in] chunk_buf The byte vector representation of the chunk. * @return The result offset in the OSF file. */ - uint64_t emit_chunk(const ts_t start_ts, const ts_t end_ts, + uint64_t emit_chunk(ts_t start_ts, ts_t end_ts, const std::vector& chunk_buf); /** * Internal filename of the OSF file. */ - std::string file_name_; + std::string filename_; /** * The size of the flatbuffer header blob. @@ -561,13 +621,17 @@ class Writer { * The internal sensor_info store ordered by stream_index. */ std::vector sensor_info_; + + // TODO[tws] make private, access from LidarScanStream via "friend class" + std::shared_ptr encoder_; }; /** * Encapsulate chunk seriualization operations. */ -class ChunkBuilder { +class OUSTER_API_CLASS ChunkBuilder { public: + OUSTER_API_FUNCTION ChunkBuilder(){}; /** @@ -580,6 +644,7 @@ class ChunkBuilder { * @param[in] sensor_ts The sensor timestamp to use for the message. * @param[in] msg_buf The message to save in the form of a byte vector. */ + OUSTER_API_FUNCTION void save_message(const uint32_t stream_id, const ts_t receive_ts, const ts_t sensor_ts, const std::vector& msg_buf); @@ -587,6 +652,7 @@ class ChunkBuilder { /** * Completely wipe all data and start the chunk anew. */ + OUSTER_API_FUNCTION void reset(); /** @@ -595,6 +661,7 @@ class ChunkBuilder { * * @return The serialized chunk in a raw flatbuffer byte vector. */ + OUSTER_API_FUNCTION std::vector finish(); /** @@ -602,6 +669,7 @@ class ChunkBuilder { * * @return The flatbufferbuilder size. */ + OUSTER_API_FUNCTION uint32_t size() const; /** @@ -609,6 +677,7 @@ class ChunkBuilder { * * @return The number of messages saved so far. */ + OUSTER_API_FUNCTION uint32_t messages_count() const; /** @@ -616,6 +685,7 @@ class ChunkBuilder { * * @return The lowest timestamp in the chunk. */ + OUSTER_API_FUNCTION ts_t start_ts() const; /** @@ -623,6 +693,7 @@ class ChunkBuilder { * * @return The highest timestamp in the chunk. */ + OUSTER_API_FUNCTION ts_t end_ts() const; private: diff --git a/ouster_osf/src/async_writer.cpp b/ouster_osf/src/async_writer.cpp new file mode 100644 index 00000000..c6dbaf6d --- /dev/null +++ b/ouster_osf/src/async_writer.cpp @@ -0,0 +1,100 @@ +#include "ouster/osf/async_writer.h" + +#include "ouster/impl/logging.h" +#include "ouster/osf/stream_lidar_scan.h" + +using ouster::sensor::logger; + +namespace ouster { +namespace osf { + +AsyncWriter::AsyncWriter(const std::string& filename, + const std::vector& info, + const std::vector& fields_to_write, + uint32_t chunk_size, std::shared_ptr encoder) + : writer_(filename, info, fields_to_write, chunk_size, encoder), + save_queue_{10} { + save_thread_ = std::thread([this] { save_thread_method(); }); +} + +void AsyncWriter::save_thread_method() { + while (true) { + nonstd::optional msg = save_queue_.pop(); + if (msg == nonstd::nullopt) { + break; + } + auto& msg_value = msg.value(); + std::lock_guard lock(stream_mutex_); + try { + writer_._save(msg_value.stream_index_, msg_value.lidar_scan_, + msg_value.timestamp_); + msg_value.promise_.set_value(); + } catch (const std::exception& ex) { + logger().error("Exception when saving LidarScan as OSF: {}", + ex.what()); + try { + msg_value.promise_.set_exception(std::current_exception()); + } catch (...) { + logger().error( + "An exception occurred during std::promise set_exception."); + } + } + } +} + +std::future AsyncWriter::save(uint32_t stream_index, + const LidarScan& scan) { + if (writer_.is_closed()) { + throw std::logic_error("ERROR: Writer is closed"); + } + std::promise promise; + std::future result = promise.get_future(); + ts_t time = ts_t(scan.get_first_valid_packet_timestamp()); + save_queue_.push(LidarScanMessage(stream_index, time, scan, promise)); + return result; +} + +std::future AsyncWriter::save(uint32_t stream_index, + const LidarScan& scan, + const ouster::osf::ts_t timestamp) { + if (writer_.is_closed()) { + throw std::logic_error("ERROR: Writer is closed"); + } + std::promise promise; + std::future result = promise.get_future(); + save_queue_.push(LidarScanMessage(stream_index, timestamp, scan, promise)); + return result; +} + +std::vector> AsyncWriter::save( + const std::vector& scans) { + if (writer_.is_closed()) { + throw std::logic_error("ERROR: Writer is closed"); + } + if (scans.size() != writer_.lidar_meta_id_.size()) { + throw std::logic_error( + "ERROR: Scans passed in to writer " + "does not match number of sensor infos"); + } else { + std::vector> results; + for (uint32_t i = 0; i < scans.size(); i++) { + ts_t time = ts_t(scans[i].get_first_valid_packet_timestamp()); + std::promise promise; + std::future result = promise.get_future(); + save_queue_.push(LidarScanMessage(i, time, scans[i], promise)); + results.push_back(std::move(result)); + } + return results; + } +} + +void AsyncWriter::close() { + save_queue_.shutdown(); + if (save_thread_.joinable()) { + save_thread_.join(); + } + writer_.close(); +} + +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/src/compat_ops.h b/ouster_osf/src/compat_ops.h index eb418ca5..ee49ce19 100644 --- a/ouster_osf/src/compat_ops.h +++ b/ouster_osf/src/compat_ops.h @@ -8,6 +8,8 @@ #include #include +#include "ouster/visibility.h" + namespace ouster { namespace osf { @@ -19,64 +21,93 @@ constexpr char FILE_SEP = '/'; #endif /// Checking the the path is it directory or not. +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool is_dir(const std::string& path); /// Check path existence on the system +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool path_exists(const std::string& path); /// Path concatenation with OS specific path separator +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION std::string path_concat(const std::string& path1, const std::string& path2); /// Get the path to unique temp directory and create it. +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool make_tmp_dir(std::string& tmp_path); /// Make directory +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool make_dir(const std::string& path); /// Get environment variable +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool get_env_var(const std::string& name, std::string& value); // Unlink path +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool unlink_path(const std::string& path); // Remove directory +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION bool remove_dir(const std::string& path); // Get file size +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION int64_t file_size(const std::string& path); // File mapping open +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION uint8_t* mmap_open(const std::string& path); // File mapping close -bool mmap_close(uint8_t* file_buf, const uint64_t file_size); +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION +bool mmap_close(uint8_t* file_buf, uint64_t file_size); /// Get the last system error and return it in a string (not wide string) +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION std::string get_last_error(); /** * Truncate a file to a certain length + * @TODO Change up tests to not use this stuff * * @param[in] path The file to truncate. * @param[in] filesize The final size of the file. * * @return The number of bytes of the final file. */ +OUSTER_API_FUNCTION int64_t truncate_file(const std::string& path, uint64_t filesize); /** * Appends one file to another + * @TODO Change up tests to not use this stuff * * @param[in] append_to_file_name The file to append to. * @param[in] append_from_file_name The file to append from. * * @return The number of bytes of the final file. */ +OUSTER_API_FUNCTION int64_t append_binary_file(const std::string& append_to_file_name, const std::string& append_from_file_name); /** * Copies trailing bytes from a file + * @TODO Change up tests to not use this stuff * * @param[in] source_file The file to copy from. * @param[in] target_file The file to copy to. @@ -84,6 +115,7 @@ int64_t append_binary_file(const std::string& append_to_file_name, * * @return The number of bytes of the target file. */ +OUSTER_API_FUNCTION int64_t copy_file_trailing_bytes(const std::string& source_file, const std::string& target_file, uint64_t offset); diff --git a/ouster_osf/src/fb_utils.h b/ouster_osf/src/fb_utils.h index ddfb5563..47c1b4ea 100644 --- a/ouster_osf/src/fb_utils.h +++ b/ouster_osf/src/fb_utils.h @@ -8,6 +8,7 @@ #include "header_generated.h" #include "metadata_generated.h" #include "ouster/osf/basics.h" +#include "ouster/visibility.h" // OSF v2 basic types for LidarSensor and LidarScan/Imu Streams #include "os_sensor/lidar_scan_stream_generated.h" @@ -16,10 +17,14 @@ namespace ouster { namespace osf { +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION inline const gen::Metadata* get_osf_metadata_from_buf(const uint8_t* buf) { return ouster::osf::gen::GetSizePrefixedMetadata(buf); } +/// @TODO Change up tests to not use this stuff +OUSTER_API_FUNCTION inline const gen::Header* get_osf_header_from_buf(const uint8_t* buf) { return gen::GetSizePrefixedHeader(buf); } @@ -27,13 +32,15 @@ inline const gen::Header* get_osf_header_from_buf(const uint8_t* buf) { /** * Verifies the validity of Header buffer and whether it's safe to read it. * It's just checking the well formed Flatbuffer table (not CRC32 check here) + * @TODO Change up tests to not use this stuff * * @param[in] buf Header buffer, size prefixed * @param[in] buf_size buffer size (with prefix size bytes but not including * CRC32) * @return true if buffer is valid and can be read */ -inline bool verify_osf_header_buf(const uint8_t* buf, const uint32_t buf_size) { +OUSTER_API_FUNCTION +inline bool verify_osf_header_buf(const uint8_t* buf, uint32_t buf_size) { auto verifier = flatbuffers::Verifier(buf, buf_size); return gen::VerifySizePrefixedHeaderBuffer(verifier); } @@ -41,25 +48,30 @@ inline bool verify_osf_header_buf(const uint8_t* buf, const uint32_t buf_size) { /** * Checks the validity of a Metadata buffer and whether it's safe to read it. * It's checking the well formed Flatbuffer table and CRC32. + * @TODO Change up tests to not use this stuff * * @param[in] buf metadata buffer, size prefixed * @param[in] buf_size buffer size (with CRC32 and prefix size bytes) * @return true if buffer is valid and can be read */ -bool check_osf_metadata_buf(const uint8_t* buf, const uint32_t buf_size); +OUSTER_API_FUNCTION +bool check_osf_metadata_buf(const uint8_t* buf, uint32_t buf_size); /** * Checks the validity of a Chunk buffer and whether it's safe to read it. * It's checking the well formed Flatbuffer table and CRC32. + * @TODO Change up tests to not use this stuff * * @param[in] buf metadata buffer, size prefixed * @param[in] buf_size buffer size (with CRC32 and prefix size bytes) * @return true if buffer is valid and can be read */ -bool check_osf_chunk_buf(const uint8_t* buf, const uint32_t buf_size); +OUSTER_API_FUNCTION +bool check_osf_chunk_buf(const uint8_t* buf, uint32_t buf_size); /** * Transforms Flatbuffers vector to a std::vector. + * @TODO Change up tests to not use this stuff * * @tparam T The type of the vector to transform. * @@ -67,7 +79,8 @@ bool check_osf_chunk_buf(const uint8_t* buf, const uint32_t buf_size); * @return The transformed vector. **/ template -std::vector vector_from_fb_vector(const flatbuffers::Vector* fb_vec); +OUSTER_API_FUNCTION std::vector vector_from_fb_vector( + const flatbuffers::Vector* fb_vec); // ============ File operations ========================== @@ -75,6 +88,7 @@ std::vector vector_from_fb_vector(const flatbuffers::Vector* fb_vec); * Saves the buffer content to the file with additional 4 bytes of calculated * CRC32 field in the end. Successfull operation writes size + 4 bytes to the * file. + * @TODO Change up tests to not use this stuff * * @param[in] buf pointer to the data to save, full content of the buffer used * to calculate CRC @@ -85,13 +99,15 @@ std::vector vector_from_fb_vector(const flatbuffers::Vector* fb_vec); * @return Number of bytes actually written to the file. Successfull write is * size + 4 bytes (4 bytes for CRC field) */ -uint64_t buffer_to_file(const uint8_t* buf, const uint64_t size, +OUSTER_API_FUNCTION +uint64_t buffer_to_file(const uint8_t* buf, uint64_t size, const std::string& filename, bool append = false); /** * Saves the content of Flatbuffer builder to the file with CRC32 field * appended to the actual bytes. Usually it's a size prefixed finished builder * but not necessarily + * @TODO Change up tests to not use this stuff * * @param[in] builder Flatbuffers builder * @param[in] filename filename to save bytes @@ -100,28 +116,32 @@ uint64_t buffer_to_file(const uint8_t* buf, const uint64_t size, * @return Number of bytes actually written to the file. Successfull write is * size + 4 bytes (4 bytes for CRC field) */ +OUSTER_API_FUNCTION uint64_t builder_to_file(flatbuffers::FlatBufferBuilder& builder, const std::string& filename, bool append = false); /** * Starts the OSF v2 file with a header (in INVALID state). + * @TODO Change up tests to not use this stuff * * @param[in] filename of the file to be created. Overwrite if file exists. * @return Number of bytes actually written to the file. */ +OUSTER_API_FUNCTION uint64_t start_osf_file(const std::string& filename); /** * Finish OSF v2 file with updated offset to metadata and filesize. As a * result file left in VALID state. + * @TODO Change up tests to not use this stuff * * @param[in] filename of the file to be created. Overwrite if file exists. * @param[in] metadata_offset The offset to the metadata blob. * @param[in] metadata_size The size of the metadata blob. * @return Number of bytes actually written to the file. */ -uint64_t finish_osf_file(const std::string& filename, - const uint64_t metadata_offset, - const uint32_t metadata_size); +OUSTER_API_FUNCTION +uint64_t finish_osf_file(const std::string& filename, uint64_t metadata_offset, + uint32_t metadata_size); } // namespace osf } // namespace ouster diff --git a/ouster_osf/src/json_utils.cpp b/ouster_osf/src/json_utils.cpp deleted file mode 100644 index 2a3a9cfa..00000000 --- a/ouster_osf/src/json_utils.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright(c) 2021, Ouster, Inc. - * All rights reserved. - */ - -#include "json_utils.h" - -#include - -namespace ouster { -namespace osf { - -bool parse_json(const std::string json_str, Json::Value& output) { - // Parse Json - Json::CharReaderBuilder jbuilder{}; - jbuilder["collectComments"] = false; - std::stringstream source{json_str}; - std::string jerrs; - return Json::parseFromStream(jbuilder, source, &output, &jerrs); -} - -std::string json_string(const Json::Value& root) { - Json::StreamWriterBuilder builder; - builder["enableYAMLCompatibility"] = true; - builder["precision"] = 6; - builder["indentation"] = " "; - return Json::writeString(builder, root); -} - -} // namespace osf -} // namespace ouster diff --git a/ouster_osf/src/json_utils.h b/ouster_osf/src/json_utils.h deleted file mode 100644 index a4da6bb6..00000000 --- a/ouster_osf/src/json_utils.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright(c) 2021, Ouster, Inc. - * All rights reserved. - */ - -#pragma once - -#include "json/json.h" - -namespace ouster { -namespace osf { - -bool parse_json(const std::string json_str, Json::Value& output); - -std::string json_string(const Json::Value& root); - -} // namespace osf -} // namespace ouster \ No newline at end of file diff --git a/ouster_osf/src/meta_lidar_sensor.cpp b/ouster_osf/src/meta_lidar_sensor.cpp index a986f136..574f361d 100644 --- a/ouster_osf/src/meta_lidar_sensor.cpp +++ b/ouster_osf/src/meta_lidar_sensor.cpp @@ -5,13 +5,14 @@ #include "ouster/osf/meta_lidar_sensor.h" +#include +#include + #include "fb_utils.h" #include "flatbuffers/flatbuffers.h" -#include "json_utils.h" #include "ouster/osf/basics.h" using sensor_info = ouster::sensor::sensor_info; - namespace ouster { namespace osf { @@ -84,16 +85,24 @@ std::unique_ptr LidarSensor::from_buffer( } std::string LidarSensor::repr() const { - Json::Value lidar_sensor_obj{}; - Json::Value sensor_info_obj{}; + jsoncons::json root; + + std::istringstream temp(sensor_info_.to_json_string()); + + jsoncons::json_decoder temp_decoder; + jsoncons::json_stream_reader reader(temp, temp_decoder); - if (!parse_json(metadata_, sensor_info_obj)) { - lidar_sensor_obj["sensor_info"] = metadata_; + std::error_code temp_error_code; + reader.read(temp_error_code); + if (!temp_error_code) { + root["sensor_info"] = temp_decoder.get_result(); } else { - lidar_sensor_obj["sensor_info"] = sensor_info_obj; + root["sensor_info"] = metadata_; } - return json_string(lidar_sensor_obj); + std::string out; + root.dump(out); + return out; }; std::string LidarSensor::to_string() const { return repr(); }; diff --git a/ouster_osf/src/meta_streaming_info.cpp b/ouster_osf/src/meta_streaming_info.cpp index f344f99e..bb0f7865 100644 --- a/ouster_osf/src/meta_streaming_info.cpp +++ b/ouster_osf/src/meta_streaming_info.cpp @@ -5,11 +5,10 @@ #include "ouster/osf/meta_streaming_info.h" +#include #include #include -#include "json/json.h" -#include "json_utils.h" #include "ouster/osf/meta_streaming_info.h" #include "streaming/streaming_info_generated.h" @@ -173,41 +172,43 @@ std::unique_ptr StreamingInfo::from_buffer( } std::string StreamingInfo::repr() const { - Json::Value si_obj{}; + jsoncons::json si_obj; - si_obj["chunks"] = Json::arrayValue; + jsoncons::json chunks(jsoncons::json_array_arg); for (const auto& ci : chunks_info_) { - Json::Value chunk_info{}; - chunk_info["offset"] = static_cast(ci.second.offset); + jsoncons::json chunk_info; + chunk_info["offset"] = static_cast(ci.second.offset); chunk_info["stream_id"] = ci.second.stream_id; chunk_info["message_count"] = ci.second.message_count; - si_obj["chunks"].append(chunk_info); + chunks.emplace_back(chunk_info); } + si_obj["chunks"] = chunks; - si_obj["stream_stats"] = Json::arrayValue; + jsoncons::json stream_stats(jsoncons::json_array_arg); for (const auto& stat : stream_stats_) { - Json::Value ss{}; + jsoncons::json ss{}; ss["stream_id"] = stat.first; - ss["start_ts"] = - static_cast(stat.second.start_ts.count()); - ss["end_ts"] = static_cast(stat.second.end_ts.count()); - ss["message_count"] = - static_cast(stat.second.message_count); + ss["start_ts"] = static_cast(stat.second.start_ts.count()); + ss["end_ts"] = static_cast(stat.second.end_ts.count()); + ss["message_count"] = static_cast(stat.second.message_count); ss["message_avg_size"] = stat.second.message_avg_size; - Json::Value st = Json::arrayValue; - Json::Value rt = Json::arrayValue; + jsoncons::json st(jsoncons::json_array_arg); + jsoncons::json rt(jsoncons::json_array_arg); for (const auto& t : stat.second.sensor_timestamps) { - st.append(static_cast(t)); + st.emplace_back(static_cast(t)); } for (const auto& t : stat.second.receive_timestamps) { - rt.append(static_cast(t)); + rt.emplace_back(static_cast(t)); } ss["sensor_timestamps"] = st; ss["receive_timestamps"] = rt; - si_obj["stream_stats"].append(ss); + stream_stats.emplace_back(ss); } + si_obj["stream_stats"] = stream_stats; - return json_string(si_obj); + std::string out; + si_obj.dump(out); + return out; }; } // namespace osf diff --git a/ouster_osf/src/operations.cpp b/ouster_osf/src/operations.cpp index 4f95311f..8626a2ff 100644 --- a/ouster_osf/src/operations.cpp +++ b/ouster_osf/src/operations.cpp @@ -8,11 +8,11 @@ #include #include #include +#include +#include #include "compat_ops.h" #include "fb_utils.h" -#include "json/json.h" -#include "json_utils.h" #include "ouster/impl/logging.h" #include "ouster/lidar_scan.h" #include "ouster/osf/file.h" @@ -31,63 +31,69 @@ std::string dump_metadata(const std::string& file, bool full) { OsfFile osf_file(file); auto osf_header = get_osf_header_from_buf(osf_file.get_header_chunk_ptr()); - Json::Value root{}; + jsoncons::json root; - root["header"]["size"] = static_cast(osf_file.size()); - root["header"]["version"] = static_cast(osf_file.version()); + root["header"]["size"] = static_cast(osf_file.size()); + root["header"]["version"] = static_cast(osf_file.version()); root["header"]["status"] = to_string(osf_header->status()); root["header"]["metadata_offset"] = - static_cast(osf_file.metadata_offset()); + static_cast(osf_file.metadata_offset()); root["header"]["chunks_offset"] = - static_cast(osf_file.chunks_offset()); + static_cast(osf_file.chunks_offset()); Reader reader(file); root["metadata"]["id"] = reader.metadata_id(); root["metadata"]["start_ts"] = - static_cast(reader.start_ts().count()); - root["metadata"]["end_ts"] = - static_cast(reader.end_ts().count()); + static_cast(reader.start_ts().count()); + root["metadata"]["end_ts"] = static_cast(reader.end_ts().count()); auto osf_metadata = get_osf_metadata_from_buf(osf_file.get_metadata_chunk_ptr()); if (full) { - root["metadata"]["chunks"] = Json::arrayValue; + jsoncons::json chunks(jsoncons::json_array_arg); for (size_t i = 0; i < osf_metadata->chunks()->size(); ++i) { auto osf_chunk = osf_metadata->chunks()->Get(i); - Json::Value chunk{}; - chunk["start_ts"] = - static_cast(osf_chunk->start_ts()); - chunk["end_ts"] = static_cast(osf_chunk->end_ts()); - chunk["offset"] = static_cast(osf_chunk->offset()); - root["metadata"]["chunks"].append(chunk); + jsoncons::json chunk; + chunk["start_ts"] = static_cast(osf_chunk->start_ts()); + chunk["end_ts"] = static_cast(osf_chunk->end_ts()); + chunk["offset"] = static_cast(osf_chunk->offset()); + chunks.emplace_back(chunk); } + root["metadata"]["chunks"] = chunks; } const MetadataStore& meta_store = reader.meta_store(); - - root["metadata"]["entries"] = Json::arrayValue; - + jsoncons::json entries(jsoncons::json_array_arg); for (const auto& me : meta_store.entries()) { - Json::Value meta_element{}; - meta_element["id"] = static_cast(me.first); + jsoncons::json meta_element; + meta_element["id"] = static_cast(me.first); meta_element["type"] = me.second->type(); - if (full) { - const std::string me_str = me.second->repr(); - Json::Value me_obj{}; - if (parse_json(me_str, me_obj)) { - meta_element["buffer"] = me_obj; + const std::string temp_data = me.second->repr(); + std::istringstream temp(temp_data); + + jsoncons::json_decoder temp_decoder; + jsoncons::json_stream_reader reader(temp, temp_decoder); + + std::error_code temp_error_code; + reader.read(temp_error_code); + if (!temp_error_code) { + meta_element["buffer"] = temp_decoder.get_result(); } else { - meta_element["buffer"] = me_str; + jsoncons::json buffer = temp_data; + meta_element["buffer"] = buffer; } } - root["metadata"]["entries"].append(meta_element); + entries.emplace_back(meta_element); } + root["metadata"]["entries"] = entries; - return json_string(root); + std::string out; + root.dump(out); + return out; } void parse_and_print(const std::string& file, bool with_decoding) { diff --git a/ouster_osf/src/png_lidarscan_encoder.cpp b/ouster_osf/src/png_lidarscan_encoder.cpp new file mode 100644 index 00000000..7bc65689 --- /dev/null +++ b/ouster_osf/src/png_lidarscan_encoder.cpp @@ -0,0 +1,413 @@ +/** + * Copyright (c) 2024, Ouster, Inc. + * All rights reserved. + */ + +// NOTE yes, we're aware that there is a high amount of duplication in this +// file. Some of the methods are "legacy" code and unlikely to change in future +// revisions. + +#include "ouster/osf/png_lidarscan_encoder.h" + +#include + +#include "ouster/impl/logging.h" +#include "png_tools.h" + +using namespace ouster::sensor; + +namespace ouster { +namespace osf { + +bool PngLidarScanEncoder::fieldEncode(const LidarScan& lidar_scan, + const ouster::FieldType& field_type, + const std::vector& px_offset, + ScanData& scan_data, + size_t scan_idx) const { + if (scan_idx >= scan_data.size()) { + throw std::invalid_argument( + "ERROR: scan_data size is not sufficient to hold idx: " + + std::to_string(scan_idx)); + } + bool res = true; + switch (field_type.element_type) { + case sensor::ChanFieldType::UINT8: + res = encode8bitImage(scan_data[scan_idx], + lidar_scan.field(field_type.name), + px_offset); + break; + case sensor::ChanFieldType::UINT16: + res = encode16bitImage(scan_data[scan_idx], + lidar_scan.field(field_type.name), + px_offset); + break; + case sensor::ChanFieldType::UINT32: + res = encode32bitImage(scan_data[scan_idx], + lidar_scan.field(field_type.name), + px_offset); + break; + case sensor::ChanFieldType::UINT64: + res = encode64bitImage(scan_data[scan_idx], + lidar_scan.field(field_type.name), + px_offset); + break; + default: + logger().error( + "ERROR: fieldEncode: UNKNOWN:" + "ChanFieldType not yet " + "implemented"); + break; + } + if (res) { + logger().error("ERROR: fieldEncode: Can't encode field {}", + field_type.name); + } + return res; +} + +ScanChannelData PngLidarScanEncoder::encodeField( + const ouster::Field& field) const { + ScanChannelData buffer; + + // do not compress, flat fields "compressed" size is greater than original + if (field.shape().size() == 1) { + buffer.resize(field.bytes()); + std::memcpy(buffer.data(), field, field.bytes()); + return buffer; + } + + // empty case + if (field.bytes() == 0) { + return buffer; + } + + FieldView view = uint_view(field); + // collapse shape + if (view.shape().size() > 2) { + size_t rows = view.shape()[0]; + size_t cols = view.size() / rows; + view = view.reshape(rows, cols); + } + + bool res = true; + switch (view.tag()) { + case sensor::ChanFieldType::UINT8: + res = encode8bitImage(buffer, view); + break; + case sensor::ChanFieldType::UINT16: + res = encode16bitImage(buffer, view); + break; + case sensor::ChanFieldType::UINT32: + res = encode32bitImage(buffer, view); + break; + case sensor::ChanFieldType::UINT64: + res = encode64bitImage(buffer, view); + break; + default: + break; + } + + if (res) { + throw std::runtime_error("encodeField: could not encode field"); + } + + return buffer; +} + +template +bool PngLidarScanEncoder::encode8bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img, + const std::vector& px_offset) const { + return PngLidarScanEncoder::encode8bitImage(res_buf, + destagger(img, px_offset)); +} + +template +bool PngLidarScanEncoder::encode8bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img) const { + const uint32_t width = static_cast(img.cols()); + const uint32_t height = static_cast(img.rows()); + + // 8 bit Gray + const int sample_depth = 8; + const int color_type = PNG_COLOR_TYPE_GRAY; + + // 8bit Encoding Sizes + std::vector row_data(width); // Gray, 8bit + + // libpng main structs + png_structp png_ptr; + png_infop png_info_ptr; + + if (png_osf_write_init(&png_ptr, &png_info_ptr)) { + return true; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &png_info_ptr); + return true; + } + + png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, + sample_depth, color_type, compression_amount_); + + for (size_t u = 0; u < height; ++u) { + for (size_t v = 0; v < width; ++v) { + // 8bit Encoding Logic + row_data[v] = static_cast(img(u, v)); + } + + png_write_row(png_ptr, + reinterpret_cast(row_data.data())); + } + + png_write_end(png_ptr, nullptr); + + png_destroy_write_struct(&png_ptr, &png_info_ptr); + + return false; // SUCCESS +} + +template +bool PngLidarScanEncoder::encode16bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img) const { + const uint32_t width = static_cast(img.cols()); + const uint32_t height = static_cast(img.rows()); + + // 16 bit Gray + const int sample_depth = 16; + const int color_type = PNG_COLOR_TYPE_GRAY; + + // 16bit Encoding Sizes + std::vector row_data(width * 2); // Gray, 16bit + + // libpng main structs + png_structp png_ptr; + png_infop png_info_ptr; + + if (png_osf_write_init(&png_ptr, &png_info_ptr)) { + return true; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &png_info_ptr); + return true; + } + + png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, + sample_depth, color_type, compression_amount_); + + // Needed to transform provided little-endian samples to internal + // PNG big endian format + png_set_swap(png_ptr); + + for (size_t u = 0; u < height; ++u) { + for (size_t v = 0; v < width; ++v) { + const uint64_t key_val = img(u, v); + + // 16bit Encoding Logic + row_data[v * 2] = static_cast(key_val & 0xff); + row_data[v * 2 + 1] = static_cast((key_val >> 8u) & 0xff); + } + + png_write_row(png_ptr, + reinterpret_cast(row_data.data())); + } + + png_write_end(png_ptr, nullptr); + + png_destroy_write_struct(&png_ptr, &png_info_ptr); + + return false; // SUCCESS +} + +template +bool PngLidarScanEncoder::encode16bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img, + const std::vector& px_offset) const { + return encode16bitImage(res_buf, destagger(img, px_offset)); +} + +template +bool PngLidarScanEncoder::encode24bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img, + const std::vector& px_offset) const { + return encode24bitImage(res_buf, destagger(img, px_offset)); +} + +template +bool PngLidarScanEncoder::encode24bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img) const { + const uint32_t width = static_cast(img.cols()); + const uint32_t height = static_cast(img.rows()); + + // 8bit RGB + const int sample_depth = 8; + const int color_type = PNG_COLOR_TYPE_RGB; + + // 24bit Encoding Sizes + std::vector row_data(width * 3); // RGB, 8bit + + // libpng main structs + png_structp png_ptr; + png_infop png_info_ptr; + + if (png_osf_write_init(&png_ptr, &png_info_ptr)) { + return true; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &png_info_ptr); + return true; + } + + png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, + sample_depth, color_type, compression_amount_); + + for (size_t u = 0; u < height; ++u) { + for (size_t v = 0; v < width; ++v) { + const uint64_t key_val = img(u, v); + + // 24bit Encoding Logic + row_data[v * 3 + 0] = static_cast(key_val & 0xff); + row_data[v * 3 + 1] = static_cast((key_val >> 8u) & 0xff); + row_data[v * 3 + 2] = static_cast((key_val >> 16u) & 0xff); + } + + png_write_row(png_ptr, + reinterpret_cast(row_data.data())); + } + + png_write_end(png_ptr, nullptr); + + png_destroy_write_struct(&png_ptr, &png_info_ptr); + + return false; // SUCCESS +} + +template +bool PngLidarScanEncoder::encode32bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img, + const std::vector& px_offset) const { + return encode32bitImage(res_buf, destagger(img, px_offset)); +} + +template +bool PngLidarScanEncoder::encode32bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img) const { + const uint32_t width = static_cast(img.cols()); + const uint32_t height = static_cast(img.rows()); + + // 8bit RGBA + const int sample_depth = 8; + const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; + + // 32bit Encoding Sizes + std::vector row_data(width * 4); // RGBA, 8bit + + // libpng main structs + png_structp png_ptr; + png_infop png_info_ptr; + + if (png_osf_write_init(&png_ptr, &png_info_ptr)) { + return true; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &png_info_ptr); + return true; + } + + png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, + sample_depth, color_type, compression_amount_); + + for (size_t u = 0; u < height; ++u) { + for (size_t v = 0; v < width; ++v) { + const uint64_t key_val = img(u, v); + + // 32bit Encoding Logic + row_data[v * 4 + 0] = static_cast(key_val & 0xff); + row_data[v * 4 + 1] = static_cast((key_val >> 8u) & 0xff); + row_data[v * 4 + 2] = static_cast((key_val >> 16u) & 0xff); + row_data[v * 4 + 3] = static_cast((key_val >> 24u) & 0xff); + } + + png_write_row(png_ptr, + reinterpret_cast(row_data.data())); + } + + png_write_end(png_ptr, nullptr); + + png_destroy_write_struct(&png_ptr, &png_info_ptr); + + return false; // SUCCESS +} + +template +bool PngLidarScanEncoder::encode64bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img, + const std::vector& px_offset) const { + return encode64bitImage(res_buf, destagger(img, px_offset)); +} + +template +bool PngLidarScanEncoder::encode64bitImage( + ScanChannelData& res_buf, const Eigen::Ref>& img) const { + const uint32_t width = static_cast(img.cols()); + const uint32_t height = static_cast(img.rows()); + + // 16bit RGBA + const int sample_depth = 16; + const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; + + // 64bit Encoding Sizes + std::vector row_data(width * 8); // RGBA, 16bit + + // libpng main structs + png_structp png_ptr; + png_infop png_info_ptr; + + if (png_osf_write_init(&png_ptr, &png_info_ptr)) { + return true; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &png_info_ptr); + return true; + } + + png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, + sample_depth, color_type, compression_amount_); + + // Needed to transform provided little-endian samples to internal + // PNG big endian format + png_set_swap(png_ptr); + + for (size_t u = 0; u < height; ++u) { + for (size_t v = 0; v < width; ++v) { + const uint64_t key_val = img(u, v); + + // 64bit Encoding Logic + row_data[v * 8 + 0] = static_cast(key_val & 0xff); + row_data[v * 8 + 1] = static_cast((key_val >> 8u) & 0xff); + row_data[v * 8 + 2] = static_cast((key_val >> 16u) & 0xff); + row_data[v * 8 + 3] = static_cast((key_val >> 24u) & 0xff); + row_data[v * 8 + 4] = static_cast((key_val >> 32u) & 0xff); + row_data[v * 8 + 5] = static_cast((key_val >> 40u) & 0xff); + row_data[v * 8 + 6] = static_cast((key_val >> 48u) & 0xff); + row_data[v * 8 + 7] = static_cast((key_val >> 56u) & 0xff); + } + + png_write_row(png_ptr, + reinterpret_cast(row_data.data())); + } + + png_write_end(png_ptr, nullptr); + + png_destroy_write_struct(&png_ptr, &png_info_ptr); + + return false; // SUCCESS +} + +} // namespace osf +} // namespace ouster diff --git a/ouster_osf/src/png_tools.cpp b/ouster_osf/src/png_tools.cpp index b197f6a9..4999394d 100644 --- a/ouster_osf/src/png_tools.cpp +++ b/ouster_osf/src/png_tools.cpp @@ -5,8 +5,6 @@ #include "png_tools.h" -#include - #include #include #include @@ -21,21 +19,6 @@ using namespace ouster::sensor; namespace ouster { namespace osf { -/** - * Effect of png_set_compression(comp level): - * - (no png out): 2s, n/a - * - comp level 1: 39s, 648M (60% speedup vs default, 10% size increase) - * - comp level 2: 38s, 643M - * - comp level 3: 45s, 639M - * - comp level 4: 48s, 590M (47% speedup vs default, <1% size increase) - * - comp level 5: 61s, 589M - * - libpng default: 98s, 586M - * - comp level 9: 328s, 580M - * - * @todo investigate other zlib options - */ -static constexpr int PNG_OSF_ZLIB_COMPRESSION_LEVEL = 4; - /** * Provides the data reader capabilities from std::vector for png_read IO */ @@ -58,7 +41,9 @@ struct VectorReader { /** * Error callback that will be fired on libpng errors + * @TODO Change up tests to not use this stuff */ +OUSTER_API_FUNCTION void png_osf_error(png_structp png_ptr, png_const_charp msg) { logger().error("ERROR libpng osf: {}", msg); longjmp(png_jmpbuf(png_ptr), 1); @@ -122,7 +107,9 @@ void png_osf_read_data(png_structp png_ptr, png_bytep bytes, /** * It's needed for custom png IO operations... but I've never seen it's called. * And also there are no need to flush writer to std::vector buffer in our case. + * @TODO Change up tests to not use this stuff */ +OUSTER_API_FUNCTION void png_osf_flush_data(png_structp){}; /** @@ -172,13 +159,13 @@ bool png_osf_read_init(png_structpp png_ptrp, png_infopp png_info_ptrp) { * Write destination is res_buf of std::vector type. */ void png_osf_write_start(png_structp png_ptr, png_infop png_info_ptr, - ScanChannelData& res_buf, const uint32_t width, - const uint32_t height, const int sample_depth, - const int color_type) { + ScanChannelData& res_buf, uint32_t width, + uint32_t height, int sample_depth, int color_type, + int compression_amount) { // Use setjmp() on upper level for errors catching png_set_write_fn(png_ptr, &res_buf, png_osf_write_data, png_osf_flush_data); - png_set_compression_level(png_ptr, PNG_OSF_ZLIB_COMPRESSION_LEVEL); + png_set_compression_level(png_ptr, compression_amount); png_set_IHDR(png_ptr, png_info_ptr, width, height, sample_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, @@ -187,582 +174,24 @@ void png_osf_write_start(png_structp png_ptr, png_infop png_info_ptr, png_write_info(png_ptr, png_info_ptr); } -// ========== Encode Functions =================================== -#ifdef OUSTER_OSF_NO_THREADING -ScanData scanEncodeFieldsSingleThread(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types) { - // Prepare scan data of size that fits all field_types we are about to - // encode - ScanData fields_data(field_types.size()); - - size_t scan_idx = 0; - for (const auto& f : field_types) { - fieldEncode(lidar_scan, f, px_offset, fields_data, scan_idx); - scan_idx += 1; - } - - return fields_data; -} -#else -ScanData scanEncodeFields(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types) { - // Prepare scan data of size that fits all field_types we are about to - // encode - ScanData fields_data(field_types.size()); - - unsigned int con_num = std::thread::hardware_concurrency(); - // looking for at least 4 cores if can't determine - if (!con_num) con_num = 4; - - const size_t fields_num = field_types.size(); - // Number of fields to pack into a single thread coder - size_t per_thread_num = (fields_num + con_num - 1) / con_num; - std::vector> futures{}; - size_t scan_idx = 0; - for (size_t t = 0; t < con_num && t * per_thread_num < fields_num; ++t) { - // Per every thread we pack the `per_thread_num` field_types encodings - // job - const size_t start_idx = t * per_thread_num; - // Fields list for a thread to encode - LidarScanFieldTypes thread_fields{}; - // Scan indices for the corresponding fields where result will be stored - std::vector thread_idxs{}; - for (size_t i = 0; i < per_thread_num && i + start_idx < fields_num; - ++i) { - thread_fields.push_back(field_types[start_idx + i]); - thread_idxs.push_back(scan_idx); - scan_idx += 1; - } - // Start an encoder thread with selected fields and corresponding - // indices list - futures.emplace_back(std::async(fieldEncodeMulti, std::cref(lidar_scan), - thread_fields, std::cref(px_offset), - std::ref(fields_data), thread_idxs)); - } - - for (auto& t : futures) { - t.get(); - } - - return fields_data; -} -#endif - -template -bool encode8bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset) { - return encode8bitImage(res_buf, destagger(img, px_offset)); -} - -template bool encode8bitImage(ScanChannelData&, - const Eigen::Ref>&, - const std::vector&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); - -template -bool encode8bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img) { - const uint32_t width = static_cast(img.cols()); - const uint32_t height = static_cast(img.rows()); - - // 8 bit Gray - const int sample_depth = 8; - const int color_type = PNG_COLOR_TYPE_GRAY; - - // 8bit Encoding Sizes - std::vector row_data(width); // Gray, 8bit - - // libpng main structs - png_structp png_ptr; - png_infop png_info_ptr; - - if (png_osf_write_init(&png_ptr, &png_info_ptr)) { - return true; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &png_info_ptr); - return true; - } - - png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, - sample_depth, color_type); - - for (size_t u = 0; u < height; ++u) { - for (size_t v = 0; v < width; ++v) { - // 8bit Encoding Logic - row_data[v] = static_cast(img(u, v)); - } - - png_write_row(png_ptr, - reinterpret_cast(row_data.data())); - } - - png_write_end(png_ptr, nullptr); - - png_destroy_write_struct(&png_ptr, &png_info_ptr); - - return false; // SUCCESS -} - -template bool encode8bitImage(ScanChannelData&, - const Eigen::Ref>&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode8bitImage( - ScanChannelData&, const Eigen::Ref>&); - -template -bool encode16bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img) { - const uint32_t width = static_cast(img.cols()); - const uint32_t height = static_cast(img.rows()); - - // 16 bit Gray - const int sample_depth = 16; - const int color_type = PNG_COLOR_TYPE_GRAY; - - // 16bit Encoding Sizes - std::vector row_data(width * 2); // Gray, 16bit - - // libpng main structs - png_structp png_ptr; - png_infop png_info_ptr; - - if (png_osf_write_init(&png_ptr, &png_info_ptr)) { - return true; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &png_info_ptr); - return true; - } - - png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, - sample_depth, color_type); - - // Needed to transform provided little-endian samples to internal - // PNG big endian format - png_set_swap(png_ptr); - - for (size_t u = 0; u < height; ++u) { - for (size_t v = 0; v < width; ++v) { - const uint64_t key_val = img(u, v); - - // 16bit Encoding Logic - row_data[v * 2] = static_cast(key_val & 0xff); - row_data[v * 2 + 1] = static_cast((key_val >> 8u) & 0xff); - } - - png_write_row(png_ptr, - reinterpret_cast(row_data.data())); - } - - png_write_end(png_ptr, nullptr); - - png_destroy_write_struct(&png_ptr, &png_info_ptr); - - return false; // SUCCESS -} - -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&); - -template -bool encode16bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset) { - return encode16bitImage(res_buf, destagger(img, px_offset)); -} - -template bool encode16bitImage(ScanChannelData&, - const Eigen::Ref>&, - const std::vector&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode16bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); - -template -bool encode24bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset) { - return encode24bitImage(res_buf, destagger(img, px_offset)); -} - -template bool encode24bitImage(ScanChannelData&, - const Eigen::Ref>&, - const std::vector&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); - -template -bool encode24bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img) { - const uint32_t width = static_cast(img.cols()); - const uint32_t height = static_cast(img.rows()); - - // 8bit RGB - const int sample_depth = 8; - const int color_type = PNG_COLOR_TYPE_RGB; - - // 24bit Encoding Sizes - std::vector row_data(width * 3); // RGB, 8bit - - // libpng main structs - png_structp png_ptr; - png_infop png_info_ptr; - - if (png_osf_write_init(&png_ptr, &png_info_ptr)) { - return true; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &png_info_ptr); - return true; - } - - png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, - sample_depth, color_type); - - for (size_t u = 0; u < height; ++u) { - for (size_t v = 0; v < width; ++v) { - const uint64_t key_val = img(u, v); - - // 24bit Encoding Logic - row_data[v * 3 + 0] = static_cast(key_val & 0xff); - row_data[v * 3 + 1] = static_cast((key_val >> 8u) & 0xff); - row_data[v * 3 + 2] = static_cast((key_val >> 16u) & 0xff); - } - - png_write_row(png_ptr, - reinterpret_cast(row_data.data())); - } - - png_write_end(png_ptr, nullptr); - - png_destroy_write_struct(&png_ptr, &png_info_ptr); - - return false; // SUCCESS -} - -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode24bitImage( - ScanChannelData&, const Eigen::Ref>&); - -template -bool encode32bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset) { - return encode32bitImage(res_buf, destagger(img, px_offset)); -} - -template bool encode32bitImage(ScanChannelData&, - const Eigen::Ref>&, - const std::vector&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); - -template -bool encode32bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img) { - const uint32_t width = static_cast(img.cols()); - const uint32_t height = static_cast(img.rows()); - - // 8bit RGBA - const int sample_depth = 8; - const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; - - // 32bit Encoding Sizes - std::vector row_data(width * 4); // RGBA, 8bit - - // libpng main structs - png_structp png_ptr; - png_infop png_info_ptr; - - if (png_osf_write_init(&png_ptr, &png_info_ptr)) { - return true; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &png_info_ptr); - return true; - } - - png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, - sample_depth, color_type); - - for (size_t u = 0; u < height; ++u) { - for (size_t v = 0; v < width; ++v) { - const uint64_t key_val = img(u, v); - - // 32bit Encoding Logic - row_data[v * 4 + 0] = static_cast(key_val & 0xff); - row_data[v * 4 + 1] = static_cast((key_val >> 8u) & 0xff); - row_data[v * 4 + 2] = static_cast((key_val >> 16u) & 0xff); - row_data[v * 4 + 3] = static_cast((key_val >> 24u) & 0xff); - } - - png_write_row(png_ptr, - reinterpret_cast(row_data.data())); - } - - png_write_end(png_ptr, nullptr); - - png_destroy_write_struct(&png_ptr, &png_info_ptr); - - return false; // SUCCESS -} - -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode32bitImage( - ScanChannelData&, const Eigen::Ref>&); - -template -bool encode64bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset) { - return encode64bitImage(res_buf, destagger(img, px_offset)); -} - -template bool encode64bitImage(ScanChannelData&, - const Eigen::Ref>&, - const std::vector&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&, - const std::vector&); - -template -bool encode64bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img) { - const uint32_t width = static_cast(img.cols()); - const uint32_t height = static_cast(img.rows()); - - // 16bit RGBA - const int sample_depth = 16; - const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; - - // 64bit Encoding Sizes - std::vector row_data(width * 8); // RGBA, 16bit - - // libpng main structs - png_structp png_ptr; - png_infop png_info_ptr; - - if (png_osf_write_init(&png_ptr, &png_info_ptr)) { - return true; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &png_info_ptr); - return true; - } - - png_osf_write_start(png_ptr, png_info_ptr, res_buf, width, height, - sample_depth, color_type); - - // Needed to transform provided little-endian samples to internal - // PNG big endian format - png_set_swap(png_ptr); - - for (size_t u = 0; u < height; ++u) { - for (size_t v = 0; v < width; ++v) { - const uint64_t key_val = img(u, v); - - // 64bit Encoding Logic - row_data[v * 8 + 0] = static_cast(key_val & 0xff); - row_data[v * 8 + 1] = static_cast((key_val >> 8u) & 0xff); - row_data[v * 8 + 2] = static_cast((key_val >> 16u) & 0xff); - row_data[v * 8 + 3] = static_cast((key_val >> 24u) & 0xff); - row_data[v * 8 + 4] = static_cast((key_val >> 32u) & 0xff); - row_data[v * 8 + 5] = static_cast((key_val >> 40u) & 0xff); - row_data[v * 8 + 6] = static_cast((key_val >> 48u) & 0xff); - row_data[v * 8 + 7] = static_cast((key_val >> 56u) & 0xff); - } - - png_write_row(png_ptr, - reinterpret_cast(row_data.data())); - } - - png_write_end(png_ptr, nullptr); - - png_destroy_write_struct(&png_ptr, &png_info_ptr); - - return false; // SUCCESS -} - -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&); -template bool encode64bitImage( - ScanChannelData&, const Eigen::Ref>&); - -void fieldEncodeMulti(const LidarScan& lidar_scan, - const LidarScanFieldTypes& field_types, - const std::vector& px_offset, ScanData& scan_data, - const std::vector& scan_idxs) { - if (field_types.size() != scan_idxs.size()) { - throw std::invalid_argument( - "ERROR: in fieldEncodeMulti field_types.size() should " - "match scan_idxs.size()"); - } - for (size_t i = 0; i < field_types.size(); ++i) { - auto err = fieldEncode(lidar_scan, field_types[i], px_offset, scan_data, - scan_idxs[i]); - if (err) { - logger().error( - "ERROR: fieldEncode: Can't encode field [{}]" - "(in fieldEncodeMulti)", - field_types[i].first); - } - } -} - -bool fieldEncode( - const LidarScan& lidar_scan, - const std::pair& field_type, - const std::vector& px_offset, ScanData& scan_data, size_t scan_idx) { - if (scan_idx >= scan_data.size()) { - throw std::invalid_argument( - "ERROR: scan_data size is not sufficient to hold idx: " + - std::to_string(scan_idx)); - } - bool res = true; - switch (field_type.second) { - case sensor::ChanFieldType::UINT8: - res = encode8bitImage(scan_data[scan_idx], - lidar_scan.field(field_type.first), - px_offset); - break; - case sensor::ChanFieldType::UINT16: - res = encode16bitImage(scan_data[scan_idx], - lidar_scan.field(field_type.first), - px_offset); - break; - case sensor::ChanFieldType::UINT32: - res = encode32bitImage(scan_data[scan_idx], - lidar_scan.field(field_type.first), - px_offset); - break; - case sensor::ChanFieldType::UINT64: - res = encode64bitImage(scan_data[scan_idx], - lidar_scan.field(field_type.first), - px_offset); - break; - default: - logger().error( - "ERROR: fieldEncode: UNKNOWN:" - "ChanFieldType not yet " - "implemented"); - break; - } - if (res) { - logger().error("ERROR: fieldEncode: Can't encode field {}", - field_type.first); - } - return res; -} - -ScanData scanEncode(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types) { -#ifdef OUSTER_OSF_NO_THREADING - return scanEncodeFieldsSingleThread(lidar_scan, px_offset, field_types); -#else - return scanEncodeFields(lidar_scan, px_offset, field_types); -#endif -} - // ========== Decode Functions =================================== -bool scanDecode(LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& field_types) { -#ifdef OUSTER_OSF_NO_THREADING - return scanDecodeFieldsSingleThread(lidar_scan, scan_data, px_offset, - field_types); -#else - return scanDecodeFields(lidar_scan, scan_data, px_offset, field_types); -#endif -} - -bool fieldDecode( - LidarScan& lidar_scan, const ScanData& scan_data, size_t start_idx, - const std::pair& field_type, - const std::vector& px_offset) { - switch (field_type.second) { +bool fieldDecode(LidarScan& lidar_scan, const ScanData& scan_data, + size_t start_idx, const ouster::FieldType& field_type, + const std::vector& px_offset) { + switch (field_type.element_type) { case sensor::ChanFieldType::UINT8: - return decode8bitImage(lidar_scan.field(field_type.first), + return decode8bitImage(lidar_scan.field(field_type.name), scan_data[start_idx], px_offset); case sensor::ChanFieldType::UINT16: - return decode16bitImage( - lidar_scan.field(field_type.first), - scan_data[start_idx], px_offset); + return decode16bitImage(lidar_scan.field(field_type.name), + scan_data[start_idx], px_offset); case sensor::ChanFieldType::UINT32: - return decode32bitImage( - lidar_scan.field(field_type.first), - scan_data[start_idx], px_offset); + return decode32bitImage(lidar_scan.field(field_type.name), + scan_data[start_idx], px_offset); case sensor::ChanFieldType::UINT64: - return decode64bitImage( - lidar_scan.field(field_type.first), - scan_data[start_idx], px_offset); + return decode64bitImage(lidar_scan.field(field_type.name), + scan_data[start_idx], px_offset); default: logger().error( "ERROR: fieldDecode: UNKNOWN:" @@ -773,108 +202,6 @@ bool fieldDecode( return true; // ERROR } -bool fieldDecodeMulti(LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& scan_idxs, - const LidarScanFieldTypes& field_types, - const std::vector& px_offset) { - if (field_types.size() != scan_idxs.size()) { - throw std::invalid_argument( - "ERROR: in fieldDecodeMulti field_types.size() should " - "match scan_idxs.size()"); - } - auto res_err = false; - for (size_t i = 0; i < field_types.size(); ++i) { - if (!lidar_scan.has_field(field_types[i].first)) { - continue; - } - auto err = fieldDecode(lidar_scan, scan_data, scan_idxs[i], - field_types[i], px_offset); - if (err) { - logger().error( - "ERROR: fieldDecodeMulti: " - "Can't decode field [{}]", - field_types[i].first); - } - res_err = res_err || err; - } - return res_err; -} -#ifdef OUSTER_OSF_NO_THREADING -bool scanDecodeFieldsSingleThread( - LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& field_types) { - size_t fields_cnt = lidar_scan.fields().size(); - size_t next_idx = 0; - for (auto ft : field_types) { - if (!lidar_scan.has_field(ft.name)) { - ++next_idx; - continue; - } - if (fieldDecode(lidar_scan, scan_data, next_idx, - {ft.name, ft.element_type}, px_offset)) { - logger().error( - "ERROR: scanDecodeFields:" - "Failed to decode field"); - return true; - } - ++next_idx; - } - return false; -} -#else -// TWS 20240301 TODO: determine if we can deduplicate this code (see -// scanEncodeFields) -bool scanDecodeFields(LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& field_types) { - size_t fields_num = field_types.size(); - - unsigned int con_num = std::thread::hardware_concurrency(); - // looking for at least 4 cores if can't determine - if (!con_num) con_num = 4; - - // Number of fields to pack into a single thread coder - size_t per_thread_num = (fields_num + con_num - 1) / con_num; - std::vector> futures{}; - size_t scan_idx = 0; - - for (size_t t = 0; t < con_num && t * per_thread_num < fields_num; ++t) { - // Per every thread we pack the `per_thread_num` field_types encodings - // job - const size_t start_idx = t * per_thread_num; - // Fields list for a thread to encode - LidarScanFieldTypes thread_fields{}; - // Scan indices for the corresponding fields where result will be stored - std::vector thread_idxs{}; - for (size_t i = 0; i < per_thread_num && i + start_idx < fields_num; - ++i) { - thread_fields.push_back({field_types[start_idx + i].name, - field_types[start_idx + i].element_type}); - thread_idxs.push_back(scan_idx); - scan_idx += 1; // for UINT64 can be 2 (NOT IMPLEMENTED YET) - } - - // Start a decoder thread with selected fields and corresponding - // indices list - - futures.emplace_back(std::async(fieldDecodeMulti, std::ref(lidar_scan), - std::cref(scan_data), thread_idxs, - thread_fields, std::cref(px_offset))); - } - - for (auto& t : futures) { - // TODO: refactor, use return std::all - bool res = t.get(); - if (!res) { - return false; - } - } - - return false; -} -#endif - template bool decode24bitImage(Eigen::Ref> img, const ScanChannelData& channel_buf, @@ -1391,54 +718,6 @@ template bool decode8bitImage(Eigen::Ref>, template bool decode8bitImage(Eigen::Ref>, const ScanChannelData&); -ScanChannelData encodeField(const ouster::Field& field) { - ScanChannelData buffer; - - // do not compress, flat fields "compressed" size is greater than original - if (field.shape().size() == 1) { - buffer.resize(field.bytes()); - std::memcpy(buffer.data(), field, field.bytes()); - return buffer; - } - - // empty case - if (field.bytes() == 0) { - return buffer; - } - - FieldView view = uint_view(field); - // collapse shape - if (view.shape().size() > 2) { - size_t rows = view.shape()[0]; - size_t cols = view.size() / rows; - view = view.reshape(rows, cols); - } - - bool res = true; - switch (view.tag()) { - case sensor::ChanFieldType::UINT8: - res = encode8bitImage(buffer, view); - break; - case sensor::ChanFieldType::UINT16: - res = encode16bitImage(buffer, view); - break; - case sensor::ChanFieldType::UINT32: - res = encode32bitImage(buffer, view); - break; - case sensor::ChanFieldType::UINT64: - res = encode64bitImage(buffer, view); - break; - default: - break; - } - - if (res) { - throw std::runtime_error("encodeField: could not encode field"); - } - - return buffer; -} - void decodeField(ouster::Field& field, const ScanChannelData& buffer) { // 1d case, uncompressed if (field.shape().size() == 1) { diff --git a/ouster_osf/src/png_tools.h b/ouster_osf/src/png_tools.h index 7773c661..0a50967c 100644 --- a/ouster_osf/src/png_tools.h +++ b/ouster_osf/src/png_tools.h @@ -5,12 +5,15 @@ #pragma once +#include + #include #include #include "os_sensor/common_generated.h" #include "os_sensor/lidar_scan_stream_generated.h" #include "ouster/lidar_scan.h" +#include "ouster/visibility.h" namespace ouster { namespace osf { @@ -21,46 +24,17 @@ using ScanChannelData = std::vector; // Encoded PNG buffers using ScanData = std::vector; -// FieldTypes container -using LidarScanFieldTypes = - std::vector>; - +bool png_osf_write_init(png_structpp png_ptrp, png_infopp png_info_ptrp); +void png_osf_write_start(png_structp png_ptr, png_infop png_info_ptr, + ScanChannelData& res_buf, uint32_t width, + uint32_t height, int sample_depth, int color_type, + int compression_amount); /** * libpng only versions for Encode/Decode LidarScan to PNG buffers */ // ========== Decode Functions =================================== -/** - * Decode the PNG buffers into LidarScan object. This is a dispatch function to - * the specific decoding functions. - * - * @param[out] lidar_scan The output object that will be filled as a result of - * decoding. - * @param[in] scan_data PNG buffers to decode. - * @param[in] px_offset Pixel shift per row used to reconstruct staggered range - * image form. - * @param[in] fields Fields to deserialize in the correct order. - * @return false (0) if operation is successful true (1) if error occured - */ -bool scanDecode(LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& fields); - -#ifdef OUSTER_OSF_NO_THREADING -/// Decoding eUDP LidarScan -// TODO[pb]: Make decoding of just some fields from scan data?? Not now ... -bool scanDecodeFieldsSingleThread(LidarScan& lidar_scan, - const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& fields); -#else -/// Decoding eUDP LidarScan, multithreaded version -bool scanDecodeFields(LidarScan& lidar_scan, const ScanData& scan_data, - const std::vector& px_offset, - const ouster::LidarScanFieldTypes& fields); -#endif - /** * Decode a single field to lidar_scan * @@ -74,10 +48,10 @@ bool scanDecodeFields(LidarScan& lidar_scan, const ScanData& scan_data, * image form. * @return false (0) if operation is successful true (1) if error occured */ -bool fieldDecode( - LidarScan& lidar_scan, const ScanData& scan_data, size_t scan_idx, - const std::pair& field_type, - const std::vector& px_offset); +OUSTER_API_FUNCTION +bool fieldDecode(LidarScan& lidar_scan, const ScanData& scan_data, + size_t scan_idx, const ouster::FieldType& field_type, + const std::vector& px_offset); /** * Decode multiple fields to lidar_scan @@ -96,6 +70,7 @@ bool fieldDecode( * image form * @return false (0) if operation is successful true (1) if error occured */ +OUSTER_API_FUNCTION bool fieldDecodeMulti(LidarScan& lidar_scan, const ScanData& scan_data, const std::vector& scan_idxs, const LidarScanFieldTypes& field_types, @@ -115,8 +90,8 @@ bool fieldDecodeMulti(LidarScan& lidar_scan, const ScanData& scan_data, /** @copydoc OSFPngDecode8 */ template -bool decode8bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf); +OUSTER_API_FUNCTION bool decode8bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf); /** * @copydoc OSFPngDecode8 @@ -124,9 +99,9 @@ bool decode8bitImage(Eigen::Ref> img, * image form */ template -bool decode8bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf, - const std::vector& px_offset); +OUSTER_API_FUNCTION bool decode8bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf, + const std::vector& px_offset); /** * @defgroup OSFPngDecode16 Decoding Functionality. @@ -147,14 +122,14 @@ bool decode8bitImage(Eigen::Ref> img, * image form */ template -bool decode16bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf, - const std::vector& px_offset); +OUSTER_API_FUNCTION bool decode16bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf, + const std::vector& px_offset); /** @copydoc OSFPngDecode16 */ template -bool decode16bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf); +OUSTER_API_FUNCTION bool decode16bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf); /** * @defgroup OSFPngDecode24 Decoding Functionality. @@ -175,14 +150,14 @@ bool decode16bitImage(Eigen::Ref> img, * image form. */ template -bool decode24bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf, - const std::vector& px_offset); +OUSTER_API_FUNCTION bool decode24bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf, + const std::vector& px_offset); /** @copydoc OSFPngDecode24 */ template -bool decode24bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf); +OUSTER_API_FUNCTION bool decode24bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf); /** * @defgroup OSFPngDecode32 Decoding Functionality. @@ -203,14 +178,14 @@ bool decode24bitImage(Eigen::Ref> img, * image form. */ template -bool decode32bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf, - const std::vector& px_offset); +OUSTER_API_FUNCTION bool decode32bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf, + const std::vector& px_offset); /** @copydoc OSFPngDecode32 */ template -bool decode32bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf); +OUSTER_API_FUNCTION bool decode32bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf); /** * @defgroup OSFPngDecode64 Decoding Functionality. @@ -231,238 +206,14 @@ bool decode32bitImage(Eigen::Ref> img, * image form. */ template -bool decode64bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf, - const std::vector& px_offset); +OUSTER_API_FUNCTION bool decode64bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf, + const std::vector& px_offset); /** @copydoc OSFPngDecode64 */ template -bool decode64bitImage(Eigen::Ref> img, - const ScanChannelData& channel_buf); - -// ========== Encode Functions =================================== - -/** - * Encode LidarScan to PNG buffers storing all field_types present in an object. - * - * @param[in] lidar_scan The LidarScan object to encode. - * @param[in] px_offset Pixel shift per row used to - * destaggered LidarScan data. - * @param[in] field_types the list of fields to encode. - * @return encoded PNG buffers, empty() if error occured. - */ -ScanData scanEncode(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types); - -#ifdef OUSTER_OSF_NO_THREADING -/** - * Encode the lidar scan fields to PNGs channel buffers (ScanData). - * Single-threaded implementation. - * - * @param[in] lidar_scan A lidar scan object to encode. - * @param[in] px_offset Pixel shift per row used to construct de-staggered range - * image form. - * @param[in] field_types the list of fields to encode. - * @return Encoded PNGs in ScanData in order of field_types. - */ -ScanData scanEncodeFieldsSingleThread(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types); -#else -/** - * Encode the lidar scan fields to PNGs channel buffers (ScanData). - * Multi-threaded implementation. - * - * @param[in] lidar_scan A lidar scan object to encode. - * @param[in] px_offset Pixel shift per row used to construct de-staggered range - * image form. - * @param[in] field_types The field types to use for encoding. - * @return Encoded PNGs in ScanData in order of field_types. - */ -ScanData scanEncodeFields(const LidarScan& lidar_scan, - const std::vector& px_offset, - const LidarScanFieldTypes& field_types); -#endif -/** - * Encode a single lidar scan field to PNGs channel buffer and place it to a - * specified `scan_data[scan_idx]` place - * - * @param[in] lidar_scan a lidar scan object to encode - * @param[in] field_type a filed_type of lidar scan to encode - * @param[in] px_offset pixel shift per row used to construct de-staggered - * range image form - * @param[out] scan_data channel buffers storage for the encoded lidar_scan - * @param[in] scan_idx index in `scan_data` of the beginning of field buffers - * where the result of encoding will be inserted - * @return false (0) if operation is successful true (1) if error occured - */ -bool fieldEncode( - const LidarScan& lidar_scan, - const std::pair& field_type, - const std::vector& px_offset, ScanData& scan_data, size_t scan_idx); - -/** - * Encode multiple lidar scan fields to PNGs channel buffers and insert them to - * a specified places `scan_idxs` in `scan_data`. - * - * @param[in] lidar_scan a lidar scan object to encode - * @param[in] field_types a vector of filed_types of - * lidar scan to encode - * @param[in] px_offset pixel shift per row used to construct de-staggered range - * image form - * @param[out] scan_data channel buffers storage for the encoded lidar_scan - * @param[in] scan_idxs a vector of indices in `scan_data` of the beginning of - * field buffers where the result of encoding will be - * inserted. `field_types.size()` should be equal to - * `scan_idxs.size()` - */ -void fieldEncodeMulti(const LidarScan& lidar_scan, - const LidarScanFieldTypes& field_types, - const std::vector& px_offset, ScanData& scan_data, - const std::vector& scan_idxs); - -/** - * @defgroup OSFPngEncode8 Encoding Functionality. - * Encode img object into a 8 bit, Gray, PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img The image object to encode. - * @return false (0) if operation is successful, true (1) if error occured - */ - -/** - * @copydoc OSFPngEncode8 - * @param[in] px_offset Pixel shift per row used to destagger img data. - */ -template -bool encode8bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset); - -/** @copydoc OSFPngEncode8 */ -template -bool encode8bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img); - -/** - * Encode img object into a 16 bit, Gray, PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img The image object to encode. - * @param[in] px_offset Pixel shift per row used to destagger img data. - * @return false (0) if operation is successful, true (1) if error occured - */ -template -bool encode16bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset); - -/** - * Encode 2D image of a typical lidar scan field channel into a 16 bit, Gray, - * PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img The image object to encode. - * @return false (0) if operation is successful, true (1) if error occured - */ -template -bool encode16bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img); - -/** - * @defgroup OSFPngEncode32 Encoding Functionality. - * Encode 2D image of a typical lidar scan field channel into a 32 bit, RGBA, - * PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img 2D image or a single LidarScan field data. - * @return false (0) if operation is successful, true (1) if error occured - */ - -/** - * @copydoc OSFPngEncode32 - * @param[in] px_offset Pixel shift per row used to destagger img data. - */ -template -bool encode32bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset); - -/** @copydoc OSFPngEncode32 */ -template -bool encode32bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img); - -/** - * @defgroup OSFPngEncode24 Encoding Functionality. - * Encode 2D image of a typical lidar scan field channel into a 24 bit, RGB, - * PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img 2D image or a single LidarScan field data. - * @return false (0) if operation is successful, true (1) if error occured - */ - -/** - * @copydoc OSFPngEncode24 - * @param[in] px_offset Pixel shift per row used to destagger img data. - */ -template -bool encode24bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset); - -/** @copydoc OSFPngEncode24 */ -template -bool encode24bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img); - -/** - * @defgroup OSFPngEncode64 Encoding Functionality. - * Encode 2D image of a typical lidar scan field channel into a 64 bit, RGBA, - * PNG buffer. - * - * @tparam T The type to use for the output array. - * - * @param[out] res_buf The output buffer with a single encoded PNG. - * @param[in] img 2D image or a single LidarScan field data. - * @return false (0) if operation is successful, true (1) if error occured - */ - -/** - * @copydoc OSFPngEncode64 - * @param[in] px_offset Pixel shift per row used to destagger img data. - */ -template -bool encode64bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img, - const std::vector& px_offset); - -/** @copydoc OSFPngEncode64 */ -template -bool encode64bitImage(ScanChannelData& res_buf, - const Eigen::Ref>& img); - -/** - * Encode Field into a data buffer. - * May use png compression, depending on field dimensionality. - * - * @param[in] field field to encode - * - * @return output buffer - */ -ScanChannelData encodeField(const ouster::Field& field); +OUSTER_API_FUNCTION bool decode64bitImage(Eigen::Ref> img, + const ScanChannelData& channel_buf); /** * Decode Field from a data buffer. @@ -471,6 +222,7 @@ ScanChannelData encodeField(const ouster::Field& field); * @param[inout] field field to store result in * @param[in] buffer buffer to decode */ +OUSTER_API_FUNCTION void decodeField(ouster::Field& field, const ScanChannelData& buffer); } // namespace osf diff --git a/ouster_osf/src/stream_lidar_scan.cpp b/ouster_osf/src/stream_lidar_scan.cpp index b11a1686..18d2888f 100644 --- a/ouster_osf/src/stream_lidar_scan.cpp +++ b/ouster_osf/src/stream_lidar_scan.cpp @@ -6,6 +6,7 @@ #include "ouster/osf/stream_lidar_scan.h" #include +#include #include #include "ouster/impl/logging.h" @@ -115,10 +116,11 @@ flatbuffers::Offset> CreateVectorOfStructs( return res_off; } -flatbuffers::Offset create_osf_field( +flatbuffers::Offset LidarScanStream::create_osf_field( flatbuffers::FlatBufferBuilder& fbb, const std::string& name, - const Field& f) { - ScanChannelData data = encodeField(f); + const Field& f) const { + ScanChannelData data = + writer_.encoder().lidar_scan_encoder().encodeField(f); std::vector shape{f.shape().begin(), f.shape().end()}; return gen::CreateFieldDirect(fbb, name.c_str(), to_osf_enum(f.tag()), &shape, to_osf_enum(f.field_class()), &data, @@ -201,10 +203,222 @@ std::string from_osf_enum(gen::CHAN_FIELD f) { return lookup(chanfield_strings, f).value(); } -flatbuffers::Offset create_lidar_scan_msg( +// ========== Encode Functions =================================== +#ifdef OUSTER_OSF_NO_THREADING + +ScanData LidarScanStream::scanEncodeFieldsSingleThread( + const LidarScan& lidar_scan, const std::vector& px_offset, + const LidarScanFieldTypes& field_types) const { + // Prepare scan data of size that fits all field_types we are about to + // encode + ScanData fields_data(field_types.size()); + + size_t scan_idx = 0; + for (const auto& f : field_types) { + writer_.encoder().lidar_scan_encoder().fieldEncode( + lidar_scan, f, px_offset, fields_data, scan_idx); + scan_idx += 1; + } + + return fields_data; +} +#else + +void LidarScanStream::fieldEncodeMulti( + const LidarScan& lidar_scan, const ouster::LidarScanFieldTypes& field_types, + const std::vector& px_offset, ScanData& scan_data, + const std::vector& scan_idxs) const { + if (field_types.size() != scan_idxs.size()) { + throw std::invalid_argument( + "ERROR: in fieldEncodeMulti field_types.size() should " + "match scan_idxs.size()"); + } + for (size_t i = 0; i < field_types.size(); ++i) { + auto err = writer_.encoder().lidar_scan_encoder().fieldEncode( + lidar_scan, field_types[i], px_offset, scan_data, scan_idxs[i]); + if (err) { + logger().error( + "ERROR: fieldEncode: Can't encode field [{}]" + "(in fieldEncodeMulti)", + field_types[i].name); + } + } +} + +ScanData LidarScanStream::scanEncodeFields( + const LidarScan& lidar_scan, const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) const { + // Prepare scan data of size that fits all field_types we are about to + // encode + ScanData fields_data(field_types.size()); + + unsigned int con_num = std::thread::hardware_concurrency(); + // looking for at least 4 cores if can't determine + if (!con_num) con_num = 4; + + const size_t fields_num = field_types.size(); + // Number of fields to pack into a single thread coder + size_t per_thread_num = (fields_num + con_num - 1) / con_num; + std::vector> futures{}; + size_t scan_idx = 0; + for (size_t t = 0; t < con_num && t * per_thread_num < fields_num; ++t) { + // Per every thread we pack the `per_thread_num` field_types encodings + // job + const size_t start_idx = t * per_thread_num; + // Fields list for a thread to encode + LidarScanFieldTypes thread_fields{}; + // Scan indices for the corresponding fields where result will be stored + std::vector thread_idxs{}; + for (size_t i = 0; i < per_thread_num && i + start_idx < fields_num; + ++i) { + thread_fields.push_back(field_types[start_idx + i]); + thread_idxs.push_back(scan_idx); + scan_idx += 1; + } + // Start an encoder thread with selected fields and corresponding + // indices list + futures.emplace_back(std::async(&LidarScanStream::fieldEncodeMulti, + this, std::cref(lidar_scan), + thread_fields, std::cref(px_offset), + std::ref(fields_data), thread_idxs)); + } + + for (auto& t : futures) { + t.get(); + } + + return fields_data; +} +#endif +ScanData LidarScanStream::scanEncode( + const LidarScan& lidar_scan, const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) const { +#ifdef OUSTER_OSF_NO_THREADING + return scanEncodeFieldsSingleThread(lidar_scan, px_offset, field_types); +#else + return scanEncodeFields(lidar_scan, px_offset, field_types); +#endif +} + +#ifdef OUSTER_OSF_NO_THREADING +bool scanDecodeFieldsSingleThread( + LidarScan& lidar_scan, const ScanData& scan_data, + const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) { + size_t fields_cnt = lidar_scan.fields().size(); + size_t next_idx = 0; + for (auto ft : field_types) { + if (!lidar_scan.has_field(ft.name)) { + ++next_idx; + continue; + } + if (fieldDecode(lidar_scan, scan_data, next_idx, + {ft.name, ft.element_type}, px_offset)) { + logger().error( + "ERROR: scanDecodeFields:" + "Failed to decode field"); + return true; + } + ++next_idx; + } + return false; +} +#else + +bool fieldDecodeMulti(LidarScan& lidar_scan, const ScanData& scan_data, + const std::vector& scan_idxs, + const ouster::LidarScanFieldTypes& field_types, + const std::vector& px_offset) { + if (field_types.size() != scan_idxs.size()) { + throw std::invalid_argument( + "ERROR: in fieldDecodeMulti field_types.size() should " + "match scan_idxs.size()"); + } + auto res_err = false; + for (size_t i = 0; i < field_types.size(); ++i) { + if (!lidar_scan.has_field(field_types[i].name)) { + continue; + } + auto err = fieldDecode(lidar_scan, scan_data, scan_idxs[i], + field_types[i], px_offset); + if (err) { + logger().error( + "ERROR: fieldDecodeMulti: " + "Can't decode field [{}]", + field_types[i].name); + } + res_err = res_err || err; + } + return res_err; +} + +// TWS 20240301 TODO: determine if we can deduplicate this code (see +// scanEncodeFields) +bool scanDecodeFields(LidarScan& lidar_scan, const ScanData& scan_data, + const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) { + size_t fields_num = field_types.size(); + + unsigned int con_num = std::thread::hardware_concurrency(); + // looking for at least 4 cores if can't determine + if (!con_num) con_num = 4; + + // Number of fields to pack into a single thread coder + size_t per_thread_num = (fields_num + con_num - 1) / con_num; + std::vector> futures{}; + size_t scan_idx = 0; + + for (size_t t = 0; t < con_num && t * per_thread_num < fields_num; ++t) { + // Per every thread we pack the `per_thread_num` field_types encodings + // job + const size_t start_idx = t * per_thread_num; + // Fields list for a thread to encode + LidarScanFieldTypes thread_fields{}; + // Scan indices for the corresponding fields where result will be stored + std::vector thread_idxs{}; + for (size_t i = 0; i < per_thread_num && i + start_idx < fields_num; + ++i) { + thread_fields.push_back({field_types[start_idx + i].name, + field_types[start_idx + i].element_type}); + thread_idxs.push_back(scan_idx); + scan_idx += 1; // for UINT64 can be 2 (NOT IMPLEMENTED YET) + } + + // Start a decoder thread with selected fields and corresponding + // indices list + + futures.emplace_back(std::async(fieldDecodeMulti, std::ref(lidar_scan), + std::cref(scan_data), thread_idxs, + thread_fields, std::cref(px_offset))); + } + + for (auto& t : futures) { + // TODO: refactor, use return std::all + bool res = t.get(); + if (!res) { + return false; + } + } + + return false; +} +#endif + +bool scanDecode(LidarScan& lidar_scan, const ScanData& scan_data, + const std::vector& px_offset, + const ouster::LidarScanFieldTypes& field_types) { +#ifdef OUSTER_OSF_NO_THREADING + return scanDecodeFieldsSingleThread(lidar_scan, scan_data, px_offset, + field_types); +#else + return scanDecodeFields(lidar_scan, scan_data, px_offset, field_types); +#endif +} + +flatbuffers::Offset LidarScanStream::create_lidar_scan_msg( flatbuffers::FlatBufferBuilder& fbb, const LidarScan& lidar_scan, const ouster::sensor::sensor_info& info, - const ouster::LidarScanFieldTypes meta_field_types) { + const ouster::LidarScanFieldTypes meta_field_types) const { const auto& ls = lidar_scan; // Prepare field_types for LidarScanMsg @@ -241,8 +455,7 @@ flatbuffers::Offset create_lidar_scan_msg( // now actually build our arrays from the sorted one std::vector field_types; - std::vector> - standard_fields; + ouster::LidarScanFieldTypes standard_fields; for (const auto& f : standard_fields_to_sort) { const auto& field = ls.field(f.second); field_types.push_back( @@ -448,9 +661,9 @@ std::unique_ptr restore_lidar_scan( // Set alert flags per lidar packet auto alert_flags_vec = ls_msg->alert_flags(); if (alert_flags_vec) { - if (static_cast(ls->alert_flags().size()) == + if (static_cast(ls->alert_flags().size()) == alert_flags_vec->size()) { - for (uint8_t i = 0; i < alert_flags_vec->size(); ++i) { + for (size_t i = 0; i < alert_flags_vec->size(); ++i) { ls->alert_flags()[i] = alert_flags_vec->Get(i); } } else if (alert_flags_vec->size() != 0) { @@ -627,6 +840,14 @@ void LidarScanStream::save(const ouster::osf::ts_t receive_ts, } std::vector LidarScanStream::make_msg(const LidarScan& lidar_scan) { + if (lidar_scan.w != sensor_info_.w() || lidar_scan.h != sensor_info_.h()) { + std::stringstream exception_msg_stream; + exception_msg_stream + << "lidar scan size (" << lidar_scan.w << ", " << lidar_scan.h + << ") does not match the sensor info resolution (" + << sensor_info_.w() << ", " << sensor_info_.h() << ")"; + throw std::invalid_argument(exception_msg_stream.str()); + } flatbuffers::FlatBufferBuilder fbb = flatbuffers::FlatBufferBuilder(32768); auto ls_msg_offset = create_lidar_scan_msg(fbb, lidar_scan, sensor_info_, field_types_); diff --git a/ouster_osf/src/writer.cpp b/ouster_osf/src/writer.cpp index 9d0b813c..4a6048af 100644 --- a/ouster_osf/src/writer.cpp +++ b/ouster_osf/src/writer.cpp @@ -12,6 +12,7 @@ #include "ouster/osf/basics.h" #include "ouster/osf/crc32.h" #include "ouster/osf/layout_streaming.h" +#include "ouster/osf/png_lidarscan_encoder.h" #include "ouster/osf/stream_lidar_scan.h" using namespace ouster::sensor; @@ -22,9 +23,11 @@ namespace ouster { namespace osf { Writer::Writer(const std::string& filename, uint32_t chunk_size) - : file_name_(filename), + : filename_(filename), metadata_id_{"ouster_sdk"}, - chunks_layout_{ChunksLayout::LAYOUT_STREAMING} { + chunks_layout_{ChunksLayout::LAYOUT_STREAMING}, + encoder_{std::make_shared(std::make_shared( + ouster::osf::DEFAULT_PNG_OSF_ZLIB_COMPRESSION_LEVEL))} { // chunks STREAMING_LAYOUT chunks_writer_ = std::make_shared(*this, chunk_size); @@ -34,7 +37,7 @@ Writer::Writer(const std::string& filename, uint32_t chunk_size) // TODO[pb]: Check if file exists, add flag overwrite/not overwrite, etc - header_size_ = start_osf_file(file_name_); + header_size_ = start_osf_file(filename_); if (header_size_ > 0) { pos_ = static_cast(header_size_); @@ -46,14 +49,14 @@ Writer::Writer(const std::string& filename, uint32_t chunk_size) Writer::Writer(const std::string& filename, const ouster::sensor::sensor_info& info, const std::vector& desired_fields, - uint32_t chunk_size) + uint32_t chunk_size, std::shared_ptr encoder) : Writer(filename, std::vector{info}, - desired_fields, chunk_size) {} + desired_fields, chunk_size, encoder) {} Writer::Writer(const std::string& filename, const std::vector& info, const std::vector& desired_fields, - uint32_t chunk_size) + uint32_t chunk_size, std::shared_ptr encoder) : Writer(filename, chunk_size) { sensor_info_ = info; for (uint32_t i = 0; i < info.size(); i++) { @@ -61,6 +64,10 @@ Writer::Writer(const std::string& filename, field_types_.push_back({}); desired_fields_.push_back(desired_fields); } + if (encoder) { + // set encoder if one is specified + encoder_ = encoder; + } } const std::vector& Writer::sensor_info() const { @@ -211,7 +218,7 @@ uint64_t Writer::append(const uint8_t* buf, const uint64_t size) { logger().info("Writer::append has nothing to append"); return 0; } - uint64_t saved_bytes = buffer_to_file(buf, size, file_name_, true); + uint64_t saved_bytes = buffer_to_file(buf, size, filename_, true); pos_ += static_cast(saved_bytes); return saved_bytes; } @@ -240,7 +247,7 @@ const std::string& Writer::metadata_id() const { return metadata_id_; } void Writer::set_metadata_id(const std::string& id) { metadata_id_ = id; } -const std::string& Writer::filename() const { return file_name_; } +const std::string& Writer::filename() const { return filename_; } ChunksLayout Writer::chunks_layout() const { return chunks_layout_; } @@ -289,8 +296,7 @@ std::vector Writer::make_metadata() const { } void Writer::close() { - if (finished_) { - // already did everything + if (is_closed()) { return; } @@ -305,7 +311,7 @@ void Writer::close() { append(metadata_buf.data(), metadata_buf.size()); if (metadata_saved_size && metadata_saved_size == metadata_buf.size() + CRC_BYTES_SIZE) { - if (finish_osf_file(file_name_, metadata_offset, metadata_saved_size) == + if (finish_osf_file(filename_, metadata_offset, metadata_saved_size) == header_size_) { finished_ = true; } else { diff --git a/ouster_osf/tests/CMakeLists.txt b/ouster_osf/tests/CMakeLists.txt index adfcef48..b9ea5898 100644 --- a/ouster_osf/tests/CMakeLists.txt +++ b/ouster_osf/tests/CMakeLists.txt @@ -1,6 +1,21 @@ -cmake_minimum_required(VERSION 3.1.0) +if (NOT (DEFINED OUSTER_TOP_LEVEL AND OUSTER_TOP_LEVEL)) + message("Tests running standalone") + get_filename_component(OUSTER_CMAKE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake" ABSOLUTE) + set(CMAKE_PREFIX_PATH "${OUSTER_CMAKE_PATH};${CMAKE_PREFIX_PATH}") + include(${OUSTER_CMAKE_PATH}/VcpkgEnv.cmake) + project(ouster_osf_tests) + find_package(OusterSDK REQUIRED) + include(CTest) + find_package(GTest CONFIG REQUIRED) + cmake_minimum_required(VERSION 3.10...3.22) + set(LIB_TO_USE OusterSDK::ouster_osf OusterSDK::ouster_client OusterSDK::ouster_pcap) +else() + message("Tests running from root") + find_package(GTest CONFIG REQUIRED) + set(LIB_TO_USE OusterSDK::ouster_osf OusterSDK::ouster_client OusterSDK::ouster_pcap) +endif() +message("Using Libraries: ${LIB_TO_USE}") -find_package(GTest REQUIRED) find_package(OpenSSL REQUIRED) # Each test file should be in a format "_test.cpp" @@ -26,9 +41,15 @@ foreach(TEST_FULL_NAME ${OSF_TESTS_SOURCES}) add_executable(osf_${TEST_FILENAME} ${TEST_FULL_NAME}) set_target_properties(osf_${TEST_FILENAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests") + if (OUSTER_OSF_NO_THREADING) + target_compile_definitions(osf_${TEST_FILENAME} PRIVATE OUSTER_OSF_NO_THREADING) + endif() - target_include_directories(osf_${TEST_FILENAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../src) - target_link_libraries(osf_${TEST_FILENAME} PRIVATE ouster_osf + target_include_directories(osf_${TEST_FILENAME} PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../src + $) + target_link_libraries(osf_${TEST_FILENAME} PRIVATE + ${LIB_TO_USE} GTest::gtest GTest::gtest_main OpenSSL::Crypto) diff --git a/ouster_osf/tests/operations_test.cpp b/ouster_osf/tests/operations_test.cpp index 7f601965..5c79e3d5 100644 --- a/ouster_osf/tests/operations_test.cpp +++ b/ouster_osf/tests/operations_test.cpp @@ -10,12 +10,15 @@ #include #include +#include #include +#include +#include +#include +#include #include #include "fb_utils.h" -#include "json/json.h" -#include "json_utils.h" #include "osf_test.h" #include "ouster/osf/basics.h" #include "ouster/osf/crc32.h" @@ -113,23 +116,29 @@ TEST_F(OperationsTest, GetOsfDumpInfo) { path_concat(test_data_dir(), "osfs/OS-1-128_v2.3.0_1024x10_lb_n3.osf"), true); - Json::Value osf_info_obj{}; + jsoncons::json osf_info_obj; - EXPECT_TRUE(parse_json(osf_info_str, osf_info_obj)); - - ASSERT_TRUE(osf_info_obj.isMember("header")); - EXPECT_TRUE(osf_info_obj["header"].isMember("status")); - EXPECT_TRUE(osf_info_obj["header"].isMember("version")); - EXPECT_TRUE(osf_info_obj["header"].isMember("size")); - EXPECT_TRUE(osf_info_obj["header"].isMember("metadata_offset")); - EXPECT_TRUE(osf_info_obj["header"].isMember("chunks_offset")); - - ASSERT_TRUE(osf_info_obj.isMember("metadata")); - EXPECT_TRUE(osf_info_obj["metadata"].isMember("id")); - EXPECT_EQ("ouster_sdk", osf_info_obj["metadata"]["id"].asString()); - EXPECT_TRUE(osf_info_obj["metadata"].isMember("start_ts")); - EXPECT_TRUE(osf_info_obj["metadata"].isMember("end_ts")); - EXPECT_TRUE(osf_info_obj["metadata"].isMember("entries")); + bool failure = false; + try { + osf_info_obj = jsoncons::json::parse(osf_info_str); + } catch (const jsoncons::ser_error&) { + failure = true; + } + EXPECT_FALSE(failure); + + ASSERT_TRUE(osf_info_obj.contains("header")); + EXPECT_TRUE(osf_info_obj["header"].contains("status")); + EXPECT_TRUE(osf_info_obj["header"].contains("version")); + EXPECT_TRUE(osf_info_obj["header"].contains("size")); + EXPECT_TRUE(osf_info_obj["header"].contains("metadata_offset")); + EXPECT_TRUE(osf_info_obj["header"].contains("chunks_offset")); + + ASSERT_TRUE(osf_info_obj.contains("metadata")); + EXPECT_TRUE(osf_info_obj["metadata"].contains("id")); + EXPECT_EQ("ouster_sdk", osf_info_obj["metadata"]["id"].as()); + EXPECT_TRUE(osf_info_obj["metadata"].contains("start_ts")); + EXPECT_TRUE(osf_info_obj["metadata"].contains("end_ts")); + EXPECT_TRUE(osf_info_obj["metadata"].contains("entries")); EXPECT_EQ(3, osf_info_obj["metadata"]["entries"].size()); } @@ -195,17 +204,19 @@ TEST_F(OperationsTest, BackupMetadataTest) { remove_dir(temp_dir); } -bool _parse_json(const std::string& json, Json::Value& root) { - Json::CharReaderBuilder build; - JSONCPP_STRING error; - const std::unique_ptr read(build.newCharReader()); - return read->parse(json.c_str(), (json.c_str() + json.length()), &root, - &error); +bool _parse_json(const std::string& json, jsoncons::json& root) { + bool failure = false; + try { + root = jsoncons::json::parse(json); + } catch (const jsoncons::ser_error&) { + failure = true; + } + return !failure; } ouster::sensor::sensor_info _gen_new_metadata(int start_number) { ouster::sensor::sensor_info new_metadata; - new_metadata.sn = "DEADBEEF"; + new_metadata.sn = 123456; new_metadata.fw_rev = "sqrt(-1) friends"; new_metadata.config.lidar_mode = ouster::sensor::MODE_512x10; new_metadata.prod_line = "OS-1-128"; @@ -213,9 +224,6 @@ ouster::sensor::sensor_info _gen_new_metadata(int start_number) { new_metadata.format.pixels_per_column = 128; new_metadata.format.columns_per_packet = 2 + start_number; new_metadata.format.columns_per_frame = 3 + start_number; - new_metadata.format.pixel_shift_by_row = { - 4 + start_number, 5 + start_number, 6 + start_number, 7 + start_number, - 8 + start_number}; new_metadata.format.column_window = {9 + start_number, 10 + start_number}; new_metadata.format.udp_profile_lidar = ouster::sensor::PROFILE_RNG15_RFL8_NIR8; @@ -235,15 +243,28 @@ ouster::sensor::sensor_info _gen_new_metadata(int start_number) { new_metadata.config.udp_port_lidar = 24 + start_number; new_metadata.config.udp_port_imu = 25 + start_number; - new_metadata.build_date = "Made in SAN FRANCISCO"; + new_metadata.build_date = "2023-02-03T21:45:40Z"; new_metadata.image_rev = "IDK, ask someone else"; new_metadata.prod_pn = "import random; print(random.random())"; new_metadata.status = "Not just good but great"; + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + double value = 0.0; + if (i == j) { + value = 1.0; + } + new_metadata.extrinsic(i, j) = value; + } + } + + for (size_t i = 0; i < new_metadata.format.pixels_per_column; i++) { + new_metadata.format.pixel_shift_by_row.push_back(i + start_number); + } return new_metadata; } -void _verify_empty_metadata(Json::Value& test_root, int entry_count = 0) { +void _verify_empty_metadata(jsoncons::json& test_root, int entry_count = 0) { EXPECT_EQ(test_root["metadata"]["chunks"].size(), 0); EXPECT_EQ(test_root["metadata"]["entries"].size(), entry_count); EXPECT_EQ(test_root["metadata"]["end_ts"], 0); @@ -289,7 +310,7 @@ TEST_F(OperationsTest, MetadataRewriteTestSimple) { _write_init_metadata(temp_file, header_size); std::string metadata_json = dump_metadata(temp_file, true); - Json::Value test_root{}; + jsoncons::json test_root{}; EXPECT_TRUE(_parse_json(metadata_json, test_root)); _verify_empty_metadata(test_root); @@ -298,13 +319,12 @@ TEST_F(OperationsTest, MetadataRewriteTestSimple) { osf_file_modify_metadata(temp_file, {new_metadata}); std::string output_metadata_json = dump_metadata(temp_file, true); - Json::Value output_root{}; + jsoncons::json output_root{}; EXPECT_TRUE(_parse_json(output_metadata_json, output_root)); EXPECT_NE(test_root, output_root); - Json::Value new_root{}; + jsoncons::json new_root{}; EXPECT_TRUE(_parse_json(new_metadata.to_json_string(), new_root)); - EXPECT_EQ(new_root, output_root["metadata"]["entries"][0]["buffer"]["sensor_info"]); unlink_path(temp_file); @@ -319,7 +339,7 @@ TEST_F(OperationsTest, MetadataRewriteTestMulti) { _write_init_metadata(temp_file, header_size); std::string metadata_json = dump_metadata(temp_file, true); - Json::Value test_root{}; + jsoncons::json test_root{}; EXPECT_TRUE(_parse_json(metadata_json, test_root)); _verify_empty_metadata(test_root); @@ -329,13 +349,13 @@ TEST_F(OperationsTest, MetadataRewriteTestMulti) { osf_file_modify_metadata(temp_file, {new_metadata, new_metadata2}); std::string output_metadata_json = dump_metadata(temp_file, true); - Json::Value output_root{}; + jsoncons::json output_root{}; EXPECT_TRUE(_parse_json(output_metadata_json, output_root)); EXPECT_NE(test_root, output_root); - Json::Value new_root{}; + jsoncons::json new_root{}; EXPECT_TRUE(_parse_json(new_metadata.to_json_string(), new_root)); - Json::Value new_root2{}; + jsoncons::json new_root2{}; auto temp_string = new_metadata2.to_json_string(); EXPECT_TRUE(_parse_json(temp_string, new_root2)); @@ -357,7 +377,7 @@ TEST_F(OperationsTest, MetadataRewriteTestPreExisting) { _write_init_metadata(temp_file, header_size, meta_store_); std::string metadata_json = dump_metadata(temp_file, true); - Json::Value test_root{}; + jsoncons::json test_root{}; EXPECT_TRUE(_parse_json(metadata_json, test_root)); _verify_empty_metadata(test_root, 1); @@ -371,11 +391,11 @@ TEST_F(OperationsTest, MetadataRewriteTestPreExisting) { osf_file_modify_metadata(temp_file, {new_metadata}); std::string output_metadata_json = dump_metadata(temp_file, true); - Json::Value output_root{}; + jsoncons::json output_root{}; EXPECT_TRUE(_parse_json(output_metadata_json, output_root)); EXPECT_NE(test_root, output_root); - Json::Value new_root{}; + jsoncons::json new_root{}; EXPECT_TRUE(_parse_json(new_metadata.to_json_string(), new_root)); EXPECT_EQ(output_root["metadata"]["entries"][0]["buffer"], diff --git a/ouster_osf/tests/png_tools_test.cpp b/ouster_osf/tests/png_tools_test.cpp index ab81c7f5..97c634a1 100644 --- a/ouster_osf/tests/png_tools_test.cpp +++ b/ouster_osf/tests/png_tools_test.cpp @@ -7,8 +7,6 @@ #include #include -#include -#include #include #include @@ -22,6 +20,7 @@ #include "ouster/impl/logging.h" #include "ouster/lidar_scan.h" #include "ouster/osf/basics.h" +#include "ouster/osf/png_lidarscan_encoder.h" #include "ouster/types.h" namespace ouster { @@ -75,287 +74,49 @@ TEST_F(OsfPngToolsTest, MakesLidarScan) { EXPECT_EQ(ls.status().size(), si.format.columns_per_frame); } -#define ENCODE_IMAGE_TEST(TEST_NAME, ENCODE_FUNC, DECODE_FUNC) \ - template \ - struct TEST_NAME { \ - template \ - bool to(const LidarScan& ls, const std::vector& px_offset, \ - Ti mask_bits = 0) { \ - ScanChannelData encoded_channel; \ - img_t key_orig(ls.h, ls.w); \ - key_orig = key_orig.unaryExpr([=](Ti) { \ - double sr = static_cast(std::rand()) / RAND_MAX; \ - return static_cast( \ - sr * static_cast(std::numeric_limits::max())); \ - }); \ - if (mask_bits && sizeof(Ti) * 8 > mask_bits) { \ - key_orig = key_orig.unaryExpr([=](Ti a) { \ - return static_cast(a & ((1LL << mask_bits) - 1)); \ - }); \ - } \ - bool res_enc = \ - ENCODE_FUNC(encoded_channel, key_orig, px_offset); \ - EXPECT_FALSE(res_enc); \ - std::cout << #ENCODE_FUNC \ - << ": encoded bytes = " << encoded_channel.size() \ - << " ================= " << std::endl; \ - EXPECT_TRUE(!encoded_channel.empty()); \ - img_t decoded_img{ls.h, ls.w}; \ - bool res_dec = \ - DECODE_FUNC(decoded_img, encoded_channel, px_offset); \ - EXPECT_FALSE(res_dec); \ - bool round_trip = (key_orig.template cast() == \ - decoded_img.template cast()) \ - .all(); \ - auto round_trip_cnt = (key_orig.template cast() == \ - decoded_img.template cast()) \ - .count(); \ - std::cout << "cnt = " << round_trip_cnt << std::endl; \ - return round_trip; \ - } \ - }; +#ifndef OUSTER_OSF_NO_THREADING -ENCODE_IMAGE_TEST(test8bitImageCoders, encode8bitImage, decode8bitImage) -ENCODE_IMAGE_TEST(test16bitImageCoders, encode16bitImage, decode16bitImage) -ENCODE_IMAGE_TEST(test24bitImageCoders, encode24bitImage, decode24bitImage) -ENCODE_IMAGE_TEST(test32bitImageCoders, encode32bitImage, decode32bitImage) -ENCODE_IMAGE_TEST(test64bitImageCoders, encode64bitImage, decode64bitImage) +TEST_F(OsfPngToolsTest, scanDecodeFields) { + // it should propagate the exception + // if destagger throws std::invalid_argument -// Check encodeXXbitImage functions on RANGE fields of LidarScan -// converted to various img_t of different unsigned int sizes. -TEST_F(OsfPngToolsTest, ImageCoders) { + // create a writer with a sensor info from DATA_DIR const sensor_info si = sensor::metadata_from_json( path_concat(test_data_dir(), "pcaps/OS-1-128_v2.3.0_1024x10.json")); - LidarScan ls = get_random_lidar_scan(si); - auto px_offset = si.format.pixel_shift_by_row; - - // ======== 8bit ========== - - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - EXPECT_TRUE(test8bitImageCoders().to(ls, px_offset, 8)); - - // ======== 16bit ====== - // clang-format off - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - - EXPECT_FALSE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - - EXPECT_FALSE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - - EXPECT_FALSE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - EXPECT_TRUE(test16bitImageCoders().to(ls, px_offset, 16)); - // clang-format on - - // ======== 24bit ====== - - EXPECT_TRUE(test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - - EXPECT_FALSE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - - EXPECT_FALSE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_FALSE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - - EXPECT_FALSE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_FALSE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - EXPECT_TRUE( - test24bitImageCoders().to(ls, px_offset, 24)); - - // ======== 32bit ====== - - EXPECT_TRUE(test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_FALSE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); + // assert precondition + ASSERT_EQ(si.format.pixel_shift_by_row.size(), 128); + std::string output_osf_filename = tmp_file("scan_decode_fields_test.osf"); - EXPECT_FALSE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_FALSE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - - EXPECT_FALSE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_FALSE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - EXPECT_TRUE( - test32bitImageCoders().to(ls, px_offset, 32)); - - // ======== 64bit ====== - - EXPECT_TRUE(test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); - - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_FALSE( - test64bitImageCoders().to(ls, px_offset, 64)); - EXPECT_TRUE( - test64bitImageCoders().to(ls, px_offset, 64)); -} - -TEST_F(OsfPngToolsTest, InternalsTest) { - // This is unused but is still required, test calling it - // Not expecting any returns - png_structp foo = - png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - png_osf_flush_data(foo); - - bool error_caught = false; - std::stringstream output_stream; - auto ostream_sink = std::make_shared< - spdlog::sinks::ostream_sink>( - output_stream); - ouster::sensor::impl::Logger::instance().configure_generic_sink( - ostream_sink, "info"); - - if (setjmp(png_jmpbuf(foo))) { - error_caught = true; - } else { - png_osf_error( - foo, - "Also Checkout Porcupine Tree - Arriving Somewhere But Not Here"); - } - - std::string output_error = output_stream.str(); - auto error_loc = output_error.find("[error]"); - EXPECT_NE(error_loc, std::string::npos); - output_error = output_error.substr(error_loc); - EXPECT_TRUE(error_caught); -#ifdef _WIN32 - EXPECT_EQ(output_error, - "[error] ERROR libpng osf: Also Checkout Porcupine Tree" - " - Arriving Somewhere But Not Here\r\n"); -#else - EXPECT_EQ(output_error, - "[error] ERROR libpng osf: Also Checkout Porcupine Tree" - " - Arriving Somewhere But Not Here\n"); -#endif -} - -#ifndef OUSTER_OSF_NO_THREADING -TEST_F(OsfPngToolsTest, scanDecodeFields) { - // it should propagate the exception - // if destagger throws std::invalid_argument + // create a lidar scan that's the wrong size int w = 32; int h = 32; auto scan = ouster::LidarScan(w, h); auto field_types = scan.field_types(); - std::vector> fields; - for (const auto& field : field_types) { - fields.push_back({field.name, field.element_type}); - } std::vector shift_by_row; EXPECT_THROW( { try { - scanEncodeFields(scan, shift_by_row, fields); - } catch (const std::invalid_argument& e) { + Writer writer(output_osf_filename, si); + writer.save(0, scan); + writer.close(); + } catch (std::invalid_argument& e) { ASSERT_STREQ(e.what(), - "image height does not match shifts size"); + "lidar scan size (32, 32) does not match the " + "sensor info resolution (1024, 128)"); throw; } }, std::invalid_argument); } + #endif TEST(OsfFieldEncodeTest, field_encode_decode_test) { auto test_field_encoding = [](const ouster::Field& f) { ScanChannelData compressed; - EXPECT_NO_THROW({ compressed = encodeField(f); }); + PngLidarScanEncoder encoder(4); + EXPECT_NO_THROW({ compressed = encoder.encodeField(f); }); Field decoded(f.desc()); EXPECT_NO_THROW({ decodeField(decoded, compressed); }); EXPECT_EQ(f, decoded); diff --git a/ouster_osf/tests/reader_test.cpp b/ouster_osf/tests/reader_test.cpp index f280a975..98995627 100644 --- a/ouster_osf/tests/reader_test.cpp +++ b/ouster_osf/tests/reader_test.cpp @@ -6,8 +6,6 @@ #include "ouster/osf/reader.h" #include -#include -#include #include "common.h" #include "osf_test.h" @@ -35,130 +33,90 @@ TEST_F(ReaderTest, Basics) { // Get first sensor (it's the first by metadata_id) (i.e. first added) auto sensor = reader.meta_store().get(); EXPECT_TRUE(sensor); - EXPECT_EQ( - sensor->to_string(), - "{\n \"sensor_info\": \n {\n \"beam_intrinsics\": \n {\n " - "\"beam_altitude_angles\": \n [\n 20.95,\n 20.67,\n " - " 20.36,\n 20.03,\n 19.73,\n 19.41,\n " - " 19.11,\n 18.76,\n 18.47,\n 18.14,\n " - "17.82,\n 17.5,\n 17.19,\n 16.86,\n " - "16.53,\n 16.2,\n 15.89,\n 15.56,\n " - "15.23,\n 14.9,\n 14.57,\n 14.23,\n " - "13.9,\n 13.57,\n 13.25,\n 12.91,\n " - "12.57,\n 12.22,\n 11.9,\n 11.55,\n " - "11.2,\n 10.87,\n 10.54,\n 10.18,\n " - "9.84,\n 9.51,\n 9.15,\n 8.81,\n 8.47,\n " - " 8.11,\n 7.78,\n 7.43,\n 7.08,\n " - "6.74,\n 6.39,\n 6.04,\n 5.7,\n 5.34,\n " - " 4.98,\n 4.64,\n 4.29,\n 3.93,\n " - "3.58,\n 3.24,\n 2.88,\n 2.53,\n 2.17,\n " - " 1.82,\n 1.47,\n 1.12,\n 0.78,\n " - "0.41,\n 0.07,\n -0.28,\n -0.64,\n " - "-0.99,\n -1.35,\n -1.7,\n -2.07,\n " - "-2.4,\n -2.75,\n -3.11,\n -3.46,\n " - "-3.81,\n -4.15,\n -4.5,\n -4.86,\n " - "-5.22,\n -5.57,\n -5.9,\n -6.27,\n " - "-6.61,\n -6.97,\n -7.3,\n -7.67,\n " - "-8.01,\n -8.35,\n -8.69,\n -9.05,\n " - "-9.38,\n -9.71,\n -10.07,\n -10.42,\n " - "-10.76,\n -11.09,\n -11.43,\n -11.78,\n " - "-12.12,\n -12.46,\n -12.78,\n -13.15,\n " - "-13.46,\n -13.8,\n -14.12,\n -14.48,\n " - "-14.79,\n -15.11,\n -15.46,\n -15.79,\n " - "-16.12,\n -16.45,\n -16.76,\n -17.11,\n " - "-17.44,\n -17.74,\n -18.06,\n -18.39,\n " - "-18.72,\n -19.02,\n -19.32,\n -19.67,\n " - "-19.99,\n -20.27,\n -20.57,\n -20.92,\n " - "-21.22,\n -21.54,\n -21.82\n ],\n " - "\"beam_azimuth_angles\": \n [\n 4.21,\n 1.41,\n " - " -1.4,\n -4.22,\n 4.22,\n 1.41,\n " - "-1.4,\n -4.23,\n 4.21,\n 1.4,\n -1.42,\n " - " -4.2,\n 4.22,\n 1.41,\n -1.4,\n " - "-4.23,\n 4.21,\n 1.41,\n -1.41,\n " - "-4.21,\n 4.22,\n 1.4,\n -1.41,\n -4.2,\n " - " 4.22,\n 1.42,\n -1.4,\n -4.2,\n " - "4.22,\n 1.41,\n -1.42,\n -4.21,\n 4.22,\n " - " 1.41,\n -1.4,\n -4.21,\n 4.2,\n " - "1.4,\n -1.4,\n -4.22,\n 4.21,\n 1.41,\n " - " -1.41,\n -4.21,\n 4.22,\n 1.41,\n " - "-1.4,\n -4.21,\n 4.21,\n 1.41,\n -1.4,\n " - " -4.21,\n 4.2,\n 1.41,\n -1.4,\n " - "-4.21,\n 4.2,\n 1.4,\n -1.41,\n -4.21,\n " - " 4.22,\n 1.4,\n -1.4,\n -4.21,\n " - "4.22,\n 1.42,\n -1.4,\n -4.2,\n 4.2,\n " - " 1.42,\n -1.4,\n -4.22,\n 4.22,\n " - "1.41,\n -1.4,\n -4.2,\n 4.23,\n 1.41,\n " - " -1.4,\n -4.2,\n 4.21,\n 1.41,\n " - "-1.4,\n -4.21,\n 4.21,\n 1.41,\n -1.4,\n " - " -4.21,\n 4.22,\n 1.41,\n -1.39,\n " - "-4.21,\n 4.23,\n 1.41,\n -1.39,\n " - "-4.22,\n 4.23,\n 1.4,\n -1.4,\n -4.2,\n " - " 4.21,\n 1.41,\n -1.41,\n -4.2,\n " - "4.22,\n 1.42,\n -1.39,\n -4.22,\n 4.24,\n " - " 1.41,\n -1.41,\n -4.22,\n 4.23,\n " - "1.41,\n -1.39,\n -4.21,\n 4.23,\n 1.41,\n " - " -1.39,\n -4.2,\n 4.23,\n 1.4,\n " - "-1.39,\n -4.2,\n 4.22,\n 1.42,\n -1.39,\n " - " -4.2\n ],\n \"beam_to_lidar_transform\": \n [\n " - " 1.0,\n 0.0,\n 0.0,\n 15.806,\n " - "0.0,\n 1.0,\n 0.0,\n 0.0,\n 0.0,\n " - "0.0,\n 1.0,\n 0.0,\n 0.0,\n 0.0,\n " - "0.0,\n 1.0\n ],\n " - "\"lidar_origin_to_beam_origin_mm\": 15.806\n },\n " - "\"calibration_status\": {},\n \"config_params\": \n {\n " - "\"lidar_mode\": \"1024x10\",\n \"udp_port_imu\": 7503,\n " - "\"udp_port_lidar\": 7502\n },\n \"imu_intrinsics\": \n {\n " - " \"imu_to_sensor_transform\": \n [\n 1.0,\n " - "0.0,\n 0.0,\n 6.253,\n 0.0,\n 1.0,\n " - " 0.0,\n -11.775,\n 0.0,\n 0.0,\n 1.0,\n " - " 7.645,\n 0.0,\n 0.0,\n 0.0,\n 1.0\n " - " ]\n },\n \"lidar_data_format\": \n {\n " - "\"column_window\": \n [\n 0,\n 1023\n ],\n " - " \"columns_per_frame\": 1024,\n \"columns_per_packet\": 16,\n " - " \"fps\": 10,\n \"pixel_shift_by_row\": \n [\n " - "24,\n 16,\n 8,\n 0,\n 24,\n 16,\n " - " 8,\n 0,\n 24,\n 16,\n 8,\n " - "0,\n 24,\n 16,\n 8,\n 0,\n 24,\n " - " 16,\n 8,\n 0,\n 24,\n 16,\n " - "8,\n 0,\n 24,\n 16,\n 8,\n 0,\n " - " 24,\n 16,\n 8,\n 0,\n 24,\n " - "16,\n 8,\n 0,\n 24,\n 16,\n 8,\n " - " 0,\n 24,\n 16,\n 8,\n 0,\n " - "24,\n 16,\n 8,\n 0,\n 24,\n 16,\n " - " 8,\n 0,\n 24,\n 16,\n 8,\n " - "0,\n 24,\n 16,\n 8,\n 0,\n 24,\n " - " 16,\n 8,\n 0,\n 24,\n 16,\n " - "8,\n 0,\n 24,\n 16,\n 8,\n 0,\n " - " 24,\n 16,\n 8,\n 0,\n 24,\n " - "16,\n 8,\n 0,\n 24,\n 16,\n 8,\n " - " 0,\n 24,\n 16,\n 8,\n 0,\n " - "24,\n 16,\n 8,\n 0,\n 24,\n 16,\n " - " 8,\n 0,\n 24,\n 16,\n 8,\n " - "0,\n 24,\n 16,\n 8,\n 0,\n 24,\n " - " 16,\n 8,\n 0,\n 24,\n 16,\n " - "8,\n 0,\n 24,\n 16,\n 8,\n 0,\n " - " 24,\n 16,\n 8,\n 0,\n 24,\n " - "16,\n 8,\n 0,\n 24,\n 16,\n 8,\n " - " 0\n ],\n \"pixels_per_column\": 128,\n " - "\"udp_profile_imu\": \"LEGACY\",\n \"udp_profile_lidar\": " - "\"RNG15_RFL8_NIR8\"\n },\n \"lidar_intrinsics\": \n {\n " - "\"lidar_to_sensor_transform\": \n [\n -1.0,\n " - "0.0,\n 0.0,\n 0.0,\n 0.0,\n -1.0,\n " - " 0.0,\n 0.0,\n 0.0,\n 0.0,\n 1.0,\n " - " 36.18,\n 0.0,\n 0.0,\n 0.0,\n 1.0\n " - "]\n },\n \"ouster-sdk\": \n {\n \"client_version\": " - "\"ouster_client 0.13.0\",\n \"extrinsic\": \n [\n " - "1.0,\n 0.0,\n 0.0,\n 0.0,\n 0.0,\n " - "1.0,\n 0.0,\n 0.0,\n 0.0,\n 0.0,\n " - "1.0,\n 0.0,\n 0.0,\n 0.0,\n 0.0,\n " - "1.0\n ],\n \"output_source\": \"sensor_info_to_string\"\n " - " },\n \"sensor_info\": \n {\n \"build_date\": " - "\"2022-04-14T21:11:47Z\",\n \"build_rev\": \"v2.3.0\",\n " - "\"image_rev\": \"ousteros-image-prod-aries-v2.3.0+20220415163956\",\n " - " \"initialization_id\": 7109750,\n \"prod_line\": " - "\"OS-1-128\",\n \"prod_pn\": \"840-103575-06\",\n " - "\"prod_sn\": \"122201000998\",\n \"status\": \"RUNNING\"\n " - "},\n \"user_data\": \"\"\n }\n}"); + std::stringstream actual; + actual + << "{\"sensor_info\":{\"beam_intrinsics\":{\"beam_altitude_angles\":[" + "20." + "95,20.67,20.36,20.03,19.73,19.41,19.11,18.76,18.47,18.14,17.82,17." + "5," + "17.19,16.86,16.53,16.2,15.89,15.56,15.23,14.9,14.57,14.23,13.9,13." + "57," + "13.25,12.91,12.57,12.22,11.9,11.55,11.2,10.87,10.54,10.18,9.84,9." + "51,9." + "15,8.81,8.47,8.11,7.78,7.43,7.08,6.74,6.39,6.04,5.7,5.34,4.98,4.64," + "4." + "29,3.93,3.58,3.24,2.88,2.53,2.17,1.82,1.47,1.12,0.78,0.41,0.07,-0." + "28,-" + "0.64,-0.99,-1.35,-1.7,-2.07,-2.4,-2.75,-3.11,-3.46,-3.81,-4.15,-4." + "5,-" + "4.86,-5.22,-5.57,-5.9,-6.27,-6.61,-6.97,-7.3,-7.67,-8.01,-8.35,-8." + "69,-" + "9.05,-9.38,-9.71,-10.07,-10.42,-10.76,-11.09,-11.43,-11.78,-12.12,-" + "12." + "46,-12.78,-13.15,-13.46,-13.8,-14.12,-14.48,-14.79,-15.11,-15.46,-" + "15." + "79,-16.12,-16.45,-16.76,-17.11,-17.44,-17.74,-18.06,-18.39,-18.72,-" + "19." + "02,-19.32,-19.67,-19.99,-20.27,-20.57,-20.92,-21.22,-21.54,-21.82]," + "\"beam_azimuth_angles\":[4.21,1.41,-1.4,-4.22,4.22,1.41,-1.4,-4.23," + "4." + "21,1.4,-1.42,-4.2,4.22,1.41,-1.4,-4.23,4.21,1.41,-1.41,-4.21,4.22," + "1.4," + "-1.41,-4.2,4.22,1.42,-1.4,-4.2,4.22,1.41,-1.42,-4.21,4.22,1.41,-1." + "4,-" + "4.21,4.2,1.4,-1.4,-4.22,4.21,1.41,-1.41,-4.21,4.22,1.41,-1.4,-4.21," + "4." + "21,1.41,-1.4,-4.21,4.2,1.41,-1.4,-4.21,4.2,1.4,-1.41,-4.21,4.22,1." + "4,-" + "1.4,-4.21,4.22,1.42,-1.4,-4.2,4.2,1.42,-1.4,-4.22,4.22,1.41,-1.4,-" + "4.2," + "4.23,1.41,-1.4,-4.2,4.21,1.41,-1.4,-4.21,4.21,1.41,-1.4,-4.21,4.22," + "1." + "41,-1.39,-4.21,4.23,1.41,-1.39,-4.22,4.23,1.4,-1.4,-4.2,4.21,1.41,-" + "1." + "41,-4.2,4.22,1.42,-1.39,-4.22,4.24,1.41,-1.41,-4.22,4.23,1.41,-1." + "39,-" + "4.21,4.23,1.41,-1.39,-4.2,4.23,1.4,-1.39,-4.2,4.22,1.42,-1.39,-4.2]" + "," + "\"beam_to_lidar_transform\":[1.0,0.0,0.0,15.806,0.0,1.0,0.0,0.0,0." + "0,0." + "0,1.0,0.0,0.0,0.0,0.0,1.0],\"lidar_origin_to_beam_origin_mm\":15." + "806}," + "\"calibration_status\":{},\"config_params\":{\"lidar_mode\":" + "\"1024x10\",\"udp_port_imu\":7503,\"udp_port_lidar\":7502},\"imu_" + "intrinsics\":{\"imu_to_sensor_transform\":[1.0,0.0,0.0,6.253,0.0,1." + "0," + "0.0,-11.775,0.0,0.0,1.0,7.645,0.0,0.0,0.0,1.0]},\"lidar_data_" + "format\":" + "{\"column_window\":[0,1023],\"columns_per_frame\":1024,\"columns_" + "per_" + "packet\":16,\"fps\":10,\"pixel_shift_by_row\":[24,16,8,0,24,16,8,0," + "24," + "16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0," + "24," + "16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0," + "24," + "16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0," + "24," + "16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0,24,16,8,0," + "24," + "16,8,0,24,16,8,0],\"pixels_per_column\":128,\"udp_profile_imu\":" + "\"LEGACY\",\"udp_profile_lidar\":\"RNG15_RFL8_NIR8\"},\"lidar_" + "intrinsics\":{\"lidar_to_sensor_transform\":[-1.0,0.0,0.0,0.0,0.0,-" + "1." + "0,0.0,0.0,0.0,0.0,1.0,36.18,0.0,0.0,0.0,1.0]},\"ouster-sdk\":{" + "\"client_version\":\""; + actual << ouster::sensor::client_version(); + actual + << "\",\"extrinsic\":[1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0." + "0,0.0,0.0,0.0,1.0],\"output_source\":\"sensor_info_to_string\"}," + "\"sensor_info\":{\"build_date\":\"2022-04-14T21:11:47Z\",\"build_" + "rev\":\"v2.3.0\",\"image_rev\":\"ousteros-image-prod-aries-v2.3.0+" + "20220415163956\",\"initialization_id\":7109750,\"prod_line\":\"OS-" + "1-" + "128\",\"prod_pn\":\"840-103575-06\",\"prod_sn\":\"122201000998\"," + "\"status\":\"RUNNING\"},\"user_data\":\"\"}}"; + EXPECT_EQ(sensor->to_string(), actual.str()); EXPECT_EQ(1, reader.meta_store().count()); EXPECT_EQ( @@ -319,42 +277,44 @@ TEST_F(ReaderTest, MessagesReadingStreaming) { EXPECT_EQ(3, std::distance(scan_msgs_full.begin(), scan_msgs_full.end())); } -TEST_F(ReaderTest, MetadataFromBufferTest) { - OsfFile osf_file( - path_concat(test_data_dir(), "osfs/OS-1-128_v2.3.0_1024x10_lb_n3.osf")); - - Reader reader(osf_file); - - auto sensor = reader.meta_store().entries().begin()->second; - - std::vector buf; - std::stringstream output_stream; - auto ostream_sink = std::make_shared< - spdlog::sinks::ostream_sink>( - output_stream); - ouster::sensor::impl::Logger::instance().configure_generic_sink( - ostream_sink, "info"); - - auto result = sensor->from_buffer(buf, "NON EXISTENT"); - - std::string output_error = output_stream.str(); - auto error_loc = output_error.find("[error]"); - EXPECT_NE(error_loc, std::string::npos); - - output_error = output_error.substr(error_loc); -#ifdef _WIN32 - EXPECT_EQ(output_error, "[error] UNKNOWN TYPE: NON EXISTENT\r\n"); -#else - EXPECT_EQ(output_error, "[error] UNKNOWN TYPE: NON EXISTENT\n"); -#endif - EXPECT_EQ(result, nullptr); - - result = sensor->from_buffer(sensor->buffer(), - "ouster/v1/os_sensor/LidarSensor"); - EXPECT_NE(result, nullptr); - EXPECT_EQ(result->id(), 0); - EXPECT_EQ(result->type(), "ouster/v1/os_sensor/LidarSensor"); -} +// @TODO Reeanble this test when we have generic spdlog functonality. +// TEST_F(ReaderTest, MetadataFromBufferTest) { +// OsfFile osf_file( +// path_concat(test_data_dir(), +// "osfs/OS-1-128_v2.3.0_1024x10_lb_n3.osf")); + +// Reader reader(osf_file); + +// auto sensor = reader.meta_store().entries().begin()->second; + +// std::vector buf; +// std::stringstream output_stream; +// auto ostream_sink = std::make_shared< +// spdlog::sinks::ostream_sink>( +// output_stream); +// ouster::sensor::impl::Logger::instance().configure_generic_sink( +// ostream_sink, "info"); + +// auto result = sensor->from_buffer(buf, "NON EXISTENT"); + +// std::string output_error = output_stream.str(); +// auto error_loc = output_error.find("[error]"); +// EXPECT_NE(error_loc, std::string::npos); + +// output_error = output_error.substr(error_loc); +// #ifdef _WIN32 +// EXPECT_EQ(output_error, "[error] UNKNOWN TYPE: NON EXISTENT\r\n"); +// #else +// EXPECT_EQ(output_error, "[error] UNKNOWN TYPE: NON EXISTENT\n"); +// #endif +// EXPECT_EQ(result, nullptr); + +// result = sensor->from_buffer(sensor->buffer(), +// "ouster/v1/os_sensor/LidarSensor"); +// EXPECT_NE(result, nullptr); +// EXPECT_EQ(result->id(), 0); +// EXPECT_EQ(result->type(), "ouster/v1/os_sensor/LidarSensor"); +// } } // namespace } // namespace osf diff --git a/ouster_osf/tests/vcpkg.json b/ouster_osf/tests/vcpkg.json new file mode 100644 index 00000000..1230161b --- /dev/null +++ b/ouster_osf/tests/vcpkg.json @@ -0,0 +1,5 @@ +{ + "name": "ouster-sdk-tests", + "dependencies": [ "eigen3", "gtest" ], + "builtin-baseline": "943c5ef1c8f6b5e6ced092b242c8299caae2ff01" +} diff --git a/ouster_pcap/CMakeLists.txt b/ouster_pcap/CMakeLists.txt index f88296f9..b93a7cef 100644 --- a/ouster_pcap/CMakeLists.txt +++ b/ouster_pcap/CMakeLists.txt @@ -5,28 +5,38 @@ find_package(libtins REQUIRED) include(Coverage) # ==== Libraries ==== -add_library(ouster_pcap src/pcap.cpp src/os_pcap.cpp src/indexed_pcap_reader.cpp src/ip_reassembler.cpp) +add_library(ouster_pcap STATIC src/pcap.cpp src/os_pcap.cpp src/indexed_pcap_reader.cpp src/ip_reassembler.cpp) target_include_directories(ouster_pcap SYSTEM PRIVATE ${PCAP_INCLUDE_DIR}) target_include_directories(ouster_pcap PUBLIC $ $) +set_property(TARGET ouster_pcap PROPERTY POSITION_INDEPENDENT_CODE ON) +if(BUILD_SHARED_LIBRARY) + set_target_properties(ouster_pcap PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() CodeCoverageFunctionality(ouster_pcap) +if(BUILD_SHARED_LIBRARY) + target_compile_definitions(ouster_pcap PRIVATE BUILD_SHARED_LIBS_EXPORT) +endif() + if(WIN32) target_link_libraries(ouster_pcap PUBLIC ws2_32) endif() target_link_libraries(ouster_pcap PUBLIC OusterSDK::ouster_client - PRIVATE libpcap::libpcap libtins::libtins) + $ + $ + PRIVATE + $ + $) add_library(OusterSDK::ouster_pcap ALIAS ouster_pcap) # ==== Install ==== install(TARGETS ouster_pcap EXPORT ouster-sdk-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include) diff --git a/ouster_pcap/include/ouster/indexed_pcap_reader.h b/ouster_pcap/include/ouster/indexed_pcap_reader.h index 9bc2386b..eb961afd 100644 --- a/ouster_pcap/include/ouster/indexed_pcap_reader.h +++ b/ouster_pcap/include/ouster/indexed_pcap_reader.h @@ -10,11 +10,12 @@ #include "ouster/os_pcap.h" #include "ouster/pcap.h" #include "ouster/types.h" +#include "ouster/visibility.h" namespace ouster { namespace sensor_utils { -class PcapIndex { +class OUSTER_API_CLASS PcapIndex { public: using frame_index = std::vector; ///< Maps a frame number to a file offset @@ -33,6 +34,7 @@ class PcapIndex { // over 50 mins. std::vector frame_id_indices_; + OUSTER_API_FUNCTION PcapIndex(size_t num_sensors) : frame_indices_(num_sensors), frame_timestamp_indices_(num_sensors), @@ -41,6 +43,7 @@ class PcapIndex { /** * Simple method to clear the index. */ + OUSTER_API_FUNCTION void clear(); /** @@ -51,6 +54,7 @@ class PcapIndex { * the desired frame count. * @return The number of frames in the sensor's frame index. */ + OUSTER_API_FUNCTION size_t frame_count(size_t sensor_index) const; // TODO[UN]: in my opinion we are better off removing this method from this @@ -68,6 +72,7 @@ class PcapIndex { * seek for. * @param[in] frame_number The frame number to seek to. */ + OUSTER_API_FUNCTION void seek_to_frame(PcapReader& reader, size_t sensor_index, unsigned int frame_number); }; @@ -78,12 +83,13 @@ class PcapIndex { * The index must be computed by iterating through all packets and calling * `update_index_for_current_packet()` for each one. */ -class IndexedPcapReader : public PcapReader { +class OUSTER_API_CLASS IndexedPcapReader : public PcapReader { public: /** * @param[in] pcap_filename A file path of the pcap to read * @param[in] metadata_filenames A vector of sensor metadata filepaths */ + OUSTER_API_FUNCTION IndexedPcapReader( const std::string& pcap_filename, ///< [in] - A file path of the pcap to read @@ -97,6 +103,7 @@ class IndexedPcapReader : public PcapReader { * @param[in] sensor_infos A vector of sensor info structures for each * sensors */ + OUSTER_API_FUNCTION IndexedPcapReader( const std::string& pcap_filename, const std::vector& sensor_infos); @@ -105,6 +112,7 @@ class IndexedPcapReader : public PcapReader { * This method constructs the index. Call this method before requesting the * index information using get_index() */ + OUSTER_API_FUNCTION void build_index(); /** @@ -112,6 +120,7 @@ class IndexedPcapReader : public PcapReader { * * @return returns a PcapIndex object */ + OUSTER_API_FUNCTION const PcapIndex& get_index() const; /** @@ -120,6 +129,7 @@ class IndexedPcapReader : public PcapReader { * * @return An optional packet format for the current packet */ + OUSTER_API_FUNCTION nonstd::optional sensor_idx_for_current_packet() const; /** @@ -127,6 +137,7 @@ class IndexedPcapReader : public PcapReader { * if the packet is associated with a sensor (and its corresponding packet * format) */ + OUSTER_API_FUNCTION nonstd::optional current_frame_id() const; /** @@ -139,6 +150,7 @@ class IndexedPcapReader : public PcapReader { * * @return the progress of indexing as an int from [0, 100] */ + OUSTER_API_FUNCTION int update_index_for_current_packet(); /** @@ -150,6 +162,7 @@ class IndexedPcapReader : public PcapReader { * @param[in] current The current frame id. * @return true if the frame id has rolled over. */ + OUSTER_API_FUNCTION static bool frame_id_rolled_over(uint16_t previous, uint16_t current); protected: diff --git a/ouster_pcap/include/ouster/os_pcap.h b/ouster_pcap/include/ouster/os_pcap.h index f4f57a6f..87695478 100644 --- a/ouster_pcap/include/ouster/os_pcap.h +++ b/ouster_pcap/include/ouster/os_pcap.h @@ -19,24 +19,28 @@ #include "ouster/pcap.h" #include "ouster/types.h" +#include "ouster/visibility.h" + namespace ouster { namespace sensor_utils { /** * Structure representing a hash key/sorting key for a udp stream */ -struct stream_key { +struct OUSTER_API_CLASS stream_key { std::string dst_ip; ///< The destination IP std::string src_ip; ///< The source IP int src_port; ///< The src port int dst_port; ///< The destination port + OUSTER_API_FUNCTION bool operator==(const struct stream_key& other) const; }; } // namespace sensor_utils } // namespace ouster template <> -struct std::hash { +struct OUSTER_API_CLASS std::hash { + OUSTER_API_FUNCTION std::size_t operator()( const ouster::sensor_utils::stream_key& key) const noexcept { return std::hash{}(key.src_ip) ^ @@ -59,12 +63,13 @@ using ts = std::chrono::microseconds; ///< Microsecond timestamp * * @return The new output stream containing concatted stream_in and data. */ +OUSTER_API_FUNCTION std::ostream& operator<<(std::ostream& stream_in, const packet_info& data); /** * Structure representing a hash key/sorting key for a udp stream */ -struct guessed_ports { +struct OUSTER_API_CLASS guessed_ports { int lidar; ///< Guessed lidar port int imu; ///< Guessed imu port }; @@ -77,9 +82,10 @@ struct guessed_ports { * * @return The new output stream containing concatted stream_in and data. */ +OUSTER_API_FUNCTION std::ostream& operator<<(std::ostream& stream_in, const stream_key& data); -struct stream_data { +struct OUSTER_API_CLASS stream_data { uint64_t count; ///< Number of packets in a specified stream std::map payload_size_counts; ///< Packet sizes detected in a specified stream @@ -103,12 +109,13 @@ struct stream_data { * * @return The new output stream containing concatted stream_in and data. */ +OUSTER_API_FUNCTION std::ostream& operator<<(std::ostream& stream_in, const stream_data& data); /** * Structure representing the information about network streams in a pcap file */ -struct stream_info { +struct OUSTER_API_CLASS stream_info { uint64_t total_packets; ///< The total number of packets detected uint32_t encapsulation_protocol; ///< The encapsulation protocol for the ///< pcap file @@ -129,12 +136,25 @@ struct stream_info { * * @return The new output stream containing concatted stream_info and data. */ +OUSTER_API_FUNCTION std::ostream& operator<<(std::ostream& stream_in, const stream_info& data); -/** Struct to hide the stepwise playback details. */ +/** + * @struct playback_handle + * + * @brief struct to hide the stepwise playback details. + * + * This struct handles stepwise playback details. + */ struct playback_handle; -/** Struct to hide the record details. */ +/** + * @struct record_handle + * + * @brief struct to hide the record details. + * + * This struct handles hiding the record details. + */ struct record_handle; /** @@ -144,6 +164,7 @@ struct record_handle; * * @return A handle to the initialized playback struct. */ +OUSTER_API_FUNCTION std::shared_ptr replay_initialize(const std::string& file); /** @@ -151,6 +172,7 @@ std::shared_ptr replay_initialize(const std::string& file); * * @param[in] handle A handle to the initialized playback struct. */ +OUSTER_API_FUNCTION void replay_uninitialize(playback_handle& handle); /** @@ -158,6 +180,7 @@ void replay_uninitialize(playback_handle& handle); * * @param[in] handle A handle to the initialized playback struct. */ +OUSTER_API_FUNCTION void replay_reset(playback_handle& handle); /** @@ -169,6 +192,7 @@ void replay_reset(playback_handle& handle); * * @return The status on whether there is a new packet or not. */ +OUSTER_API_FUNCTION bool next_packet_info(playback_handle& handle, packet_info& info); /** @@ -182,6 +206,7 @@ bool next_packet_info(playback_handle& handle, packet_info& info); * * @return 0 on no new packet, > 0 the size of the bytes recieved. */ +OUSTER_API_FUNCTION size_t read_packet(playback_handle& handle, uint8_t* buf, size_t buffer_size); /** @@ -193,6 +218,7 @@ size_t read_packet(playback_handle& handle, uint8_t* buf, size_t buffer_size); * @param[in] use_sll_encapsulation Whether to use sll encapsulation. * @return record_handle A handle to the initialized record. */ +OUSTER_API_FUNCTION std::shared_ptr record_initialize( const std::string& file, int frag_size, bool use_sll_encapsulation = false); @@ -201,6 +227,7 @@ std::shared_ptr record_initialize( * * @param[in] handle An initialized handle for the recording state. */ +OUSTER_API_FUNCTION void record_uninitialize(record_handle& handle); /** @@ -216,6 +243,7 @@ void record_uninitialize(record_handle& handle); * @param[in] microsecond_timestamp The timestamp to record the packet as * microseconds. */ +OUSTER_API_FUNCTION void record_packet(record_handle& handle, const std::string& src_ip, const std::string& dst_ip, int src_port, int dst_port, const uint8_t* buf, size_t buffer_size, @@ -229,6 +257,7 @@ void record_packet(record_handle& handle, const std::string& src_ip, * @param[in] buf The buffer to record to the pcap file. * @param[in] buffer_size The size of the buffer to record to the pcap file. */ +OUSTER_API_FUNCTION void record_packet(record_handle& handle, const packet_info& info, const uint8_t* buf, size_t buffer_size); @@ -241,6 +270,7 @@ void record_packet(record_handle& handle, const packet_info& info, * * @return A pointer to the resulting stream_info */ +OUSTER_API_FUNCTION std::shared_ptr get_stream_info(const std::string& file, int packets_to_process = -1); @@ -259,6 +289,7 @@ std::shared_ptr get_stream_info(const std::string& file, * * @return A pointer to the resulting stream_info */ +OUSTER_API_FUNCTION std::shared_ptr get_stream_info( const std::string& file, std::function @@ -281,6 +312,7 @@ std::shared_ptr get_stream_info( * * @return A pointer to the resulting stream_info */ +OUSTER_API_FUNCTION std::shared_ptr get_stream_info( PcapReader& pcap_reader, std::function progress_callback, @@ -298,6 +330,7 @@ std::shared_ptr get_stream_info( * * @return A vector (sorted by most likely to least likely) of the guessed ports */ +OUSTER_API_FUNCTION std::vector guess_ports(stream_info& info, int lidar_packet_size, int imu_packet_size, int expected_lidar_port, diff --git a/ouster_pcap/include/ouster/pcap.h b/ouster_pcap/include/ouster/pcap.h index 5577c8b8..32424093 100644 --- a/ouster_pcap/include/ouster/pcap.h +++ b/ouster_pcap/include/ouster/pcap.h @@ -12,6 +12,8 @@ #include #include +#include "ouster/visibility.h" + namespace ouster { namespace sensor_utils { @@ -20,7 +22,7 @@ struct pcap_writer_impl; static constexpr int IANA_UDP = 17; -struct packet_info { +struct OUSTER_API_CLASS packet_info { using ts = std::chrono::microseconds; // TODO: use numerical IPs for efficient filtering @@ -42,7 +44,7 @@ struct packet_info { /** * Class for dealing with reading pcap files */ -class PcapReader { +class OUSTER_API_CLASS PcapReader { protected: std::unique_ptr impl; ///< Private implementation pointer packet_info info; ///< Cached packet info @@ -53,7 +55,9 @@ class PcapReader { /** * @param[in] file A filepath of the pcap to read */ + OUSTER_API_FUNCTION PcapReader(const std::string& file); + OUSTER_API_FUNCTION virtual ~PcapReader(); /** @@ -63,6 +67,7 @@ class PcapReader { * * @return The size of the packet payload */ + OUSTER_API_FUNCTION size_t next_packet(); /** @@ -72,6 +77,7 @@ class PcapReader { * * @return A pointer to a byte array containing the packet data */ + OUSTER_API_FUNCTION const uint8_t* current_data() const; /** @@ -80,6 +86,7 @@ class PcapReader { * * @return The size of the byte array */ + OUSTER_API_FUNCTION size_t current_length() const; /** @@ -88,16 +95,19 @@ class PcapReader { * * @return A packet_info object on the current packet */ + OUSTER_API_FUNCTION const packet_info& current_info() const; /** * @return The size of the PCAP file in bytes */ + OUSTER_API_FUNCTION int64_t file_size() const; /** * Return the read position to the start of the PCAP file */ + OUSTER_API_FUNCTION void reset(); /** @@ -112,8 +122,10 @@ class PcapReader { * subsequent packet reads from this PcapReader will be * invalid until PcapReader::reset is called. */ + OUSTER_API_FUNCTION void seek(uint64_t offset); + OUSTER_API_FUNCTION int64_t current_offset() const; private: @@ -124,7 +136,7 @@ class PcapReader { /** * Class for dealing with writing udp pcap files */ -class PcapWriter { +class OUSTER_API_CLASS PcapWriter { public: /** * Enum to describe the current encapsulation for a pcap file @@ -140,8 +152,10 @@ class PcapWriter { * @param[in] encap The encapsulation to use for the pcap * @param[in] frag_size The fragmentation size to use (Currently broken) */ + OUSTER_API_FUNCTION PcapWriter(const std::string& file, PacketEncapsulation encap, uint16_t frag_size); + OUSTER_API_FUNCTION virtual ~PcapWriter(); /** @@ -157,6 +171,7 @@ class PcapWriter { * @note The timestamp parameter does not affect the order of packets being * recorded, it is strictly recorded FIFO. */ + OUSTER_API_FUNCTION void write_packet(const uint8_t* buf, size_t buf_size, const std::string& src_ip, const std::string& dst_ip, uint16_t src_port, uint16_t dst_port, @@ -172,17 +187,20 @@ class PcapWriter { * @note The timestamp parameter in info does not affect the order of * packets being recorded, it is strictly recorded FIFO. */ + OUSTER_API_FUNCTION void write_packet(const uint8_t* buf, size_t buf_size, const packet_info& info); /** * Write all pending data to the pcap file */ + OUSTER_API_FUNCTION void flush(); /** * Flushes and cleans up all memory in use by the pap writer */ + OUSTER_API_FUNCTION void close(); protected: @@ -202,6 +220,7 @@ class PcapWriter { * * @return The new output stream containing concatted stream_in and data. */ +OUSTER_API_FUNCTION std::ostream& operator<<(std::ostream& stream_in, const packet_info& data); } // namespace sensor_utils } // namespace ouster diff --git a/ouster_pcap/src/indexed_pcap_reader.cpp b/ouster_pcap/src/indexed_pcap_reader.cpp index 61734fc6..a048261f 100644 --- a/ouster_pcap/src/indexed_pcap_reader.cpp +++ b/ouster_pcap/src/indexed_pcap_reader.cpp @@ -3,6 +3,7 @@ #include #include +#include "ouster/packet.h" #include "ouster/types.h" namespace ouster { @@ -34,7 +35,7 @@ IndexedPcapReader::IndexedPcapReader( void IndexedPcapReader::init_() { uint64_t index = 0; for (auto it : sensor_infos_) { - std::string sn_lidar = it.sn; + std::string sn_lidar = std::to_string(it.sn); std::string sn_imu = "LEGACY_IMU"; if (it.config.udp_profile_lidar == ouster::sensor::UDPProfileLidar::PROFILE_LIDAR_LEGACY) { @@ -77,8 +78,7 @@ nonstd::optional IndexedPcapReader::sensor_idx_for_current_packet() for (auto it : temp_match->second) { auto res = validate_packet( sensor_infos_[it.second], packet_formats_[it.second], data, - pkt_info.payload_size, - ouster::sensor::PacketValidationType::LIDAR); + pkt_info.payload_size, ouster::sensor::PacketType::Lidar); if (res == ouster::sensor::PacketValidationFailure::NONE) { return it.second; } diff --git a/ouster_viz/CMakeLists.txt b/ouster_viz/CMakeLists.txt index 4f9eff5a..ffd344ee 100644 --- a/ouster_viz/CMakeLists.txt +++ b/ouster_viz/CMakeLists.txt @@ -3,20 +3,6 @@ set(OpenGL_GL_PREFERENCE LEGACY) find_package(OpenGL REQUIRED) include(Coverage) -# default to glad, if found. Note: this can be overridden from the command line -find_package(glad QUIET) -option(OUSTER_VIZ_USE_GLAD "Use GLAD instead of GLEW." ${glad_FOUND}) -if(OUSTER_VIZ_USE_GLAD) - message(STATUS "Configured GL loader: glad") - find_package(glad REQUIRED) - set(GL_LOADER glad::glad) - add_definitions("-DOUSTER_VIZ_USE_GLAD") -else() - message(STATUS "Configured GL loader: GLEW") - find_package(GLEW REQUIRED) - set(GL_LOADER GLEW::GLEW) -endif() - find_package(glfw3 REQUIRED) find_package(Eigen3 REQUIRED) @@ -24,22 +10,29 @@ find_package(Eigen3 REQUIRED) # use only MPL-licensed parts of eigen add_definitions(-DEIGEN_MPL2_ONLY) -add_library(ouster_viz src/point_viz.cpp src/cloud.cpp src/camera.cpp src/image.cpp +add_library(ouster_viz STATIC src/point_viz.cpp src/cloud.cpp src/camera.cpp src/image.cpp src/gltext.cpp src/misc.cpp src/glfw.cpp) target_link_libraries(ouster_viz - PRIVATE Eigen3::Eigen glfw ${GL_LOADER} OpenGL::GL ouster_client) + PRIVATE Eigen3::Eigen glfw ${GL_LOADER} OpenGL::GL ouster_client glad) +set_property(TARGET ouster_viz PROPERTY POSITION_INDEPENDENT_CODE ON) +if(BUILD_SHARED_LIBRARY) + set_target_properties(ouster_viz PROPERTIES CXX_VISIBILITY_PRESET hidden) +endif() CodeCoverageFunctionality(ouster_viz) -target_include_directories(ouster_viz PUBLIC - $ - $) +if(BUILD_SHARED_LIBRARY) + target_compile_definitions(ouster_viz PRIVATE BUILD_SHARED_LIBS_EXPORT) +endif() + +target_include_directories(ouster_viz + PUBLIC + $ + $) add_library(OusterSDK::ouster_viz ALIAS ouster_viz) # ==== Install ==== install(TARGETS ouster_viz EXPORT ouster-sdk-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include) diff --git a/ouster_viz/include/ouster/point_viz.h b/ouster_viz/include/ouster/point_viz.h index 9df14cce..8f6c1705 100644 --- a/ouster_viz/include/ouster/point_viz.h +++ b/ouster_viz/include/ouster/point_viz.h @@ -17,6 +17,7 @@ #include #include "nonstd/optional.hpp" +#include "ouster/visibility.h" namespace ouster { namespace viz { @@ -117,9 +118,8 @@ constexpr int default_window_height = 600; * when a nontrivial amount of processing needs to run concurrently with * rendering (e.g. when streaming data from a running sensor). */ -class PointViz { - friend void add_default_controls(viz::PointViz& viz, std::mutex* mx); - +class OUSTER_API_CLASS PointViz { + protected: /** * Get a reference to the current camera controls * @@ -128,6 +128,18 @@ class PointViz { Camera& current_camera(); public: + /** + * Add default keyboard and mouse bindings to a visualizer instance + * + * Controls will modify the camera from the thread that calls run() or + * run_once(), which will require synchronization when using multiple + * threads. + * + * @param[in] mx mutex to lock while modifying camera + */ + OUSTER_API_FUNCTION + void add_default_controls(std::mutex* mx); + struct Impl; /** @@ -140,6 +152,7 @@ class PointViz { * @param[in] window_height Window height to set, * else uses the default_window_height */ + OUSTER_API_FUNCTION explicit PointViz(const std::string& name, bool fix_aspect = false, int window_width = default_window_width, int window_height = default_window_height); @@ -147,14 +160,19 @@ class PointViz { // Because PointViz uses the PIMPL pattern // and the Impl owns the window context, // we can't realistically copy or move instances of PointViz. + OUSTER_API_FUNCTION PointViz(const PointViz&) = delete; + OUSTER_API_FUNCTION PointViz(PointViz&&) = delete; + OUSTER_API_FUNCTION PointViz& operator=(PointViz&) = delete; + OUSTER_API_FUNCTION PointViz& operator=(PointViz&&) = delete; /** * Tears down the rendering context and closes the viz window */ + OUSTER_API_FUNCTION ~PointViz(); /** @@ -162,6 +180,7 @@ class PointViz { * * Should be called from the main thread for macos compatibility */ + OUSTER_API_FUNCTION void run(); /** @@ -169,6 +188,7 @@ class PointViz { * * Should be called from the main thread for macos compatibility */ + OUSTER_API_FUNCTION void run_once(); /** @@ -176,6 +196,7 @@ class PointViz { * * @return true if the run() loop is currently executing */ + OUSTER_API_FUNCTION bool running(); /** @@ -183,6 +204,7 @@ class PointViz { * * @param[in] state new value of the flag */ + OUSTER_API_FUNCTION void running(bool state); /** @@ -190,6 +212,7 @@ class PointViz { * * @param[in] state true to show */ + OUSTER_API_FUNCTION void visible(bool state); /** @@ -197,6 +220,7 @@ class PointViz { * * Send state updates to be rendered on the next frame. */ + OUSTER_API_FUNCTION void update(); /** @@ -207,6 +231,7 @@ class PointViz { * callback's return value determines whether the remaining key callbacks * should be called. */ + OUSTER_API_FUNCTION void push_key_handler( std::function&& callback); @@ -222,6 +247,7 @@ class PointViz { * The callback's return value determines whether * the remaining mouse button callbacks should be called. */ + OUSTER_API_FUNCTION void push_mouse_button_handler( std::function&& callback); @@ -253,6 +280,7 @@ class PointViz { * The callback's return value determines whether * the remaining mouse position callbacks should be called. */ + OUSTER_API_FUNCTION void push_mouse_pos_handler( std::function&& callback); @@ -267,46 +295,55 @@ class PointViz { * fb_height) The callback's return value determines whether the remaining * frame buffer callbacks should be called. */ + OUSTER_API_FUNCTION void push_frame_buffer_handler( std::function&, int, int)>&& callback); /** * Remove the last added callback for handling keyboard events */ + OUSTER_API_FUNCTION void pop_key_handler(); /** * Remove the last added callback for handling mouse button events */ + OUSTER_API_FUNCTION void pop_mouse_button_handler(); /** * Remove the last added callback for handling mouse scroll events */ + OUSTER_API_FUNCTION void pop_scroll_handler(); /** * Remove the last added callback for handling mouse position events */ + OUSTER_API_FUNCTION void pop_mouse_pos_handler(); /** * Remove the last added callback for handling frame buffer events */ + OUSTER_API_FUNCTION void pop_frame_buffer_handler(); /** * Add a callback for handling frame buffer resize events. + * * @param[in] callback function callback of the form f(const WindowCtx&). * The callback's return value determines whether the remaining frame buffer * resize callbacks should be called. */ + OUSTER_API_FUNCTION void push_frame_buffer_resize_handler( std::function&& callback); /** * Remove the last added callback for handling frame buffer resize events. */ + OUSTER_API_FUNCTION void pop_frame_buffer_resize_handler(); /** @@ -314,6 +351,7 @@ class PointViz { * * @return Handler to the camera object */ + OUSTER_API_FUNCTION Camera& camera(); /** @@ -321,6 +359,7 @@ class PointViz { * * @return Handler to the target display controls */ + OUSTER_API_FUNCTION TargetDisplay& target_display(); /** @@ -328,6 +367,7 @@ class PointViz { * * @param[in] cloud Adds a point cloud to the scene */ + OUSTER_API_FUNCTION void add(const std::shared_ptr& cloud); /** @@ -335,6 +375,7 @@ class PointViz { * * @param[in] image Adds an image to the scene */ + OUSTER_API_FUNCTION void add(const std::shared_ptr& image); /** @@ -342,6 +383,7 @@ class PointViz { * * @param[in] cuboid Adds a cuboid to the scene */ + OUSTER_API_FUNCTION void add(const std::shared_ptr& cuboid); /** @@ -349,6 +391,7 @@ class PointViz { * * @param[in] label Adds a label to the scene */ + OUSTER_API_FUNCTION void add(const std::shared_ptr