File size: 3,772 Bytes
1ea7ba6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | """
figstyle.py β one shared, publication-grade figure design system.
Import this at the top of every plot script:
import figstyle
figstyle.apply()
...
figstyle.save(fig, "outputs/shortcut_matrix") # writes .pdf AND .png (600 dpi)
Design goals (Transactions/CEA-grade):
* Colorblind-safe (Okabe-Ito categorical + cividis sequential β both survive
grayscale printing, unlike the old red/green).
* Embedded TrueType fonts in PDF (pdf.fonttype=42) so no Type-3 AQC warnings.
* White background, uniform typography, vector PDF + 600-dpi PNG fallback.
Nothing here needs a GPU; it is pure matplotlib configuration.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
# ββ Okabe-Ito colorblind-safe categorical palette ββββββββββββββββββββββββββββ
OKABE_ITO = {
"black": "#000000",
"orange": "#E69F00",
"sky": "#56B4E9",
"green": "#009E73",
"yellow": "#F0E442",
"blue": "#0072B2",
"vermillion": "#D55E00",
"purple": "#CC79A7",
"grey": "#8C8C8C",
}
# ββ Semantic roles for THIS paper (kept consistent across every figure) βββββββ
# SS = Spice_Spectrum = in-the-wild (the robust/hero source)
# IN = Indian_Spices = studio
PALETTE = {
"wild": OKABE_ITO["blue"], # SS-trained / in-the-wild
"studio": OKABE_ITO["orange"], # Indian-trained / studio
"within": OKABE_ITO["green"], # within-source (good)
"cross_free": OKABE_ITO["sky"], # the free cross direction (wild->studio)
"cross_broken": OKABE_ITO["vermillion"], # the collapse (studio->wild)
"chance": OKABE_ITO["grey"], # random baseline line
}
# Sequential map for the accuracy heatmap: colorblind- AND grayscale-safe.
MATRIX_CMAP = "cividis"
def apply():
"""Set global rcParams. Call once at the top of a plot script."""
plt.rcParams.update({
"pdf.fonttype": 42, # embed TrueType (no Type-3)
"ps.fonttype": 42,
"svg.fonttype": "none",
"font.family": "sans-serif",
"font.sans-serif": ["DejaVu Sans", "Arial", "Helvetica"],
"font.size": 11,
"axes.titlesize": 12,
"axes.labelsize": 11,
"axes.labelweight": "bold",
"xtick.labelsize": 10,
"ytick.labelsize": 10,
"legend.fontsize": 9,
"figure.facecolor": "white",
"axes.facecolor": "white",
"savefig.facecolor": "white",
"axes.spines.top": False,
"axes.spines.right": False,
"axes.grid": False,
"figure.dpi": 120,
})
def save(fig, stem, dpi_png: int = 600, also_tiff: bool = False):
"""Save `fig` to `<stem>.pdf` (vector) and `<stem>.png` (600 dpi), white bg.
`stem` may include a directory; parents are created. Returns the list of paths.
"""
stem = Path(stem)
stem.parent.mkdir(parents=True, exist_ok=True)
out = []
pdf = stem.with_suffix(".pdf")
png = stem.with_suffix(".png")
fig.savefig(pdf, bbox_inches="tight", facecolor="white")
fig.savefig(png, dpi=dpi_png, bbox_inches="tight", facecolor="white")
out += [pdf, png]
if also_tiff:
tif = stem.with_suffix(".tiff")
fig.savefig(tif, dpi=300, bbox_inches="tight", facecolor="white",
pil_kwargs={"compression": "tiff_lzw"})
out.append(tif)
plt.close(fig)
for p in out:
print(f" saved {p}")
return out
|