mplify 1.0.0__tar.gz
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.
- mplify-1.0.0/PKG-INFO +380 -0
- mplify-1.0.0/README.md +368 -0
- mplify-1.0.0/pyproject.toml +22 -0
- mplify-1.0.0/pyproject.toml.orig +24 -0
- mplify-1.0.0/src/mplify/DEFAULT_PARAMS.py +186 -0
- mplify-1.0.0/src/mplify/__init__.py +56 -0
- mplify-1.0.0/src/mplify/_colorbar.py +98 -0
- mplify-1.0.0/src/mplify/_colors.py +163 -0
- mplify-1.0.0/src/mplify/_core.py +551 -0
- mplify-1.0.0/src/mplify/_fonts.py +27 -0
- mplify-1.0.0/src/mplify/_scalebar.py +99 -0
- mplify-1.0.0/src/mplify/_ticks.py +122 -0
- mplify-1.0.0/src/mplify/_utils.py +48 -0
mplify-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mplify
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MatPlotLib Prettifier - make plots pretty with a single function call
|
|
5
|
+
Author: m-beau
|
|
6
|
+
Author-email: m-beau <maximebeaujeanroch047@gmail.com>
|
|
7
|
+
Requires-Dist: ipykernel>=7.3.0
|
|
8
|
+
Requires-Dist: matplotlib
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# mplify
|
|
14
|
+
|
|
15
|
+
**MatPlotLib prettifier.** One function, `mplp()` (MPLP: MatPlotLib Prettify, or Make Plot Pretty), that turns a matplotlib plot into a great v1 figure ready for your slides, poster, or paper.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
from mplify import mplp
|
|
20
|
+
|
|
21
|
+
plt.plot(x, y)
|
|
22
|
+
mplp() # applies to the last figure and axis
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="doc/img/01_hero.png" width="100%" alt="matplotlib defaults vs mplp()">
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## The problem
|
|
32
|
+
|
|
33
|
+
Matplotlib is very highly customizable. That is the problem.
|
|
34
|
+
|
|
35
|
+
Say you want to rotate your x tick labels 30°, right-align them so they don't
|
|
36
|
+
collide with the axis, bump the axis label font, and drop the top and right
|
|
37
|
+
spines. Four small, obvious, universally wanted things. Here is matplotlib:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
ax.set_xticks(positions)
|
|
41
|
+
ax.set_xticklabels(labels, rotation=30, ha='right', fontsize=16, fontweight='regular')
|
|
42
|
+
ax.set_xlabel('Condition', size=18, labelpad=0)
|
|
43
|
+
ax.set_ylabel('Response', size=18, labelpad=0)
|
|
44
|
+
ax.tick_params(axis='both', width=1, length=4, direction='out',
|
|
45
|
+
bottom=True, left=True, top=False, right=False)
|
|
46
|
+
ax.spines['top'].set_visible(False)
|
|
47
|
+
ax.spines['right'].set_visible(False)
|
|
48
|
+
for sp in ('left', 'bottom'):
|
|
49
|
+
ax.spines[sp].set_lw(1)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Five different APIs (`set_xticklabels`, `set_xlabel`, `tick_params`, `spines`), three different spellings of the same concept (`fontsize`, `size`, `fontweight`/`weight`), and potential order-related bugs: ticks and limits interact, so calling them in the wrong order silently gives you a different figure.
|
|
53
|
+
|
|
54
|
+
To achieve the desired result, the knobs exist, but they're scattered across a documentation surface large enough that most of us end up doing one of three things:
|
|
55
|
+
|
|
56
|
+
1. copy-pasting the same 15 lines of boilerplate into every script;
|
|
57
|
+
2. re-googling "matplotlib rotate xticklabels" for the 200th time;
|
|
58
|
+
3. asking an LLM, which returns 40 lines of code, half of it redundant, and all of it subtly different from the 40 lines it gave you last week.
|
|
59
|
+
|
|
60
|
+
## The solution
|
|
61
|
+
|
|
62
|
+
`mplp()` is one callable with a flat, self-explanatory argument list. Everything
|
|
63
|
+
above becomes:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
mplp(xticks=positions, xtickslabels=labels, xtickrot=30, xtickha='right', xlabel='Condition', ylabel='Response')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Three things make this work:
|
|
70
|
+
|
|
71
|
+
**Sensible defaults.** Call `mplp()` with no arguments and it applies a handcrafted default styling: larger font sizes, fatter spines, top and right spines gone, ticks pointing out, editable text in saved PDFs.
|
|
72
|
+
|
|
73
|
+
**Implicitly callable.** `mplp()` reads the current figure via `plt.gcf()`/`plt.gca()`, so you can simply call it at the end of your script, whether you're using matplotlib in explicit (object-oriented) or implicit (without declaring figures and axis, MATLAB-inherited) mode. But you can always feed `fig` and `ax` to mplp explicitly.
|
|
74
|
+
|
|
75
|
+
**One flat layer of arguments.** All the common figure tweaks are a self-explanatory keyword that can be remembered through checking the arguments of `mplp()`: `xtickrot`, `ticklab_s`, `hide_top_right`, `hlines`, `clabel`, `legend_loc`, `saveFig`. Anything you don't pass keeps mplify's default; anything you do pass takes precedence.
|
|
76
|
+
|
|
77
|
+
And it stays out of your way: `mplp()` edits the axis you hand it and nothing else — no style sheet to install, no `rcParams` rewritten mid-script, no surprises in the next figure. (The one exception is deliberate: importing mplify sets `pdf`/`ps`/`svg` font types to keep text editable in saved vector files. See [Saving](#saving).)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install mplify # or: uv add mplify
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
From source:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git clone https://github.com/m-beau/mplify.git
|
|
91
|
+
cd mplify && uv sync
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Requires Python ≥ 3.10, matplotlib, numpy.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Tour
|
|
99
|
+
|
|
100
|
+
Left panel is matplotlib's default in every figure below. Right panel is one
|
|
101
|
+
`mplp()` call. Full runnable versions of all of these live in
|
|
102
|
+
[`quickstart.ipynb`](quickstart.ipynb).
|
|
103
|
+
|
|
104
|
+
### Limits, ticks and labels, in the right order
|
|
105
|
+
|
|
106
|
+
`mplp` applies limits before ticks (and re-applies them after), so you never have
|
|
107
|
+
to remember which call comes first.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
mplp(xlim=(0, 8), ylim=(-0.5, 1),
|
|
111
|
+
xticks=[0, 2, 4, 6, 8], yticks=[-0.5, 0, 0.5, 1],
|
|
112
|
+
xlabel='Time (s)', ylabel='Amplitude (a.u.)')
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
### Tick labels: text, rotation, alignment
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
mplp(xticks=range(4), xtickslabels=categories, xtickrot=30, xtickha='right')
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
### Reference lines
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
mplp(hlines=[0], vlines=[np.pi, 2*np.pi],
|
|
129
|
+
lines_kwargs={'lw': 2, 'ls': ':', 'color': 'grey'})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+

|
|
133
|
+
|
|
134
|
+
### Good-looking colorbars
|
|
135
|
+
|
|
136
|
+
`plt.colorbar()` steals space from the parent axes, so a row of subplots ends up
|
|
137
|
+
with panels of different widths (and one of them mysteriously narrower than its
|
|
138
|
+
neighbours). mplify's colorbar is an inset anchored to the axis: the data area
|
|
139
|
+
keeps the exact size you gave it.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
mplp(colorbar=True, vmin=-3, vmax=3, cmap='RdBu_r',
|
|
143
|
+
clabel='Z-score', cticks=[-2, 0, 2])
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
### Exotic colormaps
|
|
149
|
+
|
|
150
|
+
If your data span −2 to 5, `cmap='RdBu_r'` puts white at **1.5**. Half your
|
|
151
|
+
"blue" values are positive numbers. `center=0` re-anchors the colormap so white
|
|
152
|
+
means zero, without clipping the range.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
ax.imshow(data, vmin=-2, vmax=5, cmap=get_bounded_cmap('RdBu_r', -2, 0, 5))
|
|
156
|
+
mplp(colorbar=True, cmap='RdBu_r', vmin=-2, center=0, vmax=5)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+

|
|
160
|
+
|
|
161
|
+
### Scalebars instead of axes
|
|
162
|
+
|
|
163
|
+
For traces where the absolute values are meaningless but the scale isn't —
|
|
164
|
+
ephys, imaging, anything with a time base.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
mplp(hide_axis=True,
|
|
168
|
+
xscalebar=5, yscalebar=200,
|
|
169
|
+
xscalebar_unit=' ms', yscalebar_unit=' μV')
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+
|
|
174
|
+
### `size=` — one figure, three media
|
|
175
|
+
|
|
176
|
+
The most common figure reformatting need: scaling a figure's "metadata" with respect to its data for different media. `size` rescales fonts, spine widths, tick widths, colorbar thickness and scalebar text for the viewing distance..
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
mplp(size='paper') # or 'slide' (default), 'poster'
|
|
180
|
+
mplp(size='xs') # or 's', 'm', 'l', 'xl', 'xxl'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+

|
|
184
|
+
|
|
185
|
+
### Bonus: color families for nested designs
|
|
186
|
+
|
|
187
|
+
Genotype × dose, region × condition, subject × session. One hue per group, one
|
|
188
|
+
shade within it — so the structure of the design is visible without reading the
|
|
189
|
+
legend.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
families = get_color_families(ncolors=3, nfamilies=3, cmapstr='viridis')
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+

|
|
196
|
+
|
|
197
|
+
Plus the usual conveniences:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
get_ncolors_cmap(8, 'viridis') # N evenly spaced colors from any colormap
|
|
201
|
+
to_hex((70, 130, 180)) # accepts 0-1 or 0-255, hex, names, 'r'
|
|
202
|
+
html_palette(colors) # preview swatches inline in a notebook
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+

|
|
206
|
+
|
|
207
|
+
### Everything at once
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
mplp(xlabel='Feature 1', ylabel='Feature 2',
|
|
211
|
+
colorbar=True, vmin=c.min(), vmax=c.max(), cmap='magma', clabel='F1 + F2',
|
|
212
|
+
hlines=[y.mean()], vlines=[x.mean()],
|
|
213
|
+
lines_kwargs={'lw': 1, 'ls': '--', 'color': 'grey', 'zorder': -1})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+

|
|
217
|
+
|
|
218
|
+
Four lines. Just to bring the point home, here is the raw matplotlib code that would be needed to produces the exact same panel (i.e. that an LLM would provide):
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
### Raw matplotlib code - much more verbose..!
|
|
222
|
+
import numpy as np
|
|
223
|
+
from matplotlib.font_manager import FontProperties
|
|
224
|
+
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
|
|
225
|
+
|
|
226
|
+
# labels, title, fonts
|
|
227
|
+
ax.set_xlabel('Feature 1', size=18, weight='regular', labelpad=0, fontname='Arial')
|
|
228
|
+
ax.set_ylabel('Feature 2', size=18, weight='regular', labelpad=0, fontname='Arial')
|
|
229
|
+
ax.set_title('by hand', size=20, weight='regular')
|
|
230
|
+
|
|
231
|
+
# tick labels — set_ticks() first, or matplotlib warns and may mislabel them
|
|
232
|
+
fig.canvas.draw()
|
|
233
|
+
xticks, yticks = ax.get_xticks(), ax.get_yticks()
|
|
234
|
+
ax.set_xticks(xticks)
|
|
235
|
+
ax.set_xticklabels([f'{t:g}' for t in xticks], fontsize=16, fontweight='regular',
|
|
236
|
+
color=(0, 0, 0), rotation=0, ha='center', va='top', fontname='Arial')
|
|
237
|
+
ax.set_yticks(yticks)
|
|
238
|
+
ax.set_yticklabels([f'{t:g}' for t in yticks], fontsize=16, fontweight='regular',
|
|
239
|
+
color=(0, 0, 0), rotation=0, ha='right', va='center', fontname='Arial')
|
|
240
|
+
ax.set_xlim(xlim); ax.set_ylim(ylim) # ticks just widened your limits. put them back
|
|
241
|
+
|
|
242
|
+
# spines and ticks
|
|
243
|
+
ax.tick_params(axis='both', bottom=1, left=1, top=0, right=0,
|
|
244
|
+
width=1, length=4, direction='out')
|
|
245
|
+
for sp in ('left', 'bottom'):
|
|
246
|
+
ax.spines[sp].set_lw(1)
|
|
247
|
+
ax.spines['top'].set_visible(False)
|
|
248
|
+
ax.spines['right'].set_visible(False)
|
|
249
|
+
|
|
250
|
+
# reference lines
|
|
251
|
+
ax.axhline(y=y.mean(), lw=1, ls='--', color='grey', zorder=-1)
|
|
252
|
+
ax.axvline(x=x.mean(), lw=1, ls='--', color='grey', zorder=-1)
|
|
253
|
+
|
|
254
|
+
# a colorbar that doesn't steal width from the axis
|
|
255
|
+
cax = inset_axes(ax, width='3%', height='40%', loc='lower right',
|
|
256
|
+
bbox_to_anchor=(0.04, 0, 1, 1), bbox_transform=ax.transAxes,
|
|
257
|
+
borderpad=0)
|
|
258
|
+
sm = plt.cm.ScalarMappable(cmap='magma', norm=plt.Normalize(c.min(), c.max()))
|
|
259
|
+
sm.set_array([])
|
|
260
|
+
fig.colorbar(sm, cax=cax, ax=ax, orientation='vertical', label='F1 + F2')
|
|
261
|
+
cticks = np.arange(20, 141, 20)
|
|
262
|
+
cax.yaxis.set_ticks(cticks)
|
|
263
|
+
cax.yaxis.set_ticklabels([f'{t:g}' for t in cticks], ha='left')
|
|
264
|
+
cax.yaxis.set_tick_params(pad=5, labelsize=16)
|
|
265
|
+
cax.yaxis.label.set_font_properties(FontProperties(weight='regular', size=18))
|
|
266
|
+
cax.yaxis.label.set_rotation(-90)
|
|
267
|
+
cax.yaxis.label.set_va('bottom')
|
|
268
|
+
cax.yaxis.label.set_ha('center')
|
|
269
|
+
cax.yaxis.labelpad = 5
|
|
270
|
+
|
|
271
|
+
# line up labels across subplots, white figure background
|
|
272
|
+
fig.align_xlabels(fig.axes)
|
|
273
|
+
fig.align_ylabels(fig.axes)
|
|
274
|
+
fig.patch.set_facecolor('white')
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**41 lines, three imports, six APIs, two footguns** (`set_ticklabels` before
|
|
278
|
+
`set_ticks` warns and can silently mislabel your axis; setting ticks quietly
|
|
279
|
+
widens your limits, so you have to restore them afterwards).
|
|
280
|
+
|
|
281
|
+
### `prettify=False` — surgical mode
|
|
282
|
+
|
|
283
|
+
Sometimes you've already got a figure you like and you want to change exactly one
|
|
284
|
+
thing. `prettify=False` applies *only* what you pass and leaves everything else
|
|
285
|
+
alone.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
mplp(prettify=False, hide_top_right=True)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+

|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Cheat sheet
|
|
296
|
+
|
|
297
|
+
| | Argument |
|
|
298
|
+
|---|---|
|
|
299
|
+
| Figure / axis size (inches) | `figsize=(w, h)`, `axsize=(w, h)` |
|
|
300
|
+
| Scale text for medium | `size='paper' / 'slide' / 'poster'` (also `xs`–`xxl`) |
|
|
301
|
+
| Limits | `xlim`, `ylim` |
|
|
302
|
+
| Tick positions | `xticks`, `yticks`, `reset_xticks`, `reset_yticks` |
|
|
303
|
+
| Tick label text | `xtickslabels`, `ytickslabels` |
|
|
304
|
+
| Tick label rotation / alignment | `xtickrot`, `ytickrot`, `xtickha`, `xtickva`, `ytickha`, `ytickva` |
|
|
305
|
+
| Font sizes | `title_s`, `axlab_s`, `ticklab_s`, `clabel_s`, `cticks_s` |
|
|
306
|
+
| Font weights | `title_w`, `axlab_w`, `ticklab_w`, `clabel_w` |
|
|
307
|
+
| Font family | `font_family` |
|
|
308
|
+
| Labels / title | `xlabel`, `ylabel`, `title`, `xlabelpad`, `ylabelpad` |
|
|
309
|
+
| Spines | `lw`, `hide_top_right`, `hide_axis` |
|
|
310
|
+
| Tick direction | `ticks_direction='in' / 'out'` |
|
|
311
|
+
| Legend | `show_legend`, `hide_legend`, `legend_loc=(x, y)` |
|
|
312
|
+
| Colorbar | `colorbar=True`, `vmin`, `vmax`, `cmap`, `center`, `clabel`, `cticks`, `ctickslabels`, `cbar_w`, `cbar_h`, `cbar_pad`, `clim` |
|
|
313
|
+
| Reference lines | `hlines`, `vlines`, `lines_kwargs` |
|
|
314
|
+
| Scalebars | `xscalebar`, `yscalebar`, `xscalebar_unit`, `yscalebar_unit`, `scalebarkwargs` |
|
|
315
|
+
| Subplot spacing | `hspace`, `wspace`, `tight_layout` |
|
|
316
|
+
| Label alignment across subplots | `align_x_labels`, `align_y_labels` |
|
|
317
|
+
| Transparent background | `transparent_background=True` |
|
|
318
|
+
| Save | `saveFig=True`, `saveDir`, `figname`, `_format` |
|
|
319
|
+
| Change only what I pass | `prettify=False` |
|
|
320
|
+
|
|
321
|
+
### Helpers exported alongside `mplp`
|
|
322
|
+
|
|
323
|
+
| Function | Does |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `get_bestticks(start, end, step=None, light=False)` | Ticks on round numbers (1 / 5 / 10 steps) |
|
|
326
|
+
| `get_bestticks_from_array(arr, ...)` | Same, from data |
|
|
327
|
+
| `get_labels_from_ticks(ticks)` | Consistently formatted tick label strings |
|
|
328
|
+
| `sci_notation(1.23e6, 2)` | `1.23·10⁶` as mathtext |
|
|
329
|
+
| `get_cmap`, `get_bounded_cmap`, `get_ncolors_cmap`, `get_color_families` | Colormaps and palettes |
|
|
330
|
+
| `to_rgb`, `to_hex`, `to_hsv`, `html_palette` | Color conversion and preview |
|
|
331
|
+
| `add_colorbar(fig, ax, ...)` | The size-preserving colorbar, standalone |
|
|
332
|
+
| `plot_scalebar(ax, ...)` | Scalebars, standalone |
|
|
333
|
+
| `set_ax_size(ax, w, h)` | Exact axis dimensions in inches |
|
|
334
|
+
| `save_mpl_fig(fig, name, dir, fmt)` | Save with Type-42 (editable) text |
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Saving
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
mplp(saveFig=True, saveDir='./figures', figname='fig2b', _format='pdf')
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Saves at 500 dpi with `pdf.fonttype = 42`, i.e. **text stays text**. You can open the PDF in Illustrator/Inkscape and fix your typo without
|
|
345
|
+
re-running the analysis (You will. There is always a label to fix.)
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Changing the defaults
|
|
350
|
+
|
|
351
|
+
mplify's defaults live in one hand-editable file,
|
|
352
|
+
[`src/mplify/DEFAULT_PARAMS.py`](src/mplify/DEFAULT_PARAMS.py): `default_mplp_params` for the base style, `SIZE_PRESETS` for the paper/slide/poster xs/s/m/l/xl/xxl defaults.
|
|
353
|
+
|
|
354
|
+
Edit it and your next `mplp()` call picks the change up immediately — the file is re-read from disk whenever its mtime changes. No kernel restart or `%autoreload` needed.
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
from mplify import default_mplp_params, SIZE_PRESETS # snapshots, for inspection
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Not a style sheet, not a wrapper
|
|
363
|
+
|
|
364
|
+
- **Not a style sheet.** Style sheets set global `rcParams` across all figures; they can't rotate specific tick labels or put a colorbar on a specific axis. mplify operates per-axis, at call time, after your data is plotted.
|
|
365
|
+
- **Not a plotting wrapper.** mplify never draws your data. You keep `ax.plot`, `ax.imshow`, seaborn, whatever you already use (if it's built on top of matplotlib, of course).
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Development
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
uv sync # editable install into .venv
|
|
373
|
+
uv run python doc/make_figures.py # regenerate the README figures into doc img/
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The full gallery is [`quickstart.ipynb`](quickstart.ipynb) — open it in your editor of choice and point the kernel at `.venv`.
|
|
377
|
+
|
|
378
|
+
## Related
|
|
379
|
+
|
|
380
|
+
[NeuroPyxels](https://github.com/m-beau/NeuroPyxels) — Neuropixels data analysis, where this codebase slowly grew up since 2016.
|