"""Build the training manifest for one pseudo-labeling round. base = labeled patient-level train split (w=1.0) — the same train/val usage as Pipeline A: the val split is never trained on (it is the internal-validation set). Accepted pseudo-label rows (pos + neg) are appended with w= (negative rows point at the zero mask written by stage 03), de-duplicated by key (first occurrence wins). Every row must point at existing pimg/plabel niftis. Usage: python scripts_monai/01_monai_build_rows.py \ --train data/manifests/split_train.jsonl \ --accepted data/pseudo_monai/round1/accepted.jsonl \ --pseudo-weight 0.3 --out data/pseudo_monai/round1_rows.jsonl """ import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from src.common import load_jsonl, save_jsonl def main(): ap = argparse.ArgumentParser() ap.add_argument("--train", required=True, help="labeled train split jsonl ({key,pimg,plabel})") ap.add_argument("--accepted", action="append", default=None, help="accepted pseudo jsonl (repeatable); pos + neg rows") ap.add_argument("--pseudo-weight", type=float, default=0.3) ap.add_argument("--no-neg", action="store_true", help="drop negative pseudo rows") ap.add_argument("--out", required=True) args = ap.parse_args() rows, seen = [], set() for r in load_jsonl(args.train): rows.append({"key": r["key"], "subject": r.get("subject"), "pimg": r["pimg"], "plabel": r["plabel"], "w": 1.0, "role": "labeled"}) seen.add(r["key"]) n_pos, n_neg = 0, 0 for f in (args.accepted or []): for r in load_jsonl(f): if not r.get("label") or r["key"] in seen: continue if r["role"] == "neg" and args.no_neg: continue seen.add(r["key"]) rows.append({"key": r["key"], "subject": r.get("subject"), "pimg": r["pimg"], "plabel": r["label"], "w": args.pseudo_weight, "role": r["role"]}) if r["role"] == "pos": n_pos += 1 else: n_neg += 1 missing = [r["key"] for r in rows if not (os.path.exists(r["pimg"]) and os.path.exists(r["plabel"]))] if missing: raise FileNotFoundError(f"{len(missing)} rows missing pimg/plabel, e.g. {missing[:3]}") save_jsonl(rows, args.out) n_labeled = len(rows) - n_pos - n_neg print(f"[monai:rows] {args.out}: total={len(rows)} labeled={n_labeled} pos={n_pos} neg={n_neg}", flush=True) if __name__ == "__main__": main()