Shared Preprocessing and Postprocessing Library for Point Cloud Object Detection
This repository provides shared C++ and Python components for point cloud object detection training, model export, and ROS 2 inference. Using the same geometry, decoding, non-maximum suppression (NMS), and model-manifest implementations keeps the training and inference pipelines consistent.
The library includes PBOD decoding, rotated NMS, point filtering, CUDA kernels for pillarization and rotated NMS, and a model-manifest schema shared by the C++ and Python APIs.
π Quick Start β’ π» Development β’ π Documentation
Important
This repository is part of OpenADS, the Open Automated Driving Systems project. OpenADS and its modules have been initiated and are currently being maintained by the Institute for Automotive Engineering (ika) at RWTH Aachen University.
- CMake 3.16 or newer
- A C++17 compiler
- yaml-cpp
- Python 3.12 or newer for the Python package
- Optional: a CUDA toolkit compatible with the installed PyTorch build and a CUDA-capable GPU for the CUDA extensions
Clone, build, and install the C++ library:
git clone https://github.com/openads-project/pcod-common.git
cmake -S pcod-common -B build/pcod-common -DPCOD_COMMON_BUILD_TESTS=OFF
cmake --build build/pcod-common
cmake --install build/pcod-common --prefix /path/to/prefixAfter installation, link your CMake target to the package:
find_package(pcod_common CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE pcod_common::pcod_common)When using a custom installation prefix, add it to CMAKE_PREFIX_PATH when configuring the consuming project, for example with -DCMAKE_PREFIX_PATH=/path/to/prefix.
Alternatively, add this repository directly to your CMake project:
add_subdirectory(pcod-common)
target_link_libraries(my_target PRIVATE pcod_common)Install the Python package from the repository root:
pip install .from pcod_common.preprocessing.pillars import PillarPreprocessor, PillarPreprocessorConfig
config = PillarPreprocessorConfig(
x_min=-50.0,
x_max=50.0,
y_min=-50.0,
y_max=50.0,
z_min=-2.0,
z_max=3.0,
voxel_x=0.2,
voxel_y=0.2,
point_feature_dim=1,
)
preprocessor = PillarPreprocessor(config)include/pcod_common/: public C++ headerssrc/: C++ implementationscsrc/: CUDA/C++ kernels for PyTorch extensionspython/pcod_common/: Python package sourcesschemas/: JSON schema for the model manifesttests/: C++ testspython/tests/: Python tests
On Debian or Ubuntu, install the required build dependencies and run the test suite:
apt-get update && apt-get install -y cmake g++ pkg-config libyaml-cpp-dev python3 python3-yaml
cmake -S . -B build -DPCOD_COMMON_BUILD_TESTS=ON
cmake --build build
ctest --test-dir buildSome C++ tests compare the Python and C++ contracts and require python3 to be available on PATH.
Install the package in editable mode with its development dependencies and run the test suite:
pip install -e ".[dev]"
pytestpython/tests/test_postprocess.py requires PyTorch and TorchVision. Tests whose optional dependencies or CUDA extensions are unavailable are skipped; the manifest tests still run.
Build and validate the wheel and source distribution from the repository root:
python3 -m pip install build twine
python3 -m build
python3 -m twine check dist/*Published distributions include the model-manifest schema and the C++/CUDA sources required to build the optional PyTorch extensions at runtime. Validating a distribution does not require a GPU. Compiling the extensions requires Ninja and a CUDA toolkit compatible with the installed PyTorch build; running them requires a CUDA-capable GPU.
A basic development container configuration is provided in .devcontainer/. In the container, install the system and Python dependencies, including PyTorch, and run:
sudo apt-get update
sudo apt-get install -y pkg-config libyaml-cpp-dev
pip install -e ".[dev]"
cmake -S . -B build -DPCOD_COMMON_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build
pytestCUDA kernels are built on demand by the PyTorch extension loaders in python/pcod_common/torch_extensions/.
This example demonstrates point filtering and PBOD decoding with the C++ API. It uses four pillars and two classes to keep the control flow easy to follow.
#include "pcod_common/pbod_postprocess.hpp"
#include "pcod_common/pillar_grid.hpp"
#include "pcod_common/point_preprocess.hpp"
#include <vector>
int main() {
pcod_common::PointPreprocessConfig pre_cfg;
pre_cfg.x_min = -1.0f;
pre_cfg.x_max = 1.0f;
pre_cfg.y_min = -1.0f;
pre_cfg.y_max = 1.0f;
pre_cfg.z_min = -1.0f;
pre_cfg.z_max = 1.0f;
pre_cfg.normalization_type = pcod_common::PointFeatureNormalizationType::kValueThreshold;
pre_cfg.value_threshold = 10.0f;
// 1) Basic point filtering (range checks + optional masks).
pcod_common::PointPreprocessor preprocessor(pre_cfg);
if (!preprocessor.IsPointValid(0.5f, 0.1f, 0.0f)) {
return 1;
}
// 2) Build a 2x2 pillar grid so the decoder has a center location.
pcod_common::PillarGrid grid = pcod_common::BuildPillarGrid(
{2, 2}, {{{0.0f, 2.0f}, {0.0f, 2.0f}, {0.0f, 1.0f}}}, 1, 1);
// 3) Dummy model outputs for four pillars and two classes.
// Two pillars will be filtered out by the score threshold below.
const int num_pillars = 4;
const int num_classes = 2;
float focal_logits[num_pillars] = {2.0f, -2.0f, 2.0f, -2.0f};
float class_logits[num_pillars * num_classes] = {
0.1f, 0.9f, 0.1f, 0.9f, 0.1f, 0.9f, 0.1f, 0.9f};
std::vector<float> size_posterior(num_pillars * num_classes * 3, 1.0f);
std::vector<float> reg_logits(num_pillars * num_classes * 7, 0.0f);
pcod_common::PbodOutputsView view;
view.focal_logits = focal_logits;
view.size_posterior = size_posterior.data();
view.class_logits = class_logits;
view.reg_logits = reg_logits.data();
view.num_pillars = num_pillars;
view.num_classes = num_classes;
view.reg_dim = 7;
// 4) Decode into bounding boxes (class list sets the output metadata).
pcod_common::PbodPostprocessConfig post_cfg;
post_cfg.class_names = {"car", "pedestrian"};
post_cfg.score_thresholds = {0.5f};
auto boxes = pcod_common::DecodePbod(view, grid, post_cfg);
const std::size_t expected_boxes = 2;
return boxes.size() == expected_boxes ? 0 : 1;
}Each exported model bundle contains a model_manifest.yml file with three sections:
artifact: bundle metadata and references to files within the bundlefrozen_contract: model settings that inference applications cannot override and that must match the exported modelruntime_defaults: default inference settings that applications may override
The ROS 2 inference node uses frozen_contract as the authoritative model configuration and initializes overridable ROS parameters, such as preprocessing.point_feature.value_threshold and the NMS thresholds, from runtime_defaults. The schema is defined in schemas/model_manifest.schema.json.
- For training and model export, include
pcod-commonas a Git submodule and add it to the Python environment, for example withpip install -e pcod-common. - For ROS 2 inference, include
pcod-commonas a Git submodule and link against the C++ library.
For a complete ROS 2 integration example, see point_cloud_object_detection, which includes pcod-common as a Git submodule and links against its C++ library.
Implementation details are available in the Source Code Documentation.
The source code in this repository is licensed under Apache-2.0, see LICENSE.
Development and maintenance of this repository are supported by the following projects. We acknowledge the funding of the respective institutions.
| Project | Funding Institution | Grant Number |
|---|---|---|
| AIGGREGATE | πͺπΊ European Union | 101202457 |
Funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Climate, Infrastructure and Environment Executive Agency (CINEA). Neither the European Union nor CINEA can be held responsible for them.
