Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified paper_figures/fig0_graphical_abstract.pdf
Binary file not shown.
Binary file modified paper_figures/fig0_graphical_abstract.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified paper_figures/fig1_interface.pdf
Binary file not shown.
Binary file modified paper_figures/fig1_interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified paper_figures/fig2_learning_cycle.pdf
Binary file not shown.
Binary file modified paper_figures/fig3_landmarks.pdf
Binary file not shown.
Binary file modified paper_figures/fig4_qc_catch.pdf
Binary file not shown.
Binary file modified paper_figures/fig5_difficulty.pdf
Binary file not shown.
Binary file modified paper_figures/fig5_difficulty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified paper_figures/fig6_two_difficulties.pdf
Binary file not shown.
Binary file modified paper_figures/ga_crop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified paper_figures/ss_learn_hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 16 additions & 59 deletions scripts/make_figures.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,10 +365,10 @@ def fig5_difficulty():
for thr, lab in ((0.5, ""), (1.0, ""), (2.0, "")):
axA.axvline(thr, color="#c8c8c8", ls=":", lw=0.9, zorder=0)
axA.set_yticks(y)
axA.set_yticklabels([a for a, _ in items], fontsize=5.6)
axA.set_yticklabels([a for a, _ in items], fontsize=8)
axA.set_ylim(-0.7, len(items) - 0.3)
axA.set_xlim(0, 2.6)
axA.set_xlabel("Trained-rater localization error, AFLE (mm)", fontsize=8.5)
axA.set_xlabel("Trained-rater localization error, AFLE (mm)", fontsize=11)
axA.plot([], [], "o", color="#555", ms=5.5, label="median")
axA.plot(
[], [], "x", color="#555", ms=4.5, mew=1.1, label="mean (heavy tail)"
Expand All @@ -377,9 +377,9 @@ def fig5_difficulty():
[], [], "-", color="#999", lw=2.6, alpha=0.6, label="IQR (p25–p75)"
)
axA.legend(
fontsize=6.3, loc="lower right", frameon=False, handletextpad=0.4
fontsize=9, loc="lower right", frameon=False, handletextpad=0.4
)
panel_letter(axA, "A", "A 3.9× difficulty spectrum across 32 landmarks")
panel_letter(axA, "A", "")

# Panel B — the same 1.2 mm placement, two verdicts
axB = fig.add_subplot(gs[0, 1])
Expand Down Expand Up @@ -417,40 +417,13 @@ def fig5_difficulty():
markeredgecolor="white",
markeredgewidth=0.8,
)
xs, ys = _pctile_curve(s)
pct = float(np.interp(probe, xs, ys)) # error rank (drives the verdict)
p = round(100 - pct) # precision pct shown (higher = better)
suf = ("th" if 11 <= p % 100 <= 13
else {1: "st", 2: "nd", 3: "rd"}.get(p % 10, "th"))
verdict = (
f"within range\n(≈{p}{suf} pct, near median)"
if pct < 75
else f"outside expert range\n(≈{p}{suf} pct)"
)
axB.annotate(
f"{a}: {verdict}",
(probe, yi + 0.22),
fontsize=7.2,
ha="center",
va="bottom",
color=c,
fontweight="bold",
)
axB.axvline(probe, color="#111111", ls="--", lw=1.3, zorder=5)
axB.annotate(
"one placement:\n1.2 mm from target",
(probe, -0.62),
fontsize=7.4,
ha="center",
va="top",
color="#111111",
)
axB.set_yticks([0, 1])
axB.set_yticklabels(["LIGO\n(hard)", "AC\n(easy)"], fontsize=7.5)
axB.set_yticklabels(["LIGO\n(hard)", "AC\n(easy)"], fontsize=10.5)
axB.set_ylim(-1.05, 1.7)
axB.set_xlim(0, 4.0)
axB.set_xlabel("Trained-rater AFLE distribution (mm)", fontsize=8.5)
panel_letter(axB, "B", "The same 1.2 mm — two verdicts")
axB.set_xlabel("Trained-rater AFLE distribution (mm)", fontsize=11)
panel_letter(axB, "B", "")

# Panel C — the mm→percentile calibration the platform applies
axC = fig.add_subplot(gs[0, 2])
Expand Down Expand Up @@ -480,17 +453,16 @@ def fig5_difficulty():
axC.axhline(50, color="#dddddd", lw=0.8, zorder=0)
axC.set_xlim(0, 4)
axC.set_ylim(0, 100)
axC.set_xlabel("Placement error (mm)", fontsize=8.5)
axC.set_ylabel("Precision percentile (vs trained raters)", fontsize=8.5)
axC.set_xlabel("Placement error (mm)", fontsize=11)
axC.set_ylabel("Precision percentile (vs trained raters)", fontsize=11)
axC.legend(
fontsize=7,
fontsize=9,
loc="lower right",
frameon=False,
title="landmark",
title_fontsize=7,
title_fontsize=9,
)
axC.annotate("1.2 mm", (1.2, 4), fontsize=6.6, color="#111111", ha="left")
panel_letter(axC, "C", "One error, calibrated per landmark")
panel_letter(axC, "C", "")

_save(fig, "fig5_difficulty")

Expand Down Expand Up @@ -872,23 +844,6 @@ def fig1_interface():
color="black",
zorder=6,
)
legend = (
"\u2460 Multiplanar MRI viewer (NiiVue) "
"\u2461 Rater-calibrated result: error + percentile vs raters\n"
"\u2462 Difficulty-aware, anatomy-first AI tutor "
"\u2463 Bring-your-own-key + active model "
"\u2464 32-landmark progress + export"
)
fig.text(
0.5,
0.05,
legend,
ha="center",
va="center",
fontsize=10.5,
color="#222222",
linespacing=1.9,
)
_save(fig, "fig1_interface")


Expand Down Expand Up @@ -1017,9 +972,11 @@ def fig0_graphical_abstract():
figure set still regenerates from ``python make_figures.py``.
"""
import runpy
from pathlib import Path as _P

runpy.run_path("make_ga_assets.py", run_name="__main__")
runpy.run_path("make_graphical_abstract.py", run_name="__main__")
here = _P(__file__).resolve().parent
runpy.run_path(str(here / "make_ga_assets.py"), run_name="__main__")
runpy.run_path(str(here / "make_graphical_abstract.py"), run_name="__main__")


if __name__ == "__main__":
Expand Down
84 changes: 3 additions & 81 deletions scripts/make_graphical_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,50 +527,6 @@ def ic_file(cx, cy, s, color):
)


def ic_chat(cx, cy, s, color):
w, h = s * 1.7, s * 1.25
rrect(
cx - w / 2,
cy - h / 2 + 0.12,
w,
h,
fc="white",
ec=color,
lw=1.6,
r=0.14,
z=7,
)
ax.add_patch(
Polygon(
[
(cx - s * 0.2, cy - h / 2 + 0.18),
(cx - s * 0.55, cy - h / 2 - 0.14),
(cx + s * 0.12, cy - h / 2 + 0.18),
],
closed=True,
fc="white",
ec=color,
lw=1.6,
zorder=7,
joinstyle="round",
)
)
ax.add_patch(
Rectangle(
(cx - s * 0.5, cy - h / 2 + 0.14),
s * 0.7,
0.1,
fc="white",
ec="none",
zorder=7.15,
)
)
for dx in (-0.42, 0, 0.42):
ax.add_patch(
Circle((cx + dx * s, cy + 0.14), s * 0.11, fc=color, zorder=7.2)
)


def ic_gear(cx, cy, s, color):
n, outer, inner = 8, s, s * 0.66
pts = [
Expand Down Expand Up @@ -1159,50 +1115,17 @@ def data_row(y, nb, count, label, color):
)

# ══════════════════════════════════════════════════════════════════════════════
# THE METRIC — a slim shared header spanning both product cards
# (the "same mm, two verdicts" proof lives inside the cards themselves)
# (metric band removed)
# ══════════════════════════════════════════════════════════════════════════════
_thesis = "One Euclidean error — two uses"
_ty = BR_y0 + 0.40
txt(SEAM, _ty, _thesis, size=11.5, weight="bold", color=SLATE, ha="center")
txt(
SEAM,
BR_y0 + 0.17,
"the same millimetre, always read against how hard the landmark is",
size=8.2,
color=MUT,
ha="center",
style="italic",
)
# quiet colour cue mapping the shared metric onto each card, no heavy arrows
txt(
M + 0.30,
BR_y0 + 0.13,
"taught in context",
size=7.8,
color=LEARN_C,
weight="bold",
ha="left",
)
txt(
XR - 0.30,
BR_y0 + 0.13,
"audited at scale",
size=7.8,
color=VALID_C,
weight="bold",
ha="right",
)

# ══════════════════════════════════════════════════════════════════════════════
# 03 — LEARN (left card)
# ══════════════════════════════════════════════════════════════════════════════
LX0 = M
LX1 = M + CARDW
card("LEARN", LX0, P_y0, CARDW, H_PROD, LEARN_C)
ic_chat(LX0 + 0.42, P_y1 - 0.38, 0.18, LEARN_C)
section(
LX0 + 0.78,
LX0 + 0.42,
P_y1 - 0.38,
"02",
LEARN_C,
Expand Down Expand Up @@ -1345,9 +1268,8 @@ def data_row(y, nb, count, label, color):
VX0 = M + CARDW + GAP
VX1 = XR
card("VALIDATE", VX0, P_y0, CARDW, H_PROD, VALID_C)
ic_gear(VX0 + 0.42, P_y1 - 0.38, 0.17, VALID_C)
section(
VX0 + 0.78,
VX0 + 0.42,
P_y1 - 0.38,
"03",
VALID_C,
Expand Down
Loading