swcgeom 0.17.1__tar.gz → 0.17.2__tar.gz

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 (109) hide show
  1. {swcgeom-0.17.1 → swcgeom-0.17.2}/CHANGELOG.md +20 -0
  2. {swcgeom-0.17.1/swcgeom.egg-info → swcgeom-0.17.2}/PKG-INFO +2 -2
  3. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/pytorch/branch_dataset.py +1 -1
  4. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/pytorch/tree_folder_dataset.py +1 -1
  5. {swcgeom-0.17.1 → swcgeom-0.17.2}/pyproject.toml +1 -1
  6. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/_version.py +2 -2
  7. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/feature_extractor.py +12 -3
  8. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/features.py +16 -4
  9. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/lmeasure.py +28 -7
  10. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/sholl.py +6 -24
  11. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/node.py +15 -1
  12. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/path.py +3 -7
  13. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/population.py +28 -3
  14. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/checker.py +3 -11
  15. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/tree.py +22 -9
  16. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/tree_utils.py +2 -7
  17. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/images/folder.py +2 -10
  18. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/images/io.py +2 -5
  19. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/image_stack.py +14 -3
  20. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/images.py +2 -10
  21. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/tree.py +30 -18
  22. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/download.py +44 -21
  23. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/sdf.py +2 -5
  24. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/transforms.py +1 -1
  25. {swcgeom-0.17.1 → swcgeom-0.17.2/swcgeom.egg-info}/PKG-INFO +2 -2
  26. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom.egg-info/requires.txt +1 -1
  27. {swcgeom-0.17.1 → swcgeom-0.17.2}/.github/workflows/build.yml +0 -0
  28. {swcgeom-0.17.1 → swcgeom-0.17.2}/.github/workflows/github-publish.yml +0 -0
  29. {swcgeom-0.17.1 → swcgeom-0.17.2}/.github/workflows/pypi-publish.yml +0 -0
  30. {swcgeom-0.17.1 → swcgeom-0.17.2}/.github/workflows/test.yml +0 -0
  31. {swcgeom-0.17.1 → swcgeom-0.17.2}/.gitignore +0 -0
  32. {swcgeom-0.17.1 → swcgeom-0.17.2}/.pylintrc +0 -0
  33. {swcgeom-0.17.1 → swcgeom-0.17.2}/.vscode/settings.json +0 -0
  34. {swcgeom-0.17.1 → swcgeom-0.17.2}/LICENSE +0 -0
  35. {swcgeom-0.17.1 → swcgeom-0.17.2}/README.md +0 -0
  36. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/Branch.ipynb +0 -0
  37. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/BranchTree.ipynb +0 -0
  38. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/CollectTips.ipynb +0 -0
  39. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/CutTree.ipynb +0 -0
  40. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/Features.ipynb +0 -0
  41. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/GeometryTransform.ipynb +0 -0
  42. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/ImageStack.ipynb +0 -0
  43. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/MST.ipynb +0 -0
  44. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/SpectralClustering.ipynb +0 -0
  45. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/Tree.ipynb +0 -0
  46. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -0
  47. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -0
  48. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -0
  49. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/data/toydata.swc +0 -0
  50. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/dgl/graph.py +0 -0
  51. {swcgeom-0.17.1 → swcgeom-0.17.2}/examples/pytorch/branch.py +0 -0
  52. {swcgeom-0.17.1 → swcgeom-0.17.2}/git-conventional-commits.yaml +0 -0
  53. {swcgeom-0.17.1 → swcgeom-0.17.2}/setup.cfg +0 -0
  54. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/__init__.py +0 -0
  55. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/__init__.py +0 -0
  56. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/trunk.py +0 -0
  57. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/visualization.py +0 -0
  58. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/visualization3d.py +0 -0
  59. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/analysis/volume.py +0 -0
  60. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/__init__.py +0 -0
  61. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/branch.py +0 -0
  62. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/branch_tree.py +0 -0
  63. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/compartment.py +0 -0
  64. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc.py +0 -0
  65. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/__init__.py +0 -0
  66. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/assembler.py +0 -0
  67. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/base.py +0 -0
  68. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/io.py +0 -0
  69. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/normalizer.py +0 -0
  70. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/swc_utils/subtree.py +0 -0
  71. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/core/tree_utils_impl.py +0 -0
  72. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/images/__init__.py +0 -0
  73. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/images/augmentation.py +0 -0
  74. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/images/contrast.py +0 -0
  75. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/__init__.py +0 -0
  76. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/base.py +0 -0
  77. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/branch.py +0 -0
  78. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/geometry.py +0 -0
  79. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/image_preprocess.py +0 -0
  80. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/mst.py +0 -0
  81. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/neurolucida_asc.py +0 -0
  82. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/path.py +0 -0
  83. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/population.py +0 -0
  84. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/transforms/tree_assembler.py +0 -0
  85. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/__init__.py +0 -0
  86. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/debug.py +0 -0
  87. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/dsu.py +0 -0
  88. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/ellipse.py +0 -0
  89. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/file.py +0 -0
  90. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/neuromorpho.py +0 -0
  91. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/numpy_helper.py +0 -0
  92. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/plotter_2d.py +0 -0
  93. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/plotter_3d.py +0 -0
  94. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/renderer.py +0 -0
  95. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/solid_geometry.py +0 -0
  96. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom/utils/volumetric_object.py +0 -0
  97. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom.egg-info/SOURCES.txt +0 -0
  98. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom.egg-info/dependency_links.txt +0 -0
  99. {swcgeom-0.17.1 → swcgeom-0.17.2}/swcgeom.egg-info/top_level.txt +0 -0
  100. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/__init__.py +0 -0
  101. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/analysis/test_volume.py +0 -0
  102. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/transforms/test_neurolucida_asc.py +0 -0
  103. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/__init__.py +0 -0
  104. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_dsu.py +0 -0
  105. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_numpy_helper.py +0 -0
  106. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_sdf.py +0 -0
  107. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_solid_geometry.py +0 -0
  108. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_transforms.py +0 -0
  109. {swcgeom-0.17.1 → swcgeom-0.17.2}/tests/utils/test_volumetric_object.py +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## **0.17.2**&emsp;<sub><sup>2024-08-07 ([9d5347d...59a5cbf](https://github.com/yzx9/swcgeom/compare/9d5347d0a427f460b92fd86c596e40a23812e48d...59a5cbfbe78f18a95ff8f35e72a4a0f0cdbc5009?diff=split))</sup></sub>
4
+
5
+ ### Bug Fixes
6
+
7
+ ##### &ensp;`analysis`
8
+
9
+ - handle zero partition asymmetry in lmeasure ([1f332f2](https://github.com/yzx9/swcgeom/commit/1f332f22f465ea1ec7ff0b5e4013ddb6d64ffa5d))
10
+ - workaround with angle ([05d2ff7](https://github.com/yzx9/swcgeom/commit/05d2ff77ba5f4b4331daca0bade89e10a56f564d))
11
+
12
+ ##### &ensp;`utils`
13
+
14
+ - download with multiprocessing should avoid locals func ([6aff3e0](https://github.com/yzx9/swcgeom/commit/6aff3e0ee06fb527f759e6ec9ac09ab1ca745dad))
15
+
16
+
17
+ ### BREAKING CHANGES
18
+ - rename bifurcation to furcation ([26e9dec](https://github.com/yzx9/swcgeom/commit/26e9dec2db5b7d457dfe52d9a8e0177d26bc5b07))
19
+ - `core` lazy loading in range indexing ([7d38ea8](https://github.com/yzx9/swcgeom/commit/7d38ea80fefc907c3f55663c052db9b81f144d68))
20
+ <br>
21
+
22
+
3
23
  ## **0.17.1**&emsp;<sub><sup>2024-04-05 ([a8007ac...0ef3c2b](https://github.com/yzx9/swcgeom/compare/a8007ac6c5eb7de03298dbdcc6be59d3c282e125...0ef3c2b370a570366a79ef456c1125d12f006409?diff=split))</sup></sub>
4
24
 
5
25
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: swcgeom
3
- Version: 0.17.1
3
+ Version: 0.17.2
4
4
  Summary: Neuron geometry library for swc format
5
5
  Author-email: yzx9 <yuan.zx@outlook.com>
6
6
  License: Apache-2.0
@@ -20,7 +20,7 @@ Requires-Dist: seaborn>=0.12.0
20
20
  Requires-Dist: tifffile>=2022.8.12
21
21
  Requires-Dist: typing_extensions>=4.4.0
22
22
  Requires-Dist: tqdm>=4.46.1
23
- Requires-Dist: v3d-py-helper>=0.1.0
23
+ Requires-Dist: v3d-py-helper==0.1.0
24
24
  Provides-Extra: all
25
25
  Requires-Dist: beautifulsoup4>=4.11.1; extra == "all"
26
26
  Requires-Dist: certifi>=2023.5.7; extra == "all"
@@ -41,7 +41,7 @@ class BranchDataset(torch.utils.data.Dataset, Generic[T]):
41
41
  save : Union[str, bool], default `True`
42
42
  Save branch data to file if not False. If `True`, automatically
43
43
  generate file name.
44
- transfroms : Transfroms[Branch, T], optional
44
+ transforms : Transforms[Branch, T], optional
45
45
  Branch transformations.
46
46
 
47
47
  See Also
@@ -30,7 +30,7 @@ class TreeFolderDataset(torch.utils.data.Dataset, Generic[T]):
30
30
  ----------
31
31
  swc_dir : str
32
32
  Path of SWC file directory.
33
- transfroms : Transfroms[Tree, T], optional
33
+ transforms : Transforms[Tree, T], optional
34
34
  Branch transformations.
35
35
 
36
36
  See Also
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "tifffile>=2022.8.12",
24
24
  "typing_extensions>=4.4.0",
25
25
  "tqdm>=4.46.1",
26
- "v3d-py-helper>=0.1.0",
26
+ "v3d-py-helper==0.1.0",
27
27
  ]
28
28
 
29
29
  [project.optional-dependencies]
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.17.1'
16
- __version_tuple__ = version_tuple = (0, 17, 1)
15
+ __version__ = version = '0.17.2'
16
+ __version_tuple__ = version_tuple = (0, 17, 2)
@@ -19,8 +19,8 @@ import seaborn as sns
19
19
  from matplotlib.axes import Axes
20
20
 
21
21
  from swcgeom.analysis.features import (
22
- BifurcationFeatures,
23
22
  BranchFeatures,
23
+ FurcationFeatures,
24
24
  NodeFeatures,
25
25
  PathFeatures,
26
26
  TipFeatures,
@@ -40,6 +40,9 @@ Feature = Literal[
40
40
  "node_count",
41
41
  "node_radial_distance",
42
42
  "node_branch_order",
43
+ # furcation nodes
44
+ "furcation_count",
45
+ "furcation_radial_distance",
43
46
  # bifurcation nodes
44
47
  "bifurcation_count",
45
48
  "bifurcation_radial_distance",
@@ -57,7 +60,7 @@ Feature = Literal[
57
60
  NDArrayf32 = npt.NDArray[np.float32]
58
61
  FeatAndKwargs = Feature | tuple[Feature, dict[str, Any]]
59
62
 
60
- Feature1D = set(["length", "volume", "node_count", "bifurcation_count", "tip_count"])
63
+ Feature1D = set(["length", "volume", "node_count", "furcation_count", "tip_count"])
61
64
 
62
65
 
63
66
  class Features:
@@ -70,7 +73,7 @@ class Features:
70
73
  @cached_property
71
74
  def node_features(self) -> NodeFeatures: return NodeFeatures(self.tree)
72
75
  @cached_property
73
- def bifurcation_features(self) -> BifurcationFeatures: return BifurcationFeatures(self.node_features)
76
+ def furcation_features(self) -> FurcationFeatures: return FurcationFeatures(self.node_features)
74
77
  @cached_property
75
78
  def tip_features(self) -> TipFeatures: return TipFeatures(self.node_features)
76
79
  @cached_property
@@ -214,6 +217,12 @@ class FeatureExtractor(ABC):
214
217
  ) -> Axes:
215
218
  raise NotImplementedError()
216
219
 
220
+ def get_bifurcation_count(self, **kwargs):
221
+ raise DeprecationWarning("Use `furcation_count` instead.")
222
+
223
+ def get_bifurcation_radial_distance(self, **kwargs):
224
+ raise DeprecationWarning("Use `furcation_radial_distance` instead.")
225
+
217
226
 
218
227
  class TreeFeatureExtractor(FeatureExtractor):
219
228
  """Extract feature from tree."""
@@ -6,7 +6,7 @@ from typing import TypeVar
6
6
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
9
- from typing_extensions import Self
9
+ from typing_extensions import Self, deprecated
10
10
 
11
11
  from swcgeom.core import Branch, BranchTree, Tree
12
12
 
@@ -121,12 +121,24 @@ class _SubsetNodesFeatures(ABC):
121
121
  return cls(NodeFeatures(tree))
122
122
 
123
123
 
124
- class BifurcationFeatures(_SubsetNodesFeatures):
125
- """Evaluate bifurcation node feature of tree."""
124
+ class FurcationFeatures(_SubsetNodesFeatures):
125
+ """Evaluate furcation node feature of tree."""
126
126
 
127
127
  @cached_property
128
128
  def nodes(self) -> npt.NDArray[np.bool_]:
129
- return np.array([n.is_bifurcation() for n in self._features.tree])
129
+ return np.array([n.is_furcation() for n in self._features.tree])
130
+
131
+
132
+ @deprecated("Use FurcationFeatures instead")
133
+ class BifurcationFeatures(FurcationFeatures):
134
+ """Evaluate bifurcation node feature of tree.
135
+
136
+ Notes
137
+ -----
138
+ Deprecated due to the wrong spelling of furcation. For now, it
139
+ is just an alias of `FurcationFeatures` and raise a warning. It
140
+ will be change to raise an error in the future.
141
+ """
130
142
 
131
143
 
132
144
  class TipFeatures(_SubsetNodesFeatures):
@@ -5,9 +5,7 @@ from typing import Literal
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
-
9
8
  from swcgeom.core import Branch, Compartment, Node, Tree
10
- from swcgeom.utils import angle
11
9
 
12
10
  __all__ = ["LMeasure"]
13
11
 
@@ -69,7 +67,7 @@ class LMeasure:
69
67
  --------
70
68
  L-Measure: http://cng.gmu.edu:8080/Lm/help/N_bifs.htm
71
69
  """
72
- return len(tree.get_bifurcations())
70
+ return len(tree.get_furcations())
73
71
 
74
72
  def n_branch(self, tree: Tree) -> int:
75
73
  """Number of branches.
@@ -163,12 +161,15 @@ class LMeasure:
163
161
  --------
164
162
  L-Measure: http://cng.gmu.edu:8080/Lm/help/Partition_asymmetry.htm
165
163
  """
164
+
166
165
  children = n.children()
167
166
  assert (
168
167
  len(children) == 2
169
168
  ), "Partition asymmetry is only defined for bifurcations"
170
169
  n1 = len(children[0].subtree().get_tips())
171
170
  n2 = len(children[1].subtree().get_tips())
171
+ if n1 == n2:
172
+ return 0
172
173
  return abs(n1 - n2) / (n1 + n2 - 2)
173
174
 
174
175
  def fractal_dim(self):
@@ -336,7 +337,7 @@ class LMeasure:
336
337
  return (da**rall_power + db**rall_power) / dp**rall_power
337
338
 
338
339
  def bif_ampl_local(self, bif: Tree.Node) -> float:
339
- """Bifuraction angle.
340
+ """Bifurcation angle.
340
341
 
341
342
  Given a bifurcation, this function returns the angle between
342
343
  the first two compartments (in degree).
@@ -350,7 +351,7 @@ class LMeasure:
350
351
  return np.degrees(angle(v1, v2))
351
352
 
352
353
  def bif_ampl_remote(self, bif: Tree.Node) -> float:
353
- """Bifuraction angle.
354
+ """Bifurcation angle.
354
355
 
355
356
  This function returns the angle between two bifurcation points
356
357
  or between bifurcation point and terminal point or between two
@@ -361,7 +362,7 @@ class LMeasure:
361
362
  L-Measure: http://cng.gmu.edu:8080/Lm/help/Bif_ampl_remote.htm
362
363
  """
363
364
 
364
- v1, v2 = self._bif_vector_local(bif)
365
+ v1, v2 = self._bif_vector_remote(bif)
365
366
  return np.degrees(angle(v1, v2))
366
367
 
367
368
  def bif_tilt_local(self, bif: Tree.Node) -> float:
@@ -665,7 +666,7 @@ class LMeasure:
665
666
  n = node
666
667
  order = 0
667
668
  while n is not None:
668
- if n.is_bifurcation():
669
+ if n.is_furcation():
669
670
  order += 1
670
671
  n = n.parent()
671
672
  return order
@@ -819,3 +820,23 @@ def pill_surface_area(ra: float, rb: float, h: float) -> float:
819
820
  bottom_hemisphere_area = 2 * math.pi * rb**2
820
821
  total_area = lateral_area + top_hemisphere_area + bottom_hemisphere_area
821
822
  return total_area
823
+
824
+
825
+ # TODO: move to `utils`
826
+ def angle(a: npt.ArrayLike, b: npt.ArrayLike) -> float:
827
+ """Get the angle of vectors.
828
+
829
+ Returns
830
+ -------
831
+ angle : float
832
+ Angle [0, PI) in radians.
833
+ """
834
+
835
+ a, b = np.array(a), np.array(b)
836
+ if np.linalg.norm(a) == 0 or np.linalg.norm(b) == 0:
837
+ raise ValueError("Input vectors must not be zero vectors.")
838
+
839
+ costheta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
840
+ costheta = np.clip(costheta, -1, 1) # avoid numerical errors
841
+ theta = np.arccos(costheta)
842
+ return theta
@@ -8,6 +8,7 @@ import numpy.typing as npt
8
8
  import seaborn as sns
9
9
  from matplotlib.axes import Axes
10
10
  from matplotlib.figure import Figure
11
+ from typing_extensions import deprecated
11
12
 
12
13
  from swcgeom.analysis.visualization import draw
13
14
  from swcgeom.core import Tree
@@ -160,20 +161,17 @@ class Sholl:
160
161
 
161
162
  return self.get_rs(self.rmax, steps)
162
163
 
164
+ @deprecated("Use `Sholl.get(x)` instead")
163
165
  def get_count(self) -> npt.NDArray[np.int32]:
164
166
  """Get the count of intersection.
165
167
 
166
168
  .. deprecated:: 0.5.0
167
- Use :meth:`Sholl.get` instead.
169
+ Use :meth:`Sholl(x).get()` instead.
168
170
  """
169
171
 
170
- warnings.warn(
171
- "`Sholl.get_count` has been renamed to `get` since v0.5.0, "
172
- "and will be removed in next version",
173
- DeprecationWarning,
174
- )
175
172
  return self.get().astype(np.int32)
176
173
 
174
+ @deprecated("Use `Shool(x).get().mean()` instead")
177
175
  def avg(self) -> float:
178
176
  """Get the average of the count of intersection.
179
177
 
@@ -181,14 +179,9 @@ class Sholl:
181
179
  Use :meth:`Shool(x).get().mean()` instead.
182
180
  """
183
181
 
184
- warnings.warn(
185
- "`Sholl.avg` has been deprecated since v0.6.0 and will be "
186
- "removed in next version, use `Shool(x).get().mean()` "
187
- "instead",
188
- DeprecationWarning,
189
- )
190
182
  return self.get().mean()
191
183
 
184
+ @deprecated("Use `Shool(x).get().std()` instead")
192
185
  def std(self) -> float:
193
186
  """Get the std of the count of intersection.
194
187
 
@@ -196,14 +189,9 @@ class Sholl:
196
189
  Use :meth:`Shool(x).get().std()` instead.
197
190
  """
198
191
 
199
- warnings.warn(
200
- "`Sholl.std` has been deprecate since v0.6.0 and will be "
201
- "removed in next version, use `Shool(x).get().std()` "
202
- "instead",
203
- DeprecationWarning,
204
- )
205
192
  return self.get().std()
206
193
 
194
+ @deprecated("Use `Shool(x).get().sum()` instead")
207
195
  def sum(self) -> int:
208
196
  """Get the sum of the count of intersection.
209
197
 
@@ -211,10 +199,4 @@ class Sholl:
211
199
  Use :meth:`Shool(x).get().sum()` instead.
212
200
  """
213
201
 
214
- warnings.warn(
215
- "`Sholl.sum` has been deprecate since v0.6.0 and will be "
216
- "removed in next version, use `Shool(x).get().sum()` "
217
- "instead",
218
- DeprecationWarning,
219
- )
220
202
  return self.get().sum()
@@ -5,6 +5,7 @@ from typing import Any, Generic
5
5
 
6
6
  import numpy as np
7
7
  import numpy.typing as npt
8
+ from typing_extensions import deprecated
8
9
 
9
10
  from swcgeom.core.swc import DictSWC, SWCTypeVar
10
11
  from swcgeom.core.swc_utils import SWCNames
@@ -95,9 +96,22 @@ class Node(Generic[SWCTypeVar]):
95
96
  items = [self.id, self.type, x, y, z, r, self.pid]
96
97
  return " ".join(map(str, items))
97
98
 
98
- def is_bifurcation(self) -> bool:
99
+ def is_furcation(self) -> bool:
100
+ """Is furcation node."""
99
101
  return np.count_nonzero(self.attach.pid() == self.id) > 1
100
102
 
103
+ @deprecated("Use is_furcation instead")
104
+ def is_bifurcation(self) -> bool:
105
+ """Is furcation node.
106
+
107
+ Notes
108
+ -----
109
+ Deprecated due to the wrong spelling of furcation. For now, it
110
+ is just an alias of `is_furcation` and raise a warning. It will
111
+ be change to raise an error in the future.
112
+ """
113
+ return self.is_furcation()
114
+
101
115
  def is_tip(self) -> bool:
102
116
  return self.id not in self.attach.pid()
103
117
 
@@ -1,11 +1,11 @@
1
1
  """Nueron path."""
2
2
 
3
- import warnings
4
3
  from collections.abc import Iterable, Iterator
5
4
  from typing import Generic, overload
6
5
 
7
6
  import numpy as np
8
7
  import numpy.typing as npt
8
+ from typing_extensions import deprecated
9
9
 
10
10
  from swcgeom.core.node import Node
11
11
  from swcgeom.core.swc import DictSWC, SWCLike, SWCTypeVar
@@ -16,7 +16,7 @@ __all__ = ["Path"]
16
16
  class Path(SWCLike, Generic[SWCTypeVar]):
17
17
  """Neuron path.
18
18
 
19
- A path is a linear set of points without bifurcations.
19
+ A path is a linear set of points without furcations.
20
20
  """
21
21
 
22
22
  attach: SWCTypeVar
@@ -75,6 +75,7 @@ class Path(SWCLike, Generic[SWCTypeVar]):
75
75
  def get_ndata(self, key: str) -> npt.NDArray:
76
76
  return self.attach.get_ndata(key)[self.idx]
77
77
 
78
+ @deprecated("Use `path.node` instead.")
78
79
  def get_node(self, idx: int | np.integer) -> Node:
79
80
  """Get the count of intersection.
80
81
 
@@ -82,11 +83,6 @@ class Path(SWCLike, Generic[SWCTypeVar]):
82
83
  Use :meth:`path.node` instead.
83
84
  """
84
85
 
85
- warnings.warn(
86
- "`Path.get_node` has been deprecated since v0.16.0 and "
87
- "will be removed in future version",
88
- DeprecationWarning,
89
- )
90
86
  return self.node(idx)
91
87
 
92
88
  def node(self, idx: int | np.integer) -> Node:
@@ -97,6 +97,19 @@ class ChainTrees:
97
97
  return (self[i] for i in range(self.__len__()))
98
98
 
99
99
 
100
+ class NestTrees:
101
+ def __init__(self, trees: Trees, idx: Iterable[int], /) -> None:
102
+ super().__init__()
103
+ self.trees = trees
104
+ self.idx = list(idx)
105
+
106
+ def __getitem__(self, key: int, /) -> Tree:
107
+ return self.trees[self.idx[key]]
108
+
109
+ def __len__(self) -> int:
110
+ return len(self.idx)
111
+
112
+
100
113
  class Population:
101
114
  """Neuron population."""
102
115
 
@@ -135,13 +148,14 @@ class Population:
135
148
 
136
149
  # fmt:off
137
150
  @overload
138
- def __getitem__(self, key: slice) -> list[Tree]: ...
151
+ def __getitem__(self, key: slice) -> Trees: ...
139
152
  @overload
140
153
  def __getitem__(self, key: int) -> Tree: ...
141
154
  # fmt:on
142
- def __getitem__(self, key):
155
+ def __getitem__(self, key: int | slice):
143
156
  if isinstance(key, slice):
144
- return [cast(Tree, self.trees[i]) for i in range(*key.indices(len(self)))]
157
+ trees = NestTrees(self.trees, range(*key.indices(len(self))))
158
+ return cast(Trees, trees)
145
159
 
146
160
  if isinstance(key, (int, np.integer)):
147
161
  return cast(Tree, self.trees[int(key)])
@@ -328,3 +342,14 @@ def _get_idx(key: int, length: int) -> int:
328
342
  key += length
329
343
 
330
344
  return key
345
+
346
+
347
+ # experimental
348
+ def filter_population(
349
+ pop: Population, predicate: Callable[[Tree], bool]
350
+ ) -> "Population":
351
+ """Filter trees in the population."""
352
+
353
+ # TODO: how to avoid load trees
354
+ idx = [i for i, t in enumerate(pop) if predicate(t)]
355
+ return Population(NestTrees(pop.trees, idx), root=pop.root)
@@ -1,11 +1,11 @@
1
1
  """Check common """
2
2
 
3
- import warnings
4
3
  from collections import defaultdict
5
4
  from typing import Optional
6
5
 
7
6
  import numpy as np
8
7
  import pandas as pd
8
+ from typing_extensions import deprecated
9
9
 
10
10
  from swcgeom.core.swc_utils.base import SWCNames, Topology, get_dsu, get_names, traverse
11
11
  from swcgeom.utils import DisjointSetUnion
@@ -81,6 +81,7 @@ def has_cyclic(topology: Topology) -> bool:
81
81
  return False
82
82
 
83
83
 
84
+ @deprecated("Use `is_single_root` instead")
84
85
  def check_single_root(*args, **kwargs) -> bool:
85
86
  """Check if the tree is single root.
86
87
 
@@ -88,14 +89,10 @@ def check_single_root(*args, **kwargs) -> bool:
88
89
  Use :meth:`is_single_root` instead.
89
90
  """
90
91
 
91
- warnings.warn(
92
- "`check_single_root` has been renamed to `is_single_root` since"
93
- "v0.5.0, and will be removed in next version",
94
- DeprecationWarning,
95
- )
96
92
  return is_single_root(*args, **kwargs)
97
93
 
98
94
 
95
+ @deprecated("Use `is_bifurcate` instead")
99
96
  def is_binary_tree(
100
97
  df: pd.DataFrame, exclude_root: bool = True, *, names: Optional[SWCNames] = None
101
98
  ) -> bool:
@@ -105,11 +102,6 @@ def is_binary_tree(
105
102
  Use :meth:`is_bifurcate` instead.
106
103
  """
107
104
 
108
- warnings.warn(
109
- "`is_binary_tree` has been replaced by to `is_bifurcate` since"
110
- "v0.8.0, and will be removed in next version",
111
- DeprecationWarning,
112
- )
113
105
  names = get_names(names)
114
106
  topo = (df[names.id].to_numpy(), df[names.pid].to_numpy())
115
107
  return is_bifurcate(topo, exclude_root=exclude_root)
@@ -8,6 +8,7 @@ from typing import Literal, Optional, TypeVar, Union, overload
8
8
  import numpy as np
9
9
  import numpy.typing as npt
10
10
  import pandas as pd
11
+ from typing_extensions import deprecated
11
12
 
12
13
  from swcgeom.core.branch import Branch
13
14
  from swcgeom.core.compartment import Compartment, Compartments
@@ -38,11 +39,11 @@ class Tree(DictSWC):
38
39
 
39
40
  def branch(self) -> "Tree.Branch":
40
41
  ns: list["Tree.Node"] = [self]
41
- while not ns[-1].is_bifurcation() and (p := ns[-1].parent()) is not None:
42
+ while not ns[-1].is_furcation() and (p := ns[-1].parent()) is not None:
42
43
  ns.append(p)
43
44
 
44
45
  ns.reverse()
45
- while not (ns[-1].is_bifurcation() or ns[-1].is_tip()):
46
+ while not (ns[-1].is_furcation() or ns[-1].is_tip()):
46
47
  ns.append(ns[-1].children()[0])
47
48
 
48
49
  return Tree.Branch(self.attach, [n.id for n in ns])
@@ -187,16 +188,28 @@ class Tree(DictSWC):
187
188
  raise ValueError(f"no soma found in: {self.source}")
188
189
  return n
189
190
 
190
- def get_bifurcations(self) -> list[Node]:
191
- """Get all node of bifurcations."""
192
- bifurcations: list[int] = []
191
+ def get_furcations(self) -> list[Node]:
192
+ """Get all node of furcations."""
193
+ furcations: list[int] = []
193
194
 
194
- def collect_bifurcations(n: Tree.Node, children: list[None]) -> None:
195
+ def collect_furcations(n: Tree.Node, children: list[None]) -> None:
195
196
  if len(children) > 1:
196
- bifurcations.append(n.id)
197
+ furcations.append(n.id)
198
+
199
+ self.traverse(leave=collect_furcations)
200
+ return [self.node(i) for i in furcations]
197
201
 
198
- self.traverse(leave=collect_bifurcations)
199
- return [self.node(i) for i in bifurcations]
202
+ @deprecated("Use `get_furcations` instead")
203
+ def get_bifurcations(self) -> list[Node]:
204
+ """Get all node of furcations.
205
+
206
+ Notes
207
+ -----
208
+ Deprecated due to the wrong spelling of furcation. For now, it
209
+ is just an alias of `get_furcations` and raise a warning. It
210
+ will be change to raise an error in the future.
211
+ """
212
+ return self.get_furcations()
200
213
 
201
214
  def get_tips(self) -> list[Node]:
202
215
  """Get all node of tips."""
@@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable
5
5
  from typing import Optional, TypeVar, overload
6
6
 
7
7
  import numpy as np
8
+ from typing_extensions import deprecated
8
9
 
9
10
  from swcgeom.core.swc import SWCLike
10
11
  from swcgeom.core.swc_utils import (
@@ -95,6 +96,7 @@ def cut_tree(tree: Tree, *, enter=None, leave=None):
95
96
  return to_subtree(tree, removals)
96
97
 
97
98
 
99
+ @deprecated("Use `to_subtree` instead")
98
100
  def to_sub_tree(swc_like: SWCLike, sub: Topology) -> tuple[Tree, dict[int, int]]:
99
101
  """Create subtree from origin tree.
100
102
 
@@ -111,13 +113,6 @@ def to_sub_tree(swc_like: SWCLike, sub: Topology) -> tuple[Tree, dict[int, int]]
111
113
  id_map : dict[int, int]
112
114
  """
113
115
 
114
- warnings.warn(
115
- "`to_sub_tree` will be removed in v0.6.0, it is replaced by "
116
- "`to_subtree` beacuse it is easy to use, and this will be "
117
- "removed in next version",
118
- DeprecationWarning,
119
- )
120
-
121
116
  sub = propagate_removal(sub)
122
117
  (new_id, new_pid), id_map_arr = to_sub_topology(sub)
123
118
 
@@ -3,7 +3,6 @@
3
3
  import math
4
4
  import os
5
5
  import re
6
- import warnings
7
6
  from collections.abc import Callable, Iterable
8
7
  from dataclasses import dataclass
9
8
  from typing import Generic, Literal, Optional, TypeVar, overload
@@ -11,7 +10,7 @@ from typing import Generic, Literal, Optional, TypeVar, overload
11
10
  import numpy as np
12
11
  import numpy.typing as npt
13
12
  from tqdm import tqdm
14
- from typing_extensions import Self
13
+ from typing_extensions import Self, deprecated
15
14
 
16
15
  from swcgeom.images.io import ScalarType, read_imgs
17
16
  from swcgeom.transforms import Identity, Transform
@@ -65,6 +64,7 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
65
64
  return fs
66
65
 
67
66
  @staticmethod
67
+ @deprecated("Use `~swcgeom.images.io.read_imgs(fname).get_full()` instead")
68
68
  def read_imgs(fname: str) -> npt.NDArray[np.float32]:
69
69
  """Read images.
70
70
 
@@ -72,14 +72,6 @@ class ImageStackFolderBase(Generic[ScalarType, T]):
72
72
  Use :meth:`~swcgeom.images.io.read_imgs(fname).get_full()` instead.
73
73
  """
74
74
 
75
- warnings.warn(
76
- "`ImageStackFolderBase.read_imgs` serves as a "
77
- "straightforward wrapper for `~swcgeom.images.io.read_imgs(fname).get_full()`. "
78
- "However, as it is not utilized within our internal "
79
- "processes, it is scheduled for removal in the "
80
- "forthcoming version.",
81
- DeprecationWarning,
82
- )
83
75
  return read_imgs(fname).get_full()
84
76
 
85
77
 
@@ -12,6 +12,7 @@ import nrrd
12
12
  import numpy as np
13
13
  import numpy.typing as npt
14
14
  import tifffile
15
+ from typing_extensions import deprecated
15
16
  from v3dpy.loaders import PBD, Raw
16
17
 
17
18
  __all__ = ["read_imgs", "save_tiff", "read_images"]
@@ -601,6 +602,7 @@ class GrayImageStack:
601
602
  return self.imgs.shape[:-1]
602
603
 
603
604
 
605
+ @deprecated("Use `read_imgs` instead")
604
606
  def read_images(*args, **kwargs) -> GrayImageStack:
605
607
  """Read images.
606
608
 
@@ -608,9 +610,4 @@ def read_images(*args, **kwargs) -> GrayImageStack:
608
610
  Use :meth:`read_imgs` instead.
609
611
  """
610
612
 
611
- warnings.warn(
612
- "`read_images` has been replaced by `read_imgs` because it"
613
- "provide rgb support, and this will be removed in next version",
614
- DeprecationWarning,
615
- )
616
613
  return GrayImageStack(read_imgs(*args, **kwargs))