diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..15838da --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,77 @@ +name: Build Wheels + +permissions: + contents: "write" + id-token: "write" + +on: + push: + tags: + - 'v*' + pull_request: + branches: [ main ] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install cmeel[build] + pip install build + + - name: Build wheels + env: + CIBW_BEFORE_BUILD: | + bash scripts/install_deps.sh + bash scripts/build_ffmpeg.sh + CIBW_BUILD: "cp3*-*" + CIBW_SKIP: "pp* *-win32 *-win_amd64" + run: | + cmake --version + bash scripts/install_deps.sh + bash scripts/build_ffmpeg.sh + python -m build --wheel + + release: + needs: build_wheels + runs-on: ubuntu-latest + steps: + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + Changes in this Release + - First release + draft: true + prerelease: false + - name: Upload release asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./dist/spatialmp4-*.whl + asset_name: spatialmp4-*.whl + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index 86796a3..acb8253 100644 --- a/.gitignore +++ b/.gitignore @@ -37,13 +37,29 @@ tmp* build/* +build-editable/* # ffmpeg build scripts/build_ffmpeg -# python +# python venv .venv -pyc +*.pyc + +# cibuildwheel +wheelhouse # mac .DS_Store + +# python build +dist/* + +# cache files +*.png +*.pkl +*.ply +*.json + +# examples +examples/python/view_control.pkl diff --git a/CMakeLists.txt b/CMakeLists.txt index b25530f..1bcf9fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,31 @@ cmake_minimum_required(VERSION 3.24.1 FATAL_ERROR) -project(spatialmp4) +project(spatialmp4 VERSION 0.1.0) + +# Configure version header +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/spatialmp4/version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/spatialmp4/version.h + @ONLY +) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_SHARED_LINKER_FLAGS "-Wl,-Bsymbolic-functions") set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) option(BUILD_ANDROID "whether build for android" OFF) option(BUILD_TESTING "whether build unit test" ON) +option(BUILD_PYTHON "whether build python bindings" ON) + if(BUILD_ANDROID) message(FATAL_ERROR "Don't support android building.") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -fPIC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fPIC -DEIGEN_MPL2_ONLY") +set(WITH_OPENGL OFF CACHE BOOL "disable opengl" FORCE) include(cmake/ffmpeg_local.cmake) include(cmake/opencv_host.cmake) @@ -22,20 +33,33 @@ include(cmake/spdlog.cmake) include(cmake/fmt.cmake) include(cmake/eigen.cmake) include(cmake/sophus.cmake) + +if(BUILD_PYTHON) + include(cmake/pybind11.cmake) +endif() set(SRC + ./src/spatialmp4/utilities/RgbdUtils.cc ./src/spatialmp4/utilities/OpencvUtils.cc ./src/spatialmp4/utils.cc ./src/spatialmp4/reader.cc ) -add_library(${CMAKE_PROJECT_NAME}_static STATIC ${SRC}) -target_include_directories(${CMAKE_PROJECT_NAME}_static PUBLIC +add_library(${CMAKE_PROJECT_NAME}_lib STATIC ${SRC}) + +target_include_directories(${CMAKE_PROJECT_NAME}_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR}/include ) -target_link_libraries(${CMAKE_PROJECT_NAME}_static PUBLIC + + +if(EXISTS $ENV{CONDA_PREFIX}/lib) + link_directories($ENV{CONDA_PREFIX}/lib) +endif() + +target_link_libraries(${CMAKE_PROJECT_NAME}_lib PUBLIC PkgConfig::LIBAV - lib_opencv + PkgConfig::OpenCV spdlog fmt::fmt Eigen3::Eigen @@ -44,10 +68,58 @@ target_link_libraries(${CMAKE_PROJECT_NAME}_static PUBLIC # On newer versions of macOS (>=10.15) and with newer compilers, # filesystem is part of the standard library and doesn't need explicit linking if(UNIX AND NOT APPLE) - target_link_libraries(${CMAKE_PROJECT_NAME}_static PRIVATE stdc++fs) + target_link_libraries(${CMAKE_PROJECT_NAME}_lib PRIVATE stdc++fs) endif() +# Add macOS framework linking +if(APPLE) + set_target_properties(${CMAKE_PROJECT_NAME}_lib PROPERTIES + INSTALL_RPATH "@loader_path" + BUILD_WITH_INSTALL_RPATH TRUE + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python + ) + + # Find macOS frameworks + find_library(FOUNDATION_FRAMEWORK Foundation) + find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox) + find_library(COREAUDIO_FRAMEWORK CoreAudio) + find_library(AVFOUNDATION_FRAMEWORK AVFoundation) + find_library(COREVIDEO_FRAMEWORK CoreVideo) + find_library(COREMEDIA_FRAMEWORK CoreMedia) + find_library(COREGRAPHICS_FRAMEWORK CoreGraphics) + find_library(OPENGL_FRAMEWORK OpenGL) + find_library(APPLICATIONSERVICES_FRAMEWORK ApplicationServices) + find_library(COREFOUNDATION_FRAMEWORK CoreFoundation) + find_library(APPKIT_FRAMEWORK AppKit) + find_library(CARBON_FRAMEWORK Carbon) + find_library(METAL_FRAMEWORK Metal) + find_library(VIDEOTOOLBOX_FRAMEWORK VideoToolbox) + find_library(COREIMAGE_FRAMEWORK CoreImage) + find_library(CORESERVICES_FRAMEWORK CoreServices) + find_library(SECURITY_FRAMEWORK Security) + + target_link_libraries(${CMAKE_PROJECT_NAME}_lib PUBLIC + ${FOUNDATION_FRAMEWORK} + ${AUDIOTOOLBOX_FRAMEWORK} + ${COREAUDIO_FRAMEWORK} + ${AVFOUNDATION_FRAMEWORK} + ${COREVIDEO_FRAMEWORK} + ${COREMEDIA_FRAMEWORK} + ${COREGRAPHICS_FRAMEWORK} + ${OPENGL_FRAMEWORK} + ${APPLICATIONSERVICES_FRAMEWORK} + ${COREFOUNDATION_FRAMEWORK} + ${APPKIT_FRAMEWORK} + ${CARBON_FRAMEWORK} + ${METAL_FRAMEWORK} + ${VIDEOTOOLBOX_FRAMEWORK} + ${COREIMAGE_FRAMEWORK} + ${CORESERVICES_FRAMEWORK} + ${SECURITY_FRAMEWORK} + ) +endif() + if(BUILD_TESTING) include(cmake/gtest.cmake) add_executable(test_reader @@ -57,7 +129,7 @@ if(BUILD_TESTING) ./src/spatialmp4/reader_test.cc ) target_link_libraries(test_reader PRIVATE - ${CMAKE_PROJECT_NAME}_static + ${CMAKE_PROJECT_NAME}_lib gtest gtest_main ) @@ -68,3 +140,14 @@ if(BUILD_TESTING) target_link_libraries(test_reader PRIVATE stdc++fs) endif() endif() + +if(BUILD_PYTHON) + add_subdirectory(bindings) +endif() + +if (APPLE) + set_target_properties(${CMAKE_PROJECT_NAME}_lib PROPERTIES INSTALL_RPATH "@loader_path") +else() + set_target_properties(${CMAKE_PROJECT_NAME}_lib PROPERTIES INSTALL_RPATH "\$ORIGIN") +endif() +install(TARGETS ${CMAKE_PROJECT_NAME}_lib DESTINATION lib) diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8fcf336 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include spatialmp4 *.so *.dylib +include pyproject.toml diff --git a/README.md b/README.md index 494552d..4a2878f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The project depends on the following third-party libraries: - [**fmt**](https://github.com/fmtlib/fmt): Modern C++ formatting library - [**Google Test**](https://github.com/google/googletest): Unit testing framework (optional) -## 🛠️ Build and Installation +## 🛠️ Build and Installation (cpp) ### 1. Clone Repository @@ -65,13 +65,17 @@ bash scripts/install_deps.sh mkdir build && cd build # Configure project -cmake .. -DCMAKE_BUILD_TYPE=Release +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=OFF # Build make -j$(nproc) # On Linux make -j$(sysctl -n hw.ncpu) # On macOS ``` +Sometime it can be difficult to build a c++ source project. [FAQ for installation](docs/install_faq.md) +may help you. If still not working, welcome [submit a issue](https://github.com/Pico-Developer/SpatialMP4/issues). + + ### 5. Run Tests (Optional) ```bash @@ -85,7 +89,36 @@ cd .. ./build/test_reader ``` -## 📖 Usage Guide +## 🛠️ Build and Installation (python) + +### 1. Clone Repository + +```bash +git clone https://github.com/Pico-Developer/SpatialMP4 +cd SpatialMP4 +``` + +### 2. Build FFmpeg + +Build `ffmpeg` first: + +```bash +bash scripts/build_ffmpeg.sh +``` + +### 3. Install Dependencies + +```bash +bash scripts/install_deps.sh +``` + +### 4. Build and Install + +```bash +pip3 install . +``` + +## 📖 Usage Guide (cpp) ### Basic Usage @@ -207,7 +240,7 @@ Utilities::RgbdToPointcloud(rgb_frame.left_rgb, projected_depth, Utilities::SavePointcloudToFile("output.obj", pcd); ``` -## 📚 API Reference +## 📚 API Reference (cpp) ### SpatialML::Reader @@ -284,20 +317,121 @@ struct pose_frame { }; ``` -## 🔍 Utility Functions +## 📖 Usage Guide (python) + + +### Basic Usage Example + +```python +import spatialmp4 -### Image Processing (OpencvUtils) -- `VisualizeMat()`: Visualize matrix data -- `DumpMat()` / `LoadMat()`: Save/load matrices -- `ConcatenateMat()`: Concatenate multiple images +# Create a reader +reader = spatialmp4.Reader("your_video.mp4") + +# Check available streams +print("Has RGB:", reader.has_rgb()) +print("Has Depth:", reader.has_depth()) +print("Has Pose:", reader.has_pose()) + +# Set reading mode +reader.set_read_mode(spatialmp4.ReadMode.DEPTH_FIRST) + +# Read frames +while reader.has_next(): + rgb_frame, depth_frame = reader.load_both() + left_rgb = rgb_frame.left_rgb # numpy array (H, W, 3) + depth = depth_frame.depth # numpy array (H, W) + pose = rgb_frame.pose + print("RGB timestamp:", rgb_frame.timestamp, "Pose:", pose.x, pose.y, pose.z) +``` -### RGBD Processing (RgbdUtils) -- `ProjectDepthToRgb()`: Project depth to RGB -- `RgbdToPointcloud()`: Convert RGBD to point cloud +## 📚 API Reference (python) + +### Main Classes and Methods + +#### `spatialmp4.Reader` +Main class for reading SpatialMP4 files. + +- `Reader(filename: str)` — Create a new reader for the given file. +- `has_rgb() -> bool` — Whether the file contains RGB data. +- `has_depth() -> bool` — Whether the file contains depth data. +- `has_pose() -> bool` — Whether the file contains pose data. +- `has_audio() -> bool` — Whether the file contains audio data. +- `has_disparity() -> bool` — Whether the file contains disparity data. +- `get_duration() -> float` — Get video duration in seconds. +- `get_rgb_fps() -> float` — Get RGB stream FPS. +- `get_depth_fps() -> float` — Get depth stream FPS. +- `get_rgb_width() -> int` — Get RGB frame width. +- `get_rgb_height() -> int` — Get RGB frame height. +- `get_depth_width() -> int` — Get depth frame width. +- `get_depth_height() -> int` — Get depth frame height. +- `get_rgb_intrinsics_left() -> CameraIntrinsics` — Get left RGB camera intrinsics. +- `get_rgb_intrinsics_right() -> CameraIntrinsics` — Get right RGB camera intrinsics. +- `get_rgb_extrinsics_left() -> CameraExtrinsics` — Get left RGB camera extrinsics. +- `get_rgb_extrinsics_right() -> CameraExtrinsics` — Get right RGB camera extrinsics. +- `get_depth_intrinsics() -> CameraIntrinsics` — Get depth camera intrinsics. +- `get_depth_extrinsics() -> CameraExtrinsics` — Get depth camera extrinsics. +- `get_pose_frames() -> List[PoseFrame]` — Get all pose frames. +- `set_read_mode(mode: ReadMode)` — Set reading mode (see enums below). +- `has_next() -> bool` — Whether there is a next frame. +- `reset()` — Reset to the beginning of the file. +- `get_index() -> int` — Get current frame index. +- `get_frame_count() -> int` — Get total number of frames. +- `load_rgb() -> RGBFrame` — Load the next RGB frame. +- `load_depth(raw_head_pose: bool = False) -> DepthFrame` — Load the next depth frame. +- `load_both() -> (RGBFrame, DepthFrame)` — Load the next RGB and depth frames simultaneously. +- `load_rgbd(densify: bool = False) -> Rgbd` — Load RGBD data (for advanced use). + +#### `spatialmp4.RGBFrame` +- `timestamp: float` — Frame timestamp. +- `left_rgb: np.ndarray` — Left RGB image (H, W, 3, uint8). +- `right_rgb: np.ndarray` — Right RGB image (H, W, 3, uint8). +- `pose: PoseFrame` — Associated pose data. + +#### `spatialmp4.DepthFrame` +- `timestamp: float` — Frame timestamp. +- `depth: np.ndarray` — Depth image (H, W, float32, meters). +- `pose: PoseFrame` — Associated pose data. + +#### `spatialmp4.PoseFrame` +- `timestamp: float` — Pose timestamp. +- `x, y, z: float` — Position. +- `qw, qx, qy, qz: float` — Quaternion orientation. +- `as_se3()` — Convert to SE(3) representation (requires Sophus/Eigen, advanced use). + +#### `spatialmp4.CameraIntrinsics` +- `fx, fy, cx, cy: float` — Camera intrinsic parameters. +- `as_cvmat()` — Return as OpenCV matrix. + +#### `spatialmp4.CameraExtrinsics` +- `extrinsics: np.ndarray` — 4x4 extrinsic matrix. +- `as_cvmat()` — Return as OpenCV matrix. +- `as_se3()` — Return as SE(3) (advanced use). + + +### Enums + +#### `spatialmp4.ReadMode` +- `RGB_ONLY` — Only read RGB frames. +- `DEPTH_ONLY` — Only read depth frames. +- `DEPTH_FIRST` — Read both RGB and depth frames, depth as reference. + +#### `spatialmp4.StreamType` +- `UNKNOWN` — Unknown stream type +- `AUDIO` — Audio stream +- `AUDIO_2` — Secondary audio stream +- `RGB` — RGB video stream +- `DISPARITY` — Disparity stream +- `POSE` — Pose data stream +- `DEPTH` — Depth stream + + +### Advanced Usage + +- See [examples/python/visualize_rerun.py](./examples/python/visualize_rerun.py) and [examples/python/generate_pcd.py](./examples/python/generate_pcd.py) for advanced usage, including point cloud generation and visualization with Open3D or Rerun. +- All image and depth data are returned as NumPy arrays for easy integration with OpenCV, Open3D, PyTorch, etc. +- Camera parameters and pose data can be used for 3D reconstruction and SLAM applications. -### Point Cloud Processing (PointcloudUtils) -- `SavePointcloudToFile()`: Save point cloud files -- Support for OBJ format output ## 🐛 Debugging and Logging diff --git a/README_zh.md b/README_zh.md index 7684831..0f6f870 100644 --- a/README_zh.md +++ b/README_zh.md @@ -36,7 +36,7 @@ - [**fmt**](https://github.com/fmtlib/fmt): 现代C++格式化库 - [**Google Test**](https://github.com/google/googletest): 单元测试框架 (可选) -## 🛠️ 编译安装 +## 🛠️ 编译安装 (cpp) ### 1. 克隆仓库 @@ -65,12 +65,14 @@ bash scripts/install_deps.sh mkdir build && cd build # 配置项目 -cmake .. -DCMAKE_BUILD_TYPE=Release +cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON=OFF # 编译 make -j$(nproc) # Linux系统 make -j$(sysctl -n hw.ncpu) # macOS系统 ``` +如果编译报错,请参考[FAQ for installation](docs/install_faq.md)。如果还是解决不了, +欢迎[提issue](https://github.com/Pico-Developer/SpatialMP4/issues),我们会及时帮助你。 ### 5. 运行测试 (可选) @@ -85,6 +87,35 @@ cd .. ./build/test_reader ``` +## 🛠️ 编译安装 (python) + +### 1. 克隆代码 + +```bash +git clone https://github.com/Pico-Developer/SpatialMP4 +cd SpatialMP4 +``` + +### 2. 编译FFmpeg + +Build `ffmpeg` first: + +```bash +bash scripts/build_ffmpeg.sh +``` + +### 3. 安装依赖 + +```bash +bash scripts/install_deps.sh +``` + +### 4. 构建安装 + +```bash +pip3 install . +``` + ## 📖 使用指南 ### 基本用法 @@ -284,20 +315,120 @@ struct pose_frame { }; ``` -## 🔍 工具函数 +## 📖 使用指南 (python) + +### 基本用法示例 + +```python +import spatialmp4 + +# 创建读取器 +reader = spatialmp4.Reader("your_video.mp4") + +# 检查可用流 +print("Has RGB:", reader.has_rgb()) +print("Has Depth:", reader.has_depth()) +print("Has Pose:", reader.has_pose()) + +# 设置读取模式 +reader.set_read_mode(spatialmp4.ReadMode.DEPTH_FIRST) + +# 读取帧 +while reader.has_next(): + rgb_frame, depth_frame = reader.load_both() + left_rgb = rgb_frame.left_rgb # numpy数组 (H, W, 3) + depth = depth_frame.depth # numpy数组 (H, W) + pose = rgb_frame.pose + print("RGB时间戳:", rgb_frame.timestamp, "位姿:", pose.x, pose.y, pose.z) +``` -### 图像处理 (OpencvUtils) -- `VisualizeMat()`: 可视化矩阵数据 -- `DumpMat()` / `LoadMat()`: 保存/加载矩阵 -- `ConcatenateMat()`: 拼接多个图像 -### RGBD处理 (RgbdUtils) -- `ProjectDepthToRgb()`: 深度投影到RGB -- `RgbdToPointcloud()`: RGBD转点云 +## 📚 API 参考 (python) + +### 主要类与方法 + +#### `spatialmp4.Reader` +SpatialMP4 文件读取主类。 + +- `Reader(filename: str)` — 创建读取器。 +- `has_rgb() -> bool` — 是否包含RGB数据。 +- `has_depth() -> bool` — 是否包含深度数据。 +- `has_pose() -> bool` — 是否包含位姿数据。 +- `has_audio() -> bool` — 是否包含音频数据。 +- `has_disparity() -> bool` — 是否包含视差数据。 +- `get_duration() -> float` — 获取视频时长(秒)。 +- `get_rgb_fps() -> float` — 获取RGB帧率。 +- `get_depth_fps() -> float` — 获取深度帧率。 +- `get_rgb_width() -> int` — 获取RGB宽度。 +- `get_rgb_height() -> int` — 获取RGB高度。 +- `get_depth_width() -> int` — 获取深度宽度。 +- `get_depth_height() -> int` — 获取深度高度。 +- `get_rgb_intrinsics_left() -> CameraIntrinsics` — 获取左RGB相机内参。 +- `get_rgb_intrinsics_right() -> CameraIntrinsics` — 获取右RGB相机内参。 +- `get_rgb_extrinsics_left() -> CameraExtrinsics` — 获取左RGB相机外参。 +- `get_rgb_extrinsics_right() -> CameraExtrinsics` — 获取右RGB相机外参。 +- `get_depth_intrinsics() -> CameraIntrinsics` — 获取深度相机内参。 +- `get_depth_extrinsics() -> CameraExtrinsics` — 获取深度相机外参。 +- `get_pose_frames() -> List[PoseFrame]` — 获取所有位姿帧。 +- `set_read_mode(mode: ReadMode)` — 设置读取模式(见下方枚举)。 +- `has_next() -> bool` — 是否有下一帧。 +- `reset()` — 重置到文件开头。 +- `get_index() -> int` — 获取当前帧索引。 +- `get_frame_count() -> int` — 获取总帧数。 +- `load_rgb() -> RGBFrame` — 读取下一个RGB帧。 +- `load_depth(raw_head_pose: bool = False) -> DepthFrame` — 读取下一个深度帧。 +- `load_both() -> (RGBFrame, DepthFrame)` — 同时读取下一个RGB和深度帧。 +- `load_rgbd(densify: bool = False) -> Rgbd` — 读取RGBD数据(高级用法)。 + +#### `spatialmp4.RGBFrame` +- `timestamp: float` — 帧时间戳。 +- `left_rgb: np.ndarray` — 左RGB图像 (H, W, 3, uint8)。 +- `right_rgb: np.ndarray` — 右RGB图像 (H, W, 3, uint8)。 +- `pose: PoseFrame` — 对应位姿数据。 + +#### `spatialmp4.DepthFrame` +- `timestamp: float` — 帧时间戳。 +- `depth: np.ndarray` — 深度图像 (H, W, float32, 单位米)。 +- `pose: PoseFrame` — 对应位姿数据。 + +#### `spatialmp4.PoseFrame` +- `timestamp: float` — 位姿时间戳。 +- `x, y, z: float` — 位置。 +- `qw, qx, qy, qz: float` — 四元数旋转。 +- `as_se3()` — 转换为SE(3)表示(需Sophus/Eigen,高级用法)。 + +#### `spatialmp4.CameraIntrinsics` +- `fx, fy, cx, cy: float` — 相机内参。 +- `as_cvmat()` — 以OpenCV矩阵返回。 + +#### `spatialmp4.CameraExtrinsics` +- `extrinsics: np.ndarray` — 4x4外参矩阵。 +- `as_cvmat()` — 以OpenCV矩阵返回。 +- `as_se3()` — 以SE(3)返回(高级用法)。 + +### 枚举类型 + +#### `spatialmp4.ReadMode` +- `RGB_ONLY` — 仅读取RGB帧。 +- `DEPTH_ONLY` — 仅读取深度帧。 +- `DEPTH_FIRST` — 同时读取RGB和深度帧,以深度为参考。 + +#### `spatialmp4.StreamType` +- `UNKNOWN` — 未知流类型 +- `AUDIO` — 音频流 +- `AUDIO_2` — 第二音频流 +- `RGB` — RGB视频流 +- `DISPARITY` — 视差流 +- `POSE` — 位姿数据流 +- `DEPTH` — 深度流 + + +### 高级用法 + +- 参见 [examples/python/visualize_rerun.py](./examples/python/visualize_rerun.py) 和 [examples/python/generate_pcd.py](./examples/python/generate_pcd.py) 获取点云生成、Open3D/Rerun可视化等高级用法。 +- 所有图像和深度数据均以NumPy数组返回,便于与OpenCV、Open3D、PyTorch等生态集成。 +- 相机参数和位姿数据可用于三维重建和SLAM等应用。 -### 点云处理 (PointcloudUtils) -- `SavePointcloudToFile()`: 保存点云文件 -- 支持OBJ格式输出 ## 🐛 调试和日志 diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 0000000..c729b7b --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required(VERSION 3.14) + +if (NOT PYTHON_SITELIB) +set(PYTHON_SITELIB "lib") +endif() + +if ("${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" STREQUAL "") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +endif() + +set(CMAKE_SHARED_MODULE_PREFIX "") + +# Find Python +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + +# Add Python bindings library +pybind11_add_module(spatialmp4 spatialmp4.cpp) + +# Link against main library and dependencies +target_link_libraries(spatialmp4 PRIVATE + spatialmp4_lib + ${OpenCV_LIBS} + Sophus::Sophus + Eigen3::Eigen + ${FFMPEG_LIBRARIES} +) + +# Include directories +target_include_directories(spatialmp4 PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${OpenCV_INCLUDE_DIRS} + ${EIGEN3_INCLUDE_DIR} + ${FFMPEG_INCLUDE_DIRS} +) + +if (APPLE) +set_target_properties(spatialmp4 PROPERTIES INSTALL_RPATH "@loader_path/../..") +else() +set_target_properties(spatialmp4 PROPERTIES INSTALL_RPATH "\$ORIGIN/../..") +endif() + +# Set output directory +set_target_properties(spatialmp4 PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python +) + +# Install rules +install(TARGETS spatialmp4 + LIBRARY DESTINATION ${PYTHON_SITELIB} + RUNTIME DESTINATION ${PYTHON_SITELIB} +) + +# add_custom_command( +# TARGET spatialmp4 POST_BUILD +# COMMAND doxystub +# --module spatialmp4 +# --doxygen_directory "${CMAKE_CURRENT_SOURCE_DIR}" +# --output "${CMAKE_BINARY_DIR}/${PYTHON_SITELIB}/spatialmp4.pyi" +# WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/${PYTHON_SITELIB}" +# COMMENT "Generating stubs..." +# ) + +# install(FILES ${CMAKE_BINARY_DIR}/${PYTHON_SITELIB}/spatialmp4.pyi DESTINATION ${PYTHON_SITELIB}) \ No newline at end of file diff --git a/bindings/README.md b/bindings/README.md new file mode 100644 index 0000000..ef986b9 --- /dev/null +++ b/bindings/README.md @@ -0,0 +1,129 @@ +# SpatialMP4 Python Bindings + +This directory contains Python bindings for the SpatialMP4 library using pybind11. + +## Requirements + +- Python 3.6 or later +- CMake 3.24 or later +- pybind11 +- OpenCV Python +- NumPy + +## Building the Bindings + +1. Make sure you have all the dependencies installed: + +```bash +# Install Python dependencies +pip install numpy opencv-python + +# Install pybind11 (Ubuntu/Debian) +sudo apt-get install pybind11-dev + +# Install pybind11 (macOS) +brew install pybind11 +``` + +2. Build the project with Python bindings enabled: + +```bash +mkdir build && cd build +cmake .. -DBUILD_PYTHON_BINDINGS=ON +make -j +``` + +The Python module will be built in the `build/python` directory. + +## Installation + +After building, you can install the Python module system-wide: + +```bash +sudo make install +``` + +Or for development, you can add the build directory to your PYTHONPATH: + +```bash +export PYTHONPATH=/path/to/build/python:$PYTHONPATH +``` + +## Usage + +See `example.py` for a complete example of how to use the Python bindings. Here's a basic example: + +```python +import spatialmp4 + +# Create a reader +reader = spatialmp4.Reader("path/to/your/video.mp4") + +# Check available streams +print(f"Has RGB: {reader.has_rgb()}") +print(f"Has Depth: {reader.has_depth()}") +print(f"Has Pose: {reader.has_pose()}") + +# Set read mode +reader.set_read_mode(spatialmp4.ReadMode.RGB_ONLY) + +# Read frames +while reader.has_next(): + rgb_frame = reader.load_rgb() + left_rgb = rgb_frame.left_rgb # NumPy array + right_rgb = rgb_frame.right_rgb # NumPy array + pose = rgb_frame.pose +``` + +## API Reference + +### Classes + +#### Reader +- Main class for reading SpatialMP4 files +- Methods: + - `has_rgb()`: Check if RGB stream is available + - `has_depth()`: Check if depth stream is available + - `has_pose()`: Check if pose data is available + - `get_duration()`: Get video duration in seconds + - `get_rgb_fps()`: Get RGB stream FPS + - `get_depth_fps()`: Get depth stream FPS + - And more... + +#### RGBFrame +- Contains RGB frame data and associated pose +- Properties: + - `timestamp`: Frame timestamp + - `left_rgb`: Left RGB image as NumPy array + - `right_rgb`: Right RGB image as NumPy array + - `pose`: Associated pose data + +#### DepthFrame +- Contains depth frame data and associated pose +- Properties: + - `timestamp`: Frame timestamp + - `depth`: Depth image as NumPy array + - `pose`: Associated pose data + +#### PoseFrame +- Contains pose data +- Properties: + - `timestamp`: Pose timestamp + - `x`, `y`, `z`: Position + - `qw`, `qx`, `qy`, `qz`: Quaternion orientation + +### Enums + +#### ReadMode +- `RGB_ONLY`: Only read RGB frames +- `DEPTH_ONLY`: Only read depth frames +- `DEPTH_FIRST`: Read both RGB and depth frames, depth frame as reference + +#### StreamType +- `UNKNOWN`: Unknown stream type +- `AUDIO`: Audio stream +- `AUDIO_2`: Secondary audio stream +- `RGB`: RGB video stream +- `DISPARITY`: Disparity stream +- `POSE`: Pose data stream +- `DEPTH`: Depth stream diff --git a/bindings/spatialmp4.cpp b/bindings/spatialmp4.cpp new file mode 100644 index 0000000..21739d8 --- /dev/null +++ b/bindings/spatialmp4.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "spatialmp4/reader.h" +#include "spatialmp4/data_types.h" +#include "spatialmp4/utils.h" +#include "spatialmp4/version.h" +#include "spatialmp4/utilities/RgbdUtils.h" + +namespace py = pybind11; + +// Helper function to convert cv::Mat to numpy array +py::array_t mat_to_numpy(const cv::Mat &mat) { + if (mat.empty()) { + return py::array_t(); + } + + py::array_t array({mat.rows, mat.cols, mat.channels()}); + std::memcpy(array.mutable_data(), mat.data, mat.total() * mat.elemSize()); + return array; +} + +// Helper function to convert depth cv::Mat to numpy array +py::array_t depth_mat_to_numpy(const cv::Mat &mat) { + if (mat.empty()) { + return py::array_t(); + } + + py::array_t array({mat.rows, mat.cols}); + std::memcpy(array.mutable_data(), mat.data, mat.total() * mat.elemSize()); + return array; +} + +PYBIND11_MODULE(spatialmp4, m) { + m.doc() = "SpatialMP4 Python Bindings"; + + // 添加版本信息 + m.attr("__version__") = SpatialML::GetVersion(); + m.def("get_version", &SpatialML::GetVersion, "Get the version string"); + m.def("get_major_version", &SpatialML::GetMajorVersion, "Get the major version number"); + m.def("get_minor_version", &SpatialML::GetMinorVersion, "Get the minor version number"); + m.def("get_patch_version", &SpatialML::GetPatchVersion, "Get the patch version number"); + + // Bind StreamType enum + py::enum_(m, "StreamType") + .value("UNKNOWN", SpatialML::MEDIA_TYPE_UNKNOWN) + .value("AUDIO", SpatialML::MEDIA_TYPE_AUDIO) + .value("AUDIO_2", SpatialML::MEDIA_TYPE_AUDIO_2) + .value("RGB", SpatialML::MEDIA_TYPE_RGB) + .value("DISPARITY", SpatialML::MEDIA_TYPE_DISPARITY) + .value("POSE", SpatialML::MEDIA_TYPE_POSE) + .value("DEPTH", SpatialML::MEDIA_TYPE_DEPTH) + .export_values(); + + // Bind pose_frame struct + py::class_(m, "PoseFrame") + .def(py::init<>()) + .def_readwrite("timestamp", &SpatialML::pose_frame::timestamp) + .def_readwrite("x", &SpatialML::pose_frame::x) + .def_readwrite("y", &SpatialML::pose_frame::y) + .def_readwrite("z", &SpatialML::pose_frame::z) + .def_readwrite("qw", &SpatialML::pose_frame::qw) + .def_readwrite("qx", &SpatialML::pose_frame::qx) + .def_readwrite("qy", &SpatialML::pose_frame::qy) + .def_readwrite("qz", &SpatialML::pose_frame::qz) + .def("as_se3", &SpatialML::pose_frame::as_se3) + .def("__repr__", [](const SpatialML::pose_frame &p) { + std::ostringstream ss; + ss << p; + return ss.str(); + }); + + // Bind rgb_frame struct + py::class_(m, "RGBFrame") + .def(py::init<>()) + .def_readwrite("timestamp", &SpatialML::rgb_frame::timestamp) + .def_property( + "left_rgb", [](const SpatialML::rgb_frame &f) { return mat_to_numpy(f.left_rgb); }, + [](SpatialML::rgb_frame &f, py::array_t arr) { + // TODO: implement setter if needed + }) + .def_property( + "right_rgb", [](const SpatialML::rgb_frame &f) { return mat_to_numpy(f.right_rgb); }, + [](SpatialML::rgb_frame &f, py::array_t arr) { + // TODO: implement setter if needed + }) + .def_readwrite("pose", &SpatialML::rgb_frame::pose) + .def("__repr__", [](const SpatialML::rgb_frame &f) { + std::ostringstream ss; + ss << f; + return ss.str(); + }); + + // Bind depth_frame struct + py::class_(m, "DepthFrame") + .def(py::init<>()) + .def_readwrite("timestamp", &SpatialML::depth_frame::timestamp) + .def_property( + "depth", [](const SpatialML::depth_frame &f) { return depth_mat_to_numpy(f.depth); }, + [](SpatialML::depth_frame &f, py::array_t arr) { + // TODO: implement setter if needed + }) + .def_readwrite("pose", &SpatialML::depth_frame::pose) + .def("__repr__", [](const SpatialML::depth_frame &f) { + std::ostringstream ss; + ss << f; + return ss.str(); + }); + + // Bind camera_intrinsics struct + py::class_(m, "CameraIntrinsics") + .def(py::init<>()) + .def_readwrite("fx", &SpatialML::camera_intrinsics::fx) + .def_readwrite("fy", &SpatialML::camera_intrinsics::fy) + .def_readwrite("cx", &SpatialML::camera_intrinsics::cx) + .def_readwrite("cy", &SpatialML::camera_intrinsics::cy) + .def("as_cvmat", &SpatialML::camera_intrinsics::as_cvmat) + .def("__repr__", [](const SpatialML::camera_intrinsics &i) { + std::ostringstream ss; + ss << i; + return ss.str(); + }); + + // Bind camera_extrinsics struct + py::class_(m, "CameraExtrinsics") + .def(py::init<>()) + .def_readwrite("extrinsics", &SpatialML::camera_extrinsics::extrinsics) + .def("as_cvmat", &SpatialML::camera_extrinsics::as_cvmat) + .def("as_se3", &SpatialML::camera_extrinsics::as_se3) + .def("__repr__", [](const SpatialML::camera_extrinsics &e) { + std::ostringstream ss; + ss << e; + return ss.str(); + }); + + // Bind Reader class + py::class_(m, "Reader") + .def(py::init()) + .def("has_rgb", &SpatialML::Reader::HasRGB) + .def("has_depth", &SpatialML::Reader::HasDepth) + .def("has_pose", &SpatialML::Reader::HasPose) + .def("has_audio", &SpatialML::Reader::HasAudio) + .def("has_disparity", &SpatialML::Reader::HasDisparity) + .def("get_start_timestamp", &SpatialML::Reader::GetStartTimestamp) + .def("get_duration", &SpatialML::Reader::GetDuration) + .def("get_rgb_fps", &SpatialML::Reader::GetRgbFPS) + .def("get_rgb_width", &SpatialML::Reader::GetRgbWidth) + .def("get_rgb_height", &SpatialML::Reader::GetRgbHeight) + .def("get_rgb_intrinsics_left", &SpatialML::Reader::GetRgbIntrinsicsLeft) + .def("get_rgb_extrinsics_left", &SpatialML::Reader::GetRgbExtrinsicsLeft) + .def("get_rgb_intrinsics_right", &SpatialML::Reader::GetRgbIntrinsicsRight) + .def("get_rgb_extrinsics_right", &SpatialML::Reader::GetRgbExtrinsicsRight) + .def("get_depth_fps", &SpatialML::Reader::GetDepthFPS) + .def("get_depth_width", &SpatialML::Reader::GetDepthWidth) + .def("get_depth_height", &SpatialML::Reader::GetDepthHeight) + .def("get_depth_intrinsics", &SpatialML::Reader::GetDepthIntrinsics) + .def("get_depth_extrinsics", &SpatialML::Reader::GetDepthExtrinsics) + .def("get_pose_frames", &SpatialML::Reader::GetPoseFrames) + .def("get_rgb_keyframe_index", &SpatialML::Reader::GetRgbKeyframeIndex) + .def("get_depth_keyframe_index", &SpatialML::Reader::GetDepthKeyframeIndex) + .def("is_rgb_distorted", &SpatialML::Reader::IsRgbDistorted) + .def("get_rgb_distortion_model", &SpatialML::Reader::GetRgbDistortionModel) + .def("get_rgb_distortion_params_left", &SpatialML::Reader::GetRgbDistortionParamsLeft) + .def("get_rgb_distortion_params_right", &SpatialML::Reader::GetRgbDistortionParamsRight) + .def("get_depth_distortion_model", &SpatialML::Reader::GetDepthDistortionModel) + .def("get_depth_distortion_params", &SpatialML::Reader::GetDepthDistortionParams) + .def("get_rgb_timebase", &SpatialML::Reader::GetRgbTimebase) + .def("get_depth_timebase", &SpatialML::Reader::GetDepthTimebase) + .def("set_read_mode", &SpatialML::Reader::SetReadMode) + .def("has_next", &SpatialML::Reader::HasNext) + .def("load_rgb", + [](SpatialML::Reader &self) { + SpatialML::rgb_frame frame; + self.Load(frame); + return frame; + }) + .def( + "load_depth", + [](SpatialML::Reader &self, bool raw_head_pose) { + SpatialML::depth_frame frame; + self.Load(frame, raw_head_pose); + return frame; + }, + py::arg("raw_head_pose") = false) + .def("load_both", + [](SpatialML::Reader &self) { + SpatialML::rgb_frame rgb_frame; + SpatialML::depth_frame depth_frame; + self.Load(rgb_frame, depth_frame); + return py::make_tuple(rgb_frame, depth_frame); + }) + .def("load_rgbd", + [](SpatialML::Reader &self, bool densify = false) { + Utilities::Rgbd rgbd; + self.Load(rgbd, densify); + return rgbd; + }) + .def("reset", &SpatialML::Reader::Reset) + .def("get_index", &SpatialML::Reader::GetIndex) + .def("get_frame_count", &SpatialML::Reader::GetFrameCount); + + // Bind ReadMode enum + py::enum_(m, "ReadMode") + .value("RGB_ONLY", SpatialML::Reader::RGB_ONLY) + .value("DEPTH_ONLY", SpatialML::Reader::DEPTH_ONLY) + .value("DEPTH_FIRST", SpatialML::Reader::DEPTH_FIRST) + .export_values(); + + // Bind RandomAccessVideoReader class + py::class_(m, "RandomAccessVideoReader") + .def(py::init<>()) + .def("open", &SpatialML::RandomAccessVideoReader::Open) + .def("get_frame", + [](SpatialML::RandomAccessVideoReader &self, int64_t frame_number) -> py::object { + AVFrame *frame = self.GetFrame(frame_number); + if (!frame) { + return py::none(); + } + std::pair rgb_mats; + SpatialML::FrameToBGR24(frame, rgb_mats); + return py::make_tuple(mat_to_numpy(rgb_mats.first), mat_to_numpy(rgb_mats.second)); + }) + .def("get_frame_count", &SpatialML::RandomAccessVideoReader::GetFrameCount) + .def("debug", &SpatialML::RandomAccessVideoReader::Debug); + + // Bind Utilities::Rgbd struct + py::class_(m, "Rgbd") + .def(py::init<>()) + .def_readwrite("timestamp", &Utilities::Rgbd::timestamp) + .def_property( + "rgb", [](const Utilities::Rgbd &f) { return mat_to_numpy(f.rgb); }, + [](Utilities::Rgbd &f, py::array_t arr) { + // TODO: implement setter if needed + }) + .def_property( + "depth", [](const Utilities::Rgbd &f) { return depth_mat_to_numpy(f.depth); }, + [](Utilities::Rgbd &f, py::array_t arr) { + // TODO: implement setter if needed + }) + .def_property_readonly("T_W_S", + [](const Utilities::Rgbd &f) { + // Expose as Eigen::Matrix4d for now + Eigen::Matrix4d T = f.T_W_S.matrix(); + return T; + }) + .def("__repr__", [](const Utilities::Rgbd &f) { + std::ostringstream ss; + ss << "Rgbd(timestamp=" << f.timestamp << ", rgb shape=[" << f.rgb.rows << "," << f.rgb.cols << "," + << f.rgb.channels() << "]"; + ss << ", depth shape=[" << f.depth.rows << "," << f.depth.cols << "]"; + ss << ")"; + return ss.str(); + }); +} diff --git a/cmake/eigen.cmake b/cmake/eigen.cmake index 33eaa52..638f242 100644 --- a/cmake/eigen.cmake +++ b/cmake/eigen.cmake @@ -1,7 +1,8 @@ include(FetchContent) FetchContent_Declare( eigen - URL https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.tar.gz + # URL https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.tar.gz + URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz DOWNLOAD_EXTRACT_TIMESTAMP true ) @@ -19,4 +20,4 @@ if(NOT TARGET Eigen3::Eigen) INTERFACE_INCLUDE_DIRECTORIES "${eigen_SOURCE_DIR}") endif() -message(STATUS "EIGEN_INCLUDE_DIR: ${eigen_SOURCE_DIR}") \ No newline at end of file +message(STATUS "EIGEN_INCLUDE_DIR: ${eigen_SOURCE_DIR}") diff --git a/cmake/ffmpeg_local.cmake b/cmake/ffmpeg_local.cmake index 871dff5..fe61292 100644 --- a/cmake/ffmpeg_local.cmake +++ b/cmake/ffmpeg_local.cmake @@ -5,8 +5,14 @@ # Install library by `scripts/install/install_ffmpeg.sh` # 设置 PKG_CONFIG_PATH 环境变量 -set(ffmpeg_HOME ${CMAKE_CURRENT_SOURCE_DIR}/scripts/build_ffmpeg/ffmpeg_install) +if(DEFINED ENV{CONDA_PREFIX}) + set(ffmpeg_HOME $ENV{CONDA_PREFIX}) +else() + set(ffmpeg_HOME ${CMAKE_CURRENT_SOURCE_DIR}/scripts/build_ffmpeg/ffmpeg_install) +endif() + set(ENV{PKG_CONFIG_PATH} "${ffmpeg_HOME}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") +set(ENV{PKG_CONFIG} "pkg-config --static") find_package(PkgConfig REQUIRED) pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET diff --git a/cmake/opencv_host.cmake b/cmake/opencv_host.cmake index c8bac1f..6aff4b5 100644 --- a/cmake/opencv_host.cmake +++ b/cmake/opencv_host.cmake @@ -1,19 +1,15 @@ # Usage: # >> include(cmake/opencv_host.cmake) -# >> "Then you can add `lib_opencv` as link library name to your target." +# >> "Then you can add `PkgConfig::OpenCV` as link library name to your target." -find_package( OpenCV REQUIRED ) -add_compile_options(-std=c++11) +if(DEFINED ENV{CONDA_PREFIX}) + set(ENV{PKG_CONFIG_PATH} "${CONDA_PREFIX}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") +endif() -# find_package(PkgConfig) -# pkg_check_modules(OpenCV REQUIRED opencv>=2.4.9) -# set(OpenCV_INCLUDE_DIRS ${OpenCV_INCLUDE_DIRS} /usr/local/opencv_itseez/include/opencv2) -# set(OpenCV_LIBS ${OpenCV_LIBRARIES}) -# LINK_DIRECTORIES(${OpenCV_LIBDIR}) +set(ENV{PKG_CONFIG} "pkg-config --static") -include_directories(${OpenCV_INCLUDE_DIRS}) -add_library(lib_opencv INTERFACE) -target_link_libraries(lib_opencv INTERFACE ${OpenCV_LIBS}) +find_package(PkgConfig) +pkg_check_modules(OpenCV REQUIRED IMPORTED_TARGET opencv4) message(STATUS "OpenCV_INCLUDE_DIRS=${OpenCV_INCLUDE_DIRS}") -message(STATUS "OpenCV_LIBS=${OpenCV_LIBS}") +message(STATUS "OpenCV_LIBRARIES=${OpenCV_LIBRARIES}") \ No newline at end of file diff --git a/cmake/pybind11.cmake b/cmake/pybind11.cmake new file mode 100644 index 0000000..58c6fab --- /dev/null +++ b/cmake/pybind11.cmake @@ -0,0 +1,29 @@ +include(FetchContent) + +# 首先查找 Python +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + +# 设置 Python 相关变量 +set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) +set(PYTHON_INCLUDE_DIR ${Python3_INCLUDE_DIRS}) +set(PYTHON_LIBRARY ${Python3_LIBRARIES}) + +# 配置 pybind11 +FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.11.1 # 使用最新的稳定版本 +) + +# 设置 pybind11 选项 +set(PYBIND11_PYTHON_VERSION ${Python3_VERSION}) +set(PYBIND11_FINDPYTHON ON) +set(PYBIND11_NOPYTHON OFF) + +# 获取并配置 pybind11 +FetchContent_MakeAvailable(pybind11) + +# 确保 pybind11 被正确配置 +if(NOT TARGET pybind11::module) + message(FATAL_ERROR "pybind11::module target not found") +endif() \ No newline at end of file diff --git a/docs/install_faq.md b/docs/install_faq.md new file mode 100644 index 0000000..cb7b735 --- /dev/null +++ b/docs/install_faq.md @@ -0,0 +1,58 @@ +# FAQ for build and install + +Source build a project sometimes can be not easy. This document helps you out of +the trouble. + +## Question 1 +``` +CMake Error at eigen-subbuild/eigen-populate-prefix/src/eigen-populate-stamp/download-eigen-populate.cmake:162 (message): + Each download failed! + + error: downloading 'https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.tar.gz' failed + status_code: 35 + status_string: "SSL connect error" + log: + --- LOG BEGIN --- + Trying 172.65.251.78:443... +``` +`eigen-3.3.7.tar.gz` is not accessable in your computer. You can check by open this link in the browser. +If so, please fix network issue and continue building. + +Another solution is to download `eigen-3.3.7.tar.gz` to local path, and set path in [cmake/eigen.cmake](../cmake/eigen.cmake#L4). + +## Question 2 +``` +Compiling the C compiler identification source file "CMakeCCompilerId.c" failed. +Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc +Build flags: +Id flags: + +The output was: +1 +ld: library 'System' not found +cc: error: linker command failed with exit code 1 (use -v to see invocation) + + +Compiling the CXX compiler identification source file "CMakeCXXCompilerId.cpp" failed. +Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ +Build flags: +Id flags: + +The output was: +1 +ld: library 'c++' not found +c++: error: linker command failed with exit code 1 (use -v to see invocation) +``` + +Make sure you have installed xcode and Command Line Tools. Try below commands: +```bash +xcode-select --install +sudo xcode-select --reset +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer + +# Then rebuild +rm -rf build +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(sysctl -n hw.ncpu) +``` diff --git a/examples/python/generate_colmap_format.py b/examples/python/generate_colmap_format.py new file mode 100644 index 0000000..a61c88d --- /dev/null +++ b/examples/python/generate_colmap_format.py @@ -0,0 +1,72 @@ +import spatialmp4 as sm +import os +import cv2 +import numpy as np + +def write_cameras_txt(path, width, height, fx, fy, cx, cy): + with open(path, 'w') as f: + # SIMPLE_PINHOLE model: + # params: fx, cx, cy + f.write(f"1 PINHOLE {width} {height} {fx} {cx} {cy}\n") + +def write_images_txt(path, poses, img_names, camera_id=1): + with open(path, 'w') as f: + for i, (pose, name) in enumerate(zip(poses, img_names)): + # + f.write(f"{i+1} {pose['qw']} {pose['qx']} {pose['qy']} {pose['qz']} {pose['tx']} {pose['ty']} {pose['tz']} {camera_id} {name}\n\n") + +def main(input_file, output_dir): + reader = sm.Reader(input_file) + imgdir = os.path.join(output_dir, "images") + colmapdir = os.path.join(output_dir, "sparse/0/") + os.makedirs(imgdir, exist_ok=True) + os.makedirs(colmapdir, exist_ok=True) + + # 获取相机内参 + width = reader.get_rgb_width() + height = reader.get_rgb_height() + intr = reader.get_rgb_intrinsics_left() + fx = intr.fx + fy = intr.fy + cx = intr.cx + cy = intr.cy + + reader.set_read_mode(sm.ReadMode.RGB_ONLY) + + img_names = [] + poses = [] + + while reader.has_next(): + # Load RGB frame + rgb_frame = reader.load_rgb() + img = rgb_frame.left_rgb + pose = rgb_frame.pose + i = len(img_names) + img_name = f"{i+1:08d}.png" + cv2.imwrite(os.path.join(imgdir, img_name), img) + img_names.append(img_name) + poses.append({ + "qw": pose.qw, + "qx": pose.qx, + "qy": pose.qy, + "qz": pose.qz, + "tx": pose.x, + "ty": pose.y, + "tz": pose.z, + }) + + # 写 cameras.txt + write_cameras_txt(os.path.join(colmapdir, "cameras.txt"), width, height, fx, fy, cx, cy) + # 写 images.txt + write_images_txt(os.path.join(colmapdir, "images.txt"), poses, img_names) + # 空的 points3D.txt + open(os.path.join(colmapdir, "points3D.txt"), 'w').close() + + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--input-file", type=str, required=True) + parser.add_argument("--output-dir", type=str, required=True) + args = parser.parse_args() + main(args.input_file, args.output_dir) diff --git a/examples/python/generate_pcd.py b/examples/python/generate_pcd.py new file mode 100644 index 0000000..a97e933 --- /dev/null +++ b/examples/python/generate_pcd.py @@ -0,0 +1,245 @@ +from typing import Optional +import typer +import os +import pickle +import cv2 +import numpy as np +import open3d as o3d +import spatialmp4 as sm + +from scipy.spatial.transform import Rotation + + +def pico_pose_to_open3d(extrinsic): + convert = np.array([ + [0, 0, 1, 0], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ]) + output = convert @ extrinsic + return output + + +def generate_pcd( + video_file: str, + topk: Optional[int] = typer.Option(None, help="topk frames"), + skip_last_frame: Optional[bool] = typer.Option(True, help="skip last frame"), + output: Optional[str] = typer.Option(None, help="output name"), + vis_rerun: Optional[bool] = typer.Option(False, help="visualize using rerun"), + vis_open3d: Optional[bool] = typer.Option(False, help="visualize using open3d"), +): + """Generate pcd file from spatialmp4, using TSDF in open3d.""" + if not output: + output = video_file.replace(".mp4", "_pcd.ply") + + reader = sm.Reader(video_file) + reader.set_read_mode(sm.ReadMode.DEPTH_FIRST) + + if not reader.has_depth(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + if not reader.has_pose(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + + voxel_length = 0.01 + sdf_trunc = 0.04 + down_voxel_size = 0.02 + volume = o3d.pipelines.integration.ScalableTSDFVolume( + voxel_length=voxel_length, + sdf_trunc=sdf_trunc, + color_type=o3d.pipelines.integration.TSDFVolumeColorType.NoColor + ) + + if vis_rerun: + import rerun as rr + import rerun.blueprint as rrb + blueprint=rrb.Horizontal( + rrb.Vertical( + rrb.Spatial3DView(name="3D", origin="world"), + rrb.TextDocumentView(name="Description", origin="/description"), + row_shares=[7, 3], + ), + rrb.Vertical( + rrb.Spatial2DView( + name="RGB & Depth", + origin="world/camera/image", + overrides={"world/camera/image/rgb": rr.Image.from_fields(opacity=0.5)}, + ), + rrb.Tabs( + rrb.Spatial2DView(name="RGB", origin="world/camera/image", contents="world/camera/image/rgb"), + rrb.Spatial2DView(name="Depth", origin="world/camera/image", contents="world/camera/image/depth"), + ), + name="2D", + row_shares=[3, 3, 2], + ), + column_shares=[2, 1], + ) + rr.init(f"spatialmp4_{os.path.basename(video_file)}", spawn=True) + rr.send_blueprint(blueprint) + rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Y_UP, static=True) # same as open3d + view_coord = rr.ViewCoordinates.UBR + + positions = [] + frames = [] + + intrinsic = o3d.camera.PinholeCameraIntrinsic() + width = reader.get_rgb_width() + height = reader.get_rgb_height() + intrinsic.set_intrinsics( + width=width, + height=height, + fx=float(reader.get_rgb_intrinsics_left().fx), + fy=float(reader.get_rgb_intrinsics_left().fy), + cx=float(reader.get_rgb_intrinsics_left().cx), + cy=float(reader.get_rgb_intrinsics_left().cy) + ) + intrinsic_o3d = o3d.camera.PinholeCameraIntrinsic( + width=height, + height=width, + fx=float(reader.get_rgb_intrinsics_left().fy), + fy=float(reader.get_rgb_intrinsics_left().fx), + cx=float(reader.get_rgb_intrinsics_left().cy), + cy=width - 1 - float(reader.get_rgb_intrinsics_left().cx) + ) + + while reader.has_next(): + # rgbd = reader.load_rgbd(True) + rgb_frame, depth_frame = reader.load_both() + import ipdb; ipdb.set_trace() + + if topk is not None and reader.get_index() >= topk: + break + if skip_last_frame and reader.get_index() == reader.get_frame_count() - 1: + continue + print(f"Loading frame {reader.get_index() + 1} / {reader.get_frame_count()}, timestamp: {rgbd.timestamp}") + + # preprocess on depthmap + depth_np = rgbd.depth + depth_uint16 = (depth_np * 1000).astype(np.uint16) + sobelx = cv2.Sobel(depth_uint16, cv2.CV_32F, 1, 0, ksize=3) + sobely = cv2.Sobel(depth_uint16, cv2.CV_32F, 0, 1, ksize=3) + grad_mag = np.sqrt(sobelx**2 + sobely**2) + depth_np[grad_mag > 500] = 0 + depth_np[(depth_np < 0.2) | (depth_np > 5)] = 0 + + extrinsic = rgbd.T_W_S + extrinsic = pico_pose_to_open3d(extrinsic) + + rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth( + o3d.geometry.Image(rgbd.rgb), + o3d.geometry.Image(depth_np), + depth_scale=1.0, + depth_trunc=5.0, + convert_rgb_to_intensity=False + ) + volume.integrate(rgbd_image, intrinsic, extrinsic) + + pcd_i = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, intrinsic) + pcd_i.transform(np.linalg.inv(extrinsic)) + + o3d.io.write_point_cloud(output+f"_{reader.get_index()}.ply", pcd_i) + + pcd_i.paint_uniform_color([0, 0, 1]) + frames.append(pcd_i) + + if vis_rerun: + rr.set_time_seconds("time", rgbd.timestamp) + rr.log("world/xyz", rr.Arrows3D(vectors=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], colors=[[255, 0, 0], [0, 255, 0], [0, 0, 255]])) + rr.log("world/camera/image", rr.Pinhole( + resolution=[width, height], + focal_length=[intrinsic.intrinsic_matrix[0, 0], intrinsic.intrinsic_matrix[1, 1]], + principal_point=[intrinsic.intrinsic_matrix[0, 2], intrinsic.intrinsic_matrix[1, 2]], + camera_xyz=view_coord, + )) + position = extrinsic[:3, 3] + rotation = Rotation.from_matrix(extrinsic[:3, :3]).as_quat() + rr.log("world/camera", rr.Transform3D(translation=position, rotation=rr.Quaternion(xyzw=rotation))) + rr.log("world/camera/image/rgb", rr.Image(rgbd.rgb, color_model="BGR").compress(jpeg_quality=95)) + rr.log("world/camera/image/depth", rr.DepthImage(depth_np, meter=1.0)) + + pcd = volume.extract_point_cloud() + pcd.paint_uniform_color([1, 1, 0]) + + print("Pointcloud post processing...") + pcd = pcd.remove_non_finite_points() + pcd = pcd.voxel_down_sample(voxel_size=down_voxel_size) + _, ind = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2) + print(f"Valid points: {len(ind)} / {len(pcd.points)} ({len(ind) / len(pcd.points) * 100:.2f} %)") + pcd = pcd.select_by_index(ind) + o3d.io.write_point_cloud(output, pcd) + print(f"Saved {len(pcd.points)} total points to {output}") + + if vis_open3d: + origin = o3d.geometry.TriangleMesh.create_coordinate_frame(size=1.0, origin=[0, 0, 0]) + vis=o3d.visualization.VisualizerWithKeyCallback() + vis.create_window() + idx = 0 + vis.add_geometry(origin) + vis.add_geometry(frames[idx]) + + def save_view_control(vis): + vc = vis.get_view_control() + params = vc.convert_to_pinhole_camera_parameters() + data = { + "extrinsic": params.extrinsic, + "intrinsic_matrix": params.intrinsic.intrinsic_matrix, + "width": params.intrinsic.width, + "height": params.intrinsic.height, + } + with open("view_control.pkl", "wb") as f: + pickle.dump(data, f) + print("Saved view control") + + def click_next(vis): + nonlocal idx + print('a_click') + vis.clear_geometries() + vis.add_geometry(origin) + vis.add_geometry(pcd) + vis.add_geometry(frames[idx]) + idx = (idx+1) % len(frames) + + if os.path.exists("view_control.pkl"): + vc = vis.get_view_control() + with open("view_control.pkl", "rb") as f: + data = pickle.load(f) + params = o3d.camera.PinholeCameraParameters() + params.extrinsic = data["extrinsic"] + params.intrinsic = o3d.camera.PinholeCameraIntrinsic( + data["width"], data["height"], data["intrinsic_matrix"] + ) + vc.convert_from_pinhole_camera_parameters(params) + + def click_prev(vis): + nonlocal idx + print('b_click') + vis.clear_geometries() + vis.add_geometry(origin) + vis.add_geometry(pcd) + vis.add_geometry(frames[idx]) + idx = (idx-1) % len(frames) + idx = max(idx, 0) + if os.path.exists("view_control.pkl"): + vc = vis.get_view_control() + with open("view_control.pkl", "rb") as f: + data = pickle.load(f) + params = o3d.camera.PinholeCameraParameters() + params.extrinsic = data["extrinsic"] + params.intrinsic = o3d.camera.PinholeCameraIntrinsic( + data["width"], data["height"], data["intrinsic_matrix"] + ) + vc.convert_from_pinhole_camera_parameters(params) + + def exit_key(vis): + vis.destroy_window() + + vis.register_key_callback(65, click_next) # glfw.KEY_A + vis.register_key_callback(66, click_prev) # glfw.KEY_B + vis.register_key_callback(83, save_view_control) # glfw.KEY_S + vis.register_key_callback(256, exit_key) # glfw.KEY_ESCAPE + vis.poll_events() + vis.run() + + +if __name__ == "__main__":# + typer.run(generate_pcd) diff --git a/examples/python/visualize_open3d.py b/examples/python/visualize_open3d.py new file mode 100644 index 0000000..7cdc9d6 --- /dev/null +++ b/examples/python/visualize_open3d.py @@ -0,0 +1,217 @@ +import typer +import os +import pickle +import cv2 +import numpy as np +import open3d as o3d +from scipy.spatial.transform import Rotation +import spatialmp4 as sm + +''' +Rerun for different data source: + + colmap: + rr.ViewCoordinates.RIGHT_HAND_Y_DOWN + rr.log("camera", rr.ViewCoordinates.RDF, static=True) + +''' + +def pico_pose_to_open3d(extrinsic, depth_only=False): + if depth_only: + convert = np.array([ + [1, 0, 0, 0], + [0, 0, -1, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ]) + else: + convert = np.array([ + [0, 0, 1, 0], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ]) + output = convert @ extrinsic + return output + + +def main( + video_file: str, + depth_only: bool = False, + topk: int = None, + save_pcd: str = None, +): + """Generate pcd file from spatialmp4, using TSDF in open3d. + """ + reader = sm.Reader(video_file) + if depth_only: + reader.set_read_mode(sm.ReadMode.DEPTH_ONLY) + else: + reader.set_read_mode(sm.ReadMode.DEPTH_FIRST) + + if not reader.has_depth(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + return + if not reader.has_pose(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + return + + positions = [] + frames = [] + + if depth_only: + width = reader.get_depth_width() + height = reader.get_depth_height() + fx = float(reader.get_depth_intrinsics().fx) + fy = float(reader.get_depth_intrinsics().fy) + cx = float(reader.get_depth_intrinsics().cx) + cy = float(reader.get_depth_intrinsics().cy) + else: + width = reader.get_rgb_width() + height = reader.get_rgb_height() + fx = float(reader.get_rgb_intrinsics_left().fx) + fy = float(reader.get_rgb_intrinsics_left().fy) + cx = float(reader.get_rgb_intrinsics_left().cx) + cy = float(reader.get_rgb_intrinsics_left().cy) + + intrinsic = o3d.camera.PinholeCameraIntrinsic() + intrinsic.set_intrinsics( + width=width, + height=height, + fx=fx, + fy=fy, + cx=cx, + cy=cy + ) + intrinsic_o3d = o3d.camera.PinholeCameraIntrinsic( + width=height, + height=width, + fx=float(reader.get_rgb_intrinsics_left().fy), + fy=float(reader.get_rgb_intrinsics_left().fx), + cx=float(reader.get_rgb_intrinsics_left().cy), + cy=width - 1 - float(reader.get_rgb_intrinsics_left().cx) + ) + + while reader.has_next(): + if depth_only: + depth_frame = reader.load_depth() + timestamp = depth_frame.timestamp + depth_np = depth_frame.depth + TWH = depth_frame.pose # T_W_H + if TWH.timestamp == 0: + continue # invalid pose data + extrinsic = np.eye(4) + extrinsic[:3, 3] = [TWH.x, TWH.y, TWH.z] + extrinsic[:3, :3] = Rotation.from_quat((TWH.qx, TWH.qy, TWH.qz, TWH.qw)).as_matrix() + fake_rgb = np.zeros((height, width, 3), dtype=np.uint8) + else: + rgbd = reader.load_rgbd(True) + timestamp = rgbd.timestamp + depth_np = rgbd.depth + extrinsic = rgbd.T_W_S + if topk is not None and reader.get_index() >= topk: + break + print(f"Loading frame {reader.get_index() + 1} / {reader.get_frame_count()}, timestamp: {timestamp}") + + # preprocess on depthmap + depth_uint16 = (depth_np * 1000).astype(np.uint16) + sobelx = cv2.Sobel(depth_uint16, cv2.CV_32F, 1, 0, ksize=3) + sobely = cv2.Sobel(depth_uint16, cv2.CV_32F, 0, 1, ksize=3) + grad_mag = np.sqrt(sobelx**2 + sobely**2) + depth_np[grad_mag > 500] = 0 + depth_np[(depth_np < 0.2) | (depth_np > 5)] = 0 + + extrinsic = pico_pose_to_open3d(extrinsic, depth_only) + + rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth( + o3d.geometry.Image(fake_rgb if depth_only else rgbd.rgb), + o3d.geometry.Image(depth_np), + depth_scale=1.0, + depth_trunc=5.0, + convert_rgb_to_intensity=False + ) + + pcd_i = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, intrinsic) + pcd_i.transform(np.linalg.inv(extrinsic)) + if depth_only: + pcd_i.paint_uniform_color([1, 0, 0]) + frames.append(pcd_i) + + origin = o3d.geometry.TriangleMesh.create_coordinate_frame(size=1.0, origin=[0, 0, 0]) + vis=o3d.visualization.VisualizerWithKeyCallback() + vis.create_window() + idx=0 + vis.add_geometry(origin) + vis.add_geometry(frames[idx]) + + if save_pcd: + for i in range(len(frames)): + os.makedirs(save_pcd, exist_ok=True) + savename = os.path.join(save_pcd, f"{i:04d}.ply") + o3d.io.write_point_cloud(savename, frames[i]) + print(f"saved to {save_pcd}") + + def save_view_control(vis): + vc = vis.get_view_control() + params = vc.convert_to_pinhole_camera_parameters() + data = { + "extrinsic": params.extrinsic, + "intrinsic_matrix": params.intrinsic.intrinsic_matrix, + "width": params.intrinsic.width, + "height": params.intrinsic.height, + } + with open("view_control.pkl", "wb") as f: + pickle.dump(data, f) + print("Saved view control") + + def click_next(vis): + nonlocal idx + print('a_click') + vis.clear_geometries() + vis.add_geometry(origin) + vis.add_geometry(frames[idx]) + idx = (idx+1) % len(frames) + + if os.path.exists("view_control.pkl"): + vc = vis.get_view_control() + with open("view_control.pkl", "rb") as f: + data = pickle.load(f) + params = o3d.camera.PinholeCameraParameters() + params.extrinsic = data["extrinsic"] + params.intrinsic = o3d.camera.PinholeCameraIntrinsic( + data["width"], data["height"], data["intrinsic_matrix"] + ) + vc.convert_from_pinhole_camera_parameters(params) + + def click_prev(vis): + nonlocal idx + print('b_click') + vis.clear_geometries() + vis.add_geometry(origin) + vis.add_geometry(frames[idx]) + idx = (idx-1) % len(frames) + idx = max(idx, 0) + if os.path.exists("view_control.pkl"): + vc = vis.get_view_control() + with open("view_control.pkl", "rb") as f: + data = pickle.load(f) + params = o3d.camera.PinholeCameraParameters() + params.extrinsic = data["extrinsic"] + params.intrinsic = o3d.camera.PinholeCameraIntrinsic( + data["width"], data["height"], data["intrinsic_matrix"] + ) + vc.convert_from_pinhole_camera_parameters(params) + + def exit_key(vis): + vis.destroy_window() + + vis.register_key_callback(65, click_next) # glfw.KEY_A + vis.register_key_callback(66, click_prev) # glfw.KEY_B + vis.register_key_callback(83, save_view_control) # glfw.KEY_S + vis.register_key_callback(256, exit_key) # glfw.KEY_ESCAPE + vis.poll_events() + vis.run() + + +if __name__ == "__main__":# + typer.run(main) diff --git a/examples/python/visualize_rerun.py b/examples/python/visualize_rerun.py new file mode 100644 index 0000000..fbae9a0 --- /dev/null +++ b/examples/python/visualize_rerun.py @@ -0,0 +1,128 @@ +import typer +import os +import cv2 +import numpy as np +from scipy.spatial.transform import Rotation +import spatialmp4 as sm +import rerun as rr +import rerun.blueprint as rrb + + +def pico_pose_to_open3d(extrinsic): + convert = np.array([ + [0, 0, 1, 0], + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 1] + ]) + output = convert @ extrinsic + return output + + +def main( + video_file: str, + depth_only: bool = False, +): + """Visualize spatialmp4 using rerun.""" + reader = sm.Reader(video_file) + + if depth_only: + reader.set_read_mode(sm.ReadMode.DEPTH_ONLY) + else: + reader.set_read_mode(sm.ReadMode.DEPTH_FIRST) + + if not reader.has_depth(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + return + if not reader.has_pose(): + typer.echo(typer.style(f"No depth found in input file", fg=typer.colors.RED)) + return + + blueprint=rrb.Horizontal( + rrb.Vertical( + rrb.Spatial3DView(name="3D", origin="world"), + rrb.TextDocumentView(name="Description", origin="/description"), + row_shares=[7, 3], + ), + rrb.Vertical( + rrb.Spatial2DView( + name="RGB & Depth", + origin="world/camera/image", + overrides={"world/camera/image/rgb": rr.Image.from_fields(opacity=0.5)}, + ), + rrb.Tabs( + rrb.Spatial2DView(name="RGB", origin="world/camera/image", contents="world/camera/image/rgb"), + rrb.Spatial2DView(name="Depth", origin="world/camera/image", contents="world/camera/image/depth"), + ), + name="2D", + row_shares=[3, 3, 2], + ), + column_shares=[2, 1], + ) + rr.init(f"spatialmp4_{os.path.basename(video_file)}", spawn=True) + rr.send_blueprint(blueprint) + rr.log("world", rr.ViewCoordinates.RIGHT_HAND_Y_UP, static=True) # same as open3d + view_coord = rr.ViewCoordinates.UBR + + + if depth_only: + width = reader.get_depth_width() + height = reader.get_depth_height() + fx = float(reader.get_depth_intrinsics().fx) + fy = float(reader.get_depth_intrinsics().fy) + cx = float(reader.get_depth_intrinsics().cx) + cy = float(reader.get_depth_intrinsics().cy) + else: + width = reader.get_rgb_width() + height = reader.get_rgb_height() + fx = float(reader.get_rgb_intrinsics_left().fx) + fy = float(reader.get_rgb_intrinsics_left().fy) + cx = float(reader.get_rgb_intrinsics_left().cx) + cy = float(reader.get_rgb_intrinsics_left().cy) + + while reader.has_next(): + if depth_only: + depth_frame = reader.load_depth() + timestamp = depth_frame.timestamp + depth_np = depth_frame.depth + TWH = depth_frame.pose # T_W_H + if TWH.timestamp == 0: + continue # invalid pose data + extrinsic = np.eye(4) + extrinsic[:3, 3] = [TWH.x, TWH.y, TWH.z] + extrinsic[:3, :3] = Rotation.from_quat((TWH.qx, TWH.qy, TWH.qz, TWH.qw)).as_matrix() + else: + rgbd = reader.load_rgbd(True) + timestamp = rgbd.timestamp + depth_np = rgbd.depth + extrinsic = rgbd.T_W_S + print(f"Loading frame {reader.get_index() + 1} / {reader.get_frame_count()}, timestamp: {timestamp}") + + # preprocess on depthmap + depth_uint16 = (depth_np * 1000).astype(np.uint16) + sobelx = cv2.Sobel(depth_uint16, cv2.CV_32F, 1, 0, ksize=3) + sobely = cv2.Sobel(depth_uint16, cv2.CV_32F, 0, 1, ksize=3) + grad_mag = np.sqrt(sobelx**2 + sobely**2) + depth_np[grad_mag > 500] = 0 + depth_np[(depth_np < 0.2) | (depth_np > 5)] = 0 + + extrinsic = pico_pose_to_open3d(extrinsic) + + rr.set_time_seconds("time", timestamp) + rr.log("world/xyz", rr.Arrows3D(vectors=[[1, 0, 0], [0, 1, 0], [0, 0, 1]], colors=[[255, 0, 0], [0, 255, 0], [0, 0, 255]])) + rr.log("world/camera/image", rr.Pinhole( + resolution=[width, height], + focal_length=[fx, fy], + principal_point=[cx, cy], + camera_xyz=view_coord, + )) + position = extrinsic[:3, 3] + rotation = Rotation.from_matrix(extrinsic[:3, :3]).as_quat() + rr.log("world/camera", rr.Transform3D(translation=position, rotation=rr.Quaternion(xyzw=rotation))) + rr.log("world/camera/image/depth", rr.DepthImage(depth_np, meter=1.0)) + if not depth_only: + rr.log("world/camera/image/rgb", rr.Image(rgbd.rgb, color_model="BGR").compress(jpeg_quality=95)) + + +if __name__ == "__main__":# + typer.run(main) diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..33ba5c8 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,1547 @@ +version: 6 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.6.15-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/cmake-3.26.4-hcfe8598_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-15.1.0-hc55bae6_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-15.1.0-h33e79ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.1.0-h4393ad2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glib-2.80.2-hf974151_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.80.2-hb6ce0ca_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-15.1.0-h33e79ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.1.0-h6a1bac1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libass-0.14.0-hc2a3f6d_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-32_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb9d3cd8_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-32_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.1.2-h409715c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.1.0-h4c094af_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.80.2-hf974151_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.9.1-hd6dc26d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-32_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-devel-5.8.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.58.0-h47da74e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.1.0-h97b714f_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.0-hde9e2c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.1.0-h4c094af_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libva-2.18.0-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-hca2bb57_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h4ab18f5_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/make-4.4.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.13.0-h7aa8ee6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.1-h7b32b05_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.2-h29eaf8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.3-hab00c5b_0_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.3-hd590300_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.9.0-hf52228f_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.0-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-fixesproto-5.0-hb9d3cd8_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-hb9d3cd8_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.4-h0b41bf4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h0b41bf4_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-5.0.3-h7f98852_1004.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-hb9d3cd8_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-hb9d3cd8_1004.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-hb9d3cd8_1008.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.8.1-hbcc6ac9_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-gpl-tools-5.8.1-hbcc6ac9_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xz-tools-5.8.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h4ab18f5_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 + sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 + md5: d7c89558ba9fa0495403155b64376d81 + license: None + size: 2562 + timestamp: 1578324546067 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + build_number: 16 + sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 + md5: 73aaf86a425cc6e73fcf236a5a46396d + depends: + - _libgcc_mutex 0.1 conda_forge + - libgomp >=7.5.0 + constrains: + - openmp_impl 9999 + license: BSD-3-Clause + license_family: BSD + size: 23621 + timestamp: 1650670423406 +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2 + sha256: 2c0a618d0fa695e4e01a30e7ff31094be540c52e9085cbd724edb132c65cf9cd + md5: be733e69048951df1e4b4b7bb8c7666f + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + license_family: GPL + size: 592320 + timestamp: 1666699031168 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_5.conda + sha256: 27ae158d415ff2942214b32ac7952e642f0f4c2a45ab683691e2a9a9159f868c + md5: 18852d82df8e5737e320a8731ace51b9 + depends: + - ld_impl_linux-64 2.43 h712a8e2_5 + - sysroot_linux-64 + license: GPL-3.0-only + license_family: GPL + size: 6376971 + timestamp: 1749852878015 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.1.0-hb9d3cd8_3.conda + sha256: c969baaa5d7a21afb5ed4b8dd830f82b78e425caaa13d717766ed07a61630bec + md5: 5d08a0ac29e6a5a984817584775d4131 + depends: + - __glibc >=2.17,<3.0.a0 + - brotli-bin 1.1.0 hb9d3cd8_3 + - libbrotlidec 1.1.0 hb9d3cd8_3 + - libbrotlienc 1.1.0 hb9d3cd8_3 + - libgcc >=13 + license: MIT + license_family: MIT + size: 19810 + timestamp: 1749230148642 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hb9d3cd8_3.conda + sha256: ab74fa8c3d1ca0a055226be89e99d6798c65053e2d2d3c6cb380c574972cd4a7 + md5: 58178ef8ba927229fba6d84abf62c108 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlidec 1.1.0 hb9d3cd8_3 + - libbrotlienc 1.1.0 hb9d3cd8_3 + - libgcc >=13 + license: MIT + license_family: MIT + size: 19390 + timestamp: 1749230137037 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda + sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d + md5: 62ee74e96c5ebb0af99386de58cf9553 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: bzip2-1.0.6 + license_family: BSD + size: 252783 + timestamp: 1720974456583 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + sha256: f8003bef369f57396593ccd03d08a8e21966157269426f71e943f96e4b579aeb + md5: f7f0d6cc2dc986d42ac2689ec88192be + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 206884 + timestamp: 1744127994291 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.6.15-hbd8a1cb_0.conda + sha256: 7cfec9804c84844ea544d98bda1d9121672b66ff7149141b8415ca42dfcd44f6 + md5: 72525f07d72806e3b639ad4504c30ce5 + depends: + - __unix + license: ISC + size: 151069 + timestamp: 1749990087500 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2 + sha256: f062cf56e6e50d3ad4b425ebb3765ca9138c6ebc52e6a42d1377de8bc8d954f6 + md5: d1a88f3ed5b52e1024b80d4bcd26a7a0 + depends: + - fontconfig >=2.13.96,<3.0a0 + - fonts-conda-ecosystem + - freetype >=2.12.1,<3.0a0 + - icu >=70.1,<71.0a0 + - libgcc-ng >=12 + - libglib >=2.72.1,<3.0a0 + - libpng >=1.6.38,<1.7.0a0 + - libxcb >=1.13,<1.14.0a0 + - libzlib >=1.2.12,<2.0.0a0 + - pixman >=0.40.0,<1.0a0 + - xorg-libice + - xorg-libsm + - xorg-libx11 + - xorg-libxext + - xorg-libxrender + - zlib >=1.2.12,<1.3.0a0 + license: LGPL-2.1-only or MPL-1.1 + size: 1576122 + timestamp: 1663568213559 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cmake-3.26.4-hcfe8598_0.conda + sha256: 37533b572a676017704c989c392998c344e889010786d6555dccdfc524a8e238 + md5: 1714cf0f0facaeb609a0846e4270aff2 + depends: + - bzip2 >=1.0.8,<2.0a0 + - expat + - libcurl >=8.1.0,<9.0a0 + - libexpat >=2.5.0,<3.0a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - libuv + - libzlib >=1.2.13,<2.0.0a0 + - ncurses >=6.3,<7.0a0 + - rhash <=1.4.3 + - xz >=5.2.6,<6.0a0 + - zlib + - zstd >=1.5.2,<1.6.0a0 + license: BSD-3-Clause + license_family: BSD + size: 16343060 + timestamp: 1684460894541 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-15.1.0-hc55bae6_3.conda + sha256: d3206e920c44e1637c6b60366670cebd7872812812fba834dfadcc02aa76dad5 + md5: 907e04c7ccdecba6ff583022ade324d4 + depends: + - gcc_impl_linux-64 >=15.1.0,<15.1.1.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 33184 + timestamp: 1750808533112 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.0-pyhd8ed1ab_0.conda + sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca + md5: 72e42d28960d875c7654614f8b50939a + depends: + - python >=3.9 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + size: 21284 + timestamp: 1746947398083 +- conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.7.0-h5888daf_0.conda + sha256: dd5530ddddca93b17318838b97a2c9d7694fa4d57fc676cf0d06da649085e57a + md5: d6845ae4dea52a2f90178bf1829a21f8 + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat 2.7.0 h5888daf_0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 140050 + timestamp: 1743431809745 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b + md5: 0c96522c6bdaed4b1566d11387caaf45 + license: BSD-3-Clause + license_family: BSD + size: 397370 + timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c + md5: 34893075a5c9e55cdafac56607368fc6 + license: OFL-1.1 + license_family: Other + size: 96530 + timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 + md5: 4d59c254e01d9cde7957100457e2d5fb + license: OFL-1.1 + license_family: Other + size: 700814 + timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 + md5: 49023d73832ef61042f6a237cb2687e7 + license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 + license_family: Other + size: 1620504 + timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda + sha256: 155d534c9037347ea7439a2c6da7c24ffec8e5dd278889b4c57274a1d91e0a83 + md5: 0f69b688f52ff6da70bccb7ff7001d1d + depends: + - expat >=2.5.0,<3.0a0 + - freetype >=2.12.1,<3.0a0 + - libgcc-ng >=12 + - libuuid >=2.32.1,<3.0a0 + - libzlib >=1.2.13,<2.0.0a0 + license: MIT + license_family: MIT + size: 272010 + timestamp: 1674828850194 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 + md5: fee5683a3f04bd15cbd8318b096a27ab + depends: + - fonts-conda-forge + license: BSD-3-Clause + license_family: BSD + size: 3667 + timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 + sha256: 53f23a3319466053818540bcdf2091f253cbdbab1e0e9ae7b9e509dcaa2a5e38 + md5: f766549260d6815b0c52253f1fb1bb29 + depends: + - font-ttf-dejavu-sans-mono + - font-ttf-inconsolata + - font-ttf-source-code-pro + - font-ttf-ubuntu + license: BSD-3-Clause + license_family: BSD + size: 4102 + timestamp: 1566932280397 +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda + sha256: b2e3c449ec9d907dd4656cb0dc93e140f447175b125a3824b31368b06c666bb6 + md5: 9ae35c3d96db2c94ce0cef86efdfa2cb + depends: + - libgcc-ng >=12 + - libpng >=1.6.39,<1.7.0a0 + - libzlib >=1.2.13,<2.0.0a0 + license: GPL-2.0-only OR FTL + size: 634972 + timestamp: 1694615932610 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2 + sha256: 5d7b6c0ee7743ba41399e9e05a58ccc1cfc903942e49ff6f677f6e423ea7a627 + md5: ac7bc6a654f8f41b352b38f4051135f8 + depends: + - libgcc-ng >=7.5.0 + license: LGPL-2.1 + size: 114383 + timestamp: 1604416621168 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-15.1.0-h33e79ad_3.conda + sha256: 69cc03e5b20e064ad5a9ac88ac3b792a1b089c838fa4ab5eb56990a9c166ce5a + md5: 45ddc7242fed76e1b4ff5ee062e6d926 + depends: + - conda-gcc-specs + - gcc_impl_linux-64 15.1.0.* + license: BSD-3-Clause + license_family: BSD + size: 30820 + timestamp: 1750808688614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-15.1.0-h4393ad2_3.conda + sha256: fed91c71f8e7b3f0970f883c589d7dd74a3503b7020d0be273cea301a64ee3af + md5: f39f96280dd8b1ec8cbd395a3d3fdd1e + depends: + - binutils_impl_linux-64 >=2.40 + - libgcc >=15.1.0 + - libgcc-devel_linux-64 15.1.0 h4c094af_103 + - libgomp >=15.1.0 + - libsanitizer 15.1.0 h97b714f_3 + - libstdcxx >=15.1.0 + - sysroot_linux-64 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 76098052 + timestamp: 1750808341511 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glib-2.80.2-hf974151_0.conda + sha256: d10a0f194d2c125617352a81a4ff43a17cf5835e88e8f151da9f9710e2db176d + md5: d427988dc3dbd0a4c136f52db356cc6a + depends: + - glib-tools 2.80.2 hb6ce0ca_0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libglib 2.80.2 hf974151_0 + - python * + license: LGPL-2.1-or-later + size: 600389 + timestamp: 1715252749399 +- conda: https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.80.2-hb6ce0ca_0.conda + sha256: 221cd047f998301b96b1517d9f7d3fb0e459e8ee18778a1211f302496f6e110d + md5: a965aeaf060289528a3fbe09326edae2 + depends: + - libgcc-ng >=12 + - libglib 2.80.2 hf974151_0 + license: LGPL-2.1-or-later + size: 114359 + timestamp: 1715252713902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.14-h5888daf_0.conda + sha256: cac69f3ff7756912bbed4c28363de94f545856b35033c0b86193366b95f5317d + md5: 951ff8d9e5536896408e89d63230b8d5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: LGPL-2.0-or-later + license_family: LGPL + size: 98419 + timestamp: 1750079957535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-15.1.0-h33e79ad_3.conda + sha256: b071ff43b64e9c8366411f1d2a8030fb9769c0c588ffa6e16a498957dba96f45 + md5: 5917fb5bed3e2fa4fce4fbbe1b541946 + depends: + - gcc 15.1.0.* + - gxx_impl_linux-64 15.1.0.* + license: BSD-3-Clause + license_family: BSD + size: 30268 + timestamp: 1750808715356 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.1.0-h6a1bac1_3.conda + sha256: 8208b77f3d97f2ce5722f1c5623b829caabe5859e30c2a49417a26405bcbc12a + md5: d71cc504fcfdbee8dd7925ebb9c2bf85 + depends: + - gcc_impl_linux-64 15.1.0 h4393ad2_3 + - libstdcxx-devel_linux-64 15.1.0 h4c094af_103 + - sysroot_linux-64 + - tzdata + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 15489397 + timestamp: 1750808654357 +- conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2 + sha256: d9ca0d32a771554a06406bbf6ab8f41b5d5899e1de46a54c23fe575753f80223 + md5: ac83998fdc71721da8c8f0846a25a503 + depends: + - cairo >=1.16.0,<2.0.0a0 + - freetype >=2.10.4,<3.0a0 + - graphite2 + - icu >=70.1,<71.0a0 + - libgcc-ng >=12 + - libglib >=2.70.2,<3.0a0 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + size: 2096772 + timestamp: 1656503454174 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2 + sha256: 1d7950f3be4637ab915d886304e57731d39a41ab705ffc95c4681655c459374a + md5: 87473a15119779e021c314249d4b4aed + depends: + - libgcc-ng >=10.3.0 + - libstdcxx-ng >=10.3.0 + license: MIT + license_family: MIT + size: 14191488 + timestamp: 1648050221778 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda + sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca + md5: 6837f3eff7dcea42ecd714ce1ac2b108 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 11474 + timestamp: 1733223232820 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2 + sha256: 0110ee167e8fe386f9019f98757e299a0c42dc6ccdcce161c9bb552b79e459a3 + md5: ee8b844357a0946870901c7c6f418268 + depends: + - libgcc-ng >=12 + license: IJG + size: 275125 + timestamp: 1656500806614 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda + sha256: a922841ad80bd7b222502e65c07ecb67e4176c4fa5b03678a005f39fcc98be4b + md5: ad8527bf134a90e1c9ed35fa0b64318c + constrains: + - sysroot_linux-64 ==2.17 + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later AND MPL-2.0 + license_family: GPL + size: 943486 + timestamp: 1729794504440 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 + sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb + md5: 30186d27e2c9fa62b45fb1476b7200e3 + depends: + - libgcc-ng >=10.3.0 + license: LGPL-2.1-or-later + size: 117831 + timestamp: 1646151697040 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda + sha256: 51a346807ce981e1450eb04c3566415b05eed705bc9e6c98c198ec62367b7c62 + md5: 89a41adce7106749573d883b2f657d78 + depends: + - keyutils >=1.6.1,<2.0a0 + - libedit >=3.1.20191231,<3.2.0a0 + - libedit >=3.1.20191231,<4.0a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - openssl >=3.0.7,<4.0a0 + license: MIT + license_family: MIT + size: 1329877 + timestamp: 1671091750695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_5.conda + sha256: de097284f497b391fe9d000c75b684583c30aad172d9508ed05df23ce39d75cb + md5: acd9213a63cb62521290e581ef82de80 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - binutils_impl_linux-64 2.43 + license: GPL-3.0-only + license_family: GPL + size: 670525 + timestamp: 1749852860076 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libass-0.14.0-hc2a3f6d_3.tar.bz2 + sha256: dc20ef020b864cc034e117aba344cb9893084b776bcec89599b820dde18abedc + md5: 78e21631f18d404594bf51c2a8cfede7 + depends: + - expat >=2.4.7,<3.0a0 + - fontconfig >=2.13.96,<3.0a0 + - fonts-conda-ecosystem + - freetype >=2.10.4,<3.0a0 + - fribidi >=1.0.10,<2.0a0 + - harfbuzz >=4.0.1,<5.0a0 + - libgcc-ng >=10.3.0 + - libzlib >=1.2.11,<2.0.0a0 + license: ISC + license_family: OTHER + size: 122389 + timestamp: 1647824984482 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-32_h59b9bed_openblas.conda + build_number: 32 + sha256: 1540bf739feb446ff71163923e7f044e867d163c50b605c8b421c55ff39aa338 + md5: 2af9f3d5c2e39f417ce040f5a35c40c6 + depends: + - libopenblas >=0.3.30,<0.3.31.0a0 + - libopenblas >=0.3.30,<1.0a0 + constrains: + - libcblas 3.9.0 32*_openblas + - mkl <2025 + - liblapacke 3.9.0 32*_openblas + - blas 2.132 openblas + - liblapack 3.9.0 32*_openblas + license: BSD-3-Clause + license_family: BSD + size: 17330 + timestamp: 1750388798074 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hb9d3cd8_3.conda + sha256: 462a8ed6a7bb9c5af829ec4b90aab322f8bcd9d8987f793e6986ea873bbd05cf + md5: cb98af5db26e3f482bebb80ce9d947d3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 69233 + timestamp: 1749230099545 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hb9d3cd8_3.conda + sha256: 3eb27c1a589cbfd83731be7c3f19d6d679c7a444c3ba19db6ad8bf49172f3d83 + md5: 1c6eecffad553bde44c5238770cfb7da + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.1.0 hb9d3cd8_3 + - libgcc >=13 + license: MIT + license_family: MIT + size: 33148 + timestamp: 1749230111397 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hb9d3cd8_3.conda + sha256: 76e8492b0b0a0d222bfd6081cae30612aa9915e4309396fdca936528ccf314b7 + md5: 3facafe58f3858eb95527c7d3a3fc578 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.1.0 hb9d3cd8_3 + - libgcc >=13 + license: MIT + license_family: MIT + size: 282657 + timestamp: 1749230124839 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-32_he106b2a_openblas.conda + build_number: 32 + sha256: 92a001fc181e6abe4f4a672b81d9413ca2f22609f8a95327dfcc6eee593ffeb9 + md5: 3d3f9355e52f269cd8bc2c440d8a5263 + depends: + - libblas 3.9.0 32_h59b9bed_openblas + constrains: + - blas 2.132 openblas + - liblapack 3.9.0 32*_openblas + - liblapacke 3.9.0 32*_openblas + license: BSD-3-Clause + license_family: BSD + size: 17308 + timestamp: 1750388809353 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.1.2-h409715c_0.conda + sha256: d572c31ff48d2db6ca5bab476bf325811cfc82577480b3791487c3fe7bff2ffa + md5: 50c873c9660ed116707ae15b663928d8 + depends: + - krb5 >=1.20.1,<1.21.0a0 + - libgcc-ng >=12 + - libnghttp2 >=1.52.0,<2.0a0 + - libssh2 >=1.10.0,<2.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - openssl >=3.1.0,<4.0a0 + - zstd >=1.5.2,<1.6.0a0 + license: curl + license_family: MIT + size: 372833 + timestamp: 1685447685782 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.125-hb9d3cd8_0.conda + sha256: f53458db897b93b4a81a6dbfd7915ed8fa4a54951f97c698dde6faa028aadfd2 + md5: 4c0ab57463117fbb8df85268415082f5 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libpciaccess >=0.18,<0.19.0a0 + license: MIT + license_family: MIT + size: 246161 + timestamp: 1749904704373 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda + sha256: d789471216e7aba3c184cd054ed61ce3f6dac6f87a50ec69291b9297f8c18724 + md5: c277e0a4d549b03ac1e9d6cbbe3d017b + depends: + - ncurses + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: BSD-2-Clause + license_family: BSD + size: 134676 + timestamp: 1738479519902 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda + sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 + md5: 172bf1cd1ff8629f2b1179945ed45055 + depends: + - libgcc-ng >=12 + license: BSD-2-Clause + license_family: BSD + size: 112766 + timestamp: 1702146165126 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + sha256: 33ab03438aee65d6aa667cf7d90c91e5e7d734c19a67aa4c7040742c0a13d505 + md5: db0bfbe7dd197b68ad5f30333bae6ce0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - expat 2.7.0.* + license: MIT + license_family: MIT + size: 74427 + timestamp: 1743431794976 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab + md5: ede4673863426c0883c0063d853bbd85 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 57433 + timestamp: 1743434498161 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_3.conda + sha256: 59a87161212abe8acc57d318b0cc8636eb834cdfdfddcf1f588b5493644b39a3 + md5: 9e60c55e725c20d23125a5f0dd69af5d + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==15.1.0=*_3 + - libgomp 15.1.0 h767d61c_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 824921 + timestamp: 1750808216066 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-15.1.0-h4c094af_103.conda + sha256: 770a59a79f83dbd8ada4565b08633b429c0c54040b2b1f78c4db4648c7c7a805 + md5: ea67e87d658d31dc33818f9574563269 + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 2728790 + timestamp: 1750808123770 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_3.conda + sha256: b0b0a5ee6ce645a09578fc1cb70c180723346f8a45fdb6d23b3520591c6d6996 + md5: e66f2b8ad787e7beb0f846e4bd7e8493 + depends: + - libgcc 15.1.0 h767d61c_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 29033 + timestamp: 1750808224854 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_3.conda + sha256: 77dd1f1efd327e6991e87f09c7c97c4ae1cfbe59d9485c41d339d6391ac9c183 + md5: bfbca721fd33188ef923dfe9ba172f29 + depends: + - libgfortran5 15.1.0 hcea5267_3 + constrains: + - libgfortran-ng ==15.1.0=*_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 29057 + timestamp: 1750808257258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_3.conda + sha256: eea6c3cf22ad739c279b4d665e6cf20f8081f483b26a96ddd67d4df3c88dfa0a + md5: 530566b68c3b8ce7eec4cd047eae19fe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.1.0 + constrains: + - libgfortran 15.1.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 1565627 + timestamp: 1750808236464 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.80.2-hf974151_0.conda + sha256: 93e03b6cf4765bc06d64fa3dac65f22c53ae4a30247bb0e2dea0bd9c47a3fb26 + md5: 72724f6a78ecb15559396966226d5838 + depends: + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libiconv >=1.17,<2.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - pcre2 >=10.43,<10.44.0a0 + constrains: + - glib 2.80.2 *_0 + license: LGPL-2.1-or-later + size: 3912673 + timestamp: 1715252654366 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_3.conda + sha256: 43710ab4de0cd7ff8467abff8d11e7bb0e36569df04ce1c099d48601818f11d1 + md5: 3cd1a7238a0dd3d0860fdefc496cc854 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 447068 + timestamp: 1750808138400 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.9.1-hd6dc26d_0.conda + sha256: 39bb53aa6ae0cab734568a58ad31ffe82ea244a82f575cd5c67abba785e442ee + md5: a3ede1b8e47f993ff1fe3908b23bb307 + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - libxml2 >=2.10.3,<2.14.0a0 + license: BSD-3-Clause + license_family: BSD + size: 2569104 + timestamp: 1680713440274 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + sha256: 18a4afe14f731bfb9cf388659994263904d20111e42f841e9eea1bb6f91f4ab4 + md5: e796ff8ddc598affdf7c173d6145f087 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + size: 713084 + timestamp: 1740128065462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2 + sha256: 0d8b666ca4deabf948a76654df0fa1277145bed1c9e8a58e18a649c22c5f1c3e + md5: b4f717df2d377410b462328bf0e8fb7d + depends: + - libgcc-ng >=12 + license: IJG, modified 3-clause BSD and zlib + size: 1011471 + timestamp: 1664781971968 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-32_h7ac8fdf_openblas.conda + build_number: 32 + sha256: 5b55a30ed1b3f8195dad9020fe1c6d0f514829bfaaf0cf5e393e93682af009f2 + md5: 6c3f04ccb6c578138e9f9899da0bd714 + depends: + - libblas 3.9.0 32_h59b9bed_openblas + constrains: + - libcblas 3.9.0 32*_openblas + - blas 2.132 openblas + - liblapacke 3.9.0 32*_openblas + license: BSD-3-Clause + license_family: BSD + size: 17316 + timestamp: 1750388820745 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_2.conda + sha256: f2591c0069447bbe28d4d696b7fcb0c5bd0b4ac582769b89addbcf26fb3430d8 + md5: 1a580f7796c7bf6393fddb8bbbde58dc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + constrains: + - xz 5.8.1.* + license: 0BSD + size: 112894 + timestamp: 1749230047870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-devel-5.8.1-hb9d3cd8_2.conda + sha256: 329e66330a8f9cbb6a8d5995005478188eb4ba8a6b6391affa849744f4968492 + md5: f61edadbb301530bd65a32646bd81552 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - liblzma 5.8.1 hb9d3cd8_2 + license: 0BSD + size: 439868 + timestamp: 1749230061968 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.58.0-h47da74e_0.conda + sha256: 151b18e4f92dcca263a6d23e4beb0c4e2287aa1c7d0587ff71ef50035ed34aca + md5: 9b13d5ee90fc9f09d54fd403247342b4 + depends: + - c-ares >=1.21.0,<2.0a0 + - libev >=4.33,<4.34.0a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + - openssl >=3.1.4,<4.0a0 + license: MIT + license_family: MIT + size: 631397 + timestamp: 1699440427647 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda + sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 + md5: d864d34357c3b65a4b731f78c0801dc4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-only + license_family: GPL + size: 33731 + timestamp: 1750274110928 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.5-hd0c01bc_1.conda + sha256: ffb066ddf2e76953f92e06677021c73c85536098f1c21fcd15360dbc859e22e4 + md5: 68e52064ed3897463c0e958ab5c8f91b + depends: + - libgcc >=13 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + size: 218500 + timestamp: 1745825989535 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_0.conda + sha256: 225f4cfdb06b3b73f870ad86f00f49a9ca0a8a2d2afe59440521fafe2b6c23d9 + md5: 323dc8f259224d13078aaf7ce96c3efe + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + constrains: + - openblas >=0.3.30,<0.3.31.0a0 + license: BSD-3-Clause + license_family: BSD + size: 5916819 + timestamp: 1750379877844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hb9d3cd8_0.conda + sha256: 0bd91de9b447a2991e666f284ae8c722ffb1d84acb594dbd0c031bd656fa32b2 + md5: 70e3400cbbfa03e96dcde7fc13e38c7b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 28424 + timestamp: 1749901812541 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda + sha256: 502f6ff148ac2777cc55ae4ade01a8fc3543b4ffab25c4e0eaa15f94e90dd997 + md5: 009981dd9cfcaa4dbfa25ffaed86bcae + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: zlib-acknowledgement + size: 288221 + timestamp: 1708780443939 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-15.1.0-h97b714f_3.conda + sha256: 75fa45d8ea2344bfe96667b478fd00f1d038026f399cb680eceec836c90e67bc + md5: bbcff9bf972a0437bea8e431e4b327bb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.1.0 + - libstdcxx >=15.1.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 5224736 + timestamp: 1750808272292 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.0-hde9e2c9_0.conda + sha256: daee3f68786231dad457d0dfde3f7f1f9a7f2018adabdbb864226775101341a8 + md5: 18aa975d2094c34aef978060ae7da7d8 + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0a0 + license: Unlicense + size: 865346 + timestamp: 1718050628718 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda + sha256: 50e47fd9c4f7bf841a11647ae7486f65220cfc988ec422a4475fe8d5a823824d + md5: 1f5a58e686b13bcfde88b93f547d23fe + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + - openssl >=3.1.1,<4.0a0 + license: BSD-3-Clause + license_family: BSD + size: 271133 + timestamp: 1685837707056 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_3.conda + sha256: 7650837344b7850b62fdba02155da0b159cf472b9ab59eb7b472f7bd01dff241 + md5: 6d11a5edae89fe413c0569f16d308f5a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.1.0 h767d61c_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 3896407 + timestamp: 1750808251302 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-15.1.0-h4c094af_103.conda + sha256: 3b68242567a104738d6902629e7485a9636e262c66a681e93c0a058656f1b8a3 + md5: 83bbc814f0aeccccb5ea10267bea0d2e + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 14743289 + timestamp: 1750808154361 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_3.conda + sha256: bbaea1ecf973a7836f92b8ebecc94d3c758414f4de39d2cc6818a3d10cb3216b + md5: 57541755b5a51691955012b8e197c06c + depends: + - libstdcxx 15.1.0 h8f9b012_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + size: 29093 + timestamp: 1750808292700 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 + md5: 40b61aab5c7ba9ff276c41cfffe6b80b + depends: + - libgcc-ng >=12 + license: BSD-3-Clause + license_family: BSD + size: 33601 + timestamp: 1680112270483 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb9d3cd8_0.conda + sha256: 770ca175d64323976c9fe4303042126b2b01c1bd54c8c96cafeaba81bdb481b8 + md5: 1349c022c92c5efd3fd705a79a5804d8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 890145 + timestamp: 1748304699136 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libva-2.18.0-h0b41bf4_0.conda + sha256: e7254d0111a403ffe707e2ad39b6ce49a2be733e751d14a7255b0cb20da2a16b + md5: 56e049224de34bbe0478aad422227942 + depends: + - libdrm >=2.4.114,<2.5.0a0 + - libgcc-ng >=12 + - xorg-libx11 >=1.8.4,<2.0a0 + - xorg-libxext >=1.3.4,<2.0a0 + - xorg-libxfixes + license: MIT + license_family: MIT + size: 186715 + timestamp: 1679400122269 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2 + sha256: 53080d72388a57b3c31ad5805c93a7328e46ff22fab7c44ad2a86d712740af33 + md5: 309dec04b70a3cc0f1e84a4013683bc0 + depends: + - libgcc-ng >=9.3.0 + - libogg >=1.3.4,<1.4.0a0 + - libstdcxx-ng >=9.3.0 + license: BSD-3-Clause + license_family: BSD + size: 286280 + timestamp: 1610609811627 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2 + sha256: 8d5d24cbeda9282dd707edd3156e5fde2e3f3fe86c802fa7ce08c8f1e803bfd9 + md5: b3653fdc58d03face9724f602218a904 + depends: + - libgcc-ng >=9.4.0 + - pthread-stubs + - xorg-libxau + - xorg-libxdmcp + license: MIT + license_family: MIT + size: 399895 + timestamp: 1636658924671 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c + md5: 5aa797f8787fe7a17d1b0821485b5adc + depends: + - libgcc-ng >=12 + license: LGPL-2.1-or-later + size: 100393 + timestamp: 1702724383534 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-hca2bb57_4.conda + sha256: d4170f1fe356768758b13a51db123f990bff81b0eae0d5a0ba11c7ca6b9536f4 + md5: bb808b654bdc3c783deaf107a2ffb503 + depends: + - icu >=70.1,<71.0a0 + - libgcc-ng >=12 + - libiconv >=1.17,<2.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - xz >=5.2.6,<6.0a0 + license: MIT + license_family: MIT + size: 713891 + timestamp: 1679341466192 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h4ab18f5_6.conda + sha256: 8ced4afed6322172182af503f21725d072a589a6eb918f8a58135c1e00d35980 + md5: 27329162c0dc732bcf67a4e0cd488125 + depends: + - libgcc-ng >=12 + constrains: + - zlib 1.2.13 *_6 + license: Zlib + license_family: Other + size: 61571 + timestamp: 1716874066944 +- conda: https://conda.anaconda.org/conda-forge/linux-64/make-4.4.1-hb9d3cd8_2.conda + sha256: d652c7bd4d3b6f82b0f6d063b0d8df6f54cc47531092d7ff008e780f3261bdda + md5: 33405d2a66b1411db9f7242c8b97c9e7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: GPL-3.0-or-later + license_family: GPL + size: 513088 + timestamp: 1727801714848 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: X11 AND BSD-3-Clause + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.13.0-h7aa8ee6_0.conda + sha256: 8cf09470430b5aba5165c7aefed070d2c8f998f69fede01197ef838bf17fa81a + md5: 2f67cb5c5ec172faeba94348ae8af444 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=13 + - libgcc >=13 + license: Apache-2.0 + license_family: APACHE + size: 180917 + timestamp: 1750273173789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py312heda63a1_0.conda + sha256: fe3459c75cf84dcef6ef14efcc4adb0ade66038ddd27cadb894f34f4797687d8 + md5: d8285bea2a350f63fab23bf460221f3f + depends: + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc-ng >=12 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx-ng >=12 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + size: 7484186 + timestamp: 1707225809722 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.1-h7b32b05_0.conda + sha256: 942347492164190559e995930adcdf84e2fea05307ec8012c02a505f5be87462 + md5: c87df2ab1448ba69169652ab9547082d + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=13 + license: Apache-2.0 + license_family: Apache + size: 3131002 + timestamp: 1751390382076 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 + md5: 58335b26c38bf4a20f399384c33cbcf9 + depends: + - python >=3.8 + - python + license: Apache-2.0 + license_family: APACHE + size: 62477 + timestamp: 1745345660407 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2 + sha256: 8f35c244b1631a4f31fb1d66ab6e1d9bfac0ca9b679deced1112c7225b3ad138 + md5: c05d1820a6d34ff07aaaab7a9b7eddaa + depends: + - libgcc-ng >=9.3.0 + - libstdcxx-ng >=9.3.0 + license: BSD-3-Clause + license_family: BSD + size: 259377 + timestamp: 1623788789327 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda + sha256: 766dd986a7ed6197676c14699000bba2625fd26c8a890fcb7a810e5cf56155bc + md5: 8292dea9e022d9610a11fce5e0896ed8 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: BSD-3-Clause + license_family: BSD + size: 950847 + timestamp: 1708118050286 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda + sha256: ebfa591d39092b111b9ebb3210eb42251be6da89e26c823ee03e5e838655a43e + md5: 32d0781ace05105cc99af55d36cbec7c + depends: + - python >=3.9,<3.13.0a0 + - setuptools + - wheel + license: MIT + license_family: MIT + size: 1242995 + timestamp: 1746249983238 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.2-h29eaf8c_0.conda + sha256: 6cb261595b5f0ae7306599f2bb55ef6863534b6d4d1bc0dcfdfa5825b0e4e53d + md5: 39b4228a867772d610c02e06f939a5b8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + license: MIT + license_family: MIT + size: 402222 + timestamp: 1749552884791 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda + sha256: c9601efb1af5391317e04eca77c6fe4d716bf1ca1ad8da2a05d15cb7c28d7d4e + md5: 1bee70681f504ea424fb07cdb090c001 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + license: GPL-2.0-or-later + license_family: GPL + size: 115175 + timestamp: 1720805894943 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhd8ed1ab_0.conda + sha256: a8eb555eef5063bbb7ba06a379fa7ea714f57d9741fe0efdb9442dbbc2cccbcc + md5: 7da7ccd349dbf6487a7778579d2bb971 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 24246 + timestamp: 1747339794916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda + sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973 + md5: b3c17d95b5a10c6e64a21fa17573e70e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 8252 + timestamp: 1726802366959 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda + sha256: 5577623b9f6685ece2697c6eb7511b4c9ac5fb607c9babc2646c811b428fd46a + md5: 6b6ece66ebcae2d5f326c77ef2c5a066 + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + size: 889287 + timestamp: 1750615908735 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.4.1-pyhd8ed1ab_0.conda + sha256: 93e267e4ec35353e81df707938a6527d5eb55c97bf54c3b87229b69523afb59d + md5: a49c2283f24696a7b30367b7346a0144 + depends: + - colorama >=0.4 + - exceptiongroup >=1 + - iniconfig >=1 + - packaging >=20 + - pluggy >=1.5,<2 + - pygments >=2.7.2 + - python >=3.9 + - tomli >=1 + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + size: 276562 + timestamp: 1750239526127 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.3-hab00c5b_0_cpython.conda + sha256: f9865bcbff69f15fd89a33a2da12ad616e98d65ce7c83c644b92e66e5016b227 + md5: 2540b74d304f71d3e89c81209db4db84 + depends: + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.6.2,<3.0a0 + - libffi >=3.4,<4.0a0 + - libgcc-ng >=12 + - libnsl >=2.0.1,<2.1.0a0 + - libsqlite >=3.45.2,<4.0a0 + - libuuid >=2.38.1,<3.0a0 + - libxcrypt >=4.4.36 + - libzlib >=1.2.13,<2.0.0a0 + - ncurses >=6.4.20240210,<7.0a0 + - openssl >=3.2.1,<4.0a0 + - readline >=8.2,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - xz >=5.2.6,<6.0a0 + constrains: + - python_abi 3.12.* *_cp312 + license: Python-2.0 + size: 31991381 + timestamp: 1713208036041 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda + build_number: 7 + sha256: a1bbced35e0df66cc713105344263570e835625c28d1bdee8f748f482b2d7793 + md5: 0dfcdc155cf23812a0c9deada86fb723 + constrains: + - python 3.12.* *_cpython + license: BSD-3-Clause + license_family: BSD + size: 6971 + timestamp: 1745258861359 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 + depends: + - libgcc >=13 + - ncurses >=6.5,<7.0a0 + license: GPL-3.0-only + license_family: GPL + size: 282480 + timestamp: 1740379431762 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rhash-1.4.3-hd590300_2.conda + sha256: 475f68cac8981ff2b10c56e53c2f376fc3c805fbc7ec30d22f870cd88f1479ba + md5: 4cabe3858a856bff08d9a0992e413084 + depends: + - libgcc-ng >=12 + license: MIT + license_family: MIT + size: 184509 + timestamp: 1693427593121 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.9.0-pyhff2d567_0.conda + sha256: 972560fcf9657058e3e1f97186cc94389144b46dbdf58c807ce62e83f977e863 + md5: 4de79c071274a53dcaf2a8c749d1499e + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 748788 + timestamp: 1748804951958 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda + sha256: 69ab5804bdd2e8e493d5709eebff382a72fab3e9af6adf93a237ccf8f7dbd624 + md5: 460eba7851277ec1fd80a1a24080787a + depends: + - kernel-headers_linux-64 3.10.0 he073ed8_18 + - tzdata + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later AND MPL-2.0 + license_family: GPL + size: 15166921 + timestamp: 1735290488259 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.9.0-hf52228f_0.conda + sha256: 86352f4361e8dc2374a95d9d1dfee742beecaa59dcb0e76ca36ca06a4efe1df2 + md5: f495e42d3d2020b025705625edf35490 + depends: + - libgcc-ng >=12 + - libhwloc >=2.9.1,<2.9.2.0a0 + - libstdcxx-ng >=12 + license: Apache-2.0 + license_family: APACHE + size: 1527865 + timestamp: 1681486787952 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e + md5: d453b98d9c83e71da0741bb0ff4d76bc + depends: + - libgcc-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: TCL + license_family: BSD + size: 3318875 + timestamp: 1699202167581 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda + sha256: 18636339a79656962723077df9a56c0ac7b8a864329eb8f847ee3d38495b863e + md5: ac944244f1fed2eb49bae07193ae8215 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 19167 + timestamp: 1733256819729 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.0-pyhe01879c_0.conda + sha256: 8561db52f278c5716b436da6d4ee5521712a49e8f3c70fcae5350f5ebb4be41c + md5: 2adcd9bb86f656d3d43bf84af59a1faf + depends: + - python >=3.9 + - python + license: PSF-2.0 + license_family: PSF + size: 50978 + timestamp: 1748959427551 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a + license: LicenseRef-Public-Domain + size: 122968 + timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce + md5: 75cb7132eb58d97896e173ef12ac9986 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 62931 + timestamp: 1733130309598 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-fixesproto-5.0-hb9d3cd8_1003.conda + sha256: 07268980b659a84a4bac64b475329348e9cf5fa4aee255fa94aa0407ae5b804c + md5: 19fe37721037acc0a1ed76b8cf937359 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-xextproto >=7.3.0,<8.0a0 + license: MIT + license_family: MIT + size: 11311 + timestamp: 1727033761080 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-hb9d3cd8_1003.conda + sha256: 849555ddf7fee334a5a6be9f159d2931c9d076ffb310a9e75b9124f789049d3e + md5: e87bfacb110d85e1eb6099c9ed8e7236 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 30242 + timestamp: 1726846706299 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda + sha256: c12396aabb21244c212e488bbdc4abcdef0b7404b15761d9329f5a4a39113c4b + md5: fb901ff28063514abb6046c9ec2c4a45 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 58628 + timestamp: 1734227592886 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 + md5: 1c74ff8c35dcadf952a16f752ca5aa49 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libuuid >=2.38.1,<3.0a0 + - xorg-libice >=1.1.2,<2.0a0 + license: MIT + license_family: MIT + size: 27590 + timestamp: 1741896361728 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.4-h0b41bf4_0.conda + sha256: 3c6862a01a39cdea3870b132706ad7256824299947a3a94ae361d863d402d704 + md5: ea8fbfeb976ac49cbeb594e985393514 + depends: + - libgcc-ng >=12 + - libxcb 1.* + - libxcb >=1.13,<1.14.0a0 + - xorg-kbproto + - xorg-xextproto >=7.3.0,<8.0a0 + - xorg-xproto + license: MIT + license_family: MIT + size: 829872 + timestamp: 1677611125385 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda + sha256: ed10c9283974d311855ae08a16dfd7e56241fac632aec3b92e3cfe73cff31038 + md5: f6ebe2cb3f82ba6c057dde5d9debe4f7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 14780 + timestamp: 1734229004433 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda + sha256: 6b250f3e59db07c2514057944a3ea2044d6a8cdde8a47b6497c254520fade1ee + md5: 8035c64cb77ed555e3f150b7b3972480 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 19901 + timestamp: 1727794976192 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h0b41bf4_2.conda + sha256: 73e5cfbdff41ef8a844441f884412aa5a585a0f0632ec901da035a03e1fe1249 + md5: 82b6df12252e6f32402b96dacc656fec + depends: + - libgcc-ng >=12 + - xorg-libx11 >=1.7.2,<2.0a0 + - xorg-xextproto + license: MIT + license_family: MIT + size: 50143 + timestamp: 1677036907815 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-5.0.3-h7f98852_1004.tar.bz2 + sha256: 1e426a1abb774ef1dcf741945ed5c42ad12ea2dc7aeed7682d293879c3e1e4c3 + md5: e9a21aa4d5e3e5f1aed71e8cefd46b6a + depends: + - libgcc-ng >=9.3.0 + - xorg-fixesproto + - xorg-libx11 >=1.7.0,<2.0a0 + license: MIT + license_family: MIT + size: 18145 + timestamp: 1617717802636 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2 + sha256: 7d907ed9e2ec5af5d7498fb3ab744accc298914ae31497ab6dcc6ef8bd134d00 + md5: f59c1242cc1dd93e72c2ee2b360979eb + depends: + - libgcc-ng >=9.3.0 + - xorg-libx11 >=1.7.0,<2.0a0 + - xorg-renderproto + license: MIT + license_family: MIT + size: 32906 + timestamp: 1614866792944 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-hb9d3cd8_1003.conda + sha256: 54dd934b0e1c942e54759eb13672fd59b7e523fabea6e69a32d5bf483e45b329 + md5: bf90782559bce8447609933a7d45995a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 11867 + timestamp: 1726802820431 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-hb9d3cd8_1004.conda + sha256: f302a3f6284ee9ad3b39e45251d7ed15167896564dc33e006077a896fd3458a6 + md5: bc4cd53a083b6720d61a1519a1900878 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 30549 + timestamp: 1726846235301 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-hb9d3cd8_1008.conda + sha256: ea02425c898d6694167952794e9a865e02e14e9c844efb067374f90b9ce8ce33 + md5: a63f5b66876bb1ec734ab4bdc4d11e86 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: MIT + license_family: MIT + size: 73315 + timestamp: 1726845753874 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xz-5.8.1-hbcc6ac9_2.conda + sha256: 802725371682ea06053971db5b4fb7fbbcaee9cb1804ec688f55e51d74660617 + md5: 68eae977d7d1196d32b636a026dc015d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - liblzma 5.8.1 hb9d3cd8_2 + - liblzma-devel 5.8.1 hb9d3cd8_2 + - xz-gpl-tools 5.8.1 hbcc6ac9_2 + - xz-tools 5.8.1 hb9d3cd8_2 + license: 0BSD AND LGPL-2.1-or-later AND GPL-2.0-or-later + size: 23987 + timestamp: 1749230104359 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xz-gpl-tools-5.8.1-hbcc6ac9_2.conda + sha256: 840838dca829ec53f1160f3fca6dbfc43f2388b85f15d3e867e69109b168b87b + md5: bf627c16aa26231720af037a2709ab09 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - liblzma 5.8.1 hb9d3cd8_2 + constrains: + - xz 5.8.1.* + license: 0BSD AND LGPL-2.1-or-later AND GPL-2.0-or-later + size: 33911 + timestamp: 1749230090353 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xz-tools-5.8.1-hb9d3cd8_2.conda + sha256: 58034f3fca491075c14e61568ad8b25de00cb3ae479de3e69be6d7ee5d3ace28 + md5: 1bad2995c8f1c8075c6c331bf96e46fb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - liblzma 5.8.1 hb9d3cd8_2 + constrains: + - xz 5.8.1.* + license: 0BSD AND LGPL-2.1-or-later + size: 96433 + timestamp: 1749230076687 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h4ab18f5_6.conda + sha256: 534824ea44939f3e59ca8ebb95e3ece6f50f9d2a0e69999fbc692311252ed6ac + md5: 559d338a4234c2ad6e676f460a093e67 + depends: + - libgcc-ng >=12 + - libzlib 1.2.13 h4ab18f5_6 + license: Zlib + license_family: Other + size: 92883 + timestamp: 1716874088980 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + sha256: c558b9cc01d9c1444031bd1ce4b9cff86f9085765f17627a6cd85fc623c8a02b + md5: 4d056880988120e29d75bfff282e0f45 + depends: + - libgcc-ng >=12 + - libstdcxx-ng >=12 + - libzlib >=1.2.13,<2.0.0a0 + license: BSD-3-Clause + license_family: BSD + size: 554846 + timestamp: 1714722996770 diff --git a/pixi.toml b/pixi.toml new file mode 100644 index 0000000..e9bb343 --- /dev/null +++ b/pixi.toml @@ -0,0 +1,46 @@ +[project] +name = "spatialmp4" +channels = ["conda-forge"] +platforms = ["linux-64"] + +[tasks] +build-ffmpeg = "bash scripts/build_ffmpeg.sh" +build = "cmake -G Ninja -DCMAKE_PREFIX_PATH=$CONDA_PREFIX -B build -S . && cmake --build build" +rebuild = "rm -rf build; cmake -B build -S . && cmake --build build" +test = "cd build && VIDEO=../video/test.mp4 ./test_reader" +test-python = "pytest -s python/tests" + +[dependencies] +cmake = ">=3.24.1,<4.0" +ninja = ">=1.13.0,<2" +make = "*" +pkg-config = "*" +zlib = "*" +libpng = ">=1.6.43,<2" +libjpeg-turbo = "*" +freetype = "*" +harfbuzz = "*" +libass = "*" +fontconfig = "*" +expat = "*" +glib = "*" +pcre = "*" +graphite2 = "*" +fribidi = "*" +brotli = "*" +tbb = ">=2021.9.0,<2023" +pytest = ">=8.4.1,<9" +setuptools = ">=80.9.0,<81" +jpeg = ">=9e,<10a" +libvorbis = ">=1.3.7,<2" +numpy = "<2.0" +pip = ">=25.1.1,<26" + +[target.linux-64.dependencies] +gcc = "*" +gxx = "*" +libdrm = "*" +libxcb = "*" +libva = "*" +alsa-lib = "*" +libuuid = "*" \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8995a95 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[build-system] +build-backend = "cmeel" +requires = [ + "cmeel[build]", + "cmake<4", + "doxystub >= 0.0.13" +] + +[project] +name = "spatialmp4" +version = "0.1.0" +description = "SpatialMP4 Python Package" +authors = [{ email = "bingwen.ai@bytedance.com", name = "Bingwen Wang" }] +license = "MIT" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +dependencies = [ + "numpy", + "opencv-python" +] +dependencies-dev = [ + "pytest", + "setuptools", + "pip", + "wheel", + "twine", + "cmeel", + "cmake", + "build", +] + +[project.urls] +repository = "https://github.com/Pico-Developer/SpatialMP4" + +[tool.cmeel] +run-tests = false + +[tool.cibuildwheel] +before-all = "bash scripts/install_deps.sh && bash scripts/build_ffmpeg.sh" +build = "cp3*-*" +skip = "pp* *-win32 *-win_amd64" \ No newline at end of file diff --git a/python/TODO b/python/TODO deleted file mode 100644 index e69de29..0000000 diff --git a/bindings/TODO b/python/tests/__init__.py similarity index 100% rename from bindings/TODO rename to python/tests/__init__.py diff --git a/python/tests/test_reader.py b/python/tests/test_reader.py new file mode 100644 index 0000000..9cf0921 --- /dev/null +++ b/python/tests/test_reader.py @@ -0,0 +1,50 @@ +import spatialmp4 + + +def test_version(): + print(spatialmp4.__version__) + + +def test_reader(): + reader = spatialmp4.Reader("video/test.mp4") + + # Print basic information + print(f"Has RGB: {reader.has_rgb()}") + print(f"Has Depth: {reader.has_depth()}") + print(f"Has Pose: {reader.has_pose()}") + print(f"Duration: {reader.get_duration()} seconds") + print(f"RGB FPS: {reader.get_rgb_fps()}") + print(f"Depth FPS: {reader.get_depth_fps()}") + + # Get camera parameters + rgb_intrinsics = reader.get_rgb_intrinsics_left() + print(f"RGB Intrinsics: fx={rgb_intrinsics.fx}, fy={rgb_intrinsics.fy}, cx={rgb_intrinsics.cx}, cy={rgb_intrinsics.cy}") + + # Set read mode + reader.set_read_mode(spatialmp4.ReadMode.RGB_ONLY) + + # Read frames + while reader.has_next(): + # Load RGB frame + rgb_frame = reader.load_rgb() + + # Access the frame data (returns numpy arrays) + left_rgb = rgb_frame.left_rgb # Shape: (height, width, 3) + right_rgb = rgb_frame.right_rgb # Shape: (height, width, 3) + + # Get pose data + pose = rgb_frame.pose + print(f"Frame timestamp: {rgb_frame.timestamp}") + print(f"Pose: x={pose.x}, y={pose.y}, z={pose.z}, qw={pose.qw}, qx={pose.qx}, qy={pose.qy}, qz={pose.qz}") + + # Example of loading both RGB and depth + reader.reset() + reader.set_read_mode(spatialmp4.ReadMode.DEPTH_FIRST) + + if reader.has_next(): + # Load both RGB and depth frames + rgb_frame, depth_frame = reader.load_both() + + # Access depth data (returns numpy array) + depth = depth_frame.depth # Shape: (height, width) + print(f"Depth: {depth.shape}") diff --git a/scripts/build_ffmpeg.sh b/scripts/build_ffmpeg.sh index 2759cc2..b7ca258 100755 --- a/scripts/build_ffmpeg.sh +++ b/scripts/build_ffmpeg.sh @@ -7,10 +7,26 @@ cur=$(dirname $_curfile) opt=$cur/build_ffmpeg INSTALL_PREFIX=$opt/ffmpeg_install +CMAKE_EXTRA_CONFIG="" +if [ -n "$CONDA_PREFIX" ];then + INSTALL_PREFIX=$CONDA_PREFIX + CMAKE_EXTRA_CONFIG="-DCMAKE_INCLUDE_PATH=${CONDA_PREFIX}/include" +fi +echo "INSTALL_PREFIX: $INSTALL_PREFIX" + if [ ! -d $opt ];then mkdir -p $opt fi +if [[ "$(uname)" == "Darwin" ]];then + make_args="-j$(sysctl -n hw.ncpu)" +elif [[ "$(uname)" == "Linux" ]]; then + make_args="-j$(nproc)" +else + echo "Not supported windows." + exit 1 +fi + install_deps() { if [[ "$(uname)" == "Darwin" ]];then # https://trac.ffmpeg.org/wiki/CompilationGuide/macOS @@ -59,22 +75,120 @@ build_install_ffmpeg() { cd ffmpeg git reset --hard b6f84cd7 git apply $cur/ffmpeg_b6f84cd7.patch + + # Clean previous build + make distclean || true + + # EXTRA_CONFIG="--enable-shared --disable-static" + EXTRA_CONFIG="--enable-static --disable-shared --enable-pic --disable-x86asm --disable-asm" + + # if [[ "$(uname)" == "Darwin" ]];then + # EXTRA_CONFIG="--enable-shared --disable-static" + # else + # EXTRA_CONFIG="--enable-static --disable-shared --pkg-config-flags='--static'" + # fi + + # Configure with enhanced PIC support + + export CFLAGS="-fPIC" + export CXXFLAGS="-fPIC" + export ASFLAGS="-DPIC" ./configure \ - --disable-doc \ --prefix=$INSTALL_PREFIX \ - --pkg-config-flags="--static" \ - --extra-cflags="-I/usr/local/include" \ - --extra-ldflags="-L/usr/local/lib" \ + --extra-cflags="-I/usr/local/include -fPIC" \ + --extra-cxxflags="-fPIC" \ + --extra-ldflags="-L/usr/local/lib -fPIC" \ --enable-libass \ --enable-libfreetype \ --enable-libvorbis \ - --enable-version3 - + --enable-version3 \ + --disable-ffplay \ + --disable-doc \ + ${EXTRA_CONFIG} # --enable-libmp3lame \ - make -j$(nproc) + make $make_args # V=1 make install } -install_deps + +build_install_opencv() { + if [[ "$(uname)" == "Darwin" && -z "$CONDA_PREFIX" ]];then + # Darwin without pixi don't need to install opencv from source + return + fi + opencv_version="4.6.0" + cd $opt + if [ ! -d opencv ];then + git clone https://github.com/opencv/opencv.git -b ${opencv_version} + fi + + BUILD_FLAGS=" + -D CMAKE_BUILD_TYPE=RELEASE + -D BUILD_LIST=core,imgproc,imgcodecs + -D WITH_TBB=ON + -D WITH_OPENEXR=ON + -D WITH_EIGEN=ON + -D WITH_GSTREAMER=OFF + -D WITH_OPENCL=OFF + -D WITH_CUDA=OFF + -D WITH_QT=OFF + -D OPENCV_GENERATE_PKGCONFIG=ON + -D BUILD_DOCS=OFF + -D BUILD_TESTS=OFF + -D BUILD_PERF_TESTS=OFF + -D BUILD_EXAMPLES=OFF + -D BUILD_opencv_world=ON + -D BUILD_opencv_imgcodecs=ON + -D BUILD_opencv_apps=OFF + -D BUILD_opencv_calib3d=OFF + -D BUILD_opencv_dnn=OFF + -D BUILD_opencv_features2d=OFF + -D BUILD_opencv_flann=OFF + -D BUILD_opencv_gapi=OFF + -D BUILD_opencv_ml=OFF + -D BUILD_opencv_photo=OFF + -D BUILD_opencv_shape=OFF + -D BUILD_opencv_videoio=OFF + -D BUILD_opencv_videostab=OFF + -D BUILD_opencv_highgui=OFF + -D BUILD_opencv_superres=OFF + -D BUILD_opencv_stitching=OFF + -D BUILD_opencv_java=OFF + -D BUILD_opencv_java_bindings_generator=OFF + -D BUILD_opencv_js=OFF + -D BUILD_opencv_gpu=OFF + -D BUILD_opencv_gpuarithm=OFF + -D BUILD_opencv_gpubgsegm=OFF + -D BUILD_opencv_python2=OFF + -D BUILD_opencv_python3=ON + -D OPENCV_PYTHON3_INSTALL_PATH=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") + -D PYTHON_EXECUTABLE=$(which python3) + -D BUILD_SHARED_LIBS=OFF + -D OPENCV_GENERATE_PKGCONFIG=ON + -D CMAKE_POLICY_VERSION_MINIMUM=3.5 + ${CMAKE_EXTRA_CONFIG} + " + + cd $opt/opencv + git reset --hard $opencv_version + git apply $cur/opencv_460_on_macos.patch + git apply $cur/opencv_for_new_cmake.patch + + if [ -d release ];then + rm -rf release + fi + if [ -d modules/gapi ];then + rm -rf modules/gapi + fi + mkdir release && cd release + cmake $BUILD_FLAGS -D CMAKE_INSTALL_PREFIX=$INSTALL_PREFIX .. + make $make_args # V=1 + make install +} + +if [ -z "$CONDA_PREFIX" ];then + install_deps +fi build_install_ffmpeg +build_install_opencv diff --git a/scripts/ffmpeg_b6f84cd7.patch b/scripts/ffmpeg_b6f84cd7.patch index afdb314..1673a45 100644 --- a/scripts/ffmpeg_b6f84cd7.patch +++ b/scripts/ffmpeg_b6f84cd7.patch @@ -289,9 +289,9 @@ index 8a094b1ea0..6fec10583f 100644 + value_str_right[strlen(value_str_right) - 1] = '\0'; + } + av_log(c->fc, AV_LOG_DEBUG, "DSTR box: %s, %s\n", value_str_left, value_str_right); -+ ++ + AVStream *st = c->fc->streams[c->fc->nb_streams-1]; -+ ++ + // Store individual components in metadata + av_dict_set(&st->metadata, "camera_model", camera_model, 0); + av_dict_set(&st->metadata, "distortion_model", distortion_model, 0); @@ -299,7 +299,7 @@ index 8a094b1ea0..6fec10583f 100644 + if (st->codecpar->codec_id != 0) { // 0 is for depth track + av_dict_set(&st->metadata, "distortion_params_1", value_str_right, 0); + } -+ ++ + av_freep(&value_str_left); + av_freep(&value_str_right); + av_freep(&additional_data); @@ -307,7 +307,7 @@ index 8a094b1ea0..6fec10583f 100644 +} + +static int mov_read_tbtm(MOVContext *c, AVIOContext *pb, MOVAtom atom) -+{ ++{ + if (!c || !c->fc) { + av_log(NULL, AV_LOG_ERROR, "Invalid context for TBTM box\n"); + return AVERROR_INVALIDDATA; diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 05728c8..c85acd8 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -7,7 +7,15 @@ if [[ "$(uname)" == "Darwin" ]];then brew install --formula $cur/cmake.rb brew install opencv elif [[ "$(uname)" == "Linux" ]]; then - sudo apt update && sudo apt install -y libopencv-dev cmake + SUDO="sudo" + if [ $(id -u) -eq 0 ];then + SUDO="" + fi + APT="apt" + if hash yum 2>/dev/null;then + APT="yum" + fi + ${SUDO} ${APT} update && ${SUDO} ${APT} install -y libopencv-dev cmake elif [[ "$(uname)" == *"_NT"* ]]; then echo "Not supported windows now." fi diff --git a/scripts/opencv_460_on_macos.patch b/scripts/opencv_460_on_macos.patch new file mode 100644 index 0000000..1d9c828 --- /dev/null +++ b/scripts/opencv_460_on_macos.patch @@ -0,0 +1,28 @@ +diff --git a/3rdparty/libpng/pngpriv.h b/3rdparty/libpng/pngpriv.h +index 583c26f9bd..ca7bd6ffaf 100644 +--- a/3rdparty/libpng/pngpriv.h ++++ b/3rdparty/libpng/pngpriv.h +@@ -524,7 +524,7 @@ + * if possible. + */ + # if !defined(__MATH_H__) && !defined(__MATH_H) && !defined(__cmath__) +-# include ++# include + # endif + # else + # include +diff --git a/3rdparty/zlib/zutil.h b/3rdparty/zlib/zutil.h +index d9a20ae1bf..f9b0694163 100644 +--- a/3rdparty/zlib/zutil.h ++++ b/3rdparty/zlib/zutil.h +@@ -142,10 +142,6 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ + # ifndef Z_SOLO + # if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os + # include /* for fdopen */ +-# else +-# ifndef fdopen +-# define fdopen(fd,mode) NULL /* No fdopen() */ +-# endif + # endif + # endif + #endif diff --git a/scripts/opencv_for_new_cmake.patch b/scripts/opencv_for_new_cmake.patch new file mode 100644 index 0000000..f749a21 --- /dev/null +++ b/scripts/opencv_for_new_cmake.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/OpenCVGenPkgconfig.cmake b/cmake/OpenCVGenPkgconfig.cmake +index 43d6a87e74..6da10dce55 100644 +--- a/cmake/OpenCVGenPkgconfig.cmake ++++ b/cmake/OpenCVGenPkgconfig.cmake +@@ -110,7 +110,7 @@ endif() + # ============================================================================= + else() # DEFINED CMAKE_HELPER_SCRIPT + +-cmake_minimum_required(VERSION 2.8.12.2) ++cmake_minimum_required(VERSION 3.5) + cmake_policy(SET CMP0012 NEW) + include("${CMAKE_HELPER_SCRIPT}") + include("${OpenCV_SOURCE_DIR}/cmake/OpenCVUtils.cmake") diff --git a/src/spatialmp4/reader.cc b/src/spatialmp4/reader.cc index c43ef1d..86b481c 100644 --- a/src/spatialmp4/reader.cc +++ b/src/spatialmp4/reader.cc @@ -310,7 +310,7 @@ Reader::Reader(const std::string& filename) if (has_rgb_) { rgb_fps_ = pFormatCtx_->streams[rgb_frame_id_]->r_frame_rate.num / pFormatCtx_->streams[rgb_frame_id_]->r_frame_rate.den; - rgb_width_ = pFormatCtx_->streams[rgb_frame_id_]->codecpar->width; + rgb_width_ = pFormatCtx_->streams[rgb_frame_id_]->codecpar->width / 2; // side-by-side stereo rgb_height_ = pFormatCtx_->streams[rgb_frame_id_]->codecpar->height; AVDictionaryEntry* tag = NULL; @@ -499,6 +499,18 @@ int Reader::GetIndex() const { } } +int Reader::GetFrameCount() const { + switch (read_mode_) { + case ReadMode::DEPTH_FIRST: + case ReadMode::DEPTH_ONLY: + return keyframes_depth.size(); + case ReadMode::RGB_ONLY: + return allframes_rgb.size(); + default: + return -1; + } +} + void Reader::Load(rgb_frame& frame_rgb, depth_frame& frame_depth) { if (pFormatCtx_ == NULL) { std::cerr << "pFormatCtx_ is NULL" << std::endl; @@ -517,6 +529,7 @@ void Reader::Load(rgb_frame& frame_rgb, depth_frame& frame_depth) { rgb_frame_pts_queue_.addPose(rgb_timestamp, rgb_frame_pts); } } + // rgb_frame_pts_queue_.print(); av_seek_frame(pFormatCtx_, depth_frame_id_, keyframes_depth[keyframe_depth_idx_], AVSEEK_FLAG_BACKWARD); while (av_read_frame(pFormatCtx_, ¤t_packet_) >= 0) { @@ -525,7 +538,7 @@ void Reader::Load(rgb_frame& frame_rgb, depth_frame& frame_depth) { av_packet_unref(¤t_packet_); continue; } - ParseDepthFrame(current_packet_, frame_depth); + ParseDepthFrame(current_packet_, frame_depth, true); keyframe_depth_idx_++; break; } @@ -538,7 +551,13 @@ void Reader::Load(rgb_frame& frame_rgb, depth_frame& frame_depth) { int64_t target_rgb_pts; double time_diff; double depth_timestamp = frame_depth.timestamp; - rgb_frame_pts_queue_.findNearestPose(depth_timestamp, target_rgb_pts, time_diff); + if (!rgb_frame_pts_queue_.findNearestPose(depth_timestamp, target_rgb_pts, time_diff)) { + std::cerr << "findNearestPose failed" << std::endl; + return; + } + if (time_diff > 0.1) { + spdlog::warn("time_diff is too large: {:.4f}", time_diff); + } // 2. get nearest rgb packet auto compare = [](int64_t a, int64_t b) { return a < b; }; @@ -608,7 +627,7 @@ void Reader::Load(rgb_frame& frame_rgb) { } } -void Reader::Load(depth_frame& frame_depth) { +void Reader::Load(depth_frame& frame_depth, bool raw_head_pose) { if (pFormatCtx_ == NULL) { std::cerr << "pFormatCtx_ is NULL" << std::endl; return; @@ -623,7 +642,7 @@ void Reader::Load(depth_frame& frame_depth) { } while (av_read_frame(pFormatCtx_, ¤t_packet_) >= 0) { if (current_packet_.stream_index == depth_frame_id_) { - ParseDepthFrame(current_packet_, frame_depth); + ParseDepthFrame(current_packet_, frame_depth, raw_head_pose); keyframe_depth_idx_++; break; } @@ -631,6 +650,64 @@ void Reader::Load(depth_frame& frame_depth) { } } +void Reader::Load(Utilities::Rgbd& rgbd, bool densify) { + if (read_mode_ != ReadMode::DEPTH_FIRST) { + std::cerr << "Read mode should be DEPTH_FIRST, but got " << ReadMode2String(read_mode_) << std::endl; + return; + } + if (!has_depth_ || !has_rgb_) { + std::cerr << "depth frame or rgb frame is not found" << std::endl; + return; + } + + rgb_frame frame_rgb; + depth_frame frame_depth; + Load(frame_rgb, frame_depth); + + // project depth to rgb + cv::Mat projected_depth; + auto T_I_Srgb = GetRgbExtrinsicsLeft().as_se3(); + auto T_I_Stof = GetDepthExtrinsics().as_se3(); + + // Read head_model_offset from /system/etc/pvr/config/config_head.txt#line_1 + auto head_model_offset = Eigen::Vector3d(-0.05057, -0.01874, 0.04309); + + // Note: + // W: World + // H: Head + // S: Sensor, rgb sensor or depth sensor + // I: IMU + auto T_W_Hrgb = frame_rgb.pose.as_se3(); + auto T_W_Htof = frame_depth.pose.as_se3(); + Sophus::SE3d T_W_Irgb, T_W_Itof; + Utilities::HeadToImu(T_W_Hrgb, head_model_offset, T_W_Irgb); + Utilities::HeadToImu(T_W_Htof, head_model_offset, T_W_Itof); + auto T_W_Srgb = T_W_Irgb * T_I_Srgb; + auto T_W_Stof = T_W_Itof * T_I_Stof; + auto T_Srgb_Stof = T_W_Srgb.inverse() * T_W_Stof; + + cv::Matx33d K_tof = GetDepthIntrinsics().as_cvmat(); + if (densify) { + // densify depth by nearest neighbor interpolation + float scale = frame_rgb.left_rgb.cols / frame_depth.depth.cols; + cv::resize(frame_depth.depth, frame_depth.depth, cv::Size(frame_rgb.left_rgb.cols, frame_rgb.left_rgb.rows), + cv::INTER_NEAREST); + K_tof = GetDepthIntrinsics().as_cvmat(); + K_tof(0, 0) *= scale; + K_tof(1, 1) *= scale; + K_tof(0, 2) *= scale; + K_tof(1, 2) *= scale; + } + + Utilities::ProjectDepthToRgb(frame_depth.depth, frame_rgb.left_rgb, GetRgbIntrinsicsLeft().as_cvmat(), K_tof, + T_Srgb_Stof, projected_depth, true); + // timestamp is the time of depth frame + // T_W_Srgb is the transform from world to rgb sensor + rgbd = Utilities::Rgbd(frame_rgb.left_rgb, projected_depth, frame_depth.timestamp, T_W_Srgb); + // Why it is bad with T_W_Srgb? + // rgbd = Utilities::Rgbd(frame_rgb.left_rgb, projected_depth, frame_depth.timestamp, T_W_Stof); +} + void Reader::LoadAllPoseData(int frame_id) { const AVStream* pose_stream = pFormatCtx_->streams[frame_id]; AVPacket pkt; @@ -654,7 +731,7 @@ void Reader::LoadAllPoseData(int frame_id) { av_seek_frame(pFormatCtx_, -1, 0, AVSEEK_FLAG_BACKWARD); } -void Reader::ParseDepthFrame(const AVPacket& pkt, depth_frame& frame_depth) { +void Reader::ParseDepthFrame(const AVPacket& pkt, depth_frame& frame_depth, bool raw_head_pose) { frame_depth.timestamp = pkt.pts * av_q2d(pFormatCtx_->streams[depth_frame_id_]->time_base); frame_depth.depth = cv::Mat(depth_height_, depth_width_, CV_16UC1, pkt.data); frame_depth.depth.convertTo(frame_depth.depth, CV_32FC1, 0.001); // unit: meter @@ -662,10 +739,40 @@ void Reader::ParseDepthFrame(const AVPacket& pkt, depth_frame& frame_depth) { double time_diff; pose_frames_.findNearestPose(frame_depth.timestamp, target_pose, time_diff); frame_depth.pose = target_pose; - if (time_diff <= find_pose_distance_) { + + bool pose_is_valid = false; + if (time_diff <= find_pose_distance_ || IsLastFrame()) { frame_depth.pose = target_pose; + pose_is_valid = true; } else { frame_depth.pose = pose_frame(); + pose_is_valid = false; + } + if (!raw_head_pose && pose_is_valid) { + // Read head_model_offset from /system/etc/pvr/config/config_head.txt#line_1 + auto head_model_offset = Eigen::Vector3d(-0.05057, -0.01874, 0.04309); + // convert T_W_Htof to T_W_Stof + // Note: + // W: World + // H: Head + // S: Sensor, rgb sensor or depth sensor + // I: IMU + auto T_W_Htof = frame_depth.pose.as_se3(); + auto T_I_Stof = GetDepthExtrinsics().as_se3(); + + Sophus::SE3d T_W_Itof; + Utilities::HeadToImu(T_W_Htof, head_model_offset, T_W_Itof); + auto T_W_Stof = T_W_Itof * T_I_Stof; + + Eigen::Vector3d t = T_W_Stof.translation(); + Eigen::Quaterniond q = T_W_Stof.unit_quaternion(); + frame_depth.pose.x = t.x(); + frame_depth.pose.y = t.z(); + frame_depth.pose.z = t.z(); + frame_depth.pose.qx = q.x(); + frame_depth.pose.qy = q.y(); + frame_depth.pose.qz = q.z(); + frame_depth.pose.qw = q.w(); } } @@ -690,7 +797,7 @@ void Reader::ParseRgbFrame(const AVPacket& pkt, rgb_frame& frame_rgb, bool skip) pose_frame target_pose; double time_diff; pose_frames_.findNearestPose(frame_rgb.timestamp, target_pose, time_diff); - if (time_diff <= find_pose_distance_) { + if (time_diff <= find_pose_distance_ || IsLastFrame()) { frame_rgb.pose = target_pose; } else { frame_rgb.pose = pose_frame(); @@ -713,4 +820,9 @@ bool Reader::SeekToRgbKeyframe(int64_t target_pts) { return true; } +bool Reader::IsLastFrame() { + std::cout << "GetIndex: " << GetIndex() << ", GetFrameCount: " << GetFrameCount() << std::endl; + return GetIndex() == GetFrameCount() - 1; +} + } // namespace SpatialML diff --git a/src/spatialmp4/reader.h b/src/spatialmp4/reader.h index cec618f..1cdc884 100644 --- a/src/spatialmp4/reader.h +++ b/src/spatialmp4/reader.h @@ -15,12 +15,21 @@ */ #pragma once + +// 导出宏定义 +#if defined(_WIN32) +#define SPATIALMP4_EXPORT __declspec(dllexport) +#else +#define SPATIALMP4_EXPORT __attribute__((visibility("default"))) +#endif + #include #include #include #include #include #include "utilities/SyncPose.hpp" +#include "utilities/RgbdUtils.h" #include "spatialmp4/data_types.h" extern "C" { @@ -45,7 +54,7 @@ namespace SpatialML { * Random access video reader. * This class is used to read a video file in random access mode. */ -class RandomAccessVideoReader { +class SPATIALMP4_EXPORT RandomAccessVideoReader { public: RandomAccessVideoReader() = default; ~RandomAccessVideoReader(); @@ -73,7 +82,7 @@ class RandomAccessVideoReader { * This class is used to read a SpatialMP4 file. * The file format is described in the SpatialMP4 format specification. */ -class Reader { +class SPATIALMP4_EXPORT Reader { public: enum ReadMode { RGB_ONLY, @@ -131,17 +140,20 @@ class Reader { void SetReadMode(ReadMode mode) { read_mode_ = mode; } bool HasNext() const; void Load(rgb_frame& rgb_frame); - void Load(depth_frame& depth_frame); + void Load(depth_frame& depth_frame, bool raw_head_pose = false); void Load(rgb_frame& rgb_frame, depth_frame& depth_frame); + void Load(Utilities::Rgbd& rgbd, bool densify = false); void Reset(); int GetIndex() const; + int GetFrameCount() const; protected: void LoadAllPoseData(int frame_id); - void ParseDepthFrame(const AVPacket& pkt, depth_frame& depth_frame); + void ParseDepthFrame(const AVPacket& pkt, depth_frame& depth_frame, bool raw_head_pose = false); void ParseRgbFrame(const AVPacket& pkt, rgb_frame& rgb_frame, bool skip = false); bool SeekToRgbKeyframe(int64_t timestamp); + bool IsLastFrame(); private: std::string filename_; diff --git a/src/spatialmp4/reader_test.cc b/src/spatialmp4/reader_test.cc index 9900441..3ea03aa 100644 --- a/src/spatialmp4/reader_test.cc +++ b/src/spatialmp4/reader_test.cc @@ -23,11 +23,26 @@ #include "utilities/PointcloudUtils.h" #include "spdlog/spdlog.h" #include +#include const std::string kTestFile = "video/test.mp4"; const std::string kVisRgbDir = "./tmp_vis_rgb"; const std::string kVisRgbDir2 = "./tmp_vis_rgb_random"; const std::string kVisDepthDir = "./tmp_vis_depth"; +const std::string kVisRgbdDir = "./tmp_vis_rgbd"; + +std::string GetVideoPath() { + const char* env = std::getenv("VIDEO"); + if (env) { + return std::string(env); + } + std::ifstream file(kTestFile); + if (!file.good()) { + throw std::runtime_error("Please set VIDEO environment variable!"); + } + file.close(); + return kTestFile; +} TEST(SpatialMP4Test, FilenameCheck_ReaderTest) { EXPECT_THROW(SpatialML::Reader(std::string("video/3DVideo_30min Stationary.mp4")), std::runtime_error); @@ -35,7 +50,7 @@ TEST(SpatialMP4Test, FilenameCheck_ReaderTest) { } TEST(SpatialMP4Test, Basic_ReaderTest) { - SpatialML::Reader reader(kTestFile); + SpatialML::Reader reader(GetVideoPath()); ASSERT_TRUE(reader.HasRGB()); ASSERT_TRUE(reader.HasDepth()); ASSERT_TRUE(reader.HasPose()); @@ -112,7 +127,7 @@ TEST(SpatialMP4Test, DepthFirst_ReaderTest) { } fs::create_directory(kVisDepthDir); - SpatialML::Reader reader(kTestFile); + SpatialML::Reader reader(GetVideoPath()); reader.SetReadMode(SpatialML::Reader::ReadMode::DEPTH_FIRST); reader.Reset(); while (reader.HasNext()) { @@ -184,6 +199,8 @@ TEST(SpatialMP4Test, DepthFirst_ReaderTest) { } } + + TEST(SpatialMP4Test, HeadModel_ReaderTest) { // test data auto head_model_offset = Eigen::Vector3d(-0.05057, -0.01874, 0.04309); @@ -207,7 +224,7 @@ TEST(SpatialMP4Test, RgbOnly_ReaderTest) { } fs::create_directory(kVisRgbDir); - SpatialML::Reader reader(kTestFile); + SpatialML::Reader reader(GetVideoPath()); ASSERT_TRUE(reader.HasRGB()); reader.Reset(); reader.SetReadMode(SpatialML::Reader::ReadMode::RGB_ONLY); @@ -222,7 +239,7 @@ TEST(SpatialMP4Test, RgbOnly_ReaderTest) { } TEST(SpatialMP4Test, DepthOnly_ReaderTest) { - SpatialML::Reader reader(kTestFile); + SpatialML::Reader reader(GetVideoPath()); ASSERT_TRUE(reader.HasDepth()); reader.Reset(); reader.SetReadMode(SpatialML::Reader::ReadMode::DEPTH_ONLY); @@ -243,7 +260,7 @@ TEST(SpatialMP4Test, RandomAccess_ReaderTest) { fs::create_directory(kVisRgbDir2); SpatialML::RandomAccessVideoReader reader; - if (!reader.Open(kTestFile, true)) { + if (!reader.Open(GetVideoPath(), true)) { std::cerr << "Failed to open video" << std::endl; return; } @@ -269,3 +286,41 @@ TEST(SpatialMP4Test, RandomAccess_ReaderTest) { } } } + +TEST(SpatialMP4Test, DepthFirst_ReaderTest_Rgbd) { + // spdlog::set_level(spdlog::level::debug); // debug mode + + if (fs::exists(kVisRgbdDir)) { + fs::remove_all(kVisRgbdDir); + } + fs::create_directory(kVisRgbdDir); + + SpatialML::Reader reader(GetVideoPath()); + reader.SetReadMode(SpatialML::Reader::ReadMode::DEPTH_FIRST); + reader.Reset(); + while (reader.HasNext()) { + Utilities::Rgbd rgbd; + reader.Load(rgbd); + + std::cout << "rgbd frame: \t" << rgbd.timestamp << std::endl; + std::stringstream ss; + ss << std::setw(4) << std::setfill('0') << reader.GetIndex(); + + cv::Mat vis_projected_depth; + Utilities::VisualizeMat(rgbd.depth, vis_projected_depth, "projected_depth", &rgbd.rgb, 0, 5, true); + + std::string filename = kVisRgbdDir + "/depth_" + ss.str() + ".png"; + cv::putText(vis_projected_depth, "depth_" + std::to_string(rgbd.timestamp), cv::Point(100, 150), + cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2); + cv::imwrite(filename, vis_projected_depth); + + // projection depth to pointcloud + Utilities::Pointcloud pcd; + Utilities::RgbdToPointcloud(rgbd.rgb, rgbd.depth, reader.GetRgbIntrinsicsLeft().as_cvmat(), pcd, 10); + std::string pcd_filename = kVisRgbdDir + "/pcd_" + ss.str() + ".obj"; + Utilities::SavePointcloudToFile(pcd_filename, pcd); + + EXPECT_TRUE(rgbd.depth.data != nullptr); + EXPECT_TRUE(rgbd.rgb.data != nullptr); + } +} \ No newline at end of file diff --git a/src/spatialmp4/utilities/RgbdUtils.cc b/src/spatialmp4/utilities/RgbdUtils.cc index 6808321..55e8ab2 100644 --- a/src/spatialmp4/utilities/RgbdUtils.cc +++ b/src/spatialmp4/utilities/RgbdUtils.cc @@ -15,6 +15,7 @@ */ #include "RgbdUtils.h" +#include #include #include diff --git a/src/spatialmp4/utilities/RgbdUtils.h b/src/spatialmp4/utilities/RgbdUtils.h index 9aa3942..6513e2f 100644 --- a/src/spatialmp4/utilities/RgbdUtils.h +++ b/src/spatialmp4/utilities/RgbdUtils.h @@ -33,4 +33,19 @@ void ImuToHead(const Sophus::SE3d& in, const Eigen::Vector3d& head_model_offset, void HeadToImu(const Sophus::SE3d& in, const Eigen::Vector3d& head_model_offset, Sophus::SE3d& out); +struct Rgbd { + Rgbd(const cv::Mat& rgb, const cv::Mat& depth, double timestamp, const Sophus::SE3d& T_W_S) + : rgb(rgb), depth(depth), timestamp(timestamp), T_W_S(T_W_S) {} + Rgbd() = default; + Rgbd(const Rgbd& other) = default; + Rgbd& operator=(const Rgbd& other) = default; + Rgbd(Rgbd&& other) = default; + Rgbd& operator=(Rgbd&& other) = default; + + cv::Mat rgb; + cv::Mat depth; + double timestamp; + Sophus::SE3d T_W_S; +}; + } // namespace Utilities \ No newline at end of file diff --git a/src/spatialmp4/utilities/SyncPose.hpp b/src/spatialmp4/utilities/SyncPose.hpp index 3aded75..59ea281 100644 --- a/src/spatialmp4/utilities/SyncPose.hpp +++ b/src/spatialmp4/utilities/SyncPose.hpp @@ -78,14 +78,28 @@ class SynchronizedQueue { } } + void print() const { + std::unique_lock lock(mtx); + // print top3 and tail 3 + for (int i = 0; i < 3; i++) { + std::cout << "timestamp: " << pose_queue[i].timestamp << ", data: " << pose_queue[i].data << std::endl; + } + std::cout << "..." << std::endl; + for (int i = pose_queue.size() - 3; i < pose_queue.size(); i++) { + std::cout << "timestamp: " << pose_queue[i].timestamp << ", data: " << pose_queue[i].data << std::endl; + } + } + bool findNearestPose(double rgb_timestamp, PoseType& pose, double& time_diff) { std::unique_lock lock(mtx); if (pose_queue.empty()) { + std::cerr << "pose_queue is empty" << std::endl; return false; } - if (rgb_timestamp < pose_queue.front().timestamp || rgb_timestamp > pose_queue.back().timestamp) { - return false; - } + // if (rgb_timestamp < pose_queue.front().timestamp || rgb_timestamp > pose_queue.back().timestamp) { + // std::cerr << "rgb_timestamp is out of range" << std::endl; + // return false; + // } auto it = std::lower_bound(pose_queue.begin(), pose_queue.end(), rgb_timestamp, [](const TimestampedData& data, double ts) { return data.timestamp < ts; }); diff --git a/src/spatialmp4/utils.cc b/src/spatialmp4/utils.cc index 412f4b5..b79fa6e 100644 --- a/src/spatialmp4/utils.cc +++ b/src/spatialmp4/utils.cc @@ -17,6 +17,7 @@ #include "spatialmp4/utils.h" #include #include +#include namespace SpatialML { @@ -172,21 +173,20 @@ std::string FFmpegErrorString(int errnum) { } std::string microsecondsToDateTime(int64_t timestamp_microseconds) { - // split seconds and microseconds - int64_t timestamp_seconds = timestamp_microseconds / 1000000; - int64_t microseconds = timestamp_microseconds % 1000000; - - // convert to time structure - std::time_t time = timestamp_seconds; - std::tm* tm = std::localtime(&time); - - // format output - std::ostringstream oss; - oss << std::put_time(tm, "%Y-%m-%d %H:%M:%S"); - oss << "." << std::setfill('0') << std::setw(6) << microseconds; - - return oss.str(); -} + // split seconds and microseconds + int64_t timestamp_seconds = timestamp_microseconds / 1000000; + int64_t microseconds = timestamp_microseconds % 1000000; + + // convert to time structure + std::time_t time = timestamp_seconds; + std::tm* tm = std::localtime(&time); + // format output + std::ostringstream oss; + oss << std::put_time(tm, "%Y-%m-%d %H:%M:%S"); + oss << "." << std::setfill('0') << std::setw(6) << microseconds; + + return oss.str(); +} } // namespace SpatialML diff --git a/src/spatialmp4/utils.h b/src/spatialmp4/utils.h index 5dd3c40..698be31 100644 --- a/src/spatialmp4/utils.h +++ b/src/spatialmp4/utils.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include "utilities/SyncPose.hpp" #include "spatialmp4/data_types.h" @@ -32,6 +34,13 @@ extern "C" { namespace SpatialML { +// 导出宏定义 +#if defined(_WIN32) +#define SPATIALMP4_EXPORT __declspec(dllexport) +#else +#define SPATIALMP4_EXPORT __attribute__((visibility("default"))) +#endif + std::vector String2DoubleVector(const std::string& str); std::string StreamTypeToString(StreamType type); @@ -40,7 +49,13 @@ void PrintStreamInfo(AVFormatContext* pFormatCtx); std::unordered_map GetStreamTypeInfo(AVFormatContext* pFormatCtx); -bool FrameToBGR24(AVFrame* rgb_frame, std::pair& rgb_pair); +/** + * Convert AVFrame to BGR24 format. + * @param rgb_frame Input AVFrame + * @param rgb_pair Output pair of cv::Mat for left and right images + * @return true if successful, false otherwise + */ +SPATIALMP4_EXPORT bool FrameToBGR24(AVFrame* rgb_frame, std::pair& rgb_pair); std::string FFmpegErrorString(int errnum); diff --git a/src/spatialmp4/version.h.in b/src/spatialmp4/version.h.in new file mode 100644 index 0000000..7c903c6 --- /dev/null +++ b/src/spatialmp4/version.h.in @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +namespace SpatialML { + +// Version number definitions +#define SPATIALMP4_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define SPATIALMP4_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define SPATIALMP4_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define SPATIALMP4_VERSION "@PROJECT_VERSION@" + +// Version string +inline const char* GetVersion() { + return SPATIALMP4_VERSION; +} + +// Get major version number +inline int GetMajorVersion() { + return @PROJECT_VERSION_MAJOR@; +} + +// Get minor version number +inline int GetMinorVersion() { + return @PROJECT_VERSION_MINOR@; +} + +// Get patch version number +inline int GetPatchVersion() { + return @PROJECT_VERSION_PATCH@; +} + +} // namespace SpatialML \ No newline at end of file