swcgeom 0.14.0__py3-none-any.whl → 0.16.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 (45) hide show
  1. swcgeom/_version.py +2 -2
  2. swcgeom/analysis/lmeasure.py +821 -0
  3. swcgeom/analysis/sholl.py +31 -2
  4. swcgeom/core/__init__.py +4 -0
  5. swcgeom/core/branch.py +9 -4
  6. swcgeom/core/branch_tree.py +2 -3
  7. swcgeom/core/{segment.py → compartment.py} +14 -9
  8. swcgeom/core/node.py +0 -8
  9. swcgeom/core/path.py +21 -6
  10. swcgeom/core/population.py +42 -3
  11. swcgeom/core/swc_utils/assembler.py +20 -138
  12. swcgeom/core/swc_utils/base.py +12 -5
  13. swcgeom/core/swc_utils/checker.py +12 -2
  14. swcgeom/core/swc_utils/subtree.py +2 -2
  15. swcgeom/core/tree.py +53 -49
  16. swcgeom/core/tree_utils.py +27 -5
  17. swcgeom/core/tree_utils_impl.py +22 -6
  18. swcgeom/images/augmentation.py +6 -1
  19. swcgeom/images/contrast.py +107 -0
  20. swcgeom/images/folder.py +111 -29
  21. swcgeom/images/io.py +79 -40
  22. swcgeom/transforms/__init__.py +2 -0
  23. swcgeom/transforms/base.py +41 -21
  24. swcgeom/transforms/branch.py +5 -5
  25. swcgeom/transforms/geometry.py +42 -18
  26. swcgeom/transforms/image_preprocess.py +100 -0
  27. swcgeom/transforms/image_stack.py +46 -28
  28. swcgeom/transforms/images.py +76 -6
  29. swcgeom/transforms/mst.py +10 -18
  30. swcgeom/transforms/neurolucida_asc.py +495 -0
  31. swcgeom/transforms/population.py +2 -2
  32. swcgeom/transforms/tree.py +12 -14
  33. swcgeom/transforms/tree_assembler.py +85 -19
  34. swcgeom/utils/__init__.py +1 -0
  35. swcgeom/utils/neuromorpho.py +425 -300
  36. swcgeom/utils/numpy_helper.py +14 -4
  37. swcgeom/utils/plotter_2d.py +130 -0
  38. swcgeom/utils/renderer.py +28 -139
  39. swcgeom/utils/sdf.py +5 -1
  40. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/METADATA +3 -3
  41. swcgeom-0.16.0.dist-info/RECORD +67 -0
  42. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/WHEEL +1 -1
  43. swcgeom-0.14.0.dist-info/RECORD +0 -62
  44. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/LICENSE +0 -0
  45. {swcgeom-0.14.0.dist-info → swcgeom-0.16.0.dist-info}/top_level.txt +0 -0
@@ -25,6 +25,7 @@ from sdflit import (
25
25
  Scene,
26
26
  SDFObject,
27
27
  )
28
+ from tqdm import tqdm
28
29
 
29
30
  from swcgeom.core import Population, Tree
30
31
  from swcgeom.transforms.base import Transform
@@ -37,7 +38,7 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
37
38
 
38
39
  resolution: npt.NDArray[np.float32]
39
40
 
40
- def __init__(self, resolution: float | npt.ArrayLike = 1) -> None:
41
+ def __init__(self, resolution: int | float | npt.ArrayLike = 1) -> None:
41
42
  """Transform tree to image stack.
42
43
 
43
44
  Parameters
@@ -46,11 +47,11 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
46
47
  Resolution of image stack.
47
48
  """
48
49
 
49
- if isinstance(resolution, float):
50
- resolution = [resolution, resolution, resolution]
50
+ if isinstance(resolution, (int, float, np.integer, np.floating)):
51
+ resolution = [resolution, resolution, resolution] # type: ignore
51
52
 
52
- self.resolution = (resolution := np.array(resolution, dtype=np.float32))
53
- assert tuple(resolution.shape) == (3,), "resolution shoule be vector of 3d."
53
+ self.resolution = np.array(resolution, dtype=np.float32)
54
+ assert len(self.resolution) == 3, "resolution shoule be vector of 3d."
54
55
 
55
56
  def __call__(self, x: Tree) -> npt.NDArray[np.uint8]:
56
57
  """Transform tree to image stack.
@@ -58,57 +59,74 @@ class ToImageStack(Transform[Tree, npt.NDArray[np.uint8]]):
58
59
  Notes
59
60
  -----
60
61
  This method loads the entire image stack into memory, so it
61
- ONLY works for small image stacks, use
62
- :meth`transform_and_save` for big image stack.
62
+ ONLY works for small image stacks, use :meth`transform_and_save`
63
+ for big image stack.
63
64
  """
64
- return np.concatenate(list(self.transfrom(x, verbose=False)), axis=0)
65
-
66
- def __repr__(self) -> str:
67
- return "ToImageStack" + f"-resolution-{'-'.join(self.resolution)}"
65
+ return np.stack(list(self.transfrom(x, verbose=False)), axis=0)
68
66
 
69
67
  def transfrom(
70
- self, x: Tree, verbose: bool = True
68
+ self,
69
+ x: Tree,
70
+ verbose: bool = True,
71
+ *,
72
+ ranges: Optional[Tuple[npt.ArrayLike, npt.ArrayLike]] = None,
71
73
  ) -> Iterable[npt.NDArray[np.uint8]]:
72
- from tqdm import tqdm
73
-
74
74
  if verbose:
75
75
  print("To image stack: " + x.source)
76
76
  time_start = time.time()
77
77
 
78
78
  scene = self._get_scene(x)
79
79
 
80
- xyz, r = x.xyz(), x.r().reshape(-1, 1)
81
- coord_min = np.floor(np.min(xyz - r, axis=0))
82
- coord_max = np.ceil(np.max(xyz + r, axis=0))
80
+ if ranges is None:
81
+ xyz, r = x.xyz(), x.r().reshape(-1, 1)
82
+ coord_min = np.floor(np.min(xyz - r, axis=0))
83
+ coord_max = np.ceil(np.max(xyz + r, axis=0))
84
+ else:
85
+ assert len(ranges) == 2
86
+ coord_min = np.array(ranges[0])
87
+ coord_max = np.array(ranges[1])
88
+ assert len(coord_min) == len(coord_max) == 3
83
89
 
84
90
  samplers = self._get_samplers(coord_min, coord_max)
85
- total = (
86
- ((coord_max[2] - coord_min[2]) / self.resolution[2]).astype(np.int64).item()
87
- )
88
91
 
89
92
  if verbose:
93
+ total = (coord_max[2] - coord_min[2]) / self.resolution[2]
94
+ samplers = tqdm(samplers, total=total.astype(np.int64).item())
95
+
90
96
  time_end = time.time()
91
97
  print("Prepare in: ", time_end - time_start, "s") # type: ignore
92
98
 
93
- for sampler in tqdm(samplers, total=total) if verbose else samplers:
94
- voxel = sampler.sample(scene) # shoule be shape of (x, y, z, 3) and z = 1
99
+ for sampler in samplers:
100
+ voxel = sampler.sample(scene) # should be shape of (x, y, z, 3) and z = 1
95
101
  frame = (255 * voxel[..., 0, 0]).astype(np.uint8)
96
102
  yield frame
97
103
 
98
- def transform_and_save(self, fname: str, x: Tree, verbose: bool = True) -> None:
99
- self.save_tif(fname, self.transfrom(x, verbose=verbose))
104
+ def transform_and_save(
105
+ self, fname: str, x: Tree, verbose: bool = True, **kwargs
106
+ ) -> None:
107
+ self.save_tif(fname, self.transfrom(x, verbose=verbose, **kwargs))
100
108
 
101
109
  def transform_population(
102
110
  self, population: Population | str, verbose: bool = True
103
111
  ) -> None:
104
- if isinstance(population, str):
105
- population = Population.from_swc(population)
112
+ trees = (
113
+ Population.from_swc(population)
114
+ if isinstance(population, str)
115
+ else population
116
+ )
117
+
118
+ if verbose:
119
+ trees = tqdm(trees)
106
120
 
107
121
  # TODO: multiprocess
108
- for tree in population:
122
+ for tree in trees:
109
123
  tif = re.sub(r".swc$", ".tif", tree.source)
110
124
  if not os.path.isfile(tif):
111
- self.transform_and_save(tif, tree, verbose=verbose)
125
+ self.transform_and_save(tif, tree, verbose=False)
126
+
127
+ def extra_repr(self):
128
+ res = ",".join(f"{a:.4f}" for a in self.resolution)
129
+ return f"resolution=({res})"
112
130
 
113
131
  def _get_scene(self, x: Tree) -> Scene:
114
132
  material = ColoredMaterial((1, 0, 0)).into()
@@ -1,6 +1,6 @@
1
1
  """Image stack related transform."""
2
2
 
3
-
3
+ import warnings
4
4
  from typing import Tuple
5
5
 
6
6
  import numpy as np
@@ -8,10 +8,20 @@ import numpy.typing as npt
8
8
 
9
9
  from swcgeom.transforms.base import Transform
10
10
 
11
- __all__ = ["Center"]
11
+ __all__ = [
12
+ "ImagesCenterCrop",
13
+ "ImagesScale",
14
+ "ImagesClip",
15
+ "ImagesNormalizer",
16
+ "ImagesMeanVarianceAdjustment",
17
+ "Center", # legacy
18
+ ]
19
+
12
20
 
21
+ NDArrayf32 = npt.NDArray[np.float32]
13
22
 
14
- class Center(Transform[npt.NDArray[np.float32], npt.NDArray[np.float32]]):
23
+
24
+ class ImagesCenterCrop(Transform[NDArrayf32, NDArrayf32]):
15
25
  """Get image stack center."""
16
26
 
17
27
  def __init__(self, shape_out: int | Tuple[int, int, int]):
@@ -22,11 +32,71 @@ class Center(Transform[npt.NDArray[np.float32], npt.NDArray[np.float32]]):
22
32
  else (shape_out, shape_out, shape_out)
23
33
  )
24
34
 
25
- def __call__(self, x: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
35
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
26
36
  diff = np.subtract(x.shape[:3], self.shape_out)
27
37
  s = diff // 2
28
38
  e = np.add(s, self.shape_out)
29
39
  return x[s[0] : e[0], s[1] : e[1], s[2] : e[2], :]
30
40
 
31
- def __repr__(self) -> str:
32
- return f"Center-{self.shape_out[0]}-{self.shape_out[1]}-{self.shape_out[2]}"
41
+ def extra_repr(self) -> str:
42
+ return f"shape_out=({','.join(str(a) for a in self.shape_out)})"
43
+
44
+
45
+ class Center(ImagesCenterCrop):
46
+ """Get image stack center.
47
+
48
+ .. deprecated:: 0.5.0
49
+ Use :class:`ImagesCenterCrop` instead.
50
+ """
51
+
52
+ def __init__(self, shape_out: int | Tuple[int, int, int]):
53
+ warnings.warn(
54
+ "`Center` is deprecated, use `ImagesCenterCrop` instead",
55
+ DeprecationWarning,
56
+ stacklevel=2,
57
+ )
58
+ super().__init__(shape_out)
59
+
60
+
61
+ class ImagesScale(Transform[NDArrayf32, NDArrayf32]):
62
+ def __init__(self, scaler: float) -> None:
63
+ super().__init__()
64
+ self.scaler = scaler
65
+
66
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
67
+ return self.scaler * x
68
+
69
+
70
+ class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
71
+ def __init__(self, vmin: float = 0, vmax: float = 1, /) -> None:
72
+ super().__init__()
73
+ self.vmin, self.vmax = vmin, vmax
74
+
75
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
76
+ return np.clip(x, self.vmin, self.vmax)
77
+
78
+
79
+ class ImagesNormalizer(Transform[NDArrayf32, NDArrayf32]):
80
+ """Normalize image stack."""
81
+
82
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
83
+ mean = np.mean(x)
84
+ variance = np.var(x)
85
+ return (x - mean) / variance
86
+
87
+
88
+ class ImagesMeanVarianceAdjustment(Transform[NDArrayf32, NDArrayf32]):
89
+ """Adjust image stack mean and variance.
90
+
91
+ See Also
92
+ --------
93
+ ~swcgeom.images.ImageStackFolder.stat
94
+ """
95
+
96
+ def __init__(self, mean: float, variance: float) -> None:
97
+ super().__init__()
98
+ self.mean = mean
99
+ self.variance = variance
100
+
101
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
102
+ return (x - self.mean) / self.variance
swcgeom/transforms/mst.py CHANGED
@@ -23,11 +23,11 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
23
23
 
24
24
  References
25
25
  ----------
26
- [1] Cuntz, H., Forstner, F., Borst, A. & Häusser, M. One Rule to
27
- Grow Them Al: A General Theory of Neuronal Branching and Its
28
- Practical Application. PLOS Comput Biol 6, e1000877 (2010).
29
- [2] Cuntz, H., Borst, A. & Segev, I. Optimization principles of
30
- dendritic structure. Theor Biol Med Model 4, 21 (2007).
26
+ .. [1] Cuntz, H., Forstner, F., Borst, A. & Häusser, M. One Rule to
27
+ Grow Them Al: A General Theory of Neuronal Branching and Its
28
+ Practical Application. PLOS Comput Biol 6, e1000877 (2010).
29
+ .. [2] Cuntz, H., Borst, A. & Segev, I. Optimization principles of
30
+ dendritic structure. Theor Biol Med Model 4, 21 (2007).
31
31
  """
32
32
 
33
33
  def __init__(
@@ -140,13 +140,8 @@ class PointsToCuntzMST(Transform[npt.NDArray[np.float32], Tree]):
140
140
  t = sort_tree(t)
141
141
  return t
142
142
 
143
- def __repr__(self) -> str:
144
- return (
145
- f"PointsToCuntzMST"
146
- f"-bf-{self.bf}"
147
- f"-furcations-{self.furcations}"
148
- f"-{'exclude-soma' if self.exclude_soma else 'include-soma'}"
149
- ) # TODO: names, types
143
+ def extra_repr(self) -> str: # TODO: names, types
144
+ return f"bf={self.bf:.4f}, furcations={self.furcations}, exclude_soma={self.exclude_soma}, sort={self.sort}"
150
145
 
151
146
 
152
147
  class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
@@ -173,6 +168,7 @@ class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
173
168
  names : SWCNames, optional
174
169
  types : SWCTypes, optional
175
170
  """
171
+
176
172
  if k_furcations is not None:
177
173
  warnings.warn(
178
174
  "`PointsToMST(k_furcations=...)` has been renamed to "
@@ -191,9 +187,5 @@ class PointsToMST(PointsToCuntzMST): # pylint: disable=too-few-public-methods
191
187
  **kwargs,
192
188
  )
193
189
 
194
- def __repr__(self) -> str:
195
- return (
196
- f"PointsToMST"
197
- f"-furcations-{self.furcations}"
198
- f"-{'exclude-soma' if self.exclude_soma else 'include-soma'}"
199
- )
190
+ def extra_repr(self) -> str:
191
+ return f"furcations-{self.furcations}, exclude-soma={self.exclude_soma}"