Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions research/vestibular_schwannoma/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ training, inference on new cases, and PACS deployment.

## Contents

- `train_5fold.py`: command-line five-fold training and evaluation.
- `notebooks/01_five_fold_cross_validation.ipynb`: train and compare UNet, DynUNet, and
optional SegMamba models.
- `notebooks/02_inference_new_cases.ipynb`: run one declared model or an explicit ensemble.
- `workflow/`: project-local configuration, training model definitions, result aggregation,
and inference artifact handling used by the notebooks.
and inference artifact handling shared by the CLI and notebooks.
- `data/ml_dataset.csv`: public case index and fixed fold assignments.
- `deployment/pacs/`: Safetensors bundle builder and ROR/PACS container.
- `tests/workflow/`: CPU-only workflow contract and orchestration tests.
Expand All @@ -32,7 +33,18 @@ optional SegMamba fork when needed.

## Run

Start Jupyter from this directory or `notebooks/`:
Use the CLI for unattended training. Examples for a quick check, one complete model, and the
full comparison:

```bash
python train_5fold.py --models unet --folds 1 --epochs 5 --no-compile
python train_5fold.py --models unet # One model, all five folds
python train_5fold.py --skip-unavailable
```

The default requests four models across five folds for 500 epochs; run
`python train_5fold.py --help` before starting. For interactive inspection and visualizations,
start Jupyter from this directory or `notebooks/`:

```bash
jupyter lab notebooks/01_five_fold_cross_validation.ipynb
Expand Down
17 changes: 17 additions & 0 deletions research/vestibular_schwannoma/tests/workflow/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_defaults_preserve_the_notebook_experiment(self):
self.assertEqual(config.training_seed, 42)
self.assertEqual(config.queue_num_workers, 4)
self.assertEqual(config.queue_length, 300)
self.assertEqual(config.foreground_sampling_probability, 0.8)
self.assertTrue(config.use_tta)

def test_configuration_is_frozen(self):
Expand All @@ -44,6 +45,14 @@ def test_invalid_declarations_fail_early(self):
{"model_keys": ("unet",), "training_seed": -1},
{"model_keys": ("unet",), "training_seed": True},
{"model_keys": ("unet",), "patch_size": (192, 192, 0)},
{
"model_keys": ("unet",),
"foreground_sampling_probability": 0,
},
{
"model_keys": ("unet",),
"foreground_sampling_probability": 1,
},
]
for kwargs in cases:
with self.subTest(kwargs=kwargs), self.assertRaises(ValueError):
Expand Down Expand Up @@ -71,6 +80,14 @@ def test_patch_factory_preserves_training_and_inference_contract(self):
},
)

def test_patch_factory_supports_70_30_sampling(self):
config = ExperimentConfig(
model_keys=("unet",), foreground_sampling_probability=0.7
)
patch = make_patch_config(config, [])
self.assertAlmostEqual(patch.label_probabilities[0], 0.3)
self.assertAlmostEqual(patch.label_probabilities[1], 0.7)


if __name__ == "__main__":
unittest.main()
5 changes: 5 additions & 0 deletions research/vestibular_schwannoma/tests/workflow/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ class TrainingModelConfigTests(unittest.TestCase):
def test_specs_are_the_single_architecture_declaration(self):
self.assertEqual(models.UNET_SPEC["arch_id"], "monai.unet")
self.assertEqual(models.DYNUNET_SPEC["arch_id"], "monai.dynunet")
self.assertEqual(models.DYNUNET_SMALL_SPEC["arch_id"], "monai.dynunet")
self.assertEqual(
models.DYNUNET_SPEC["wrapper_spec"][0]["wrapper_id"],
"fastmonai.dynunet_ds_adapter",
)
self.assertEqual(
models.DYNUNET_SMALL_SPEC["arch_kwargs"]["filters"],
[32, 64, 128, 256, 512],
)
self.assertEqual(models.SEGMAMBA_SPEC["arch_id"], "segmamba.v2")

def test_declared_order_is_preserved(self):
Expand Down
27 changes: 27 additions & 0 deletions research/vestibular_schwannoma/tests/workflow/test_train_5fold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import unittest

from vestibular_schwannoma import train_5fold


class FiveFoldLauncherTests(unittest.TestCase):
def test_defaults_select_all_models_and_preserve_80_20_control(self):
args = train_5fold._parser().parse_args([])

self.assertEqual(tuple(args.models), train_5fold.DEFAULT_MODELS)
self.assertEqual(args.folds, [1, 2, 3, 4, 5])
self.assertEqual(args.foreground_probability, 0.8)
self.assertNotIn("use_tta", vars(args))

def test_70_30_sampling_remains_an_explicit_comparison(self):
args = train_5fold._parser().parse_args(["--foreground-probability", "0.7"])

self.assertEqual(args.foreground_probability, 0.7)

def test_small_dynunet_uses_the_intermediate_512_bottleneck(self):
spec = train_5fold.TRAINING_MODEL_CONFIGS["dynunet_small"].model_spec

self.assertEqual(spec["arch_kwargs"]["filters"], [32, 64, 128, 256, 512])


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def test_fold_tracking_uses_fixed_model_and_output_contract(self):
)
extra_params = create_callback.call_args.kwargs["extra_params"]
self.assertEqual(extra_params["training_seed"], 42)
self.assertEqual(extra_params["foreground_sampling_probability"], 0.8)
self.assertEqual(
json.loads(extra_params["gpu_augmentations"]),
[
Expand Down Expand Up @@ -332,6 +333,7 @@ def build_model(*args, **kwargs):
)
extra_params = create_callback.call_args.kwargs["extra_params"]
self.assertEqual(extra_params["training_seed"], 42)
self.assertEqual(extra_params["foreground_sampling_probability"], 0.8)
self.assertEqual(
json.loads(extra_params["gpu_augmentations"]),
[
Expand Down
Loading