Newer
Older
MedSegAgentWebApp / skills / medsegagent-nnunet-runner / scripts / list_nnunet_results.py
@ziyan huang ziyan huang on 3 Apr 5 KB add medsegagent skill
#!/usr/bin/env python3
import argparse
import json
import os
from pathlib import Path


def resolve_results_dir():
    for key in ("nnUNet_results", "RESULTS_FOLDER"):
        value = os.environ.get(key)
        if value:
            return key, Path(value)
    return None, None


def load_json(path):
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None


def normalize_label_value(value):
    if isinstance(value, str) and value.isdigit():
        return int(value)
    if isinstance(value, list):
        return [normalize_label_value(v) for v in value]
    return value


def first_dataset_json(dataset_dir):
    candidates = sorted(dataset_dir.glob("*/dataset.json"))
    if candidates:
        return candidates[0]
    direct = dataset_dir / "dataset.json"
    if direct.is_file():
        return direct
    return None


def checkpoint_count(trainer_dir):
    count = 0
    for pattern in ("**/checkpoint_final.pth", "**/checkpoint_best.pth"):
        count += len(list(trainer_dir.glob(pattern)))
    return count


def parse_model_folder_name(name):
    parts = name.split("__")
    return {
        "trainer": parts[0] if len(parts) > 0 else None,
        "plans": parts[1] if len(parts) > 1 else None,
        "configuration": parts[2] if len(parts) > 2 else None,
    }


def list_available_folds(trainer_dir):
    folds = []
    for fold_dir in sorted(p for p in trainer_dir.iterdir() if p.is_dir() and p.name.startswith("fold_")):
        fold_name = fold_dir.name.removeprefix("fold_")
        fold_info = {
            "fold": fold_name,
            "has_checkpoint_final": (fold_dir / "checkpoint_final.pth").is_file(),
            "has_checkpoint_best": (fold_dir / "checkpoint_best.pth").is_file(),
        }
        folds.append(fold_info)
    return folds


def summarize_dataset(dataset_dir):
    trainer_dirs = sorted([p for p in dataset_dir.iterdir() if p.is_dir()])
    dataset_json_path = first_dataset_json(dataset_dir)
    dataset_json = load_json(dataset_json_path) if dataset_json_path else {}

    labels = (dataset_json or {}).get("labels", {})
    channel_names = (dataset_json or {}).get("channel_names", {})
    input_modalities = (dataset_json or {}).get("input_modalities", {})
    modalities = list(channel_names.values()) or list(input_modalities.values())

    trainers = []
    for trainer_dir in trainer_dirs:
        parsed = parse_model_folder_name(trainer_dir.name)
        folds = list_available_folds(trainer_dir)
        trainer_info = {
            "name": trainer_dir.name,
            "trainer": parsed["trainer"],
            "plans": parsed["plans"],
            "configuration": parsed["configuration"],
            "has_dataset_json": (trainer_dir / "dataset.json").is_file(),
            "has_plans_json": (trainer_dir / "plans.json").is_file(),
            "checkpoint_count": checkpoint_count(trainer_dir),
            "folds": folds,
        }
        trainers.append(trainer_info)

    normalized_labels = {
        key: normalize_label_value(value)
        for key, value in labels.items()
        if key != "background"
    }

    return {
        "folder": dataset_dir.name,
        "dataset_name": (dataset_json or {}).get("name", dataset_dir.name),
        "modalities": modalities,
        "labels": normalized_labels,
        "trainer_count": len(trainers),
        "trainers": trainers,
    }


def collect_results(results_dir):
    dataset_dirs = sorted(
        p for p in results_dir.iterdir() if p.is_dir() and p.name.startswith("Dataset")
    )
    return [summarize_dataset(dataset_dir) for dataset_dir in dataset_dirs]


def main():
    parser = argparse.ArgumentParser(
        description="List datasets, trainers, modalities, and labels from an nnUNet results directory."
    )
    parser.add_argument("--json", action="store_true", help="Emit JSON output")
    args = parser.parse_args()

    env_key, results_dir = resolve_results_dir()
    if not results_dir:
        raise SystemExit("No nnUNet results directory found in nnUNet_results or RESULTS_FOLDER.")
    if not results_dir.exists():
        raise SystemExit(f"{env_key} points to a missing path: {results_dir}")

    datasets = collect_results(results_dir)

    if args.json:
        print(
            json.dumps(
                {
                    "env_var": env_key,
                    "results_dir": str(results_dir),
                    "dataset_count": len(datasets),
                    "datasets": datasets,
                },
                ensure_ascii=False,
                indent=2,
            )
        )
        return

    print(f"{env_key}={results_dir}")
    print(f"datasets={len(datasets)}")
    for dataset in datasets:
        print(f"- {dataset['folder']} | {dataset['dataset_name']}")
        if dataset["modalities"]:
            print(f"  modalities: {', '.join(dataset['modalities'])}")
        print(f"  labels: {len(dataset['labels'])}")
        print(f"  trainers: {dataset['trainer_count']}")
        for trainer in dataset["trainers"]:
            print(
                f"    - {trainer['trainer']} | {trainer['configuration']} | folds={len(trainer['folds'])} | checkpoints={trainer['checkpoint_count']}"
            )


if __name__ == "__main__":
    main()