swcgeom 0.17.0__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.
- {swcgeom-0.17.0 → swcgeom-0.17.2}/CHANGELOG.md +31 -0
- {swcgeom-0.17.0/swcgeom.egg-info → swcgeom-0.17.2}/PKG-INFO +2 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/CollectTips.ipynb +2 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/CutTree.ipynb +1 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Features.ipynb +2 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/dgl/graph.py +4 -4
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/branch.py +2 -1
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/branch_dataset.py +3 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/pytorch/tree_folder_dataset.py +1 -1
- {swcgeom-0.17.0 → swcgeom-0.17.2}/pyproject.toml +1 -1
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/_version.py +2 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/feature_extractor.py +25 -15
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/features.py +20 -8
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/lmeasure.py +33 -12
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/sholl.py +10 -28
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/trunk.py +12 -11
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/visualization.py +9 -9
- swcgeom-0.17.2/swcgeom/analysis/visualization3d.py +85 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/volume.py +4 -4
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/branch.py +4 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/branch_tree.py +3 -4
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/compartment.py +3 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/node.py +17 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/path.py +6 -9
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/population.py +43 -29
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc.py +11 -10
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/base.py +8 -17
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/checker.py +3 -11
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/io.py +7 -6
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/normalizer.py +4 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/subtree.py +2 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree.py +41 -40
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree_utils.py +13 -17
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/tree_utils_impl.py +3 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/augmentation.py +3 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/folder.py +12 -26
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/io.py +21 -35
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/image_stack.py +20 -8
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/images.py +3 -12
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/neurolucida_asc.py +4 -6
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/population.py +1 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/tree.py +38 -25
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/tree_assembler.py +4 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/download.py +44 -21
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/ellipse.py +3 -4
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/neuromorpho.py +17 -16
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/plotter_2d.py +12 -6
- swcgeom-0.17.2/swcgeom/utils/plotter_3d.py +31 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/renderer.py +6 -6
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/sdf.py +4 -7
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/solid_geometry.py +1 -3
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/transforms.py +2 -4
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/volumetric_object.py +8 -10
- {swcgeom-0.17.0 → swcgeom-0.17.2/swcgeom.egg-info}/PKG-INFO +2 -2
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/SOURCES.txt +2 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/requires.txt +1 -1
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/build.yml +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/github-publish.yml +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/pypi-publish.yml +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.github/workflows/test.yml +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.gitignore +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.pylintrc +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/.vscode/settings.json +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/LICENSE +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/README.md +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Branch.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/BranchTree.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/GeometryTransform.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/ImageStack.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/MST.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/SpectralClustering.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/Tree.ipynb +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/examples/data/toydata.swc +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/git-conventional-commits.yaml +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/setup.cfg +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/analysis/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/core/swc_utils/assembler.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/images/contrast.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/base.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/branch.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/geometry.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/image_preprocess.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/mst.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/transforms/path.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/debug.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/dsu.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/file.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom/utils/numpy_helper.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/dependency_links.txt +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/swcgeom.egg-info/top_level.txt +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/analysis/test_volume.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/transforms/test_neurolucida_asc.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/__init__.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_dsu.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_numpy_helper.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_sdf.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_solid_geometry.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_transforms.py +0 -0
- {swcgeom-0.17.0 → swcgeom-0.17.2}/tests/utils/test_volumetric_object.py +0 -0
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## **0.17.2** <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
|
+
#####  `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
|
+
#####  `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
|
+
|
|
23
|
+
## **0.17.1** <sub><sup>2024-04-05 ([a8007ac...0ef3c2b](https://github.com/yzx9/swcgeom/compare/a8007ac6c5eb7de03298dbdcc6be59d3c282e125...0ef3c2b370a570366a79ef456c1125d12f006409?diff=split))</sup></sub>
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
#####  `utils`
|
|
28
|
+
|
|
29
|
+
- set round style ([90030e3](https://github.com/yzx9/swcgeom/commit/90030e3af1dc7ba763e04c313cb93f154e2f052c))
|
|
30
|
+
|
|
31
|
+
<br>
|
|
32
|
+
|
|
33
|
+
|
|
3
34
|
## **0.17.0** <sub><sup>2024-04-04 ([59257cb...2072139](https://github.com/yzx9/swcgeom/compare/59257cbfb6264afd3ca31fcba002f692bbb69ed0...20721393086e5a890b097b3ae32aaa1e4ad6b898?diff=split))</sup></sub>
|
|
4
35
|
|
|
5
36
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: swcgeom
|
|
3
|
-
Version: 0.17.
|
|
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
|
|
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"
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"import os\n",
|
|
24
24
|
"from functools import reduce\n",
|
|
25
25
|
"from operator import add\n",
|
|
26
|
-
"from typing import List\n",
|
|
27
26
|
"\n",
|
|
28
27
|
"import numpy as np\n",
|
|
29
28
|
"\n",
|
|
@@ -37,11 +36,11 @@
|
|
|
37
36
|
"metadata": {},
|
|
38
37
|
"outputs": [],
|
|
39
38
|
"source": [
|
|
40
|
-
"def collect_tips(cur: swcgeom.Tree.Node, children:
|
|
39
|
+
"def collect_tips(cur: swcgeom.Tree.Node, children: list[int]) -> int:\n",
|
|
41
40
|
" cur[\"tips\"] = max(1, reduce(add, children, 0))\n",
|
|
42
41
|
" return cur[\"tips\"]\n",
|
|
43
42
|
"\n",
|
|
44
|
-
"def collect_length(cur: swcgeom.Tree.Node, children:
|
|
43
|
+
"def collect_length(cur: swcgeom.Tree.Node, children: list[swcgeom.Tree.Node]) -> swcgeom.Tree.Node:\n",
|
|
45
44
|
" def length(acc: float, child: swcgeom.Tree.Node) -> float:\n",
|
|
46
45
|
" return acc + cur.distance(child) + child[\"length\"]\n",
|
|
47
46
|
"\n",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"outputs": [],
|
|
31
31
|
"source": [
|
|
32
32
|
"import math\n",
|
|
33
|
-
"from typing import List\n",
|
|
34
33
|
"\n",
|
|
35
34
|
"import numpy as np\n",
|
|
36
35
|
"import matplotlib.pyplot as plt\n",
|
|
@@ -138,7 +137,7 @@
|
|
|
138
137
|
"\n",
|
|
139
138
|
" removals = []\n",
|
|
140
139
|
"\n",
|
|
141
|
-
" def collect_cut(n: Tree.Node, children:
|
|
140
|
+
" def collect_cut(n: Tree.Node, children: list[int]) -> int:\n",
|
|
142
141
|
" if len(children) <= 1:\n",
|
|
143
142
|
" return n.id\n",
|
|
144
143
|
"\n",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"metadata": {},
|
|
21
21
|
"outputs": [],
|
|
22
22
|
"source": [
|
|
23
|
-
"from typing import Any, cast
|
|
23
|
+
"from typing import Any, cast\n",
|
|
24
24
|
"\n",
|
|
25
25
|
"import numpy as np\n",
|
|
26
26
|
"import matplotlib.pyplot as plt\n",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
],
|
|
89
89
|
"source": [
|
|
90
90
|
"features = extract_feature(tree3)\n",
|
|
91
|
-
"all_features:
|
|
91
|
+
"all_features: dict[Feature, dict] = {\n",
|
|
92
92
|
" \"length\": {},\n",
|
|
93
93
|
" \"node_radial_distance\": {},\n",
|
|
94
94
|
" \"node_branch_order\": {},\n",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Dgl grapth transforms."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import cast
|
|
4
4
|
|
|
5
5
|
import dgl
|
|
6
6
|
|
|
@@ -19,13 +19,13 @@ class ToDGLGraph(Transform[Tree, dgl.DGLGraph]):
|
|
|
19
19
|
this class is more of a toy and template.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
keys:
|
|
22
|
+
keys: list[str] | None
|
|
23
23
|
to_bidirected: bool
|
|
24
24
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
27
27
|
to_bidirected: bool = False,
|
|
28
|
-
keys:
|
|
28
|
+
keys: list[str] | None = None,
|
|
29
29
|
) -> None:
|
|
30
30
|
"""Transofrm tree to dgl graph.
|
|
31
31
|
|
|
@@ -33,7 +33,7 @@ class ToDGLGraph(Transform[Tree, dgl.DGLGraph]):
|
|
|
33
33
|
----------
|
|
34
34
|
to_bidirected : bool, default to `False`
|
|
35
35
|
If True, return bidirected graph.
|
|
36
|
-
keys :
|
|
36
|
+
keys : list[str], optional
|
|
37
37
|
Copy these keys as ndata of graph.
|
|
38
38
|
"""
|
|
39
39
|
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import warnings
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Generic, TypeVar, cast
|
|
6
7
|
|
|
7
8
|
import torch
|
|
8
9
|
import torch.utils.data
|
|
@@ -40,7 +41,7 @@ class BranchDataset(torch.utils.data.Dataset, Generic[T]):
|
|
|
40
41
|
save : Union[str, bool], default `True`
|
|
41
42
|
Save branch data to file if not False. If `True`, automatically
|
|
42
43
|
generate file name.
|
|
43
|
-
|
|
44
|
+
transforms : Transforms[Branch, T], optional
|
|
44
45
|
Branch transformations.
|
|
45
46
|
|
|
46
47
|
See Also
|
|
@@ -7,10 +7,11 @@ naming specification.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
+
from collections.abc import Callable
|
|
10
11
|
from functools import cached_property
|
|
11
12
|
from itertools import chain
|
|
12
13
|
from os.path import basename
|
|
13
|
-
from typing import Any,
|
|
14
|
+
from typing import Any, Literal, overload
|
|
14
15
|
|
|
15
16
|
import numpy as np
|
|
16
17
|
import numpy.typing as npt
|
|
@@ -18,8 +19,8 @@ import seaborn as sns
|
|
|
18
19
|
from matplotlib.axes import Axes
|
|
19
20
|
|
|
20
21
|
from swcgeom.analysis.features import (
|
|
21
|
-
BifurcationFeatures,
|
|
22
22
|
BranchFeatures,
|
|
23
|
+
FurcationFeatures,
|
|
23
24
|
NodeFeatures,
|
|
24
25
|
PathFeatures,
|
|
25
26
|
TipFeatures,
|
|
@@ -39,6 +40,9 @@ Feature = Literal[
|
|
|
39
40
|
"node_count",
|
|
40
41
|
"node_radial_distance",
|
|
41
42
|
"node_branch_order",
|
|
43
|
+
# furcation nodes
|
|
44
|
+
"furcation_count",
|
|
45
|
+
"furcation_radial_distance",
|
|
42
46
|
# bifurcation nodes
|
|
43
47
|
"bifurcation_count",
|
|
44
48
|
"bifurcation_radial_distance",
|
|
@@ -54,9 +58,9 @@ Feature = Literal[
|
|
|
54
58
|
]
|
|
55
59
|
|
|
56
60
|
NDArrayf32 = npt.NDArray[np.float32]
|
|
57
|
-
FeatAndKwargs = Feature |
|
|
61
|
+
FeatAndKwargs = Feature | tuple[Feature, dict[str, Any]]
|
|
58
62
|
|
|
59
|
-
Feature1D = set(["length", "volume", "node_count", "
|
|
63
|
+
Feature1D = set(["length", "volume", "node_count", "furcation_count", "tip_count"])
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
class Features:
|
|
@@ -69,7 +73,7 @@ class Features:
|
|
|
69
73
|
@cached_property
|
|
70
74
|
def node_features(self) -> NodeFeatures: return NodeFeatures(self.tree)
|
|
71
75
|
@cached_property
|
|
72
|
-
def
|
|
76
|
+
def furcation_features(self) -> FurcationFeatures: return FurcationFeatures(self.node_features)
|
|
73
77
|
@cached_property
|
|
74
78
|
def tip_features(self) -> TipFeatures: return TipFeatures(self.node_features)
|
|
75
79
|
@cached_property
|
|
@@ -121,9 +125,9 @@ class FeatureExtractor(ABC):
|
|
|
121
125
|
@overload
|
|
122
126
|
def get(self, feature: Feature, **kwargs) -> NDArrayf32: ...
|
|
123
127
|
@overload
|
|
124
|
-
def get(self, feature:
|
|
128
|
+
def get(self, feature: list[FeatAndKwargs]) -> list[NDArrayf32]: ...
|
|
125
129
|
@overload
|
|
126
|
-
def get(self, feature:
|
|
130
|
+
def get(self, feature: dict[Feature, dict[str, Any]]) -> dict[str, NDArrayf32]: ...
|
|
127
131
|
# fmt:on
|
|
128
132
|
def get(self, feature, **kwargs):
|
|
129
133
|
"""Get feature.
|
|
@@ -168,7 +172,7 @@ class FeatureExtractor(ABC):
|
|
|
168
172
|
|
|
169
173
|
# Custom Plots
|
|
170
174
|
|
|
171
|
-
def plot_node_branch_order(self, feature_kwargs:
|
|
175
|
+
def plot_node_branch_order(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
172
176
|
vals = self._get("node_branch_order", **feature_kwargs)
|
|
173
177
|
bin_edges = np.arange(int(np.ceil(vals.max() + 1))) + 0.5
|
|
174
178
|
return self._plot_histogram_impl(vals, bin_edges, **kwargs)
|
|
@@ -213,6 +217,12 @@ class FeatureExtractor(ABC):
|
|
|
213
217
|
) -> Axes:
|
|
214
218
|
raise NotImplementedError()
|
|
215
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
|
+
|
|
216
226
|
|
|
217
227
|
class TreeFeatureExtractor(FeatureExtractor):
|
|
218
228
|
"""Extract feature from tree."""
|
|
@@ -234,7 +244,7 @@ class TreeFeatureExtractor(FeatureExtractor):
|
|
|
234
244
|
|
|
235
245
|
def plot_sholl(
|
|
236
246
|
self,
|
|
237
|
-
feature_kwargs:
|
|
247
|
+
feature_kwargs: dict[str, Any], # pylint: disable=unused-argument
|
|
238
248
|
**kwargs,
|
|
239
249
|
) -> Axes:
|
|
240
250
|
_, ax = self._features.sholl.plot(**kwargs)
|
|
@@ -264,7 +274,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
264
274
|
"""Extract features from population."""
|
|
265
275
|
|
|
266
276
|
_population: Population
|
|
267
|
-
_features:
|
|
277
|
+
_features: list[Features]
|
|
268
278
|
|
|
269
279
|
def __init__(self, population: Population) -> None:
|
|
270
280
|
super().__init__()
|
|
@@ -279,7 +289,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
279
289
|
|
|
280
290
|
# Custom Plots
|
|
281
291
|
|
|
282
|
-
def plot_sholl(self, feature_kwargs:
|
|
292
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
283
293
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
284
294
|
ax = self._lineplot(xs=rs, ys=vals.flatten(), **kwargs)
|
|
285
295
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -295,7 +305,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
295
305
|
|
|
296
306
|
def _get_sholl_impl(
|
|
297
307
|
self, steps: int = 20, **kwargs
|
|
298
|
-
) ->
|
|
308
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
299
309
|
rmax = max(t.sholl.rmax for t in self._features)
|
|
300
310
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
|
301
311
|
vals = self._get_impl("sholl", steps=rs, **kwargs)
|
|
@@ -333,7 +343,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
333
343
|
"""Extract feature from population."""
|
|
334
344
|
|
|
335
345
|
_populations: Populations
|
|
336
|
-
_features:
|
|
346
|
+
_features: list[list[Features]]
|
|
337
347
|
|
|
338
348
|
def __init__(self, populations: Populations) -> None:
|
|
339
349
|
super().__init__()
|
|
@@ -348,7 +358,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
348
358
|
|
|
349
359
|
# Custom Plots
|
|
350
360
|
|
|
351
|
-
def plot_sholl(self, feature_kwargs:
|
|
361
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
352
362
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
353
363
|
ax = self._lineplot(xs=rs, ys=vals, **kwargs)
|
|
354
364
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -369,7 +379,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
369
379
|
|
|
370
380
|
def _get_sholl_impl(
|
|
371
381
|
self, steps: int = 20, **kwargs
|
|
372
|
-
) ->
|
|
382
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
373
383
|
rmaxs = chain.from_iterable((t.sholl.rmax for t in p) for p in self._features)
|
|
374
384
|
rmax = max(rmaxs)
|
|
375
385
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from functools import cached_property
|
|
5
|
-
from typing import
|
|
5
|
+
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
|
|
125
|
-
"""Evaluate
|
|
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.
|
|
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):
|
|
@@ -163,7 +175,7 @@ class PathFeatures:
|
|
|
163
175
|
return np.array([path.tortuosity() for path in self._paths], dtype=np.float32)
|
|
164
176
|
|
|
165
177
|
@cached_property
|
|
166
|
-
def _paths(self) ->
|
|
178
|
+
def _paths(self) -> list[Tree.Path]:
|
|
167
179
|
return self.tree.get_paths()
|
|
168
180
|
|
|
169
181
|
|
|
@@ -201,7 +213,7 @@ class BranchFeatures:
|
|
|
201
213
|
return self.calc_angle(self._branches, eps=eps)
|
|
202
214
|
|
|
203
215
|
@staticmethod
|
|
204
|
-
def calc_angle(branches:
|
|
216
|
+
def calc_angle(branches: list[T], eps: float = 1e-7) -> npt.NDArray[np.float32]:
|
|
205
217
|
"""Calc agnle between branches.
|
|
206
218
|
|
|
207
219
|
Returns
|
|
@@ -219,5 +231,5 @@ class BranchFeatures:
|
|
|
219
231
|
return angle
|
|
220
232
|
|
|
221
233
|
@cached_property
|
|
222
|
-
def _branches(self) ->
|
|
234
|
+
def _branches(self) -> list[Tree.Branch]:
|
|
223
235
|
return self.tree.get_branches()
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""L-Measure analysis."""
|
|
2
2
|
|
|
3
3
|
import math
|
|
4
|
-
from typing import Literal
|
|
4
|
+
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.
|
|
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):
|
|
@@ -274,7 +275,7 @@ class LMeasure:
|
|
|
274
275
|
rall_power, _, _, _ = self._rall_power(bif)
|
|
275
276
|
return rall_power
|
|
276
277
|
|
|
277
|
-
def _rall_power_d(self, bif: Tree.Node) ->
|
|
278
|
+
def _rall_power_d(self, bif: Tree.Node) -> tuple[float, float, float]:
|
|
278
279
|
children = bif.children()
|
|
279
280
|
assert len(children) == 2, "Rall Power is only defined for bifurcations"
|
|
280
281
|
parent = bif.parent()
|
|
@@ -284,7 +285,7 @@ class LMeasure:
|
|
|
284
285
|
da, db = 2 * children[0].r, 2 * children[1].r
|
|
285
286
|
return dp, da, db
|
|
286
287
|
|
|
287
|
-
def _rall_power(self, bif: Tree.Node) ->
|
|
288
|
+
def _rall_power(self, bif: Tree.Node) -> tuple[float, float, float, float]:
|
|
288
289
|
dp, da, db = self._rall_power_d(bif)
|
|
289
290
|
start, stop, step = 0, 5, 5 / 1000
|
|
290
291
|
xs = np.arange(start, stop, step)
|
|
@@ -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
|
-
"""
|
|
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
|
-
"""
|
|
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.
|
|
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:
|
|
@@ -501,7 +502,7 @@ class LMeasure:
|
|
|
501
502
|
|
|
502
503
|
def _bif_vector_local(
|
|
503
504
|
self, bif: Tree.Node
|
|
504
|
-
) ->
|
|
505
|
+
) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
|
|
505
506
|
children = bif.children()
|
|
506
507
|
assert len(children) == 2, "Only defined for bifurcations"
|
|
507
508
|
|
|
@@ -511,7 +512,7 @@ class LMeasure:
|
|
|
511
512
|
|
|
512
513
|
def _bif_vector_remote(
|
|
513
514
|
self, bif: Tree.Node
|
|
514
|
-
) ->
|
|
515
|
+
) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
|
|
515
516
|
children = bif.children()
|
|
516
517
|
assert len(children) == 2, "Only defined for bifurcations"
|
|
517
518
|
|
|
@@ -665,7 +666,7 @@ class LMeasure:
|
|
|
665
666
|
n = node
|
|
666
667
|
order = 0
|
|
667
668
|
while n is not None:
|
|
668
|
-
if n.
|
|
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
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Sholl analysis."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Literal, Optional
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
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
|
|
@@ -84,18 +85,18 @@ class Sholl:
|
|
|
84
85
|
|
|
85
86
|
def plot( # pylint: disable=too-many-arguments
|
|
86
87
|
self,
|
|
87
|
-
steps:
|
|
88
|
+
steps: list[float] | int = 20,
|
|
88
89
|
plot_type: str | None = None,
|
|
89
90
|
kind: Literal["bar", "linechart", "circles"] = "circles",
|
|
90
91
|
fig: Figure | None = None,
|
|
91
92
|
ax: Axes | None = None,
|
|
92
93
|
**kwargs,
|
|
93
|
-
) ->
|
|
94
|
+
) -> tuple[Figure, Axes]:
|
|
94
95
|
"""Plot Sholl analysis.
|
|
95
96
|
|
|
96
97
|
Parameters
|
|
97
98
|
----------
|
|
98
|
-
steps : int or
|
|
99
|
+
steps : int or list[float], default to 20
|
|
99
100
|
Steps of raius of circle. If steps is int, then it will be
|
|
100
101
|
evenly divided into n radii.
|
|
101
102
|
kind : "bar" | "linechart" | "circles", default `circles`
|
|
@@ -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()
|