Skip to content

Commit 8eccb5b

Browse files
committed
pgeof2
1 parent 660905b commit 8eccb5b

26 files changed

+1366
-662
lines changed

.clang-format

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
Language: Cpp
2+
BasedOnStyle: Google
3+
# ---
4+
#AccessModifierOffset: -4
5+
AlignAfterOpenBracket: AlwaysBreak # Values: Align, DontAlign, AlwaysBreak
6+
AlignConsecutiveAssignments: true
7+
AlignConsecutiveDeclarations: true
8+
#AlignEscapedNewlinesLeft: true
9+
#AlignOperands: false
10+
AlignTrailingComments: false # Should be off, causes many dummy problems!!
11+
#AllowAllParametersOfDeclarationOnNextLine: true
12+
AllowShortBlocksOnASingleLine: true
13+
#AllowShortCaseLabelsOnASingleLine: false
14+
#AllowShortFunctionsOnASingleLine: Empty
15+
#AllowShortIfStatementsOnASingleLine: false
16+
#AllowShortLoopsOnASingleLine: false
17+
#AlwaysBreakAfterDefinitionReturnType: None
18+
#AlwaysBreakAfterReturnType: None
19+
#AlwaysBreakBeforeMultilineStrings: true
20+
#AlwaysBreakTemplateDeclarations: true
21+
#BinPackArguments: false
22+
#BinPackParameters: false
23+
#BraceWrapping:
24+
#AfterClass: false
25+
#AfterControlStatement: false
26+
#AfterEnum: false
27+
#AfterFunction: false
28+
#AfterNamespace: false
29+
#AfterObjCDeclaration: false
30+
#AfterStruct: false
31+
#AfterUnion: false
32+
#BeforeCatch: false
33+
#BeforeElse: true
34+
#IndentBraces: false
35+
#BreakBeforeBinaryOperators: None
36+
BreakBeforeBraces: Allman
37+
#BreakBeforeTernaryOperators: true
38+
#BreakConstructorInitializersBeforeComma: false
39+
ColumnLimit: 120
40+
#CommentPragmas: ''
41+
#ConstructorInitializerAllOnOneLineOrOnePerLine: true
42+
#ConstructorInitializerIndentWidth: 4
43+
#ContinuationIndentWidth: 4
44+
#Cpp11BracedListStyle: true
45+
#DerivePointerAlignment: false
46+
#DisableFormat: false
47+
#ExperimentalAutoDetectBinPacking: false
48+
##FixNamespaceComments: true # Not applicable in 3.8
49+
#ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
50+
#IncludeCategories:
51+
#- Regex: '.*'
52+
#Priority: 1
53+
IndentCaseLabels: true
54+
IndentWidth: 4
55+
IndentWrappedFunctionNames: true
56+
#KeepEmptyLinesAtTheStartOfBlocks: true
57+
#MacroBlockBegin: ''
58+
#MacroBlockEnd: ''
59+
MaxEmptyLinesToKeep: 1
60+
NamespaceIndentation: None
61+
#PenaltyBreakBeforeFirstCallParameter: 19
62+
#PenaltyBreakComment: 300
63+
#PenaltyBreakFirstLessLess: 120
64+
#PenaltyBreakString: 1000
65+
#PenaltyExcessCharacter: 1000000
66+
#PenaltyReturnTypeOnItsOwnLine: 200
67+
DerivePointerAlignment: false
68+
#PointerAlignment: Left
69+
ReflowComments: true # Should be true, otherwise clang-format doesn't touch comments
70+
SortIncludes: true
71+
#SpaceAfterCStyleCast: false
72+
SpaceBeforeAssignmentOperators: true
73+
#SpaceBeforeParens: ControlStatements
74+
#SpaceInEmptyParentheses: false
75+
#SpacesBeforeTrailingComments: 2
76+
#SpacesInAngles: false
77+
#SpacesInContainerLiterals: true
78+
#SpacesInCStyleCastParentheses: false
79+
#SpacesInParentheses: false
80+
#SpacesInSquareBrackets: false
81+
Standard: Cpp11
82+
TabWidth: 4
83+
UseTab: Never # Available options are Never, Always, ForIndentation

.github/workflows/tests.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Tests
2+
3+
on:
4+
- push
5+
- pull_request
6+
7+
jobs:
8+
build:
9+
name: Tests on ${{ matrix.os }}
10+
runs-on: ${{ matrix.os }}
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-22.04, windows-2022]
15+
python-version: ['3.9', '3.10', '3.11', '3.12']
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
submodules: True
20+
- name: Set up Python ${{ matrix.python-version }}
21+
uses: actions/setup-python@v4
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
python -m pip install tox tox-gh-actions
28+
- name: Test with tox
29+
run: tox

.github/workflows/wheels.yml

+9-12
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,16 @@ jobs:
1515
runs-on: ${{ matrix.os }}
1616
strategy:
1717
matrix:
18-
os: [ubuntu-20.04, windows-2019]
18+
os: [ubuntu-22.04, windows-2022, macos-12]
19+
fail-fast: false
1920

20-
env:
21-
CIBW_ENVIRONMENT: ${{ matrix.os == 'windows-2019' && 'EIGEN_LIB_PATH=.' || 'EIGEN_LIB_PATH=/project' }}
2221
steps:
23-
- uses: actions/checkout@v4
24-
2522
- uses: actions/checkout@v4
2623
with:
27-
repository: eigen-mirror/eigen
28-
ref: "3.4.0"
29-
path: eigen3
30-
24+
submodules: True
25+
3126
# Used to host cibuildwheel
32-
- uses: actions/setup-python@v3
27+
- uses: actions/setup-python@v4
3328

3429
- name: Install cibuildwheel
3530
run: python -m pip install cibuildwheel==2.15
@@ -49,9 +44,11 @@ jobs:
4944
# Only publish to PyPI when a commit is tagged
5045
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
5146
steps:
52-
- uses: actions/checkout@v3
47+
- uses: actions/checkout@v4
48+
with:
49+
submodules: True
5350

54-
- uses: actions/setup-python@v3
51+
- uses: actions/setup-python@v4
5552

5653
- uses: actions/download-artifact@v3
5754
with:

.gitmodules

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[submodule "eigen"]
2+
path = third_party/eigen
3+
url = https://gitlab.com/libeigen/eigen
4+
branch = 3.4
5+
[submodule "nanoflann"]
6+
path = third_party/nanoflann
7+
url = https://github.com/jlblancoc/nanoflann
8+
[submodule "taskflow"]
9+
path = third_party/taskflow
10+
url = https://github.com/taskflow/taskflow

CMakeLists.txt

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
cmake_minimum_required(VERSION 3.15...3.27)
2+
project(pgeof2)
3+
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)
4+
5+
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
6+
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
7+
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
8+
endif()
9+
10+
# Detect the installed nanobind package and import it into CMake
11+
execute_process(
12+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
13+
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
14+
list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
15+
find_package(nanobind CONFIG REQUIRED)
16+
17+
nanobind_add_module(pgeof2_ext NOMINSIZE STABLE_ABI LTO src/pgeof2_ext.cpp)
18+
19+
# for nanobind 2
20+
# nanobind_add_stub(
21+
# pgeof2_ext_stub
22+
# MODULE pgeof2_ext
23+
# OUTPUT pgeof2_ext.pyi
24+
# PYTHON_PATH $<TARGET_FILE_DIR:pgeof2_ext>
25+
# DEPENDS pgeof2_ext
26+
# )
27+
28+
# All lib are headeer only. it's facter to include like this than using exported targets
29+
# (i.e add_subdirectories(...))
30+
target_include_directories(pgeof2_ext PRIVATE "include" "third_party/eigen" "third_party/nanoflann/include" "third_party/taskflow")
31+
32+
install(TARGETS pgeof2_ext LIBRARY DESTINATION pgeof2)

MANIFEST.in

-1
This file was deleted.

README.md

+24-82
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
# Point Geometric Features
44

5-
![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black)
6-
[![python](https://img.shields.io/badge/-Python_3.8-blue?logo=python&logoColor=white)](https://github.com/pre-commit/pre-commit)
5+
[![python](https://img.shields.io/badge/-Python_3.9_%7C_3.10_%7C_3.11_%7C_3.12-blue?logo=python&logoColor=white)](#)
76
![C++](https://img.shields.io/badge/c++-%2300599C.svg?style=for-the-badge&logo=c%2B%2B&logoColor=white)
8-
[![license](https://img.shields.io/badge/License-MIT-green.svg?labelColor=gray)](https://github.com/ashleve/lightning-hydra-template#license)
7+
[![license](https://img.shields.io/badge/License-MIT-green.svg?labelColor=gray)](#)
98

109

1110
</div>
@@ -21,7 +20,7 @@ Python wrapper around C++ helper to compute, for each point in a 3D point cloud,
2120
- linearity
2221
- planarity
2322
- scattering
24-
- verticality
23+
- verticality (two formulations)
2524
- normal_x
2625
- normal_y
2726
- normal_z
@@ -32,109 +31,41 @@ Python wrapper around C++ helper to compute, for each point in a 3D point cloud,
3231
- optimal neighborhood size
3332
</details>
3433

34+
The wrapper allows to compute feature in multiple fashions (on the fly subset of features _a la_ jakteritics or an array of features, multiscale features). Moreover, it offers basic interfaces to compute fast K-NN or Radius search on point clouds.
35+
The overall code is not intended to be DRY nor generic, it aims at providing efficient as possible implementations for some limited scopes and usages.
3536

3637
## 🧱 Installation
3738

38-
Pgeof will __soon__ be available as pre compiled package on PyPI for both Linux and Windows OSes.
39-
4039
```bash
41-
python -m pip install pgeof
40+
python -m pip install pgeof2
4241
```
4342

4443
### building from sources
4544

46-
Pgeof depends on [Eigen library](https://eigen.tuxfamily.org/) and numpy headers at build time.
47-
The good version of numpy will be fetched from PyPI automatically by the build system but your are responsible for providing
48-
the path to the Eigen library you want to use (for example py using `CXXFLAGS` variable on Linux or setting `EIGEN_LIB_PATH`)
45+
Pgeof depends on [Eigen library](https://eigen.tuxfamily.org/), [Taskflow](https://github.com/taskflow/taskflow),
46+
[nanoflann](https://github.com/jlblancoc/nanoflann) and [nanobind](https://github.com/wjakob/nanobind).
4947

50-
```bash
51-
# clone project
52-
git clone https://github.com/drprojects/point_geometric_features.git
53-
cd point_geometric_features
5448

55-
# set the EIGEN_LIB_PATH if needed
56-
export EIGEN_LIB_PATH="path_to_eigen_root_dir"
57-
# build and install the package
58-
python -m pip install .
59-
```
49+
Pgeof adhere to [PEP 517](https://peps.python.org/pep-0517/) and use [scikit-build-core](https://github.com/scikit-build/scikit-build-core) as build backend. Build dependencies (nanobind, scikit-build-core...) are fetched at build time. C++ third party libraries are embedded as submodules.
6050

61-
### conda
62-
63-
The following will install the project in a new `pgeof` conda environment.
6451

6552
```bash
6653
# clone project
67-
git clone https://github.com/drprojects/point_geometric_features.git
54+
git clone --recurse-submodules https://github.com/drprojects/point_geometric_features.git
6855
cd point_geometric_features
69-
70-
# Installation in a new dedicated `pgeof` conda environment
71-
bash install.sh
56+
# build and install the package
57+
python -m pip install .
7258
```
7359

74-
You can easily adapt `install.sh` to install the project in an already-existing
75-
environment.
76-
7760
## 🚀 Using Point Geometric Features
7861

79-
The `pgeof` function should be used as follows:
80-
81-
```python
82-
from pgeof import pgeof
83-
84-
pgeof(
85-
xyz, # [n_points, 3] float32 2D array - 3D point coordinates
86-
nn, # [num_neighborhoods] uint32 1D array - Flattened neighbor indices. Make sure those are all positive, '-1' indices will either crash or silently compute incorrect features
87-
nn_ptr, # [n_points+1] uint32 1D array - Pointers wrt `nn`. More specifically, the neighbors of point `i` are `nn[nn_ptr[i]:nn_ptr[i + 1]]`
88-
k_min=1, # (optional, default=1) int - Minimum number of neighbors to consider for features computation. If a point has less, it will be given 0 features
89-
k_step=-1, # (optional, default=-1) int - Step size to take when searching for the optimal neighborhood size for each point, following: http://lareg.ensg.eu/labos/matis/pdf/articles_revues/2015/isprs_wjhm_15.pdf. If k_step < 1, pgeof will not search for the optimal neighborhood and features will be computed based on the all available neighbors for each point
90-
k_min_search=10, # (optional, default=10) int - Minimum neighborhood size at which to start when searching for the optimal neighborhood size for each point. It is advised to use a value of 10 or higher, for geometric features robustness
91-
verbose=False) # (optional, default=False) bool - Whether computation progress should be printed out
92-
93-
# Print details on how pgeof works and expected input parameters
94-
print(help(pgeof))
95-
```
96-
97-
👇 You may check out the provided `demo.py` script to get started.
98-
99-
```bash
100-
python demo.py
101-
```
62+
👇 You may check out the provided `test_pgeof.py` script to get started.
10263

10364
⚠️ Please note the **neighbors are expected in CSR format**. This allows
10465
expressing neighborhoods of varying sizes with dense arrays (eg the output of a
10566
radius search). Here are examples of how to easily convert typical k-NN or
10667
radius-NN neighborhoods to CSR format.
10768

108-
```python
109-
from sklearn.neighbors import NearestNeighbors
110-
import numpy as np
111-
112-
# Generate a random synthetic point cloud and k-nearest neighbors
113-
num_points = 10000
114-
k = 20
115-
xyz = np.random.rand(num_points, 3)
116-
kneigh = NearestNeighbors(n_neighbors=k).fit(xyz).kneighbors(xyz)
117-
118-
# Converting k-nearest neighbors to CSR format
119-
nn_ptr = np.arange(num_points + 1) * k
120-
nn = kneigh[1].flatten()
121-
```
122-
123-
```python
124-
from sklearn.neighbors import NearestNeighbors
125-
import numpy as np
126-
127-
# Generate a random synthetic point cloud and radius neighbors
128-
num_points = 10000
129-
radius = 0.1
130-
xyz = np.random.rand(num_points, 3)
131-
rneigh = NearestNeighbors(radius=radius).fit(xyz).radius_neighbors(xyz)
132-
133-
# Converting radius neighbors to CSR format
134-
nn_ptr = np.r_[0, np.array([x.shape[0] for x in rneigh[1]]).cumsum()]
135-
nn = np.concatenate(rneigh[1])
136-
```
137-
13869

13970
## 💳 Credits
14071
This implementation was largely inspired from [Superpoint Graph](https://github.com/loicland/superpoint_graph). The main modifications here allow:
@@ -143,6 +74,17 @@ This implementation was largely inspired from [Superpoint Graph](https://github.
14374
- optimal neighborhood search from this [paper](http://lareg.ensg.eu/labos/matis/pdf/articles_revues/2015/isprs_wjhm_15.pdf)
14475
- some corrections on geometric features computation
14576

77+
Some heavy refactoring (port to nanobind, test, benchmarks), packaging, speed optimization, feature addition (NN search, on the fly feature computation...) were funded by:
78+
79+
Centre of Wildfire Research of Swansea University (UK) in collaboration with the Research Institute of Biodiversity (CSIC, Spain) and the Department of Mining Exploitation of the University of Oviedo (Spain).
80+
81+
Funding provided by the UK NERC project (NE/T001194/1):
82+
83+
'Advancing 3D Fuel Mapping for Wildfire Behaviour and Risk Mitigation Modelling'
84+
85+
and by the Spanish Knowledge Generation project (PID2021-126790NB-I00):
86+
87+
‘Advancing carbon emission estimations from wildfires applying artificial intelligence to 3D terrestrial point clouds’.
14688

14789
## License
14890

0 commit comments

Comments
 (0)