From e2434fdd9f51d2bf3dc29193f8cdc756a9aa2e3f Mon Sep 17 00:00:00 2001 From: Wenqi Li <831580+wyli@users.noreply.github.com> Date: Thu, 15 Dec 2022 22:48:38 +0000 Subject: [PATCH 1/6] 5626 adds what's new in 1.1 (#5739) Signed-off-by: Wenqi Li part of #5626 ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Wenqi Li Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- README.md | 9 +++-- docs/images/exp_mgmt.png | Bin 0 -> 40961 bytes docs/images/python.svg | 1 + docs/source/whatsnew.rst | 1 + docs/source/whatsnew_1_0.md | 2 +- docs/source/whatsnew_1_1.md | 67 ++++++++++++++++++++++++++++++++++++ setup.cfg | 18 ++++++++++ 7 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 docs/images/exp_mgmt.png create mode 100644 docs/images/python.svg create mode 100644 docs/source/whatsnew_1_1.md diff --git a/README.md b/README.md index c3fd93f522..54257a7b39 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,14 @@ **M**edical **O**pen **N**etwork for **AI** [![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) +[![PyPI version](https://badge.fury.io/py/monai.svg)](https://badge.fury.io/py/monai) +[![docker](https://img.shields.io/badge/docker-pull-green.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/projectmonai/monai) +[![conda](https://img.shields.io/conda/vn/conda-forge/monai?color=green)](https://anaconda.org/conda-forge/monai) + +![Supported Python versions](https://raw.githubusercontent.com/Project-MONAI/MONAI/dev/docs/images/python.svg) [![CI Build](https://github.com/Project-MONAI/MONAI/workflows/build/badge.svg?branch=dev)](https://github.com/Project-MONAI/MONAI/commits/dev) -[![Documentation Status](https://readthedocs.org/projects/monai/badge/?version=latest)](https://docs.monai.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/monai/badge/?version=latest)](https://docs.monai.io/en/latest/) [![codecov](https://codecov.io/gh/Project-MONAI/MONAI/branch/dev/graph/badge.svg?token=6FTC7U1JJ4)](https://codecov.io/gh/Project-MONAI/MONAI) -[![PyPI version](https://badge.fury.io/py/monai.svg)](https://badge.fury.io/py/monai) -[![conda](https://img.shields.io/conda/vn/conda-forge/monai)](https://anaconda.org/conda-forge/monai) MONAI is a [PyTorch](https://pytorch.org/)-based, [open-source](https://github.com/Project-MONAI/MONAI/blob/dev/LICENSE) framework for deep learning in healthcare imaging, part of [PyTorch Ecosystem](https://pytorch.org/ecosystem/). Its ambitions are: diff --git a/docs/images/exp_mgmt.png b/docs/images/exp_mgmt.png new file mode 100644 index 0000000000000000000000000000000000000000..f3faf4c09ff96edb92751e773513f2dd5b27028e GIT binary patch literal 40961 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
  • Xd&jssoc7nx4I4JZzJ2>JZ{ECW$M6aUQn#Xl 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 literal 0 HcmV?d00001 diff --git a/docs/images/python.svg b/docs/images/python.svg new file mode 100644 index 0000000000..2d24bd007f --- /dev/null +++ b/docs/images/python.svg @@ -0,0 +1 @@ +pythonpython3.7+3.7+ diff --git a/docs/source/whatsnew.rst b/docs/source/whatsnew.rst index bb6665e621..9dd00bcd1e 100644 --- a/docs/source/whatsnew.rst +++ b/docs/source/whatsnew.rst @@ -6,6 +6,7 @@ What's New .. toctree:: :maxdepth: 1 + whatsnew_1_1.md whatsnew_1_0.md whatsnew_0_9.md whatsnew_0_8.md diff --git a/docs/source/whatsnew_1_0.md b/docs/source/whatsnew_1_0.md index 36ab393af1..e8e0b031c1 100644 --- a/docs/source/whatsnew_1_0.md +++ b/docs/source/whatsnew_1_0.md @@ -1,4 +1,4 @@ -# What's new in 1.0 🎉🎉 +# What's new in 1.0 - Model Zoo - Auto3DSeg diff --git a/docs/source/whatsnew_1_1.md b/docs/source/whatsnew_1_1.md new file mode 100644 index 0000000000..2c16a417ad --- /dev/null +++ b/docs/source/whatsnew_1_1.md @@ -0,0 +1,67 @@ +# What's new in 1.1 🎉🎉 + +- Digital pathology workflows +- Experiment management for MONAI bundle +- Auto3dSeg enhancements +- New models in MONAI Model Zoo +- State-of-the-art SurgToolLoc solution + +## Digital pathology workflows + +Hover-Net is a model for simultaneous segmentation and classification of nuclei in multi-tissue histology images (Graham et al. Medical Image Analysis, 2019). +We have added support for this model in MONAI by implementing several new components, enhancing existing ones and providing pipelines and examples for training, validation and inference. + +Along with the modules release, new digital pathology analysis tutorials are made available: + +- [HoVerNet pipelines](https://github.com/Project-MONAI/tutorials/tree/main/pathology/hovernet) based on MONAI workflows for training, validation and inference +- [HoVerNet tutorial](https://github.com/Project-MONAI/tutorials/blob/main/pathology/hovernet/hovernet_torch.ipynb) for training, validation and inference +- NuClick (Interactive Annotation for Pathology) tutorials for [training](https://github.com/Project-MONAI/tutorials/blob/main/pathology/nuclick/nuclick_training_notebook.ipynb) +and [inference](https://github.com/Project-MONAI/tutorials/blob/main/pathology/nuclick/nuclick_infer.ipynb) +- Nuclei classification tutorials for [training](https://github.com/Project-MONAI/tutorials/blob/main/pathology/nuclick/nuclei_classification_training_notebook.ipynb) +and [inference](https://github.com/Project-MONAI/tutorials/blob/main/pathology/nuclick/nuclei_classification_infer.ipynb) + +## Experiment management for MONAI bundle + +In this release, experiment management features are integrated with MONAI bundle. +It provides essential APIs for managing the end-to-end model bundle lifecycle. +Users can start tracking experiments by, for example, appending `--tracking "mlflow"` to the training or inference commands to enable the MLFlow-based management. +By default, MLFlow will track the executed bundle config, model quality measurements, and source code versioning. +For more details, please refer to the [tutorial](https://github.com/Project-MONAI/tutorials/blob/main/experiment_management/bundle_integrate_mlflow.ipynb). + +## Auto3dSeg enhancements + +Multiple improvements have been added in `Auto3DSeg` both in terms of +usability and performance. +- Multi-modality support is added and applied for +automated segmentation of the HECKTOR22 challenge dataset, which includes input 3D +CT and PET images of various resolutions and sizes. A tutorial example of +running Auto3DSeg on the HECKTOR22 challenge dataset is available in MONAI +Tutorials. The tutorial is based on [the HECKTOR22 challenge 1st place solution](https://arxiv.org/abs/2209.10809). +- A new improved version of `Segresnet` Algo is now available in `AutoRunner`. +- Automatic customization and optimization of the model training configuration +can be achieved according to the GPU devices used. The feature +focuses on determining parameters including batch size of model +training and sliding-window inference, allocated devices for +data in sliding-window inference. For more details about how to enable it, please see [the tutorials](https://github.com/Project-MONAI/tutorials/tree/main/auto3dseg). + +## New models in MONAI Model Zoo + +New pretrained models are being created and released [in the Model Zoo](https://monai.io/model-zoo.html). +Notably, + +- The `mednist_reg` model demonstrates how to build image registration workflows in MONAI bundle +format. The model uses a ResNet and spatial transformer for hand X-ray image registration based on +[the registration_mednist tutorial](https://github.com/Project-MONAI/tutorials/blob/main/2d_registration/registration_mednist.ipynb), +- `pathology_nuclei_segmentation_and_classification`, `pathology_nuclick_annotation`, and +`pathology_nuclei_classification` bundles are built for digital pathology image analysis. + +For more details about how to use the models, please see [the tutorials](https://github.com/Project-MONAI/tutorials/tree/main/model_zoo). + +## State-of-the-art SurgToolLoc solution + +[SurgToolLoc](https://surgtoolloc.grand-challenge.org/Home/) is a part of the +[EndoVis](https://endovis.grand-challenge.org/) challenge at [MICCAI 2022](https://conferences.miccai.org/2022/en/). +The challenge focuses on endoscopic video analysis and is divided into (1) fully supervised tool classification +and (2) weakly supervised tool classification/localization. +Team NVIDIA won prizes by finishing [third](https://surgtoolloc.grand-challenge.org/results/) in both categories. +The core components of the solutions [are released in MONAI](https://github.com/Project-MONAI/tutorials/tree/main/competitions/MICCAI/surgtoolloc). diff --git a/setup.cfg b/setup.cfg index d14b7fe30b..30352d50db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,6 +14,24 @@ project_urls = Documentation=https://docs.monai.io/ Bug Tracker=https://github.com/Project-MONAI/MONAI/issues Source Code=https://github.com/Project-MONAI/MONAI +classifiers = + Intended Audience :: Developers + Intended Audience :: Education + Intended Audience :: Science/Research + Intended Audience :: Healthcare Industry + Programming Language :: C++ + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Topic :: Scientific/Engineering + Topic :: Scientific/Engineering :: Artificial Intelligence + Topic :: Scientific/Engineering :: Medical Science Apps. + Topic :: Scientific/Engineering :: Information Analysis + Topic :: Software Development + Topic :: Software Development :: Libraries + Typing :: Typed [options] python_requires = >= 3.7 From 0abd04e9cd1fd955d48501fe7a0c149f5fad86cf Mon Sep 17 00:00:00 2001 From: Wenqi Li <831580+wyli@users.noreply.github.com> Date: Fri, 16 Dec 2022 12:06:31 +0000 Subject: [PATCH 2/6] testing with torch 1.13.1 (#5758) Signed-off-by: Wenqi Li ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Wenqi Li --- .github/workflows/pythonapp-gpu.yml | 2 +- .github/workflows/pythonapp-min.yml | 4 ++-- .github/workflows/pythonapp.yml | 4 ++-- .github/workflows/setupapp.yml | 2 +- README.md | 6 ++++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pythonapp-gpu.yml b/.github/workflows/pythonapp-gpu.yml index 9541bd1caa..47259be9aa 100644 --- a/.github/workflows/pythonapp-gpu.yml +++ b/.github/workflows/pythonapp-gpu.yml @@ -44,7 +44,7 @@ jobs: pytorch: "-h" # we explicitly set pytorch to -h to avoid pip install error base: "nvcr.io/nvidia/pytorch:22.09-py3" - environment: PT113+CUDA116 - pytorch: "torch==1.13.0 torchvision==0.14.0" + pytorch: "torch==1.13.1 torchvision==0.14.1" base: "nvcr.io/nvidia/cuda:11.6.1-devel-ubuntu18.04" container: image: ${{ matrix.base }} diff --git a/.github/workflows/pythonapp-min.yml b/.github/workflows/pythonapp-min.yml index 317fcbbcd2..a5e24639bf 100644 --- a/.github/workflows/pythonapp-min.yml +++ b/.github/workflows/pythonapp-min.yml @@ -52,11 +52,11 @@ jobs: - if: runner.os == 'windows' name: Install torch cpu from pytorch.org (Windows only) run: | - python -m pip install torch==1.13+cpu -f https://download.pytorch.org/whl/torch_stable.html + python -m pip install torch==1.13.1+cpu -f https://download.pytorch.org/whl/torch_stable.html - name: Install the dependencies run: | # min. requirements - python -m pip install torch==1.13 + python -m pip install torch==1.13.1 python -m pip install -r requirements-min.txt python -m pip list BUILD_MONAI=0 python setup.py develop # no compile of extensions diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index fb68e04b44..a603deb8a2 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -88,14 +88,14 @@ jobs: - if: runner.os == 'windows' name: Install torch cpu from pytorch.org (Windows only) run: | - python -m pip install torch==1.13.0+cpu torchvision==0.14.0+cpu -f https://download.pytorch.org/whl/torch_stable.html + python -m pip install torch==1.13.1+cpu torchvision==0.14.1+cpu -f https://download.pytorch.org/whl/torch_stable.html - if: runner.os == 'Linux' name: Install itk pre-release (Linux only) run: | python -m pip install --pre -U itk - name: Install the dependencies run: | - python -m pip install torch==1.13.0 torchvision==0.14.0 + python -m pip install torch==1.13.1 torchvision==0.14.1 cat "requirements-dev.txt" python -m pip install -r requirements-dev.txt python -m pip list diff --git a/.github/workflows/setupapp.yml b/.github/workflows/setupapp.yml index 7aa2649918..b8efda471e 100644 --- a/.github/workflows/setupapp.yml +++ b/.github/workflows/setupapp.yml @@ -100,7 +100,7 @@ jobs: - name: Install the dependencies run: | python -m pip install --upgrade pip wheel - python -m pip install torch==1.13.0 torchvision==0.14.0 + python -m pip install torch==1.13.1 torchvision==0.14.1 python -m pip install -r requirements-dev.txt - name: Run quick tests CPU ubuntu run: | diff --git a/README.md b/README.md index 54257a7b39..d54aa7e1a7 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,15 @@ **M**edical **O**pen **N**etwork for **AI** +![Supported Python versions](https://raw.githubusercontent.com/Project-MONAI/MONAI/dev/docs/images/python.svg) [![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) [![PyPI version](https://badge.fury.io/py/monai.svg)](https://badge.fury.io/py/monai) [![docker](https://img.shields.io/badge/docker-pull-green.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/projectmonai/monai) [![conda](https://img.shields.io/conda/vn/conda-forge/monai?color=green)](https://anaconda.org/conda-forge/monai) -![Supported Python versions](https://raw.githubusercontent.com/Project-MONAI/MONAI/dev/docs/images/python.svg) -[![CI Build](https://github.com/Project-MONAI/MONAI/workflows/build/badge.svg?branch=dev)](https://github.com/Project-MONAI/MONAI/commits/dev) +[![premerge](https://github.com/Project-MONAI/MONAI/actions/workflows/pythonapp.yml/badge.svg?branch=dev)](https://github.com/Project-MONAI/MONAI/actions/workflows/pythonapp.yml) +[![postmerge](https://img.shields.io/github/checks-status/project-monai/monai/dev?label=postmerge)](https://github.com/Project-MONAI/MONAI/actions?query=branch%3Adev) +[![docker](https://github.com/Project-MONAI/MONAI/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/Project-MONAI/MONAI/actions/workflows/docker.yml) [![Documentation Status](https://readthedocs.org/projects/monai/badge/?version=latest)](https://docs.monai.io/en/latest/) [![codecov](https://codecov.io/gh/Project-MONAI/MONAI/branch/dev/graph/badge.svg?token=6FTC7U1JJ4)](https://codecov.io/gh/Project-MONAI/MONAI) From 67d84d3194590839604cd55afed89f642d27925a Mon Sep 17 00:00:00 2001 From: Yiheng Wang <68361391+yiheng-wang-nv@users.noreply.github.com> Date: Sat, 17 Dec 2022 22:19:24 +0800 Subject: [PATCH 3/6] add ngc download bundle (#5710) Signed-off-by: Yiheng Wang Fixes #5679 and #5320 ### Description This PR adds the support of download bundles from ngc: https://catalog.ngc.nvidia.com/models?filters=&orderBy=scoreDESC&query=monai In addition, when "version" is not provided, it changes to download the latest version in default. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Yiheng Wang --- .../{cron-mmar.yml => cron-ngc-bundle.yml} | 16 +- monai/bundle/scripts.py | 139 ++++++++++++++---- ...mmar_loading.py => ngc_bundle_download.py} | 36 +++++ tests/test_bundle_download.py | 22 ++- 4 files changed, 164 insertions(+), 49 deletions(-) rename .github/workflows/{cron-mmar.yml => cron-ngc-bundle.yml} (77%) rename tests/{ngc_mmar_loading.py => ngc_bundle_download.py} (68%) diff --git a/.github/workflows/cron-mmar.yml b/.github/workflows/cron-ngc-bundle.yml similarity index 77% rename from .github/workflows/cron-mmar.yml rename to .github/workflows/cron-ngc-bundle.yml index ae65388a8b..2b16d05ce7 100644 --- a/.github/workflows/cron-mmar.yml +++ b/.github/workflows/cron-ngc-bundle.yml @@ -1,15 +1,15 @@ -# daily tests for clara mmar models -name: cron-mmar +# daily tests for ngc bundles +name: cron-ngc-bundle on: - # schedule: - # - cron: "0 2 * * *" # at 02:00 UTC + schedule: + - cron: "0 2 * * *" # at 02:00 UTC # Allows you to run this workflow manually from the Actions tab workflow_dispatch: concurrency: # automatically cancel the previously triggered workflows when there's a newer version - group: mmar-tests-${{ github.event.pull_request.number || github.ref }} + group: bundle-tests-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -33,12 +33,12 @@ jobs: key: ${{ runner.os }}-pip-${{ steps.pip-cache.outputs.datew }} - name: Install dependencies run: | - rm -rf /github/home/.cache/torch/hub/mmars/ + rm -rf /github/home/.cache/torch/hub/bundle/ python -m pip install --upgrade pip wheel python -m pip install -r requirements-dev.txt - - name: Loading MMARs + - name: Loading Bundles run: | # clean up temporary files $(pwd)/runtests.sh --build --clean # run tests - python -m tests.ngc_mmar_loading + python -m tests.ngc_bundle_download diff --git a/monai/bundle/scripts.py b/monai/bundle/scripts.py index cf9be1f98d..dc1fc2f2d7 100644 --- a/monai/bundle/scripts.py +++ b/monai/bundle/scripts.py @@ -25,6 +25,7 @@ import torch from torch.cuda import is_available +from monai.apps.mmars.mmars import _get_all_ngc_models from monai.apps.utils import _basename, download_url, extractall, get_logger from monai.bundle.config_item import ConfigComponent from monai.bundle.config_parser import ConfigParser @@ -42,6 +43,9 @@ logger = get_logger(module_name=__name__) +# set BUNDLE_DOWNLOAD_SRC="ngc" to use NGC source in default for bundle download +download_source = os.environ.get("BUNDLE_DOWNLOAD_SRC", "github") + def _update_args(args: Optional[Union[str, Dict]] = None, ignore_none: bool = True, **kwargs) -> Dict: """ @@ -130,9 +134,11 @@ def _get_git_release_url(repo_owner: str, repo_name: str, tag_name: str, filenam return f"https://github.com/{repo_owner}/{repo_name}/releases/download/{tag_name}/{filename}" +def _get_ngc_bundle_url(model_name: str, version: str): + return f"https://api.ngc.nvidia.com/v2/models/nvidia/monaitoolkit/{model_name}/versions/{version}/zip" + + def _download_from_github(repo: str, download_path: Path, filename: str, progress: bool = True): - if len(repo.split("/")) != 3: - raise ValueError("if source is `github`, repo should be in the form of `repo_owner/repo_name/release_tag`.") repo_owner, repo_name, tag_name = repo.split("/") if ".zip" not in filename: filename += ".zip" @@ -142,6 +148,45 @@ def _download_from_github(repo: str, download_path: Path, filename: str, progres extractall(filepath=filepath, output_dir=download_path, has_base=True) +def _add_ngc_prefix(name: str, prefix: str = "monai_"): + if name.startswith(prefix): + return name + return f"{prefix}{name}" + + +def _remove_ngc_prefix(name: str, prefix: str = "monai_"): + if name.startswith(prefix): + return name[len(prefix) :] + return name + + +def _download_from_ngc(download_path: Path, filename: str, version: str, remove_prefix: Optional[str], progress: bool): + # ensure prefix is contained + filename = _add_ngc_prefix(filename) + url = _get_ngc_bundle_url(model_name=filename, version=version) + filepath = download_path / f"{filename}_v{version}.zip" + if remove_prefix: + filename = _remove_ngc_prefix(filename) + extract_path = download_path / f"{filename}" + download_url(url=url, filepath=filepath, hash_val=None, progress=progress) + extractall(filepath=filepath, output_dir=extract_path, has_base=True) + + +def _get_latest_bundle_version(source: str, name: str, repo: str): + if source == "ngc": + name = _add_ngc_prefix(name) + model_dict = _get_all_ngc_models(name) + for v in model_dict.values(): + if v["name"] == name: + return v["latest"] + return None + elif source == "github": + repo_owner, repo_name, tag_name = repo.split("/") + return get_bundle_versions(name, repo=os.path.join(repo_owner, repo_name), tag=tag_name)["latest_version"] + else: + raise ValueError(f"To get the latest bundle version, source should be 'github' or 'ngc', got {source}.") + + def _process_bundle_dir(bundle_dir: Optional[PathLike] = None): if bundle_dir is None: get_dir, has_home = optional_import("torch.hub", name="get_dir") @@ -156,9 +201,10 @@ def download( name: Optional[str] = None, version: Optional[str] = None, bundle_dir: Optional[PathLike] = None, - source: str = "github", - repo: str = "Project-MONAI/model-zoo/hosting_storage_v1", + source: str = download_source, + repo: Optional[str] = None, url: Optional[str] = None, + remove_prefix: Optional[str] = "monai_", progress: bool = True, args_file: Optional[str] = None, ): @@ -175,9 +221,12 @@ def download( # Execute this module as a CLI entry, and download bundle from the model-zoo repo: python -m monai.bundle download --name --version "0.1.0" --bundle_dir "./" - # Execute this module as a CLI entry, and download bundle: + # Execute this module as a CLI entry, and download bundle from specified github repo: python -m monai.bundle download --name --source "github" --repo "repo_owner/repo_name/release_tag" + # Execute this module as a CLI entry, and download bundle from ngc with latest version: + python -m monai.bundle download --name --source "ngc" --bundle_dir "./" + # Execute this module as a CLI entry, and download bundle via URL: python -m monai.bundle download --name --url @@ -190,18 +239,27 @@ def download( Args: name: bundle name. If `None` and `url` is `None`, it must be provided in `args_file`. - for example: "spleen_ct_segmentation", "prostate_mri_anatomy" in the model-zoo: + for example: + "spleen_ct_segmentation", "prostate_mri_anatomy" in model-zoo: https://github.com/Project-MONAI/model-zoo/releases/tag/hosting_storage_v1. - version: version name of the target bundle to download, like: "0.1.0". + "monai_brats_mri_segmentation" in ngc: + https://catalog.ngc.nvidia.com/models?filters=&orderBy=scoreDESC&query=monai. + version: version name of the target bundle to download, like: "0.1.0". If `None`, will download + the latest version. bundle_dir: target directory to store the downloaded data. Default is `bundle` subfolder under `torch.hub.get_dir()`. source: storage location name. This argument is used when `url` is `None`. - "github" is currently the only supported value. - repo: repo name. This argument is used when `url` is `None`. - If `source` is "github", it should be in the form of "repo_owner/repo_name/release_tag". + In default, the value is achieved from the environment variable BUNDLE_DOWNLOAD_SRC, and + it should be "ngc" or "github". + repo: repo name. This argument is used when `url` is `None` and `source` is "github". + If used, it should be in the form of "repo_owner/repo_name/release_tag". url: url to download the data. If not `None`, data will be downloaded directly and `source` will not be checked. If `name` is `None`, filename is determined by `monai.apps.utils._basename(url)`. + remove_prefix: This argument is used when `source` is "ngc". Currently, all ngc bundles + have the ``monai_`` prefix, which is not existing in their model zoo contrasts. In order to + maintain the consistency between these two sources, remove prefix is necessary. + Therefore, if specified, downloaded folder name will remove the prefix. progress: whether to display a progress bar. args_file: a JSON or YAML file to provide default values for all the args in this function. so that the command line inputs can be simplified. @@ -215,17 +273,20 @@ def download( source=source, repo=repo, url=url, + remove_prefix=remove_prefix, progress=progress, ) _log_input_summary(tag="download", args=_args) - source_, repo_, progress_, name_, version_, bundle_dir_, url_ = _pop_args( - _args, "source", "repo", "progress", name=None, version=None, bundle_dir=None, url=None + source_, progress_, remove_prefix_, repo_, name_, version_, bundle_dir_, url_ = _pop_args( + _args, "source", "progress", remove_prefix=None, repo=None, name=None, version=None, bundle_dir=None, url=None ) bundle_dir_ = _process_bundle_dir(bundle_dir_) - if name_ is not None and version_ is not None: - name_ = "_v".join([name_, version_]) + if repo_ is None: + repo_ = "Project-MONAI/model-zoo/hosting_storage_v1" + if len(repo_.split("/")) != 3: + raise ValueError("repo should be in the form of `repo_owner/repo_name/release_tag`.") if url_ is not None: if name_ is not None: @@ -234,14 +295,27 @@ def download( filepath = bundle_dir_ / f"{_basename(url_)}" download_url(url=url_, filepath=filepath, hash_val=None, progress=progress_) extractall(filepath=filepath, output_dir=bundle_dir_, has_base=True) - elif source_ == "github": - if name_ is None: - raise ValueError(f"To download from source: Github, `name` must be provided, got {name_}.") - _download_from_github(repo=repo_, download_path=bundle_dir_, filename=name_, progress=progress_) else: - raise NotImplementedError( - f"Currently only download from provided URL in `url` or Github is implemented, got source: {source_}." - ) + if name_ is None: + raise ValueError(f"To download from source: {source_}, `name` must be provided.") + if version_ is None: + version_ = _get_latest_bundle_version(source=source_, name=name_, repo=repo_) + if source_ == "github": + if version_ is not None: + name_ = "_v".join([name_, version_]) + _download_from_github(repo=repo_, download_path=bundle_dir_, filename=name_, progress=progress_) + elif source_ == "ngc": + _download_from_ngc( + download_path=bundle_dir_, + filename=name_, + version=version_, + remove_prefix=remove_prefix_, + progress=progress_, + ) + else: + raise NotImplementedError( + f"Currently only download from `url`, source 'github' or 'ngc' are implemented, got source: {source_}." + ) def load( @@ -250,8 +324,8 @@ def load( model_file: Optional[str] = None, load_ts_module: bool = False, bundle_dir: Optional[PathLike] = None, - source: str = "github", - repo: str = "Project-MONAI/model-zoo/hosting_storage_v1", + source: str = download_source, + repo: Optional[str] = None, progress: bool = True, device: Optional[str] = None, key_in_ckpt: Optional[str] = None, @@ -263,18 +337,25 @@ def load( Load model weights or TorchScript module of a bundle. Args: - name: bundle name, for example: "spleen_ct_segmentation", "prostate_mri_anatomy" in the model-zoo: + name: bundle name. If `None` and `url` is `None`, it must be provided in `args_file`. + for example: + "spleen_ct_segmentation", "prostate_mri_anatomy" in model-zoo: https://github.com/Project-MONAI/model-zoo/releases/tag/hosting_storage_v1. - version: version name of the target bundle to download, like: "0.1.0". + "monai_brats_mri_segmentation" in ngc: + https://catalog.ngc.nvidia.com/models?filters=&orderBy=scoreDESC&query=monai. + version: version name of the target bundle to download, like: "0.1.0". If `None`, will download + the latest version. model_file: the relative path of the model weights or TorchScript module within bundle. If `None`, "models/model.pt" or "models/model.ts" will be used. load_ts_module: a flag to specify if loading the TorchScript module. bundle_dir: directory the weights/TorchScript module will be loaded from. Default is `bundle` subfolder under `torch.hub.get_dir()`. source: storage location name. This argument is used when `model_file` is not existing locally and need to be - downloaded first. "github" is currently the only supported value. - repo: repo name. This argument is used when `model_file` is not existing locally and need to be - downloaded first. If `source` is "github", it should be in the form of "repo_owner/repo_name/release_tag". + downloaded first. + In default, the value is achieved from the environment variable BUNDLE_DOWNLOAD_SRC, and + it should be "ngc" or "github". + repo: repo name. This argument is used when `url` is `None` and `source` is "github". + If used, it should be in the form of "repo_owner/repo_name/release_tag". progress: whether to display a progress bar when downloading. device: target device of returned weights or module, if `None`, prefer to "cuda" if existing. key_in_ckpt: for nested checkpoint like `{"model": XXX, "optimizer": XXX, ...}`, specify the key of model @@ -421,7 +502,7 @@ def get_bundle_versions( bundles_info = _get_all_bundles_info(repo=repo, tag=tag, auth_token=auth_token) if bundle_name not in bundles_info: - raise ValueError(f"bundle: {bundle_name} is not existing.") + raise ValueError(f"bundle: {bundle_name} is not existing in repo: {repo}.") bundle_info = bundles_info[bundle_name] all_versions = sorted(bundle_info.keys()) diff --git a/tests/ngc_mmar_loading.py b/tests/ngc_bundle_download.py similarity index 68% rename from tests/ngc_mmar_loading.py rename to tests/ngc_bundle_download.py index 8dca4f72c0..5e69bf4d63 100644 --- a/tests/ngc_mmar_loading.py +++ b/tests/ngc_bundle_download.py @@ -11,14 +11,50 @@ import os import sys +import tempfile import unittest import torch from parameterized import parameterized +from monai.apps import check_hash from monai.apps.mmars import MODEL_DESC, load_from_mmar +from monai.bundle import download from monai.config import print_debug_info from monai.networks.utils import copy_model_state +from tests.utils import skip_if_downloading_fails, skip_if_quick, skip_if_windows + +TEST_CASE_NGC_1 = [ + "spleen_ct_segmentation", + "0.3.7", + None, + "monai_spleen_ct_segmentation", + "models/model.pt", + "b418a2dc8672ce2fd98dc255036e7a3d", +] +TEST_CASE_NGC_2 = [ + "monai_spleen_ct_segmentation", + "0.3.7", + "monai_", + "spleen_ct_segmentation", + "models/model.pt", + "b418a2dc8672ce2fd98dc255036e7a3d", +] + + +@skip_if_windows +class TestNgcBundleDownload(unittest.TestCase): + @parameterized.expand([TEST_CASE_NGC_1, TEST_CASE_NGC_2]) + @skip_if_quick + def test_ngc_download_bundle(self, bundle_name, version, remove_prefix, download_name, file_path, hash_val): + with skip_if_downloading_fails(): + with tempfile.TemporaryDirectory() as tempdir: + download( + name=bundle_name, source="ngc", version=version, bundle_dir=tempdir, remove_prefix=remove_prefix + ) + full_file_path = os.path.join(tempdir, download_name, file_path) + self.assertTrue(os.path.exists(full_file_path)) + self.assertTrue(check_hash(filepath=full_file_path, val=hash_val)) @unittest.skip("deprecating mmar tests") diff --git a/tests/test_bundle_download.py b/tests/test_bundle_download.py index 0bb7834dac..09cd0128f9 100644 --- a/tests/test_bundle_download.py +++ b/tests/test_bundle_download.py @@ -31,18 +31,16 @@ TEST_CASE_1 = ["test_bundle", None] -TEST_CASE_2 = ["test_bundle_v0.1.1", None] +TEST_CASE_2 = ["test_bundle", "0.1.1"] -TEST_CASE_3 = ["test_bundle", "0.1.1"] - -TEST_CASE_4 = [ +TEST_CASE_3 = [ ["model.pt", "model.ts", "network.json", "test_output.pt", "test_input.pt"], "test_bundle", "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/test_bundle.zip", "a131d39a0af717af32d19e565b434928", ] -TEST_CASE_5 = [ +TEST_CASE_4 = [ ["model.pt", "model.ts", "network.json", "test_output.pt", "test_input.pt"], "test_bundle", "Project-MONAI/MONAI-extra-test-data/0.8.1", @@ -50,7 +48,7 @@ "model.pt", ] -TEST_CASE_6 = [ +TEST_CASE_5 = [ ["test_output.pt", "test_input.pt"], "test_bundle", "0.1.1", @@ -62,9 +60,9 @@ @skip_if_windows class TestDownload(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_3]) + @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) @skip_if_quick - def test_download_bundle(self, bundle_name, version): + def test_github_download_bundle(self, bundle_name, version): bundle_files = ["model.pt", "model.ts", "network.json", "test_output.pt", "test_input.pt"] repo = "Project-MONAI/MONAI-extra-test-data/0.8.1" hash_val = "a131d39a0af717af32d19e565b434928" @@ -72,7 +70,7 @@ def test_download_bundle(self, bundle_name, version): # download a whole bundle from github releases with tempfile.TemporaryDirectory() as tempdir: cmd = ["coverage", "run", "-m", "monai.bundle", "download", "--name", bundle_name, "--source", "github"] - cmd += ["--bundle_dir", tempdir, "--repo", repo, "--progress", "False"] + cmd += ["--bundle_dir", tempdir, "--repo", repo] if version is not None: cmd += ["--version", version] command_line_tests(cmd) @@ -82,7 +80,7 @@ def test_download_bundle(self, bundle_name, version): if file == "network.json": self.assertTrue(check_hash(filepath=file_path, val=hash_val)) - @parameterized.expand([TEST_CASE_4]) + @parameterized.expand([TEST_CASE_3]) @skip_if_quick def test_url_download_bundle(self, bundle_files, bundle_name, url, hash_val): with skip_if_downloading_fails(): @@ -103,7 +101,7 @@ def test_url_download_bundle(self, bundle_files, bundle_name, url, hash_val): class TestLoad(unittest.TestCase): - @parameterized.expand([TEST_CASE_5]) + @parameterized.expand([TEST_CASE_4]) @skip_if_quick def test_load_weights(self, bundle_files, bundle_name, repo, device, model_file): with skip_if_downloading_fails(): @@ -150,7 +148,7 @@ def test_load_weights(self, bundle_files, bundle_name, repo, device, model_file) output_2 = model_2.forward(input_tensor) assert_allclose(output_2, expected_output, atol=1e-4, rtol=1e-4, type_test=False) - @parameterized.expand([TEST_CASE_6]) + @parameterized.expand([TEST_CASE_5]) @skip_if_quick @SkipIfBeforePyTorchVersion((1, 7, 1)) def test_load_ts_module(self, bundle_files, bundle_name, version, repo, device, model_file): From a068209d7d15eabdbb6be33ed963d4f8c6827c9f Mon Sep 17 00:00:00 2001 From: Wenqi Li <831580+wyli@users.noreply.github.com> Date: Sun, 18 Dec 2022 11:01:24 +0000 Subject: [PATCH 4/6] update figures (#5760) Signed-off-by: Wenqi Li part of #5626 ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Wenqi Li --- docs/images/hovernet_diagram.png | Bin 0 -> 55130 bytes docs/source/whatsnew_1_1.md | 12 ++++++++++-- monai/bundle/scripts.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 docs/images/hovernet_diagram.png diff --git a/docs/images/hovernet_diagram.png b/docs/images/hovernet_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..aa7adcbdcfe0666c033715e10ae1dbcdedf493a4 GIT binary patch literal 55130 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 literal 0 HcmV?d00001 diff --git a/docs/source/whatsnew_1_1.md b/docs/source/whatsnew_1_1.md index 2c16a417ad..261af460fc 100644 --- a/docs/source/whatsnew_1_1.md +++ b/docs/source/whatsnew_1_1.md @@ -8,6 +8,8 @@ ## Digital pathology workflows +![hovernet](../images/hovernet_diagram.png) + Hover-Net is a model for simultaneous segmentation and classification of nuclei in multi-tissue histology images (Graham et al. Medical Image Analysis, 2019). We have added support for this model in MONAI by implementing several new components, enhancing existing ones and providing pipelines and examples for training, validation and inference. @@ -22,6 +24,8 @@ and [inference](https://github.com/Project-MONAI/tutorials/blob/main/pathology/n ## Experiment management for MONAI bundle +![exp_mgmt](../images/exp_mgmt.png) + In this release, experiment management features are integrated with MONAI bundle. It provides essential APIs for managing the end-to-end model bundle lifecycle. Users can start tracking experiments by, for example, appending `--tracking "mlflow"` to the training or inference commands to enable the MLFlow-based management. @@ -38,6 +42,8 @@ CT and PET images of various resolutions and sizes. A tutorial example of running Auto3DSeg on the HECKTOR22 challenge dataset is available in MONAI Tutorials. The tutorial is based on [the HECKTOR22 challenge 1st place solution](https://arxiv.org/abs/2209.10809). - A new improved version of `Segresnet` Algo is now available in `AutoRunner`. +In this version, data caching is more efficient and the preprocessing transforms are more flexible. +The workflow progresses including the timings of steps are written to console output as well as a YAML file. - Automatic customization and optimization of the model training configuration can be achieved according to the GPU devices used. The feature focuses on determining parameters including batch size of model @@ -52,8 +58,10 @@ Notably, - The `mednist_reg` model demonstrates how to build image registration workflows in MONAI bundle format. The model uses a ResNet and spatial transformer for hand X-ray image registration based on [the registration_mednist tutorial](https://github.com/Project-MONAI/tutorials/blob/main/2d_registration/registration_mednist.ipynb), -- `pathology_nuclei_segmentation_and_classification`, `pathology_nuclick_annotation`, and -`pathology_nuclei_classification` bundles are built for digital pathology image analysis. +- `pathology_nuclei_segmentation_and_classification`, + `pathology_nuclick_annotation`, and `pathology_nuclei_classification` bundles + are built for [digital pathology image + analysis](https://github.com/Project-MONAI/model-zoo/tree/dev/models/pathology_nuclei_segmentation_classification). For more details about how to use the models, please see [the tutorials](https://github.com/Project-MONAI/tutorials/tree/main/model_zoo). diff --git a/monai/bundle/scripts.py b/monai/bundle/scripts.py index dc1fc2f2d7..f81905a961 100644 --- a/monai/bundle/scripts.py +++ b/monai/bundle/scripts.py @@ -166,7 +166,7 @@ def _download_from_ngc(download_path: Path, filename: str, version: str, remove_ url = _get_ngc_bundle_url(model_name=filename, version=version) filepath = download_path / f"{filename}_v{version}.zip" if remove_prefix: - filename = _remove_ngc_prefix(filename) + filename = _remove_ngc_prefix(filename, prefix=remove_prefix) extract_path = download_path / f"{filename}" download_url(url=url, filepath=filepath, hash_val=None, progress=progress) extractall(filepath=filepath, output_dir=extract_path, has_base=True) From 8037fcdfb81a578b59966497f1184b8bc3215761 Mon Sep 17 00:00:00 2001 From: Dong Yang Date: Mon, 19 Dec 2022 04:53:38 -0500 Subject: [PATCH 5/6] [Auto3DSeg] Update ALGO_BASH for support num_worker and test cases to reflect unification of epoch-based training (#5763) Signed-off-by: dongy ### Description - Update ALGO_HASH to support `num_workers` override - Update test cases. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. - [x] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [x] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. Signed-off-by: dongy Co-authored-by: dongy Co-authored-by: Mingxin Zheng <18563433+mingxin-zheng@users.noreply.github.com> --- CITATION.cff | 2 -- monai/apps/auto3dseg/bundle_gen.py | 2 +- tests/test_auto3dseg_ensemble.py | 7 +++---- tests/test_auto3dseg_hpo.py | 7 +++---- tests/test_integration_autorunner.py | 22 +++++++++------------ tests/test_integration_gpu_customization.py | 7 +++---- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/CITATION.cff b/CITATION.cff index 9b8a9f6ec5..d00c8a364a 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -48,8 +48,6 @@ preferred-citation: family-names: "Xu" - given-names: "Ali" family-names: "Hatamizadeh" - - given-names: "Andriy" - family-names: "Myronenko" - given-names: "Wentao" family-names: "Zhu" - given-names: "Yun" diff --git a/monai/apps/auto3dseg/bundle_gen.py b/monai/apps/auto3dseg/bundle_gen.py index efbecb2061..59e90b795b 100644 --- a/monai/apps/auto3dseg/bundle_gen.py +++ b/monai/apps/auto3dseg/bundle_gen.py @@ -32,7 +32,7 @@ from monai.utils import ensure_tuple logger = get_logger(module_name=__name__) -ALGO_HASH = os.environ.get("MONAI_ALGO_HASH", "c812e5f") +ALGO_HASH = os.environ.get("MONAI_ALGO_HASH", "1dde7a1") __all__ = ["BundleAlgo", "BundleGen"] diff --git a/tests/test_auto3dseg_ensemble.py b/tests/test_auto3dseg_ensemble.py index 514844ff07..24cf37201e 100644 --- a/tests/test_auto3dseg_ensemble.py +++ b/tests/test_auto3dseg_ensemble.py @@ -49,11 +49,10 @@ train_param = ( { "CUDA_VISIBLE_DEVICES": list(range(num_gpus)), - "num_iterations": int(4 / num_gpus), - "num_iterations_per_validation": int(4 / num_gpus), "num_images_per_batch": 2, - "num_epochs": 1, - "num_warmup_iterations": int(4 / num_gpus), + "num_epochs": 2, + "num_epochs_per_validation": 1, + "num_warmup_epochs": 1, "use_pretrain": False, "pretrained_path": "", } diff --git a/tests/test_auto3dseg_hpo.py b/tests/test_auto3dseg_hpo.py index acb2721732..30c2361ef9 100644 --- a/tests/test_auto3dseg_hpo.py +++ b/tests/test_auto3dseg_hpo.py @@ -33,11 +33,10 @@ override_param = ( { "CUDA_VISIBLE_DEVICES": list(range(num_gpus)), - "num_iterations": int(4 / num_gpus), - "num_iterations_per_validation": int(4 / num_gpus), "num_images_per_batch": 2, - "num_epochs": 1, - "num_warmup_iterations": int(4 / num_gpus), + "num_epochs": 2, + "num_epochs_per_validation": 1, + "num_warmup_epochs": 1, "use_pretrain": False, "pretrained_path": "", } diff --git a/tests/test_integration_autorunner.py b/tests/test_integration_autorunner.py index 4067aa1c6d..237045fda7 100644 --- a/tests/test_integration_autorunner.py +++ b/tests/test_integration_autorunner.py @@ -49,11 +49,10 @@ train_param = ( { "CUDA_VISIBLE_DEVICES": list(range(num_gpus)), - "num_iterations": int(4 / num_gpus), - "num_iterations_per_validation": int(4 / num_gpus), "num_images_per_batch": 2, - "num_epochs": 1, - "num_warmup_iterations": int(4 / num_gpus), + "num_epochs": 2, + "num_epochs_per_validation": 1, + "num_warmup_epochs": 1, "use_pretrain": False, "pretrained_path": "", } @@ -145,24 +144,21 @@ def test_autorunner_hpo(self) -> None: runner = AutoRunner(work_dir=work_dir, input=self.data_src_cfg, hpo=True, ensemble=False) hpo_param = { "CUDA_VISIBLE_DEVICES": train_param["CUDA_VISIBLE_DEVICES"], - "num_iterations": train_param["num_iterations"], - "num_iterations_per_validation": train_param["num_iterations_per_validation"], + "num_epochs_per_validation": train_param["num_epochs_per_validation"], "num_images_per_batch": train_param["num_images_per_batch"], "num_epochs": train_param["num_epochs"], - "num_warmup_iterations": train_param["num_warmup_iterations"], + "num_warmup_epochs": train_param["num_warmup_epochs"], "use_pretrain": train_param["use_pretrain"], "pretrained_path": train_param["pretrained_path"], # below are to shorten the time for dints - "training#num_iterations": train_param["num_iterations"], - "training#num_iterations_per_validation": train_param["num_iterations_per_validation"], + "training#num_epochs_per_validation": train_param["num_epochs_per_validation"], "training#num_images_per_batch": train_param["num_images_per_batch"], "training#num_epochs": train_param["num_epochs"], - "training#num_warmup_iterations": train_param["num_warmup_iterations"], - "searching#num_iterations": train_param["num_iterations"], - "searching#num_iterations_per_validation": train_param["num_iterations_per_validation"], + "training#num_warmup_epochs": train_param["num_warmup_epochs"], + "searching#num_epochs_per_validation": train_param["num_epochs_per_validation"], "searching#num_images_per_batch": train_param["num_images_per_batch"], "searching#num_epochs": train_param["num_epochs"], - "searching#num_warmup_iterations": train_param["num_warmup_iterations"], + "searching#num_warmup_epochs": train_param["num_warmup_epochs"], "nni_dry_run": True, } search_space = {"learning_rate": {"_type": "choice", "_value": [0.0001, 0.001, 0.01, 0.1]}} diff --git a/tests/test_integration_gpu_customization.py b/tests/test_integration_gpu_customization.py index c9e2cfec08..787c222c9f 100644 --- a/tests/test_integration_gpu_customization.py +++ b/tests/test_integration_gpu_customization.py @@ -49,11 +49,10 @@ train_param = ( { "CUDA_VISIBLE_DEVICES": list(range(num_gpus)), - "num_iterations": int(4 / num_gpus), - "num_iterations_per_validation": int(4 / num_gpus), "num_images_per_batch": 2, - "num_epochs": 1, - "num_warmup_iterations": int(4 / num_gpus), + "num_epochs": 2, + "num_epochs_per_validation": 1, + "num_warmup_epochs": 1, "use_pretrain": False, "pretrained_path": "", } From b159ce78d0ee14041e6f1f19f478394d91ab1d85 Mon Sep 17 00:00:00 2001 From: Yiheng Wang <68361391+yiheng-wang-nv@users.noreply.github.com> Date: Mon, 19 Dec 2022 22:28:54 +0800 Subject: [PATCH 6/6] 5770 add remove prefix to monai.bundle.load (#5771) Signed-off-by: Yiheng Wang Fixes #5770 . ### Description This PR enables the `remove_prefix` arg in `monai.bundle.load` ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. Signed-off-by: Yiheng Wang --- monai/bundle/scripts.py | 19 ++++++++++++++++++- tests/ngc_bundle_download.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/monai/bundle/scripts.py b/monai/bundle/scripts.py index f81905a961..b625578049 100644 --- a/monai/bundle/scripts.py +++ b/monai/bundle/scripts.py @@ -326,6 +326,7 @@ def load( bundle_dir: Optional[PathLike] = None, source: str = download_source, repo: Optional[str] = None, + remove_prefix: Optional[str] = "monai_", progress: bool = True, device: Optional[str] = None, key_in_ckpt: Optional[str] = None, @@ -356,6 +357,10 @@ def load( it should be "ngc" or "github". repo: repo name. This argument is used when `url` is `None` and `source` is "github". If used, it should be in the form of "repo_owner/repo_name/release_tag". + remove_prefix: This argument is used when `source` is "ngc". Currently, all ngc bundles + have the ``monai_`` prefix, which is not existing in their model zoo contrasts. In order to + maintain the consistency between these two sources, remove prefix is necessary. + Therefore, if specified, downloaded folder name will remove the prefix. progress: whether to display a progress bar when downloading. device: target device of returned weights or module, if `None`, prefer to "cuda" if existing. key_in_ckpt: for nested checkpoint like `{"model": XXX, "optimizer": XXX, ...}`, specify the key of model @@ -379,9 +384,21 @@ def load( if model_file is None: model_file = os.path.join("models", "model.ts" if load_ts_module is True else "model.pt") + if source == "ngc": + name = _add_ngc_prefix(name) + if remove_prefix: + name = _remove_ngc_prefix(name, prefix=remove_prefix) full_path = os.path.join(bundle_dir_, name, model_file) if not os.path.exists(full_path): - download(name=name, version=version, bundle_dir=bundle_dir_, source=source, repo=repo, progress=progress) + download( + name=name, + version=version, + bundle_dir=bundle_dir_, + source=source, + repo=repo, + remove_prefix=remove_prefix, + progress=progress, + ) if device is None: device = "cuda:0" if is_available() else "cpu" diff --git a/tests/ngc_bundle_download.py b/tests/ngc_bundle_download.py index 5e69bf4d63..2b376c3c2d 100644 --- a/tests/ngc_bundle_download.py +++ b/tests/ngc_bundle_download.py @@ -19,10 +19,10 @@ from monai.apps import check_hash from monai.apps.mmars import MODEL_DESC, load_from_mmar -from monai.bundle import download +from monai.bundle import download, load from monai.config import print_debug_info from monai.networks.utils import copy_model_state -from tests.utils import skip_if_downloading_fails, skip_if_quick, skip_if_windows +from tests.utils import assert_allclose, skip_if_downloading_fails, skip_if_quick, skip_if_windows TEST_CASE_NGC_1 = [ "spleen_ct_segmentation", @@ -41,6 +41,30 @@ "b418a2dc8672ce2fd98dc255036e7a3d", ] +TESTCASE_WEIGHTS = { + "key": "model.0.conv.unit0.adn.N.bias", + "value": torch.tensor( + [ + -0.0705, + -0.0937, + -0.0422, + -0.2068, + 0.1023, + -0.2007, + -0.0883, + 0.0018, + -0.1719, + 0.0116, + 0.0285, + -0.0044, + 0.1223, + -0.1287, + -0.1858, + 0.0460, + ] + ), +} + @skip_if_windows class TestNgcBundleDownload(unittest.TestCase): @@ -56,6 +80,13 @@ def test_ngc_download_bundle(self, bundle_name, version, remove_prefix, download self.assertTrue(os.path.exists(full_file_path)) self.assertTrue(check_hash(filepath=full_file_path, val=hash_val)) + weights = load( + name=bundle_name, source="ngc", version=version, bundle_dir=tempdir, remove_prefix=remove_prefix + ) + assert_allclose( + weights[TESTCASE_WEIGHTS["key"]], TESTCASE_WEIGHTS["value"], atol=1e-4, rtol=1e-4, type_test=False + ) + @unittest.skip("deprecating mmar tests") class TestAllDownloadingMMAR(unittest.TestCase):