sciglyph 0.1.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.
- sciglyph-0.1.0/LICENSE +21 -0
- sciglyph-0.1.0/PKG-INFO +187 -0
- sciglyph-0.1.0/README.md +166 -0
- sciglyph-0.1.0/pyproject.toml +29 -0
- sciglyph-0.1.0/sciglyph/__init__.py +35 -0
- sciglyph-0.1.0/sciglyph/_canvas.py +37 -0
- sciglyph-0.1.0/sciglyph/_nudge.py +58 -0
- sciglyph-0.1.0/sciglyph/arch.py +241 -0
- sciglyph-0.1.0/sciglyph/bio.py +193 -0
- sciglyph-0.1.0/sciglyph/layout.py +175 -0
- sciglyph-0.1.0/sciglyph.egg-info/PKG-INFO +187 -0
- sciglyph-0.1.0/sciglyph.egg-info/SOURCES.txt +15 -0
- sciglyph-0.1.0/sciglyph.egg-info/dependency_links.txt +1 -0
- sciglyph-0.1.0/sciglyph.egg-info/requires.txt +2 -0
- sciglyph-0.1.0/sciglyph.egg-info/top_level.txt +1 -0
- sciglyph-0.1.0/setup.cfg +4 -0
- sciglyph-0.1.0/tests/test_layout.py +117 -0
sciglyph-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guo Cheng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sciglyph-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sciglyph
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Publication-quality scientific illustration in pure matplotlib - no BioRender, no Illustrator
|
|
5
|
+
Author: Guo Cheng
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/GuoCheng24/sciglyph
|
|
8
|
+
Project-URL: Issues, https://github.com/GuoCheng24/sciglyph/issues
|
|
9
|
+
Keywords: matplotlib,scientific-figures,publication,illustration,graphical-abstract,bioinformatics,deep-learning,diagram
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: matplotlib>=3.5
|
|
19
|
+
Requires-Dist: numpy>=1.20
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# sciglyph
|
|
23
|
+
|
|
24
|
+
[](https://github.com/GuoCheng24/sciglyph/actions/workflows/test.yml) [](https://www.python.org/) [](LICENSE)
|
|
25
|
+
|
|
26
|
+
**Publication-quality scientific illustration in pure matplotlib — no BioRender, no Illustrator.**
|
|
27
|
+
|
|
28
|
+
Overview figures and architecture diagrams are usually drawn by hand in a
|
|
29
|
+
subscription tool. That makes them pretty, but also unreproducible: you cannot
|
|
30
|
+
diff them, you cannot regenerate them when the numbers change, and you cannot
|
|
31
|
+
put them under version control.
|
|
32
|
+
|
|
33
|
+
`sciglyph` gives you the primitives to draw the same figures as **code**.
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<img src="gallery/overview_figure.png" width="88%">
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="gallery/architecture.png" width="100%">
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<sub>Both figures above are generated by the scripts in
|
|
44
|
+
<a href="examples/">examples/</a> — nothing was touched by hand. The content is
|
|
45
|
+
synthetic; swap in your own numbers and the layout carries over.</sub>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
| | subscription tools | `sciglyph` |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| Reproducible | ✗ manual pixel-pushing | ✓ a script |
|
|
54
|
+
| Version control | ✗ binary blobs | ✓ diffable source |
|
|
55
|
+
| Data-driven | ✗ retype every number | ✓ read straight from your results |
|
|
56
|
+
| Vector output | ~ depends on export | ✓ PDF/SVG with editable text |
|
|
57
|
+
| Cost | subscription | free, MIT |
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install git+https://github.com/GuoCheng24/sciglyph
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Only `matplotlib` and `numpy`. Nothing else.
|
|
66
|
+
|
|
67
|
+
<sub>Not on PyPI yet, so the git URL above is the install line that works today.
|
|
68
|
+
When it lands, `pip install sciglyph` will too.</sub>
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import matplotlib.pyplot as plt
|
|
74
|
+
from sciglyph import bio, set_canvas, report, RC
|
|
75
|
+
|
|
76
|
+
plt.rcParams.update(RC)
|
|
77
|
+
fig = plt.figure(figsize=(7.2, 3.0), dpi=300)
|
|
78
|
+
ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
|
|
79
|
+
set_canvas(fig) # required on non-square canvases
|
|
80
|
+
|
|
81
|
+
bio.person(ax, .08, .55, s=.30)
|
|
82
|
+
bio.dna(ax, .25, .55, w=.05, h=.45, n=2)
|
|
83
|
+
bio.cell(ax, .42, .55, r=.06, seed=1)
|
|
84
|
+
bio.seq_logo(ax, .60, .40, [("A", .6), ("C", .9), ("G", .4), ("T", .7)], w=.03)
|
|
85
|
+
|
|
86
|
+
report(fig, ax) # catch text collisions before saving
|
|
87
|
+
fig.savefig("figure.pdf", bbox_inches="tight")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Run the full examples:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python examples/overview_figure.py # -> gallery/overview_figure.png
|
|
94
|
+
python examples/architecture.py # -> gallery/architecture.png
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## What's included
|
|
98
|
+
|
|
99
|
+
**`sciglyph.bio`** — glyphs for Nature/Science-style overview figures:
|
|
100
|
+
`person` (cohorts) · `dna` · `cell` · `lipid` · `metabolite` ·
|
|
101
|
+
`nucleosome_chain` · `umap_layer` (the stacked atlas look) ·
|
|
102
|
+
`seq_logo` (information-scaled letters, no logomaker needed) ·
|
|
103
|
+
`stacked_planes` · `rbox` · `arr`
|
|
104
|
+
|
|
105
|
+
**`sciglyph.arch`** — glyphs for architecture diagrams:
|
|
106
|
+
`cuboid` / `feature_stack` (3-D feature blocks) · `trapezoid` (encoders) ·
|
|
107
|
+
`module_stack` (`Conv|BN|ReLU` bars) · `dashed_group` (the `(a)/(b)/(c)`
|
|
108
|
+
language) · `flow` · `op_circle` · `snowflake` (frozen backbone) ·
|
|
109
|
+
`image_thumb` · `embedding_space` (contrastive panels) · `loss_tag` · `bracket`
|
|
110
|
+
|
|
111
|
+
**`sciglyph.layout`** — pre-flight collision detection.
|
|
112
|
+
|
|
113
|
+
## Catching layout bugs before you save
|
|
114
|
+
|
|
115
|
+
When a figure breaks, it is almost never the artwork — it is the layout.
|
|
116
|
+
`report()` uses the real rendered bounding boxes to find overlapping text, so
|
|
117
|
+
you do not have to hunt for it by eye:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
report(fig, ax)
|
|
121
|
+
# [sciglyph.layout] 36 text objects
|
|
122
|
+
# ! 'CD4 Treg/-FOXP3' x 'SMR' overlap 92%
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
It also works from the command line on any script that exposes `fig` and `ax`:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
python -m sciglyph.layout my_figure.py
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
It checks three things, each of which shipped a broken figure before it existed:
|
|
132
|
+
|
|
133
|
+
| check | what it catches |
|
|
134
|
+
|---|---|
|
|
135
|
+
| text overlap | two labels drawn over each other |
|
|
136
|
+
| **artwork overlap** | a row of boxes laid out slightly too wide, so each one covers its neighbour — the strings may not overlap at all, so text-level checks miss it entirely |
|
|
137
|
+
| **missing glyphs** | a character the font cannot draw, rendered as an empty box. Symbols typed as literals (`✓`, `❄`) are the usual casualty |
|
|
138
|
+
|
|
139
|
+
Two kinds of overlap are deliberately *not* reported, because they are the
|
|
140
|
+
layout working: a panel containing its contents, and an unfilled dashed shape —
|
|
141
|
+
a ring drawn around the thing it annotates.
|
|
142
|
+
|
|
143
|
+
**These are geometric checks.** Whether the figure actually *reads* well still
|
|
144
|
+
needs your eyes.
|
|
145
|
+
|
|
146
|
+
## Notes from actually shipping these figures
|
|
147
|
+
|
|
148
|
+
- **Call `set_canvas(fig)`.** In `[0,1]` coordinates a "circle" is `r·W` wide
|
|
149
|
+
and `r·H` tall. On a 12×3 canvas, every circle becomes a rugby ball.
|
|
150
|
+
- **Anchor arrows to what `feature_stack` returns**, not to hard-coded
|
|
151
|
+
coordinates — otherwise changing the number of blocks silently breaks them.
|
|
152
|
+
- **Never put symbol codepoints in figure text.** `❄` (U+2744) is missing from
|
|
153
|
+
most sans fonts and renders as a tofu box. Draw it (`arch.snowflake`).
|
|
154
|
+
- **Overlapping translucent fills blend into one muddy colour.** Keep the fill
|
|
155
|
+
under `alpha=0.15`, stroke each curve, *and* offset the peaks. Tuning alpha
|
|
156
|
+
alone will not save you.
|
|
157
|
+
- **Fonts:** Arial/Helvetica are often absent on Linux. `RC` falls back to
|
|
158
|
+
Liberation Sans (metric-compatible with Arial) and sets `pdf.fonttype=42`
|
|
159
|
+
so text stays editable in the PDF — a hard requirement at most journals.
|
|
160
|
+
- **Don't move elements toward whitespace.** Whitespace relocates, it does not
|
|
161
|
+
disappear. Decide which row an element belongs to, move it as a group, then
|
|
162
|
+
verify with the quadrant ink distribution.
|
|
163
|
+
|
|
164
|
+
## Honest scope
|
|
165
|
+
|
|
166
|
+
This gets you clean flat schematics combined with data panels — the register
|
|
167
|
+
of a Nature/Science overview figure or a TPAMI architecture diagram. It will
|
|
168
|
+
**not** reproduce hand-drawn illustration (shaded organs, textured cells,
|
|
169
|
+
gradients). For that, embed a CC-BY asset and cite it rather than fake it.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT © Guo Cheng
|
|
174
|
+
|
|
175
|
+
## 关于那行 star 提示
|
|
176
|
+
|
|
177
|
+
调用 `report()` 时,`sciglyph` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
|
|
178
|
+
|
|
179
|
+
它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
|
|
180
|
+
|
|
181
|
+
永久关掉:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
export SCIGLYPH_NO_NUDGE=1
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
计数存在 `$XDG_STATE_HOME/sciglyph/usage.json`(默认 `~/.local/state/sciglyph/usage.json`),删掉即重置。
|
sciglyph-0.1.0/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# sciglyph
|
|
2
|
+
|
|
3
|
+
[](https://github.com/GuoCheng24/sciglyph/actions/workflows/test.yml) [](https://www.python.org/) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
**Publication-quality scientific illustration in pure matplotlib — no BioRender, no Illustrator.**
|
|
6
|
+
|
|
7
|
+
Overview figures and architecture diagrams are usually drawn by hand in a
|
|
8
|
+
subscription tool. That makes them pretty, but also unreproducible: you cannot
|
|
9
|
+
diff them, you cannot regenerate them when the numbers change, and you cannot
|
|
10
|
+
put them under version control.
|
|
11
|
+
|
|
12
|
+
`sciglyph` gives you the primitives to draw the same figures as **code**.
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="gallery/overview_figure.png" width="88%">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="gallery/architecture.png" width="100%">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<sub>Both figures above are generated by the scripts in
|
|
23
|
+
<a href="examples/">examples/</a> — nothing was touched by hand. The content is
|
|
24
|
+
synthetic; swap in your own numbers and the layout carries over.</sub>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Why
|
|
29
|
+
|
|
30
|
+
| | subscription tools | `sciglyph` |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| Reproducible | ✗ manual pixel-pushing | ✓ a script |
|
|
33
|
+
| Version control | ✗ binary blobs | ✓ diffable source |
|
|
34
|
+
| Data-driven | ✗ retype every number | ✓ read straight from your results |
|
|
35
|
+
| Vector output | ~ depends on export | ✓ PDF/SVG with editable text |
|
|
36
|
+
| Cost | subscription | free, MIT |
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install git+https://github.com/GuoCheng24/sciglyph
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Only `matplotlib` and `numpy`. Nothing else.
|
|
45
|
+
|
|
46
|
+
<sub>Not on PyPI yet, so the git URL above is the install line that works today.
|
|
47
|
+
When it lands, `pip install sciglyph` will too.</sub>
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import matplotlib.pyplot as plt
|
|
53
|
+
from sciglyph import bio, set_canvas, report, RC
|
|
54
|
+
|
|
55
|
+
plt.rcParams.update(RC)
|
|
56
|
+
fig = plt.figure(figsize=(7.2, 3.0), dpi=300)
|
|
57
|
+
ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
|
|
58
|
+
set_canvas(fig) # required on non-square canvases
|
|
59
|
+
|
|
60
|
+
bio.person(ax, .08, .55, s=.30)
|
|
61
|
+
bio.dna(ax, .25, .55, w=.05, h=.45, n=2)
|
|
62
|
+
bio.cell(ax, .42, .55, r=.06, seed=1)
|
|
63
|
+
bio.seq_logo(ax, .60, .40, [("A", .6), ("C", .9), ("G", .4), ("T", .7)], w=.03)
|
|
64
|
+
|
|
65
|
+
report(fig, ax) # catch text collisions before saving
|
|
66
|
+
fig.savefig("figure.pdf", bbox_inches="tight")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Run the full examples:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python examples/overview_figure.py # -> gallery/overview_figure.png
|
|
73
|
+
python examples/architecture.py # -> gallery/architecture.png
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## What's included
|
|
77
|
+
|
|
78
|
+
**`sciglyph.bio`** — glyphs for Nature/Science-style overview figures:
|
|
79
|
+
`person` (cohorts) · `dna` · `cell` · `lipid` · `metabolite` ·
|
|
80
|
+
`nucleosome_chain` · `umap_layer` (the stacked atlas look) ·
|
|
81
|
+
`seq_logo` (information-scaled letters, no logomaker needed) ·
|
|
82
|
+
`stacked_planes` · `rbox` · `arr`
|
|
83
|
+
|
|
84
|
+
**`sciglyph.arch`** — glyphs for architecture diagrams:
|
|
85
|
+
`cuboid` / `feature_stack` (3-D feature blocks) · `trapezoid` (encoders) ·
|
|
86
|
+
`module_stack` (`Conv|BN|ReLU` bars) · `dashed_group` (the `(a)/(b)/(c)`
|
|
87
|
+
language) · `flow` · `op_circle` · `snowflake` (frozen backbone) ·
|
|
88
|
+
`image_thumb` · `embedding_space` (contrastive panels) · `loss_tag` · `bracket`
|
|
89
|
+
|
|
90
|
+
**`sciglyph.layout`** — pre-flight collision detection.
|
|
91
|
+
|
|
92
|
+
## Catching layout bugs before you save
|
|
93
|
+
|
|
94
|
+
When a figure breaks, it is almost never the artwork — it is the layout.
|
|
95
|
+
`report()` uses the real rendered bounding boxes to find overlapping text, so
|
|
96
|
+
you do not have to hunt for it by eye:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
report(fig, ax)
|
|
100
|
+
# [sciglyph.layout] 36 text objects
|
|
101
|
+
# ! 'CD4 Treg/-FOXP3' x 'SMR' overlap 92%
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
It also works from the command line on any script that exposes `fig` and `ax`:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
python -m sciglyph.layout my_figure.py
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
It checks three things, each of which shipped a broken figure before it existed:
|
|
111
|
+
|
|
112
|
+
| check | what it catches |
|
|
113
|
+
|---|---|
|
|
114
|
+
| text overlap | two labels drawn over each other |
|
|
115
|
+
| **artwork overlap** | a row of boxes laid out slightly too wide, so each one covers its neighbour — the strings may not overlap at all, so text-level checks miss it entirely |
|
|
116
|
+
| **missing glyphs** | a character the font cannot draw, rendered as an empty box. Symbols typed as literals (`✓`, `❄`) are the usual casualty |
|
|
117
|
+
|
|
118
|
+
Two kinds of overlap are deliberately *not* reported, because they are the
|
|
119
|
+
layout working: a panel containing its contents, and an unfilled dashed shape —
|
|
120
|
+
a ring drawn around the thing it annotates.
|
|
121
|
+
|
|
122
|
+
**These are geometric checks.** Whether the figure actually *reads* well still
|
|
123
|
+
needs your eyes.
|
|
124
|
+
|
|
125
|
+
## Notes from actually shipping these figures
|
|
126
|
+
|
|
127
|
+
- **Call `set_canvas(fig)`.** In `[0,1]` coordinates a "circle" is `r·W` wide
|
|
128
|
+
and `r·H` tall. On a 12×3 canvas, every circle becomes a rugby ball.
|
|
129
|
+
- **Anchor arrows to what `feature_stack` returns**, not to hard-coded
|
|
130
|
+
coordinates — otherwise changing the number of blocks silently breaks them.
|
|
131
|
+
- **Never put symbol codepoints in figure text.** `❄` (U+2744) is missing from
|
|
132
|
+
most sans fonts and renders as a tofu box. Draw it (`arch.snowflake`).
|
|
133
|
+
- **Overlapping translucent fills blend into one muddy colour.** Keep the fill
|
|
134
|
+
under `alpha=0.15`, stroke each curve, *and* offset the peaks. Tuning alpha
|
|
135
|
+
alone will not save you.
|
|
136
|
+
- **Fonts:** Arial/Helvetica are often absent on Linux. `RC` falls back to
|
|
137
|
+
Liberation Sans (metric-compatible with Arial) and sets `pdf.fonttype=42`
|
|
138
|
+
so text stays editable in the PDF — a hard requirement at most journals.
|
|
139
|
+
- **Don't move elements toward whitespace.** Whitespace relocates, it does not
|
|
140
|
+
disappear. Decide which row an element belongs to, move it as a group, then
|
|
141
|
+
verify with the quadrant ink distribution.
|
|
142
|
+
|
|
143
|
+
## Honest scope
|
|
144
|
+
|
|
145
|
+
This gets you clean flat schematics combined with data panels — the register
|
|
146
|
+
of a Nature/Science overview figure or a TPAMI architecture diagram. It will
|
|
147
|
+
**not** reproduce hand-drawn illustration (shaded organs, textured cells,
|
|
148
|
+
gradients). For that, embed a CC-BY asset and cite it rather than fake it.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT © Guo Cheng
|
|
153
|
+
|
|
154
|
+
## 关于那行 star 提示
|
|
155
|
+
|
|
156
|
+
调用 `report()` 时,`sciglyph` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
|
|
157
|
+
|
|
158
|
+
它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
|
|
159
|
+
|
|
160
|
+
永久关掉:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
export SCIGLYPH_NO_NUDGE=1
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
计数存在 `$XDG_STATE_HOME/sciglyph/usage.json`(默认 `~/.local/state/sciglyph/usage.json`),删掉即重置。
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sciglyph"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Publication-quality scientific illustration in pure matplotlib - no BioRender, no Illustrator"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Guo Cheng" }]
|
|
13
|
+
keywords = ["matplotlib", "scientific-figures", "publication", "illustration",
|
|
14
|
+
"graphical-abstract", "bioinformatics", "deep-learning", "diagram"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["matplotlib>=3.5", "numpy>=1.20"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/GuoCheng24/sciglyph"
|
|
26
|
+
Issues = "https://github.com/GuoCheng24/sciglyph/issues"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
include = ["sciglyph*"]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""sciglyph - publication-quality scientific illustration in pure matplotlib.
|
|
2
|
+
|
|
3
|
+
Two glyph families plus a layout checker:
|
|
4
|
+
|
|
5
|
+
* :mod:`sciglyph.bio` biological / omics glyphs for Nature-style overview figures
|
|
6
|
+
* :mod:`sciglyph.arch` neural-network architecture glyphs (TPAMI / CVPR style)
|
|
7
|
+
* :mod:`sciglyph.layout` pre-flight text-collision detection
|
|
8
|
+
|
|
9
|
+
Quick start::
|
|
10
|
+
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
from sciglyph import bio, set_canvas, RC, report
|
|
13
|
+
|
|
14
|
+
plt.rcParams.update(RC)
|
|
15
|
+
fig = plt.figure(figsize=(7.2, 4.0), dpi=300)
|
|
16
|
+
ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 1); ax.set_ylim(0, 1); ax.axis("off")
|
|
17
|
+
set_canvas(fig) # required on non-square canvases
|
|
18
|
+
|
|
19
|
+
bio.dna(ax, .2, .5, w=.05, h=.4, n=2)
|
|
20
|
+
bio.cell(ax, .4, .5, r=.05, seed=1)
|
|
21
|
+
|
|
22
|
+
report(fig, ax) # check for text collisions
|
|
23
|
+
fig.savefig("figure.pdf", bbox_inches="tight")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from ._canvas import set_canvas, aspect
|
|
27
|
+
from .layout import text_collisions, report
|
|
28
|
+
from . import bio
|
|
29
|
+
from . import arch
|
|
30
|
+
|
|
31
|
+
RC = bio.RC
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.0"
|
|
34
|
+
__all__ = ["bio", "arch", "layout", "set_canvas", "aspect",
|
|
35
|
+
"text_collisions", "report", "RC", "__version__"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Shared canvas state: aspect-ratio compensation for round primitives.
|
|
2
|
+
|
|
3
|
+
In a [0,1] x [0,1] coordinate system a "circle" of radius r is physically
|
|
4
|
+
r*W wide and r*H tall, where W and H are the figure size in inches. On any
|
|
5
|
+
non-square canvas circles therefore render as ellipses -- measured on a 6x2
|
|
6
|
+
canvas, human heads and cells came out badly flattened.
|
|
7
|
+
|
|
8
|
+
Every round primitive in sciglyph divides its horizontal extent by this ratio,
|
|
9
|
+
so you get visually round shapes on any canvas. Call `set_canvas(fig)` once,
|
|
10
|
+
right after creating the figure.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from matplotlib.patches import Ellipse
|
|
14
|
+
|
|
15
|
+
__all__ = ["set_canvas", "aspect", "circle"]
|
|
16
|
+
|
|
17
|
+
_STATE = {"ar": 1.0}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_canvas(fig):
|
|
21
|
+
"""Register the figure aspect ratio. **Required on any non-square canvas.**
|
|
22
|
+
|
|
23
|
+
Returns the ratio (width / height), mostly so you can assert on it.
|
|
24
|
+
"""
|
|
25
|
+
w, h = fig.get_size_inches()
|
|
26
|
+
_STATE["ar"] = float(w) / float(h)
|
|
27
|
+
return _STATE["ar"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def aspect():
|
|
31
|
+
"""Current width/height ratio (1.0 until `set_canvas` is called)."""
|
|
32
|
+
return _STATE["ar"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def circle(ax, xy, r, **kw):
|
|
36
|
+
"""A visually round circle, corrected for the canvas aspect ratio."""
|
|
37
|
+
return ax.add_patch(Ellipse(xy, width=2 * r / _STATE["ar"], height=2 * r, **kw))
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Mention the repo once or twice, to people who are actually using this.
|
|
2
|
+
|
|
3
|
+
Deliberately quiet: never on the first run, never when stderr is not a
|
|
4
|
+
terminal (so piped and redirected output stays clean), never in CI, and
|
|
5
|
+
never more than twice in the lifetime of an install. `SCIGLYPH_NO_NUDGE=1`
|
|
6
|
+
turns it off for good.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
REPO = "GuoCheng24/sciglyph"
|
|
14
|
+
_SHOW_AT = (5, 25) # run counts at which we say something
|
|
15
|
+
_ENV_OFF = "SCIGLYPH_NO_NUDGE"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _state_path():
|
|
19
|
+
base = os.environ.get("XDG_STATE_HOME") or (Path.home() / ".local" / "state")
|
|
20
|
+
return Path(base) / "sciglyph" / "usage.json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _quiet():
|
|
24
|
+
if os.environ.get(_ENV_OFF):
|
|
25
|
+
return True
|
|
26
|
+
if os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"):
|
|
27
|
+
return True
|
|
28
|
+
# Not a terminal means someone is piping or redirecting us; stay out of it.
|
|
29
|
+
return not (hasattr(sys.stderr, "isatty") and sys.stderr.isatty())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def record_run():
|
|
33
|
+
"""Count this run and, at two points, print a single line to stderr.
|
|
34
|
+
|
|
35
|
+
Any failure here is swallowed: a nudge must never break the tool or
|
|
36
|
+
change its exit status.
|
|
37
|
+
"""
|
|
38
|
+
if _quiet():
|
|
39
|
+
return
|
|
40
|
+
try:
|
|
41
|
+
p = _state_path()
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(p.read_text())
|
|
44
|
+
except Exception:
|
|
45
|
+
data = {}
|
|
46
|
+
n = int(data.get("runs", 0)) + 1
|
|
47
|
+
data["runs"] = n
|
|
48
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
p.write_text(json.dumps(data))
|
|
50
|
+
if n in _SHOW_AT:
|
|
51
|
+
print(
|
|
52
|
+
"\n── sciglyph has been useful " + str(n) + " times. If it saved you time,\n"
|
|
53
|
+
" a star helps other people find it: https://github.com/" + REPO + "\n"
|
|
54
|
+
" (silence this with SCIGLYPH_NO_NUDGE=1)",
|
|
55
|
+
file=sys.stderr,
|
|
56
|
+
)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|