From 49d88909c2790dd7f245b2afd5639f0f517a2395 Mon Sep 17 00:00:00 2001
From: Rhobar9 <86721432+Rhobar9@users.noreply.github.com>
Date: Mon, 29 May 2023 13:40:37 -0700
Subject: [PATCH 1/7] added_docs_for_exceptions_and_Delaunay_class
---
.idea/.gitignore | 3 +++
.idea/alpha_shapes.iml | 17 ++++++++++++++++
.../inspectionProfiles/profiles_settings.xml | 6 ++++++
.idea/misc.xml | 7 +++++++
.idea/modules.xml | 8 ++++++++
.idea/vcs.xml | 6 ++++++
alpha_shapes/alpha_shapes.py | 20 +++++++++++++++----
7 files changed, 63 insertions(+), 4 deletions(-)
create mode 100644 .idea/.gitignore
create mode 100644 .idea/alpha_shapes.iml
create mode 100644 .idea/inspectionProfiles/profiles_settings.xml
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/vcs.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/alpha_shapes.iml b/.idea/alpha_shapes.iml
new file mode 100644
index 0000000..5195124
--- /dev/null
+++ b/.idea/alpha_shapes.iml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..971acd1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..871fc4d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index ea78528..d0bcdc7 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -1,5 +1,6 @@
"""
-Utility module for the calculation of alpha shapes
+This is a core module of package, which contains exceptions, functions
+and classes essential to printing figures.
"""
import numpy as np
@@ -10,28 +11,37 @@
class AlphaException(Exception):
+ """Abstract class for all exceptions which will be raised within Alpha_Shaper class
+ directly or through Delaunay class"""
pass
class NotEnoughPoints(AlphaException):
+ """If instance of class Delaunay has less than 3 points, this exception will be raised"""
pass
class OptimizationFailure(AlphaException):
+ """If Alpha_Shaper instance can't cover all vertices, this exception will be raised"""
pass
-class OptimizationWarnging(UserWarning):
+class OptimizationWarning(UserWarning):
+ """Warns user without interrupting the program"""
pass
class Delaunay(Triangulation):
"""
- Visitor sublclass of matplotlib.tri.Triangulation.
- Mimics scipy.spatial.Delaunay interface.
+ Delaunay is abstract class, which derives from matplotlib.tri.Triangulation.
+ It adds set of coordinates and essential methods.
"""
def __init__(self, coords: NDArray):
+ """In try block function invokes __init__ method of class
+ Triangulation from matplotlib package and sends
+ to it default set of coordinates. If ValueError occurs,
+ function will tackle it."""
try:
super().__init__(x=coords[:, 0], y=coords[:, 1])
except ValueError as e:
@@ -42,9 +52,11 @@ def __init__(self, coords: NDArray):
@property
def simplices(self):
+ """Creates simplices property essential to further operations"""
return self.triangles
def __len__(self):
+ """Returns amount of object's edges"""
return self.simplices.shape[0]
From a99fd0a534263bff1d7c5588b256e8de21911de3 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Sat, 3 Jun 2023 13:04:47 -0700
Subject: [PATCH 2/7] added_docs_and_type_hints_for_Alpha_Shaper_class
---
alpha_shapes/alpha_shapes.py | 89 ++++++++++++++++++++----------------
1 file changed, 50 insertions(+), 39 deletions(-)
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index d0bcdc7..5608053 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -61,7 +61,9 @@ def __len__(self):
class Alpha_Shaper(Delaunay):
+ """Crucial class to creating alpha-shapes"""
def __init__(self, points: ArrayLike, normalize=True):
+ """Assings points to instance, creates basic properties and denormalizes points"""
self.normalized = normalize
points = np.array(points)
@@ -77,37 +79,50 @@ def __init__(self, points: ArrayLike, normalize=True):
if self.normalized:
self._denormalize(center, scale)
- def _denormalize(self, center, scale):
+ def _denormalize(self, center: ArrayLike, scale: ArrayLike):
+ """Transforms back points into their orginal scale"""
self.x = self.x * scale[0] + center[0]
self.y = self.y * scale[1] + center[1]
- def _calculate_cirumradii_sq_of_internal_triangles(self):
+ def _calculate_cirumradii_sq_of_internal_triangles(self) -> NDArray:
+ """Main-line method, which calculates circumradius squares of all internal triangles
+ and saves them into numpy.array. It's important during initialization of object."""
circumradii_sq = [
self._get_circumradius_sq_of_internal_simplex(smpl)
for smpl in self.simplices
]
return np.array(circumradii_sq)
- def _get_circumradius_sq_of_internal_simplex(self, smpl):
+ def _get_circumradius_sq_of_internal_simplex(self, smpl: ArrayLike) -> NDArray:
+ """Reads values of simplices and then
+ sends it to _calculate_cirumradius_sq_of_triangle function."""
x = self.x[smpl]
y = self.y[smpl]
return _calculate_cirumradius_sq_of_triangle(x, y)
- def _sorted_simplices(self):
+ def _sorted_simplices(self) -> NDArray:
+ """Returns all simplices of instance, sorted before by given axis"""
return self.simplices[self.argsort]
- def _sorted_circumradii_sw(self):
+ def _sorted_circumradii_sw(self) -> NDArray:
+ """Returns sorted values of squares circumraiduses of internal triangles"""
return self.circumradii_sq[self.argsort]
- def _shape_from_simplices(self, simplices):
+ def _shape_from_simplices(self, simplices: ArrayLike) -> ArrayLike:
+ """Sends values of simplices to _simplex_to_triangle
+ function and saves triangles. Function shapely.ops.unary_union can receive and return many types of objects,
+ its output depends on input types. In this case function will return array of ints."""
triangles = [_simplex_to_triangle(smpl, self) for smpl in simplices]
return unary_union(triangles)
- def get_mask(self, alpha):
+ def get_mask(self, alpha: float) -> NDArray:
+ """Creates mask, based on squares circumradiuses of internal triangles"""
return self.circumradii_sq > 1 / alpha**2
- def get_shape(self, alpha):
+ def get_shape(self, alpha: float) -> ArrayLike:
+ """Returns shape, constrained by aplha in form of array.
+ If aplha is less than 0, function will use original array of simplices"""
if alpha > 0:
select = self.circumradii_sq <= 1 / alpha**2
simplices = self.simplices[select]
@@ -116,29 +131,23 @@ def get_shape(self, alpha):
return self._shape_from_simplices(simplices)
- def _nth_shape(self, n):
- """
- return the shape formed by the n smallest simplices
- """
+ def _nth_shape(self, n: int) -> ArrayLike:
+ """Returns the shape formed by the amount of n-smallest simplices"""
simplices = self._sorted_simplices()[:n]
return self._shape_from_simplices(simplices)
- def all_vertices(self):
+ def all_vertices(self) -> set:
+ """Returns all vertices of object by set"""
return set(np.ravel(self.simplices))
- def _uncovered_vertices(self, simplices):
- """
- Return a set of vertices that is not covered by the
- specified simplices.
- """
+ def _uncovered_vertices(self, simplices: ArrayLike) -> set:
+ """Returns a set of vertices, which is not covered by the specified simplices."""
return self.all_vertices() - set(np.ravel(simplices))
- def _get_minimum_fully_covering_index_of_simplices(self):
- """
- Return the minimum index of simplices needed to cover all vertices.
- The set of all simplices up to this index is fully covering.
- """
- # At least N//3 triangles are needed to connect N points.
+ def _get_minimum_fully_covering_index_of_simplices(self) -> ArrayLike:
+ """Returns the minimum amount of simplices essential to cover all vertices.
+ If function face problem with vertices, it will raise OptimizationFailure exception."""
+ # We have to use at least N//3 triangles to connect N points.
simplices = self._sorted_simplices()
n_start = len(self) // 3
n_finish = len(self)
@@ -156,8 +165,14 @@ def _get_minimum_fully_covering_index_of_simplices(self):
if uncovered_vertices:
raise OptimizationFailure("Maybe there are duplicate points?")
- def optimize(self):
- # At least N//3 triangles are needed to connect N points.
+ def optimize(self) -> (NDArray, ArrayLike):
+ """Eliminates redundant simplices and then sets appropriate mask.
+
+ Returns:
+ alpha_opt: themost accurate alpha value based on minimal amount of simplices
+ shape: shape after optimization
+ """
+ # We have to use at least N//3 triangles to connect N points
n_min = self._get_minimum_fully_covering_index_of_simplices()
alpha_opt = 1 / np.sqrt(self._sorted_circumradii_sw()[n_min]) - 1e-10
simplices = self._sorted_simplices()
@@ -166,9 +181,7 @@ def optimize(self):
return alpha_opt, shape
def set_mask_at_alpha(self, alpha: float):
- """
- Set the mask for the alpha shape at the specified alpha value.
- """
+ """Sets the mask for the alpha shape based on the appropriate alpha value."""
mask = self.get_mask(alpha)
self.set_mask(mask)
return self
@@ -221,15 +234,8 @@ def _circumradius_sq(lengths):
return num / denom
-def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike):
- """
- calculates the squared circumradius of a triangle with coordinates x, y
-
- Parameters:
- -----------
- x, y: array-like, shape(3,)
- coordinates of the triangle
- """
+def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray:
+ """Calculates the squared circumradius of a triangle with coordinates x, y"""
dx = x - np.roll(x, shift=-1)
dy = y - np.roll(y, shift=-1)
@@ -237,7 +243,12 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike):
return _circumradius_sq(lengths)
-def _simplex_to_triangle(smpl, tri):
+def _simplex_to_triangle(smpl, tri) -> Polygon:
+ """Creates internal triangles from given simplices.
+
+ Returns:
+
+ """
x = tri.x[smpl]
y = tri.y[smpl]
From 1f20e0456ee59eae83597a1731ec7f8d1ae9e107 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Mon, 5 Jun 2023 11:13:56 -0700
Subject: [PATCH 3/7] added_docs_for_all_objects_in_plotting.py_file
---
.idea/.name | 1 +
alpha_shapes/alpha_shapes.py | 2 +-
alpha_shapes/plotting.py | 19 ++++++++++++++++---
3 files changed, 18 insertions(+), 4 deletions(-)
create mode 100644 .idea/.name
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..4ecca9f
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+plotting.py
\ No newline at end of file
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index 5608053..8f17721 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -169,7 +169,7 @@ def optimize(self) -> (NDArray, ArrayLike):
"""Eliminates redundant simplices and then sets appropriate mask.
Returns:
- alpha_opt: themost accurate alpha value based on minimal amount of simplices
+ alpha_opt: the most accurate alpha value based on minimal amount of simplices
shape: shape after optimization
"""
# We have to use at least N//3 triangles to connect N points
diff --git a/alpha_shapes/plotting.py b/alpha_shapes/plotting.py
index 9f9698d..9d3cee8 100644
--- a/alpha_shapes/plotting.py
+++ b/alpha_shapes/plotting.py
@@ -1,9 +1,19 @@
+"""This module contains mechanisms essential to printing figures.
+Functions receive sets of points from alpha_shapes and then plot appropriate shapes.
+This pattern is used in examples directory.
+"""
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch
def plot_alpha_shape(ax, alpha_shape):
+ """Main-line function, which plots all sets of figure's points.
+
+ Args:
+ ax: Axes object received from matplotlib.subplots function
+ alpha_shape: set of points given by Alpha_Shaper._get_shape method
+ """
try:
geoms = alpha_shape.geoms
except AttributeError:
@@ -14,9 +24,12 @@ def plot_alpha_shape(ax, alpha_shape):
def _plot_polygon(ax, polygon):
- """
- Plot a polygon using matplotlib's PathPatch.
- see https://stackoverflow.com/a/70533052/6060982
+ """Plots a polygon using matplotlib's PathPatch.
+ This thread on stackoverflow may be helpful https://stackoverflow.com/a/70533052/6060982.
+
+ Args:
+ ax: Axes object received from matplotlib.subplots function.
+ polygon: Polygon object (from shapely.geometry) nested in object returned by Alpha_Shaper._get_shape method.
"""
xe, ye = polygon.exterior.xy
exterior = Path(np.column_stack([xe, ye]))
From c2a54f455fbbe22c4366e7a4913e3f94b04855e8 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Fri, 9 Jun 2023 03:36:02 -0700
Subject: [PATCH 4/7] last_amendments
---
.idea/.name | 2 +-
alpha_shapes/alpha_shapes.py | 56 +++++++++++++++++++++++-------------
2 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/.idea/.name b/.idea/.name
index 4ecca9f..5954dd7 100644
--- a/.idea/.name
+++ b/.idea/.name
@@ -1 +1 @@
-plotting.py
\ No newline at end of file
+alpha_shapes.py
\ No newline at end of file
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index 8f17721..7248d9b 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -187,26 +187,24 @@ def set_mask_at_alpha(self, alpha: float):
return self
-def _normalize_points(points: NDArray):
- """
- Normalize points to the unit square, centered at the origin.
+def _normalize_points(points: ArrayLike):
+ """Normalizes points to the unit square, centered at the origin.
- Parameters:
- -----------
+ Args:
points: array-like, shape(N,2)
coordinates of the points
Returns:
- --------
- points: array, shape(N,2)
- normalized coordinates of the points
+ points: array, shape(N,2)
+ normalized coordinates of the points
- center: array, shape(2,)
- coordinates of the center of the points
+ center: array, shape(2,)
+ coordinates of the center of the points
- scale: array, shape(2,)
- scale factors for the normalization
+ scale: array, shape(2,)
+ scale factors for the normalization
"""
+
center = points.mean(axis=0)
scale = np.ptp(points, axis=0) # peak to peak distance
normalized_points = (points - center) / scale
@@ -214,13 +212,17 @@ def _normalize_points(points: NDArray):
return normalized_points, center, scale
-def _circumradius_sq(lengths):
- r"""
- Calculate the squared circumradius `r_c^2`,
- where
- r_c = \frac {abc}{4{\sqrt {s(s-a)(s-b)(s-c)}}}
- See: `https://en.wikipedia.org/wiki/Circumscribed_circle`
+def _circumradius_sq(lengths: NDArray) -> NDArray:
+ """ Calculates the squared circumradius of triangle.
+ See more about it on: `https://en.wikipedia.org/wiki/Circumscribed_circle`.
+
+ Args:
+ lengths: contains lengths of triangle's sides.
+
+ Returns:
+ Contains values of squared circumradiuses.
"""
+
lengths = np.asarray(lengths)
s = np.sum(lengths) / 2
@@ -235,7 +237,16 @@ def _circumradius_sq(lengths):
def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray:
- """Calculates the squared circumradius of a triangle with coordinates x, y"""
+ """Calculates the squared circumradius of a triangle with coordinates x, y.
+
+ Args:
+ x: Contains all x values of triangle's points.
+ y: Contains all y values of triangle's points.
+
+ Returns:
+ NDArray with outcome from _circumradius_sq. It contains squared circumradius of internal triangle.
+ """
+
dx = x - np.roll(x, shift=-1)
dy = y - np.roll(y, shift=-1)
@@ -246,9 +257,14 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray
def _simplex_to_triangle(smpl, tri) -> Polygon:
"""Creates internal triangles from given simplices.
- Returns:
+ Args:
+ smpl: values of simplex.
+ tri: particular triangle.
+ Returns:
+ Polygon(shapely.geometry): contains points values of internal triangle.
"""
+
x = tri.x[smpl]
y = tri.y[smpl]
From d273e36e922cd07005e43f1da96aa9cb5333e397 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Tue, 20 Jun 2023 17:29:19 +0200
Subject: [PATCH 5/7] Remove .idea directory from version control
---
.idea/.gitignore | 3 ---
.idea/.name | 1 -
.idea/alpha_shapes.iml | 17 -----------------
.idea/inspectionProfiles/profiles_settings.xml | 6 ------
.idea/misc.xml | 7 -------
.idea/modules.xml | 8 --------
.idea/vcs.xml | 6 ------
7 files changed, 48 deletions(-)
delete mode 100644 .idea/.gitignore
delete mode 100644 .idea/.name
delete mode 100644 .idea/alpha_shapes.iml
delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml
delete mode 100644 .idea/misc.xml
delete mode 100644 .idea/modules.xml
delete mode 100644 .idea/vcs.xml
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 5954dd7..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-alpha_shapes.py
\ No newline at end of file
diff --git a/.idea/alpha_shapes.iml b/.idea/alpha_shapes.iml
deleted file mode 100644
index 5195124..0000000
--- a/.idea/alpha_shapes.iml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 971acd1..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 871fc4d..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
From 933618090b9b0514f32a9457be98173c349904f3 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Fri, 7 Jul 2023 18:32:02 +0200
Subject: [PATCH 6/7] Amendments_after_first_review_
---
alpha_shapes/alpha_shapes.py | 87 +++++++++++++++---------------------
alpha_shapes/plotting.py | 20 +++++----
2 files changed, 48 insertions(+), 59 deletions(-)
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index 62cec0e..806dfd0 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -13,18 +13,17 @@
class AlphaException(Exception):
- """Abstract class for all exceptions which will be raised within Alpha_Shaper class
- directly or through Delaunay class."""
+ """Abstract class for exceptions which could be raised during the work of Alpha_Shaper class."""
pass
class NotEnoughPoints(AlphaException):
- """If instance of class Delaunay has less than 3 points, this exception will be raised."""
+ """Raised when an operation requires a certain number of points and that condition is not met."""
pass
class OptimizationFailure(AlphaException):
- """If Alpha_Shaper instance can't cover all vertices, this exception will be raised."""
+ """Raised when the conditions for optimization are not met."""
pass
@@ -34,20 +33,14 @@ class OptimizationWarning(UserWarning):
class Delaunay(Triangulation):
- """
- Delaunay is abstract class, which derives from matplotlib.tri.Triangulation.
- It adds set of coordinates and essential methods.
+ """Abstract class, which provides useful interface. This idea is similar to scipy.spatial.Delaunay solution.
+ Coordinates of future Alpha_Shaper object will be added via Delaunay class.
+ If the coordinates do not meet the conditions, the class will raise an appropriate error.
+ It also adds key methods.
"""
def __init__(self, coords: NDArray) -> None:
- """In try block function invokes __init__ method of class
- Triangulation from matplotlib package and sends
- to it default set of coordinates. If ValueError occurs,
- function will tackle it.
-
- """
-
try:
super().__init__(x=coords[:, 0], y=coords[:, 1])
except ValueError as e:
@@ -67,10 +60,12 @@ def __len__(self) -> int:
class Alpha_Shaper(Delaunay):
- """Crucial class to creating alpha-shapes."""
+ mask: NDArray # for type hinting
+ """Crucial class to creating alpha-shapes.
+ The class handles points, creates internal triangles and generates shapes.
+ """
def __init__(self, points: ArrayLike, normalize=True) -> None:
- """Assign points to instance, create basic properties and denormalize points"""
self.normalized = normalize
points = np.array(points)
@@ -84,9 +79,7 @@ def __init__(self, points: ArrayLike, normalize=True) -> None:
self._initialize(points)
def _initialize(self, points: NDArray) -> None:
- """
- _initialize the alpha shaper.
- """
+ """_initialize the alpha shaper."""
super().__init__(points)
@@ -95,15 +88,13 @@ def _initialize(self, points: NDArray) -> None:
default_mask = np.full_like(self.circumradii_sq, False, dtype=bool)
self.set_mask(default_mask)
- def _denormalize(self, center: ArrayLike, scale: ArrayLike) -> None:
- """Transform back points into their orginal scale."""
+ def _denormalize(self, center: NDArray, scale: NDArray) -> None:
+ """Transform back points into their original scale."""
self.x = self.x * scale[0] + center[0]
self.y = self.y * scale[1] + center[1]
def _calculate_cirumradii_sq_of_internal_triangles(self) -> NDArray:
- """Main-line method, which calculates circumradius squares of all internal triangles
- and saves them into numpy.array. It's important during initialization of object.
- """
+ """Method calculates circumradiuses squares of all internal triangles."""
circumradii_sq = [
self._get_circumradius_sq_of_internal_simplex(smpl)
@@ -111,16 +102,13 @@ def _calculate_cirumradii_sq_of_internal_triangles(self) -> NDArray:
]
return np.array(circumradii_sq)
- def _get_circumradius_sq_of_internal_simplex(self, smpl: ArrayLike) -> NDArray:
- """Read values of simplices and then
- send it to _calculate_cirumradius_sq_of_triangle function.
- """
-
+ def _get_circumradius_sq_of_internal_simplex(self, smpl: slice) -> NDArray:
+ """Read value of squared circumradius of internal triangle."""
x = self.x[smpl]
y = self.y[smpl]
return _calculate_cirumradius_sq_of_triangle(x, y)
- def _sorted_simplices(self) -> NDArray:
+ def _sorted_simplices(self) -> NDArray[np.float64]:
"""Return all simplices of instance, sorted before by given axis."""
return self.simplices[self.argsort]
@@ -129,22 +117,19 @@ def _sorted_circumradii_sw(self) -> NDArray[np.float64]:
return self.circumradii_sq[self.argsort]
def _shape_from_simplices(self, simplices: ArrayLike) -> ArrayLike:
- """Send values of simplices to _simplex_to_triangle
- function and save triangles. Function shapely.ops.unary_union can receive and return many types of objects,
- its output depends on input types. In this case function will return array of ints.
- """
+ """From given simplices create triangles. Then make union."""
triangles = [_simplex_to_triangle(smpl, self) for smpl in simplices]
return unary_union(triangles)
def get_mask(self, alpha: float) -> NDArray:
- """Create mask, based on squares circumradiuses of internal triangles."""
+ """Create mask, based on squares of circumradiuses of internal triangles."""
return self.circumradii_sq > 1 / alpha**2
def get_shape(self, alpha: float) -> ArrayLike:
- """Return shape, constrained by aplha in form of array.
- If alpha is less than 0, function will use original array of simplices.
+ """Return shape, constrained by alpha.
+ If alpha is less or equal to 0, function will use original array of simplices.
"""
if alpha > 0:
@@ -161,17 +146,17 @@ def _nth_shape(self, n: int) -> ArrayLike:
return self._shape_from_simplices(simplices)
def all_vertices(self) -> set:
- """Return all vertices of object by set."""
+ """Return all vertices of object."""
return set(np.ravel(self.simplices))
def _uncovered_vertices(self, simplices: ArrayLike) -> set:
"""Return a set of vertices, which is not covered by the specified simplices."""
return self.all_vertices() - set(np.ravel(simplices))
- def _get_minimum_fully_covering_index_of_simplices(self) -> ArrayLike:
+ def _get_minimum_fully_covering_index_of_simplices(self) -> int:
"""Return the minimum amount of simplices essential to cover all vertices.
The set of all simplices up to this index is fully covering.
- If function face problem with vertices, it will raise OptimizationFailure exception.
+ If function face problems, it will raise appropriate exceptions.
"""
# We have to use at least N//3 triangles to connect N points.
@@ -191,12 +176,12 @@ def _get_minimum_fully_covering_index_of_simplices(self) -> ArrayLike:
raise OptimizationFailure("Maybe there are duplicate points?")
- def optimize(self) -> (NDArray, ArrayLike):
- """Eliminate redundant simplices and then sets appropriate mask.
+ def optimize(self) -> Tuple[NDArray, ArrayLike]:
+ """Eliminate redundant simplices and then set appropriate mask.
Returns:
- alpha_opt: the most accurate alpha value based on minimal amount of simplices
- shape: shape after optimization
+ alpha_opt: the most appropriate alpha value based on minimal amount of simplices.
+ shape: shape after optimization.
"""
# We have to use at least N//3 triangles to connect N points
@@ -208,7 +193,7 @@ def optimize(self) -> (NDArray, ArrayLike):
return alpha_opt, shape
def set_mask_at_alpha(self, alpha: float):
- """Set the mask for the alpha shape based on the appropriate alpha value."""
+ """Set the mask for the alpha shape based on the given alpha value."""
mask = self.get_mask(alpha)
self.set_mask(mask)
return self
@@ -241,7 +226,7 @@ def _normalize_points(points: NDArray) -> Tuple[NDArray, NDArray, NDArray]:
def _circumradius_sq(lengths: NDArray) -> NDArray:
- """ Calculate the squared circumradius of triangle.
+ """Calculate the squared circumradius of triangle.
See more about it on: `https://en.wikipedia.org/wiki/Circumscribed_circle`.
Args:
@@ -273,7 +258,7 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray
y: Contains all y values of triangle's points.
Returns:
- NDArray with outcome from _circumradius_sq. It contains squared circumradius of internal triangle.
+ outcome: Contains squared circumradius of internal triangle.
"""
@@ -284,15 +269,15 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray
return _circumradius_sq(lengths)
-def _simplex_to_triangle(smpl, tri) -> Polygon:
- """Create internal triangles from given simplices.
+def _simplex_to_triangle(smpl: slice, tri) -> Polygon:
+ """Create internal triangle from given simplices.
Args:
- smpl: values of simplex.
+ smpl: value of simplex.
tri: particular triangle.
Returns:
- Polygon(shapely.geometry): contains points values of internal triangle.
+ Polygon: contains points values of internal triangle.
"""
diff --git a/alpha_shapes/plotting.py b/alpha_shapes/plotting.py
index 9d3cee8..45fc1d8 100644
--- a/alpha_shapes/plotting.py
+++ b/alpha_shapes/plotting.py
@@ -1,19 +1,21 @@
"""This module contains mechanisms essential to printing figures.
-Functions receive sets of points from alpha_shapes and then plot appropriate shapes.
-This pattern is used in examples directory.
+Functions receive sets of points and then plot appropriate shapes.
"""
+
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch
def plot_alpha_shape(ax, alpha_shape):
- """Main-line function, which plots all sets of figure's points.
+ """Mainline function, which plots all sets of figure's points.
Args:
- ax: Axes object received from matplotlib.subplots function
- alpha_shape: set of points given by Alpha_Shaper._get_shape method
+ ax(Axes.matplotlib.subplots): axes of image.
+ alpha_shape(numpy.ArrayLike): set of points to print.
+
"""
+
try:
geoms = alpha_shape.geoms
except AttributeError:
@@ -24,13 +26,15 @@ def plot_alpha_shape(ax, alpha_shape):
def _plot_polygon(ax, polygon):
- """Plots a polygon using matplotlib's PathPatch.
+ """Plot a polygon using matplotlib's PathPatch.
This thread on stackoverflow may be helpful https://stackoverflow.com/a/70533052/6060982.
Args:
- ax: Axes object received from matplotlib.subplots function.
- polygon: Polygon object (from shapely.geometry) nested in object returned by Alpha_Shaper._get_shape method.
+ ax(Axes.matplotlib.subplots): axes of image.
+ polygon(shapely.geometry.Polygon): set of points to print.
+
"""
+
xe, ye = polygon.exterior.xy
exterior = Path(np.column_stack([xe, ye]))
holes = [Path(np.asarray(hole.coords)) for hole in polygon.interiors]
From 7b2d80dfd78769afe0d8ba7fdfa9858ba9764c19 Mon Sep 17 00:00:00 2001
From: Aleksander Koprowski
Date: Sun, 5 Nov 2023 20:08:24 +0100
Subject: [PATCH 7/7] Amendments_after_second_review
---
alpha_shapes/alpha_shapes.py | 98 +++++++++++++++++++++++-------------
alpha_shapes/plotting.py | 4 +-
2 files changed, 66 insertions(+), 36 deletions(-)
diff --git a/alpha_shapes/alpha_shapes.py b/alpha_shapes/alpha_shapes.py
index 806dfd0..c691cf8 100644
--- a/alpha_shapes/alpha_shapes.py
+++ b/alpha_shapes/alpha_shapes.py
@@ -1,9 +1,9 @@
"""
-This is a core module of package, which contains exceptions, functions
-and classes essential to printing figures.
+This is a core module of package which contains exceptions, functions
+and classes essential to creating and working with figures.
"""
-from typing import Tuple
+from typing import Tuple, Union
import numpy as np
from matplotlib.tri import Triangulation
@@ -33,14 +33,22 @@ class OptimizationWarning(UserWarning):
class Delaunay(Triangulation):
- """Abstract class, which provides useful interface. This idea is similar to scipy.spatial.Delaunay solution.
- Coordinates of future Alpha_Shaper object will be added via Delaunay class.
- If the coordinates do not meet the conditions, the class will raise an appropriate error.
- It also adds key methods.
-
+ """Abstract class with useful interface.
+ Visitor sublclass of matplotlib.tri.Triangulation.
+ See similar idea on scipy.spatial.Delaunay solution.
"""
def __init__(self, coords: NDArray) -> None:
+ """Set the interface object and pass the coords into it.
+
+ Args:
+ coords(NDArray): raw coords at which preparation process will be performed.
+
+ Raises:
+ - NotEnoughPoints: If there are fewer than 3 points provided.
+ - ValueError: For other value-related issues with the coordinates.
+ """
+
try:
super().__init__(x=coords[:, 0], y=coords[:, 1])
except ValueError as e:
@@ -51,21 +59,30 @@ def __init__(self, coords: NDArray) -> None:
@property
def simplices(self) -> NDArray:
- """Create simplices property essential to further operations."""
+ """Return the collection of triangles."""
return self.triangles
def __len__(self) -> int:
- """Return amount of object's edges."""
+ """Return amount of object's simplices."""
return self.simplices.shape[0]
class Alpha_Shaper(Delaunay):
+ """The class enables the creation of shapes and further operations on them."""
+
mask: NDArray # for type hinting
- """Crucial class to creating alpha-shapes.
- The class handles points, creates internal triangles and generates shapes.
- """
def __init__(self, points: ArrayLike, normalize=True) -> None:
+ """Pass points into shaper. Optionally perform normalization.
+
+ Args:
+ points(ArrayLike): points used to create shapes.
+
+ normalize(bool): The flag determines whether the normalization process will be carried out.
+ See more about normalization on https://github.com/panosz/alpha_shapes.
+
+ """
+
self.normalized = normalize
points = np.array(points)
@@ -79,7 +96,12 @@ def __init__(self, points: ArrayLike, normalize=True) -> None:
self._initialize(points)
def _initialize(self, points: NDArray) -> None:
- """_initialize the alpha shaper."""
+ """_initialize the alpha shaper.
+
+ Args:
+ points(NDArray): points at which normalization will be performed.
+
+ """
super().__init__(points)
@@ -109,7 +131,7 @@ def _get_circumradius_sq_of_internal_simplex(self, smpl: slice) -> NDArray:
return _calculate_cirumradius_sq_of_triangle(x, y)
def _sorted_simplices(self) -> NDArray[np.float64]:
- """Return all simplices of instance, sorted before by given axis."""
+ """Return the collection of simplices, sorted by their circumradius."""
return self.simplices[self.argsort]
def _sorted_circumradii_sw(self) -> NDArray[np.float64]:
@@ -117,19 +139,21 @@ def _sorted_circumradii_sw(self) -> NDArray[np.float64]:
return self.circumradii_sq[self.argsort]
def _shape_from_simplices(self, simplices: ArrayLike) -> ArrayLike:
- """From given simplices create triangles. Then make union."""
+ """Return the shape from simplices.
+ Output is in unary_union form which makes further operations on a shape easier. """
triangles = [_simplex_to_triangle(smpl, self) for smpl in simplices]
return unary_union(triangles)
def get_mask(self, alpha: float) -> NDArray:
- """Create mask, based on squares of circumradiuses of internal triangles."""
+ """Return mask, based on squares of circumradiuses of internal triangles.
+ Mask specifies which elements should be considered for triangulation."""
return self.circumradii_sq > 1 / alpha**2
def get_shape(self, alpha: float) -> ArrayLike:
- """Return shape, constrained by alpha.
- If alpha is less or equal to 0, function will use original array of simplices.
+ """Return shape, based on the given alpha.
+ If alpha is less or equal to 0, the shape creation will be based on external points.
"""
if alpha > 0:
@@ -154,11 +178,15 @@ def _uncovered_vertices(self, simplices: ArrayLike) -> set:
return self.all_vertices() - set(np.ravel(simplices))
def _get_minimum_fully_covering_index_of_simplices(self) -> int:
- """Return the minimum amount of simplices essential to cover all vertices.
+ """Return the minimum index of simplices needed to cover all vertices.
The set of all simplices up to this index is fully covering.
- If function face problems, it will raise appropriate exceptions.
+
+ Raises:
+ - OptimizationFailure: For issues when the conditions for optimization are not met.
+ A common issue is duplicate points in the dataset.
"""
+
# We have to use at least N//3 triangles to connect N points.
simplices = self._sorted_simplices()
n_start = len(self) // 3
@@ -177,13 +205,15 @@ def _get_minimum_fully_covering_index_of_simplices(self) -> int:
raise OptimizationFailure("Maybe there are duplicate points?")
def optimize(self) -> Tuple[NDArray, ArrayLike]:
- """Eliminate redundant simplices and then set appropriate mask.
+ """Return the alpha value that allows plotting the shape with the minimum number of triangles.
+ Vertices of initial triangulation aren't left uncovered.
Returns:
- alpha_opt: the most appropriate alpha value based on minimal amount of simplices.
- shape: shape after optimization.
+ alpha_opt(NDArray): optimized alpha value.
+ shape(ArrayLike): shape based on the optimized alpha value.
"""
+
# We have to use at least N//3 triangles to connect N points
n_min = self._get_minimum_fully_covering_index_of_simplices()
alpha_opt = 1 / np.sqrt(self._sorted_circumradii_sw()[n_min]) - 1e-10
@@ -225,15 +255,15 @@ def _normalize_points(points: NDArray) -> Tuple[NDArray, NDArray, NDArray]:
return normalized_points, center, scale
-def _circumradius_sq(lengths: NDArray) -> NDArray:
- """Calculate the squared circumradius of triangle.
+def _circumradius_sq(lengths: NDArray) -> Union[float, np.inf]:
+ """Calculate the squared circumradius `r_c^2` where r_c = \frac {abc}{4{\sqrt {s(s-a)(s-b)(s-c)}}}.
See more about it on: `https://en.wikipedia.org/wiki/Circumscribed_circle`.
Args:
- lengths: contains lengths of triangle's sides.
+ lengths(NDArray): contains lengths of triangle's sides.
Returns:
- Contains values of squared circumradiuses.
+ Union[float, np.inf]: value of squared circumradius.
"""
@@ -254,11 +284,11 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray
"""Calculate the squared circumradius of a triangle with coordinates x, y.
Args:
- x: Contains all x values of triangle's points.
- y: Contains all y values of triangle's points.
+ x, y: array-like, shape(3,)
+ coordinates of the triangle
Returns:
- outcome: Contains squared circumradius of internal triangle.
+ NDArray: squared circumradius of internal triangle.
"""
@@ -270,14 +300,14 @@ def _calculate_cirumradius_sq_of_triangle(x: ArrayLike, y: ArrayLike) -> NDArray
def _simplex_to_triangle(smpl: slice, tri) -> Polygon:
- """Create internal triangle from given simplices.
+ """Return triangle points.
Args:
- smpl: value of simplex.
+ smpl(slice): value of simplex.
tri: particular triangle.
Returns:
- Polygon: contains points values of internal triangle.
+ Polygon: contains points values of triangle.
"""
diff --git a/alpha_shapes/plotting.py b/alpha_shapes/plotting.py
index 45fc1d8..e158365 100644
--- a/alpha_shapes/plotting.py
+++ b/alpha_shapes/plotting.py
@@ -1,5 +1,5 @@
"""This module contains mechanisms essential to printing figures.
-Functions receive sets of points and then plot appropriate shapes.
+Functions make necessary operations on points sets and plot appropriate shapes.
"""
import numpy as np
@@ -8,7 +8,7 @@
def plot_alpha_shape(ax, alpha_shape):
- """Mainline function, which plots all sets of figure's points.
+ """Plot final set of figure's points.
Args:
ax(Axes.matplotlib.subplots): axes of image.