PySourceviz 0.1.0__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.
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PySourceviz
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Publication-ready static cortical source-estimate figures
|
|
5
|
+
Author-email: LiaoPan <LiaoPan_2015@163.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/LiaoPan/PySourceviz
|
|
8
|
+
Project-URL: Repository, https://github.com/LiaoPan/PySourceviz
|
|
9
|
+
Project-URL: Issues, https://github.com/LiaoPan/PySourceviz/issues
|
|
10
|
+
Keywords: MNE-Python,MEG,EEG,source localization,cortical surface,BrainSpace,neuroimaging,visualization
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE.txt
|
|
23
|
+
Requires-Dist: mne>=1.7
|
|
24
|
+
Requires-Dist: brainspace>=0.2
|
|
25
|
+
Requires-Dist: numpy>=1.23
|
|
26
|
+
Requires-Dist: nibabel>=5.0
|
|
27
|
+
Provides-Extra: nilearn
|
|
28
|
+
Requires-Dist: nilearn>=0.12; extra == "nilearn"
|
|
29
|
+
Provides-Extra: comparison
|
|
30
|
+
Requires-Dist: nilearn>=0.12; extra == "comparison"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
33
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
34
|
+
Requires-Dist: tomli>=2; python_version < "3.11" and extra == "dev"
|
|
35
|
+
Requires-Dist: pillow>=8; extra == "dev"
|
|
36
|
+
Requires-Dist: pyyaml>=6; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# PySourceviz
|
|
41
|
+
|
|
42
|
+
**English** | [简体中文](https://github.com/LiaoPan/PySourceviz/blob/main/README.zh-CN.md)
|
|
43
|
+
|
|
44
|
+
[](https://github.com/LiaoPan/PySourceviz/actions/workflows/tests.yml)
|
|
45
|
+
[](https://github.com/LiaoPan/PySourceviz/actions/workflows/quality-gates.yml)
|
|
46
|
+
|
|
47
|
+
PySourceviz turns MNE-Python cortical source estimates into consistent,
|
|
48
|
+
publication-friendly surface figures with a selectable BrainSpace or Nilearn
|
|
49
|
+
renderer. It keeps source-estimate semantics in MNE, uses FreeSurfer anatomy
|
|
50
|
+
for the mesh and medial wall, and renders four comparable views with one
|
|
51
|
+
whole-brain activation colorbar.
|
|
52
|
+
|
|
53
|
+
The v0.1 visual defaults are an inflated cortical surface, white background,
|
|
54
|
+
soft white-to-light-gray sulcal shading, a transparent low-value overlay, and
|
|
55
|
+
this anatomical order:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
LH lateral | LH medial | RH medial | RH lateral
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For source-localization overview figures, PySourceviz recommends the inflated
|
|
62
|
+
surface used by default in
|
|
63
|
+
[`mne.SourceEstimate.plot`](https://mne.tools/stable/generated/mne.SourceEstimate.html#mne.SourceEstimate.plot).
|
|
64
|
+
Inflation exposes activation that would be hidden inside sulci and avoids
|
|
65
|
+
visually equating detailed fold boundaries with the spatial resolution of the
|
|
66
|
+
inverse solution. It changes only display coordinates: source vertices,
|
|
67
|
+
activation values, timing, interpolation, thresholds, and color limits remain
|
|
68
|
+
unchanged. `pial` and `white` remain available when their anatomy is the
|
|
69
|
+
intended subject of the figure.
|
|
70
|
+
|
|
71
|
+
## Result gallery
|
|
72
|
+
|
|
73
|
+
**PySourceviz · MNE audvis dSPM · inflated surface · 110 ms actual sample**
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
The requested time above is 112 ms. The STC is sampled every 10 ms, so
|
|
78
|
+
PySourceviz selects 110 ms and records that actual sample in the returned
|
|
79
|
+
metadata.
|
|
80
|
+
|
|
81
|
+
**PySourceviz · BrainSpace backend · inflated surface · p98-p99.9 color controls**
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
**PySourceviz · Nilearn backend · inflated surface · the same activation and limits**
|
|
86
|
+
|
|
87
|
+

|
|
88
|
+
|
|
89
|
+
Both images above are produced by built-in PySourceviz backends. They use the
|
|
90
|
+
same STC and time sample. They also use the same inflated geometry, expanded
|
|
91
|
+
vertex values, medial-wall mask, FreeSurfer `sulc` background, colormap, and
|
|
92
|
+
color limits; only the final renderer changes. The white-gray texture is
|
|
93
|
+
therefore retained on the inflated coordinates rather than being tied to pial
|
|
94
|
+
geometry.
|
|
95
|
+
|
|
96
|
+
**PySourceviz · BrainSpace · pial surface · optional anatomical view**
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
This pial view is included as an optional anatomical presentation.
|
|
101
|
+
It is not the recommended default for source-localization overviews. Use it
|
|
102
|
+
when the relationship to native gyral and sulcal folding is itself important.
|
|
103
|
+
The source estimate, selected 110 ms sample, interpolation, threshold, and
|
|
104
|
+
p98-p99.9 color controls are unchanged; only the display geometry differs.
|
|
105
|
+
|
|
106
|
+
## Quickstart
|
|
107
|
+
|
|
108
|
+
Install PySourceviz with its default BrainSpace backend from PyPI after the
|
|
109
|
+
release is published:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python -m pip install PySourceviz
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Install the optional Nilearn backend when you want Matplotlib-based surface
|
|
116
|
+
figures as well:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python -m pip install "PySourceviz[nilearn]"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Load an MNE surface STC, point `subjects_dir` to the directory that contains
|
|
123
|
+
the matching FreeSurfer subject, and save a PNG:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pathlib import Path
|
|
127
|
+
|
|
128
|
+
import mne
|
|
129
|
+
|
|
130
|
+
from pysourceviz import plot_source
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
stc = mne.read_source_estimate("/data/inverse/audvis")
|
|
134
|
+
subjects_dir = Path("/data/freesurfer/subjects")
|
|
135
|
+
|
|
136
|
+
info = plot_source(
|
|
137
|
+
stc,
|
|
138
|
+
subjects_dir=subjects_dir,
|
|
139
|
+
backend="brainspace",
|
|
140
|
+
template="native",
|
|
141
|
+
time=0.112,
|
|
142
|
+
units="dSPM",
|
|
143
|
+
title="Auditory response · 112 ms request",
|
|
144
|
+
output="figures/audvis_112ms.png",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
print(info["selected_time"]) # actual nearest STC sample, for example 0.11
|
|
148
|
+
print(info["color_range"]) # shared whole-brain display range
|
|
149
|
+
print(info["output"]) # resolved PNG path
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Render the same prepared source map with Nilearn by changing one argument:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
nilearn_info = plot_source(
|
|
156
|
+
stc,
|
|
157
|
+
subjects_dir=subjects_dir,
|
|
158
|
+
backend="nilearn",
|
|
159
|
+
template="native",
|
|
160
|
+
time=0.112,
|
|
161
|
+
clim={"kind": "percent", "lims": [98, 99, 99.9]},
|
|
162
|
+
units="dSPM",
|
|
163
|
+
title="Auditory response · Nilearn",
|
|
164
|
+
output="figures/audvis_112ms_nilearn.png",
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Time selection, interpolation, threshold, `clim`, colormap, layout, output
|
|
169
|
+
size, and returned scientific metadata have the same meaning for both
|
|
170
|
+
backends. Nilearn is imported only when `backend="nilearn"` is selected.
|
|
171
|
+
|
|
172
|
+
For `stc.subject == "fsaverage"`, the example expects files such as
|
|
173
|
+
`/data/freesurfer/subjects/fsaverage/surf/lh.inflated`. `subjects_dir` is one
|
|
174
|
+
level above the subject; it is not the `fsaverage` directory itself.
|
|
175
|
+
|
|
176
|
+
## Complete usage reference
|
|
177
|
+
|
|
178
|
+
The recipes below enumerate every supported source family and public control.
|
|
179
|
+
All time values are seconds. PySourceviz never modifies the input STC.
|
|
180
|
+
|
|
181
|
+
### Supported source-estimate families
|
|
182
|
+
|
|
183
|
+
| MNE object | PySourceviz v0.1 behavior |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `mne.SourceEstimate` | Real scalar cortical data; supports `mode="magnitude"` and `mode="signed"`. |
|
|
186
|
+
| `mne.VectorSourceEstimate` | Cortical vector magnitude; signed display is not defined. |
|
|
187
|
+
| `mne.MixedSourceEstimate` | Uses only the cortical surface component. |
|
|
188
|
+
| `mne.MixedVectorSourceEstimate` | Uses the cortical surface component and converts vectors to magnitude. |
|
|
189
|
+
| `mne.VolSourceEstimate` / `mne.VolVectorSourceEstimate` | Unsupported volume-only data; use a volume plotting workflow instead. |
|
|
190
|
+
|
|
191
|
+
Complex STCs are unsupported. PySourceviz also does not compute inverse
|
|
192
|
+
solutions or silently turn volumetric results into cortical surface data.
|
|
193
|
+
|
|
194
|
+
### Choose BrainSpace or Nilearn
|
|
195
|
+
|
|
196
|
+
BrainSpace is the backward-compatible default and uses VTK. Nilearn is an
|
|
197
|
+
optional Matplotlib-based renderer:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
plot_source(
|
|
201
|
+
stc,
|
|
202
|
+
subjects_dir=subjects_dir,
|
|
203
|
+
backend="brainspace",
|
|
204
|
+
time=0.112,
|
|
205
|
+
output="brainspace.png",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
plot_source(
|
|
209
|
+
stc,
|
|
210
|
+
subjects_dir=subjects_dir,
|
|
211
|
+
backend="nilearn",
|
|
212
|
+
time=0.112,
|
|
213
|
+
output="nilearn.png",
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Both calls pass the same prepared vertex arrays to the selected renderer. A
|
|
218
|
+
static-only save returns `info["plotter"] is None`. Without a static-only save,
|
|
219
|
+
that field contains a BrainSpace plotter or a Matplotlib `Figure`, respectively.
|
|
220
|
+
Call `.close()` on a retained BrainSpace plotter or
|
|
221
|
+
`matplotlib.pyplot.close(info["plotter"])` on a retained Nilearn figure when it
|
|
222
|
+
is no longer needed.
|
|
223
|
+
|
|
224
|
+
### Select a time or reduce a time window
|
|
225
|
+
|
|
226
|
+
`time=None` is valid for an STC that already has exactly one sample:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
plot_source(single_time_stc, subjects_dir=subjects_dir, time=None)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A numeric request chooses the nearest actual sample. `time="peak"` chooses the
|
|
233
|
+
sample containing the largest whole-brain magnitude:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112)
|
|
237
|
+
plot_source(stc, subjects_dir=subjects_dir, time="peak")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
For an inclusive window, use either the signed/absolute mean or non-negative
|
|
241
|
+
RMS reduction:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
plot_source(
|
|
245
|
+
stc,
|
|
246
|
+
subjects_dir=subjects_dir,
|
|
247
|
+
time_window=(0.080, 0.160),
|
|
248
|
+
reduce="mean",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
plot_source(
|
|
252
|
+
stc,
|
|
253
|
+
subjects_dir=subjects_dir,
|
|
254
|
+
time_window=(0.080, 0.160),
|
|
255
|
+
reduce="rms",
|
|
256
|
+
mode="magnitude",
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`time` and `time_window` are mutually exclusive. Signed RMS is rejected because
|
|
261
|
+
RMS has no sign.
|
|
262
|
+
|
|
263
|
+
### Choose native, fsaverage, or another matching subject
|
|
264
|
+
|
|
265
|
+
`template="native"` resolves to `stc.subject`. An explicit subject name must
|
|
266
|
+
match `stc.subject`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
plot_source(
|
|
270
|
+
stc_native,
|
|
271
|
+
subjects_dir=subjects_dir,
|
|
272
|
+
template="native",
|
|
273
|
+
time=0.112,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
plot_source(
|
|
277
|
+
stc_fsaverage,
|
|
278
|
+
subjects_dir=subjects_dir,
|
|
279
|
+
template="fsaverage",
|
|
280
|
+
time=0.112,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
plot_source(
|
|
284
|
+
stc_study_average,
|
|
285
|
+
subjects_dir=subjects_dir,
|
|
286
|
+
template="my_study_average",
|
|
287
|
+
time=0.112,
|
|
288
|
+
)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Cross-subject registration is deliberately external. Morph first with MNE,
|
|
292
|
+
then plot the already matching result:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
import mne
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
morph = mne.compute_source_morph(
|
|
299
|
+
stc_native,
|
|
300
|
+
subject_from=stc_native.subject,
|
|
301
|
+
subject_to="fsaverage",
|
|
302
|
+
subjects_dir=subjects_dir,
|
|
303
|
+
)
|
|
304
|
+
stc_fsaverage = morph.apply(stc_native)
|
|
305
|
+
|
|
306
|
+
plot_source(
|
|
307
|
+
stc_fsaverage,
|
|
308
|
+
subjects_dir=subjects_dir,
|
|
309
|
+
template="fsaverage",
|
|
310
|
+
time=0.112,
|
|
311
|
+
output="subject_on_fsaverage.png",
|
|
312
|
+
)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Choose cortical anatomy and display interpolation
|
|
316
|
+
|
|
317
|
+
All three supported FreeSurfer surfaces are explicit:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, surface="inflated")
|
|
321
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, surface="pial")
|
|
322
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, surface="white")
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`inflated` is the recommended default for source-localization overview
|
|
326
|
+
figures. It unfolds buried cortex without changing the data assigned to each
|
|
327
|
+
vertex. Use `pial` when the relationship to native cortical folding is itself
|
|
328
|
+
important, and `white` when the gray-white boundary is the intended anatomy;
|
|
329
|
+
neither alternative changes the inverse solution.
|
|
330
|
+
|
|
331
|
+
Sparse source vertices can be expanded to the display mesh in three ways:
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
# Recommended default: let MNE propagate until the surface is covered.
|
|
335
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, smoothing_steps=None)
|
|
336
|
+
|
|
337
|
+
# Legacy piecewise-constant nearest-source assignment.
|
|
338
|
+
plot_source(
|
|
339
|
+
stc,
|
|
340
|
+
subjects_dir=subjects_dir,
|
|
341
|
+
time=0.112,
|
|
342
|
+
smoothing_steps="nearest",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Exact non-negative number of adjacency-propagation iterations.
|
|
346
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, smoothing_steps=5)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
This is same-subject display interpolation, not Gaussian or statistical
|
|
350
|
+
smoothing. Thresholds and color limits are computed from original sparse
|
|
351
|
+
values before display expansion.
|
|
352
|
+
|
|
353
|
+
### Choose magnitude or signed color semantics
|
|
354
|
+
|
|
355
|
+
Magnitude is non-negative and defaults to the sequential `Reds` colormap:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
plot_source(
|
|
359
|
+
stc,
|
|
360
|
+
subjects_dir=subjects_dir,
|
|
361
|
+
time=0.112,
|
|
362
|
+
mode="magnitude",
|
|
363
|
+
cmap="Reds",
|
|
364
|
+
units="dSPM",
|
|
365
|
+
)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Signed scalar data default to `RdBu_r` and a symmetric range:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
plot_source(
|
|
372
|
+
stc_scalar,
|
|
373
|
+
subjects_dir=subjects_dir,
|
|
374
|
+
time_window=(0.080, 0.160),
|
|
375
|
+
reduce="mean",
|
|
376
|
+
mode="signed",
|
|
377
|
+
output="signed_mean.png",
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Signed mode is unavailable for vector and mixed-vector STCs.
|
|
382
|
+
|
|
383
|
+
### Choose a threshold and color limits
|
|
384
|
+
|
|
385
|
+
Leave the hard display mask disabled, use a numerical cutoff, or resolve a
|
|
386
|
+
percentile from the combined sparse hemispheres:
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, threshold=None)
|
|
390
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, threshold=3.0)
|
|
391
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, threshold="95%")
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Color limits support a robust percentile, an explicit range, and MNE-style
|
|
395
|
+
three-control dictionaries:
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, clim="robust")
|
|
399
|
+
|
|
400
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, clim=(0.0, 12.5))
|
|
401
|
+
|
|
402
|
+
plot_source(
|
|
403
|
+
stc,
|
|
404
|
+
subjects_dir=subjects_dir,
|
|
405
|
+
time=0.112,
|
|
406
|
+
clim={"kind": "percent", "lims": [98, 99, 99.9]},
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
plot_source(
|
|
410
|
+
stc,
|
|
411
|
+
subjects_dir=subjects_dir,
|
|
412
|
+
time=0.112,
|
|
413
|
+
clim={"kind": "value", "lims": [3.0, 4.0, 6.0]},
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
plot_source(
|
|
417
|
+
stc_signed,
|
|
418
|
+
subjects_dir=subjects_dir,
|
|
419
|
+
time=0.112,
|
|
420
|
+
mode="signed",
|
|
421
|
+
clim={"kind": "percent", "pos_lims": [95, 99, 99.9]},
|
|
422
|
+
)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
`robust_percentile=98.0` changes the percentile used by `clim="robust"`. For
|
|
426
|
+
dictionary clim, the lower, middle, and upper controls set the start of the
|
|
427
|
+
visibility ramp, the middle palette color/full overlay opacity, and the
|
|
428
|
+
saturation point. These controls and thresholds are visualization choices,
|
|
429
|
+
not tests of statistical significance.
|
|
430
|
+
|
|
431
|
+
### Choose layout, styling, and output behavior
|
|
432
|
+
|
|
433
|
+
Use a four-panel row or a two-by-two grid, and optionally control the rest of
|
|
434
|
+
the presentation:
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, layout="row")
|
|
438
|
+
plot_source(stc, subjects_dir=subjects_dir, time=0.112, layout="grid")
|
|
439
|
+
|
|
440
|
+
plot_source(
|
|
441
|
+
stc,
|
|
442
|
+
subjects_dir=subjects_dir,
|
|
443
|
+
time=0.112,
|
|
444
|
+
title="Auditory response",
|
|
445
|
+
colorbar=False,
|
|
446
|
+
background="white",
|
|
447
|
+
size=(1600, 420),
|
|
448
|
+
scale=(2, 2),
|
|
449
|
+
output="auditory.png",
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
plot_source(
|
|
453
|
+
stc,
|
|
454
|
+
subjects_dir=subjects_dir,
|
|
455
|
+
time=0.112,
|
|
456
|
+
background=(1.0, 1.0, 1.0),
|
|
457
|
+
transparent=True,
|
|
458
|
+
output="auditory_transparent.png",
|
|
459
|
+
)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The output/display combinations are:
|
|
463
|
+
|
|
464
|
+
```python
|
|
465
|
+
# Save a PNG and close the static renderer (show defaults to False here).
|
|
466
|
+
saved = plot_source(
|
|
467
|
+
stc,
|
|
468
|
+
subjects_dir=subjects_dir,
|
|
469
|
+
time=0.112,
|
|
470
|
+
output="source.png",
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# Open an interactive window (show defaults to True with no output).
|
|
474
|
+
interactive = plot_source(stc, subjects_dir=subjects_dir, time=0.112, show=True)
|
|
475
|
+
|
|
476
|
+
# Save and also create a separate interactive scene.
|
|
477
|
+
both = plot_source(
|
|
478
|
+
stc,
|
|
479
|
+
subjects_dir=subjects_dir,
|
|
480
|
+
time=0.112,
|
|
481
|
+
output="source_and_window.png",
|
|
482
|
+
show=True,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Retain an offscreen plotter without saving or opening a window.
|
|
486
|
+
offscreen = plot_source(stc, subjects_dir=subjects_dir, time=0.112, show=False)
|
|
487
|
+
offscreen["plotter"].close()
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`output` accepts PNG paths only and creates missing parent directories. The
|
|
491
|
+
default logical size is 1600 x 420 for `layout="row"` and 900 x 760 for
|
|
492
|
+
`layout="grid"`; screenshot `scale=(2, 2)` doubles both pixel axes.
|
|
493
|
+
|
|
494
|
+
### Compare conditions with one shared scale
|
|
495
|
+
|
|
496
|
+
Use the same preregistered or pooled range for every condition instead of a
|
|
497
|
+
separate robust range per image:
|
|
498
|
+
|
|
499
|
+
```python
|
|
500
|
+
shared_clim = (0.0, 12.5)
|
|
501
|
+
|
|
502
|
+
info_a = plot_source(
|
|
503
|
+
stc_condition_a,
|
|
504
|
+
subjects_dir=subjects_dir,
|
|
505
|
+
time=0.112,
|
|
506
|
+
clim=shared_clim,
|
|
507
|
+
title="Condition A",
|
|
508
|
+
output="condition_a.png",
|
|
509
|
+
)
|
|
510
|
+
info_b = plot_source(
|
|
511
|
+
stc_condition_b,
|
|
512
|
+
subjects_dir=subjects_dir,
|
|
513
|
+
time=0.112,
|
|
514
|
+
clim=(0.0, 12.5),
|
|
515
|
+
title="Condition B",
|
|
516
|
+
output="condition_b.png",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
assert info_a["color_range"] == info_b["color_range"] == shared_clim
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Inspect returned metadata
|
|
523
|
+
|
|
524
|
+
Every call returns a dictionary containing the resolved template and surface,
|
|
525
|
+
interpolation setting, time/window selection, threshold, whole-brain color
|
|
526
|
+
range and control points, colormap, layout, logical/pixel size controls, output
|
|
527
|
+
path, and `plotter`. Useful checks include:
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
for key in (
|
|
531
|
+
"backend",
|
|
532
|
+
"template",
|
|
533
|
+
"surface",
|
|
534
|
+
"smoothing_steps",
|
|
535
|
+
"selected_time",
|
|
536
|
+
"sampled_time_window",
|
|
537
|
+
"threshold",
|
|
538
|
+
"color_range",
|
|
539
|
+
"color_control_points",
|
|
540
|
+
"output",
|
|
541
|
+
):
|
|
542
|
+
print(key, info[key])
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Run the bundled examples
|
|
546
|
+
|
|
547
|
+
| Program | Purpose |
|
|
548
|
+
| --- | --- |
|
|
549
|
+
| `examples/basic_usage.py` | Reusable BrainSpace/Nilearn fixed-time, window-RMS, and shared-clim helpers. |
|
|
550
|
+
| `examples/real_data_validation.py` | Five-image acceptance set plus a JSON provenance manifest. |
|
|
551
|
+
| `examples/compare_nilearn_backend.py` | Controlled BrainSpace/Nilearn comparison from one surface STC. |
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
python examples/basic_usage.py
|
|
555
|
+
|
|
556
|
+
python examples/real_data_validation.py \
|
|
557
|
+
--stc-base /data/fsaverage_audvis_trunc-meg \
|
|
558
|
+
--subjects-dir /data/subjects \
|
|
559
|
+
--subject fsaverage \
|
|
560
|
+
--output-dir validation-output
|
|
561
|
+
|
|
562
|
+
python examples/compare_nilearn_backend.py \
|
|
563
|
+
--stc-base /data/fsaverage_audvis_trunc-meg \
|
|
564
|
+
--subjects-dir /data/subjects \
|
|
565
|
+
--subject fsaverage \
|
|
566
|
+
--output-dir renderer-comparison
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Complete `plot_source()` parameter reference
|
|
570
|
+
|
|
571
|
+
The table is synchronized with the public function signature. “Required” means
|
|
572
|
+
there is no default; all arguments after `stc` are keyword-only.
|
|
573
|
+
|
|
574
|
+
| Parameter | Default | Accepted values and effect |
|
|
575
|
+
| --- | --- | --- |
|
|
576
|
+
| `stc` | required | Real `mne.SourceEstimate`, vector surface STC, or mixed STC with a cortical component. |
|
|
577
|
+
| `subjects_dir` | required | Path containing the matching FreeSurfer subject directory. |
|
|
578
|
+
| `backend` | `"brainspace"` | `"brainspace"` or optional `"nilearn"`; scientific preprocessing is shared. |
|
|
579
|
+
| `template` | `"native"` | `"native"` or an explicit subject name equal to `stc.subject`. |
|
|
580
|
+
| `surface` | `"inflated"` | `"inflated"`, `"pial"`, or `"white"`. |
|
|
581
|
+
| `smoothing_steps` | `None` | Automatic MNE coverage, `"nearest"`, or a non-negative integer. |
|
|
582
|
+
| `time` | `None` | Seconds, `"peak"`, or `None`; mutually exclusive with `time_window`. |
|
|
583
|
+
| `time_window` | `None` | Inclusive `(start, stop)` seconds or `None`. |
|
|
584
|
+
| `reduce` | `"mean"` | `"mean"` or `"rms"` for a time window. |
|
|
585
|
+
| `mode` | `"magnitude"` | `"magnitude"` or scalar `"signed"`. |
|
|
586
|
+
| `threshold` | `None` | Non-negative number, percentile string such as `"95%"`, or `None`. |
|
|
587
|
+
| `clim` | `"robust"` | `"robust"`, `(lower, upper)`, or MNE-style three-control dictionary. |
|
|
588
|
+
| `robust_percentile` | `98.0` | Percentile in `(0, 100]` used by robust limits. |
|
|
589
|
+
| `cmap` | `None` | Matplotlib colormap name or mode-aware default. |
|
|
590
|
+
| `units` | `None` | Optional activation colorbar title. |
|
|
591
|
+
| `layout` | `"row"` | `"row"` or `"grid"`. |
|
|
592
|
+
| `title` | `None` | Optional figure title. |
|
|
593
|
+
| `colorbar` | `True` | Show or hide the shared activation colorbar. |
|
|
594
|
+
| `background` | `"white"` | Matplotlib color name or RGB triple. |
|
|
595
|
+
| `output` | `None` | Optional `.png` path; missing parent directories are created. |
|
|
596
|
+
| `size` | `None` | Logical `(width, height)` or the layout-specific default. |
|
|
597
|
+
| `scale` | `(2, 2)` | Positive integer screenshot scale for each pixel axis. |
|
|
598
|
+
| `transparent` | `False` | Use an alpha background for saved PNG output. |
|
|
599
|
+
| `show` | `None` | `True`, `False`, or output-aware default behavior. |
|
|
600
|
+
|
|
601
|
+
## FreeSurfer `subjects_dir` in detail
|
|
602
|
+
|
|
603
|
+
For `subject="sample"` and `surface="inflated"`, PySourceviz reads:
|
|
604
|
+
|
|
605
|
+
```text
|
|
606
|
+
<subjects_dir>/sample/surf/lh.inflated
|
|
607
|
+
<subjects_dir>/sample/surf/rh.inflated
|
|
608
|
+
<subjects_dir>/sample/surf/lh.sulc
|
|
609
|
+
<subjects_dir>/sample/surf/rh.sulc
|
|
610
|
+
<subjects_dir>/sample/label/lh.cortex.label
|
|
611
|
+
<subjects_dir>/sample/label/rh.cortex.label
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Missing surface or sulc files are fatal and their full paths appear in the
|
|
615
|
+
error. Missing cortex labels emit one warning and disable medial wall masking
|
|
616
|
+
for the affected hemisphere, so install or generate those labels before making
|
|
617
|
+
final figures.
|
|
618
|
+
|
|
619
|
+
## Display interpolation and source-space resolution
|
|
620
|
+
|
|
621
|
+
`smoothing_steps=None` asks MNE to propagate sparse source values over surface
|
|
622
|
+
adjacency until the display mesh is covered. `smoothing_steps="nearest"`
|
|
623
|
+
reproduces the piecewise-constant legacy view. A non-negative integer requests
|
|
624
|
+
an exact number of propagation iterations; if it is too small for a dense mesh,
|
|
625
|
+
MNE warns rather than pretending the surface was completely filled.
|
|
626
|
+
|
|
627
|
+
Interpolation is applied to a one-time display copy. Numeric/percentile
|
|
628
|
+
thresholds, robust limits, and percent clim controls are computed from combined
|
|
629
|
+
left/right sparse values before expansion, so repeated dense vertices cannot
|
|
630
|
+
bias a percentile. The input STC is unchanged.
|
|
631
|
+
|
|
632
|
+
Very coarse inverse source spaces can remain visibly patchy even with automatic
|
|
633
|
+
interpolation. For scientifically smoother spatial detail, improve the
|
|
634
|
+
source-space resolution in the forward/inverse model or use an explicitly
|
|
635
|
+
reported analysis-stage smoothing method. Do not hide a low-resolution inverse
|
|
636
|
+
solution with undocumented plotting-only Gaussian blur.
|
|
637
|
+
|
|
638
|
+
## Display threshold, color limits, and inference
|
|
639
|
+
|
|
640
|
+
A threshold is an optional hard display mask: values below it become fully
|
|
641
|
+
transparent after surface expansion. Abrupt masking can make isolated patches
|
|
642
|
+
look prominent. MNE-style clim dictionaries instead create a gradual
|
|
643
|
+
transparent-to-visible transition around peak regions.
|
|
644
|
+
|
|
645
|
+
The bundled audvis reference uses:
|
|
646
|
+
|
|
647
|
+
```python
|
|
648
|
+
clim={"kind": "percent", "lims": [98, 99, 99.9]}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
It starts the ramp at the 98th percentile, reveals more localized activation
|
|
652
|
+
than the stricter p99 example, and avoids a cortex-wide red cast. Signed data
|
|
653
|
+
use `pos_lims` with a diverging palette.
|
|
654
|
+
|
|
655
|
+
Neither threshold nor clim performs a statistical significance test,
|
|
656
|
+
multiple-comparison correction, or inference. Determine significance in the
|
|
657
|
+
analysis pipeline, then choose display controls that faithfully communicate
|
|
658
|
+
that result. When color must be quantitatively comparable, use a shared clim
|
|
659
|
+
for every condition.
|
|
660
|
+
|
|
661
|
+
## Layout, PNG output, and metadata
|
|
662
|
+
|
|
663
|
+
The row layout is `LH lateral | LH medial | RH medial | RH lateral`. The grid
|
|
664
|
+
places LH/RH lateral views on the first row and LH/RH medial views on the
|
|
665
|
+
second. BrainSpace uses global camera rotations internally; the anatomical
|
|
666
|
+
labels describe the hemisphere-relative view shown to the reader.
|
|
667
|
+
|
|
668
|
+
Static-only output returns `plotter=None`. Interactive or explicitly retained
|
|
669
|
+
scenes return a BrainSpace plotter or Nilearn Matplotlib `Figure`, according to
|
|
670
|
+
`backend`. The caller should close retained renderer objects. Returned
|
|
671
|
+
`selected_time` is always an actual STC sample, and `sampled_time_window`
|
|
672
|
+
records the first and last samples included in a window.
|
|
673
|
+
|
|
674
|
+
## Reproduce the real-data acceptance set
|
|
675
|
+
|
|
676
|
+
The workflow follows MNE's
|
|
677
|
+
[source-estimate visualization tutorial](https://mne.tools/stable/auto_tutorials/inverse/60_visualize_stc.html).
|
|
678
|
+
With an existing MNE sample dataset:
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
python examples/real_data_validation.py \
|
|
682
|
+
--sample-data-dir /path/to/MNE-sample-data \
|
|
683
|
+
--output-dir validation-output
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Or use an explicit two-hemisphere STC base and subject anatomy:
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
python examples/real_data_validation.py \
|
|
690
|
+
--stc-base /data/fsaverage_audvis_trunc-meg \
|
|
691
|
+
--subjects-dir /data/subjects \
|
|
692
|
+
--subject fsaverage \
|
|
693
|
+
--output-dir validation-output
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
The script creates fixed 112 ms, RMS 80-160 ms, inflated, pial, and shared-clim
|
|
697
|
+
comparison images plus `validation.json`. Its 0.65x comparison control checks
|
|
698
|
+
display consistency only; it is not an independent experimental condition.
|
|
699
|
+
`--download` opts into MNE's roughly 1.45 GB sample download. The default never
|
|
700
|
+
starts that download.
|
|
701
|
+
|
|
702
|
+
## Compare the built-in BrainSpace and Nilearn renderers
|
|
703
|
+
|
|
704
|
+
Install the optional backend in an editable source checkout:
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
python -m pip install -e ".[nilearn]"
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
The repository keeps `comparison` as a compatibility alias for the controlled
|
|
711
|
+
acceptance workflow:
|
|
712
|
+
|
|
713
|
+
```bash
|
|
714
|
+
python -m pip install -e ".[comparison]"
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
Nilearn's `plot_img_on_surf` accepts a 3D Niimg-like volume and projects voxels
|
|
718
|
+
onto a surface. An MNE surface `SourceEstimate` already contains values at
|
|
719
|
+
cortical vertices, so converting it to a volume and projecting it back would
|
|
720
|
+
add interpolation and would not be a controlled renderer comparison. The
|
|
721
|
+
PySourceviz Nilearn backend therefore keeps the shared time selection, display
|
|
722
|
+
interpolation, medial-wall mask, threshold, colormap, and color range, then
|
|
723
|
+
passes the same vertex arrays to Nilearn's `plot_surf_stat_map`.
|
|
724
|
+
|
|
725
|
+
The often-cited `plot_img_on_surf` gallery image is a different data case. Its
|
|
726
|
+
3D motor statistical map has negative and positive voxels, uses a diverging
|
|
727
|
+
`RdBu_r` palette, and projects multiple volumetric samples near the cortical
|
|
728
|
+
depth/normal. The audvis dSPM reference here is non-negative at the selected
|
|
729
|
+
sample, so the sequential `Reds` palette is scientifically appropriate rather
|
|
730
|
+
than inventing a blue negative branch.
|
|
731
|
+
|
|
732
|
+
Run the controlled comparison with explicit settings:
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
python examples/compare_nilearn_backend.py \
|
|
736
|
+
--stc-base /data/fsaverage_audvis_trunc-meg \
|
|
737
|
+
--subjects-dir /data/subjects \
|
|
738
|
+
--subject fsaverage \
|
|
739
|
+
--time 0.112 \
|
|
740
|
+
--surface inflated \
|
|
741
|
+
--smoothing-steps auto \
|
|
742
|
+
--clim-percent 98 99 99.9 \
|
|
743
|
+
--mode magnitude \
|
|
744
|
+
--cmap Reds \
|
|
745
|
+
--output-dir renderer-comparison
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
The comparison defaults to the recommended `surface="inflated"` and automatic
|
|
749
|
+
interpolation (`smoothing_steps=None`; CLI spelling `--smoothing-steps auto`).
|
|
750
|
+
It creates
|
|
751
|
+
`brainspace.png`, `nilearn.png`, and `comparison.json`. Both images share the
|
|
752
|
+
1600 x 420 logical canvas, four-view order, source sample, activation array,
|
|
753
|
+
sulcal anatomy, cmap, and `(vmin, vmax)`. To inspect native folding explicitly,
|
|
754
|
+
pass `--surface pial`; to inspect the gray-white boundary, pass
|
|
755
|
+
`--surface white`.
|
|
756
|
+
|
|
757
|
+
The manifest records source/surface vertex counts, density ratio, versions,
|
|
758
|
+
pixel sizes, `display_value_range`, and `display_sign_counts`. BrainSpace/VTK
|
|
759
|
+
and Nilearn/Matplotlib can still look different because VTK shades point-data
|
|
760
|
+
actors, while Nilearn uses mean vertex-to-face averaging on triangular faces.
|
|
761
|
+
This affects surface appearance, not the prepared source values. Use
|
|
762
|
+
`plot_img_on_surf` only when the source result genuinely is a 3D NIfTI volume
|
|
763
|
+
and the volume-to-surface projection is scientifically intended.
|
|
764
|
+
|
|
765
|
+
## Building and publishing
|
|
766
|
+
|
|
767
|
+
For an editable development environment with tests and release tools:
|
|
768
|
+
|
|
769
|
+
```bash
|
|
770
|
+
python -m pip install -e ".[dev]"
|
|
771
|
+
python -m pytest -q
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
The release helper resolves the project root from its own location. From the
|
|
775
|
+
repository root, run:
|
|
776
|
+
|
|
777
|
+
```bash
|
|
778
|
+
./scripts/release.sh build
|
|
779
|
+
./scripts/release.sh testpypi --clean
|
|
780
|
+
# After installing and testing the TestPyPI candidate:
|
|
781
|
+
./scripts/release.sh pypi --reuse
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
Every target builds with PEP 517 unless `--reuse` is selected, and runs
|
|
785
|
+
Twine's strict metadata check before any upload. `--clean` removes only old
|
|
786
|
+
`.tar.gz` and `.whl` files. `--reuse` rechecks and uploads the exact existing
|
|
787
|
+
pair, so the PyPI release can match the tested TestPyPI artifacts. Production
|
|
788
|
+
upload asks for `publish`; use `--yes` only for an intentional non-interactive
|
|
789
|
+
release. Override the interpreter or output directory with
|
|
790
|
+
`PYSOURCEVIZ_PYTHON` or `PYSOURCEVIZ_DIST_DIR`. Without `--clean` or
|
|
791
|
+
`--reuse`, the helper refuses to overwrite existing distributions. Run
|
|
792
|
+
`./scripts/release.sh --help` for the complete command reference.
|
|
793
|
+
|
|
794
|
+
For the equivalent manual workflow, update the version in `pyproject.toml`,
|
|
795
|
+
make sure `dist/` contains no artifacts from an older version, then build both
|
|
796
|
+
the source distribution and wheel with the modern PEP 517 frontend:
|
|
797
|
+
|
|
798
|
+
The PyPI long description cannot resolve repository-relative images, so this
|
|
799
|
+
README uses GitHub Raw URLs. Always
|
|
800
|
+
push the release commit containing the gallery assets to GitHub before either
|
|
801
|
+
upload so all four result images are already reachable.
|
|
802
|
+
|
|
803
|
+
```bash
|
|
804
|
+
python -m build
|
|
805
|
+
python -m twine check --strict dist/*
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
Upload to TestPyPI first and install the candidate without resolving runtime
|
|
809
|
+
dependencies from the test index:
|
|
810
|
+
|
|
811
|
+
```bash
|
|
812
|
+
python -m twine upload --repository testpypi dist/*
|
|
813
|
+
python -m pip install --index-url https://test.pypi.org/simple/ --no-deps PySourceviz
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
After testing the candidate, publish the same checked artifacts to PyPI:
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
python -m twine upload dist/*
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
Enter an API token through Twine/keyring or the upload prompt; never write a
|
|
823
|
+
token into this repository. See the official
|
|
824
|
+
[PyPA packaging tutorial](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
|
|
825
|
+
for the standards-based build and upload flow.
|
|
826
|
+
|
|
827
|
+
## Common errors
|
|
828
|
+
|
|
829
|
+
| Message or symptom | Meaning and action |
|
|
830
|
+
| --- | --- |
|
|
831
|
+
| `template='native' requires stc.subject` | Set the STC subject when creating/loading it, or use a correctly matching explicit template. |
|
|
832
|
+
| `STC subject space ... does not match` | Morph the STC externally with `mne.compute_source_morph`; do not relabel it. |
|
|
833
|
+
| `Required FreeSurfer surface file(s) missing` | Point `subjects_dir` one level above the subject and install the requested surface plus `lh/rh.sulc`. |
|
|
834
|
+
| `Cortex label file(s) missing` warning | Medial wall masking is disabled for that hemisphere; restore `lh/rh.cortex.label` before final export. |
|
|
835
|
+
| `STC has multiple time samples` | Set `time`, `time="peak"`, or `time_window`. |
|
|
836
|
+
| Large piecewise-constant activation patches | Use `smoothing_steps=None`; if a coarse source-space resolution remains visible, improve the inverse model instead of adding hidden plotting blur. |
|
|
837
|
+
| `output must use the .png extension` | Export PNG directly; convert formats afterward if needed. |
|
|
838
|
+
| `backend="nilearn" requires the optional Nilearn renderer` | Install `PySourceviz[nilearn]`, then rerun the same call. |
|
|
839
|
+
| Native VTK window/backend failure | Configure a supported VTK backend for the machine or render in a desktop session. |
|
|
840
|
+
|
|
841
|
+
## Known limits
|
|
842
|
+
|
|
843
|
+
PySourceviz v0.1 renders real cortical scalar STCs, vector-surface magnitude,
|
|
844
|
+
and the surface portion of mixed STCs through BrainSpace or optional Nilearn.
|
|
845
|
+
It does not render volume-only or complex STCs, compute inverse solutions,
|
|
846
|
+
perform cross-subject morphing, or conduct statistical inference. Its
|
|
847
|
+
same-subject adjacency propagation is display interpolation; it must not be
|
|
848
|
+
interpreted as Gaussian/statistical smoothing or an increase in source-space
|
|
849
|
+
resolution.
|