bsplot 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bsplot/__init__.py +23 -0
- bsplot/anat.py +55 -0
- bsplot/animate.py +194 -0
- bsplot/bioicons.py +777 -0
- bsplot/brain.py +188 -0
- bsplot/colors.py +1935 -0
- bsplot/data/HCP_avg-SC.txt +379 -0
- bsplot/data/MNI152.rh.pial +0 -0
- bsplot/data/STN_lh.nii.gz +0 -0
- bsplot/data/STN_rh.nii.gz +0 -0
- bsplot/data/__init__.py +22 -0
- bsplot/data/left_electrode.ply +257270 -0
- bsplot/data/parcellations/HCP-MMP1.L.label.gii +442 -0
- bsplot/data/parcellations/HCP-MMP1.R.label.gii +442 -0
- bsplot/data/parcellations/code/split_hcpmmp1.sh +4 -0
- bsplot/data/right_electrode.ply +257270 -0
- bsplot/data/surface.py +112 -0
- bsplot/data/tpl-MNI152NLin2009b_atlas-hcpmmp1_desc-ordered_dseg.nii.gz +0 -0
- bsplot/figure.py +177 -0
- bsplot/graph/__init__.py +31 -0
- bsplot/graph/edges.py +312 -0
- bsplot/graph/flowchart.py +762 -0
- bsplot/graph/layout.py +201 -0
- bsplot/graph/network.py +974 -0
- bsplot/graph/nodes.py +452 -0
- bsplot/panels.py +110 -0
- bsplot/scientific_color_maps/__init__.py +0 -0
- bsplot/scientific_color_maps/_sync_from_zenodo_download.sh +1 -0
- bsplot/scientific_color_maps/batlowK.txt +256 -0
- bsplot/scientific_color_maps/batlowW.txt +256 -0
- bsplot/scientific_color_maps/cyclic/bamO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/brocO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/corkO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/romaO.txt +256 -0
- bsplot/scientific_color_maps/cyclic/vikO.txt +256 -0
- bsplot/scientific_color_maps/diverging/bam.txt +256 -0
- bsplot/scientific_color_maps/diverging/berlin.txt +256 -0
- bsplot/scientific_color_maps/diverging/broc.txt +256 -0
- bsplot/scientific_color_maps/diverging/cork.txt +256 -0
- bsplot/scientific_color_maps/diverging/lisbon.txt +256 -0
- bsplot/scientific_color_maps/diverging/managua.txt +256 -0
- bsplot/scientific_color_maps/diverging/roma.txt +256 -0
- bsplot/scientific_color_maps/diverging/tofino.txt +256 -0
- bsplot/scientific_color_maps/diverging/vanimo.txt +256 -0
- bsplot/scientific_color_maps/diverging/vik.txt +256 -0
- bsplot/scientific_color_maps/multisequential/bukavu.txt +256 -0
- bsplot/scientific_color_maps/multisequential/fes.txt +256 -0
- bsplot/scientific_color_maps/multisequential/oleron.txt +256 -0
- bsplot/scientific_color_maps/naviaW.txt +256 -0
- bsplot/scientific_color_maps/sequential/acton.txt +256 -0
- bsplot/scientific_color_maps/sequential/bamako.txt +256 -0
- bsplot/scientific_color_maps/sequential/batlow.txt +256 -0
- bsplot/scientific_color_maps/sequential/bilbao.txt +256 -0
- bsplot/scientific_color_maps/sequential/buda.txt +256 -0
- bsplot/scientific_color_maps/sequential/davos.txt +256 -0
- bsplot/scientific_color_maps/sequential/devon.txt +256 -0
- bsplot/scientific_color_maps/sequential/glasgow.txt +256 -0
- bsplot/scientific_color_maps/sequential/grayC.txt +256 -0
- bsplot/scientific_color_maps/sequential/hawaii.txt +256 -0
- bsplot/scientific_color_maps/sequential/imola.txt +256 -0
- bsplot/scientific_color_maps/sequential/lajolla.txt +256 -0
- bsplot/scientific_color_maps/sequential/lapaz.txt +256 -0
- bsplot/scientific_color_maps/sequential/lipari.txt +256 -0
- bsplot/scientific_color_maps/sequential/navia.txt +256 -0
- bsplot/scientific_color_maps/sequential/nuuk.txt +256 -0
- bsplot/scientific_color_maps/sequential/oslo.txt +256 -0
- bsplot/scientific_color_maps/sequential/tokyo.txt +256 -0
- bsplot/scientific_color_maps/sequential/turku.txt +256 -0
- bsplot/streamlines.py +314 -0
- bsplot/style.py +791 -0
- bsplot/styles/black.mplstyle +78 -0
- bsplot/styles/bss.mplstyle +50 -0
- bsplot/styles/bwcomp.mplstyle +30 -0
- bsplot/styles/nature.mplstyle +57 -0
- bsplot/styles/transparent.mplstyle +16 -0
- bsplot/styles/tvbo.mplstyle +57 -0
- bsplot/surface.py +2370 -0
- bsplot/templates.py +270 -0
- bsplot/text.py +199 -0
- bsplot/text2obj.py +59 -0
- bsplot/timeseries.py +10 -0
- bsplot/utils.py +13 -0
- bsplot/volume.py +958 -0
- bsplot-0.0.2.dist-info/METADATA +60 -0
- bsplot-0.0.2.dist-info/RECORD +89 -0
- bsplot-0.0.2.dist-info/WHEEL +5 -0
- bsplot-0.0.2.dist-info/entry_points.txt +2 -0
- bsplot-0.0.2.dist-info/licenses/LICENSE +193 -0
- bsplot-0.0.2.dist-info/top_level.txt +1 -0
bsplot/graph/layout.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for layout adjustments.
|
|
3
|
+
|
|
4
|
+
This module provides helpers to spread nodes that lie on the same level using
|
|
5
|
+
only an existing `pos` dict.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from typing import Dict, Tuple, Optional, Callable
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
Position = Tuple[float, float]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def scatter_nodes(
|
|
15
|
+
pos: Dict[object, Position],
|
|
16
|
+
orientation: str = "horizontal",
|
|
17
|
+
spacing: Optional[float] = None,
|
|
18
|
+
jitter: float = 0.0,
|
|
19
|
+
tol: Optional[float] = None,
|
|
20
|
+
preserve_center: bool = True,
|
|
21
|
+
level_extractor: Optional[Callable[[Position], float]] = None,
|
|
22
|
+
seed: Optional[int] = None,
|
|
23
|
+
*,
|
|
24
|
+
spacing_unit: str = "data",
|
|
25
|
+
spacing_multiplier: Optional[float] = None,
|
|
26
|
+
# Axis-specific controls (override generic when provided)
|
|
27
|
+
spacing_x: Optional[float] = None,
|
|
28
|
+
spacing_y: Optional[float] = None,
|
|
29
|
+
spacing_unit_x: Optional[str] = None,
|
|
30
|
+
spacing_unit_y: Optional[str] = None,
|
|
31
|
+
spacing_multiplier_x: Optional[float] = None,
|
|
32
|
+
spacing_multiplier_y: Optional[float] = None,
|
|
33
|
+
) -> Dict[object, Position]:
|
|
34
|
+
"""
|
|
35
|
+
Return a new pos where nodes sharing the same level are evenly spread along
|
|
36
|
+
the specified orientation.
|
|
37
|
+
|
|
38
|
+
Parameters:
|
|
39
|
+
- pos: mapping of node -> (x, y)
|
|
40
|
+
- orientation: 'horizontal' to spread along x for nodes with same y;
|
|
41
|
+
'vertical' to spread along y for nodes with same x;
|
|
42
|
+
'both' to apply both passes (horizontal then vertical) using
|
|
43
|
+
the original positions for level grouping.
|
|
44
|
+
- spacing: distance between adjacent nodes along the scattering axis.
|
|
45
|
+
If spacing_unit='data', interpreted in data units.
|
|
46
|
+
If spacing_unit='fraction', treated as a fraction of the global
|
|
47
|
+
span along the scattering axis (0..1). If None, a robust value is
|
|
48
|
+
inferred; optionally multiplied by spacing_multiplier.
|
|
49
|
+
- jitter: optional random jitter added along the scattering axis
|
|
50
|
+
(uniform in [-jitter/2, +jitter/2]).
|
|
51
|
+
- tol: tolerance for grouping levels; if None, exact equality defines a level
|
|
52
|
+
on the grouping axis.
|
|
53
|
+
- preserve_center: if True, keep the mean coordinate (on the scattering axis)
|
|
54
|
+
of each level unchanged; else start at 0.
|
|
55
|
+
- level_extractor: override level extraction for single-axis modes only.
|
|
56
|
+
Ignored for orientation='both'.
|
|
57
|
+
- seed: RNG seed for deterministic jitter.
|
|
58
|
+
- spacing_unit: 'data' (default) or 'fraction' of global axis span.
|
|
59
|
+
- spacing_multiplier: optional multiplicative factor applied to inferred
|
|
60
|
+
spacing when spacing is None.
|
|
61
|
+
- spacing_x / spacing_y: axis-specific spacing overrides for x- or y-pass.
|
|
62
|
+
- spacing_unit_x / spacing_unit_y: axis-specific units ('data'|'fraction').
|
|
63
|
+
- spacing_multiplier_x / spacing_multiplier_y: axis-specific multipliers.
|
|
64
|
+
|
|
65
|
+
Notes:
|
|
66
|
+
- Only the scattering axis coordinate is modified; the other axis is preserved.
|
|
67
|
+
- Node order within a level is based on their current coordinate along the
|
|
68
|
+
scattering axis (ascending).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if not pos:
|
|
72
|
+
return {}
|
|
73
|
+
|
|
74
|
+
# Work on copies
|
|
75
|
+
base_pos = dict(pos)
|
|
76
|
+
new_pos: Dict[object, Position] = dict(pos)
|
|
77
|
+
|
|
78
|
+
orientation = orientation.lower()
|
|
79
|
+
if orientation not in {"horizontal", "vertical", "both"}:
|
|
80
|
+
raise ValueError("orientation must be 'horizontal', 'vertical', or 'both'")
|
|
81
|
+
|
|
82
|
+
def _norm_unit(u: Optional[str]) -> str:
|
|
83
|
+
return (u or spacing_unit).lower()
|
|
84
|
+
|
|
85
|
+
unit_x = _norm_unit(spacing_unit_x)
|
|
86
|
+
unit_y = _norm_unit(spacing_unit_y)
|
|
87
|
+
|
|
88
|
+
if unit_x not in {"data", "fraction"} or unit_y not in {"data", "fraction"}:
|
|
89
|
+
raise ValueError("spacing units must be 'data' or 'fraction'")
|
|
90
|
+
|
|
91
|
+
rng = np.random.default_rng(seed) if jitter and jitter > 0 else None
|
|
92
|
+
|
|
93
|
+
# Global spans used for fraction-based spacing
|
|
94
|
+
xs = np.array([xy[0] for xy in base_pos.values()], dtype=float)
|
|
95
|
+
ys = np.array([xy[1] for xy in base_pos.values()], dtype=float)
|
|
96
|
+
span_x = float(xs.max() - xs.min()) if xs.size else 1.0
|
|
97
|
+
span_y = float(ys.max() - ys.min()) if ys.size else 1.0
|
|
98
|
+
if span_x == 0:
|
|
99
|
+
span_x = 1.0
|
|
100
|
+
if span_y == 0:
|
|
101
|
+
span_y = 1.0
|
|
102
|
+
|
|
103
|
+
def group_levels(values: Dict[object, Position], extractor: Callable[[Position], float]):
|
|
104
|
+
# Deterministic ordering by (level, x, y)
|
|
105
|
+
sorted_items = sorted(values.items(), key=lambda kv: (extractor(kv[1]), kv[1][0], kv[1][1]))
|
|
106
|
+
groups = [] # list of (rep_level_value, [nodes])
|
|
107
|
+
for node, xy in sorted_items:
|
|
108
|
+
lvl = extractor(xy)
|
|
109
|
+
placed = False
|
|
110
|
+
if tol is not None:
|
|
111
|
+
for rep, nodes in groups:
|
|
112
|
+
if abs(lvl - rep) <= tol:
|
|
113
|
+
nodes.append(node)
|
|
114
|
+
placed = True
|
|
115
|
+
break
|
|
116
|
+
else:
|
|
117
|
+
for rep, nodes in groups:
|
|
118
|
+
if lvl == rep:
|
|
119
|
+
nodes.append(node)
|
|
120
|
+
placed = True
|
|
121
|
+
break
|
|
122
|
+
if not placed:
|
|
123
|
+
groups.append((lvl, [node]))
|
|
124
|
+
return groups
|
|
125
|
+
|
|
126
|
+
def infer_spacing(coords: np.ndarray, axis_idx: int) -> float:
|
|
127
|
+
# Choose axis-specific overrides
|
|
128
|
+
if axis_idx == 0:
|
|
129
|
+
s_val = spacing_x if spacing_x is not None else spacing
|
|
130
|
+
s_unit = unit_x
|
|
131
|
+
s_mult = spacing_multiplier_x if spacing_multiplier_x is not None else spacing_multiplier
|
|
132
|
+
base_span = span_x
|
|
133
|
+
else:
|
|
134
|
+
s_val = spacing_y if spacing_y is not None else spacing
|
|
135
|
+
s_unit = unit_y
|
|
136
|
+
s_mult = spacing_multiplier_y if spacing_multiplier_y is not None else spacing_multiplier
|
|
137
|
+
base_span = span_y
|
|
138
|
+
|
|
139
|
+
# Explicit spacing
|
|
140
|
+
if s_val is not None:
|
|
141
|
+
if s_unit == "data":
|
|
142
|
+
base = float(s_val)
|
|
143
|
+
else: # fraction
|
|
144
|
+
base = float(s_val) * base_span
|
|
145
|
+
else:
|
|
146
|
+
# Inferred spacing from coords
|
|
147
|
+
n = coords.size
|
|
148
|
+
if n >= 2:
|
|
149
|
+
diffs = np.diff(np.sort(coords))
|
|
150
|
+
diffs = diffs[diffs > 0]
|
|
151
|
+
base = float(np.median(diffs)) if diffs.size > 0 else 1.0
|
|
152
|
+
else:
|
|
153
|
+
base = 1.0
|
|
154
|
+
if s_mult is not None:
|
|
155
|
+
base *= float(s_mult)
|
|
156
|
+
return base
|
|
157
|
+
|
|
158
|
+
def apply_pass(groups, axis_idx: int):
|
|
159
|
+
for _, nodes_on_level in groups:
|
|
160
|
+
coords = np.array([new_pos[n][axis_idx] for n in nodes_on_level], dtype=float)
|
|
161
|
+
n = len(nodes_on_level)
|
|
162
|
+
level_spacing = infer_spacing(coords, axis_idx)
|
|
163
|
+
if preserve_center:
|
|
164
|
+
center = float(coords.mean()) if n > 0 else 0.0
|
|
165
|
+
start = center - level_spacing * (n - 1) / 2.0
|
|
166
|
+
else:
|
|
167
|
+
start = 0.0
|
|
168
|
+
order = np.argsort(coords)
|
|
169
|
+
ordered_nodes = [nodes_on_level[i] for i in order]
|
|
170
|
+
for i, node in enumerate(ordered_nodes):
|
|
171
|
+
x_old, y_old = new_pos[node]
|
|
172
|
+
coord_new = start + i * level_spacing
|
|
173
|
+
if rng is not None:
|
|
174
|
+
coord_new += float(rng.uniform(-jitter / 2.0, jitter / 2.0))
|
|
175
|
+
if axis_idx == 0:
|
|
176
|
+
new_pos[node] = (coord_new, y_old)
|
|
177
|
+
else:
|
|
178
|
+
new_pos[node] = (x_old, coord_new)
|
|
179
|
+
|
|
180
|
+
if orientation == "horizontal":
|
|
181
|
+
extractor = (lambda xy: xy[1]) if level_extractor is None else level_extractor
|
|
182
|
+
groups_y = group_levels(new_pos, extractor)
|
|
183
|
+
apply_pass(groups_y, axis_idx=0)
|
|
184
|
+
return new_pos
|
|
185
|
+
|
|
186
|
+
if orientation == "vertical":
|
|
187
|
+
extractor = (lambda xy: xy[0]) if level_extractor is None else level_extractor
|
|
188
|
+
groups_x = group_levels(new_pos, extractor)
|
|
189
|
+
apply_pass(groups_x, axis_idx=1)
|
|
190
|
+
return new_pos
|
|
191
|
+
|
|
192
|
+
# orientation == 'both': use original base_pos for grouping to avoid interference
|
|
193
|
+
groups_y = group_levels(base_pos, lambda xy: xy[1]) # same y-levels
|
|
194
|
+
apply_pass(groups_y, axis_idx=0)
|
|
195
|
+
groups_x = group_levels(base_pos, lambda xy: xy[0]) # same x-levels
|
|
196
|
+
apply_pass(groups_x, axis_idx=1)
|
|
197
|
+
return new_pos
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Backward-compatible alias (deprecated)
|
|
201
|
+
scatter_nodes_on_same_level_horizontally = scatter_nodes
|