Skip to content

Commit

Permalink
Feature/update api (#11)
Browse files Browse the repository at this point in the history
* clean CI

* add python part, add iter implementation

* similar API as rdp

* add fhirschmann/rdp test

* test pass

* update readme

* lint code

* fix

* 0.1.2

* release
  • Loading branch information
district10 authored Mar 2, 2023
1 parent a2c8031 commit 7f5c0dd
Show file tree
Hide file tree
Showing 14 changed files with 485 additions and 129 deletions.
7 changes: 0 additions & 7 deletions .github/dependabot.yml

This file was deleted.

48 changes: 0 additions & 48 deletions .github/workflows/conda.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
fail-fast: false
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
python-version: ["3.6", "3.8", "3.11-dev"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]

runs-on: ${{ matrix.platform }}

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ repos:

# Sort your imports in a standard form
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
rev: 5.11.5
hooks:
- id: isort

Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ set(CMAKE_CXX_STANDARD 14)
set(PYBIND11_CPP_STANDARD -std=c++14)

add_subdirectory(pybind11)
pybind11_add_module(pybind11_rdp src/main.cpp)
pybind11_add_module(_pybind11_rdp src/main.cpp)

# EXAMPLE_VERSION_INFO is defined by setup.py and passed into the C++ code as a
# define (VERSION_INFO) here.
target_compile_definitions(pybind11_rdp
target_compile_definitions(_pybind11_rdp
PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO})
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ python_sdist:
$(PYTHON) setup.py sdist
# tar -tvf dist/pybind11_rdp-*.tar.gz
python_test:
$(PYTHON) -m pip install pytest rdp
$(PYTHON) -c 'from pybind11_rdp import rdp; print(rdp([[1, 1], [2, 2], [3, 3], [4, 4]]))'
pytest tests
$(PYTHON) test.py

# conda create -y -n py36 python=3.6
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,27 @@ array([[1, 1],
## Tests

```
python3 test.py
make python_install
make python_test
```

## Links
## Notice

- https://github.com/fhirschmann/rdp
As <https://github.com/fhirschmann/rdp/issues/13> points out, `pdist` in `rdp` is **WRONGLY** Point-to-Line distance.
We use Point-to-LineSegment distance.

```
from rdp import rdp
print(rdp([[0, 0], [10, 0.1], [1, 0]], epsilon=1.0)) # wrong
# [[0.0, 0.0],
# [1.0, 0.0]]
from pybind11_rdp import rdp
print(rdp([[0, 0], [10, 0.1], [1, 0]], epsilon=1.0)) # correct
# [[ 0. 0. ]
# [10. 0.1]
# [ 1. 0. ]]
```

## References

Expand Down
39 changes: 0 additions & 39 deletions conda.recipe/meta.yaml

This file was deleted.

39 changes: 39 additions & 0 deletions pybind11_rdp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sys

import numpy as np
from _pybind11_rdp import LineSegment # noqa
from _pybind11_rdp import __version__ # noqa
from _pybind11_rdp import rdp as _rdp # noqa
from _pybind11_rdp import rdp_mask as _rdp_mask # noqa


def __notify_dist_fn(dist):
if dist is None:
return
print(
"we don't support dist function, the only built-in dist function is dist(point,line_segment) (NOT dist(point,line))",
file=sys.stderr,
)


def rdp_rec(points, epsilon: float, dist=None):
__notify_dist_fn(dist)
points = np.asarray(points, dtype=np.float64)
return _rdp(points, epsilon=epsilon, recursive=True)


def rdp_iter(points, epsilon: float, dist=None, return_mask=False):
__notify_dist_fn(dist)
points = np.asarray(points, dtype=np.float64)
if return_mask:
return _rdp_mask(points, epsilon=epsilon, recursive=False)
return _rdp(points, epsilon=epsilon, recursive=False)


def rdp(points, epsilon: float = 0.0, dist=None, algo="iter", return_mask=False):
__notify_dist_fn(dist)
points = np.asarray(points, dtype=np.float64)
recursive = "iter" != algo
if return_mask:
return _rdp_mask(points, epsilon=epsilon, recursive=recursive)
return _rdp(points, epsilon=epsilon, recursive=recursive)
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import subprocess
import sys

from setuptools import Extension, setup
from setuptools import Extension, find_packages, setup
from setuptools.command.build_ext import build_ext

# Convert distutils Windows platform specifiers to CMake -A arguments
Expand Down Expand Up @@ -122,13 +122,14 @@ def build_extension(self, ext):
# logic and declaration, and simpler if you include description/version in a file.
setup(
name="pybind11_rdp",
version="0.1.1",
version="0.1.2",
author="tzx",
author_email="[email protected]",
url="https://github.com/cubao/pybind11-rdp",
description="C++/pybind11/NumPy implementation of the Ramer-Douglas-Peucker algorithm (Ramer 1972; Douglas and Peucker 1973) for 2D and 3D data.",
long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
packages=find_packages(),
ext_modules=[CMakeExtension("pybind11_rdp")],
cmdclass={"build_ext": CMakeBuild},
zip_safe=False,
Expand Down
88 changes: 66 additions & 22 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <pybind11/iostream.h>
#include <pybind11/pybind11.h>

#include <queue>

#define STRINGIFY(x) #x
#define MACRO_STRINGIFY(x) STRINGIFY(x)

Expand Down Expand Up @@ -70,13 +72,48 @@ void douglas_simplify(const Eigen::Ref<const RowVectors> &coords,
douglas_simplify(coords, to_keep, max_index, j, epsilon);
}

void douglas_simplify_iter(const Eigen::Ref<const RowVectors> &coords,
Eigen::VectorXi &to_keep, const double epsilon)
{
std::queue<std::pair<int, int>> q;
q.push({0, to_keep.size() - 1});
while (!q.empty()) {
int i = q.front().first;
int j = q.front().second;
q.pop();
to_keep[i] = to_keep[j] = 1;
if (j - i <= 1) {
continue;
}
LineSegment line(coords.row(i), coords.row(j));
double max_dist2 = 0.0;
int max_index = i;
for (int k = i + 1; k < j; ++k) {
double dist2 = line.distance2(coords.row(k));
if (dist2 > max_dist2) {
max_dist2 = dist2;
max_index = k;
}
}
if (max_dist2 <= epsilon * epsilon) {
continue;
}
q.push({i, max_index});
q.push({max_index, j});
}
}

Eigen::VectorXi
douglas_simplify_mask(const Eigen::Ref<const RowVectors> &coords,
double epsilon)
double epsilon, bool recursive)
{
Eigen::VectorXi mask(coords.rows());
mask.setZero();
douglas_simplify(coords, mask, 0, mask.size() - 1, epsilon);
if (recursive) {
douglas_simplify(coords, mask, 0, mask.size() - 1, epsilon);
} else {
douglas_simplify_iter(coords, mask, epsilon);
}
return mask;
}

Expand All @@ -93,9 +130,9 @@ Eigen::VectorXi mask2indexes(const Eigen::Ref<const Eigen::VectorXi> &mask)

Eigen::VectorXi
douglas_simplify_indexes(const Eigen::Ref<const RowVectors> &coords,
double epsilon)
double epsilon, bool recursive)
{
return mask2indexes(douglas_simplify_mask(coords, epsilon));
return mask2indexes(douglas_simplify_mask(coords, epsilon, recursive));
}

RowVectors select_by_mask(const Eigen::Ref<const RowVectors> &coords,
Expand All @@ -112,15 +149,16 @@ RowVectors select_by_mask(const Eigen::Ref<const RowVectors> &coords,
}

inline RowVectors douglas_simplify(const Eigen::Ref<const RowVectors> &coords,
double epsilon)
double epsilon, bool recursive)
{
return select_by_mask(coords, douglas_simplify_mask(coords, epsilon));
return select_by_mask(coords,
douglas_simplify_mask(coords, epsilon, recursive));
}

namespace py = pybind11;
using namespace pybind11::literals;

PYBIND11_MODULE(pybind11_rdp, m)
PYBIND11_MODULE(_pybind11_rdp, m)
{
m.doc() = R"pbdoc(
c++/pybind11 version of Ramer-Douglas-Peucker (rdp) algorithm
Expand Down Expand Up @@ -154,41 +192,47 @@ PYBIND11_MODULE(pybind11_rdp, m)

m.def(
"rdp",
[](const Eigen::Ref<const RowVectors> &coords, double epsilon)
-> RowVectors { return douglas_simplify(coords, epsilon); },
rdp_doc, "coords"_a, py::kw_only(), "epsilon"_a = 0.0);
[](const Eigen::Ref<const RowVectors> &coords, double epsilon,
bool recursive) -> RowVectors {
return douglas_simplify(coords, epsilon, recursive);
},
rdp_doc, "coords"_a, //
py::kw_only(), "epsilon"_a = 0.0, "recursive"_a = true);
m.def(
"rdp",
[](const Eigen::Ref<const RowVectorsNx2> &coords,
double epsilon) -> RowVectorsNx2 {
[](const Eigen::Ref<const RowVectorsNx2> &coords, double epsilon,
bool recursive) -> RowVectorsNx2 {
RowVectors xyzs(coords.rows(), 3);
xyzs.setZero();
xyzs.leftCols(2) = coords;
return douglas_simplify(xyzs, epsilon).leftCols(2);
return douglas_simplify(xyzs, epsilon, recursive).leftCols(2);
},
rdp_doc, "coords"_a, py::kw_only(), "epsilon"_a = 0.0);
rdp_doc, "coords"_a, //
py::kw_only(), "epsilon"_a = 0.0, "recursive"_a = true);

auto rdp_mask_doc = R"pbdoc(
Simplifies a given array of points using the Ramer-Douglas-Peucker algorithm.
return a mask.
)pbdoc";
m.def(
"rdp_mask",
[](const Eigen::Ref<const RowVectors> &coords,
double epsilon) -> Eigen::VectorXi {
return douglas_simplify_mask(coords, epsilon);
[](const Eigen::Ref<const RowVectors> &coords, double epsilon,
bool recursive) -> Eigen::VectorXi {
return douglas_simplify_mask(coords, epsilon, recursive);
},
rdp_mask_doc, "coords"_a, py::kw_only(), "epsilon"_a = 0.0);
rdp_mask_doc, "coords"_a, //
py::kw_only(), "epsilon"_a = 0.0, "recursive"_a = true);
m.def(
"rdp_mask",
[](const Eigen::Ref<const RowVectorsNx2> &coords,
double epsilon) -> Eigen::VectorXi {
[](const Eigen::Ref<const RowVectorsNx2> &coords, double epsilon,
bool recursive) -> Eigen::VectorXi {
RowVectors xyzs(coords.rows(), 3);
xyzs.setZero();
xyzs.leftCols(2) = coords;
return douglas_simplify_mask(xyzs, epsilon);
return douglas_simplify_mask(xyzs, epsilon, recursive);
},
rdp_mask_doc, "coords"_a, py::kw_only(), "epsilon"_a = 0.0);
rdp_mask_doc, "coords"_a, //
py::kw_only(), "epsilon"_a = 0.0, "recursive"_a = true);

#ifdef VERSION_INFO
m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
Expand Down
Loading

0 comments on commit 7f5c0dd

Please sign in to comment.