Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion AUTHOR.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This library was made by Ethan Uppal and Utku Melemetci.
This library was made by Ethan Uppal, Jess Wang, and Utku Melemetci.
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ 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.

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.
70 changes: 70 additions & 0 deletions book/developing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 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.
136 changes: 61 additions & 75 deletions book/writing_icp.md
Original file line number Diff line number Diff line change
@@ -1,104 +1,90 @@
<!--
Copyright (C) 2025 Ethan Uppal.
SPDX-License-Identifier: MIT
--->
\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<ICP> {
return std::make_unique<NameOfClass>();
}));
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<int>` | 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
<!-- ICP_DOCS_BUILDER EDIT MARKER START -->
...
<!-- ICP_DOCS_BUILDER EDIT MARKER END -->
```
## 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<std::string, ICPX::MethodConstructor> 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. |
2 changes: 1 addition & 1 deletion include/icp/impl/trimmed_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace icp {

class Trimmed3d : public ICP3 {
class Trimmed3d final : public ICP3 {
public:
Trimmed3d(const Config& config);
Trimmed3d();
Expand Down
2 changes: 1 addition & 1 deletion include/icp/impl/vanilla_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace icp {

class Vanilla3d : public ICP3 {
class Vanilla3d final : public ICP3 {
public:
Vanilla3d(const Config& config);
Vanilla3d();
Expand Down
Loading