Skip to content

Commit

Permalink
Drop scipy convex hull (#21)
Browse files Browse the repository at this point in the history
* add convex_hull

* update

* convex hull

* normalize index

* updatec

* bind more

* fix spelling

* test include colinear

* more tests

* better API

* ready to release

* update docs

* update docs

* scipy to test requires
  • Loading branch information
district10 authored Apr 5, 2023
1 parent ce9926b commit b67c38e
Show file tree
Hide file tree
Showing 16 changed files with 463 additions and 238 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ _generate/
*env*
wheelhouse
!test.py
site
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
"slist": "cpp",
"future": "cpp",
"regex": "cpp",
"core": "cpp"
"core": "cpp",
"specialfunctions": "cpp"
},
"workbench.colorCustomizations": {
"activityBar.background": "#143328",
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test_in_linux:

PYTHON ?= python3
python_install:
$(PYTHON) setup.py install
$(PYTHON) setup.py install --force
python_build:
$(PYTHON) setup.py bdist_wheel
python_sdist:
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Credits goes to:

Online document: <https://concave-hull.readthedocs.io/en/latest/>

<!--intro-start-->

## Install

### via pip
Expand Down Expand Up @@ -48,14 +50,18 @@ concave_hull_indexes(
*,
concavity: float = 2.0,
length_threshold: float = 0.0,
convex_hull_indexes: numpy.ndarray[numpy.int32[m, 1]] = None, # will use integrated scipy ConvexHull if None
# you can just ignore "convex_hull_indexes"
convex_hull_indexes: numpy.ndarray[numpy.int32[m, 1]] = None,
) -> numpy.ndarray[numpy.int32[m, 1]]

# get concave hull points
concave_hull(
points: Union[numpy.ndarray, List, Tuple],
*args, *kwargs # will proxy all to covcave_hull_indexes
... # same as
) -> Union[numpy.ndarray, List, Tuple]

# P.S., we provide convex_hull (Graham scan)
from concave_hull import convex_hull, convex_hull_indexes
```

- `concavity` is a relative measure of concavity. 1 results in a relatively
Expand Down Expand Up @@ -98,9 +104,6 @@ for simplex in convex_hull.simplices:
idxes = concave_hull_indexes(
points[:, :2],
length_threshold=50,
# for concave_hull>=0.0.3
# it's not necessary to provide convex_hull_indexes
# convex_hull_indexes=convex_hull.vertices.astype(np.int32),
)
# you can get coordinates by `points[idxes]`
assert np.all(points[idxes] == concave_hull(points, length_threshold=50))
Expand All @@ -112,6 +115,8 @@ for f, t in zip(idxes[:-1], idxes[1:]): # noqa
plt.show()
```

<!--intro-end-->

## Tests

```
Expand Down
60 changes: 53 additions & 7 deletions concave_hull/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from typing import List, Tuple, Union

import numpy as np
import pybind11_concave_hull # noqa
from pybind11_concave_hull import __version__ # noqa
from pybind11_concave_hull import clockwise, colinear # noqa
from pybind11_concave_hull import ( # noqa
concave_hull_indexes as concave_hull_indexes_impl,
)
from pybind11_concave_hull import wgs84_to_east_north
from scipy.spatial import ConvexHull
from pybind11_concave_hull import convex_hull_indexes as convex_hull_indexes_impl
from pybind11_concave_hull import orientation, wgs84_to_east_north # noqa


def concave_hull_indexes(
points: Union[np.ndarray, List, Tuple],
*,
concavity: float = 2.0,
length_threshold: float = 0.0,
convex_hull_indexes: np.ndarray = None,
convex_hull_indexes: np.ndarray = None, # noqa
is_wgs84: bool = False,
):
"""
Expand All @@ -35,8 +37,7 @@ def concave_hull_indexes(
if is_wgs84:
points = wgs84_to_east_north(points)
if convex_hull_indexes is None:
convex_hull = ConvexHull(points)
convex_hull_indexes = convex_hull.vertices.astype(np.int32)
convex_hull_indexes = convex_hull_indexes_impl(points)
return concave_hull_indexes_impl(
points,
concavity=concavity,
Expand All @@ -45,8 +46,53 @@ def concave_hull_indexes(
)


def concave_hull(points: Union[np.ndarray, List, Tuple], *args, **kwargs):
indexes = concave_hull_indexes(points, *args, **kwargs)
def concave_hull(
points: Union[np.ndarray, List, Tuple],
*,
concavity: float = 2.0,
length_threshold: float = 0.0,
convex_hull_indexes: np.ndarray = None, # noqa
is_wgs84: bool = False,
):
indexes = concave_hull_indexes(
points,
concavity=concavity,
length_threshold=length_threshold,
convex_hull_indexes=convex_hull_indexes,
is_wgs84=is_wgs84,
)
return (
points[indexes]
if isinstance(points, np.ndarray)
else [points[i] for i in indexes]
)


def convex_hull_indexes(
points: Union[np.ndarray, List, Tuple],
*,
include_colinear: bool = False,
order_only: bool = False,
):
points = np.asarray(points, dtype=np.float64)
return convex_hull_indexes_impl(
points[:, :2],
include_colinear=include_colinear,
order_only=order_only,
)


def convex_hull(
points: Union[np.ndarray, List, Tuple],
*,
include_colinear: bool = False,
order_only: bool = False,
):
indexes = convex_hull_indexes(
points,
include_colinear=include_colinear,
order_only=order_only,
)
return (
points[indexes]
if isinstance(points, np.ndarray)
Expand Down
4 changes: 4 additions & 0 deletions docs/about/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ To upgrade `concave_hull` to the latest version, use pip:
pip install -U concave_hull
```

## Version 0.0.6 (2023-04-05)

* Integrate Grahams-Scan algorithm, drop scipy dependency

## Version 0.0.5 (2023-03-04)

* Add macOS, Linux arm64 release
Expand Down
Binary file modified docs/hull.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 6 additions & 106 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,114 +2,14 @@

A very fast 2D concave hull algorithm.

Released as a python package for easy integration. All credits goes to:

- <https://github.com/mapbox/concaveman>
- <https://github.com/sadaszewski/concaveman-cpp>

## Install

### via pip

```bash
pip install -U concave_hull
```

### from source

```bash
git clone --recursive https://github.com/cubao/concave_hull
pip install ./concave_hull
```

Or

```bash
pip install git+https://github.com/cubao/concave_hull.git
```

(you can build wheels for later reuse by ` pip wheel git+https://github.com/cubao/concave_hull.git`)

## Usage

Signature:

```python
# import
from concave_hull import concave_hull, concave_hull_indexes

# get concave hull indexes
concave_hull_indexes(
points: Union[numpy.ndarray, List, Tuple],
*,
concavity: float = 2.0,
length_threshold: float = 0.0,
convex_hull_indexes: numpy.ndarray[numpy.int32[m, 1]] = None, # will use integrated scipy ConvexHull if None
) -> numpy.ndarray[numpy.int32[m, 1]]

# get concave hull points
concave_hull(
points: Union[numpy.ndarray, List, Tuple],
*args, *kwargs # will proxy all to covcave_hull_indexes
) -> Union[numpy.ndarray, List, Tuple]
```

- `concavity` is a relative measure of concavity. 1 results in a relatively
detailed shape, Infinity results in a convex hull. You can use values lower
than 1, but they can produce pretty crazy shapes.
- `length_threshold`: when a segment length is under this threshold, it stops
being considered for further detalization. Higher values result in simpler
shapes.

(document from <https://github.com/mapbox/concaveman>)

Example (see full code in [`test.py`](https://github.com/cubao/concave_hull/blob/master/test.py)):

```python
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import ConvexHull

from concave_hull import concave_hull, concave_hull_indexes

points = []
c = np.array([250, 250])
for x in np.arange(100, 400, 5 * np.pi):
for y in np.arange(100, 400, 5 * np.pi):
if x > c[0] and y > c[1]:
continue
r = np.linalg.norm(c - [x, y])
if r > 150:
continue
points.append([x, y])
points = np.array(points)
convex_hull = ConvexHull(points[:, :2]) # it's already N-by-2, I'm just emphasizing

# https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.ConvexHull.html

plt.plot(points[:, 0], points[:, 1], "o")
for simplex in convex_hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1], "g-", alpha=0.5)

idxes = concave_hull_indexes(
points[:, :2],
length_threshold=50,
# for concave_hull>=0.0.3
# it's not necessary to provide convex_hull_indexes
# convex_hull_indexes=convex_hull.vertices.astype(np.int32),
)
# you can get coordinates by `points[idxes]`
assert np.all(points[idxes] == concave_hull(points, length_threshold=50))

for f, t in zip(idxes[:-1], idxes[1:]): # noqa
seg = points[[f, t]]
plt.plot(seg[:, 0], seg[:, 1], "r-", alpha=0.5)
# plt.savefig('hull.png')
plt.show()
```

![](hull.png)

{%
include-markdown "../README.md"
start="<!--intro-start-->"
end="<!--intro-end-->"
%}

<div class="text-center">
<a href="https://github.com/cubao/concave_hull" class="btn btn-primary" role="button">Code on GitHub</a>
<a href="https://pypi.org/project/concave-hull" class="btn btn-primary" role="button">Package on PyPi</a>
Expand Down
3 changes: 2 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
mkdocs>=1.3.0
mkdocs==1.4.2
mkdocs-include-markdown-plugin==4.0.3
10 changes: 10 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Usage

## test_basic.py

```python
{%
include-markdown "../tests/test_basic.py"
comments=false
%}
```
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ edit_uri: blob/master/docs/
theme: readthedocs
nav:
- Home: index.md
- Usage: usage.md
- WGS84: wgs84.md
- About:
- Release Notes: about/release-notes.md
- License: about/license.md

plugins:
- include-markdown
copyright: Copyright &copy; 2023 <a href="https://cubao.readthedocs.io/">cubao team</a>.
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def build_extension(self, ext):
# logic and declaration, and simpler if you include description/version in a file.
setup(
name="concave_hull",
version="0.0.5",
version="0.0.6",
author="tzx",
author_email="[email protected]",
url="https://concave-hull.readthedocs.io",
Expand All @@ -133,6 +133,6 @@ def build_extension(self, ext):
ext_modules=[CMakeExtension("concave_hull")],
cmdclass={"build_ext": CMakeBuild},
zip_safe=False,
install_requires=["numpy", "scipy"],
extras_require={"test": ["pytest>=6.0"]},
install_requires=["numpy"],
extras_require={"test": ["pytest>=6.0", "scipy"]},
)
Loading

0 comments on commit b67c38e

Please sign in to comment.