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
Binary file removed .DS_Store
Binary file not shown.
File renamed without changes.
4 changes: 2 additions & 2 deletions .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ jobs:

- name: Build documentation
run: |
cd docs
cd einit_docs
sphinx-build -b html . _build/html

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/main'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_build/html
publish_dir: ./einit_docs/_build/html
publish_branch: gh-pages
force_orphan: true # Clean the branch each time
user_name: 'github-actions[bot]'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Lint with pylint
run: |
# Run pylint with configuration file
pylint einit --rcfile=pylint.rc
pylint einit --rcfile=.github/workflows/.pylintrc

test:
runs-on: ubuntu-latest
Expand All @@ -50,5 +50,5 @@ jobs:

- name: Test with pytest
run: |
pytest tests/test_einit.py -v
pytest tests/test_integration.py -v
pytest einit_tests/test_einit.py -v
pytest einit_tests/test_integration.py -v
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ __pycache__/

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
Expand All @@ -17,6 +19,7 @@ lib64/
parts/
var/
share/python-wheels/
*.egg-info/
.installed.cfg
MANIFEST

Expand Down Expand Up @@ -64,6 +67,7 @@ instance/

# Sphinx documentation
docs/_build/
einit_docs/_build/

# PyBuilder
.pybuilder/
Expand Down Expand Up @@ -169,3 +173,17 @@ cython_debug/

# Development and research files
dev/

# Local development - reiya components
reiya/
reiya_*/
*reiya*.py

# macOS system files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
18 changes: 18 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cff-version: 1.2.0
message: "If you use this work, please cite the paper below."
preferred-citation:
type: article
title: "An Approach to Robust ICP Initialization"
authors:
- family-names: Kolpakov
given-names: Alexander
- family-names: Werman
given-names: Michael
journal: "IEEE Transactions on Pattern Analysis and Machine Intelligence"
volume: 45
issue: 10
year: 2023
start: 12685
end: 12691
doi: "10.1109/TPAMI.2023.3287468"
url: "https://ieeexplore.ieee.org/document/10155262"
7 changes: 7 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include README.md
include LICENSE
include CITATION.cff
include pyproject.toml
recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude * .DS_Store
113 changes: 75 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
# einit: Fast and Robust ICP Initialization
<p align="center">
<img src="einit_docs/einit.png" alt="einit logo" height="320"/>
</p>

<h1 align="center">Fast and Robust ICP Initialization</h1>

<p align="center">
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"/>
</a>
<a href="https://www.python.org/downloads/">
<img src="https://img.shields.io/badge/python-3.6+-blue.svg" alt="Python 3.6+"/>
</a>
<a href="https://pypi.org/project/einit/">
<img src="https://img.shields.io/pypi/v/einit.svg" alt="PyPI"/>
</a>
<a href="https://pepy.tech/projects/einit">
<img src="https://static.pepy.tech/personalized-badge/einit?period=monthly&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads%2Fmonth" alt="PyPI Downloads">
</a>
<a href="https://github.com/sashakolpakov/einit/actions/workflows/pylint.yml">
<img src="https://img.shields.io/github/actions/workflow/status/sashakolpakov/einit/pylint.yml?branch=main&label=CI&logo=github" alt="CI"/>
</a>
<a href="https://github.com/sashakolpakov/einit/actions/workflows/deploy_docs.yml">
<img src="https://img.shields.io/github/actions/workflow/status/sashakolpakov/einit/deploy_docs.yml?branch=main&label=Docs&logo=github" alt="Docs"/>
</a>
<a href="https://sashakolpakov.github.io/einit/">
<img src="https://img.shields.io/website-up-down-green-red/https/sashakolpakov.github.io/einit?label=API%20Documentation" alt="Docs Status"/>
</a>
</p>

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Python 3.6+](https://img.shields.io/badge/python-3.6+-blue.svg)](https://www.python.org/downloads/)
[![PyPI](https://img.shields.io/pypi/v/einit.svg)](https://pypi.org/project/einit/)

<!-- CI status from GitHub Actions -->
[![CI](https://img.shields.io/github/actions/workflow/status/sashakolpakov/einit/pylint.yml?branch=main&label=CI&logo=github)](https://github.com/sashakolpakov/einit/actions/workflows/pylint.yml) <!-- Docs status from GitHub Actions -->
[![Docs](https://img.shields.io/github/actions/workflow/status/sashakolpakov/einit/deploy_docs.yml?branch=main&label=Docs&logo=github)](https://github.com/sashakolpakov/einit/actions/workflows/deploy_docs.yml) <!-- Docs health via HTTP ping -->
[![Docs](https://img.shields.io/website-up-down-green-red/https/sashakolpakov.github.io/einit?label=API%20Documentation)](https://sashakolpakov.github.io/einit/)


**einit** provides fast and robust initialization for 3D point cloud alignment using ellipsoid analysis. It computes initial transformations by analyzing the ellipsoids of inertia of point clouds and uses KD-tree correspondence recovery for robustness to real-world scenarios.
Expand All @@ -26,18 +46,18 @@

```python
import numpy as np
from einit import ellipsoid_init_icp
from einit import register_ellipsoid

# Your point clouds (N x 3 arrays)
src_points = np.random.randn(1000, 3)
dst_points = src_points @ R + t # Apply some transformation

# Get the transformation matrix
T = ellipsoid_init_icp(src_points, dst_points)
T = register_ellipsoid(src_points, dst_points)
print(T) # 4x4 homogeneous transformation matrix

# With custom parameters for robustness control
T = ellipsoid_init_icp(
T = register_ellipsoid(
src_points, dst_points,
max_correspondence_distance=0.1, # Maximum distance for valid correspondences
min_inlier_fraction=0.7, # Require 70% valid correspondences
Expand Down Expand Up @@ -78,11 +98,11 @@ Real-world performance on test datasets:

| Dataset | Points | RMSE | Time |
|---------|--------|-------|------------------|
| Sphere | 1500 | 0.03 | 0.006 ± 0.002 ms |
| Cube | 3375 | 0.02 | 0.010 ± 0.008 ms |
| Sphere | 1500 | 0.035 | 0.006 ± 0.002 ms |
| Cube | 3375 | 0.025 | 0.010 ± 0.008 ms |
| Bunny | 992 | 0.02 | 0.047 ± 0.021 ms |

*With 0.01-0.02 standard Gaussian noise and ~ 80% overlap*
*With 0.01-0.02 standard Gaussian noise and partial overlap*

## Algorithm

Expand All @@ -100,10 +120,10 @@ KD-tree correspondence recovery makes the algorithm robust to point cloud permut

```python
import cv2
from einit import ellipsoid_init_icp
from einit import register_ellipsoid

# Get initial transformation
T_init = ellipsoid_init_icp(src, dst)
T_init = register_ellipsoid(src, dst)

# Refine alignment with OpenCV
src_aligned = apply_transform(src, T_init)
Expand All @@ -117,54 +137,54 @@ retval, T_refined, inliers = cv2.estimateAffine3D(

### Running Examples

The `examples/` directory contains demonstrations and visualizations:
The `einit_examples/` directory contains demonstrations and visualizations:

**Interactive Jupyter Notebook:**
```bash
jupyter notebook examples/visual_tests.ipynb
jupyter notebook einit_examples/visual_tests.ipynb
```
[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](
https://colab.research.google.com/github/sashakolpakov/einit/blob/main/examples/visual_tests.ipynb
https://colab.research.google.com/github/sashakolpakov/einit/blob/main/einit_examples/visual_tests.ipynb
)
Comprehensive visual demonstrations including sphere, cube, and Stanford bunny alignments with performance analysis.

**Permutation Invariance Test:**
```bash
python examples/point_reoder_test.py
python einit_examples/point_reoder_test.py
```
Demonstrates that einit correctly handles randomly permuted point clouds.

**Partial Overlap Test:**
```bash
python examples/rand_overlap_test.py
python einit_examples/rand_overlap_test.py
```
Tests algorithm robustness with realistic partial overlap scenarios using Stanford bunny data.
Tests algorithm robustness with randomized partial overlap scenarios using the Stanford bunny.

**Bounding Box Overlap Test:**
```bash
python examples/bbox_overlap_test.py
python einit_examples/bbox_overlap_test.py
```
Evaluates performance with geometric bounding box constraints.
Evaluates performance on the Stanford bunny with geometric bounding box constraints.

### Running Tests
> **Note**: Unlike randomized overlaps, this is a known failure mode of the algorithm. Low success rate is expected.

The `tests/` directory contains comprehensive test suites validating core functionality:

### Running Tests

```bash
# All tests
pytest tests/ -v
pytest einit_tests/ -v

# Specific test categories
pytest tests/test_einit.py -v # Core algorithm tests
pytest tests/test_integration.py -v # Integration and robustness tests
# Core algorithm tests
pytest einit_tests/test_einit.py -v

# Test permutation invariance specifically
pytest tests/test_einit.py::test_random_permutation_invariance -v
# Stanford bunny integration test
pytest einit_tests/test_integration.py -v
```

**Test Coverage:**
- **Core Algorithm Tests** (`test_einit.py`): Basic functionality, permutation invariance, noise robustness, and Stanford bunny dataset validation
- **Integration Tests** (`test_integration.py`): End-to-end pipeline testing with real-world scenarios
- **Core Tests**: Basic functionality, synthetic shapes (spheres, cube surfaces), and Stanford bunny validation
- **Integration Test**: Real-world Stanford bunny PLY dataset with partial overlap and noise

## Documentation

Expand All @@ -186,8 +206,25 @@ This work is supported by the Google Cloud Research Award number GCP19980904.

## Citation

Based on the original paper by Kolpakov and Werman:
If you use this work, please cite the paper below.

**BibTeX:**
```bibtex
@article{kolpakov2023approach,
title={An Approach to Robust ICP Initialization},
author={Kolpakov, Alexander and Werman, Michael},
journal={IEEE Transactions on Pattern Analysis and Machine Intelligence},
volume={45},
number={10},
pages={12685--12691},
year={2023},
publisher={IEEE},
doi={10.1109/TPAMI.2023.3287468},
url={https://ieeexplore.ieee.org/document/10155262}
}
```

[![Paper](https://img.shields.io/badge/arXiv-read%20PDF-b31b1b.svg)](https://arxiv.org/abs/2212.05332)
**APA:**
Kolpakov, A., & Werman, M. (2023). An Approach to Robust ICP Initialization. *IEEE Transactions on Pattern Analysis and Machine Intelligence*, 45(10), 12685-12691. https://doi.org/10.1109/TPAMI.2023.3287468

*"An approach to robust ICP initialization"*
[![Paper](https://img.shields.io/badge/arXiv-read%20PDF-b31b1b.svg)](https://arxiv.org/abs/2212.05332)
2 changes: 1 addition & 1 deletion build_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def main():

# Get project root directory
project_root = Path(__file__).parent
docs_dir = project_root / "docs"
docs_dir = project_root / "einit_docs"
build_dir = docs_dir / "_build"

print("Building einit documentation...")
Expand Down
4 changes: 2 additions & 2 deletions einit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .einit import ellipsoid_init_icp, barycentered
from .einit import register_ellipsoid, barycentered

__all__ = ["ellipsoid_init_icp", "barycentered"]
__all__ = ["register_ellipsoid", "barycentered"]
12 changes: 6 additions & 6 deletions einit/einit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
einit – Ellipsoid ICP initialization

Provides ellipsoid_init_icp(src, dst) that:
Provides register_ellipsoid(src, dst) that:
1. Centers src & dst by their centroids.
2. Computes ellipsoid matrices; eigen-decomposes to get principal axes.
3. Searches all 8 diagonal ±1 reflections for best alignment.
Expand All @@ -12,7 +12,7 @@
import numpy as np
from scipy.spatial import cKDTree

__all__ = ["ellipsoid_init_icp", "barycentered"]
__all__ = ["register_ellipsoid", "barycentered"]


def barycentered(points):
Expand All @@ -21,7 +21,7 @@ def barycentered(points):
return points - centroid


def ellipsoid_init_icp(src_points, dst_points, max_correspondence_distance=None,
def register_ellipsoid(src_points, dst_points, max_correspondence_distance=None,
min_inlier_fraction=0.5, leafsize=16, positive_only=False):
"""
Compute initial transformation between 3D point clouds using ellipsoid analysis.
Expand Down Expand Up @@ -73,16 +73,16 @@ def ellipsoid_init_icp(src_points, dst_points, max_correspondence_distance=None,
Basic usage:

>>> import numpy as np
>>> from einit import ellipsoid_init_icp
>>> from einit import register_ellipsoid
>>> src = np.random.randn(100, 3)
>>> dst = np.random.randn(80, 3) # Different size OK
>>> T = ellipsoid_init_icp(src, dst)
>>> T = register_ellipsoid(src, dst)
>>> T.shape
(4, 4)

With custom parameters:

>>> T = ellipsoid_init_icp(
>>> T = register_ellipsoid(
... src, dst,
... max_correspondence_distance=0.1,
... min_inlier_fraction=0.7,
Expand Down
Loading