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
1 change: 1 addition & 0 deletions docs/source/apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,4 @@ FastMRIReader
.. autofunction:: monai.apps.nnunet.convert_nnunet_to_monai_bundle
.. autofunction:: monai.apps.nnunet.convert_monai_bundle_to_nnunet
.. autofunction:: monai.apps.nnunet.get_network_from_nnunet_plans
.. autodata:: monai.apps.nnunet.DEFAULT_NNUNET_NETWORK_MODULES
1 change: 1 addition & 0 deletions monai/apps/nnunet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from __future__ import annotations

from .nnunet_bundle import (
DEFAULT_NNUNET_NETWORK_MODULES,
ModelnnUNetWrapper,
convert_monai_bundle_to_nnunet,
convert_nnunet_to_monai_bundle,
Expand Down
90 changes: 87 additions & 3 deletions monai/apps/nnunet/nnunet_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import os
import shutil
from collections.abc import Sequence
from pathlib import Path
from pydoc import locate
from typing import Any

import numpy as np
Expand All @@ -32,8 +34,60 @@
"convert_nnunet_to_monai_bundle",
"convert_monai_bundle_to_nnunet",
"ModelnnUNetWrapper",
"DEFAULT_NNUNET_NETWORK_MODULES",
]

#: Modules that nnUNet plans may reference for the network class and its imported keyword arguments.
DEFAULT_NNUNET_NETWORK_MODULES: tuple[str, ...] = ("dynamic_network_architectures", "torch.nn")


def _in_allowed_modules(dotted_name: str, allowed_modules: Sequence[str]) -> bool:
return any(dotted_name == m or dotted_name.startswith(m + ".") for m in allowed_modules)


def _check_nnunet_network_spec(
class_name: Any, init_kwargs: Any, kwargs_requiring_import: Any, allowed_modules: Sequence[str] | None = None
) -> None:
"""
Check the network specification read from an nnUNet ``plans.json`` before nnUNet resolves and builds it.
nnUNet imports ``network_class_name`` and every keyword argument listed in ``_kw_requires_import`` by dotted
name and calls them, so each of these names must be under one of ``allowed_modules`` and, if it can be
imported, must refer to a ``torch.nn.Module`` subclass. The module prefix is checked before anything is
imported.
Args:
class_name: the ``network_class_name`` from the plans.
init_kwargs: the ``arch_kwargs`` from the plans.
kwargs_requiring_import: the ``_kw_requires_import`` names from the plans.
allowed_modules: dotted module prefixes that may be referenced. Defaults to
``DEFAULT_NNUNET_NETWORK_MODULES``. Add the module of a custom architecture here to load it.
Raises:
ValueError: if a name is outside ``allowed_modules`` or does not resolve to a ``torch.nn.Module`` subclass.
"""
allowed = tuple(DEFAULT_NNUNET_NETWORK_MODULES if allowed_modules is None else allowed_modules)
hint = "Pass the module in `allowed_network_modules` if this architecture is trusted."

def _check(name: Any, what: str) -> None:
if not isinstance(name, str) or not _in_allowed_modules(name, allowed):
raise ValueError(f"Refusing to load {what} {name!r} from nnUNet plans: not in {list(allowed)}. {hint}")
resolved = locate(name)
# an unresolved name is left to nnUNet, which only searches inside dynamic_network_architectures for it
if resolved is not None and not (isinstance(resolved, type) and issubclass(resolved, torch.nn.Module)):
raise ValueError(f"Refusing to load {what} {name!r} from nnUNet plans: not a torch.nn.Module subclass.")

_check(class_name, "network class")
init_kwargs = init_kwargs or {}
if not isinstance(init_kwargs, dict):
raise ValueError(
f"Invalid network arguments in nnUNet plans: expected a dict, got {type(init_kwargs).__name__}."
)
for key in kwargs_requiring_import or ():
value = init_kwargs.get(key)
if value is not None:
_check(value, f"network argument {key!r}")


def get_nnunet_trainer(
dataset_name_or_id: str | int,
Expand Down Expand Up @@ -152,6 +206,9 @@ class ModelnnUNetWrapper(torch.nn.Module):
The folder path where the model and related files are stored.
model_name : str, optional
The name of the model file, by default "model.pt".
allowed_network_modules : Sequence[str], optional
Module prefixes that the plans may reference for the network class and imported network arguments,
by default ``DEFAULT_NNUNET_NETWORK_MODULES``.
Attributes
----------
Expand All @@ -166,7 +223,13 @@ class ModelnnUNetWrapper(torch.nn.Module):
restoring network architecture, and setting up the predictor for inference.
"""

def __init__(self, predictor: object, model_folder: str | Path, model_name: str = "model.pt"): # type: ignore
def __init__(
self,
predictor: object,
model_folder: str | Path,
model_name: str = "model.pt",
allowed_network_modules: Sequence[str] | None = None,
): # type: ignore
super().__init__()
self.predictor = predictor

Expand Down Expand Up @@ -216,6 +279,12 @@ def __init__(self, predictor: object, model_folder: str | Path, model_name: str
f"Unable to locate trainer class {trainer_name} in nnunetv2.training.nnUNetTrainer. "
f"Please place it there (in any .py file)!"
)
_check_nnunet_network_spec(
configuration_manager.network_arch_class_name,
configuration_manager.network_arch_init_kwargs,
configuration_manager.network_arch_init_kwargs_req_import,
allowed_network_modules,
)
network = trainer_class.build_network_architecture(
configuration_manager.network_arch_class_name,
configuration_manager.network_arch_init_kwargs,
Expand Down Expand Up @@ -294,7 +363,9 @@ def forward(self, x: MetaTensor) -> MetaTensor:
return MetaTensor(out_tensor, meta=x.meta)


def get_nnunet_monai_predictor(model_folder: str | Path, model_name: str = "model.pt") -> ModelnnUNetWrapper:
def get_nnunet_monai_predictor(
model_folder: str | Path, model_name: str = "model.pt", allowed_network_modules: Sequence[str] | None = None
) -> ModelnnUNetWrapper:
"""
Initializes and returns a `nnUNetMONAIModelWrapper` containing the corresponding `nnUNetPredictor`.
The model folder should contain the following files, created during training:
Expand Down Expand Up @@ -325,6 +396,9 @@ def get_nnunet_monai_predictor(model_folder: str | Path, model_name: str = "mode
The folder where the model is stored.
model_name : str, optional
The name of the model file, by default "model.pt".
allowed_network_modules : Sequence[str], optional
Module prefixes that ``plans.json`` may reference for the network class and imported network arguments,
by default ``DEFAULT_NNUNET_NETWORK_MODULES``. Extend it to load a trusted custom architecture.
Returns
-------
Expand All @@ -344,7 +418,7 @@ def get_nnunet_monai_predictor(model_folder: str | Path, model_name: str = "mode
allow_tqdm=True,
)
# initializes the network architecture, loads the checkpoint
wrapper = ModelnnUNetWrapper(predictor, model_folder, model_name)
wrapper = ModelnnUNetWrapper(predictor, model_folder, model_name, allowed_network_modules)
return wrapper


Expand Down Expand Up @@ -428,6 +502,7 @@ def get_network_from_nnunet_plans(
configuration: str,
model_ckpt: str | None = None,
model_key_in_ckpt: str = "model",
allowed_network_modules: Sequence[str] | None = None,
) -> torch.nn.Module | Any:
"""
Load and initialize a nnUNet network based on nnUNet plans and configuration.
Expand All @@ -444,6 +519,9 @@ def get_network_from_nnunet_plans(
Path to the model checkpoint file. If None, the network is returned without loading weights (default is None).
model_key_in_ckpt : str, optional
The key in the checkpoint file that contains the model state dictionary (default is "model").
allowed_network_modules : Sequence[str], optional
Module prefixes that ``plans_file`` may reference for the network class and imported network arguments,
by default ``DEFAULT_NNUNET_NETWORK_MODULES``. Extend it to load a trusted custom architecture.
Returns
-------
Expand All @@ -465,6 +543,12 @@ def get_network_from_nnunet_plans(

enable_deep_supervision = True

_check_nnunet_network_spec(
configuration_manager.network_arch_class_name,
configuration_manager.network_arch_init_kwargs,
configuration_manager.network_arch_init_kwargs_req_import,
allowed_network_modules,
)
network = get_network_from_plans(
configuration_manager.network_arch_class_name,
configuration_manager.network_arch_init_kwargs,
Expand Down
Loading
Loading