Skip to content

Commit 7fbb57e

Browse files
author
gabe-rbo
committed
type annotations fixes
1 parent fb471fb commit 7fbb57e

10 files changed

Lines changed: 95 additions & 44 deletions

File tree

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ authors:
55
given-names: "Gabriel"
66
email: "gabriel.ribeiro@dcc.ufmg.br"
77
title: "pySurgery: Computational Surgery Theory in Python"
8-
version: 2.2.1
8+
version: 2.2.2
99
date-released: 01-05-2026
1010
url: "https://github.com/gabe-rbo/pysurgery"
1111
repository-code: "https://github.com/gabe-rbo/pysurgery"

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pysurgery"
7-
version = "2.2.1"
7+
version = "2.2.2"
88
description = "A high-performance Python library for Computational Surgery Theory."
99
dependencies = [
1010
"numpy>=2.4.4",
@@ -83,7 +83,7 @@ python_files = ["test_*.py"]
8383
"*" = ["*.jl", "*.yaml", "*.json"]
8484

8585
[tool.bumpversion]
86-
current_version = "2.2.1"
86+
current_version = "2.2.2"
8787
commit = true
8888
tag = true
8989

pysurgery/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@
316316

317317
from . import integrations
318318

319-
__version__ = "2.2.1"
319+
__version__ = "2.2.2"
320320

321321
def __getattr__(name):
322322
if name == "JuliaBridge":

pysurgery/bridge/surgery_backend.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3498,11 +3498,11 @@ Returns (R, B_aligned, disparity).
34983498
function orthogonal_procrustes(A::AbstractMatrix{Float64}, B::AbstractMatrix{Float64})
34993499
M = transpose(B) * A
35003500
F = svd(M)
3501-
R = F.U * transpose(F.Vt)
3501+
R = F.U * F.Vt
35023502
if det(R) < 0
35033503
U = copy(F.U)
35043504
U[:, end] .*= -1
3505-
R = U * transpose(F.Vt)
3505+
R = U * F.Vt
35063506
end
35073507
B_aligned = B * R
35083508
disparity = norm(A - B_aligned)

pysurgery/geometry/embedding.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232

3333
from dataclasses import dataclass, field
3434
from functools import lru_cache
35-
from typing import Optional, Sequence
35+
from typing import Optional, Sequence, Union, TYPE_CHECKING
36+
if TYPE_CHECKING:
37+
from .point_cloud import PointCloud
3638

3739
import numpy as np
3840
from scipy.spatial import cKDTree
@@ -327,7 +329,7 @@ def __post_init__(self) -> None:
327329
def from_source(
328330
cls,
329331
source: object,
330-
coordinates: Optional[np.ndarray] = None,
332+
coordinates: Optional[Union[np.ndarray, "PointCloud"]] = None,
331333
*,
332334
projection_matrix: Optional[np.ndarray] = None,
333335
source_name: Optional[str] = None,
@@ -553,7 +555,7 @@ def to_legacy_dict(self) -> dict[str, object]:
553555

554556
def analyze_embedding(
555557
source: object,
556-
coordinates: Optional[np.ndarray] = None,
558+
coordinates: Optional[Union[np.ndarray, "PointCloud"]] = None,
557559
*,
558560
target_dimension: Optional[int] = None,
559561
allow_projection: bool = False,
@@ -973,7 +975,7 @@ def detect_self_intersections(
973975

974976

975977
def project_coordinates(
976-
points: np.ndarray,
978+
points: np.ndarray | PointCloud,
977979
target_dimension: int,
978980
*,
979981
method: str = "pca",
@@ -1062,7 +1064,7 @@ def project_coordinates(
10621064
raise ValueError("Unknown projection method: {!r}".format(method))
10631065

10641066

1065-
def jitter_coordinates(points: np.ndarray, *, scale: float = 1e-8, random_state: int = 0) -> np.ndarray:
1067+
def jitter_coordinates(points: np.ndarray | PointCloud, *, scale: float = 1e-8, random_state: int = 0) -> np.ndarray:
10661068
"""Apply a deterministic small jitter for transversality-style retries.
10671069
10681070
Args:
@@ -1159,7 +1161,7 @@ def _coerce_source_complex(source: object) -> tuple[SimplicialComplex, list[int]
11591161
)
11601162

11611163

1162-
def _coerce_coordinates(source: object, *, coordinates: Optional[np.ndarray] = None) -> np.ndarray:
1164+
def _coerce_coordinates(source: object, *, coordinates: Optional[Union[np.ndarray, "PointCloud"]] = None) -> np.ndarray:
11631165
"""Coerce various source types and optional input into a coordinate array.
11641166
11651167
Args:

pysurgery/geometry/intrinsic_dimension.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
from __future__ import annotations
2323

2424
from dataclasses import dataclass
25-
from typing import Optional, Sequence
25+
from typing import Optional, Sequence, Union, TYPE_CHECKING
26+
if TYPE_CHECKING:
27+
from .point_cloud import PointCloud
2628

2729
import numpy as np
2830
from pydantic import BaseModel, ConfigDict, Field
@@ -164,8 +166,8 @@ class _NeighborhoodCache:
164166

165167

166168
def _coerce_point_cloud(
167-
data: np.ndarray | SimplicialComplex | object,
168-
coordinates: Optional[np.ndarray] = None,
169+
data: np.ndarray | PointCloud | SimplicialComplex | object,
170+
coordinates: Optional[np.ndarray | PointCloud] = None,
169171
) -> np.ndarray:
170172
"""Coerce a point-cloud-like input to a dense float array.
171173
@@ -185,7 +187,8 @@ def _coerce_point_cloud(
185187
TypeError: If the input type is not supported.
186188
ValueError: If the dimensions or sample size are invalid.
187189
"""
188-
if isinstance(data, np.ndarray):
190+
from .point_cloud import PointCloud
191+
if isinstance(data, (np.ndarray, PointCloud)):
189192
points = np.asarray(data, dtype=np.float64)
190193
elif coordinates is not None:
191194
points = np.asarray(coordinates, dtype=np.float64)
@@ -204,7 +207,7 @@ def _coerce_point_cloud(
204207

205208

206209
def _compute_knn_cache(
207-
points: Optional[np.ndarray],
210+
points: Optional[np.ndarray | PointCloud],
208211
*,
209212
k: int,
210213
distance_matrix: Optional[np.ndarray] = None,
@@ -373,10 +376,10 @@ def _aggregate_method_result(
373376

374377

375378
def levina_bickel_mle(
376-
data: np.ndarray | SimplicialComplex | object,
379+
data: np.ndarray | PointCloud | SimplicialComplex | object,
377380
k: int = 10,
378381
*,
379-
coordinates: Optional[np.ndarray] = None,
382+
coordinates: Optional[np.ndarray | PointCloud] = None,
380383
distance_matrix: Optional[np.ndarray] = None,
381384
) -> IntrinsicDimensionMethodResult:
382385
"""Estimate intrinsic dimension with the Levina--Bickel MLE.
@@ -440,9 +443,9 @@ def levina_bickel_mle(
440443

441444

442445
def twonn(
443-
data: np.ndarray | SimplicialComplex | object,
446+
data: np.ndarray | PointCloud | SimplicialComplex | object,
444447
*,
445-
coordinates: Optional[np.ndarray] = None,
448+
coordinates: Optional[np.ndarray | PointCloud] = None,
446449
distance_matrix: Optional[np.ndarray] = None,
447450
) -> IntrinsicDimensionMethodResult:
448451
"""Estimate intrinsic dimension using the TwoNN method.
@@ -525,10 +528,10 @@ def twonn(
525528

526529

527530
def local_pca_tangent_space_dimension(
528-
data: np.ndarray | SimplicialComplex | object,
531+
data: np.ndarray | PointCloud | SimplicialComplex | object,
529532
k: int = 12,
530533
*,
531-
coordinates: Optional[np.ndarray] = None,
534+
coordinates: Optional[np.ndarray | PointCloud] = None,
532535
distance_matrix: Optional[np.ndarray] = None,
533536
variance_threshold: float = 0.9,
534537
max_dimension: Optional[int] = None,
@@ -710,10 +713,10 @@ def exact_intrinsic_dimension(
710713

711714

712715
def estimate_intrinsic_dimension(
713-
data: np.ndarray | SimplicialComplex | object,
716+
data: np.ndarray | PointCloud | SimplicialComplex | object,
714717
k: int = 10,
715718
*,
716-
coordinates: Optional[np.ndarray] = None,
719+
coordinates: Optional[np.ndarray | PointCloud] = None,
717720
distance_matrix: Optional[np.ndarray] = None,
718721
methods: Sequence[str] = ("mle", "twonn", "pca"),
719722
variance_threshold: float = 0.9,

pysurgery/geometry/metrics.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import numpy as np
2020
import scipy.spatial.distance as dist
21-
from typing import Tuple, Optional
21+
from typing import Tuple, Optional, Union, TYPE_CHECKING
22+
if TYPE_CHECKING:
23+
from .point_cloud import PointCloud
2224
from ..bridge.julia_bridge import julia_engine
2325

24-
def orthogonal_procrustes(A: np.ndarray, B: np.ndarray, backend: str = "auto") -> Tuple[np.ndarray, np.ndarray, float]:
26+
def orthogonal_procrustes(A: Union[np.ndarray, "PointCloud"], B: Union[np.ndarray, "PointCloud"], backend: str = "auto") -> Tuple[np.ndarray, np.ndarray, float]:
2527
"""Find the optimal orthogonal transformation to align two point clouds.
2628
2729
What is Being Computed?:
@@ -55,6 +57,8 @@ def orthogonal_procrustes(A: np.ndarray, B: np.ndarray, backend: str = "auto") -
5557
Example:
5658
R, aligned, err = orthogonal_procrustes(A, B)
5759
"""
60+
A = np.asarray(A)
61+
B = np.asarray(B)
5862
# Normalize backend
5963
backend_norm = str(backend).lower().strip()
6064
use_julia = (backend_norm == "julia") or (backend_norm == "auto" and julia_engine.available)
@@ -77,7 +81,7 @@ def orthogonal_procrustes(A: np.ndarray, B: np.ndarray, backend: str = "auto") -
7781
disparity = float(np.linalg.norm(A - B_aligned))
7882
return R, B_aligned, disparity
7983

80-
def compute_distance_matrix(data: np.ndarray, metric: str = "euclidean", backend: str = "auto") -> np.ndarray:
84+
def compute_distance_matrix(data: Union[np.ndarray, "PointCloud"], metric: str = "euclidean", backend: str = "auto") -> np.ndarray:
8185
"""Computes pairwise distance matrix using the optimal implementation.
8286
8387
What is Being Computed?:
@@ -112,6 +116,7 @@ def compute_distance_matrix(data: np.ndarray, metric: str = "euclidean", backend
112116
Example:
113117
dist_mat = compute_distance_matrix(cloud, metric='euclidean')
114118
"""
119+
data = np.asarray(data)
115120
# Normalize backend
116121
backend_norm = str(backend).lower().strip()
117122
use_julia = (backend_norm == "julia") or (backend_norm == "auto" and julia_engine.available)
@@ -137,7 +142,7 @@ def compute_distance_matrix(data: np.ndarray, metric: str = "euclidean", backend
137142
else:
138143
raise ValueError(f"Unsupported metric: {metric}")
139144

140-
def frechet_distance(curve_a: np.ndarray, curve_b: np.ndarray, backend: str = "auto") -> float:
145+
def frechet_distance(curve_a: Union[np.ndarray, "PointCloud"], curve_b: Union[np.ndarray, "PointCloud"], backend: str = "auto") -> float:
141146
"""Computes the Discrete Fréchet distance between two ordered point sequences.
142147
143148
What is Being Computed?:
@@ -169,6 +174,8 @@ def frechet_distance(curve_a: np.ndarray, curve_b: np.ndarray, backend: str = "a
169174
Example:
170175
dist = frechet_distance(path1, path2)
171176
"""
177+
curve_a = np.asarray(curve_a)
178+
curve_b = np.asarray(curve_b)
172179
# Normalize backend
173180
backend_norm = str(backend).lower().strip()
174181
use_julia = (backend_norm == "julia") or (backend_norm == "auto" and julia_engine.available)
@@ -246,6 +253,12 @@ def gromov_wasserstein_distance(
246253
Example:
247254
gw = gromov_wasserstein_distance(D1, D2, epsilon=0.05)
248255
"""
256+
dist_matrix_A = np.asarray(dist_matrix_A)
257+
dist_matrix_B = np.asarray(dist_matrix_B)
258+
if p is not None:
259+
p = np.asarray(p)
260+
if q is not None:
261+
q = np.asarray(q)
249262
# Normalize backend
250263
backend_norm = str(backend).lower().strip()
251264
use_julia = (backend_norm == "julia") or (backend_norm == "auto" and julia_engine.available)
@@ -303,7 +316,7 @@ def gromov_wasserstein_distance(
303316
gw_dist = max(0.0, term1 + term2 - term3)
304317
return float(np.sqrt(gw_dist))
305318

306-
def farthest_point_sampling(points: np.ndarray, n_samples: int, initial_idx: int = 0) -> np.ndarray:
319+
def farthest_point_sampling(points: Union[np.ndarray, "PointCloud"], n_samples: int, initial_idx: int = 0) -> np.ndarray:
307320
"""Subsample a point cloud by greedily picking points that maximize distance to the current set.
308321
309322
What is Being Computed?:

pysurgery/topology/complexes.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from pysurgery.topology.fundamental_polyhedron import FundamentalPolyhedron
1515
from pysurgery.manifolds.handle_decompositions import HandleDecomposition
1616
from pysurgery.algebra.exact_sequences import ExactSequence
17+
from pysurgery.geometry.point_cloud import PointCloud
1718

1819
from pysurgery.algebra.math_core import get_sparse_snf_diagonal
1920
from pysurgery.core.exceptions import NotAManifoldError
@@ -2453,7 +2454,7 @@ def clear_cache(self, namespace: str | None = None) -> None:
24532454
@classmethod
24542455
def from_vietoris_rips(
24552456
cls,
2456-
points: np.ndarray,
2457+
points: Union[np.ndarray, "PointCloud"],
24572458
epsilon: float,
24582459
max_dimension: int,
24592460
coefficient_ring: str = "Z",
@@ -2660,7 +2661,7 @@ def from_maximal_simplices(
26602661
@classmethod
26612662
def from_point_cloud_cknn(
26622663
cls,
2663-
points: np.ndarray,
2664+
points: Union[np.ndarray, "PointCloud"],
26642665
k: int = 5,
26652666
delta: float = 1.0,
26662667
max_dimension: int = 2,
@@ -2747,7 +2748,7 @@ def from_point_cloud_cknn(
27472748
@classmethod
27482749
def from_alpha_complex(
27492750
cls,
2750-
points: np.ndarray,
2751+
points: Union[np.ndarray, "PointCloud"],
27512752
alpha: float | None = None,
27522753
*,
27532754
max_alpha_square: Optional[float] = None,
@@ -2856,7 +2857,7 @@ def from_alpha_complex(
28562857
@classmethod
28572858
def from_crust_algorithm(
28582859
cls,
2859-
points: np.ndarray,
2860+
points: Union[np.ndarray, "PointCloud"],
28602861
coefficient_ring: str = "Z",
28612862
backend: str = "auto",
28622863
) -> "SimplicialComplex":
@@ -2981,7 +2982,7 @@ def _from_delaunay_filtered(cls, points, value_fn, threshold, max_dimension, coe
29812982
@classmethod
29822983
def from_delaunay_rips(
29832984
cls,
2984-
points: np.ndarray,
2985+
points: Union[np.ndarray, "PointCloud"],
29852986
threshold: float | None = None,
29862987
max_dimension: int = 2,
29872988
*,
@@ -3015,7 +3016,7 @@ def from_delaunay_rips(
30153016
@classmethod
30163017
def from_delaunay_cech(
30173018
cls,
3018-
points: np.ndarray,
3019+
points: Union[np.ndarray, "PointCloud"],
30193020
threshold: float | None = None,
30203021
max_dimension: int = 2,
30213022
*,
@@ -3049,7 +3050,7 @@ def from_delaunay_cech(
30493050
@classmethod
30503051
def from_witness(
30513052
cls,
3052-
points: np.ndarray,
3053+
points: Union[np.ndarray, "PointCloud"],
30533054
n_landmarks: int,
30543055
alpha: float | None = None,
30553056
*,
@@ -3164,7 +3165,7 @@ def simplices_to_point_cloud(self) -> Dict[Tuple[int, ...], np.ndarray]:
31643165
"""Return the mapping from simplices to their point cloud coordinates."""
31653166
return self._simplices_to_point_cloud
31663167

3167-
def _generate_point_cloud_mappings(self, points: np.ndarray) -> None:
3168+
def _generate_point_cloud_mappings(self, points: Union[np.ndarray, "PointCloud"]) -> None:
31683169
"""Generate mappings between point cloud indices/coordinates and simplices.
31693170
31703171
Args:

0 commit comments

Comments
 (0)