From 3695a12d8ff234e566c841d73552db7d2c18fab2 Mon Sep 17 00:00:00 2001 From: Yey007 <55263178+Yey007@users.noreply.github.com> Date: Thu, 4 Sep 2025 20:01:50 -0400 Subject: [PATCH 1/4] Developing documentation --- AUTHOR.md | 2 +- README.md | 3 -- book/developing.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 book/developing.md diff --git a/AUTHOR.md b/AUTHOR.md index fea813e..a71c453 100644 --- a/AUTHOR.md +++ b/AUTHOR.md @@ -1 +1 @@ -This library was made by Ethan Uppal and Utku Melemetci. +This library was made by Ethan Uppal, Jess Wang, and Utku Melemetci. diff --git a/README.md b/README.md index a4c411c..843c9fd 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,9 @@ This repository hosts CEV's implementation of Iterative Closest Points (ICP) as Please see [LICENSE](LICENSE). Note that all code is licensed under the MIT license, with the exception of some additions to `doxygen-style.css`. Files with copyright headers denoting specific copyright holders belong to said holders. Files under `book/asset` are from [doxygen_theme_flat_design](https://github.com/kcwongjoe/doxygen_theme_flat_design) under its MIT license (again with the exception of some additions to `doxygen-style.css`). Files without copyright headers are under the MIT license, Copyright (c) 2024-2025 Cornell Electric Vehicles. ## Install - You can view installation instructions at [INSTALL.md](INSTALL.md). ## Usage and Documentation - We host the usage information and documentation at [cornellev.github.io/icp/](https://cornellev.github.io/icp/). Please see there for information on how to download and how to use or extend the library. @@ -29,5 +27,4 @@ You can build the documentation yourself locally with `make docs`. The main page will be located at `docs/index.html` relative to the project root. ## Versions - Version information can be found in the [releases](https://github.com/cornellev/icp/releases) page. diff --git a/book/developing.md b/book/developing.md new file mode 100644 index 0000000..85e84e2 --- /dev/null +++ b/book/developing.md @@ -0,0 +1,72 @@ +# Developing + +This document explains how to effectively develop `icp`. It covers development environment setup, the general repository structure, how to add new ICP methods, and how to add benchmarks and tests. + +## Development environment + +### Dependencies +`libicp` itself can be built and installed with only Eigen as a dependency---this is by design. However, to effectively develop the project, you'll need a few extra things. + +1. [libcmdapp2](https://github.com/cornellev/libcmdapp2), a command-line parsing library (built by Ethan) which we use for the 2D visualization. +2. [sdl-wrapper](https://github.com/cornellev/sdl-wrapper), a C++ wrapper for SDL to make things easier for the visualization. +3. [simple-test](https://github.com/cornellev/simple-test), a header-only testing library. +4. [PCL](https://pointclouds.org/) 1.12.1 (ideally), a point cloud processing library. We actually only use this to parse 3D point clouds for testing. + +Each of the links above should have installation instructions. If you have everything installed correctly, you should be able to [build](#building) the project and not have any errors. + +### Tooling setup +I'll mainly cover VSCode here as it's relatively popular, but other IDEs shouldn't be too difficult. + +You'll need the [clangd](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) and [clang-format](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format) extensions. You may also need to install `clang-format` itself. Please install at least version 19 as some of the rules depend on recent versions. + +The build system, by default, will generate the `compile_commands.json` file that `cland` relies on. However, it will be in `build/`. To configure VSCode to pick up on this file, you can add the following to your `.vscode/settings.json` + +```json +{ + "clangd.arguments": [ + "-compile-commands-dir=build" + ], +} +``` + +## Repository structure +``` +. +├── bench - code for benchmarking +├── book - documentation and files for the website +├── common - common code across benches, tests, library, and vis +├── ex_data +│ ├── ply - .ply example data (3D) +│ └── scan{n} - .csv example data (2D) +├── include - library header files +├── lib - library source files +├── script - various utility scripts +├── tests - unit test code +└── vis - 2D visualization app +``` + +A few things to note: +1. `tests` also contains `test_ply.cpp`. This isn't really a test, but a program you can run to look at the output from aligning two `.ply` scans. We might want to mvoe this in the future. +2. A lot of the library code is templated. So you may need to look in `include` for some of the core functionality. +3. You'll probably be doing most of your work in `include` and `lib`. These are the core folders. + +## Building +`CMakeLists.txt` is the main build file. However, for convenience, there is a `Makefile` that shortens the `cmake` commands. Just typing `make` will build everything: the library, visualization, benchmarks, and tests. + +There are some extra options you should be aware of. Setting `OPT=[Release | Debug]` (i.e. `make OPT=Release`) changes the optimization level. `USE_SANTIZERS=ON` will enable the address and undefined behavior sanitizers, which can be useful when debugging UB. `CI=ON` will run a CI build, which compiles with warnings as errors and enables the sanitizers. + +Make sure to check the `Makefile` for other utilities you might find useful. You may be interested in `make tidy` and `make view`. + +## Testing +You can run `make test` to run the 2D tests and `make test_3d` to run the 3D tests. Ideally, in the future, we combine these into one executable, but we haven't done this yet. + +### Adding Tests +To add tests, you can follow the examples already in `test.cpp` and `test3d.cpp`. If you want to create a new test file, it's probably best that you refactor and bring all the tests under one executable. + +## Benchmarking +You can use `make bench` to run the benchmarks. They'll run 2D ICP on the example data. There are currently no 3D ICP benchmarks. + +## Continuous Integration +The CI system is defined in `.github/workflows/ci.yaml`. It's pretty standard: it just runs the tests on MacOS and Linux. + +## Writing a new ICP method From b9b6959e446b116c78ee51d73c2eb0fef450141b Mon Sep 17 00:00:00 2001 From: jesswang7 Date: Sat, 6 Sep 2025 15:06:58 -0400 Subject: [PATCH 2/4] doc for adding new icp instance --- doc_new/ADD_ICP_INSTANCE.md | 99 +++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 doc_new/ADD_ICP_INSTANCE.md diff --git a/doc_new/ADD_ICP_INSTANCE.md b/doc_new/ADD_ICP_INSTANCE.md new file mode 100644 index 0000000..3557045 --- /dev/null +++ b/doc_new/ADD_ICP_INSTANCE.md @@ -0,0 +1,99 @@ +# Contributing: Adding a New ICP Instance + +This section explains how to implement and document a new **ICP instance**. + +--- + +## 1. Overview + +An ICP instance is a specific implementation of the Iterative Closest Point algorithm. Each instance should be: + +- A `final` C++ class that inherits from `icp::ICP` +- Properly registered so users can instantiate it + +--- + +## 2. File and Class Requirements + +Create a new `method_xd.cpp` file in `lib/icp/impl/`, and implement your instance class. + +### Required + +- Class must be `final` and inherit from `public icp::ICP` +- Must implement `void iterate() override` +- Must provide a constructor that calls the `icp::ICP` constructor +- A destructor (can be empty) + +Example: + +```cpp +class MyICP final : public icp::ICP { +public: + MyICP(); + MyICP(const Config& config); + ~MyICP(); + + void setup() override; // Optional + void iterate() override; +}; +```` + +--- + +## 3. ICP Lifecycle + +### Provided Instance Variables + +| Variable | Type | Description | +| ----------- | ------------------ | ---------------------------------- | +| `a` | `PointCloud` | Source point cloud | +| `b` | `PointCloud` | Target point cloud | +| `match` | `std::vector` | Matches from `a` to `b` | +| `transform` | `RBTransform` | Current accumulated transformation | + +### Method Responsibilities + +| Method | When it is Called | What to Do | +| ----------- | --------------------------- | ---------------------------------------------- | +| `setup()` | Once before first iteration | Initialize resources (e.g. KD-tree) | +| `iterate()` | Once per iteration | Compute correspondences and update `transform` | + +--- + +## 4. Registration + +To allow users to select your ICP instance by name, add it to the static methods map in `ICP2` or `ICP3` in `icp.cpp` + +```cpp +#include "icp/impl/my_icp.h" + +namespace icp { + template<> + std::unordered_map ICPX::methods{ + {"my_icp", CONSTRUCT_CONFIG(MyICP)}, // ← Register your method here + }; +} +``` + +--- + +## 5. Recommended Helper Functions + +```cpp +Neighbors nearest_neighbor(const PointCloud& src); +RBTransform best_fit_transform(const PointCloud& A, const PointCloud& B); +void calculate_cost(const std::vector& distances); +``` + + +## 7. Examples + +See the following reference implementations: + +| File | Type | +| -------------------------------------------------------- | ---------| +| [`vanilla.cpp`](src/icp/impl/vanilla.cpp) | 2d | +| [`trimmed.cpp`](src/icp/impl/trimmed.cpp) | 2d | +| [`feature_aware.cpp`](src/icp/impl/feature_aware.cpp) | 2d | +| [`vanilla_3d.cpp`](src/icp/impl/vanilla_3d.cpp) | 3d | +| [`trimmed_3d.cpp`](src/icp/impl/trimmed_3d.cpp) | 3d. | From e258074794e080a13bac14bccc11017440e430eb Mon Sep 17 00:00:00 2001 From: jesswang7 Date: Sat, 6 Sep 2025 15:13:13 -0400 Subject: [PATCH 3/4] update links --- doc_new/ADD_ICP_INSTANCE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc_new/ADD_ICP_INSTANCE.md b/doc_new/ADD_ICP_INSTANCE.md index 3557045..416a6fc 100644 --- a/doc_new/ADD_ICP_INSTANCE.md +++ b/doc_new/ADD_ICP_INSTANCE.md @@ -92,8 +92,8 @@ See the following reference implementations: | File | Type | | -------------------------------------------------------- | ---------| -| [`vanilla.cpp`](src/icp/impl/vanilla.cpp) | 2d | -| [`trimmed.cpp`](src/icp/impl/trimmed.cpp) | 2d | -| [`feature_aware.cpp`](src/icp/impl/feature_aware.cpp) | 2d | -| [`vanilla_3d.cpp`](src/icp/impl/vanilla_3d.cpp) | 3d | -| [`trimmed_3d.cpp`](src/icp/impl/trimmed_3d.cpp) | 3d. | +| [`vanilla.cpp`](/lib/icp/impl/vanilla.cpp) | 2d | +| [`trimmed.cpp`](/lib/icp/impl/trimmed.cpp) | 2d | +| [`feature_aware.cpp`](/lib/icp/impl/feature_aware.cpp) | 2d | +| [`vanilla_3d.cpp`](/lib/icp/impl/vanilla_3d.cpp) | 3d | +| [`trimmed_3d.cpp`](/lib/icp/impl/trimmed_3d.cpp) | 3d. | From 2fa3d9e24c7edbc1b251d7665c66ffc0d8a4db8c Mon Sep 17 00:00:00 2001 From: Yey007 <55263178+Yey007@users.noreply.github.com> Date: Sun, 7 Sep 2025 21:38:11 -0400 Subject: [PATCH 4/4] Move writing icp docs --- book/developing.md | 2 - book/writing_icp.md | 136 +++++++++++++++------------------- doc_new/ADD_ICP_INSTANCE.md | 99 ------------------------- include/icp/impl/trimmed_3d.h | 2 +- include/icp/impl/vanilla_3d.h | 2 +- 5 files changed, 63 insertions(+), 178 deletions(-) delete mode 100644 doc_new/ADD_ICP_INSTANCE.md diff --git a/book/developing.md b/book/developing.md index 85e84e2..ce467d9 100644 --- a/book/developing.md +++ b/book/developing.md @@ -68,5 +68,3 @@ You can use `make bench` to run the benchmarks. They'll run 2D ICP on the exampl ## Continuous Integration The CI system is defined in `.github/workflows/ci.yaml`. It's pretty standard: it just runs the tests on MacOS and Linux. - -## Writing a new ICP method diff --git a/book/writing_icp.md b/book/writing_icp.md index 48dc507..ae3e0ef 100644 --- a/book/writing_icp.md +++ b/book/writing_icp.md @@ -1,104 +1,90 @@ - -\page write_icp_instance Writing an ICP Instance +# Contributing: Adding a New ICP Instance -\tableofcontents +This section explains how to implement and document a new **ICP instance**. -To write an ICP instance, create a C++ source file with at least the following: +--- -1. A `final` class that inherits from `public icp::ICP` -2. A static initialization variable (described below) +## 1. Overview -It is highly recommended you also provide documentation for your ICP instance in the format described here. +An ICP instance is a specific implementation of the Iterative Closest Point algorithm. Each instance should be: -\section core_func_sec Core Functionality +- A `final` C++ class that inherits from `icp::ICP` +- Properly registered so users can instantiate it -The class must define the following behavior: +--- -- A constructor that calls the `icp::ICP` constructor -- An overridden destructor (which may do nothing) -- `void iterate() override` +## 2. File and Class Requirements -In `iterate`, the point clouds are given by the instance variables `a` and `b`. -There is also the `match` instance variable, allocated to have size `a.size()`, which cannot be assumed to contain any definite values. -At the end of `iterate`, the `transform` instance variable should have been updated (although the update may be zero). +Create a new `method_xd.cpp` file in `lib/icp/impl/`, and implement your instance class. -Optionally, the class can override: +### Required -- `void setup() override` +- Class must be `final` and inherit from `public icp::ICP` +- Must implement `void iterate() override` +- Must provide a constructor that calls the `icp::ICP` constructor +- A destructor (can be empty) -`setup` is invoked upon the user call to `ICP::begin` after the internals of ICP have been readied. +Example: -\section static_init_sec Static Initialization +```cpp +class MyICP final : public icp::ICP { +public: + MyICP(); + MyICP(const Config& config); + ~MyICP(); -TODO: update -- we do this in icp.cpp now and the documentation builder will -automatically search there. + void setup() override; // Optional + void iterate() override; +}; +```` -The static initialization is required so that users can instantiate your ICP instance. -Define +--- -```cpp -static bool static_initialization = []() { - assert(ICP::register_method("name_of_instance", []() -> std::unique_ptr { - return std::make_unique(); - })); - return true; -}(); -``` +## 3. ICP Lifecycle -where `"name_of_instance"` is the name of your ICP implementation and `NameOfClass` is the name of the class. +### Provided Instance Variables -\section icp_dpc_sec Documentation +| Variable | Type | Description | +| ----------- | ------------------ | ---------------------------------- | +| `a` | `PointCloud` | Source point cloud | +| `b` | `PointCloud` | Target point cloud | +| `match` | `std::vector` | Matches from `a` to `b` | +| `transform` | `RBTransform` | Current accumulated transformation | -The script icp_doc_builder.py will automatically generate documentation for your ICP instances as markdown files and place them in a desired directory. The invocation format is: +### Method Responsibilities -```shell -python3 icp_doc_builder.py dir/where/your/icps/are/ dir/where/markdown/should/go/ where/main/file/is.md -``` +| Method | When it is Called | What to Do | +| ----------- | --------------------------- | ---------------------------------------------- | +| `setup()` | Once before first iteration | Initialize resources (e.g. KD-tree) | +| `iterate()` | Once per iteration | Compute correspondences and update `transform` | -Notably, `where/main/file/is.md` should contain two lines of the form +--- -```md - -... - -``` +## 4. Registration -the contents between which markers will be automatically updated by the -documentation builder. - -If the file name is `foo_bar.cpp`, then the Doxygen page reference (from which you can refer to from other pages) will be be `foo_bar_icp Foo_bar`. Information about the file should be encoded in special block comments of the following format. +To allow users to select your ICP instance by name, add it to the static methods map in `ICP2` or `ICP3` in `icp.cpp` ```cpp -/* -#command values... -*/ +#include "icp/impl/my_icp.h" + +namespace icp { + template<> + std::unordered_map ICPX::methods{ + {"my_icp", CONSTRUCT_CONFIG(MyICP)}, // ← Register your method here + }; +} ``` -Supported commands are described below. - -- The name of the instance should be given by `/* #name Name of Instance */`. -- An overview of the algorithm should be provided by `/* #desc Overview of algorithm. */`. This description will be rendered as Doxygen source, so you can use markdown and Doxygen commands such as `\ref`. -- If your instance uses icp::ICP::Config parameters, document them as `/* #conf "name_of_param" This is a sentence. This is another sentence describing the parameter. */`. This description will be rendered as Doxygen source. -- Every major step your instance takes should be documented by - - ```cpp - /* - #step My Step: brief description - - Detailed explanation. +--- - Sources: - https://www.example.com - https://www.example.com - https://www.example.com - https://www.example.com - */ - ``` +## 5. Examples - The `: brief description` section is optional, as are the detailed explanation and sources sections. - These descriptions will be rendered as Doxygen source. +See the following reference implementations: -See the source code of vanilla.cpp or trimmed.cpp as examples. +| File | Type | +| -------------------------------------------------------- | ---------| +| [`vanilla.cpp`](/lib/icp/impl/vanilla.cpp) | 2d | +| [`trimmed.cpp`](/lib/icp/impl/trimmed.cpp) | 2d | +| [`feature_aware.cpp`](/lib/icp/impl/feature_aware.cpp) | 2d | +| [`vanilla_3d.cpp`](/lib/icp/impl/vanilla_3d.cpp) | 3d | +| [`trimmed_3d.cpp`](/lib/icp/impl/trimmed_3d.cpp) | 3d. | diff --git a/doc_new/ADD_ICP_INSTANCE.md b/doc_new/ADD_ICP_INSTANCE.md deleted file mode 100644 index 416a6fc..0000000 --- a/doc_new/ADD_ICP_INSTANCE.md +++ /dev/null @@ -1,99 +0,0 @@ -# Contributing: Adding a New ICP Instance - -This section explains how to implement and document a new **ICP instance**. - ---- - -## 1. Overview - -An ICP instance is a specific implementation of the Iterative Closest Point algorithm. Each instance should be: - -- A `final` C++ class that inherits from `icp::ICP` -- Properly registered so users can instantiate it - ---- - -## 2. File and Class Requirements - -Create a new `method_xd.cpp` file in `lib/icp/impl/`, and implement your instance class. - -### Required - -- Class must be `final` and inherit from `public icp::ICP` -- Must implement `void iterate() override` -- Must provide a constructor that calls the `icp::ICP` constructor -- A destructor (can be empty) - -Example: - -```cpp -class MyICP final : public icp::ICP { -public: - MyICP(); - MyICP(const Config& config); - ~MyICP(); - - void setup() override; // Optional - void iterate() override; -}; -```` - ---- - -## 3. ICP Lifecycle - -### Provided Instance Variables - -| Variable | Type | Description | -| ----------- | ------------------ | ---------------------------------- | -| `a` | `PointCloud` | Source point cloud | -| `b` | `PointCloud` | Target point cloud | -| `match` | `std::vector` | Matches from `a` to `b` | -| `transform` | `RBTransform` | Current accumulated transformation | - -### Method Responsibilities - -| Method | When it is Called | What to Do | -| ----------- | --------------------------- | ---------------------------------------------- | -| `setup()` | Once before first iteration | Initialize resources (e.g. KD-tree) | -| `iterate()` | Once per iteration | Compute correspondences and update `transform` | - ---- - -## 4. Registration - -To allow users to select your ICP instance by name, add it to the static methods map in `ICP2` or `ICP3` in `icp.cpp` - -```cpp -#include "icp/impl/my_icp.h" - -namespace icp { - template<> - std::unordered_map ICPX::methods{ - {"my_icp", CONSTRUCT_CONFIG(MyICP)}, // ← Register your method here - }; -} -``` - ---- - -## 5. Recommended Helper Functions - -```cpp -Neighbors nearest_neighbor(const PointCloud& src); -RBTransform best_fit_transform(const PointCloud& A, const PointCloud& B); -void calculate_cost(const std::vector& distances); -``` - - -## 7. Examples - -See the following reference implementations: - -| File | Type | -| -------------------------------------------------------- | ---------| -| [`vanilla.cpp`](/lib/icp/impl/vanilla.cpp) | 2d | -| [`trimmed.cpp`](/lib/icp/impl/trimmed.cpp) | 2d | -| [`feature_aware.cpp`](/lib/icp/impl/feature_aware.cpp) | 2d | -| [`vanilla_3d.cpp`](/lib/icp/impl/vanilla_3d.cpp) | 3d | -| [`trimmed_3d.cpp`](/lib/icp/impl/trimmed_3d.cpp) | 3d. | diff --git a/include/icp/impl/trimmed_3d.h b/include/icp/impl/trimmed_3d.h index a610c7c..8af2617 100644 --- a/include/icp/impl/trimmed_3d.h +++ b/include/icp/impl/trimmed_3d.h @@ -12,7 +12,7 @@ namespace icp { - class Trimmed3d : public ICP3 { + class Trimmed3d final : public ICP3 { public: Trimmed3d(const Config& config); Trimmed3d(); diff --git a/include/icp/impl/vanilla_3d.h b/include/icp/impl/vanilla_3d.h index 9ed5417..cbfe69b 100644 --- a/include/icp/impl/vanilla_3d.h +++ b/include/icp/impl/vanilla_3d.h @@ -12,7 +12,7 @@ namespace icp { - class Vanilla3d : public ICP3 { + class Vanilla3d final : public ICP3 { public: Vanilla3d(const Config& config); Vanilla3d();