swcgeom 0.16.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 +48 -12
  6. swcgeom/analysis/sholl.py +25 -28
  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 +31 -12
  12. swcgeom/core/branch.py +19 -3
  13. swcgeom/core/branch_tree.py +18 -4
  14. swcgeom/core/compartment.py +18 -2
  15. swcgeom/core/node.py +32 -3
  16. swcgeom/core/path.py +21 -9
  17. swcgeom/core/population.py +58 -29
  18. swcgeom/core/swc.py +26 -10
  19. swcgeom/core/swc_utils/__init__.py +21 -7
  20. swcgeom/core/swc_utils/assembler.py +15 -0
  21. swcgeom/core/swc_utils/base.py +23 -17
  22. swcgeom/core/swc_utils/checker.py +19 -12
  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 +56 -40
  27. swcgeom/core/tree_utils.py +28 -17
  28. swcgeom/core/tree_utils_impl.py +18 -3
  29. swcgeom/images/__init__.py +17 -2
  30. swcgeom/images/augmentation.py +18 -3
  31. swcgeom/images/contrast.py +15 -0
  32. swcgeom/images/folder.py +27 -26
  33. swcgeom/images/io.py +94 -117
  34. swcgeom/transforms/__init__.py +28 -12
  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 +15 -0
  40. swcgeom/transforms/image_stack.py +36 -9
  41. swcgeom/transforms/images.py +121 -14
  42. swcgeom/transforms/mst.py +15 -0
  43. swcgeom/transforms/neurolucida_asc.py +20 -7
  44. swcgeom/transforms/path.py +15 -0
  45. swcgeom/transforms/population.py +16 -3
  46. swcgeom/transforms/tree.py +84 -30
  47. swcgeom/transforms/tree_assembler.py +23 -7
  48. swcgeom/utils/__init__.py +27 -12
  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 +35 -23
  55. swcgeom/utils/numpy_helper.py +15 -0
  56. swcgeom/utils/plotter_2d.py +27 -6
  57. swcgeom/utils/plotter_3d.py +48 -0
  58. swcgeom/utils/renderer.py +21 -6
  59. swcgeom/utils/sdf.py +19 -7
  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.16.0.dist-info → swcgeom-0.18.3.dist-info}/LICENSE +1 -1
  64. {swcgeom-0.16.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.16.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.16.0.dist-info/RECORD +0 -67
  72. {swcgeom-0.16.0.dist-info → swcgeom-0.18.3.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,24 @@
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
  """NeuroMorpho.org.
2
17
 
3
18
  Examples
4
19
  --------
5
20
 
6
- Metadata:
21
+ Metadata:
7
22
 
8
23
  ```json
9
24
  {
@@ -81,7 +96,8 @@ import logging
81
96
  import math
82
97
  import os
83
98
  import urllib.parse
84
- from typing import Any, Callable, Dict, Iterable, List, Literal, Optional, Tuple
99
+ from collections.abc import Callable, Iterable
100
+ from typing import Any, Literal, Optional
85
101
 
86
102
  from tqdm import tqdm
87
103
 
@@ -116,7 +132,7 @@ SIZE_METADATA = 2 * GB
116
132
  SIZE_DATA = 20 * GB
117
133
 
118
134
  RESOURCES = Literal["morpho_cng", "morpho_source", "log_cng", "log_source"]
119
- DOWNLOAD_CONFIGS: Dict[RESOURCES, Tuple[str, int]] = {
135
+ DOWNLOAD_CONFIGS: dict[RESOURCES, tuple[str, int]] = {
120
136
  # name/path: (url, size)
121
137
  "morpho_cng": (URL_MORPHO_CNG, 20 * GB),
122
138
  "morpho_source": (URL_LOG_CNG, 512 * GB),
@@ -145,7 +161,7 @@ invalid_ids = [
145
161
  # fmt: on
146
162
 
147
163
 
148
- def neuromorpho_is_valid(metadata: Dict[str, Any]) -> bool:
164
+ def neuromorpho_is_valid(metadata: dict[str, Any]) -> bool:
149
165
  return metadata["neuron_id"] not in invalid_ids
150
166
 
151
167
 
@@ -209,7 +225,7 @@ class NeuroMorpho:
209
225
  self._info("skip download metadata")
210
226
 
211
227
  # file
212
- def dumps(keys: List[bytes]) -> str:
228
+ def dumps(keys: list[bytes]) -> str:
213
229
  return json.dumps([i.decode("utf-8") for i in keys])
214
230
 
215
231
  for name in resources:
@@ -238,8 +254,8 @@ class NeuroMorpho:
238
254
  self,
239
255
  dest: Optional[str] = None,
240
256
  *,
241
- group_by: Optional[str | Callable[[Dict[str, Any]], str | None]] = None,
242
- where: Optional[Callable[[Dict[str, Any]], bool]] = None,
257
+ group_by: Optional[str | Callable[[dict[str, Any]], str | None]] = None,
258
+ where: Optional[Callable[[dict[str, Any]], bool]] = None,
243
259
  encoding: str | None = "utf-8",
244
260
  ) -> None:
245
261
  r"""Convert lmdb format to SWCs.
@@ -249,11 +265,11 @@ class NeuroMorpho:
249
265
  path : str
250
266
  dest : str, optional
251
267
  If None, use `path/swc`.
252
- group_by : str | (metadata: Dict[str, Any]) -> str | None, optional
268
+ group_by : str | (metadata: dict[str, Any]) -> str | None, optional
253
269
  Group neurons by metadata. If a None is returned then no
254
270
  grouping. If a string is entered, use it as a metadata
255
271
  attribute name for grouping, e.g.: `archive`, `species`.
256
- where : (metadata: Dict[str, Any]) -> bool, optional
272
+ where : (metadata: dict[str, Any]) -> bool, optional
257
273
  Filter neurons by metadata.
258
274
  encoding : str | None, default to `utf-8`
259
275
  Change swc encoding, part of the original data is not utf-8
@@ -286,13 +302,9 @@ class NeuroMorpho:
286
302
  where = where or (lambda _: True)
287
303
  if isinstance(group_by, str):
288
304
  key = group_by
289
- group_by = lambda v: v[
290
- key
291
- ] # pylint: disable=unnecessary-lambda-assignment
305
+ group_by = lambda v: v[key] # pylint: disable=unnecessary-lambda-assignment
292
306
  elif group_by is None:
293
- group_by = (
294
- lambda _: None
295
- ) # pylint: disable=unnecessary-lambda-assignment
307
+ group_by = lambda _: None # pylint: disable=unnecessary-lambda-assignment
296
308
  items = []
297
309
  for k, v in tx_m.cursor():
298
310
  metadata = json.loads(v)
@@ -346,14 +358,14 @@ class NeuroMorpho:
346
358
  pages: Optional[Iterable[int]] = None,
347
359
  page_size: int = API_PAGE_SIZE_MAX,
348
360
  **kwargs,
349
- ) -> List[int]:
361
+ ) -> list[int]:
350
362
  r"""Download all neuron metadata.
351
363
 
352
364
  Parameters
353
365
  ----------
354
366
  path : str
355
367
  Path to save data.
356
- pages : list of int, optional
368
+ pages : List of int, optional
357
369
  If is None, download all pages.
358
370
  verbose : bool, default False
359
371
  Show verbose log.
@@ -362,7 +374,7 @@ class NeuroMorpho:
362
374
 
363
375
  Returns
364
376
  -------
365
- err_pages : list of int
377
+ err_pages : List of int
366
378
  Failed pages.
367
379
  """
368
380
 
@@ -402,7 +414,7 @@ class NeuroMorpho:
402
414
  override: bool = False,
403
415
  map_size: int = 512 * GB,
404
416
  **kwargs,
405
- ) -> List[bytes]:
417
+ ) -> list[bytes]:
406
418
  """Download files.
407
419
 
408
420
  Parameters
@@ -412,7 +424,7 @@ class NeuroMorpho:
412
424
  Path to save data.
413
425
  path_metadata : str
414
426
  Path to lmdb of metadata.
415
- keys : list of bytes, optional
427
+ keys : List of bytes, optional
416
428
  If exist, ignore `override` option. If None, download all key.
417
429
  override : bool, default False
418
430
  Override even exists.
@@ -422,7 +434,7 @@ class NeuroMorpho:
422
434
 
423
435
  Returns
424
436
  -------
425
- err_keys : list of str
437
+ err_keys : List of str
426
438
  Failed keys.
427
439
  """
428
440
 
@@ -459,7 +471,7 @@ class NeuroMorpho:
459
471
 
460
472
  def _get_metadata(
461
473
  self, page: int, page_size: int = API_PAGE_SIZE_MAX, **kwargs
462
- ) -> Dict[str, Any]:
474
+ ) -> dict[str, Any]:
463
475
  params = {
464
476
  "page": page,
465
477
  "size": page_size,
@@ -470,7 +482,7 @@ class NeuroMorpho:
470
482
  resp = self._get(url, **kwargs)
471
483
  return json.loads(resp)
472
484
 
473
- def _get_file(self, url: str, metadata: Dict[str, Any], **kwargs) -> bytes:
485
+ def _get_file(self, url: str, metadata: dict[str, Any], **kwargs) -> bytes:
474
486
  """Get file.
475
487
 
476
488
  Returns
@@ -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
@@ -1,6 +1,21 @@
1
- """Rendering related utils."""
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.
2
14
 
3
- from typing import Optional, Tuple
15
+
16
+ """2D Plotting utils."""
17
+
18
+ from typing import Optional
4
19
 
5
20
  import matplotlib.pyplot as plt
6
21
  import numpy as np
@@ -19,7 +34,12 @@ __all__ = ["draw_lines", "draw_direction_indicator", "draw_circles", "get_fig_ax
19
34
 
20
35
 
21
36
  def draw_lines(
22
- ax: Axes, lines: npt.NDArray[np.floating], camera: Camera, **kwargs
37
+ ax: Axes,
38
+ lines: npt.NDArray[np.floating],
39
+ camera: Camera,
40
+ joinstyle="round",
41
+ capstyle="round",
42
+ **kwargs,
23
43
  ) -> LineCollection:
24
44
  """Draw lines.
25
45
 
@@ -43,11 +63,12 @@ def draw_lines(
43
63
  starts, ends = np.dot(T, starts.T).T[:, 0:2], np.dot(T, ends.T).T[:, 0:2]
44
64
 
45
65
  edges = np.stack([starts, ends], axis=1)
46
- return ax.add_collection(LineCollection(edges, **kwargs)) # type: ignore
66
+ collection = LineCollection(edges, joinstyle=joinstyle, capstyle=capstyle, **kwargs) # type: ignore
67
+ return ax.add_collection(collection) # type: ignore
47
68
 
48
69
 
49
70
  def draw_direction_indicator(
50
- ax: Axes, camera: Camera, loc: Tuple[float, float]
71
+ ax: Axes, camera: Camera, loc: tuple[float, float]
51
72
  ) -> None:
52
73
  x, y = loc
53
74
  direction = camera.MV.dot(
@@ -120,7 +141,7 @@ def draw_circles(
120
141
 
121
142
  def get_fig_ax(
122
143
  fig: Optional[Figure] = None, ax: Optional[Axes] = None
123
- ) -> Tuple[Figure, Axes]:
144
+ ) -> tuple[Figure, Axes]:
124
145
  if fig is None and ax is not None:
125
146
  fig = ax.get_figure()
126
147
  assert fig is not None, "expecting a figure from the axes"
@@ -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,7 +1,22 @@
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, Tuple, cast
19
+ from typing import Literal, cast
5
20
 
6
21
  import numpy as np
7
22
  import numpy.typing as npt
@@ -15,9 +30,9 @@ from swcgeom.utils.transforms import (
15
30
 
16
31
  __all__ = ["CameraOptions", "Camera", "SimpleCamera", "palette"]
17
32
 
18
- CameraOption = Vec3f | Tuple[Vec3f, Vec3f] | Tuple[Vec3f, Vec3f, Vec3f]
33
+ CameraOption = Vec3f | tuple[Vec3f, Vec3f] | tuple[Vec3f, Vec3f, Vec3f]
19
34
  CameraPreset = Literal["xy", "yz", "zx", "yx", "zy", "xz"]
20
- CameraPresets: Dict[CameraPreset, Tuple[Vec3f, Vec3f, Vec3f]] = {
35
+ CameraPresets: dict[CameraPreset, tuple[Vec3f, Vec3f, Vec3f]] = {
21
36
  "xy": ((0.0, 0.0, 0.0), (+0.0, +0.0, -1.0), (+0.0, +1.0, +0.0)),
22
37
  "yz": ((0.0, 0.0, 0.0), (-1.0, +0.0, +0.0), (+0.0, +0.0, +1.0)),
23
38
  "zx": ((0.0, 0.0, 0.0), (+0.0, -1.0, +0.0), (+1.0, +0.0, +0.0)),
@@ -77,7 +92,7 @@ class SimpleCamera(Camera):
77
92
  if isinstance(camera[0], tuple):
78
93
  return cls((0, 0, 0), cast(Vec3f, camera), (0, 1, 0))
79
94
 
80
- return cls(*cast(Tuple[Vec3f, Vec3f, Vec3f], camera))
95
+ return cls(*cast(tuple[Vec3f, Vec3f, Vec3f], camera))
81
96
 
82
97
 
83
98
  class Palette:
@@ -85,8 +100,8 @@ class Palette:
85
100
 
86
101
  # pylint: disable=too-few-public-methods
87
102
 
88
- default: Dict[int, str]
89
- vaa3d: Dict[int, str]
103
+ default: dict[int, str]
104
+ vaa3d: dict[int, str]
90
105
 
91
106
  def __init__(self):
92
107
  default = [
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,6 +189,7 @@ class SDFDifference(SDF):
173
189
  return flags
174
190
 
175
191
 
192
+ @deprecated("Use `SDFUnion` instead")
176
193
  class SDFCompose(SDFUnion):
177
194
  """Compose multiple SDFs.
178
195
 
@@ -181,11 +198,6 @@ class SDFCompose(SDFUnion):
181
198
  """
182
199
 
183
200
  def __init__(self, sdfs: Iterable[SDF]) -> None:
184
- warnings.warn(
185
- "`SDFCompose` has been replace by `SDFUnion` since v0.14.0, "
186
- "and will be removed in next version",
187
- DeprecationWarning,
188
- )
189
201
  sdfs = list(sdfs)
190
202
  if len(sdfs) == 1:
191
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
  -------
@@ -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
  """Volumetric object.
2
17
 
3
18
  This library implements the calculation of volumes for any shape
@@ -16,7 +31,7 @@ computations.
16
31
 
17
32
  import warnings
18
33
  from abc import ABC, abstractmethod
19
- from typing import Generic, Optional, Tuple, TypeVar
34
+ from typing import Generic, Optional, TypeVar
20
35
 
21
36
  import numpy as np
22
37
  import numpy.typing as npt
@@ -95,7 +110,7 @@ class VolMCObject(VolObject, ABC):
95
110
  self.n_samples = n_samples
96
111
 
97
112
  @abstractmethod
98
- def sample(self, n: int) -> Tuple[npt.NDArray[np.float32], float]:
113
+ def sample(self, n: int) -> tuple[npt.NDArray[np.float32], float]:
99
114
  """Sample points.
100
115
 
101
116
  Parameters
@@ -172,7 +187,7 @@ class VolSDFObject(VolMCObject):
172
187
  super().__init__(**kwargs)
173
188
  self.sdf = sdf
174
189
 
175
- def sample(self, n: int) -> Tuple[npt.NDArray[np.float32], float]:
190
+ def sample(self, n: int) -> tuple[npt.NDArray[np.float32], float]:
176
191
  (min_x, min_y, min_z), (max_x, max_y, max_z) = self.sdf.bounding_box()
177
192
  samples = np.random.uniform(
178
193
  (min_x, min_y, min_z), (max_x, max_y, max_z), size=(n, 3)
@@ -186,17 +201,17 @@ class VolSDFObject(VolMCObject):
186
201
  def union(self, obj: VolObject) -> VolObject:
187
202
  if isinstance(obj, VolSDFObject):
188
203
  return VolSDFUnion(self, obj)
189
- return super().union(obj)
204
+ raise NotImplementedError()
190
205
 
191
206
  def intersect(self, obj: VolObject) -> VolObject:
192
207
  if isinstance(obj, VolSDFObject):
193
208
  return VolSDFIntersection(self, obj)
194
- return super().intersect(obj)
209
+ raise NotImplementedError()
195
210
 
196
211
  def subtract(self, obj: VolObject) -> VolObject:
197
212
  if isinstance(obj, VolSDFObject):
198
213
  return VolSDFDifference(self, obj)
199
- return super().subtract(obj)
214
+ raise NotImplementedError()
200
215
 
201
216
 
202
217
  T = TypeVar("T", bound=VolSDFObject)
@@ -386,9 +401,7 @@ class VolSphere2Intersection(VolSDFIntersection[VolSphere, VolSphere]):
386
401
  return VolSphere.calc_volume(min(r1, r2))
387
402
 
388
403
  part1 = (np.pi / (12 * d)) * (r1 + r2 - d) ** 2
389
- part2 = (
390
- d**2 + 2 * d * r1 - 3 * r1**2 + 2 * d * r2 - 3 * r2**2 + 6 * r1 * r2
391
- )
404
+ part2 = d**2 + 2 * d * r1 - 3 * r1**2 + 2 * d * r2 - 3 * r2**2 + 6 * r1 * r2
392
405
  return part1 * part2
393
406
 
394
407
 
@@ -497,7 +510,7 @@ class VolSphereFrustumConeUnion(VolSDFUnion[VolSphere, VolFrustumCone]):
497
510
  )
498
511
 
499
512
 
500
- def _tp3f(x: npt.NDArray) -> Tuple[float, float, float]:
513
+ def _tp3f(x: npt.NDArray) -> tuple[float, float, float]:
501
514
  """Convert to tuple of 3 floats."""
502
515
 
503
516
  assert len(x) == 3
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [2024] [Zexin Yuan]
189
+ Copyright [yyyy] [name of copyright owner]
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,33 +1,33 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: swcgeom
3
- Version: 0.16.0
3
+ Version: 0.18.3
4
4
  Summary: Neuron geometry library for swc format
5
- Author-email: yzx9 <yuan.zx@outlook.com>
5
+ Author-email: yzx9 <pypi@yzx9.xyz>
6
6
  License: Apache-2.0
7
7
  Project-URL: repository, https://github.com/yzx9/swcgeom
8
8
  Keywords: neuronscience,neuron,neuroanatomy,neuron-morphology
9
9
  Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: imagecodecs >=2023.3.16
13
- Requires-Dist: matplotlib >=3.5.2
14
- Requires-Dist: numpy >=1.22.3
15
- Requires-Dist: pandas >=1.4.2
16
- Requires-Dist: pynrrd >=1.0.0
17
- Requires-Dist: scipy >=1.9.1
18
- Requires-Dist: sdflit >=0.2.1
19
- Requires-Dist: seaborn >=0.12.0
20
- Requires-Dist: tifffile >=2022.8.12
21
- Requires-Dist: typing-extensions >=4.4.0
22
- Requires-Dist: tqdm >=4.46.1
23
- Requires-Dist: v3d-py-helper >=0.1.0
12
+ Requires-Dist: imagecodecs>=2023.3.16
13
+ Requires-Dist: matplotlib>=3.5.2
14
+ Requires-Dist: numpy>=1.22.3
15
+ Requires-Dist: pandas>=1.4.2
16
+ Requires-Dist: pynrrd>=1.1.0
17
+ Requires-Dist: scipy>=1.9.1
18
+ Requires-Dist: sdflit>=0.2.1
19
+ Requires-Dist: seaborn>=0.12.0
20
+ Requires-Dist: tifffile>=2022.8.12
21
+ Requires-Dist: typing_extensions>=4.4.0
22
+ Requires-Dist: tqdm>=4.46.1
23
+ Requires-Dist: v3d-py-helper>=0.4.1
24
24
  Provides-Extra: all
25
- Requires-Dist: beautifulsoup4 >=4.11.1 ; extra == 'all'
26
- Requires-Dist: certifi >=2023.5.7 ; extra == 'all'
27
- Requires-Dist: chardet >=5.2.0 ; extra == 'all'
28
- Requires-Dist: lmdb >=1.4.1 ; extra == 'all'
29
- Requires-Dist: requests >=2.0.0 ; extra == 'all'
30
- Requires-Dist: urllib3 >=1.26.0 ; extra == 'all'
25
+ Requires-Dist: beautifulsoup4>=4.11.1; extra == "all"
26
+ Requires-Dist: certifi>=2023.5.7; extra == "all"
27
+ Requires-Dist: chardet>=5.2.0; extra == "all"
28
+ Requires-Dist: lmdb>=1.4.1; extra == "all"
29
+ Requires-Dist: requests>=2.0.0; extra == "all"
30
+ Requires-Dist: urllib3>=1.26.0; extra == "all"
31
31
 
32
32
  # SWCGEOM
33
33
 
@@ -56,16 +56,20 @@ pip install build
56
56
  pip install --editable .
57
57
  ```
58
58
 
59
- Static analysis don't support import hook used in editable install for [PEP660](https://peps.python.org/pep-0660/) since upgrade to setuptools v64+, detail infomation at [setuptools#3518](https://github.com/pypa/setuptools/issues/3518), a workaround for vscode with pylance:
59
+ Static analysis don't support import hook used in editable install for
60
+ [PEP660](https://peps.python.org/pep-0660/) since upgrade to setuptools v64+,
61
+ detail information at [setuptools#3518](https://github.com/pypa/setuptools/issues/3518),
62
+ a workaround for vscode with pylance:
60
63
 
61
64
  ```json
62
65
  {
63
- "python.analysis.extraPaths": ["/path/to/this/project"]
66
+ "python.analysis.extraPaths": ["/path/to/this/project"]
64
67
  }
65
68
  ```
66
69
 
67
70
  ## LICENSE
68
71
 
69
- This work is licensed under a <a rel="license" href="https://www.apache.org/licenses/">Apache-2.0</a>.
72
+ This work is licensed under a
73
+ <a rel="license" href="https://www.apache.org/licenses/">Apache-2.0</a>.
70
74
 
71
75
  Copyright (c) 2022-present, Zexin Yuan