Skip to content

Commit 6b377e1

Browse files
authored
Merge pull request #375 from jo-mueller/replace-pygeodesics
Replace pygeodesics with potpourri3d and gdist
2 parents 4740563 + 40c9ec6 commit 6b377e1

9 files changed

Lines changed: 371 additions & 42 deletions

File tree

docs/04_FAQ/geodesics_tools.ipynb

Lines changed: 301 additions & 0 deletions
Large diffs are not rendered by default.

docs/_toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ parts:
6868
- file: 04_FAQ/validation/validation_stress_paper
6969
- file: 04_FAQ/validation/analyze_validation
7070
- file: 04_FAQ/validation/create_validation_data
71+
- file: 04_FAQ/geodesics_tools
7172

7273
- caption: API
7374
chapters:

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ dependencies = [
3333
"napari-tools-menu>=0.1.15",
3434
"numpy<1.24.0",
3535
"pandas",
36-
"pygeodesic",
3736
"scikit-image",
3837
"scipy>=1.9.0",
3938
"seaborn",
@@ -42,6 +41,8 @@ dependencies = [
4241
"vedo>=2023.5.0",
4342
"vispy",
4443
"deprecation",
44+
"gdist",
45+
"potpourri3d",
4546
# "bokeh >= 3.1.0",
4647
]
4748

src/napari_stress/_measurements/geodesics.py

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
from typing import List
22

33
import numpy as np
4-
import tqdm
54
from napari.types import LayerDataTuple, SurfaceData
65
from napari_tools_menu import register_function
76

87
from .._utils.frame_by_frame import frame_by_frame
98

109

11-
def geodesic_distance_matrix(
12-
surface: SurfaceData, show_progress: bool = False
13-
) -> np.ndarray:
10+
def geodesic_distance_matrix(surface: SurfaceData) -> np.ndarray:
1411
"""
1512
Calculate a pairwise distance matrix for vertices of a surface.
1613
@@ -28,27 +25,16 @@ def geodesic_distance_matrix(
2825
at `distance_matrix[i, j]`
2926
3027
"""
31-
from pygeodesic import geodesic
28+
import gdist
29+
from .._utils import sanitize_faces
3230

33-
geoalg = geodesic.PyGeodesicAlgorithmExact(surface[0], surface[1])
31+
# Reorder the faces of the surface to ensure consistent orientation
32+
sanitized_surface = sanitize_faces(surface)
33+
vertices = sanitized_surface[0]
34+
faces = sanitized_surface[1]
3435

35-
n_points = len(surface[0])
36-
distance_matrix = np.zeros((n_points, n_points))
37-
points = surface[0]
38-
39-
if show_progress:
40-
iterator = tqdm.tqdm(
41-
enumerate(points), desc="Calculating geodesic distances"
42-
)
43-
else:
44-
iterator = enumerate(points)
45-
46-
for idx, pt in iterator:
47-
distances, _ = geoalg.geodesicDistances(
48-
[idx], np.arange(idx + 1, n_points)
49-
)
50-
distance_matrix[idx, idx + 1 :] = distances
51-
distance_matrix[idx + 1 :, idx] = distances
36+
distance_matrix = gdist.local_gdist_matrix(
37+
vertices, faces, max_distance=1e9).toarray()
5238

5339
return distance_matrix
5440

@@ -77,10 +63,15 @@ def geodesic_path(
7763
coordinates, the second dimension is the vector from point to point.
7864
7965
"""
80-
from pygeodesic import geodesic
66+
import potpourri3d as pp3d
67+
from .._utils import sanitize_faces
8168

82-
geoalg = geodesic.PyGeodesicAlgorithmExact(surface[0], surface[1])
83-
distances, path = geoalg.geodesicDistance(index_1, index_2)
69+
sanitized_surface = sanitize_faces(surface)
70+
vertices = sanitized_surface[0]
71+
faces = sanitized_surface[1]
72+
73+
path_solver = pp3d.EdgeFlipGeodesicSolver(vertices, faces)
74+
path = path_solver.find_geodesic_path(index_1, index_2)
8475

8576
# convert points to vectors from point to point
8677
vectors = []
@@ -240,8 +231,10 @@ def local_extrema_analysis(
240231
- `min_max_pair_distances`: Distances between all pairs of local minima and maxima.
241232
- `min_max_pair_anisotropies`: Difference in input value `(vertices, faces, values)` between all pairs of local minima and maxima.
242233
"""
243-
triangles = surface[1]
234+
from .._utils import sanitize_faces
244235
feature = surface[2]
236+
surface = sanitize_faces(surface)
237+
triangles = surface[1]
245238

246239
quad_fit = len(feature)
247240

src/napari_stress/_measurements/toolbox.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ def comprehensive_analysis(
595595
GDM = None
596596
if GDM is None:
597597
GDM = measurements.geodesic_distance_matrix(
598-
surface_cell_stress, show_progress=verbose
598+
surface_cell_stress
599599
)
600600

601601
if maximal_distance is None:

src/napari_stress/_reconstruction/reconstruct_surface.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import numpy as np
2-
from napari.types import PointsData, SurfaceData
32
from napari_tools_menu import register_function
43

54
from .._utils.frame_by_frame import frame_by_frame
@@ -10,8 +9,8 @@
109
)
1110
@frame_by_frame
1211
def reconstruct_surface_from_quadrature_points(
13-
points: PointsData,
14-
) -> SurfaceData:
12+
points: "napari.types.PointsData",
13+
) -> "napari.types.SurfaceData":
1514
"""
1615
Reconstruct the surface for a given set of quadrature points.
1716
@@ -22,12 +21,12 @@ def reconstruct_surface_from_quadrature_points(
2221
2322
Returns
2423
-------
25-
Sphere_Triangulation_Array : TYPE
26-
DESCRIPTION.
24+
tuple
25+
Tuple of points and faces
2726
2827
"""
2928
from scipy.spatial import Delaunay
30-
29+
from .._utils import sanitize_faces
3130
from .._stress import lebedev_write_SPB as lebedev_write
3231

3332
n_quadrature_points = len(points)
@@ -53,4 +52,4 @@ def reconstruct_surface_from_quadrature_points(
5352
delauney_triangles[tri_i, vert_ind] = vertex
5453
vert_ind = vert_ind + 1
5554

56-
return (points, delauney_triangles.astype(int))
55+
return sanitize_faces((points, delauney_triangles.astype(int)))

src/napari_stress/_tests/test_measurements.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,3 @@ def test_stresses():
823823
H_i, H0, H_i_ellipsoid, H0_ellipsoid, gamma
824824
)
825825
measurements.maximal_tissue_anisotropy(ellipsoid, gamma=gamma)
826-
827-
828-
if __name__ == "__main__":
829-
import napari
830-
831-
test_comprehensive_stress_toolbox_4d(napari.Viewer)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from ._aggregate_measurements import compile_data_from_layers
22
from .frame_by_frame import TimelapseConverter, frame_by_frame
33
from .import_export_settings import export_settings, import_settings
4+
from ._utils import sanitize_faces
45

56
__all__ = [
67
"import_settings",
78
"export_settings",
89
"compile_data_from_layers",
910
"TimelapseConverter",
1011
"frame_by_frame",
12+
"sanitize_faces",
1113
]

src/napari_stress/_utils/_utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import numpy as np
2+
3+
def sanitize_faces(surface: "napari.types.SurfaceData") -> "napari.types.SurfaceData":
4+
"""
5+
Sanitize the faces of a surface to ensure they are in the correct format.
6+
7+
This function ensures that the faces of the surface are in the correct
8+
format and order. It also ensures that the vertices are in the correct
9+
format.
10+
11+
Parameters
12+
----------
13+
surface : 'napari.types.SurfaceData'
14+
The surface data to sanitize.
15+
16+
Returns
17+
-------
18+
'napari.types.SurfaceData'
19+
The sanitized surface data.
20+
"""
21+
# Ensure faces are in the correct format
22+
faces = surface[1]
23+
vertices = surface[0]
24+
center = np.mean(vertices, axis=0)
25+
26+
for idx, triangle in enumerate(faces):
27+
v1 = vertices[int(triangle[0])]
28+
v2 = vertices[int(triangle[1])]
29+
v3 = vertices[int(triangle[2])]
30+
31+
normal = np.cross(v2 - v1, v3 - v1)
32+
normal = normal / np.linalg.norm(normal)
33+
center_triangle = (v1 + v2 + v3) / 3
34+
35+
if np.dot(normal, center_triangle - center) < 0:
36+
faces[idx] = faces[idx][::-1]
37+
38+
return (vertices.astype(np.float64), faces.astype(np.int32))

0 commit comments

Comments
 (0)