@yibeichan/claude-skills 1.1.0 → 1.2.0
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.
- package/README.md +2 -1
- package/package.json +1 -1
- package/skills/neuro-plotting/SKILL.md +500 -0
- package/skills/neuro-plotting/references/brain-rendering.md +319 -0
- package/skills/neuro-plotting/references/dynamic-fc-plots.md +475 -0
- package/skills/neuro-plotting/references/heatmaps-matrices.md +312 -0
- package/skills/neuro-plotting/references/multi-panel-composition.md +242 -0
- package/skills.json +9 -0
- package/skills/fmri-ssm.zip +0 -0
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A collection of reusable Claude skills for neuroimaging research workflows and s
|
|
|
11
11
|
| [bids-format](skills/bids-format/SKILL.md) | `npx @yibeichan/claude-skills install bids-format` | BIDS standard for all data types — naming conventions, dataset creation, multi-modal conversion (heudiconv, MNE-BIDS, pypet2bids), validation, derivatives, project organization, DataLad, sharing. |
|
|
12
12
|
| [neuroimaging-qc](skills/neuroimaging-qc/SKILL.md) | `npx @yibeichan/claude-skills install neuroimaging-qc` | Evidence-based QC decisions for fMRI, EEG, fNIRS using metrics from fMRIPrep, MRIQC, FreeSurfer. |
|
|
13
13
|
| [bidsapp-nidm-standards](skills/bidsapp-nidm-standards/SKILL.md) | `npx @yibeichan/claude-skills install bidsapp-nidm-standards` | Standards for creating NIDM-integrated BIDSapps that run through BABS. |
|
|
14
|
+
| [neuro-plotting](skills/neuro-plotting/SKILL.md) | `npx @yibeichan/claude-skills install neuro-plotting` | Publication-quality matplotlib for neuroscience: colorblind palettes, journal sizing, brain surfaces, transition matrices, heatmaps, multi-panel composition. |
|
|
14
15
|
| [scientific-writer](skills/scientific-writer/SKILL.md) | `npx @yibeichan/claude-skills install scientific-writer` | Rigorous scientific manuscripts following IMRAD, CONSORT/STROBE/PRISMA guidelines. |
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
@@ -96,4 +97,4 @@ See [CLAUDE.md](CLAUDE.md) for development guidelines or [MAINTAINERS.md](MAINTA
|
|
|
96
97
|
|
|
97
98
|
## License
|
|
98
99
|
|
|
99
|
-
MIT
|
|
100
|
+
MIT
|
package/package.json
CHANGED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: neuro-plotting
|
|
3
|
+
description: >
|
|
4
|
+
Publication-quality matplotlib plotting for scientific papers, with deep support for
|
|
5
|
+
cognitive neuroscience. Covers: colorblind-friendly palettes and colormaps, compact figure
|
|
6
|
+
sizing for journal columns, font scaling by figure width, separate colorbar placement,
|
|
7
|
+
clean x-axis labels (no rotation), rcParams for publication DPI/fonts/SVG, headless
|
|
8
|
+
rendering on SLURM/HPC, and savefig wrappers that prevent memory leaks. Also covers
|
|
9
|
+
brain-specific plotting: cortical surface rendering (surfplot, yabplot, nilearn), Yeo-7
|
|
10
|
+
network colors, atlas parcellation heatmaps, transition matrices, and state occupancy
|
|
11
|
+
plots. Use this skill whenever the user is making any matplotlib figure for a paper,
|
|
12
|
+
poster, or presentation — whether it's a bar chart, heatmap, scatter plot, line plot,
|
|
13
|
+
violin plot, or brain map. Use when the user asks about figure DPI, font sizes, colormaps,
|
|
14
|
+
colorbar placement, colorblind-safe colors, journal figure formatting, or matplotlib
|
|
15
|
+
styles. Also use when they mention brain surfaces, Yeo networks, Schaefer parcellations,
|
|
16
|
+
fMRI activation maps, transition matrices, dwell times, or any neuroimaging visualization.
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Publication-Quality Scientific Plotting
|
|
20
|
+
|
|
21
|
+
Standards and patterns for creating clean, consistent, journal-ready figures in
|
|
22
|
+
matplotlib. General-purpose plotting conventions (colors, fonts, sizing, colorbars)
|
|
23
|
+
with deep support for cognitive neuroscience visualizations (brain surfaces, network
|
|
24
|
+
palettes, parcellation heatmaps).
|
|
25
|
+
|
|
26
|
+
## When to Use This Skill
|
|
27
|
+
|
|
28
|
+
- Creating or editing any matplotlib figure for a scientific paper, poster, or presentation
|
|
29
|
+
- Setting up consistent matplotlib styles across a project's figures
|
|
30
|
+
- Choosing colormaps, color palettes, or colorblind-friendly schemes
|
|
31
|
+
- Figuring out figure sizes or font sizes for journal submission
|
|
32
|
+
- Placing colorbars without overlapping axes
|
|
33
|
+
- Formatting axis labels, tick labels, or legends
|
|
34
|
+
- Plotting heatmaps, bar charts, scatter plots, violin plots, line plots
|
|
35
|
+
- Rendering brain surface maps (cortical/subcortical) from parcellated data
|
|
36
|
+
- Plotting transition matrices, state occupancies, dwell times, or dynamic FC results
|
|
37
|
+
- Working with Yeo-7 network labels, Schaefer parcellations, or similar brain atlases
|
|
38
|
+
- Debugging figure rendering issues on HPC/SLURM (headless, memory leaks, segfaults)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Publication Style Setup
|
|
43
|
+
|
|
44
|
+
### The `apply_publication_style()` Pattern
|
|
45
|
+
|
|
46
|
+
Every project should have a single style-configuration function that sets all rcParams
|
|
47
|
+
in one place. This prevents font-size drift across scripts and ensures every figure
|
|
48
|
+
in the paper looks like it belongs together.
|
|
49
|
+
|
|
50
|
+
Font sizes should scale with figure size. Smaller figures need smaller fonts to avoid
|
|
51
|
+
text dominating the plot. Pick a tier and stay consistent across the project:
|
|
52
|
+
|
|
53
|
+
| Figure width | Title | Axis labels | Tick labels | Annotations |
|
|
54
|
+
|-------------|-------|-------------|-------------|-------------|
|
|
55
|
+
| <= 3.5 in (single-col) | 7 | 6 | 5 | 5 |
|
|
56
|
+
| 3.5-5.0 in (1.5-col) | 8 | 7 | 6 | 5-6 |
|
|
57
|
+
| 5.0-7.0 in (double-col) | 9 | 8 | 7 | 6 |
|
|
58
|
+
| > 7.0 in (wide panel) | 10 | 9 | 8 | 7 |
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import matplotlib
|
|
62
|
+
matplotlib.use("Agg") # must come before pyplot import for headless environments
|
|
63
|
+
import matplotlib.pyplot as plt
|
|
64
|
+
|
|
65
|
+
def apply_publication_style(figwidth="double"):
|
|
66
|
+
"""Call once at script start, before any plt.figure().
|
|
67
|
+
|
|
68
|
+
figwidth: "single" (<=3.5in), "1.5" (3.5-5in), "double" (5-7in), "wide" (>7in)
|
|
69
|
+
"""
|
|
70
|
+
font_tiers = {
|
|
71
|
+
"single": {"font.size": 6, "figure.titlesize": 7, "axes.titlesize": 7,
|
|
72
|
+
"axes.labelsize": 6, "xtick.labelsize": 5, "ytick.labelsize": 5},
|
|
73
|
+
"1.5": {"font.size": 7, "figure.titlesize": 8, "axes.titlesize": 8,
|
|
74
|
+
"axes.labelsize": 7, "xtick.labelsize": 6, "ytick.labelsize": 6},
|
|
75
|
+
"double": {"font.size": 8, "figure.titlesize": 9, "axes.titlesize": 9,
|
|
76
|
+
"axes.labelsize": 8, "xtick.labelsize": 7, "ytick.labelsize": 7},
|
|
77
|
+
"wide": {"font.size": 9, "figure.titlesize": 10, "axes.titlesize": 10,
|
|
78
|
+
"axes.labelsize": 9, "xtick.labelsize": 8, "ytick.labelsize": 8},
|
|
79
|
+
}
|
|
80
|
+
params = {
|
|
81
|
+
"figure.dpi": 300,
|
|
82
|
+
"axes.facecolor": "white",
|
|
83
|
+
"figure.facecolor": "white",
|
|
84
|
+
"svg.fonttype": "none", # editable text in Illustrator/Inkscape
|
|
85
|
+
}
|
|
86
|
+
params.update(font_tiers.get(figwidth, font_tiers["double"]))
|
|
87
|
+
plt.rcParams.update(params)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Why these values:**
|
|
91
|
+
- **300 DPI** is the minimum most journals accept; setting it globally means you never forget.
|
|
92
|
+
- **Scaled font tiers** keep text proportional to figure area — a 3.5-inch figure with 10 pt labels looks cramped; 5-6 pt breathes.
|
|
93
|
+
- **`svg.fonttype: "none"`** keeps text as real glyphs (not paths) so collaborators can edit labels in vector editors.
|
|
94
|
+
- **White backgrounds** prevent the grey canvas that matplotlib defaults can produce.
|
|
95
|
+
|
|
96
|
+
If using seaborn, call `apply_publication_style()` **after** `sns.set_theme()` so your
|
|
97
|
+
rcParams take precedence.
|
|
98
|
+
|
|
99
|
+
### Saving Figures
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
def savefig(fig, path, dpi=300):
|
|
103
|
+
"""Save and close — prevents memory leaks in batch/SLURM jobs."""
|
|
104
|
+
fig.savefig(path, dpi=dpi, bbox_inches="tight")
|
|
105
|
+
plt.close(fig)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Always close figures after saving. On HPC jobs that loop over subjects/states, unclosed
|
|
109
|
+
figures accumulate and eventually OOM-kill the process. The `savefig` wrapper makes this
|
|
110
|
+
automatic.
|
|
111
|
+
|
|
112
|
+
- Use `.png` for raster, `.svg` for vector.
|
|
113
|
+
- Always call `fig.tight_layout()` before saving to avoid clipped labels.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Figure Sizes
|
|
118
|
+
|
|
119
|
+
Prefer compact figures. Journals shrink large figures to fit columns anyway, so a
|
|
120
|
+
figure designed at its final printed size will look sharper than one designed large
|
|
121
|
+
and scaled down (where fonts become illegibly small). Start small and only go bigger
|
|
122
|
+
if the data genuinely needs space.
|
|
123
|
+
|
|
124
|
+
| Layout | Size (w x h inches) | When to use |
|
|
125
|
+
|--------|---------------------|-------------|
|
|
126
|
+
| Single-column | `(3.5, 2.5)` | Most inline figures |
|
|
127
|
+
| 1.5-column | `(5.0, 3.0)` | Medium panels, grouped bar charts |
|
|
128
|
+
| Double-column / full-width | `(7.0, 4.0)` | Multi-panel composites |
|
|
129
|
+
| Wide heatmap | `(10, max(3, K*0.3+1))` | Transition matrices, large grids |
|
|
130
|
+
| Brain gallery (cortical + subcortical) | `(8, 3)` per state | Side-by-side brain views |
|
|
131
|
+
| Dwell-time grid | `(3*ncols, 2.5*nrows)` | Per-state distribution panels |
|
|
132
|
+
|
|
133
|
+
Scale height dynamically when the number of rows depends on data (e.g., K states, N networks).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## X-Axis Labels: Never Rotate
|
|
138
|
+
|
|
139
|
+
Rotated x-axis labels are hard to read and look messy. If labels are too long to fit
|
|
140
|
+
horizontally, wrap them onto two lines instead:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
# Bad: rotated labels
|
|
144
|
+
ax.set_xticklabels(labels, rotation=45, ha="right")
|
|
145
|
+
|
|
146
|
+
# Good: wrap long labels with newlines
|
|
147
|
+
wrapped = [lab.replace(" ", "\n") if len(lab) > 10 else lab for lab in labels]
|
|
148
|
+
ax.set_xticklabels(wrapped)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For network names or condition labels, abbreviate rather than rotate:
|
|
152
|
+
- `"SalVentAttn"` -> `"Sal/\nVentAttn"` or just `"SVA"`
|
|
153
|
+
- `"Default Mode"` -> `"Default\nMode"` or `"DMN"`
|
|
154
|
+
|
|
155
|
+
If even wrapping doesn't fit, the figure is probably too narrow — widen it or use
|
|
156
|
+
a horizontal bar chart instead.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Colorbars: Always Separate
|
|
161
|
+
|
|
162
|
+
Never let matplotlib auto-place a colorbar with `plt.colorbar()` — it steals space
|
|
163
|
+
from the axes and causes misalignment in multi-panel figures. Instead, create a
|
|
164
|
+
dedicated axes for the colorbar:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
168
|
+
|
|
169
|
+
# Option 1: Adjacent colorbar via axes_grid1
|
|
170
|
+
divider = make_axes_locatable(ax)
|
|
171
|
+
cax = divider.append_axes("right", size="3%", pad=0.08)
|
|
172
|
+
fig.colorbar(im, cax=cax)
|
|
173
|
+
|
|
174
|
+
# Option 2: Explicit axes in gridspec (best for multi-panel)
|
|
175
|
+
fig, axes = plt.subplots(1, 3, figsize=(7, 3),
|
|
176
|
+
gridspec_kw={"width_ratios": [1, 1, 0.05]})
|
|
177
|
+
im = axes[0].imshow(data, cmap="RdBu_r", vmin=-vmax, vmax=vmax)
|
|
178
|
+
axes[1].imshow(data2, cmap="RdBu_r", vmin=-vmax, vmax=vmax)
|
|
179
|
+
fig.colorbar(im, cax=axes[2])
|
|
180
|
+
|
|
181
|
+
# Option 3: Single colorbar for an entire row
|
|
182
|
+
fig.colorbar(im, ax=axes[:2], location="right", shrink=0.8, pad=0.02)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The key idea: the colorbar gets its own axes with explicit size/position, so it
|
|
186
|
+
never overlaps or squeezes the main plot. For multi-panel figures that share a
|
|
187
|
+
colormap, one shared colorbar is cleaner than one per panel.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Color Palettes for Neuroscience
|
|
192
|
+
|
|
193
|
+
### Default: Colorblind-Friendly
|
|
194
|
+
|
|
195
|
+
About 8% of men and 0.5% of women have some form of colour vision deficiency. Every
|
|
196
|
+
figure should be readable by everyone. Use colorblind-safe palettes by default, not
|
|
197
|
+
as an afterthought.
|
|
198
|
+
|
|
199
|
+
**For categorical data** (conditions, groups, states), use one of these:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import seaborn as sns
|
|
203
|
+
|
|
204
|
+
# Best default — 10 distinct, colorblind-safe colors
|
|
205
|
+
CB_PALETTE = sns.color_palette("colorblind")
|
|
206
|
+
|
|
207
|
+
# Alternative: Wong (2011) palette — 8 colors, widely used in science
|
|
208
|
+
WONG_PALETTE = [
|
|
209
|
+
"#000000", # black
|
|
210
|
+
"#E69F00", # orange
|
|
211
|
+
"#56B4E9", # sky blue
|
|
212
|
+
"#009E73", # bluish green
|
|
213
|
+
"#F0E442", # yellow
|
|
214
|
+
"#0072B2", # blue
|
|
215
|
+
"#D55E00", # vermilion
|
|
216
|
+
"#CC79A7", # reddish purple
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
# Use directly
|
|
220
|
+
ax.bar(x, y, color=CB_PALETTE[:len(x)])
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Only fall back to non-colorblind palettes when there's a strong domain reason (e.g.,
|
|
224
|
+
Yeo-7 network colors, which are field-standard).
|
|
225
|
+
|
|
226
|
+
### Yeo-7 Network Colors (+ Subcortical)
|
|
227
|
+
|
|
228
|
+
The Yeo 2011 7-network parcellation has canonical colors used across the field. These
|
|
229
|
+
are NOT fully colorblind-safe, but they're the field standard for brain network
|
|
230
|
+
visualizations. Use them only for network-labeled data; for everything else, prefer
|
|
231
|
+
the colorblind palettes above.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
NETWORK_COLORS = {
|
|
235
|
+
"Vis": "#781286",
|
|
236
|
+
"SomMot": "#4682B4",
|
|
237
|
+
"DorsAttn": "#00760E",
|
|
238
|
+
"SalVentAttn": "#C43AFA",
|
|
239
|
+
"Limbic": "#DCF8A4",
|
|
240
|
+
"Cont": "#E69422",
|
|
241
|
+
"Default": "#CD3E4E",
|
|
242
|
+
"Subcortical": "#808080",
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
NETWORK_ORDER = [
|
|
246
|
+
"Vis", "SomMot", "DorsAttn", "SalVentAttn",
|
|
247
|
+
"Limbic", "Cont", "Default", "Subcortical",
|
|
248
|
+
]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Define these once in a shared module and import everywhere. Never redefine in individual
|
|
252
|
+
scripts — that's how colors silently diverge between figures.
|
|
253
|
+
|
|
254
|
+
When using Yeo colors, always add a second visual channel (hatching, markers, linestyle)
|
|
255
|
+
to compensate for the colorblind-unfriendly palette.
|
|
256
|
+
|
|
257
|
+
### State Role Colors (for Transition Topology)
|
|
258
|
+
|
|
259
|
+
When labeling states by their role in a transition graph (gateway, sink, source):
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
STATE_ROLE_COLORS = {
|
|
263
|
+
"gateway": "#E69F00", # orange (Wong)
|
|
264
|
+
"sink": "#D55E00", # vermilion (Wong)
|
|
265
|
+
"source": "#0072B2", # blue (Wong)
|
|
266
|
+
"intermediate": "#999999", # grey
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Legend Helpers
|
|
271
|
+
|
|
272
|
+
Build legend handles programmatically rather than relying on plot order:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from matplotlib.patches import Patch
|
|
276
|
+
|
|
277
|
+
def make_network_legend_handles(networks, colors=NETWORK_COLORS):
|
|
278
|
+
return [Patch(facecolor=colors[n], label=n) for n in networks]
|
|
279
|
+
|
|
280
|
+
def make_role_legend_handles(roles=STATE_ROLE_COLORS):
|
|
281
|
+
return [Patch(facecolor=c, label=r) for r, c in roles.items()]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Colormap Conventions
|
|
287
|
+
|
|
288
|
+
Choosing the right colormap for your data type matters for interpretability. Prefer
|
|
289
|
+
colormaps that are **perceptually uniform** and **colorblind-safe**. All recommendations
|
|
290
|
+
below meet both criteria.
|
|
291
|
+
|
|
292
|
+
| Data type | Colormap | CB-safe? | Notes |
|
|
293
|
+
|-----------|----------|----------|-------|
|
|
294
|
+
| Brain activation (diverging) | `"RdBu_r"` | yes | Centre at 0; symmetric `vmin/vmax` |
|
|
295
|
+
| Brain activation (positive-only) | `"YlOrRd"` | yes | |
|
|
296
|
+
| Transition probability (off-diag) | `"YlOrRd"` | yes | |
|
|
297
|
+
| Transition probability (full) | `"viridis"` + `LogNorm` | yes | Log scale for sparse matrices |
|
|
298
|
+
| Occupancy / recurrence | `"cividis"` | yes | Better than `YlGnBu` for CVD |
|
|
299
|
+
| Cosine similarity | `"RdBu_r"` | yes | Centre at 0 |
|
|
300
|
+
| Count / confusion matrix | `"viridis"` or `"cividis"` | yes | Normalise rows first |
|
|
301
|
+
|
|
302
|
+
**Avoid:** `"jet"`, `"rainbow"`, `"hot"`, `"hsv"` — these are not perceptually uniform
|
|
303
|
+
and fail badly for colorblind viewers. If you see `jet` in existing code, replace it.
|
|
304
|
+
|
|
305
|
+
**Key principle:** diverging data (positive and negative values around zero) needs a
|
|
306
|
+
diverging colormap centered at zero. Sequential data (counts, probabilities) needs a
|
|
307
|
+
sequential colormap. Getting this wrong misleads readers.
|
|
308
|
+
|
|
309
|
+
For diverging colormaps, compute symmetric limits:
|
|
310
|
+
```python
|
|
311
|
+
vmax = np.percentile(np.abs(data), 95)
|
|
312
|
+
im = ax.imshow(data, cmap="RdBu_r", vmin=-vmax, vmax=vmax)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The 95th percentile avoids letting outliers wash out the color range.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Typography in Figures
|
|
320
|
+
|
|
321
|
+
- **Axes titles**: sentence case, no trailing period (`"Recurrence score"`)
|
|
322
|
+
- **State labels**: `k=N` inline; `State N` in axis labels
|
|
323
|
+
- **Units**: always include — `"Dwell time (s)"`, `"Time (TRs)"`, `"Z-score"`
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Accessibility
|
|
328
|
+
|
|
329
|
+
1. **Colorblind-safe palettes first.** (See Color Palettes section above.)
|
|
330
|
+
2. **Dual encoding**: colour AND shape/linestyle for every categorical distinction.
|
|
331
|
+
Scatter: vary marker shape. Lines: vary linestyle. Readable in greyscale.
|
|
332
|
+
3. **Minimum font sizes**: 5 pt absolute minimum; scale with figure size per font tier table.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Dynamic FC Plots (Transition Matrices, Occupancy, Dwell Times)
|
|
337
|
+
|
|
338
|
+
For dynamic functional connectivity analyses, see `references/dynamic-fc-plots.md` for
|
|
339
|
+
complete plotting functions. Quick patterns:
|
|
340
|
+
|
|
341
|
+
- **Transition matrices**: Use `YlOrRd` for probabilities, `LogNorm` for sparse matrices,
|
|
342
|
+
`RdBu_r` centered at 0 for difference matrices. Always create a separate colorbar axes.
|
|
343
|
+
- **State occupancy**: Grouped bar charts with error bars. Use Wong palette colors for groups.
|
|
344
|
+
Add significance brackets with `add_significance_bracket()`.
|
|
345
|
+
- **Dwell times**: Violin or raincloud plots per state. Grid layout with `ncols=4`.
|
|
346
|
+
- **State sequences**: Color-coded strips via `imshow` with `ListedColormap`. Carpet plots
|
|
347
|
+
for multi-subject comparisons.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Heatmaps and Connectivity Matrices
|
|
352
|
+
|
|
353
|
+
For parcellation-level heatmaps and FC matrices, see `references/heatmaps-matrices.md`.
|
|
354
|
+
Key principles:
|
|
355
|
+
|
|
356
|
+
- Sort parcels by network so block structure is visible
|
|
357
|
+
- Draw thin black lines at network boundaries (`ax.axhline`, `ax.axvline`)
|
|
358
|
+
- Use symmetric color range for correlation-based data (`RdBu_r`, centered at 0)
|
|
359
|
+
- Overlay significance with FDR correction for statistical maps
|
|
360
|
+
- Use `sns.clustermap` only for exploration — switch to manual ordering for publication
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Multi-Panel Figure Composition
|
|
365
|
+
|
|
366
|
+
For complex multi-panel layouts, see `references/multi-panel-composition.md`. Key patterns:
|
|
367
|
+
|
|
368
|
+
- Use `gridspec` (not `plt.subplots`) when panels have different sizes
|
|
369
|
+
- Use `subgridspec` for nested layouts (e.g., brain maps row + metrics row)
|
|
370
|
+
- Panel labels: bold uppercase **A**, **B**, **C** at top-left, 1-2 pt larger than axis labels
|
|
371
|
+
- Shared colorbars: always via explicit `cax` in gridspec, never auto-placed
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Brain Surface Rendering
|
|
376
|
+
|
|
377
|
+
For detailed guidance on rendering cortical and subcortical brain maps (using surfplot,
|
|
378
|
+
yabplot, nilearn, or pyvista), including headless/HPC setup and atlas management, see
|
|
379
|
+
`references/brain-rendering.md`.
|
|
380
|
+
|
|
381
|
+
Quick pattern for surfplot (recommended for publication-quality cortical surfaces):
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from surfplot import Plot
|
|
385
|
+
from neuromaps.datasets import fetch_fslr
|
|
386
|
+
|
|
387
|
+
surfaces = fetch_fslr()
|
|
388
|
+
p = Plot(surfaces["inflated"], views=["lateral", "medial"],
|
|
389
|
+
size=(400, 200), zoom=1.2)
|
|
390
|
+
p.add_layer(stat_map_lh, stat_map_rh, cmap="RdBu_r",
|
|
391
|
+
color_range=(-vmax, vmax))
|
|
392
|
+
fig = p.build()
|
|
393
|
+
fig.savefig("brain_surface.png", dpi=300, bbox_inches="tight")
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Quick pattern for yabplot:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
# Headless setup — MUST come before any pyvista/yabplot import
|
|
400
|
+
from utils.viz_yabplot import setup_yabplot_headless
|
|
401
|
+
setup_yabplot_headless()
|
|
402
|
+
|
|
403
|
+
from utils.viz_yabplot import load_parcel_labels, render_brain_pattern
|
|
404
|
+
|
|
405
|
+
labels_df = load_parcel_labels("atlas-4S156Parcels")
|
|
406
|
+
vmax = np.percentile(np.abs(all_patterns), 95)
|
|
407
|
+
cortical_img, subcortical_img = render_brain_pattern(
|
|
408
|
+
pattern, labels_df, "atlas-4S156Parcels", (-vmax, vmax), cmap="RdBu_r"
|
|
409
|
+
)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Then composite into matplotlib:
|
|
413
|
+
```python
|
|
414
|
+
fig, axes = plt.subplots(1, 2, figsize=(10, 4),
|
|
415
|
+
gridspec_kw={"width_ratios": [2.5, 1.5]})
|
|
416
|
+
axes[0].imshow(cortical_img); axes[0].axis("off")
|
|
417
|
+
axes[1].imshow(subcortical_img); axes[1].axis("off")
|
|
418
|
+
savefig(fig, out_path)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Atlas Metadata and Network Sorting
|
|
424
|
+
|
|
425
|
+
When plotting parcellation-level data (heatmaps, connectivity matrices), sort parcels
|
|
426
|
+
by network so the block structure is visible:
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
from utils.atlas import load_atlas_metadata, sort_parcels_by_network
|
|
430
|
+
|
|
431
|
+
atlas_df = load_atlas_metadata("atlas-4S156Parcels")
|
|
432
|
+
sort_idx, boundaries = sort_parcels_by_network(atlas_df)
|
|
433
|
+
|
|
434
|
+
# Reorder matrix rows/columns by network
|
|
435
|
+
sorted_matrix = matrix[np.ix_(sort_idx, sort_idx)]
|
|
436
|
+
|
|
437
|
+
# Draw network boundaries
|
|
438
|
+
for b in boundaries:
|
|
439
|
+
ax.axhline(b, color="k", linewidth=0.5)
|
|
440
|
+
ax.axvline(b, color="k", linewidth=0.5)
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Common Pitfalls
|
|
446
|
+
|
|
447
|
+
| Problem | Solution |
|
|
448
|
+
|---------|----------|
|
|
449
|
+
| Blank figure on SLURM / HPC | `matplotlib.use("Agg")` before importing pyplot (or use `apply_publication_style()`) |
|
|
450
|
+
| Memory leak in batch jobs | Always `plt.close(fig)` after saving — use the `savefig` wrapper |
|
|
451
|
+
| Network colors differ between scripts | Import from one shared module; never redefine |
|
|
452
|
+
| Font sizes inconsistent | All sizes from `apply_publication_style()`; don't override per-script |
|
|
453
|
+
| Wrong DPI in saved file | Set DPI in `savefig()`, not in `plt.show()` |
|
|
454
|
+
| Seaborn theme overrides your style | Call `apply_publication_style()` after `sns.set_theme()` |
|
|
455
|
+
| SVG text rendered as paths | Set `svg.fonttype: "none"` (the style function handles this) |
|
|
456
|
+
| Brain rendering segfaults on HPC | Call headless setup before any pyvista/vtk import; or use surfplot (no VTK needed) |
|
|
457
|
+
| Color range differs between panels | Compute `vmin/vmax` once from all data, pass to every panel |
|
|
458
|
+
| Axis labels clipped in saved file | Use `bbox_inches="tight"` in `savefig` |
|
|
459
|
+
| Rotated x-axis labels | Never rotate — wrap text to two lines or abbreviate instead |
|
|
460
|
+
| Colorbar overlapping axes | Create a separate `cax` for the colorbar; never use bare `plt.colorbar()` |
|
|
461
|
+
| Colorblind-unfriendly palette | Use `seaborn "colorblind"` or Wong palette; reserve Yeo-7 for network plots only |
|
|
462
|
+
| `jet` or `rainbow` colormap | Replace with `viridis`, `cividis`, or `RdBu_r` — always perceptually uniform |
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Project Setup Checklist
|
|
467
|
+
|
|
468
|
+
When setting up plotting for a new neuroscience project:
|
|
469
|
+
|
|
470
|
+
1. Create `utils/plot_style.py` with `apply_publication_style()`, color constants, and `savefig()`
|
|
471
|
+
2. Define project-specific constants (TR, atlas name, condition colors) in the same module
|
|
472
|
+
3. If rendering brain surfaces, create `utils/viz_brain.py` with headless setup and rendering functions
|
|
473
|
+
4. If using parcellated data, create `utils/atlas.py` for metadata loading and network sorting
|
|
474
|
+
5. Every plotting script imports from these modules — no local redefinitions
|
|
475
|
+
|
|
476
|
+
This structure ensures that changing a color, font size, or DPI propagates to every
|
|
477
|
+
figure in the project automatically.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Quick Reference: Imports Template
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
import numpy as np
|
|
485
|
+
import matplotlib
|
|
486
|
+
matplotlib.use("Agg")
|
|
487
|
+
import matplotlib.pyplot as plt
|
|
488
|
+
|
|
489
|
+
from utils.plot_style import (
|
|
490
|
+
apply_publication_style, savefig,
|
|
491
|
+
NETWORK_COLORS, NETWORK_ORDER,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
apply_publication_style()
|
|
495
|
+
|
|
496
|
+
# ... your plotting code ...
|
|
497
|
+
# fig = plt.figure(figsize=(7.0, 5.0))
|
|
498
|
+
# ...
|
|
499
|
+
# savefig(fig, "figures/my_figure.png")
|
|
500
|
+
```
|