├── .bumpversion.cfg
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .readthedocs.yml
├── HISTORY.rst
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
├── Makefile
├── conf.py
├── img
│ └── logo.png
├── index.rst
├── installation.rst
├── make.bat
├── reference.rst
├── requirements.txt
└── usage.rst
├── jakteristics
├── __about__.py
├── __init__.py
├── __main__.py
├── ckdtree
│ ├── README.rst
│ ├── __init__.py
│ ├── _lib
│ │ └── _c99compat.h
│ ├── ckdtree.pxd
│ ├── ckdtree.pyx
│ └── ckdtree
│ │ └── src
│ │ ├── build.cxx
│ │ ├── ckdtree_decl.h
│ │ ├── coo_entries.h
│ │ ├── count_neighbors.cxx
│ │ ├── distance.h
│ │ ├── distance_base.h
│ │ ├── ordered_pair.h
│ │ ├── partial_sort.h
│ │ ├── query.cxx
│ │ ├── query_ball_point.cxx
│ │ ├── query_ball_tree.cxx
│ │ ├── query_pairs.cxx
│ │ ├── rectangle.h
│ │ └── sparse_distances.cxx
├── constants.py
├── extension.pyx
├── las_utils.py
├── main.py
├── utils.pxd
└── utils.pyx
├── pyproject.toml
├── requirements-dev.txt
├── requirements.txt
├── setup.py
└── tests
├── conftest.py
├── data
└── test_0.02_seconde.las
├── test_cli.py
├── test_features.py
└── test_las_utils.py
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.6.2
3 | commit = True
4 | tag = True
5 | tag_name = {new_version}
6 |
7 | [bumpversion:file:jakteristics/__about__.py]
8 |
9 | [bumpversion:file:HISTORY.rst]
10 | search = History
11 | -------
12 |
13 | Unreleased
14 | ----------
15 | replace = History
16 | -------
17 |
18 | Unreleased
19 | ----------
20 |
21 |
22 | {new_version} ({now:%Y-%m-%d})
23 | ------------------
24 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: ["*"]
6 | tags: ["*"]
7 | pull_request:
8 | branches: ["*"]
9 |
10 | jobs:
11 | build_and_push:
12 | name: Build for ${{ matrix.os }}
13 | runs-on: ${{ matrix.os }}
14 | strategy:
15 | matrix:
16 | os: [ubuntu-22.04, windows-2022]
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 |
21 | - uses: actions/setup-python@v5
22 |
23 | - name: Install cibuildwheel
24 | run: python -m pip install --user cibuildwheel==2.18.1
25 |
26 | - name: Build wheels
27 | run: python -m cibuildwheel --output-dir wheelhouse
28 | env:
29 | CIBW_BEFORE_TEST: pip install -r requirements-dev.txt
30 | CIBW_TEST_COMMAND: pytest {project}/tests
31 | CIBW_BUILD: cp3{8,9,10,11,12}-{manylinux_x86_64,win_amd64}
32 |
33 | - uses: actions/upload-artifact@v4
34 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
35 | with:
36 | name: wheels-${{ runner.os }}
37 | path: wheelhouse/*.whl
38 |
39 | publish:
40 | name: Publish to PyPI
41 | needs: build_and_push
42 | runs-on: ubuntu-20.04
43 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
44 | steps:
45 | - uses: actions/checkout@v4
46 |
47 | - uses: actions/setup-python@v5
48 |
49 | - uses: actions/download-artifact@v4
50 | with:
51 | path: wheelhouse
52 | merge-multiple: true
53 |
54 | - name: Publish to PyPI
55 | env:
56 | TWINE_USERNAME: __token__
57 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
58 | run: |
59 | pip install -r requirements.txt
60 | pip install twine
61 | python setup.py sdist
62 | twine upload dist/*
63 | twine upload wheelhouse/*.whl
64 |
65 | - name: Create GitHub Release
66 | uses: softprops/action-gh-release@v2
67 | with:
68 | files: |
69 | dist/*
70 | wheelhouse/*.whl
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /data
2 | run_main.py
3 | jakteristics/**/*.html
4 | jakteristics/**/*.c
5 | jakteristics/**/*.cpp
6 |
7 | .eggs
8 |
9 | # Byte-compiled / optimized / DLL files
10 | __pycache__/
11 | *.py[cod]
12 | *$py.class
13 | nilearn_cache/
14 | .pytest_cache/
15 | .vscode/
16 | .idea/
17 |
18 | # C extensions
19 | *.so
20 |
21 | # Packages
22 | *.egg
23 | *.egg-info
24 | dist
25 | build
26 | eggs
27 | parts
28 | bin
29 | var
30 | sdist
31 | develop-eggs
32 | .installed.cfg
33 | lib
34 | lib64
35 | .cache
36 |
37 | # Installer logs
38 | pip-log.txt
39 |
40 | # Unit test / coverage reports
41 | .coverage
42 | .tox
43 | nosetests.xml
44 | htmlcov
45 |
46 | # Translations
47 | *.mo
48 |
49 | # Mr Developer
50 | .mr.developer.cfg
51 | .project
52 | .pydevproject
53 |
54 | # Complexity
55 | output/*.html
56 | output/*/index.html
57 |
58 | # Sphinx
59 | docs/_build
60 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: docs/conf.py
11 |
12 | # Optionally build your docs in additional formats such as PDF and ePub
13 | formats: all
14 |
15 | # Optionally set the version of Python and requirements required to build your docs
16 | python:
17 | version: 3.7
18 | install:
19 | - requirements: docs/requirements.txt
20 | - requirements: requirements.txt
21 | - method: pip
22 | path: .
23 |
--------------------------------------------------------------------------------
/HISTORY.rst:
--------------------------------------------------------------------------------
1 | .. :changelog:
2 |
3 | History
4 | -------
5 |
6 | Unreleased
7 | ----------
8 |
9 |
10 | 0.6.2 (2024-07-22)
11 | ------------------
12 |
13 |
14 | 0.6.1 (2024-06-04)
15 | ------------------
16 |
17 |
18 | 0.6.0 (2023-04-20)
19 | ------------------
20 |
21 | * add: number_of_neighbors feature
22 | * add: eigenvalues and eigenvectors features
23 |
24 |
25 | 0.5.1 (2023-04-11)
26 | ------------------
27 |
28 | * fix: computing features when kdtree is not built from the same points for which we want to compute the features
29 | * drop python 3.6, add wheels for python 3.7-3.11 on linux and windows
30 |
31 | 0.5.0 (2022-01-26)
32 | ------------------
33 |
34 | * fix: compatibility with latest laspy version (>= 2.1.1, (2.1.0 has a bug))
35 |
36 |
37 | 0.4.3 (2020-09-24)
38 | ------------------
39 |
40 | * the default value when features can't be computed should be NaN
41 |
42 |
43 | 0.4.2 (2020-04-20)
44 | ------------------
45 |
46 | * fix extension import statement
47 |
48 |
49 | 0.4.1 (2020-04-17)
50 | ------------------
51 |
52 | * fix: create parent directories for output file
53 | * fix: rename --num_threads to --num-threads
54 | * fix: require laspy 1.7 for upper case names in extra dimensions
55 |
56 |
57 | 0.4.0 (2020-04-16)
58 | ------------------
59 |
60 | * first pypi release
61 | * add github actions
62 |
63 |
64 | 0.3.0 (2020-04-14)
65 | ------------------
66 |
67 | * add feature-names parameter to compute specific features
68 |
69 |
70 | 0.2.0 (2020-04-10)
71 | ------------------
72 |
73 | * fix windows compilation with openmp
74 | * add example cloudcompare script
75 | * add num_threads cli parameter and help documentation
76 | * write extra dimensions in the correct order
77 |
78 |
79 | 0.1.2 (2020-04-10)
80 | ------------------
81 |
82 | * Fix tests
83 |
84 |
85 | 0.1.1 (2020-04-10)
86 | ------------------
87 |
88 | * Fix bug where single precision was used for intermediate variables
89 |
90 |
91 | 0.1.0 (2020-04-10)
92 | ------------------
93 |
94 | * First release
95 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include pyproject.toml
2 | include HISTORY.rst
3 | include README.rst
4 | include requirements.txt
5 | recursive-include jakteristics/ckdtree *
6 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: help build install install-dev test clean
2 |
3 | .DEFAULT: help
4 | help:
5 | @echo "make build"
6 | @echo " build extensions in place"
7 | @echo "make install"
8 | @echo " install in the current python environment"
9 | @echo "make install-dev"
10 | @echo " install in development mode with dev dependencies"
11 | @echo "make test"
12 | @echo " run tests"
13 | @echo "make clean"
14 | @echo " remove build artifacts and __pycache__"
15 |
16 | build:
17 | python setup.py build_ext -i
18 |
19 | install:
20 | pip install .
21 |
22 | install-dev:
23 | pip install -r requirements-dev.txt
24 | pip install -e .
25 |
26 | test:
27 | python setup.py pytest
28 |
29 | clean:
30 | rm -rf ./**/__pycache__
31 | rm -rf build
32 | rm -rf *.egg-info
33 | find . -wholename "./jakteristics/*.cpp" -type f -delete
34 | find . -wholename "./jakteristics/*.c" -type f -delete
35 | find . -wholename "./jakteristics/*.html" -type f -delete
36 | find . -wholename "./jakteristics/*.so" -type f -delete
37 | find . -wholename "./jakteristics/*.pyd" -type f -delete
38 |
39 | tag:
40 | @if [ -z $(tag) ]; then\
41 | echo "Please provide the 'tag' variable.";\
42 | else\
43 | git tag -a $(tag) -m "$(tag)";\
44 | git push --delete upstream latest;\
45 | git tag -d latest;\
46 | git tag -a -m "latest" latest;\
47 | fi
48 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 |
2 | Jakteristics
3 | ~~~~~~~~~~~~
4 |
5 | * **Documentation**: https://jakteristics.readthedocs.io
6 | * **Github**: https://github.com/jakarto3d/jakteristics
7 |
8 | Jakteristics is a python package to compute point cloud geometric features.
9 |
10 | A **geometric feature** is a description of the geometric shape around a point based on its
11 | neighborhood. For example, a point located on a wall will have a high *planarity*.
12 |
13 | The features used in this package are described in the paper
14 | `Contour detection in unstructured 3D point clouds`_.
15 | They are computed based on the eigenvalues and eigenvectors:
16 |
17 | * Eigenvalue sum
18 | * Omnivariance
19 | * Eigenentropy
20 | * Anisotropy
21 | * Planarity
22 | * Linearity
23 | * PCA1
24 | * PCA2
25 | * Surface Variation
26 | * Sphericity
27 | * Verticality
28 | * Nx, Ny, Nz (The normal vector)
29 |
30 | It's inspired from a similar tool in `CloudCompare `_.
31 |
32 | It's implemented in cython using the BLAS and LAPACK scipy wrappers. It can use multiple cpus,
33 | and the performance is quite good (at least twice as fast as CloudCompare).
34 |
35 | .. _`Contour detection in unstructured 3D point clouds`: https://ethz.ch/content/dam/ethz/special-interest/baug/igp/photogrammetry-remote-sensing-dam/documents/pdf/timo-jan-cvpr2016.pdf
36 |
37 |
38 | Installation
39 | ============
40 |
41 | .. code:: bash
42 |
43 | python -m pip install jakteristics
44 |
45 |
46 | Usage
47 | =====
48 |
49 | Refer to the `documentation `_ for more details.
50 |
51 |
52 | From python
53 | -----------
54 |
55 | .. code:: python
56 |
57 | from jakteristics import compute_features
58 |
59 | features = compute_features(xyz, search_radius=0.15, feature_names=['planarity', 'linearity'])
60 |
61 |
62 | CLI
63 | ---
64 |
65 | Once the package is installed, you can use the `jakteristics` command:
66 |
67 | .. code:: bash
68 |
69 | jakteristics input/las/file.las output/file.las --search-radius 0.15 --num-threads 4
70 |
71 |
72 | Run tests
73 | =========
74 |
75 | .. code:: bash
76 |
77 | python -m pip install -r requirements-dev.txt
78 | python setup.py pytest
79 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 |
16 | try:
17 | import jakteristics
18 | except ImportError:
19 | # if package is not installed, add the parent path
20 | sys.path.insert(0, os.path.abspath(".."))
21 |
22 |
23 | # -- Project information -----------------------------------------------------
24 |
25 | project = "jakteristics"
26 | copyright = "2020, David Caron"
27 | author = "David Caron"
28 |
29 |
30 | # -- General configuration ---------------------------------------------------
31 |
32 | master_doc = "index"
33 |
34 | # Add any Sphinx extension module names here, as strings. They can be
35 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
36 | # ones.
37 | extensions = [
38 | "sphinx.ext.autodoc",
39 | "sphinx.ext.napoleon",
40 | "sphinx_autodoc_typehints",
41 | "sphinx_rtd_theme",
42 | "sphinx_click.ext",
43 | ]
44 |
45 | # Add any paths that contain templates here, relative to this directory.
46 | templates_path = ["_templates"]
47 |
48 | # List of patterns, relative to source directory, that match files and
49 | # directories to ignore when looking for source files.
50 | # This pattern also affects html_static_path and html_extra_path.
51 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
52 |
53 | autodoc_default_options = {
54 | "undoc-members": True,
55 | }
56 |
57 | # -- Options for HTML output -------------------------------------------------
58 |
59 | # The theme to use for HTML and HTML Help pages. See the documentation for
60 | # a list of builtin themes.
61 | #
62 | html_theme = "sphinx_rtd_theme"
63 | html_logo = "img/logo.png"
64 |
65 | # Add any paths that contain custom static files (such as style sheets) here,
66 | # relative to this directory. They are copied after the builtin static files,
67 | # so a file named "default.css" will overwrite the builtin "default.css".
68 | html_static_path = ["_static"]
69 |
--------------------------------------------------------------------------------
/docs/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakarto3d/jakteristics/6a22e0072a42cf4b41a04a6e214fd7ef3df5ec67/docs/img/logo.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Jakteristics
2 | ============
3 |
4 | Jakteristics is a python package to compute point cloud geometric features.
5 |
6 | A **geometric feature** is a description of the geometric shape around a point based on its
7 | neighborhood. For example, a point located on a wall will have a high *planarity*.
8 |
9 | The features used in this package are described in the paper
10 | `Contour detection in unstructured 3D point clouds`_.
11 | They are based on the eigenvalues *λ1*, *λ2* and *λ3* and the eigenvectors *e1*, *e2* and *e3*.
12 |
13 |
14 | * Eigenvalue sum : :math:`λ1 + λ2 + λ3`
15 | * Omnivariance: :math:`(λ1 \cdot λ2 \cdot λ3) ^ {1 / 3}`
16 | * Eigenentropy: :math:`-∑_{i=1}^3 λi \cdot \ln(λi)`
17 | * Anisotropy: :math:`(λ1 − λ3)/λ1`
18 | * Planarity: :math:`(λ2−λ3)/λ1`
19 | * Linearity: :math:`(λ1−λ2)/λ1`
20 | * PCA1: :math:`λ1/(λ1 + λ2 + λ3)`
21 | * PCA2: :math:`λ2/(λ1 + λ2 + λ3)`
22 | * Surface Variation: :math:`λ3/(λ1+λ2+λ3)`
23 | * Sphericity: :math:`λ3/λ1`
24 | * Verticality: :math:`1-|e3[2]|`
25 | * Nx, Ny, Nz: The normal vector
26 |
27 | .. _`Contour detection in unstructured 3D point clouds`: https://ethz.ch/content/dam/ethz/special-interest/baug/igp/photogrammetry-remote-sensing-dam/documents/pdf/timo-jan-cvpr2016.pdf
28 |
29 | .. toctree::
30 | :maxdepth: 2
31 |
32 | installation.rst
33 | usage.rst
34 | reference.rst
35 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 |
5 | Pypi
6 | ----
7 |
8 | To install from pypi, run::
9 |
10 | pip install jakteristics
11 |
12 |
13 | Building
14 | --------
15 |
16 | To build the package from source, you need to have a compiler to build the cython extensions.
17 |
18 | On Windows, you need `Microsoft C++ Build Tools `_
19 |
20 | Then, run::
21 |
22 | python -m pip install .
23 |
24 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/reference.rst:
--------------------------------------------------------------------------------
1 | Reference
2 | =========
3 |
4 | jakteristics
5 | ------------
6 |
7 | .. autofunction:: jakteristics.compute_features
8 |
9 | jakteristics.las_utils
10 | ----------------------
11 |
12 | .. autofunction:: jakteristics.las_utils.read_las_xyz
13 |
14 | .. autofunction:: jakteristics.las_utils.write_with_extra_dims
15 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx
2 | sphinx-autodoc-typehints
3 | sphinx-rtd-theme
4 | sphinx-click
5 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | =====
3 |
4 | From python
5 | -----------
6 |
7 | If your input is a las file, you can use the
8 | :py:func:`jakteristics.las_utils.read_las_xyz` function to read the xyz coordinates
9 | from your input file as a numpy array.
10 |
11 | If your input is already a numpy array, you can use the :py:func:`jakteristics.compute_features`
12 | directly. By default, every geometry feature will be computed. In a future version,
13 | the :py:arg:`feature_names` argument will be required.
14 |
15 | Once you have the features, you can use them like any numpy array, or write them
16 | to a las file using :py:func:`jakteristics.write_with_extra_dims`.
17 |
18 | Example:
19 |
20 | .. code-block:: python
21 |
22 | from jakteristics import las_utils, compute_features, FEATURE_NAMES
23 |
24 | input_path = "/path/to/a/las_file.las"
25 | xyz = las_utils.read_las_xyz(input_path)
26 |
27 | features = compute_features(xyz, search_radius=0.15, feature_names=FEATURE_NAMES)
28 |
29 | output_path = "/path/to/output_file.las"
30 | las_utils.write_with_extra_dims(input_path, output_path, features, FEATURE_NAMES)
31 |
32 | # or for a specific feature:
33 | omnivariance = compute_features(xyz, search_radius=0.15, feature_names=["omnivariance"])
34 | output_omnivariance = "/path/to/output_omnivariance.las"
35 | las_utils.write_with_extra_dims(input_path, output_omnivariance, omnivariance, ["omnivariance"])
36 |
37 |
38 | If you want to to all of these steps (read a las file, compute certain features
39 | and write it back to disk), you can use the command line:
40 |
41 |
42 | .. code:: bash
43 |
44 | jakteristics input/las/file.las output/file.las --search-radius 0.15 --num-threads 4
45 |
46 |
47 | CLI
48 | ---
49 |
50 | .. click:: jakteristics.__main__:click_command
51 | :prog: jakteristics
52 |
--------------------------------------------------------------------------------
/jakteristics/__about__.py:
--------------------------------------------------------------------------------
1 | __author__ = "David Caron"
2 | __email__ = "david.caron@jakarto.com"
3 | __version__ = "0.6.2"
4 |
--------------------------------------------------------------------------------
/jakteristics/__init__.py:
--------------------------------------------------------------------------------
1 | from .__about__ import __author__, __email__, __version__
2 |
3 | from .main import compute_features
4 | from .constants import FEATURE_NAMES
5 | from .ckdtree.ckdtree import cKDTree
6 |
--------------------------------------------------------------------------------
/jakteristics/__main__.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from pathlib import Path
3 | from time import perf_counter
4 | from typing import List
5 |
6 | import typer
7 | from typer import Option, Argument
8 |
9 | from . import extension, las_utils
10 | from .constants import FEATURE_NAMES
11 |
12 |
13 | app = typer.Typer()
14 |
15 |
16 | def show_features_callback(value):
17 | if value:
18 | for name in FEATURE_NAMES:
19 | typer.echo(name)
20 | raise typer.Exit()
21 |
22 |
23 | @app.command()
24 | def typer_main(
25 | las_input: Path = Argument(...),
26 | output: Path = Argument(...),
27 | search_radius: float = Option(
28 | ...,
29 | "--search-radius",
30 | "-s",
31 | help="The search radius to use to query neighbors.",
32 | ),
33 | num_threads: int = Option(
34 | -1,
35 | "--num-threads",
36 | "-t",
37 | help=(
38 | "The number of threads to use for computation. "
39 | "Defaults to the number of cpu on the machine."
40 | ),
41 | ),
42 | feature_names: List[str] = Option(
43 | FEATURE_NAMES,
44 | "--feature",
45 | "-f",
46 | help=(
47 | "The feature names to compute. Repeat this parameter "
48 | "to compute multiple features. "
49 | "Use --show-features to see the list of possible choices. "
50 | "Default: All features."
51 | ),
52 | ),
53 | manhattan_distance: bool = Option(
54 | False,
55 | "--manhattan-distance",
56 | "-m",
57 | is_flag=True,
58 | help=(
59 | "How to compute the distance between 2 points. "
60 | "If provided, the sum-of-absolute-values is used ('Manhattan' distance)."
61 | "By default, the standard Euclidean distance is used. "
62 | ),
63 | ),
64 | eps: float = Option(
65 | 0,
66 | "--eps",
67 | "-e",
68 | show_default=True,
69 | help=(
70 | "Return approximate nearest neighbors; the k-th returned value "
71 | "is guaranteed to be no further than (1+eps) times the "
72 | "distance to the real k-th nearest neighbor."
73 | ),
74 | ),
75 | show_features: bool = typer.Option(
76 | None,
77 | "--show-features",
78 | help="Show a list of possible feature names and exit.",
79 | callback=show_features_callback,
80 | is_eager=True,
81 | ),
82 | ):
83 | t = perf_counter()
84 | typer.echo("Computing geometric features...")
85 |
86 | xyz = las_utils.read_las_xyz(las_input)
87 | feature_names_str = []
88 | for name in feature_names:
89 | if name not in FEATURE_NAMES:
90 | choices = ", ".join(FEATURE_NAMES)
91 | raise typer.BadParameter(f"invalid choice: {name}. (choose from {choices})")
92 | feature_names_str.append(name)
93 |
94 | output.parent.mkdir(parents=True, exist_ok=True)
95 |
96 | features = extension.compute_features(
97 | xyz,
98 | search_radius,
99 | num_threads=num_threads,
100 | euclidean_distance=not manhattan_distance,
101 | eps=eps,
102 | feature_names=feature_names_str,
103 | )
104 |
105 | las_utils.write_with_extra_dims(las_input, output, features, feature_names_str)
106 | typer.echo(f"Done in {perf_counter() - t:0.2f} seconds.")
107 |
108 |
109 | # used for documentation
110 | click_command = typer.main.get_command(app)
111 |
112 | if __name__ == "__main__": # pragma: no cover
113 | app()
114 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/README.rst:
--------------------------------------------------------------------------------
1 |
2 | Vendorized from scipy at [scipy.spatial.cKDTree](https://github.com/scipy/scipy/tree/master/scipy/spatial)
3 |
4 | We had to do this, because there is no ckdtree.pxd file in scipy.
5 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | =============================================================
3 | Spatial algorithms and data structures (:mod:`scipy.spatial`)
4 | =============================================================
5 |
6 | .. currentmodule:: scipy.spatial
7 |
8 | Spatial transformations
9 | =======================
10 |
11 | These are contained in the `scipy.spatial.transform` submodule.
12 |
13 | Nearest-neighbor queries
14 | ========================
15 | .. autosummary::
16 | :toctree: generated/
17 |
18 | KDTree -- class for efficient nearest-neighbor queries
19 | cKDTree -- class for efficient nearest-neighbor queries (faster implementation)
20 | Rectangle
21 |
22 | Distance metrics are contained in the :mod:`scipy.spatial.distance` submodule.
23 |
24 | Delaunay triangulation, convex hulls, and Voronoi diagrams
25 | ==========================================================
26 |
27 | .. autosummary::
28 | :toctree: generated/
29 |
30 | Delaunay -- compute Delaunay triangulation of input points
31 | ConvexHull -- compute a convex hull for input points
32 | Voronoi -- compute a Voronoi diagram hull from input points
33 | SphericalVoronoi -- compute a Voronoi diagram from input points on the surface of a sphere
34 | HalfspaceIntersection -- compute the intersection points of input halfspaces
35 |
36 | Plotting helpers
37 | ================
38 |
39 | .. autosummary::
40 | :toctree: generated/
41 |
42 | delaunay_plot_2d -- plot 2-D triangulation
43 | convex_hull_plot_2d -- plot 2-D convex hull
44 | voronoi_plot_2d -- plot 2-D Voronoi diagram
45 |
46 | .. seealso:: :ref:`Tutorial `
47 |
48 |
49 | Simplex representation
50 | ======================
51 | The simplices (triangles, tetrahedra, etc.) appearing in the Delaunay
52 | tessellation (N-D simplices), convex hull facets, and Voronoi ridges
53 | (N-1-D simplices) are represented in the following scheme::
54 |
55 | tess = Delaunay(points)
56 | hull = ConvexHull(points)
57 | voro = Voronoi(points)
58 |
59 | # coordinates of the jth vertex of the ith simplex
60 | tess.points[tess.simplices[i, j], :] # tessellation element
61 | hull.points[hull.simplices[i, j], :] # convex hull facet
62 | voro.vertices[voro.ridge_vertices[i, j], :] # ridge between Voronoi cells
63 |
64 | For Delaunay triangulations and convex hulls, the neighborhood
65 | structure of the simplices satisfies the condition:
66 | ``tess.neighbors[i,j]`` is the neighboring simplex of the ith
67 | simplex, opposite to the ``j``-vertex. It is -1 in case of no neighbor.
68 |
69 | Convex hull facets also define a hyperplane equation::
70 |
71 | (hull.equations[i,:-1] * coord).sum() + hull.equations[i,-1] == 0
72 |
73 | Similar hyperplane equations for the Delaunay triangulation correspond
74 | to the convex hull facets on the corresponding N+1-D
75 | paraboloid.
76 |
77 | The Delaunay triangulation objects offer a method for locating the
78 | simplex containing a given point, and barycentric coordinate
79 | computations.
80 |
81 | Functions
82 | ---------
83 |
84 | .. autosummary::
85 | :toctree: generated/
86 |
87 | tsearch
88 | distance_matrix
89 | minkowski_distance
90 | minkowski_distance_p
91 | procrustes
92 | geometric_slerp
93 |
94 | """
95 |
96 | # from .kdtree import *
97 | # from .ckdtree import cKDTree
98 | # from .qhull import *
99 | # from ._spherical_voronoi import SphericalVoronoi
100 | # from ._plotutils import *
101 | # from ._procrustes import procrustes
102 | # from ._geometric_slerp import geometric_slerp
103 |
104 | # __all__ = [s for s in dir() if not s.startswith('_')]
105 | # __all__ += ['distance', 'transform']
106 |
107 | # from . import distance, transform
108 |
109 | # from scipy._lib._testutils import PytestTester
110 | # test = PytestTester(__name__)
111 | # del PytestTester
112 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/_lib/_c99compat.h:
--------------------------------------------------------------------------------
1 | /*
2 | C99 compatibility for SciPy
3 |
4 | The Rules:
5 | - Every function definition must have compiler guard so that it is not defined twice.
6 | - This file should only affect compilers that do not support C99 natively.
7 | - All functions should be defined as "static" to limit linker conflicts.
8 | */
9 | #ifndef SCIPY_C99_COMPAT
10 | #define SCIPY_C99_COMPAT
11 | #include
12 |
13 | #if defined(_MSC_VER) && _MSC_VER <= 1600
14 | /* MSVC 2008 and MSVC 2010 */
15 | #ifndef isnan
16 | #define isnan(x) _isnan((x))
17 | #endif
18 |
19 | #ifndef signbit
20 | static int signbit(double x)
21 | {
22 | return x > 0;
23 | }
24 | #endif
25 |
26 | #ifndef copysign
27 | static double copysign(double x, double y)
28 | {
29 | if (x >= 0) {
30 | if (y >= 0) {
31 | return x;
32 | }
33 | else {
34 | return -x;
35 | }
36 | }
37 | else {
38 | if (y >= 0) {
39 | return -x;
40 | }
41 | else {
42 | return x;
43 | }
44 | }
45 | }
46 | #endif
47 |
48 | #ifndef fmax
49 | static double fmax(double x, double y)
50 | {
51 | /* z > nan for z != nan is required by C the standard */
52 | int xnan = isnan(x), ynan = isnan(y);
53 |
54 | if (xnan || ynan)
55 | {
56 | if (xnan && !ynan)
57 | return y;
58 | if (!xnan && ynan)
59 | return x;
60 | return x;
61 | }
62 |
63 | /* +0 > -0 is preferred by C the standard */
64 | if (x == 0 && y == 0)
65 | {
66 | int xs = signbit(x), ys = signbit(y);
67 | if (xs && !ys)
68 | return y;
69 | if (!xs && ys)
70 | return x;
71 | return x;
72 | }
73 |
74 | if (x > y) {
75 | return x;
76 | }
77 | else {
78 | return y;
79 | }
80 | }
81 | #endif
82 |
83 | #ifndef fmin
84 | static double fmin(double x, double y)
85 | {
86 | /* z > nan for z != nan is required by C the standard */
87 | int xnan = isnan(x), ynan = isnan(y);
88 |
89 | if (xnan || ynan)
90 | {
91 | if (xnan && !ynan)
92 | return y;
93 | if (!xnan && ynan)
94 | return x;
95 | return x;
96 | }
97 |
98 | /* +0 > -0 is preferred by C the standard */
99 | if (x == 0 && y == 0)
100 | {
101 | int xs = signbit(x), ys = signbit(y);
102 | if (xs && !ys)
103 | return x;
104 | if (!xs && ys)
105 | return y;
106 | return y;
107 | }
108 |
109 | if (x > y) {
110 | return y;
111 | }
112 | else {
113 | return x;
114 | }
115 | }
116 | #endif
117 | #endif
118 |
119 | #if (__STDC_VERSION__ < 199901L)
120 | /* Hopefully fail in fewer cases */
121 |
122 | /* For compilers which aren't MSVC and haven't defined isnan */
123 | #ifndef isnan
124 | #define isnan(x) ((x) != (x))
125 | #endif
126 |
127 | #ifndef isfinite
128 | #ifdef _MSC_VER
129 | /* MSVC 2015 and newer still don't have everything */
130 | #define isfinite(x) _finite((x))
131 | #else
132 | #define isfinite(x) !isnan((x) + (-x))
133 | #endif
134 | #endif
135 |
136 | #ifndef isinf
137 | #define isinf(x) (!isfinite(x) && !isnan(x))
138 | #endif
139 |
140 | #ifndef fma
141 | #define fma(x, y, z) ((x)*(y) + (z))
142 | #endif
143 | #endif
144 |
145 | /*
146 | * portable isnan/isinf; in cmath only available in C++11 and npy_math
147 | * versions don't work well (they get undef'd by cmath, see gh-5689)
148 | * Implementation based on npy_math.h
149 | */
150 | #ifndef sc_isnan
151 | #define sc_isnan isnan
152 | #endif
153 | #ifndef sc_isinf
154 | #define sc_isinf isinf
155 | #endif
156 | #ifndef sc_isfinite
157 | #define sc_isfinite isfinite
158 | #endif
159 | #ifndef sc_fma
160 | #define sc_fma fma
161 | #endif
162 | #endif
163 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree.pxd:
--------------------------------------------------------------------------------
1 |
2 | from libcpp.vector cimport vector
3 | cimport numpy as np
4 |
5 | cdef extern from "ckdtree_decl.h":
6 | struct ckdtreenode:
7 | np.intp_t split_dim
8 | np.intp_t children
9 | np.float64_t split
10 | np.intp_t start_idx
11 | np.intp_t end_idx
12 | ckdtreenode *less
13 | ckdtreenode *greater
14 | np.intp_t _less
15 | np.intp_t _greater
16 |
17 | struct ckdtree:
18 | vector[ckdtreenode] *tree_buffer
19 | ckdtreenode *ctree
20 | np.float64_t *raw_data
21 | np.intp_t n
22 | np.intp_t m
23 | np.intp_t leafsize
24 | np.float64_t *raw_maxes
25 | np.float64_t *raw_mins
26 | np.intp_t *raw_indices
27 | np.float64_t *raw_boxsize_data
28 | np.intp_t size
29 |
30 | int query_ball_point(const ckdtree *tree,
31 | const np.float64_t *x,
32 | const np.float64_t *r,
33 | const np.float64_t p,
34 | const np.float64_t eps,
35 | const np.intp_t n_queries,
36 | vector[np.intp_t] **results,
37 | const int return_length) nogil except +
38 |
39 | cdef class cKDTreeNode:
40 | cdef:
41 | readonly np.intp_t level
42 | readonly np.intp_t split_dim
43 | readonly np.intp_t children
44 | readonly np.float64_t split
45 | ckdtreenode *_node
46 | np.ndarray _data
47 | np.ndarray _indices
48 | cdef void _setup(cKDTreeNode self)
49 |
50 | cdef class cKDTree:
51 | cdef:
52 | ckdtree * cself
53 | readonly cKDTreeNode tree
54 | readonly np.ndarray data
55 | readonly np.ndarray maxes
56 | readonly np.ndarray mins
57 | readonly np.ndarray indices
58 | readonly object boxsize
59 | np.ndarray boxsize_data
60 |
61 | cdef _pre_init(cKDTree self)
62 | cdef _post_init(cKDTree self)
63 | cdef _post_init_traverse(cKDTree self, ckdtreenode *node)
64 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/build.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "ordered_pair.h"
15 | #include "partial_sort.h"
16 |
17 | #define tree_buffer_root(buf) (&(buf)[0][0])
18 |
19 | static ckdtree_intp_t
20 | build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx,
21 | double *maxes, double *mins,
22 | const int _median, const int _compact)
23 | {
24 |
25 | const ckdtree_intp_t m = self->m;
26 | const double *data = self->raw_data;
27 | ckdtree_intp_t *indices = (intptr_t *)(self->raw_indices);
28 |
29 | ckdtreenode new_node, *n, *root;
30 | ckdtree_intp_t node_index, _less, _greater;
31 | ckdtree_intp_t i, j, p, q, d;
32 | double size, split, minval, maxval;
33 |
34 | /* put a new node into the node stack */
35 | self->tree_buffer->push_back(new_node);
36 | node_index = self->tree_buffer->size() - 1;
37 | root = tree_buffer_root(self->tree_buffer);
38 | n = root + node_index;
39 | memset(n, 0, sizeof(n[0]));
40 |
41 | n->start_idx = start_idx;
42 | n->end_idx = end_idx;
43 | n->children = end_idx - start_idx;
44 |
45 | if (end_idx-start_idx <= self->leafsize) {
46 | /* below brute force limit, return leafnode */
47 | n->split_dim = -1;
48 | return node_index;
49 | }
50 | else {
51 |
52 | if (CKDTREE_LIKELY(_compact)) {
53 | /* Recompute hyperrectangle bounds. This should lead to a more
54 | * compact kd-tree but comes at the expense of larger construction
55 | * time. However, construction time is usually dwarfed by the
56 | * query time by orders of magnitude.
57 | */
58 | const double *tmp_data_point;
59 | tmp_data_point = data + indices[start_idx] * m;
60 | for(i=0; i tmp ? maxes[i] : tmp;
69 | mins[i] = mins[i] < tmp ? mins[i] : tmp;
70 | }
71 | }
72 | }
73 |
74 | /* split on the dimension with largest spread */
75 | d = 0;
76 | size = 0;
77 | for (i=0; i size) {
79 | d = i;
80 | size = maxes[i] - mins[i];
81 | }
82 | }
83 | maxval = maxes[d];
84 | minval = mins[d];
85 | if (maxval == minval) {
86 | /* all points are identical; warn user?
87 | * return leafnode
88 | */
89 | n->split_dim = -1;
90 | return node_index;
91 | }
92 |
93 | /* construct new inner node */
94 |
95 | if (CKDTREE_LIKELY(_median)) {
96 | /* split on median to create a balanced tree
97 | * adopted from scikit-learn
98 | */
99 | i = (end_idx - start_idx) / 2;
100 | partition_node_indices(data, indices + start_idx, d, i, m,
101 | end_idx - start_idx);
102 | p = start_idx + i;
103 | split = data[indices[p]*m+d];
104 | }
105 | else {
106 | /* split with the sliding midpoint rule */
107 | split = (maxval + minval) / 2;
108 | }
109 |
110 | p = start_idx;
111 | q = end_idx - 1;
112 | while (p <= q) {
113 | if (data[indices[p] * m + d] < split)
114 | ++p;
115 | else if (data[indices[q] * m + d] >= split)
116 | --q;
117 | else {
118 | ckdtree_intp_t t = indices[p];
119 | indices[p] = indices[q];
120 | indices[q] = t;
121 | ++p;
122 | --q;
123 | }
124 | }
125 | /* slide midpoint if necessary */
126 | if (p == start_idx) {
127 | /* no points less than split */
128 | j = start_idx;
129 | split = data[indices[j] * m + d];
130 | for (i = start_idx+1; i < end_idx; ++i) {
131 | if (data[indices[i] * m + d] < split) {
132 | j = i;
133 | split = data[indices[j] * m + d];
134 | }
135 | }
136 | ckdtree_intp_t t = indices[start_idx];
137 | indices[start_idx] = indices[j];
138 | indices[j] = t;
139 | p = start_idx + 1;
140 | q = start_idx;
141 | }
142 | else if (p == end_idx) {
143 | /* no points greater than split */
144 | j = end_idx - 1;
145 | split = data[indices[j] * m + d];
146 | for (i = start_idx; i < end_idx-1; ++i) {
147 | if (data[indices[i] * m + d] > split) {
148 | j = i;
149 | split = data[indices[j] * m + d];
150 | }
151 | }
152 | ckdtree_intp_t t = indices[end_idx-1];
153 | indices[end_idx-1] = indices[j];
154 | indices[j] = t;
155 | p = end_idx - 1;
156 | q = end_idx - 2;
157 | }
158 |
159 | if (CKDTREE_LIKELY(_compact)) {
160 | _less = build(self, start_idx, p, maxes, mins, _median, _compact);
161 | _greater = build(self, p, end_idx, maxes, mins, _median, _compact);
162 | }
163 | else
164 | {
165 | std::vector tmp(m);
166 | double *mids = &tmp[0];
167 |
168 | for (i=0; itree_buffer);
181 | n = root + node_index;
182 | /* fill in entries */
183 | n->_less = _less;
184 | n->_greater = _greater;
185 | n->less = root + _less;
186 | n->greater = root + _greater;
187 | n->split_dim = d;
188 | n->split = split;
189 |
190 | return node_index;
191 | }
192 | }
193 |
194 |
195 |
196 | int build_ckdtree(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx,
197 | double *maxes, double *mins, int _median, int _compact)
198 |
199 | {
200 | build(self, start_idx, end_idx, maxes, mins, _median, _compact);
201 | return 0;
202 | }
203 |
204 | static double
205 | add_weights(ckdtree *self,
206 | double *node_weights,
207 | ckdtree_intp_t node_index,
208 | double *weights)
209 | {
210 |
211 | ckdtree_intp_t *indices = (intptr_t *)(self->raw_indices);
212 |
213 | ckdtreenode *n, *root;
214 |
215 | root = tree_buffer_root(self->tree_buffer);
216 |
217 | n = root + node_index;
218 |
219 | double sum = 0;
220 |
221 | if (n->split_dim != -1) {
222 | /* internal nodes; recursively calculate the total weight */
223 | double left, right;
224 | left = add_weights(self, node_weights, n->_less, weights);
225 | right = add_weights(self, node_weights, n->_greater, weights);
226 | sum = left + right;
227 | } else {
228 | ckdtree_intp_t i;
229 |
230 | /* Leaf nodes */
231 | for (i = n->start_idx; i < n->end_idx; ++i) {
232 | sum += weights[indices[i]];
233 | }
234 | }
235 |
236 | node_weights[node_index] = sum;
237 | return sum;
238 | }
239 |
240 | int
241 | build_weights (ckdtree *self, double *node_weights, double *weights)
242 | {
243 |
244 | add_weights(self, node_weights, 0, weights);
245 | return 0;
246 | }
247 |
248 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/ckdtree_decl.h:
--------------------------------------------------------------------------------
1 | #ifndef CKDTREE_CPP_DECL
2 | #define CKDTREE_CPP_DECL
3 |
4 | /*
5 | * Use numpy to provide some platform independency.
6 | * Define these functions for your platform
7 | * */
8 | #include /* needed for isinf / sc_inf from c99compat under CLANG */
9 | #include "_c99compat.h"
10 | #include
11 | #define CKDTREE_LIKELY(x) NPY_LIKELY(x)
12 | #define CKDTREE_UNLIKELY(x) NPY_UNLIKELY(x)
13 | #define CKDTREE_PREFETCH(x, rw, loc) NPY_PREFETCH(x, rw, loc)
14 |
15 | #define ckdtree_intp_t npy_intp
16 | #define ckdtree_isinf(x) sc_isinf(x)
17 | #define ckdtree_fmin(x, y) fmin(x, y)
18 | #define ckdtree_fmax(x, y) fmax(x, y)
19 | #define ckdtree_fabs(x) fabs(x)
20 |
21 | #include "ordered_pair.h"
22 | #include "coo_entries.h"
23 |
24 | struct ckdtreenode {
25 | ckdtree_intp_t split_dim;
26 | ckdtree_intp_t children;
27 | double split;
28 | ckdtree_intp_t start_idx;
29 | ckdtree_intp_t end_idx;
30 | ckdtreenode *less;
31 | ckdtreenode *greater;
32 | ckdtree_intp_t _less;
33 | ckdtree_intp_t _greater;
34 | };
35 |
36 | struct ckdtree {
37 | // tree structure
38 | std::vector *tree_buffer;
39 | ckdtreenode *ctree;
40 | // meta data
41 | double *raw_data;
42 | ckdtree_intp_t n;
43 | ckdtree_intp_t m;
44 | ckdtree_intp_t leafsize;
45 | double *raw_maxes;
46 | double *raw_mins;
47 | ckdtree_intp_t *raw_indices;
48 | double *raw_boxsize_data;
49 | ckdtree_intp_t size;
50 | };
51 |
52 | /* Build methods in C++ for better speed and GIL release */
53 |
54 | int
55 | build_ckdtree(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx,
56 | double *maxes, double *mins, int _median, int _compact);
57 |
58 | int
59 | build_weights (ckdtree *self, double *node_weights, double *weights);
60 |
61 | /* Query methods in C++ for better speed and GIL release */
62 |
63 | int
64 | query_knn(const ckdtree *self,
65 | double *dd,
66 | ckdtree_intp_t *ii,
67 | const double *xx,
68 | const ckdtree_intp_t n,
69 | const ckdtree_intp_t *k,
70 | const ckdtree_intp_t nk,
71 | const ckdtree_intp_t kmax,
72 | const double eps,
73 | const double p,
74 | const double distance_upper_bound);
75 |
76 | int
77 | query_pairs(const ckdtree *self,
78 | const double r,
79 | const double p,
80 | const double eps,
81 | std::vector *results);
82 |
83 | int
84 | count_neighbors_unweighted(const ckdtree *self,
85 | const ckdtree *other,
86 | ckdtree_intp_t n_queries,
87 | double *real_r,
88 | ckdtree_intp_t *results,
89 | const double p,
90 | int cumulative);
91 |
92 | int
93 | count_neighbors_weighted(const ckdtree *self,
94 | const ckdtree *other,
95 | double *self_weights,
96 | double *other_weights,
97 | double *self_node_weights,
98 | double *other_node_weights,
99 | ckdtree_intp_t n_queries,
100 | double *real_r,
101 | double *results,
102 | const double p,
103 | int cumulative);
104 |
105 | int
106 | query_ball_point(const ckdtree *self,
107 | const double *x,
108 | const double *r,
109 | const double p,
110 | const double eps,
111 | const ckdtree_intp_t n_queries,
112 | std::vector **results,
113 | const int return_length);
114 |
115 | int
116 | query_ball_tree(const ckdtree *self,
117 | const ckdtree *other,
118 | const double r,
119 | const double p,
120 | const double eps,
121 | std::vector **results
122 | );
123 |
124 | int
125 | sparse_distance_matrix(const ckdtree *self,
126 | const ckdtree *other,
127 | const double p,
128 | const double max_distance,
129 | std::vector *results);
130 |
131 |
132 | #endif
133 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/coo_entries.h:
--------------------------------------------------------------------------------
1 | #ifndef CKDTREE_COO_ENTRIES
2 | #define CKDTREE_COO_ENTRIES
3 |
4 | struct coo_entry {
5 | ckdtree_intp_t i;
6 | ckdtree_intp_t j;
7 | double v;
8 | };
9 |
10 | #endif
11 |
12 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/count_neighbors.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "ckdtree_decl.h"
15 | #include "rectangle.h"
16 |
17 | struct WeightedTree {
18 | const ckdtree *tree;
19 | double *weights;
20 | double *node_weights;
21 | };
22 |
23 | struct CNBParams
24 | {
25 | double *r;
26 | void * results; /* will be casted inside */
27 | WeightedTree self, other;
28 | int cumulative;
29 | };
30 |
31 | template static void
32 | traverse(
33 | RectRectDistanceTracker *tracker,
34 | const CNBParams *params,
35 | double *start, double *end,
36 | const ckdtreenode *node1,
37 | const ckdtreenode *node2)
38 | {
39 | static void (* const next)(RectRectDistanceTracker *tracker,
40 | const CNBParams *params,
41 | double *start, double *end,
42 | const ckdtreenode *node1,
43 | const ckdtreenode *node2) = traverse;
44 |
45 | ResultType *results = (ResultType*) params->results;
46 |
47 | /*
48 | * Speed through pairs of nodes all of whose children are close
49 | * and see if any work remains to be done
50 | */
51 |
52 | double * new_start = std::lower_bound(start, end, tracker->min_distance);
53 | double * new_end = std::lower_bound(start, end, tracker->max_distance);
54 |
55 |
56 | /* since max_distance >= min_distance, end < start never happens */
57 | if (params->cumulative) {
58 | double * i;
59 | if (new_end != end) {
60 | ResultType nn = WeightType::get_weight(¶ms->self, node1)
61 | * WeightType::get_weight(¶ms->other, node2);
62 |
63 | for (i = new_end; i < end; ++i) {
64 | results[i - params->r] += nn;
65 | }
66 | }
67 | /* any bins larger than end have been correctly counted, thus
68 | * thus we can truncate the queries in future of this branch of the traversal*/
69 | start = new_start;
70 | end = new_end;
71 | } else {
72 | start = new_start;
73 | end = new_end;
74 |
75 | if (end == start) {
76 | ResultType nn = WeightType::get_weight(¶ms->self, node1)
77 | * WeightType::get_weight(¶ms->other, node2);
78 | results[start - params->r] += nn;
79 | }
80 | }
81 |
82 | if (end == start) {
83 | /* this pair falls into exactly one bin, no need to probe deeper. */
84 | return;
85 | }
86 |
87 | /* OK, need to probe a bit deeper */
88 | if (node1->split_dim == -1) { /* 1 is leaf node */
89 | if (node2->split_dim == -1) { /* 1 & 2 are leaves */
90 | ckdtree_intp_t i, j;
91 | const double p = tracker->p;
92 | const double tmd = tracker->max_distance;
93 | const double *sdata = params->self.tree->raw_data;
94 | const ckdtree_intp_t *sindices = params->self.tree->raw_indices;
95 | const double *odata = params->other.tree->raw_data;
96 | const ckdtree_intp_t *oindices = params->other.tree->raw_indices;
97 | const ckdtree_intp_t m = params->self.tree->m;
98 | const ckdtree_intp_t start1 = node1->start_idx;
99 | const ckdtree_intp_t start2 = node2->start_idx;
100 | const ckdtree_intp_t end1 = node1->end_idx;
101 | const ckdtree_intp_t end2 = node2->end_idx;
102 |
103 | CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m);
104 |
105 | if (start1 < end1 - 1)
106 | CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m);
107 |
108 | /* brute-force */
109 | for (i = start1; i < end1; ++i) {
110 |
111 | if (i < end1 - 2)
112 | CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m);
113 |
114 | CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m);
115 |
116 | if (start2 < end2 - 1)
117 | CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m);
118 |
119 | for (j = start2; j < end2; ++j) {
120 |
121 | if (j < end2 - 2)
122 | CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m);
123 |
124 | double d = MinMaxDist::point_point_p(params->self.tree,
125 | sdata + sindices[i] * m,
126 | odata + oindices[j] * m,
127 | p, m, tmd);
128 |
129 | if (params->cumulative) {
130 | /*
131 | * I think it's usually cheaper to test d against all
132 | * r's than to generate a distance array, sort it, then
133 | * search for all r's via binary search
134 | */
135 | double * l;
136 | for (l = start; l < end; ++l) {
137 | if (d <= *l) {
138 | results[l - params->r] += WeightType::get_weight(¶ms->self, sindices[i])
139 | * WeightType::get_weight(¶ms->other, sindices[j]);
140 | }
141 | }
142 | } else {
143 | const double *l = std::lower_bound(start, end, d);
144 | results[l - params->r] += WeightType::get_weight(¶ms->self, sindices[i])
145 | * WeightType::get_weight(¶ms->other, sindices[j]);
146 | }
147 | }
148 | }
149 | }
150 | else { /* 1 is a leaf node, 2 is inner node */
151 | tracker->push_less_of(2, node2);
152 | next(tracker, params, start, end, node1, node2->less);
153 | tracker->pop();
154 |
155 | tracker->push_greater_of(2, node2);
156 | next(tracker, params, start, end, node1, node2->greater);
157 | tracker->pop();
158 | }
159 | }
160 | else { /* 1 is an inner node */
161 | if (node2->split_dim == -1) {
162 | /* 1 is an inner node, 2 is a leaf node */
163 | tracker->push_less_of(1, node1);
164 | next(tracker, params, start, end, node1->less, node2);
165 | tracker->pop();
166 |
167 | tracker->push_greater_of(1, node1);
168 | next(tracker, params, start, end, node1->greater, node2);
169 | tracker->pop();
170 | }
171 | else { /* 1 and 2 are inner nodes */
172 | tracker->push_less_of(1, node1);
173 | tracker->push_less_of(2, node2);
174 | next(tracker, params, start, end, node1->less, node2->less);
175 | tracker->pop();
176 |
177 | tracker->push_greater_of(2, node2);
178 | next(tracker, params, start, end, node1->less, node2->greater);
179 | tracker->pop();
180 | tracker->pop();
181 |
182 | tracker->push_greater_of(1, node1);
183 | tracker->push_less_of(2, node2);
184 | next(tracker, params, start, end, node1->greater, node2->less);
185 | tracker->pop();
186 |
187 | tracker->push_greater_of(2, node2);
188 | next(tracker, params, start, end, node1->greater, node2->greater);
189 | tracker->pop();
190 | tracker->pop();
191 | }
192 | }
193 | }
194 |
195 | template void
196 | count_neighbors(struct CNBParams *params,
197 | ckdtree_intp_t n_queries, const double p)
198 | {
199 |
200 | const ckdtree *self = params->self.tree;
201 | const ckdtree *other = params->other.tree;
202 |
203 | #define HANDLE(cond, kls) \
204 | if (cond) { \
205 | RectRectDistanceTracker tracker(self, r1, r2, p, 0.0, 0.0);\
206 | traverse(&tracker, params, params->r, params->r+n_queries, \
207 | self->ctree, other->ctree); \
208 | } else
209 |
210 | Rectangle r1(self->m, self->raw_mins, self->raw_maxes);
211 | Rectangle r2(other->m, other->raw_mins, other->raw_maxes);
212 |
213 | if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
214 | HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2)
215 | HANDLE(p == 1, MinkowskiDistP1)
216 | HANDLE(ckdtree_isinf(p), MinkowskiDistPinf)
217 | HANDLE(1, MinkowskiDistPp)
218 | {}
219 | } else {
220 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
221 | HANDLE(p == 1, BoxMinkowskiDistP1)
222 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
223 | HANDLE(1, BoxMinkowskiDistPp)
224 | {}
225 | }
226 | }
227 |
228 | struct Unweighted {
229 | /* the interface for accessing weights of unweighted data. */
230 | static inline ckdtree_intp_t
231 | get_weight(const WeightedTree *wt, const ckdtreenode * node)
232 | {
233 | return node->children;
234 | }
235 | static inline ckdtree_intp_t
236 | get_weight(const WeightedTree *wt, const ckdtree_intp_t i)
237 | {
238 | return 1;
239 | }
240 | };
241 |
242 |
243 | int
244 | count_neighbors_unweighted(const ckdtree *self, const ckdtree *other,
245 | ckdtree_intp_t n_queries, double *real_r, intptr_t *results,
246 | const double p, int cumulative) {
247 |
248 | CNBParams params = {0};
249 |
250 | params.r = real_r;
251 | params.results = (void*) results;
252 | params.self.tree = self;
253 | params.other.tree = other;
254 | params.cumulative = cumulative;
255 |
256 | count_neighbors(¶ms, n_queries, p);
257 |
258 | return 0;
259 | }
260 |
261 | struct Weighted {
262 | /* the interface for accessing weights of weighted data. */
263 | static inline double
264 | get_weight(const WeightedTree *wt, const ckdtreenode * node)
265 | {
266 | return (wt->weights != NULL)
267 | ? wt->node_weights[node - wt->tree->ctree]
268 | : node->children;
269 | }
270 | static inline double
271 | get_weight(const WeightedTree *wt, const ckdtree_intp_t i)
272 | {
273 | return (wt->weights != NULL)?wt->weights[i]:1;
274 | }
275 | };
276 |
277 | int
278 | count_neighbors_weighted(const ckdtree *self, const ckdtree *other,
279 | double *self_weights, double *other_weights,
280 | double *self_node_weights, double *other_node_weights,
281 | ckdtree_intp_t n_queries, double *real_r, double *results,
282 | const double p, int cumulative)
283 | {
284 |
285 | CNBParams params = {0};
286 |
287 | params.r = real_r;
288 | params.results = (void*) results;
289 | params.cumulative = cumulative;
290 |
291 | params.self.tree = self;
292 | params.other.tree = other;
293 | if (self_weights) {
294 | params.self.weights = self_weights;
295 | params.self.node_weights = self_node_weights;
296 | }
297 | if (other_weights) {
298 | params.other.weights = other_weights;
299 | params.other.node_weights = other_node_weights;
300 | }
301 |
302 | count_neighbors(¶ms, n_queries, p);
303 |
304 | return 0;
305 | }
306 |
307 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/distance.h:
--------------------------------------------------------------------------------
1 | #include "distance_base.h"
2 |
3 |
4 | struct PlainDist1D {
5 | static inline const double side_distance_from_min_max(
6 | const ckdtree * tree, const double x,
7 | const double min,
8 | const double max,
9 | const ckdtree_intp_t k
10 | )
11 | {
12 | double s, t;
13 | s = 0;
14 | t = x - max;
15 | if (t > s) {
16 | s = t;
17 | } else {
18 | t = min - x;
19 | if (t > s) s = t;
20 | }
21 | return s;
22 | }
23 | static inline void
24 | interval_interval(const ckdtree * tree,
25 | const Rectangle& rect1, const Rectangle& rect2,
26 | const ckdtree_intp_t k,
27 | double *min, double *max)
28 | {
29 | /* Compute the minimum/maximum distance along dimension k between points in
30 | * two hyperrectangles.
31 | */
32 | *min = ckdtree_fmax(0., fmax(rect1.mins()[k] - rect2.maxes()[k],
33 | rect2.mins()[k] - rect1.maxes()[k]));
34 | *max = ckdtree_fmax(rect1.maxes()[k] - rect2.mins()[k],
35 | rect2.maxes()[k] - rect1.mins()[k]);
36 | }
37 |
38 | static inline double
39 | point_point(const ckdtree * tree,
40 | const double *x, const double *y,
41 | const ckdtree_intp_t k) {
42 | return ckdtree_fabs(x[k] - y[k]);
43 | }
44 | };
45 |
46 | typedef BaseMinkowskiDistPp MinkowskiDistPp;
47 | typedef BaseMinkowskiDistPinf MinkowskiDistPinf;
48 | typedef BaseMinkowskiDistP1 MinkowskiDistP1;
49 | typedef BaseMinkowskiDistP2 NonOptimizedMinkowskiDistP2;
50 |
51 | /*
52 | * Measuring distances
53 | * ===================
54 | */
55 | inline double
56 | sqeuclidean_distance_double(const double *u, const double *v, ckdtree_intp_t n)
57 | {
58 | double s;
59 | ckdtree_intp_t i;
60 | // manually unrolled loop, might be vectorized
61 | double acc[4] = {0., 0., 0., 0.};
62 | for (i = 0; i < n/4; i += 4) {
63 | double _u[4] = {u[i], u[i + 1], u[i + 2], u[i + 3]};
64 | double _v[4] = {v[i], v[i + 1], v[i + 2], v[i + 3]};
65 | double diff[4] = {_u[0] - _v[0],
66 | _u[1] - _v[1],
67 | _u[2] - _v[2],
68 | _u[3] - _v[3]};
69 | acc[0] += diff[0] * diff[0];
70 | acc[1] += diff[1] * diff[1];
71 | acc[2] += diff[2] * diff[2];
72 | acc[3] += diff[3] * diff[3];
73 | }
74 | s = acc[0] + acc[1] + acc[2] + acc[3];
75 | if (i < n) {
76 | for(; i= 0) {
124 | /* do not pass though 0 */
125 | min = ckdtree_fabs(min);
126 | max = ckdtree_fabs(max);
127 | if(min < max) {
128 | *realmin = min;
129 | *realmax = max;
130 | } else {
131 | *realmin = max;
132 | *realmax = min;
133 | }
134 | } else {
135 | min = ckdtree_fabs(min);
136 | max = ckdtree_fabs(max);
137 | *realmax = ckdtree_fmax(max, min);
138 | *realmin = 0;
139 | }
140 | /* done with non-periodic dimension */
141 | return;
142 | }
143 | if(max <= 0 || min >= 0) {
144 | /* do not pass through 0 */
145 | min = ckdtree_fabs(min);
146 | max = ckdtree_fabs(max);
147 | if(min > max) {
148 | double t = min;
149 | min = max;
150 | max = t;
151 | }
152 | if(max < half) {
153 | /* all below half*/
154 | *realmin = min;
155 | *realmax = max;
156 | } else if(min > half) {
157 | /* all above half */
158 | *realmax = full - min;
159 | *realmin = full - max;
160 | } else {
161 | /* min below, max above */
162 | *realmax = half;
163 | *realmin = ckdtree_fmin(min, full - max);
164 | }
165 | } else {
166 | /* pass though 0 */
167 | min = -min;
168 | if(min > max) max = min;
169 | if(max > half) max = half;
170 | *realmax = max;
171 | *realmin = 0;
172 | }
173 | }
174 | static inline void
175 | interval_interval(const ckdtree * tree,
176 | const Rectangle& rect1, const Rectangle& rect2,
177 | const ckdtree_intp_t k,
178 | double *min, double *max)
179 | {
180 | /* Compute the minimum/maximum distance along dimension k between points in
181 | * two hyperrectangles.
182 | */
183 | _interval_interval_1d(rect1.mins()[k] - rect2.maxes()[k],
184 | rect1.maxes()[k] - rect2.mins()[k], min, max,
185 | tree->raw_boxsize_data[k], tree->raw_boxsize_data[k + rect1.m]);
186 | }
187 |
188 | static inline double
189 | point_point(const ckdtree * tree,
190 | const double *x, const double *y,
191 | const ckdtree_intp_t k)
192 | {
193 | double r1;
194 | r1 = wrap_distance(x[k] - y[k], tree->raw_boxsize_data[k + tree->m], tree->raw_boxsize_data[k]);
195 | r1 = ckdtree_fabs(r1);
196 | return r1;
197 | }
198 |
199 | static inline const double
200 | wrap_position(const double x, const double boxsize)
201 | {
202 | if (boxsize <= 0) return x;
203 | const double r = std::floor(x / boxsize);
204 | double x1 = x - r * boxsize;
205 | /* ensure result is within the box. */
206 | while(x1 >= boxsize) x1 -= boxsize;
207 | while(x1 < 0) x1 += boxsize;
208 | return x1;
209 | }
210 |
211 | static inline const double side_distance_from_min_max(
212 | const ckdtree * tree, const double x,
213 | const double min,
214 | const double max,
215 | const ckdtree_intp_t k
216 | )
217 | {
218 | double s, t, tmin, tmax;
219 | double fb = tree->raw_boxsize_data[k];
220 | double hb = tree->raw_boxsize_data[k + tree->m];
221 |
222 | if (fb <= 0) {
223 | /* non-periodic dimension */
224 | s = PlainDist1D::side_distance_from_min_max(tree, x, min, max, k);
225 | return s;
226 | }
227 |
228 | /* periodic */
229 | s = 0;
230 | tmax = x - max;
231 | tmin = x - min;
232 | /* is the test point in this range */
233 | if(CKDTREE_LIKELY(tmax < 0 && tmin > 0)) {
234 | /* yes. min distance is 0 */
235 | return 0;
236 | }
237 |
238 | /* no */
239 | tmax = ckdtree_fabs(tmax);
240 | tmin = ckdtree_fabs(tmin);
241 |
242 | /* make tmin the closer edge */
243 | if(tmin > tmax) { t = tmin; tmin = tmax; tmax = t; }
244 |
245 | /* both edges are less than half a box. */
246 | /* no wrapping, use the closer edge */
247 | if(tmax < hb) return tmin;
248 |
249 | /* both edge are more than half a box. */
250 | /* wrapping on both edge, use the
251 | * wrapped further edge */
252 | if(tmin > hb) return fb - tmax;
253 |
254 | /* the further side is wrapped */
255 | tmax = fb - tmax;
256 | if(tmin > tmax) return tmax;
257 | return tmin;
258 | }
259 |
260 | private:
261 | static inline double
262 | wrap_distance(const double x, const double hb, const double fb)
263 | {
264 | double x1;
265 | if (CKDTREE_UNLIKELY(x < -hb)) x1 = fb + x;
266 | else if (CKDTREE_UNLIKELY(x > hb)) x1 = x - fb;
267 | else x1 = x;
268 | #if 0
269 | printf("ckdtree_fabs_b x : %g x1 %g\n", x, x1);
270 | #endif
271 | return x1;
272 | }
273 |
274 |
275 | };
276 |
277 |
278 | typedef BaseMinkowskiDistPp BoxMinkowskiDistPp;
279 | typedef BaseMinkowskiDistPinf BoxMinkowskiDistPinf;
280 | typedef BaseMinkowskiDistP1 BoxMinkowskiDistP1;
281 | typedef BaseMinkowskiDistP2 BoxMinkowskiDistP2;
282 |
283 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/distance_base.h:
--------------------------------------------------------------------------------
1 | template
2 | struct BaseMinkowskiDistPp {
3 | /* 1-d pieces
4 | * These should only be used if p != infinity
5 | */
6 |
7 | static inline void
8 | interval_interval_p(const ckdtree * tree,
9 | const Rectangle& rect1, const Rectangle& rect2,
10 | const ckdtree_intp_t k, const double p,
11 | double *min, double *max)
12 | {
13 | /* Compute the minimum/maximum distance along dimension k between points in
14 | * two hyperrectangles.
15 | */
16 | Dist1D::interval_interval(tree, rect1, rect2, k, min, max);
17 | *min = std::pow(*min, p);
18 | *max = std::pow(*max, p);
19 | }
20 |
21 | static inline void
22 | rect_rect_p(const ckdtree * tree,
23 | const Rectangle& rect1, const Rectangle& rect2,
24 | const double p,
25 | double *min, double *max)
26 | {
27 | *min = 0.;
28 | *max = 0.;
29 | for(ckdtree_intp_t i=0; iupperbound)
60 | return r;
61 | }
62 | return r;
63 | }
64 |
65 | static inline double
66 | distance_p(const double s, const double p)
67 | {
68 | return std::pow(s,p);
69 | }
70 | };
71 |
72 | template
73 | struct BaseMinkowskiDistP1 : public BaseMinkowskiDistPp {
74 |
75 | static inline void
76 | interval_interval_p(const ckdtree * tree,
77 | const Rectangle& rect1, const Rectangle& rect2,
78 | const ckdtree_intp_t k, const double p,
79 | double *min, double *max)
80 | {
81 | /* Compute the minimum/maximum distance along dimension k between points in
82 | * two hyperrectangles.
83 | */
84 | Dist1D::interval_interval(tree, rect1, rect2, k, min, max);
85 | }
86 |
87 | static inline void
88 | rect_rect_p(const ckdtree * tree,
89 | const Rectangle& rect1, const Rectangle& rect2,
90 | const double p,
91 | double *min, double *max)
92 | {
93 | *min = 0.;
94 | *max = 0.;
95 | for(ckdtree_intp_t i=0; iupperbound)
117 | return r;
118 | }
119 | return r;
120 | }
121 |
122 | static inline double
123 | distance_p(const double s, const double p)
124 | {
125 | return s;
126 | }
127 | };
128 |
129 | template
130 | struct BaseMinkowskiDistPinf : public BaseMinkowskiDistPp {
131 | static inline void
132 | interval_interval_p(const ckdtree * tree,
133 | const Rectangle& rect1, const Rectangle& rect2,
134 | const ckdtree_intp_t k, double p,
135 | double *min, double *max)
136 | {
137 | return rect_rect_p(tree, rect1, rect2, p, min, max);
138 | }
139 |
140 | static inline void
141 | rect_rect_p(const ckdtree * tree,
142 | const Rectangle& rect1, const Rectangle& rect2,
143 | const double p,
144 | double *min, double *max)
145 | {
146 | *min = 0.;
147 | *max = 0.;
148 | for(ckdtree_intp_t i=0; iupperbound)
170 | return r;
171 | }
172 | return r;
173 | }
174 | static inline double
175 | distance_p(const double s, const double p)
176 | {
177 | return s;
178 | }
179 | };
180 |
181 | template
182 | struct BaseMinkowskiDistP2 : public BaseMinkowskiDistPp {
183 | static inline void
184 | interval_interval_p(const ckdtree * tree,
185 | const Rectangle& rect1, const Rectangle& rect2,
186 | const ckdtree_intp_t k, const double p,
187 | double *min, double *max)
188 | {
189 | /* Compute the minimum/maximum distance along dimension k between points in
190 | * two hyperrectangles.
191 | */
192 | Dist1D::interval_interval(tree, rect1, rect2, k, min, max);
193 | *min *= *min;
194 | *max *= *max;
195 | }
196 |
197 | static inline void
198 | rect_rect_p(const ckdtree * tree,
199 | const Rectangle& rect1, const Rectangle& rect2,
200 | const double p,
201 | double *min, double *max)
202 | {
203 | *min = 0.;
204 | *max = 0.;
205 | for(ckdtree_intp_t i=0; iupperbound)
230 | return r;
231 | }
232 | return r;
233 | }
234 | static inline double
235 | distance_p(const double s, const double p)
236 | {
237 | return s * s;
238 | }
239 | };
240 |
241 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/ordered_pair.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef CKDTREE_ORDERED_PAIR
3 | #define CKDTREE_ORDERED_PAIR
4 |
5 | struct ordered_pair {
6 | ckdtree_intp_t i;
7 | ckdtree_intp_t j;
8 | };
9 |
10 | inline void
11 | add_ordered_pair(std::vector *results,
12 | const ckdtree_intp_t i, const intptr_t j)
13 | {
14 | if (i > j) {
15 | ordered_pair p = {j,i};
16 | results->push_back(p);
17 | }
18 | else {
19 | ordered_pair p = {i,j};
20 | results->push_back(p);;
21 | }
22 | }
23 |
24 | #endif
25 |
26 |
27 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/partial_sort.h:
--------------------------------------------------------------------------------
1 | #ifndef CKDTREE_PARTIAL_SORT
2 | #define CKDTREE_PARTIAL_SORT
3 |
4 | /* Adapted version of the code originally
5 | * written by @jiefangxuanyan for scikit-learn.
6 | *
7 | */
8 |
9 | #include "ckdtree_decl.h"
10 | #include
11 |
12 | struct IndexComparator {
13 |
14 | const double *data;
15 | ckdtree_intp_t split_dim;
16 | ckdtree_intp_t n_dims;
17 |
18 | IndexComparator(const double *data,
19 | ckdtree_intp_t split_dim,
20 | ckdtree_intp_t n_dims) :
21 | data(data),
22 | split_dim(split_dim),
23 | n_dims(n_dims) {};
24 |
25 | inline bool operator()(ckdtree_intp_t a, ckdtree_intp_t b) {
26 | const double point_a = data[a * n_dims + split_dim];
27 | const double point_b = data[b * n_dims + split_dim];
28 |
29 | if CKDTREE_UNLIKELY (point_a == point_b) {
30 | return a < b;
31 | } else {
32 | return point_a < point_b;
33 | }
34 | }
35 | };
36 |
37 | /*
38 | * Partition points in the node into two groups.
39 | *
40 | */
41 |
42 | static int
43 | partition_node_indices(const double *data,
44 | ckdtree_intp_t *node_indices,
45 | ckdtree_intp_t split_dim,
46 | ckdtree_intp_t split_index,
47 | ckdtree_intp_t n_dims,
48 | ckdtree_intp_t n_points) {
49 |
50 | /* Upon return, the values in node_indices will be rearranged such that
51 | * (assuming numpy-style indexing):
52 | *
53 | * data[node_indices[0:split_index], split_dim]
54 | * <= data[node_indices[split_index], split_dim]
55 | *
56 | * and
57 | *
58 | * data[node_indices[split_index], split_dim]
59 | * <= data[node_indices[split_index:n_points], split_dim]
60 | *
61 | * This is eassentially a wrapper around the standard C++ function
62 | * ``std::nth_element``.
63 | *
64 | *
65 | * Parameters
66 | * ----------
67 | * data : double pointer
68 | * Pointer to a 2D array of the training data, of shape [N, n_dims].
69 | * N must be greater than any of the values in node_indices.
70 | * node_indices : int pointer
71 | * Pointer to a 1D array of length n_points. This lists the indices of
72 | * each of the points within the current node. This will be modified
73 | * in-place.
74 | * split_dim : int
75 | * the dimension on which to split. This will usually be computed via
76 | * the routine ``find_node_split_dim``
77 | * split_index : int
78 | * the index within node_indices around which to split the points.
79 | *
80 | * Returns
81 | * -------
82 | * status : int
83 | * integer exit status. On return, the contents of node_indices are
84 | * modified as noted above.
85 | */
86 |
87 | IndexComparator index_comparator(data, split_dim, n_dims);
88 |
89 | std::nth_element(node_indices,
90 | node_indices + split_index,
91 | node_indices + n_points,
92 | index_comparator);
93 |
94 | return 0;
95 | }
96 |
97 | #endif
98 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/query.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "ordered_pair.h"
15 | #include "rectangle.h"
16 |
17 | /*
18 | * Priority queue
19 | * ==============
20 | */
21 |
22 | union heapcontents {
23 | ckdtree_intp_t intdata;
24 | void *ptrdata;
25 | };
26 |
27 | struct heapitem {
28 | double priority;
29 | heapcontents contents;
30 | };
31 |
32 | struct heap {
33 |
34 | std::vector _heap;
35 | ckdtree_intp_t n;
36 | ckdtree_intp_t space;
37 |
38 | heap (ckdtree_intp_t initial_size) : _heap(initial_size) {
39 | space = initial_size;
40 | n = 0;
41 | }
42 |
43 | inline void push(heapitem &item) {
44 | ckdtree_intp_t i;
45 | heapitem t;
46 | n++;
47 |
48 | if (n > space) _heap.resize(2*space+1);
49 | space = _heap.size();
50 |
51 | i = n-1;
52 | _heap[i] = item;
53 | while ((i > 0) && (_heap[i].priority < _heap[(i-1)/2].priority)) {
54 | t = _heap[(i-1)/2];
55 | _heap[(i-1)/2] = _heap[i];
56 | _heap[i] = t;
57 | i = (i-1)/2;
58 | }
59 | }
60 |
61 | inline heapitem peek() {return _heap[0];}
62 |
63 | inline void remove() {
64 | heapitem t;
65 | ckdtree_intp_t i, j, k, l, nn;
66 | _heap[0] = _heap[n-1];
67 | n--;
68 | /*
69 | * No point in freeing up space as the heap empties.
70 | * The whole heap gets deallocated at the end of any
71 | * query below. Just keep the space to avoid unnecessary
72 | * reallocs.
73 | */
74 | nn = n;
75 | i=0;
76 | j=1;
77 | k=2;
78 | while (((j _heap[j].priority)) ||
79 | ((k _heap[k].priority))) {
80 | if ((k_heap[k].priority))
81 | l = k;
82 | else
83 | l = j;
84 | t = _heap[l];
85 | _heap[l] = _heap[i];
86 | _heap[i] = t;
87 | i = l;
88 | j = 2*i+1;
89 | k = 2*i+2;
90 | }
91 | }
92 |
93 | inline heapitem pop() {
94 | heapitem it = _heap[0];
95 | remove();
96 | return it;
97 | }
98 | };
99 |
100 |
101 | /*
102 | * nodeinfo
103 | * ========
104 | */
105 |
106 | struct nodeinfo {
107 | const ckdtreenode *node;
108 | ckdtree_intp_t m;
109 | double min_distance; /* full min distance */
110 | double buf[1]; // the good old struct hack
111 | /* accessors to 'packed' attributes */
112 | inline double * const side_distances() {
113 | /* min distance to the query per side; we
114 | * update this as the query is proceeded */
115 | return buf;
116 | }
117 | inline double * const maxes() {
118 | return buf + m;
119 | }
120 | inline double * const mins() {
121 | return buf + 2 * m;
122 | }
123 |
124 | inline void init_box(const struct nodeinfo * from) {
125 | std::memcpy(buf, from->buf, sizeof(double) * (3 * m));
126 | min_distance = from->min_distance;
127 | }
128 |
129 | inline void init_plain(const struct nodeinfo * from) {
130 | /* skip copying min and max, because we only need side_distance array in this case. */
131 | std::memcpy(buf, from->buf, sizeof(double) * m);
132 | min_distance = from->min_distance;
133 | }
134 |
135 | inline void update_side_distance(const int d, const double new_side_distance, const double p) {
136 | if (CKDTREE_UNLIKELY(ckdtree_isinf(p))) {
137 | min_distance = ckdtree_fmax(min_distance, new_side_distance);
138 | } else {
139 | min_distance += new_side_distance - side_distances()[d];
140 | }
141 | side_distances()[d] = new_side_distance;
142 | }
143 | };
144 |
145 | /*
146 | * Memory pool for nodeinfo structs
147 | * ================================
148 | */
149 |
150 | struct nodeinfo_pool {
151 |
152 | std::vector pool;
153 |
154 | ckdtree_intp_t alloc_size;
155 | ckdtree_intp_t arena_size;
156 | ckdtree_intp_t m;
157 | char *arena;
158 | char *arena_ptr;
159 |
160 | nodeinfo_pool(ckdtree_intp_t m) {
161 | alloc_size = sizeof(nodeinfo) + (3 * m -1)*sizeof(double);
162 | alloc_size = 64*(alloc_size/64)+64;
163 | arena_size = 4096*((64*alloc_size)/4096)+4096;
164 | arena = new char[arena_size];
165 | arena_ptr = arena;
166 | pool.push_back(arena);
167 | this->m = m;
168 | }
169 |
170 | ~nodeinfo_pool() {
171 | for (ckdtree_intp_t i = pool.size()-1; i >= 0; --i)
172 | delete [] pool[i];
173 | }
174 |
175 | inline nodeinfo *allocate() {
176 | nodeinfo *ni1;
177 | ckdtree_intp_t m1 = (ckdtree_intp_t)arena_ptr;
178 | ckdtree_intp_t m0 = (ckdtree_intp_t)arena;
179 | if ((arena_size-(ckdtree_intp_t)(m1-m0))m = m;
186 | arena_ptr += alloc_size;
187 | return ni1;
188 | }
189 | };
190 |
191 | /* k-nearest neighbor search for a single point x */
192 | template
193 | static void
194 | query_single_point(const ckdtree *self,
195 | double *result_distances,
196 | ckdtree_intp_t *result_indices,
197 | const double *x,
198 | const ckdtree_intp_t *k,
199 | const ckdtree_intp_t nk,
200 | const ckdtree_intp_t kmax,
201 | const double eps,
202 | const double p,
203 | double distance_upper_bound)
204 | {
205 | static double inf = strtod("INF", NULL);
206 |
207 | /* memory pool to allocate and automatically reclaim nodeinfo structs */
208 | nodeinfo_pool nipool(self->m);
209 |
210 | /*
211 | * priority queue for chasing nodes
212 | * entries are:
213 | * - minimum distance between the cell and the target
214 | * - distances between the nearest side of the cell and the target
215 | * the head node of the cell
216 | */
217 | heap q(12);
218 |
219 | /*
220 | * priority queue for chasing nodes
221 | * entries are:
222 | * - minimum distance between the cell and the target
223 | * - distances between the nearest side of the cell and the target
224 | * the head node of the cell
225 | */
226 | heap neighbors(kmax);
227 |
228 | ckdtree_intp_t i;
229 | const ckdtree_intp_t m = self->m;
230 | nodeinfo *ni1;
231 | nodeinfo *ni2;
232 | double d;
233 | double epsfac;
234 | heapitem it, it2, neighbor;
235 | const ckdtreenode *node;
236 | const ckdtreenode *inode;
237 |
238 | /* set up first nodeifo */
239 | ni1 = nipool.allocate();
240 | ni1->node = self->ctree;
241 |
242 | /* initialize first node, update min_distance */
243 | ni1->min_distance = 0;
244 |
245 | for (i=0; imins()[i] = self->raw_mins[i];
247 | ni1->maxes()[i] = self->raw_maxes[i];
248 |
249 | double side_distance;
250 | if(self->raw_boxsize_data != NULL) {
251 | side_distance = BoxDist1D::side_distance_from_min_max(
252 | self, x[i], self->raw_mins[i], self->raw_maxes[i], i);
253 | } else {
254 | side_distance = PlainDist1D::side_distance_from_min_max(
255 | self, x[i], self->raw_mins[i], self->raw_maxes[i], i);
256 | }
257 | side_distance = MinMaxDist::distance_p(side_distance, p);
258 |
259 | ni1->side_distances()[i] = 0;
260 | ni1->update_side_distance(i, side_distance, p);
261 | }
262 |
263 | /* fiddle approximation factor */
264 | if (CKDTREE_LIKELY(p == 2.0)) {
265 | double tmp = 1. + eps;
266 | epsfac = 1. / (tmp*tmp);
267 | }
268 | else if (eps == 0.)
269 | epsfac = 1.;
270 | else if (ckdtree_isinf(p))
271 | epsfac = 1. / (1. + eps);
272 | else
273 | epsfac = 1. / std::pow((1. + eps), p);
274 |
275 | /* internally we represent all distances as distance**p */
276 | if (CKDTREE_LIKELY(p == 2.0)) {
277 | double tmp = distance_upper_bound;
278 | distance_upper_bound = tmp*tmp;
279 | }
280 | else if ((!ckdtree_isinf(p)) && (!isinf(distance_upper_bound)))
281 | distance_upper_bound = std::pow(distance_upper_bound,p);
282 |
283 | for(;;) {
284 | if (ni1->node->split_dim == -1) {
285 |
286 | node = ni1->node;
287 |
288 | /* brute-force */
289 | {
290 | const ckdtree_intp_t start_idx = node->start_idx;
291 | const ckdtree_intp_t end_idx = node->end_idx;
292 | const double *data = self->raw_data;
293 | const ckdtree_intp_t *indices = self->raw_indices;
294 |
295 | CKDTREE_PREFETCH(data+indices[start_idx]*m, 0, m);
296 | if (start_idx < end_idx - 1)
297 | CKDTREE_PREFETCH(data+indices[start_idx+1]*m, 0, m);
298 |
299 | for (i=start_idx; inode;
332 | const ckdtree_intp_t split_dim = inode->split_dim;
333 | const double split = inode->split;
334 |
335 | /*
336 | * we don't push cells that are too far onto the queue at all,
337 | * but since the distance_upper_bound decreases, we might get
338 | * here even if the cell's too far
339 | */
340 | if (ni1->min_distance > distance_upper_bound*epsfac) {
341 | /* since this is the nearest cell, we're done, bail out */
342 | break;
343 | }
344 | // set up children for searching
345 | // ni2 will be pushed to the queue
346 |
347 | ni2 = nipool.allocate();
348 |
349 | if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
350 | /*
351 | * non periodic : the 'near' node is know from the
352 | * relative distance to the split, and
353 | * has the same distance as the parent node.
354 | *
355 | * we set ni1 to 'near', and set ni2 to 'far'.
356 | * we only recalculate the distance of 'far' later.
357 | *
358 | * This code branch doesn't use min and max.
359 | */
360 | ni2->init_plain(ni1);
361 |
362 | double side_distance;
363 |
364 | if (x[split_dim] < split) {
365 | ni1->node = inode->less;
366 | ni2->node = inode->greater;
367 | side_distance = split - x[split_dim];
368 | } else {
369 | ni1->node = inode->greater;
370 | ni2->node = inode->less;
371 | side_distance = x[split_dim] - split;
372 | }
373 |
374 | side_distance = MinMaxDist::distance_p(side_distance, p);
375 |
376 | ni2->update_side_distance(split_dim, side_distance, p);
377 | } else {
378 | /*
379 | * for periodic queries, we do not know which node is closer.
380 | * thus re-calculate ni1 and ni2 both.
381 | *
382 | * this branch we need to keep track of mins and maxes;
383 | */
384 | ni2->init_box(ni1);
385 |
386 | double side_distance;
387 |
388 | ni1->maxes()[split_dim] = split;
389 | ni1->node = inode->less;
390 |
391 | side_distance = BoxDist1D::side_distance_from_min_max(
392 | self,
393 | x[split_dim],
394 | ni1->mins()[split_dim],
395 | ni1->maxes()[split_dim], split_dim);
396 | side_distance = MinMaxDist::distance_p(side_distance, p);
397 |
398 | ni1->update_side_distance(split_dim, side_distance, p);
399 |
400 | ni2->mins()[split_dim] = split;
401 | ni2->node = inode->greater;
402 |
403 | side_distance = BoxDist1D::side_distance_from_min_max(
404 | self,
405 | x[split_dim],
406 | ni2->mins()[split_dim],
407 | ni2->maxes()[split_dim], split_dim);
408 | side_distance = MinMaxDist::distance_p(side_distance, p);
409 |
410 | ni2->update_side_distance(split_dim, side_distance, p);
411 | }
412 |
413 | /* Ensure ni1 is closer than ni2 */
414 | if (ni1->min_distance > ni2->min_distance) {
415 | {
416 | nodeinfo *tmp;
417 | tmp = ni1;
418 | ni1 = ni2;
419 | ni2 = tmp;
420 | }
421 | }
422 |
423 | /*
424 | * near child is at the same or closer than the distance as the current node
425 | * we're going here next, so no point pushing it on the queue
426 | * no need to recompute the distance or the side_distances
427 | */
428 |
429 | /*
430 | * far child can be further
431 | * push it on the queue if it's near enough
432 | */
433 |
434 | if (ni2->min_distance<=distance_upper_bound*epsfac) {
435 | it2.priority = ni2->min_distance;
436 | it2.contents.ptrdata = (void*) ni2;
437 | q.push(it2);
438 | }
439 |
440 | }
441 | }
442 |
443 | /* heapsort */
444 | std::vector sorted_neighbors(kmax);
445 | ckdtree_intp_t nnb = neighbors.n;
446 | for(i = neighbors.n - 1; i >=0; --i) {
447 | sorted_neighbors[i] = neighbors.pop();
448 | }
449 |
450 | /* fill output arrays with sorted neighbors */
451 | for (i = 0; i < nk; ++i) {
452 | if(CKDTREE_UNLIKELY(k[i] - 1 >= nnb)) {
453 | result_indices[i] = self->n;
454 | result_distances[i] = inf;
455 | } else {
456 | neighbor = sorted_neighbors[k[i] - 1];
457 | result_indices[i] = neighbor.contents.intdata;
458 | if (CKDTREE_LIKELY(p == 2.0))
459 | result_distances[i] = std::sqrt(-neighbor.priority);
460 | else if ((p == 1.) || (ckdtree_isinf(p)))
461 | result_distances[i] = -neighbor.priority;
462 | else
463 | result_distances[i] = std::pow((-neighbor.priority),(1./p));
464 | }
465 | }
466 | }
467 |
468 | /* Query n points for their k nearest neighbors */
469 |
470 | int
471 | query_knn(const ckdtree *self,
472 | double *dd,
473 | ckdtree_intp_t *ii,
474 | const double *xx,
475 | const ckdtree_intp_t n,
476 | const ckdtree_intp_t* k,
477 | const ckdtree_intp_t nk,
478 | const ckdtree_intp_t kmax,
479 | const double eps,
480 | const double p,
481 | const double distance_upper_bound)
482 | {
483 | #define HANDLE(cond, kls) \
484 | if(cond) { \
485 | query_single_point(self, dd_row, ii_row, xx_row, k, nk, kmax, eps, p, distance_upper_bound); \
486 | } else
487 |
488 | ckdtree_intp_t m = self->m;
489 | ckdtree_intp_t i;
490 |
491 | if(CKDTREE_LIKELY(!self->raw_boxsize_data)) {
492 | for (i=0; i row(m);
504 | double * xx_row = &row[0];
505 | int j;
506 | for (i=0; iraw_boxsize_data[j]);
512 | }
513 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
514 | HANDLE(p == 1, BoxMinkowskiDistP1)
515 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
516 | HANDLE(1, BoxMinkowskiDistPp) {}
517 | }
518 |
519 | }
520 | return 0;
521 | }
522 |
523 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/query_ball_point.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "rectangle.h"
15 |
16 |
17 | static void
18 | traverse_no_checking(const ckdtree *self,
19 | const int return_length,
20 | std::vector *results,
21 | const ckdtreenode *node)
22 | {
23 | const ckdtree_intp_t *indices = self->raw_indices;
24 | const ckdtreenode *lnode;
25 | ckdtree_intp_t i;
26 |
27 | if (node->split_dim == -1) { /* leaf node */
28 | lnode = node;
29 | const ckdtree_intp_t start = lnode->start_idx;
30 | const ckdtree_intp_t end = lnode->end_idx;
31 | for (i = start; i < end; ++i) {
32 | if (return_length) {
33 | (*results)[0] ++;
34 | } else {
35 | results->push_back(indices[i]);
36 | }
37 | }
38 | }
39 | else {
40 | traverse_no_checking(self, return_length, results, node->less);
41 | traverse_no_checking(self, return_length, results, node->greater);
42 | }
43 | }
44 |
45 |
46 | template static void
47 | traverse_checking(const ckdtree *self,
48 | const int return_length,
49 | std::vector *results,
50 | const ckdtreenode *node,
51 | RectRectDistanceTracker *tracker
52 | )
53 | {
54 | const ckdtreenode *lnode;
55 | double d;
56 | ckdtree_intp_t i;
57 |
58 | if (tracker->min_distance > tracker->upper_bound * tracker->epsfac) {
59 | return;
60 | }
61 | else if (tracker->max_distance < tracker->upper_bound / tracker->epsfac) {
62 | traverse_no_checking(self, return_length, results, node);
63 | }
64 | else if (node->split_dim == -1) { /* leaf node */
65 |
66 | /* brute-force */
67 | lnode = node;
68 | const double p = tracker->p;
69 | const double tub = tracker->upper_bound;
70 | const double *tpt = tracker->rect1.mins();
71 | const double *data = self->raw_data;
72 | const ckdtree_intp_t *indices = self->raw_indices;
73 | const ckdtree_intp_t m = self->m;
74 | const ckdtree_intp_t start = lnode->start_idx;
75 | const ckdtree_intp_t end = lnode->end_idx;
76 |
77 | CKDTREE_PREFETCH(data + indices[start] * m, 0, m);
78 | if (start < end - 1)
79 | CKDTREE_PREFETCH(data + indices[start+1] * m, 0, m);
80 |
81 | for (i = start; i < end; ++i) {
82 |
83 | if (i < end -2 )
84 | CKDTREE_PREFETCH(data + indices[i+2] * m, 0, m);
85 |
86 | d = MinMaxDist::point_point_p(self, data + indices[i] * m, tpt, p, m, tub);
87 |
88 | if (d <= tub) {
89 | if(return_length) {
90 | (*results)[0] ++;
91 | } else {
92 | results->push_back((ckdtree_intp_t) indices[i]);
93 | }
94 | }
95 | }
96 | }
97 | else {
98 | tracker->push_less_of(2, node);
99 | traverse_checking(self, return_length, results, node->less, tracker);
100 | tracker->pop();
101 |
102 | tracker->push_greater_of(2, node);
103 | traverse_checking(self, return_length, results, node->greater, tracker);
104 | tracker->pop();
105 | }
106 | }
107 |
108 | int
109 | query_ball_point(const ckdtree *self, const double *x,
110 | const double *r, const double p, const double eps,
111 | const ckdtree_intp_t n_queries,
112 | std::vector **results, const int return_length)
113 | {
114 | #define HANDLE(cond, kls) \
115 | if(cond) { \
116 | if(return_length) results[i]->push_back(0); \
117 | RectRectDistanceTracker tracker(self, point, rect, p, eps, r[i]); \
118 | traverse_checking(self, return_length, results[i], self->ctree, &tracker); \
119 | } else
120 |
121 | for (ckdtree_intp_t i=0; i < n_queries; ++i) {
122 | const ckdtree_intp_t m = self->m;
123 | Rectangle rect(m, self->raw_mins, self->raw_maxes);
124 | if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
125 | Rectangle point(m, x + i * m, x + i * m);
126 | HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2)
127 | HANDLE(p == 1, MinkowskiDistP1)
128 | HANDLE(ckdtree_isinf(p), MinkowskiDistPinf)
129 | HANDLE(1, MinkowskiDistPp)
130 | {}
131 | } else {
132 | Rectangle point(m, x + i * m, x + i * m);
133 | int j;
134 | for(j=0; jraw_boxsize_data[j]);
136 | }
137 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
138 | HANDLE(p == 1, BoxMinkowskiDistP1)
139 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
140 | HANDLE(1, BoxMinkowskiDistPp)
141 | {}
142 | }
143 | }
144 | return 0;
145 | }
146 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/query_ball_tree.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "rectangle.h"
15 |
16 |
17 | static void
18 | traverse_no_checking(const ckdtree *self, const ckdtree *other,
19 | std::vector **results,
20 | const ckdtreenode *node1, const ckdtreenode *node2)
21 | {
22 | const ckdtreenode *lnode1;
23 | const ckdtreenode *lnode2;
24 | const ckdtree_intp_t *sindices = self->raw_indices;
25 | const ckdtree_intp_t *oindices = other->raw_indices;
26 | std::vector *results_i;
27 | ckdtree_intp_t i, j;
28 |
29 | if (node1->split_dim == -1) { /* leaf node */
30 | lnode1 = node1;
31 |
32 | if (node2->split_dim == -1) { /* leaf node */
33 | lnode2 = node2;
34 |
35 | const ckdtree_intp_t start1 = lnode1->start_idx;
36 | const ckdtree_intp_t start2 = lnode2->start_idx;
37 | const ckdtree_intp_t end1 = lnode1->end_idx;
38 | const ckdtree_intp_t end2 = lnode2->end_idx;
39 |
40 | for (i = start1; i < end1; ++i) {
41 | results_i = results[sindices[i]];
42 | for (j = start2; j < end2; ++j)
43 | results_i->push_back(oindices[j]);
44 | }
45 | }
46 | else {
47 | traverse_no_checking(self, other, results, node1, node2->less);
48 | traverse_no_checking(self, other, results, node1, node2->greater);
49 | }
50 | }
51 | else {
52 | traverse_no_checking(self, other, results, node1->less, node2);
53 | traverse_no_checking(self, other, results, node1->greater, node2);
54 | }
55 | }
56 |
57 |
58 | template static void
59 | traverse_checking(const ckdtree *self, const ckdtree *other,
60 | std::vector **results,
61 | const ckdtreenode *node1, const ckdtreenode *node2,
62 | RectRectDistanceTracker *tracker)
63 | {
64 | const ckdtreenode *lnode1;
65 | const ckdtreenode *lnode2;
66 | std::vector *results_i;
67 | double d;
68 | ckdtree_intp_t i, j;
69 |
70 | if (tracker->min_distance > tracker->upper_bound * tracker->epsfac)
71 | return;
72 | else if (tracker->max_distance < tracker->upper_bound / tracker->epsfac)
73 | traverse_no_checking(self, other, results, node1, node2);
74 | else if (node1->split_dim == -1) { /* 1 is leaf node */
75 | lnode1 = node1;
76 |
77 | if (node2->split_dim == -1) { /* 1 & 2 are leaves */
78 |
79 | /* brute-force */
80 | lnode2 = node2;
81 | const double p = tracker->p;
82 | const double tub = tracker->upper_bound;
83 | const double tmd = tracker->max_distance;
84 | const double *sdata = self->raw_data;
85 | const ckdtree_intp_t *sindices = self->raw_indices;
86 | const double *odata = other->raw_data;
87 | const ckdtree_intp_t *oindices = other->raw_indices;
88 | const ckdtree_intp_t m = self->m;
89 | const ckdtree_intp_t start1 = lnode1->start_idx;
90 | const ckdtree_intp_t start2 = lnode2->start_idx;
91 | const ckdtree_intp_t end1 = lnode1->end_idx;
92 | const ckdtree_intp_t end2 = lnode2->end_idx;
93 |
94 | CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m);
95 |
96 | if (start1 < end1 - 1)
97 | CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m);
98 |
99 | for (i = start1; i < end1; ++i) {
100 |
101 | if (i < end1 - 2)
102 | CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m);
103 |
104 | CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m);
105 |
106 | if (start2 < end2 - 1)
107 | CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m);
108 |
109 | results_i = results[sindices[i]];
110 |
111 | for (j = start2; j < end2; ++j) {
112 |
113 | if (j < end2 - 2)
114 | CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m);
115 |
116 | d = MinMaxDist::point_point_p(
117 | self,
118 | sdata + sindices[i] * m,
119 | odata + oindices[j] * m,
120 | p, m, tmd);
121 |
122 | if (d <= tub)
123 | results_i->push_back(other->raw_indices[j]);
124 | }
125 | }
126 |
127 | }
128 | else { /* 1 is a leaf node, 2 is inner node */
129 |
130 | tracker->push_less_of(2, node2);
131 | traverse_checking(
132 | self, other, results, node1, node2->less, tracker);
133 | tracker->pop();
134 |
135 | tracker->push_greater_of(2, node2);
136 | traverse_checking(
137 | self, other, results, node1, node2->greater, tracker);
138 | tracker->pop();
139 | }
140 | }
141 | else { /* 1 is an inner node */
142 | if (node2->split_dim == -1) { /* 1 is an inner node, 2 is a leaf node */
143 | tracker->push_less_of(1, node1);
144 | traverse_checking(
145 | self, other, results, node1->less, node2, tracker);
146 | tracker->pop();
147 |
148 | tracker->push_greater_of(1, node1);
149 | traverse_checking(
150 | self, other, results, node1->greater, node2, tracker);
151 | tracker->pop();
152 | }
153 | else { /* 1 & 2 are inner nodes */
154 |
155 | tracker->push_less_of(1, node1);
156 | tracker->push_less_of(2, node2);
157 | traverse_checking(
158 | self, other, results, node1->less, node2->less, tracker);
159 | tracker->pop();
160 |
161 | tracker->push_greater_of(2, node2);
162 | traverse_checking(
163 | self, other, results, node1->less, node2->greater, tracker);
164 | tracker->pop();
165 | tracker->pop();
166 |
167 |
168 | tracker->push_greater_of(1, node1);
169 | tracker->push_less_of(2, node2);
170 | traverse_checking(
171 | self, other, results, node1->greater, node2->less, tracker);
172 | tracker->pop();
173 |
174 | tracker->push_greater_of(2, node2);
175 | traverse_checking(
176 | self, other, results, node1->greater, node2->greater, tracker);
177 | tracker->pop();
178 | tracker->pop();
179 | }
180 | }
181 | }
182 |
183 | int
184 | query_ball_tree(const ckdtree *self, const ckdtree *other,
185 | const double r, const double p, const double eps,
186 | std::vector **results)
187 | {
188 |
189 | #define HANDLE(cond, kls) \
190 | if(cond) { \
191 | RectRectDistanceTracker tracker(self, r1, r2, p, eps, r); \
192 | traverse_checking(self, other, results, self->ctree, other->ctree, \
193 | &tracker); \
194 | } else
195 |
196 | Rectangle r1(self->m, self->raw_mins, self->raw_maxes);
197 | Rectangle r2(other->m, other->raw_mins, other->raw_maxes);
198 |
199 | if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
200 | HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2)
201 | HANDLE(p == 1, MinkowskiDistP1)
202 | HANDLE(ckdtree_isinf(p), MinkowskiDistPinf)
203 | HANDLE(1, MinkowskiDistPp)
204 | {}
205 | } else {
206 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
207 | HANDLE(p == 1, BoxMinkowskiDistP1)
208 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
209 | HANDLE(1, BoxMinkowskiDistPp)
210 | {}
211 | }
212 | return 0;
213 | }
214 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/query_pairs.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "ordered_pair.h"
15 | #include "rectangle.h"
16 |
17 |
18 | static void
19 | traverse_no_checking(const ckdtree *self,
20 | std::vector *results,
21 | const ckdtreenode *node1, const ckdtreenode *node2)
22 | {
23 | const ckdtreenode *lnode1;
24 | const ckdtreenode *lnode2;
25 | ckdtree_intp_t i, j, min_j;
26 | const ckdtree_intp_t *indices = self->raw_indices;
27 |
28 | if (node1->split_dim == -1) { /* leaf node */
29 | lnode1 = node1;
30 |
31 | if (node2->split_dim == -1) { /* leaf node */
32 | lnode2 = node2;
33 |
34 | const ckdtree_intp_t start1 = lnode1->start_idx;
35 | const ckdtree_intp_t start2 = lnode2->start_idx;
36 | const ckdtree_intp_t end1 = lnode1->end_idx;
37 | const ckdtree_intp_t end2 = lnode2->end_idx;
38 |
39 | for (i = start1; i < end1; ++i) {
40 |
41 | /* Special care here to avoid duplicate pairs */
42 | if (node1 == node2)
43 | min_j = i + 1;
44 | else
45 | min_j = start2;
46 |
47 | for (j = min_j; j < end2; ++j)
48 | add_ordered_pair(results, indices[i], indices[j]);
49 | }
50 | }
51 | else {
52 | traverse_no_checking(self, results, node1, node2->less);
53 | traverse_no_checking(self, results, node1, node2->greater);
54 | }
55 | }
56 | else {
57 | if (node1 == node2) {
58 | /*
59 | * Avoid traversing (node1->less, node2->greater) and
60 | * (node1->greater, node2->less) (it's the same node pair twice
61 | * over, which is the source of the complication in the
62 | * original KDTree.query_pairs)
63 | */
64 | traverse_no_checking(self, results, node1->less, node2->less);
65 | traverse_no_checking(self, results, node1->less, node2->greater);
66 | traverse_no_checking(self, results, node1->greater, node2->greater);
67 | }
68 | else {
69 | traverse_no_checking(self, results, node1->less, node2);
70 | traverse_no_checking(self, results, node1->greater, node2);
71 | }
72 | }
73 | }
74 |
75 |
76 | template static void
77 | traverse_checking(const ckdtree *self,
78 | std::vector *results,
79 | const ckdtreenode *node1, const ckdtreenode *node2,
80 | RectRectDistanceTracker *tracker)
81 | {
82 | const ckdtreenode *lnode1;
83 | const ckdtreenode *lnode2;
84 | double d;
85 | ckdtree_intp_t i, j, min_j;
86 |
87 | if (tracker->min_distance > tracker->upper_bound * tracker->epsfac)
88 | return;
89 | else if (tracker->max_distance < tracker->upper_bound / tracker->epsfac)
90 | traverse_no_checking(self, results, node1, node2);
91 | else if (node1->split_dim == -1) { /* 1 is leaf node */
92 | lnode1 = node1;
93 |
94 | if (node2->split_dim == -1) { /* 1 & 2 are leaves */
95 | lnode2 = node2;
96 |
97 | /* brute-force */
98 | const double p = tracker->p;
99 | const double tub = tracker->upper_bound;
100 | const double *data = self->raw_data;
101 | const ckdtree_intp_t *indices = self->raw_indices;
102 | const ckdtree_intp_t m = self->m;
103 | const ckdtree_intp_t start1 = lnode1->start_idx;
104 | const ckdtree_intp_t start2 = lnode2->start_idx;
105 | const ckdtree_intp_t end1 = lnode1->end_idx;
106 | const ckdtree_intp_t end2 = lnode2->end_idx;
107 |
108 | CKDTREE_PREFETCH(data+indices[start1]*m, 0, m);
109 | if (start1 < end1 - 1)
110 | CKDTREE_PREFETCH(data+indices[start1+1]*m, 0, m);
111 |
112 | for(i = start1; i < end1; ++i) {
113 |
114 | if (i < end1 - 2)
115 | CKDTREE_PREFETCH(data+indices[i+2]*m, 0, m);
116 |
117 | /* Special care here to avoid duplicate pairs */
118 | if (node1 == node2)
119 | min_j = i + 1;
120 | else
121 | min_j = start2;
122 |
123 | if (min_j < end2)
124 | CKDTREE_PREFETCH(data+indices[min_j]*m, 0, m);
125 | if (min_j < end2 - 1)
126 | CKDTREE_PREFETCH(data+indices[min_j+1]*m, 0, m);
127 |
128 | for (j = min_j; j < end2; ++j) {
129 |
130 | if (j < end2 - 2)
131 | CKDTREE_PREFETCH(data+indices[j+2]*m, 0, m);
132 |
133 | d = MinMaxDist::point_point_p(
134 | self,
135 | data + indices[i] * m,
136 | data + indices[j] * m,
137 | p, m, tub);
138 |
139 | if (d <= tub)
140 | add_ordered_pair(results, indices[i], indices[j]);
141 | }
142 | }
143 | }
144 | else { /* 1 is a leaf node, 2 is inner node */
145 | tracker->push_less_of(2, node2);
146 | traverse_checking(self, results, node1, node2->less, tracker);
147 | tracker->pop();
148 |
149 | tracker->push_greater_of(2, node2);
150 | traverse_checking(self, results, node1, node2->greater, tracker);
151 | tracker->pop();
152 | }
153 | }
154 | else { /* 1 is an inner node */
155 | if (node2->split_dim == -1) { /* 1 is an inner node, 2 is a leaf node */
156 | tracker->push_less_of(1, node1);
157 | traverse_checking(self, results, node1->less, node2, tracker);
158 | tracker->pop();
159 |
160 | tracker->push_greater_of(1, node1);
161 | traverse_checking(self, results, node1->greater, node2, tracker);
162 | tracker->pop();
163 | }
164 | else { /* 1 and 2 are inner nodes */
165 | tracker->push_less_of(1, node1);
166 | tracker->push_less_of(2, node2);
167 | traverse_checking(self, results, node1->less, node2->less, tracker);
168 | tracker->pop();
169 |
170 | tracker->push_greater_of(2, node2);
171 | traverse_checking(self, results, node1->less, node2->greater,
172 | tracker);
173 | tracker->pop();
174 | tracker->pop();
175 |
176 | tracker->push_greater_of(1, node1);
177 | if (node1 != node2) {
178 | /*
179 | * Avoid traversing (node1->less, node2->greater) and
180 | * (node1->greater, node2->less) (it's the same node pair
181 | * twice over, which is the source of the complication in
182 | * the original KDTree.query_pairs)
183 | */
184 | tracker->push_less_of(2, node2);
185 | traverse_checking(self, results, node1->greater, node2->less,
186 | tracker);
187 | tracker->pop();
188 | }
189 | tracker->push_greater_of(2, node2);
190 | traverse_checking(self, results, node1->greater, node2->greater,
191 | tracker);
192 | tracker->pop();
193 | tracker->pop();
194 | }
195 | }
196 | }
197 |
198 |
199 | #include
200 |
201 | int
202 | query_pairs(const ckdtree *self,
203 | const double r, const double p, const double eps,
204 | std::vector *results)
205 | {
206 |
207 | #define HANDLE(cond, kls) \
208 | if(cond) { \
209 | RectRectDistanceTracker tracker(self, r1, r2, p, eps, r);\
210 | traverse_checking(self, results, self->ctree, self->ctree, \
211 | &tracker); \
212 | } else
213 |
214 | Rectangle r1(self->m, self->raw_mins, self->raw_maxes);
215 | Rectangle r2(self->m, self->raw_mins, self->raw_maxes);
216 |
217 | if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
218 | HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2)
219 | HANDLE(p == 1, MinkowskiDistP1)
220 | HANDLE(ckdtree_isinf(p), MinkowskiDistPinf)
221 | HANDLE(1, MinkowskiDistPp)
222 | {}
223 | } else {
224 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
225 | HANDLE(p == 1, BoxMinkowskiDistP1)
226 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
227 | HANDLE(1, BoxMinkowskiDistPp)
228 | {}
229 | }
230 |
231 | return 0;
232 | }
233 |
234 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/rectangle.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef CKDTREE_CPP_RECTANGLE
3 | #define CKDTREE_CPP_RECTANGLE
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 |
13 | /* Interval arithmetic
14 | * ===================
15 | */
16 |
17 | struct Rectangle {
18 |
19 | const ckdtree_intp_t m;
20 |
21 | /* the last const is to allow const Rectangle to use these functions;
22 | * also notice we had to mark buf mutable to avoid writing non const version
23 | * of the same accessors. */
24 | double * const maxes() const { return &buf[0]; }
25 | double * const mins() const { return &buf[0] + m; }
26 |
27 | Rectangle(const ckdtree_intp_t _m,
28 | const double *_mins,
29 | const double *_maxes) : m(_m), buf(2 * m) {
30 |
31 | /* copy array data */
32 | /* FIXME: use std::vector ? */
33 | std::memcpy((void*)mins(), (void*)_mins, m*sizeof(double));
34 | std::memcpy((void*)maxes(), (void*)_maxes, m*sizeof(double));
35 | };
36 |
37 | Rectangle(const Rectangle& rect) : m(rect.m), buf(rect.buf) {};
38 |
39 | private:
40 | mutable std::vector buf;
41 | };
42 |
43 | #include "distance.h"
44 |
45 | /*
46 | * Rectangle-to-rectangle distance tracker
47 | * =======================================
48 | *
49 | * The logical unit that repeats over and over is to keep track of the
50 | * maximum and minimum distances between points in two hyperrectangles
51 | * as these rectangles are successively split.
52 | *
53 | * Example
54 | * -------
55 | * node1 encloses points in rect1, node2 encloses those in rect2
56 | *
57 | * cdef RectRectDistanceTracker dist_tracker
58 | * dist_tracker = RectRectDistanceTracker(rect1, rect2, p)
59 | *
60 | * ...
61 | *
62 | * if dist_tracker.min_distance < ...:
63 | * ...
64 | *
65 | * dist_tracker.push_less_of(1, node1)
66 | * do_something(node1.less, dist_tracker)
67 | * dist_tracker.pop()
68 | *
69 | * dist_tracker.push_greater_of(1, node1)
70 | * do_something(node1.greater, dist_tracker)
71 | * dist_tracker.pop()
72 | *
73 | * Notice that Point is just a reduced case of Rectangle where
74 | * mins == maxes.
75 | *
76 | */
77 |
78 | struct RR_stack_item {
79 | ckdtree_intp_t which;
80 | ckdtree_intp_t split_dim;
81 | double min_along_dim;
82 | double max_along_dim;
83 | double min_distance;
84 | double max_distance;
85 | };
86 |
87 | const ckdtree_intp_t LESS = 1;
88 | const ckdtree_intp_t GREATER = 2;
89 |
90 | template
91 | struct RectRectDistanceTracker {
92 |
93 | const ckdtree * tree;
94 | Rectangle rect1;
95 | Rectangle rect2;
96 | double p;
97 | double epsfac;
98 | double upper_bound;
99 | double min_distance;
100 | double max_distance;
101 |
102 | ckdtree_intp_t stack_size;
103 | ckdtree_intp_t stack_max_size;
104 | std::vector stack_arr;
105 | RR_stack_item *stack;
106 |
107 | /* if min/max distance / adjustment is less than this,
108 | * we believe the incremental tracking is inaccurate */
109 | double inaccurate_distance_limit;
110 |
111 | void _resize_stack(const ckdtree_intp_t new_max_size) {
112 | stack_arr.resize(new_max_size);
113 | stack = &stack_arr[0];
114 | stack_max_size = new_max_size;
115 | };
116 |
117 | RectRectDistanceTracker(const ckdtree *_tree,
118 | const Rectangle& _rect1, const Rectangle& _rect2,
119 | const double _p, const double eps,
120 | const double _upper_bound)
121 | : tree(_tree), rect1(_rect1), rect2(_rect2), stack_arr(8) {
122 |
123 | if (rect1.m != rect2.m) {
124 | const char *msg = "rect1 and rect2 have different dimensions";
125 | throw std::invalid_argument(msg); // raises ValueError
126 | }
127 |
128 | p = _p;
129 |
130 | /* internally we represent all distances as distance ** p */
131 | if (CKDTREE_LIKELY(p == 2.0))
132 | upper_bound = _upper_bound * _upper_bound;
133 | else if ((!ckdtree_isinf(p)) && (!isinf(_upper_bound)))
134 | upper_bound = std::pow(_upper_bound,p);
135 | else
136 | upper_bound = _upper_bound;
137 |
138 | /* fiddle approximation factor */
139 | if (CKDTREE_LIKELY(p == 2.0)) {
140 | double tmp = 1. + eps;
141 | epsfac = 1. / (tmp*tmp);
142 | }
143 | else if (eps == 0.)
144 | epsfac = 1.;
145 | else if (ckdtree_isinf(p))
146 | epsfac = 1. / (1. + eps);
147 | else
148 | epsfac = 1. / std::pow((1. + eps), p);
149 |
150 | stack = &stack_arr[0];
151 | stack_max_size = 8;
152 | stack_size = 0;
153 |
154 | /* Compute initial min and max distances */
155 | MinMaxDist::rect_rect_p(tree, rect1, rect2, p, &min_distance, &max_distance);
156 | if(ckdtree_isinf(max_distance)) {
157 | const char *msg = "Encountering floating point overflow. "
158 | "The value of p too large for this dataset; "
159 | "For such large p, consider using the special case p=np.inf . ";
160 | throw std::invalid_argument(msg); // raises ValueError
161 | }
162 | inaccurate_distance_limit = max_distance;
163 | };
164 |
165 |
166 | void push(const ckdtree_intp_t which, const intptr_t direction,
167 | const ckdtree_intp_t split_dim, const double split_val) {
168 |
169 | const double p = this->p;
170 | /* subnomial is 1 if round-off is expected to taint the incremental distance tracking.
171 | * in that case we always recompute the distances.
172 | * Recomputing costs more calls to pow, thus if the round-off error does not seem
173 | * to wipe out the value, then we still do the incremental update.
174 | * */
175 | int subnomial = 0;
176 |
177 | Rectangle *rect;
178 | if (which == 1)
179 | rect = &rect1;
180 | else
181 | rect = &rect2;
182 |
183 | /* push onto stack */
184 | if (stack_size == stack_max_size)
185 | _resize_stack(stack_max_size * 2);
186 |
187 | RR_stack_item *item = &stack[stack_size];
188 | ++stack_size;
189 | item->which = which;
190 | item->split_dim = split_dim;
191 | item->min_distance = min_distance;
192 | item->max_distance = max_distance;
193 | item->min_along_dim = rect->mins()[split_dim];
194 | item->max_along_dim = rect->maxes()[split_dim];
195 |
196 | /* update min/max distances */
197 | double min1, max1;
198 | double min2, max2;
199 |
200 | MinMaxDist::interval_interval_p(tree, rect1, rect2, split_dim, p, &min1, &max1);
201 |
202 | if (direction == LESS)
203 | rect->maxes()[split_dim] = split_val;
204 | else
205 | rect->mins()[split_dim] = split_val;
206 |
207 | MinMaxDist::interval_interval_p(tree, rect1, rect2, split_dim, p, &min2, &max2);
208 |
209 | subnomial = subnomial || (min_distance < inaccurate_distance_limit || max_distance < inaccurate_distance_limit);
210 |
211 | subnomial = subnomial || ((min1 != 0 && min1 < inaccurate_distance_limit) || max1 < inaccurate_distance_limit);
212 | subnomial = subnomial || ((min2 != 0 && min2 < inaccurate_distance_limit) || max2 < inaccurate_distance_limit);
213 | subnomial = subnomial || (min_distance < inaccurate_distance_limit || max_distance < inaccurate_distance_limit);
214 |
215 | if (CKDTREE_UNLIKELY(subnomial)) {
216 | MinMaxDist::rect_rect_p(tree, rect1, rect2, p, &min_distance, &max_distance);
217 | } else {
218 | min_distance += (min2 - min1);
219 | max_distance += (max2 - max1);
220 | }
221 | };
222 |
223 | inline void push_less_of(const ckdtree_intp_t which,
224 | const ckdtreenode *node) {
225 | push(which, LESS, node->split_dim, node->split);
226 | };
227 |
228 | inline void push_greater_of(const ckdtree_intp_t which,
229 | const ckdtreenode *node) {
230 | push(which, GREATER, node->split_dim, node->split);
231 | };
232 |
233 | inline void pop() {
234 | /* pop from stack */
235 | --stack_size;
236 |
237 | /* assert stack_size >= 0 */
238 | if (CKDTREE_UNLIKELY(stack_size < 0)) {
239 | const char *msg = "Bad stack size. This error should never occur.";
240 | throw std::logic_error(msg);
241 | }
242 |
243 | RR_stack_item* item = &stack[stack_size];
244 | min_distance = item->min_distance;
245 | max_distance = item->max_distance;
246 |
247 | if (item->which == 1) {
248 | rect1.mins()[item->split_dim] = item->min_along_dim;
249 | rect1.maxes()[item->split_dim] = item->max_along_dim;
250 | }
251 | else {
252 | rect2.mins()[item->split_dim] = item->min_along_dim;
253 | rect2.maxes()[item->split_dim] = item->max_along_dim;
254 | }
255 | };
256 |
257 | };
258 |
259 |
260 | #endif
261 |
--------------------------------------------------------------------------------
/jakteristics/ckdtree/ckdtree/src/sparse_distances.cxx:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include "ckdtree_decl.h"
14 | #include "rectangle.h"
15 | #include "coo_entries.h"
16 |
17 | template static void
18 | traverse(const ckdtree *self, const ckdtree *other,
19 | std::vector *results,
20 | const ckdtreenode *node1, const ckdtreenode *node2,
21 | RectRectDistanceTracker *tracker)
22 | {
23 |
24 | if (tracker->min_distance > tracker->upper_bound)
25 | return;
26 | else if (node1->split_dim == -1) { /* 1 is leaf node */
27 |
28 | if (node2->split_dim == -1) { /* 1 & 2 are leaves */
29 | /* brute-force */
30 | const double p = tracker->p;
31 | const double tub = tracker->upper_bound;
32 | const double *sdata = self->raw_data;
33 | const ckdtree_intp_t *sindices = self->raw_indices;
34 | const double *odata = other->raw_data;
35 | const ckdtree_intp_t *oindices = other->raw_indices;
36 | const ckdtree_intp_t m = self->m;
37 | const ckdtree_intp_t start1 = node1->start_idx;
38 | const ckdtree_intp_t start2 = node2->start_idx;
39 | const ckdtree_intp_t end1 = node1->end_idx;
40 | const ckdtree_intp_t end2 = node2->end_idx;
41 |
42 | CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m);
43 | if (start1 < end1 - 1)
44 | CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m);
45 |
46 | for (ckdtree_intp_t i = start1; i < end1; ++i) {
47 |
48 | if (i < end1 - 2)
49 | CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m);
50 |
51 | CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m);
52 | if (start2 < end2 - 1)
53 | CKDTREE_PREFETCH(sdata + oindices[start2+1] * m, 0, m);
54 |
55 | for (ckdtree_intp_t j = start2; j < end2; ++j) {
56 |
57 | if (j < end2 - 2)
58 | CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m);
59 |
60 | double d = MinMaxDist::point_point_p(
61 | self,
62 | sdata + sindices[i] * m,
63 | odata + oindices[j] * m,
64 | p, m, tub);
65 |
66 | if (d <= tub) {
67 | if (CKDTREE_LIKELY(p == 2.0))
68 | d = std::sqrt(d);
69 | else if ((p != 1) && (!ckdtree_isinf(p)))
70 | d = std::pow(d, 1. / p);
71 |
72 | coo_entry e = {sindices[i], oindices[j], d};
73 | results->push_back(e);
74 | }
75 | }
76 | }
77 | }
78 | else { /* 1 is a leaf node, 2 is inner node */
79 | tracker->push_less_of(2, node2);
80 | traverse(self, other, results, node1, node2->less, tracker);
81 | tracker->pop();
82 |
83 | tracker->push_greater_of(2, node2);
84 | traverse(self, other, results, node1, node2->greater, tracker);
85 | tracker->pop();
86 | }
87 | }
88 | else { /* 1 is an inner node */
89 | if (node2->split_dim == -1) {
90 | /* 1 is an inner node, 2 is a leaf node*/
91 | tracker->push_less_of(1, node1);
92 | traverse(self, other, results, node1->less, node2, tracker);
93 | tracker->pop();
94 |
95 | tracker->push_greater_of(1, node1);
96 | traverse(self, other, results, node1->greater, node2, tracker);
97 | tracker->pop();
98 | }
99 | else { /* 1 and 2 are inner nodes */
100 | tracker->push_less_of(1, node1);
101 | tracker->push_less_of(2, node2);
102 | traverse(self, other, results, node1->less, node2->less, tracker);
103 | tracker->pop();
104 |
105 | tracker->push_greater_of(2, node2);
106 | traverse(self, other, results, node1->less, node2->greater, tracker);
107 | tracker->pop();
108 | tracker->pop();
109 |
110 | tracker->push_greater_of(1, node1);
111 | tracker->push_less_of(2, node2);
112 | traverse(self, other, results, node1->greater, node2->less, tracker);
113 | tracker->pop();
114 |
115 | tracker->push_greater_of(2, node2);
116 | traverse(self, other, results, node1->greater, node2->greater,
117 | tracker);
118 | tracker->pop();
119 | tracker->pop();
120 | }
121 | }
122 | }
123 |
124 |
125 | int
126 | sparse_distance_matrix(const ckdtree *self, const ckdtree *other,
127 | const double p,
128 | const double max_distance,
129 | std::vector *results)
130 | {
131 | #define HANDLE(cond, kls) \
132 | if(cond) { \
133 | RectRectDistanceTracker tracker(self, r1, r2, p, 0, max_distance);\
134 | traverse(self, other, results, self->ctree, other->ctree, &tracker); \
135 | } else
136 |
137 | Rectangle r1(self->m, self->raw_mins, self->raw_maxes);
138 | Rectangle r2(other->m, other->raw_mins, other->raw_maxes);
139 | if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) {
140 | HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2)
141 | HANDLE(p == 1, MinkowskiDistP1)
142 | HANDLE(ckdtree_isinf(p), MinkowskiDistPinf)
143 | HANDLE(1, MinkowskiDistPp)
144 | {}
145 | } else {
146 | HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2)
147 | HANDLE(p == 1, BoxMinkowskiDistP1)
148 | HANDLE(ckdtree_isinf(p), BoxMinkowskiDistPinf)
149 | HANDLE(1, BoxMinkowskiDistPp)
150 | {}
151 | }
152 |
153 | return 0;
154 | }
155 |
--------------------------------------------------------------------------------
/jakteristics/constants.py:
--------------------------------------------------------------------------------
1 | FEATURE_NAMES = [
2 | "eigenvalue_sum",
3 | "omnivariance",
4 | "eigenentropy",
5 | "anisotropy",
6 | "planarity",
7 | "linearity",
8 | "PCA1",
9 | "PCA2",
10 | "surface_variation",
11 | "sphericity",
12 | "verticality",
13 | "nx",
14 | "ny",
15 | "nz",
16 | "number_of_neighbors",
17 | "eigenvalue1",
18 | "eigenvalue2",
19 | "eigenvalue3",
20 | "eigenvector1x",
21 | "eigenvector1y",
22 | "eigenvector1z",
23 | "eigenvector2x",
24 | "eigenvector2y",
25 | "eigenvector2z",
26 | "eigenvector3x",
27 | "eigenvector3y",
28 | "eigenvector3z",
29 | ]
30 |
--------------------------------------------------------------------------------
/jakteristics/extension.pyx:
--------------------------------------------------------------------------------
1 | # cython: language_level=3
2 | # distutils: language = c++
3 |
4 | import numpy as np
5 | import multiprocessing
6 |
7 | from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
8 | from libc.string cimport memset
9 | from libcpp cimport bool
10 | cimport openmp
11 | cimport cython
12 | from cython.parallel import prange, parallel
13 | from libc.math cimport fabs, pow, log, sqrt
14 | cimport numpy as np
15 | from libcpp.vector cimport vector
16 | from libcpp.map cimport map as cppmap
17 | from libcpp.string cimport string
18 | from libc.stdint cimport uintptr_t, uint32_t, int8_t, uint8_t, int64_t
19 |
20 | from .ckdtree.ckdtree cimport cKDTree, ckdtree, query_ball_point
21 | from . cimport utils
22 | from .constants import FEATURE_NAMES
23 |
24 |
25 | @cython.boundscheck(False)
26 | @cython.wraparound(False)
27 | @cython.initializedcheck(False)
28 | @cython.cdivision(True)
29 | def compute_features(
30 | double [:, ::1] points,
31 | float search_radius,
32 | *,
33 | cKDTree kdtree=None,
34 | int num_threads=-1,
35 | int max_k_neighbors=50000,
36 | bint euclidean_distance=True,
37 | feature_names=FEATURE_NAMES,
38 | float eps=0.0,
39 | ):
40 | cdef:
41 | cppmap [string, uint8_t] features_map
42 |
43 | int64_t n_points = points.shape[0]
44 | double [::1, :] neighbor_points
45 | double [::1, :] eigenvectors
46 | double [:] eigenvalues
47 |
48 | int i, j, k
49 | uint32_t neighbor_id
50 | uint32_t n_neighbors_at_id
51 | int thread_id
52 | int number_of_neighbors
53 |
54 | float [:, :] features = np.full((n_points, len(feature_names)), float("NaN"), dtype=np.float32)
55 |
56 | const np.float64_t[:, ::1] radius_vector
57 | np.float64_t p = 2 if euclidean_distance else 1
58 | np.float64_t eps_scipy = 0.0
59 | vector[np.intp_t] *** threaded_vvres
60 | int return_length = False
61 |
62 | if not points.shape[1] == 3:
63 | raise ValueError("You must provide an (n x 3) numpy array.")
64 |
65 | if num_threads == -1:
66 | num_threads = multiprocessing.cpu_count()
67 |
68 | if kdtree is None:
69 | kdtree = cKDTree(points)
70 |
71 | for n, name in enumerate(feature_names):
72 | if name not in FEATURE_NAMES:
73 | raise ValueError(f"Unknown feature name: {name}")
74 | features_map[name.encode()] = n
75 |
76 | radius_vector = np.full((num_threads, 3), fill_value=search_radius)
77 | neighbor_points = np.zeros([3, max_k_neighbors * num_threads], dtype=np.float64, order="F")
78 | eigenvectors = np.zeros([3, 3 * num_threads], dtype=np.float64, order="F")
79 | eigenvalues = np.zeros(3 * num_threads, dtype=np.float64)
80 |
81 | threaded_vvres = init_result_vectors(num_threads)
82 |
83 | try:
84 | for i in prange(n_points, nogil=True, num_threads=num_threads):
85 | thread_id = openmp.omp_get_thread_num()
86 |
87 | threaded_vvres[thread_id][0].clear()
88 | query_ball_point(
89 | kdtree.cself,
90 | &points[i, 0],
91 | &radius_vector[thread_id, 0],
92 | p,
93 | eps_scipy,
94 | 1,
95 | threaded_vvres[thread_id],
96 | return_length,
97 | )
98 |
99 | n_neighbors_at_id = threaded_vvres[thread_id][0].size()
100 | number_of_neighbors = n_neighbors_at_id
101 |
102 | if n_neighbors_at_id > max_k_neighbors:
103 | n_neighbors_at_id = max_k_neighbors
104 | elif n_neighbors_at_id == 0:
105 | continue
106 |
107 | for j in range(n_neighbors_at_id):
108 | neighbor_id = threaded_vvres[thread_id][0][0][j]
109 | for k in range(3):
110 | neighbor_points[k, thread_id * max_k_neighbors + j] = kdtree.cself.raw_data[neighbor_id * 3 + k]
111 |
112 | utils.c_covariance(
113 | neighbor_points[:, thread_id * max_k_neighbors:thread_id * max_k_neighbors + n_neighbors_at_id],
114 | eigenvectors[:, thread_id * 3:(thread_id + 1) * 3],
115 | )
116 | utils.c_eigenvectors(
117 | eigenvectors[:, thread_id * 3:(thread_id + 1) * 3],
118 | eigenvalues[thread_id * 3:(thread_id + 1) * 3],
119 | )
120 |
121 | compute_features_from_eigenvectors(
122 | number_of_neighbors,
123 | eigenvalues[thread_id * 3 : thread_id * 3 + 3],
124 | eigenvectors[:, thread_id * 3 : thread_id * 3 + 3],
125 | features[i, :],
126 | features_map,
127 | )
128 |
129 | finally:
130 | free_result_vectors(threaded_vvres, num_threads)
131 |
132 | return np.asarray(features)
133 |
134 | @cython.boundscheck(False)
135 | @cython.wraparound(False)
136 | @cython.cdivision(True)
137 | cdef inline void compute_features_from_eigenvectors(
138 | int number_of_neighbors,
139 | double [:] eigenvalues,
140 | double [:, :] eigenvectors,
141 | float [:] out,
142 | cppmap [string, uint8_t] & out_map,
143 | ) nogil:
144 | cdef:
145 | float l1, l2, l3
146 | float eigenvalue_sum
147 | float n0, n1, n2
148 | float norm
149 |
150 | l1 = eigenvalues[0]
151 | l2 = eigenvalues[1]
152 | l3 = eigenvalues[2]
153 |
154 | # Those features are inspired from cloud compare implementation (https://github.com/CloudCompare/CloudCompare/blob/master/CC/src/Neighbourhood.cpp#L871)
155 | # Those features are also implemented in CGAL (https://doc.cgal.org/4.12/Classification/group__PkgClassificationFeatures.html)
156 |
157 | # Sum of eigenvalues equals the original variance of the data
158 | eigenvalue_sum = l1 + l2 + l3
159 |
160 | if out_map.count(b"eigenvalue1"):
161 | out[out_map.at(b"eigenvalue1")] = l1
162 | if out_map.count(b"eigenvalue2"):
163 | out[out_map.at(b"eigenvalue2")] = l2
164 | if out_map.count(b"eigenvalue3"):
165 | out[out_map.at(b"eigenvalue3")] = l3
166 |
167 | if out_map.count(b"number_of_neighbors"):
168 | out[out_map.at(b"number_of_neighbors")] = number_of_neighbors
169 |
170 | if out_map.count(b"eigenvalue_sum"):
171 | out[out_map.at(b"eigenvalue_sum")] = eigenvalue_sum
172 |
173 | if out_map.count(b"omnivariance"):
174 | out[out_map.at(b"omnivariance")] = pow(l1 * l2 * l3, 1.0 / 3.0)
175 |
176 | if out_map.count(b"eigenentropy"):
177 | out[out_map.at(b"eigenentropy")] = -(l1 * log(l1) + l2 * log(l2) + l3 * log(l3))
178 |
179 | # Anisotropy is the difference between the most principal direction of the point subset.
180 | # Divided by l1 allows to keep this difference in a ratio between 0 and 1
181 | # a difference close to zero (l3 close to l1) means that the subset of points are equally spread in the 3 principal directions
182 | # If the anisotropy is close to 1 (mean l3 close to zero), the subset of points is strongly related only in the first principal component. It depends mainly on one direction.
183 | if out_map.count(b"anisotropy"):
184 | out[out_map.at(b"anisotropy")] = (l1 - l3) / l1
185 | if out_map.count(b"planarity"):
186 | out[out_map.at(b"planarity")] = (l2 - l3) / l1
187 | if out_map.count(b"linearity"):
188 | out[out_map.at(b"linearity")] = (l1 - l2) / l1
189 | if out_map.count(b"PCA1"):
190 | out[out_map.at(b"PCA1")] = l1 / eigenvalue_sum
191 | if out_map.count(b"PCA2"):
192 | out[out_map.at(b"PCA2")] = l2 / eigenvalue_sum
193 | # Surface variance is how the third component contributes to the sum of the eigenvalues
194 | if out_map.count(b"surface_variation"):
195 | out[out_map.at(b"surface_variation")] = l3 / eigenvalue_sum
196 | if out_map.count(b"sphericity"):
197 | out[out_map.at(b"sphericity")] = l3 / l1
198 |
199 | if out_map.count(b"verticality"):
200 | out[out_map.at(b"verticality")] = 1.0 - fabs(eigenvectors[2, 2])
201 |
202 | # eigenvectors is col-major
203 | if out_map.count(b"nx") or out_map.count(b"ny") or out_map.count(b"nz"):
204 | n0 = eigenvectors[0, 1] * eigenvectors[1, 2] - eigenvectors[0, 2] * eigenvectors[1, 1]
205 | n1 = eigenvectors[0, 2] * eigenvectors[1, 0] - eigenvectors[0, 0] * eigenvectors[1, 2]
206 | n2 = eigenvectors[0, 0] * eigenvectors[1, 1] - eigenvectors[0, 1] * eigenvectors[1, 0]
207 | norm = sqrt(n0 * n0 + n1 * n1 + n2 * n2)
208 | if out_map.count(b"nx"):
209 | out[out_map.at(b"nx")] = n0 / norm
210 | if out_map.count(b"ny"):
211 | out[out_map.at(b"ny")] = n1 / norm
212 | if out_map.count(b"nz"):
213 | out[out_map.at(b"nz")] = n2 / norm
214 |
215 | if out_map.count(b"eigenvector1x"):
216 | out[out_map.at(b"eigenvector1x")] = eigenvectors[0, 0]
217 | if out_map.count(b"eigenvector1y"):
218 | out[out_map.at(b"eigenvector1y")] = eigenvectors[0, 1]
219 | if out_map.count(b"eigenvector1z"):
220 | out[out_map.at(b"eigenvector1z")] = eigenvectors[0, 2]
221 |
222 | if out_map.count(b"eigenvector2x"):
223 | out[out_map.at(b"eigenvector2x")] = eigenvectors[1, 0]
224 | if out_map.count(b"eigenvector2y"):
225 | out[out_map.at(b"eigenvector2y")] = eigenvectors[1, 1]
226 | if out_map.count(b"eigenvector2z"):
227 | out[out_map.at(b"eigenvector2z")] = eigenvectors[1, 2]
228 |
229 | if out_map.count(b"eigenvector3x"):
230 | out[out_map.at(b"eigenvector3x")] = eigenvectors[2, 0]
231 | if out_map.count(b"eigenvector3y"):
232 | out[out_map.at(b"eigenvector3y")] = eigenvectors[2, 1]
233 | if out_map.count(b"eigenvector3z"):
234 | out[out_map.at(b"eigenvector3z")] = eigenvectors[2, 2]
235 |
236 |
237 | cdef vector[np.intp_t] *** init_result_vectors(int num_threads):
238 | """Allocate memory for result vectors, based on thread count"""
239 | threaded_vvres = PyMem_Malloc(num_threads * sizeof(void*))
240 | if not threaded_vvres:
241 | raise MemoryError()
242 | memset( threaded_vvres, 0, num_threads * sizeof(void*))
243 | for i in range(num_threads):
244 | threaded_vvres[i] = PyMem_Malloc(sizeof(void*))
245 | if not threaded_vvres[i]:
246 | raise MemoryError()
247 | memset( threaded_vvres[i], 0, sizeof(void*))
248 | threaded_vvres[i][0] = new vector[np.intp_t]()
249 | return threaded_vvres
250 |
251 |
252 | cdef void free_result_vectors(vector[np.intp_t] *** threaded_vvres, int num_threads):
253 | """Free memory for result vectors"""
254 | if threaded_vvres != NULL:
255 | for i in range(num_threads):
256 | if threaded_vvres[i] != NULL:
257 | del threaded_vvres[i][0]
258 | PyMem_Free(threaded_vvres[i])
259 | PyMem_Free(threaded_vvres)
260 |
261 |
--------------------------------------------------------------------------------
/jakteristics/las_utils.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import List, Tuple, Union
3 |
4 |
5 | import numpy as np
6 | import laspy
7 |
8 |
9 | def read_las_xyz(
10 | filename: Union[str, Path], with_offset: bool = False
11 | ) -> Union[np.array, Tuple[np.ndarray, List[float]]]:
12 | """Reads xyz coordinates of a las file, optionally as single precision floating point.
13 |
14 | Arguments:
15 | filename:
16 | The las file to read
17 | with_offset:
18 | If True, returns a tuple of a float32 array of coordinates, and the las header offset
19 | If False, returns only float64 coordinates
20 | Default: False
21 | Returns:
22 | Depending on the `with_offset` parameter, either an (n x 3) array, or
23 | a tuple of an (n x 3) array and the file offset.
24 | """
25 | with laspy.open(filename, mode="r") as las:
26 | las_data = las.read()
27 | if with_offset:
28 | offset = las.header.offset
29 | points = np.ascontiguousarray(las_data.xyz - offset, dtype="f")
30 | return points, offset
31 | else:
32 | points = np.ascontiguousarray(las_data.xyz)
33 | return points
34 |
35 |
36 | def write_with_extra_dims(
37 | input_path: Path,
38 | output_path: Path,
39 | extra_dims: np.array,
40 | extra_dims_names: List,
41 | ):
42 | """From an existing las file, create a new las file with extra dimensions
43 |
44 | Arguments:
45 | input_path: The input las file.
46 | output_path: The output las file.
47 | extra_dims: The numpy array containing geometric features.
48 | extra_dims_names: A list of names corresponding to each column of `extra_dims`.
49 | """
50 | if input_path == output_path:
51 | raise ValueError("Paths must not be the same")
52 |
53 | with laspy.open(input_path, mode="r") as in_las:
54 | header = in_las.header
55 | if extra_dims.shape[0] != header.point_count:
56 | raise ValueError(
57 | f"The features and point counts should be equal "
58 | f"{extra_dims.shape[0]} != {header.point_count}"
59 | )
60 |
61 | data = [(name, extra_dims[:, i]) for i, name in enumerate(extra_dims_names)]
62 | new_dimensions = [
63 | laspy.ExtraBytesParams(name=name, type=extra_dims.dtype, description=name)
64 | for name in extra_dims_names
65 | ]
66 |
67 | # insert new data in previous pointcloud PackedPointRecord
68 | las_data = in_las.read()
69 | las_data.add_extra_dims(new_dimensions)
70 | las_data.update_header()
71 | for name, array in data:
72 | setattr(las_data, name, array)
73 |
74 | with laspy.open(output_path, mode="w", header=las_data.header) as out_las:
75 | out_las.write_points(las_data.points)
76 |
--------------------------------------------------------------------------------
/jakteristics/main.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 | import warnings
3 |
4 | import numpy as np
5 |
6 | import jakteristics.extension
7 | from jakteristics.ckdtree import ckdtree
8 |
9 | from .constants import FEATURE_NAMES
10 |
11 |
12 | def compute_features(
13 | points: np.ndarray,
14 | search_radius: float,
15 | *,
16 | kdtree: ckdtree.cKDTree = None,
17 | num_threads: int = -1,
18 | max_k_neighbors: int = 50000,
19 | euclidean_distance: bool = True,
20 | feature_names: Optional[List[str]] = None,
21 | eps: float = 0.0,
22 | ) -> np.ndarray:
23 | """
24 | Compute features for a set of points.
25 |
26 | Parameters:
27 | points:
28 | A contiguous (n, 3) array of xyz coordinates to query.
29 | search_radius:
30 | The radius to query neighbors at each point.
31 | kdtree:
32 | If None, the kdtree is computed from the list of points.
33 | Must be an instance of `jakteristics.cKDTree`
34 | (and not `scipy.spatial.cKDTree`).
35 | num_threads:
36 | The number of threads (OpenMP) to use when doing the computation.
37 | Default: The number of cores on the machine.
38 | max_k_neighbors:
39 | The maximum number of neighbors to query
40 | Larger number will use more memory, but the neighbor points are not
41 | all kept at the same time in memory.
42 | Note: if this number is smaller, the neighbor search will not be faster.
43 | The radius is used to do the query, and the neighbors are then removed
44 | according to this parameter.
45 | euclidean_distance:
46 | How to compute the distance between 2 points.
47 | If true, the Euclidean distance is used.
48 | If false, the sum-of-absolute-values is used ("Manhattan" distance).
49 | feature_names:
50 | The feature names to compute (see `constants.FEATURE_NAMES` for possible values)
51 | Default: all features
52 | eps:
53 | Return approximate nearest neighbors; the k-th returned value
54 | is guaranteed to be no further than (1+eps) times the
55 | distance to the real k-th nearest neighbor.
56 |
57 | Returns:
58 | The computed features, one row per query point, and one column
59 | per requested feature.
60 | """
61 |
62 | if feature_names is None:
63 | warnings.warn(
64 | "The `feature_names` argument of `compute_features` will be required "
65 | "in a future version of jakteristics."
66 | )
67 | feature_names = FEATURE_NAMES
68 |
69 | points = np.ascontiguousarray(points)
70 |
71 | return jakteristics.extension.compute_features(
72 | points,
73 | search_radius,
74 | kdtree=kdtree,
75 | num_threads=num_threads,
76 | max_k_neighbors=max_k_neighbors,
77 | euclidean_distance=euclidean_distance,
78 | feature_names=feature_names,
79 | eps=eps,
80 | )
81 |
--------------------------------------------------------------------------------
/jakteristics/utils.pxd:
--------------------------------------------------------------------------------
1 | # cython: language_level=3
2 | # distutils: language = c++
3 |
4 | cdef void c_matmul_transposed(double[::1, :] a, double[::1, :] out, double alpha) nogil
5 | cdef void c_covariance(double[::1, :] a, double[::1, :] out) nogil
6 | cdef void c_eigenvectors(double[::1, :] a, double[:] eigenvalues) nogil
7 |
--------------------------------------------------------------------------------
/jakteristics/utils.pyx:
--------------------------------------------------------------------------------
1 | # cython: language_level=3
2 | # distutils: language = c++
3 |
4 | import numpy as np
5 | cimport numpy as np
6 |
7 | cimport cython
8 | from libc.math cimport fabs
9 | from scipy.linalg.cython_blas cimport dsyrk
10 | from scipy.linalg.cython_lapack cimport dsyev
11 |
12 | cdef extern from "" namespace "std" nogil:
13 | void sort[Iter](Iter first, Iter last)
14 |
15 |
16 | @cython.boundscheck(False)
17 | @cython.wraparound(False)
18 | @cython.cdivision(True)
19 | cdef inline void c_matmul_transposed(double[::1, :] a, double[::1, :] out, double alpha) nogil:
20 | cdef:
21 | char *uplo = 'U'
22 | char *trans = 'N'
23 | int n = a.shape[0]
24 | int k = a.shape[1]
25 | int lda = a.strides[1] // sizeof(double) # for column major
26 | int ldc = out.strides[1] // sizeof(double) # for column major
27 | double beta = 0.0
28 |
29 | dsyrk(uplo, trans, &n, &k, &alpha, &a[0,0], &lda, &beta, &out[0,0], &ldc)
30 |
31 | cdef int i, j
32 |
33 | # dsyrk computes results for the upper matrix only
34 | for i in range(n):
35 | for j in range(n):
36 | if i > j:
37 | out[i, j] = out[j, i]
38 |
39 |
40 | def py_matmul_transposed(a):
41 | out = np.empty((a.shape[0], a.shape[0]), dtype=np.float64, order="F")
42 | c_matmul_transposed(np.asfortranarray(a), out, 1.0)
43 | return out
44 |
45 |
46 | @cython.boundscheck(False)
47 | @cython.wraparound(False)
48 | @cython.cdivision(True)
49 | cpdef inline void substract_mean(double[::1, :] a) nogil:
50 | """a must be of shape (3, n)"""
51 | cdef:
52 | int n_cols = a.shape[1]
53 | double *mean = [0, 0, 0]
54 | int c
55 |
56 | for c in range(n_cols):
57 | mean[0] += a[0, c]
58 | mean[1] += a[1, c]
59 | mean[2] += a[2, c]
60 |
61 | mean[0] /= n_cols
62 | mean[1] /= n_cols
63 | mean[2] /= n_cols
64 |
65 | for c in range(n_cols):
66 | a[0, c] -= mean[0]
67 | a[1, c] -= mean[1]
68 | a[2, c] -= mean[2]
69 |
70 |
71 | @cython.boundscheck(False)
72 | @cython.wraparound(False)
73 | @cython.cdivision(True)
74 | cdef inline void c_covariance(double[::1, :] a, double[::1, :] out) nogil:
75 | """a must be of shape (3, n)"""
76 | cdef:
77 | int n_rows = a.shape[0]
78 | int i, j
79 | double fact
80 |
81 | substract_mean(a)
82 |
83 | fact = 1.0 / (a.shape[1] - 1)
84 |
85 | c_matmul_transposed(a, out, fact)
86 |
87 |
88 | def py_covariance(a):
89 | out = np.empty((a.shape[0], a.shape[0]), dtype=np.float64, order="F")
90 | c_covariance(np.asfortranarray(a), out)
91 | return out
92 |
93 |
94 | @cython.boundscheck(False)
95 | @cython.wraparound(False)
96 | @cython.cdivision(True)
97 | cdef inline void c_eigenvectors(double[::1, :] a, double[:] eigenvalues) nogil:
98 | """
99 | http://www.netlib.org/lapack/explore-html/d3/d88/group__real_s_yeigen_ga63d8d12aef8f2711d711d9e6bd833e46.html#ga63d8d12aef8f2711d711d9e6bd833e46
100 | """
101 | cdef:
102 | char *jobz = 'V' # compute eigenvalues and eigenvectors
103 | char *uplo = 'U' # upper triangle of A is stored
104 | int n = a.shape[0]
105 | int lda = a.strides[1] // sizeof(double) # for column major
106 | double work[256] # not sure how to properly set the size of this array
107 | int lwork = 256
108 | int info
109 |
110 | dsyev(jobz, uplo, &n, &a[0,0], &lda, &eigenvalues[0], &work[0], &lwork, &info)
111 |
112 | c_sort_eigenvalues(a, eigenvalues)
113 |
114 |
115 | @cython.boundscheck(False)
116 | @cython.wraparound(False)
117 | cdef void c_sort_eigenvalues(double[::1, :] eigenvectors, double[:] eigenvalues) nogil:
118 | """Sort eigenvalues and eigenvectors"""
119 | cdef:
120 | int i
121 | int j
122 | int k
123 | int n = 3
124 | double eigenvalues_copy[3]
125 | double eigenvectors_copy[3][3]
126 |
127 | for i in range(n):
128 | eigenvalues[i] = fabs(eigenvalues[i])
129 |
130 | eigenvalues_copy[i] = eigenvalues[i]
131 | for j in range(n):
132 | eigenvectors_copy[i][j] = eigenvectors[i, j]
133 |
134 | sort(&eigenvalues[0], (&eigenvalues[0]) + n)
135 | # reverse
136 | cdef double temp = eigenvalues[2]
137 | eigenvalues[2] = eigenvalues[0]
138 | eigenvalues[0] = temp
139 |
140 | for i in range(n):
141 | for j in range(n):
142 | if fabs(eigenvalues[i] - eigenvalues_copy[j]) < 1e-5:
143 | for k in range(n):
144 | # eigenvectors is column-major, and eigenvectors_copy is row major
145 | eigenvectors[i, k] = eigenvectors_copy[k][j]
146 | break
147 |
148 |
149 | def py_eigenvectors(a):
150 | eigenvalues = np.empty(a.shape[0], dtype=np.float64, order="F")
151 | a = np.asfortranarray(a)
152 | c_eigenvectors(a, eigenvalues)
153 | return eigenvalues, a
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel", "Cython>=0.25", "numpy", "scipy"]
3 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | wheel
2 | black
3 | bump2version
4 | pytest
5 | pytest-runner
6 | twine
7 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | laspy>=2.1.1
2 | scipy
3 | typer
4 | Cython>=0.25
5 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 | from os.path import join
4 | from setuptools import Extension, setup
5 | from setuptools.command.build_ext import build_ext
6 |
7 | from Cython.Build import cythonize
8 | import numpy
9 |
10 |
11 | LINUX = sys.platform == "linux"
12 | WINDOWS = sys.platform == "win32"
13 |
14 |
15 | # Avoid a gcc warning below:
16 | # cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid
17 | # for C/ObjC but not for C++
18 | class BuildExt(build_ext):
19 | def build_extensions(self):
20 | if LINUX:
21 | if "-Wstrict-prototypes" in self.compiler.compiler_so:
22 | self.compiler.compiler_so.remove("-Wstrict-prototypes")
23 | super().build_extensions()
24 |
25 |
26 | ckdtree = join("jakteristics", "ckdtree")
27 | ckdtree_src = [
28 | join(ckdtree, "ckdtree", "src", x)
29 | for x in [
30 | "query.cxx",
31 | "build.cxx",
32 | "query_pairs.cxx",
33 | "count_neighbors.cxx",
34 | "query_ball_point.cxx",
35 | "query_ball_tree.cxx",
36 | "sparse_distances.cxx",
37 | ]
38 | ]
39 | ckdtree_src.append(join(ckdtree, "ckdtree.pyx"))
40 |
41 | ckdtree_includes = [
42 | numpy.get_include(),
43 | join(ckdtree, "ckdtree", "src"),
44 | join(ckdtree, "_lib"),
45 | ]
46 |
47 | extra_compile_args = ["-fopenmp"]
48 | extra_link_args = ["-fopenmp"]
49 |
50 | if WINDOWS:
51 | extra_compile_args = ["/openmp"]
52 | extra_link_args = ["/openmp"]
53 |
54 | ext_modules = [
55 | Extension(
56 | "jakteristics.extension",
57 | sources=[
58 | "jakteristics/extension.pyx",
59 | join(ckdtree, "ckdtree", "src", "query_ball_point.cxx"),
60 | ],
61 | include_dirs=ckdtree_includes,
62 | extra_compile_args=extra_compile_args,
63 | extra_link_args=extra_link_args,
64 | define_macros=[("NPY_NO_DEPRECATED_API", "1")],
65 | ),
66 | Extension(
67 | "jakteristics.utils",
68 | sources=["jakteristics/utils.pyx"],
69 | include_dirs=[numpy.get_include()],
70 | define_macros=[("NPY_NO_DEPRECATED_API", "1")],
71 | ),
72 | Extension(
73 | "jakteristics.ckdtree.ckdtree",
74 | sources=ckdtree_src,
75 | include_dirs=ckdtree_includes,
76 | define_macros=[("NPY_NO_DEPRECATED_API", "1")],
77 | ),
78 | ]
79 |
80 | ext_modules = cythonize(ext_modules, language_level=3)
81 | cmdclass = {"build_ext": BuildExt}
82 |
83 |
84 | with open("README.rst") as readme_file:
85 | readme = readme_file.read()
86 |
87 | with open("HISTORY.rst") as history_file:
88 | history = history_file.read().replace(".. :changelog:", "")
89 |
90 | requirements = [line for line in open("requirements.txt").read().split("\n") if line]
91 |
92 | about = open(join("jakteristics", "__about__.py")).read()
93 | version = re.search(r"__version__ ?= ?['\"](.+)['\"]", about).group(1)
94 |
95 | setup(
96 | name="jakteristics",
97 | version=version,
98 | description="Point cloud geometric properties from python.",
99 | long_description=readme + "\n\n" + history,
100 | author="David Caron",
101 | author_email="david.caron@jakarto.com",
102 | url="https://github.com/jakarto3d/jakteristics",
103 | packages=["jakteristics"],
104 | package_dir={"jakteristics": "jakteristics"},
105 | package_data={"": ["*.pyx", "*.pxd", "*.h", "*.cpp"]},
106 | python_requires=">=3.7",
107 | install_requires=requirements,
108 | tests_require=["pytest"],
109 | license="BSD",
110 | zip_safe=False,
111 | keywords="jakteristics",
112 | classifiers=[
113 | "Development Status :: 2 - Pre-Alpha",
114 | "Intended Audience :: Developers",
115 | "License :: OSI Approved :: BSD License",
116 | "Natural Language :: English",
117 | "Programming Language :: Cython",
118 | "Programming Language :: Python :: 3",
119 | "Programming Language :: Python :: 3.8",
120 | ],
121 | test_suite="tests",
122 | cmdclass=cmdclass,
123 | ext_modules=ext_modules,
124 | entry_points={"console_scripts": ["jakteristics = jakteristics.__main__:app"]},
125 | )
126 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import shutil
3 |
4 | import pytest
5 |
6 | TEST_DATA = Path(__file__).parent / "data"
7 |
8 |
9 | @pytest.fixture
10 | def temp_dir():
11 | tmp = TEST_DATA / "tmp"
12 | tmp.mkdir(parents=True, exist_ok=True)
13 | yield tmp
14 | shutil.rmtree(tmp, ignore_errors=True)
15 |
--------------------------------------------------------------------------------
/tests/data/test_0.02_seconde.las:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakarto3d/jakteristics/6a22e0072a42cf4b41a04a6e214fd7ef3df5ec67/tests/data/test_0.02_seconde.las
--------------------------------------------------------------------------------
/tests/test_cli.py:
--------------------------------------------------------------------------------
1 | from jakteristics.constants import FEATURE_NAMES
2 | from pathlib import Path
3 | import shutil
4 |
5 | import pytest
6 | import typer
7 | from typer.testing import CliRunner
8 |
9 | from jakteristics.__main__ import typer_main
10 |
11 |
12 | runner = CliRunner()
13 | app = typer.Typer()
14 | app.command()(typer_main)
15 |
16 | TEST_DATA = Path(__file__).parent / "data"
17 |
18 |
19 | def test_cli_help():
20 | result = runner.invoke(app, ["--help"])
21 | assert result.exit_code == 0
22 |
23 |
24 | def test_cli_show_features():
25 | result = runner.invoke(app, ["--show-features"])
26 | assert result.stdout == "\n".join(FEATURE_NAMES) + "\n"
27 | assert result.exit_code == 0
28 |
29 |
30 | def test_cli_invalid_feature_name(temp_dir):
31 | input_file = TEST_DATA / "test_0.02_seconde.las"
32 | output_file = temp_dir / "out.las"
33 | result = runner.invoke(
34 | app,
35 | [
36 | str(input_file),
37 | str(output_file),
38 | "--search-radius",
39 | "0.15",
40 | "--feature",
41 | "bananas",
42 | ],
43 | )
44 | assert result.exit_code == 2
45 | assert "invalid choice: bananas" in result.stdout
46 |
47 |
48 | def test_cli_run_basic(temp_dir):
49 | input_file = TEST_DATA / "test_0.02_seconde.las"
50 | output_file = temp_dir / "non_existing_folder" / "out.las"
51 | result = runner.invoke(
52 | app,
53 | [
54 | str(input_file),
55 | str(output_file),
56 | "--search-radius",
57 | "0.15",
58 | "--num-threads",
59 | "4",
60 | ],
61 | )
62 | assert result.exit_code == 0
63 | assert output_file.exists
64 |
--------------------------------------------------------------------------------
/tests/test_features.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import laspy
4 | import numpy as np
5 | import pytest
6 |
7 | import jakteristics
8 | from jakteristics import FEATURE_NAMES, extension, las_utils, utils
9 |
10 |
11 | data_dir = Path(__file__).parent / "data"
12 |
13 |
14 | def test_matmul_transposed():
15 | points = np.random.rand(3, 4).astype("d")
16 | np_dot = np.dot(points, points.T)
17 |
18 | result = utils.py_matmul_transposed(points)
19 |
20 | assert np.allclose(np_dot, result)
21 |
22 |
23 | def test_substract_mean():
24 | points = np.random.rand(3, 4).astype("d")
25 | expected = points - points.mean(axis=1)[:, None]
26 | result = np.asfortranarray(points.copy())
27 | utils.substract_mean(result)
28 |
29 | assert np.allclose(expected, result)
30 |
31 |
32 | def test_covariance():
33 | points = np.random.rand(3, 4).astype("d")
34 | np_cov = np.cov(points)
35 |
36 | cov = utils.py_covariance(points)
37 |
38 | assert np.allclose(np_cov, cov)
39 |
40 |
41 | def test_eigenvalues():
42 | # --- given ---
43 | points = np.random.rand(3, 4).astype("d")
44 | np_cov = np.asfortranarray(np.cov(points).astype("d"))
45 |
46 | np_eigenvalues, np_eigenvectors = np.linalg.eig(np_cov)
47 | np_eigenvalues = np.abs(np_eigenvalues)
48 |
49 | # reorder eigenvectors before comparison
50 | argsort = list(reversed(np.argsort(np_eigenvalues)))
51 | np_eigenvectors = np.array([np_eigenvectors[:, i] for i in argsort])
52 |
53 | # --- when ---
54 | eigenvalues, eigenvectors = utils.py_eigenvectors(np_cov)
55 |
56 | # flip eigenvectors that are in the opposite direction (for comparison)
57 | for i in range(3):
58 | same_sign = (
59 | eigenvectors[i, 0] < 0
60 | and np_eigenvectors[i, 0] < 0
61 | or eigenvectors[i, 0] > 0
62 | and np_eigenvectors[i, 0] > 0
63 | )
64 | if not same_sign:
65 | np_eigenvectors[i, :] *= -1
66 |
67 | # --- then ---
68 | assert np.allclose(eigenvalues, np_eigenvalues[argsort])
69 |
70 | assert np.allclose(np_eigenvectors, eigenvectors, atol=1e-4)
71 |
72 |
73 | def test_compute_features():
74 | n_points = 1000
75 | points = np.random.random((n_points, 3)) * 10
76 |
77 | features = extension.compute_features(points, 0.15, feature_names=FEATURE_NAMES)
78 |
79 | assert features.shape == (n_points, len(FEATURE_NAMES))
80 |
81 |
82 | def test_compute_some_features():
83 | input_path = data_dir / "test_0.02_seconde.las"
84 | xyz = las_utils.read_las_xyz(input_path)
85 | n_points = xyz.shape[0]
86 | all_features = extension.compute_features(xyz, 0.15, feature_names=FEATURE_NAMES)
87 |
88 | for name in FEATURE_NAMES:
89 | features = extension.compute_features(xyz, 0.15, feature_names=[name])
90 | index = FEATURE_NAMES.index(name)
91 |
92 | assert features.shape == (n_points, 1)
93 | assert np.allclose(all_features[:, index], features.reshape(-1), equal_nan=True)
94 |
95 |
96 | def test_write_extra_dims(tmp_path):
97 | input_path = data_dir / "test_0.02_seconde.las"
98 | output_path = tmp_path / "test_output.las"
99 |
100 | xyz = las_utils.read_las_xyz(input_path)
101 |
102 | features = extension.compute_features(xyz, 0.15, feature_names=FEATURE_NAMES)
103 |
104 | las_utils.write_with_extra_dims(input_path, output_path, features, FEATURE_NAMES)
105 |
106 | output_features = []
107 | with laspy.open(output_path, mode="r") as las:
108 | las_data = las.read()
109 | xyz_out = las_data.xyz
110 | for spec in las.header.point_format.extra_dimensions:
111 | name = spec.name.encode().replace(b"\x00", b"").decode()
112 | output_features.append(getattr(las_data, name))
113 |
114 | output_features = np.vstack(output_features).T
115 |
116 | assert np.allclose(xyz, xyz_out)
117 | assert np.allclose(features, output_features, equal_nan=True)
118 |
119 |
120 | def test_not_contiguous():
121 | points = np.random.random((3, 1000)).T
122 |
123 | features = jakteristics.compute_features(points, 0.15, feature_names=FEATURE_NAMES)
124 |
125 | assert features.shape == (1000, len(FEATURE_NAMES))
126 |
127 |
128 | def test_wrong_shape():
129 | points = np.random.random((3, 1000))
130 |
131 | with pytest.raises(ValueError):
132 | extension.compute_features(points, 0.15, feature_names=FEATURE_NAMES)
133 |
134 |
135 | def test_nan():
136 | points = np.random.random((3, 1000)).T
137 |
138 | # compute kdtree where points are not located
139 | kdtree = jakteristics.cKDTree((points + 2).copy())
140 |
141 | features = jakteristics.compute_features(
142 | points, 0.15, kdtree=kdtree, feature_names=FEATURE_NAMES
143 | )
144 | assert np.all(np.isnan(features))
145 |
146 |
147 | def test_with_kdtree_not_same_point_count():
148 | points = np.random.random((3, 1000)).T
149 |
150 | kdtree = jakteristics.cKDTree((points).copy())
151 | features = jakteristics.compute_features(
152 | points[::100], 0.30, kdtree=kdtree, feature_names=FEATURE_NAMES
153 | )
154 |
155 | assert not np.any(np.isnan(features))
156 |
157 | assert features.shape == (10, len(FEATURE_NAMES))
158 |
--------------------------------------------------------------------------------
/tests/test_las_utils.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import numpy as np
4 | import pytest
5 |
6 | from jakteristics import las_utils
7 |
8 |
9 | TEST_DATA = Path(__file__).parent / "data"
10 |
11 |
12 | def test_read_las():
13 | input_file = TEST_DATA / "test_0.02_seconde.las"
14 | xyz = las_utils.read_las_xyz(input_file)
15 | assert xyz.shape == (10310, 3)
16 | assert xyz.dtype == np.float64
17 |
18 |
19 | def test_read_las_offset():
20 | input_file = TEST_DATA / "test_0.02_seconde.las"
21 | xyz, offset = las_utils.read_las_xyz(input_file, with_offset=True)
22 | assert xyz.shape == (10310, 3)
23 | assert xyz.dtype == np.float32
24 | assert np.allclose(offset, np.array([362327.0, 5157620.0, 106.271]))
25 |
26 |
27 | def test_write_extra_dims_same_path():
28 | input_file = TEST_DATA / "test_0.02_seconde.las"
29 | with pytest.raises(ValueError):
30 | las_utils.write_with_extra_dims(
31 | input_file, input_file, np.array([1, 2, 3]), ["test_dim"]
32 | )
33 |
34 |
35 | def test_write_extra_dims_wrong_point_count(temp_dir):
36 | input_file = TEST_DATA / "test_0.02_seconde.las"
37 | output_file = temp_dir / "out.las"
38 | with pytest.raises(ValueError) as e:
39 | las_utils.write_with_extra_dims(
40 | input_file, output_file, np.array([1, 2, 3]).T, ["test_dim"]
41 | )
42 |
--------------------------------------------------------------------------------