Skip to content

Commit 5ecde8e

Browse files
authored
Merge pull request #581 from Project-MONAI/nnunet-example-fix
Sync nnUNet robustness updates to cchmc app from seg_metrics_op and other minor fixes
2 parents 4f777ee + 3d8207e commit 5ecde8e

6 files changed

Lines changed: 446 additions & 134 deletions

File tree

‎examples/apps/cchmc_nnunet_fifteen_ckpt_app/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ python convert_nnunet_ckpts.py --dataset_name_or_id DATASET_ID --MAP_root OUTPUT
2929

3030
The RESULTS_PATH should have "inference_information.json" file created by nnunetv2 automatically, as the conversion relies on this to figure out the best model configuration and convert those for the MAP.
3131

32+
`MAP_root` must resolve to a directory inside the current working directory. Relative paths such as `.` and `output` are supported; paths that escape the current directory are rejected.
33+
3234
### Command-line Arguments
3335

3436
| Argument | Description | Required | Default |
3537
|----------|-------------|----------|---------|
3638
| `--dataset_name_or_id` | Name or ID of the nnUNet dataset to convert | Yes | N/A |
3739
| `--MAP_root` | Output directory for the converted MONAI bundle | No | Current directory |
3840
| `--nnUNet_results` | Path to nnUNet results directory with trained models | Yes | Uses environment variable if set |
41+
| `--checkpoint_type` | Checkpoints to convert: `final` produces `final_model.pt`, `best` produces `best_model.pt`, and `both` produces both files | No | `final` |
3942

4043
#### Example
4144

@@ -62,7 +65,7 @@ MAP_root/
6265
├── 3d_fullres/ # Model configuration (if present)
6366
│ ├── nnunet_checkpoint.pth
6467
│ └── fold_X/ # Each fold's model weights
65-
│ └── best_model.pt
68+
│ └── final_model.pt
6669
├── 3d_lowres/ # Model configuration (if present)
6770
└── 3d_cascade_fullres/ # Model configuration (if present)
6871
```

‎examples/apps/cchmc_nnunet_fifteen_ckpt_app/convert_nnunet_ckpts.py‎

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,51 @@
1717
import argparse
1818
import os
1919
import sys
20+
from pathlib import Path
2021

2122
# Add the current directory to the path to find the local module
2223
current_dir = os.path.dirname(os.path.abspath(__file__))
2324
if current_dir not in sys.path:
2425
sys.path.insert(0, current_dir)
2526

26-
# Try importing from local apps.nnunet_bundle instead of from MONAI
27-
try:
28-
from my_app.nnunet_bundle import convert_best_nnunet_to_monai_bundle
29-
except ImportError:
30-
# If local import fails, try to find the module in alternate locations
27+
28+
def _import_converter():
29+
"""Deferred import so nnunetv2 is loaded AFTER nnUNet_results env var is set,
30+
preventing nnunetv2.paths from caching a None value for nnUNet_results."""
31+
try:
32+
from my_app.nnunet_bundle import convert_best_nnunet_to_monai_bundle
33+
34+
return convert_best_nnunet_to_monai_bundle
35+
except ImportError:
36+
pass
3137
try:
3238
from monai.apps.nnunet_bundle import convert_best_nnunet_to_monai_bundle
39+
40+
return convert_best_nnunet_to_monai_bundle
3341
except ImportError:
34-
print(
35-
"Error: Could not import convert_best_nnunet_to_monai_bundle from my_app.nnunet_bundle or apps.nnunet_bundle"
36-
)
37-
print("Please ensure that nnunet_bundle.py is properly installed in your project.")
38-
sys.exit(1)
42+
pass
43+
print("Error: Could not import convert_best_nnunet_to_monai_bundle from my_app.nnunet_bundle or apps.nnunet_bundle")
44+
print("Please ensure that nnunet_bundle.py is properly installed in your project.")
45+
sys.exit(1)
46+
47+
48+
def _validated_map_root(value: str) -> Path:
49+
"""Resolve the MAP output path and keep it within the current directory."""
50+
allowed_root = Path.cwd().resolve()
51+
map_root = Path(value).expanduser()
52+
if not map_root.is_absolute():
53+
map_root = allowed_root / map_root
54+
map_root = map_root.resolve(strict=False)
55+
56+
try:
57+
map_root.relative_to(allowed_root)
58+
except ValueError as exc:
59+
raise argparse.ArgumentTypeError(f"MAP_root must be inside the current directory ({allowed_root}).") from exc
60+
61+
if map_root.exists() and not map_root.is_dir():
62+
raise argparse.ArgumentTypeError(f"MAP_root is not a directory: {map_root}")
63+
64+
return map_root
3965

4066

4167
def parse_args():
@@ -57,6 +83,15 @@ def parse_args():
5783
default=None,
5884
help="Path to nnUNet results directory with trained models.",
5985
)
86+
parser.add_argument(
87+
"--checkpoint_type",
88+
type=str,
89+
default="final",
90+
choices=["final", "best", "both"],
91+
help="Which nnUNet checkpoint(s) to convert: 'final' (default) saves checkpoint_final.pth weights as "
92+
"final_model.pt; 'best' saves checkpoint_best.pth weights as best_model.pt; "
93+
"'both' saves checkpoint_final.pth as final_model.pt and checkpoint_best.pth as best_model.pt.",
94+
)
6095
return parser.parse_args()
6196

6297

@@ -69,7 +104,7 @@ def main():
69104
}
70105

71106
# Create the MAP root directory
72-
map_root = args.MAP_root
107+
map_root = _validated_map_root(args.MAP_root)
73108
os.makedirs(map_root, exist_ok=True)
74109

75110
# Set nnUNet environment variables if provided
@@ -90,9 +125,12 @@ def main():
90125
print(f"MAP will be created at: {map_root}")
91126
print(f" nnUNet_results: {os.environ.get('nnUNet_results')}")
92127

128+
# Import AFTER env vars are set so nnunetv2.paths caches the correct nnUNet_results value
129+
convert_best_nnunet_to_monai_bundle = _import_converter()
130+
93131
# Convert the nnUNet checkpoints to MONAI bundle format
94132
try:
95-
convert_best_nnunet_to_monai_bundle(nnunet_config, map_root)
133+
convert_best_nnunet_to_monai_bundle(nnunet_config, map_root, checkpoint_type=args.checkpoint_type)
96134
print(f"Successfully converted nnUNet checkpoints to MONAI bundle at: {map_root}/models")
97135
except Exception as e:
98136
print(f"Error converting nnUNet checkpoints: {e}")

0 commit comments

Comments
 (0)