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/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ from os.path import abspath, dirname, join
2
+
3
+ __version__ = "0.0.2"
4
+
5
+ ROOT = abspath(dirname(__file__))
6
+ datadir = join(ROOT, "data")
7
+
8
+ from . import volume, anat, animate, brain, colors, data, figure, streamlines, style, timeseries, surface, templates, utils
9
+ from .surface import vol_to_mesh, LabeledMeshes, project_vol_to_surf, plot_surf
10
+ from .brain import glass_brain
11
+ from .volume import plot_slice, apply_brain_mask
12
+ from .colors import explore_colormaps
13
+ from .bioicons import (
14
+ BioIcons,
15
+ plot_icon,
16
+ plot_icons_grid,
17
+ IconAnnotation,
18
+ search_icons,
19
+ list_categories,
20
+ list_licenses,
21
+ )
22
+
23
+ style.use("tvbo")
bsplot/anat.py ADDED
@@ -0,0 +1,55 @@
1
+ """Backwards compatibility module. Use bsplot.volume instead."""
2
+
3
+ import warnings
4
+ import inspect
5
+
6
+ # Only warn if imported directly by user code, not by bsplot's __init__.py
7
+ _stack = inspect.stack()
8
+ if len(_stack) >= 2:
9
+ _caller_filename = _stack[1].filename
10
+ if "bsplot/__init__.py" not in _caller_filename and "bsplot\\__init__.py" not in _caller_filename:
11
+ warnings.warn(
12
+ "bsplot.anat is deprecated, use bsplot.volume instead. "
13
+ "Also note that plot_img has been renamed to plot_slice.",
14
+ DeprecationWarning,
15
+ stacklevel=2,
16
+ )
17
+ del _stack, inspect
18
+
19
+ from bsplot.volume import (
20
+ get_center_of_mass,
21
+ get_cut_coords,
22
+ get_com_slice_index,
23
+ get_slice_index,
24
+ get_world_coords,
25
+ get_view_index,
26
+ get_plane_indices,
27
+ plot_slice,
28
+ plot_img,
29
+ translate_com,
30
+ get_com_view,
31
+ plot_img_on_bg,
32
+ plot_brain_schematics,
33
+ get_world_extent,
34
+ plot_image_extent,
35
+ apply_brain_mask,
36
+ )
37
+
38
+ __all__ = [
39
+ "get_center_of_mass",
40
+ "get_cut_coords",
41
+ "get_com_slice_index",
42
+ "get_slice_index",
43
+ "get_world_coords",
44
+ "get_view_index",
45
+ "get_plane_indices",
46
+ "plot_slice",
47
+ "plot_img",
48
+ "translate_com",
49
+ "get_com_view",
50
+ "plot_img_on_bg",
51
+ "plot_brain_schematics",
52
+ "get_world_extent",
53
+ "plot_image_extent",
54
+ "apply_brain_mask",
55
+ ]
bsplot/animate.py ADDED
@@ -0,0 +1,194 @@
1
+ """Composable animation utilities for 4D neuroimaging data (matplotlib-only).
2
+
3
+ The core abstraction is :class:`AnimationComposer`, which manages a shared
4
+ timeline and lets you plug in per-axis animation callbacks. All higher-level
5
+ patterns (single view, multi-view, multi-image comparison) can be expressed
6
+ via this composer and a small generic helper.
7
+ """
8
+
9
+ import matplotlib.pyplot as plt
10
+ import nibabel as nib
11
+ import numpy as np
12
+ from matplotlib.animation import FuncAnimation
13
+
14
+
15
+ class AnimationComposer:
16
+ """Composable multi-axes animation helper (matplotlib-only).
17
+
18
+ This class lets you register per-axis update functions that are called for
19
+ every frame with the same frame index, so you can build complex, modular
20
+ animations across several axes.
21
+
22
+ Each registered callback should have the signature::
23
+
24
+ func(frame_index: int, ax: matplotlib.axes.Axes) -> list[Artist] | Artist | None
25
+
26
+ and is expected to draw on the provided axis.
27
+ """
28
+
29
+ def __init__(self, fig, axes, n_frames, interval=100, blit=False):
30
+ """Create a new AnimationComposer.
31
+
32
+ Parameters
33
+ ----------
34
+ fig : matplotlib.figure.Figure
35
+ Parent figure for the animation.
36
+ axes : Axes or sequence of Axes
37
+ One or more axes to animate.
38
+ n_frames : int
39
+ Number of frames (time points).
40
+ interval : int, default 100
41
+ Delay between frames in milliseconds.
42
+ blit : bool, default False
43
+ Whether to use blitting.
44
+ """
45
+
46
+ self.fig = fig
47
+ if hasattr(axes, "__iter__") and not isinstance(axes, (list, tuple)):
48
+ self.axes = list(axes)
49
+ elif hasattr(axes, "__iter__"):
50
+ self.axes = list(axes)
51
+ else:
52
+ self.axes = [axes]
53
+
54
+ self.n_frames = int(n_frames)
55
+ self.interval = interval
56
+ self.blit = blit
57
+ self._callbacks = [] # list of (ax, func)
58
+
59
+ def add_axis_animation(self, ax, func):
60
+ """Register an animation callback for a given axis.
61
+
62
+ Parameters
63
+ ----------
64
+ ax : matplotlib.axes.Axes
65
+ Axis that ``func`` will draw on.
66
+ func : callable
67
+ Function with signature ``func(frame_index: int, ax)`` that returns
68
+ a Matplotlib Artist or a list of Artists (optional).
69
+ """
70
+
71
+ if ax not in self.axes:
72
+ self.axes.append(ax)
73
+ self._callbacks.append((ax, func))
74
+
75
+ def _update(self, frame):
76
+ artists = []
77
+ for ax, func in self._callbacks:
78
+ res = func(frame, ax)
79
+ if res is None:
80
+ continue
81
+ if isinstance(res, (list, tuple)):
82
+ artists.extend(res)
83
+ else:
84
+ artists.append(res)
85
+ return artists
86
+
87
+ def to_animation(self):
88
+ """Create and return the underlying FuncAnimation object."""
89
+
90
+ anim = FuncAnimation(
91
+ self.fig,
92
+ self._update,
93
+ frames=self.n_frames,
94
+ interval=self.interval,
95
+ blit=self.blit,
96
+ )
97
+ return anim
98
+
99
+
100
+ def _ensure_4d(img_4d):
101
+ """Load and validate a 4D NIfTI-like image."""
102
+ if isinstance(img_4d, str):
103
+ img_4d = nib.load(img_4d)
104
+ if len(img_4d.shape) != 4:
105
+ raise ValueError(f"Expected 4D image, got shape {img_4d.shape}")
106
+ return img_4d
107
+
108
+
109
+ def _global_scale_kwargs(img_4d, threshold=0, **plot_kwargs):
110
+ """Compute global vmin/vmax across time, unless user provided scaling.
111
+
112
+ Returns a dict with optional ``vmin``/``vmax`` to forward into ``plot_img``.
113
+ """
114
+ if "vmin" in plot_kwargs or "norm" in plot_kwargs:
115
+ return {}
116
+
117
+ data_4d = img_4d.get_fdata()
118
+ if threshold > 0:
119
+ masked = np.where(np.abs(data_4d) <= threshold, np.nan, data_4d)
120
+ vmin = np.nanmin(masked)
121
+ vmax = np.nanmax(masked)
122
+ else:
123
+ nonzero = data_4d[data_4d != 0]
124
+ vmin = np.nanmin(nonzero) if nonzero.size else np.nanmin(data_4d)
125
+ vmax = np.nanmax(data_4d)
126
+
127
+ contains_negative = np.any(data_4d < 0)
128
+ if contains_negative:
129
+ vmax_abs = max(abs(vmin), abs(vmax))
130
+ vmin, vmax = -vmax_abs, vmax_abs
131
+
132
+ return {"vmin": vmin, "vmax": vmax}
133
+
134
+
135
+ def animate_axes(fig, axes, n_frames, update_fn, context=None, interval=100, blit=False):
136
+ """Convenience wrapper to build an animation over one or many axes.
137
+
138
+ This captures the common pattern used in the docs: one timeline, multiple
139
+ axes, and a single user-defined ``update_fn`` that can branch on the axis
140
+ index and/or shared ``context``.
141
+
142
+ Parameters
143
+ ----------
144
+ fig : matplotlib.figure.Figure
145
+ Parent figure for the animation.
146
+ axes : Axes or sequence of Axes
147
+ Axes to animate. Can be a single axis, a list/tuple, or any iterable
148
+ of axes as returned by ``plt.subplots``.
149
+ n_frames : int
150
+ Number of frames in the animation.
151
+ update_fn : callable
152
+ Function with signature::
153
+
154
+ update_fn(frame: int, ax, axis_idx: int, context: dict | None)
155
+
156
+ It should draw the desired state for the given ``frame`` on ``ax`` and
157
+ return a Matplotlib Artist or list of Artists (or ``None``).
158
+ context : dict or None, optional
159
+ Arbitrary, user-defined state shared across axes and frames.
160
+ interval : int, default 100
161
+ Delay between frames in milliseconds.
162
+ blit : bool, default False
163
+ Whether to use blitting.
164
+
165
+ Returns
166
+ -------
167
+ matplotlib.animation.FuncAnimation
168
+ The constructed animation object.
169
+ """
170
+
171
+ # Normalize axes into a flat list
172
+ if hasattr(axes, "__iter__") and not isinstance(axes, (list, tuple)):
173
+ axes_list = list(axes)
174
+ elif hasattr(axes, "__iter__"):
175
+ axes_list = list(axes)
176
+ else:
177
+ axes_list = [axes]
178
+
179
+ composer = AnimationComposer(fig, axes_list, n_frames=n_frames, interval=interval, blit=blit)
180
+
181
+ for axis_idx, ax in enumerate(axes_list):
182
+
183
+ def make_cb(idx_):
184
+ def _cb(frame, ax_cb):
185
+ return update_fn(frame, ax_cb, idx_, context)
186
+
187
+ return _cb
188
+
189
+ composer.add_axis_animation(ax, make_cb(axis_idx))
190
+
191
+ return composer.to_animation()
192
+
193
+
194
+