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.
Files changed (89) hide show
  1. bsplot/__init__.py +23 -0
  2. bsplot/anat.py +55 -0
  3. bsplot/animate.py +194 -0
  4. bsplot/bioicons.py +777 -0
  5. bsplot/brain.py +188 -0
  6. bsplot/colors.py +1935 -0
  7. bsplot/data/HCP_avg-SC.txt +379 -0
  8. bsplot/data/MNI152.rh.pial +0 -0
  9. bsplot/data/STN_lh.nii.gz +0 -0
  10. bsplot/data/STN_rh.nii.gz +0 -0
  11. bsplot/data/__init__.py +22 -0
  12. bsplot/data/left_electrode.ply +257270 -0
  13. bsplot/data/parcellations/HCP-MMP1.L.label.gii +442 -0
  14. bsplot/data/parcellations/HCP-MMP1.R.label.gii +442 -0
  15. bsplot/data/parcellations/code/split_hcpmmp1.sh +4 -0
  16. bsplot/data/right_electrode.ply +257270 -0
  17. bsplot/data/surface.py +112 -0
  18. bsplot/data/tpl-MNI152NLin2009b_atlas-hcpmmp1_desc-ordered_dseg.nii.gz +0 -0
  19. bsplot/figure.py +177 -0
  20. bsplot/graph/__init__.py +31 -0
  21. bsplot/graph/edges.py +312 -0
  22. bsplot/graph/flowchart.py +762 -0
  23. bsplot/graph/layout.py +201 -0
  24. bsplot/graph/network.py +974 -0
  25. bsplot/graph/nodes.py +452 -0
  26. bsplot/panels.py +110 -0
  27. bsplot/scientific_color_maps/__init__.py +0 -0
  28. bsplot/scientific_color_maps/_sync_from_zenodo_download.sh +1 -0
  29. bsplot/scientific_color_maps/batlowK.txt +256 -0
  30. bsplot/scientific_color_maps/batlowW.txt +256 -0
  31. bsplot/scientific_color_maps/cyclic/bamO.txt +256 -0
  32. bsplot/scientific_color_maps/cyclic/brocO.txt +256 -0
  33. bsplot/scientific_color_maps/cyclic/corkO.txt +256 -0
  34. bsplot/scientific_color_maps/cyclic/romaO.txt +256 -0
  35. bsplot/scientific_color_maps/cyclic/vikO.txt +256 -0
  36. bsplot/scientific_color_maps/diverging/bam.txt +256 -0
  37. bsplot/scientific_color_maps/diverging/berlin.txt +256 -0
  38. bsplot/scientific_color_maps/diverging/broc.txt +256 -0
  39. bsplot/scientific_color_maps/diverging/cork.txt +256 -0
  40. bsplot/scientific_color_maps/diverging/lisbon.txt +256 -0
  41. bsplot/scientific_color_maps/diverging/managua.txt +256 -0
  42. bsplot/scientific_color_maps/diverging/roma.txt +256 -0
  43. bsplot/scientific_color_maps/diverging/tofino.txt +256 -0
  44. bsplot/scientific_color_maps/diverging/vanimo.txt +256 -0
  45. bsplot/scientific_color_maps/diverging/vik.txt +256 -0
  46. bsplot/scientific_color_maps/multisequential/bukavu.txt +256 -0
  47. bsplot/scientific_color_maps/multisequential/fes.txt +256 -0
  48. bsplot/scientific_color_maps/multisequential/oleron.txt +256 -0
  49. bsplot/scientific_color_maps/naviaW.txt +256 -0
  50. bsplot/scientific_color_maps/sequential/acton.txt +256 -0
  51. bsplot/scientific_color_maps/sequential/bamako.txt +256 -0
  52. bsplot/scientific_color_maps/sequential/batlow.txt +256 -0
  53. bsplot/scientific_color_maps/sequential/bilbao.txt +256 -0
  54. bsplot/scientific_color_maps/sequential/buda.txt +256 -0
  55. bsplot/scientific_color_maps/sequential/davos.txt +256 -0
  56. bsplot/scientific_color_maps/sequential/devon.txt +256 -0
  57. bsplot/scientific_color_maps/sequential/glasgow.txt +256 -0
  58. bsplot/scientific_color_maps/sequential/grayC.txt +256 -0
  59. bsplot/scientific_color_maps/sequential/hawaii.txt +256 -0
  60. bsplot/scientific_color_maps/sequential/imola.txt +256 -0
  61. bsplot/scientific_color_maps/sequential/lajolla.txt +256 -0
  62. bsplot/scientific_color_maps/sequential/lapaz.txt +256 -0
  63. bsplot/scientific_color_maps/sequential/lipari.txt +256 -0
  64. bsplot/scientific_color_maps/sequential/navia.txt +256 -0
  65. bsplot/scientific_color_maps/sequential/nuuk.txt +256 -0
  66. bsplot/scientific_color_maps/sequential/oslo.txt +256 -0
  67. bsplot/scientific_color_maps/sequential/tokyo.txt +256 -0
  68. bsplot/scientific_color_maps/sequential/turku.txt +256 -0
  69. bsplot/streamlines.py +314 -0
  70. bsplot/style.py +791 -0
  71. bsplot/styles/black.mplstyle +78 -0
  72. bsplot/styles/bss.mplstyle +50 -0
  73. bsplot/styles/bwcomp.mplstyle +30 -0
  74. bsplot/styles/nature.mplstyle +57 -0
  75. bsplot/styles/transparent.mplstyle +16 -0
  76. bsplot/styles/tvbo.mplstyle +57 -0
  77. bsplot/surface.py +2370 -0
  78. bsplot/templates.py +270 -0
  79. bsplot/text.py +199 -0
  80. bsplot/text2obj.py +59 -0
  81. bsplot/timeseries.py +10 -0
  82. bsplot/utils.py +13 -0
  83. bsplot/volume.py +958 -0
  84. bsplot-0.0.2.dist-info/METADATA +60 -0
  85. bsplot-0.0.2.dist-info/RECORD +89 -0
  86. bsplot-0.0.2.dist-info/WHEEL +5 -0
  87. bsplot-0.0.2.dist-info/entry_points.txt +2 -0
  88. bsplot-0.0.2.dist-info/licenses/LICENSE +193 -0
  89. 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