snr_bias / code /scripts /grl_make_design_figure.py
cangyeone's picture
Upload GRL reproducibility package
7170296 verified
Raw
History Blame Contribute Delete
3.78 kB
#!/usr/bin/env python3
"""Create the matched SNR filtering design figure for the GRL manuscript."""
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
import matplotlib.pyplot as plt
ROOT = Path(__file__).resolve().parents[1]
PHASE_SUMMARY = ROOT / "outputs" / "snr_transfer_seed20260609" / "summary.json"
DISP_SUMMARY = ROOT / "outputs" / "disp_snr_transfer_seed20260609" / "summary.json"
DISP_SNR_CACHE = ROOT / "outputs" / "disp_snr_transfer_seed20260609" / "ncf_snr_cache.json"
DEFAULT_OUTPUT = ROOT / "grl_overleaf" / "figures" / "snr_matched_design.png"
def read_json(path: Path) -> dict:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def dispersion_candidate_counts(summary: dict) -> list[int]:
cache = read_json(DISP_SNR_CACHE)
q1 = summary["snr_thresholds"]["q1"]
q2 = summary["snr_thresholds"]["q2"]
train_snr = [
item["snr_db"]
for item in cache.values()
if item.get("split") == "train" and math.isfinite(float(item.get("snr_db", float("nan"))))
]
return [
len(train_snr),
sum(float(value) > q1 for value in train_snr),
sum(float(value) > q2 for value in train_snr),
]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
args = parser.parse_args()
phase = read_json(PHASE_SUMMARY)
disp = read_json(DISP_SUMMARY)
phase_candidate = [
phase["candidate_train_records"]["full"],
phase["candidate_train_records"]["snr5"],
phase["candidate_train_records"]["snr10"],
]
phase_matched = [row["train_records"] for row in phase["rows"]]
disp_matched = [row["train_records"] for row in disp["rows"]]
disp_candidate = dispersion_candidate_counts(disp)
labels = ["Full", "SNR > 5", "SNR > 10"]
disp_labels = ["Full", "SNR > 3.04", "SNR > 6.77"]
colors = ["#2f6f73", "#c47f2b", "#8f4d6b"]
fig, axes = plt.subplots(1, 2, figsize=(10.8, 4.6), dpi=220)
panels = [
(axes[0], "A", "Phase picking", labels, phase_candidate, phase_matched, "records"),
(axes[1], "B", "Dispersion", disp_labels, disp_candidate, disp_matched, "samples"),
]
for ax, panel_label, title, xlabels, candidate, matched, unit in panels:
x = range(len(xlabels))
ax.bar(x, candidate, width=0.62, color="#d7d7d7", edgecolor="#555555", label="candidate pool")
ax.bar(x, matched, width=0.38, color=colors, edgecolor="#222222", label="matched training")
for idx, value in enumerate(candidate):
ax.text(idx, value * 1.02, f"{value:,}", ha="center", va="bottom", fontsize=7)
for idx, value in enumerate(matched):
ax.text(idx, value * 0.50, f"{value:,}", ha="center", va="center", fontsize=8, color="white")
ax.set_title(title, fontsize=11)
ax.set_xticks(list(x))
ax.set_xticklabels(xlabels, fontsize=8)
ax.set_ylabel(f"training {unit}")
ax.spines[["top", "right"]].set_visible(False)
ax.grid(axis="y", color="#eeeeee", linewidth=0.8)
ax.set_axisbelow(True)
ax.text(
-0.12,
1.08,
panel_label,
transform=ax.transAxes,
fontsize=13,
fontweight="bold",
va="top",
)
axes[0].legend(frameon=False, loc="upper right", fontsize=8)
fig.suptitle("Matched training budgets isolate the effect of SNR filtering", fontsize=13, y=0.98)
fig.tight_layout(rect=(0, 0, 1, 0.94))
args.output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(args.output, bbox_inches="tight")
print(f"Wrote {args.output}")
if __name__ == "__main__":
main()