swcgeom 0.16.0__py3-none-any.whl → 0.17.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.

swcgeom/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.16.0'
16
- __version_tuple__ = version_tuple = (0, 16, 0)
15
+ __version__ = version = '0.17.0'
16
+ __version_tuple__ = version_tuple = (0, 17, 0)
@@ -1,9 +1,7 @@
1
1
  """Analysis for neuron trees."""
2
2
 
3
- from swcgeom.analysis.branch_features import *
4
3
  from swcgeom.analysis.feature_extractor import *
5
- from swcgeom.analysis.node_features import *
6
- from swcgeom.analysis.path_features import *
4
+ from swcgeom.analysis.features import *
7
5
  from swcgeom.analysis.sholl import *
8
6
  from swcgeom.analysis.trunk import *
9
7
  from swcgeom.analysis.visualization import *
@@ -17,13 +17,13 @@ import numpy.typing as npt
17
17
  import seaborn as sns
18
18
  from matplotlib.axes import Axes
19
19
 
20
- from swcgeom.analysis.branch_features import BranchFeatures
21
- from swcgeom.analysis.node_features import (
20
+ from swcgeom.analysis.features import (
22
21
  BifurcationFeatures,
22
+ BranchFeatures,
23
23
  NodeFeatures,
24
+ PathFeatures,
24
25
  TipFeatures,
25
26
  )
26
- from swcgeom.analysis.path_features import PathFeatures
27
27
  from swcgeom.analysis.sholl import Sholl
28
28
  from swcgeom.analysis.volume import get_volume
29
29
  from swcgeom.core import Population, Populations, Tree
@@ -1,15 +1,26 @@
1
- """Depth distribution of tree."""
1
+ """Feature anlysis of tree."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from functools import cached_property
5
+ from typing import List, TypeVar
5
6
 
6
7
  import numpy as np
7
8
  import numpy.typing as npt
8
9
  from typing_extensions import Self
9
10
 
10
- from swcgeom.core import BranchTree, Tree
11
+ from swcgeom.core import Branch, BranchTree, Tree
11
12
 
12
- __all__ = ["NodeFeatures", "BifurcationFeatures", "TipFeatures"]
13
+ __all__ = [
14
+ "NodeFeatures",
15
+ "BifurcationFeatures",
16
+ "TipFeatures",
17
+ "PathFeatures",
18
+ "BranchFeatures",
19
+ ]
20
+
21
+ T = TypeVar("T", bound=Branch)
22
+
23
+ # Node Level
13
24
 
14
25
 
15
26
  class NodeFeatures:
@@ -31,6 +42,7 @@ class NodeFeatures:
31
42
  -------
32
43
  count : array of shape (1,)
33
44
  """
45
+
34
46
  return np.array([self.tree.number_of_nodes()], dtype=np.float32)
35
47
 
36
48
  def get_radial_distance(self) -> npt.NDArray[np.float32]:
@@ -41,6 +53,7 @@ class NodeFeatures:
41
53
  radial_distance : npt.NDArray[np.float32]
42
54
  Array of shape (N,).
43
55
  """
56
+
44
57
  xyz = self.tree.xyz() - self.tree.soma().xyz()
45
58
  radial_distance = np.linalg.norm(xyz, axis=1)
46
59
  return radial_distance
@@ -58,6 +71,7 @@ class NodeFeatures:
58
71
  order : npt.NDArray[np.int32]
59
72
  Array of shape (N,), which k is the number of branchs.
60
73
  """
74
+
61
75
  order = np.zeros_like(self._branch_tree.id(), dtype=np.int32)
62
76
 
63
77
  def assign_depth(n: Tree.Node, pre_depth: int | None) -> int:
@@ -88,6 +102,7 @@ class _SubsetNodesFeatures(ABC):
88
102
  count : npt.NDArray[np.float32]
89
103
  Array of shape (1,).
90
104
  """
105
+
91
106
  return np.array([np.count_nonzero(self.nodes)], dtype=np.float32)
92
107
 
93
108
  def get_radial_distance(self) -> npt.NDArray[np.float32]:
@@ -98,6 +113,7 @@ class _SubsetNodesFeatures(ABC):
98
113
  radial_distance : npt.NDArray[np.float32]
99
114
  Array of shape (N,).
100
115
  """
116
+
101
117
  return self._features.get_radial_distance()[self.nodes]
102
118
 
103
119
  @classmethod
@@ -119,3 +135,89 @@ class TipFeatures(_SubsetNodesFeatures):
119
135
  @cached_property
120
136
  def nodes(self) -> npt.NDArray[np.bool_]:
121
137
  return np.array([n.is_tip() for n in self._features.tree])
138
+
139
+
140
+ # Path Level
141
+
142
+
143
+ class PathFeatures:
144
+ """Path analysis of tree."""
145
+
146
+ tree: Tree
147
+
148
+ def __init__(self, tree: Tree) -> None:
149
+ self.tree = tree
150
+
151
+ def get_count(self) -> int:
152
+ return len(self._paths)
153
+
154
+ def get_length(self) -> npt.NDArray[np.float32]:
155
+ """Get length of paths."""
156
+
157
+ length = [path.length() for path in self._paths]
158
+ return np.array(length, dtype=np.float32)
159
+
160
+ def get_tortuosity(self) -> npt.NDArray[np.float32]:
161
+ """Get tortuosity of path."""
162
+
163
+ return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
164
+
165
+ @cached_property
166
+ def _paths(self) -> List[Tree.Path]:
167
+ return self.tree.get_paths()
168
+
169
+
170
+ class BranchFeatures:
171
+ """Analysis bransh of tree."""
172
+
173
+ tree: Tree
174
+
175
+ def __init__(self, tree: Tree) -> None:
176
+ self.tree = tree
177
+
178
+ def get_count(self) -> int:
179
+ return len(self._branches)
180
+
181
+ def get_length(self) -> npt.NDArray[np.float32]:
182
+ """Get length of branches."""
183
+
184
+ length = [br.length() for br in self._branches]
185
+ return np.array(length, dtype=np.float32)
186
+
187
+ def get_tortuosity(self) -> npt.NDArray[np.float32]:
188
+ """Get tortuosity of path."""
189
+
190
+ return np.array([br.tortuosity() for br in self._branches], dtype=np.float32)
191
+
192
+ def get_angle(self, eps: float = 1e-7) -> npt.NDArray[np.float32]:
193
+ """Get agnle between branches.
194
+
195
+ Returns
196
+ -------
197
+ angle : npt.NDArray[np.float32]
198
+ An array of shape (N, N), which N is length of branches.
199
+ """
200
+
201
+ return self.calc_angle(self._branches, eps=eps)
202
+
203
+ @staticmethod
204
+ def calc_angle(branches: List[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
205
+ """Calc agnle between branches.
206
+
207
+ Returns
208
+ -------
209
+ angle : npt.NDArray[np.float32]
210
+ An array of shape (N, N), which N is length of branches.
211
+ """
212
+
213
+ vector = np.array([br[-1].xyz() - br[0].xyz() for br in branches])
214
+ vector_dot = np.matmul(vector, vector.T)
215
+ vector_norm = np.linalg.norm(vector, ord=2, axis=1, keepdims=True)
216
+ vector_norm_dot = np.matmul(vector_norm, vector_norm.T) + eps
217
+ arccos = np.clip(vector_dot / vector_norm_dot, -1, 1)
218
+ angle = np.arccos(arccos)
219
+ return angle
220
+
221
+ @cached_property
222
+ def _branches(self) -> List[Tree.Branch]:
223
+ return self.tree.get_branches()
swcgeom/images/io.py CHANGED
@@ -107,26 +107,42 @@ def read_imgs(fname: str, *, dtype: None =..., **kwargs) -> ImageStack[np.float3
107
107
  # fmt:on
108
108
 
109
109
 
110
- def read_imgs(fname: str, *, dtype=None, **kwargs): # type: ignore
111
- """Read image stack."""
112
-
113
- kwargs["dtype"] = dtype or np.float32
114
-
115
- ext = os.path.splitext(fname)[-1]
116
- if ext in [".tif", ".tiff"]:
117
- return TiffImageStack(fname, **kwargs)
118
- if ext in [".nrrd"]:
119
- return NrrdImageStack(fname, **kwargs)
120
- if ext in [".v3dpbd"]:
121
- return V3dpbdImageStack(fname, **kwargs)
122
- if ext in [".v3draw"]:
123
- return V3drawImageStack(fname, **kwargs)
124
- if ext in [".npy", ".npz"]:
125
- return NDArrayImageStack(np.load(fname), **kwargs)
110
+ def read_imgs(fname: str, **kwargs): # type: ignore
111
+ """Read image stack.
112
+
113
+ Parameters
114
+ ----------
115
+ fname : str
116
+ The path of image stack.
117
+ dtype : np.dtype, default to `np.float32`
118
+ Casting data to specified dtype. If integer and float
119
+ conversions occur, they will be scaled (assuming floats are
120
+ between 0 and 1).
121
+ **kwargs : Dict[str, Any]
122
+ Forwarding to the corresponding reader.
123
+ """
124
+
125
+ kwargs.setdefault("dtype", np.float32)
126
+ if not os.path.exists(fname):
127
+ raise ValueError(f"image stack not exists: {fname}")
128
+
129
+ # match file extension
130
+ match os.path.splitext(fname)[-1]:
131
+ case ".tif" | ".tiff":
132
+ return TiffImageStack(fname, **kwargs)
133
+ case ".nrrd":
134
+ return NrrdImageStack(fname, **kwargs)
135
+ case ".v3dpbd":
136
+ return V3dpbdImageStack(fname, **kwargs)
137
+ case ".v3draw":
138
+ return V3drawImageStack(fname, **kwargs)
139
+ case ".npy":
140
+ return NDArrayImageStack(np.load(fname), **kwargs)
141
+
142
+ # try to read as terafly
126
143
  if TeraflyImageStack.is_root(fname):
127
144
  return TeraflyImageStack(fname, **kwargs)
128
- if not os.path.exists(fname):
129
- raise ValueError("image stack not exists")
145
+
130
146
  raise ValueError("unsupported image stack")
131
147
 
132
148
 
@@ -135,7 +151,6 @@ def save_tiff(
135
151
  fname: str,
136
152
  *,
137
153
  dtype: Optional[np.unsignedinteger | np.floating] = None,
138
- swap_xy: Optional[bool] = None,
139
154
  compression: str | Literal[False] = "zlib",
140
155
  **kwargs,
141
156
  ) -> None:
@@ -164,17 +179,6 @@ def save_tiff(
164
179
  data = np.expand_dims(data, -1) # (_, _, _) -> (_, _, _, C), C === 1
165
180
 
166
181
  axes = "ZXYC"
167
- if swap_xy is not None:
168
- warnings.warn(
169
- "flag `swap_xy` is easy to implement in user space and "
170
- "is more flexiable. Since this flag is rarely used, we "
171
- "decided to remove it in the next version",
172
- DeprecationWarning,
173
- )
174
- if swap_xy is True:
175
- axes = "ZYXC"
176
- data = data.swapaxes(0, 1) # (X, Y, _, _) -> (Y, X, _, _)
177
-
178
182
  assert data.ndim == 4, "should be an array of shape (X, Y, Z, C)"
179
183
  assert data.shape[-1] in [1, 3], "support 'miniblack' or 'rgb'"
180
184
 
@@ -209,12 +213,7 @@ class NDArrayImageStack(ImageStack[ScalarType]):
209
213
  """NDArray image stack."""
210
214
 
211
215
  def __init__(
212
- self,
213
- imgs: npt.NDArray[Any],
214
- swap_xy: Optional[bool] = None,
215
- filp_xy: Optional[bool] = None,
216
- *,
217
- dtype: ScalarType,
216
+ self, imgs: npt.NDArray[Any], *, dtype: Optional[ScalarType] = None
218
217
  ) -> None:
219
218
  super().__init__()
220
219
 
@@ -222,34 +221,22 @@ class NDArrayImageStack(ImageStack[ScalarType]):
222
221
  imgs = np.expand_dims(imgs, -1)
223
222
  assert imgs.ndim == 4, "Should be shape of (X, Y, Z, C)"
224
223
 
225
- if swap_xy is not None:
226
- warnings.warn(
227
- "flag `swap_xy` now is unnecessary, tifffile will "
228
- "automatically adjust dimensions according to "
229
- "`tags.axes`, so this flag will be removed in the next "
230
- " version",
231
- DeprecationWarning,
232
- )
233
- if swap_xy is True:
234
- imgs = imgs.swapaxes(0, 1) # (Y, X, _, _) -> (X, Y, _, _)
235
-
236
- if filp_xy is not None:
237
- warnings.warn(
238
- "flag `filp_xy` is easy to implement in user space and "
239
- "is more flexiable. Since this flag is rarely used, we "
240
- "decided to remove it in the next version",
241
- DeprecationWarning,
242
- )
243
- if filp_xy is True:
244
- imgs = np.flip(imgs, (0, 1)) # (X, Y, Z, C)
224
+ if dtype is not None:
225
+ dtype_raw = imgs.dtype
226
+ if np.issubdtype(dtype, np.floating) and np.issubdtype(
227
+ dtype_raw, np.unsignedinteger
228
+ ):
229
+ sclar_factor = 1.0 / UINT_MAX[dtype_raw]
230
+ imgs = sclar_factor * imgs.astype(dtype)
231
+ elif np.issubdtype(dtype, np.unsignedinteger) and np.issubdtype(
232
+ dtype_raw, np.floating
233
+ ):
234
+ sclar_factor = UINT_MAX[dtype] # type: ignore
235
+ imgs *= (sclar_factor * imgs).astype(dtype)
236
+ else:
237
+ imgs = imgs.astype(dtype)
245
238
 
246
- dtype_raw = imgs.dtype
247
- self.imgs = imgs.astype(dtype)
248
- if np.issubdtype(dtype, np.floating) and np.issubdtype(
249
- dtype_raw, np.unsignedinteger
250
- ): # TODO: add a option to disable this
251
- sclar_factor = 1.0 / UINT_MAX[imgs.dtype]
252
- self.imgs *= sclar_factor
239
+ self.imgs = imgs
253
240
 
254
241
  def __getitem__(self, key):
255
242
  return self.imgs.__getitem__(key)
@@ -265,15 +252,7 @@ class NDArrayImageStack(ImageStack[ScalarType]):
265
252
  class TiffImageStack(NDArrayImageStack[ScalarType]):
266
253
  """Tiff image stack."""
267
254
 
268
- def __init__(
269
- self,
270
- fname: str,
271
- swap_xy: Optional[bool] = None,
272
- filp_xy: Optional[bool] = None,
273
- *,
274
- dtype: ScalarType,
275
- **kwargs,
276
- ) -> None:
255
+ def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
277
256
  with tifffile.TiffFile(fname, **kwargs) as f:
278
257
  s = f.series[0]
279
258
  imgs, axes = s.asarray(), s.axes
@@ -285,23 +264,15 @@ class TiffImageStack(NDArrayImageStack[ScalarType]):
285
264
 
286
265
  orders = [AXES_ORDER[c] for c in axes]
287
266
  imgs = imgs.transpose(np.argsort(orders))
288
- super().__init__(imgs, swap_xy=swap_xy, filp_xy=filp_xy, dtype=dtype)
267
+ super().__init__(imgs, dtype=dtype)
289
268
 
290
269
 
291
270
  class NrrdImageStack(NDArrayImageStack[ScalarType]):
292
271
  """Nrrd image stack."""
293
272
 
294
- def __init__(
295
- self,
296
- fname: str,
297
- swap_xy: Optional[bool] = None,
298
- filp_xy: Optional[bool] = None,
299
- *,
300
- dtype: ScalarType,
301
- **kwargs,
302
- ) -> None:
273
+ def __init__(self, fname: str, *, dtype: ScalarType, **kwargs) -> None:
303
274
  imgs, header = nrrd.read(fname, **kwargs)
304
- super().__init__(imgs, swap_xy=swap_xy, filp_xy=filp_xy, dtype=dtype)
275
+ super().__init__(imgs, dtype=dtype)
305
276
  self.header = header
306
277
 
307
278
 
@@ -384,7 +355,12 @@ class TeraflyImageStack(ImageStack[ScalarType]):
384
355
 
385
356
  @lru_cache(maxsize=lru_maxsize)
386
357
  def read_patch(path: str) -> npt.NDArray[ScalarType]:
387
- return read_imgs(path, dtype=dtype).get_full()
358
+ match os.path.splitext(path)[-1]:
359
+ case "raw":
360
+ # Treat it as a v3draw file
361
+ return V3drawImageStack(path, dtype=dtype).get_full()
362
+ case _:
363
+ return read_imgs(path, dtype=dtype).get_full()
388
364
 
389
365
  self._listdir, self._read_patch = listdir, read_patch
390
366
 
@@ -6,14 +6,18 @@ from typing import Tuple
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
8
 
9
- from swcgeom.transforms.base import Transform
9
+ from swcgeom.transforms.base import Identity, Transform
10
10
 
11
11
  __all__ = [
12
12
  "ImagesCenterCrop",
13
13
  "ImagesScale",
14
14
  "ImagesClip",
15
+ "ImagesFlip",
16
+ "ImagesFlipY",
15
17
  "ImagesNormalizer",
16
18
  "ImagesMeanVarianceAdjustment",
19
+ "ImagesScaleToUnitRange",
20
+ "ImagesHistogramEqualization",
17
21
  "Center", # legacy
18
22
  ]
19
23
 
@@ -45,7 +49,7 @@ class ImagesCenterCrop(Transform[NDArrayf32, NDArrayf32]):
45
49
  class Center(ImagesCenterCrop):
46
50
  """Get image stack center.
47
51
 
48
- .. deprecated:: 0.5.0
52
+ .. deprecated:: 0.16.0
49
53
  Use :class:`ImagesCenterCrop` instead.
50
54
  """
51
55
 
@@ -66,6 +70,9 @@ class ImagesScale(Transform[NDArrayf32, NDArrayf32]):
66
70
  def __call__(self, x: NDArrayf32) -> NDArrayf32:
67
71
  return self.scaler * x
68
72
 
73
+ def extra_repr(self) -> str:
74
+ return f"scaler={self.scaler}"
75
+
69
76
 
70
77
  class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
71
78
  def __init__(self, vmin: float = 0, vmax: float = 1, /) -> None:
@@ -75,6 +82,41 @@ class ImagesClip(Transform[NDArrayf32, NDArrayf32]):
75
82
  def __call__(self, x: NDArrayf32) -> NDArrayf32:
76
83
  return np.clip(x, self.vmin, self.vmax)
77
84
 
85
+ def extra_repr(self) -> str:
86
+ return f"vmin={self.vmin}, vmax={self.vmax}"
87
+
88
+
89
+ class ImagesFlip(Transform[NDArrayf32, NDArrayf32]):
90
+ """Flip image stack along axis."""
91
+
92
+ def __init__(self, axis: int, /) -> None:
93
+ super().__init__()
94
+ self.axis = axis
95
+
96
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
97
+ return np.flip(x, axis=self.axis)
98
+
99
+ def extra_repr(self) -> str:
100
+ return f"axis={self.axis}"
101
+
102
+
103
+ class ImagesFlipY(ImagesFlip):
104
+ """Flip image stack along Y-axis.
105
+
106
+ See Also
107
+ --------
108
+ ~.images.io.TeraflyImageStack:
109
+ Terafly and Vaa3d use a especial right-handed coordinate system
110
+ (with origin point in the left-top and z-axis points front),
111
+ but we flip y-axis to makes it a left-handed coordinate system
112
+ (with orgin point in the left-bottom and z-axis points front).
113
+ If you need to use its coordinate system, remember to FLIP
114
+ Y-AXIS BACK.
115
+ """
116
+
117
+ def __init__(self, axis: int = 1, /) -> None:
118
+ super().__init__(axis) # (X, Y, Z, C)
119
+
78
120
 
79
121
  class ImagesNormalizer(Transform[NDArrayf32, NDArrayf32]):
80
122
  """Normalize image stack."""
@@ -100,3 +142,62 @@ class ImagesMeanVarianceAdjustment(Transform[NDArrayf32, NDArrayf32]):
100
142
 
101
143
  def __call__(self, x: NDArrayf32) -> NDArrayf32:
102
144
  return (x - self.mean) / self.variance
145
+
146
+ def extra_repr(self) -> str:
147
+ return f"mean={self.mean}, variance={self.variance}"
148
+
149
+
150
+ class ImagesScaleToUnitRange(Transform[NDArrayf32, NDArrayf32]):
151
+ """Scale image stack to unit range."""
152
+
153
+ def __init__(self, vmin: float, vmax: float, *, clip: bool = True) -> None:
154
+ """Scale image stack to unit range.
155
+
156
+ Parameters
157
+ ----------
158
+ vmin : float
159
+ Minimum value.
160
+ vmax : float
161
+ Maximum value.
162
+ clip : bool, default True
163
+ Clip values to [0, 1] to avoid numerical issues.
164
+ """
165
+
166
+ super().__init__()
167
+ self.vmin = vmin
168
+ self.vmax = vmax
169
+ self.diff = vmax - vmin
170
+ self.clip = clip
171
+ self.post = ImagesClip(0, 1) if self.clip else Identity()
172
+
173
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
174
+ return self.post((x - self.vmin) / self.diff)
175
+
176
+ def extra_repr(self) -> str:
177
+ return f"vmin={self.vmin}, vmax={self.vmax}, clip={self.clip}"
178
+
179
+
180
+ class ImagesHistogramEqualization(Transform[NDArrayf32, NDArrayf32]):
181
+ """Image histogram equalization.
182
+
183
+ References
184
+ ----------
185
+ http://www.janeriksolem.net/histogram-equalization-with-python-and.html
186
+ """
187
+
188
+ def __init__(self, bins: int = 256) -> None:
189
+ super().__init__()
190
+ self.bins = bins
191
+
192
+ def __call__(self, x: NDArrayf32) -> NDArrayf32:
193
+ # get image histogram
194
+ hist, bin_edges = np.histogram(x.flatten(), self.bins, density=True)
195
+ cdf = hist.cumsum() # cumulative distribution function
196
+ cdf = cdf / cdf[-1] # normalize
197
+
198
+ # use linear interpolation of cdf to find new pixel values
199
+ equalized = np.interp(x.flatten(), bin_edges[:-1], cdf)
200
+ return equalized.reshape(x.shape).astype(np.float32)
201
+
202
+ def extra_repr(self) -> str:
203
+ return f"bins={self.bins}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: swcgeom
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <yuan.zx@outlook.com>
6
6
  License: Apache-2.0
@@ -1,11 +1,9 @@
1
1
  swcgeom/__init__.py,sha256=z88Zwcjv-ii7c7dYd9QPg9XrUVorQjtrgGbQCsEnQhc,265
2
- swcgeom/_version.py,sha256=6rRRmvuDj83YMwAXEQTx-SPTN2ocQjA6cw2_dMCfweM,413
3
- swcgeom/analysis/__init__.py,sha256=esL_poW8u-_bmp7vR9ldcumX3_xodtaVfM1USqxQo5w,377
4
- swcgeom/analysis/branch_features.py,sha256=s6PTMwwvxrVtZXRZlQbUSIw4M9-1IG63kf-Nxc0tMB0,1958
5
- swcgeom/analysis/feature_extractor.py,sha256=coe07_bJau96BkimcnXzuf4KqjY5_QRLwqaumFsu_tQ,14031
2
+ swcgeom/_version.py,sha256=kBQrirg2HABnJUda1U0a6ZmIqeCqRLaPZOws884sTW8,413
3
+ swcgeom/analysis/__init__.py,sha256=NurxIAyjsxjYv9rbzNf65y2sv-iBcGTmLap645hYq9Y,280
4
+ swcgeom/analysis/feature_extractor.py,sha256=MBhnE6JbgCMhfTO8jEXqbLkVIiQh2WvVH2C4JR0IcpI,13948
5
+ swcgeom/analysis/features.py,sha256=meUXUOIqV1PgiRtgYQdFrVuTmyPYQRSJ0C1Ll97XDhU,5965
6
6
  swcgeom/analysis/lmeasure.py,sha256=GI5HoIXkRp_GEDHd_JXJOMeAtZ26HP6lbSfF_0L-2r8,27559
7
- swcgeom/analysis/node_features.py,sha256=fevnyrF-t4PX39ifLypiDW6EUWB8i-a3PpBnQZU3VOc,3407
8
- swcgeom/analysis/path_features.py,sha256=iE21HBxAoGLxk_qK7MBwQhyUOBqNPcnk4urVHr9SVqk,889
9
7
  swcgeom/analysis/sholl.py,sha256=KeUyEXLatHjmn4hOSs8y_0o8UKDq9VoIufJ_81SOtgw,7249
10
8
  swcgeom/analysis/trunk.py,sha256=L2tjUIUmrRQpah_W3ZETGWd16bDXJ5F8Sk2XBNGms0Q,5558
11
9
  swcgeom/analysis/visualization.py,sha256=mKOpzTPkLpr1ggGL1MZPZRTG92GEg4idLT4eN5z5KOs,5654
@@ -32,14 +30,14 @@ swcgeom/images/__init__.py,sha256=QBP1ZGGo2nWAcV7Krz-vbvW_jN4ChqXrrpoScXcUURs,96
32
30
  swcgeom/images/augmentation.py,sha256=cV4k4KR_WcsRajyr0DuhHVDRRZcN4FQ7OIzB_rb2FUo,4173
33
31
  swcgeom/images/contrast.py,sha256=ViZVW6XI-l2sLVTODLRLtHinv_7lVgtH-xZmaw1nQLw,2160
34
32
  swcgeom/images/folder.py,sha256=YY9YjF17nDwOQEXhzFe-Dj0zPTcG0WP1-gisscImmYg,6674
35
- swcgeom/images/io.py,sha256=s7YgzlVbl1fTI0PvW09p4_Z-hDdU9Ofh8d99nyhMyY4,21445
33
+ swcgeom/images/io.py,sha256=05VlDcrtt3Un7M2hFddV0aWopgj55TuukSBjHLYwLHg,20704
36
34
  swcgeom/transforms/__init__.py,sha256=1rr4X--qY_lBi7l7_NHyvvkoWpQOQOqkioRT8I20olI,562
37
35
  swcgeom/transforms/base.py,sha256=gN5Iqi-OHkYrsjllSOdxI6Yzav3jJGoi6kUPy-38FAs,4101
38
36
  swcgeom/transforms/branch.py,sha256=R0rVti--u70IiUKyHSx6MsDYJyy6zSCf18Uia2Cmh28,5410
39
37
  swcgeom/transforms/geometry.py,sha256=XR73fO_8T7otUFIllqKOWW0OnrsXBc7yA01oDT99yMc,7385
40
38
  swcgeom/transforms/image_preprocess.py,sha256=ZVPpRoO69dmLF5K7CWsGaQJXB2G5gxdvA-FcDmfz4yQ,3662
41
39
  swcgeom/transforms/image_stack.py,sha256=RIldGAOI3QeoeBtr0VKeBKJVg-fWSmzWll63SvsaTfI,5775
42
- swcgeom/transforms/images.py,sha256=ia6h8L7rIubNb02YlbOa-CTScttQJmiS5dHT4D2HpWg,2679
40
+ swcgeom/transforms/images.py,sha256=3j8X8L9q0nSMJ_fP-73jL-zYtgi3fn3Erti9Ej1UZYo,5760
43
41
  swcgeom/transforms/mst.py,sha256=Oc_HnaXjg5EXC7ZnOPneHX0-rXizDAEUcjq63GTj-ac,6251
44
42
  swcgeom/transforms/neurolucida_asc.py,sha256=O4fK1OMropPnIEVdMenbyT_sV39gEGIv_6vIl6yUOVg,14146
45
43
  swcgeom/transforms/path.py,sha256=Gk2iunGQMX7vE83bdo8xoDO-KAT1Vvep0iZs7oFLzFQ,1089
@@ -60,8 +58,8 @@ swcgeom/utils/sdf.py,sha256=zNDgwXKRNIVcV4ORMmDXup6Bhf_vlHqwa-b3WZn6KhE,10684
60
58
  swcgeom/utils/solid_geometry.py,sha256=TV02jhcoCLCqtYA9hfE50LFD_VRfixMiOSiHB5Jb2_U,2431
61
59
  swcgeom/utils/transforms.py,sha256=PmP5fL_iVguq4GR2aqXhM0TeCsiFVnrPZMZG6zLohrE,6983
62
60
  swcgeom/utils/volumetric_object.py,sha256=DVRGGmQrAL0oaW6hbNtp5TStbic9DfyJdCzsv2FNw2c,15134
63
- swcgeom-0.16.0.dist-info/LICENSE,sha256=JPtohhZ4XURqoKI0ZqnMYb7dobCOoZR_n5EpnaLTp3E,11344
64
- swcgeom-0.16.0.dist-info/METADATA,sha256=cSs43xlPOCFXa9GcEGPGUE0tjDo6vDtskZ8IvlZAcuA,2332
65
- swcgeom-0.16.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
66
- swcgeom-0.16.0.dist-info/top_level.txt,sha256=hmLyUXWS61Gxl07haswFEKKefYPBVJYlUlol8ghNkjY,8
67
- swcgeom-0.16.0.dist-info/RECORD,,
61
+ swcgeom-0.17.0.dist-info/LICENSE,sha256=JPtohhZ4XURqoKI0ZqnMYb7dobCOoZR_n5EpnaLTp3E,11344
62
+ swcgeom-0.17.0.dist-info/METADATA,sha256=wtsF7F3P38QJtBXm0wpsAQRofs2sjXAbNHrlrFDLM-4,2332
63
+ swcgeom-0.17.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
64
+ swcgeom-0.17.0.dist-info/top_level.txt,sha256=hmLyUXWS61Gxl07haswFEKKefYPBVJYlUlol8ghNkjY,8
65
+ swcgeom-0.17.0.dist-info/RECORD,,
@@ -1,67 +0,0 @@
1
- """Branch anlysis of tree."""
2
-
3
- from functools import cached_property
4
- from typing import List, TypeVar
5
-
6
- import numpy as np
7
- import numpy.typing as npt
8
-
9
- from swcgeom.core import Branch, Tree
10
-
11
- __all__ = ["BranchFeatures"]
12
-
13
- T = TypeVar("T", bound=Branch)
14
-
15
-
16
- class BranchFeatures:
17
- """Analysis bransh of tree."""
18
-
19
- tree: Tree
20
-
21
- def __init__(self, tree: Tree) -> None:
22
- self.tree = tree
23
-
24
- def get_count(self) -> int:
25
- return len(self._branches)
26
-
27
- def get_length(self) -> npt.NDArray[np.float32]:
28
- """Get length of branches."""
29
- length = [br.length() for br in self._branches]
30
- return np.array(length, dtype=np.float32)
31
-
32
- def get_tortuosity(self) -> npt.NDArray[np.float32]:
33
- """Get tortuosity of path."""
34
- return np.array([br.tortuosity() for br in self._branches], dtype=np.float32)
35
-
36
- def get_angle(self, eps: float = 1e-7) -> npt.NDArray[np.float32]:
37
- """Get agnle between branches.
38
-
39
- Returns
40
- -------
41
- angle : npt.NDArray[np.float32]
42
- An array of shape (N, N), which N is length of branches.
43
- """
44
-
45
- return self.calc_angle(self._branches, eps=eps)
46
-
47
- @staticmethod
48
- def calc_angle(branches: List[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
49
- """Calc agnle between branches.
50
-
51
- Returns
52
- -------
53
- angle : npt.NDArray[np.float32]
54
- An array of shape (N, N), which N is length of branches.
55
- """
56
-
57
- vector = np.array([br[-1].xyz() - br[0].xyz() for br in branches])
58
- vector_dot = np.matmul(vector, vector.T)
59
- vector_norm = np.linalg.norm(vector, ord=2, axis=1, keepdims=True)
60
- vector_norm_dot = np.matmul(vector_norm, vector_norm.T) + eps
61
- arccos = np.clip(vector_dot / vector_norm_dot, -1, 1)
62
- angle = np.arccos(arccos)
63
- return angle
64
-
65
- @cached_property
66
- def _branches(self) -> List[Tree.Branch]:
67
- return self.tree.get_branches()
@@ -1,37 +0,0 @@
1
- """Depth distribution of tree."""
2
-
3
-
4
- from functools import cached_property
5
- from typing import List
6
-
7
- import numpy as np
8
- import numpy.typing as npt
9
-
10
- from swcgeom.core import Tree
11
-
12
- __all__ = ["PathFeatures"]
13
-
14
-
15
- class PathFeatures:
16
- """Path analysis of tree."""
17
-
18
- tree: Tree
19
-
20
- def __init__(self, tree: Tree) -> None:
21
- self.tree = tree
22
-
23
- def get_count(self) -> int:
24
- return len(self._paths)
25
-
26
- def get_length(self) -> npt.NDArray[np.float32]:
27
- """Get length of paths."""
28
- length = [path.length() for path in self._paths]
29
- return np.array(length, dtype=np.float32)
30
-
31
- def get_tortuosity(self) -> npt.NDArray[np.float32]:
32
- """Get tortuosity of path."""
33
- return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
34
-
35
- @cached_property
36
- def _paths(self) -> List[Tree.Path]:
37
- return self.tree.get_paths()