Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
de5b5be
feat(filters): add depthlitez compression library as submodule
05F3759DF Feb 2, 2026
82c2889
feat(filters): implement depth image compression filter
05F3759DF Feb 2, 2026
9100915
feat(ros2): implement depth compression in ROS2 plugin
05F3759DF Feb 2, 2026
f8f8102
feat(recorder): add depth compression configuration support
05F3759DF Feb 2, 2026
0f95682
refactor(recorder): remove deprecated --direct mode
05F3759DF Feb 2, 2026
1f764f9
feat(recorder): fix depth compression channel registration
05F3759DF Feb 2, 2026
acd85e7
feat(ros1): add depth compression filter for ROS1 plugin
05F3759DF Feb 2, 2026
798e7e0
docs(changelog): add version 0.3.0 release notes
05F3759DF Feb 2, 2026
e467149
build: make depth compression opt-in via AXON_ENABLE_DEPTH_COMPRESSION
05F3759DF Feb 2, 2026
cd4afd5
fix(ros2): add conditional compilation for depth compression in plugi…
05F3759DF Feb 2, 2026
9799b4d
fix(filters): avoid dlz.hpp namespace pollution with wrapper enums
05F3759DF Feb 2, 2026
2a331e8
fix(docs): fix version number
05F3759DF Feb 3, 2026
0565a0b
test(recorder): fix RegisterChannel test to use get_or_create_channel
05F3759DF Feb 3, 2026
37185a9
fix(recorder): use null character as composite key separator
05F3759DF Feb 3, 2026
eabb78d
fix(ros2): capture compression filter to avoid race condition
05F3759DF Feb 3, 2026
5723e3c
fix(ros2,filters): address code review comments
05F3759DF Feb 3, 2026
df4b1a5
refactor(recorder): fix get_channel_id to use composite key lookup
05F3759DF Feb 3, 2026
42e9b54
fix(docker): use separate build dirs for zenoh publisher/subscriber
05F3759DF Feb 3, 2026
a1aa229
fix(filters): update depthlitez subproject commit reference
05F3759DF Feb 3, 2026
bd91531
refactor(filters): simplify compression logic and remove unused param…
05F3759DF Feb 3, 2026
48a1a99
fix: fix comments
05F3759DF Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ ros
*.swp
*~

# Cache
.cache/

# Python (if any)
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -145,4 +148,8 @@ core/build_uploader/

# Cache directories
.cache/
core/filters/

# DepthLiteZ private submodule (not included in public releases)
# Users with access can initialize manually with:
# git submodule update --init --recursive middlewares/filters/depthlitez
middlewares/filters/depthlitez/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "middlewares/filters/depthlitez"]
path = middlewares/filters/depthlitez
url = ../depthlitez.git
branch = main
29 changes: 28 additions & 1 deletion CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]


## [0.2.1] - 2025-02-03

### Added
- **filters**: Implement depth image compression filter (opt-in feature)
- Add `DepthCompressionFilter` class for ROS1 Noetic and ROS2 (Humble/Jazzy/Rolling)
- Support `sensor_msgs/Image` to `sensor_msgs/CompressedImage` transformation
- Integrate DepthLiteZ compression library with 5-10x compression ratio
- Support configurable compression levels: fast, medium, max
- Pass-through for non-depth images or compression failures
- **Note**: Depth compression is **disabled by default** for open-source builds
- Requires private DepthLiteZ submodule access
- Enable with: `cmake -DAXON_ENABLE_DEPTH_COMPRESSION=ON`
- See documentation for setup instructions

- **middleware**: Add depthlitez submodule reference for specialized depth compression
- Shared compression library used by both ROS1 and ROS2 plugins
- Submodule is listed in `.gitmodules` but excluded from public builds
- Users with access can initialize: `git submodule update --init --recursive middlewares/filters/depthlitez`

### Changed
- **version**: Update project version to 0.2.1
- **build**: Make depth compression an opt-in feature via `AXON_ENABLE_DEPTH_COMPRESSION` CMake option
- Default OFF for open-source compatibility
- When OFF, depth compression code is excluded from build
- ROS1/ROS2 plugins build successfully without DepthLiteZ dependency

## [0.2.0] - 2025-01-22

### Added
Expand Down Expand Up @@ -115,7 +141,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

[Unreleased]: https://github.com/ArcheBase/Axon/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/ArcheBase/Axon/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/ArcheBase/Axon/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/ArcheBase/Axon/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/ArcheBase/Axon/compare/v0.0.1...v0.1.0
[0.0.1]: https://github.com/ArcheBase/Axon/releases/tag/v0.0.1
25 changes: 20 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- Lock-free: Per-topic SPSC queues for zero-copy message handling
- Fleet-ready: Server-controlled recording via HTTP RPC API (see [docs/designs/rpc-api-design.md](docs/designs/rpc-api-design.md))
- Crash-resilient: S3 uploader with SQLite state persistence and recovery
- Plugin-based: Middleware-agnostic core with ROS1/ROS2 plugins
- Plugin-based: Middleware-agnostic core with ROS1/ROS2/Zenoh plugins
- Filter-ready: Extensible data processing pipeline for compression and transformation

## Build and Test Commands

Expand Down Expand Up @@ -133,17 +134,21 @@ Axon/
│ ├── axon_logging/ # Logging infrastructure (no ROS dependencies)
│ └── axon_uploader/ # S3 uploader (no ROS dependencies)
├── middlewares/ # Middleware-specific plugins
├── middlewares/ # Middleware-specific plugins and filters
│ ├── ros1/ # ROS1 (Noetic) plugin → libaxon_ros1.so
│ └── ros2/ # ROS2 (Humble/Jazzy/Rolling) plugin → libaxon_ros2.so
│ └── src/ros2_plugin/ # ROS2 plugin implementation
│ ├── ros2/ # ROS2 (Humble/Jazzy/Rolling) plugin → libaxon_ros2.so
│ │ └── src/ros2_plugin/ # ROS2 plugin implementation
│ ├── zenoh/ # Zenoh plugin → libaxon_zenoh.so
│ └── filters/ # Data processing filters (shared across plugins)
│ └── depthlitez/ # Depth image compression library
├── apps/ # Main applications
│ ├── axon_recorder/ # Plugin loader and HTTP RPC server
│ └── plugin_example/ # Example plugin implementation
└── docs/designs/ # Design documents
└── rpc-api-design.md # HTTP RPC API specification
├── rpc-api-design.md # HTTP RPC API specification
└── depth-compression-filter.md # Depth compression design
```

**Plugin ABI Interface:**
Expand All @@ -158,6 +163,7 @@ The plugin interface is defined in [apps/axon_recorder/plugin_loader.hpp](apps/a
3. Core libraries can be tested independently of ROS
4. Only required middleware plugins need to be deployed
5. Middleware-specific bugs are isolated to plugin code
6. Filters in `middlewares/filters/` can be shared across plugins

### State Machine

Expand Down Expand Up @@ -207,6 +213,13 @@ IDLE → READY → RECORDING ↔ PAUSED
- Console, file, and ROS sinks
- Severity filtering and rotation

**5. Depth Compression Filter** ([middlewares/filters/depthlitez/](middlewares/filters/depthlitez/))
- Specialized compression for 16-bit depth images
- 5-10x compression ratio using DepthLiteZ algorithm
- Configurable compression levels (fast/medium/max)
- Integrates with ROS2 plugin via `DepthCompressionFilter`
- See [docs/designs/depth-compression-filter.md](docs/designs/depth-compression-filter.md)

### Threading Model

| Thread | Responsibility |
Expand Down Expand Up @@ -594,6 +607,8 @@ grep -r "AXON_ROS1\|AXON_ROS2" build/
| ROS1 plugin | `middlewares/ros1/src/ros1_plugin/` (CMake) |
| ROS2 plugin | `middlewares/ros2/src/ros2_plugin/` (CMake) |
| Zenoh plugin | `middlewares/zenoh/` (CMake) |
| Filters | `middlewares/filters/` (shared data processing) |
| Depth compression | `middlewares/filters/depthlitez/` |
| Main app (HTTP RPC) | `apps/axon_recorder/` |
| Plugin example | `apps/plugin_example/` |
| Plugin ABI interface | `apps/axon_recorder/plugin_loader.hpp` |
Expand Down
13 changes: 11 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# =============================================================================

cmake_minimum_required(VERSION 3.14)
project(Axon VERSION 0.2.0 LANGUAGES CXX)
project(Axon VERSION 0.2.1 LANGUAGES CXX)

# =============================================================================
# Centralized Path Definitions
Expand Down Expand Up @@ -112,8 +112,17 @@ if(AXON_BUILD_UPLOADER AND EXISTS "${AXON_UPLOADER_DIR}/CMakeLists.txt")
add_subdirectory(${AXON_UPLOADER_DIR} ${CMAKE_CURRENT_BINARY_DIR}/axon_uploader)
endif()

# Filters (depth compression, no dependencies on other Axon modules)
set(AXON_FILTERS_DIR "${AXON_MIDDLEWARES_DIR}/filters")
set(AXON_FILTERS_DIR "${AXON_FILTERS_DIR}" CACHE INTERNAL "")
if(EXISTS "${AXON_FILTERS_DIR}/CMakeLists.txt")
add_subdirectory(${AXON_FILTERS_DIR} ${CMAKE_CURRENT_BINARY_DIR}/filters)
endif()

# Middleware plugins (ROS1, ROS2, Zenoh)
add_subdirectory(${AXON_MIDDLEWARES_DIR})
if(EXISTS "${AXON_MIDDLEWARES_DIR}/CMakeLists.txt")
add_subdirectory(${AXON_MIDDLEWARES_DIR} ${CMAKE_CURRENT_BINARY_DIR}/middlewares)
endif()

# =============================================================================
# Applications
Expand Down
13 changes: 8 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -625,10 +625,11 @@ format:
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) \
! -path "*/build/*" ! -path "*/build_*/*" -print0 2>/dev/null | \
xargs -0 $(CLANG_FORMAT) -i
@printf "%s\n" "$(YELLOW) Formatting middlewares/ros1/, ros2/, and zenoh/ code...$(NC)"
@find middlewares/ros1 middlewares/ros2 middlewares/zenoh \
@printf "%s\n" "$(YELLOW) Formatting middlewares/ code...$(NC)"
@find middlewares/ros1 middlewares/ros2 middlewares/zenoh middlewares/filters \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) \
! -path "*/build/*" ! -path "*/install/*" ! -path "*/devel/*" -print0 2>/dev/null | \
! -path "*/build/*" ! -path "*/install/*" ! -path "*/devel/*" \
! -path "*/depthlitez/*" -print0 2>/dev/null | \
xargs -0 $(CLANG_FORMAT) -i
@printf "%s\n" "$(YELLOW) Formatting apps/ code...$(NC)"
@find apps \
Expand All @@ -641,12 +642,13 @@ format:
lint:
@printf "%s\n" "$(YELLOW)Linting C++ code...$(NC)"
@if command -v cppcheck >/dev/null 2>&1; then \
find core middlewares/ros1 middlewares/ros2 middlewares/zenoh apps \
find core middlewares/ros1 middlewares/ros2 middlewares/zenoh middlewares/filters apps \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) \
! -path "*/build/*" \
! -path "*/test/*" \
! -path "*/install/*" \
! -path "*/devel/*" \
! -path "*/depthlitez/*" \
-print0 2>/dev/null | \
xargs -0 cppcheck --enable=all \
--suppress=missingInclude \
Expand Down Expand Up @@ -1161,9 +1163,10 @@ format-ci:
fi
@printf "%s\n" "$(YELLOW)Checking code formatting...$(NC)"
@printf "%s\n" "$(YELLOW) Using: $(CLANG_FORMAT)$(NC)"
@find core middlewares/ros1 middlewares/ros2 apps \
@find core middlewares/ros1 middlewares/ros2 middlewares/filters apps \
\( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" -o -name "*.c" \) \
! -path "*/build/*" ! -path "*/build_*/*" ! -path "*/install/*" ! -path "*/devel/*" \
! -path "*/depthlitez/*" \
-print0 2>/dev/null | \
xargs -0 $(CLANG_FORMAT) --dry-run --Werror > /dev/null 2>&1 || \
(printf "%s\n" "$(RED)✗ Code formatting check failed$(NC)" && \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A high-performance, plugin-based data recorder with ROS1/ROS2 compatibility and fleet-ready HTTP RPC control for task-centric recording to MCAP format.

**Current Version:** 0.2.0
**Current version:** 0.3.0

## Architecture

Expand Down
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ precedence = "override"
SPDX-FileCopyrightText = "Copyright (c) 2026 ArcheBase"
SPDX-License-Identifier = "MulanPSL-2.0"

[[annotations]]
path = ".gitmodules"
precedence = "override"
SPDX-FileCopyrightText = "Copyright (c) 2026 ArcheBase"
SPDX-License-Identifier = "MulanPSL-2.0"

[[annotations]]
path = "codecov.yml"
precedence = "override"
Expand Down
2 changes: 1 addition & 1 deletion apps/axon_recorder/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.12)
project(axon_recorder VERSION 0.2.0)
project(axon_recorder VERSION 0.2.1)

# Use parent project version if available
if(DEFINED AXON_REPO_ROOT AND EXISTS "${AXON_REPO_ROOT}/CMakeLists.txt")
Expand Down
10 changes: 6 additions & 4 deletions apps/axon_recorder/axon_recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ int main(int argc, char* argv[]) {
std::string config_file;
std::string cli_plugin_path;
std::string cli_dataset_path;
std::string cli_output_file;
std::string cli_profile;
std::string cli_compression;
std::string cli_output_file;
int cli_compression_level = -1;
size_t cli_queue_capacity = 0;
bool simple_mode = false;
Expand Down Expand Up @@ -275,6 +275,9 @@ int main(int argc, char* argv[]) {
if (!cli_dataset_path.empty()) {
config.dataset.path = cli_dataset_path;
}
if (!cli_output_file.empty()) {
config.output_file = cli_output_file;
}
if (!cli_profile.empty()) {
config.profile = cli_profile;
}
Expand Down Expand Up @@ -310,7 +313,7 @@ int main(int argc, char* argv[]) {

// Print configuration
std::cout << "Axon Recorder Configuration:\n"
<< " Mode: " << (simple_mode ? "Simple (direct recording)" : "HTTP RPC") << "\n"
<< " Mode: " << (simple_mode ? "Simple" : "HTTP RPC") << "\n"
<< " Plugin: " << config.plugin_path << "\n"
<< " Output: " << config.output_file << "\n"
<< " Dataset: " << config.dataset.path << "\n"
Expand Down Expand Up @@ -381,8 +384,7 @@ int main(int argc, char* argv[]) {
return 0;
}

// HTTP RPC mode: start HTTP server and wait for commands
// Start HTTP RPC server
// HTTP RPC mode - start server and wait for commands
std::cout << "Starting HTTP RPC server on " << config.http_server.host << ":"
<< config.http_server.port << "..." << std::endl;

Expand Down
2 changes: 1 addition & 1 deletion apps/axon_recorder/config/default_config_ros1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ plugin:
# ============================================================================
dataset:
# Output directory for MCAP files
path: /data/recordings
path: /tmp/axon

# Output filename (optional, defaults to <path>/recording.mcap)
output_file: recording.mcap
Expand Down
11 changes: 10 additions & 1 deletion apps/axon_recorder/config/default_config_ros2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ plugin:
# ============================================================================
dataset:
# Output directory for MCAP files
path: /data/recordings
# When using HTTP RPC mode, output files are named as: <path>/<task_id>.mcap
# The task_id is provided via the /rpc/config endpoint
path: /tmp/axon

# Output filename (optional, defaults to <path>/recording.mcap)
output_file: recording.mcap
Expand Down Expand Up @@ -78,6 +80,13 @@ subscriptions:
batch_size: 300
flush_interval_ms: 10000

# Depth image with compression (16UC1 -> CompressedImage)
- name: /camera/camera/depth/image_rect_raw
message_type: sensor_msgs/msg/Image
batch_size: 300
flush_interval_ms: 10000
depth_compression: true

# ============================================================================
# Recording Configuration
# ============================================================================
Expand Down
16 changes: 16 additions & 0 deletions apps/axon_recorder/config_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ bool ConfigParser::parse_subscriptions(
if (subscription_node["flush_interval_ms"]) {
subscription.flush_interval_ms = subscription_node["flush_interval_ms"].as<int>();
}
// Parse depth_compression section (can be boolean or object)
if (subscription_node["depth_compression"]) {
const auto& dc_node = subscription_node["depth_compression"];
// Check if it's a boolean (simple form: depth_compression: true)
if (dc_node.IsScalar()) {
subscription.depth_compression.enabled = dc_node.as<bool>();
} else if (dc_node.IsMap()) {
// Object form: depth_compression: { enabled: true, level: fast }
if (dc_node["enabled"]) {
subscription.depth_compression.enabled = dc_node["enabled"].as<bool>();
}
if (dc_node["level"]) {
subscription.depth_compression.level = dc_node["level"].as<std::string>();
}
}
}
subscriptions.push_back(subscription);
}
return true;
Expand Down
3 changes: 2 additions & 1 deletion apps/axon_recorder/plugin_loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ using AxonMessageCallback = void (*)(
using AxonInitFn = AxonStatus (*)(const char*);
using AxonStartFn = AxonStatus (*)();
using AxonStopFn = AxonStatus (*)();
using AxonSubscribeFn = AxonStatus (*)(const char*, const char*, AxonMessageCallback, void*);
using AxonSubscribeFn =
AxonStatus (*)(const char*, const char*, const char*, AxonMessageCallback, void*);
using AxonPublishFn = AxonStatus (*)(const char*, const uint8_t*, size_t, const char*);

// Plugin vtable structure
Expand Down
Loading