Skip to content
Open
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
8 changes: 8 additions & 0 deletions docs/source/apps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ Applications

`Clara MMARs`
-------------
.. deprecated:: 1.7.0
NVIDIA Clara MMAR support is deprecated and will be removed in version 1.9.0.
Use :doc:`MONAI Bundles <bundle>` instead.
For security reasons, ``load_from_mmar`` now raises
:class:`monai.utils.DeprecatedError` before downloading or loading a model.

.. autofunction:: get_model_spec

.. autofunction:: download_mmar

.. autofunction:: load_from_mmar
Expand Down
156 changes: 42 additions & 114 deletions monai/apps/mmars/mmars.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Utilities for accessing Nvidia MMARs
Deprecated utilities for accessing Nvidia MMARs. Use MONAI Bundles instead.

See Also:
- https://docs.nvidia.com/clara/clara-train-sdk/pt/mmar.html
Expand All @@ -18,18 +18,13 @@
from __future__ import annotations

import json
import os
import warnings
from collections.abc import Mapping
from pathlib import Path
from typing import Any

import torch

import monai.networks.nets as monai_nets
from monai.apps.utils import download_and_extract, logger
from monai.apps.utils import download_and_extract
from monai.config.type_definitions import PathLike
from monai.networks.utils import copy_model_state
from monai.utils.deprecate_utils import DeprecatedError, deprecated
from monai.utils.module import optional_import

from .model_desc import MODEL_DESC
Expand All @@ -38,8 +33,18 @@
__all__ = ["get_model_spec", "download_mmar", "load_from_mmar"]


@deprecated(
since="1.7.0",
removed="1.9.0",
msg_suffix="NVIDIA Clara MMAR support is deprecated. Please use MONAI Bundles instead.",
)
def get_model_spec(idx: int | str) -> dict | Any:
"""get model specification by `idx`. `idx` could be index of the constant tuple of dict or the actual model ID."""
"""
Get a model specification by index or model ID.

.. deprecated:: 1.7.0
NVIDIA Clara MMAR support will be removed in version 1.9.0. Use MONAI Bundles instead.
"""
if isinstance(idx, int):
return MODEL_DESC[idx]
if isinstance(idx, str):
Expand Down Expand Up @@ -99,16 +104,20 @@ def _get_ngc_url(model_name: str, version: str, model_prefix: str = "") -> str:
return f"https://api.ngc.nvidia.com/v2/models/{model_prefix}{model_name}/versions/{version}/zip"


def _get_ngc_doc_url(model_name: str, model_prefix: str = "") -> str:
return f"https://ngc.nvidia.com/catalog/models/{model_prefix}{model_name}"


@deprecated(
since="1.7.0",
removed="1.9.0",
msg_suffix="NVIDIA Clara MMAR support is deprecated. Please use MONAI Bundles instead.",
)
def download_mmar(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One deprecation ends up being reported twice here. download_mmar is decorated, and for a string item it delegates to get_model_spec, which carries the same decorator, so on 1.7.0 a single call such as download_mmar("clara_pt_prostate_mri_segmentation_1", mmar_dir=".", api=False) emits two FutureWarnings, one of them attributed to get_model_spec rather than to the function the caller invoked. Confirmed at this head commit by re-executing the module with version_val set to 1.7.0 and recording warnings: that call produced two FutureWarnings. Routing the internal lookup through a private undecorated helper would make a public call warn exactly once.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we just suppress the warning for the call to get_model_spec? The alternative is move the body of get_model_spec into an auxiliary undecorated function which it and download_mmar call.

item: str | Mapping, mmar_dir: PathLike | None = None, progress: bool = True, api: bool = True, version: int = -1
) -> Path:
"""
Download and extract Medical Model Archive (MMAR) from Nvidia Clara Train.

.. deprecated:: 1.7.0
NVIDIA Clara MMAR support will be removed in version 1.9.0. Use MONAI Bundles instead.

See Also:
- https://docs.nvidia.com/clara/
- Nvidia NGC Registry CLI
Expand Down Expand Up @@ -185,6 +194,11 @@ def download_mmar(
return model_dir


@deprecated(
since="1.7.0",
removed="1.9.0",
msg_suffix="NVIDIA Clara MMAR support is deprecated. Please use MONAI Bundles instead.",
)
def load_from_mmar(
item: Mapping | str | int,
mmar_dir: PathLike | None = None,
Expand All @@ -198,117 +212,31 @@ def load_from_mmar(
model_file: PathLike | None = None,
) -> Any:
"""
Download and extract Medical Model Archive (MMAR) model weights from Nvidia Clara Train.
Disabled loader for NVIDIA Clara Medical Model Archive (MMAR) models.

.. deprecated:: 1.7.0
Loading MMAR models is disabled for security reasons. This function will be removed
in version 1.9.0. Use MONAI Bundles instead.

Every call raises :class:`monai.utils.DeprecatedError` before downloading or
loading a model. The arguments are kept for compatibility and are ignored.
Their former meanings are listed below.

Args:
item: the corresponding model item from `MODEL_DESC`.
mmar_dir: : target directory to store the MMAR, default is mmars subfolder under `torch.hub get_dir()`.
mmar_dir: target directory to store the MMAR, default is mmars subfolder under `torch.hub get_dir()`.
progress: whether to display a progress bar when downloading the content.
version: version number of the MMAR. Set it to `-1` to use `item[Keys.VERSION]`.
map_location: pytorch API parameter for `torch.load` or `torch.jit.load`.
pretrained: whether to load the pretrained weights after initializing a network module.
weights_only: whether to load only the weights instead of initializing the network module and assign weights.
model_key: a key to search in the model file or config file for the model dictionary.
Currently this function assumes that the model dictionary has
The model dictionary was expected to have
`{"[name|path]": "test.module", "args": {'kw': 'test'}}`.
api: whether to query NGC API to get model infomation.
api: whether to query NGC API to get model information.
model_file: the relative path to the model file within an MMAR.

Examples::
>>> from monai.apps import load_from_mmar
>>> unet_model = load_from_mmar("clara_pt_prostate_mri_segmentation_1", mmar_dir=".", map_location="cpu")
>>> print(unet_model)

See Also:
https://docs.nvidia.com/clara/
"""
if api:
item = {Keys.NAME: get_model_spec(item)[Keys.NAME] if isinstance(item, int) else f"{item}"}
if not isinstance(item, Mapping):
item = get_model_spec(item)
model_dir = download_mmar(item=item, mmar_dir=mmar_dir, progress=progress, version=version, api=api)
if model_file is None:
model_file = os.path.join("models", "model.pt")
_model_file = model_dir / item.get(Keys.MODEL_FILE, model_file)
logger.info(f'\n*** "{item.get(Keys.NAME)}" available at {model_dir}.')

# loading with `torch.jit.load`
if _model_file.name.endswith(".ts"):
if not pretrained:
warnings.warn("Loading a ScriptModule, 'pretrained' option ignored.")
if weights_only:
warnings.warn("Loading a ScriptModule, 'weights_only' option ignored.")
return torch.jit.load(_model_file, map_location=map_location)

# loading with `torch.load`
model_dict = torch.load(_model_file, map_location=map_location, weights_only=True)
if weights_only:
return model_dict.get(model_key, model_dict) # model_dict[model_key] or model_dict directly

# 1. search `model_dict['train_config]` for model config spec.
model_config = _get_val(dict(model_dict).get("train_conf", {}), key=model_key, default={})
if not model_config or not isinstance(model_config, Mapping):
# 2. search json CONFIG_FILE for model config spec.
json_path = model_dir / item.get(Keys.CONFIG_FILE, os.path.join("config", "config_train.json"))
with open(json_path) as f:
conf_dict = json.load(f)
conf_dict = dict(conf_dict)
model_config = _get_val(conf_dict, key=model_key, default={})
if not model_config:
# 3. search `model_dict` for model config spec.
model_config = _get_val(dict(model_dict), key=model_key, default={})

if not (model_config and isinstance(model_config, Mapping)):
raise ValueError(
f"Could not load model config dictionary from config: {item.get(Keys.CONFIG_FILE)}, "
f"or from model file: {item.get(Keys.MODEL_FILE)}."
)

# parse `model_config` for model class and model parameters
if model_config.get("name"): # model config section is a "name"
model_name = model_config["name"]
model_cls = monai_nets.__dict__[model_name]
elif model_config.get("path"): # model config section is a "path"
# https://docs.nvidia.com/clara/clara-train-sdk/pt/byom.html
model_module, model_name = model_config.get("path", ".").rsplit(".", 1)
model_cls, has_cls = optional_import(module=model_module, name=model_name)
if not has_cls:
raise ValueError(
f"Could not load MMAR model config {model_config.get('path', '')}, "
f"Please make sure MMAR's sub-folders in '{model_dir}' is on the PYTHONPATH."
"See also: https://docs.nvidia.com/clara/clara-train-sdk/pt/byom.html"
)
else:
raise ValueError(f"Could not load model config {model_config}.")

logger.info(f"*** Model: {model_cls}")
model_kwargs = model_config.get("args", None)
if model_kwargs:
model_inst = model_cls(**model_kwargs)
logger.info(f"*** Model params: {model_kwargs}")
else:
model_inst = model_cls()
if pretrained:
_, changed, unchanged = copy_model_state(model_inst, model_dict.get(model_key, model_dict), inplace=True)
if not (changed and not unchanged): # not all model_inst variables are changed
logger.warning(f"*** Loading model state -- unchanged: {len(unchanged)}, changed: {len(changed)}.")
logger.info("\n---")
doc_url = item.get(Keys.DOC) or _get_ngc_doc_url(item[Keys.NAME], model_prefix="nvidia:med:")
logger.info(f"For more information, please visit {doc_url}\n")
return model_inst


def _get_val(input_dict: Mapping, key: str = "model", default: Any | None = None) -> Any | None:
"""
Search for the item with `key` in `config_dict`.
Returns: the first occurrence of `key` in a breadth first search.
Raises:
DeprecatedError: always, because MMAR loading is disabled for security reasons.
"""
if key in input_dict:
return input_dict[key]
for sub_dict in input_dict:
val = input_dict[sub_dict]
if isinstance(val, Mapping):
found_val = _get_val(val, key=key, default=None)
if found_val is not None:
return found_val
return default
raise DeprecatedError("load_from_mmar is disabled for security reasons. Please use MONAI Bundles instead.")
12 changes: 11 additions & 1 deletion monai/apps/mmars/model_desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,29 @@
import os
from typing import Any

from monai.utils.deprecate_utils import deprecated

__all__ = ["MODEL_DESC", "RemoteMMARKeys"]


@deprecated(
since="1.7.0",
removed="1.9.0",
msg_suffix="NVIDIA Clara MMAR support is deprecated. Please use MONAI Bundles instead.",
)
class RemoteMMARKeys:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoteMMARKeys is a constants namespace, so this decorator only fires on instantiation, which nothing in the code base does. Verified at this head commit with version_val set to 1.7.0: attribute access such as RemoteMMARKeys.NAME emits no warning; with 1.9.0, attribute access still returns the constants while instantiating raises DeprecatedError. MODEL_DESC has the same property. The new test covers the instantiation path, so it does not exercise what users would actually observe. If the intent is simply that this module disappears at 1.9.0 then that is reasonable, but a sentence to that effect in the deprecation note (or a test on the attribute-access path) would make the schedule meaningful.

"""
Data keys used for loading MMAR.
ID must uniquely define an MMAR.

.. deprecated:: 1.7.0
NVIDIA Clara MMAR support will be removed in version 1.9.0. Use MONAI Bundles instead.
"""

ID = "id" # unique MMAR
NAME = "name" # MMAR name for readability
URL = "url" # remote location of the MMAR, see also: `monai.apps.mmars.mmars._get_ngc_url`
DOC = "doc" # documentation page of the remote model, see also: `monai.apps.mmars.mmars._get_ngc_doc_url`
DOC = "doc" # documentation page of the remote model
FILE_TYPE = "file_type" # type of the compressed MMAR
HASH_TYPE = "hash_type" # hashing method for the compressed MMAR
HASH_VAL = "hash_val" # hashing value for the compressed MMAR
Expand Down
Loading
Loading