figrecipe 0.5.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.
- figrecipe/__init__.py +1090 -0
- figrecipe/_recorder.py +435 -0
- figrecipe/_reproducer.py +358 -0
- figrecipe/_seaborn.py +305 -0
- figrecipe/_serializer.py +227 -0
- figrecipe/_signatures/__init__.py +7 -0
- figrecipe/_signatures/_loader.py +186 -0
- figrecipe/_utils/__init__.py +32 -0
- figrecipe/_utils/_crop.py +261 -0
- figrecipe/_utils/_diff.py +98 -0
- figrecipe/_utils/_image_diff.py +204 -0
- figrecipe/_utils/_numpy_io.py +204 -0
- figrecipe/_utils/_units.py +200 -0
- figrecipe/_validator.py +186 -0
- figrecipe/_wrappers/__init__.py +8 -0
- figrecipe/_wrappers/_axes.py +327 -0
- figrecipe/_wrappers/_figure.py +227 -0
- figrecipe/plt.py +12 -0
- figrecipe/pyplot.py +264 -0
- figrecipe/styles/__init__.py +50 -0
- figrecipe/styles/_style_applier.py +412 -0
- figrecipe/styles/_style_loader.py +450 -0
- figrecipe-0.5.0.dist-info/METADATA +336 -0
- figrecipe-0.5.0.dist-info/RECORD +26 -0
- figrecipe-0.5.0.dist-info/WHEEL +4 -0
- figrecipe-0.5.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: figrecipe
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Reproducible matplotlib wrapper with mm-precision layouts
|
|
5
|
+
Project-URL: Homepage, https://github.com/ywatanabe1989/figrecipe
|
|
6
|
+
Project-URL: Documentation, https://github.com/ywatanabe1989/figrecipe#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/ywatanabe1989/figrecipe.git
|
|
8
|
+
Project-URL: Issues, https://github.com/ywatanabe1989/figrecipe/issues
|
|
9
|
+
Author-email: Yusuke Watanabe <ywatanabe@scitex.ai>
|
|
10
|
+
License-Expression: AGPL-3.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: matplotlib,millimeter,plotting,publication,recipe,reproducibility,scientific,visualization,yaml
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
25
|
+
Requires-Dist: numpy>=1.20.0
|
|
26
|
+
Requires-Dist: ruamel-yaml>=0.17.0
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: pandas>=1.3.0; extra == 'all'
|
|
29
|
+
Requires-Dist: pillow>=9.0.0; extra == 'all'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
|
|
31
|
+
Requires-Dist: pytest>=7.0.0; extra == 'all'
|
|
32
|
+
Requires-Dist: seaborn>=0.12.0; extra == 'all'
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
36
|
+
Provides-Extra: imaging
|
|
37
|
+
Requires-Dist: pillow>=9.0.0; extra == 'imaging'
|
|
38
|
+
Provides-Extra: seaborn
|
|
39
|
+
Requires-Dist: pandas>=1.3.0; extra == 'seaborn'
|
|
40
|
+
Requires-Dist: seaborn>=0.12.0; extra == 'seaborn'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
<!-- ---
|
|
44
|
+
!-- Timestamp: 2025-12-22 13:01:11
|
|
45
|
+
!-- Author: ywatanabe
|
|
46
|
+
!-- File: /home/ywatanabe/proj/figrecipe/README.md
|
|
47
|
+
!-- --- -->
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img src="docs/figrecipe_logo.png" alt="figrecipe logo" width="200"/>
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
# FigRecipe
|
|
54
|
+
|
|
55
|
+
**Reproducible matplotlib figures with mm-precision layouts.**
|
|
56
|
+
FigRecipe is a lightweight recording & reproduction layer for matplotlib,
|
|
57
|
+
designed for scientific figures that must remain **editable, inspectable,
|
|
58
|
+
and reproducible**.
|
|
59
|
+
|
|
60
|
+
Part of **SciTeX™ – Research OS for reproducible science**
|
|
61
|
+
https://scitex.ai
|
|
62
|
+
|
|
63
|
+
[](https://badge.fury.io/py/figrecipe)
|
|
64
|
+
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Why FigRecipe?
|
|
69
|
+
|
|
70
|
+
In scientific workflows, figures are often:
|
|
71
|
+
- hard to reproduce once scripts change,
|
|
72
|
+
- resized manually in pixels or inches,
|
|
73
|
+
- impossible to partially reuse or inspect later.
|
|
74
|
+
|
|
75
|
+
**FigRecipe solves this by recording plotting calls as a structured “recipe”**,
|
|
76
|
+
allowing figures to be:
|
|
77
|
+
- faithfully reproduced,
|
|
78
|
+
- partially re-rendered,
|
|
79
|
+
- inspected for underlying data,
|
|
80
|
+
- laid out in **exact millimeters** for publication.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Key Features
|
|
85
|
+
|
|
86
|
+
- ✅ Drop-in replacement for `matplotlib.pyplot` (use `fr.subplots()` to enable recording)
|
|
87
|
+
- ✅ Automatic recording of plotting calls
|
|
88
|
+
- ✅ Reproduce figures from a YAML recipe
|
|
89
|
+
- ✅ Extract plotted data programmatically
|
|
90
|
+
- ✅ Selective reproduction of specific plots
|
|
91
|
+
- ✅ Millimeter-based layout (journal-ready)
|
|
92
|
+
- ✅ Publication-quality style presets
|
|
93
|
+
- ✅ Dark theme support (data colors preserved)
|
|
94
|
+
- ✅ Seamless seaborn integration
|
|
95
|
+
- ✅ Crop figures to content with mm margins
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install figrecipe
|
|
103
|
+
|
|
104
|
+
# Optional extras
|
|
105
|
+
pip install figrecipe[seaborn] # seaborn + pandas support
|
|
106
|
+
pip install figrecipe[imaging] # image cropping (Pillow)
|
|
107
|
+
pip install figrecipe[all] # all extras
|
|
108
|
+
|
|
109
|
+
# Optional: for PDF export from notebooks (SVG → PDF)
|
|
110
|
+
sudo apt install inkscape # Linux
|
|
111
|
+
brew install inkscape # macOS
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Requirements:** Python >= 3.9
|
|
115
|
+
|
|
116
|
+
## Basic Usage
|
|
117
|
+
|
|
118
|
+
### Recording & Saving
|
|
119
|
+
|
|
120
|
+
``` python
|
|
121
|
+
import figrecipe as fr
|
|
122
|
+
import numpy as np
|
|
123
|
+
|
|
124
|
+
x = np.linspace(0, 10, 100)
|
|
125
|
+
y = np.sin(x)
|
|
126
|
+
|
|
127
|
+
fig, ax = fr.subplots()
|
|
128
|
+
ax.plot(x, y, color='red', linewidth=2, id='sine_wave')
|
|
129
|
+
ax.set_xlabel('Time (s)')
|
|
130
|
+
ax.set_ylabel('Amplitude')
|
|
131
|
+
|
|
132
|
+
# Save image + recipe
|
|
133
|
+
img_path, yaml_path, result = fr.save(fig, 'figure.png')
|
|
134
|
+
# → creates: figure.png + figure.yaml
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Reproducing a Figure
|
|
138
|
+
|
|
139
|
+
``` python
|
|
140
|
+
import figrecipe as fr
|
|
141
|
+
|
|
142
|
+
fig, ax = fr.reproduce('figure.yaml')
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Extracting Plotted Data
|
|
146
|
+
|
|
147
|
+
``` python
|
|
148
|
+
import figrecipe as fr
|
|
149
|
+
|
|
150
|
+
data = fr.extract_data('figure.yaml')
|
|
151
|
+
# {'sine_wave': {'x': array([...]), 'y': array([...])}}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Millimeter-Based Layout (Publication-Ready)
|
|
155
|
+
|
|
156
|
+
``` python
|
|
157
|
+
fig, ax = fr.subplots(
|
|
158
|
+
axes_width_mm=60,
|
|
159
|
+
axes_height_mm=40,
|
|
160
|
+
margin_left_mm=15,
|
|
161
|
+
margin_bottom_mm=12,
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
This guarantees consistent sizing across editors, exports, and journals.
|
|
165
|
+
|
|
166
|
+
### Style Presets
|
|
167
|
+
|
|
168
|
+
``` python
|
|
169
|
+
fr.list_presets()
|
|
170
|
+
# ['MATPLOTLIB', 'SCITEX']
|
|
171
|
+
|
|
172
|
+
# Publication-quality preset (applied globally)
|
|
173
|
+
fr.load_style('SCITEX')
|
|
174
|
+
fig, ax = fr.subplots()
|
|
175
|
+
|
|
176
|
+
# Dark theme (UI-only, data colors preserved)
|
|
177
|
+
fr.load_style('SCITEX_DARK')
|
|
178
|
+
# or: fr.load_style('SCITEX', dark=True)
|
|
179
|
+
|
|
180
|
+
# Custom style
|
|
181
|
+
fr.load_style('/path/to/my_style.yaml')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
See docs/EXAMPLE_RECIPE.yaml for a full style template.
|
|
185
|
+
|
|
186
|
+
### Recipe Format (YAML)
|
|
187
|
+
|
|
188
|
+
``` yaml
|
|
189
|
+
# Timestamp: "2025-12-22 11:53:14 (ywatanabe)"
|
|
190
|
+
# File: ./docs/EXAMPLE_RECIPE.yaml
|
|
191
|
+
# MATPLOTLIB Style Preset
|
|
192
|
+
# =======================
|
|
193
|
+
# Vanilla matplotlib defaults - minimal customization.
|
|
194
|
+
# Use this to reset to standard matplotlib behavior.
|
|
195
|
+
|
|
196
|
+
axes:
|
|
197
|
+
width_mm: null # Use matplotlib default (auto)
|
|
198
|
+
height_mm: null # Use matplotlib default (auto)
|
|
199
|
+
thickness_mm: null # Use matplotlib default
|
|
200
|
+
|
|
201
|
+
margins:
|
|
202
|
+
left_mm: null
|
|
203
|
+
right_mm: null
|
|
204
|
+
bottom_mm: null
|
|
205
|
+
top_mm: null
|
|
206
|
+
|
|
207
|
+
spacing:
|
|
208
|
+
horizontal_mm: null
|
|
209
|
+
vertical_mm: null
|
|
210
|
+
|
|
211
|
+
fonts:
|
|
212
|
+
family: null # matplotlib default (DejaVu Sans)
|
|
213
|
+
axis_label_pt: null
|
|
214
|
+
tick_label_pt: null
|
|
215
|
+
title_pt: null
|
|
216
|
+
suptitle_pt: null
|
|
217
|
+
legend_pt: null
|
|
218
|
+
annotation_pt: null
|
|
219
|
+
|
|
220
|
+
padding:
|
|
221
|
+
label_pt: null
|
|
222
|
+
tick_pt: null
|
|
223
|
+
title_pt: null
|
|
224
|
+
|
|
225
|
+
lines:
|
|
226
|
+
trace_mm: null
|
|
227
|
+
errorbar_mm: null
|
|
228
|
+
errorbar_cap_mm: null
|
|
229
|
+
|
|
230
|
+
ticks:
|
|
231
|
+
length_mm: null
|
|
232
|
+
thickness_mm: null
|
|
233
|
+
direction: null
|
|
234
|
+
n_ticks: null
|
|
235
|
+
|
|
236
|
+
markers:
|
|
237
|
+
size_mm: null
|
|
238
|
+
scatter_mm: null
|
|
239
|
+
edge_width_mm: null
|
|
240
|
+
|
|
241
|
+
legend:
|
|
242
|
+
frameon: null # matplotlib default (True)
|
|
243
|
+
bg: null # matplotlib default
|
|
244
|
+
edgecolor: null # matplotlib default
|
|
245
|
+
alpha: null # matplotlib default
|
|
246
|
+
loc: null # matplotlib default
|
|
247
|
+
|
|
248
|
+
output:
|
|
249
|
+
dpi: 300
|
|
250
|
+
transparent: false
|
|
251
|
+
format: "png"
|
|
252
|
+
|
|
253
|
+
behavior:
|
|
254
|
+
auto_scale_axes: false
|
|
255
|
+
hide_top_spine: false
|
|
256
|
+
hide_right_spine: false
|
|
257
|
+
grid: false
|
|
258
|
+
|
|
259
|
+
theme:
|
|
260
|
+
mode: "light"
|
|
261
|
+
dark:
|
|
262
|
+
figure_bg: "#1e1e1e"
|
|
263
|
+
axes_bg: "#1e1e1e"
|
|
264
|
+
legend_bg: "#1e1e1e"
|
|
265
|
+
text: "#d4d4d4"
|
|
266
|
+
spine: "#3c3c3c"
|
|
267
|
+
tick: "#d4d4d4"
|
|
268
|
+
grid: "#3a3a3a"
|
|
269
|
+
light:
|
|
270
|
+
figure_bg: "white"
|
|
271
|
+
axes_bg: "white"
|
|
272
|
+
legend_bg: "white"
|
|
273
|
+
text: "black"
|
|
274
|
+
spine: "black"
|
|
275
|
+
tick: "black"
|
|
276
|
+
grid: "#cccccc"
|
|
277
|
+
|
|
278
|
+
# Standard matplotlib color cycle (tab10)
|
|
279
|
+
colors:
|
|
280
|
+
palette:
|
|
281
|
+
- [31, 119, 180] # tab:blue
|
|
282
|
+
- [255, 127, 14] # tab:orange
|
|
283
|
+
- [44, 160, 44] # tab:green
|
|
284
|
+
- [214, 39, 40] # tab:red
|
|
285
|
+
- [148, 103, 189] # tab:purple
|
|
286
|
+
- [140, 86, 75] # tab:brown
|
|
287
|
+
- [227, 119, 194] # tab:pink
|
|
288
|
+
- [127, 127, 127] # tab:gray
|
|
289
|
+
- [188, 189, 34] # tab:olive
|
|
290
|
+
- [23, 190, 207] # tab:cyan
|
|
291
|
+
|
|
292
|
+
# EOF
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The recipe is human-readable, version-controllable, and inspectable.
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
### API Overview
|
|
299
|
+
|
|
300
|
+
| Function | Description |
|
|
301
|
+
| ----------------------------- | --------------------------------- |
|
|
302
|
+
| `fr.subplots()` | Create a recording-enabled figure |
|
|
303
|
+
| `fr.save(fig, 'fig.png')` | Save image + recipe |
|
|
304
|
+
| `fr.reproduce('fig.yaml')` | Reproduce figure from recipe |
|
|
305
|
+
| `fr.extract_data('fig.yaml')` | Extract plotted data |
|
|
306
|
+
| `fr.info('fig.yaml')` | Inspect recipe metadata |
|
|
307
|
+
| `fr.load_style()` | Load style preset (global) |
|
|
308
|
+
| `fr.list_presets()` | List available presets |
|
|
309
|
+
| `fr.crop('fig.png')` | Crop to content with mm margin |
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
### Examples
|
|
313
|
+
- 📓 Interactive demo notebook:
|
|
314
|
+
examples/figrecipe_demo.ipynb
|
|
315
|
+
|
|
316
|
+
- 🌐 View on nbviewer:
|
|
317
|
+
https://nbviewer.org/github/ywatanabe1989/figrecipe/blob/main/examples/figrecipe_demo.ipynb
|
|
318
|
+
|
|
319
|
+
The notebook includes side-by-side comparisons of original and reproduced figures.
|
|
320
|
+
|
|
321
|
+
## Positioning
|
|
322
|
+
|
|
323
|
+
FigRecipe is intentionally minimal.
|
|
324
|
+
It focuses on recording, reproduction, and layout fidelity.
|
|
325
|
+
|
|
326
|
+
Higher-level workflows (figures, tables, statistics, GUIs) are handled in:
|
|
327
|
+
|
|
328
|
+
FTS (Figure-Table-Statistics Bundle) in SciTeX ecosystem (https://scitex.ai/vis/)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
## License
|
|
332
|
+
|
|
333
|
+
AGPL-3.0 See [LICENSE](LICENSE)
|
|
334
|
+
.
|
|
335
|
+
|
|
336
|
+
<!-- EOF -->
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
figrecipe/__init__.py,sha256=X9RnSndUi5ZC_JW6ReBjvYD84kjK8D4FNvB5QjekFg4,35296
|
|
2
|
+
figrecipe/_recorder.py,sha256=mPjhc6ZaUWrzW3lBxeB_epf-ESHLj05sKs_zeHkZ9_Y,14290
|
|
3
|
+
figrecipe/_reproducer.py,sha256=IQNYJ8OcvGRKNRLe561yqtmWgIJtzkx6okCes1xIHsk,9683
|
|
4
|
+
figrecipe/_seaborn.py,sha256=dRQgIbJG9Xeh1DYCDNrgaXgQe4MsnukEXFeEolWnfeo,8348
|
|
5
|
+
figrecipe/_serializer.py,sha256=OpWbCRSlhMuA2KInRXvze-g9V_d5ToLUIEmTj-jquYs,6267
|
|
6
|
+
figrecipe/_validator.py,sha256=dFZez0J4f8V9wUnrMaz_CV-TlndBDcHHbZO4mRDC6Po,5567
|
|
7
|
+
figrecipe/plt.py,sha256=GkURHdcupAVvpWKgTCC23jn3OjmKQmTY09A9wGMUdDQ,308
|
|
8
|
+
figrecipe/pyplot.py,sha256=xw-wFa6N7wJ-A3ThLbQvhafwySNIqD05u9UupksY7S4,5685
|
|
9
|
+
figrecipe/_signatures/__init__.py,sha256=twHKZ8o_9Ho4k0SD-cff72_aaWevK0noGlWav5fauHg,244
|
|
10
|
+
figrecipe/_signatures/_loader.py,sha256=HCrVzQNWqqt6qSr13G_A1461-v94g65usfIRJ4_zmJk,4744
|
|
11
|
+
figrecipe/_utils/__init__.py,sha256=usLkRQJ6oZJSUSzZWkb1x-lkAdHO9I1g7VinLf2kz24,815
|
|
12
|
+
figrecipe/_utils/_crop.py,sha256=YQgaYGI3gAFRF0zkQeUOlTRs9vsBmAK_24vooPPcPPc,8385
|
|
13
|
+
figrecipe/_utils/_diff.py,sha256=cjRXjMrfXFKz6uZQOZ-XYifc0mzmHZyRXn_LY13K1SY,2354
|
|
14
|
+
figrecipe/_utils/_image_diff.py,sha256=9648vx2f_K628YjCB-bhCOsRws4qqWuUk2l4uJlpdrc,5406
|
|
15
|
+
figrecipe/_utils/_numpy_io.py,sha256=leckngCnZKTvceM3RfUq1P9TA6z1SibfzWqlhKtOaxY,4533
|
|
16
|
+
figrecipe/_utils/_units.py,sha256=94e0MaJ27WkF7zDqNL-P7adbve3qWitPzWrCYOuDBe8,4628
|
|
17
|
+
figrecipe/_wrappers/__init__.py,sha256=CcBUID5G6Mo_bNGpCaz5vbfB8CG7XtF-9lOm7kShQPw,214
|
|
18
|
+
figrecipe/_wrappers/_axes.py,sha256=gLymbk5WYBdaJ8pVnSJH4X8SMs_foNJIDRN4pDeP6To,10659
|
|
19
|
+
figrecipe/_wrappers/_figure.py,sha256=ZSCH5jVgZbb9kgXyj_LumV52v5vspmRQ3N1KzY37CaQ,6491
|
|
20
|
+
figrecipe/styles/__init__.py,sha256=vGKtlJkUi06i0QC4qEw3ebUwpxTgablFUlyABSYr1Co,976
|
|
21
|
+
figrecipe/styles/_style_applier.py,sha256=f0iPzV5IF2yy94yirVYD9hd7op1EDYQqo8xcL2qBZVU,14238
|
|
22
|
+
figrecipe/styles/_style_loader.py,sha256=4LazXQzZRmRsWj3TvtDgCXP_Mp5nJca9bXmMH7LnsHw,13342
|
|
23
|
+
figrecipe-0.5.0.dist-info/METADATA,sha256=WbJp4fSSIse1uB_1c1ar2KL3ktYBVznKA_78fYFusFk,8924
|
|
24
|
+
figrecipe-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
25
|
+
figrecipe-0.5.0.dist-info/licenses/LICENSE,sha256=TfPDBt3ar0uv_f9cqCDMZ5rIzW3CY8anRRd4PkL6ejs,34522
|
|
26
|
+
figrecipe-0.5.0.dist-info/RECORD,,
|