PANDA / scripts /figures /merge_all_figures.py
bryan7264's picture
Correction pass: gate-matched Dahlin, retracted unsupported claims, complete HF-placode DEG set, restyled figures
141bacd verified
Raw
History Blame Contribute Delete
6.13 kB
"""merge every paper figure into one browsable pdf.
section-title pages divide main / supplement / biology; each figure gets
a small header page with its filename so a reader can find the source.
"""
from __future__ import annotations
from pathlib import Path
from pypdf import PdfWriter
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import os as _os
from pathlib import Path as _Path
PANDA_ROOT = _Path(_os.environ.get("PANDA_ROOT", str(_Path(__file__).resolve().parents[2])))
ROOT = Path(str(PANDA_ROOT))
FIG = ROOT / "figures"
OUT = FIG / "PANDA_all_figures.pdf"
TMP = FIG / "_tmp_titlepages.pdf"
# Labels are the numbers PAPER.tex actually resolves to (PAPER.aux), not the
# historical filenames: fig5_* is Figure 2 and fig6_* is Figure 5.
MAIN = [
("Figure 1", "fig1_perclass_f1.pdf", "Per-class held-out 5-fold F1 across three systems"),
("Figure 2", "fig5_dingwall_umap.pdf", "Dingwall UMAP (predicted class and En1 genotype)"),
("Figure 3", "fig3_dahlin_heatmap.pdf", "Dahlin Kit-W41 vs WT within-class pathway module deltas"),
("Figure 4", "fig4_veres_stage_stack.pdf", "Veres predicted class fraction per protocol stage"),
("Figure 5", "fig6_multi_umap.pdf", "Discovery-target UMAPs across skin / HSC / pancreas"),
]
SUPP = [
("S1", "supplement/01_cv_summary.pdf", "CV summary"),
("S2", "supplement/02_per_class_f1.pdf", "Per-class F1"),
("S3", "supplement/03_prototype_cosine.pdf", "Prototype intra-cosine"),
# S4 removed -- contained stale class vocab
("S5", "supplement/05_adversary_purification.pdf", "Adversary purification"),
("S6", "supplement/06_cross_system_prototypes.pdf", "Cross-system prototype map"),
# S7 removed -- contained stale class vocab
# S8 removed -- contained stale class vocab
# S9 removed -- contained stale class vocab
# S10 removed -- contained stale class vocab
("S11", "supplement/11_novel_populations.pdf", "Novel populations (legacy)"),
# S12 removed -- contained stale class vocab
("S13", "supplement/13_dingwall_umap.pdf", "Dingwall UMAP (legacy)"),
("S14", "supplement/14_dahlin_umap.pdf", "Dahlin UMAP (legacy)"),
("S15", "supplement/15_veres_umap.pdf", "Veres UMAP (legacy)"),
("S16", "supplement/16_dingwall_discovery.pdf", "Dingwall discovery (legacy)"),
("S17", "supplement/17_dahlin_discovery.pdf", "Dahlin discovery (legacy)"),
("S18", "supplement/18_veres_discovery.pdf", "Veres discovery (legacy)"),
("S19", "supplement/19_myeloid_network.pdf", "Myeloid gene-gene network"),
("S20", "supplement/20_placode_wnt_module.pdf", "Placode WNT module"),
("S23", "supplement/23_anchor_delta_recall.pdf", "Anchor delta recall"),
("S24", "supplement/24_pca_vs_marker_umaps_dingwall_by_genotype.pdf", "PCA vs Marker: Dingwall by genotype"),
("S24b","supplement/24b_pca_vs_marker_umaps_dingwall_by_class.pdf", "PCA vs Marker: Dingwall by class"),
("S25", "supplement/25_pca_vs_marker_umaps_dahlin_by_genotype.pdf", "PCA vs Marker: Dahlin by genotype"),
("S25b","supplement/25b_pca_vs_marker_umaps_dahlin_by_class.pdf", "PCA vs Marker: Dahlin by class"),
("S26", "supplement/26_pca_vs_marker_umaps_veres_by_stage.pdf", "PCA vs Marker: Veres by stage"),
("S26b","supplement/26b_pca_vs_marker_umaps_veres_by_class.pdf", "PCA vs Marker: Veres by class"),
("S27", "supplement/27_dingwall_en1_enrichment.pdf", "Dingwall En1 class enrichment"),
("S28", "supplement/28_melanocyte_pathway_modules.pdf", "Melanocyte pathway modules"),
]
BIO = [
("B1", "biology/biology_01_dingwall_umap.pdf", "Dingwall UMAP (biology)"),
("B2", "biology/biology_02_primary_eden.pdf", "Primary EDEN candidate: Derm2"),
("B3", "biology/biology_03_melanoblast_mitf.pdf", "Melanoblast MITF-axis quadrant"),
("B4", "biology/biology_04_dahlin_metabolism.pdf", "Dahlin per-lineage metabolism"),
("B5", "biology/biology_05_dahlin_composition.pdf", "Dahlin composition shift"),
("B6", "biology/biology_06_veres_beta_quadrant.pdf", "Veres beta-lineage quadrant"),
("B7", "biology/biology_07_veres_polyhormonal.pdf", "Veres polyhormonal SC-alpha"),
("B8", "biology/biology_08_prototype_geometry.pdf", "Prototype geometry"),
]
def make_title_page(text_top, text_body):
fig = plt.figure(figsize=(8.5, 11))
fig.text(0.5, 0.55, text_top, ha="center", va="center", fontsize=17, weight="bold")
fig.text(0.5, 0.45, text_body, ha="center", va="center", fontsize=12)
# NO bbox_inches="tight" here: it crops the page down to the text bbox,
# so the heading fills the sheet no matter what fontsize is set.
fig.savefig(TMP)
plt.close(fig)
def append_group(w, title, items):
make_title_page(title, "")
w.append(str(TMP))
for tag, path, desc in items:
p = FIG / path
if not p.exists():
print(f" [skip] {path} missing")
continue
make_title_page(tag, f"{desc}\n\n{path}")
w.append(str(TMP))
w.append(str(p))
print(f" + {tag} {p.name}")
def main():
w = PdfWriter()
make_title_page("PANDA — all figures", "main text · supplement · biology")
w.append(str(TMP))
append_group(w, "Main text", MAIN)
append_group(w, "Supplement", SUPP)
append_group(w, "Biology", BIO)
with open(OUT, "wb") as f:
w.write(f)
TMP.unlink(missing_ok=True)
print(f"\n[all] wrote {OUT} ({OUT.stat().st_size / 1024:.0f} KB, "
f"{len(w.pages)} pages)")
if __name__ == "__main__":
main()