swcgeom 0.16.0__tar.gz → 0.17.1__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.16.0 → swcgeom-0.17.1}/CHANGELOG.md +31 -0
- {swcgeom-0.16.0/swcgeom.egg-info → swcgeom-0.17.1}/PKG-INFO +1 -1
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/CollectTips.ipynb +2 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/CutTree.ipynb +1 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/Features.ipynb +2 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/dgl/graph.py +4 -4
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/pytorch/branch.py +2 -1
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/pytorch/branch_dataset.py +2 -1
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/_version.py +2 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/__init__.py +1 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/feature_extractor.py +16 -15
- swcgeom-0.16.0/swcgeom/analysis/node_features.py → swcgeom-0.17.1/swcgeom/analysis/features.py +105 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/lmeasure.py +5 -5
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/sholl.py +4 -4
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/trunk.py +12 -11
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/visualization.py +9 -9
- swcgeom-0.17.1/swcgeom/analysis/visualization3d.py +85 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/analysis/volume.py +4 -4
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/branch.py +4 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/branch_tree.py +3 -4
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/compartment.py +3 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/node.py +2 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/path.py +3 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/population.py +16 -27
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc.py +11 -10
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/base.py +8 -17
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/io.py +7 -6
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/normalizer.py +4 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/subtree.py +2 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/tree.py +22 -34
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/tree_utils.py +11 -10
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/tree_utils_impl.py +3 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/images/augmentation.py +3 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/images/folder.py +10 -16
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/images/io.py +76 -111
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/image_stack.py +6 -5
- swcgeom-0.17.1/swcgeom/transforms/images.py +202 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/neurolucida_asc.py +4 -6
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/population.py +1 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/tree.py +8 -7
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/tree_assembler.py +4 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/ellipse.py +3 -4
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/neuromorpho.py +17 -16
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/plotter_2d.py +12 -6
- swcgeom-0.17.1/swcgeom/utils/plotter_3d.py +31 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/renderer.py +6 -6
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/sdf.py +2 -2
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/solid_geometry.py +1 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/transforms.py +1 -3
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/volumetric_object.py +8 -10
- {swcgeom-0.16.0 → swcgeom-0.17.1/swcgeom.egg-info}/PKG-INFO +1 -1
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom.egg-info/SOURCES.txt +3 -3
- swcgeom-0.16.0/swcgeom/analysis/branch_features.py +0 -67
- swcgeom-0.16.0/swcgeom/analysis/path_features.py +0 -37
- swcgeom-0.16.0/swcgeom/transforms/images.py +0 -102
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.github/workflows/build.yml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.github/workflows/github-publish.yml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.github/workflows/pypi-publish.yml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.github/workflows/test.yml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.gitignore +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.pylintrc +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/.vscode/settings.json +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/LICENSE +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/README.md +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/Branch.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/BranchTree.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/GeometryTransform.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/ImageStack.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/MST.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/SpectralClustering.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/Tree.ipynb +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/data/101711-10_4p5-of-16_initial.CNG.swc +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/data/101711-11_16-of-16_initial.CNG.swc +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/data/1059283677_15257_2226-X16029-Y23953.swc +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/data/toydata.swc +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/examples/pytorch/tree_folder_dataset.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/git-conventional-commits.yaml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/pyproject.toml +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/setup.cfg +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/assembler.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/core/swc_utils/checker.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/images/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/images/contrast.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/base.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/branch.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/geometry.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/image_preprocess.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/mst.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/transforms/path.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/debug.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/download.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/dsu.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/file.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom/utils/numpy_helper.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom.egg-info/dependency_links.txt +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom.egg-info/requires.txt +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/swcgeom.egg-info/top_level.txt +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/analysis/test_volume.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/transforms/test_neurolucida_asc.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/__init__.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_dsu.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_numpy_helper.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_sdf.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_solid_geometry.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_transforms.py +0 -0
- {swcgeom-0.16.0 → swcgeom-0.17.1}/tests/utils/test_volumetric_object.py +0 -0
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## **0.17.1** <sub><sup>2024-04-05 ([a8007ac...0ef3c2b](https://github.com/yzx9/swcgeom/compare/a8007ac6c5eb7de03298dbdcc6be59d3c282e125...0ef3c2b370a570366a79ef456c1125d12f006409?diff=split))</sup></sub>
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#####  `utils`
|
|
8
|
+
|
|
9
|
+
- set round style ([90030e3](https://github.com/yzx9/swcgeom/commit/90030e3af1dc7ba763e04c313cb93f154e2f052c))
|
|
10
|
+
|
|
11
|
+
<br>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## **0.17.0** <sub><sup>2024-04-04 ([59257cb...2072139](https://github.com/yzx9/swcgeom/compare/59257cbfb6264afd3ca31fcba002f692bbb69ed0...20721393086e5a890b097b3ae32aaa1e4ad6b898?diff=split))</sup></sub>
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
#####  `images`
|
|
19
|
+
|
|
20
|
+
- handle raw file in terafly ([67ecd63](https://github.com/yzx9/swcgeom/commit/67ecd63bf4124d49b00e779c2702a7b939ef710b))
|
|
21
|
+
- add image stack dtype support ([7313ead](https://github.com/yzx9/swcgeom/commit/7313ead09ec61970f221b9e0c3691d31c269b591))
|
|
22
|
+
|
|
23
|
+
#####  `transforms`
|
|
24
|
+
|
|
25
|
+
- add new image transformation classes ([7b5d805](https://github.com/yzx9/swcgeom/commit/7b5d805a1aa0deac3f9d7576a9aecab36b59774b))
|
|
26
|
+
- add image flip and notes ([5f2a4d1](https://github.com/yzx9/swcgeom/commit/5f2a4d1a9c2b5fb68e8221c7b024a411b247ab6d))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### BREAKING CHANGES
|
|
30
|
+
- `images` remove deprecated \`swap\_xy\` and \`filp\_xy\` flag ([2072139](https://github.com/yzx9/swcgeom/commit/20721393086e5a890b097b3ae32aaa1e4ad6b898))
|
|
31
|
+
<br>
|
|
32
|
+
|
|
33
|
+
|
|
3
34
|
## **0.16.0** <sub><sup>2024-03-16 ([bf2bf95...7029483](https://github.com/yzx9/swcgeom/compare/bf2bf95ee9fbfb7eba871db7303292f1dfcc7b8f...70294836643e51ca43d039887bbd71de3a9b561b?diff=split))</sup></sub>
|
|
4
35
|
|
|
5
36
|
### Features
|
|
@@ -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
|
|
|
@@ -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.
|
|
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 *
|
|
@@ -7,23 +7,24 @@ 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
|
|
17
18
|
import seaborn as sns
|
|
18
19
|
from matplotlib.axes import Axes
|
|
19
20
|
|
|
20
|
-
from swcgeom.analysis.
|
|
21
|
-
from swcgeom.analysis.node_features import (
|
|
21
|
+
from swcgeom.analysis.features import (
|
|
22
22
|
BifurcationFeatures,
|
|
23
|
+
BranchFeatures,
|
|
23
24
|
NodeFeatures,
|
|
25
|
+
PathFeatures,
|
|
24
26
|
TipFeatures,
|
|
25
27
|
)
|
|
26
|
-
from swcgeom.analysis.path_features import PathFeatures
|
|
27
28
|
from swcgeom.analysis.sholl import Sholl
|
|
28
29
|
from swcgeom.analysis.volume import get_volume
|
|
29
30
|
from swcgeom.core import Population, Populations, Tree
|
|
@@ -54,7 +55,7 @@ Feature = Literal[
|
|
|
54
55
|
]
|
|
55
56
|
|
|
56
57
|
NDArrayf32 = npt.NDArray[np.float32]
|
|
57
|
-
FeatAndKwargs = Feature |
|
|
58
|
+
FeatAndKwargs = Feature | tuple[Feature, dict[str, Any]]
|
|
58
59
|
|
|
59
60
|
Feature1D = set(["length", "volume", "node_count", "bifurcation_count", "tip_count"])
|
|
60
61
|
|
|
@@ -121,9 +122,9 @@ class FeatureExtractor(ABC):
|
|
|
121
122
|
@overload
|
|
122
123
|
def get(self, feature: Feature, **kwargs) -> NDArrayf32: ...
|
|
123
124
|
@overload
|
|
124
|
-
def get(self, feature:
|
|
125
|
+
def get(self, feature: list[FeatAndKwargs]) -> list[NDArrayf32]: ...
|
|
125
126
|
@overload
|
|
126
|
-
def get(self, feature:
|
|
127
|
+
def get(self, feature: dict[Feature, dict[str, Any]]) -> dict[str, NDArrayf32]: ...
|
|
127
128
|
# fmt:on
|
|
128
129
|
def get(self, feature, **kwargs):
|
|
129
130
|
"""Get feature.
|
|
@@ -168,7 +169,7 @@ class FeatureExtractor(ABC):
|
|
|
168
169
|
|
|
169
170
|
# Custom Plots
|
|
170
171
|
|
|
171
|
-
def plot_node_branch_order(self, feature_kwargs:
|
|
172
|
+
def plot_node_branch_order(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
172
173
|
vals = self._get("node_branch_order", **feature_kwargs)
|
|
173
174
|
bin_edges = np.arange(int(np.ceil(vals.max() + 1))) + 0.5
|
|
174
175
|
return self._plot_histogram_impl(vals, bin_edges, **kwargs)
|
|
@@ -234,7 +235,7 @@ class TreeFeatureExtractor(FeatureExtractor):
|
|
|
234
235
|
|
|
235
236
|
def plot_sholl(
|
|
236
237
|
self,
|
|
237
|
-
feature_kwargs:
|
|
238
|
+
feature_kwargs: dict[str, Any], # pylint: disable=unused-argument
|
|
238
239
|
**kwargs,
|
|
239
240
|
) -> Axes:
|
|
240
241
|
_, ax = self._features.sholl.plot(**kwargs)
|
|
@@ -264,7 +265,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
264
265
|
"""Extract features from population."""
|
|
265
266
|
|
|
266
267
|
_population: Population
|
|
267
|
-
_features:
|
|
268
|
+
_features: list[Features]
|
|
268
269
|
|
|
269
270
|
def __init__(self, population: Population) -> None:
|
|
270
271
|
super().__init__()
|
|
@@ -279,7 +280,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
279
280
|
|
|
280
281
|
# Custom Plots
|
|
281
282
|
|
|
282
|
-
def plot_sholl(self, feature_kwargs:
|
|
283
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
283
284
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
284
285
|
ax = self._lineplot(xs=rs, ys=vals.flatten(), **kwargs)
|
|
285
286
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -295,7 +296,7 @@ class PopulationFeatureExtractor(FeatureExtractor):
|
|
|
295
296
|
|
|
296
297
|
def _get_sholl_impl(
|
|
297
298
|
self, steps: int = 20, **kwargs
|
|
298
|
-
) ->
|
|
299
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
299
300
|
rmax = max(t.sholl.rmax for t in self._features)
|
|
300
301
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
|
301
302
|
vals = self._get_impl("sholl", steps=rs, **kwargs)
|
|
@@ -333,7 +334,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
333
334
|
"""Extract feature from population."""
|
|
334
335
|
|
|
335
336
|
_populations: Populations
|
|
336
|
-
_features:
|
|
337
|
+
_features: list[list[Features]]
|
|
337
338
|
|
|
338
339
|
def __init__(self, populations: Populations) -> None:
|
|
339
340
|
super().__init__()
|
|
@@ -348,7 +349,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
348
349
|
|
|
349
350
|
# Custom Plots
|
|
350
351
|
|
|
351
|
-
def plot_sholl(self, feature_kwargs:
|
|
352
|
+
def plot_sholl(self, feature_kwargs: dict[str, Any], **kwargs) -> Axes:
|
|
352
353
|
vals, rs = self._get_sholl_impl(**feature_kwargs)
|
|
353
354
|
ax = self._lineplot(xs=rs, ys=vals, **kwargs)
|
|
354
355
|
ax.set_ylabel("Count of Intersections")
|
|
@@ -369,7 +370,7 @@ class PopulationsFeatureExtractor(FeatureExtractor):
|
|
|
369
370
|
|
|
370
371
|
def _get_sholl_impl(
|
|
371
372
|
self, steps: int = 20, **kwargs
|
|
372
|
-
) ->
|
|
373
|
+
) -> tuple[NDArrayf32, NDArrayf32]:
|
|
373
374
|
rmaxs = chain.from_iterable((t.sholl.rmax for t in p) for p in self._features)
|
|
374
375
|
rmax = max(rmaxs)
|
|
375
376
|
rs = Sholl.get_rs(rmax=rmax, steps=steps)
|
swcgeom-0.16.0/swcgeom/analysis/node_features.py → swcgeom-0.17.1/swcgeom/analysis/features.py
RENAMED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
"""
|
|
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 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__ = [
|
|
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()
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
@@ -274,7 +274,7 @@ class LMeasure:
|
|
|
274
274
|
rall_power, _, _, _ = self._rall_power(bif)
|
|
275
275
|
return rall_power
|
|
276
276
|
|
|
277
|
-
def _rall_power_d(self, bif: Tree.Node) ->
|
|
277
|
+
def _rall_power_d(self, bif: Tree.Node) -> tuple[float, float, float]:
|
|
278
278
|
children = bif.children()
|
|
279
279
|
assert len(children) == 2, "Rall Power is only defined for bifurcations"
|
|
280
280
|
parent = bif.parent()
|
|
@@ -284,7 +284,7 @@ class LMeasure:
|
|
|
284
284
|
da, db = 2 * children[0].r, 2 * children[1].r
|
|
285
285
|
return dp, da, db
|
|
286
286
|
|
|
287
|
-
def _rall_power(self, bif: Tree.Node) ->
|
|
287
|
+
def _rall_power(self, bif: Tree.Node) -> tuple[float, float, float, float]:
|
|
288
288
|
dp, da, db = self._rall_power_d(bif)
|
|
289
289
|
start, stop, step = 0, 5, 5 / 1000
|
|
290
290
|
xs = np.arange(start, stop, step)
|
|
@@ -501,7 +501,7 @@ class LMeasure:
|
|
|
501
501
|
|
|
502
502
|
def _bif_vector_local(
|
|
503
503
|
self, bif: Tree.Node
|
|
504
|
-
) ->
|
|
504
|
+
) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
|
|
505
505
|
children = bif.children()
|
|
506
506
|
assert len(children) == 2, "Only defined for bifurcations"
|
|
507
507
|
|
|
@@ -511,7 +511,7 @@ class LMeasure:
|
|
|
511
511
|
|
|
512
512
|
def _bif_vector_remote(
|
|
513
513
|
self, bif: Tree.Node
|
|
514
|
-
) ->
|
|
514
|
+
) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]:
|
|
515
515
|
children = bif.children()
|
|
516
516
|
assert len(children) == 2, "Only defined for bifurcations"
|
|
517
517
|
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
@@ -84,18 +84,18 @@ class Sholl:
|
|
|
84
84
|
|
|
85
85
|
def plot( # pylint: disable=too-many-arguments
|
|
86
86
|
self,
|
|
87
|
-
steps:
|
|
87
|
+
steps: list[float] | int = 20,
|
|
88
88
|
plot_type: str | None = None,
|
|
89
89
|
kind: Literal["bar", "linechart", "circles"] = "circles",
|
|
90
90
|
fig: Figure | None = None,
|
|
91
91
|
ax: Axes | None = None,
|
|
92
92
|
**kwargs,
|
|
93
|
-
) ->
|
|
93
|
+
) -> tuple[Figure, Axes]:
|
|
94
94
|
"""Plot Sholl analysis.
|
|
95
95
|
|
|
96
96
|
Parameters
|
|
97
97
|
----------
|
|
98
|
-
steps : int or
|
|
98
|
+
steps : int or list[float], default to 20
|
|
99
99
|
Steps of raius of circle. If steps is int, then it will be
|
|
100
100
|
evenly divided into n radii.
|
|
101
101
|
kind : "bar" | "linechart" | "circles", default `circles`
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# pylint: disable=invalid-name
|
|
4
4
|
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from itertools import chain
|
|
6
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Literal, Optional, cast
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
10
|
import numpy.typing as npt
|
|
@@ -28,28 +29,28 @@ def draw_trunk(
|
|
|
28
29
|
*,
|
|
29
30
|
fig: Optional[Figure] = None,
|
|
30
31
|
ax: Optional[Axes] = None,
|
|
31
|
-
bound: Bounds |
|
|
32
|
-
point: bool |
|
|
32
|
+
bound: Bounds | tuple[Bounds, dict[str, Any]] | None = "ellipse",
|
|
33
|
+
point: bool | dict[str, Any] = True,
|
|
33
34
|
projection: Projection = "2d",
|
|
34
35
|
cmap: Any = "viridis",
|
|
35
36
|
**kwargs,
|
|
36
|
-
) ->
|
|
37
|
+
) -> tuple[Figure, Axes]:
|
|
37
38
|
"""Draw trunk tree.
|
|
38
39
|
|
|
39
40
|
Parameters
|
|
40
41
|
----------
|
|
41
42
|
t : Tree
|
|
42
|
-
florets : List of (int |
|
|
43
|
+
florets : List of (int | list of int)
|
|
43
44
|
The florets that needs to be removed, each floret can be a
|
|
44
45
|
subtree or multiple subtrees (e.g., dendrites are a bunch of
|
|
45
46
|
subtrees), each number is the id of a tree node.
|
|
46
47
|
fig : ~matplotlib.figure.Figure, optional
|
|
47
48
|
ax : ~matplotlib.axes.Axes, optional
|
|
48
|
-
bound : Bounds | (Bounds,
|
|
49
|
+
bound : Bounds | (Bounds, dict[str, Any]) | None, default 'ellipse'
|
|
49
50
|
Kind of bound, support 'aabb', 'ellipse'. If bound is None, no
|
|
50
51
|
bound will be drawn. If bound is a tuple, the second item will
|
|
51
52
|
used as kwargs and forward to draw function.
|
|
52
|
-
point : bool |
|
|
53
|
+
point : bool | dict[str, Any], default True
|
|
53
54
|
Draw point at the start of a subtree. If point is False, no
|
|
54
55
|
point will be drawn. If point is a dict, this will used a
|
|
55
56
|
kwargs and forward to draw function.
|
|
@@ -57,7 +58,7 @@ def draw_trunk(
|
|
|
57
58
|
Colormap, any value supported by ~matplotlib.cm.Colormap. We
|
|
58
59
|
will use the ratio of the length of the subtree to the total
|
|
59
60
|
length of the tree to determine the color.
|
|
60
|
-
**kwargs :
|
|
61
|
+
**kwargs : dict[str, Any]
|
|
61
62
|
Forward to ~swcgeom.analysis.draw.
|
|
62
63
|
"""
|
|
63
64
|
# pylint: disable=too-many-locals
|
|
@@ -83,14 +84,14 @@ def draw_trunk(
|
|
|
83
84
|
|
|
84
85
|
def split_florets(
|
|
85
86
|
t: Tree, florets: Iterable[int | Iterable[int]]
|
|
86
|
-
) ->
|
|
87
|
+
) -> tuple[Tree, list[list[Tree]]]:
|
|
87
88
|
florets = [[i] if isinstance(i, (int, np.integer)) else i for i in florets]
|
|
88
89
|
subtrees = [[get_subtree(t, ff) for ff in f] for f in florets]
|
|
89
90
|
trunk = to_subtree(t, chain(*florets))
|
|
90
91
|
return trunk, subtrees
|
|
91
92
|
|
|
92
93
|
|
|
93
|
-
def get_length_ratio(t: Tree, tss:
|
|
94
|
+
def get_length_ratio(t: Tree, tss: list[list[Tree]]) -> Any:
|
|
94
95
|
lens = np.array([sum(t.length() for t in ts) for ts in tss])
|
|
95
96
|
return lens / t.length()
|
|
96
97
|
|
|
@@ -101,7 +102,7 @@ def get_length_ratio(t: Tree, tss: List[List[Tree]]) -> Any:
|
|
|
101
102
|
def draw_bound(
|
|
102
103
|
ts: Iterable[Tree],
|
|
103
104
|
ax: Axes,
|
|
104
|
-
bound: Bounds |
|
|
105
|
+
bound: Bounds | tuple[Bounds, dict[str, Any]],
|
|
105
106
|
projection: Projection,
|
|
106
107
|
**kwargs,
|
|
107
108
|
) -> None:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import weakref
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Literal, Optional
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
from matplotlib.axes import Axes
|
|
@@ -21,15 +21,15 @@ from swcgeom.utils import (
|
|
|
21
21
|
|
|
22
22
|
__all__ = ["draw"]
|
|
23
23
|
|
|
24
|
-
Positions = Literal["lt", "lb", "rt", "rb"] |
|
|
25
|
-
locations:
|
|
24
|
+
Positions = Literal["lt", "lb", "rt", "rb"] | tuple[float, float]
|
|
25
|
+
locations: dict[Literal["lt", "lb", "rt", "rb"], tuple[float, float]] = {
|
|
26
26
|
"lt": (0.10, 0.90),
|
|
27
27
|
"lb": (0.10, 0.10),
|
|
28
28
|
"rt": (0.90, 0.90),
|
|
29
29
|
"rb": (0.90, 0.10),
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
ax_weak_memo = weakref.WeakKeyDictionary[Axes,
|
|
32
|
+
ax_weak_memo = weakref.WeakKeyDictionary[Axes, dict[str, Any]]({})
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def draw(
|
|
@@ -39,7 +39,7 @@ def draw(
|
|
|
39
39
|
ax: Optional[Axes] = None,
|
|
40
40
|
show: bool | None = None,
|
|
41
41
|
camera: CameraOptions = "xy",
|
|
42
|
-
color: Optional[
|
|
42
|
+
color: Optional[dict[int, str] | str] = None,
|
|
43
43
|
label: str | bool = True,
|
|
44
44
|
direction_indicator: Positions | Literal[False] = "rb",
|
|
45
45
|
unit: Optional[str] = None,
|
|
@@ -64,7 +64,7 @@ def draw(
|
|
|
64
64
|
vector, then then threat it as (look-at, up), so camera is
|
|
65
65
|
((0, 0, 0), look-at, up). An easy way is to use the presets
|
|
66
66
|
"xy", "yz" and "zx".
|
|
67
|
-
color :
|
|
67
|
+
color : dict[int, str] | "vaa3d" | str, optional
|
|
68
68
|
Color map. If is dict, segments will be colored by the type of
|
|
69
69
|
parent node.If is string, the value will be use for any type.
|
|
70
70
|
label : str | bool, default True
|
|
@@ -120,14 +120,14 @@ def draw(
|
|
|
120
120
|
return fig, ax
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
def get_ax_swc(ax: Axes) ->
|
|
123
|
+
def get_ax_swc(ax: Axes) -> list[SWCLike]:
|
|
124
124
|
ax_weak_memo.setdefault(ax, {})
|
|
125
125
|
return ax_weak_memo[ax]["swc"]
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
def get_ax_color(
|
|
129
|
-
ax: Axes, swc: SWCLike, color: Optional[
|
|
130
|
-
) -> str |
|
|
129
|
+
ax: Axes, swc: SWCLike, color: Optional[dict[int, str] | str] = None
|
|
130
|
+
) -> str | list[str]:
|
|
131
131
|
if color == "vaa3d":
|
|
132
132
|
color = palette.vaa3d
|
|
133
133
|
elif isinstance(color, str):
|