swcgeom 0.18.1__py3-none-any.whl → 0.19.0__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 (68) hide show
  1. swcgeom/__init__.py +12 -1
  2. swcgeom/analysis/__init__.py +6 -6
  3. swcgeom/analysis/feature_extractor.py +22 -24
  4. swcgeom/analysis/features.py +18 -40
  5. swcgeom/analysis/lmeasure.py +227 -323
  6. swcgeom/analysis/sholl.py +17 -23
  7. swcgeom/analysis/trunk.py +23 -28
  8. swcgeom/analysis/visualization.py +37 -44
  9. swcgeom/analysis/visualization3d.py +16 -25
  10. swcgeom/analysis/volume.py +33 -47
  11. swcgeom/core/__init__.py +12 -13
  12. swcgeom/core/branch.py +10 -17
  13. swcgeom/core/branch_tree.py +3 -2
  14. swcgeom/core/compartment.py +1 -1
  15. swcgeom/core/node.py +3 -6
  16. swcgeom/core/path.py +11 -16
  17. swcgeom/core/population.py +32 -51
  18. swcgeom/core/swc.py +25 -16
  19. swcgeom/core/swc_utils/__init__.py +10 -12
  20. swcgeom/core/swc_utils/assembler.py +5 -12
  21. swcgeom/core/swc_utils/base.py +40 -31
  22. swcgeom/core/swc_utils/checker.py +3 -8
  23. swcgeom/core/swc_utils/io.py +32 -47
  24. swcgeom/core/swc_utils/normalizer.py +17 -23
  25. swcgeom/core/swc_utils/subtree.py +13 -20
  26. swcgeom/core/tree.py +61 -51
  27. swcgeom/core/tree_utils.py +36 -49
  28. swcgeom/core/tree_utils_impl.py +4 -6
  29. swcgeom/images/__init__.py +2 -2
  30. swcgeom/images/augmentation.py +23 -39
  31. swcgeom/images/contrast.py +22 -46
  32. swcgeom/images/folder.py +32 -34
  33. swcgeom/images/io.py +80 -121
  34. swcgeom/transforms/__init__.py +13 -13
  35. swcgeom/transforms/base.py +28 -19
  36. swcgeom/transforms/branch.py +31 -41
  37. swcgeom/transforms/branch_tree.py +3 -1
  38. swcgeom/transforms/geometry.py +13 -4
  39. swcgeom/transforms/image_preprocess.py +2 -0
  40. swcgeom/transforms/image_stack.py +40 -35
  41. swcgeom/transforms/images.py +31 -24
  42. swcgeom/transforms/mst.py +27 -40
  43. swcgeom/transforms/neurolucida_asc.py +13 -13
  44. swcgeom/transforms/path.py +4 -0
  45. swcgeom/transforms/population.py +4 -0
  46. swcgeom/transforms/tree.py +16 -11
  47. swcgeom/transforms/tree_assembler.py +37 -54
  48. swcgeom/utils/__init__.py +12 -12
  49. swcgeom/utils/download.py +7 -14
  50. swcgeom/utils/dsu.py +12 -0
  51. swcgeom/utils/ellipse.py +26 -14
  52. swcgeom/utils/file.py +8 -13
  53. swcgeom/utils/neuromorpho.py +78 -92
  54. swcgeom/utils/numpy_helper.py +15 -12
  55. swcgeom/utils/plotter_2d.py +10 -16
  56. swcgeom/utils/plotter_3d.py +7 -9
  57. swcgeom/utils/renderer.py +16 -8
  58. swcgeom/utils/sdf.py +12 -23
  59. swcgeom/utils/solid_geometry.py +58 -2
  60. swcgeom/utils/transforms.py +164 -100
  61. swcgeom/utils/volumetric_object.py +29 -53
  62. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/METADATA +7 -6
  63. swcgeom-0.19.0.dist-info/RECORD +67 -0
  64. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/WHEEL +1 -1
  65. swcgeom/_version.py +0 -16
  66. swcgeom-0.18.1.dist-info/RECORD +0 -68
  67. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info/licenses}/LICENSE +0 -0
  68. {swcgeom-0.18.1.dist-info → swcgeom-0.19.0.dist-info}/top_level.txt +0 -0
@@ -15,12 +15,10 @@
15
15
 
16
16
  """SWC util wrapper for tree, split to avoid circle imports.
17
17
 
18
- Notes
19
- -----
20
- Do not import `Tree` and keep this file minimized.
18
+ NOTE: Do not import `Tree` and keep this file minimized.
21
19
  """
22
20
 
23
- from typing import Any, Optional
21
+ from typing import Any
24
22
 
25
23
  import numpy as np
26
24
  import numpy.typing as npt
@@ -35,7 +33,7 @@ TreeArgs = tuple[int, dict[str, npt.NDArray[Any]], str, SWCNames]
35
33
 
36
34
 
37
35
  def get_subtree_impl(
38
- swc_like: SWCLike, n: int, *, out_mapping: Optional[Mapping] = None
36
+ swc_like: SWCLike, n: int, *, out_mapping: Mapping | None = None
39
37
  ) -> TreeArgs:
40
38
  ids = []
41
39
  topo = (swc_like.id(), swc_like.pid())
@@ -51,7 +49,7 @@ def to_subtree_impl(
51
49
  swc_like: SWCLike,
52
50
  sub: Topology,
53
51
  *,
54
- out_mapping: Optional[Mapping] = None,
52
+ out_mapping: Mapping | None = None,
55
53
  ) -> TreeArgs:
56
54
  (new_id, new_pid), mapping = to_sub_topology(sub)
57
55
 
@@ -15,5 +15,5 @@
15
15
 
16
16
  """Image Stack Related."""
17
17
 
18
- from swcgeom.images.folder import *
19
- from swcgeom.images.io import *
18
+ from swcgeom.images.folder import * # noqa: F403
19
+ from swcgeom.images.io import * # noqa: F403
@@ -15,13 +15,11 @@
15
15
 
16
16
  """Play augment in image stack.
17
17
 
18
- Notes
19
- -----
20
- This is expremental code, and the API is subject to change.
18
+ NOTE: This is expremental code, and the API is subject to change.
21
19
  """
22
20
 
23
21
  import random
24
- from typing import Literal, Optional
22
+ from typing import Literal
25
23
 
26
24
  import numpy as np
27
25
  import numpy.typing as npt
@@ -67,7 +65,7 @@ class Augmentation:
67
65
  self.seed = seed
68
66
  self.rand = random.Random(seed)
69
67
 
70
- def swapaxes(self, x, mode: Optional[Literal["xy", "xz", "yz"]] = None) -> NDArrf32:
68
+ def swapaxes(self, x, mode: Literal["xy", "xz", "yz"] | None = None) -> NDArrf32:
71
69
  if mode is None:
72
70
  modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
73
71
  mode = modes[self.rand.randint(0, 2)]
@@ -82,7 +80,7 @@ class Augmentation:
82
80
  case _:
83
81
  raise ValueError(f"invalid mode: {mode}")
84
82
 
85
- def flip(self, x, mode: Optional[Literal["xy", "xz", "yz"]] = None) -> NDArrf32:
83
+ def flip(self, x, mode: Literal["xy", "xz", "yz"] | None = None) -> NDArrf32:
86
84
  if mode is None:
87
85
  modes: list[Literal["xy", "xz", "yz"]] = ["xy", "xz", "yz"]
88
86
  mode = modes[random.randint(0, 2)]
@@ -101,16 +99,13 @@ class Augmentation:
101
99
  fns = list(augs.keys())
102
100
 
103
101
 
104
- def play_augment(x: NDArrf32, method: Optional[Augmentation | int] = None) -> NDArrf32:
102
+ def play_augment(x: NDArrf32, method: Augmentation | int | None = None) -> NDArrf32:
105
103
  """Play augment in x.
106
104
 
107
- Parameters
108
- ----------
109
- x : Array
110
- Array of shape (X, Y, Z, C)
111
- method : int or str, optional
112
- Augmentation method index / name. if not provided, a random
113
- augment will be apply.
105
+ Args
106
+ x: Array of shape (X, Y, Z, C)
107
+ method: Augmentation method index / name.
108
+ If not provided, a random augment will be apply.
114
109
  """
115
110
 
116
111
  if isinstance(method, str):
@@ -132,31 +127,20 @@ def random_augmentations(
132
127
  ) -> npt.NDArray[np.int64]:
133
128
  """Generate a sequence of augmentations.
134
129
 
135
- Parameters
136
- ----------
137
- n : int
138
- Size of image stacks.
139
- k : int
140
- Each image stack augmented to K image stack.
141
- seed : int | None, optional
142
- Random seed, forwarding to `random.Random`
143
- include_identity : bool, default `True`
144
- Include identity transform.
145
-
146
- Returns
147
- -------
148
- augmentations : List of (int, int)
149
- Sequence of length N * K, contains image index and augmentation
150
- method index.
151
-
152
- Examples
153
- --------
154
- ```python
155
- xs = os.listdir("path_to_imgs")
156
- augs = generate_random_augmentations(len(xs), 5)
157
- for i, j in range(augs):
158
- x = play_augment(read_imgs(os.path.join("path_to_imgs", xs[i])), j)
159
- ```
130
+ >>> xs = os.listdir("path_to_imgs") # doctest: +SKIP
131
+ >>> augs = generate_random_augmentations(len(xs), 5) # doctest: +SKIP
132
+ >>> for i, j in range(augs): # doctest: +SKIP
133
+ ... x = play_augment(read_imgs(os.path.join("path_to_imgs", xs[i])), j)
134
+
135
+ Args:
136
+ n: Size of image stacks.
137
+ k: Each image stack augmented to K image stack.
138
+ seed: Random seed, forwarding to `random.Random`
139
+ include_identity: Include identity transform.
140
+
141
+ Returns:
142
+ augmentations: List of (int, int)
143
+ Sequence of length N * K, contains index of image and augmentation method.
160
144
  """
161
145
 
162
146
  rand = random.Random(seed)
@@ -15,12 +15,10 @@
15
15
 
16
16
  """The contrast of an image.
17
17
 
18
- Notes
19
- -----
20
- This is expremental code, and the API is subject to change.
18
+ NOTE: This is expremental code, and the API is subject to change.
21
19
  """
22
20
 
23
- from typing import Optional, overload
21
+ from typing import overload
24
22
 
25
23
  import numpy as np
26
24
  import numpy.typing as npt
@@ -34,13 +32,11 @@ Array3D = npt.NDArray[np.float32]
34
32
  def contrast_std(image: Array3D) -> float:
35
33
  """Get the std contrast of an image stack.
36
34
 
37
- Parameters
38
- ----------
39
- imgs : ndarray
35
+ Args:
36
+ imgs: ndarray
40
37
 
41
- Returns
42
- -------
43
- contrast : float
38
+ Returns:
39
+ contrast
44
40
  """
45
41
  ...
46
42
 
@@ -49,21 +45,17 @@ def contrast_std(image: Array3D) -> float:
49
45
  def contrast_std(image: Array3D, contrast: float) -> Array3D:
50
46
  """Adjust the contrast of an image stack.
51
47
 
52
- Parameters
53
- ----------
54
- imgs : ndarray
55
- constrast : float
56
- The contrast adjustment factor. 1.0 leaves the image unchanged.
48
+ Args:
49
+ imgs: ndarray
50
+ contrast: The contrast adjustment factor. 1.0 leaves the image unchanged.
57
51
 
58
- Returns
59
- -------
60
- imgs : ndarray
61
- The adjusted image.
52
+ Returns:
53
+ imgs: The adjusted image.
62
54
  """
63
55
  ...
64
56
 
65
57
 
66
- def contrast_std(image: Array3D, contrast: Optional[float] = None):
58
+ def contrast_std(image: Array3D, contrast: float | None = None):
67
59
  if contrast is None:
68
60
  return np.std(image).item()
69
61
  else:
@@ -73,15 +65,9 @@ def contrast_std(image: Array3D, contrast: Optional[float] = None):
73
65
  def contrast_michelson(image: Array3D) -> float:
74
66
  """Get the Michelson contrast of an image stack.
75
67
 
76
- Parameters
77
- ----------
78
- imgs : ndarray
79
-
80
- Returns
81
- -------
82
- contrast : float
68
+ Returns:
69
+ contrast: float
83
70
  """
84
-
85
71
  vmax = np.max(image)
86
72
  vmin = np.min(image)
87
73
  return ((vmax - vmin) / (vmax + vmin)).item()
@@ -90,33 +76,23 @@ def contrast_michelson(image: Array3D) -> float:
90
76
  def contrast_rms(imgs: npt.NDArray[np.float32]) -> float:
91
77
  """Get the RMS contrast of an image stack.
92
78
 
93
- Parameters
94
- ----------
95
- imgs : ndarray
96
-
97
- Returns
98
- -------
99
- contrast : float
79
+ Returns:
80
+ contrast
100
81
  """
101
-
102
82
  return np.sqrt(np.mean(imgs**2)).item()
103
83
 
104
84
 
105
85
  def contrast_weber(imgs: Array3D, mask: npt.NDArray[np.bool_]) -> float:
106
86
  """Get the Weber contrast of an image stack.
107
87
 
108
- Parameters
109
- ----------
110
- imgs : ndarray
111
- mask : ndarray of bool
112
- The mask to segment the foreground and background. 1 for
113
- foreground, 0 for background.
88
+ Args:
89
+ imgs: ndarray
90
+ mask: The mask to segment the foreground and background.
91
+ 1 for foreground, 0 for background.
114
92
 
115
- Returns
116
- -------
117
- contrast : float
93
+ Returns:
94
+ contrast
118
95
  """
119
-
120
96
  l_foreground = np.mean(imgs, where=mask)
121
97
  l_background = np.mean(imgs, where=np.logical_not(mask))
122
98
  return ((l_foreground - l_background) / l_background).item()
swcgeom/images/folder.py CHANGED
@@ -20,7 +20,7 @@ import os
20
20
  import re
21
21
  from collections.abc import Callable, Iterable
22
22
  from dataclasses import dataclass
23
- from typing import Generic, Literal, Optional, TypeVar, overload
23
+ from typing import Generic, Literal, TypeVar, overload
24
24
 
25
25
  import numpy as np
26
26
  import numpy.typing as npt
@@ -41,13 +41,21 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
41
41
  files: list[str]
42
42
  transform: Transform[npt.NDArray[ScalarType], T]
43
43
 
44
- # fmt: off
45
44
  @overload
46
- def __init__(self, files: Iterable[str], *, dtype: None = ..., transform: Optional[Transform[npt.NDArray[np.float32], T]] = None) -> None: ...
45
+ def __init__(
46
+ self,
47
+ files: Iterable[str],
48
+ *,
49
+ transform: Transform[npt.NDArray[np.float32], T] | None = ...,
50
+ ) -> None: ...
47
51
  @overload
48
- def __init__(self, files: Iterable[str], *, dtype: ScalarType, transform: Optional[Transform[npt.NDArray[ScalarType], T]] = None) -> None: ...
49
- # fmt: on
50
-
52
+ def __init__(
53
+ self,
54
+ files: Iterable[str],
55
+ *,
56
+ dtype: ScalarType,
57
+ transform: Transform[npt.NDArray[ScalarType], T] | None = ...,
58
+ ) -> None: ...
51
59
  def __init__(self, files: Iterable[str], *, dtype=None, transform=None) -> None:
52
60
  super().__init__()
53
61
  self.files = list(files)
@@ -66,7 +74,7 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
66
74
  return read_imgs(fname, dtype=self.dtype).get_full() # type: ignore
67
75
 
68
76
  @staticmethod
69
- def scan(root: str, *, pattern: Optional[str] = None) -> list[str]:
77
+ def scan(root: str, *, pattern: str | None = None) -> list[str]:
70
78
  if not os.path.isdir(root):
71
79
  raise NotADirectoryError(f"not a directory: {root}")
72
80
 
@@ -108,16 +116,12 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
108
116
  def stat(self, *, transform: bool = False, verbose: bool = False) -> Statistics:
109
117
  """Statistics of folder.
110
118
 
111
- Parameters
112
- ----------
113
- transform : bool, default to False
114
- Apply transform to the images. If True, you need to make
115
- sure the transformed data is a ndarray.
116
- verbose : bool, optional
119
+ NOTE: We are asserting that the images are of the same shape.
117
120
 
118
- Notes
119
- -----
120
- We are asserting that the images are of the same shape.
121
+ Args:
122
+ transform: Apply transform to the images.
123
+ If True, you need to make sure the transformed data is a ndarray.
124
+ verbose: Show progress bar.
121
125
  """
122
126
 
123
127
  vmin, vmax = math.inf, -math.inf
@@ -134,7 +138,7 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
134
138
  M2 = np.zeros_like(imgs)
135
139
 
136
140
  n += 1
137
- delta = imgs - mean # type: ignore
141
+ delta = imgs - mean
138
142
  mean += delta / n
139
143
  delta2 = imgs - mean
140
144
  M2 += delta * delta2
@@ -152,15 +156,12 @@ class ImageStackFolder(ImageStackFolderBase[ScalarType, T]):
152
156
  )
153
157
 
154
158
  @classmethod
155
- def from_dir(cls, root: str, *, pattern: Optional[str] = None, **kwargs) -> Self:
159
+ def from_dir(cls, root: str, *, pattern: str | None = None, **kwargs) -> Self:
156
160
  """
157
- Parameters
158
- ----------
159
- root : str
160
- pattern : str, optional
161
- Filter files by pattern.
162
- **kwargs
163
- Pass to `cls.__init__`
161
+ Args:
162
+ root: str
163
+ pattern: Filter files by pattern.
164
+ **kwargs: Pass to `cls.__init__`
164
165
  """
165
166
 
166
167
  return cls(cls.scan(root, pattern=pattern), **kwargs)
@@ -184,7 +185,7 @@ class LabeledImageStackFolder(ImageStackFolderBase[ScalarType, T]):
184
185
  root: str,
185
186
  label: int | Callable[[str], int],
186
187
  *,
187
- pattern: Optional[str] = None,
188
+ pattern: str | None = None,
188
189
  **kwargs,
189
190
  ) -> Self:
190
191
  files = cls.scan(root, pattern=pattern)
@@ -211,15 +212,12 @@ class PathImageStackFolder(ImageStackFolderBase[ScalarType, T]):
211
212
  return self._get(self.files[idx]), relpath
212
213
 
213
214
  @classmethod
214
- def from_dir(cls, root: str, *, pattern: Optional[str] = None, **kwargs) -> Self:
215
+ def from_dir(cls, root: str, *, pattern: str | None = None, **kwargs) -> Self:
215
216
  """
216
- Parameters
217
- ----------
218
- root : str
219
- pattern : str, optional
220
- Filter files by pattern.
221
- **kwargs
222
- Pass to `cls.__init__`
217
+ Args:
218
+ root: str
219
+ pattern: Filter files by pattern.
220
+ **kwargs: Pass to `cls.__init__`
223
221
  """
224
222
 
225
223
  return cls(cls.scan(root, pattern=pattern), root=root, **kwargs)