From cc452e302dcf7d9a04ade316279403deb2e92855 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Wed, 7 Jul 2021 17:34:37 +0100 Subject: [PATCH 1/6] adds whats new 0.6 Signed-off-by: Wenqi Li --- docs/source/index.rst | 3 ++- docs/source/{whatsnew.md => whatsnew_0_5.md} | 2 +- docs/source/whatsnew_0_6.md | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) rename docs/source/{whatsnew.md => whatsnew_0_5.md} (99%) create mode 100644 docs/source/whatsnew_0_6.md diff --git a/docs/source/index.rst b/docs/source/index.rst index 0e7c0eb90f..30671427a4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -45,7 +45,8 @@ Technical documentation is available at `docs.monai.io `_ :maxdepth: 1 :caption: Feature highlights - whatsnew.md + whatsnew_0_6.md + whatsnew_0_5.md highlights.md .. toctree:: diff --git a/docs/source/whatsnew.md b/docs/source/whatsnew_0_5.md similarity index 99% rename from docs/source/whatsnew.md rename to docs/source/whatsnew_0_5.md index bce94f6562..546944351d 100644 --- a/docs/source/whatsnew.md +++ b/docs/source/whatsnew_0_5.md @@ -1,4 +1,4 @@ -# What's new in 0.5 🎉 +# What's new in 0.5 ## Invert spatial transforms and test-time augmentations It is often desirable to invert the previously applied spatial transforms (resize, flip, rotate, zoom, crop, pad, etc.) with the deep learning workflows, for example, to resume to the original imaging space after processing the image data in a normalized data space. We enhance almost all the spatial transforms with an `inverse` operation and release this experimental feature in v0.5. Users can easily invert all the spatial transforms for one transformed data item or a batch of data items. It also can be achieved within the workflows by using the `TransformInverter` handler. diff --git a/docs/source/whatsnew_0_6.md b/docs/source/whatsnew_0_6.md new file mode 100644 index 0000000000..73f165d493 --- /dev/null +++ b/docs/source/whatsnew_0_6.md @@ -0,0 +1,13 @@ +# What's new in 0.6 🎉🎉 + +## Pythonic APIs to load the pretrained models from Clara Train MMARs + +## Enhanced base metric interfaces + +## Decollating mini-batches as an essential postprocessing step + +## MONAI C++/CUDA extension modules via PyTorch JIT compilation + +## Enhanced pre-merge continuous integration and continuous delivery via Blossom + +## Collaboration with the MONAILabel project for smooth integration \ No newline at end of file From 723b250c994c7d57426528eacb31e43bb515f434 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 8 Jul 2021 13:32:27 +0100 Subject: [PATCH 2/6] update docs Signed-off-by: Wenqi Li --- docs/source/highlights.md | 4 +-- docs/source/whatsnew_0_6.md | 50 +++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/docs/source/highlights.md b/docs/source/highlights.md index ff56ddae47..61935fd3dc 100644 --- a/docs/source/highlights.md +++ b/docs/source/highlights.md @@ -390,8 +390,8 @@ Distributed data parallel is an important feature of PyTorch to connect multiple ### 3. C++/CUDA optimized modules To further accelerate the domain-specific routines in the workflows, MONAI C++/CUDA implementation are introduced as extensions of the PyTorch native implementations. -MONAI provides the modules using [the two ways of building C++ extensions from PyTorch](https://pytorch.org/tutorials/advanced/cpp_extension.html#jit-compiling-extensions): -- via `setuptools`, for modules such as `Resampler`, `Conditional random field (CRF)`, `Fast bilateral filtering using the permutohedral lattice`, +MONAI provides the modules using [the two ways of building C++ extensions from PyTorch](https://pytorch.org/tutorials/advanced/cpp_extension.html#custom-c-and-cuda-extensions): +- via `setuptools`, for modules including `Resampler`, `Conditional random field (CRF)`, `Fast bilateral filtering using the permutohedral lattice`. - via just-in-time (JIT) compilation, for the `Gaussian mixtures` module. This approach allows for dynamic optimisation according to the user-specified parameters and local system environments. The following figure shows results of MONAI's Gaussian mixture models applied to tissue and surgical tools segmentation: ![Gaussian mixture models as a postprocessing step](../images/gmm_feature_set_comparison_s.png) diff --git a/docs/source/whatsnew_0_6.md b/docs/source/whatsnew_0_6.md index 73f165d493..f6e8be8430 100644 --- a/docs/source/whatsnew_0_6.md +++ b/docs/source/whatsnew_0_6.md @@ -1,13 +1,53 @@ # What's new in 0.6 🎉🎉 +## Decollating mini-batches as an essential post-processing step +`decollate batch` is introduced in MONAI v0.6, to simplify the post-processing transforms and enable flexible following operations on a batch of model outputs. +It can decollate batched data (e.g. model inference predictions) into a list of tensors -- as an 'inverse' operation of `collate_fn` of the PyTorch data loader -- for the benefits such as: +1. enabling postprocessing transforms for each item independently, for example, randomised transforms could be applied differently for each predicted item in a batch. +2. simplifying the transform APIs and reducing the input validation burdens, because both the preprocessing and postprocessing transforms now only support the "channel-first" input format. +3. enabling the `Invertd` transform for the predictions and the inverted data with different shapes, as the data items are decollated to a list, instead of being stacked in a single tensor. +4. allowing for both a "batch-first" tensor and a list of "channel-first" tensors for flexible metric computation. + +A typical process of `decollate batch` is illustrated as follows (with a `batch_size=N` model predictions and labels as an example): +![decollate_batch](../images/decollate_batch.png) + +[decollate batch tutorial](https://github.com/Project-MONAI/tutorials/blob/master/modules/decollate_batch.ipynb) shows a detailed usage example based on a PyTorch native workflow. + ## Pythonic APIs to load the pretrained models from Clara Train MMARs +[The MMAR (Medical Model ARchive)](https://docs.nvidia.com/clara/clara-train-sdk/pt/mmar.html) +defines a data structure for organizing all artifacts produced during the model development life cycle. +NVIDIA Clara provides [various MMARs of medical domain-specific models](https://ngc.nvidia.com/catalog/models?orderBy=scoreDESC&pageNumber=0&query=clara_pt&quickFilter=&filters=). +These MMARs include all the information about the model including configurations and scripts to provide a workspace to perform model development tasks. To better leverage the trained MMARs released on Nvidia GPU cloud, MONAI provides pythonic APIs to access them. + +To demonstrate this new feature, a medical image segmentation tutorial is created within +[`project-monai/tutorials`](https://github.com/Project-MONAI/tutorials/blob/master/modules/transfer_mmar.ipynb)). +It mainly produces the following figure to compare the loss curves and validation scores for +(1) training from scratch (the green line), +(2) applying pretrained MMAR weights without training (the magenta line), +(3) training from the MMAR model weights (the blue +line), according to the number of training epochs: +![transfer_mmar](../images/transfer_mmar.png) + +The tutorial shows the capability of encapsulating the details of MMAR parsing, as well as the potential of using pretrained MMARs for transfer learning. +These APIs are also being integrated into AI-assisted interactive workflows to accelerate the manual annotating processes (e.g. via [project-MONAI/MONAILabel](https://github.com/Project-MONAI/MONAILabel)). -## Enhanced base metric interfaces +## Enhancements of the base metric interfaces +The base API for metrics is now enhanced to support the essential computation logic for both iteration and epoch-based metrics. +With this update, the MONAI metrics module becomes more extensible, and thus a good starting point for customised metrics. +The APIs also by default support data parallel computation and consider the computation efficiency: with a `Cumulative` base class, intermediate metric outcomes can be automatically buffered, cumulated, synced across distributed processes, and aggregated for the final results. The [multi-processing computation example](https://github.com/Project-MONAI/tutorials/blob/master/modules/compute_metric.py) shows how to compute metrics based on saved predictions and labels in multi-processing environment. -## Decollating mini-batches as an essential postprocessing step +## C++/CUDA extension modules via PyTorch JIT compilation +To further accelerate the domain-specific routines in the workflows, MONAI C++/CUDA modules are introduced as extensions of the PyTorch native implementation. +It now provides modules using [the two ways of building C++ extensions from PyTorch](https://pytorch.org/tutorials/advanced/cpp_extension.html#custom-c-and-cuda-extensions): +- via `setuptools` (since MONAI v0.5), for modules including `Resampler`, `Conditional random field (CRF)`, `Fast bilateral filtering using the permutohedral lattice`. +- via just-in-time (JIT) compilation (since MONAI v0.6), for the `Gaussian mixtures` module. This approach allows for dynamic optimisation according to the user-specified parameters and local system environments. +The following figure shows results of MONAI's Gaussian mixture models applied to a tissue and surgical tools segmentation task: +![Gaussian mixture models as a postprocessing step](../images/gmm_feature_set_comparison_s.png) -## MONAI C++/CUDA extension modules via PyTorch JIT compilation +## Backward compatibility and enhanced continuous integration/continuous delivery +Starting from this version, we experiment with basic policies of backward compatibility. +New utilities are introduced on top of the existing semantic versioning modules, and the git branching model. -## Enhanced pre-merge continuous integration and continuous delivery via Blossom +At the same time, we actively analyze efficient, scalable, and secure CI/CD solutions to accommodate fast and collaborative codebase development. -## Collaboration with the MONAILabel project for smooth integration \ No newline at end of file +Although a complete mechanism is still under development, These provide another essential step towards API-stable versions of MONAI, sustainable release cycles, and efficient open-source collaborations. From 27d9585926b430865ffe6c343311e42909494aa7 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 8 Jul 2021 13:38:27 +0100 Subject: [PATCH 3/6] add a list Signed-off-by: Wenqi Li --- docs/source/whatsnew_0_5.md | 5 +++++ docs/source/whatsnew_0_6.md | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/source/whatsnew_0_5.md b/docs/source/whatsnew_0_5.md index 546944351d..f353084303 100644 --- a/docs/source/whatsnew_0_5.md +++ b/docs/source/whatsnew_0_5.md @@ -1,5 +1,10 @@ # What's new in 0.5 +- Invert spatial transforms and test-time augmentations +- Lesion detection in digital pathology +- DeepGrow modules for interactive segmentation +- Various usability improvements + ## Invert spatial transforms and test-time augmentations It is often desirable to invert the previously applied spatial transforms (resize, flip, rotate, zoom, crop, pad, etc.) with the deep learning workflows, for example, to resume to the original imaging space after processing the image data in a normalized data space. We enhance almost all the spatial transforms with an `inverse` operation and release this experimental feature in v0.5. Users can easily invert all the spatial transforms for one transformed data item or a batch of data items. It also can be achieved within the workflows by using the `TransformInverter` handler. diff --git a/docs/source/whatsnew_0_6.md b/docs/source/whatsnew_0_6.md index f6e8be8430..16ccc0f52e 100644 --- a/docs/source/whatsnew_0_6.md +++ b/docs/source/whatsnew_0_6.md @@ -1,4 +1,12 @@ # What's new in 0.6 🎉🎉 + +- Decollating mini-batches as an essential post-processing step +- Pythonic APIs to load the pretrained models from Clara Train MMARs +- Enhancements of the base metric interfaces +- C++/CUDA extension modules via PyTorch JIT compilation +- Backward compatibility and enhanced continuous integration/continuous delivery + + ## Decollating mini-batches as an essential post-processing step `decollate batch` is introduced in MONAI v0.6, to simplify the post-processing transforms and enable flexible following operations on a batch of model outputs. It can decollate batched data (e.g. model inference predictions) into a list of tensors -- as an 'inverse' operation of `collate_fn` of the PyTorch data loader -- for the benefits such as: From 6f485bd8bfd6d2f67a95702f5e876c6d626162a9 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 8 Jul 2021 15:37:41 +0100 Subject: [PATCH 4/6] update according to comments Signed-off-by: Wenqi Li --- docs/source/whatsnew_0_6.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/source/whatsnew_0_6.md b/docs/source/whatsnew_0_6.md index 16ccc0f52e..a5144ed533 100644 --- a/docs/source/whatsnew_0_6.md +++ b/docs/source/whatsnew_0_6.md @@ -10,10 +10,10 @@ ## Decollating mini-batches as an essential post-processing step `decollate batch` is introduced in MONAI v0.6, to simplify the post-processing transforms and enable flexible following operations on a batch of model outputs. It can decollate batched data (e.g. model inference predictions) into a list of tensors -- as an 'inverse' operation of `collate_fn` of the PyTorch data loader -- for the benefits such as: -1. enabling postprocessing transforms for each item independently, for example, randomised transforms could be applied differently for each predicted item in a batch. -2. simplifying the transform APIs and reducing the input validation burdens, because both the preprocessing and postprocessing transforms now only support the "channel-first" input format. -3. enabling the `Invertd` transform for the predictions and the inverted data with different shapes, as the data items are decollated to a list, instead of being stacked in a single tensor. -4. allowing for both a "batch-first" tensor and a list of "channel-first" tensors for flexible metric computation. +- enabling postprocessing transforms for each item independently, for example, randomised transforms could be applied differently for each predicted item in a batch. +- simplifying the transform APIs and reducing the input validation burdens, because both the preprocessing and postprocessing transforms now only support the "channel-first" input format. +- enabling the `Invertd` transform for the predictions and the inverted data with different shapes, as the data items are decollated to a list, instead of being stacked in a single tensor. +- allowing for both a "batch-first" tensor and a list of "channel-first" tensors for flexible metric computation. A typical process of `decollate batch` is illustrated as follows (with a `batch_size=N` model predictions and labels as an example): ![decollate_batch](../images/decollate_batch.png) @@ -30,10 +30,11 @@ These MMARs include all the information about the model including configurations To demonstrate this new feature, a medical image segmentation tutorial is created within [`project-monai/tutorials`](https://github.com/Project-MONAI/tutorials/blob/master/modules/transfer_mmar.ipynb)). It mainly produces the following figure to compare the loss curves and validation scores for -(1) training from scratch (the green line), -(2) applying pretrained MMAR weights without training (the magenta line), -(3) training from the MMAR model weights (the blue +- training from scratch (the green line), +- applying pretrained MMAR weights without training (the magenta line), +- training from the MMAR model weights (the blue line), according to the number of training epochs: + ![transfer_mmar](../images/transfer_mmar.png) The tutorial shows the capability of encapsulating the details of MMAR parsing, as well as the potential of using pretrained MMARs for transfer learning. From a9119b39b79ae2bb9bea0afe8b32409af3ebfa43 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 8 Jul 2021 16:25:48 +0100 Subject: [PATCH 5/6] fixes typos Signed-off-by: Wenqi Li --- docs/source/whatsnew_0_6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/whatsnew_0_6.md b/docs/source/whatsnew_0_6.md index a5144ed533..309373c702 100644 --- a/docs/source/whatsnew_0_6.md +++ b/docs/source/whatsnew_0_6.md @@ -12,7 +12,7 @@ It can decollate batched data (e.g. model inference predictions) into a list of tensors -- as an 'inverse' operation of `collate_fn` of the PyTorch data loader -- for the benefits such as: - enabling postprocessing transforms for each item independently, for example, randomised transforms could be applied differently for each predicted item in a batch. - simplifying the transform APIs and reducing the input validation burdens, because both the preprocessing and postprocessing transforms now only support the "channel-first" input format. -- enabling the `Invertd` transform for the predictions and the inverted data with different shapes, as the data items are decollated to a list, instead of being stacked in a single tensor. +- enabling the transform inverse operation for data items in different original shapes, as the inverted items are in a list, instead of being stacked in a single tensor. - allowing for both a "batch-first" tensor and a list of "channel-first" tensors for flexible metric computation. A typical process of `decollate batch` is illustrated as follows (with a `batch_size=N` model predictions and labels as an example): From 81323e4e22fa02ed9bef9aec507b0fde6ce1c6a6 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Thu, 8 Jul 2021 17:50:15 +0100 Subject: [PATCH 6/6] Revert "update lmdbdataset (#2531)" This reverts commit a980ae4d98ef2742a5c1352fff1d11de6640c227. Signed-off-by: Wenqi Li --- monai/data/dataset.py | 76 ++++++++++++++++----------------------- tests/test_lmdbdataset.py | 5 +-- 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index b863bb58fe..4d18bd4e0d 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -413,10 +413,7 @@ def __init__( self.lmdb_kwargs = lmdb_kwargs or {} if not self.lmdb_kwargs.get("map_size", 0): self.lmdb_kwargs["map_size"] = 1024 ** 4 # default map_size - self._env = None - # lmdb is single-writer multi-reader by default - # the cache is created without multi-threading - self._read_env = self._fill_cache_start_reader(show_progress=self.progress) + self._read_env = None print(f"Accessing lmdb file: {self.db_file.absolute()}.") def set_data(self, data: Sequence): @@ -425,56 +422,43 @@ def set_data(self, data: Sequence): """ super().set_data(data=data) - self._env = None - self._read_env = self._fill_cache_start_reader(show_progress=self.progress) + self._read_env = None - def _fill_cache_start_reader(self, show_progress=True): - """ - Check the LMDB cache and write the cache if needed. py-lmdb doesn't have a good support for concurrent write. - This method can be used with multiple processes, but it may have a negative impact on the performance. - - Args: - show_progress: whether to show the progress bar if possible. - """ + def _fill_cache_start_reader(self): # create cache self.lmdb_kwargs["readonly"] = False - if self._env is None: - self._env = lmdb.open(path=f"{self.db_file}", subdir=False, **self.lmdb_kwargs) - env = self._env - if show_progress and not has_tqdm: + env = lmdb.open(path=f"{self.db_file}", subdir=False, **self.lmdb_kwargs) + if self.progress and not has_tqdm: warnings.warn("LMDBDataset: tqdm is not installed. not displaying the caching progress.") - with env.begin(write=False) as search_txn: - for item in tqdm(self.data) if has_tqdm and show_progress else self.data: - key = self.hash_func(item) - done, retry, val = False, 5, None - while not done and retry > 0: - try: - with search_txn.cursor() as cursor: + for item in tqdm(self.data) if has_tqdm and self.progress else self.data: + key = self.hash_func(item) + done, retry, val = False, 5, None + while not done and retry > 0: + try: + with env.begin(write=True) as txn: + with txn.cursor() as cursor: done = cursor.set_key(key) - if done: - continue + if done: + continue if val is None: val = self._pre_transform(deepcopy(item)) # keep the original hashed val = pickle.dumps(val, protocol=self.pickle_protocol) - with env.begin(write=True) as txn: - txn.put(key, val) - done = True - except lmdb.MapFullError: - done, retry = False, retry - 1 - size = env.info()["map_size"] - new_size = size * 2 - warnings.warn( - f"Resizing the cache database from {int(size) >> 20}MB" f" to {int(new_size) >> 20}MB." - ) - env.set_mapsize(new_size) - except lmdb.MapResizedError: - # the mapsize is increased by another process - # set_mapsize with a size of 0 to adopt the new size - env.set_mapsize(0) - if not done: # still has the map full error + txn.put(key, val) + done = True + except lmdb.MapFullError: + done, retry = False, retry - 1 size = env.info()["map_size"] - env.close() - raise ValueError(f"LMDB map size reached, increase size above current size of {size}.") + new_size = size * 2 + warnings.warn(f"Resizing the cache database from {int(size) >> 20}MB to {int(new_size) >> 20}MB.") + env.set_mapsize(new_size) + except lmdb.MapResizedError: + # the mapsize is increased by another process + # set_mapsize with a size of 0 to adopt the new size, + env.set_mapsize(0) + if not done: # still has the map full error + size = env.info()["map_size"] + env.close() + raise ValueError(f"LMDB map size reached, increase size above current size of {size}.") size = env.info()["map_size"] env.close() # read-only database env @@ -492,7 +476,7 @@ def _cachecheck(self, item_transformed): """ if self._read_env is None: - self._read_env = self._fill_cache_start_reader(show_progress=False) + self._read_env = self._fill_cache_start_reader() with self._read_env.begin(write=False) as txn: data = txn.get(self.hash_func(item_transformed)) if data is None: diff --git a/tests/test_lmdbdataset.py b/tests/test_lmdbdataset.py index fbdb651297..3e3aed709f 100644 --- a/tests/test_lmdbdataset.py +++ b/tests/test_lmdbdataset.py @@ -191,15 +191,12 @@ def test_shape(self, transform, expected_shape, kwargs=None): "extra": os.path.join(tempdir, "test_extra2_new.nii.gz"), }, ] + dataset_postcached.set_data(data=test_data_new) # test new exchanged cache content if transform is None: - dataset_postcached.set_data(data=test_data_new) self.assertEqual(dataset_postcached[0]["image"], os.path.join(tempdir, "test_image1_new.nii.gz")) self.assertEqual(dataset_postcached[0]["label"], os.path.join(tempdir, "test_label1_new.nii.gz")) self.assertEqual(dataset_postcached[1]["extra"], os.path.join(tempdir, "test_extra2_new.nii.gz")) - else: - with self.assertRaises(RuntimeError): - dataset_postcached.set_data(data=test_data_new) # filename list updated, files do not exist @skip_if_windows