|
14 | 14 | import argparse |
15 | 15 | import os |
16 | 16 | import numpy as np |
17 | | -import nibabel as nib |
18 | 17 |
|
19 | 18 | from metrics import ( |
20 | 19 | compute_whole_body_suv_mae, |
21 | | - compute_brain_outlier_score, |
22 | 20 | compute_organ_bias_from_totalseg, |
23 | 21 | compute_whole_body_mu_mae, |
24 | 22 | ) |
25 | 23 |
|
26 | 24 |
|
27 | | -def main(): |
28 | | - |
29 | | - parser = argparse.ArgumentParser( |
30 | | - description="PET Attenuation Correction Challenge — Evaluation" |
31 | | - ) |
| 25 | +def evaluate_subject(subject_path, pred_pet_path=None, pred_ct_path=None): |
| 26 | + """ |
| 27 | + Run metrics for a single subject. |
32 | 28 |
|
33 | | - parser.add_argument( |
34 | | - "--subject_path", |
35 | | - required=True, |
36 | | - help="Path to subject directory" |
37 | | - ) |
| 29 | + Parameters |
| 30 | + ---------- |
| 31 | + subject_path : str |
| 32 | + Path to the subject directory (must contain ct-label/ and pet-label/). |
| 33 | + pred_pet_path : str or None |
| 34 | + Path to predicted PET NIfTI. If given, runs PET metrics (SUV MAE, Organ Bias). |
| 35 | + pred_ct_path : str or None |
| 36 | + Path to predicted CT NIfTI. If given, runs CT MAE. |
38 | 37 |
|
39 | | - parser.add_argument( |
40 | | - "--pred_pet", |
41 | | - required=True, |
42 | | - help="Path to predicted PET NIfTI" |
43 | | - ) |
| 38 | + Note |
| 39 | + ---- |
| 40 | + Brain Outlier Score is a dataset-level metric and cannot be computed per-subject. |
| 41 | + Use compute_brain_outlier_score() directly with paths from multiple subjects. |
44 | 42 |
|
45 | | - parser.add_argument( |
46 | | - "--pred_ct", |
47 | | - required=True, |
48 | | - help="Path to predicted CT NIfTI" |
49 | | -) |
| 43 | + Returns |
| 44 | + ------- |
| 45 | + dict |
| 46 | + {metric_name: float} |
| 47 | + """ |
50 | 48 |
|
51 | | - parser.add_argument( |
52 | | - "-all", |
53 | | - action="store_true", |
54 | | - help="Run all metrics" |
55 | | - ) |
| 49 | + if pred_pet_path is None and pred_ct_path is None: |
| 50 | + raise ValueError("At least one of pred_pet_path or pred_ct_path must be provided.") |
56 | 51 |
|
57 | | - parser.add_argument( |
58 | | - "-specific_metric", |
59 | | - choices=[ |
60 | | - "whole_body_mae", |
61 | | - "brain_outlier", |
62 | | - "organ_bias", |
63 | | - "ct_mae", |
64 | | - ], |
65 | | - help="Run specific metric only" |
66 | | - ) |
67 | | - |
68 | | - args = parser.parse_args() |
69 | | - |
70 | | - subject_path = args.subject_path |
71 | 52 | ct_label_dir = os.path.join(subject_path, "ct-label") |
72 | 53 | pet_label_dir = os.path.join(subject_path, "pet-label") |
73 | | - features_dir = os.path.join(subject_path, "features") |
74 | | - |
75 | | - gt_pet = os.path.join(pet_label_dir, "pet.nii.gz") |
76 | | - gt_ct = os.path.join(ct_label_dir, "ct.nii.gz") |
77 | | - body_seg_pet = os.path.join(pet_label_dir, "body_seg.nii.gz") |
78 | | - organ_seg_pet = os.path.join(pet_label_dir, "organ_seg.nii.gz") |
79 | | - body_seg_ct = os.path.join(ct_label_dir, "body_seg.nii.gz") |
80 | | - meta_json = os.path.join(features_dir, "metadata.json") |
81 | 54 |
|
82 | 55 | results = {} |
83 | 56 |
|
84 | | - # ===================================================== |
85 | | - # 1. Whole-body SUV MAE |
86 | | - # ===================================================== |
87 | | - |
88 | | - if args.all or args.specific_metric == "whole_body_mae": |
| 57 | + if pred_pet_path is not None: |
| 58 | + gt_pet = os.path.join(pet_label_dir, "pet.nii.gz") |
| 59 | + body_seg_pet = os.path.join(pet_label_dir, "body_seg.nii.gz") |
| 60 | + organ_seg_pet = os.path.join(pet_label_dir, "organ_seg.nii.gz") |
89 | 61 |
|
90 | 62 | results["Whole-body SUV MAE"] = compute_whole_body_suv_mae( |
91 | | - pred_pet_path=args.pred_pet, |
| 63 | + pred_pet_path=pred_pet_path, |
92 | 64 | gt_pet_path=gt_pet, |
93 | 65 | body_mask_path=body_seg_pet, |
94 | | - liver_mask_path=organ_seg_pet, |
95 | | - json_path=meta_json, |
96 | | - ) |
97 | | - |
98 | | - # ===================================================== |
99 | | - # 2. Brain Outlier Score |
100 | | - # ===================================================== |
101 | | - |
102 | | - if args.all or args.specific_metric == "brain_outlier": |
103 | | - |
104 | | - results["Brain Outlier Score"] = compute_brain_outlier_score( |
105 | | - pred_paths=[args.pred_pet], |
106 | | - gt_paths=[gt_pet], |
107 | | - totalseg_paths=[organ_seg_pet], |
| 66 | + organ_seg_path=organ_seg_pet, |
108 | 67 | ) |
109 | 68 |
|
110 | | - # ===================================================== |
111 | | - # 3. Organ Bias |
112 | | - # ===================================================== |
113 | | - |
114 | | - if args.all or args.specific_metric == "organ_bias": |
115 | | - |
116 | | - |
117 | 69 | results["Organ Bias"] = compute_organ_bias_from_totalseg( |
118 | | - pred_path=args.pred_pet, |
| 70 | + pred_path=pred_pet_path, |
119 | 71 | gt_path=gt_pet, |
120 | 72 | totalseg_path=organ_seg_pet, |
121 | | - json_path=meta_json, |
| 73 | + body_mask_path=body_seg_pet, |
122 | 74 | ) |
123 | 75 |
|
124 | | - |
125 | | - # ===================================================== |
126 | | - # 4. CT MAE |
127 | | - # ===================================================== |
128 | | - |
129 | | - if args.all or args.specific_metric == "ct_mae": |
| 76 | + if pred_ct_path is not None: |
| 77 | + gt_ct = os.path.join(ct_label_dir, "ct.nii.gz") |
| 78 | + body_seg_ct = os.path.join(ct_label_dir, "body_seg.nii.gz") |
| 79 | + organ_seg_ct = os.path.join(ct_label_dir, "organ_seg.nii.gz") |
130 | 80 |
|
131 | 81 | results["CT MAE"] = compute_whole_body_mu_mae( |
132 | | - pred_ct_path=args.pred_ct, |
| 82 | + pred_ct_path=pred_ct_path, |
133 | 83 | gt_ct_path=gt_ct, |
134 | 84 | body_mask_path=body_seg_ct, |
135 | | - liver_mask_path=organ_seg_pet, |
| 85 | + organ_seg_path=organ_seg_ct, |
136 | 86 | ) |
137 | 87 |
|
138 | | - # ===================================================== |
139 | | - # Print Results |
140 | | - # ===================================================== |
| 88 | + return results |
141 | 89 |
|
142 | | - print("\n================ Evaluation Results ================") |
143 | | - print(f"Subject: {os.path.basename(subject_path)}") |
144 | | - print("----------------------------------------------------") |
145 | 90 |
|
146 | | - if not results: |
147 | | - print("No metric selected.") |
148 | | - else: |
149 | | - for name, value in results.items(): |
150 | | - if name == "Organ Bias": |
151 | | - print(f"{name:<25}: {value:.6f}%") |
152 | | - else: |
153 | | - print(f"{name:<25}: {value:.6f}") |
| 91 | +def main(): |
154 | 92 |
|
| 93 | + parser = argparse.ArgumentParser( |
| 94 | + description="PET Attenuation Correction Challenge — Evaluation" |
| 95 | + ) |
| 96 | + |
| 97 | + parser.add_argument("--subject_path", required=True, help="Path to subject directory") |
| 98 | + parser.add_argument("--pred_pet", default=None, help="Path to predicted PET NIfTI") |
| 99 | + parser.add_argument("--pred_ct", default=None, help="Path to predicted CT NIfTI") |
| 100 | + |
| 101 | + args = parser.parse_args() |
| 102 | + |
| 103 | + if args.pred_pet is None and args.pred_ct is None: |
| 104 | + parser.error("At least one of --pred_pet or --pred_ct must be provided.") |
| 105 | + |
| 106 | + results = evaluate_subject(args.subject_path, args.pred_pet, args.pred_ct) |
| 107 | + |
| 108 | + print("\n================ Evaluation Results ================") |
| 109 | + print(f"Subject: {os.path.basename(args.subject_path)}") |
| 110 | + print("----------------------------------------------------") |
| 111 | + for name, value in results.items(): |
| 112 | + unit = "%" if name == "Organ Bias" else "" |
| 113 | + print(f"{name:<25}: {value:.6f}{unit}") |
155 | 114 | print("====================================================\n") |
156 | 115 |
|
| 116 | + return results |
| 117 | + |
157 | 118 |
|
158 | 119 | if __name__ == "__main__": |
159 | 120 | main() |
0 commit comments