Skip to content

Commit

Permalink
Merge pull request #821 from PrimozGodec/fix-hull
Browse files Browse the repository at this point in the history
[FIX] Concave hull: fix cases when all points inline
  • Loading branch information
VesnaT authored Apr 22, 2022
2 parents e68f46f + fa326ed commit ff9949e
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 34 deletions.
43 changes: 10 additions & 33 deletions orangecontrib/text/concave_hull.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,22 @@ def _smoothen_hull(

# pyclipper work with integer so points need to be scaled first
scaling_factor = 1000
dist_from_cluster = 0.06
dist_from_cluster = 0.03
scaled_hull = pyclipper.scale_to_clipper(hull, scaling_factor)

# buffer the hull for dist_from_cluster * 3
pco1 = pyclipper.PyclipperOffset()
pco1.AddPath(scaled_hull, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
im_solution = pco1.Execute(dist_from_cluster * scaling_factor * 3)

if len(im_solution) == 0:
# when the solution is empty it means that polygon either consist of
# one point, two points or all points are in line
# ET_CLOSEDPOLYGON can't offset those polygons but ET_OPENROUND can
pco1 = pyclipper.PyclipperOffset()
pco1.AddPath(scaled_hull, pyclipper.JT_ROUND, pyclipper.ET_OPENROUND)
im_solution = pco1.Execute(dist_from_cluster * scaling_factor * 3)

# buffer the hull for dist_from_cluster * -2
pco2 = pyclipper.PyclipperOffset()
pco2.AddPath(im_solution[0], pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
Expand All @@ -212,34 +221,6 @@ def _smoothen_hull(
)


def _generate_additional_points(points: np.ndarray, x_diff: float, y_diff: float):
"""
When less than three points in "polygon" clipper cannot offset the polygon.
This function generates multiple points on the ellipsis around every point
Parameters
----------
points
Base points to generate new points around
x_diff
Global difference between max and min point for x-axis
y_diff
Global difference between max and min point for y-axis
Returns
-------
New points - 20 points around each existing point
"""
new_pt = []
for x, y in points:
r_x, r_y = x_diff * 0.05, y_diff * 0.05
theta = np.random.random(20) * 2 * np.pi
x = x + r_x * np.cos(theta)
y = y + r_y * np.sin(theta)
new_pt.append(np.hstack((x[:, None], y[:, None])))
return np.vstack(new_pt)


def _global_range(points):
""" Compute global min and max - for equal offsetting in each direction"""
x, y = points[:, 0], points[:, 1]
Expand Down Expand Up @@ -291,10 +272,6 @@ def compute_concave_hulls(
# remove duplicates
points = np.unique(points, axis=0)

if points.shape[0] < 3:
# if cluster consist of less than 3 points generate additional pts
points = _generate_additional_points(points, x_max - x_min, y_max - y_min)

# compute hull around the cluster
hull = _get_shape_around_points(points, epsilon)
# smoothen and extend the hull
Expand Down
14 changes: 13 additions & 1 deletion orangecontrib/text/tests/test_concave_hull.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
from Orange.data import Table

from orangecontrib.text.concave_hull import compute_concave_hulls
from orangecontrib.text.concave_hull import compute_concave_hulls, _smoothen_hull


class TestConcaveHull(unittest.TestCase):
Expand Down Expand Up @@ -80,6 +80,18 @@ def test_non_float_data(self):
self.assertEqual(1, len(hulls))
self.assertEqual(2, hulls[0].shape[1]) # hull have x and y

def test_special_cases(self):
# case where polygon became the line after scale_to_clipper
data = np.array(
[
[-0.57389557, -2.261084],
[-0.56854552, -2.27204857],
[-0.46539558, -2.22137609],
]
)
hulls = _smoothen_hull(data, -10.584, 16.449, -6.569, 20.773)
self.assertGreater(len(hulls), 5)


if __name__ == "__main__":
unittest.main()

0 comments on commit ff9949e

Please sign in to comment.