swcgeom 0.19.4__cp312-cp312-macosx_14_0_arm64.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 +21 -0
  2. swcgeom/analysis/__init__.py +13 -0
  3. swcgeom/analysis/feature_extractor.py +454 -0
  4. swcgeom/analysis/features.py +218 -0
  5. swcgeom/analysis/lmeasure.py +750 -0
  6. swcgeom/analysis/sholl.py +201 -0
  7. swcgeom/analysis/trunk.py +183 -0
  8. swcgeom/analysis/visualization.py +191 -0
  9. swcgeom/analysis/visualization3d.py +81 -0
  10. swcgeom/analysis/volume.py +143 -0
  11. swcgeom/core/__init__.py +19 -0
  12. swcgeom/core/branch.py +129 -0
  13. swcgeom/core/branch_tree.py +65 -0
  14. swcgeom/core/compartment.py +107 -0
  15. swcgeom/core/node.py +130 -0
  16. swcgeom/core/path.py +155 -0
  17. swcgeom/core/population.py +341 -0
  18. swcgeom/core/swc.py +247 -0
  19. swcgeom/core/swc_utils/__init__.py +19 -0
  20. swcgeom/core/swc_utils/assembler.py +35 -0
  21. swcgeom/core/swc_utils/base.py +180 -0
  22. swcgeom/core/swc_utils/checker.py +107 -0
  23. swcgeom/core/swc_utils/io.py +204 -0
  24. swcgeom/core/swc_utils/normalizer.py +163 -0
  25. swcgeom/core/swc_utils/subtree.py +70 -0
  26. swcgeom/core/tree.py +384 -0
  27. swcgeom/core/tree_utils.py +277 -0
  28. swcgeom/core/tree_utils_impl.py +58 -0
  29. swcgeom/images/__init__.py +9 -0
  30. swcgeom/images/augmentation.py +149 -0
  31. swcgeom/images/contrast.py +87 -0
  32. swcgeom/images/folder.py +217 -0
  33. swcgeom/images/io.py +578 -0
  34. swcgeom/images/loaders/__init__.py +8 -0
  35. swcgeom/images/loaders/pbd.cpython-312-darwin.so +0 -0
  36. swcgeom/images/loaders/pbd.pyx +523 -0
  37. swcgeom/images/loaders/raw.cpython-312-darwin.so +0 -0
  38. swcgeom/images/loaders/raw.pyx +183 -0
  39. swcgeom/transforms/__init__.py +20 -0
  40. swcgeom/transforms/base.py +136 -0
  41. swcgeom/transforms/branch.py +223 -0
  42. swcgeom/transforms/branch_tree.py +74 -0
  43. swcgeom/transforms/geometry.py +270 -0
  44. swcgeom/transforms/image_preprocess.py +107 -0
  45. swcgeom/transforms/image_stack.py +219 -0
  46. swcgeom/transforms/images.py +206 -0
  47. swcgeom/transforms/mst.py +183 -0
  48. swcgeom/transforms/neurolucida_asc.py +498 -0
  49. swcgeom/transforms/path.py +56 -0
  50. swcgeom/transforms/population.py +36 -0
  51. swcgeom/transforms/tree.py +265 -0
  52. swcgeom/transforms/tree_assembler.py +161 -0
  53. swcgeom/utils/__init__.py +18 -0
  54. swcgeom/utils/debug.py +23 -0
  55. swcgeom/utils/download.py +119 -0
  56. swcgeom/utils/dsu.py +58 -0
  57. swcgeom/utils/ellipse.py +131 -0
  58. swcgeom/utils/file.py +90 -0
  59. swcgeom/utils/neuromorpho.py +581 -0
  60. swcgeom/utils/numpy_helper.py +70 -0
  61. swcgeom/utils/plotter_2d.py +134 -0
  62. swcgeom/utils/plotter_3d.py +35 -0
  63. swcgeom/utils/renderer.py +145 -0
  64. swcgeom/utils/sdf.py +324 -0
  65. swcgeom/utils/solid_geometry.py +154 -0
  66. swcgeom/utils/transforms.py +367 -0
  67. swcgeom/utils/volumetric_object.py +483 -0
  68. swcgeom-0.19.4.dist-info/METADATA +86 -0
  69. swcgeom-0.19.4.dist-info/RECORD +72 -0
  70. swcgeom-0.19.4.dist-info/WHEEL +5 -0
  71. swcgeom-0.19.4.dist-info/licenses/LICENSE +201 -0
  72. swcgeom-0.19.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """Finds the Minimum Volume Enclosing Ellipsoid."""
6
+
7
+ # pylint: disable=invalid-name
8
+
9
+ from functools import cached_property
10
+
11
+ import numpy as np
12
+ import numpy.linalg as la
13
+ import numpy.typing as npt
14
+
15
+ __all__ = ["mvee"]
16
+
17
+
18
+ class Ellipse:
19
+ """Ellipse wrapper."""
20
+
21
+ def __init__(
22
+ self, A: npt.NDArray[np.float64], centroid: npt.NDArray[np.float64]
23
+ ) -> None:
24
+ self.A = A
25
+ self.centroid = centroid
26
+
27
+ @property
28
+ def radii(self) -> tuple[float, float]:
29
+ # x, y radii.
30
+ _U, D, _V = self.svd
31
+ rx, ry = 1.0 / np.sqrt(D)
32
+ return rx, ry
33
+
34
+ @property
35
+ def a(self) -> float:
36
+ a, _b = self.axes
37
+ return a
38
+
39
+ @property
40
+ def b(self) -> float:
41
+ _a, b = self.axes
42
+ return b
43
+
44
+ @property
45
+ def axes(self) -> tuple[float, float]:
46
+ # Major and minor semi-axis of the ellipse.
47
+ rx, ry = self.radii
48
+ dx, dy = 2 * rx, 2 * ry
49
+ a, b = max(dx, dy), min(dx, dy)
50
+ return a, b
51
+
52
+ @property
53
+ def eccentricity(self) -> float:
54
+ a, b = self.axes
55
+ e = np.sqrt(a**2 - b**2) / a
56
+ return e
57
+
58
+ @property
59
+ def alpha(self) -> float:
60
+ # Orientation angle (with respect to the x axis counterclockwise).
61
+ _U, _D, V = self.svd
62
+ arcsin = -1.0 * np.rad2deg(np.arcsin(V[0][0]))
63
+ arccos = np.rad2deg(np.arccos(V[0][1]))
64
+ alpha = arccos if arcsin > 0.0 else -1.0 * arccos
65
+ return alpha
66
+
67
+ @cached_property
68
+ def svd(self):
69
+ # V is the rotation matrix that gives the orientation of the ellipsoid.
70
+ # https://en.wikipedia.org/wiki/Rotation_matrix
71
+ # http://mathworld.wolfram.com/RotationMatrix.html
72
+ U, D, V = la.svd(self.A)
73
+ return U, D, V
74
+
75
+
76
+ def mvee(points: npt.NDArray[np.floating], tol: float = 1e-3) -> Ellipse:
77
+ """Finds the Minimum Volume Enclosing Ellipsoid.
78
+
79
+ >>> # Create a set of 2D points
80
+ >>> points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]], dtype=np.float64)
81
+ >>> ellipse = mvee(points)
82
+
83
+ >>> # Check centroid is at center of points
84
+ >>> np.allclose(ellipse.centroid, [0.5, 0.5])
85
+ True
86
+
87
+ >>> # Check ellipse properties
88
+ >>> rx, ry = ellipse.radii
89
+ >>> np.allclose([rx, ry], [np.sqrt(2) / 2, np.sqrt(2) / 2], rtol=1e-5)
90
+ True
91
+
92
+ Reference:
93
+ 1. http://stackoverflow.com/questions/14016898/port-matlab-bounding-ellipsoid-code-to-python
94
+ 2. http://stackoverflow.com/questions/1768197/bounding-ellipse/1768440#1768440
95
+ 3. https://minillinim.github.io/GroopM/dev_docs/groopm.ellipsoid-pysrc.html
96
+
97
+ Args:
98
+ points: Array of shape (N, d) where N is number of points and d is dimension
99
+ tol: Tolerance for convergence
100
+
101
+ Returns:
102
+ Ellipse: An Ellipse object containing the minimum volume enclosing ellipse
103
+ """
104
+
105
+ A, centroid = _mvee(points, tol=tol)
106
+ return Ellipse(A, centroid)
107
+
108
+
109
+ def _mvee(
110
+ points: npt.NDArray[np.floating], tol: float = 1e-3
111
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
112
+ N, d = points.shape
113
+ Q = np.column_stack((points, np.ones(N))).T
114
+ err = tol + 1.0
115
+ u = np.ones(N) / N
116
+ while err > tol:
117
+ # assert u.sum() == 1 # invariant
118
+ X = np.dot(np.dot(Q, np.diag(u)), Q.T)
119
+ M = np.diag(np.dot(np.dot(Q.T, la.inv(X)), Q))
120
+ jdx = np.argmax(M)
121
+ step_size = (M[jdx] - d - 1.0) / ((d + 1) * (M[jdx] - 1.0))
122
+ new_u = (1 - step_size) * u
123
+ new_u[jdx] += step_size
124
+ err = la.norm(new_u - u)
125
+ u = new_u
126
+ c = np.dot(u, points)
127
+ A = (
128
+ la.inv(np.dot(np.dot(points.T, np.diag(u)), points) - np.multiply.outer(c, c))
129
+ / d
130
+ )
131
+ return A, c
swcgeom/utils/file.py ADDED
@@ -0,0 +1,90 @@
1
+ # SPDX-FileCopyrightText: 2022 - 2025 Zexin Yuan <pypi@yzx9.xyz>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """File related utils.
6
+
7
+ NOTE: If character coding is enabled, all denpendencies need to be installed, try:
8
+
9
+ ```sh
10
+ pip install swcgeom[all]
11
+ ```
12
+ """
13
+
14
+ import warnings
15
+ from io import BytesIO, TextIOBase, TextIOWrapper
16
+ from typing import Literal
17
+
18
+ __all__ = ["FileReader", "PathOrIO"]
19
+
20
+ PathOrIO = int | str | bytes | BytesIO | TextIOBase
21
+
22
+
23
+ class FileReader:
24
+ def __init__(
25
+ self,
26
+ fname: PathOrIO,
27
+ *,
28
+ encoding: Literal["detect"] | str = "utf-8",
29
+ low_confidence: float = 0.9,
30
+ **kwargs,
31
+ ) -> None:
32
+ """Read file.
33
+
34
+ Args:
35
+ fname: PathOrIO
36
+ encoding: The name of the encoding used to decode the file.
37
+ If is `detect`, we will try to detect the character encoding.
38
+ low_confidence: The confidence threshold for character encoding detection.
39
+ Used for detect character endocing, raising warning when parsing with
40
+ low confidence.
41
+ """
42
+ # TODO: support StringIO
43
+ self.fname, self.fb, self.f = "", None, None
44
+ if isinstance(fname, TextIOBase):
45
+ self.f = fname
46
+ encoding = fname.encoding # skip detect
47
+ elif isinstance(fname, BytesIO):
48
+ self.fb = fname
49
+ else:
50
+ self.fname = fname
51
+
52
+ if encoding == "detect":
53
+ encoding = detect_encoding(fname, low_confidence=low_confidence)
54
+ self.encoding = encoding
55
+ self.kwargs = kwargs
56
+
57
+ def __enter__(self) -> TextIOBase:
58
+ if isinstance(self.fb, BytesIO):
59
+ self.f = TextIOWrapper(self.fb, encoding=self.encoding)
60
+ elif self.f is None:
61
+ self.f = open(self.fname, "r", encoding=self.encoding, **self.kwargs)
62
+
63
+ return self.f
64
+
65
+ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
66
+ if self.f:
67
+ self.f.close()
68
+ return True
69
+
70
+
71
+ def detect_encoding(fname: PathOrIO, *, low_confidence: float = 0.9) -> str:
72
+ import chardet
73
+
74
+ if isinstance(fname, TextIOBase):
75
+ return fname.encoding
76
+ elif isinstance(fname, BytesIO):
77
+ data = fname.read()
78
+ fname.seek(0, 0)
79
+ else:
80
+ with open(fname, "rb") as f:
81
+ data = f.read()
82
+
83
+ result = chardet.detect(data)
84
+ encoding = result["encoding"] or "utf-8"
85
+ if result["confidence"] < low_confidence:
86
+ warnings.warn(
87
+ f"parse as `{encoding}` with low confidence "
88
+ f"{result['confidence']} in `{fname}`"
89
+ )
90
+ return encoding