Download code/mr/predict_report.py from odurugol/vlm3d-beyond-normals: direct link, hf CLI and curl.
- Browser
- Download file 3.15 kB
-
https://huggingface.co/odurugol/vlm3d-beyond-normals/resolve/main/code/mr/predict_report.py
- Command line
-
hf download hf://odurugol/vlm3d-beyond-normals/code/mr/predict_report.py
-
curl -L -o predict_report.py https://huggingface.co/odurugol/vlm3d-beyond-normals/resolve/main/code/mr/predict_report.py
3.15 kB
| """MR report generation: a folder of study zips -> predictions.json with one report per study. | |
| python predict_report.py --weights mr_report_generation \ | |
| --input studies/ --output reports.json | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| from safetensors.torch import load_file | |
| from implications import closure_matrix, select_with_implications | |
| from model import build_model | |
| from predict_classification import discover | |
| from preprocess import process_files, process_zip | |
| from report import ReportWriter | |
| from tensors import to_model_input | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--weights", type=Path, required=True) | |
| ap.add_argument("--input", type=Path, required=True) | |
| ap.add_argument("--output", type=Path, required=True) | |
| args = ap.parse_args() | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| cfg = json.loads((args.weights / "config.json").read_text()) | |
| rc = json.loads((args.weights / "report_config.json").read_text()) | |
| net = build_model(cfg["arch"], in_ch=cfg["in_channels"], num_classes=len(cfg["labels"])) | |
| net.load_state_dict(load_file(str(args.weights / "model.safetensors"))) | |
| net = net.to(device).eval() | |
| order = rc["order"] # the 32 scored diagnoses | |
| idx = [cfg["labels"].index(c) for c in order] | |
| thr = np.array([rc["thresholds"][c] for c in order]) | |
| closure = closure_matrix(order) | |
| writer = ReportWriter(rc["template_lines"], rc["bullets"], order, | |
| header_findings=rc.get("header_findings", True), | |
| header_impression=rc.get("header_impression", True), | |
| normal_line=rc.get("normal_line", ""), | |
| short_template=rc.get("short_template_lines"), | |
| short_from=int(rc.get("short_from", 0))) | |
| fallback = writer.write(set(rc.get("fallback_positives", []))) # for a study that cannot be decoded | |
| reports = [] | |
| for i, (name, study) in enumerate(discover(args.input), 1): | |
| geo = cfg["geometry"] | |
| per, n_used = process_zip(study, (geo,)) if isinstance(study, Path) else process_files(study, (geo,)) | |
| if n_used == 0: | |
| text = fallback | |
| else: | |
| vol, presence = per[geo] | |
| x = to_model_input(torch.from_numpy(vol)[None].to(device), | |
| torch.from_numpy(presence.astype(np.float32))[None].to(device)) | |
| with torch.no_grad(): | |
| probs = torch.sigmoid(net(x).float())[0].cpu().numpy().astype(np.float64)[idx] | |
| chosen = select_with_implications(probs[None], thr, int(rc.get("max_bullets", 0)), closure)[0] | |
| text = writer.write({c for c, k in zip(order, chosen) if k}) | |
| reports.append({"input_image_name": f"{name}.nii.gz", "report": text}) | |
| print(f"[{i}] {name}", flush=True) | |
| args.output.parent.mkdir(parents=True, exist_ok=True) | |
| args.output.write_text(json.dumps({"generated_reports": reports})) | |
| if __name__ == "__main__": | |
| main() | |