diff --git a/.gitignore b/.gitignore
index 85bea34b..3b00ad39 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,18 @@ python/**/*.pyd
# visual studio
.vs/
-out/
\ No newline at end of file
+out/
+
+# vim
+*.swp
+
+# CMake, Ninja
+.ninja_deps
+.ninja_log
+CMakeFiles
+CTestTestfile.cmake
+
+# Built artifacts
+generated
+rules.ninja
+*.a
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c37ae89b..a72ccb91 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,64 @@
Changelog
=========
+20230710
+========
+
+* Update vcpkg ref of build to 2023-02-24
+*
+
+ouster_osf
+----------
+
+* Add Ouster OSF C++/Python library to save stream of LidarScans with metadata
+
+ouster_client
+-------------
+
+* Add ``LidarScan.pose`` with poses per column
+* Add ``_client.IndexedPcapReader`` and ``_client.PcapIndex`` to enable random
+ pcap file access by frame number
+* [BREAKING] remove ``ouster::Imu`` object
+* Add get_field_types function for LidarScan, from sensor_info
+* bugfix: return metadata regardless of sensor_info status field
+* Make timeout on curl more configurable
+
+ouster_viz
+----------
+
+* [BREAKING] Changed Python binding for ``Cloud.set_column_poses()`` to accept ``[Wx4x4]`` array
+ of poses, column-storage order
+* bugfix: fix label re-use
+* Add ``LidarScan.pose`` handling to ``viz.LidarScanViz``, and new ``T`` keyboard
+ binding to toggle column poses usage
+
+ouster_pcap
+-----------
+* bugfix: Use unordered map to store stream_keys to avoid comparison operators on map
+
+Python SDK
+----------
+* Add Python 3.11 wheels
+* Retire simple-viz for ouster-cli utility
+* Add default ? key binding to LidarScanViz and consolidate bindings into stored definition
+* Remove pcap-to-csv for ouster-cli utility
+* Add validator class for LidarPacket
+
+ouster-cli
+----------
+This release also marks the introduction of the ouster-cli utility which includes, among many features:
+* Visualization from a connected sensor with automatic configuration
+* Recording from a connected sensor
+* Simultaneous record and viz from a connected sensor
+* Obtaining metadata from a connected sensor
+* Visualization from a specified PCAP
+* Slice, info, and conversion for a specificed PCAP
+* Utilities for benchmarking system, printing system-info
+* Discovery which indicates all connected sensors on network
+* Automatic logging to .ouster-cli
+* Mapping utilities
+
+
[20230403]
==========
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 76a051a8..b5828d3f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,16 +7,17 @@ include(DefaultBuildType)
include(VcpkgEnv)
# ==== Project Name ====
-project(ouster_example VERSION 20230403)
+project(ouster_example VERSION 20230710)
# generate version header
-set(OusterSDK_VERSION_STRING 0.8.1)
+set(OusterSDK_VERSION_STRING 0.9.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." OFF)
option(BUILD_VIZ "Build Ouster visualizer." ON)
option(BUILD_TESTING "Build tests" OFF)
option(BUILD_EXAMPLES "Build C++ examples" OFF)
@@ -56,6 +57,10 @@ if(BUILD_PCAP)
add_subdirectory(ouster_pcap)
endif()
+if(BUILD_OSF)
+ add_subdirectory(ouster_osf)
+endif()
+
if(BUILD_VIZ)
add_subdirectory(ouster_viz)
endif()
@@ -88,7 +93,7 @@ set(CPACK_SOURCE_IGNORE_FILES /.git /dist)
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")
+ "libjsoncpp-dev, libeigen3-dev, libtins-dev, libglfw3-dev, libglew-dev, libspdlog-dev, libpng-dev, libflatbuffers-dev")
include(CPack)
# ==== Install ====
diff --git a/README.rst b/README.rst
index 71b964d0..e3da782b 100644
--- a/README.rst
+++ b/README.rst
@@ -18,6 +18,7 @@ reading and visualizing data.
* `ouster_client `_ contains an example C++ client for ouster sensors
* `ouster_pcap `_ contains C++ pcap functions for ouster sensors
+* `ouster_osf `_ contains C++ OSF library to store ouster sensors data
* `ouster_viz `_ contains a customizable point cloud visualizer
* `python `_ contains the code for the ouster sensor python SDK (``ouster-sdk`` Python package)
diff --git a/cmake/FindPcap.cmake b/cmake/FindPcap.cmake
index ec325f38..b7c91f66 100644
--- a/cmake/FindPcap.cmake
+++ b/cmake/FindPcap.cmake
@@ -14,6 +14,14 @@ find_library(PCAP_LIBRARY
NAMES pcap pcap_static wpcap
HINTS ${PC_PCAP_LIBRARY_DIRS})
+if(NOT TARGET libpcap::libpcap)
+ add_library(libpcap::libpcap UNKNOWN IMPORTED)
+ set_target_properties(libpcap::libpcap PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${PCAP_INCLUDE_DIR}"
+ IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+ IMPORTED_LOCATION "${PCAP_LIBRARY}")
+endif()
+
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Pcap
REQUIRED_VARS PCAP_LIBRARY PCAP_INCLUDE_DIR
diff --git a/cmake/FindPybind11Internal.cmake b/cmake/FindPybind11Internal.cmake
new file mode 100644
index 00000000..5a2dd59e
--- /dev/null
+++ b/cmake/FindPybind11Internal.cmake
@@ -0,0 +1,54 @@
+include(FindPackageHandleStandardArgs)
+
+if(DEFINED PYTHON_EXECUTABLE)
+ execute_process(
+ COMMAND
+ "${PYTHON_EXECUTABLE}" "-c"
+ "import sys; print(f'{str(sys.version_info.major)}.{str(sys.version_info.minor)}.{str(sys.version_info.micro)}')"
+ OUTPUT_VARIABLE _version_full
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+ execute_process(
+ COMMAND
+ "${PYTHON_EXECUTABLE}" "-c"
+ "import pybind11;print(pybind11.get_cmake_dir())"
+ OUTPUT_VARIABLE _pybind_dir
+ RESULT_VARIABLE _pybind_result
+ ERROR_VARIABLE _pybind_error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(${_pybind_result} EQUAL 0)
+ LIST(APPEND CMAKE_PREFIX_PATH "${_pybind_dir}")
+ if("${_version_full}" VERSION_GREATER_EQUAL "3.11.0")
+ find_package(pybind11 2.10 REQUIRED)
+ else()
+ find_package(pybind11 2.0 REQUIRED)
+ endif()
+
+ set(_PYBIND11_INTERNAL_PYTHON_VERSION "")
+ if(NOT PYTHON_VERSION STREQUAL "")
+ message("Found Python Version VIA: PYTHON_VERSION")
+ set(_PYBIND11_INTERNAL_PYTHON_VERSION "${PYTHON_VERSION}")
+ elseif(NOT PYTHONLIBS_VERSION_STRING STREQUAL "")
+ message("Found Python Version VIA: PYTHONLIBS_VERSION_STRING")
+ set(_PYBIND11_INTERNAL_PYTHON_VERSION "${PYTHONLIBS_VERSION_STRING}")
+ elseif(NOT PYTHON_VERSION_STRING STREQUAL "")
+ message("Found Python Version VIA: PYTHON_VERSION_STRING")
+ set(_PYBIND11_INTERNAL_PYTHON_VERSION "${PYTHON_VERSION_STRING}")
+ endif()
+ if(VCPKG_TOOLCHAIN AND NOT "${_version_full}" VERSION_EQUAL "${_PYBIND11_INTERNAL_PYTHON_VERSION}")
+ message(FATAL_ERROR "Python Versions Do Not Match
+\tRequested Version:
+\t\t${_version_full}
+\tVersions Found:
+\t\tPYTHON_VERSION: \"${PYTHON_VERSION}\"
+\t\tPYTHONLIBS_VERSION_STRING: \"${PYTHONLIBS_VERSION_STRING}\"
+\t\tPYTHON_VERSION_STRING: \"${PYTHON_VERSION_STRING}\"
+\tInternal Cache: \"${_PYBIND11_INTERNAL_PYTHON_VERSION}\"")
+ endif()
+ else()
+ message(FATAL_ERROR "ERROR In Setting Pybind11 CMAKE Prefix Path: ${_pybind_error}")
+ endif()
+else()
+ message(FATAL_ERROR "PYTHON_EXECUTABLE NOT SET")
+endif()
+
diff --git a/cmake/Findlibtins.cmake b/cmake/Findlibtins.cmake
index ae181213..55c87d38 100644
--- a/cmake/Findlibtins.cmake
+++ b/cmake/Findlibtins.cmake
@@ -54,5 +54,6 @@ if(NOT TARGET libtins)
add_library(libtins INTERFACE)
set_target_properties(libtins PROPERTIES
INTERFACE_LINK_LIBRARIES ${LIBTINS_LIBRARIES})
+ add_library(libtins::libtins ALIAS libtins)
install(TARGETS libtins EXPORT ouster-sdk-targets)
endif()
diff --git a/cmake/OusterSDKConfig.cmake.in b/cmake/OusterSDKConfig.cmake.in
index 3d7692cd..0ca13a80 100644
--- a/cmake/OusterSDKConfig.cmake.in
+++ b/cmake/OusterSDKConfig.cmake.in
@@ -8,6 +8,13 @@ find_dependency(jsoncpp)
find_dependency(CURL)
find_dependency(spdlog)
+# ouster_osf dependencies
+if(@BUILD_OSF@)
+ find_package(ZLIB REQUIRED)
+ find_package(PNG REQUIRED)
+ find_package(Flatbuffers NAMES Flatbuffers FlatBuffers)
+endif()
+
# viz dependencies
if(@BUILD_VIZ@)
set(OpenGL_GL_PREFERENCE GLVND)
diff --git a/conan/test_package/conanfile.py b/conan/test_package/conanfile.py
index a52cf82b..bc77cb2c 100644
--- a/conan/test_package/conanfile.py
+++ b/conan/test_package/conanfile.py
@@ -11,6 +11,7 @@ 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")
@@ -27,4 +28,7 @@ def test(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(".%s%sclient_example" % (exec_path, os.sep))
+ self.run(f".{exec_path}{os.sep}client_example")
+
+ # Smoke test OSF lib
+ self.run(f".{exec_path}{os.sep}osf_reader_example")
diff --git a/conanfile.py b/conanfile.py
index 26fd9018..96f42d50 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -17,6 +17,7 @@ class OusterSDKConan(ConanFile):
options = {
"build_viz": [True, False],
"build_pcap": [True, False],
+ "build_osf": [True, False],
"shared": [True, False],
"fPIC": [True, False],
"ensure_cpp17": [True, False],
@@ -25,6 +26,7 @@ class OusterSDKConan(ConanFile):
default_options = {
"build_viz": False,
"build_pcap": False,
+ "build_osf": False,
"shared": False,
"fPIC": True,
"ensure_cpp17": False,
@@ -37,6 +39,7 @@ class OusterSDKConan(ConanFile):
"conan/*",
"ouster_client/*",
"ouster_pcap/*",
+ "ouster_osf/*",
"ouster_viz/*",
"tests/*",
"CMakeLists.txt",
@@ -57,20 +60,19 @@ def config_options(self):
del self.options.fPIC
def requirements(self):
- # not required directly here but because libtins and libcurl pulling
- # slightly different versions of zlib and openssl we need to set it
- # here explicitly
- self.requires("zlib/1.2.13")
- self.requires("openssl/1.1.1s")
-
self.requires("eigen/3.4.0")
self.requires("jsoncpp/1.9.5")
- self.requires("spdlog/1.10.0")
+ self.requires("spdlog/1.11.0")
+ self.requires("fmt/9.1.0")
self.requires("libcurl/7.84.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":
@@ -83,6 +85,7 @@ def configure_cmake(self):
cmake = CMake(self)
cmake.definitions["BUILD_VIZ"] = self.options.build_viz
cmake.definitions["BUILD_PCAP"] = self.options.build_pcap
+ cmake.definitions["BUILD_OSF"] = self.options.build_osf
cmake.definitions["OUSTER_USE_EIGEN_MAX_ALIGN_BYTES_32"] = self.options.eigen_max_align_bytes
# alt way, but we use CMAKE_TOOLCHAIN_FILE in other pipeline so avoid overwrite
# cmake.definitions["CMAKE_TOOLCHAIN_FILE"] = os.path.join(self.build_folder, "conan_paths.cmake")
diff --git a/docs/cli/changelog.rst b/docs/cli/changelog.rst
new file mode 100644
index 00000000..3869ccaf
--- /dev/null
+++ b/docs/cli/changelog.rst
@@ -0,0 +1,6 @@
+=========
+Changelog
+=========
+
+[0.9.0]
+* Initial Public Release
diff --git a/docs/cli/common-use-cases.rst b/docs/cli/common-use-cases.rst
new file mode 100644
index 00000000..96f24c30
--- /dev/null
+++ b/docs/cli/common-use-cases.rst
@@ -0,0 +1,128 @@
+.. _common commands:
+
+
+Common Use Cases
+----------------
+
+One of the goals of the Ouster Sensor Tools package is to easily allow the most common sensor and
+recorded data interactions. We cover some common use cases here, listed alphabetically. Please note
+that wherever is used, you are expected to substitute in your sensor's hostname,
+e.g., ``os1-991913000010.local``.
+
+
+Benchmarking
+++++++++++++
+
+To help users obtain performance metrics for the Ouster SDK, we
+provide a benchmarking utility which will download `8 seconds of OS2 data`_, gather system info,
+time various data operations, and generate a report which you can share with others. Give it a try!
+
+.. code:: bash
+
+ $ ouster-cli util benchmark
+
+This will generate a report in a directory named ouster-bench, which will be located in the current working directory.
+
+.. _8 seconds of OS2 data: https://data.ouster.dev/drive/7377
+
+
+Discovering sensors on local network
+++++++++++++++++++++++++++++++++++++
+
+Sensors announce their presence on the network using Multicast Domain Name Service (mDNS). Use
+helper utility command ``discover`` to list names and IPs af all available sensors on the local
+network:
+
+.. code:: bash
+
+ $ ouster-cli discover
+
+Collecting Metadata
++++++++++++++++++++
+
+Sensor metadata, necessary for interpreting and parsing the pcap data, can be collected from sensors
+using:
+
+.. code:: bash
+
+ $ ouster-cli source info > .json
+
+This will generate a ``.json`` file named ``.json`` with the metadata inside. To
+output it to a differently named file, simply change ``.json`` to
+``.json``. You can also print the metadata to screen by removing ``>`` and
+everything after it in the command.
+
+
+Configuring Your Sensor
++++++++++++++++++++++++
+
+``ouster-cli`` provides utilities for configuring your sensor with configuration parameters such as
+``lidar_mode`` and ``azimuth_window``.
+
+To quickly auto-configure a sensor with with standard ports, azimuth window, operating mode, and
+auto udp dest:
+
+.. code:: bash
+
+ $ ouster-cli source config
+
+But what if you want to specify the ports and lidar_mode? You can use the ``sensor config`` command thusly:
+
+.. code:: bash
+
+ $ ouster-cli source config lidar_mode 1024x10 udp_port_lidar 29847
+
+.. note::
+
+ Multiple `` `` pairs can be passed this way!
+
+You may have a configuration that you want to use repeatedly. Typing these in at the command line
+every time would be annoying. You can instead save your config to a json, named CONFIG_JSON, here,
+and run:
+
+.. code:: bash
+
+ $ ouster-cli source config -c
+
+And finally, you may wish to save a configuration after setting your sensor up perfectly. To do so:
+
+.. code:: bash
+
+ $ ouster-cli source config -d
+
+That will print your json to stdout. Use ``>`` to redirect it to a file!
+
+
+Recording Pcaps
++++++++++++++++
+
+To record data from a udp port (7502 by default) to a pcap file in the current directory and write
+the metadata to a json file with the same name, simply use:
+
+.. code:: bash
+
+ $ ouster-cli source record
+
+This will record until you keyboard interrupt, i.e., use ``CTRL+C``. You can also set it to record
+a specific length or number of packets, or to use different ports for lidar and IMU data. As always
+with ``ouster-cli``, use ``--help`` to discover how those options work.
+
+
+Visualizing Lidar Data
+++++++++++++++++++++++
+
+The following visualizes lidar data arriving on a udp port. Note that you may have to use
+``ouster-cli source config`` first to configure your sensor properly.
+
+.. code:: bash
+
+ $ ouster-cli source viz
+
+
+The following replays lidar data saved in a pcap file and visualizes the output. It will looks for a
+metadata json file with the same name as PCAP FILE by default, but you can specify a file using ``-m
+``.
+
+.. code:: bash
+
+ $ ouster-cli source viz
diff --git a/docs/cli/getting-started.rst b/docs/cli/getting-started.rst
new file mode 100644
index 00000000..3a833258
--- /dev/null
+++ b/docs/cli/getting-started.rst
@@ -0,0 +1,94 @@
+Getting Started
+===============
+
+Installation
+------------
+The ouster sensor command line utility comes with the ouster-sdk python package.
+
+.. code::
+
+ pip3 install ouster-sdk
+
+Using the cli
+-------------
+
+After installation you should have access to the ``ouster-cli`` utility, a command line interface
+which is the main entry point to our internal tools. It is organized as a tree of
+commands. To see what this means, open a terminal and type:
+
+.. code::
+
+ ouster-cli
+
+.. note::
+
+ On Ubuntu, if you're working outside a `virtual environment`_, you may have to add ``ouster-cli``
+ to your path:
+
+ .. code::
+
+ export PATH=$PATH:/.local/bin
+
+ We recommend using python virtual environments for all users!
+
+That should return a menu of some possible commands, among them, ``discover``, ``mapping``, ``source`` and ``util``.
+Each of these is itself the key to a submenu of further commands. Let's try looking
+at ``source``:
+
+1. First download the sample data here :ref:`sample-data-download`.
+2. Next run the following to see a list of commands
+.. code::
+
+ ouster-cli source
+
+You should see a few commands listed there, including ``viz`` and ``info``. To figure out how to use
+them, try using ``--help``. For example, for help with the ``ouster-cli source viz`` command:
+
+.. code::
+
+ ouster-cli source viz --help
+
+Remember that you can use ``--help`` with any ``ouster-cli`` command, regardless how far down the
+menu tree you are.
+
+If you have a live sensor, you can replace the with the hostname of the sensor
+
+.. code::
+
+ ouster-cli source viz --help
+
+.. admonition:: Ubuntu UFW Firewall may cause: ``No packets received within 1.0s``
+
+ On some Ubuntu setups we've observed the situations when everything is configured properly so
+ that:
+
+ - sensor is seen on the network and its Web page can be reached
+ - sensor destination IP is set to the IP of the computer where data is expeted
+ - sensor lidar port is known (i.e. default ``7502``, or some others)
+ - sensor is in ``RUNNING`` state
+ - sensor lidar packets traffic is seen on the expected machine and can be recorded with
+ ``tcpdump -w`` command to a pcap file (or ``Wireshark`` tools)
+ - CLI comamnd ``ouster-cli source {info,config}`` are working properly
+ - Viz ``ouster-cli source [pcap file] viz`` from the ``tcpdump`` recorded pcap can be played and visualized
+
+ But ``ouster-cli source viz``, or ``ouster-cli source record`` still can't receive any packets
+ and get the following error::
+
+ ouster.client.core.ClientTimeout: No packets received within 1.0s
+
+ Please check your `UFW Firewall`_ settings and try to allow the UDP traffic for ``7502``
+ (or whatever the **UDP Port Lidar** is set on the sensor)::
+
+ sudo ufw allow 7502/udp
+
+.. _UFW Firewall: https://help.ubuntu.com/community/UFW
+
+
+.. _virtual environment: https://docs.python.org/3/library/venv.html
+
+What next?
+----------
+
+You can now to use ``ouster-cli`` as you please, exploring available utilities with the handy
+``---help``. If you'd prefer something more documented, you can check out our :ref:`sample sessions` to
+see what an ``ouster-cli`` workflow might look like, or you can read through :ref:`common commands`.
diff --git a/docs/cli/overview.rst b/docs/cli/overview.rst
new file mode 100644
index 00000000..d1001ffc
--- /dev/null
+++ b/docs/cli/overview.rst
@@ -0,0 +1,12 @@
+============================
+Ouster Sensor Tools Overview
+============================
+
+The Ouster Sensor Tools CLI provides users features and tools for
+interacting with sensor hardware and sensor data, including:
+
+- benchmarking performance from the command line
+- collecting metadata from the command line
+- configuring your sensor from the command line
+- recording pcaps from the command line
+- visualizing lidar data from pcaps or a sensor from the command line
diff --git a/docs/cli/sample-sessions.rst b/docs/cli/sample-sessions.rst
new file mode 100644
index 00000000..d4893bab
--- /dev/null
+++ b/docs/cli/sample-sessions.rst
@@ -0,0 +1,104 @@
+.. _sample sessions:
+
+Sample Sessions
+===============
+
+These sample sessions will provide sample workflows for working with pcaps, sensors, and OSFs.
+Either can be done independently, but users with access to a sensor may wish to work with the sensor
+first, so that they will have recorded a pcap play back in the second sample.
+
+
+Sample sensor session
+---------------------
+
+Connect a sensor via Ethernet to the system with Ouster SDK installed.
+.. note::
+ Bear in mind that following the steps below will modify the sensor's configuration.
+
+First we configure the sensor with standard ports, azimuth window, operating mode, and auto udp
+dest:
+
+.. code:: bash
+
+ $ ouster-cli source config
+
+You should get a return that looks like
+
+.. code:: bash
+
+ No config specified; using defaults and auto UDP dest:
+ {
+ "azimuth_window":
+ [
+ 0,
+ 360000
+ ],
+ "operating_mode": "NORMAL",
+ "udp_port_imu": 7503,
+ "udp_port_lidar": 7502
+ }
+
+Let's see what the sensor is seeing in a pretty visualizer:
+
+.. code:: bash
+
+ $ ouster-cli source viz
+
+That looked nice! Let's record ten seconds of data to a pcap so we can view it on repeat!
+
+.. code:: bash
+
+ $ ouster-cli source record -s 10
+
+That should produce screen output that looks something like:
+
+.. code:: bash
+
+ Connecting to
+ Recording for up to 10.0 seconds...
+ Wrote X GiB to ./OS--_.pcap
+
+Go ahead and look in the current directory for the named pcap file and associated metadata file.
+
+Continue to the next section, `Sample pcap session`_ to see what you can do with your new pcap file.
+
+
+Sample pcap session
+-------------------
+
+If you don't have a pcap lying around or that you just recorded from a sensor, you can download one
+the `OS2 bridge sample data`_ and unzip the contents.
+
+Let's take a look at your pcap:
+
+.. code:: bash
+
+ $ ouster-cli source info
+
+This should output something that looks like:
+
+.. code:: bash
+ Reading pcap: [####################################] 100%
+ File size: 2247.16M
+ Packets read: 85085
+ Encapsulation: ETHERNET
+ Capture start: 2023-02-16 22:28:58.159505
+ Capture end: 2023-02-16 22:30:49.369547
+ Duration: 0:01:51.210042
+ UDP Streams:
+ Src IP Dst IP Src Port Dst Port AF Frag Size Count
+ 127.0.0.1 127.0.0.1 7502 7502 4 No 33024 71182
+ 127.0.0.1 127.0.0.1 7503 7503 4 No 48 13903
+
+That tells us the number of packets belonging to each port captured in the pcap, and the associated
+size.
+
+To visualize the pcap at 2x speed while looping back:
+
+.. code:: bash
+
+ $ ouster-cli source viz -r 2.0 -e loop
+
+You can check check out all the available options by typing ``--help`` after ``ouster-cli source viz``.
+
+ .. _OS2 bridge sample data: https://data.ouster.io/sdk-samples/OS2/OS2_128_bridge_sample.zip
diff --git a/docs/conf.py b/docs/conf.py
index da693219..b414ea34 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -65,9 +65,13 @@ def parse_version():
'sphinx_rtd_theme',
'sphinx_copybutton',
'sphinx_tabs.tabs',
- 'breathe'
+ 'breathe',
+ 'sphinx_rtd_size',
]
+# Page width
+sphinx_rtd_size_width = "70%"
+
# Full path generated Doxygen XML dir resolved in do_doxygen_generate_xml()
# handler below
breathe_projects = {'cpp_api': "xml"}
diff --git a/docs/cpp/building.rst b/docs/cpp/building.rst
index c548b970..b4570988 100644
--- a/docs/cpp/building.rst
+++ b/docs/cpp/building.rst
@@ -14,7 +14,7 @@ The C++ example code is available `on the Ouster Github
Building on Linux / macOS
=========================
-To install build dependencies on Ubuntu, run:
+To install build dependencies on Ubuntu:20.04+, run:
.. code:: console
@@ -47,10 +47,25 @@ defaults:
-DBUILD_VIZ=OFF # Do not build the sample visualizer
-DBUILD_PCAP=OFF # Do not build pcap tools
+ -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
+.. admonition:: Additional dependencies required to build Ouster OSF lib
+
+ To build Ouster OSF library as part of the SDK you need to pass ``BUILD_OSF=ON`` and ensure that
+ ``libpng`` and ``flatbuffers`` packages are available on the system.
+
+ On Ubuntu:20.04+ systems::
+
+ $ sudo apt install libpng-dev libflatbuffers-dev
+
+ On macOS::
+
+ $ brew install libpng flatbuffers
+
+
Building on Windows
===================
@@ -62,10 +77,10 @@ for dependencies. Follow the official documentation to set up your build environ
`_
* `Visual Studio CPP Support
`_
-* `Vcpkg, at tag "2022.02.23" installed and integrated with Visual Studio
+* `Vcpkg, at tag "2023.02.24" installed and integrated with Visual Studio
`_
-**Note** You'll need to run ``git checkout 2022.02.23`` in the vcpkg directory before bootstrapping
+**Note** You'll need to run ``git checkout 2023.02.24`` in the vcpkg directory before bootstrapping
to use the correct versions of the dependencies. Building may fail unexpectedly if you skip this
step.
@@ -79,7 +94,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
+ PS > .\vcpkg.exe install --triplet x64-windows jsoncpp eigen3 curl libtins glfw3 glew spdlog libpng flatbuffers
After these steps are complete, you should be able to open, build and run the ``ouster_example``
project using Visual Studio:
diff --git a/docs/index.rst b/docs/index.rst
index c7606d5b..f70cbd0d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -38,6 +38,16 @@
C++ API Reference
Changelog
+.. toctree::
+ :caption: Command Line Utility Guide
+ :hidden:
+
+ Overview
+ Getting Started
+ Sample Sessions
+ Common Use Cases
+ Changelog
+
.. toctree::
:caption: Migration Guides
:hidden:
diff --git a/docs/installation.rst b/docs/installation.rst
index f4745ad4..077ed21a 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -35,11 +35,22 @@ Ubuntu 18.04, the default Python 3 version is is 3.6, so you'll have to install
.. note::
- Using a virtual environment with the Ouster Python SDK is recommended. Users newer to Python should
- read the official `venv instructions`_ and ensure that they upgrade pip *after* activating their
- venv. If you're using venv on Windows, you'll want to use ``python`` and ``pip`` instead of ``py
+ Using a virtual environment with the Ouster Python SDK is recommended.
+ Users newer to Python should read the official `venv instructions`_ and
+ ensure that they upgrade pip *after* activating their venv. If you're using
+ venv on Windows, you'll want to use ``python`` and ``pip`` instead of ``py
-3`` and ``py -3 -m pip`` in the following Powershell snippets.
+.. note::
+
+ Python 3 when installed with macOS Developer Tools uses LibreSSL 2.8.3 (or
+ an older version.) OusterSDK, like many Python 3-compatible packages,
+ requires urllib3 which is not compatible with LibreSSL and requires OpenSSL
+ 1.1.1 or newer. To account for this, macOS users should install an official
+ distribution of Python 3 or one provided by Homebrew, as these use OpenSSL
+ 1.1.1 or newer. In either case, installing OusterSDK into a Python 3
+ virtual enviroment is still recommended.
+
If you're using an unsupported platform like a non-glibc-based linux distribution, or wish to modify
the Ouster Python SDK, you will need to build from source. See the `build instructions`_ for
requirements needed to build from a source distribution or from a clone of the repository.
diff --git a/docs/overview.rst b/docs/overview.rst
index a4c9a03e..70fc74df 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -12,11 +12,11 @@ Quick links
* :doc:`python/quickstart`
* :doc:`cpp/building`
* `Ouster ROS 1 driver`_
-
+* :doc:`python/viz/index`
+ :doc:`cli/getting-started`
.. figure:: /images/simple-viz.png
:align: center
-* :doc:`python/viz/index`
Compatibility with FW
---------------------
diff --git a/docs/python/devel.rst b/docs/python/devel.rst
index beebec34..b6284b46 100644
--- a/docs/python/devel.rst
+++ b/docs/python/devel.rst
@@ -16,6 +16,8 @@ Building the Python SDK from source requires several dependencies:
- `jsoncpp `_ >= 1.7
- `libtins `_ >= 3.4
- `libpcap `_
+- `libpng `_ >= 1.6
+- `flatbuffers `_ >= 1.1
- `libglfw3 `_ >= 3.2
- `libglew `_ >= 2.1 or `glad `_
- `spdlog `_ >= 1.9
@@ -33,14 +35,15 @@ On supported Debian-based linux systems, you can install all build dependencies
$ sudo apt install build-essential cmake \
libeigen3-dev libjsoncpp-dev libtins-dev libpcap-dev \
- python3-dev python3-pip pybind11-dev libcurl4-openssl-dev \
- libglfw3-dev libglew-dev libspdlog-dev
+ python3-dev python3-pip libcurl4-openssl-dev \
+ libglfw3-dev libglew-dev libspdlog-dev \
+ libpng-dev libflatbuffers-dev
On macos >= 10.13, using homebrew, you should be able to run:
.. code:: console
- $ brew install cmake eigen curl jsoncpp libtins python3 pybind11 glfw glew spdlog
+ $ brew install cmake eigen curl jsoncpp libtins python3 glfw glew spdlog libpng flatbuffers
After you have the system dependencies, you can build the SDK with:
@@ -52,6 +55,9 @@ After you have the system dependencies, you can build the SDK with:
# make sure you have an up-to-date version of pip installed
$ python3 -m pip install --user --upgrade pip
+ # install pybind11
+ $ python3 -m pip install pybind11
+
# then, build an installable "wheel" package
$ python3 -m pip wheel --no-deps $OUSTER_SDK_PATH/python
@@ -74,9 +80,9 @@ package manager and run:
.. code:: powershell
- PS > vcpkg install --triplet=x64-windows eigen3 jsoncpp libtins pybind11 glfw3 glad[gl-api-33] spdlog
+ PS > vcpkg install --triplet=x64-windows eigen3 jsoncpp libtins glfw3 glad[gl-api-33] spdlog libpng flatbuffers
-The currently tested vcpkg tag is ``2022.02.23``. After that, using a developer powershell prompt:
+The currently tested vcpkg tag is ``2023.02.24``. After that, using a developer powershell prompt:
.. code:: powershell
@@ -92,6 +98,9 @@ The currently tested vcpkg tag is ``2022.02.23``. After that, using a developer
# set build options related to the compiler
PS > $env:CMAKE_GENERATOR_PLATFORM="x64"
PS > $env:CMAKE_GENERATOR="Visual Studio 15 2017"
+
+ # install pybind11
+ PS > py -m pip install pybind11
# then, build an installable "wheel" package
PS > py -m pip wheel --no-deps "$env:OUSTER_SDK_PATH\python"
@@ -116,7 +125,9 @@ change. For a local debug build, you can also add the ``-g`` flag.
The Ouster SDK package includes configuration for ``flake8`` and ``mypy``. To run:
.. code:: console
-
+ # install pybind11
+ $ python3 -m pip install pybind11
+
# install and run flake8 linter
$ python3 -m pip install flake8
$ cd ${OUSTER_SDK_PATH}/python
@@ -169,8 +180,8 @@ image, run:
--build-arg BASE=ubuntu:20.04 \
-t ouster-sdk-tox
-the ``BASE`` argument will default to ``ubuntu:18.04``, but can also be set to other docker tags,
-e.g. ``ubuntu:20.04``, ``ubuntu:22.04`` or ``debian:11``. Then, run the container to invoke tox:
+the ``BASE`` argument will default to ``ubuntu:20.04``, but can also be set to other docker tags,
+e.g. ``ubuntu:22.04`` or ``debian:11``. Then, run the container to invoke tox:
.. code:: console
diff --git a/docs/python/examples/conversion.rst b/docs/python/examples/conversion.rst
index cb1d3c6f..735d00cd 100644
--- a/docs/python/examples/conversion.rst
+++ b/docs/python/examples/conversion.rst
@@ -31,10 +31,9 @@ To convert the first ``5`` scans of our sample data from a pcap file, you can tr
The source code of an example below:
-.. literalinclude:: /../python/src/ouster/sdk/examples/pcap.py
+.. literalinclude:: /../python/src/ouster/cli/core/pcap.py
:start-after: [doc-stag-pcap-to-csv]
:end-before: [doc-etag-pcap-to-csv]
- :emphasize-lines: 37-41
:linenos:
:dedent:
@@ -112,4 +111,4 @@ To convert to the first ``5`` scans of our sample data from a pcap file to ``PLY
Checkout the :func:`.examples.pcap.pcap_to_ply` documentation for the example source code.
.. _Open3d File IO: http://www.open3d.org/docs/release/tutorial/geometry/file_io.html#Point-cloud
-.. _plyfile: https://pypi.org/project/plyfile/
\ No newline at end of file
+.. _plyfile: https://pypi.org/project/plyfile/
diff --git a/docs/python/viz/index.rst b/docs/python/viz/index.rst
index 9851f0f0..5528acf0 100644
--- a/docs/python/viz/index.rst
+++ b/docs/python/viz/index.rst
@@ -1,6 +1,6 @@
-=======================
+======================
Point Cloud Visualizer
-=======================
+======================
The Ouster visualization toolkit is written in C++ with Python bindings for Python functionality. It
consists of the following:
diff --git a/docs/python/viz/viz-run.rst b/docs/python/viz/viz-run.rst
index 03f240be..852a7932 100644
--- a/docs/python/viz/viz-run.rst
+++ b/docs/python/viz/viz-run.rst
@@ -51,13 +51,16 @@ Keyboard controls:
``2`` Toggle second return point cloud visibility
``0`` Toggle orthographic camera
``=/-`` Dolly in/out
- ``(space)`` Toggle pause
+ ``?`` Prints key bindings
+ ``space`` Toggle pause
+ ``esc`` Exit visualization
``./,`` Step one frame forward/back
``ctrl + ./,`` Step 10 frames forward/back
``>/<`` Increase/decrease playback rate (during replay)
``shift`` Camera Translation with mouse drag
- ``shift-z`` Save a screenshot of the current view
- ``shift-x`` Toggle a continuous saving of screenshots
+ ``shift+z`` Save a screenshot of the current view
+ ``shift+x`` Toggle a continuous saving of screenshots
+ ``?`` Print keys to standard out
============== ===============================================
..
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index b22f5285..84fad334 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -17,6 +17,13 @@ else()
message(STATUS "No ouster_pcap library available; skipping examples")
endif()
+if(TARGET OusterSDK::ouster_osf)
+ add_executable(osf_reader_example osf_reader_example.cpp)
+ target_link_libraries(osf_reader_example PRIVATE OusterSDK::ouster_osf)
+else()
+ message(STATUS "No ouster_osf library available; skipping examples")
+endif()
+
if(TARGET OusterSDK::ouster_viz)
add_executable(viz_example viz_example.cpp)
target_link_libraries(viz_example PRIVATE OusterSDK::ouster_client OusterSDK::ouster_viz)
diff --git a/examples/client_example.cpp b/examples/client_example.cpp
index d28b419e..abeabf68 100644
--- a/examples/client_example.cpp
+++ b/examples/client_example.cpp
@@ -37,9 +37,8 @@ int main(int argc, char* argv[]) {
return argc == 1 ? EXIT_SUCCESS : EXIT_FAILURE;
}
- // Limit ouster_client log statements to "info" and direct the output to log
- // file rather than the console (default).
- sensor::init_logger("info", "ouster.log");
+ // Limit ouster_client log statements to "info"
+ sensor::init_logger("info");
std::cerr << "Ouster client example " << ouster::SDK_VERSION << std::endl;
/*
diff --git a/examples/osf_reader_example.cpp b/examples/osf_reader_example.cpp
new file mode 100644
index 00000000..b575e220
--- /dev/null
+++ b/examples/osf_reader_example.cpp
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2023, Ouster, Inc.
+ * All rights reserved.
+ *
+ * This file contains example code for working with the LidarScan class of the
+ * C++ Ouster SDK. Please see the sdk docs at static.ouster.dev for clearer
+ * explanations.
+ */
+
+#include
+
+#include "ouster/impl/build.h"
+#include "ouster/osf/reader.h"
+#include "ouster/osf/stream_lidar_scan.h"
+
+using namespace ouster;
+
+int main(int argc, char* argv[]) {
+ if (argc != 2) {
+ std::cerr << "Version: " << ouster::SDK_VERSION_FULL << " ("
+ << ouster::BUILD_SYSTEM << ")"
+ << "\n\nUsage: osf_reader_example " << std::endl;
+
+ return (argc == 1) ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+
+ const std::string osf_file = argv[1];
+
+ // open OSF file
+ osf::Reader reader(osf_file);
+
+ // read all messages from OSF in timestamp order
+ for (const auto& m : reader.messages()) {
+ std::cout << "m.ts: " << m.ts().count() << ", m.id: " << m.id()
+ << std::endl;
+
+ // In OSF file there maybe different type of messages stored, so here we
+ // only interested in LidarScan messages
+ if (m.is()) {
+ // Decoding LidarScan messages
+ auto ls = m.decode_msg();
+
+ // if decoded successfully just print on the screen LidarScan
+ if (ls) {
+ std::cout << "ls = " << to_string(*ls) << std::endl;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ouster_client/CMakeLists.txt b/ouster_client/CMakeLists.txt
index 049b4f48..e6bf44d9 100644
--- a/ouster_client/CMakeLists.txt
+++ b/ouster_client/CMakeLists.txt
@@ -7,7 +7,8 @@ find_package(spdlog REQUIRED)
# ==== Libraries ====
add_library(ouster_client src/client.cpp src/types.cpp src/netcompat.cpp src/lidar_scan.cpp
src/image_processing.cpp src/buffered_udp_source.cpp src/parsing.cpp
- src/sensor_http.cpp src/sensor_http_imp.cpp src/sensor_tcp_imp.cpp src/logging.cpp)
+ src/sensor_http.cpp src/sensor_http_imp.cpp src/sensor_tcp_imp.cpp src/logging.cpp
+ src/profile_extension.cpp)
target_link_libraries(ouster_client
PUBLIC
Eigen3::Eigen
diff --git a/ouster_client/include/ouster/client.h b/ouster_client/include/ouster/client.h
index 81320e90..1ddacca4 100644
--- a/ouster_client/include/ouster/client.h
+++ b/ouster_client/include/ouster/client.h
@@ -14,6 +14,7 @@
#include "ouster/types.h"
#include "ouster/version.h"
+#include "ouster/defaults.h"
namespace ouster {
namespace sensor {
@@ -96,7 +97,7 @@ std::shared_ptr init_client(const std::string& hostname,
lidar_mode ld_mode = MODE_UNSPEC,
timestamp_mode ts_mode = TIME_FROM_UNSPEC,
int lidar_port = 0, int imu_port = 0,
- int timeout_sec = 60);
+ int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
/**
* [BETA] Connect to and configure the sensor and start listening for data via
@@ -119,7 +120,7 @@ std::shared_ptr init_client(const std::string& hostname,
std::shared_ptr mtp_init_client(const std::string& hostname,
const sensor_config& config,
const std::string& mtp_dest_host,
- bool main, int timeout_sec = 60);
+ bool main, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
/** @}*/
@@ -170,7 +171,9 @@ bool read_imu_packet(const client& cli, uint8_t* buf, const packet_format& pf);
*
* @throw runtime_error if the sensor is in ERROR state, the firmware version
* used to initialize the HTTP or TCP client is invalid, the metadata could
- * not be retrieved from the sensor, or the response could not be parsed.
+ * not be retrieved from the sensor within the timeout period,
+ * a timeout occured while waiting for the sensor to finish initializing,
+ * or the response could not be parsed.
*
* @param[in] cli client returned by init_client associated with the connection.
* @param[in] timeout_sec how long to wait for the sensor to initialize.
@@ -178,7 +181,7 @@ bool read_imu_packet(const client& cli, uint8_t* buf, const packet_format& pf);
*
* @return a text blob of metadata parseable into a sensor_info struct.
*/
-std::string get_metadata(client& cli, int timeout_sec = 60,
+std::string get_metadata(client& cli, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS,
bool legacy_format = false);
/**
@@ -193,7 +196,7 @@ std::string get_metadata(client& cli, int timeout_sec = 60,
* @return true if sensor config successfully populated.
*/
bool get_config(const std::string& hostname, sensor_config& config,
- bool active = true);
+ bool active = true, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
// clang-format off
/**
@@ -221,7 +224,7 @@ enum config_flags : uint8_t {
* @return true if config params successfuly set on sensor.
*/
bool set_config(const std::string& hostname, const sensor_config& config,
- uint8_t config_flags = 0);
+ uint8_t config_flags = 0, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
/**
* Return the port used to listen for lidar UDP data.
diff --git a/ouster_client/include/ouster/defaults.h b/ouster_client/include/ouster/defaults.h
new file mode 100644
index 00000000..f0c500a9
--- /dev/null
+++ b/ouster_client/include/ouster/defaults.h
@@ -0,0 +1,3 @@
+#pragma once
+
+constexpr int DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS = 40;
diff --git a/ouster_client/include/ouster/impl/profile_extension.h b/ouster_client/include/ouster/impl/profile_extension.h
new file mode 100644
index 00000000..804e88c0
--- /dev/null
+++ b/ouster_client/include/ouster/impl/profile_extension.h
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2023, Ouster, Inc.
+ * All rights reserved.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "ouster/types.h"
+
+namespace ouster {
+namespace sensor {
+namespace impl {
+
+struct FieldInfo {
+ ChanFieldType ty_tag;
+ size_t offset;
+ uint64_t mask;
+ int shift;
+};
+
+} // namespace impl
+
+void add_custom_profile(
+ int profile_nr, const std::string& name,
+ const std::vector>& fields,
+ size_t chan_data_size);
+
+} // namespace sensor
+} // namespace ouster
diff --git a/ouster_client/include/ouster/lidar_scan.h b/ouster_client/include/ouster/lidar_scan.h
index 3abc9150..6b141394 100644
--- a/ouster_client/include/ouster/lidar_scan.h
+++ b/ouster_client/include/ouster/lidar_scan.h
@@ -52,6 +52,7 @@ class LidarScan {
Header timestamp_;
Header measurement_id_;
Header status_;
+ std::vector pose_;
std::map fields_;
LidarScanFieldTypes field_types_;
@@ -236,6 +237,15 @@ class LidarScan {
/** @copydoc status() */
Eigen::Ref> status() const;
+ /**
+ * Access the vector of poses (per each timestamp).
+ *
+ * @return a reference to vector with poses (4x4) homogeneous.
+ */
+ std::vector& pose();
+ /** @copydoc pose() */
+ const std::vector& pose() const;
+
/**
* Assess completeness of scan.
* @param[in] window The column window to use for validity assessment
@@ -482,47 +492,6 @@ class ScanBatcher {
bool operator()(const uint8_t* packet_buf, LidarScan& ls);
};
-/**
- * Imu Data
- */
-struct Imu {
- union {
- std::array angular_vel;
- struct {
- double wx, wy, wz;
- };
- };
- union {
- std::array linear_accel;
- struct {
- double ax, ay, az;
- };
- };
- union {
- std::array ts;
- struct {
- uint64_t sys_ts, accel_ts, gyro_ts;
- };
- };
-};
-
-/** Equality for Imu */
-inline bool operator==(const Imu& a, const Imu& b) {
- return a.angular_vel == b.angular_vel && a.linear_accel == b.linear_accel &&
- a.ts == b.ts;
-};
-
-/** Not Equality for Imu */
-inline bool operator!=(const Imu& a, const Imu& b) {
- return !(a == b);
-};
-
-std::string to_string(const Imu& imu);
-
-/// Reconstructs buf with UDP imu_packet to osf::Imu object
-void packet_to_imu(const uint8_t* buf, const ouster::sensor::packet_format& pf,
- Imu& imu);
-
} // namespace ouster
#include "ouster/impl/cartesian.h"
diff --git a/ouster_client/include/ouster/sensor_http.h b/ouster_client/include/ouster/sensor_http.h
index 2b764a06..8e01fa40 100644
--- a/ouster_client/include/ouster/sensor_http.h
+++ b/ouster_client/include/ouster/sensor_http.h
@@ -11,6 +11,7 @@
#include
#include
+#include
#include
@@ -122,23 +123,23 @@ class SensorHttp {
*
* @param[in] hostname hostname of the sensor to communicate with.
*/
- static std::string firmware_version_string(const std::string& hostname);
+ static std::string firmware_version_string(const std::string& hostname, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
/**
* Retrieves sensor firmware version information.
*
* @param[in] hostname hostname of the sensor to communicate with.
*/
- static ouster::util::version firmware_version(const std::string& hostname);
+ static ouster::util::version firmware_version(const std::string& hostname, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
/**
* Creates an instance of the SensorHttp interface.
*
* @param[in] hostname hostname of the sensor to communicate with.
*/
- static std::unique_ptr create(const std::string& hostname);
+ static std::unique_ptr create(const std::string& hostname, int timeout_sec = DEFAULT_HTTP_REQUEST_TIMEOUT_SECONDS);
};
} // namespace util
} // namespace sensor
-} // namespace ouster
\ No newline at end of file
+} // namespace ouster
diff --git a/ouster_client/include/ouster/types.h b/ouster_client/include/ouster/types.h
index 3cae90e5..fd433083 100644
--- a/ouster_client/include/ouster/types.h
+++ b/ouster_client/include/ouster/types.h
@@ -367,7 +367,7 @@ struct data_format {
ColumnWindow column_window; ///< window of columns over which sensor fires
UDPProfileLidar udp_profile_lidar; ///< profile of lidar packet
UDPProfileIMU udp_profile_imu; ///< profile of imu packet
- uint16_t fps; ///< frames per second
+ uint16_t fps; ///< frames per second
};
/** Stores necessary information from sensor to parse and project sensor data.
@@ -675,10 +675,13 @@ void check_signal_multiplier(const double signal_multiplier);
* @throw runtime_error if the text is not valid json
*
* @param[in] metadata a text blob returned by get_metadata from client.h.
+ * @param[in] skip_beam_validation whether to skip validation on metdata - not
+ * for use on recorded data or metadata from sensors
*
* @return a sensor_info struct populated with a subset of the metadata.
*/
-sensor_info parse_metadata(const std::string& metadata);
+sensor_info parse_metadata(const std::string& metadata,
+ bool skip_beam_validation = false);
/**
* Parse metadata given path to a json file.
@@ -686,10 +689,13 @@ sensor_info parse_metadata(const std::string& metadata);
* @throw runtime_error if json file does not exist or is malformed.
*
* @param[in] json_file path to a json file containing sensor metadata.
+ * @param[in] skip_beam_validation whether to skip validation on metadata - not
+ * for use on recorded data or metadata from sensors
*
* @return a sensor_info struct populated with a subset of the metadata.
*/
-sensor_info metadata_from_json(const std::string& json_file);
+sensor_info metadata_from_json(const std::string& json_file,
+ bool skip_beam_validation = false);
/**
* Get a string representation of the sensor_info. All fields included. Not
@@ -1165,5 +1171,12 @@ class packet_format final {
*/
const packet_format& get_format(const sensor_info& info);
+namespace impl {
+
+/** Maximum number of allowed lidar profiles */
+constexpr int MAX_NUM_PROFILES = 32;
+
+} // namespace impl
+
} // namespace sensor
} // namespace ouster
diff --git a/ouster_client/src/client.cpp b/ouster_client/src/client.cpp
index c92820b0..e5ab05f4 100644
--- a/ouster_client/src/client.cpp
+++ b/ouster_client/src/client.cpp
@@ -25,8 +25,8 @@
#include "logging.h"
#include "netcompat.h"
-#include "ouster/types.h"
#include "ouster/sensor_http.h"
+#include "ouster/types.h"
using namespace std::chrono_literals;
namespace chrono = std::chrono;
@@ -245,45 +245,51 @@ SOCKET mtp_data_socket(int port, const std::string& udp_dest_host = "",
}
Json::Value collect_metadata(const std::string& hostname, int timeout_sec) {
- auto sensor_http = SensorHttp::create(hostname);
+ // 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
+ auto sensor_http = SensorHttp::create(hostname, timeout_sec);
auto timeout_time =
chrono::steady_clock::now() + chrono::seconds{timeout_sec};
std::string status;
// TODO: can remove this loop when we drop support for FW 2.4
do {
- if (chrono::steady_clock::now() >= timeout_time) return false;
+ if (chrono::steady_clock::now() >= timeout_time) {
+ throw std::runtime_error(
+ "A timeout occurred while waiting for the sensor to initialize."
+ );
+ }
std::this_thread::sleep_for(1s);
status = sensor_http->sensor_info()["status"].asString();
} while (status == "INITIALIZING");
- // not all metadata available when sensor isn't RUNNING
- if (status != "RUNNING") {
+ try {
+ auto metadata = sensor_http->metadata();
+ // merge extra info into metadata
+ metadata["client_version"] = client_version();
+ return metadata;
+ } catch (const std::runtime_error& e) {
throw std::runtime_error(
"Cannot obtain full metadata with sensor status: " + status +
". Please ensure that sensor is not in a STANDBY, UNCONFIGURED, "
"WARMUP, or ERROR state");
}
-
- auto metadata = sensor_http->metadata();
- // merge extra info into metadata
- metadata["client_version"] = client_version();
- return metadata;
}
} // namespace
bool get_config(const std::string& hostname, sensor_config& config,
- bool active) {
- auto sensor_http = SensorHttp::create(hostname);
+ bool active, int timeout_sec) {
+ auto sensor_http = SensorHttp::create(hostname, timeout_sec);
auto res = sensor_http->get_config_params(active);
config = parse_config(res);
return true;
}
bool set_config(const std::string& hostname, const sensor_config& config,
- uint8_t config_flags) {
- auto sensor_http = SensorHttp::create(hostname);
+ uint8_t config_flags, int timeout_sec) {
+ auto sensor_http = SensorHttp::create(hostname, timeout_sec);
// reset staged config to avoid spurious errors
auto config_params = sensor_http->active_config_params();
@@ -358,10 +364,13 @@ bool set_config(const std::string& hostname, const sensor_config& config,
}
std::string get_metadata(client& cli, int timeout_sec, bool legacy_format) {
+ // Note, this function calls functions that throw std::runtime_error
+ // on timeout.
try {
cli.meta = collect_metadata(cli.hostname, timeout_sec);
} catch (const std::exception& e) {
- logger().warn(std::string("Unable to retrieve sensor metadata: ") + e.what());
+ logger().warn(std::string("Unable to retrieve sensor metadata: ") +
+ e.what());
throw;
}
@@ -386,7 +395,7 @@ std::string get_metadata(client& cli, int timeout_sec, bool legacy_format) {
// TODO: remove after release of FW 3.2/3.3 (sufficient warning)
sensor_config config;
get_config(cli.hostname, config);
- auto fw_version = SensorHttp::firmware_version(cli.hostname);
+ auto fw_version = SensorHttp::firmware_version(cli.hostname, timeout_sec);
// only warn for people on the latest FW, as people on older FWs may not
// care
if (fw_version.major >= 3 &&
@@ -550,8 +559,8 @@ client_state poll_client(const client& c, const int timeout_sec) {
}
static bool recv_fixed(SOCKET fd, void* buf, int64_t len) {
- // Have to read longer than len because you need to know if the packet is
- // too large
+ // Have to read longer than len because you need to know if the packet
+ // is too large
int64_t bytes_read = recv(fd, (char*)buf, len + 1, 0);
if (bytes_read == len) {
@@ -577,12 +586,15 @@ int get_lidar_port(client& cli) { return get_sock_port(cli.lidar_fd); }
int get_imu_port(client& cli) { return get_sock_port(cli.imu_fd); }
-bool in_multicast(const std::string& addr) { return IN_MULTICAST(ntohl(inet_addr(addr.c_str()))); }
+bool in_multicast(const std::string& addr) {
+ return IN_MULTICAST(ntohl(inet_addr(addr.c_str())));
+}
/**
* Return the socket file descriptor used to listen for lidar UDP data.
*
- * @param[in] cli client returned by init_client associated with the connection.
+ * @param[in] cli client returned by init_client associated with the
+ * connection.
*
* @return the socket file descriptor.
*/
@@ -591,7 +603,8 @@ extern SOCKET get_lidar_socket_fd(client& cli) { return cli.lidar_fd; }
/**
* Return the socket file descriptor used to listen for imu UDP data.
*
- * @param[in] cli client returned by init_client associated with the connection.
+ * @param[in] cli client returned by init_client associated with the
+ * connection.
*
* @return the socket file descriptor.
*/
diff --git a/ouster_client/src/curl_client.h b/ouster_client/src/curl_client.h
index 2b93afc8..7e0295f9 100644
--- a/ouster_client/src/curl_client.h
+++ b/ouster_client/src/curl_client.h
@@ -6,12 +6,13 @@
class CurlClient : public ouster::util::HttpClient {
public:
- CurlClient(const std::string& base_url_) : HttpClient(base_url_) {
+ CurlClient(const std::string& base_url_, int timeout_seconds) : HttpClient(base_url_) {
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION,
&CurlClient::write_memory_callback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, timeout_seconds);
}
virtual ~CurlClient() override {
diff --git a/ouster_client/src/lidar_scan.cpp b/ouster_client/src/lidar_scan.cpp
index 40481996..55e552ba 100644
--- a/ouster_client/src/lidar_scan.cpp
+++ b/ouster_client/src/lidar_scan.cpp
@@ -93,7 +93,9 @@ struct DefaultFieldsEntry {
size_t n_fields;
};
-Table default_scan_fields{
+using ouster::sensor::impl::MAX_NUM_PROFILES;
+// clang-format off
+Table default_scan_fields{
{{UDPProfileLidar::PROFILE_LIDAR_LEGACY,
{legacy_field_slots.data(), legacy_field_slots.size()}},
{UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16_DUAL,
@@ -126,10 +128,10 @@ LidarScan::LidarScan(size_t w, size_t h, LidarScanFieldTypes field_types)
: timestamp_{Header::Zero(w)},
measurement_id_{Header::Zero(w)},
status_{Header::Zero(w)},
+ pose_(w, mat4d::Identity()),
field_types_{std::move(field_types)},
w{static_cast(w)},
h{static_cast(h)} {
- // TODO: error on duplicate fields
for (const auto& ft : field_types_) {
if (fields_.count(ft.first) > 0)
throw std::invalid_argument("Duplicated fields found");
@@ -206,6 +208,9 @@ Eigen::Ref> LidarScan::status() const {
return status_;
}
+std::vector& LidarScan::pose() { return pose_; }
+const std::vector& LidarScan::pose() const { return pose_; }
+
bool LidarScan::complete(sensor::ColumnWindow window) const {
const auto& status = this->status();
auto start = window.first;
@@ -231,7 +236,7 @@ bool operator==(const LidarScan& a, const LidarScan& b) {
a.field_types_ == b.field_types_ &&
(a.timestamp() == b.timestamp()).all() &&
(a.measurement_id() == b.measurement_id()).all() &&
- (a.status() == b.status()).all();
+ (a.status() == b.status()).all() && a.pose() == b.pose();
}
LidarScanFieldTypes get_field_types(const LidarScan& ls) {
@@ -289,7 +294,7 @@ std::string to_string(const LidarScan& ls) {
auto st = ls.status().cast();
ss << " status = (" << st.minCoeff() << "; " << st.mean() << "; "
<< st.maxCoeff() << ")" << std::endl;
-
+ ss << " poses = (size: " << ls.pose().size() << ")" << std::endl;
ss << "}";
return ss.str();
}
@@ -633,46 +638,4 @@ bool ScanBatcher::operator()(const uint8_t* packet_buf, LidarScan& ls) {
return false;
}
-std::string to_string(const Imu& imu) {
- std::stringstream ss;
- ss << "Imu: ";
- ss << "linear_accel: [";
- for (size_t i = 0; i < imu.linear_accel.size(); ++i) {
- if (i > 0) ss << ", ";
- ss << imu.linear_accel[i];
- }
- ss << "]";
- ss << ", angular_vel = [";
- for (size_t i = 0; i < imu.angular_vel.size(); ++i) {
- if (i > 0) ss << ", ";
- ss << imu.angular_vel[i];
- }
- ss << "]";
- ss << ", ts: [";
- std::array labels{"sys_ts", "accel_ts", "gyro_ts"};
- for (size_t i = 0; i < imu.ts.size(); ++i) {
- if (i > 0) ss << ", ";
- ss << labels[i] << " = ";
- ss << imu.ts[i];
- }
- ss << "]";
- return ss.str();
-}
-
-void packet_to_imu(const uint8_t* buf, const ouster::sensor::packet_format& pf,
- Imu& imu) {
- // Storing all available timestamps
- imu.sys_ts = pf.imu_sys_ts(buf);
- imu.accel_ts = pf.imu_accel_ts(buf);
- imu.gyro_ts = pf.imu_gyro_ts(buf);
-
- imu.linear_accel[0] = pf.imu_la_x(buf);
- imu.linear_accel[1] = pf.imu_la_y(buf);
- imu.linear_accel[2] = pf.imu_la_z(buf);
-
- imu.angular_vel[0] = pf.imu_av_x(buf);
- imu.angular_vel[1] = pf.imu_av_y(buf);
- imu.angular_vel[2] = pf.imu_av_z(buf);
-}
-
} // namespace ouster
diff --git a/ouster_client/src/parsing.cpp b/ouster_client/src/parsing.cpp
index 72ee3d8a..f918e2d9 100644
--- a/ouster_client/src/parsing.cpp
+++ b/ouster_client/src/parsing.cpp
@@ -103,7 +103,7 @@ static const Table five_word_pixel_info{{
{ChanField::RAW32_WORD5, {UINT32, 16, 0, 0}},
}};
-Table profiles{{
+Table profiles{{
{UDPProfileLidar::PROFILE_LIDAR_LEGACY,
{legacy_field_info.data(), legacy_field_info.size(), 12}},
{UDPProfileLidar::PROFILE_RNG19_RFL8_SIG16_NIR16_DUAL,
diff --git a/ouster_client/src/profile_extension.cpp b/ouster_client/src/profile_extension.cpp
new file mode 100644
index 00000000..eed240b8
--- /dev/null
+++ b/ouster_client/src/profile_extension.cpp
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2023, Ouster, Inc.
+ * All rights reserved.
+ */
+
+#include "ouster/impl/profile_extension.h"
+
+#include
+#include
+#include
+#include
+
+#include "ouster/types.h"
+
+template
+using Table = std::array, N>;
+using ouster::sensor::ChanField;
+using ouster::sensor::ChanFieldType;
+using ouster::sensor::UDPProfileLidar;
+using ouster::sensor::impl::MAX_NUM_PROFILES;
+
+namespace ouster {
+
+namespace impl {
+
+// definition copied from lidar_scan.cpp
+struct DefaultFieldsEntry {
+ const std::pair* fields;
+ size_t n_fields;
+};
+
+// lidar_scan.cpp
+extern Table
+ default_scan_fields;
+
+static void extend_default_scan_fields(
+ UDPProfileLidar profile,
+ const std::vector>& scan_fields) {
+ auto end = impl::default_scan_fields.end();
+ auto it = std::find_if(impl::default_scan_fields.begin(), end,
+ [](const auto& kv) { return kv.first == 0; });
+
+ if (it == end)
+ throw std::runtime_error("Limit of scan fields has been reached");
+
+ *it = {profile, {scan_fields.data(), scan_fields.size()}};
+}
+
+} // namespace impl
+
+namespace sensor {
+namespace impl {
+
+struct ExtendedProfile {
+ UDPProfileLidar profile;
+ std::string name;
+ std::vector> slots;
+ std::vector> fields;
+ size_t chan_data_size;
+};
+
+/**
+ * Storage container for dynamically added profiles
+ *
+ * used with std::list because we need pointers to elements inside to stay valid
+ * when new elements are added
+ */
+std::list extended_profiles_data{};
+
+// definition copied from parsing.cpp
+struct ProfileEntry {
+ const std::pair* fields;
+ size_t n_fields;
+ size_t chan_data_size;
+};
+
+// types.cpp
+extern Table
+ udp_profile_lidar_strings;
+// parsing.cpp
+extern Table profiles;
+
+static void extend_profile_entries(
+ UDPProfileLidar profile,
+ const std::vector>& fields,
+ size_t chan_data_size) {
+ auto end = impl::profiles.end();
+ auto it = std::find_if(impl::profiles.begin(), end,
+ [](const auto& kv) { return kv.first == 0; });
+
+ if (it == end)
+ throw std::runtime_error("Limit of parsing profiles has been reached");
+
+ *it = {profile, {fields.data(), fields.size(), chan_data_size}};
+}
+
+} // namespace impl
+
+void extend_udp_profile_lidar_strings(UDPProfileLidar profile,
+ const char* name) {
+ auto begin = impl::udp_profile_lidar_strings.begin();
+ auto end = impl::udp_profile_lidar_strings.end();
+
+ if (end != std::find_if(begin, end, [profile](const auto& p) {
+ return p.first == profile;
+ }))
+ throw std::invalid_argument(
+ "Lidar profile of given number already exists");
+ if (end != std::find_if(begin, end, [name](const auto& p) {
+ return p.second && std::strcmp(p.second, name) == 0;
+ }))
+ throw std::invalid_argument(
+ "Lidar profile of given name already exists");
+
+ auto it =
+ std::find_if(begin, end, [](const auto& kv) { return kv.first == 0; });
+
+ if (it == end)
+ throw std::runtime_error("Limit of lidar profiles has been reached");
+
+ *it = std::make_pair(profile, name);
+}
+
+void add_custom_profile(int profile_nr, // int as UDPProfileLidar
+ const std::string& name,
+ const std::vector>&
+ fields, // int as ChanField
+ size_t chan_data_size) {
+ if (profile_nr == 0)
+ throw std::invalid_argument("profile_nr of 0 are not allowed");
+
+ auto udp_profile = static_cast(profile_nr);
+
+ {
+ // fill in profile
+ impl::ExtendedProfile profile{
+ udp_profile, name, {}, {}, chan_data_size};
+ for (auto&& pair : fields) {
+ ChanField chan = static_cast(pair.first);
+
+ profile.slots.emplace_back(chan, pair.second.ty_tag);
+ profile.fields.emplace_back(chan, pair.second);
+ }
+
+ impl::extended_profiles_data.push_back(std::move(profile));
+ }
+
+ // retake reference to stored data, profile.name.c_str() can get invalidated
+ // after move
+ auto&& profile = impl::extended_profiles_data.back();
+
+ extend_udp_profile_lidar_strings(profile.profile, profile.name.c_str());
+ impl::extend_profile_entries(profile.profile, profile.fields,
+ profile.chan_data_size);
+ ouster::impl::extend_default_scan_fields(profile.profile, profile.slots);
+}
+
+} // namespace sensor
+} // namespace ouster
diff --git a/ouster_client/src/sensor_http.cpp b/ouster_client/src/sensor_http.cpp
index 964c4133..da5fedf7 100644
--- a/ouster_client/src/sensor_http.cpp
+++ b/ouster_client/src/sensor_http.cpp
@@ -14,13 +14,13 @@ using namespace ouster::sensor;
using namespace ouster::sensor::util;
using namespace ouster::sensor::impl;
-string SensorHttp::firmware_version_string(const string& hostname) {
- auto http_client = std::make_unique("http://" + hostname);
+string SensorHttp::firmware_version_string(const string& hostname, int timeout_sec) {
+ auto http_client = std::make_unique("http://" + hostname, timeout_sec);
return http_client->get("api/v1/system/firmware");
}
-version SensorHttp::firmware_version(const string& hostname) {
- auto result = firmware_version_string(hostname);
+version SensorHttp::firmware_version(const string& hostname, int timeout_sec) {
+ auto result = firmware_version_string(hostname, timeout_sec);
auto rgx = std::regex(R"(v(\d+).(\d+)\.(\d+))");
std::smatch matches;
std::regex_search(result, matches, rgx);
@@ -36,8 +36,8 @@ version SensorHttp::firmware_version(const string& hostname) {
}
}
-std::unique_ptr SensorHttp::create(const string& hostname) {
- auto fw = firmware_version(hostname);
+std::unique_ptr SensorHttp::create(const string& hostname, int timeout_sec) {
+ auto fw = firmware_version(hostname, timeout_sec);
if (fw == invalid_version || fw.major < 2) {
throw std::runtime_error(
@@ -52,11 +52,11 @@ std::unique_ptr SensorHttp::create(const string& hostname) {
// FW 2.0 doesn't work properly with http
return std::make_unique(hostname);
case 1:
- return std::make_unique(hostname);
+ return std::make_unique(hostname, timeout_sec);
case 2:
- return std::make_unique(hostname);
+ return std::make_unique(hostname, timeout_sec);
}
}
- return std::make_unique(hostname);
+ return std::make_unique(hostname, timeout_sec);
}
diff --git a/ouster_client/src/sensor_http_imp.cpp b/ouster_client/src/sensor_http_imp.cpp
index 8f03405b..2225057a 100644
--- a/ouster_client/src/sensor_http_imp.cpp
+++ b/ouster_client/src/sensor_http_imp.cpp
@@ -5,8 +5,8 @@
using std::string;
using namespace ouster::sensor::impl;
-SensorHttpImp::SensorHttpImp(const string& hostname)
- : http_client(std::make_unique("http://" + hostname)) {}
+SensorHttpImp::SensorHttpImp(const string& hostname, int timeout_sec)
+ : http_client(std::make_unique("http://" + hostname, timeout_sec)) {}
SensorHttpImp::~SensorHttpImp() = default;
@@ -96,16 +96,16 @@ void SensorHttpImp::execute(const string& url, const string& validation) const {
validation + "]");
}
-SensorHttpImp_2_2::SensorHttpImp_2_2(const string& hostname)
- : SensorHttpImp(hostname) {}
+SensorHttpImp_2_2::SensorHttpImp_2_2(const string& hostname, int timeout_sec)
+ : SensorHttpImp(hostname, timeout_sec) {}
void SensorHttpImp_2_2::set_udp_dest_auto() const {
return execute("api/v1/sensor/cmd/set_udp_dest_auto",
"\"set_config_param\"");
}
-SensorHttpImp_2_1::SensorHttpImp_2_1(const string& hostname)
- : SensorHttpImp_2_2(hostname) {}
+SensorHttpImp_2_1::SensorHttpImp_2_1(const string& hostname, int timeout_sec)
+ : SensorHttpImp_2_2(hostname, timeout_sec) {}
Json::Value SensorHttpImp_2_1::metadata() const {
Json::Value root;
diff --git a/ouster_client/src/sensor_http_imp.h b/ouster_client/src/sensor_http_imp.h
index f8827e23..dc5ae06b 100644
--- a/ouster_client/src/sensor_http_imp.h
+++ b/ouster_client/src/sensor_http_imp.h
@@ -26,7 +26,7 @@ class SensorHttpImp : public util::SensorHttp {
*
* @param[in] hostname hostname of the sensor to communicate with.
*/
- SensorHttpImp(const std::string& hostname);
+ SensorHttpImp(const std::string& hostname, int timeout_sec);
/**
* Deconstruct the sensor http interface.
@@ -130,7 +130,7 @@ class SensorHttpImp : public util::SensorHttp {
// TODO: remove when firmware 2.2 has been fully phased out
class SensorHttpImp_2_2 : public SensorHttpImp {
public:
- SensorHttpImp_2_2(const std::string& hostname);
+ SensorHttpImp_2_2(const std::string& hostname, int timeout_sec);
void set_udp_dest_auto() const override;
};
@@ -145,7 +145,7 @@ class SensorHttpImp_2_1 : public SensorHttpImp_2_2 {
*
* @param[in] hostname hostname of the sensor to communicate with.
*/
- SensorHttpImp_2_1(const std::string& hostname);
+ SensorHttpImp_2_1(const std::string& hostname, int timeout_sec);
/**
* Queries the sensor metadata.
diff --git a/ouster_client/src/types.cpp b/ouster_client/src/types.cpp
index a26cfe92..9f2ffaa9 100644
--- a/ouster_client/src/types.cpp
+++ b/ouster_client/src/types.cpp
@@ -101,7 +101,8 @@ Table chanfield_strings{{
{ChanField::RAW32_WORD9, "RAW32_WORD9"},
}};
-Table udp_profile_lidar_strings{{
+// clang-format off
+Table udp_profile_lidar_strings{{
{PROFILE_LIDAR_LEGACY, "LEGACY"},
{PROFILE_RNG19_RFL8_SIG16_NIR16_DUAL, "RNG19_RFL8_SIG16_NIR16_DUAL"},
{PROFILE_RNG19_RFL8_SIG16_NIR16, "RNG19_RFL8_SIG16_NIR16"},
@@ -368,7 +369,7 @@ static optional rlookup(const impl::Table table,
auto end = table.end();
auto res = std::find_if(table.begin(), end,
[&](const std::pair& p) {
- return std::strcmp(p.second, v) == 0;
+ return p.second && std::strcmp(p.second, v) == 0;
});
return res == end ? nullopt : make_optional(res->first);
@@ -760,7 +761,8 @@ static data_format parse_data_format(const Json::Value& root) {
if (profile) {
format.udp_profile_lidar = profile.value();
} else {
- throw std::runtime_error{"Unexpected udp lidar profile"};
+ throw std::runtime_error{"Unexpected udp lidar profile: " +
+ root["udp_profile_lidar"].asString()};
}
} else {
logger().warn("No lidar profile found. Using LEGACY lidar profile");
@@ -791,7 +793,7 @@ static data_format parse_data_format(const Json::Value& root) {
return format;
} // namespace sensor
-static sensor_info parse_legacy(const std::string& meta) {
+static sensor_info parse_legacy(const std::string& meta, bool skip_beam_validation) {
Json::Value root{};
Json::CharReaderBuilder builder{};
std::string errors{};
@@ -1030,8 +1032,12 @@ static sensor_info parse_legacy(const std::string& meta) {
}
};
- zero_check(info.beam_altitude_angles, "beam_altitude_angles");
- zero_check(info.beam_azimuth_angles, "beam_azimuth_angles");
+ if (!skip_beam_validation) {
+ zero_check(info.beam_altitude_angles, "beam_altitude_angles");
+ zero_check(info.beam_azimuth_angles, "beam_azimuth_angles");
+ } else {
+ logger().warn("Skipping all 0 beam angle check");
+ }
info.extrinsic = mat4d::Identity();
@@ -1101,7 +1107,7 @@ std::string convert_to_legacy(const std::string& metadata) {
return Json::writeString(write_builder, result);
}
-sensor_info parse_metadata(const std::string& metadata) {
+sensor_info parse_metadata(const std::string& metadata, bool skip_beam_validation) {
Json::Value root{};
Json::CharReaderBuilder builder{};
std::string errors{};
@@ -1116,15 +1122,15 @@ sensor_info parse_metadata(const std::string& metadata) {
sensor_info info{};
if (is_new_format(metadata)) {
logger().debug("parsing non-legacy metadata format");
- info = parse_legacy(convert_to_legacy(metadata));
+ info = parse_legacy(convert_to_legacy(metadata), skip_beam_validation);
} else {
logger().debug("parsing legacy metadata format");
- info = parse_legacy(metadata);
+ info = parse_legacy(metadata, skip_beam_validation);
}
return info;
}
-sensor_info metadata_from_json(const std::string& json_file) {
+sensor_info metadata_from_json(const std::string& json_file, bool skip_beam_validation) {
std::stringstream buf{};
std::ifstream ifs{};
ifs.open(json_file);
@@ -1137,7 +1143,7 @@ sensor_info metadata_from_json(const std::string& json_file) {
throw std::runtime_error{ss.str()};
}
- return parse_metadata(buf.str());
+ return parse_metadata(buf.str(), skip_beam_validation);
}
std::string to_string(const sensor_info& info) {
diff --git a/ouster_osf/CHANGELOG.rst b/ouster_osf/CHANGELOG.rst
new file mode 100644
index 00000000..ef9fdd82
--- /dev/null
+++ b/ouster_osf/CHANGELOG.rst
@@ -0,0 +1,6 @@
+2023-07-01
+===========
+
+Initial public Ouster OSF soft release
+
+* C++ OSF lib with Python binding
\ No newline at end of file
diff --git a/ouster_osf/CMakeLists.txt b/ouster_osf/CMakeLists.txt
new file mode 100644
index 00000000..768c082b
--- /dev/null
+++ b/ouster_osf/CMakeLists.txt
@@ -0,0 +1,158 @@
+# ==== Debug params ============
+# == NOTE(pb): Left intentionally
+# set(CMAKE_VERBOSE_MAKEFILE OFF)
+# set(CMAKE_FIND_DEBUG_MODE OFF)
+
+option(OUSTER_OSF_NO_MMAP "Don't use mmap(), useful for WASM targets" OFF)
+option(OUSTER_OSF_NO_THREADING "Don't use threads, useful for WASM targets" OFF)
+
+# ==== Requirements ====
+find_package(ZLIB REQUIRED)
+find_package(PNG REQUIRED)
+find_package(Eigen3 REQUIRED)
+find_package(jsoncpp REQUIRED)
+find_package(spdlog REQUIRED)
+
+# TODO: Extract to a separate FindFlatbuffers cmake file
+# 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)
+ 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
+# builds.
+# STORY: We need to make it static (but with -fPIC) for Python bindings.
+# However in Debian packages we can only use shared libs because static
+# are not compiled with PIC. Though in vcpkg it uses static lib,
+# which we've confirmed to be the case and what we need for manylinux.
+# find_library(flatbuffers_lib NAMES flatbuffers REQUIRED)
+# set(flatbuffers_lib flatbuffers::flatbuffers)
+
+# === Flatbuffer builder functions ====
+include(cmake/osf_fb_utils.cmake)
+
+set(OSF_FB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/fb)
+
+set(FB_SOURCE_GEN_DIR ${CMAKE_CURRENT_BINARY_DIR}/fb_source_generated)
+set(FB_BINARY_SCHEMA_DIR ${CMAKE_CURRENT_BINARY_DIR}/fb_binary_schemas)
+
+set(FB_MODULES_TO_BUILD os_sensor streaming ml)
+
+# ======= Typescript Flatbuffer Generators =====
+set(FB_TS_GENERATED_DIR ${FB_SOURCE_GEN_DIR}/ts)
+build_ts_fb_modules(
+ TARGET ts_gen
+ FB_DIR "${OSF_FB_DIR}"
+ FB_MODULES "${FB_MODULES_TO_BUILD}"
+ SOURCE_GEN_DIR "${FB_TS_GENERATED_DIR}"
+ )
+
+# ======= Python Flatbuffer Generators =====
+set(FB_PYTHON_GENERATED_DIR ${FB_SOURCE_GEN_DIR}/python)
+build_py_fb_modules(
+ TARGET py_gen
+ FB_DIR "${OSF_FB_DIR}"
+ SOURCE_GEN_DIR "${FB_PYTHON_GENERATED_DIR}"
+ )
+
+# ======= C++ Flatbuffer Generators =====
+set(FB_CPP_GENERATED_DIR ${FB_SOURCE_GEN_DIR}/cpp)
+build_cpp_fb_modules(
+ TARGET cpp_gen
+ FB_DIR "${OSF_FB_DIR}"
+ FB_MODULES "${FB_MODULES_TO_BUILD}"
+ SOURCE_GEN_DIR "${FB_CPP_GENERATED_DIR}"
+ BINARY_SCHEMA_DIR "${FB_BINARY_SCHEMA_DIR}"
+ )
+
+# === Always generate C++ stubs ==============
+# and skip Typescript and Python code from FB specs generation
+# since they not needed during a regular OSF lib builds
+add_custom_target(all_fb_gen ALL DEPENDS cpp_gen) # ts_gen py_gen
+
+add_library(ouster_osf STATIC src/compat_ops.cpp
+ src/png_tools.cpp
+ src/basics.cpp
+ src/crc32.cpp
+ src/metadata.cpp
+ src/writer.cpp
+ src/meta_lidar_sensor.cpp
+ src/meta_extrinsics.cpp
+ src/meta_streaming_info.cpp
+ src/stream_lidar_scan.cpp
+ src/layout_standard.cpp
+ src/layout_streaming.cpp
+ src/file.cpp
+ src/reader.cpp
+ src/operations.cpp
+ src/json_utils.cpp
+ src/fb_utils.cpp
+ src/pcap_source.cpp
+)
+
+if (OUSTER_OSF_NO_MMAP)
+ target_compile_definitions(ouster_osf PRIVATE OUSTER_OSF_NO_MMAP)
+endif()
+
+if (OUSTER_OSF_NO_THREADING)
+ target_compile_definitions(ouster_osf PRIVATE OUSTER_OSF_NO_THREADING)
+endif()
+
+# Include Flatbuffers generated C++ headers
+target_include_directories(ouster_osf PUBLIC
+ $
+ $
+)
+target_link_libraries(ouster_osf
+ PUBLIC
+ OusterSDK::ouster_client OusterSDK::ouster_pcap PNG::PNG
+ flatbuffers::flatbuffers ZLIB::ZLIB jsoncpp_lib
+)
+target_include_directories(ouster_osf PUBLIC
+ $
+ $
+ ${EIGEN3_INCLUDE_DIR}
+)
+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...
+get_target_property(CLIENT_OPTIONS OusterSDK::ouster_client COMPILE_OPTIONS)
+if(CLIENT_OPTIONS MATCHES ".*-mavx2.*")
+ target_compile_options(ouster_osf PUBLIC -mavx2)
+endif()
+
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
+ enable_testing()
+ add_subdirectory(tests)
+endif()
+
+# ==== Install =========================================================
+install(TARGETS ouster_osf
+ EXPORT ouster-sdk-targets
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib
+ RUNTIME DESTINATION bin
+ INCLUDES DESTINATION include)
+
+install(DIRECTORY include/ouster DESTINATION include)
+install(DIRECTORY ${FB_CPP_GENERATED_DIR}/ DESTINATION include/fb_generated)
diff --git a/ouster_osf/README.rst b/ouster_osf/README.rst
new file mode 100644
index 00000000..a9b54725
--- /dev/null
+++ b/ouster_osf/README.rst
@@ -0,0 +1,31 @@
+===================
+Ouster OSF library
+===================
+
+:Maintainers: Pavlo Bashmakov
+:Description: OSF File format and tools to store/process lidar sensor data
+:Project-type: lib
+
+Summary
+-------
+
+Ouster OSF is an extendable file format to efficiently store the lidar information
+with sensor intrinsics and streaming capabilities.
+
+This is the beginning of the public OSF to be able to read and process OSF files
+generated by Ouster tools (i.e. mapping or Data App)
+
+
+Build & Test
+-------------
+
+See the main Ouser SDK Building docs for details.
+
+
+TODOs
+-----
+
+- [ ] OSF API Documentaion
+- [ ] OSF Usage Documentaion
+- [ ] OSF examples
+- [ ] more OSF related tools ...
\ No newline at end of file
diff --git a/ouster_osf/cmake/osf_fb_utils.cmake b/ouster_osf/cmake/osf_fb_utils.cmake
new file mode 100644
index 00000000..4f909d4b
--- /dev/null
+++ b/ouster_osf/cmake/osf_fb_utils.cmake
@@ -0,0 +1,395 @@
+# =======================================================================
+# ======== Flatbuffers Generators for C++/Python/Javasctipt =============
+
+# This file provides three generator functions that makes rules for convertion
+# of Flatbuffer's specs (*.fbs) to the corresponding language stubs:
+# - 'build_cpp_fb_modules' - for C++ include headers
+# - 'build_ts_fb_modules' - for Typescript modules
+# - 'build_py_fb_modules' - for Python modules
+
+# All functions have the common structure and use the common parameters
+# (with binary schemas generators available only in C++):
+
+# TARGET - (REQUIRED) Name of the target that will combine all custom
+# generator commands. It can later be used as dependency for other
+# targets (including ALL - to always generate code if needed)
+
+# FB_DIR - (OPTIONAL) Root of Flatbuffers definitions, where specs organized
+# by modules. (see FB_MODULES param)
+# Example layout of FB_DIR ('project-luna/fb') for imaginary
+# 'project-luna':
+# - project-luna
+# - fb
+# - background_rad.fbs
+# - rover_base
+# - rover_state.fbs
+# - sars_stream.fbs
+# - laser_eye
+# - photons_stream.fbs
+# - neutrino_extrinsics.fbs
+# default: if ommited defaults to 'current-project/fb'
+# (i.e. "${CMAKE_CURRENT_SOURCE_DIR}/fb")
+
+# FB_MODULES - (OPTIONAL) List of modules (i.e. subdirectories of FB_DIR)
+# to use for code generation. The resulting subdirectories
+# structure is preserved (Typescript/C++ only).
+# For example (C++) file 'FB_DIR/rover_base/sars_stream.fbs' will
+# be converted to 'SOURCE_GEN_DIR/reover_base/sars_stream_generated.h'
+# For the 'project-luna' above we have two modules:
+# 'rover_base' and 'laser_eye'.
+# default: empty, i.e. no modules used for generation and only
+# root *.fbs files (FB_DIR/*.fbs) used. (except Python,
+# where FB_MODULES is not used and all files recursively
+# is generated)
+
+# SOURCE_GEN_DIR - (OPTIONAL) Location of the generated source files.
+# It's also available as TARGET property by SOURCE_GEN_DIR
+# name which is usefull if using default SOURCE_GEN_DIR
+# location.
+# default: "${CMAKE_CURRENT_BINARY_DIR}/fb_source_generated"
+
+# BINARY_SCHEMA_DIR - (OPTIONAL) Location of the generated binary schemas
+# files (*.bfbs).
+# It's also available as property BINARY_SCHEMA_DIR
+# for generated TARGET.
+# default: "${CMAKE_CURRENT_BINARY_DIR}/fb_binary_schemas"
+
+
+# ======= Common Initializer =============================================
+
+macro(initialize_osf_fb_utils_defaults)
+ # Use flatc reolve from flatbuffers/CMake/BuildFlatBuffers.cmake source
+ # Test if including from FindFlatBuffers
+ if(FLATBUFFERS_FLATC_EXECUTABLE)
+ set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE})
+ else()
+ set(FLATC flatc)
+ endif()
+ set(FB_DEFAULT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fb")
+ set(FB_DEFAULT_SOURCE_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/fb_source_generated")
+ set(FB_DEFAULT_BINARY_SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}/fb_binary_schemas")
+endmacro()
+
+# --- Log/debug helper function
+# logs messages if FB_VERBOSE is true (in cmake sense, e.g. 1, Y, TRUE, YES, ON)
+macro(fb_log MSG)
+ if(FB_VERBOSE)
+ message(STATUS ${MSG})
+ endif()
+endmacro()
+
+
+# ======= C++ Generation =============================================
+
+# --- Helper macro:
+# Generates Flatbuffers binary schema generation target for FB_FILE in FB_MODULE
+# Result placed in RESULT_DIR
+# Side effect: Variable ALL_GEN_FILE appended with generated file.
+macro(make_binary_schemas_command)
+ set(PARSE_ONE_VALUE_ARGS FB_FILE FB_MODULE RESULT_DIR)
+ cmake_parse_arguments(MOD_ARG "" "${PARSE_ONE_VALUE_ARGS}" "" ${ARGN})
+ fb_log("BIN MOD: FB_FILE = ${MOD_ARG_FB_FILE}")
+ fb_log("BIN MOD: FB_MODULE = ${MOD_ARG_FB_MODULE}")
+ fb_log("BIN MOD: RESULT_DIR = ${MOD_ARG_RESULT_DIR}")
+ if (NOT ${MOD_ARG_FB_MODULE} STREQUAL "")
+ set(BIN_DIR ${MOD_ARG_RESULT_DIR}/${MOD_ARG_FB_MODULE})
+ else()
+ set(BIN_DIR ${MOD_ARG_RESULT_DIR})
+ endif()
+ get_filename_component(FB_NAME ${MOD_ARG_FB_FILE} NAME_WE)
+ set(BIN_FILE ${BIN_DIR}/${FB_NAME}.bfbs)
+ add_custom_command(
+ OUTPUT ${BIN_FILE}
+ DEPENDS ${MOD_ARG_FB_FILE}
+ COMMAND
+ ${FLATC} -b --schema
+ -o ${BIN_DIR}
+ ${MOD_ARG_FB_FILE}
+ )
+ list(APPEND ALL_GEN_FILES ${BIN_FILE})
+endmacro()
+
+# --- Helper macro:
+# Generates Flatbuffers C++ schema generation target for FB_FILE in FB_MODULE
+# Result placed in RESULT_DIR
+# Side effect: Variable ALL_GEN_FILE appended with generated file.
+macro(make_cpp_gen_command)
+ set(PARSE_ONE_VALUE_ARGS FB_FILE FB_MODULE RESULT_DIR)
+ cmake_parse_arguments(MOD_ARG "" "${PARSE_ONE_VALUE_ARGS}" "" ${ARGN})
+ fb_log("CPP MOD: FB_FILE = ${MOD_ARG_FB_FILE}")
+ fb_log("CPP MOD: FB_MODULE = ${MOD_ARG_FB_MODULE}")
+ fb_log("CPP MOD: RESULT_DIR = ${MOD_ARG_RESULT_DIR}")
+ if (NOT ${MOD_ARG_FB_MODULE} STREQUAL "")
+ set(GEN_DIR ${MOD_ARG_RESULT_DIR}/${MOD_ARG_FB_MODULE})
+ else()
+ set(GEN_DIR ${MOD_ARG_RESULT_DIR})
+ endif()
+ get_filename_component(FB_NAME ${MOD_ARG_FB_FILE} NAME_WE)
+ set(GEN_FILE ${GEN_DIR}/${FB_NAME}_generated.h)
+ add_custom_command(
+ OUTPUT ${GEN_FILE}
+ DEPENDS ${MOD_ARG_FB_FILE}
+ COMMAND
+ ${FLATC} --cpp --no-includes --scoped-enums
+ -o ${GEN_DIR}
+ ${MOD_ARG_FB_FILE}
+ )
+ list(APPEND ALL_GEN_FILES ${GEN_FILE})
+endmacro()
+
+# C++ Flatbuffer generators
+# Params: TARGET (REQUIRED), FB_DIR, FB_MODULES, SOURCE_GEN_DIR, BINARY_SCHEMA_DIR
+# Properties available on generated TARGET: SOURCE_GEN_DIR, BINARY_SCHEMA_DIR
+function(build_cpp_fb_modules)
+
+ initialize_osf_fb_utils_defaults()
+
+ set(PARSE_OPTIONS "")
+ set(PARSE_ONE_VALUE_ARGS
+ TARGET
+ FB_DIR
+ SOURCE_GEN_DIR
+ BINARY_SCHEMA_DIR
+ )
+ set(PARSE_MULTI_VALUE_ARGS FB_MODULES)
+ cmake_parse_arguments(ARGS "${PARSE_OPTIONS}" "${PARSE_ONE_VALUE_ARGS}"
+ "${PARSE_MULTI_VALUE_ARGS}" ${ARGN} )
+
+ fb_log("... BUILDING CPP FB MODULES ............ ")
+ fb_log("ARGS_TARGET = ${ARGS_TARGET}")
+ fb_log("ARGS_FB_DIR = ${ARGS_FB_DIR}")
+ fb_log("ARGS_FB_MODULES = ${ARGS_FB_MODULES}")
+ fb_log("ARG_SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+ fb_log("ARG_BINARY_GEN_DIR = ${ARGS_BINARY_SCHEMA_DIR}")
+
+ if (NOT DEFINED ARGS_FB_DIR)
+ set(ARGS_FB_DIR "${FB_DEFAULT_ROOT_DIR}")
+ fb_log("using default FB_DIR = ${ARGS_FB_DIR}")
+ endif()
+
+ if (NOT DEFINED ARGS_SOURCE_GEN_DIR)
+ set(ARGS_SOURCE_GEN_DIR "${FB_DEFAULT_SOURCE_GEN_DIR}/cpp")
+ fb_log("using default SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+ endif()
+
+ if (NOT DEFINED ARGS_BINARY_SCHEMA_DIR)
+ set(ARGS_BINARY_SCHEMA_DIR "${FB_DEFAULT_BINARY_SCHEMA_DIR}")
+ fb_log("using default BINARY_SCHEMA_DIR = ${ARGS_BINARY_SCHEMA_DIR}")
+ endif()
+
+ # List accumulated in macros 'make_*_command' calls
+ set(ALL_GEN_FILES "")
+
+ file(GLOB MOD_FB_CORE_FILES LIST_DIRECTORIES false ${ARGS_FB_DIR}/*.fbs)
+ foreach(FB_FILE ${MOD_FB_CORE_FILES})
+
+ make_cpp_gen_command(
+ FB_FILE ${FB_FILE}
+ RESULT_DIR ${ARGS_SOURCE_GEN_DIR}
+ )
+
+ if (NOT ${ARGS_BINARY_SCHEMA_DIR} STREQUAL "")
+ make_binary_schemas_command(
+ FB_FILE ${FB_FILE}
+ RESULT_DIR ${ARGS_BINARY_SCHEMA_DIR}
+ )
+ endif()
+
+ endforeach()
+
+ foreach(FB_MODULE ${ARGS_FB_MODULES})
+ file(GLOB FB_MODULE_FILES LIST_DIRECTORIES false ${ARGS_FB_DIR}/${FB_MODULE}/*.fbs)
+
+ foreach(FB_FILE ${FB_MODULE_FILES})
+
+ make_cpp_gen_command(
+ FB_FILE ${FB_FILE}
+ FB_MODULE ${FB_MODULE}
+ RESULT_DIR ${ARGS_SOURCE_GEN_DIR}
+ )
+
+ if (NOT ${ARGS_BINARY_SCHEMA_DIR} STREQUAL "")
+ make_binary_schemas_command(
+ FB_FILE ${FB_FILE}
+ FB_MODULE ${FB_MODULE}
+ RESULT_DIR ${ARGS_BINARY_SCHEMA_DIR}
+ )
+ endif()
+
+ endforeach()
+
+ endforeach()
+
+ add_custom_target(${ARGS_TARGET} DEPENDS ${ALL_GEN_FILES}
+ COMMENT "Generating C++ code from Flatbuffers spec")
+
+ set_property(TARGET ${ARGS_TARGET}
+ PROPERTY SOURCE_GEN_DIR
+ ${ARGS_SOURCE_GEN_DIR}
+ )
+
+ set_property(TARGET ${ARGS_TARGET}
+ PROPERTY BINARY_SCHEMA_DIR
+ ${ARGS_BINARY_SCHEMA_DIR}
+ )
+
+endfunction()
+
+
+# ======= Typescript Generation ========================================
+
+# --- Helper macro:
+# Generates Flatbuffers Typescript generation command for FB_FILE in FB_MODULE
+# Result placed in RESULT_DIR
+# Side effect: Variable ALL_GEN_FILE appended with generated file.
+macro(make_ts_gen_command)
+ set(PARSE_ONE_VALUE_ARGS FB_FILE FB_MODULE RESULT_DIR)
+ cmake_parse_arguments(MOD_ARG "" "${PARSE_ONE_VALUE_ARGS}" "" ${ARGN})
+ fb_log("TS MOD: FB_FILE = ${MOD_ARG_FB_FILE}")
+ fb_log("TS MOD: FB_MODULE = ${MOD_ARG_FB_MODULE}")
+ fb_log("TS MOD: RESULT_DIR = ${MOD_ARG_RESULT_DIR}")
+ if (NOT ${MOD_ARG_FB_MODULE} STREQUAL "")
+ set(GEN_DIR ${MOD_ARG_RESULT_DIR}/${MOD_ARG_FB_MODULE})
+ else()
+ set(GEN_DIR ${MOD_ARG_RESULT_DIR})
+ endif()
+ get_filename_component(FB_NAME ${MOD_ARG_FB_FILE} NAME_WE)
+ set(GEN_FILE ${GEN_DIR}/${FB_NAME}_generated.ts)
+ add_custom_command(
+ OUTPUT ${GEN_FILE}
+ DEPENDS ${MOD_ARG_FB_FILE}
+ COMMAND
+ ${FLATC} --ts
+ -o ${GEN_DIR}
+ ${MOD_ARG_FB_FILE}
+ )
+ list(APPEND ALL_GEN_FILES ${GEN_FILE})
+endmacro()
+
+# Typescript Flatbuffer generators
+# Params: TARGET (REQUIRED), FB_DIR, FB_MODULES, SOURCE_GEN_DIR
+# Properties available on generated TARGET: SOURCE_GEN_DIR
+function(build_ts_fb_modules)
+
+ initialize_osf_fb_utils_defaults()
+
+ set(PARSE_OPTIONS "")
+ set(PARSE_ONE_VALUE_ARGS
+ TARGET
+ FB_DIR
+ SOURCE_GEN_DIR
+ )
+ set(PARSE_MULTI_VALUE_ARGS FB_MODULES)
+ cmake_parse_arguments(ARGS "${PARSE_OPTIONS}" "${PARSE_ONE_VALUE_ARGS}"
+ "${PARSE_MULTI_VALUE_ARGS}" ${ARGN} )
+
+ fb_log("... BUILDING TS FB MODULES ............ ")
+ fb_log("ARGS_TARGET = ${ARGS_TARGET}")
+ fb_log("ARGS_FB_DIR = ${ARGS_FB_DIR}")
+ fb_log("ARGS_FB_MODULES = ${ARGS_FB_MODULES}")
+ fb_log("ARG_SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+
+ if (NOT DEFINED ARGS_FB_DIR)
+ set(ARGS_FB_DIR "${FB_DEFAULT_ROOT_DIR}")
+ fb_log("using default FB_DIR = ${ARGS_FB_DIR}")
+ endif()
+
+ if (NOT DEFINED ARGS_SOURCE_GEN_DIR)
+ set(ARGS_SOURCE_GEN_DIR "${FB_DEFAULT_SOURCE_GEN_DIR}/ts")
+ fb_log("using default SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+ endif()
+
+ # List accumulated in macros 'make_*_command' calls
+ set(ALL_GEN_FILES "")
+
+ file(GLOB MOD_FB_CORE_FILES LIST_DIRECTORIES false ${ARGS_FB_DIR}/*.fbs)
+ foreach(FB_FILE ${MOD_FB_CORE_FILES})
+
+ make_ts_gen_command(
+ FB_FILE ${FB_FILE}
+ FB_MODULE ${FB_MODULE}
+ RESULT_DIR ${ARGS_SOURCE_GEN_DIR}
+ )
+
+ endforeach()
+
+ foreach(FB_MODULE ${ARGS_FB_MODULES})
+
+ file(GLOB FB_MODULE_FILES LIST_DIRECTORIES false ${ARGS_FB_DIR}/${FB_MODULE}/*.fbs)
+
+ foreach(FB_FILE ${FB_MODULE_FILES})
+
+ make_ts_gen_command(
+ FB_FILE ${FB_FILE}
+ FB_MODULE ${FB_MODULE}
+ RESULT_DIR ${ARGS_SOURCE_GEN_DIR}
+ )
+
+ endforeach()
+
+ endforeach()
+
+ add_custom_target(${ARGS_TARGET} DEPENDS ${ALL_GEN_FILES}
+ COMMENT "Generating Typescript code from Flatbuffers spec")
+
+ set_property(TARGET ${ARGS_TARGET}
+ PROPERTY SOURCE_GEN_DIR
+ ${ARGS_SOURCE_GEN_DIR}
+ )
+
+endfunction()
+
+# ======= Python Generation =============================================
+
+# Python Flatbuffer generators
+# Params: TARGET (REQUIRED), FB_DIR, SOURCE_GEN_DIR
+# Properties available on generated TARGET: SOURCE_GEN_DIR
+function(build_py_fb_modules)
+
+ initialize_osf_fb_utils_defaults()
+
+ set(PARSE_OPTIONS "")
+ set(PARSE_ONE_VALUE_ARGS
+ TARGET
+ FB_DIR
+ SOURCE_GEN_DIR
+ )
+ set(PARSE_MULTI_VALUE_ARGS "")
+ cmake_parse_arguments(ARGS "${PARSE_OPTIONS}" "${PARSE_ONE_VALUE_ARGS}"
+ "${PARSE_MULTI_VALUE_ARGS}" ${ARGN} )
+ fb_log("... BUILDING PYTHON FB MODULES ............ ")
+ fb_log("ARGS_TARGET = ${ARGS_TARGET}")
+ fb_log("ARGS_FB_DIR = ${ARGS_FB_DIR}")
+ fb_log("ARG_SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+
+ if (NOT DEFINED ARGS_FB_DIR)
+ set(ARGS_FB_DIR "${FB_DEFAULT_ROOT_DIR}")
+ fb_log("using default FB_DIR = ${ARGS_FB_DIR}")
+ endif()
+
+ if (NOT DEFINED ARGS_SOURCE_GEN_DIR)
+ set(ARGS_SOURCE_GEN_DIR "${FB_DEFAULT_SOURCE_GEN_DIR}/cpp")
+ fb_log("using default SOURCE_GEN_DIR = ${ARGS_SOURCE_GEN_DIR}")
+ endif()
+
+ file(GLOB_RECURSE ALL_FBS_FILES LIST_DIRECTORIES false ${ARGS_FB_DIR}/*.fbs)
+
+ # Using random file names to allow multiproject use of this function
+ string(RANDOM LENGTH 4 PY_GEN_RANDOM)
+ set(PY_GEN_TIMESTAMP_FILE ${CMAKE_CURRENT_BINARY_DIR}/py_gen_${PY_GEN_RANDOM}.timestamp)
+ add_custom_command(
+ OUTPUT ${PY_GEN_TIMESTAMP_FILE}
+ DEPENDS ${ALL_FBS_FILES}
+ COMMAND ${FLATC} --python -o ${ARGS_SOURCE_GEN_DIR} ${ALL_FBS_FILES}
+ COMMAND ${CMAKE_COMMAND} -E touch ${PY_GEN_TIMESTAMP_FILE}
+ )
+
+ add_custom_target(${ARGS_TARGET} DEPENDS ${PY_GEN_TIMESTAMP_FILE}
+ COMMENT "Generating Python code from Flatbuffers spec")
+
+ set_property(TARGET ${ARGS_TARGET}
+ PROPERTY SOURCE_GEN_DIR
+ ${ARGS_SOURCE_GEN_DIR}
+ )
+
+endfunction()
diff --git a/ouster_osf/fb/chunk.fbs b/ouster_osf/fb/chunk.fbs
new file mode 100644
index 00000000..3d6a301d
--- /dev/null
+++ b/ouster_osf/fb/chunk.fbs
@@ -0,0 +1,27 @@
+namespace ouster.osf.v2;
+
+// See 'msgs/*.fb' files for messages and corresponding stream specs
+
+table StampedMessage {
+
+ // timestamp in nanoseconds
+ ts:uint64 (key);
+
+ // 'id' should always refer to the existing Metadata.entries[] field
+ // that is then used to reconstruct the 'StampedMessage.buffer' field to
+ // the corresponding msg Flatbuffer object.
+ // (e.g metadata.entries[].id == StampedMessage.id)
+ id:uint32;
+
+ // Flatbuffer encoded object that described in Metadata entry (by 'id')
+ buffer:[uint8];
+}
+
+table Chunk {
+ // List of messages in chunk (sorting by ts is not guaranteed)
+ messages:[StampedMessage];
+}
+
+file_identifier "OSF!";
+file_extension "osfc";
+root_type Chunk;
diff --git a/ouster_osf/fb/header.fbs b/ouster_osf/fb/header.fbs
new file mode 100644
index 00000000..3fca9871
--- /dev/null
+++ b/ouster_osf/fb/header.fbs
@@ -0,0 +1,37 @@
+namespace ouster.osf.v2;
+
+
+enum HEADER_STATUS:uint8 {
+ UNKNOWN = 0,
+
+ // INVALID is used during writing and generally means that
+ // file wasn't finished properly
+ INVALID,
+
+ // VALID means that the OSF file is properly finished and
+ // reader might expect the `metadata_offset` pointing to valid metadata
+ // object
+ VALID
+}
+
+table Header {
+ version:uint64;
+ status:HEADER_STATUS;
+
+ // Offset to the Metadata object from the beginning of the file
+ metadata_offset:uint64 = 1;
+
+ // Length of the file in bytes, should be used for validation
+ // that file was constructed and finished correctly
+ file_length:uint64 = 1;
+
+ // metadata_offset and file_length defaults to 1 (and not 0), so
+ // we can write actual zero without forcing Flatbuffer to do so.
+ // Because when we use the default value the value is actually
+ // not inserted in the buffer and so it is smaller but we want
+ // it to have a stable byte size.
+}
+
+file_identifier "OSF$";
+file_extension "osfh";
+root_type Header;
\ No newline at end of file
diff --git a/ouster_osf/fb/metadata.fbs b/ouster_osf/fb/metadata.fbs
new file mode 100644
index 00000000..acd44a20
--- /dev/null
+++ b/ouster_osf/fb/metadata.fbs
@@ -0,0 +1,38 @@
+namespace ouster.osf.v2;
+
+struct ChunkOffset {
+ // lowest timestamp of a StampedMessage in a Chunk
+ start_ts:uint64;
+ // highest timestamp of a StampedMessage in a Chunk
+ end_ts:uint64;
+ // offset from the beginning of Chunks (the address following Header's CRC32)
+ offset:uint64;
+}
+
+// Contains file information (streams metadata, file/session data, etc)
+table MetadataEntry {
+ // used in references from other metadata entries
+ // and from StampedMessage.id
+ id:uint32 (key);
+
+ // type identifier of the stream or metas,
+ // (e.g. 'ouster/os_sensor/LidarSensor') used to dispatch to the fb spec
+ // for parsing 'buffer' field.
+ type:string;
+
+ // Flatbuffer encoded object of 'type' (see 'type' field description)
+ buffer:[uint8];
+}
+
+table Metadata {
+ id:string;
+ start_ts:uint64;
+ end_ts:uint64;
+ chunks:[ChunkOffset];
+ entries:[MetadataEntry]; // required to be sorted and unique
+ // by MetadataEntry.id for fast lookups
+}
+
+file_identifier "OSF#";
+file_extension "osfs";
+root_type Metadata;
\ No newline at end of file
diff --git a/ouster_osf/fb/os_sensor/extrinsics.fbs b/ouster_osf/fb/os_sensor/extrinsics.fbs
new file mode 100644
index 00000000..5b17c894
--- /dev/null
+++ b/ouster_osf/fb/os_sensor/extrinsics.fbs
@@ -0,0 +1,14 @@
+namespace ouster.osf.v2;
+
+// ============ Extrinsics ========================================
+
+table Extrinsics {
+ extrinsics:[double]; // vector of 16, row-major representaton of [4x4]
+ // matrix
+ ref_id:uint32; // reference metadata id of the object it's related to
+ name:string; // optional name of the extrinsics source or originator
+}
+
+// MetadataEntry.type: ouster/v1/os_sensor/Extrinsics
+root_type Extrinsics;
+file_identifier "oExt";
\ No newline at end of file
diff --git a/ouster_osf/fb/os_sensor/lidar_scan_stream.fbs b/ouster_osf/fb/os_sensor/lidar_scan_stream.fbs
new file mode 100644
index 00000000..9e369986
--- /dev/null
+++ b/ouster_osf/fb/os_sensor/lidar_scan_stream.fbs
@@ -0,0 +1,88 @@
+namespace ouster.osf.v2;
+
+// sensor::ChanField enum mapping
+enum CHAN_FIELD:uint8 {
+ UNKNOWN = 0,
+ RANGE = 1,
+ RANGE2 = 2,
+ SIGNAL = 3,
+ SIGNAL2 = 4,
+ REFLECTIVITY = 5,
+ REFLECTIVITY2 = 6,
+ NEAR_IR = 7,
+ FLAGS = 8,
+ FLAGS2 = 9,
+ RAW_HEADERS = 40,
+ RAW32_WORD5 = 45,
+ RAW32_WORD6 = 46,
+ RAW32_WORD7 = 47,
+ RAW32_WORD8 = 48,
+ RAW32_WORD9 = 49,
+ CUSTOM0 = 50,
+ CUSTOM1 = 51,
+ CUSTOM2 = 52,
+ CUSTOM3 = 53,
+ CUSTOM4 = 54,
+ CUSTOM5 = 55,
+ CUSTOM6 = 56,
+ CUSTOM7 = 57,
+ CUSTOM8 = 58,
+ CUSTOM9 = 59,
+ RAW32_WORD1 = 60,
+ RAW32_WORD2 = 61,
+ RAW32_WORD3 = 62,
+ RAW32_WORD4 = 63
+}
+
+// sensor::ChanFieldType enum mapping
+enum CHAN_FIELD_TYPE:uint8 {
+ VOID = 0,
+ UINT8 = 1,
+ UINT16 = 2,
+ UINT32 = 3,
+ UINT64 = 4
+}
+
+// PNG encoded channel fields of LidarScan
+table ChannelData {
+ buffer:[uint8];
+}
+
+// Single lidar field spec
+struct ChannelField {
+ chan_field:CHAN_FIELD;
+ chan_field_type:CHAN_FIELD_TYPE;
+}
+
+table LidarScanMsg {
+ // ====== LidarScan Channels =======================
+ // encoded ChanField data
+ channels:[ChannelData];
+ // corresponding ChanField description to what is contained in channels[] above
+ field_types:[ChannelField];
+
+ // ===== LidarScan Headers =========================
+ header_timestamp:[uint64];
+ header_measurement_id:[uint16];
+ header_status:[uint32];
+ frame_id:int32;
+ // pose vector of 4x4 matrices per every timestamp element (i.e. number of
+ // elements in a vector should be 16 x timestamp.size()), every 4x4 matrix
+ // has a col-major storage
+ pose:[double];
+}
+
+// Scan data from a lidar sensor. One scan is a sweep of a sensor (360 degree).
+table LidarScanStream {
+ sensor_id:uint32; // referenced to metadata.entry[].id with LidarScan
+
+ // LidarScan field_types spec, used only as a HINT about what type
+ // of messages to expect from LidarScanMsg in a stream.
+ // NOTE: For LidarScanMsg decoding field types from
+ // LidarScanMsg.field_types[] should be used.
+ field_types:[ChannelField];
+}
+
+// MetadataEntry.type: ouster/v1/os_sensor/LidarScanStream
+root_type LidarScanStream;
+file_identifier "oLSS";
\ No newline at end of file
diff --git a/ouster_osf/fb/os_sensor/lidar_sensor.fbs b/ouster_osf/fb/os_sensor/lidar_sensor.fbs
new file mode 100644
index 00000000..353c4e8f
--- /dev/null
+++ b/ouster_osf/fb/os_sensor/lidar_sensor.fbs
@@ -0,0 +1,11 @@
+namespace ouster.osf.v2;
+
+// ============ Lidar Sensor ========================================
+
+table LidarSensor {
+ metadata:string;
+}
+
+// MetadataEntry.type: ouster/v1/os_sensor/LidarSensor
+root_type LidarSensor;
+file_identifier "oLS_";
\ No newline at end of file
diff --git a/ouster_osf/fb/streaming/streaming_info.fbs b/ouster_osf/fb/streaming/streaming_info.fbs
new file mode 100644
index 00000000..5d1c0798
--- /dev/null
+++ b/ouster_osf/fb/streaming/streaming_info.fbs
@@ -0,0 +1,41 @@
+namespace ouster.osf.v2;
+
+table StreamStats {
+ // refers to metadata.entries[id] that describes a stream
+ stream_id:uint32;
+ // host timestamp of the first message in `stream_id` stream
+ // in the whole OSF file
+ start_ts:uint64;
+ // host timestamp of the last message in `stream_id` stream
+ // in the whole OSF file
+ end_ts:uint64;
+ // total number of messages in a `stream_id` in the whole OSF file
+ message_count:uint64;
+ // avg size of the messages in bytes for a `stream_id` in the whole
+ // OSF file
+ message_avg_size:uint32;
+}
+
+table ChunkInfo {
+ // offset of the chunk, matches the offset of `metadata.chunks[].offset` and
+ // serves as a key to address specific Chunk. (offsets always unique per OSF file)
+ offset:uint64;
+ // type of messages present in a chunk
+ stream_id:uint32;
+ // number of messages in a chunk
+ message_count:uint32;
+}
+
+// If StreamingInfo is present in metadata it marks that chunks were stored in a
+// particular way and the file can be readily used for streaming messages data for
+// visualization (web-viz/Data-App etc.) with a guaranteed order by timestamp.
+
+table StreamingInfo {
+ // chunk information that describes the message/streams per chunk
+ chunks:[ChunkInfo];
+ // stream statistics per stream for the whole OSF file
+ stream_stats:[StreamStats];
+}
+
+// MetadataEntry.type: ouster/v1/streaming/StreamingInfo
+root_type StreamingInfo;
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/basics.h b/ouster_osf/include/ouster/osf/basics.h
new file mode 100644
index 00000000..ea907f11
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/basics.h
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file basics.h
+ * @brief basic functions for OSF
+ *
+ */
+#pragma once
+
+#include "ouster/lidar_scan.h"
+#include "ouster/types.h"
+
+#include "chunk_generated.h"
+#include "header_generated.h"
+#include "metadata_generated.h"
+
+// OSF basic types for LidarSensor and LidarScan/Imu Streams
+#include "os_sensor/lidar_scan_stream_generated.h"
+#include "os_sensor/lidar_sensor_generated.h"
+
+namespace ouster {
+
+/**
+ * %OSF v2 space
+ */
+namespace osf {
+
+// current fb generated code in ouster::osf::gen
+namespace gen {
+using namespace v2;
+}
+
+enum OSF_VERSION {
+ V_INVALID = 0,
+ V_1_0, // Original version of the OSF (2019/9/16)
+ V_1_1, // Add gps/imu/car trajectory to the OSF (2019/11/14)
+ V_1_2, // Change gps_waypoint type to Table in order to
+ // support Python language generator
+ V_1_3, // Add extension for Message in osfChunk
+ // and for Session in osfSession (2020/03/18)
+ V_1_4, // Gen2/128 support (2020/08/11)
+
+ V_2_0 = 20 // Second Generation OSF v2
+};
+
+/// Chunking strategies. Refer to RFC0018 for more details.
+enum ChunksLayout {
+ LAYOUT_STANDARD = 0, ///< not used currently
+ LAYOUT_STREAMING = 1 ///< default layout (the only one for a user)
+};
+
+std::string to_string(ChunksLayout chunks_layout);
+ChunksLayout chunks_layout_of_string(const std::string& s);
+
+// stable common types mapped to ouster::osf
+using v2::HEADER_STATUS;
+
+/** Common timestamp for all time in ouster::osf */
+using ts_t = std::chrono::nanoseconds;
+
+/**
+ * Standard Flatbuffers prefix size
+ * @todo [pb]: Rename this beast?
+ */
+static constexpr uint32_t FLATBUFFERS_PREFIX_LENGTH = 4;
+
+/** Return string representation of header */
+std::string to_string(const HEADER_STATUS status);
+
+/** Debug method to get hex buf values in string */
+std::string to_string(const uint8_t* buf, const size_t count,
+ const size_t max_show_count = 0);
+
+/// Open read test file to a string.
+std::string read_text_file(const std::string& filename);
+
+/**
+ * Reads the prefix size of the Flatbuffers buffer. First 4 bytes.
+ * @param buf pointer to Flatbuffers buffer stared with prefixed size
+ * @return the size recovered from the stored prefix size
+ */
+uint32_t get_prefixed_size(const uint8_t* buf);
+
+/**
+ * Calculates the full size of the block (prefixed_size + size + CRC32).
+ * @param buf pointer to Flatbuffers buffer stared with prefixed size
+ * @return the calculated size of the block
+ */
+uint32_t get_block_size(const uint8_t* buf);
+
+/**
+ * Check the prefixed size buffer CRC32 fields.
+ *
+ * @param buf is structured as size prefixed Flatbuffer buffer, i.e. first
+ * 4 bytes is the size of the buffer (excluding 4 bytes of the size),
+ * and the 4 bytes that follows right after the 4 +
+ * is the CRC32 bytes.
+ * @param max_size total number of bytes that can be accessed in the buffer,
+ * as a safety precaution if buffer is not well formed, or if
+ * first prefixed size bytes are broken.
+ * @return true if CRC field is correct, false otherwise
+ *
+ */
+bool check_prefixed_size_block_crc(
+ const uint8_t* buf,
+ const uint32_t max_size = std::numeric_limits::max());
+
+/**
+ * Makes the closure to batch lidar_packets and emit LidarScan object.
+ * Result returned through callback handler(ts, LidarScan).
+ * LidarScan uses user modified field types
+ */
+template
+std::function make_build_ls(
+ const ouster::sensor::sensor_info& info,
+ const LidarScanFieldTypes& ls_field_types, ResultHandler&& handler) {
+ const auto w = info.format.columns_per_frame;
+ const auto h = info.format.pixels_per_column;
+
+ std::shared_ptr ls(nullptr);
+ if (ls_field_types.empty()) {
+ auto default_ls_field_types = get_field_types(info);
+ ls = std::make_shared(w, h, default_ls_field_types.begin(),
+ default_ls_field_types.end());
+
+ } else {
+ ls = std::make_shared(w, h, ls_field_types.begin(),
+ ls_field_types.end());
+ }
+
+ auto pf = ouster::sensor::get_format(info);
+ auto build_ls_imp = ScanBatcher(w, pf);
+ osf::ts_t first_msg_ts{-1};
+ return [handler, build_ls_imp, ls, first_msg_ts](
+ const osf::ts_t msg_ts, const uint8_t* buf) mutable {
+ if (first_msg_ts == osf::ts_t{-1}) {
+ first_msg_ts = msg_ts;
+ }
+ if (build_ls_imp(buf, *ls)) {
+ handler(first_msg_ts, *ls);
+ // At this point we've just started accumulating new LidarScan, so
+ // we are saving the msg_ts (i.e. timestamp of a UDP packet)
+ // which contained the first lidar_packet
+ first_msg_ts = msg_ts;
+ }
+ };
+}
+
+/**
+ * The above make_build_ls() function overload. In this function, LidarScan
+ * uses default field types by the profile
+ */
+template
+std::function make_build_ls(
+ const ouster::sensor::sensor_info& info, ResultHandler&& handler) {
+ return make_build_ls(info, {}, handler);
+}
+
+} // namespace osf
+} // namespace ouster
diff --git a/ouster_osf/include/ouster/osf/crc32.h b/ouster_osf/include/ouster/osf/crc32.h
new file mode 100644
index 00000000..1209da72
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/crc32.h
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file crc32.h
+ * @brief crc32 utility
+ *
+ */
+#pragma once
+
+#include
+#include
+
+namespace ouster {
+namespace osf {
+
+/// Size of the CRC field in a buffer
+const uint32_t CRC_BYTES_SIZE = 4;
+
+/**
+ * Caclulate CRC value for the buffer of given size. (ZLIB version)
+ * @param buf pointer to the data buffer
+ * @param size size of the buffer in bytes
+ * @return CRC32 value
+ */
+uint32_t crc32(const uint8_t* buf, uint32_t size);
+
+/**
+ * Caclulate and append CRC value for the buffer of given size and append
+ * it to the initial crc value. (ZLIB version)
+ * @param initial_crc initial crc value to append to
+ * @param buf pointer to the data buffer
+ * @param size size of the buffer in bytes
+ * @return CRC32 value
+ */
+uint32_t crc32(uint32_t initial_crc, const uint8_t* buf, uint32_t size);
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/file.h b/ouster_osf/include/ouster/osf/file.h
new file mode 100644
index 00000000..5bac0080
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/file.h
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file file.h
+ * @brief common OSF file resource for Reader and Writer operations
+ *
+ */
+#pragma once
+
+#include
+#include
+
+#include "ouster/osf/basics.h"
+
+namespace ouster {
+namespace osf {
+
+enum class OpenMode : uint8_t { READ = 0, WRITE = 1 };
+
+/** State of %OSF file */
+enum class FileState : uint8_t { GOOD = 0, BAD = 1 };
+
+/** Chunk buffer type to store raw byte buffers of data. */
+using ChunkBuffer = std::vector;
+
+/**
+ * Interface to abstract the way of how we handle file system read/write
+ * operations.
+ */
+class OsfFile {
+ public:
+ explicit OsfFile();
+
+ /**
+ * Opens the file.
+ * @note Only OpenMode::READ is supported
+ */
+ explicit OsfFile(const std::string& filename,
+ OpenMode mode = OpenMode::READ);
+ ~OsfFile();
+
+ // Header Info
+ uint64_t size() const { return size_; };
+ std::string filename() const { return filename_; }
+ OSF_VERSION version();
+ uint64_t metadata_offset();
+ uint64_t chunks_offset();
+
+ /** Checks the validity of header and session/file_info blocks. */
+ bool valid();
+
+ /**
+ * Get the goodness of the file.
+ * @todo Need to have more states here (eod, valid, error, etc)
+ */
+ bool good() const { return state_ == FileState::GOOD; }
+
+ // Convenience operators
+ bool operator!() const { return !good(); };
+ explicit operator bool() const { return good(); };
+
+ /**
+ * Sequential access to the file.
+ * This is mimicking the regular file access with the offset
+ */
+ uint64_t offset() const { return offset_; }
+
+ /**
+ * File seek (in mmap mode it's just moving the offset_ pointer
+ * without any file system opeations.)
+ * @param pos position in the file
+ */
+ OsfFile& seek(const uint64_t pos);
+
+ /**
+ * Read from file (in current mmap mode it's copying data from
+ * mmap address to the 'buf' address).
+ *
+ * @todo Handle errors in future and get the way to read them back
+ * with FileState etc.
+ */
+ OsfFile& read(uint8_t* buf, const uint64_t count);
+
+ bool is_memory_mapped() const;
+
+ /**
+ * Mmap access to the file content with the specified offset from the
+ * beginning of the file.
+ */
+ const uint8_t* buf(const uint64_t offset = 0) const;
+
+ /**
+ * Clears file handle and allocated resources. In current mmap
+ * implementation it's unmapping memory and essentially invalidates the
+ * memory addresses that might be captured within MessageRefs
+ * and Reader.
+ * @sa ouster::osf::MessageRef, ouster::osf::Reader
+ */
+ void close();
+
+ /** Debug helper method to dump OsfFile state to a string. */
+ std::string to_string();
+
+ // Copy policy
+ // Don't allow the copying of the file handler
+ OsfFile(const OsfFile&) = delete;
+ OsfFile& operator=(const OsfFile&) = delete;
+
+ // Move policy
+ // But it's ok to move with the ownership transfer of the underlying file
+ // handler (mmap).
+ OsfFile(OsfFile&& other);
+ OsfFile& operator=(OsfFile&& other);
+
+ std::shared_ptr read_chunk(uint64_t offset);
+
+ uint8_t* get_header_chunk_ptr();
+ uint8_t* get_metadata_chunk_ptr();
+
+ private:
+ // Convenience method to set error and print it's content.
+ // TODO[pb]: Adding more error states will probably extend the set of this
+ // function.
+ void error(const std::string& msg = std::string());
+
+ // Opened filename as it was passed in contructor.
+ std::string filename_;
+
+ // Current offset to the file. (not used in mmaped implementation) but used
+ // for copying(reading) blocks of memory from the file to the specified
+ // memory.
+ uint64_t offset_;
+
+ // Size of the opened file in bytes
+ uint64_t size_;
+
+ // Mmaped memory address pointed to the beginning of the file (byte 0)
+ uint8_t* file_buf_;
+
+ // File reading access
+ std::ifstream file_stream_;
+ std::shared_ptr header_chunk_;
+ std::shared_ptr metadata_chunk_;
+
+ // Last read chunk cached, to save the double read on the sequence of verify
+ // and then read iterator access (used only when compiled with
+ // OUSTER_OSF_NO_MMAP, and in mmap version we rely on the OS/kernel caching)
+ std::shared_ptr chunk_cache_;
+ uint64_t chunk_cache_offset_;
+
+ // Internal state
+ FileState state_;
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/layout_standard.h b/ouster_osf/include/ouster/osf/layout_standard.h
new file mode 100644
index 00000000..403279f9
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/layout_standard.h
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file layout_standard.h
+ * @brief OSF Standard Layout strategy.
+ *
+ */
+#pragma once
+
+#include "ouster/osf/writer.h"
+
+namespace ouster {
+namespace osf {
+
+constexpr uint32_t STANDARD_DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024; // not strict ...
+
+/**
+ * Standard Layout chunking strategy
+ *
+ * When messages laid out into chunks in the order as they come and not
+ * exceeding `chunk_size` (if possible). However if a single
+ * message size is bigger than specified `chunk_size` it's still recorded.
+ */
+class StandardLayoutCW : public ChunksWriter {
+ public:
+ StandardLayoutCW(Writer& writer,
+ uint32_t chunk_size = STANDARD_DEFAULT_CHUNK_SIZE);
+ void saveMessage(const uint32_t stream_id, const ts_t ts,
+ const std::vector& msg_buf) override;
+
+ void finish() override;
+
+ uint32_t chunk_size() const override { return chunk_size_; }
+
+ private:
+ void finish_chunk();
+
+ const uint32_t chunk_size_;
+ ChunkBuilder chunk_builder_{};
+
+ Writer& writer_;
+
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/layout_streaming.h b/ouster_osf/include/ouster/osf/layout_streaming.h
new file mode 100644
index 00000000..c2f70926
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/layout_streaming.h
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file layout_streaming.h
+ * @brief OSF Streaming Layout
+ *
+ */
+#pragma once
+
+#include "ouster/osf/writer.h"
+
+#include "ouster/osf/meta_streaming_info.h"
+
+namespace ouster {
+namespace osf {
+
+constexpr uint32_t STREAMING_DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024; // not strict ...
+
+/**
+ * Sreaming Layout chunking strategy
+ *
+ * TODO[pb]: sorting TBD as in RFC0018 but first pass should be good enough
+ * because usually messages from the same source and of the same type comes
+ * sorted from pcap/bag sources.
+ *
+ * When messages laid out into chunks in an ordered as they come (not full
+ * RFC0018 compliant, see TODO below), with every chunk holding messages
+ * exclusively of a single stream_id. Tries not to exceede `chunk_size` (if
+ * possible). However if a single message size is bigger than specified
+ * `chunk_size` it's still recorded.
+ */
+class StreamingLayoutCW : public ChunksWriter {
+ public:
+ StreamingLayoutCW(Writer& writer,
+ uint32_t chunk_size = STREAMING_DEFAULT_CHUNK_SIZE);
+ void saveMessage(const uint32_t stream_id, const ts_t ts,
+ const std::vector& msg_buf) override;
+
+ void finish() override;
+
+ uint32_t chunk_size() const override { return chunk_size_; }
+
+ private:
+ void stats_message(const uint32_t stream_id, const ts_t ts,
+ const std::vector& msg_buf);
+ void finish_chunk(uint32_t stream_id,
+ const std::shared_ptr& chunk_builder);
+
+ const uint32_t chunk_size_;
+ std::map> chunk_builders_{};
+ std::vector> chunk_stream_id_{};
+ std::map stream_stats_{};
+ Writer& writer_;
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/meta_extrinsics.h b/ouster_osf/include/ouster/osf/meta_extrinsics.h
new file mode 100644
index 00000000..8c082f56
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/meta_extrinsics.h
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file meta_extrinsics.h
+ * @brief Metadata entry Extrinsics
+ *
+ */
+#pragma once
+
+#include
+#include
+
+#include "ouster/types.h"
+#include "ouster/osf/metadata.h"
+
+namespace ouster {
+namespace osf {
+
+/**
+ * Metadata entry to store sensor Extrinsics.
+ *
+ * @verbatim
+ * Fields:
+ * extrinsics: mat4d - 4x4 homogeneous transform
+ * ref_meta_id: uint32_t - reference to other metadata entry, typically
+ * LidarSensor
+ * name: string - named id if needed, to support multiple extrinsics per
+ * object (i.e. LidarSensor, or Gps) with name maybe used
+ * to associate extrinsics to some external system of
+ * records or just name the source originator of the
+ * extrinsics information.
+ *
+ * OSF type:
+ * ouster/v1/os_sensor/Extrinsics
+ *
+ * Flatbuffer definition file:
+ * fb/os_sensor/extrinsics.fbs
+ * @endverbatim
+ *
+ */
+class Extrinsics : public MetadataEntryHelper {
+ public:
+ explicit Extrinsics(const mat4d& extrinsics, uint32_t ref_meta_id = 0,
+ const std::string& name = "")
+ : extrinsics_(extrinsics), ref_meta_id_{ref_meta_id}, name_{name} {}
+ const mat4d& extrinsics() const { return extrinsics_; }
+ const std::string& name() const { return name_; }
+ uint32_t ref_meta_id() const { return ref_meta_id_; }
+
+ std::vector buffer() const final;
+
+ static std::unique_ptr from_buffer(
+ const std::vector& buf);
+
+ std::string repr() const override;
+
+ private:
+ mat4d extrinsics_;
+ uint32_t ref_meta_id_;
+ std::string name_;
+};
+
+template <>
+struct MetadataTraits {
+ static const std::string type() {
+ return "ouster/v1/os_sensor/Extrinsics";
+ }
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/meta_lidar_sensor.h b/ouster_osf/include/ouster/osf/meta_lidar_sensor.h
new file mode 100644
index 00000000..b029b787
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/meta_lidar_sensor.h
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file meta_lidar_sensor.h
+ * @brief Metadata entry LidarSensor
+ *
+ */
+#pragma once
+
+#include
+#include
+
+#include "ouster/types.h"
+#include "ouster/osf/metadata.h"
+
+namespace ouster {
+namespace osf {
+
+/**
+ * Metadata entry to store lidar sensor_info, i.e. Ouster sensor configuration.
+ *
+ * @verbatim
+ * Fields:
+ * metadata: string - lidar metadata in json
+ *
+ * OSF type:
+ * ouster/v1/os_sensor/LidarSensor
+ *
+ * Flatbuffer definition file:
+ * fb/os_sensor/lidar_sensor.fbs
+ * @endverbatim
+ *
+ */
+class LidarSensor : public MetadataEntryHelper {
+ using sensor_info = ouster::sensor::sensor_info;
+ public:
+
+ /// TODO]pb]: This is soft DEPRECATED until we have an updated sensor_info,
+ /// since we are not encouraging storing the serialized metadata
+ explicit LidarSensor(const sensor_info& si)
+ : sensor_info_(si), metadata_(sensor::to_string(si)) {
+ throw std::invalid_argument(
+ "\nERROR: `osf::LidarSensor()` constructor accepts only metadata_json "
+ "(full string of the file metadata.json or what was received from "
+ "sensor) and not a `sensor::sensor_info` object.\n\n"
+ "We are so sorry that we deprecated it's so hardly but the thing is "
+ "that `sensor::sensor_info` object doesn't equal the original "
+ "metadata.json file (or string) that we used to construct it.\n"
+ "However, Data App when tries to get metadata from OSF looks for "
+ "fields (like `image_rev`) that only present in metadata.json but "
+ "not `sensor::sensor_info` which effectively leads to OSF that "
+ "couldn't be uploaded to Data App.\n");
+ }
+
+ explicit LidarSensor(const std::string& sensor_metadata)
+ : sensor_info_(sensor::parse_metadata(sensor_metadata)),
+ metadata_(sensor_metadata) {}
+
+ const sensor_info& info() const { return sensor_info_; }
+
+ const std::string& metadata() const { return metadata_; }
+
+ // === Simplified with MetadataEntryHelper: type()+clone()
+ // std::string type() const override;
+ // std::unique_ptr clone() const override;
+
+ std::vector buffer() const final;
+
+ static std::unique_ptr from_buffer(
+ const std::vector& buf);
+
+ std::string repr() const override;
+
+ private:
+ sensor_info sensor_info_;
+ const std::string metadata_;
+};
+
+template <>
+struct MetadataTraits {
+ static const std::string type() {
+ return "ouster/v1/os_sensor/LidarSensor";
+ }
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/meta_streaming_info.h b/ouster_osf/include/ouster/osf/meta_streaming_info.h
new file mode 100644
index 00000000..2063ad7a
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/meta_streaming_info.h
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file meta_streaming_info.h
+ * @brief Metadata entry StreamingInfo
+ *
+ */
+#pragma once
+
+#include
+#include
+
+#include "ouster/types.h"
+#include "ouster/osf/metadata.h"
+
+namespace ouster {
+namespace osf {
+
+struct ChunkInfo {
+ uint64_t offset;
+ uint32_t stream_id;
+ uint32_t message_count;
+};
+
+struct StreamStats {
+ uint32_t stream_id;
+ ts_t start_ts;
+ ts_t end_ts;
+ uint64_t message_count;
+ uint32_t message_avg_size;
+ StreamStats() = default;
+ StreamStats(uint32_t s_id, ts_t t, uint32_t msg_size)
+ : stream_id{s_id},
+ start_ts{t},
+ end_ts{t},
+ message_count{1},
+ message_avg_size{msg_size} {};
+ void update(ts_t t, uint32_t msg_size) {
+ if (start_ts > t) start_ts = t;
+ if (end_ts < t) end_ts = t;
+ ++message_count;
+ int avg_size = static_cast(message_avg_size);
+ avg_size = avg_size + (static_cast(msg_size) - avg_size) /
+ static_cast(message_count);
+ message_avg_size = static_cast(avg_size);
+ }
+};
+
+std::string to_string(const ChunkInfo& chunk_info);
+std::string to_string(const StreamStats& stream_stats);
+
+/**
+ * Metadata entry to store StreamingInfo, to support StreamingLayout (RFC 0018)
+ *
+ * @verbatim
+ * Fields:
+ * chunks_info: chunk -> stream_id map
+ * stream_stats: stream statistics of messages in file
+ *
+ * OSF type:
+ * ouster/v1/streaming/StreamingInfo
+ *
+ * Flatbuffer definition file:
+ * fb/streaming/streaming_info.fbs
+ * @endverbatim
+ *
+ */
+class StreamingInfo : public MetadataEntryHelper {
+ public:
+
+ StreamingInfo() {}
+
+ StreamingInfo(
+ const std::vector>& chunks_info,
+ const std::vector>& stream_stats)
+ : chunks_info_{chunks_info.begin(), chunks_info.end()},
+ stream_stats_{stream_stats.begin(), stream_stats.end()} {}
+
+ std::map& chunks_info() { return chunks_info_; }
+ std::map& stream_stats() { return stream_stats_; }
+
+ std::vector buffer() const override final;
+ static std::unique_ptr from_buffer(
+ const std::vector& buf);
+ std::string repr() const override;
+
+ private:
+ std::map chunks_info_{};
+ std::map stream_stats_{};
+};
+
+template <>
+struct MetadataTraits {
+ static const std::string type() {
+ return "ouster/v1/streaming/StreamingInfo";
+ }
+};
+
+} // namespace osf
+} // namespace ouster
\ No newline at end of file
diff --git a/ouster_osf/include/ouster/osf/metadata.h b/ouster_osf/include/ouster/osf/metadata.h
new file mode 100644
index 00000000..2082a169
--- /dev/null
+++ b/ouster_osf/include/ouster/osf/metadata.h
@@ -0,0 +1,432 @@
+/**
+ * Copyright (c) 2021, Ouster, Inc.
+ * All rights reserved.
+ *
+ * @file metadata.h
+ * @brief Core MetadataEntry class with meta store, registry etc.
+ *
+ */
+#pragma once
+
+#include
+#include
+#include