longitudinal/scripts_monai/01_build_rows.py
Furen Xiao a516408c4e Add iterative pseudo-labeling scripts for MONAI and nnU-Net
- Implemented 09_run_iterative.py for MONAI to orchestrate iterative pseudo-labeling rounds, including training, predicting, and evaluation.
- Created 01_prepare_dataset.py, 02_plan_preprocess.py, 06_pseudo_label.py, 07_train.py, 08_eval.py, and 09_run_iterative.py for nnU-Net to manage dataset preparation, planning, training, evaluation, and iterative pseudo-labeling.
- Enhanced error handling and logging throughout the scripts for better traceability.
- Added functionality to handle negative pseudo-labels and TTA options during inference.
- Included visualization of Dice scores across rounds for both MONAI and nnU-Net.
2026-10-08 06:13:13 +08:00

64 lines
No EOL
2.7 KiB
Python

"""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=<pseudo-weight>
(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()