Skip to content

Commit 8a8321e

Browse files
committed
Initial commit
0 parents  commit 8a8321e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5120
-0
lines changed

.clang-format

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
Language: Cpp
3+
BasedOnStyle: Mozilla
4+
# Only modifications deviating from the base style are specified
5+
Standard: c++17
6+
AccessModifierOffset: -4
7+
AllowAllArgumentsOnNextLine: false
8+
AllowAllConstructorInitializersOnNextLine: false
9+
AlwaysBreakAfterReturnType: All
10+
BreakBeforeBraces: Allman
11+
ColumnLimit: 120
12+
ContinuationIndentWidth: 8
13+
FixNamespaceComments: true
14+
IndentPPDirectives: BeforeHash
15+
IndentWidth: 4
16+
ReflowComments: true
17+
SpaceAfterTemplateKeyword: true
18+
SpaceInEmptyBlock: true
19+
...
20+

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
build
2+
__pycache__
3+
.*_cache
4+
*.py[cod]
5+
src/torchhull.egg-info
6+
typings
7+
.coverage*
8+
.benchmarks
9+
.nox
10+
data/cache

.vscode/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"editor.formatOnSave": true,
3+
"[python]": {
4+
"editor.defaultFormatter": "ms-python.black-formatter",
5+
"editor.codeActionsOnSave": {
6+
"source.organizeImports": "explicit"
7+
}
8+
},
9+
"python.analysis.diagnosticSeverityOverrides": {
10+
"reportMissingModuleSource": "none",
11+
}
12+
}

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
9+
## [0.1.0] - 2024-MM-DD
10+
11+
- Initial version
12+
13+
[0.1.0]: https://github.com/vc-bonn/torchhull/releases/tag/v0.1.0

LICENSE

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Patrick Stotko
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
23+
24+
25+
BSD 3-Clause License
26+
27+
For Marching Cubes implementation, which is a modified version from PyTorch3D.
28+
29+
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
30+
31+
Redistribution and use in source and binary forms, with or without modification,
32+
are permitted provided that the following conditions are met:
33+
34+
* Redistributions of source code must retain the above copyright notice, this
35+
list of conditions and the following disclaimer.
36+
37+
* Redistributions in binary form must reproduce the above copyright notice,
38+
this list of conditions and the following disclaimer in the documentation
39+
and/or other materials provided with the distribution.
40+
41+
* Neither the name Meta nor the names of its contributors may be used to
42+
endorse or promote products derived from this software without specific
43+
prior written permission.
44+
45+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
46+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
47+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
48+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
49+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
50+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
51+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
52+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
53+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
54+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
graft src/torchhull/_C
2+
prune data
3+
prune tools

README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<h1 align="center">torchhull: A fast Visual Hull implementation</h1>
2+
3+
<!-- start readme -->
4+
5+
<p align="center">
6+
<a href="https://pypi.python.org/pypi/torchhull">
7+
<img alt="PyPI - Version" src="https://img.shields.io/pypi/v/torchhull">
8+
</a>
9+
<a href="https://pypi.python.org/pypi/torchhull">
10+
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/torchhull">
11+
</a>
12+
<a href="https://github.com/vc-bonn/torchhull/blob/main/LICENSE">
13+
<img alt="GitHub License" src="https://img.shields.io/badge/License-MIT-green.svg"/>
14+
</a>
15+
<a href="https://github.com/vc-bonn/torchhull/blob/main/LICENSE">
16+
<img alt="GitHub License" src="https://img.shields.io/badge/License-BSD--3--Clause-green.svg"/>
17+
</a>
18+
<a href="https://github.com/vc-bonn/torchhull/actions/workflows/lint.yml">
19+
<img alt="Lint" src="https://github.com/vc-bonn/torchhull/actions/workflows/lint.yml/badge.svg">
20+
</a>
21+
<a href="https://vc-bonn.github.io/torchhull">
22+
<img alt="Documentation" src="https://img.shields.io/badge/docs-Latest-green.svg"/>
23+
</a>
24+
</p>
25+
26+
27+
torchhull is an extremely fast Torch C++/CUDA implementation for computing visual hulls from mask images and comes with Python bindings through [charonload](https://github.com/vc-bonn/charonload):
28+
29+
- ⚡ Up to real-time capable speed depending on chosen resolution
30+
- 🗜️ Memory-efficient computation by constructing sparse voxel octrees
31+
- 🌊 Watertight mesh generation via Marching Cubes
32+
- 🛠️ Support for partially visible objects, i.e. clipped mask images, and fully observed objects
33+
34+
35+
In particular, torchhull is a GPU implementation of the following paper:
36+
37+
```bib
38+
@article{scharr2017fast,
39+
title={{Fast High Resolution Volume Carving for 3D Plant Shoot Reconstruction}},
40+
author={Scharr, Hanno and Briese, Christoph and Embgenbroich, Patrick and Fischbach, Andreas and Fiorani, Fabio and M{\"u}ller-Linow, Mark},
41+
journal={Frontiers in Plant Science},
42+
volume={8},
43+
pages={303692},
44+
year={2017},
45+
publisher={Frontiers}
46+
}
47+
```
48+
49+
50+
## Installation
51+
52+
torchhull requires the following prerequites (for JIT compilation):
53+
54+
- Python >= 3.9
55+
- CUDA >= 12.1
56+
- C++17 compiler
57+
58+
The package itself can be installed from PyPI:
59+
60+
```sh
61+
pip install torchhull
62+
```
63+
64+
65+
## Quick Start
66+
67+
torchhull gets as input mask images with camera information:
68+
69+
- `masks`: Single-channel images `M` with binary values {0, 1}.
70+
- `transforms`: Fused extrinsic and intrinsic matrix `K * T`, i.e. transformation from world coordinates to OpenGL clip space (right before perspective division).
71+
72+
The visual hull is then evaluated inside a cube with bottom-front-left corner `cube_corner_bfl` and extent `cube_length` at extracted at octree level `level`. The remaining flags control how the output mesh `(verts, faces)` should look like.
73+
74+
```python
75+
import torchhull
76+
77+
verts, faces = torchhull.visual_hull(masks, # [B, H, W, 1]
78+
transforms, # [B, 4, 4]
79+
level,
80+
cube_corner_bfl,
81+
cube_length,
82+
masks_partial=False,
83+
unique_verts=True,
84+
)
85+
```
86+
87+
88+
## License
89+
90+
This software is provided under MIT license, with parts under BSD 3-Clause license. See [`LICENSE`](https://github.com/vc-bonn/torchhull/blob/main/LICENSE) for more information.
91+
92+
93+
## Contact
94+
95+
Patrick Stotko - <a href="mailto:[email protected]">[email protected]</a><br/>
96+
97+
<!-- end readme -->
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from __future__ import annotations
2+
3+
import enum
4+
5+
import pytest
6+
import torch
7+
8+
import torchhull
9+
10+
try:
11+
import pytorch3d.ops.marching_cubes
12+
13+
pytorch3d_available = True
14+
except ImportError:
15+
pytorch3d_available = False
16+
17+
18+
DEVICE = torch.device("cuda")
19+
20+
21+
class TensorType(enum.Enum):
22+
DENSE = enum.auto()
23+
SPARSE = enum.auto()
24+
25+
def __str__(self) -> str:
26+
return f"{self.name}"
27+
28+
29+
class ImplementationType(enum.Enum):
30+
TORCHHULL = enum.auto()
31+
PYTORCH3D = enum.auto()
32+
33+
def __str__(self) -> str:
34+
return f"{self.name}"
35+
36+
37+
def sdf_sphere(center: torch.Tensor, radius: float, samples: torch.Tensor) -> torch.Tensor:
38+
return torch.linalg.norm(samples - torch.unsqueeze(center, 0), dim=1) - radius
39+
40+
41+
def run_marching_cubes(sdf: torch.Tensor, implementation_type: ImplementationType) -> tuple[torch.Tensor, torch.Tensor]:
42+
if implementation_type == ImplementationType.TORCHHULL:
43+
v, f = torchhull.marching_cubes(sdf, isolevel=0, return_local_coords=False)
44+
verts, faces = [], []
45+
verts.append(v)
46+
faces.append(f)
47+
elif implementation_type == ImplementationType.PYTORCH3D and pytorch3d_available:
48+
verts, faces = pytorch3d.ops.marching_cubes.marching_cubes(sdf, isolevel=0, return_local_coords=False)
49+
else:
50+
verts, faces = [], []
51+
verts.append(torch.empty([0, 3], dtype=torch.float32, device=DEVICE))
52+
faces.append(torch.empty([0, 3], dtype=torch.int64, device=DEVICE))
53+
return verts[0], faces[0]
54+
55+
56+
def list_sizes_mc() -> list[int]:
57+
return [100, 200, 300, 400, 500, 600]
58+
59+
60+
@pytest.mark.parametrize("size", list_sizes_mc())
61+
@pytest.mark.parametrize(
62+
("implementation_type", "tensor_type"),
63+
[
64+
pytest.param(
65+
ImplementationType.PYTORCH3D,
66+
TensorType.DENSE,
67+
marks=pytest.mark.skipif(not pytorch3d_available, reason="PyTorch3D not available"),
68+
),
69+
(ImplementationType.TORCHHULL, TensorType.DENSE),
70+
(ImplementationType.TORCHHULL, TensorType.SPARSE),
71+
],
72+
)
73+
def test_marching_cubes(
74+
benchmark, # noqa: ANN001
75+
size: int,
76+
implementation_type: ImplementationType,
77+
tensor_type: TensorType,
78+
) -> None:
79+
torch.cuda.empty_cache()
80+
81+
grid_1d = torch.arange(size, device=DEVICE)
82+
83+
grid_verts = torch.stack(
84+
torch.meshgrid(3 * [grid_1d], indexing="ij"),
85+
dim=0,
86+
) # 3 x size x size x size
87+
grid_verts = grid_verts.reshape([3, -1]).T # size^3 x 3
88+
89+
center = torch.full((3,), size / 2, dtype=torch.float32, device=DEVICE)
90+
radius = 0.8731945 * (size / 2) # Use very uneven fraction to avoid zero values at the vertices
91+
sdf = sdf_sphere(center, radius, grid_verts)
92+
93+
sdf = sdf.reshape([1, *(3 * [size])])
94+
95+
if tensor_type == TensorType.SPARSE:
96+
truncation_distance = 2 # max(3, 0.01 * max(sdf.shape))
97+
sdf[sdf.abs() > truncation_distance] = 0
98+
99+
sdf = sdf.to_sparse()
100+
101+
# Warmup
102+
run_marching_cubes(sdf=sdf, implementation_type=implementation_type)
103+
104+
benchmark(run_marching_cubes, sdf=sdf, implementation_type=implementation_type)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from __future__ import annotations
2+
3+
import functools
4+
import pathlib
5+
import sys
6+
7+
import pytest
8+
import torch
9+
10+
import torchhull
11+
12+
DATA_DIR = pathlib.Path(__file__).parents[1] / "data"
13+
sys.path.append(str(DATA_DIR))
14+
from generate_dataset import generate_dataset # noqa: E402
15+
16+
generate_dataset = functools.cache(generate_dataset)
17+
18+
19+
DEVICE = torch.device("cuda")
20+
21+
22+
@pytest.mark.parametrize("level", [7, 8, 9, 10, 11])
23+
@pytest.mark.parametrize("number_cameras", [10, 20, 30, 40, 50])
24+
def test_visual_hull(benchmark, level: int, number_cameras: int) -> None: # noqa: ANN001
25+
torch.cuda.empty_cache()
26+
27+
data_dir = pathlib.Path(__file__).parents[1] / "data"
28+
file = "Armadillo.ply"
29+
30+
projection_matrices, view_matrices, masks = generate_dataset(
31+
mesh_file=data_dir / file,
32+
number_cameras=number_cameras,
33+
device=DEVICE,
34+
)
35+
transforms = projection_matrices @ view_matrices
36+
37+
masks = masks.to(dtype=torch.float32, device=DEVICE)
38+
transforms = transforms.to(dtype=torch.float32, device=DEVICE)
39+
40+
scale = 1.1
41+
42+
# Warmup
43+
torchhull.visual_hull(
44+
masks=masks,
45+
transforms=transforms,
46+
level=level,
47+
cube_corner_bfl=(-scale, -scale, -scale),
48+
cube_length=2.0 * scale,
49+
masks_partial=False,
50+
unique_verts=True,
51+
)
52+
53+
benchmark(
54+
torchhull.visual_hull,
55+
masks=masks,
56+
transforms=transforms,
57+
level=level,
58+
cube_corner_bfl=(-scale, -scale, -scale),
59+
cube_length=2.0 * scale,
60+
masks_partial=False,
61+
unique_verts=True,
62+
)

data/Armadillo.ply

6.6 MB
Binary file not shown.

0 commit comments

Comments
 (0)