swcgeom 0.15.0__py3-none-any.whl → 0.18.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of swcgeom might be problematic. Click here for more details.

Files changed (72) hide show
  1. swcgeom/__init__.py +26 -1
  2. swcgeom/analysis/__init__.py +21 -8
  3. swcgeom/analysis/feature_extractor.py +43 -18
  4. swcgeom/analysis/features.py +250 -0
  5. swcgeom/analysis/lmeasure.py +857 -0
  6. swcgeom/analysis/sholl.py +55 -29
  7. swcgeom/analysis/trunk.py +27 -11
  8. swcgeom/analysis/visualization.py +24 -9
  9. swcgeom/analysis/visualization3d.py +100 -0
  10. swcgeom/analysis/volume.py +19 -4
  11. swcgeom/core/__init__.py +32 -9
  12. swcgeom/core/branch.py +28 -7
  13. swcgeom/core/branch_tree.py +18 -4
  14. swcgeom/core/{segment.py → compartment.py} +31 -10
  15. swcgeom/core/node.py +31 -10
  16. swcgeom/core/path.py +37 -10
  17. swcgeom/core/population.py +103 -34
  18. swcgeom/core/swc.py +26 -10
  19. swcgeom/core/swc_utils/__init__.py +21 -7
  20. swcgeom/core/swc_utils/assembler.py +27 -1
  21. swcgeom/core/swc_utils/base.py +25 -12
  22. swcgeom/core/swc_utils/checker.py +31 -14
  23. swcgeom/core/swc_utils/io.py +24 -7
  24. swcgeom/core/swc_utils/normalizer.py +20 -4
  25. swcgeom/core/swc_utils/subtree.py +17 -2
  26. swcgeom/core/tree.py +85 -72
  27. swcgeom/core/tree_utils.py +31 -16
  28. swcgeom/core/tree_utils_impl.py +18 -3
  29. swcgeom/images/__init__.py +17 -2
  30. swcgeom/images/augmentation.py +24 -4
  31. swcgeom/images/contrast.py +122 -0
  32. swcgeom/images/folder.py +97 -39
  33. swcgeom/images/io.py +108 -121
  34. swcgeom/transforms/__init__.py +28 -10
  35. swcgeom/transforms/base.py +17 -2
  36. swcgeom/transforms/branch.py +74 -8
  37. swcgeom/transforms/branch_tree.py +82 -0
  38. swcgeom/transforms/geometry.py +22 -7
  39. swcgeom/transforms/image_preprocess.py +115 -0
  40. swcgeom/transforms/image_stack.py +37 -13
  41. swcgeom/transforms/images.py +184 -7
  42. swcgeom/transforms/mst.py +20 -5
  43. swcgeom/transforms/neurolucida_asc.py +508 -0
  44. swcgeom/transforms/path.py +15 -0
  45. swcgeom/transforms/population.py +16 -3
  46. swcgeom/transforms/tree.py +89 -31
  47. swcgeom/transforms/tree_assembler.py +23 -7
  48. swcgeom/utils/__init__.py +27 -11
  49. swcgeom/utils/debug.py +15 -0
  50. swcgeom/utils/download.py +59 -21
  51. swcgeom/utils/dsu.py +15 -0
  52. swcgeom/utils/ellipse.py +18 -4
  53. swcgeom/utils/file.py +15 -0
  54. swcgeom/utils/neuromorpho.py +439 -302
  55. swcgeom/utils/numpy_helper.py +29 -4
  56. swcgeom/utils/plotter_2d.py +151 -0
  57. swcgeom/utils/plotter_3d.py +48 -0
  58. swcgeom/utils/renderer.py +49 -145
  59. swcgeom/utils/sdf.py +24 -8
  60. swcgeom/utils/solid_geometry.py +16 -3
  61. swcgeom/utils/transforms.py +17 -4
  62. swcgeom/utils/volumetric_object.py +23 -10
  63. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
  64. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/METADATA +28 -24
  65. swcgeom-0.18.3.dist-info/RECORD +67 -0
  66. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/WHEEL +1 -1
  67. swcgeom/_version.py +0 -16
  68. swcgeom/analysis/branch_features.py +0 -67
  69. swcgeom/analysis/node_features.py +0 -121
  70. swcgeom/analysis/path_features.py +0 -37
  71. swcgeom-0.15.0.dist-info/RECORD +0 -62
  72. {swcgeom-0.15.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,18 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Numpy related utils."""
2
17
 
3
18
  from contextlib import contextmanager
@@ -11,7 +26,7 @@ __all__ = ["padding1d", "numpy_err"]
11
26
 
12
27
  def padding1d(
13
28
  n: int,
14
- v: npt.NDArray | None,
29
+ v: npt.ArrayLike | None,
15
30
  padding_value: Any = 0,
16
31
  dtype: npt.DTypeLike | None = None,
17
32
  ) -> npt.NDArray:
@@ -31,9 +46,19 @@ def padding1d(
31
46
  x will used, otherwise defaults to `~numpy.float32`.
32
47
  """
33
48
 
34
- dtype = dtype or (v is not None and v.dtype) or np.float32
35
- v = np.zeros(n, dtype=dtype) if v is None else v
36
- v = v.astype(dtype) if v.dtype != dtype else v
49
+ if not isinstance(v, np.ndarray):
50
+ dtype = dtype or np.float32
51
+ if v is not None:
52
+ v = np.array(v, dtype=dtype)
53
+ else:
54
+ v = np.zeros(n, dtype=dtype)
55
+
56
+ if dtype is None:
57
+ dtype = v.dtype
58
+
59
+ if v.dtype != dtype:
60
+ v = v.astype(dtype)
61
+
37
62
  assert v.ndim == 1
38
63
 
39
64
  if v.shape[0] >= n:
@@ -0,0 +1,151 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """2D Plotting utils."""
17
+
18
+ from typing import Optional
19
+
20
+ import matplotlib.pyplot as plt
21
+ import numpy as np
22
+ import numpy.typing as npt
23
+ from matplotlib import cm
24
+ from matplotlib.axes import Axes
25
+ from matplotlib.collections import LineCollection, PatchCollection
26
+ from matplotlib.colors import Colormap, Normalize
27
+ from matplotlib.figure import Figure
28
+ from matplotlib.patches import Circle
29
+
30
+ from swcgeom.utils.renderer import Camera
31
+ from swcgeom.utils.transforms import to_homogeneous, translate3d
32
+
33
+ __all__ = ["draw_lines", "draw_direction_indicator", "draw_circles", "get_fig_ax"]
34
+
35
+
36
+ def draw_lines(
37
+ ax: Axes,
38
+ lines: npt.NDArray[np.floating],
39
+ camera: Camera,
40
+ joinstyle="round",
41
+ capstyle="round",
42
+ **kwargs,
43
+ ) -> LineCollection:
44
+ """Draw lines.
45
+
46
+ Parameters
47
+ ----------
48
+ ax : ~matplotlib.axes.Axes
49
+ lines : A collection of coords of lines
50
+ Excepting a ndarray of shape (N, 2, 3), the axis-2 holds two points,
51
+ and the axis-3 holds the coordinates (x, y, z).
52
+ camera : Camera
53
+ Camera position.
54
+ **kwargs : dict[str, Unknown]
55
+ Forwarded to `~matplotlib.collections.LineCollection`.
56
+ """
57
+
58
+ T = camera.MVP
59
+ T = translate3d(*camera.position).dot(T) # keep origin
60
+
61
+ starts, ends = lines[:, 0], lines[:, 1]
62
+ starts, ends = to_homogeneous(starts, 1), to_homogeneous(ends, 1)
63
+ starts, ends = np.dot(T, starts.T).T[:, 0:2], np.dot(T, ends.T).T[:, 0:2]
64
+
65
+ edges = np.stack([starts, ends], axis=1)
66
+ collection = LineCollection(edges, joinstyle=joinstyle, capstyle=capstyle, **kwargs) # type: ignore
67
+ return ax.add_collection(collection) # type: ignore
68
+
69
+
70
+ def draw_direction_indicator(
71
+ ax: Axes, camera: Camera, loc: tuple[float, float]
72
+ ) -> None:
73
+ x, y = loc
74
+ direction = camera.MV.dot(
75
+ [
76
+ [1, 0, 0, 1],
77
+ [0, 1, 0, 1],
78
+ [0, 0, 1, 1],
79
+ [0, 0, 0, 1],
80
+ ]
81
+ )
82
+
83
+ arrow_length, text_offset = 0.05, 0.05 # TODO: may still overlap
84
+ text_colors = [["x", "red"], ["y", "green"], ["z", "blue"]]
85
+ for (dx, dy, dz, _), (text, color) in zip(direction, text_colors):
86
+ if 1 - abs(dz) < 1e-5:
87
+ continue
88
+
89
+ ax.arrow(
90
+ x,
91
+ y,
92
+ arrow_length * dx,
93
+ arrow_length * dy,
94
+ head_length=0.02,
95
+ head_width=0.01,
96
+ color=color,
97
+ transform=ax.transAxes,
98
+ )
99
+
100
+ ax.text(
101
+ x + (arrow_length + text_offset) * dx,
102
+ y + (arrow_length + text_offset) * dy,
103
+ text,
104
+ color=color,
105
+ transform=ax.transAxes,
106
+ horizontalalignment="center",
107
+ verticalalignment="center",
108
+ )
109
+
110
+
111
+ def draw_circles(
112
+ ax: Axes,
113
+ x: npt.NDArray,
114
+ y: npt.NDArray,
115
+ *,
116
+ y_min: Optional[float] = None,
117
+ y_max: Optional[float] = None,
118
+ cmap: str | Colormap = "viridis",
119
+ ) -> PatchCollection:
120
+ """Draw a sequential of circles."""
121
+
122
+ y_min = y.min() if y_min is None else y_min
123
+ y_max = y.max() if y_max is None else y_max
124
+ norm = Normalize(y_min, y_max)
125
+
126
+ color_map = cmap if isinstance(cmap, Colormap) else cm.get_cmap(name=cmap)
127
+ colors = color_map(norm(y))
128
+
129
+ circles = [
130
+ Circle((0, 0), xi, color=color) for xi, color in reversed(list(zip(x, colors)))
131
+ ]
132
+ patches = PatchCollection(circles, match_original=True)
133
+ patches.set_cmap(color_map)
134
+ patches.set_norm(norm)
135
+ patches: PatchCollection = ax.add_collection(patches) # type: ignore
136
+
137
+ ax.set_aspect(1)
138
+ ax.autoscale()
139
+ return patches
140
+
141
+
142
+ def get_fig_ax(
143
+ fig: Optional[Figure] = None, ax: Optional[Axes] = None
144
+ ) -> tuple[Figure, Axes]:
145
+ if fig is None and ax is not None:
146
+ fig = ax.get_figure()
147
+ assert fig is not None, "expecting a figure from the axes"
148
+
149
+ fig = fig or plt.gcf()
150
+ ax = ax or plt.gca()
151
+ return fig, ax
@@ -0,0 +1,48 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ """3D Plotting utils."""
17
+
18
+ import numpy as np
19
+ import numpy.typing as npt
20
+ from mpl_toolkits.mplot3d import Axes3D
21
+ from mpl_toolkits.mplot3d.art3d import Line3DCollection
22
+
23
+ __all__ = ["draw_lines_3d"]
24
+
25
+
26
+ def draw_lines_3d(
27
+ ax: Axes3D,
28
+ lines: npt.NDArray[np.floating],
29
+ joinstyle="round",
30
+ capstyle="round",
31
+ **kwargs,
32
+ ):
33
+ """Draw lines.
34
+
35
+ Parameters
36
+ ----------
37
+ ax : ~matplotlib.axes.Axes
38
+ lines : A collection of coords of lines
39
+ Excepting a ndarray of shape (N, 2, 3), the axis-2 holds two points,
40
+ and the axis-3 holds the coordinates (x, y, z).
41
+ **kwargs : dict[str, Unknown]
42
+ Forwarded to `~mpl_toolkits.mplot3d.art3d.Line3DCollection`.
43
+ """
44
+
45
+ line_collection = Line3DCollection(
46
+ lines, joinstyle=joinstyle, capstyle=capstyle, **kwargs
47
+ ) # type: ignore
48
+ return ax.add_collection3d(line_collection)
swcgeom/utils/renderer.py CHANGED
@@ -1,40 +1,38 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Rendering related utils."""
2
17
 
3
18
  from functools import cached_property
4
- from typing import Dict, Literal, Optional, Tuple, cast
19
+ from typing import Literal, cast
5
20
 
6
- import matplotlib.pyplot as plt
7
21
  import numpy as np
8
22
  import numpy.typing as npt
9
- from matplotlib import cm
10
- from matplotlib.axes import Axes
11
- from matplotlib.collections import LineCollection, PatchCollection
12
- from matplotlib.colors import Colormap, Normalize
13
- from matplotlib.figure import Figure
14
- from matplotlib.patches import Circle
15
23
  from typing_extensions import Self
16
24
 
17
25
  from swcgeom.utils.transforms import (
18
26
  Vec3f,
19
27
  model_view_transformation,
20
28
  orthographic_projection_simple,
21
- to_homogeneous,
22
- translate3d,
23
29
  )
24
30
 
25
- __all__ = [
26
- "CameraOptions",
27
- "SimpleCamera",
28
- "palette",
29
- "draw_lines",
30
- "draw_direction_indicator",
31
- "draw_circles",
32
- "get_fig_ax",
33
- ]
34
-
35
- CameraOption = Vec3f | Tuple[Vec3f, Vec3f] | Tuple[Vec3f, Vec3f, Vec3f]
31
+ __all__ = ["CameraOptions", "Camera", "SimpleCamera", "palette"]
32
+
33
+ CameraOption = Vec3f | tuple[Vec3f, Vec3f] | tuple[Vec3f, Vec3f, Vec3f]
36
34
  CameraPreset = Literal["xy", "yz", "zx", "yx", "zy", "xz"]
37
- CameraPresets: Dict[CameraPreset, Tuple[Vec3f, Vec3f, Vec3f]] = {
35
+ CameraPresets: dict[CameraPreset, tuple[Vec3f, Vec3f, Vec3f]] = {
38
36
  "xy": ((0.0, 0.0, 0.0), (+0.0, +0.0, -1.0), (+0.0, +1.0, +0.0)),
39
37
  "yz": ((0.0, 0.0, 0.0), (-1.0, +0.0, +0.0), (+0.0, +0.0, +1.0)),
40
38
  "zx": ((0.0, 0.0, 0.0), (+0.0, -1.0, +0.0), (+1.0, +0.0, +0.0)),
@@ -45,13 +43,35 @@ CameraPresets: Dict[CameraPreset, Tuple[Vec3f, Vec3f, Vec3f]] = {
45
43
  CameraOptions = CameraOption | CameraPreset
46
44
 
47
45
 
48
- class SimpleCamera:
46
+ class Camera:
47
+ _position: Vec3f
48
+ _look_at: Vec3f
49
+ _up: Vec3f
50
+
51
+ # fmt: off
52
+ @property
53
+ def position(self) -> Vec3f: return self._position
54
+ @property
55
+ def look_at(self) -> Vec3f: return self._look_at
56
+ @property
57
+ def up(self) -> Vec3f: return self._up
58
+
59
+ @property
60
+ def MV(self) -> npt.NDArray[np.float32]: raise NotImplementedError()
61
+ @property
62
+ def P(self) -> npt.NDArray[np.float32]: raise NotImplementedError()
63
+ @property
64
+ def MVP(self) -> npt.NDArray[np.float32]: return self.P.dot(self.MV)
65
+ # fmt: on
66
+
67
+
68
+ class SimpleCamera(Camera):
49
69
  """Simplest camera."""
50
70
 
51
71
  def __init__(self, position: Vec3f, look_at: Vec3f, up: Vec3f):
52
- self.position = position
53
- self.look_at = look_at
54
- self.up = up
72
+ self._position = position
73
+ self._look_at = look_at
74
+ self._up = up
55
75
 
56
76
  @cached_property
57
77
  def MV(self) -> npt.NDArray[np.float32]: # pylint: disable=invalid-name
@@ -61,10 +81,6 @@ class SimpleCamera:
61
81
  def P(self) -> npt.NDArray[np.float32]: # pylint: disable=invalid-name
62
82
  return orthographic_projection_simple()
63
83
 
64
- @cached_property
65
- def MVP(self) -> npt.NDArray[np.float32]: # pylint: disable=invalid-name
66
- return self.P.dot(self.MV)
67
-
68
84
  @classmethod
69
85
  def from_options(cls, camera: CameraOptions) -> Self:
70
86
  if isinstance(camera, str):
@@ -76,7 +92,7 @@ class SimpleCamera:
76
92
  if isinstance(camera[0], tuple):
77
93
  return cls((0, 0, 0), cast(Vec3f, camera), (0, 1, 0))
78
94
 
79
- return cls(*cast(Tuple[Vec3f, Vec3f, Vec3f], camera))
95
+ return cls(*cast(tuple[Vec3f, Vec3f, Vec3f], camera))
80
96
 
81
97
 
82
98
  class Palette:
@@ -84,8 +100,8 @@ class Palette:
84
100
 
85
101
  # pylint: disable=too-few-public-methods
86
102
 
87
- default: Dict[int, str]
88
- vaa3d: Dict[int, str]
103
+ default: dict[int, str]
104
+ vaa3d: dict[int, str]
89
105
 
90
106
  def __init__(self):
91
107
  default = [
@@ -130,115 +146,3 @@ class Palette:
130
146
 
131
147
 
132
148
  palette = Palette()
133
-
134
-
135
- def draw_lines(
136
- ax: Axes, lines: npt.NDArray[np.floating], camera: SimpleCamera, **kwargs
137
- ) -> LineCollection:
138
- """Draw lines.
139
-
140
- Parameters
141
- ----------
142
- ax : ~matplotlib.axes.Axes
143
- lines : A collection of coords of lines
144
- Excepting a ndarray of shape (N, 2, 3), the axis-2 holds two points,
145
- and the axis-3 holds the coordinates (x, y, z).
146
- camera : Camera
147
- Camera position.
148
- **kwargs : dict[str, Unknown]
149
- Forwarded to `~matplotlib.collections.LineCollection`.
150
- """
151
-
152
- T = camera.MVP
153
- T = translate3d(*camera.position).dot(T) # keep origin
154
-
155
- starts, ends = lines[:, 0], lines[:, 1]
156
- starts, ends = to_homogeneous(starts, 1), to_homogeneous(ends, 1)
157
- starts, ends = np.dot(T, starts.T).T[:, 0:2], np.dot(T, ends.T).T[:, 0:2]
158
-
159
- edges = np.stack([starts, ends], axis=1)
160
- return ax.add_collection(LineCollection(edges, **kwargs)) # type: ignore
161
-
162
-
163
- def draw_direction_indicator(
164
- ax: Axes, camera: SimpleCamera, loc: Tuple[float, float]
165
- ) -> None:
166
- x, y = loc
167
- direction = camera.MV.dot(
168
- [
169
- [1, 0, 0, 1],
170
- [0, 1, 0, 1],
171
- [0, 0, 1, 1],
172
- [0, 0, 0, 1],
173
- ]
174
- )
175
-
176
- arrow_length, text_offset = 0.05, 0.05 # TODO: may still overlap
177
- text_colors = [["x", "red"], ["y", "green"], ["z", "blue"]]
178
- for (dx, dy, dz, _), (text, color) in zip(direction, text_colors):
179
- if 1 - abs(dz) < 1e-5:
180
- continue
181
-
182
- ax.arrow(
183
- x,
184
- y,
185
- arrow_length * dx,
186
- arrow_length * dy,
187
- head_length=0.02,
188
- head_width=0.01,
189
- color=color,
190
- transform=ax.transAxes,
191
- )
192
-
193
- ax.text(
194
- x + (arrow_length + text_offset) * dx,
195
- y + (arrow_length + text_offset) * dy,
196
- text,
197
- color=color,
198
- transform=ax.transAxes,
199
- horizontalalignment="center",
200
- verticalalignment="center",
201
- )
202
-
203
-
204
- def draw_circles(
205
- ax: Axes,
206
- x: npt.NDArray,
207
- y: npt.NDArray,
208
- *,
209
- y_min: Optional[float] = None,
210
- y_max: Optional[float] = None,
211
- cmap: str | Colormap = "viridis",
212
- ) -> PatchCollection:
213
- """Draw a sequential of circles."""
214
-
215
- y_min = y.min() if y_min is None else y_min
216
- y_max = y.max() if y_max is None else y_max
217
- norm = Normalize(y_min, y_max)
218
-
219
- color_map = cmap if isinstance(cmap, Colormap) else cm.get_cmap(name=cmap)
220
- colors = color_map(norm(y))
221
-
222
- circles = [
223
- Circle((0, 0), xi, color=color) for xi, color in reversed(list(zip(x, colors)))
224
- ]
225
- patches = PatchCollection(circles, match_original=True)
226
- patches.set_cmap(color_map)
227
- patches.set_norm(norm)
228
- patches: PatchCollection = ax.add_collection(patches) # type: ignore
229
-
230
- ax.set_aspect(1)
231
- ax.autoscale()
232
- return patches
233
-
234
-
235
- def get_fig_ax(
236
- fig: Figure | None = None, ax: Axes | None = None
237
- ) -> Tuple[Figure, Axes]:
238
- if fig is None:
239
- fig = plt.gcf() if ax is None else ax.get_figure()
240
-
241
- if ax is None:
242
- ax = fig.gca()
243
-
244
- return fig, ax
swcgeom/utils/sdf.py CHANGED
@@ -1,3 +1,18 @@
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
1
16
  """Signed distance functions.
2
17
 
3
18
  Refs: https://iquilezles.org/articles/distfunctions/
@@ -10,10 +25,11 @@ the future, use `sdflit` instead.
10
25
 
11
26
  import warnings
12
27
  from abc import ABC, abstractmethod
13
- from typing import Iterable, Tuple
28
+ from collections.abc import Iterable
14
29
 
15
30
  import numpy as np
16
31
  import numpy.typing as npt
32
+ from typing_extensions import deprecated
17
33
 
18
34
  from swcgeom.utils.solid_geometry import project_vector_on_plane
19
35
 
@@ -29,7 +45,7 @@ __all__ = [
29
45
  ]
30
46
 
31
47
  # Axis-aligned bounding box, tuple of array of shape (3,)
32
- AABB = Tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]
48
+ AABB = tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]
33
49
 
34
50
 
35
51
  class SDF(ABC):
@@ -173,15 +189,15 @@ class SDFDifference(SDF):
173
189
  return flags
174
190
 
175
191
 
192
+ @deprecated("Use `SDFUnion` instead")
176
193
  class SDFCompose(SDFUnion):
177
- """Compose multiple SDFs."""
194
+ """Compose multiple SDFs.
195
+
196
+ .. deprecated:: 0.14.0
197
+ Use :cls:`SDFUnion` instead.
198
+ """
178
199
 
179
200
  def __init__(self, sdfs: Iterable[SDF]) -> None:
180
- warnings.warn(
181
- "`SDFCompose` has been replace by `SDFUnion` since v0.14.0, "
182
- "and will be removed in next version",
183
- DeprecationWarning,
184
- )
185
201
  sdfs = list(sdfs)
186
202
  if len(sdfs) == 1:
187
203
  warnings.warn("compose only one SDF, use SDFCompose.compose instead")
@@ -1,6 +1,19 @@
1
- """Solid Geometry."""
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
2
15
 
3
- from typing import List, Tuple
16
+ """Solid Geometry."""
4
17
 
5
18
  import numpy as np
6
19
  import numpy.typing as npt
@@ -31,7 +44,7 @@ def find_sphere_line_intersection(
31
44
  sphere_radius: float,
32
45
  line_point_a: npt.NDArray,
33
46
  line_point_b: npt.NDArray,
34
- ) -> List[Tuple[float, npt.NDArray[np.float64]]]:
47
+ ) -> list[tuple[float, npt.NDArray[np.float64]]]:
35
48
  A = np.array(line_point_a)
36
49
  B = np.array(line_point_b)
37
50
  C = np.array(sphere_center)
@@ -1,6 +1,19 @@
1
- """3D geometry transformations."""
1
+ # Copyright 2022-2025 Zexin Yuan
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
2
15
 
3
- from typing import Tuple
16
+ """3D geometry transformations."""
4
17
 
5
18
  import numpy as np
6
19
  import numpy.typing as npt
@@ -19,11 +32,11 @@ __all__ = [
19
32
  "orthographic_projection_simple",
20
33
  ]
21
34
 
22
- Vec3f = Tuple[float, float, float]
35
+ Vec3f = tuple[float, float, float]
23
36
 
24
37
 
25
38
  def angle(a: npt.ArrayLike, b: npt.ArrayLike) -> float:
26
- """Get the agnle of vectors.
39
+ """Get the angle of vectors.
27
40
 
28
41
  Returns
29
42
  -------