From dd74c59b430716cf0546efe966ede99c5597f19a Mon Sep 17 00:00:00 2001 From: Can Zhao <69829124+Can-Zhao@users.noreply.github.com> Date: Wed, 5 Jan 2022 12:41:27 -0500 Subject: [PATCH] rebase dev and add box utils (#3586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 3415 Update WSIReader (#3417) * Update WSIReader level/location/size calculation Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update location downsampling Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update tests and add a new test case Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update few names and logics Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Fix the dependency issue Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Check for imagecodecs + tifffile Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Remove new test case that uses too much memory Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Add new case and ignore level=0 for TiffFile Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Address comments Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> Co-authored-by: Nic Ma * update create_file_basename (#3436) Signed-off-by: Wenqi Li * Update TiffFile backend in WSIReader (#3438) * Update TiffFile backend to read only the entire image Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * 3429 Enhance the scalar write logic of TensorBoardStatsHandler (#3431) * [DLMED] extract write logic Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * 3430 support dataframes and streams in CSVDataset (#3440) * [DLMED] add dataframe Signed-off-by: Nic Ma * [DLMED] enhance CSV iterable dataset Signed-off-by: Nic Ma * [DLMED] add unit tests Signed-off-by: Nic Ma * [DLMED] fix typehints Signed-off-by: Nic Ma * [DLMED] add comment Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] fix file close issue Signed-off-by: Nic Ma * [DLMED] fix doc Signed-off-by: Nic Ma * [MONAI] python code formatting Signed-off-by: monai-bot Co-authored-by: monai-bot * Add base class for workflows (#3445) * [DLMED] add BaseWorkflow Signed-off-by: Nic Ma * [DLMED] fix typo Signed-off-by: Nic Ma * [MONAI] python code formatting Signed-off-by: monai-bot * [DLMED] add *args, **kwargs Signed-off-by: Nic Ma Co-authored-by: monai-bot * Enhance deprecated arg for kwargs in CSV datasets (#3446) * [DLMED] fix deprecated arg Signed-off-by: Nic Ma * [MONAI] python code formatting Signed-off-by: monai-bot Co-authored-by: monai-bot * 3293 Remove extra deep supervision modules of DynUNet (#3427) * enhance dynunet Signed-off-by: Yiheng Wang * fix black issue Signed-off-by: Yiheng Wang * use strict=False Signed-off-by: Yiheng Wang * fix black 21.12 error Signed-off-by: Yiheng Wang * enhance code and update docstring Signed-off-by: Yiheng Wang * 471- fixes deprecated args (#3447) * fixes deprecated args Signed-off-by: Wenqi Li * update based on comments Signed-off-by: Wenqi Li * improve error message if reader nott available (#3457) improve error message if reader nott available * adds the missing imports (#3462) Signed-off-by: Wenqi Li * revise MILModel docstring (#3459) Signed-off-by: Wenqi Li * deprecate reduction (#3464) Signed-off-by: Wenqi Li * 3444 Add DatasetFunc (#3456) * [DLMED] add dataset generator Signed-off-by: Nic Ma * [DLMED] add DatasetGenerator Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [MONAI] python code formatting Signed-off-by: monai-bot * [DLMED] fix wrong test Signed-off-by: Nic Ma * [DLMED] simplify according to comments Signed-off-by: Nic Ma * [DLMED] remove return Signed-off-by: Nic Ma * [DLMED] update rtol for CI Signed-off-by: Nic Ma Co-authored-by: monai-bot * Add missing components to API doc (#3468) * [DLMED] add missing docs Signed-off-by: Nic Ma * [DLMED] add missing components Signed-off-by: Nic Ma * [DLMED] fix test Signed-off-by: Nic Ma * 3466 3467 Add `channel_wise` and correct doc-string (#3469) * [DLMED] add channel-wise Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] fix typo Signed-off-by: Nic Ma * [DLMED] skip test if before 1.7 Signed-off-by: Nic Ma * [MONAI] python code formatting Signed-off-by: monai-bot Co-authored-by: monai-bot * [DLMED] remove cls (#3475) Signed-off-by: Nic Ma * Add Iteration base class (#3472) * [DLMED] add Iteration base class Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * fix link error (#3488) Signed-off-by: Yiheng Wang * 3465 Support string as dtype (#3478) * [DLMED] support string dtype Signed-off-by: Nic Ma * [DLMED] fix typo Signed-off-by: Nic Ma * [DLMED] enhance dtype in ToCupy Signed-off-by: Nic Ma * [DLMED] update to 0.4.7 (#3483) Signed-off-by: Nic Ma * Improve NVTX Range Naming (#3484) * Update to not include number for the name of the first range Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update CuCIM and TorchVision wrappers to include name Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update nvtx range to append undelying class for wrapper tranforms Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Add new test cases to cover changes Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * Update cucim and torchvision check Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com> * 3471 3491 Add example images for intensity transforms (#3494) * [DLMED] add missing images Signed-off-by: Nic Ma * [DLMED] fix 3471 Signed-off-by: Nic Ma * [DLMED] fix AsDiscrete Signed-off-by: Nic Ma * Make bending energy loss invariant to resolution (#3493) * make bending energy loss invariant to resolution fixes #3485 Signed-off-by: Ebrahim Ebrahim * set BendingEnergyLoss default normalize to False Maybe it's more important that the default behavior match usage of the term "bending energy" elsewhere, rather than that it be the most convenient behavior. Signed-off-by: Ebrahim Ebrahim * Removes redundant casting -- ensure tuple (#3495) Signed-off-by: Wenqi Li * 3498 Correct `kwargs` arg for `convert_to_torchscript` (#3499) * [DLMED] correct kwargs Signed-off-by: Nic Ma * [DLMED] fix grammar Signed-off-by: Nic Ma * update tests with 1.10.1 (#3500) Signed-off-by: Wenqi Li * 3501 Add dict version SavitzkyGolaySmoothd (#3502) * [DLMED] add SavitzkyGolaySmoothd Signed-off-by: Nic Ma * [DLMED] fix typo Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * remove file (#3507) Signed-off-by: Wenqi Li * avoid 60.0.0 (#3514) Signed-off-by: Wenqi Li * [DLMED] add 6 new transform images (#3512) Signed-off-by: Nic Ma * support of reversed indexing (#3508) Signed-off-by: Wenqi Li * Adding Torchscript utility functions (#3138) * Adding Torchscript utility functions Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [MONAI] python code formatting Signed-off-by: monai-bot * Adding Torchscript utility functions Signed-off-by: Eric Kerfoot * Added test for extra files Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Signed-off-by: Eric Kerfoot * Update Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update Signed-off-by: Eric Kerfoot * Update Signed-off-by: Eric Kerfoot * Updates Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updates Signed-off-by: Eric Kerfoot * Updates Signed-off-by: Eric Kerfoot Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: monai-bot Co-authored-by: Nic Ma * 3517 Refine AddCoordinateChannels transform (#3524) * [DLMED] change to utility transforms Signed-off-by: Nic Ma * [DLMED] enhance args Signed-off-by: Nic Ma * 3521 Copyright header update (#3522) * adds missing item Signed-off-by: Wenqi Li * update the contributing guide Signed-off-by: Wenqi Li * update copyright headers Signed-off-by: Wenqi Li * 3521 - adds a util to check the licence info (#3523) * util to check the licence info Signed-off-by: Wenqi Li * update flags Signed-off-by: Wenqi Li * update based on comments Signed-off-by: Wenqi Li * 3350 Remove PyTorch 1.5.x related logic and mark versions for all new APIs (#3526) * [DLMED] clarify old APIs Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * 3533 Update PyTorch docker to 21.12 (#3534) * [DLMED] update to 21.12 Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] add PyTorch 1.9 test Signed-off-by: Nic Ma * 3531 Add args to subclass of CacheDataset (#3532) * [DLMED] add missing args Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * [DLMED] update progress arg Signed-off-by: Nic Ma * 3525 Fix invertible issue in OneOf compose (#3530) * [DLMED] fix oneof Signed-off-by: Nic Ma * [DLMED] add more unit tests Signed-off-by: Nic Ma * [DLMED] update index Signed-off-by: Nic Ma * [DLMED] update according to comments Signed-off-by: Nic Ma * Revert "[DLMED] update according to comments" This reverts commit c6c3a35f451828570216622ee42c9a25caa1a769. Signed-off-by: Nic Ma * Revert "[DLMED] update index" This reverts commit 649a7c59e094283be9625b7b496c53b5884265ab. Signed-off-by: Nic Ma * 3535 - drop python 36 support (#3536) * drop py36 support Signed-off-by: Wenqi Li * drop 20.09 test because of python min version 3.6 Signed-off-by: Wenqi Li * update tests Signed-off-by: Wenqi Li * error->warning, revise copyright Signed-off-by: Wenqi Li * 3541 has cupy check (#3544) * has cupy check Signed-off-by: Wenqi Li * update based on comments Signed-off-by: Wenqi Li * 3053 release *_dist.py tests memory to avoid OOM (#3537) * adds min. memory testing utils Signed-off-by: Wenqi Li * include valueerror for robust outcome Signed-off-by: Wenqi Li * ensure float Signed-off-by: Wenqi Li * msg improvements Signed-off-by: Wenqi Li * update threshold Signed-off-by: Wenqi Li * remove ref Signed-off-by: Wenqi Li * separate disttests Signed-off-by: Wenqi Li * update based on comments Signed-off-by: Wenqi Li * 3539 Remove decollate warning (#3545) * [DLMED] remove warning Signed-off-by: Nic Ma * [DLMED] fix typo Signed-off-by: Nic Ma * [DLMED] enhance set_determinism (#3547) Signed-off-by: Nic Ma Co-authored-by: Wenqi Li * Smooth Deform (#3551) * Adding smooth deform transform Signed-off-by: Eric Kerfoot * Update Signed-off-by: Eric Kerfoot * Updates Signed-off-by: Eric Kerfoot * Docs update Signed-off-by: Eric Kerfoot * Type fixing Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [MONAI] python code formatting Signed-off-by: monai-bot * Fix Signed-off-by: Eric Kerfoot * Fix Signed-off-by: Eric Kerfoot * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix for moveaxis Signed-off-by: Eric Kerfoot * Fix for moveaxis Signed-off-by: Eric Kerfoot * Adding example images, random field sized reduced to (10,10,10) Signed-off-by: Eric Kerfoot * Changed backend Signed-off-by: Eric Kerfoot * [MONAI] python code formatting Signed-off-by: monai-bot * Tweak Signed-off-by: Eric Kerfoot Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: monai-bot * 3552 - runtest.sh defaults to no build/install (#3555) * runtest.sh defaults to no build/install Signed-off-by: Wenqi Li * following test case conventions for multiprocessing - adding `_dist` to multiprocessing test cases - decouple multiprocessing LMDB tests from `test_lmdbdataset` Signed-off-by: Wenqi Li * exclude lmdbdataset tests in min_tests Signed-off-by: Wenqi Li Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> * Remove apply_same_field (#3556) Signed-off-by: Eric Kerfoot * skipping pretraining network loading when downloading is unsuccessful (#3558) Signed-off-by: Wenqi Li * [DLMED] fix mypy errors (#3562) Signed-off-by: Nic Ma * 3559 Enhance `DatasetSummary` for several points (#3560) * [DLMED] update dataset summary Signed-off-by: Nic Ma * [DLMED] enhance data type Signed-off-by: Nic Ma * [DLMED] fix pickle issue Signed-off-by: Nic Ma Co-authored-by: Wenqi Li * add box util in monai/data * add box util in monai/data * [pre-commit.ci] pre-commit suggestions (#3568) updates: - [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0) - [github.com/asottile/pyupgrade: v2.29.0 → v2.31.0](https://github.com/asottile/pyupgrade/compare/v2.29.0...v2.31.0) - [github.com/asottile/yesqa: v1.2.3 → v1.3.0](https://github.com/asottile/yesqa/compare/v1.2.3...v1.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * 3565 - adds metadata when loading dicom series (#3566) * adds metadata when loading dicom series Signed-off-by: Wenqi Li * fixes timed tests Signed-off-by: Wenqi Li * update based on comments Signed-off-by: Wenqi Li * 3580 - create codeql-analysis.yml (#3579) * Create codeql-analysis.yml Signed-off-by: Wenqi Li * build cpp Signed-off-by: Wenqi Li * fixes Multiplication result converted to larger type Signed-off-by: Wenqi Li * fixes url parsing Signed-off-by: Wenqi Li * 498 Add logger_handler to LrScheduleHandler (#3570) * [DLMED] add log handler Signed-off-by: Nic Ma * [DLMED] fix CI tests Signed-off-by: Nic Ma * [DLMED] fix CI test Signed-off-by: Nic Ma * [DLMED] test CI Signed-off-by: Nic Ma * [DLMED] fix logging Signed-off-by: Nic Ma * [DLMED] temp test Signed-off-by: Nic Ma * [DLMED] fix wrong unit test Signed-off-by: Nic Ma * [DLMED] fix wrong test cases Signed-off-by: Nic Ma Co-authored-by: Behrooz <3968947+drbeh@users.noreply.github.com> Co-authored-by: Nic Ma Co-authored-by: Wenqi Li Co-authored-by: monai-bot Co-authored-by: Yiheng Wang <68361391+yiheng-wang-nv@users.noreply.github.com> Co-authored-by: Richard Brown <33289025+rijobro@users.noreply.github.com> Co-authored-by: Ebrahim Ebrahim Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/pull_request_template.md | 2 +- .github/workflows/codeql-analysis.yml | 71 ++++ .github/workflows/cron-mmar.yml | 2 +- .github/workflows/cron.yml | 16 +- .github/workflows/integration.yml | 6 +- .github/workflows/pythonapp-gpu.yml | 26 +- .github/workflows/pythonapp-min.yml | 8 +- .github/workflows/pythonapp.yml | 8 +- .github/workflows/release.yml | 2 +- .github/workflows/setupapp.yml | 12 +- .pre-commit-config.yaml | 8 +- CONTRIBUTING.md | 6 +- Dockerfile | 4 +- docs/requirements.txt | 2 +- docs/source/apps.rst | 4 + docs/source/conf.py | 2 +- docs/source/data.rst | 7 + docs/source/engines.rst | 16 +- docs/source/installation.md | 2 +- docs/source/metrics.rst | 9 + docs/source/networks.rst | 26 +- docs/source/optimizers.rst | 20 + docs/source/transforms.rst | 107 ++++- docs/source/utils.rst | 25 ++ docs/source/visualize.rst | 5 + matshow3d_rgb_test.png | Bin 15705 -> 0 bytes monai/__init__.py | 16 +- monai/_extensions/__init__.py | 2 +- monai/_extensions/gmm/gmm.cpp | 2 +- monai/_extensions/gmm/gmm.h | 2 +- monai/_extensions/gmm/gmm_cpu.cpp | 2 +- monai/_extensions/gmm/gmm_cuda.cu | 2 +- monai/_extensions/gmm/gmm_cuda_linalg.cuh | 2 +- monai/_extensions/loader.py | 2 +- monai/apps/__init__.py | 2 +- monai/apps/datasets.py | 58 ++- monai/apps/deepedit/__init__.py | 2 +- monai/apps/deepedit/transforms.py | 2 +- monai/apps/deepgrow/__init__.py | 2 +- monai/apps/deepgrow/dataset.py | 2 +- monai/apps/deepgrow/interaction.py | 3 +- monai/apps/deepgrow/transforms.py | 2 +- monai/apps/mmars/__init__.py | 2 +- monai/apps/mmars/mmars.py | 2 +- monai/apps/mmars/model_desc.py | 2 +- monai/apps/pathology/__init__.py | 2 +- monai/apps/pathology/data/__init__.py | 2 +- monai/apps/pathology/data/datasets.py | 8 +- monai/apps/pathology/handlers/__init__.py | 2 +- .../pathology/handlers/prob_map_producer.py | 2 +- monai/apps/pathology/metrics/__init__.py | 2 +- monai/apps/pathology/metrics/lesion_froc.py | 2 +- monai/apps/pathology/transforms/__init__.py | 2 +- .../pathology/transforms/spatial/__init__.py | 2 +- .../pathology/transforms/spatial/array.py | 4 +- .../transforms/spatial/dictionary.py | 2 +- .../pathology/transforms/stain/__init__.py | 2 +- .../apps/pathology/transforms/stain/array.py | 2 +- .../pathology/transforms/stain/dictionary.py | 2 +- monai/apps/pathology/utils.py | 2 +- monai/apps/utils.py | 5 +- monai/config/__init__.py | 4 +- monai/config/deviceconfig.py | 6 +- monai/config/type_definitions.py | 6 +- monai/csrc/ext.cpp | 2 +- monai/csrc/filtering/bilateral/bilateral.cpp | 2 +- monai/csrc/filtering/bilateral/bilateral.h | 2 +- .../bilateral/bilateralfilter_cpu.cpp | 2 +- .../bilateral/bilateralfilter_cpu_phl.cpp | 4 +- .../bilateral/bilateralfilter_cuda.cu | 2 +- .../bilateral/bilateralfilter_cuda_phl.cu | 2 +- monai/csrc/filtering/filtering.h | 2 +- .../filtering/permutohedral/hash_table.cuh | 2 +- .../filtering/permutohedral/permutohedral.cpp | 2 +- .../filtering/permutohedral/permutohedral.h | 2 +- .../permutohedral/permutohedral_cpu.cpp | 2 +- .../permutohedral/permutohedral_cuda.cu | 2 +- monai/csrc/lltm/lltm.h | 2 +- monai/csrc/lltm/lltm_cpu.cpp | 2 +- monai/csrc/lltm/lltm_cuda.cu | 2 +- monai/csrc/resample/bounds_common.h | 2 +- monai/csrc/resample/interpolation_common.h | 2 +- monai/csrc/resample/pushpull.h | 2 +- monai/csrc/resample/pushpull_cpu.cpp | 2 +- monai/csrc/resample/pushpull_cuda.cu | 2 +- monai/csrc/utils/common_utils.h | 2 +- monai/csrc/utils/meta_macros.h | 2 +- monai/csrc/utils/resample_utils.h | 2 +- monai/csrc/utils/tensor_description.h | 2 +- monai/data/__init__.py | 4 +- monai/data/box_utils.py | 383 ++++++++++++++++++ monai/data/csv_saver.py | 2 +- monai/data/dataloader.py | 2 +- monai/data/dataset.py | 80 +++- monai/data/dataset_summary.py | 32 +- monai/data/decathlon_datalist.py | 2 +- monai/data/grid_dataset.py | 2 +- monai/data/image_dataset.py | 2 +- monai/data/image_reader.py | 142 +++++-- monai/data/iterable_dataset.py | 64 ++- monai/data/nifti_saver.py | 2 +- monai/data/nifti_writer.py | 2 +- monai/data/png_saver.py | 2 +- monai/data/png_writer.py | 2 +- monai/data/samplers.py | 2 +- monai/data/synthetic.py | 2 +- monai/data/test_time_augmentation.py | 2 +- monai/data/thread_buffer.py | 2 +- monai/data/torchscript_utils.py | 149 +++++++ monai/data/utils.py | 18 +- monai/engines/__init__.py | 3 +- monai/engines/evaluator.py | 34 +- monai/engines/multi_gpu_supervised_trainer.py | 2 +- monai/engines/trainer.py | 26 +- monai/engines/utils.py | 6 +- monai/engines/workflow.py | 33 +- monai/handlers/__init__.py | 2 +- monai/handlers/checkpoint_loader.py | 2 +- monai/handlers/checkpoint_saver.py | 2 +- monai/handlers/classification_saver.py | 15 +- monai/handlers/confusion_matrix.py | 2 +- monai/handlers/decollate_batch.py | 2 +- monai/handlers/earlystop_handler.py | 2 +- monai/handlers/garbage_collector.py | 2 +- monai/handlers/hausdorff_distance.py | 2 +- monai/handlers/ignite_metric.py | 2 +- monai/handlers/lr_schedule_handler.py | 8 +- monai/handlers/mean_dice.py | 2 +- monai/handlers/metric_logger.py | 2 +- monai/handlers/metrics_saver.py | 11 +- monai/handlers/mlflow_handler.py | 2 +- monai/handlers/nvtx_handlers.py | 2 +- monai/handlers/parameter_scheduler.py | 2 +- monai/handlers/postprocessing.py | 2 +- monai/handlers/regression_metrics.py | 2 +- monai/handlers/roc_auc.py | 2 +- monai/handlers/segmentation_saver.py | 2 +- monai/handlers/smartcache_handler.py | 2 +- monai/handlers/stats_handler.py | 9 +- monai/handlers/surface_distance.py | 2 +- monai/handlers/tensorboard_handlers.py | 37 +- monai/handlers/utils.py | 2 +- monai/handlers/validation_handler.py | 2 +- monai/inferers/__init__.py | 2 +- monai/inferers/inferer.py | 2 +- monai/inferers/utils.py | 2 +- monai/losses/__init__.py | 2 +- monai/losses/contrastive.py | 33 +- monai/losses/deform.py | 31 +- monai/losses/dice.py | 2 +- monai/losses/focal_loss.py | 2 +- monai/losses/image_dissimilarity.py | 2 +- monai/losses/multi_scale.py | 2 +- monai/losses/spatial_mask.py | 2 +- monai/losses/tversky.py | 2 +- monai/metrics/__init__.py | 2 +- monai/metrics/confusion_matrix.py | 2 +- monai/metrics/cumulative_average.py | 2 +- monai/metrics/froc.py | 2 +- monai/metrics/hausdorff_distance.py | 2 +- monai/metrics/meandice.py | 2 +- monai/metrics/metric.py | 2 +- monai/metrics/regression.py | 2 +- monai/metrics/rocauc.py | 2 +- monai/metrics/surface_distance.py | 2 +- monai/metrics/utils.py | 4 +- monai/networks/__init__.py | 2 +- monai/networks/blocks/__init__.py | 2 +- monai/networks/blocks/acti_norm.py | 2 +- monai/networks/blocks/activation.py | 4 +- monai/networks/blocks/aspp.py | 2 +- monai/networks/blocks/convolutions.py | 2 +- monai/networks/blocks/crf.py | 2 +- monai/networks/blocks/dints_block.py | 2 +- monai/networks/blocks/downsample.py | 2 +- monai/networks/blocks/dynunet_block.py | 2 +- monai/networks/blocks/fcn.py | 2 +- monai/networks/blocks/localnet_block.py | 2 +- monai/networks/blocks/mlp.py | 2 +- monai/networks/blocks/patchembedding.py | 3 +- monai/networks/blocks/regunet_block.py | 2 +- monai/networks/blocks/segresnet_block.py | 2 +- monai/networks/blocks/selfattention.py | 2 +- .../networks/blocks/squeeze_and_excitation.py | 2 +- monai/networks/blocks/transformerblock.py | 2 +- monai/networks/blocks/unetr_block.py | 2 +- monai/networks/blocks/upsample.py | 2 +- monai/networks/blocks/warp.py | 2 +- monai/networks/layers/__init__.py | 2 +- monai/networks/layers/convutils.py | 2 +- monai/networks/layers/factories.py | 2 +- monai/networks/layers/filtering.py | 2 +- monai/networks/layers/gmm.py | 2 +- monai/networks/layers/simplelayers.py | 2 +- monai/networks/layers/spatial_transforms.py | 2 +- monai/networks/layers/utils.py | 2 +- monai/networks/nets/__init__.py | 2 +- monai/networks/nets/ahnet.py | 2 +- monai/networks/nets/autoencoder.py | 2 +- monai/networks/nets/basic_unet.py | 2 +- monai/networks/nets/classifier.py | 2 +- monai/networks/nets/densenet.py | 2 +- monai/networks/nets/dints.py | 2 +- monai/networks/nets/dynunet.py | 100 +++-- monai/networks/nets/efficientnet.py | 2 +- monai/networks/nets/fullyconnectednet.py | 2 +- monai/networks/nets/generator.py | 2 +- monai/networks/nets/highresnet.py | 2 +- monai/networks/nets/milmodel.py | 32 +- monai/networks/nets/netadapter.py | 2 +- monai/networks/nets/regressor.py | 2 +- monai/networks/nets/regunet.py | 2 +- monai/networks/nets/resnet.py | 3 +- monai/networks/nets/segresnet.py | 2 +- monai/networks/nets/senet.py | 2 +- monai/networks/nets/torchvision_fc.py | 2 +- monai/networks/nets/transchex.py | 2 +- monai/networks/nets/unet.py | 2 +- monai/networks/nets/unetr.py | 2 +- monai/networks/nets/varautoencoder.py | 2 +- monai/networks/nets/vit.py | 2 +- monai/networks/nets/vitautoenc.py | 2 +- monai/networks/nets/vnet.py | 2 +- monai/networks/utils.py | 6 +- monai/optimizers/__init__.py | 3 +- monai/optimizers/lr_finder.py | 34 +- monai/optimizers/lr_scheduler.py | 2 +- monai/optimizers/novograd.py | 4 +- monai/optimizers/utils.py | 2 +- monai/transforms/__init__.py | 22 +- monai/transforms/adaptors.py | 2 +- monai/transforms/compose.py | 11 +- monai/transforms/croppad/__init__.py | 2 +- monai/transforms/croppad/array.py | 2 +- monai/transforms/croppad/batch.py | 2 +- monai/transforms/croppad/dictionary.py | 2 +- monai/transforms/intensity/__init__.py | 2 +- monai/transforms/intensity/array.py | 38 +- monai/transforms/intensity/dictionary.py | 53 ++- monai/transforms/inverse.py | 2 +- monai/transforms/inverse_batch_transform.py | 2 +- monai/transforms/io/__init__.py | 2 +- monai/transforms/io/array.py | 6 +- monai/transforms/io/dictionary.py | 2 +- monai/transforms/nvtx.py | 2 +- monai/transforms/post/__init__.py | 2 +- monai/transforms/post/array.py | 4 +- monai/transforms/post/dictionary.py | 2 +- monai/transforms/smooth_field/__init__.py | 2 +- monai/transforms/smooth_field/array.py | 349 +++++++++++++--- monai/transforms/smooth_field/dictionary.py | 185 +++++++-- monai/transforms/spatial/__init__.py | 2 +- monai/transforms/spatial/array.py | 72 +--- monai/transforms/spatial/dictionary.py | 35 +- monai/transforms/transform.py | 2 +- monai/transforms/utility/__init__.py | 2 +- monai/transforms/utility/array.py | 56 ++- monai/transforms/utility/dictionary.py | 55 ++- monai/transforms/utils.py | 8 +- .../transforms/utils_create_transform_ims.py | 72 +++- .../utils_pytorch_numpy_unification.py | 25 +- monai/utils/__init__.py | 3 +- monai/utils/aliases.py | 2 +- monai/utils/decorators.py | 2 +- monai/utils/deprecate_utils.py | 17 +- monai/utils/dist.py | 2 +- monai/utils/enums.py | 14 +- monai/utils/jupyter_utils.py | 2 +- monai/utils/misc.py | 16 +- monai/utils/module.py | 2 +- monai/utils/nvtx.py | 11 +- monai/utils/profiling.py | 2 +- monai/utils/state_cacher.py | 2 +- monai/utils/type_conversion.py | 24 +- monai/visualize/__init__.py | 2 +- monai/visualize/class_activation_maps.py | 2 +- monai/visualize/img2tensorboard.py | 2 +- monai/visualize/occlusion_sensitivity.py | 2 +- monai/visualize/utils.py | 2 +- monai/visualize/visualizer.py | 2 +- pyproject.toml | 2 +- requirements-dev.txt | 2 +- requirements-min.txt | 2 +- runtests.sh | 62 ++- setup.cfg | 6 +- setup.py | 2 +- tests/__init__.py | 2 +- tests/clang_format_utils.py | 2 +- tests/hvd_evenly_divisible_all_gather.py | 2 +- tests/min_tests.py | 3 +- tests/ngc_mmar_loading.py | 2 +- tests/runner.py | 2 +- tests/test_acn_block.py | 2 +- tests/test_activations.py | 2 +- tests/test_activationsd.py | 2 +- tests/test_adaptors.py | 2 +- tests/test_add_channeld.py | 2 +- tests/test_add_coordinate_channels.py | 10 +- tests/test_add_coordinate_channelsd.py | 14 +- tests/test_add_extreme_points_channel.py | 2 +- tests/test_add_extreme_points_channeld.py | 2 +- tests/test_adjust_contrast.py | 2 +- tests/test_adjust_contrastd.py | 2 +- tests/test_adn.py | 2 +- tests/test_affine.py | 2 +- tests/test_affine_grid.py | 2 +- tests/test_affine_transform.py | 2 +- tests/test_affined.py | 2 +- tests/test_ahnet.py | 2 +- tests/test_alias.py | 2 +- tests/test_apply_filter.py | 2 +- tests/test_arraydataset.py | 2 +- tests/test_as_channel_first.py | 2 +- tests/test_as_channel_firstd.py | 2 +- tests/test_as_channel_last.py | 2 +- tests/test_as_channel_lastd.py | 2 +- tests/test_as_discrete.py | 2 +- tests/test_as_discreted.py | 2 +- tests/test_autoencoder.py | 2 +- tests/test_basic_unet.py | 2 +- tests/test_bending_energy.py | 45 +- tests/test_bilateral_approx_cpu.py | 2 +- tests/test_bilateral_approx_cuda.py | 2 +- tests/test_bilateral_precise.py | 2 +- tests/test_blend_images.py | 2 +- tests/test_border_pad.py | 2 +- tests/test_border_padd.py | 2 +- tests/test_bounding_rect.py | 2 +- tests/test_bounding_rectd.py | 2 +- tests/test_cachedataset.py | 2 +- tests/test_cachedataset_parallel.py | 2 +- tests/test_cachedataset_persistent_workers.py | 2 +- tests/test_cachentransdataset.py | 2 +- tests/{test_distcall.py => test_call_dist.py} | 2 +- tests/test_cast_to_type.py | 8 +- tests/test_cast_to_typed.py | 7 +- tests/test_center_scale_crop.py | 2 +- tests/test_center_scale_cropd.py | 2 +- tests/test_center_spatial_crop.py | 2 +- tests/test_center_spatial_cropd.py | 2 +- tests/test_channel_pad.py | 2 +- tests/test_check_hash.py | 2 +- tests/test_check_missing_files.py | 2 +- tests/test_classes_to_indices.py | 2 +- tests/test_classes_to_indicesd.py | 2 +- tests/test_compose.py | 2 +- tests/test_compose_get_number_conversions.py | 2 +- tests/test_compute_confusion_matrix.py | 2 +- tests/test_compute_froc.py | 2 +- tests/test_compute_meandice.py | 2 +- tests/test_compute_regression_metrics.py | 2 +- tests/test_compute_roc_auc.py | 2 +- tests/test_concat_itemsd.py | 2 +- tests/test_contrastive_loss.py | 4 +- tests/test_convert_data_type.py | 2 +- tests/test_convert_to_multi_channel.py | 2 +- tests/test_convert_to_multi_channeld.py | 2 +- tests/test_convert_to_torchscript.py | 3 +- tests/test_convolutions.py | 2 +- tests/test_copy_itemsd.py | 2 +- tests/test_copy_model_state.py | 2 +- tests/test_correct_crop_centers.py | 2 +- .../test_create_cross_validation_datalist.py | 2 +- tests/test_create_grid_and_affine.py | 2 +- tests/test_crf_cpu.py | 2 +- tests/test_crf_cuda.py | 2 +- tests/test_crop_foreground.py | 2 +- tests/test_crop_foregroundd.py | 2 +- tests/test_cross_validation.py | 2 +- tests/test_csv_dataset.py | 39 +- tests/test_csv_iterable_dataset.py | 62 ++- tests/test_csv_saver.py | 2 +- tests/test_cucim_dict_transform.py | 8 +- tests/test_cucim_transform.py | 8 +- tests/test_cumulative.py | 2 +- tests/test_cumulative_average.py | 2 +- tests/test_cumulative_average_dist.py | 2 +- tests/test_data_stats.py | 2 +- tests/test_data_statsd.py | 2 +- tests/test_dataloader.py | 2 +- tests/test_dataset.py | 2 +- tests/test_dataset_func.py | 52 +++ tests/test_dataset_summary.py | 20 +- tests/test_decathlondataset.py | 3 +- tests/test_decollate.py | 2 +- tests/test_deepedit_transforms.py | 2 +- tests/test_deepgrow_dataset.py | 2 +- tests/test_deepgrow_interaction.py | 2 +- tests/test_deepgrow_transforms.py | 2 +- tests/test_delete_itemsd.py | 2 +- tests/test_densenet.py | 2 +- tests/test_deprecated.py | 47 ++- tests/test_detect_envelope.py | 2 +- tests/test_dice_ce_loss.py | 2 +- tests/test_dice_focal_loss.py | 2 +- tests/test_dice_loss.py | 2 +- tests/test_dints_cell.py | 2 +- tests/test_dints_mixop.py | 2 +- tests/test_dints_network.py | 2 +- tests/test_discriminator.py | 2 +- tests/test_divisible_pad.py | 2 +- tests/test_divisible_padd.py | 2 +- tests/test_download_and_extract.py | 2 +- tests/test_downsample_block.py | 2 +- tests/test_dvf2ddf.py | 2 +- tests/test_dynunet.py | 2 +- tests/test_dynunet_block.py | 2 +- tests/test_efficientnet.py | 3 +- tests/test_ensemble_evaluator.py | 2 +- tests/test_ensure_channel_first.py | 2 +- tests/test_ensure_channel_firstd.py | 2 +- tests/test_ensure_type.py | 2 +- tests/test_ensure_typed.py | 2 +- tests/test_enum_bound_interp.py | 2 +- tests/test_eval_mode.py | 2 +- .../test_evenly_divisible_all_gather_dist.py | 2 +- tests/test_factorized_increase.py | 2 +- tests/test_factorized_reduce.py | 2 +- tests/test_fg_bg_to_indices.py | 2 +- tests/test_fg_bg_to_indicesd.py | 2 +- tests/test_file_basename.py | 7 +- tests/test_fill_holes.py | 2 +- tests/test_fill_holesd.py | 2 +- tests/test_flip.py | 2 +- tests/test_flipd.py | 2 +- tests/test_focal_loss.py | 2 +- tests/test_fourier.py | 2 +- tests/test_fullyconnectednet.py | 2 +- tests/test_gaussian.py | 2 +- tests/test_gaussian_filter.py | 2 +- tests/test_gaussian_sharpen.py | 2 +- tests/test_gaussian_sharpend.py | 2 +- tests/test_gaussian_smooth.py | 2 +- tests/test_gaussian_smoothd.py | 2 +- tests/test_generalized_dice_loss.py | 2 +- .../test_generalized_wasserstein_dice_loss.py | 2 +- ...est_generate_label_classes_crop_centers.py | 2 +- tests/test_generate_param_groups.py | 2 +- ...est_generate_pos_neg_label_crop_centers.py | 2 +- tests/test_generate_spatial_bounding_box.py | 2 +- tests/test_generator.py | 2 +- tests/test_get_equivalent_dtype.py | 2 +- tests/test_get_extreme_points.py | 2 +- tests/test_get_layers.py | 2 +- tests/test_get_package_version.py | 2 +- tests/test_gibbs_noise.py | 2 +- tests/test_gibbs_noised.py | 2 +- tests/test_global_mutual_information_loss.py | 2 +- tests/test_globalnet.py | 2 +- tests/test_gmm.py | 2 +- tests/test_grid_dataset.py | 2 +- tests/test_grid_distortion.py | 2 +- tests/test_grid_distortiond.py | 2 +- tests/test_grid_pull.py | 2 +- tests/test_handler_checkpoint_loader.py | 2 +- tests/test_handler_checkpoint_saver.py | 2 +- tests/test_handler_classification_saver.py | 2 +- .../test_handler_classification_saver_dist.py | 2 +- tests/test_handler_confusion_matrix.py | 2 +- tests/test_handler_confusion_matrix_dist.py | 2 +- tests/test_handler_decollate_batch.py | 2 +- tests/test_handler_early_stop.py | 2 +- tests/test_handler_garbage_collector.py | 2 +- tests/test_handler_hausdorff_distance.py | 2 +- tests/test_handler_lr_scheduler.py | 50 ++- tests/test_handler_mean_dice.py | 2 +- tests/test_handler_metric_logger.py | 2 +- tests/test_handler_metrics_saver.py | 2 +- tests/test_handler_metrics_saver_dist.py | 2 +- tests/test_handler_mlflow.py | 2 +- tests/test_handler_nvtx.py | 2 +- tests/test_handler_parameter_scheduler.py | 2 +- tests/test_handler_post_processing.py | 2 +- tests/test_handler_prob_map_producer.py | 2 +- tests/test_handler_regression_metrics.py | 2 +- tests/test_handler_regression_metrics_dist.py | 2 +- tests/test_handler_rocauc.py | 2 +- tests/test_handler_rocauc_dist.py | 2 +- tests/test_handler_segmentation_saver.py | 2 +- tests/test_handler_smartcache.py | 2 +- tests/test_handler_stats.py | 58 +-- tests/test_handler_surface_distance.py | 2 +- tests/test_handler_tb_image.py | 2 +- tests/test_handler_tb_stats.py | 2 +- tests/test_handler_validation.py | 2 +- tests/test_hashing.py | 2 +- tests/test_hausdorff_distance.py | 2 +- tests/test_header_correct.py | 2 +- tests/test_highresnet.py | 2 +- tests/test_hilbert_transform.py | 2 +- tests/test_histogram_normalize.py | 2 +- tests/test_histogram_normalized.py | 2 +- tests/test_identity.py | 2 +- tests/test_identityd.py | 2 +- tests/test_image_dataset.py | 2 +- tests/test_img2tensorboard.py | 2 +- tests/test_init_reader.py | 2 +- tests/test_integration_classification_2d.py | 2 +- tests/test_integration_determinism.py | 2 +- tests/test_integration_fast_train.py | 2 +- tests/test_integration_segmentation_3d.py | 2 +- tests/test_integration_sliding_window.py | 2 +- tests/test_integration_stn.py | 2 +- tests/test_integration_unet_2d.py | 2 +- tests/test_integration_workflows.py | 2 +- tests/test_integration_workflows_gan.py | 2 +- tests/test_intensity_stats.py | 2 +- tests/test_intensity_statsd.py | 2 +- tests/test_inverse.py | 2 +- tests/test_inverse_collation.py | 2 +- tests/test_invertd.py | 2 +- tests/test_is_supported_format.py | 2 +- tests/test_iterable_dataset.py | 2 +- tests/test_k_space_spike_noise.py | 2 +- tests/test_k_space_spike_noised.py | 2 +- .../test_keep_largest_connected_component.py | 2 +- .../test_keep_largest_connected_componentd.py | 2 +- tests/test_label_filter.py | 2 +- tests/test_label_filterd.py | 2 +- tests/test_label_to_contour.py | 2 +- tests/test_label_to_contourd.py | 2 +- tests/test_label_to_mask.py | 2 +- tests/test_label_to_maskd.py | 2 +- tests/test_lambda.py | 2 +- tests/test_lambdad.py | 2 +- tests/test_lesion_froc.py | 2 +- tests/test_list_data_collate.py | 2 +- tests/test_list_to_dict.py | 2 +- tests/test_lltm.py | 2 +- tests/test_lmdbdataset.py | 2 +- tests/test_lmdbdataset_dist.py | 72 ++++ tests/test_load_decathlon_datalist.py | 2 +- tests/test_load_image.py | 40 +- tests/test_load_imaged.py | 2 +- tests/test_load_spacing_orientation.py | 2 +- tests/test_loader_semaphore.py | 2 +- ...local_normalized_cross_correlation_loss.py | 2 +- tests/test_localnet.py | 2 +- tests/test_localnet_block.py | 2 +- tests/test_look_up_option.py | 2 +- tests/test_lr_finder.py | 2 +- tests/test_lr_scheduler.py | 2 +- tests/test_map_binary_to_indices.py | 2 +- tests/test_map_classes_to_indices.py | 2 +- tests/test_map_label_value.py | 2 +- tests/test_map_label_valued.py | 2 +- tests/test_map_transform.py | 2 +- tests/test_mask_intensity.py | 2 +- tests/test_mask_intensityd.py | 2 +- tests/test_masked_dice_loss.py | 2 +- tests/test_masked_inference_wsi_dataset.py | 2 +- tests/test_masked_loss.py | 2 +- tests/test_matshow3d.py | 2 +- tests/test_mean_ensemble.py | 2 +- tests/test_mean_ensembled.py | 2 +- tests/test_mednistdataset.py | 6 +- tests/test_milmodel.py | 2 +- tests/test_mlp.py | 2 +- tests/test_mmar_download.py | 5 +- tests/test_module_list.py | 2 +- tests/test_multi_scale.py | 2 +- tests/test_net_adapter.py | 2 +- tests/test_network_consistency.py | 8 +- tests/test_nifti_endianness.py | 2 +- tests/test_nifti_header_revise.py | 2 +- tests/test_nifti_rw.py | 2 +- tests/test_nifti_saver.py | 2 +- tests/test_normalize_intensity.py | 8 +- tests/test_normalize_intensityd.py | 2 +- tests/test_npzdictitemdataset.py | 2 +- tests/test_numpy_reader.py | 2 +- tests/test_nvtx_decorator.py | 45 +- tests/test_nvtx_transform.py | 2 +- tests/test_occlusion_sensitivity.py | 2 +- tests/test_one_of.py | 73 +++- tests/test_optim_novograd.py | 2 +- tests/test_optional_import.py | 2 +- tests/test_orientation.py | 2 +- tests/test_orientationd.py | 2 +- tests/test_p3d_block.py | 2 +- tests/test_pad_collation.py | 2 +- tests/test_parallel_execution.py | 2 +- tests/test_partition_dataset.py | 2 +- tests/test_partition_dataset_classes.py | 2 +- tests/test_patch_dataset.py | 2 +- tests/test_patch_wsi_dataset.py | 2 +- tests/test_patchembedding.py | 2 +- tests/test_pathology_he_stain.py | 2 +- tests/test_pathology_he_stain_dict.py | 2 +- tests/test_pathology_prob_nms.py | 2 +- tests/test_persistentdataset.py | 2 +- tests/test_persistentdataset_dist.py | 2 +- tests/test_phl_cpu.py | 2 +- tests/test_phl_cuda.py | 2 +- tests/test_pil_reader.py | 2 +- tests/test_plot_2d_or_3d_image.py | 2 +- tests/test_png_rw.py | 2 +- tests/test_png_saver.py | 2 +- tests/test_polyval.py | 2 +- tests/test_prepare_batch_default.py | 2 +- tests/test_prepare_batch_extra_input.py | 2 +- tests/test_print_info.py | 2 +- tests/test_print_transform_backends.py | 2 +- tests/test_probnms.py | 2 +- tests/test_probnmsd.py | 2 +- tests/test_pytorch_version_after.py | 2 +- tests/test_query_memory.py | 2 +- tests/test_rand_adjust_contrast.py | 2 +- tests/test_rand_adjust_contrastd.py | 2 +- tests/test_rand_affine.py | 2 +- tests/test_rand_affine_grid.py | 2 +- tests/test_rand_affined.py | 2 +- tests/test_rand_axis_flip.py | 2 +- tests/test_rand_axis_flipd.py | 2 +- tests/test_rand_bias_field.py | 2 +- tests/test_rand_bias_fieldd.py | 2 +- tests/test_rand_coarse_dropout.py | 2 +- tests/test_rand_coarse_dropoutd.py | 2 +- tests/test_rand_coarse_shuffle.py | 2 +- tests/test_rand_coarse_shuffled.py | 2 +- tests/test_rand_crop_by_label_classes.py | 2 +- tests/test_rand_crop_by_label_classesd.py | 2 +- tests/test_rand_crop_by_pos_neg_label.py | 2 +- tests/test_rand_crop_by_pos_neg_labeld.py | 2 +- tests/test_rand_cucim_dict_transform.py | 8 +- tests/test_rand_cucim_transform.py | 8 +- tests/test_rand_deform_grid.py | 2 +- tests/test_rand_elastic_2d.py | 2 +- tests/test_rand_elastic_3d.py | 2 +- tests/test_rand_elasticd_2d.py | 2 +- tests/test_rand_elasticd_3d.py | 2 +- tests/test_rand_flip.py | 2 +- tests/test_rand_flipd.py | 2 +- tests/test_rand_gaussian_noise.py | 2 +- tests/test_rand_gaussian_noised.py | 2 +- tests/test_rand_gaussian_sharpen.py | 2 +- tests/test_rand_gaussian_sharpend.py | 2 +- tests/test_rand_gaussian_smooth.py | 2 +- tests/test_rand_gaussian_smoothd.py | 2 +- tests/test_rand_gibbs_noise.py | 2 +- tests/test_rand_gibbs_noised.py | 2 +- tests/test_rand_grid_distortion.py | 2 +- tests/test_rand_grid_distortiond.py | 2 +- tests/test_rand_histogram_shift.py | 2 +- tests/test_rand_histogram_shiftd.py | 2 +- tests/test_rand_k_space_spike_noise.py | 2 +- tests/test_rand_k_space_spike_noised.py | 2 +- tests/test_rand_lambda.py | 2 +- tests/test_rand_lambdad.py | 2 +- tests/test_rand_rician_noise.py | 2 +- tests/test_rand_rician_noised.py | 2 +- tests/test_rand_rotate.py | 2 +- tests/test_rand_rotate90.py | 2 +- tests/test_rand_rotate90d.py | 2 +- tests/test_rand_rotated.py | 2 +- tests/test_rand_scale_crop.py | 2 +- tests/test_rand_scale_cropd.py | 2 +- tests/test_rand_scale_intensity.py | 2 +- tests/test_rand_scale_intensityd.py | 2 +- tests/test_rand_shift_intensity.py | 2 +- tests/test_rand_shift_intensityd.py | 2 +- tests/test_rand_spatial_crop.py | 2 +- tests/test_rand_spatial_crop_samples.py | 2 +- tests/test_rand_spatial_crop_samplesd.py | 2 +- tests/test_rand_spatial_cropd.py | 2 +- tests/test_rand_std_shift_intensity.py | 2 +- tests/test_rand_std_shift_intensityd.py | 2 +- tests/test_rand_weighted_crop.py | 2 +- tests/test_rand_weighted_cropd.py | 2 +- tests/test_rand_zoom.py | 2 +- tests/test_rand_zoomd.py | 2 +- tests/test_randomizable.py | 2 +- tests/test_randtorchvisiond.py | 2 +- tests/test_reg_loss_integration.py | 8 +- tests/test_regunet.py | 2 +- tests/test_regunet_block.py | 2 +- tests/test_remove_repeated_channel.py | 2 +- tests/test_remove_repeated_channeld.py | 2 +- tests/test_repeat_channel.py | 2 +- tests/test_repeat_channeld.py | 2 +- tests/test_require_pkg.py | 2 +- tests/test_resample_datalist.py | 2 +- tests/test_resampler.py | 2 +- tests/test_resize.py | 2 +- tests/test_resize_with_pad_or_crop.py | 2 +- tests/test_resize_with_pad_or_cropd.py | 2 +- tests/test_resized.py | 2 +- tests/test_resnet.py | 2 +- tests/test_rotate.py | 2 +- tests/test_rotate90.py | 2 +- tests/test_rotate90d.py | 2 +- tests/test_rotated.py | 2 +- tests/test_saliency_inferer.py | 2 +- ...ibuted_sampler.py => test_sampler_dist.py} | 2 +- tests/test_save_classificationd.py | 2 +- tests/test_save_image.py | 2 +- tests/test_save_imaged.py | 2 +- tests/test_savitzky_golay_filter.py | 2 +- tests/test_savitzky_golay_smooth.py | 14 +- tests/test_savitzky_golay_smoothd.py | 72 ++++ tests/test_scale_intensity.py | 2 +- tests/test_scale_intensity_range.py | 2 +- .../test_scale_intensity_range_percentiles.py | 26 +- ...test_scale_intensity_range_percentilesd.py | 29 +- tests/test_scale_intensity_ranged.py | 2 +- tests/test_scale_intensityd.py | 2 +- tests/test_se_block.py | 2 +- tests/test_se_blocks.py | 2 +- tests/test_seg_loss_integration.py | 2 +- tests/test_segresnet.py | 2 +- tests/test_segresnet_block.py | 2 +- tests/test_select_cross_validation_folds.py | 2 +- tests/test_select_itemsd.py | 2 +- tests/test_selfattention.py | 2 +- tests/test_senet.py | 2 +- tests/test_separable_filter.py | 2 +- tests/test_set_determinism.py | 4 +- tests/test_set_visible_devices.py | 2 +- tests/test_shift_intensity.py | 2 +- tests/test_shift_intensityd.py | 2 +- tests/test_simple_aspp.py | 2 +- tests/test_simulatedelay.py | 2 +- tests/test_simulatedelayd.py | 2 +- tests/test_skip_connection.py | 2 +- tests/test_sliding_window_inference.py | 2 +- tests/test_smartcache_patch_wsi_dataset.py | 3 +- tests/test_smartcachedataset.py | 2 +- tests/test_smooth_field.py | 124 ++++-- tests/test_spacing.py | 2 +- tests/test_spacingd.py | 2 +- tests/test_spatial_crop.py | 2 +- tests/test_spatial_cropd.py | 2 +- tests/test_spatial_pad.py | 2 +- tests/test_spatial_padd.py | 2 +- tests/test_split_channel.py | 2 +- tests/test_split_channeld.py | 2 +- tests/test_split_on_grid.py | 2 +- tests/test_split_on_grid_dict.py | 2 +- tests/test_squeezedim.py | 2 +- tests/test_squeezedimd.py | 2 +- tests/test_state_cacher.py | 2 +- tests/test_std_shift_intensity.py | 2 +- tests/test_std_shift_intensityd.py | 2 +- tests/test_subpixel_upsample.py | 2 +- tests/test_surface_distance.py | 2 +- tests/test_synthetic.py | 2 +- tests/test_testtimeaugmentation.py | 2 +- tests/test_thread_buffer.py | 2 +- tests/test_threadcontainer.py | 2 +- tests/test_threshold_intensity.py | 2 +- tests/test_threshold_intensityd.py | 2 +- tests/test_tile_on_grid.py | 2 +- tests/test_tile_on_grid_dict.py | 2 +- ...st_timedcall.py => test_timedcall_dist.py} | 4 +- tests/test_to_cupy.py | 8 +- tests/test_to_cupyd.py | 12 +- tests/test_to_device.py | 2 +- tests/test_to_deviced.py | 2 +- tests/test_to_numpy.py | 12 +- tests/test_to_numpyd.py | 8 +- tests/test_to_onehot.py | 2 +- tests/test_to_pil.py | 2 +- tests/test_to_pild.py | 2 +- tests/test_to_tensor.py | 8 +- tests/test_torchscript_utils.py | 112 +++++ tests/test_torchvision.py | 2 +- tests/test_torchvision_fc_model.py | 2 +- tests/test_torchvision_fully_conv_model.py | 2 +- tests/test_torchvisiond.py | 2 +- tests/test_traceable_transform.py | 2 +- tests/test_train_mode.py | 2 +- tests/test_transchex.py | 2 +- tests/test_transformerblock.py | 2 +- tests/test_transpose.py | 2 +- tests/test_transposed.py | 2 +- tests/test_tversky_loss.py | 2 +- tests/test_unet.py | 2 +- tests/test_unetr.py | 2 +- tests/test_unetr_block.py | 2 +- tests/test_upsample_block.py | 2 +- tests/test_utils_pytorch_numpy_unification.py | 16 +- tests/test_varautoencoder.py | 2 +- tests/test_version_leq.py | 2 +- tests/test_vis_cam.py | 2 +- tests/test_vis_gradcam.py | 2 +- tests/test_vis_gradcampp.py | 2 +- tests/test_vit.py | 2 +- tests/test_vitautoenc.py | 2 +- tests/test_vnet.py | 2 +- tests/test_vote_ensemble.py | 2 +- tests/test_vote_ensembled.py | 2 +- tests/test_warp.py | 2 +- ...y => test_weighted_random_sampler_dist.py} | 2 +- tests/test_with_allow_missing_keys.py | 2 +- tests/test_write_metrics_reports.py | 2 +- tests/test_wsireader.py | 58 ++- tests/test_zipdataset.py | 2 +- tests/test_zoom.py | 2 +- tests/test_zoom_affine.py | 2 +- tests/test_zoomd.py | 2 +- tests/testing_data/cpp_resample_answers.py | 2 +- tests/testing_data/integration_answers.py | 2 +- tests/utils.py | 43 +- 803 files changed, 4032 insertions(+), 1550 deletions(-) create mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 matshow3d_rgb_test.png create mode 100644 monai/data/box_utils.py create mode 100644 monai/data/torchscript_utils.py rename tests/{test_distcall.py => test_call_dist.py} (96%) create mode 100644 tests/test_dataset_func.py create mode 100644 tests/test_lmdbdataset_dist.py rename tests/{test_distributed_sampler.py => test_sampler_dist.py} (97%) create mode 100644 tests/test_savitzky_golay_smoothd.py rename tests/{test_timedcall.py => test_timedcall_dist.py} (95%) create mode 100644 tests/test_torchscript_utils.py rename tests/{test_distributed_weighted_random_sampler.py => test_weighted_random_sampler_dist.py} (98%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f7024f1a08..e1eeb92c6b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,6 +12,6 @@ A few sentences describing the changes proposed in this pull request. - [ ] 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`. +- [ ] 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. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..4e72a7dbcf --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ dev, main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ dev ] + schedule: + - cron: '18 1 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - name: Build + run: | + python -m pip install -r requirements-dev.txt + BUILD_MONAI=1 ./runtests.sh --build + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/cron-mmar.yml b/.github/workflows/cron-mmar.yml index 735c23117c..f61ba59368 100644 --- a/.github/workflows/cron-mmar.yml +++ b/.github/workflows/cron-mmar.yml @@ -37,6 +37,6 @@ jobs: - name: Loading MMARs run: | # clean up temporary files - $(pwd)/runtests.sh --clean + $(pwd)/runtests.sh --build --clean # run tests python -m tests.ngc_mmar_loading diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 835069c833..f50363b1e0 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -48,8 +48,8 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5, 3, device=torch.device("cuda:0")))' - BUILD_MONAI=1 ./runtests.sh --coverage --unittests # unit tests with coverage report - BUILD_MONAI=1 ./runtests.sh --coverage --net # integration tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --unittests --disttests # unit tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --net # integration tests with coverage report coverage xml if pgrep python; then pkill python; fi - name: Upload coverage @@ -62,7 +62,7 @@ jobs: if: github.repository == 'Project-MONAI/MONAI' strategy: matrix: - container: ["pytorch:21.02", "pytorch:21.10"] # 21.02 for backward comp. + container: ["pytorch:21.02", "pytorch:21.12"] # 21.02 for backward comp. container: image: nvcr.io/nvidia/${{ matrix.container }}-py3 # testing with the latest pytorch base image options: "--gpus all" @@ -91,8 +91,8 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5, 3, device=torch.device("cuda:0")))' - BUILD_MONAI=1 ./runtests.sh --coverage --unittests # unit tests with coverage report - BUILD_MONAI=1 ./runtests.sh --coverage --net # integration tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --unittests --disttests # unit tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --net # integration tests with coverage report coverage xml if pgrep python; then pkill python; fi - name: Upload coverage @@ -106,7 +106,7 @@ jobs: if: github.repository == 'Project-MONAI/MONAI' strategy: matrix: - container: ["pytorch:21.02", "pytorch:21.10"] # 21.02 for backward comp. + container: ["pytorch:21.02", "pytorch:21.12"] # 21.02 for backward comp. container: image: nvcr.io/nvidia/${{ matrix.container }}-py3 # testing with the latest pytorch base image options: "--gpus all" @@ -190,8 +190,8 @@ jobs: python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5,3, device=torch.device("cuda:0")))' ngc --version - BUILD_MONAI=1 ./runtests.sh --coverage --pytype --unittests # unit tests with pytype checks, coverage report - BUILD_MONAI=1 ./runtests.sh --coverage --net # integration tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --pytype --unittests --disttests # unit tests with pytype checks, coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --net # integration tests with coverage report coverage xml if pgrep python; then pkill python; fi - name: Upload coverage diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ff77a171ee..4b93632723 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -34,7 +34,7 @@ jobs: which python python -m pip install --upgrade pip wheel python -m pip uninstall -y torch torchvision - python -m pip install torch==1.10.0+cu111 torchvision==0.11.1+cu111 -f https://download.pytorch.org/whl/torch_stable.html + python -m pip install torch==1.10.1+cu111 torchvision==0.11.2+cu111 -f https://download.pytorch.org/whl/torch_stable.html python -m pip install -r requirements-dev.txt - name: Run integration tests run: | @@ -46,8 +46,8 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5,3, device=torch.device("cuda:0")))' - BUILD_MONAI=1 ./runtests.sh --net - BUILD_MONAI=1 ./runtests.sh --unittests + BUILD_MONAI=1 ./runtests.sh --build --net + BUILD_MONAI=1 ./runtests.sh --build --unittests --disttests if pgrep python; then pkill python; fi shell: bash - name: Add reaction diff --git a/.github/workflows/pythonapp-gpu.yml b/.github/workflows/pythonapp-gpu.yml index 6e460a2ade..848eaedcd6 100644 --- a/.github/workflows/pythonapp-gpu.yml +++ b/.github/workflows/pythonapp-gpu.yml @@ -20,34 +20,36 @@ jobs: matrix: environment: - "PT17+CUDA102" - - "PT17+CUDA110" - "PT18+CUDA102" - - "PT18+CUDA110" + - "PT18+CUDA112" - "PT19+CUDA114" + - "PT110+CUDA115" - "PT110+CUDA102" include: + # https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes - environment: PT17+CUDA102 pytorch: "torch==1.7.1 torchvision==0.8.2" base: "nvcr.io/nvidia/cuda:10.2-devel-ubuntu18.04" - - environment: PT17+CUDA110 - # we explicitly set pytorch to -h to avoid pip install error - pytorch: "-h" - base: "nvcr.io/nvidia/pytorch:20.09-py3" - environment: PT18+CUDA102 pytorch: "torch==1.8.1 torchvision==0.9.1" base: "nvcr.io/nvidia/cuda:10.2-devel-ubuntu18.04" - - environment: PT18+CUDA110 + - environment: PT18+CUDA112 # we explicitly set pytorch to -h to avoid pip install error + # 21.03: 1.9.0a0+df837d0 pytorch: "-h" - base: "nvcr.io/nvidia/pytorch:21.02-py3" + base: "nvcr.io/nvidia/pytorch:21.03-py3" - environment: PT19+CUDA114 # we explicitly set pytorch to -h to avoid pip install error - # https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes # 21.10: 1.10.0a0+0aef44c pytorch: "-h" base: "nvcr.io/nvidia/pytorch:21.10-py3" + - environment: PT110+CUDA115 + # we explicitly set pytorch to -h to avoid pip install error + # 21.12: 1.11.0a0+b6df043 + pytorch: "-h" + base: "nvcr.io/nvidia/pytorch:21.12-py3" - environment: PT110+CUDA102 - pytorch: "torch==1.10.0 torchvision==0.11.1" + pytorch: "torch==1.10.1 torchvision==0.11.2" base: "nvcr.io/nvidia/cuda:10.2-devel-ubuntu18.04" container: image: ${{ matrix.base }} @@ -61,7 +63,7 @@ jobs: [ ${{ matrix.environment }} = "PT18+CUDA102" ] || \ [ ${{ matrix.environment }} = "PT110+CUDA102" ] then - PYVER=3.6 PYSFX=3 DISTUTILS=python3-distutils && \ + PYVER=3.7 PYSFX=3 DISTUTILS=python3-distutils && \ apt-get update && apt-get install -y --no-install-recommends \ curl \ pkg-config \ @@ -122,7 +124,7 @@ jobs: python -c 'import torch; print(torch.rand(5, 3, device=torch.device("cuda:0")))' python -c "import monai; monai.config.print_config()" # build for the current self-hosted CI Tesla V100 - BUILD_MONAI=1 TORCH_CUDA_ARCH_LIST="7.0" ./runtests.sh --quick --unittests + BUILD_MONAI=1 TORCH_CUDA_ARCH_LIST="7.0" ./runtests.sh --build --quick --unittests --disttests if [ ${{ matrix.environment }} = "PT110+CUDA102" ]; then # test the clang-format tool downloading once coverage run -m tests.clang_format_utils diff --git a/.github/workflows/pythonapp-min.yml b/.github/workflows/pythonapp-min.yml index e2ed6c7513..b7816542ea 100644 --- a/.github/workflows/pythonapp-min.yml +++ b/.github/workflows/pythonapp-min.yml @@ -51,11 +51,11 @@ jobs: - if: runner.os == 'windows' name: Install torch cpu from pytorch.org (Windows only) run: | - python -m pip install torch==1.10.0+cpu -f https://download.pytorch.org/whl/torch_stable.html + python -m pip install torch==1.10.1+cpu -f https://download.pytorch.org/whl/torch_stable.html - name: Install the dependencies run: | # min. requirements - python -m pip install torch==1.10.0 + python -m pip install torch==1.10.1 python -m pip install -r requirements-min.txt python -m pip list BUILD_MONAI=0 python setup.py develop # no compile of extensions @@ -74,7 +74,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] timeout-minutes: 40 steps: - uses: actions/checkout@v2 @@ -101,7 +101,7 @@ jobs: - name: Install the dependencies run: | # min. requirements - python -m pip install torch==1.10.0 + python -m pip install torch==1.10.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 2e2318c8a8..7237c8d54d 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -44,9 +44,9 @@ jobs: - name: Lint and type check run: | # clean up temporary files - $(pwd)/runtests.sh --clean + $(pwd)/runtests.sh --build --clean # Git hub actions have 2 cores, so parallize pytype - $(pwd)/runtests.sh --codeformat -j 2 + $(pwd)/runtests.sh --build --codeformat -j 2 quick-py3: # full dependencies installed tests for different OS runs-on: ${{ matrix.os }} @@ -87,10 +87,10 @@ jobs: - if: runner.os == 'windows' name: Install torch cpu from pytorch.org (Windows only) run: | - python -m pip install torch==1.10.0+cpu torchvision==0.11.1+cpu -f https://download.pytorch.org/whl/torch_stable.html + python -m pip install torch==1.10.1+cpu torchvision==0.11.2+cpu -f https://download.pytorch.org/whl/torch_stable.html - name: Install the dependencies run: | - python -m pip install torch==1.10.0 torchvision==0.11.1 + python -m pip install torch==1.10.1 torchvision==0.11.2 cat "requirements-dev.txt" python -m pip install -r requirements-dev.txt python -m pip list diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34f8390fa9..9cef1ff090 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/setupapp.yml b/.github/workflows/setupapp.yml index eaf91f8876..ede2c87b92 100644 --- a/.github/workflows/setupapp.yml +++ b/.github/workflows/setupapp.yml @@ -44,7 +44,7 @@ jobs: python -m pip install --upgrade pip wheel python -m pip uninstall -y torch torchvision rm -rf $(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")/ruamel* - python -m pip install torch==1.10.0 torchvision==0.11.1 + python -m pip install torch==1.10.1 torchvision==0.11.2 python -m pip install -r requirements-dev.txt - name: Run unit tests report coverage run: | @@ -59,8 +59,8 @@ jobs: python -c $'import torch\na,b=torch.zeros(1,device="cuda:0"),torch.zeros(1,device="cuda:1");\nwhile True:print(a,b)' > /dev/null & python -c "import torch; print(torch.__version__); print('{} of GPUs available'.format(torch.cuda.device_count()))" python -c 'import torch; print(torch.rand(5, 3, device=torch.device("cuda:0")))' - BUILD_MONAI=1 ./runtests.sh --coverage --unittests # unit tests with coverage report - BUILD_MONAI=1 ./runtests.sh --coverage --net # integration tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --unittests --disttests # unit tests with coverage report + BUILD_MONAI=1 ./runtests.sh --build --coverage --net # integration tests with coverage report coverage xml if pgrep python; then pkill python; fi shell: bash @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 with: @@ -98,13 +98,13 @@ jobs: - name: Install the dependencies run: | python -m pip install --upgrade pip wheel - python -m pip install torch==1.10.0 torchvision==0.11.1 + python -m pip install torch==1.10.1 torchvision==0.11.2 python -m pip install -r requirements-dev.txt - name: Run quick tests CPU ubuntu run: | python -m pip list python -c 'import torch; print(torch.__version__); print(torch.rand(5,3))' - BUILD_MONAI=1 ./runtests.sh --quick --unittests + BUILD_MONAI=1 ./runtests.sh --build --quick --unittests --disttests coverage xml - name: Upload coverage uses: codecov/codecov-action@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea637fe329..3fc762db46 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace @@ -23,10 +23,10 @@ repos: - id: detect-private-key - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v2.31.0 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] name: Upgrade code exclude: | (?x)^( @@ -35,7 +35,7 @@ repos: )$ - repo: https://github.com/asottile/yesqa - rev: v1.2.3 + rev: v1.3.0 hooks: - id: yesqa name: Unused noqa diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 954549581a..ec6bf35ef9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ python -m pip install -U -r requirements-dev.txt License information: all source code files should start with this paragraph: ``` -# Copyright MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -113,7 +113,7 @@ or (for new features that would not break existing functionality): ``` It is recommended that the new test `test_[module_name].py` is constructed by using only -python 3.6+ build-in functions, `torch`, `numpy`, `coverage` (for reporting code coverages) and `parameterized` (for organising test cases) packages. +python 3.7+ build-in functions, `torch`, `numpy`, `coverage` (for reporting code coverages) and `parameterized` (for organising test cases) packages. If it requires any other external packages, please make sure: - the packages are listed in [`requirements-dev.txt`](requirements-dev.txt) - the new test `test_[module_name].py` is added to the `exclude_cases` in [`./tests/min_tests.py`](./tests/min_tests.py) so that @@ -228,7 +228,7 @@ Notably, for example, ``import monai.transforms.Spacing`` is the equivalent of ``monai.transforms.spatial.array.Spacing`` if ``class Spacing`` defined in file `monai/transforms/spatial/array.py` is decorated with ``@export("monai.transforms")``. -For string definition, [f-string](https://www.python.org/dev/peps/pep-0498/) is recommended to use over `%-print` and `format-print` from python 3.6. So please try to use `f-string` if you need to define any string object. +For string definition, [f-string](https://www.python.org/dev/peps/pep-0498/) is recommended to use over `%-print` and `format-print`. So please try to use `f-string` if you need to define any string object. #### Backwards compatibility MONAI is currently under active development, and with major version zero (following the [Semantic Versioning](https://semver.org/)). diff --git a/Dockerfile b/Dockerfile index a76613f14a..43dc103e0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -11,7 +11,7 @@ # To build with a different base image # please run `docker build` using the `--build-arg PYTORCH_IMAGE=...` flag. -ARG PYTORCH_IMAGE=nvcr.io/nvidia/pytorch:21.10-py3 +ARG PYTORCH_IMAGE=nvcr.io/nvidia/pytorch:21.12-py3 FROM ${PYTORCH_IMAGE} LABEL maintainer="monai.contact@gmail.com" diff --git a/docs/requirements.txt b/docs/requirements.txt index ac9c7f38a7..b56fd970d2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ -f https://download.pytorch.org/whl/cpu/torch-1.6.0%2Bcpu-cp37-cp37m-linux_x86_64.whl torch>=1.6 -pytorch-ignite==0.4.6 +pytorch-ignite==0.4.7 numpy>=1.17 itk>=5.2 nibabel diff --git a/docs/source/apps.rst b/docs/source/apps.rst index 11d60767ec..f4f7aff2d2 100644 --- a/docs/source/apps.rst +++ b/docs/source/apps.rst @@ -114,7 +114,11 @@ Clara MMARs .. automodule:: monai.apps.pathology.transforms.spatial.array .. autoclass:: SplitOnGrid :members: +.. autoclass:: TileOnGrid + :members: .. automodule:: monai.apps.pathology.transforms.spatial.dictionary .. autoclass:: SplitOnGridd :members: +.. autoclass:: TileOnGridd + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index fe10c546cd..47a45a78e8 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ # -- Project information ----------------------------------------------------- project = "MONAI" -copyright = "2020 - 2021 MONAI Consortium" +copyright = "MONAI Consortium" author = "MONAI Contributors" # The full version, including alpha/beta/rc tags diff --git a/docs/source/data.rst b/docs/source/data.rst index 0ab64edb7b..7f95354e8c 100644 --- a/docs/source/data.rst +++ b/docs/source/data.rst @@ -21,6 +21,12 @@ Generic Interfaces :members: :special-members: __next__ +`DatasetFunc` +~~~~~~~~~~~~~ +.. autoclass:: DatasetFunc + :members: + :special-members: __next__ + `ShuffleBuffer` ~~~~~~~~~~~~~~~ .. autoclass:: ShuffleBuffer @@ -202,6 +208,7 @@ Decathlon Datalist .. autofunction:: monai.data.load_decathlon_datalist .. autofunction:: monai.data.load_decathlon_properties .. autofunction:: monai.data.check_missing_files +.. autofunction:: monai.data.create_cross_validation_datalist DataLoader diff --git a/docs/source/engines.rst b/docs/source/engines.rst index cc0ec3c659..0cd40afb78 100644 --- a/docs/source/engines.rst +++ b/docs/source/engines.rst @@ -11,20 +11,21 @@ Multi-GPU data parallel .. automodule:: monai.engines.multi_gpu_supervised_trainer :members: - Workflows --------- -.. automodule:: monai.engines.workflow -.. currentmodule:: monai.engines.workflow +.. currentmodule:: monai.engines + +`BaseWorkflow` +~~~~~~~~~~~~~~ +.. autoclass:: BaseWorkflow + :members: `Workflow` ~~~~~~~~~~ .. autoclass:: Workflow :members: -.. currentmodule:: monai.engines - `Trainer` ~~~~~~~~~ .. autoclass:: Trainer @@ -54,3 +55,8 @@ Workflows ~~~~~~~~~~~~~~~~~~~ .. autoclass:: EnsembleEvaluator :members: + +Utilities +--------- +.. automodule:: monai.engines.utils + :members: diff --git a/docs/source/installation.md b/docs/source/installation.md index c431b3389a..2635a6c386 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -14,7 +14,7 @@ --- -MONAI's core functionality is written in Python 3 (>= 3.6) and only requires [Numpy](https://numpy.org/) and [Pytorch](https://pytorch.org/). +MONAI's core functionality is written in Python 3 (>= 3.7) and only requires [Numpy](https://numpy.org/) and [Pytorch](https://pytorch.org/). The package is currently distributed via Github as the primary source code repository, and the Python package index (PyPI). The pre-built Docker images are made available on DockerHub. diff --git a/docs/source/metrics.rst b/docs/source/metrics.rst index d6e2aff75b..332571d345 100644 --- a/docs/source/metrics.rst +++ b/docs/source/metrics.rst @@ -8,6 +8,8 @@ Metrics `FROC` ------ +.. autofunction:: compute_fp_tp_probs +.. autofunction:: compute_froc_curve_data .. autofunction:: compute_froc_score `Metric` @@ -47,6 +49,7 @@ Metrics `Confusion matrix` ------------------ .. autofunction:: get_confusion_matrix +.. autofunction:: compute_confusion_matrix_metric .. autoclass:: ConfusionMatrixMetric :members: @@ -54,6 +57,7 @@ Metrics `Hausdorff distance` -------------------- .. autofunction:: compute_hausdorff_distance +.. autofunction:: compute_percent_hausdorff_distance .. autoclass:: HausdorffDistanceMetric :members: @@ -89,3 +93,8 @@ Metrics -------------------- .. autoclass:: CumulativeAverage :members: + +Utilities +--------- +.. automodule:: monai.metrics.utils + :members: diff --git a/docs/source/networks.rst b/docs/source/networks.rst index bb9d24e6f7..720a3723dc 100644 --- a/docs/source/networks.rst +++ b/docs/source/networks.rst @@ -21,7 +21,7 @@ Blocks :members: `CRF` -~~~~~~~~~~~~~ +~~~~~ .. autoclass:: CRF :members: @@ -73,6 +73,8 @@ Blocks :members: .. autoclass:: UnetUpBlock :members: +.. autoclass:: UnetOutBlock + :members: `SegResnet Block` ~~~~~~~~~~~~~~~~~ @@ -254,6 +256,11 @@ Layers .. automodule:: monai.networks.layers.Conv :members: +`Pad` +~~~~~ +.. automodule:: monai.networks.layers.Pad + :members: + `Pool` ~~~~~~ .. automodule:: monai.networks.layers.Pool @@ -300,7 +307,7 @@ Layers :members: `PHLFilter` -~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~ .. autoclass:: PHLFilter `GaussianMixtureModel` @@ -386,6 +393,11 @@ Nets .. autoclass:: EfficientNet :members: +`BlockArgs` +~~~~~~~~~~~ +.. autoclass:: BlockArgs + :members: + `EfficientNetBN` ~~~~~~~~~~~~~~~~ .. autoclass:: EfficientNetBN @@ -406,6 +418,11 @@ Nets .. autoclass:: SegResNetVAE :members: +`ResNet` +~~~~~~~~ +.. autoclass:: ResNet + :members: + `SENet` ~~~~~~~ .. autoclass:: SENet @@ -513,6 +530,11 @@ Nets .. autoclass:: FullyConnectedNet :members: +`VarFullyConnectedNet` +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: VarFullyConnectedNet + :members: + `Generator` ~~~~~~~~~~~ .. autoclass:: Generator diff --git a/docs/source/optimizers.rst b/docs/source/optimizers.rst index c766ac3cf9..67cbdc0951 100644 --- a/docs/source/optimizers.rst +++ b/docs/source/optimizers.rst @@ -6,6 +6,11 @@ Optimizers ========== .. currentmodule:: monai.optimizers +`LearningRateFinder` +-------------------- +.. autoclass:: LearningRateFinder + :members: + `Novograd` ---------- .. autoclass:: Novograd @@ -14,3 +19,18 @@ Optimizers `Generate parameter groups` --------------------------- .. autofunction:: generate_param_groups + +`ExponentialLR` +--------------- +.. autoclass:: ExponentialLR + :members: + +`LinearLR` +---------- +.. autoclass:: LinearLR + :members: + +`WarmupCosineSchedule` +---------------------- +.. autoclass:: WarmupCosineSchedule + :members: diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst index 37a4a4cf44..49ed4c9e6c 100644 --- a/docs/source/transforms.rst +++ b/docs/source/transforms.rst @@ -43,6 +43,11 @@ Generic Interfaces .. autoclass:: InvertibleTransform :members: +`TraceableTransform` +^^^^^^^^^^^^^^^^^^^^ +.. autoclass:: TraceableTransform + :members: + `BatchInverseTransform` ^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: BatchInverseTransform @@ -64,6 +69,12 @@ Vanilla Transforms Crop and Pad ^^^^^^^^^^^^ +`PadListDataCollate` +"""""""""""""""""""" +.. autoclass:: PadListDataCollate + :members: + :special-members: __call__ + `Pad` """"" .. autoclass:: Pad @@ -313,6 +324,8 @@ Intensity `SavitzkyGolaySmooth` """"""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/SavitzkyGolaySmooth.png + :alt: example of SavitzkyGolaySmooth .. autoclass:: SavitzkyGolaySmooth :members: :special-members: __call__ @@ -395,6 +408,14 @@ Intensity :members: :special-members: __call__ +`RandRicianNoise` +""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandRicianNoise.png + :alt: example of RandRicianNoise +.. autoclass:: RandRicianNoise + :members: + :special-members: __call__ + `RandCoarseTransform` """"""""""""""""""""" .. autoclass:: RandCoarseTransform @@ -626,6 +647,8 @@ Spatial `GridDistortion` """""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/GridDistortion.png + :alt: example of GridDistortion .. autoclass:: GridDistortion :members: :special-members: __call__ @@ -702,28 +725,33 @@ Spatial :members: :special-members: __call__ -`AddCoordinateChannels` -""""""""""""""""""""""" -.. autoclass:: AddCoordinateChannels - :members: - :special-members: __call__ - - Smooth Field ^^^^^^^^^^^^ `RandSmoothFieldAdjustContrast` """"""""""""""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothFieldAdjustContrast.png + :alt: example of RandSmoothFieldAdjustContrast .. autoclass:: RandSmoothFieldAdjustContrast :members: :special-members: __call__ `RandSmoothFieldAdjustIntensity` """""""""""""""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothFieldAdjustIntensity.png + :alt: example of RandSmoothFieldAdjustIntensity .. autoclass:: RandSmoothFieldAdjustIntensity :members: :special-members: __call__ +`RandSmoothDeform` +"""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothDeform.png + :alt: example of RandSmoothDeform +.. autoclass:: RandSmoothDeform + :members: + :special-members: __call__ + Utility ^^^^^^^ @@ -830,6 +858,12 @@ Utility :members: :special-members: __call__ +`RemoveRepeatedChannel` +""""""""""""""""""""""" +.. autoclass:: RemoveRepeatedChannel + :members: + :special-members: __call__ + `LabelToMask` """"""""""""" .. autoclass:: LabelToMask @@ -902,6 +936,12 @@ Utility :members: :special-members: __call__ +`AddCoordinateChannels` +""""""""""""""""""""""" +.. autoclass:: AddCoordinateChannels + :members: + :special-members: __call__ + Dictionary Transforms --------------------- @@ -1150,6 +1190,14 @@ Intensity (Dict) :members: :special-members: __call__ +`RandRicianNoised` +"""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandRicianNoised.png + :alt: example of RandRicianNoised +.. autoclass:: RandRicianNoised + :members: + :special-members: __call__ + `ScaleIntensityRangePercentilesd` """"""""""""""""""""""""""""""""" .. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/ScaleIntensityRangePercentilesd.png @@ -1182,6 +1230,14 @@ Intensity (Dict) :members: :special-members: __call__ +`SavitzkyGolaySmoothd` +"""""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/SavitzkyGolaySmoothd.png + :alt: example of SavitzkyGolaySmoothd +.. autoclass:: SavitzkyGolaySmoothd + :members: + :special-members: __call__ + `GaussianSmoothd` """"""""""""""""" .. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/GaussianSmoothd.png @@ -1470,14 +1526,10 @@ Spatial (Dict) :members: :special-members: __call__ -`AddCoordinateChannelsd` -"""""""""""""""""""""""" -.. autoclass:: AddCoordinateChannelsd - :members: - :special-members: __call__ - `GridDistortiond` """"""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/GridDistortiond.png + :alt: example of GridDistortiond .. autoclass:: GridDistortiond :members: :special-members: __call__ @@ -1495,16 +1547,28 @@ Smooth Field (Dict) `RandSmoothFieldAdjustContrastd` """""""""""""""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothFieldAdjustContrastd.png + :alt: example of RandSmoothFieldAdjustContrastd .. autoclass:: RandSmoothFieldAdjustContrastd :members: :special-members: __call__ `RandSmoothFieldAdjustIntensityd` """"""""""""""""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothFieldAdjustIntensityd.png + :alt: example of RandSmoothFieldAdjustIntensityd .. autoclass:: RandSmoothFieldAdjustIntensityd :members: :special-members: __call__ +`RandSmoothDeformd` +""""""""""""""""""" +.. image:: https://github.com/Project-MONAI/DocImages/raw/main/transforms/RandSmoothDeformd.png + :alt: example of RandSmoothDeformd +.. autoclass:: RandSmoothDeformd + :members: + :special-members: __call__ + Utility (Dict) ^^^^^^^^^^^^^^ @@ -1568,6 +1632,12 @@ Utility (Dict) :members: :special-members: __call__ +`ToPIL` +""""""" +.. autoclass:: ToPIL + :members: + :special-members: __call__ + `ToCupyd` """"""""" .. autoclass:: ToCupyd @@ -1706,10 +1776,21 @@ Utility (Dict) :members: :special-members: __call__ +`AddCoordinateChannelsd` +"""""""""""""""""""""""" +.. autoclass:: AddCoordinateChannelsd + :members: + :special-members: __call__ + Transform Adaptors ------------------ .. automodule:: monai.transforms.adaptors +`FunctionSignature` +^^^^^^^^^^^^^^^^^^^ +.. autoclass:: FunctionSignature + :members: + `adaptor` ^^^^^^^^^ .. autofunction:: monai.transforms.adaptors.adaptor diff --git a/docs/source/utils.rst b/docs/source/utils.rst index c97d16de17..881519936b 100644 --- a/docs/source/utils.rst +++ b/docs/source/utils.rst @@ -51,3 +51,28 @@ Type conversion --------------- .. automodule:: monai.utils.type_conversion :members: + +Decorators +---------- +.. automodule:: monai.utils.decorators + :members: + +Distributed Data Parallel +------------------------- +.. automodule:: monai.utils.dist + :members: + +Enums +----- +.. automodule:: monai.utils.enums + :members: + +Jupyter Utilities +----------------- +.. automodule:: monai.utils.jupyter_utils + :members: + +State Cacher +------------ +.. automodule:: monai.utils.state_cacher + :members: diff --git a/docs/source/visualize.rst b/docs/source/visualize.rst index 850fd51770..3779feec88 100644 --- a/docs/source/visualize.rst +++ b/docs/source/visualize.rst @@ -24,3 +24,8 @@ Occlusion sensitivity .. automodule:: monai.visualize.occlusion_sensitivity :members: + +Utilities +--------- +.. automodule:: monai.visualize.utils + :members: diff --git a/matshow3d_rgb_test.png b/matshow3d_rgb_test.png deleted file mode 100644 index 7c8e224c0eb95962b849b760e6892cd864c69ce1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15705 zcmeHuRajJS)b9Wi(jd|eQX)B|bV*AKib$7$fWV*blx}Hh0hJOEk?xif5$Wz0knk+N zi*tQ0&gI85z~i`?z4!aB_{G{08tRYmuqm+-2n3#zqMR1|?z#EK!hoM2^*?39FA*1c zeHZOlmM;I9I9njpOk5o8Ub)!Wn9{phIKQ@e<-pH{qWB zpC7f|Dw)D}ayb)uf=C$nuvoBoDQ`1$U@|f@pS94<0YkY0q_*;L4~jXESOOXUB7nXZW7H`r_`>A&=diujLmKp-aN}NF>si z!SG+XqW{~Ta#JrE#B zh!-vLzX$)nuExAkM+SLlWq94lcmE~f;=i6ayo3n%tCPtQJ3-zo^~$ebv2%vQxSP&U zcigBVJ@p;-_KP6Cg~H-Usp>ro07O>~?l`#z;yA;)tGM(U>4ZE=+{}vuCoo8d8l$ zVK(Vlw@}IP!^mU19jP&6mRfjlx3{-j){b(g>=<)~&BSPz_V)=9aq7hfbN{a95dM;9 zL6R*?-LWHkr@WNW6`w!fYUd%ux z53;C^$?=c}!@1;Jz`bWJ)s&(i3=d4+%VZ&s3>xbMB}ys9(xOp;ocIl(b0JBjYg`)+9JnXlTM*jk)87=o+Gw3^Un3g zG+jrcUl9gMR z4#JP@8_aKVu+2M+<8fE|LGlA5Z?k&HkX zm6I(v>f>kIPZOuX|7ewvDC86U+Nq@uZEu$qF@$x4kcO90`4imiF4>>84>0avhgdSo zlusrOzaq!@d)4H=dGo=MnvvO(sr|is({M;jkP56Lk`k|e(xL8Ip5(vT73Wr@8mz;Y z83$PFV^47rruGFw*r%}#k`qswDLcI=5z1&(;TRX%Sj@4Fov#S-y#saQC45!%iG@03n(gtq5WQz{TbHM0|pbQg<(h==u*qQ5Cq$;o#;_N3^ct(x;e zLa`+)!>%+MJ;o;(3sYofRH*&@IT((C+Yn)c7@VG-hRlW{pvX+jqo$pyDohQLbU6E@ z+^F;XXl>#2;-=*2m4&DmGYth|$YHLWUYJY^!gYwz#*&NDL_t85=-lK#N3sk@ayqZf z1pCC6D{r4=b;Q$;7-L8%V zxxLhv4{2j_p4~Ed8}P-DOO@r$+oA}!hUljsLp>W~;Lfy4Zgo4w4O-rNUdf3AyH!?J z2BlT_zg_B~Z1G44b18ay5$-L*ZYPcM$%UhHNX@w6wUx$=LP*b;m>3PNRHy4p5ATcV zxvu2QV@aYB;hm<71+blRSPF&4JXi>*;)fKhiMK_w zhN4C&LK)cgxl-F*M3avPsyORYzI8#$7V2MITv#J1^`AfQ+}-7ZT4T)9vbk9ryl>7I z;UXFh$qpR~mcQC+c#-aUfv%#VA#%foVCzuRlC zF$|U&DxIS+F2WgsP~0516O316Mo`aL$96M~6i#j_Jx38U+tT4SDxbXfeEq~z-^i%T zoG)Isg~S1B7YTOmge{7H0mVSC0ggxv(+L$p$dWJ|rlmpz!T0Tti>N%3GWpa}V(-M$ zYP7_y0k3>A63(g5iP7y;@Xk^hNsV71ZPYA}T5)Ox-m*OUsdr z?YmXyivxj|KY|q^Bi**q+KK}4z(X1R-L}SY>&Ju3shlf{rt6b(CKBvcC+h9JJ*bm# z5(aWyM#^YLI3aB;6w}YsI~1?FsBR(Xe9suT48Qn8>>@MNpf4z~2e!WBxu21dvF|SS z#p8&jbfT1c)m;o9Djl?zP@yzc!f1sNYh*`7_bVxn5t#vKr2o7o#OZmGjvH^rDa-08vr)q&!crEP{xS zvE)P+GHyxOeI@xxMRhe=J5L<6#RxfAj_*5eo!`~Qy46S^*8Q+&E6shEy|&B)4BrW=)LX zaXs;TKHKp0BdUKvMyQFRvV6WV34MdYA!EEE)|N12D`mo>8cQMs8L{|`gV3Kpf0i5V zr@Ina9|OL+sSdoxO$0QgOoT6j6zK57LgXl7yB7)9<4h~xdh0dq9<{XLn)9X2d(BnRja_WZ}U@OIDVT>e)IOs<2$XJpToaq}HCj zYeP+?FrnZbbjq6rVWh@iS{=Lz&u}?L=lKZek?|oNC^Q|AA<$Z2FP6`tptgNI_;-5Y z6H(O>M&}#8=e_puY`>jg0icdUU7@AZw<()yx*Q-}$6C6cjB?Z{=igHF_wBt($@AS%)T8Emo zsX}03*M_oW>!v$bS6M`}VhU8h^+GJoo=!Y@AH zWzhF+IueXnJIhG>D)n*ce2$6UbTaP#t`EW?<9au@heMS6!v?_DmG29DuaA1NB{~US zZ8BE_Y5Y*;iR~#)?kD#R2uP}N4oKnS9=&(zjX8M)V8Rm#i!rrODq8bsFnMHjKc6&LcWae@;!c?(Tl_&Rz=$p$MPb>&B$> z_J_iS2iWS{q`R0x!%Wx3UNb>#cMyOWC{KW}0ZMuw_Ob#AIQvo86hbBBFh6&_GY4Vr zwDU{A&cR`EW2~58hg-y}M)6hMG+S!Fu&8KTUDL?}oig2eX~2b06rc#p)KqchrkeBb z#$K<-IyyS~!@>Zt27G49mo`uk@gW3jb)=!80dg*ne7@D=a5=)|_}NENZwWd8)qoa0 z8P*3r{m8;T18nEv-nXFqd?xbH0BBb`JN8h$E;n+n*{|Wp?RrD?zvw_GZ(`x_{F`uY zti)eM>N{I3{Z(>zZ|I3(ledBOv2D#8g?yBDo{E~95CC7C+}un#!%LCeO;XiUN$&@q zq6)KE0LFuiGOZ+sBEO$cccr+@yY0ghSo8FO-K$qiGyl!q?UpN)63j@>#=eW@lEl$^ zAZ9FF{BifM{3}y(AW&c2b|@DQgrQPZ;3r|<+$%s(;2J6_Du5PDr2y9Ecpu)rz8wBX zeY!W7YM5%2hF%Tu4T^Q3y_XjiVt=<`?+r=E-f2HN#N^_=hgtZ*^Zs5*v@P&ti+S+n z6PYsE^QB?k?I%$bz;g55n`yvAoXws#g+W3!ze+DUR)8S*-coBHS&11W-Mg`on=x4A zvW?eHSR3Pdw~zveXe_NbDgyTT+tJX&JQCw$#_7)Ab5)$V>QD6a3hF8=D;a1rhya@d zzy|K(b-JrGvnY%Y5cr0RAilV6ks{_H2;hE4mrT=pU7}*5N@faGQUT=LSDS`K23P?g zsIc%JBy5D}gza5GxH2l`A3yrIcD_Mqu#qflRJY0-&D7c(HhSKL(nROIj{@ZM(~IVq zoXcg_v+L95*t$7iDkxba)+E4xfjbXnJ#?gg6g9|$8(;oDWPNmWG>V9quR^-W(}hw= zj5cgZ7{1~z-_DrzsY3x77gMEBQX;+yidpw1Jf~Y?nU_8ZxS=NWsr{`yxYRT>O5(sB z3nU^G@*xICeHbYo9-29o_`-wO3!LkF!;9>M0IX6YGIetRhD;gAWfm0H1vLIAAps zwkH4mM+U`d#hC(%GvMKtA3sR-o;<1i_AN?>NT4Pkc^k-)pZl5v7rfwP} z4eOexZ97Mg6`BEQqYK>q5*8LFEG8yLPbdW(zDZRgryFht$RT9&fGbpjv+3rmXS{^O z2+p79q8}drlonFE)8F5pSHr0Gj4QS9$HHE`UG4&+-()YlINe;inNS`PX2XTm*tx+FK%Lxd4`6?s8Sg zX>Ew++wrK%Swr*1Lvua~=q3!Lk*%QXz)E>|P(T6SaG`{xgY^Og9PHJxGhJt5K$_6Y z%w_Z~97<~lpLTt#G5tyV!a^=dlPYGKXuLy#OWnn$J4_2gAyqC+=(!APrQbXe1&H$9POuVWwB|WUCU2b@{dzKRW8xLVuWiI=&0f7CfUN_*@ZSE1d&f$Ds(kIJOb@c z^56Ne{4eJOHW4*W&cJj3xPUIWRpAfu~enz2%KM4y^4rwd)1Y^Tl}u8)BLeKkT0C22fY@3lQY! z{vFT(^XbuNqw>^=9L;|}_KLOhC3RK0da+i+4akA~fMV~gZ{tqgweXgcg1R(VyCjKP zk5+?ny4OLFU=Fx_(?RqrMGh&Ch$UeVNr^wgSsXV;?;Qg`7uIQUYk01NlQGEBwy=-O z-%VtZr&aO}%K2%Lb}rvrS9I1R{gxwR?cFxRoOaU>6XYW$7#aZNp~VI~dEuj5aEK_t z$Xm{yjC3WWD&+iqe)VhTCUrn?0x1*dEb`fgel{lQFE*Xx!yaX*aG-~e2^zaZYe(a$y<%jS=14H$o52enF3rXWfBt2939X_imQ3n}!a4+<-g(;B-PD}DrB z{*B=)1u^B<2rMELDcr2r0!ZhkS`%_0{h)Mdy>bkX)YM6CcwDRvl}s-Q<5q@0M`t;u zuz#9q4xSoF=&{{a9mz=ph_g*9tVj{WD0|WG0PR4rFJe;ETdFLHfFxyrLsw6c^oDT6 zE#VdYHWX*WZT~>?_Sx&9i^G-v1XL*TCN?&=si>%cKenmImzw{QO=f?%8|9ee`*$O1 z$lBgsO%WZN$$pyj{Fb9P)|6ibrwStz<`+fQFqz!6z~opz_4YxR5>JB4Ln(kCUUGth^9MKqV1AItV1#|>*C_T~6 z0x1f_M%3@XO91<_KF(GYY$>`g{do9u^glJ$PJHo~oNs8(tC8L@sPHN9GWjFCr!ITZ zSLWd(kyC(c&LZ41%oycDFO&$UxDjN8sexI%)8PQ61n2&!ouI{Hj3}E{Y3X(bk^dDw z<-9dAJ-+n2K6@w21EmO`O8rcBH<9=9OGVb2Y65Q)c-5BF3W<2Ha6)AGBFk-)6w#`$ z*Ih&bXDrq{=OTmP;_=#KuplWaX33QfYgba{2sM!6Cw{Fm;39fA$lUf3g9%i~-KI_Q z(T&g6mFpcn1-wryYnQym#vT{>sxDAQcMWur8PUy#C7xb@{`4Rvw2N^+dBU{uVz)u- zSzI+~5lcqakm=1rT*+VQ5(~#fw zx9Z1bLp7&Vh&W5*w_<);k<*fMRsQcUH=a zJh!?4El|tFl8-`UT`c??3dhdc>4g(eNa%+dLa+KvEQ1+C%0??My+ci~UdoXw*kc^h zSChS~;z#UX?fKCKNFnauzyHN?0o8x0i2ZJhSSD-X5oh?h&(Zm1)SvL`qs?Dbw9Jc= z*GrN&iWA6bQ`T)k{j%!Z!;_1qxQN4}OQTy}6i6LiZH0;k^}|4Mu2RM?Ps3|&Ns^UI z;>l+ zrm>pnb-xy|miJ8D)Yd9xr7{aOng3PuvS-B^y6WN7#>aDtFE5{DUFnfMZ7}2l7)3!E zJ^Li&?<|;s%$F};zWm~Q^>v(`1AXADZ1VIN58Jx}Gfiz~1>c%l`o0xDFkjsM`|lw3 z-&rgiC<-*^9`@07g=XI>&s^l7nS$dOvhZX z%GBF8U{SGuPIJDOsFWKsf#n9$^v~MplM`1Pe7?qaWI6&Csy6Qa9>(w8SCCAgQ%OjU z$v+Xd0ofER>k^%|&PcMAblwLm0>)YLg%3g!Kyni-;SY4Q;?=gG)ktilr zmDO{}i*0^@tOh?@7(}${qrAs`D#)*v+L)NIlRo*O!CA~Fv|V*b z=W0f$TYTV74ZLc!#q3tz{Kx5?zWGsZ+o$NLC`!`$X9xZ~uEtAI3hjAuxM^Q;)pQfI zf^QFaIwFL<{(RsZ(0<8X6qrAvxO}3T#+Gd!UF7MM{9yX$&o}|`54XpI4o6kEbB)SX zgt9babFy+mY^cUKH=Cdj^#uLoTw$WsXDf3J6eaULT2;HDU5dqH(DwD8K27i?b`-;C zxmK??1lyUoDCU{Py4d|>>^GfxzqJL4r-V#A&&4Xf2b2Iu4GY8!_)5E)bzJ&wW5ilV ziyig?j8PSQM7Z7Zq~Fz~e5s?B=OC48?1w4^B#8oA%w*~Gq(?>lI=*|+m5pG=7nhAu z)hGG_>P5U@kjd8@l9q_pXo?*da7`I56)=r)dMf#58}s~+a9Y)#I%gmd2km>lE1C+T z5+IxmE{|yiK7%xfru)mica#Et9$?b%IbHpA)uc|0d)KZhU=1P`sHPxnw#WKinh{0r zZf}D#tW>ICGYI1TM zwBAwhH^*&jC^m;{?bB8)-*h0Q(O>)ON(^3OzkX50!}-^MdJl|St9Pq!46n+sew8ad z4$9Wg!d!?R017#?c&htIEv=p3=Yw9EwO~eG@CpM)%)Mh!ylSiCOqjIg_FEvNhy4Q02xClfU z(Ykzxd!1}&x%+mT51miJt5SX)D zChJ|@)`{oO-@h|U;@RdwTIc)MY99zHAt1i=DG^oM6r)VdxBV@#F)@~TWT{WU44y(6 zkjFMO9@I3_b-P|4VKUhUmmMs_{gZM(8iX95pSycFNP=L@gH;o8xp`u#yd@kXr=D;l za_cv)tgNK{9fcaT$xu@+Kc&f%7=M5@+ifR!F~K#8(sRhqMK{lA<;m^IaJUEJ(wgMI z`rr}hFRCm|{2P}(TA?5HR1|b3g7O4wKoo{HaU-$70!BYd=3S6Ml~{mCf>D9K+GfyR zg=XDlwdAoWbs4g;H#@1jTmSwQ1wZuu^rJ70L44jnw8^-Qk_zv7x8PFnl9q6`Y@K{X z@=bI)9WO?af<^&WR&G?)VME>12L?DKfRP1D98>uk1QOHaL7D#jo215xC9xIOqhQE1%VxTETpP(*^P?+}-#swg&V2rrKYcmK-e^3O2VNPTT%%WB&%8Wq&F+_$2aC zI%?-{AJt9UZwP7!d+`0P79s|b-rb#zikaK!8=DV7Q6zZvWL$=XysJY)G2^HuI@lX7 zPv4tpO}uJ4XOXx%)-GYfBArRtZfNb&QsRdKwd9y2(OQ+7jQip6?my#v=r zEi!n)VL2bPn2F;_fC6#CXgN|REDqcbLgLy99kt4t1=UtN)42iC@l^e zZ?+BrE+rZ6^$Zx{BFDu>?<0}kVJlP9C}IUIuGidIp@;@MWSJf)49Gs=ti>I3&|JQ` zYp&}~*WS>!oU3gs#`>PMBVZ2vd&5CR_`#i~y!E7+^%S@mFmaSPcn&JzkG*{&Ox1SC zo#jd*9nP}�^q@GzG`<$@?3LHtC=RL#HQ1g~kZ2MQ<}v>g{m+#vzaY3BCJB;|V` zWlg#E_q2%k?DAW>s1=X#C4j(=4x=e!UW_~7NHM?BK!{RCoBL=KYPEuBVq25`L9K7? z=!^RSLn`&WX0s1<50ny}T;-|6_Y zBh++&2SDV_sl|G<9HaZ+Ieq4FF|F9g(Q!d(hD?NpMAELxt7NJnx#~vu z%12CC_-%yIz{LhNOj>A^7>dZgHCueOjiBO)On)Bd z-%*Nani)k&*%w#7Ab5k!L<{Pnn}*Y6lBCz(tT?Zu+sG8z&E!^XZS8&#-KA75|KY<@ zK9>`l_U-52dG!6_kQ%4NM;C_@?}6__dV$xS#g z-*XG-a`hOOpGa7>^xEYekfN^Fft`Zd8O`Sz&8>UayL;HZ(j%+~r+)uV_s_i({+4(- zOuaJx7cn>`$H*&NS^M-sy+n$|Lbv2KZ`BkhU}<^VwrmjbQ>}_Y$`W(mM{I3vIdhZa zk>iOpx23P|?`!0xPO4ccp_;4-fUhfS3Y%xQ4AlqTLX5<=wwTP?@$TEyY^ z7ls59(hA$BMQ&prcx;99!`1)z$#Kom?lpc(|AQd3e^Nf#4qjP@M_gBtt(3U6p|ub- zekTJAu`&o^1ff7tl5?cIBVc!nObL4LW!J0A(1M6>3Q&KS#l*Ie`X^8bcx(^d@$|Qk z#C%rj8er%_j*&ssPq4u~=)EmEeH7*+B>SMoOG46VG1cQQNR|ny-MMCN=SbK{6Suc@ z)mVCT*S(*m@9I@0_O}nn|2V$>;Hm?4{P#DHJCML{)F|(in}XeDnBgws2AD1DNQ{Wc=$Qg1eB#- zlqVA<-`I||3fS0FmTtYHED47>(*6au5#tAKlQ1@wM_c%4u?^MLJ)H05=-XpYRW)@E z+ZSFt0`UR+hy(F)8iUQnK+vJvEjTkh$aUEzlx`J?A~{T<$HCa49?raVIL~?E3FBN@{%VZoiUJmiTvq4`&=sRf}4~KPv2u zbwH)YnONVL=oR!7r`4FXTivv^Z9OhANP(b~GRr)hAf##&~+`xJriK`o- z)^1ih_`E;m#E#)z`d=-BmSbhL6VQ(>>6d@%mbey=5%tB-2GfLJ_&R6)77(F!>YD8Up(2QN|lSxJ@?YnpC z`&i;z>2&UYtW<3zAs&UBR|Y!#bH#OLNtK)`ZeKhbjea-zfNKuk<1~K==Zi$+uTq1= zuU`u;th?%-+@~P-jrDDgg}K#;aPPjIz00A!@NV<#!KA=}n6KV}Hu&XeO8(lRqI>y${K$j>Q_{~Ggt0tZ>J0m5-T;AOIC+`RiZ;+ zWylEd?%Ta)coB4kPWqi=SUul@BhM`_jj^KQ7S9;jJEF=u8NL98c1DVpDCM_NS$|n` zm3P6~fF8o;H_`ph=5iR2Uak{@kf2ZI$z8H4V+*v$&+%Lm(iRsi%p661{s{Xgx|Dem zPE0whwr+mIntM_x8DN!wO}V3Fb30lZ?`YfOeb@URdYj~&c(Hh*TyG&q_z309it;sA z*pOn;W9>3@twpx>iqo2-t!#Ot{$d?S%5Eo1%0S!Jq9Q5k2Ho5$=HI_iq_l*JtT*HE zaePQqF7aUm7>2k+|04at66!TexUD{3>CPHmCJ`7_LQ1CczMskcfj-;V{F%Yo8lV#d zHPt`hluiB*|MuTZn%GaD!BiE@dd4dk%B7B3BZKkstX6Yi%2sr?fN{c}ne_sHVCL~q zP=&+%mZthA!PcI4@oJjv&x(A5PAGDA6u87XI<+)WWRVQp_sm72!s?Hyn)O*SK z2VqT(=rE>uU{m4k_k#^*-q5$C;=97a$=0OP#UFdH(amnOl{}C4Ki5su+gv($aG62L z@tcyj>XTtI!M2s|9G!mMyizCLn&nfoM6J7wqdgN-#0tD>B{^KA zqKQK?7a?RuQZU5T{O_V!z?>b>-~=GS6M@{s%L*mEhWkWyI3shsc2V$H&m)5*fR@ z_)E$5zn@gbCNDnftu#J%5MOg=^TJ0OR!MK`zL!-UIGQLV7CQqa8vJ>O(}Sa5y1nQG zND2;5KVOz13q&=a7oU?VJDr3#TY@9kdw6;SL}8dph0!_U5~Ml-4u2eUC=YUPaO#KC z6`2d(53M*S6u%Zr4%t`Iz5fI&y1QGYT&Y28xS)^VefCP*1~d{XQX7)XFcC2?*33^# zI4jOHd)bSxAHon7$+l|pxQvIrJsx1;Lp!I<4n+*au$v;UOgwu&S43VjOm>0W0^=&W zdNfW2wR&ZPxZXCI_)uDZW6124*t+Voz5e4L7H5=#SqY86&+mo#*zbyNlCTVd`uHqk z?hu=luTjyDKd;aeFro;)77T#{y?jBrX}a05 znBhaDtFKh$Kk6Vc+Op#YkE!gdY+Br8bvei6=Y9H8D)yhtW}c?xp^6Lj&Y`MzDaykL z=-y1))lFAr#gz~qbdtRo;FO|&{wxR5IeZH1} z`hc5T#k0HchxsRW#5vChxVq1seCbNjuxR!9w2N%ON4-(p?DMXBB?e)>Q;aL^^&%!F zCOmDE#|dGEps)Gf8~-f&p9ra&Kx#@#!5-ME7L+k+Vh)E(sF<#Lzj$!raf7ICvzzB4 zuvS5p<;|kye#J!^$+rhjoW!cdGq5YX!bmYQNGeQyUwlbid7(q3i1=vF9A| zit{k734?Hq*f+~*0pm(A;x_fG_2lh~i(iw*>D`YXEmjlOf6j>fFtxxM(AtULQt1?{ z0E3jS!ZA#vPLw1o_da+5*)I+X`9aV5roKPrx$&n?bL>vozW!#b_P8q)(jnVeS(?~; zFu3OK=Bffdkk9zXd&kSU8j7^rN^mkh1?JnCe*^Z#J<@(8UEw9Ye#!KK#W6)tx0oQz*3pXDCKO@_xxY%-kwTMt0^QjV5v?OgqQA+i;g*>}iyrUfu&Z z*UeN8Fu?V7#mz`CFhduBS^wiA!9G+ak{F2R_$9}9GZ_sS7B~4<CVfX85>VK4jcZosAE;|P2?ondCWH`_U+o=m3DmuN1mN6W5H@MKUV}(=0_}% zEuYA2SpP(U3nlW;nt0dF24)(IZ2&Pcb}(;TcSm&@f6Yij@K#C0h2p^cx#bjz^GSN8+CW#TTPne3Guj_#Rz`P@YU<|Q-RcPmrri# zZ~#`@*O2~^FDYSB38DkTvOQ`6o2sHYOWnv~C>^g~-vQ<_O7rKg5ey%dnCM6H>}Wn2 zc2p0-%&9?ri4s`7!^#w=%*05=0%S_cvyTzXvAH|XK`m{vY>+q! zHpj%&UBP{Q$1)_hP*Pcj%y^+w>Dm*?y1%qhJe#Aa*OI>falENXKXs}6z7_<63JG>d znv?b?Qe7+2aZ7rFyX?kAk90Z@=X868QjP8)Y5&~*`OqY{JZACGb)#Ol^OLRAxKBx9 zLMFGS7m*=Z$LaE#IqUPCqC4Z}yc{LvopZXFAfXbaKRcCHC&Ot-7i070DgLUPwGr}l zBTllrd8wT*t?aeyQ16T)RLd7{=v%5E(IbZO`It!N!op&ovT=zsJ?uorh*Rlq_tZRO zrDH~u(xMkd`C)WzQ%&e@_{6d5m1(+UB^KRqLGy{7**;QMRsRq0pV-RB9P zESdwN2*+3`F$*b=dpP5C%hgSoT4g`ro0N1F?arFgz(wa4p^WOg#~IuKLa#H=x0Jw>uIF{pPQ!@KW?kT!EJbbTf>gn%z4d3Cu@GRA@b2hp&~SpWb4 diff --git a/monai/__init__.py b/monai/__init__.py index dbe0a0aa03..68a232b46d 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,22 +15,24 @@ from ._version import get_versions PY_REQUIRED_MAJOR = 3 -PY_REQUIRED_MINOR = 6 +PY_REQUIRED_MINOR = 7 version_dict = get_versions() __version__: str = version_dict.get("version", "0+unknown") __revision_id__: str = version_dict.get("full-revisionid") del get_versions, version_dict -__copyright__ = "(c) 2020 - 2021 MONAI Consortium" +__copyright__ = "(c) MONAI Consortium" __basedir__ = os.path.dirname(__file__) if sys.version_info.major != PY_REQUIRED_MAJOR or sys.version_info.minor < PY_REQUIRED_MINOR: - raise RuntimeError( - "MONAI requires Python {}.{} or higher. But the current Python is: {}".format( - PY_REQUIRED_MAJOR, PY_REQUIRED_MINOR, sys.version - ) + import warnings + + warnings.warn( + f"MONAI requires Python {PY_REQUIRED_MAJOR}.{PY_REQUIRED_MINOR} or higher. " + f"But the current Python is: {sys.version}", + category=RuntimeWarning, ) from .utils.module import load_submodules # noqa: E402 diff --git a/monai/_extensions/__init__.py b/monai/_extensions/__init__.py index 3718894b7c..fd32d71840 100644 --- a/monai/_extensions/__init__.py +++ b/monai/_extensions/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/_extensions/gmm/gmm.cpp b/monai/_extensions/gmm/gmm.cpp index ecb85e252a..686fddb721 100644 --- a/monai/_extensions/gmm/gmm.cpp +++ b/monai/_extensions/gmm/gmm.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/_extensions/gmm/gmm.h b/monai/_extensions/gmm/gmm.h index 9a43351eb9..6317baa41a 100644 --- a/monai/_extensions/gmm/gmm.h +++ b/monai/_extensions/gmm/gmm.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/_extensions/gmm/gmm_cpu.cpp b/monai/_extensions/gmm/gmm_cpu.cpp index 87ab1c4dab..c9b55490eb 100644 --- a/monai/_extensions/gmm/gmm_cpu.cpp +++ b/monai/_extensions/gmm/gmm_cpu.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/_extensions/gmm/gmm_cuda.cu b/monai/_extensions/gmm/gmm_cuda.cu index 36af48b06c..765ffe5b1c 100644 --- a/monai/_extensions/gmm/gmm_cuda.cu +++ b/monai/_extensions/gmm/gmm_cuda.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/_extensions/gmm/gmm_cuda_linalg.cuh b/monai/_extensions/gmm/gmm_cuda_linalg.cuh index 49e68c8442..9d54d80d3b 100644 --- a/monai/_extensions/gmm/gmm_cuda_linalg.cuh +++ b/monai/_extensions/gmm/gmm_cuda_linalg.cuh @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/_extensions/loader.py b/monai/_extensions/loader.py index d7ebca64e3..2b57302fb0 100644 --- a/monai/_extensions/loader.py +++ b/monai/_extensions/loader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/__init__.py b/monai/apps/__init__.py index 1df6d74f9d..893f7877d2 100644 --- a/monai/apps/__init__.py +++ b/monai/apps/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/datasets.py b/monai/apps/datasets.py index 2b2f48f5d0..8b03393fd6 100644 --- a/monai/apps/datasets.py +++ b/monai/apps/datasets.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -51,6 +51,12 @@ class MedNISTDataset(Randomizable, CacheDataset): will take the minimum of (cache_num, data_length x cache_rate, data_length). num_workers: the number of worker threads to use. if 0 a single thread will be used. Default is 0. + progress: whether to display a progress bar when downloading dataset and computing the transform cache content. + copy_cache: whether to `deepcopy` the cache content before applying the random transforms, + default to `True`. if the random transforms don't modify the cached content + (for example, randomly crop from the cached image and deepcopy the crop region) + or if every cache item is only used once in a `multi-processing` environment, + may set `copy=False` for better performance. Raises: ValueError: When ``root_dir`` is not a directory. @@ -75,6 +81,8 @@ def __init__( cache_num: int = sys.maxsize, cache_rate: float = 1.0, num_workers: int = 0, + progress: bool = True, + copy_cache: bool = True, ) -> None: root_dir = Path(root_dir) if not root_dir.is_dir(): @@ -87,7 +95,14 @@ def __init__( dataset_dir = root_dir / self.dataset_folder_name self.num_class = 0 if download: - download_and_extract(self.resource, tarfile_name, root_dir, self.md5) + download_and_extract( + url=self.resource, + filepath=tarfile_name, + output_dir=root_dir, + hash_val=self.md5, + hash_type="md5", + progress=progress, + ) if not dataset_dir.is_dir(): raise RuntimeError( @@ -97,10 +112,17 @@ def __init__( if transform == (): transform = LoadImaged("image") CacheDataset.__init__( - self, data, transform, cache_num=cache_num, cache_rate=cache_rate, num_workers=num_workers + self, + data=data, + transform=transform, + cache_num=cache_num, + cache_rate=cache_rate, + num_workers=num_workers, + progress=progress, + copy_cache=copy_cache, ) - def randomize(self, data: List[int]) -> None: + def randomize(self, data: np.ndarray) -> None: self.R.shuffle(data) def get_num_classes(self) -> int: @@ -177,6 +199,12 @@ class DecathlonDataset(Randomizable, CacheDataset): will take the minimum of (cache_num, data_length x cache_rate, data_length). num_workers: the number of worker threads to use. if 0 a single thread will be used. Default is 0. + progress: whether to display a progress bar when downloading dataset and computing the transform cache content. + copy_cache: whether to `deepcopy` the cache content before applying the random transforms, + default to `True`. if the random transforms don't modify the cached content + (for example, randomly crop from the cached image and deepcopy the crop region) + or if every cache item is only used once in a `multi-processing` environment, + may set `copy=False` for better performance. Raises: ValueError: When ``root_dir`` is not a directory. @@ -241,6 +269,8 @@ def __init__( cache_num: int = sys.maxsize, cache_rate: float = 1.0, num_workers: int = 0, + progress: bool = True, + copy_cache: bool = True, ) -> None: root_dir = Path(root_dir) if not root_dir.is_dir(): @@ -253,7 +283,14 @@ def __init__( dataset_dir = root_dir / task tarfile_name = f"{dataset_dir}.tar" if download: - download_and_extract(self.resource[task], tarfile_name, root_dir, self.md5[task]) + download_and_extract( + url=self.resource[task], + filepath=tarfile_name, + output_dir=root_dir, + hash_val=self.md5[task], + hash_type="md5", + progress=progress, + ) if not dataset_dir.exists(): raise RuntimeError( @@ -277,7 +314,14 @@ def __init__( if transform == (): transform = LoadImaged(["image", "label"]) CacheDataset.__init__( - self, data, transform, cache_num=cache_num, cache_rate=cache_rate, num_workers=num_workers + self, + data=data, + transform=transform, + cache_num=cache_num, + cache_rate=cache_rate, + num_workers=num_workers, + progress=progress, + copy_cache=copy_cache, ) def get_indices(self) -> np.ndarray: @@ -287,7 +331,7 @@ def get_indices(self) -> np.ndarray: """ return self.indices - def randomize(self, data: List[int]) -> None: + def randomize(self, data: np.ndarray) -> None: self.R.shuffle(data) def get_properties(self, keys: Optional[Union[Sequence[str], str]] = None): diff --git a/monai/apps/deepedit/__init__.py b/monai/apps/deepedit/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/apps/deepedit/__init__.py +++ b/monai/apps/deepedit/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 33054f4a13..abd9e01473 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/deepgrow/__init__.py b/monai/apps/deepgrow/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/apps/deepgrow/__init__.py +++ b/monai/apps/deepgrow/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/deepgrow/dataset.py b/monai/apps/deepgrow/dataset.py index 1dcdc4ec25..763377763a 100644 --- a/monai/apps/deepgrow/dataset.py +++ b/monai/apps/deepgrow/dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/deepgrow/interaction.py b/monai/apps/deepgrow/interaction.py index 81e82c958d..73bc8e7e0b 100644 --- a/monai/apps/deepgrow/interaction.py +++ b/monai/apps/deepgrow/interaction.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,6 +22,7 @@ class Interaction: """ Ignite process_function used to introduce interactions (simulation of clicks) for Deepgrow Training/Evaluation. + For more details please refer to: https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. This implementation is based on: Sakinis et al., Interactive segmentation of medical images through diff --git a/monai/apps/deepgrow/transforms.py b/monai/apps/deepgrow/transforms.py index 4954c1c5fb..d70eb31c99 100644 --- a/monai/apps/deepgrow/transforms.py +++ b/monai/apps/deepgrow/transforms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/mmars/__init__.py b/monai/apps/mmars/__init__.py index 396be2e87d..8f1448bb06 100644 --- a/monai/apps/mmars/__init__.py +++ b/monai/apps/mmars/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/mmars/mmars.py b/monai/apps/mmars/mmars.py index 6356e9a027..801b826bd1 100644 --- a/monai/apps/mmars/mmars.py +++ b/monai/apps/mmars/mmars.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/mmars/model_desc.py b/monai/apps/mmars/model_desc.py index fca6f60da5..ae0e9cae30 100644 --- a/monai/apps/mmars/model_desc.py +++ b/monai/apps/mmars/model_desc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/__init__.py b/monai/apps/pathology/__init__.py index 80f32403ea..81742caf65 100644 --- a/monai/apps/pathology/__init__.py +++ b/monai/apps/pathology/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/data/__init__.py b/monai/apps/pathology/data/__init__.py index 64556b6f6e..e1b2ef7bd2 100644 --- a/monai/apps/pathology/data/__init__.py +++ b/monai/apps/pathology/data/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/data/datasets.py b/monai/apps/pathology/data/datasets.py index c9521b1201..bfab7c49da 100644 --- a/monai/apps/pathology/data/datasets.py +++ b/monai/apps/pathology/data/datasets.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -122,6 +122,10 @@ class SmartCachePatchWSIDataset(SmartCacheDataset): num_replace_workers: the number of worker threads to prepare the replacement cache for every epoch. If num_replace_workers is None then the number returned by os.cpu_count() is used. progress: whether to display a progress bar when caching for the first epoch. + copy_cache: whether to `deepcopy` the cache content before applying the random transforms, + default to `True`. if the random transforms don't modify the cache content + or every cache item is only used once in a `multi-processing` environment, + may set `copy=False` for better performance. """ @@ -139,6 +143,7 @@ def __init__( num_init_workers: Optional[int] = None, num_replace_workers: Optional[int] = None, progress: bool = True, + copy_cache: bool = True, ): patch_wsi_dataset = PatchWSIDataset( data=data, @@ -157,6 +162,7 @@ def __init__( num_replace_workers=num_replace_workers, progress=progress, shuffle=False, + copy_cache=copy_cache, ) diff --git a/monai/apps/pathology/handlers/__init__.py b/monai/apps/pathology/handlers/__init__.py index 3a788ffa26..0638950bd8 100644 --- a/monai/apps/pathology/handlers/__init__.py +++ b/monai/apps/pathology/handlers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/handlers/prob_map_producer.py b/monai/apps/pathology/handlers/prob_map_producer.py index 469e9d3c25..0615b44b8f 100644 --- a/monai/apps/pathology/handlers/prob_map_producer.py +++ b/monai/apps/pathology/handlers/prob_map_producer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/metrics/__init__.py b/monai/apps/pathology/metrics/__init__.py index ad62df524a..f19811dcaf 100644 --- a/monai/apps/pathology/metrics/__init__.py +++ b/monai/apps/pathology/metrics/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/metrics/lesion_froc.py b/monai/apps/pathology/metrics/lesion_froc.py index 1a7f61b921..6073bd0cda 100644 --- a/monai/apps/pathology/metrics/lesion_froc.py +++ b/monai/apps/pathology/metrics/lesion_froc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/__init__.py b/monai/apps/pathology/transforms/__init__.py index b418e20279..290c0ba6a8 100644 --- a/monai/apps/pathology/transforms/__init__.py +++ b/monai/apps/pathology/transforms/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/spatial/__init__.py b/monai/apps/pathology/transforms/spatial/__init__.py index c9971254e7..eed111d2b6 100644 --- a/monai/apps/pathology/transforms/spatial/__init__.py +++ b/monai/apps/pathology/transforms/spatial/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/spatial/array.py b/monai/apps/pathology/transforms/spatial/array.py index 47c95ed814..fe1383c08d 100644 --- a/monai/apps/pathology/transforms/spatial/array.py +++ b/monai/apps/pathology/transforms/spatial/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -60,7 +60,7 @@ def __call__(self, image: NdarrayOrTensor) -> NdarrayOrTensor: if isinstance(image, torch.Tensor): return torch.stack([image]) elif isinstance(image, np.ndarray): - return np.stack([image]) + return np.stack([image]) # type: ignore else: raise ValueError(f"Input type [{type(image)}] is not supported.") diff --git a/monai/apps/pathology/transforms/spatial/dictionary.py b/monai/apps/pathology/transforms/spatial/dictionary.py index aae98e7c8d..d5c34a0840 100644 --- a/monai/apps/pathology/transforms/spatial/dictionary.py +++ b/monai/apps/pathology/transforms/spatial/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/stain/__init__.py b/monai/apps/pathology/transforms/stain/__init__.py index 824f40a579..dfa235de55 100644 --- a/monai/apps/pathology/transforms/stain/__init__.py +++ b/monai/apps/pathology/transforms/stain/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/stain/array.py b/monai/apps/pathology/transforms/stain/array.py index 3a03777299..3b3a293451 100644 --- a/monai/apps/pathology/transforms/stain/array.py +++ b/monai/apps/pathology/transforms/stain/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/transforms/stain/dictionary.py b/monai/apps/pathology/transforms/stain/dictionary.py index 976af1e7c7..eb8eba43f8 100644 --- a/monai/apps/pathology/transforms/stain/dictionary.py +++ b/monai/apps/pathology/transforms/stain/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/pathology/utils.py b/monai/apps/pathology/utils.py index 30bdde91bb..5a57364a11 100644 --- a/monai/apps/pathology/utils.py +++ b/monai/apps/pathology/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/apps/utils.py b/monai/apps/utils.py index f1619b9964..9db62f336d 100644 --- a/monai/apps/utils.py +++ b/monai/apps/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -21,6 +21,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Optional from urllib.error import ContentTooShortError, HTTPError, URLError +from urllib.parse import urlparse from urllib.request import urlretrieve from monai.config.type_definitions import PathLike @@ -185,7 +186,7 @@ def download_url( with tempfile.TemporaryDirectory() as tmp_dir: tmp_name = Path(tmp_dir, _basename(filepath)) - if url.startswith("https://drive.google.com"): + if urlparse(url).netloc == "drive.google.com": if not has_gdown: raise RuntimeError("To download files from Google Drive, please install the gdown dependency.") gdown.download(url, f"{tmp_name}", quiet=not progress) diff --git a/monai/config/__init__.py b/monai/config/__init__.py index e3f623823c..bf1b66fe92 100644 --- a/monai/config/__init__.py +++ b/monai/config/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,7 +12,9 @@ from .deviceconfig import ( USE_COMPILED, IgniteInfo, + get_config_values, get_gpu_info, + get_optional_config_values, get_system_info, print_config, print_debug_info, diff --git a/monai/config/deviceconfig.py b/monai/config/deviceconfig.py index e542da14ab..91b944bde5 100644 --- a/monai/config/deviceconfig.py +++ b/monai/config/deviceconfig.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -90,6 +90,7 @@ def print_config(file=sys.stdout): print(f"{k} version: {v}", file=file, flush=True) print(f"MONAI flags: HAS_EXT = {HAS_EXT}, USE_COMPILED = {USE_COMPILED}") print(f"MONAI rev id: {monai.__revision_id__}") + print(f"MONAI __file__: {monai.__file__}") print("\nOptional dependencies:", file=file, flush=True) for k, v in get_optional_config_values().items(): @@ -200,8 +201,7 @@ def get_gpu_info() -> OrderedDict: if num_gpus > 0: _dict_append(output, "Current device", torch.cuda.current_device) - if hasattr(torch.cuda, "get_arch_list"): # get_arch_list is new in torch 1.7.1 - _dict_append(output, "Library compiled for CUDA architectures", torch.cuda.get_arch_list) + _dict_append(output, "Library compiled for CUDA architectures", torch.cuda.get_arch_list) for gpu in range(num_gpus): gpu_info = torch.cuda.get_device_properties(gpu) diff --git a/monai/config/type_definitions.py b/monai/config/type_definitions.py index f5f5fc2626..686befb2eb 100644 --- a/monai/config/type_definitions.py +++ b/monai/config/type_definitions.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -60,8 +60,8 @@ # container must be iterable. IndexSelection = Union[Iterable[int], int] -#: Type of datatypes: Adapted from https://github.com/numpy/numpy/blob/master/numpy/typing/_dtype_like.py -DtypeLike = Union[np.dtype, type, None] +#: Type of datatypes: Adapted from https://github.com/numpy/numpy/blob/v1.21.4/numpy/typing/_dtype_like.py#L121 +DtypeLike = Union[np.dtype, type, str, None] #: NdarrayTensor # diff --git a/monai/csrc/ext.cpp b/monai/csrc/ext.cpp index b4bb0f2c04..a2fa8bfc56 100644 --- a/monai/csrc/ext.cpp +++ b/monai/csrc/ext.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/bilateral/bilateral.cpp b/monai/csrc/filtering/bilateral/bilateral.cpp index 2720d312e2..183e13bb23 100644 --- a/monai/csrc/filtering/bilateral/bilateral.cpp +++ b/monai/csrc/filtering/bilateral/bilateral.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/bilateral/bilateral.h b/monai/csrc/filtering/bilateral/bilateral.h index c7a68d7457..288684666a 100644 --- a/monai/csrc/filtering/bilateral/bilateral.h +++ b/monai/csrc/filtering/bilateral/bilateral.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/bilateral/bilateralfilter_cpu.cpp b/monai/csrc/filtering/bilateral/bilateralfilter_cpu.cpp index 2e6c7dbe20..51573ebbc0 100644 --- a/monai/csrc/filtering/bilateral/bilateralfilter_cpu.cpp +++ b/monai/csrc/filtering/bilateral/bilateralfilter_cpu.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/bilateral/bilateralfilter_cpu_phl.cpp b/monai/csrc/filtering/bilateral/bilateralfilter_cpu_phl.cpp index 847a452396..ec28c05520 100644 --- a/monai/csrc/filtering/bilateral/bilateralfilter_cpu_phl.cpp +++ b/monai/csrc/filtering/bilateral/bilateralfilter_cpu_phl.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -57,7 +57,7 @@ void BilateralFilterPHLCpu( int coord = offsetRemainder / desc.strides[d]; offsetRemainder -= coord * desc.strides[d]; - features[i * featureChannels + desc.channelCount + d] = invSpatialSigma * coord; + features[i * featureChannels + desc.channelCount + d] = (scalar_t)invSpatialSigma * coord; } } diff --git a/monai/csrc/filtering/bilateral/bilateralfilter_cuda.cu b/monai/csrc/filtering/bilateral/bilateralfilter_cuda.cu index f73ae19ac9..a24e6ed092 100644 --- a/monai/csrc/filtering/bilateral/bilateralfilter_cuda.cu +++ b/monai/csrc/filtering/bilateral/bilateralfilter_cuda.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/bilateral/bilateralfilter_cuda_phl.cu b/monai/csrc/filtering/bilateral/bilateralfilter_cuda_phl.cu index 719d1643d3..20df9419fa 100644 --- a/monai/csrc/filtering/bilateral/bilateralfilter_cuda_phl.cu +++ b/monai/csrc/filtering/bilateral/bilateralfilter_cuda_phl.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/filtering.h b/monai/csrc/filtering/filtering.h index 3dcdfc473b..3e680010ed 100644 --- a/monai/csrc/filtering/filtering.h +++ b/monai/csrc/filtering/filtering.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/permutohedral/hash_table.cuh b/monai/csrc/filtering/permutohedral/hash_table.cuh index 1acff5f276..5507034dea 100644 --- a/monai/csrc/filtering/permutohedral/hash_table.cuh +++ b/monai/csrc/filtering/permutohedral/hash_table.cuh @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/permutohedral/permutohedral.cpp b/monai/csrc/filtering/permutohedral/permutohedral.cpp index d8fd3eaaeb..6ffe966f2c 100644 --- a/monai/csrc/filtering/permutohedral/permutohedral.cpp +++ b/monai/csrc/filtering/permutohedral/permutohedral.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/permutohedral/permutohedral.h b/monai/csrc/filtering/permutohedral/permutohedral.h index 1c9d1a031e..cdc0f693d0 100644 --- a/monai/csrc/filtering/permutohedral/permutohedral.h +++ b/monai/csrc/filtering/permutohedral/permutohedral.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/permutohedral/permutohedral_cpu.cpp b/monai/csrc/filtering/permutohedral/permutohedral_cpu.cpp index 8c0dc8e546..bc4b2cabc7 100644 --- a/monai/csrc/filtering/permutohedral/permutohedral_cpu.cpp +++ b/monai/csrc/filtering/permutohedral/permutohedral_cpu.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/filtering/permutohedral/permutohedral_cuda.cu b/monai/csrc/filtering/permutohedral/permutohedral_cuda.cu index d1d78eb940..fa06590fa9 100644 --- a/monai/csrc/filtering/permutohedral/permutohedral_cuda.cu +++ b/monai/csrc/filtering/permutohedral/permutohedral_cuda.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/lltm/lltm.h b/monai/csrc/lltm/lltm.h index 33e17416f8..398ea92bb9 100644 --- a/monai/csrc/lltm/lltm.h +++ b/monai/csrc/lltm/lltm.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/lltm/lltm_cpu.cpp b/monai/csrc/lltm/lltm_cpu.cpp index 295c592d00..7cd2251f9f 100644 --- a/monai/csrc/lltm/lltm_cpu.cpp +++ b/monai/csrc/lltm/lltm_cpu.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/lltm/lltm_cuda.cu b/monai/csrc/lltm/lltm_cuda.cu index 4633348477..1293bec7e9 100644 --- a/monai/csrc/lltm/lltm_cuda.cu +++ b/monai/csrc/lltm/lltm_cuda.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/resample/bounds_common.h b/monai/csrc/resample/bounds_common.h index 4997c7d968..2a7778b76b 100644 --- a/monai/csrc/resample/bounds_common.h +++ b/monai/csrc/resample/bounds_common.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/resample/interpolation_common.h b/monai/csrc/resample/interpolation_common.h index 35899298bf..8e2edbc8b7 100644 --- a/monai/csrc/resample/interpolation_common.h +++ b/monai/csrc/resample/interpolation_common.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/resample/pushpull.h b/monai/csrc/resample/pushpull.h index 1c20cc0114..b056bb77c2 100644 --- a/monai/csrc/resample/pushpull.h +++ b/monai/csrc/resample/pushpull.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/resample/pushpull_cpu.cpp b/monai/csrc/resample/pushpull_cpu.cpp index dd10dd76ee..d83557c6c3 100644 --- a/monai/csrc/resample/pushpull_cpu.cpp +++ b/monai/csrc/resample/pushpull_cpu.cpp @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/resample/pushpull_cuda.cu b/monai/csrc/resample/pushpull_cuda.cu index 38d34ffe98..4a2d6c27ef 100644 --- a/monai/csrc/resample/pushpull_cuda.cu +++ b/monai/csrc/resample/pushpull_cuda.cu @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/utils/common_utils.h b/monai/csrc/utils/common_utils.h index 4d09377e65..aabea3c99f 100644 --- a/monai/csrc/utils/common_utils.h +++ b/monai/csrc/utils/common_utils.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/utils/meta_macros.h b/monai/csrc/utils/meta_macros.h index 980b253bbe..0f15b623e3 100644 --- a/monai/csrc/utils/meta_macros.h +++ b/monai/csrc/utils/meta_macros.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/utils/resample_utils.h b/monai/csrc/utils/resample_utils.h index bbdf258b4c..77df65e924 100644 --- a/monai/csrc/utils/resample_utils.h +++ b/monai/csrc/utils/resample_utils.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/csrc/utils/tensor_description.h b/monai/csrc/utils/tensor_description.h index dadd26c5f5..c604003b9d 100644 --- a/monai/csrc/utils/tensor_description.h +++ b/monai/csrc/utils/tensor_description.h @@ -1,5 +1,5 @@ /* -Copyright 2020 - 2021 MONAI Consortium +Copyright (c) MONAI Consortium Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/monai/data/__init__.py b/monai/data/__init__.py index e7fa2b3107..ae0bb86c35 100644 --- a/monai/data/__init__.py +++ b/monai/data/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,6 +17,7 @@ CacheNTransDataset, CSVDataset, Dataset, + DatasetFunc, LMDBDataset, NPZDictItemDataset, PersistentDataset, @@ -42,6 +43,7 @@ from .synthetic import create_test_image_2d, create_test_image_3d from .test_time_augmentation import TestTimeAugmentation from .thread_buffer import ThreadBuffer, ThreadDataLoader +from .torchscript_utils import load_net_with_metadata, save_net_with_metadata from .utils import ( compute_importance_map, compute_shape_offset, diff --git a/monai/data/box_utils.py b/monai/data/box_utils.py new file mode 100644 index 0000000000..daca40ef85 --- /dev/null +++ b/monai/data/box_utils.py @@ -0,0 +1,383 @@ +# Copyright 2020 - 2021 MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from copy import deepcopy +from typing import Sequence, Union + +import numpy as np +import torch + +import point_utils + +SUPPORT_MODE = ["xxyy", "xxyyzz", "xyxy", "xyzxyz", "xywh", "xyzwhd"] +STANDARD_MODE = ["xxyy", "xxyyzz"] # [2d_mode, 3d_mode] + +# TO_REMOVE = 0 if in 'xxyy','xxyyzz' mode, the bottom-right corner is not included in the box, +# i.e., when x_min=1, x_max=2, we have w = 1 +# if in 'xxyy','xxyyzz' mode, the bottom-right corner is included in the box, +# i.e., when x_min=1, x_max=2, we have w = 2 +TO_REMOVE = 0 # x_max-x_min = w -TO_REMOVE + +""" +The following variables share the same definition across the functions in this file. +Args: + bbox: Nx4 or Nx6 torch tensor + mode: choose from SUPPORT_MODE. If mode is not given, these funcs will assume mode is STANDARD_MODE + image_size: Length of 2 or 3. Data format is list, or np.ndarray, or tensor of int +""" + + +def check_support_mode(mode): + """ + Check if the mode is supported + """ + if mode not in SUPPORT_MODE: + raise ValueError("mode should be a string in {}.".format(SUPPORT_MODE)) + return + + +def check_standard_mode(mode): + """ + Check if the mode is supported + """ + if mode not in STANDARD_MODE: + raise ValueError("Standard mode should be a string in {}.".format(STANDARD_MODE)) + return + + +def convert_to_list(in_sequence: Union[Sequence, torch.Tensor, np.ndarray]) -> list: + """ + convert a torch.Tensor, or np array input to list + Args: + in_sequence: + Returns: in_sequence_list + + """ + in_sequence_list = deepcopy(in_sequence) + if torch.is_tensor(in_sequence): + in_sequence_list.cpu().detach().numpy().tolist() + elif isinstance(in_sequence, np.ndarray): + in_sequence_list.tolist() + elif not isinstance(in_sequence, list): + in_sequence_list = list(in_sequence_list) + return in_sequence_list + + +def get_dimension( + bbox: torch.Tensor = None, image_size: Union[Sequence[int], torch.Tensor, np.ndarray] = None, mode: str = None +) -> int: + """ + Get spatial dimension for the giving setting. + Missing input is allowed. But at least one of the input value should be given. + Returns: spatial_dimension + """ + spatial_dims = set() + if image_size is not None: + spatial_dims.add(len(image_size)) + if mode is not None: + spatial_dims.add(len(mode) / 2) + if bbox is not None: + spatial_dims.add(int(bbox.shape[1] / 2)) + spatial_dims = list(spatial_dims) + if len(spatial_dims) == 0: + raise ValueError("At least one of bbox, image_size, and mode needs to be non-empty.") + elif len(spatial_dims) == 1: + if spatial_dims[0] not in [2, 3]: + raise ValueError("Images should have 2 or 3 dimensions, got {}".format(spatial_dims[0])) + return int(spatial_dims[0]) + else: + raise ValueError("The dimension of bbox, image_size, mode should match with each other.") + + +def get_standard_mode(spatial_dims: int) -> str: + """ + Get the mode name for the given spatial dimension + Args: + spatial_dims: 2 or 3 + + Returns: mode + + """ + if spatial_dims == 2: + return STANDARD_MODE[0] + elif spatial_dims == 3: + return STANDARD_MODE[1] + else: + ValueError("Images should have 2 or 3 dimensions, got {}".format(spatial_dims)) + + +def point_interp( + point1: Union[Sequence, torch.Tensor, np.ndarray], zoom: Union[Sequence[float], float] +) -> Union[Sequence, torch.Tensor, np.ndarray]: + """ + Convert point position from one pixel/voxel size to another pixel/voxel size + Args: + point1: point coordinate on an image with pixel/voxel size of pix_size1 + zoom: The zoom factor along the spatial axes. + If a float, zoom is the same for each spatial axis. + If a sequence, zoom should contain one value for each spatial axis. + Returns: + point2: point coordinate on an image with pixel/voxel size of pix_size2 + """ + # make sure the spatial dimensions of the inputs match with each other + spatial_dims = len(point1) + if spatial_dims not in [2, 3]: + raise ValueError("Images should have 2 or 3 dimensions, got {}".format(spatial_dims)) + + # compute new point + point2 = deepcopy(point1) + _zoom = monai.utils.misc.ensure_tuple_rep(zoom, spatial_dims) + for axis in range(0, spatial_dims): + point2[axis] = point1[axis] * _zoom[axis] + return point2 + + +def box_interp(bbox1: torch.Tensor, zoom: Union[Sequence[float], float], mode1: str = None) -> torch.Tensor: + """ + Interpolate bbox + Args: + zoom: The zoom factor along the spatial axes. + If a float, zoom is the same for each spatial axis. + If a sequence, zoom should contain one value for each spatial axis. + + Returns: + bbox2: returned bbox has the same mode as bbox1 + """ + if mode1 is None: + mode1 = get_standard_mode(int(bbox1.shape[1] / 2)) + check_support_mode(mode1) + spatial_dims = get_dimension(bbox=bbox1, mode=mode1) + + mode_standard = get_standard_mode(spatial_dims) + bbox1_standard = box_convert_mode(bbox1=bbox1, mode1=mode1, mode2=mode_standard) + + corner_lt = point_utils.point_interp(bbox1_standard[:, ::2], zoom) + corner_rb = point_utils.point_interp(bbox1_standard[:, 1::2], zoom) + + bbox2_standard_interp = deepcopy(bbox2_standard) + bbox2_standard_interp[:, ::2] = corner_lt + bbox2_standard_interp[:, 1::2] = corner_rb + + return box_convert_mode(bbox1=bbox2_standard_interp, mode1=mode_standard, mode2=mode1) + + +def split_into_corners(bbox: torch.Tensor, mode: str): + """ + This internal function outputs the corner coordinates of the bbox + + Returns: + if 2D image, outputs (xmin, xmax, ymin, ymax) + if 3D images, outputs (xmin, xmax, ymin, ymax, zmin, zmax) + xmin for example, is a Nx1 tensor + + """ + check_support_mode(mode) + if mode in STANDARD_MODE: + return bbox.split(1, dim=-1) + elif mode == "xyzxyz": + xmin, ymin, zmin, xmax, ymax, zmax = bbox.split(1, dim=-1) + return ( + xmin, + xmax, + ymin, + ymax, + zmin, + zmax, + ) + elif mode == "xyxy": + xmin, ymin, xmax, ymax = bbox.split(1, dim=-1) + return (xmin, xmax, ymin, ymax) + elif mode == "xyzwhd": + xmin, ymin, zmin, w, h, d = bbox.split(1, dim=-1) + return ( + xmin, + xmin + (w - TO_REMOVE).clamp(min=0), + ymin, + ymin + (h - TO_REMOVE).clamp(min=0), + zmin, + zmin + (d - TO_REMOVE).clamp(min=0), + ) + elif mode == "xywh": + xmin, ymin, w, h = bbox.split(1, dim=-1) + return (xmin, xmin + (w - TO_REMOVE).clamp(min=0), ymin, ymin + (h - TO_REMOVE).clamp(min=0)) + else: + raise RuntimeError("Should not be here") + + +def box_convert_mode(bbox1: torch.Tensor, mode1: str, mode2: str) -> torch.Tensor: + """ + This function converts the bbox1 in mode 1 to the mode2 + """ + # 1. check whether the bbox and the new mode is valid + check_support_mode(mode1) + check_support_mode(mode2) + + spatial_dims = get_dimension(bbox=bbox1, mode=mode1) + if len(mode1) != len(mode2): + raise ValueError("The dimension of the new mode should have the same spatial dimension as the old mode.") + + # 2. if mode not changed, return original boxlist + if mode1 == mode2: + return deepcopy(bbox1) + + # 3. convert mode for bbox + if mode2 in STANDARD_MODE: + corners = split_into_corners(deepcopy(bbox1), mode1) + return torch.cat(corners, dim=-1) + + if spatial_dims == 3: + xmin, xmax, ymin, ymax, zmin, zmax = split_into_corners(deepcopy(bbox1), mode1) + if mode2 == "xyzxyz": + bbox2 = torch.cat((xmin, ymin, zmin, xmax, ymax, zmax), dim=-1) + elif mode2 == "xyzwhd": + bbox2 = torch.cat( + (xmin, ymin, zmin, xmax - xmin + TO_REMOVE, ymax - ymin + TO_REMOVE, zmax - zmin + TO_REMOVE), dim=-1 + ) + else: + raise ValueError("We support only bbox mode in " + str(SUPPORT_MODE) + ", got {}".format(mode2)) + elif spatial_dims == 2: + xmin, xmax, ymin, ymax = split_into_corners(deepcopy(bbox1), mode1) + if mode2 == "xyxy": + bbox2 = torch.cat((xmin, ymin, xmax, ymax), dim=-1) + elif mode2 == "xywh": + bbox2 = torch.cat((xmin, ymin, xmax - xmin + TO_REMOVE, ymax - ymin + TO_REMOVE), dim=-1) + else: + raise ValueError("We support only bbox mode in " + str(SUPPORT_MODE) + ", got {}".format(mode2)) + else: + raise ValueError("Images should have 2 or 3 dimensions, got {}".format(spatial_dims)) + + return bbox2 + + +def box_convert_standard_mode(bbox: torch.Tensor, mode: str) -> torch.Tensor: + """ + This function convert the bbox in mode 1 to 'xyxy' or 'xyzxyz' + """ + check_support_mode(mode) + spatial_dims = get_dimension(bbox=bbox, mode=mode) + mode_standard = get_standard_mode(spatial_dims) + return box_convert_mode(bbox1=bbox, mode1=mode, mode2=mode_standard) + + +def box_area(bbox: torch.Tensor, mode: str = None) -> torch.tensor: + """ + This function computes the area of each box + Returns: + area: 1-D tensor + """ + + if mode is None: + mode = get_standard_mode(int(bbox.shape[1] / 2)) + check_standard_mode(mode) + spatial_dims = get_dimension(bbox=bbox, mode=mode) + + area = bbox[:, 1] - bbox[:, 0] + TO_REMOVE + for axis in range(1, spatial_dims): + area = area * (bbox[:, 2 * axis + 1] - bbox[:, 2 * axis] + TO_REMOVE) + + return area + + +def box_clip_to_image( + bbox: torch.Tensor, + image_size: Union[Sequence[int], torch.Tensor, np.ndarray], + mode: str = None, + remove_empty: bool = True, +) -> dict: + """ + This function makes sure the bounding boxes are within the image. + Args: + remove_empty: whether to remove the boxes that are actually empty + Returns: + updated box + """ + if mode is None: + mode = get_standard_mode(int(bbox.shape[1] / 2)) + check_standard_mode(mode) + spatial_dims = get_dimension(bbox=bbox, image_size=image_size, mode=mode) + new_bbox = deepcopy(bbox) + if bbox.shape[0] == 0: + return deepcopy(bbox) + + # 1. convert to standard mode + mode_standard = get_standard_mode(spatial_dims) + new_bbox = box_convert_mode(bbox1=new_bbox, mode1=mode, mode2=mode_standard) + + # 2. makes sure the bounding boxes are within the image + for axis in range(0, spatial_dims): + new_bbox[:, 2 * axis].clamp_(min=0, max=image_size[axis] - TO_REMOVE) + new_bbox[:, 2 * axis + 1].clamp_(min=0, max=image_size[axis] - TO_REMOVE) + + # 3. remove the boxes that are actually empty + if remove_empty: + keep = (new_bbox[:, 1] > new_bbox[:, 0]) & (new_bbox[:, 3] > new_bbox[:, 2]) + if spatial_dims == 3: + keep = keep & (new_bbox[:, 5] > new_bbox[:, 4]) + new_bbox = new_bbox[keep] + + # 4. return updated boxlist + new_bbox = box_convert_mode(bbox1=new_bbox, mode1=mode_standard, mode2=mode) + + return new_bbox + + +def box_iou(bbox1: torch.Tensor, bbox2: torch.Tensor, mode1: str = None, mode2: str = None, gpubool: bool = True): + """ + Compute the intersection over union of two set of boxes. This function is not differentialable. + + IMPORTANT: Please run box_clip_to_image(bbox, image_size, mode, remove_empty=True) before computing IoU + + Implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py + with slight modifications. + + Arguments: + bbox1: Nx4 or Nx6, make sure they are non-empty + bbox2: Mx4 or Mx6, make sure they are non-empty + gpubool: whether to send the final IoU results to GPU + + Returns: + (tensor) iou, sized [N,M]. + + Reference: + https://github.com/chainer/chainercv/blob/master/chainercv/utils/bbox/bbox_iou.py + """ + + if mode1 is None: + mode1 = get_standard_mode(int(bbox1.shape[1] / 2)) + if mode2 is None: + mode2 = get_standard_mode(int(bbox2.shape[1] / 2)) + check_standard_mode(mode1) + check_standard_mode(mode2) + spatial_dims = get_dimension(bbox=bbox1, mode=mode1) + + # we do computation on cpu + device = bbox1.device + + # compute area for the bbox + area1 = box_area(bbox=bbox1, mode=mode1).cpu() # Nx1 + area2 = box_area(bbox=bbox2, mode=mode2).cpu() # Mx1 + + # get the left top and right bottom points for the NxM combinations + lt = torch.max(bbox1[:, None, ::2], bbox2[:, ::2]) # [N,M,spatial_dims] left top + rb = torch.min(bbox1_corner[:, None, 1::2], bbox2_corner[:, 1::2]) # [N,M,spatial_dims] right bottom + # compute size for the intersection region for the NxM combinations + wh = (rb - lt + TO_REMOVE).clamp(min=0) # [N,M,spatial_dims] + inter = wh[:, :, 0] # [N,M] + for axis in range(1, spatial_dims): + inter = inter * wh[:, :, axis] + + # compute IoU + iou = inter / (area1[:, None] + area2 - inter + torch.finfo(torch.float32).eps) # [N,M,spatial_dims] + + if gpubool: + iou = iou.to(device) # [N,M,spatial_dims] + + return iou diff --git a/monai/data/csv_saver.py b/monai/data/csv_saver.py index d4005b528e..d4ca0e7576 100644 --- a/monai/data/csv_saver.py +++ b/monai/data/csv_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/dataloader.py b/monai/data/dataloader.py index bfb6c01c4e..3117a27c02 100644 --- a/monai/data/dataloader.py +++ b/monai/data/dataloader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/dataset.py b/monai/data/dataset.py index f1b481d598..cbb534f04a 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -32,7 +32,7 @@ from monai.data.utils import SUPPORTED_PICKLE_MOD, convert_tables_to_dicts, pickle_hashing from monai.transforms import Compose, Randomizable, ThreadUnsafe, Transform, apply_transform -from monai.utils import MAX_SEED, ensure_tuple, get_seed, look_up_option, min_version, optional_import +from monai.utils import MAX_SEED, deprecated_arg, get_seed, look_up_option, min_version, optional_import from monai.utils.misc import first if TYPE_CHECKING: @@ -97,6 +97,56 @@ def __getitem__(self, index: Union[int, slice, Sequence[int]]): return self._transform(index) +class DatasetFunc(Dataset): + """ + Execute function on the input dataset and leverage the output to act as a new Dataset. + It can be used to load / fetch the basic dataset items, like the list of `image, label` paths. + Or chain together to execute more complicated logic, like `partition_dataset`, `resample_datalist`, etc. + The `data` arg of `Dataset` will be applied to the first arg of callable `func`. + Usage example:: + + data_list = DatasetFunc( + data="path to file", + func=monai.data.load_decathlon_datalist, + data_list_key="validation", + base_dir="path to base dir", + ) + # partition dataset for every rank + data_partition = DatasetFunc( + data=data_list, + func=lambda **kwargs: monai.data.partition_dataset(**kwargs)[torch.distributed.get_rank()], + num_partitions=torch.distributed.get_world_size(), + ) + dataset = Dataset(data=data_partition, transform=transforms) + + Args: + data: input data for the func to process, will apply to `func` as the first arg. + func: callable function to generate dataset items. + kwargs: other arguments for the `func` except for the first arg. + + """ + + def __init__(self, data: Any, func: Callable, **kwargs) -> None: + super().__init__(data=None, transform=None) # type:ignore + self.src = data + self.func = func + self.kwargs = kwargs + self.reset() + + def reset(self, data: Optional[Any] = None, func: Optional[Callable] = None, **kwargs): + """ + Reset the dataset items with specified `func`. + + Args: + data: if not None, execute `func` on it, default to `self.src`. + func: if not None, execute the `func` with specified `kwargs`, default to `self.func`. + kwargs: other arguments for the `func` except for the first arg. + + """ + src = self.src if data is None else data + self.data = self.func(src, **self.kwargs) if func is None else func(src, **kwargs) + + class PersistentDataset(Dataset): """ Persistent storage of pre-computed values to efficiently manage larger than memory dictionary format data, @@ -1222,8 +1272,9 @@ class CSVDataset(Dataset): ] Args: - filename: the filename of expected CSV file to load. if providing a list - of filenames, it will load all the files and join tables. + src: if provided the filename of CSV file, it can be a str, URL, path object or file-like object to load. + also support to provide pandas `DataFrame` directly, will skip loading from filename. + if provided a list of filenames or pandas `DataFrame`, it will join the tables. row_indices: indices of the expected rows to load. it should be a list, every item can be a int number or a range `[start, end)` for the indices. for example: `row_indices=[[0, 100], 200, 201, 202, 300]`. if None, @@ -1249,11 +1300,15 @@ class CSVDataset(Dataset): transform: transform to apply on the loaded items of a dictionary data. kwargs: additional arguments for `pandas.merge()` API to join tables. + .. deprecated:: 0.8.0 + ``filename`` is deprecated, use ``src`` instead. + """ + @deprecated_arg(name="filename", new_name="src", since="0.8", msg_suffix="please use `src` instead.") def __init__( self, - filename: Union[str, Sequence[str]], + src: Optional[Union[str, Sequence[str]]] = None, # also can be `DataFrame` or sequense of `DataFrame` row_indices: Optional[Sequence[Union[int, str]]] = None, col_names: Optional[Sequence[str]] = None, col_types: Optional[Dict[str, Optional[Dict[str, Any]]]] = None, @@ -1261,8 +1316,19 @@ def __init__( transform: Optional[Callable] = None, **kwargs, ): - files = ensure_tuple(filename) - dfs = [pd.read_csv(f) for f in files] + srcs = (src,) if not isinstance(src, (tuple, list)) else src + dfs: List = [] + for i in srcs: + if isinstance(i, str): + dfs.append(pd.read_csv(i)) + elif isinstance(i, pd.DataFrame): + dfs.append(i) + else: + raise ValueError("`src` must be file path or pandas `DataFrame`.") + + # in case treating deprecated arg `filename` as kwargs, remove it from `kwargs` + kwargs.pop("filename", None) + data = convert_tables_to_dicts( dfs=dfs, row_indices=row_indices, col_names=col_names, col_types=col_types, col_groups=col_groups, **kwargs ) diff --git a/monai/data/dataset_summary.py b/monai/data/dataset_summary.py index dfc22f9bc8..dd8a94143b 100644 --- a/monai/data/dataset_summary.py +++ b/monai/data/dataset_summary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,8 +15,11 @@ import numpy as np import torch +from monai.config import KeysCollection from monai.data.dataloader import DataLoader from monai.data.dataset import Dataset +from monai.transforms import concatenate +from monai.utils import convert_data_type class DatasetSummary: @@ -38,6 +41,7 @@ def __init__( dataset: Dataset, image_key: Optional[str] = "image", label_key: Optional[str] = "label", + meta_key: Optional[KeysCollection] = None, meta_key_postfix: str = "meta_dict", num_workers: int = 0, **kwargs, @@ -47,11 +51,16 @@ def __init__( dataset: dataset from which to load the data. image_key: key name of images (default: ``image``). label_key: key name of labels (default: ``label``). + meta_key: explicitly indicate the key of the corresponding meta data dictionary. + for example, for data with key `image`, the metadata by default is in `image_meta_dict`. + the meta data is a dictionary object which contains: filename, affine, original_shape, etc. + if None, will try to construct meta_keys by `{image_key}_{meta_key_postfix}`. meta_key_postfix: use `{image_key}_{meta_key_postfix}` to fetch the meta data from dict, the meta data is a dictionary object (default: ``meta_dict``). num_workers: how many subprocesses to use for data loading. ``0`` means that the data will be loaded in the main process (default: ``0``). - kwargs: other parameters (except batch_size) for DataLoader (this class forces to use ``batch_size=1``). + kwargs: other parameters (except `batch_size` and `num_workers`) for DataLoader, + this class forces to use ``batch_size=1``. """ @@ -59,18 +68,17 @@ def __init__( self.image_key = image_key self.label_key = label_key - if image_key: - self.meta_key = f"{image_key}_{meta_key_postfix}" + self.meta_key = meta_key or f"{image_key}_{meta_key_postfix}" self.all_meta_data: List = [] def collect_meta_data(self): """ This function is used to collect the meta data for all images of the dataset. """ - if not self.meta_key: - raise ValueError("To collect meta data for the dataset, `meta_key` should exist.") for data in self.data_loader: + if self.meta_key not in data: + raise ValueError(f"To collect meta data for the dataset, key `{self.meta_key}` must exist in `data`.") self.all_meta_data.append(data[self.meta_key]) def get_target_spacing(self, spacing_key: str = "pixdim", anisotropic_threshold: int = 3, percentile: float = 10.0): @@ -78,8 +86,8 @@ def get_target_spacing(self, spacing_key: str = "pixdim", anisotropic_threshold: Calculate the target spacing according to all spacings. If the target spacing is very anisotropic, decrease the spacing value of the maximum axis according to percentile. - So far, this function only supports NIFTI images which store spacings in headers with key "pixdim". After loading - with `monai.DataLoader`, "pixdim" is in the form of `torch.Tensor` with size `(batch_size, 8)`. + So far, this function only supports NIFTI images which store spacings in headers with key "pixdim". + After loading with `monai.DataLoader`, "pixdim" is in the form of `torch.Tensor` with size `(batch_size, 8)`. Args: spacing_key: key of spacing in meta data (default: ``pixdim``). @@ -92,8 +100,8 @@ def get_target_spacing(self, spacing_key: str = "pixdim", anisotropic_threshold: self.collect_meta_data() if spacing_key not in self.all_meta_data[0]: raise ValueError("The provided spacing_key is not in self.all_meta_data.") - - all_spacings = torch.cat([data[spacing_key][:, 1:4] for data in self.all_meta_data], dim=0).numpy() + all_spacings = concatenate(to_cat=[data[spacing_key][:, 1:4] for data in self.all_meta_data], axis=0) + all_spacings, *_ = convert_data_type(data=all_spacings, output_type=np.ndarray, wrap_sequence=True) target_spacing = np.median(all_spacings, axis=0) if max(target_spacing) / min(target_spacing) >= anisotropic_threshold: @@ -126,6 +134,8 @@ def calculate_statistics(self, foreground_threshold: int = 0): image, label = data[self.image_key], data[self.label_key] else: image, label = data + image, *_ = convert_data_type(data=image, output_type=torch.Tensor) + label, *_ = convert_data_type(data=label, output_type=torch.Tensor) voxel_max.append(image.max().item()) voxel_min.append(image.min().item()) @@ -169,6 +179,8 @@ def calculate_percentiles( image, label = data[self.image_key], data[self.label_key] else: image, label = data + image, *_ = convert_data_type(data=image, output_type=torch.Tensor) + label, *_ = convert_data_type(data=label, output_type=torch.Tensor) intensities = image[torch.where(label > foreground_threshold)].tolist() if sampling_flag: diff --git a/monai/data/decathlon_datalist.py b/monai/data/decathlon_datalist.py index 134d8a9fcb..d2a9c3d220 100644 --- a/monai/data/decathlon_datalist.py +++ b/monai/data/decathlon_datalist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/grid_dataset.py b/monai/data/grid_dataset.py index 5c330f10e4..7a0f79d00e 100644 --- a/monai/data/grid_dataset.py +++ b/monai/data/grid_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/image_dataset.py b/monai/data/image_dataset.py index 874b9dc004..0ab71cd444 100644 --- a/monai/data/image_dataset.py +++ b/monai/data/image_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index 6e9cbca809..207430d933 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -155,16 +155,31 @@ class ITKReader(ImageReader): series_name: the name of the DICOM series if there are multiple ones. used when loading DICOM series. + reverse_indexing: whether to use a reversed spatial indexing convention for the returned data array. + If ``False``, the spatial indexing follows the numpy convention; + otherwise, the spatial indexing convention is reversed to be compatible with ITK. Default is ``False``. + This option does not affect the metadata. + series_meta: whether to load the metadata of the DICOM series (using the metadata from the first slice). + This flag is checked only when loading DICOM series. Default is ``False``. kwargs: additional args for `itk.imread` API. more details about available args: https://github.com/InsightSoftwareConsortium/ITK/blob/master/Wrapping/Generators/Python/itk/support/extras.py """ - def __init__(self, channel_dim: Optional[int] = None, series_name: str = "", **kwargs): + def __init__( + self, + channel_dim: Optional[int] = None, + series_name: str = "", + reverse_indexing: bool = False, + series_meta: bool = False, + **kwargs, + ): super().__init__() self.kwargs = kwargs self.channel_dim = channel_dim self.series_name = series_name + self.reverse_indexing = reverse_indexing + self.series_meta = series_meta def verify_suffix(self, filename: Union[Sequence[PathLike], PathLike]) -> bool: """ @@ -214,7 +229,17 @@ def read(self, data: Union[Sequence[PathLike], PathLike], **kwargs): series_identifier = series_uid[0] if not self.series_name else self.series_name name = names_generator.GetFileNames(series_identifier) - img_.append(itk.imread(name, **kwargs_)) + _obj = itk.imread(name, **kwargs_) + if self.series_meta: + _reader = itk.ImageSeriesReader.New(FileNames=name) + _reader.Update() + _meta = _reader.GetMetaDataDictionaryArray() + if len(_meta) > 0: + # TODO: using the first slice's meta. this could be improved to filter unnecessary tags. + _obj.SetMetaDataDictionary(_meta[0]) + img_.append(_obj) + else: + img_.append(itk.imread(name, **kwargs_)) return img_ if len(filenames) > 1 else img_[0] def get_data(self, img): @@ -308,19 +333,21 @@ def _get_array_data(self, img): Following PyTorch conventions, the returned array data has contiguous channels, e.g. for an RGB image, all red channel image pixels are contiguous in memory. - The first axis of the returned array is the channel axis. + The last axis of the returned array is the channel axis. + + See also: + + - https://github.com/InsightSoftwareConsortium/ITK/blob/v5.2.1/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in Args: img: an ITK image object loaded from an image file. """ - channels = img.GetNumberOfComponentsPerPixel() - np_data = itk.array_view_from_image(img).T - if channels == 1: - return np_data - if channels != np_data.shape[0]: - warnings.warn("itk_img.GetNumberOfComponentsPerPixel != numpy data channels") - return np.moveaxis(np_data, 0, -1) # channel last is compatible with `write_nifti` + np_img = itk.array_view_from_image(img, keep_axes=False) + if img.GetNumberOfComponentsPerPixel() == 1: # handling spatial images + return np_img if self.reverse_indexing else np_img.T + # handling multi-channel images + return np_img if self.reverse_indexing else np.moveaxis(np_img.T, 0, -1) @require_pkg(pkg_name="nibabel") @@ -681,14 +708,14 @@ class WSIReader(ImageReader): Read whole slide images and extract patches. Args: - backend: backend library to load the images, available options: "cuCIM", "OpenSlide" and "Tifffile". + backend: backend library to load the images, available options: "cuCIM", "OpenSlide" and "TiffFile". level: the whole slide image level at which the image is extracted. (default=0) This is overridden if the level argument is provided in `get_data`. Note: - While "cucim" and "OpenSlide" backends both can load patches from large whole slide images - without loading the entire image into memory, "Tifffile" backend needs to load the entire image into memory - before extracting any patch; thus, memory consideration is needed when using "Tifffile" backend for + While "cuCIM" and "OpenSlide" backends both can load patches from large whole slide images + without loading the entire image into memory, "TiffFile" backend needs to load the entire image into memory + before extracting any patch; thus, memory consideration is needed when using "TiffFile" backend for patch extraction. """ @@ -765,19 +792,22 @@ def get_data( grid_shape: (row, columns) tuple define a grid to extract patches on that patch_size: (height, width) the size of extracted patches at the given level """ + # Verify inputs if level is None: - level = self.level - - if self.backend == "openslide" and size is None: - # the maximum size is set to WxH at the specified level - size = (img.shape[0] // (2 ** level) - location[0], img.shape[1] // (2 ** level) - location[1]) + level = self._check_level(img, level) + # Extract a region or the entire image region = self._extract_region(img, location=location, size=size, level=level, dtype=dtype) + # Add necessary metadata metadata: Dict = {} metadata["spatial_shape"] = np.asarray(region.shape[:-1]) metadata["original_channel_dim"] = -1 + + # Make it channel first region = EnsureChannelFirst()(region, metadata) + + # Split into patches if patch_size is None: patches = region else: @@ -788,6 +818,45 @@ def get_data( return patches, metadata + def _check_level(self, img, level): + level = self.level + + level_count = 0 + if self.backend == "openslide": + level_count = img.level_count + elif self.backend == "cucim": + level_count = img.resolutions["level_count"] + elif self.backend == "tifffile": + level_count = len(img.pages) + + if level > level_count - 1: + raise ValueError(f"The maximum level of this image is {level_count - 1} while level={level} is requested)!") + + return level + + def _get_image_size(self, img, size, level, location): + """ + Calculate the maximum region size for the given level and starting location (if size is None). + Note that region size in OpenSlide and cuCIM are WxH (but the final image output would be HxW) + """ + if size is not None: + return size[::-1] + + max_size = [] + downsampling_factor = [] + if self.backend == "openslide": + downsampling_factor = img.level_downsamples[level] + max_size = img.level_dimensions[level] + elif self.backend == "cucim": + downsampling_factor = img.resolutions["level_downsamples"][level] + max_size = img.resolutions["level_dimensions"][level] + + # subtract the top left corner of the patch (at given level) from maximum size + location_at_level = (round(location[1] / downsampling_factor), round(location[0] / downsampling_factor)) + size = [max_size[i] - location_at_level[i] for i in range(len(max_size))] + + return size + def _extract_region( self, img_obj, @@ -797,21 +866,25 @@ def _extract_region( dtype: DtypeLike = np.uint8, ): if self.backend == "tifffile": - with img_obj: - region = img_obj.asarray(level=level) - if size is None: - region = region[location[0] :, location[1] :] - else: - region = region[location[0] : location[0] + size[0], location[1] : location[1] + size[1]] - + # Read the entire image + if size is not None: + raise ValueError( + f"TiffFile backend reads the entire image only, so size '{size}'' should not be provided!", + "For more flexibility or extracting regions, please use cuCIM or OpenSlide backend.", + ) + if location != (0, 0): + raise ValueError( + f"TiffFile backend reads the entire image only, so location '{location}' should not be provided!", + "For more flexibility and extracting regions, please use cuCIM or OpenSlide backend.", + ) + region = img_obj.asarray(level=level) else: - # reverse the order of dimensions for size and location to be compatible with image shape - location = location[::-1] - if size is None: - region = img_obj.read_region(location=location, level=level) - else: - size = size[::-1] - region = img_obj.read_region(location=location, size=size, level=level) + # Get region size to be extracted + region_size = self._get_image_size(img_obj, size, level, location) + # reverse the order of location's dimensions to become WxH (for cuCIM and OpenSlide) + region_location = location[::-1] + # Extract a region (or the entire image) + region = img_obj.read_region(location=region_location, size=region_size, level=level) region = self.convert_to_rgb_array(region, dtype) return region @@ -824,6 +897,7 @@ def convert_to_rgb_array(self, raw_region, dtype: DtypeLike = np.uint8): # convert to numpy (if not already in numpy) raw_region = np.asarray(raw_region, dtype=dtype) + # remove alpha channel if exist (RGBA) if raw_region.shape[-1] > 3: raw_region = raw_region[..., :3] diff --git a/monai/data/iterable_dataset.py b/monai/data/iterable_dataset.py index d1365fa220..19efc925fc 100644 --- a/monai/data/iterable_dataset.py +++ b/monai/data/iterable_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union import numpy as np from torch.utils.data import IterableDataset as _TorchIterableDataset @@ -18,7 +18,7 @@ from monai.data.utils import convert_tables_to_dicts from monai.transforms import apply_transform from monai.transforms.transform import Randomizable -from monai.utils import ensure_tuple, optional_import +from monai.utils import deprecated_arg, optional_import pd, _ = optional_import("pandas") @@ -147,8 +147,9 @@ class CSVIterableDataset(IterableDataset): ] Args: - filename: the filename of CSV file to load. it can be a str, URL, path object or file-like object. - if providing a list of filenames, it will load all the files and join tables. + src: if provided the filename of CSV file, it can be a str, URL, path object or file-like object to load. + also support to provide iter for stream input directly, will skip loading from filename. + if provided a list of filenames or iters, it will join the tables. chunksize: rows of a chunk when loading iterable data from CSV files, default to 1000. more details: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html. buffer_size: size of the buffer to store the loaded chunks, if None, set to `2 x chunksize`. @@ -177,11 +178,15 @@ class CSVIterableDataset(IterableDataset): https://github.com/pytorch/pytorch/blob/v1.10.0/torch/utils/data/distributed.py#L98. kwargs: additional arguments for `pandas.merge()` API to join tables. + .. deprecated:: 0.8.0 + ``filename`` is deprecated, use ``src`` instead. + """ + @deprecated_arg(name="filename", new_name="src", since="0.8", msg_suffix="please use `src` instead.") def __init__( self, - filename: Union[str, Sequence[str]], + src: Union[Union[str, Sequence[str]], Union[Iterable, Sequence[Iterable]]], chunksize: int = 1000, buffer_size: Optional[int] = None, col_names: Optional[Sequence[str]] = None, @@ -192,7 +197,7 @@ def __init__( seed: int = 0, **kwargs, ): - self.files = ensure_tuple(filename) + self.src = src self.chunksize = chunksize self.buffer_size = 2 * chunksize if buffer_size is None else buffer_size self.col_names = col_names @@ -200,17 +205,50 @@ def __init__( self.col_groups = col_groups self.shuffle = shuffle self.seed = seed + # in case treating deprecated arg `filename` as kwargs, remove it from `kwargs` + kwargs.pop("filename", None) self.kwargs = kwargs - self.iters = self.reset() + + self.iters: List[Iterable] = self.reset() super().__init__(data=None, transform=transform) # type: ignore - def reset(self, filename: Optional[Union[str, Sequence[str]]] = None): - if filename is not None: - # update files if necessary - self.files = ensure_tuple(filename) - self.iters = [pd.read_csv(f, chunksize=self.chunksize) for f in self.files] + @deprecated_arg(name="filename", new_name="src", since="0.8", msg_suffix="please use `src` instead.") + def reset(self, src: Optional[Union[Union[str, Sequence[str]], Union[Iterable, Sequence[Iterable]]]] = None): + """ + Reset the pandas `TextFileReader` iterable object to read data. For more details, please check: + https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html?#iteration. + + Args: + src: if not None and provided the filename of CSV file, it can be a str, URL, path object + or file-like object to load. also support to provide iter for stream input directly, + will skip loading from filename. if provided a list of filenames or iters, it will join the tables. + default to `self.src`. + + """ + src = self.src if src is None else src + srcs = (src,) if not isinstance(src, (tuple, list)) else src + self.iters = [] + for i in srcs: + if isinstance(i, str): + self.iters.append(pd.read_csv(i, chunksize=self.chunksize)) + elif isinstance(i, Iterable): + self.iters.append(i) + else: + raise ValueError("`src` must be file path or iterable object.") return self.iters + def close(self): + """ + Close the pandas `TextFileReader` iterable objects. + If the input src is file path, TextFileReader was created internally, need to close it. + If the input src is iterable object, depends on users requirements whether to close it in this function. + For more details, please check: + https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html?#iteration. + + """ + for i in self.iters: + i.close() + def _flattened(self): for chunks in zip(*self.iters): yield from convert_tables_to_dicts( diff --git a/monai/data/nifti_saver.py b/monai/data/nifti_saver.py index 75805479d7..f31926cb6c 100644 --- a/monai/data/nifti_saver.py +++ b/monai/data/nifti_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/nifti_writer.py b/monai/data/nifti_writer.py index 078ea2e605..35044977e0 100644 --- a/monai/data/nifti_writer.py +++ b/monai/data/nifti_writer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/png_saver.py b/monai/data/png_saver.py index 5154ac1ab4..2e31597837 100644 --- a/monai/data/png_saver.py +++ b/monai/data/png_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/png_writer.py b/monai/data/png_writer.py index ea028a07be..6f3b2ef86e 100644 --- a/monai/data/png_writer.py +++ b/monai/data/png_writer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/samplers.py b/monai/data/samplers.py index f69c6091ca..40eed03187 100644 --- a/monai/data/samplers.py +++ b/monai/data/samplers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/synthetic.py b/monai/data/synthetic.py index 0a1c179a7f..46d555cf11 100644 --- a/monai/data/synthetic.py +++ b/monai/data/synthetic.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/test_time_augmentation.py b/monai/data/test_time_augmentation.py index 28a172beac..c06d567b54 100644 --- a/monai/data/test_time_augmentation.py +++ b/monai/data/test_time_augmentation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/thread_buffer.py b/monai/data/thread_buffer.py index 4b6db4f6a4..cdd7c05f31 100644 --- a/monai/data/thread_buffer.py +++ b/monai/data/thread_buffer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/data/torchscript_utils.py b/monai/data/torchscript_utils.py new file mode 100644 index 0000000000..585db14712 --- /dev/null +++ b/monai/data/torchscript_utils.py @@ -0,0 +1,149 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json +import os +from typing import IO, Any, Mapping, Optional, Sequence, Tuple, Union + +import torch + +from monai.config import get_config_values +from monai.utils import JITMetadataKeys +from monai.utils.module import pytorch_after + +METADATA_FILENAME = "metadata.json" + + +def save_net_with_metadata( + jit_obj: torch.nn.Module, + filename_prefix_or_stream: Union[str, IO[Any]], + include_config_vals: bool = True, + append_timestamp: bool = False, + meta_values: Optional[Mapping[str, Any]] = None, + more_extra_files: Optional[Mapping[str, bytes]] = None, +) -> None: + """ + Save the JIT object (script or trace produced object) `jit_obj` to the given file or stream with metadata + included as a JSON file. The Torchscript format is a zip file which can contain extra file data which is used + here as a mechanism for storing metadata about the network being saved. The data in `meta_values` should be + compatible with conversion to JSON using the standard library function `dumps`. The intent is this metadata will + include information about the network applicable to some use case, such as describing the input and output format, + a network name and version, a plain language description of what the network does, and other relevant scientific + information. Clients can use this information to determine automatically how to use the network, and users can + read what the network does and keep track of versions. + + Examples:: + + net = torch.jit.script(monai.networks.nets.UNet(2, 1, 1, [8, 16], [2])) + + meta = { + "name": "Test UNet", + "used_for": "demonstration purposes", + "input_dims": 2, + "output_dims": 2 + } + + # save the Torchscript bundle with the above dictionary stored as an extra file + save_net_with_metadata(m, "test", meta_values=meta) + + # load the network back, `loaded_meta` has same data as `meta` plus version information + loaded_net, loaded_meta, _ = load_net_with_metadata("test.pt") + + + Args: + jit_obj: object to save, should be generated by `script` or `trace`. + filename_prefix_or_stream: filename or file-like stream object, if filename has no extension it becomes `.pt`. + include_config_vals: if True, MONAI, Pytorch, and Numpy versions are included in metadata. + append_timestamp: if True, a timestamp for "now" is appended to the file's name before the extension. + meta_values: metadata values to store with the object, not limited just to keys in `JITMetadataKeys`. + more_extra_files: other extra file data items to include in bundle, see `_extra_files` of `torch.jit.save`. + """ + + now = datetime.datetime.now() + metadict = {} + + if include_config_vals: + metadict.update(get_config_values()) + metadict[JITMetadataKeys.TIMESTAMP.value] = now.astimezone().isoformat() + + if meta_values is not None: + metadict.update(meta_values) + + json_data = json.dumps(metadict) + + # Pytorch>1.6 can use dictionaries directly, otherwise need to use special map object + if pytorch_after(1, 7): + extra_files = {METADATA_FILENAME: json_data.encode()} + + if more_extra_files is not None: + extra_files.update(more_extra_files) + else: + extra_files = torch._C.ExtraFilesMap() # type:ignore[attr-defined] + extra_files[METADATA_FILENAME] = json_data.encode() + + if more_extra_files is not None: + for k, v in more_extra_files.items(): + extra_files[k] = v + + if isinstance(filename_prefix_or_stream, str): + filename_no_ext, ext = os.path.splitext(filename_prefix_or_stream) + if ext == "": + ext = ".pt" + + if append_timestamp: + filename_prefix_or_stream = now.strftime(f"{filename_no_ext}_%Y%m%d%H%M%S{ext}") + else: + filename_prefix_or_stream = filename_no_ext + ext + + torch.jit.save(jit_obj, filename_prefix_or_stream, extra_files) + + +def load_net_with_metadata( + filename_prefix_or_stream: Union[str, IO[Any]], + map_location: Optional[torch.device] = None, + more_extra_files: Sequence[str] = (), +) -> Tuple[torch.nn.Module, dict, dict]: + """ + Load the module object from the given Torchscript filename or stream, and convert the stored JSON metadata + back to a dict object. This will produce an empty dict if the metadata file is not present. + + Args: + filename_prefix_or_stream: filename or file-like stream object. + map_location: network map location as in `torch.jit.load`. + more_extra_files: other extra file data names to load from bundle, see `_extra_files` of `torch.jit.load`. + Returns: + Triple containing loaded object, metadata dict, and extra files dict containing other file data if present + """ + # Pytorch>1.6 can use dictionaries directly, otherwise need to use special map object + if pytorch_after(1, 7): + extra_files = {f: "" for f in more_extra_files} + extra_files[METADATA_FILENAME] = "" + else: + extra_files = torch._C.ExtraFilesMap() # type:ignore[attr-defined] + extra_files[METADATA_FILENAME] = "" + + for f in more_extra_files: + extra_files[f] = "" + + jit_obj = torch.jit.load(filename_prefix_or_stream, map_location, extra_files) + + extra_files = dict(extra_files.items()) # compatibility with ExtraFilesMap + + if METADATA_FILENAME in extra_files: + json_data = extra_files[METADATA_FILENAME] + del extra_files[METADATA_FILENAME] + else: + json_data = "{}" + + json_data_dict = json.loads(json_data) + + return jit_obj, json_data_dict, extra_files diff --git a/monai/data/utils.py b/monai/data/utils.py index 61c773c204..0cd3c1594a 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -539,7 +539,7 @@ def rectify_header_sform_qform(img_nii): return img_nii -def zoom_affine(affine: np.ndarray, scale: Sequence[float], diagonal: bool = True): +def zoom_affine(affine: np.ndarray, scale: Union[np.ndarray, Sequence[float]], diagonal: bool = True): """ To make column norm of `affine` the same as `scale`. If diagonal is False, returns an affine that combines orthogonal rotation and the new scale. @@ -676,14 +676,15 @@ def create_file_basename( folder_path: PathLike, data_root_dir: PathLike = "", separate_folder: bool = True, - patch_index: Optional[int] = None, + patch_index=None, + makedirs: bool = True, ) -> str: """ Utility function to create the path to the output file based on the input filename (file name extension is not added by this function). When `data_root_dir` is not specified, the output file name is: - `folder_path/input_file_name (no ext.) /input_file_name (no ext.)[_postfix]` + `folder_path/input_file_name (no ext.) /input_file_name (no ext.)[_postfix][_patch_index]` otherwise the relative path with respect to `data_root_dir` will be inserted, for example: input_file_name: /foo/bar/test1/image.png, @@ -704,6 +705,7 @@ def create_file_basename( `image.nii`, postfix is `seg` and folder_path is `output`, if `True`, save as: `output/image/image_seg.nii`, if `False`, save as `output/image_seg.nii`. default to `True`. patch_index: if not None, append the patch index to filename. + makedirs: whether to create the folder if it does not exist. """ # get the filename and directory @@ -722,8 +724,10 @@ def create_file_basename( if separate_folder: output = os.path.join(output, filename) - # create target folder if no existing - os.makedirs(output, exist_ok=True) + + if makedirs: + # create target folder if no existing + os.makedirs(output, exist_ok=True) # add the sub-folder plus the postfix name to become the file basename in the output path output = os.path.join(output, (filename + "_" + postfix) if len(postfix) > 0 else filename) @@ -731,7 +735,7 @@ def create_file_basename( if patch_index is not None: output += f"_{patch_index}" - return os.path.abspath(output) + return os.path.normpath(output) def compute_importance_map( diff --git a/monai/engines/__init__.py b/monai/engines/__init__.py index d04401829f..88f094c732 100644 --- a/monai/engines/__init__.py +++ b/monai/engines/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -24,3 +24,4 @@ engine_apply_transform, get_devices_spec, ) +from .workflow import BaseWorkflow, Workflow diff --git a/monai/engines/evaluator.py b/monai/engines/evaluator.py index 3ddbca45bf..b329462e24 100644 --- a/monai/engines/evaluator.py +++ b/monai/engines/evaluator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union import torch from torch.utils.data import DataLoader @@ -45,9 +45,13 @@ class Evaluator(Workflow): epoch_length: number of iterations for one epoch, default to `len(val_data_loader)`. non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. - prepare_batch: function to parse image and label for current iteration. + prepare_batch: function to parse expected data (usually `image`, `label` and other network args) + from `engine.state.batch` for every iteration, for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. key_val_metric: compute metric when every iteration completed, and save average value to @@ -80,7 +84,7 @@ def __init__( epoch_length: Optional[int] = None, non_blocking: bool = False, prepare_batch: Callable = default_prepare_batch, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, postprocessing: Optional[Transform] = None, key_val_metric: Optional[Dict[str, Metric]] = None, additional_metrics: Optional[Dict[str, Metric]] = None, @@ -147,9 +151,13 @@ class SupervisedEvaluator(Evaluator): epoch_length: number of iterations for one epoch, default to `len(val_data_loader)`. non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. - prepare_batch: function to parse image and label for current iteration. + prepare_batch: function to parse expected data (usually `image`, `label` and other network args) + from `engine.state.batch` for every iteration, for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. inferer: inference method that execute model forward on input data, like: SlidingWindow, etc. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. @@ -184,7 +192,7 @@ def __init__( epoch_length: Optional[int] = None, non_blocking: bool = False, prepare_batch: Callable = default_prepare_batch, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, inferer: Optional[Inferer] = None, postprocessing: Optional[Transform] = None, key_val_metric: Optional[Dict[str, Metric]] = None, @@ -275,9 +283,13 @@ class EnsembleEvaluator(Evaluator): the length must exactly match the number of networks. non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. - prepare_batch: function to parse image and label for current iteration. + prepare_batch: function to parse expected data (usually `image`, `label` and other network args) + from `engine.state.batch` for every iteration, for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. inferer: inference method that execute model forward on input data, like: SlidingWindow, etc. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. @@ -313,7 +325,7 @@ def __init__( epoch_length: Optional[int] = None, non_blocking: bool = False, prepare_batch: Callable = default_prepare_batch, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, inferer: Optional[Inferer] = None, postprocessing: Optional[Transform] = None, key_val_metric: Optional[Dict[str, Metric]] = None, diff --git a/monai/engines/multi_gpu_supervised_trainer.py b/monai/engines/multi_gpu_supervised_trainer.py index 3736d257cb..7c59b670b7 100644 --- a/monai/engines/multi_gpu_supervised_trainer.py +++ b/monai/engines/multi_gpu_supervised_trainer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/engines/trainer.py b/monai/engines/trainer.py index c7a8b49e30..774e535e7f 100644 --- a/monai/engines/trainer.py +++ b/monai/engines/trainer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union import torch from torch.optim.optimizer import Optimizer @@ -75,9 +75,13 @@ class SupervisedTrainer(Trainer): epoch_length: number of iterations for one epoch, default to `len(train_data_loader)`. non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. - prepare_batch: function to parse image and label for current iteration. + prepare_batch: function to parse expected data (usually `image`, `label` and other network args) + from `engine.state.batch` for every iteration, for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. inferer: inference method that execute model forward on input data, like: SlidingWindow, etc. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. @@ -115,7 +119,7 @@ def __init__( epoch_length: Optional[int] = None, non_blocking: bool = False, prepare_batch: Callable = default_prepare_batch, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, inferer: Optional[Inferer] = None, postprocessing: Optional[Transform] = None, key_train_metric: Optional[Dict[str, Metric]] = None, @@ -241,12 +245,16 @@ class GanTrainer(Trainer): non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. d_prepare_batch: callback function to prepare batchdata for D inferer. - Defaults to return ``GanKeys.REALS`` in batchdata dict. + Defaults to return ``GanKeys.REALS`` in batchdata dict. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. g_prepare_batch: callback function to create batch of latent input for G inferer. - Defaults to return random latents. + Defaults to return random latents. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. g_update_latents: Calculate G loss with new latent codes. Defaults to ``True``. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. key_train_metric: compute metric when every iteration completed, and save average value to @@ -286,7 +294,7 @@ def __init__( d_prepare_batch: Callable = default_prepare_batch, g_prepare_batch: Callable = default_make_latent, g_update_latents: bool = True, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, postprocessing: Optional[Transform] = None, key_train_metric: Optional[Dict[str, Metric]] = None, additional_metrics: Optional[Dict[str, Metric]] = None, diff --git a/monai/engines/utils.py b/monai/engines/utils.py index 25e09fe2b6..726dfc8e98 100644 --- a/monai/engines/utils.py +++ b/monai/engines/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -166,9 +166,9 @@ class PrepareBatchExtraInput(PrepareBatch): Args: extra_keys: if a string or list provided, every item is the key of extra data in current batch, - and will pass the extra data to the network(*args) in order. + and will pass the extra data to the `network(*args)` in order. If a dictionary is provided, every `{k, v}` pair is the key of extra data in current batch, - `k` the param name in network, `v` is the key of extra data in current batch, + `k` is the param name in network, `v` is the key of extra data in current batch, and will pass the `{k1: batch[v1], k2: batch[v2], ...}` as kwargs to the network. """ diff --git a/monai/engines/workflow.py b/monai/engines/workflow.py index 48e2dc1774..4222db0593 100644 --- a/monai/engines/workflow.py +++ b/monai/engines/workflow.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,7 +10,8 @@ # limitations under the License. import warnings -from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Sequence, Union +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Sequence, Union import torch import torch.distributed as dist @@ -19,7 +20,7 @@ from monai.config import IgniteInfo from monai.engines.utils import IterationEvents, default_metric_cmp_fn, default_prepare_batch -from monai.transforms import Decollated, Transform +from monai.transforms import Decollated from monai.utils import ensure_tuple, is_scalar, min_version, optional_import from .utils import engine_apply_transform @@ -37,6 +38,18 @@ EventEnum, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "EventEnum") +class BaseWorkflow(ABC): + """ + Base class for any MONAI style workflow. + `run()` is designed to execute the train, evaluation or inference logic. + + """ + + @abstractmethod + def run(self, *args, **kwargs): + raise NotImplementedError(f"Subclass {self.__class__.__name__} must implement this method.") + + class Workflow(IgniteEngine): # type: ignore[valid-type, misc] # due to optional_import """ Workflow defines the core work process inheriting from Ignite engine. @@ -54,9 +67,13 @@ class Workflow(IgniteEngine): # type: ignore[valid-type, misc] # due to optiona epoch_length: number of iterations for one epoch, default to `len(data_loader)`. non_blocking: if True and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect. - prepare_batch: function to parse image and label for every iteration. + prepare_batch: function to parse expected data (usually `image`, `label` and other network args) + from `engine.state.batch` for every iteration, for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.create_supervised_trainer.html. iteration_update: the callable function for every iteration, expect to accept `engine` - and `batchdata` as input parameters. if not provided, use `self._iteration()` instead. + and `engine.state.batch` as inputs, return data will be stored in `engine.state.output`. + if not provided, use `self._iteration()` instead. for more details please refer to: + https://pytorch.org/ignite/generated/ignite.engine.engine.Engine.html. postprocessing: execute additional transformation for the model output data. Typically, several Tensor based transforms composed by `Compose`. key_metric: compute metric when every iteration completed, and save average value to @@ -94,7 +111,7 @@ def __init__( epoch_length: Optional[int] = None, non_blocking: bool = False, prepare_batch: Callable = default_prepare_batch, - iteration_update: Optional[Callable] = None, + iteration_update: Optional[Callable[[Engine, Any], Any]] = None, postprocessing: Optional[Callable] = None, key_metric: Optional[Dict[str, Metric]] = None, additional_metrics: Optional[Dict[str, Metric]] = None, @@ -169,8 +186,8 @@ def set_sampler_epoch(engine: Engine): self._register_decollate() if postprocessing is not None: - if not decollate and isinstance(postprocessing, Transform): - warnings.warn("MONAI transforms expect `channel-first` data, `decollate=False` may not work here.") + # tips: if `decollate=False` and `postprocessing` is MONAI transforms, it may not work well + # because all the MONAI transforms expect `channel-first` data self._register_postprocessing(postprocessing) if key_metric is not None: self._register_metrics(key_metric, additional_metrics) diff --git a/monai/handlers/__init__.py b/monai/handlers/__init__.py index 520af0a94c..03aaa37412 100644 --- a/monai/handlers/__init__.py +++ b/monai/handlers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/checkpoint_loader.py b/monai/handlers/checkpoint_loader.py index 7c30584b13..91cfca354a 100644 --- a/monai/handlers/checkpoint_loader.py +++ b/monai/handlers/checkpoint_loader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/checkpoint_saver.py b/monai/handlers/checkpoint_saver.py index 607cd11b25..f7abca4aa0 100644 --- a/monai/handlers/checkpoint_saver.py +++ b/monai/handlers/checkpoint_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/classification_saver.py b/monai/handlers/classification_saver.py index 4481ae0fec..bb37e36826 100644 --- a/monai/handlers/classification_saver.py +++ b/monai/handlers/classification_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -98,7 +98,14 @@ def attach(self, engine: Engine) -> None: if not engine.has_event_handler(self._finalize, Events.EPOCH_COMPLETED): engine.add_event_handler(Events.EPOCH_COMPLETED, self._finalize) - def _started(self, engine: Engine) -> None: + def _started(self, _engine: Engine) -> None: + """ + Initialize internal buffers. + + Args: + _engine: Ignite Engine, unused argument. + + """ self._outputs = [] self._filenames = [] @@ -120,12 +127,12 @@ def __call__(self, engine: Engine) -> None: o = o.detach() self._outputs.append(o) - def _finalize(self, engine: Engine) -> None: + def _finalize(self, _engine: Engine) -> None: """ All gather classification results from ranks and save to CSV file. Args: - engine: Ignite Engine, it can be a trainer, validator or evaluator. + _engine: Ignite Engine, unused argument. """ ws = idist.get_world_size() if self.save_rank >= ws: diff --git a/monai/handlers/confusion_matrix.py b/monai/handlers/confusion_matrix.py index a3a896ccb9..e3fc4bfbf1 100644 --- a/monai/handlers/confusion_matrix.py +++ b/monai/handlers/confusion_matrix.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/decollate_batch.py b/monai/handlers/decollate_batch.py index 0905ee6ebc..a0d0ef3ad2 100644 --- a/monai/handlers/decollate_batch.py +++ b/monai/handlers/decollate_batch.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/earlystop_handler.py b/monai/handlers/earlystop_handler.py index e194b50d59..8d57526676 100644 --- a/monai/handlers/earlystop_handler.py +++ b/monai/handlers/earlystop_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/garbage_collector.py b/monai/handlers/garbage_collector.py index 1eb970e795..74ccac8a72 100644 --- a/monai/handlers/garbage_collector.py +++ b/monai/handlers/garbage_collector.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/hausdorff_distance.py b/monai/handlers/hausdorff_distance.py index 3a1580f714..739c9e9935 100644 --- a/monai/handlers/hausdorff_distance.py +++ b/monai/handlers/hausdorff_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/ignite_metric.py b/monai/handlers/ignite_metric.py index fa17fe8970..f28923af68 100644 --- a/monai/handlers/ignite_metric.py +++ b/monai/handlers/ignite_metric.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/lr_schedule_handler.py b/monai/handlers/lr_schedule_handler.py index 3e57ac7bbd..207af306d4 100644 --- a/monai/handlers/lr_schedule_handler.py +++ b/monai/handlers/lr_schedule_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -36,6 +36,7 @@ def __init__( name: Optional[str] = None, epoch_level: bool = True, step_transform: Callable[[Engine], Any] = lambda engine: (), + logger_handler: Optional[logging.Handler] = None, ) -> None: """ Args: @@ -47,6 +48,9 @@ def __init__( `True` is epoch level, `False` is iteration level. step_transform: a callable that is used to transform the information from `engine` to expected input data of lr_scheduler.step() function if necessary. + logger_handler: if `print_lr` is True, add additional handler to log the learning rate: save to file, etc. + all the existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html. + the handler should have a logging level of at least `INFO`. Raises: TypeError: When ``step_transform`` is not ``callable``. @@ -59,6 +63,8 @@ def __init__( if not callable(step_transform): raise TypeError(f"step_transform must be callable but is {type(step_transform).__name__}.") self.step_transform = step_transform + if logger_handler is not None: + self.logger.addHandler(logger_handler) self._name = name diff --git a/monai/handlers/mean_dice.py b/monai/handlers/mean_dice.py index ec62d4d64b..c5609c6746 100644 --- a/monai/handlers/mean_dice.py +++ b/monai/handlers/mean_dice.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/metric_logger.py b/monai/handlers/metric_logger.py index 048f230d1a..350d1978de 100644 --- a/monai/handlers/metric_logger.py +++ b/monai/handlers/metric_logger.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/metrics_saver.py b/monai/handlers/metrics_saver.py index d6aa0c7b9f..1ec26eece7 100644 --- a/monai/handlers/metrics_saver.py +++ b/monai/handlers/metrics_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -105,7 +105,14 @@ def attach(self, engine: Engine) -> None: engine.add_event_handler(Events.ITERATION_COMPLETED, self._get_filenames) engine.add_event_handler(Events.EPOCH_COMPLETED, self) - def _started(self, engine: Engine) -> None: + def _started(self, _engine: Engine) -> None: + """ + Initialize internal buffers. + + Args: + _engine: Ignite Engine, unused argument. + + """ self._filenames = [] def _get_filenames(self, engine: Engine) -> None: diff --git a/monai/handlers/mlflow_handler.py b/monai/handlers/mlflow_handler.py index 7bf6596437..9475b2be35 100644 --- a/monai/handlers/mlflow_handler.py +++ b/monai/handlers/mlflow_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/nvtx_handlers.py b/monai/handlers/nvtx_handlers.py index 8e44248f7d..327c156f63 100644 --- a/monai/handlers/nvtx_handlers.py +++ b/monai/handlers/nvtx_handlers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/parameter_scheduler.py b/monai/handlers/parameter_scheduler.py index b6eb35562f..c0e18edcd0 100644 --- a/monai/handlers/parameter_scheduler.py +++ b/monai/handlers/parameter_scheduler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/postprocessing.py b/monai/handlers/postprocessing.py index 29029306d2..4a89c86f47 100644 --- a/monai/handlers/postprocessing.py +++ b/monai/handlers/postprocessing.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/regression_metrics.py b/monai/handlers/regression_metrics.py index 2132e2914e..bf4ac3af1d 100644 --- a/monai/handlers/regression_metrics.py +++ b/monai/handlers/regression_metrics.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/roc_auc.py b/monai/handlers/roc_auc.py index 125a4991ea..31b046064e 100644 --- a/monai/handlers/roc_auc.py +++ b/monai/handlers/roc_auc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/segmentation_saver.py b/monai/handlers/segmentation_saver.py index 8af92cbf2a..79ebfd3a22 100644 --- a/monai/handlers/segmentation_saver.py +++ b/monai/handlers/segmentation_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/smartcache_handler.py b/monai/handlers/smartcache_handler.py index e3adcbf4a0..56fee78b1d 100644 --- a/monai/handlers/smartcache_handler.py +++ b/monai/handlers/smartcache_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index 9251453b50..8410aaec87 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -82,7 +82,8 @@ def __init__( tag_name: scalar_value to logger. Defaults to ``'Loss'``. key_var_format: a formatting string to control the output string format of key: value. logger_handler: add additional handler to handle the stats data: save to file, etc. - Add existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html + all the existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html. + the handler should have a logging level of at least `INFO`. """ self.epoch_print_logger = epoch_print_logger @@ -143,14 +144,14 @@ def iteration_completed(self, engine: Engine) -> None: else: self._default_iteration_print(engine) - def exception_raised(self, engine: Engine, e: Exception) -> None: + def exception_raised(self, _engine: Engine, e: Exception) -> None: """ Handler for train or validation/evaluation exception raised Event. Print the exception information and traceback. This callback may be skipped because the logic with Ignite can only trigger the first attached handler for `EXCEPTION_RAISED` event. Args: - engine: Ignite Engine, it can be a trainer, validator or evaluator. + _engine: Ignite Engine, unused argument. e: the exception caught in Ignite during engine.run(). """ diff --git a/monai/handlers/surface_distance.py b/monai/handlers/surface_distance.py index 22eb1646c3..77f0debfe9 100644 --- a/monai/handlers/surface_distance.py +++ b/monai/handlers/surface_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/tensorboard_handlers.py b/monai/handlers/tensorboard_handlers.py index 11b487ec99..dcf60973b0 100644 --- a/monai/handlers/tensorboard_handlers.py +++ b/monai/handlers/tensorboard_handlers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -173,6 +173,21 @@ def iteration_completed(self, engine: Engine) -> None: else: self._default_iteration_writer(engine, self._writer) + def _write_scalar(self, _engine: Engine, writer: SummaryWriter, tag: str, value: Any, step: int) -> None: + """ + Write scale value into TensorBoard. + Default to call `SummaryWriter.add_scalar()`. + + Args: + _engine: Ignite Engine, unused argument. + writer: TensorBoard or TensorBoardX writer, passed or created in TensorBoardHandler. + tag: tag name in the TensorBoard. + value: value of the scalar data for current step. + step: index of current step. + + """ + writer.add_scalar(tag, value, step) + def _default_epoch_writer(self, engine: Engine, writer: SummaryWriter) -> None: """ Execute epoch level event write operation. @@ -188,11 +203,11 @@ def _default_epoch_writer(self, engine: Engine, writer: SummaryWriter) -> None: summary_dict = engine.state.metrics for name, value in summary_dict.items(): if is_scalar(value): - writer.add_scalar(name, value, current_epoch) + self._write_scalar(engine, writer, name, value, current_epoch) if self.state_attributes is not None: for attr in self.state_attributes: - writer.add_scalar(attr, getattr(engine.state, attr, None), current_epoch) + self._write_scalar(engine, writer, attr, getattr(engine.state, attr, None), current_epoch) writer.flush() def _default_iteration_writer(self, engine: Engine, writer: SummaryWriter) -> None: @@ -221,12 +236,20 @@ def _default_iteration_writer(self, engine: Engine, writer: SummaryWriter) -> No " {}:{}".format(name, type(value)) ) continue # not plot multi dimensional output - writer.add_scalar( - name, value.item() if isinstance(value, torch.Tensor) else value, engine.state.iteration + self._write_scalar( + _engine=engine, + writer=writer, + tag=name, + value=value.item() if isinstance(value, torch.Tensor) else value, + step=engine.state.iteration, ) elif is_scalar(loss): # not printing multi dimensional output - writer.add_scalar( - self.tag_name, loss.item() if isinstance(loss, torch.Tensor) else loss, engine.state.iteration + self._write_scalar( + _engine=engine, + writer=writer, + tag=self.tag_name, + value=loss.item() if isinstance(loss, torch.Tensor) else loss, + step=engine.state.iteration, ) else: warnings.warn( diff --git a/monai/handlers/utils.py b/monai/handlers/utils.py index 06766c3e14..23947d3054 100644 --- a/monai/handlers/utils.py +++ b/monai/handlers/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/handlers/validation_handler.py b/monai/handlers/validation_handler.py index 6214461a4f..171c901fbb 100644 --- a/monai/handlers/validation_handler.py +++ b/monai/handlers/validation_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/inferers/__init__.py b/monai/inferers/__init__.py index 030344728d..20d829297f 100644 --- a/monai/inferers/__init__.py +++ b/monai/inferers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/inferers/inferer.py b/monai/inferers/inferer.py index 25d9fd1fb0..c7b70e06ca 100644 --- a/monai/inferers/inferer.py +++ b/monai/inferers/inferer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/inferers/utils.py b/monai/inferers/utils.py index 0ca53529c7..b27e13eef6 100644 --- a/monai/inferers/utils.py +++ b/monai/inferers/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/__init__.py b/monai/losses/__init__.py index 3eca68cc4f..1922996fb6 100644 --- a/monai/losses/__init__.py +++ b/monai/losses/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/contrastive.py b/monai/losses/contrastive.py index 22caf3fe7d..aef596a492 100644 --- a/monai/losses/contrastive.py +++ b/monai/losses/contrastive.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -9,13 +9,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union - import torch from torch.nn import functional as F from torch.nn.modules.loss import _Loss -from monai.utils import LossReduction +from monai.utils import deprecated_arg class ContrastiveLoss(_Loss): @@ -31,19 +29,23 @@ class ContrastiveLoss(_Loss): """ - def __init__( - self, temperature: float = 0.5, batch_size: int = 1, reduction: Union[LossReduction, str] = LossReduction.SUM - ) -> None: + @deprecated_arg(name="reduction", since="0.8", msg_suffix="`reduction` is no longer supported.") + def __init__(self, temperature: float = 0.5, batch_size: int = 1, reduction="sum") -> None: """ Args: temperature: Can be scaled between 0 and 1 for learning from negative samples, ideally set to 0.5. + batch_size: The number of samples. Raises: - AssertionError: When an input of dimension length > 2 is passed - AssertionError: When input and target are of different shapes + ValueError: When an input of dimension length > 2 is passed + ValueError: When input and target are of different shapes + + .. deprecated:: 0.8.0 + + `reduction` is no longer supported. """ - super().__init__(reduction=LossReduction(reduction).value) + super().__init__() self.batch_size = batch_size self.temperature = temperature @@ -53,18 +55,15 @@ def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor: Args: input: the shape should be B[F]. target: the shape should be B[F]. - - Raises: - ValueError: When ``self.reduction`` is not one of ["sum", "none"]. """ if len(target.shape) > 2 or len(input.shape) > 2: - raise AssertionError( + raise ValueError( f"Either target or input has dimensions greater than 2 where target " f"shape is ({target.shape}) and input shape is ({input.shape})" ) if target.shape != input.shape: - raise AssertionError(f"ground truth has differing shape ({target.shape}) from input ({input.shape})") + raise ValueError(f"ground truth has differing shape ({target.shape}) from input ({input.shape})") temperature_tensor = torch.tensor(self.temperature).to(input.device) @@ -86,6 +85,4 @@ def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor: loss_partial = -torch.log(nominator / torch.sum(denominator, dim=1)) - if self.reduction == LossReduction.SUM.value: - return torch.sum(loss_partial) / (2 * self.batch_size) - raise ValueError(f"Unsupported reduction: {self.reduction}, " f'available options are ["mean", "sum", "none"].') + return torch.sum(loss_partial) / (2 * self.batch_size) diff --git a/monai/losses/deform.py b/monai/losses/deform.py index fea56010c7..0f5e263a53 100644 --- a/monai/losses/deform.py +++ b/monai/losses/deform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -52,9 +52,12 @@ class BendingEnergyLoss(_Loss): DeepReg (https://github.com/DeepRegNet/DeepReg) """ - def __init__(self, reduction: Union[LossReduction, str] = LossReduction.MEAN) -> None: + def __init__(self, normalize: bool = False, reduction: Union[LossReduction, str] = LossReduction.MEAN) -> None: """ Args: + normalize: + Whether to divide out spatial sizes in order to make the computation roughly + invariant to image scale (i.e. vector field sampling resolution). Defaults to False. reduction: {``"none"``, ``"mean"``, ``"sum"``} Specifies the reduction to apply to the output. Defaults to ``"mean"``. @@ -63,6 +66,7 @@ def __init__(self, reduction: Union[LossReduction, str] = LossReduction.MEAN) -> - ``"sum"``: the output will be summed. """ super().__init__(reduction=LossReduction(reduction).value) + self.normalize = normalize def forward(self, pred: torch.Tensor) -> torch.Tensor: """ @@ -74,20 +78,35 @@ def forward(self, pred: torch.Tensor) -> torch.Tensor: """ if pred.ndim not in [3, 4, 5]: - raise ValueError(f"expecting 3-d, 4-d or 5-d pred, instead got pred of shape {pred.shape}") + raise ValueError(f"Expecting 3-d, 4-d or 5-d pred, instead got pred of shape {pred.shape}") for i in range(pred.ndim - 2): if pred.shape[-i - 1] <= 4: - raise ValueError("all spatial dimensions must > 4, got pred of shape {pred.shape}") + raise ValueError(f"All spatial dimensions must be > 4, got spatial dimensions {pred.shape[2:]}") + if pred.shape[1] != pred.ndim - 2: + raise ValueError( + f"Number of vector components, {pred.shape[1]}, does not match number of spatial dimensions, {pred.ndim-2}" + ) # first order gradient first_order_gradient = [spatial_gradient(pred, dim) for dim in range(2, pred.ndim)] + # spatial dimensions in a shape suited for broadcasting below + if self.normalize: + spatial_dims = torch.tensor(pred.shape, device=pred.device)[2:].reshape((1, -1) + (pred.ndim - 2) * (1,)) + energy = torch.tensor(0) for dim_1, g in enumerate(first_order_gradient): dim_1 += 2 - energy = spatial_gradient(g, dim_1) ** 2 + energy + if self.normalize: + g *= pred.shape[dim_1] / spatial_dims + energy = energy + (spatial_gradient(g, dim_1) * pred.shape[dim_1]) ** 2 + else: + energy = energy + spatial_gradient(g, dim_1) ** 2 for dim_2 in range(dim_1 + 1, pred.ndim): - energy = 2 * spatial_gradient(g, dim_2) ** 2 + energy + if self.normalize: + energy = energy + 2 * (spatial_gradient(g, dim_2) * pred.shape[dim_2]) ** 2 + else: + energy = energy + 2 * spatial_gradient(g, dim_2) ** 2 if self.reduction == LossReduction.MEAN.value: energy = torch.mean(energy) # the batch and channel average diff --git a/monai/losses/dice.py b/monai/losses/dice.py index 484433f676..7f2037ca54 100644 --- a/monai/losses/dice.py +++ b/monai/losses/dice.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/focal_loss.py b/monai/losses/focal_loss.py index 39f36c368f..bf31682748 100644 --- a/monai/losses/focal_loss.py +++ b/monai/losses/focal_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/image_dissimilarity.py b/monai/losses/image_dissimilarity.py index ce38dfd08d..b527522cd7 100644 --- a/monai/losses/image_dissimilarity.py +++ b/monai/losses/image_dissimilarity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/multi_scale.py b/monai/losses/multi_scale.py index 182cb2f7a6..5e80af30bc 100644 --- a/monai/losses/multi_scale.py +++ b/monai/losses/multi_scale.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/spatial_mask.py b/monai/losses/spatial_mask.py index 387300e507..aa232f882e 100644 --- a/monai/losses/spatial_mask.py +++ b/monai/losses/spatial_mask.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/losses/tversky.py b/monai/losses/tversky.py index 1cc0e1d8d7..ee6d7d933b 100644 --- a/monai/losses/tversky.py +++ b/monai/losses/tversky.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/__init__.py b/monai/metrics/__init__.py index 752f226b7d..d18c20f7b2 100644 --- a/monai/metrics/__init__.py +++ b/monai/metrics/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/confusion_matrix.py b/monai/metrics/confusion_matrix.py index 956ac214ff..dd1f81a4b4 100644 --- a/monai/metrics/confusion_matrix.py +++ b/monai/metrics/confusion_matrix.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/cumulative_average.py b/monai/metrics/cumulative_average.py index 985c0a914e..090d65a44c 100644 --- a/monai/metrics/cumulative_average.py +++ b/monai/metrics/cumulative_average.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/froc.py b/monai/metrics/froc.py index 011021f33b..1c3e64579d 100644 --- a/monai/metrics/froc.py +++ b/monai/metrics/froc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/hausdorff_distance.py b/monai/metrics/hausdorff_distance.py index 203abe8bb8..082311aa67 100644 --- a/monai/metrics/hausdorff_distance.py +++ b/monai/metrics/hausdorff_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/meandice.py b/monai/metrics/meandice.py index 7ccd3bd587..1e6065b59c 100644 --- a/monai/metrics/meandice.py +++ b/monai/metrics/meandice.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/metric.py b/monai/metrics/metric.py index 59c8396bc5..60dcd0b52d 100644 --- a/monai/metrics/metric.py +++ b/monai/metrics/metric.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/regression.py b/monai/metrics/regression.py index 91e602e66f..bd63134d6c 100644 --- a/monai/metrics/regression.py +++ b/monai/metrics/regression.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/rocauc.py b/monai/metrics/rocauc.py index 4c71fe6374..341d4cba2f 100644 --- a/monai/metrics/rocauc.py +++ b/monai/metrics/rocauc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/surface_distance.py b/monai/metrics/surface_distance.py index 78aeb0b18f..04eed97a5d 100644 --- a/monai/metrics/surface_distance.py +++ b/monai/metrics/surface_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/metrics/utils.py b/monai/metrics/utils.py index 1cd42ed302..ccb6d93862 100644 --- a/monai/metrics/utils.py +++ b/monai/metrics/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -28,6 +28,7 @@ def ignore_background(y_pred: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch.Tensor]): """ This function is used to remove background (the first channel) for `y_pred` and `y`. + Args: y_pred: predictions. As for classification tasks, `y_pred` should has the shape [BN] where N is larger than 1. As for segmentation tasks, @@ -35,6 +36,7 @@ def ignore_background(y_pred: Union[np.ndarray, torch.Tensor], y: Union[np.ndarr y: ground truth, the first dim is batch. """ + y = y[:, 1:] if y.shape[1] > 1 else y y_pred = y_pred[:, 1:] if y_pred.shape[1] > 1 else y_pred return y_pred, y diff --git a/monai/networks/__init__.py b/monai/networks/__init__.py index 4dec09c889..4e607dd298 100644 --- a/monai/networks/__init__.py +++ b/monai/networks/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/__init__.py b/monai/networks/blocks/__init__.py index 01a5bfca2a..0fdc944760 100644 --- a/monai/networks/blocks/__init__.py +++ b/monai/networks/blocks/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/acti_norm.py b/monai/networks/blocks/acti_norm.py index 593ca6baa7..d07d78f1ad 100644 --- a/monai/networks/blocks/acti_norm.py +++ b/monai/networks/blocks/acti_norm.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/activation.py b/monai/networks/blocks/activation.py index b136eb7f1f..1526b37056 100644 --- a/monai/networks/blocks/activation.py +++ b/monai/networks/blocks/activation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,7 +19,6 @@ def monai_mish(x, inplace: bool = False): return torch.nn.functional.mish(x, inplace=inplace) - else: def monai_mish(x, inplace: bool = False): @@ -31,7 +30,6 @@ def monai_mish(x, inplace: bool = False): def monai_swish(x, inplace: bool = False): return torch.nn.functional.silu(x, inplace=inplace) - else: def monai_swish(x, inplace: bool = False): diff --git a/monai/networks/blocks/aspp.py b/monai/networks/blocks/aspp.py index 9796ea8148..8d43530fa7 100644 --- a/monai/networks/blocks/aspp.py +++ b/monai/networks/blocks/aspp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/convolutions.py b/monai/networks/blocks/convolutions.py index e12eb6fc8f..37530668a3 100644 --- a/monai/networks/blocks/convolutions.py +++ b/monai/networks/blocks/convolutions.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/crf.py b/monai/networks/blocks/crf.py index 21da3bb74f..ccaef17679 100644 --- a/monai/networks/blocks/crf.py +++ b/monai/networks/blocks/crf.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/dints_block.py b/monai/networks/blocks/dints_block.py index 316627d774..f76e125fe0 100644 --- a/monai/networks/blocks/dints_block.py +++ b/monai/networks/blocks/dints_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/downsample.py b/monai/networks/blocks/downsample.py index 9bee4c596e..9b0d5dd4b9 100644 --- a/monai/networks/blocks/downsample.py +++ b/monai/networks/blocks/downsample.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/dynunet_block.py b/monai/networks/blocks/dynunet_block.py index 43d3c46cc9..8b22cb16a9 100644 --- a/monai/networks/blocks/dynunet_block.py +++ b/monai/networks/blocks/dynunet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/fcn.py b/monai/networks/blocks/fcn.py index 09d5d4779e..5833d4a262 100644 --- a/monai/networks/blocks/fcn.py +++ b/monai/networks/blocks/fcn.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/localnet_block.py b/monai/networks/blocks/localnet_block.py index d3b81ff494..41b76c7d4c 100644 --- a/monai/networks/blocks/localnet_block.py +++ b/monai/networks/blocks/localnet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/mlp.py b/monai/networks/blocks/mlp.py index 9f6d12594e..a1728365cf 100644 --- a/monai/networks/blocks/mlp.py +++ b/monai/networks/blocks/mlp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/patchembedding.py b/monai/networks/blocks/patchembedding.py index 492e7bf236..063a1fded1 100644 --- a/monai/networks/blocks/patchembedding.py +++ b/monai/networks/blocks/patchembedding.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -97,7 +97,6 @@ def __init__( Rearrange(f"{from_chars} -> {to_chars}", **axes_len), nn.Linear(self.patch_dim, hidden_size) ) self.position_embeddings = nn.Parameter(torch.zeros(1, self.n_patches, hidden_size)) - self.cls_token = nn.Parameter(torch.zeros(1, 1, hidden_size)) self.dropout = nn.Dropout(dropout_rate) self.trunc_normal_(self.position_embeddings, mean=0.0, std=0.02, a=-2.0, b=2.0) self.apply(self._init_weights) diff --git a/monai/networks/blocks/regunet_block.py b/monai/networks/blocks/regunet_block.py index b65f08a443..78e2598b4b 100644 --- a/monai/networks/blocks/regunet_block.py +++ b/monai/networks/blocks/regunet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/segresnet_block.py b/monai/networks/blocks/segresnet_block.py index 1ed6c08daa..ded270ab52 100644 --- a/monai/networks/blocks/segresnet_block.py +++ b/monai/networks/blocks/segresnet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/selfattention.py b/monai/networks/blocks/selfattention.py index 932475b06c..4a86cd84bc 100644 --- a/monai/networks/blocks/selfattention.py +++ b/monai/networks/blocks/selfattention.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/squeeze_and_excitation.py b/monai/networks/blocks/squeeze_and_excitation.py index 46cd48d6aa..a9ac57aa4f 100644 --- a/monai/networks/blocks/squeeze_and_excitation.py +++ b/monai/networks/blocks/squeeze_and_excitation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/transformerblock.py b/monai/networks/blocks/transformerblock.py index 5ccc2090b4..616d84e067 100644 --- a/monai/networks/blocks/transformerblock.py +++ b/monai/networks/blocks/transformerblock.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/unetr_block.py b/monai/networks/blocks/unetr_block.py index ccc055e889..a9d871a644 100644 --- a/monai/networks/blocks/unetr_block.py +++ b/monai/networks/blocks/unetr_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/upsample.py b/monai/networks/blocks/upsample.py index a6aa13dde4..6a8498fe6f 100644 --- a/monai/networks/blocks/upsample.py +++ b/monai/networks/blocks/upsample.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/blocks/warp.py b/monai/networks/blocks/warp.py index e9cc908464..2cb349f8f0 100644 --- a/monai/networks/blocks/warp.py +++ b/monai/networks/blocks/warp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/__init__.py b/monai/networks/layers/__init__.py index b6c13472c4..5115c00af3 100644 --- a/monai/networks/layers/__init__.py +++ b/monai/networks/layers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/convutils.py b/monai/networks/layers/convutils.py index 9aa11fa7d0..5efb6e792f 100644 --- a/monai/networks/layers/convutils.py +++ b/monai/networks/layers/convutils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/factories.py b/monai/networks/layers/factories.py index d4de08fc50..6379f49449 100644 --- a/monai/networks/layers/factories.py +++ b/monai/networks/layers/factories.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/filtering.py b/monai/networks/layers/filtering.py index 3b2214d59a..bbf925eba9 100644 --- a/monai/networks/layers/filtering.py +++ b/monai/networks/layers/filtering.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/gmm.py b/monai/networks/layers/gmm.py index 3091f95458..eb9a3f91e4 100644 --- a/monai/networks/layers/gmm.py +++ b/monai/networks/layers/gmm.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/simplelayers.py b/monai/networks/layers/simplelayers.py index 0f5c20953e..b17eae6c5b 100644 --- a/monai/networks/layers/simplelayers.py +++ b/monai/networks/layers/simplelayers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/spatial_transforms.py b/monai/networks/layers/spatial_transforms.py index 6b5acb166a..01e45b2e67 100644 --- a/monai/networks/layers/spatial_transforms.py +++ b/monai/networks/layers/spatial_transforms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/layers/utils.py b/monai/networks/layers/utils.py index 380a77552c..42fac58716 100644 --- a/monai/networks/layers/utils.py +++ b/monai/networks/layers/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/__init__.py b/monai/networks/nets/__init__.py index 7e730da1a1..22fcef4903 100644 --- a/monai/networks/nets/__init__.py +++ b/monai/networks/nets/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/ahnet.py b/monai/networks/nets/ahnet.py index 21e3c33bf3..b481374aa1 100644 --- a/monai/networks/nets/ahnet.py +++ b/monai/networks/nets/ahnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/autoencoder.py b/monai/networks/nets/autoencoder.py index f4a0451dc7..75edde70eb 100644 --- a/monai/networks/nets/autoencoder.py +++ b/monai/networks/nets/autoencoder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/basic_unet.py b/monai/networks/nets/basic_unet.py index 4a72dcdd9a..1e46846576 100644 --- a/monai/networks/nets/basic_unet.py +++ b/monai/networks/nets/basic_unet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/classifier.py b/monai/networks/nets/classifier.py index a1f913ea23..7f4e43eedb 100644 --- a/monai/networks/nets/classifier.py +++ b/monai/networks/nets/classifier.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/densenet.py b/monai/networks/nets/densenet.py index 59576f5dd4..8fb2c269ab 100644 --- a/monai/networks/nets/densenet.py +++ b/monai/networks/nets/densenet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/dints.py b/monai/networks/nets/dints.py index eb79145fad..c024d6e0f1 100644 --- a/monai/networks/nets/dints.py +++ b/monai/networks/nets/dints.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/dynunet.py b/monai/networks/nets/dynunet.py index 4cd3046261..337a99acd8 100644 --- a/monai/networks/nets/dynunet.py +++ b/monai/networks/nets/dynunet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -31,13 +31,13 @@ class DynUNetSkipLayer(nn.Module): forward passes of the network. """ - heads: List[torch.Tensor] + heads: Optional[List[torch.Tensor]] - def __init__(self, index, heads, downsample, upsample, super_head, next_layer): + def __init__(self, index, downsample, upsample, next_layer, heads=None, super_head=None): super().__init__() self.downsample = downsample - self.upsample = upsample self.next_layer = next_layer + self.upsample = upsample self.super_head = super_head self.heads = heads self.index = index @@ -46,8 +46,8 @@ def forward(self, x): downout = self.downsample(x) nextout = self.next_layer(downout) upout = self.upsample(nextout, downout) - - self.heads[self.index] = self.super_head(upout) + if self.super_head is not None and self.heads is not None and self.index > 0: + self.heads[self.index - 1] = self.super_head(upout) return upout @@ -79,6 +79,8 @@ class DynUNet(nn.Module): For example, if `strides=((1, 2, 4), 2, 1, 1)`, the minimal spatial size of the input is `(8, 16, 32)`, and the spatial size of the output is `(8, 8, 8)`. + For backwards compatibility with old weights, please set `strict=False` when calling `load_state_dict`. + Usage example with medical segmentation decathlon dataset is available at: https://github.com/Project-MONAI/tutorials/tree/master/modules/dynunet_pipeline. @@ -100,18 +102,16 @@ class DynUNet(nn.Module): norm_name: feature normalization type and arguments. Defaults to ``INSTANCE``. act_name: activation layer type and arguments. Defaults to ``leakyrelu``. deep_supervision: whether to add deep supervision head before output. Defaults to ``False``. - If ``True``, in training mode, the forward function will output not only the last feature - map, but also the previous feature maps that come from the intermediate up sample layers. + If ``True``, in training mode, the forward function will output not only the final feature map + (from `output_block`), but also the feature maps that come from the intermediate up sample layers. In order to unify the return type (the restriction of TorchScript), all intermediate - feature maps are interpolated into the same size as the last feature map and stacked together + feature maps are interpolated into the same size as the final feature map and stacked together (with a new dimension in the first axis)into one single tensor. - For instance, if there are three feature maps with shapes: (1, 2, 32, 24), (1, 2, 16, 12) and - (1, 2, 8, 6). The last two will be interpolated into (1, 2, 32, 24), and the stacked tensor - will has the shape (1, 3, 2, 8, 6). + For instance, if there are two intermediate feature maps with shapes: (1, 2, 16, 12) and + (1, 2, 8, 6), and the final feature map has the shape (1, 2, 32, 24), then all intermediate feature maps + will be interpolated into (1, 2, 32, 24), and the stacked tensor will has the shape (1, 3, 2, 32, 24). When calculating the loss, you can use torch.unbind to get all feature maps can compute the loss one by one with the ground truth, then do a weighted average for all losses to achieve the final loss. - (To be added: a corresponding tutorial link) - deep_supr_num: number of feature maps that will output during deep supervision head. The value should be larger than 0 and less than the number of up sample layers. Defaults to 1. @@ -160,16 +160,17 @@ def __init__( self.upsamples = self.get_upsamples() self.output_block = self.get_output_block(0) self.deep_supervision = deep_supervision - self.deep_supervision_heads = self.get_deep_supervision_heads() self.deep_supr_num = deep_supr_num + # initialize the typed list of supervision head outputs so that Torchscript can recognize what's going on + self.heads: List[torch.Tensor] = [torch.rand(1)] * self.deep_supr_num + if self.deep_supervision: + self.deep_supervision_heads = self.get_deep_supervision_heads() + self.check_deep_supr_num() + self.apply(self.initialize_weights) self.check_kernel_stride() - self.check_deep_supr_num() - # initialize the typed list of supervision head outputs so that Torchscript can recognize what's going on - self.heads: List[torch.Tensor] = [torch.rand(1)] * (len(self.deep_supervision_heads) + 1) - - def create_skips(index, downsamples, upsamples, superheads, bottleneck): + def create_skips(index, downsamples, upsamples, bottleneck, superheads=None): """ Construct the UNet topology as a sequence of skip layers terminating with the bottleneck layer. This is done recursively from the top down since a recursive nn.Module subclass is being used to be compatible @@ -180,30 +181,50 @@ def create_skips(index, downsamples, upsamples, superheads, bottleneck): if len(downsamples) != len(upsamples): raise ValueError(f"{len(downsamples)} != {len(upsamples)}") - if (len(downsamples) - len(superheads)) not in (1, 0): - raise ValueError(f"{len(downsamples)}-(0,1) != {len(superheads)}") if len(downsamples) == 0: # bottom of the network, pass the bottleneck block return bottleneck + + if superheads is None: + next_layer = create_skips(1 + index, downsamples[1:], upsamples[1:], bottleneck) + return DynUNetSkipLayer(index, downsample=downsamples[0], upsample=upsamples[0], next_layer=next_layer) + + super_head_flag = False if index == 0: # don't associate a supervision head with self.input_block - current_head, rest_heads = nn.Identity(), superheads - elif not self.deep_supervision: # bypass supervision heads by passing nn.Identity in place of a real one - current_head, rest_heads = nn.Identity(), superheads[1:] + rest_heads = superheads else: - current_head, rest_heads = superheads[0], superheads[1:] + if len(superheads) > 0: + super_head_flag = True + rest_heads = superheads[1:] + else: + rest_heads = nn.ModuleList() # create the next layer down, this will stop at the bottleneck layer - next_layer = create_skips(1 + index, downsamples[1:], upsamples[1:], rest_heads, bottleneck) - - return DynUNetSkipLayer(index, self.heads, downsamples[0], upsamples[0], current_head, next_layer) - - self.skip_layers = create_skips( - 0, - [self.input_block] + list(self.downsamples), - self.upsamples[::-1], - self.deep_supervision_heads, - self.bottleneck, - ) + next_layer = create_skips(1 + index, downsamples[1:], upsamples[1:], bottleneck, superheads=rest_heads) + if super_head_flag: + return DynUNetSkipLayer( + index, + downsample=downsamples[0], + upsample=upsamples[0], + next_layer=next_layer, + heads=self.heads, + super_head=superheads[0], + ) + + return DynUNetSkipLayer(index, downsample=downsamples[0], upsample=upsamples[0], next_layer=next_layer) + + if not self.deep_supervision: + self.skip_layers = create_skips( + 0, [self.input_block] + list(self.downsamples), self.upsamples[::-1], self.bottleneck + ) + else: + self.skip_layers = create_skips( + 0, + [self.input_block] + list(self.downsamples), + self.upsamples[::-1], + self.bottleneck, + superheads=self.deep_supervision_heads, + ) def check_kernel_stride(self): kernels, strides = self.kernel_size, self.strides @@ -242,8 +263,7 @@ def forward(self, x): out = self.output_block(out) if self.training and self.deep_supervision: out_all = [out] - feature_maps = self.heads[1 : self.deep_supr_num + 1] - for feature_map in feature_maps: + for feature_map in self.heads: out_all.append(interpolate(feature_map, out.shape[2:])) return torch.stack(out_all, dim=1) return out @@ -334,7 +354,7 @@ def get_module_list( return nn.ModuleList(layers) def get_deep_supervision_heads(self): - return nn.ModuleList([self.get_output_block(i + 1) for i in range(len(self.upsamples) - 1)]) + return nn.ModuleList([self.get_output_block(i + 1) for i in range(self.deep_supr_num)]) @staticmethod def initialize_weights(module): diff --git a/monai/networks/nets/efficientnet.py b/monai/networks/nets/efficientnet.py index c35b46fd75..fa5efbc4ef 100644 --- a/monai/networks/nets/efficientnet.py +++ b/monai/networks/nets/efficientnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/fullyconnectednet.py b/monai/networks/nets/fullyconnectednet.py index 19197bd58d..810c07431b 100644 --- a/monai/networks/nets/fullyconnectednet.py +++ b/monai/networks/nets/fullyconnectednet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/generator.py b/monai/networks/nets/generator.py index 434629f4e8..a69cae4d7b 100644 --- a/monai/networks/nets/generator.py +++ b/monai/networks/nets/generator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/highresnet.py b/monai/networks/nets/highresnet.py index 2937cda32a..95c0c758af 100644 --- a/monai/networks/nets/highresnet.py +++ b/monai/networks/nets/highresnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/milmodel.py b/monai/networks/nets/milmodel.py index 75cd9cf53e..2f4afaffbe 100644 --- a/monai/networks/nets/milmodel.py +++ b/monai/networks/nets/milmodel.py @@ -1,3 +1,14 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from typing import Dict, Optional, Union, cast import torch @@ -11,23 +22,24 @@ class MILModel(nn.Module): """ Multiple Instance Learning (MIL) model, with a backbone classification model. - Currently, it only works for 2D images, typical use case is for classification of the + Currently, it only works for 2D images, a typical use case is for classification of the digital pathology whole slide images. The expected shape of input data is `[B, N, C, H, W]`, - where `B` is the batch_size of PyTorch Dataloader and `N` is the number of the instances + where `B` is the batch_size of PyTorch Dataloader and `N` is the number of instances extracted from every original image in the batch. A tutorial example is available at: https://github.com/Project-MONAI/tutorials/tree/master/pathology/multiple_instance_learning. Args: num_classes: number of output classes. - mil_mode: MIL algorithm, available values: - "mean" - average features from all instances, equivalent to pure CNN (non MIL). - "max - retain only the instance with the max probability for loss calculation. - "att" - attention based MIL https://arxiv.org/abs/1802.04712. - "att_trans" - transformer MIL https://arxiv.org/abs/2111.01556. - "att_trans_pyramid" - transformer pyramid MIL https://arxiv.org/abs/2111.01556. - Defaults to ``att``. + mil_mode: MIL algorithm, available values (Defaults to ``"att"``): + + - ``"mean"`` - average features from all instances, equivalent to pure CNN (non MIL). + - ``"max"`` - retain only the instance with the max probability for loss calculation. + - ``"att"`` - attention based MIL https://arxiv.org/abs/1802.04712. + - ``"att_trans"`` - transformer MIL https://arxiv.org/abs/2111.01556. + - ``"att_trans_pyramid"`` - transformer pyramid MIL https://arxiv.org/abs/2111.01556. + pretrained: init backbone with pretrained weights, defaults to ``True``. - backbone: Backbone classifier CNN (either None, nn.Module that returns features, + backbone: Backbone classifier CNN (either ``None``, a ``nn.Module`` that returns features, or a string name of a torchvision model). Defaults to ``None``, in which case ResNet50 is used. backbone_num_features: Number of output features of the backbone CNN diff --git a/monai/networks/nets/netadapter.py b/monai/networks/nets/netadapter.py index 68d9bbe648..425c1d5820 100644 --- a/monai/networks/nets/netadapter.py +++ b/monai/networks/nets/netadapter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/regressor.py b/monai/networks/nets/regressor.py index bc8feb7527..0a1e6258a9 100644 --- a/monai/networks/nets/regressor.py +++ b/monai/networks/nets/regressor.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/regunet.py b/monai/networks/nets/regunet.py index ead12382eb..174431cc3c 100644 --- a/monai/networks/nets/regunet.py +++ b/monai/networks/nets/regunet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/resnet.py b/monai/networks/nets/resnet.py index fc400895df..a263c8e8b3 100644 --- a/monai/networks/nets/resnet.py +++ b/monai/networks/nets/resnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -154,6 +154,7 @@ class ResNet(nn.Module): ResNet based on: `Deep Residual Learning for Image Recognition `_ and `Can Spatiotemporal 3D CNNs Retrace the History of 2D CNNs and ImageNet? `_. Adapted from ``_. + Args: block: which ResNet block to use, either Basic or Bottleneck. layers: how many layers to use. diff --git a/monai/networks/nets/segresnet.py b/monai/networks/nets/segresnet.py index 37d47a0d31..d2c45dd3a3 100644 --- a/monai/networks/nets/segresnet.py +++ b/monai/networks/nets/segresnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/senet.py b/monai/networks/nets/senet.py index a58c2adb51..a85d32ba5a 100644 --- a/monai/networks/nets/senet.py +++ b/monai/networks/nets/senet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/torchvision_fc.py b/monai/networks/nets/torchvision_fc.py index 21c6b0325a..e93019d050 100644 --- a/monai/networks/nets/torchvision_fc.py +++ b/monai/networks/nets/torchvision_fc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/transchex.py b/monai/networks/nets/transchex.py index 481ed2ec2e..7b3afe3e5e 100644 --- a/monai/networks/nets/transchex.py +++ b/monai/networks/nets/transchex.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/unet.py b/monai/networks/nets/unet.py index e1ebaaf6f9..21259936e7 100644 --- a/monai/networks/nets/unet.py +++ b/monai/networks/nets/unet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/unetr.py b/monai/networks/nets/unetr.py index b75bc15892..b22f0584a2 100644 --- a/monai/networks/nets/unetr.py +++ b/monai/networks/nets/unetr.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/varautoencoder.py b/monai/networks/nets/varautoencoder.py index b4ef8be93d..7386883124 100644 --- a/monai/networks/nets/varautoencoder.py +++ b/monai/networks/nets/varautoencoder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/vit.py b/monai/networks/nets/vit.py index 2707e5ad1d..62e92603ab 100644 --- a/monai/networks/nets/vit.py +++ b/monai/networks/nets/vit.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/vitautoenc.py b/monai/networks/nets/vitautoenc.py index a08b10d00d..3ec89488ff 100644 --- a/monai/networks/nets/vitautoenc.py +++ b/monai/networks/nets/vitautoenc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/nets/vnet.py b/monai/networks/nets/vnet.py index 1b1d3bfba7..ede5f688aa 100644 --- a/monai/networks/nets/vnet.py +++ b/monai/networks/nets/vnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/networks/utils.py b/monai/networks/utils.py index ef0cff0eed..f7fd2e2956 100644 --- a/monai/networks/utils.py +++ b/monai/networks/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -458,11 +458,13 @@ def convert_to_torchscript( device: target device to verify the model, if None, use CUDA if available. rtol: the relative tolerance when comparing the outputs of PyTorch model and TorchScript model. atol: the absolute tolerance when comparing the outputs of PyTorch model and TorchScript model. + kwargs: other arguments except `obj` for `torch.jit.script()` to convert model, for more details: + https://pytorch.org/docs/master/generated/torch.jit.script.html. """ model.eval() with torch.no_grad(): - script_module = torch.jit.script(model) + script_module = torch.jit.script(model, **kwargs) if filename_or_obj is not None: if not pytorch_after(1, 7): torch.jit.save(m=script_module, f=filename_or_obj) diff --git a/monai/optimizers/__init__.py b/monai/optimizers/__init__.py index e53aa8d468..8ce5d3f925 100644 --- a/monai/optimizers/__init__.py +++ b/monai/optimizers/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,5 +10,6 @@ # limitations under the License. from .lr_finder import LearningRateFinder +from .lr_scheduler import ExponentialLR, LinearLR, WarmupCosineSchedule from .novograd import Novograd from .utils import generate_param_groups diff --git a/monai/optimizers/lr_finder.py b/monai/optimizers/lr_finder.py index 9cd66c226d..ce092d33ab 100644 --- a/monai/optimizers/lr_finder.py +++ b/monai/optimizers/lr_finder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -146,30 +146,30 @@ class LearningRateFinder: and what is the optimal learning rate. Example (fastai approach): - >>> lr_finder = LearningRateFinder(net, optimizer, criterion) - >>> lr_finder.range_test(data_loader, end_lr=100, num_iter=100) - >>> lr_finder.get_steepest_gradient() - >>> lr_finder.plot() # to inspect the loss-learning rate graph + >>> lr_finder = LearningRateFinder(net, optimizer, criterion) + >>> lr_finder.range_test(data_loader, end_lr=100, num_iter=100) + >>> lr_finder.get_steepest_gradient() + >>> lr_finder.plot() # to inspect the loss-learning rate graph Example (Leslie Smith's approach): - >>> lr_finder = LearningRateFinder(net, optimizer, criterion) - >>> lr_finder.range_test(train_loader, val_loader=val_loader, end_lr=1, num_iter=100, step_mode="linear") + >>> lr_finder = LearningRateFinder(net, optimizer, criterion) + >>> lr_finder.range_test(train_loader, val_loader=val_loader, end_lr=1, num_iter=100, step_mode="linear") Gradient accumulation is supported; example: - >>> train_data = ... # prepared dataset - >>> desired_bs, real_bs = 32, 4 # batch size - >>> accumulation_steps = desired_bs // real_bs # required steps for accumulation - >>> data_loader = torch.utils.data.DataLoader(train_data, batch_size=real_bs, shuffle=True) - >>> acc_lr_finder = LearningRateFinder(net, optimizer, criterion) - >>> acc_lr_finder.range_test(data_loader, end_lr=10, num_iter=100, accumulation_steps=accumulation_steps) + >>> train_data = ... # prepared dataset + >>> desired_bs, real_bs = 32, 4 # batch size + >>> accumulation_steps = desired_bs // real_bs # required steps for accumulation + >>> data_loader = torch.utils.data.DataLoader(train_data, batch_size=real_bs, shuffle=True) + >>> acc_lr_finder = LearningRateFinder(net, optimizer, criterion) + >>> acc_lr_finder.range_test(data_loader, end_lr=10, num_iter=100, accumulation_steps=accumulation_steps) By default, image will be extracted from data loader with x["image"] and x[0], depending on whether batch data is a dictionary or not (and similar behaviour for extracting the label). If your data loader returns something other than this, pass a callable function to extract it, e.g.: - >>> image_extractor = lambda x: x["input"] - >>> label_extractor = lambda x: x[100] - >>> lr_finder = LearningRateFinder(net, optimizer, criterion) - >>> lr_finder.range_test(train_loader, val_loader, image_extractor, label_extractor) + >>> image_extractor = lambda x: x["input"] + >>> label_extractor = lambda x: x[100] + >>> lr_finder = LearningRateFinder(net, optimizer, criterion) + >>> lr_finder.range_test(train_loader, val_loader, image_extractor, label_extractor) References: Modified from: https://github.com/davidtvs/pytorch-lr-finder. diff --git a/monai/optimizers/lr_scheduler.py b/monai/optimizers/lr_scheduler.py index 5ad52c5286..83412c61ea 100644 --- a/monai/optimizers/lr_scheduler.py +++ b/monai/optimizers/lr_scheduler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/optimizers/novograd.py b/monai/optimizers/novograd.py index 6d14a055fb..07a6aff90a 100644 --- a/monai/optimizers/novograd.py +++ b/monai/optimizers/novograd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,7 +20,7 @@ class Novograd(Optimizer): Novograd based on `Stochastic Gradient Methods with Layer-wise Adaptive Moments for Training of Deep Networks `_. The code is adapted from the implementations in `Jasper for PyTorch - `_, + `_, and `OpenSeq2Seq `_. Args: diff --git a/monai/optimizers/utils.py b/monai/optimizers/utils.py index 08949912d7..1a040927d8 100644 --- a/monai/optimizers/utils.py +++ b/monai/optimizers/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 3f7e53f514..c9cd1b4e0d 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -178,6 +178,9 @@ RandStdShiftIntensityd, RandStdShiftIntensityD, RandStdShiftIntensityDict, + SavitzkyGolaySmoothd, + SavitzkyGolaySmoothD, + SavitzkyGolaySmoothDict, ScaleIntensityd, ScaleIntensityD, ScaleIntensityDict, @@ -274,10 +277,14 @@ VoteEnsembled, VoteEnsembleDict, ) -from .smooth_field.array import RandSmoothFieldAdjustContrast, RandSmoothFieldAdjustIntensity, SmoothField -from .smooth_field.dictionary import RandSmoothFieldAdjustContrastd, RandSmoothFieldAdjustIntensityd +from .smooth_field.array import ( + RandSmoothDeform, + RandSmoothFieldAdjustContrast, + RandSmoothFieldAdjustIntensity, + SmoothField, +) +from .smooth_field.dictionary import RandSmoothDeformd, RandSmoothFieldAdjustContrastd, RandSmoothFieldAdjustIntensityd from .spatial.array import ( - AddCoordinateChannels, Affine, AffineGrid, Flip, @@ -302,9 +309,6 @@ Zoom, ) from .spatial.dictionary import ( - AddCoordinateChannelsd, - AddCoordinateChannelsD, - AddCoordinateChannelsDict, Affined, AffineD, AffineDict, @@ -363,6 +367,7 @@ from .transform import MapTransform, Randomizable, RandomizableTransform, ThreadUnsafe, Transform, apply_transform from .utility.array import ( AddChannel, + AddCoordinateChannels, AddExtremePointsChannel, AsChannelFirst, AsChannelLast, @@ -398,6 +403,9 @@ AddChanneld, AddChannelD, AddChannelDict, + AddCoordinateChannelsd, + AddCoordinateChannelsD, + AddCoordinateChannelsDict, AddExtremePointsChanneld, AddExtremePointsChannelD, AddExtremePointsChannelDict, diff --git a/monai/transforms/adaptors.py b/monai/transforms/adaptors.py index 434d1f1c05..92fd11cf79 100644 --- a/monai/transforms/adaptors.py +++ b/monai/transforms/adaptors.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index d0d14bcc2d..165d9b732f 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -256,12 +256,9 @@ def inverse(self, data): # and then remove the OneOf transform self.pop_transform(data, key) if index is None: - raise RuntimeError("No invertible transforms have been applied") + # no invertible transforms have been applied + return data - # if applied transform is not InvertibleTransform, throw error _transform = self.transforms[index] - if not isinstance(_transform, InvertibleTransform): - raise RuntimeError(f"Applied OneOf transform is not invertible (applied index: {index}).") - # apply the inverse - return _transform.inverse(data) + return _transform.inverse(data) if isinstance(_transform, InvertibleTransform) else data diff --git a/monai/transforms/croppad/__init__.py b/monai/transforms/croppad/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/croppad/__init__.py +++ b/monai/transforms/croppad/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index ba503aedd1..faf5306ce0 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/croppad/batch.py b/monai/transforms/croppad/batch.py index 0cdaa3a2d8..6edaf4622d 100644 --- a/monai/transforms/croppad/batch.py +++ b/monai/transforms/croppad/batch.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index fd7fabe5a1..03c75705ab 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/intensity/__init__.py b/monai/transforms/intensity/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/intensity/__init__.py +++ b/monai/transforms/intensity/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 31249d547b..33da679df5 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -129,10 +129,10 @@ class RandRicianNoise(RandomizableTransform): """ Add Rician noise to image. Rician noise in MRI is the result of performing a magnitude operation on complex - data with Gaussian noise of the same variance in both channels, as described in `Noise in Magnitude - Magnetic Resonance Images `_. This transform is adapted from - `DIPY`_. See also: `The rician distribution of noisy mri data - `_. + data with Gaussian noise of the same variance in both channels, as described in + `Noise in Magnitude Magnetic Resonance Images `_. + This transform is adapted from `DIPY `_. + See also: `The rician distribution of noisy mri data `_. Args: prob: Probability to add Rician noise. @@ -443,6 +443,7 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: ValueError: When ``self.minv=None`` or ``self.maxv=None`` and ``self.factor=None``. Incompatible values. """ + ret: NdarrayOrTensor if self.minv is not None or self.maxv is not None: if self.channel_wise: out = [rescale_array(d, self.minv, self.maxv, dtype=self.dtype) for d in img] @@ -591,7 +592,7 @@ def __call__(self, img: NdarrayOrTensor, randomize: bool = True) -> NdarrayOrTen axis=0, ) img_np, *_ = convert_data_type(img, np.ndarray) - out = img_np * np.exp(_bias_fields) + out: NdarrayOrTensor = img_np * np.exp(_bias_fields) out, *_ = convert_to_dst_type(src=out, dst=img, dtype=self.dtype or img.dtype) return out @@ -608,8 +609,8 @@ class NormalizeIntensity(Transform): subtrahend: the amount to subtract by (usually the mean). divisor: the amount to divide by (usually the standard deviation). nonzero: whether only normalize non-zero values. - channel_wise: if using calculated mean and std, calculate on each channel separately - or calculate on the entire image directly. + channel_wise: if True, calculate on each channel separately, otherwise, calculate on + the entire image directly. default to False. dtype: output data type, if None, same as input image. defaults to float32. """ @@ -919,6 +920,8 @@ class ScaleIntensityRangePercentiles(Transform): b_max: intensity target range max. clip: whether to perform clip after scaling. relative: whether to scale to the corresponding percentiles of [b_min, b_max]. + channel_wise: if True, compute intensity percentile and normalize every channel separately. + default to False. dtype: output data type, if None, same as input image. defaults to float32. """ @@ -932,6 +935,7 @@ def __init__( b_max: Optional[float], clip: bool = False, relative: bool = False, + channel_wise: bool = False, dtype: DtypeLike = np.float32, ) -> None: if lower < 0.0 or lower > 100.0: @@ -944,12 +948,10 @@ def __init__( self.b_max = b_max self.clip = clip self.relative = relative + self.channel_wise = channel_wise self.dtype = dtype - def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: - """ - Apply the transform to `img`. - """ + def _normalize(self, img: NdarrayOrTensor) -> NdarrayOrTensor: a_min: float = percentile(img, self.lower) # type: ignore a_max: float = percentile(img, self.upper) # type: ignore b_min = self.b_min @@ -967,6 +969,18 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: img = scalar(img) return img + def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: + """ + Apply the transform to `img`. + """ + if self.channel_wise: + for i, d in enumerate(img): + img[i] = self._normalize(img=d) # type: ignore + else: + img = self._normalize(img=img) + + return img + class MaskIntensity(Transform): """ diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py index fa2de4c7b8..48a259dbc5 100644 --- a/monai/transforms/intensity/dictionary.py +++ b/monai/transforms/intensity/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -44,6 +44,7 @@ RandScaleIntensity, RandShiftIntensity, RandStdShiftIntensity, + SavitzkyGolaySmooth, ScaleIntensity, ScaleIntensityRange, ScaleIntensityRangePercentiles, @@ -73,6 +74,7 @@ "RandAdjustContrastd", "ScaleIntensityRangePercentilesd", "MaskIntensityd", + "SavitzkyGolaySmoothd", "GaussianSmoothd", "RandGaussianSmoothd", "GaussianSharpend", @@ -115,6 +117,8 @@ "ScaleIntensityRangePercentilesDict", "MaskIntensityD", "MaskIntensityDict", + "SavitzkyGolaySmoothD", + "SavitzkyGolaySmoothDict", "GaussianSmoothD", "GaussianSmoothDict", "RandGaussianSmoothD", @@ -655,8 +659,8 @@ class NormalizeIntensityd(MapTransform): subtrahend: the amount to subtract by (usually the mean) divisor: the amount to divide by (usually the standard deviation) nonzero: whether only normalize non-zero values. - channel_wise: if using calculated mean and std, calculate on each channel separately - or calculate on the entire image directly. + channel_wise: if True, calculate on each channel separately, otherwise, calculate on + the entire image directly. default to False. dtype: output data type, if None, same as input image. defaults to float32. allow_missing_keys: don't raise exception if key is missing. """ @@ -844,6 +848,8 @@ class ScaleIntensityRangePercentilesd(MapTransform): b_max: intensity target range max. clip: whether to perform clip after scaling. relative: whether to scale to the corresponding percentiles of [b_min, b_max] + channel_wise: if True, compute intensity percentile and normalize every channel separately. + default to False. dtype: output data type, if None, same as input image. defaults to float32. allow_missing_keys: don't raise exception if key is missing. """ @@ -859,11 +865,12 @@ def __init__( b_max: Optional[float], clip: bool = False, relative: bool = False, + channel_wise: bool = False, dtype: DtypeLike = np.float32, allow_missing_keys: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) - self.scaler = ScaleIntensityRangePercentiles(lower, upper, b_min, b_max, clip, relative, dtype) + self.scaler = ScaleIntensityRangePercentiles(lower, upper, b_min, b_max, clip, relative, channel_wise, dtype) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) @@ -914,6 +921,43 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N return d +class SavitzkyGolaySmoothd(MapTransform): + """ + Dictionary-based wrapper of :py:class:`monai.transforms.SavitzkyGolaySmooth`. + + Args: + keys: keys of the corresponding items to be transformed. + See also: :py:class:`monai.transforms.compose.MapTransform` + window_length: length of the filter window, must be a positive odd integer. + order: order of the polynomial to fit to each window, must be less than ``window_length``. + axis: optional axis along which to apply the filter kernel. Default 1 (first spatial dimension). + mode: optional padding mode, passed to convolution class. ``'zeros'``, ``'reflect'``, ``'replicate'`` + or ``'circular'``. default: ``'zeros'``. See ``torch.nn.Conv1d()`` for more information. + allow_missing_keys: don't raise exception if key is missing. + + """ + + backend = SavitzkyGolaySmooth.backend + + def __init__( + self, + keys: KeysCollection, + window_length: int, + order: int, + axis: int = 1, + mode: str = "zeros", + allow_missing_keys: bool = False, + ) -> None: + super().__init__(keys, allow_missing_keys) + self.converter = SavitzkyGolaySmooth(window_length=window_length, order=order, axis=axis, mode=mode) + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = dict(data) + for key in self.key_iterator(d): + d[key] = self.converter(d[key]) + return d + + class GaussianSmoothd(MapTransform): """ Dictionary-based wrapper of :py:class:`monai.transforms.GaussianSmooth`. @@ -1623,6 +1667,7 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N RandAdjustContrastD = RandAdjustContrastDict = RandAdjustContrastd ScaleIntensityRangePercentilesD = ScaleIntensityRangePercentilesDict = ScaleIntensityRangePercentilesd MaskIntensityD = MaskIntensityDict = MaskIntensityd +SavitzkyGolaySmoothD = SavitzkyGolaySmoothDict = SavitzkyGolaySmoothd GaussianSmoothD = GaussianSmoothDict = GaussianSmoothd RandGaussianSmoothD = RandGaussianSmoothDict = RandGaussianSmoothd GaussianSharpenD = GaussianSharpenDict = GaussianSharpend diff --git a/monai/transforms/inverse.py b/monai/transforms/inverse.py index 90ceba7489..c8bfeeca05 100644 --- a/monai/transforms/inverse.py +++ b/monai/transforms/inverse.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/inverse_batch_transform.py b/monai/transforms/inverse_batch_transform.py index 9d9d8c2a5d..ae0317cea8 100644 --- a/monai/transforms/inverse_batch_transform.py +++ b/monai/transforms/inverse_batch_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/io/__init__.py b/monai/transforms/io/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/io/__init__.py +++ b/monai/transforms/io/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/io/array.py b/monai/transforms/io/array.py index cd2d93a0c7..19fafbcbf4 100644 --- a/monai/transforms/io/array.py +++ b/monai/transforms/io/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -204,8 +204,10 @@ def __call__(self, filename: Union[Sequence[PathLike], PathLike], reader: Option break if img is None or reader is None: + if isinstance(filename, tuple) and len(filename) == 1: + filename = filename[0] raise RuntimeError( - f"can not find a suitable reader for file: {filename}.\n" + f"cannot find a suitable reader for file: {filename}.\n" " Please install the reader libraries, see also the installation instructions:\n" " https://docs.monai.io/en/latest/installation.html#installing-the-recommended-dependencies.\n" f" The current registered: {self.readers}.\n" diff --git a/monai/transforms/io/dictionary.py b/monai/transforms/io/dictionary.py index f714dd2831..cb73567afb 100644 --- a/monai/transforms/io/dictionary.py +++ b/monai/transforms/io/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/nvtx.py b/monai/transforms/nvtx.py index 6dd5c3b0a3..f00145efbc 100644 --- a/monai/transforms/nvtx.py +++ b/monai/transforms/nvtx.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/post/__init__.py b/monai/transforms/post/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/post/__init__.py +++ b/monai/transforms/post/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/post/array.py b/monai/transforms/post/array.py index 5dca7df3f6..c5fe05d220 100644 --- a/monai/transforms/post/array.py +++ b/monai/transforms/post/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -405,7 +405,7 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: raise NotImplementedError(f"{self.__class__} can not handle data of type {type(img)}.") if isinstance(img, torch.Tensor): - if hasattr(torch, "isin"): + if hasattr(torch, "isin"): # `isin` is new in torch 1.10.0 appl_lbls = torch.as_tensor(self.applied_labels, device=img.device) return torch.where(torch.isin(img, appl_lbls), img, torch.tensor(0.0).to(img)) else: diff --git a/monai/transforms/post/dictionary.py b/monai/transforms/post/dictionary.py index 9d6173ac80..9d7c9652bf 100644 --- a/monai/transforms/post/dictionary.py +++ b/monai/transforms/post/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/smooth_field/__init__.py b/monai/transforms/smooth_field/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/smooth_field/__init__.py +++ b/monai/transforms/smooth_field/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/smooth_field/array.py b/monai/transforms/smooth_field/array.py index b8016dd3fd..31ce76e5b5 100644 --- a/monai/transforms/smooth_field/array.py +++ b/monai/transforms/smooth_field/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -14,90 +14,163 @@ from typing import Any, Optional, Sequence, Union import numpy as np +import torch +from torch.nn.functional import grid_sample, interpolate import monai -from monai.transforms.spatial.array import Resize -from monai.transforms.transform import Randomizable, RandomizableTransform, Transform -from monai.transforms.utils import rescale_array -from monai.utils import InterpolateMode, ensure_tuple +from monai.config.type_definitions import NdarrayOrTensor +from monai.transforms.transform import Randomizable, RandomizableTransform +from monai.transforms.utils_pytorch_numpy_unification import moveaxis +from monai.utils import GridSampleMode, GridSamplePadMode, InterpolateMode from monai.utils.enums import TransformBackends -from monai.utils.type_conversion import convert_to_dst_type +from monai.utils.module import look_up_option, pytorch_after +from monai.utils.type_conversion import convert_to_dst_type, convert_to_tensor -__all__ = ["SmoothField", "RandSmoothFieldAdjustContrast", "RandSmoothFieldAdjustIntensity"] +__all__ = ["SmoothField", "RandSmoothFieldAdjustContrast", "RandSmoothFieldAdjustIntensity", "RandSmoothDeform"] class SmoothField(Randomizable): """ - Generate a smooth field array by defining a smaller randomized field and then resizing to the desired size. This - exploits interpolation to create a smoothly varying field used for other applications. + Generate a smooth field array by defining a smaller randomized field and then reinterpolating to the desired size. + + This exploits interpolation to create a smoothly varying field used for other applications. An initial randomized + field is defined with `rand_size` dimensions with `pad` number of values padding it along each dimension using + `pad_val` as the value. If `spatial_size` is given this is interpolated to that size, otherwise if None the random + array is produced uninterpolated. The output is always a Pytorch tensor allocated on the specified device. Args: - spatial_size: final output size of the array rand_size: size of the randomized field to start from - padder: optional transform to add padding to the randomized field - mode: interpolation mode to use when upsampling - align_corners: if True align the corners when upsampling field + pad: number of pixels/voxels along the edges of the field to pad with `pad_val` + pad_val: value with which to pad field edges low: low value for randomized field high: high value for randomized field channels: number of channels of final output + spatial_size: final output size of the array, None to produce original uninterpolated field + mode: interpolation mode for resizing the field + align_corners: if True align the corners when upsampling field + device: Pytorch device to define field on """ def __init__( self, - spatial_size: Union[Sequence[int], int], - rand_size: Union[Sequence[int], int], - padder: Optional[Transform] = None, - mode: Union[InterpolateMode, str] = InterpolateMode.AREA, - align_corners: Optional[bool] = None, + rand_size: Sequence[int], + pad: int = 0, + pad_val: float = 0, low: float = -1.0, high: float = 1.0, channels: int = 1, + spatial_size: Optional[Sequence[int]] = None, + mode: Union[InterpolateMode, str] = InterpolateMode.AREA, + align_corners: Optional[bool] = None, + device: Optional[torch.device] = None, ): - self.resizer: Transform = Resize(spatial_size, mode=mode, align_corners=align_corners) - self.rand_size: tuple = ensure_tuple(rand_size) - self.padder: Optional[Transform] = padder - self.field: Optional[np.ndarray] = None - self.low: float = low - self.high: float = high - self.channels: int = channels + self.rand_size = tuple(rand_size) + self.pad = pad + self.low = low + self.high = high + self.channels = channels + self.mode = mode + self.align_corners = align_corners + self.device = device + + self.spatial_size: Optional[Sequence[int]] = None + self.spatial_zoom: Optional[Sequence[float]] = None + + if low >= high: + raise ValueError("Value for `low` must be less than `high` otherwise field will be zeros") + + self.total_rand_size = tuple(rs + self.pad * 2 for rs in self.rand_size) + + self.field = torch.ones((1, self.channels) + self.total_rand_size, device=self.device) * pad_val + + self.crand_size = (self.channels,) + self.rand_size + + pad_slice = slice(None) if self.pad == 0 else slice(self.pad, -self.pad) + self.rand_slices = (0, slice(None)) + (pad_slice,) * len(self.rand_size) + + self.set_spatial_size(spatial_size) def randomize(self, data: Optional[Any] = None) -> None: - self.field = self.R.uniform(self.low, self.high, (self.channels,) + self.rand_size) # type: ignore - if self.padder is not None: - self.field = self.padder(self.field) + self.field[self.rand_slices] = torch.from_numpy(self.R.uniform(self.low, self.high, self.crand_size)) - def __call__(self): - resized_field = self.resizer(self.field) + def set_spatial_size(self, spatial_size: Optional[Sequence[int]]) -> None: + """ + Set the `spatial_size` and `spatial_zoom` attributes used for interpolating the field to the given + dimension, or not interpolate at all if None. + + Args: + spatial_size: new size to interpolate to, or None to not interpolate + """ + if spatial_size is None: + self.spatial_size = None + self.spatial_zoom = None + else: + self.spatial_size = tuple(spatial_size) + self.spatial_zoom = tuple(s / f for s, f in zip(self.spatial_size, self.total_rand_size)) + + def set_mode(self, mode: Union[monai.utils.InterpolateMode, str]) -> None: + self.mode = mode + + def __call__(self, randomize=False) -> torch.Tensor: + if randomize: + self.randomize() - return rescale_array(resized_field, self.field.min(), self.field.max()) + field = self.field.clone() + + if self.spatial_zoom is not None: + resized_field = interpolate( # type: ignore + input=field, # type: ignore + scale_factor=self.spatial_zoom, + mode=look_up_option(self.mode, InterpolateMode).value, + align_corners=self.align_corners, + recompute_scale_factor=False, + ) + + mina = resized_field.min() + maxa = resized_field.max() + minv = self.field.min() + maxv = self.field.max() + + # faster than rescale_array, this uses in-place operations and doesn't perform unneeded range checks + norm_field = (resized_field.squeeze(0) - mina).div_(maxa - mina) + field = norm_field.mul_(maxv - minv).add_(minv) + + return field class RandSmoothFieldAdjustContrast(RandomizableTransform): """ - Randomly adjust the contrast of input images by calculating a randomized smooth field for each invocation. This - uses SmoothFieldAdjustContrast and SmoothField internally. + Randomly adjust the contrast of input images by calculating a randomized smooth field for each invocation. + + This uses SmoothField internally to define the adjustment over the image. If `pad` is greater than 0 the + edges of the input volume of that width will be mostly unchanged. Contrast is changed by raising input + values by the power of the smooth field so the range of values given by `gamma` should be chosen with this + in mind. For example, a minimum value of 0 in `gamma` will produce white areas so this should be avoided. + Afte the contrast is adjusted the values of the result are rescaled to the range of the original input. Args: spatial_size: size of input array's spatial dimensions rand_size: size of the randomized field to start from - padder: optional transform to add padding to the randomized field + pad: number of pixels/voxels along the edges of the field to pad with 1 mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range for exponential field + device: Pytorch device to define field on """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, - spatial_size: Union[Sequence[int], int], - rand_size: Union[Sequence[int], int], - padder: Optional[Transform] = None, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, mode: Union[InterpolateMode, str] = InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.5, 4.5), + device: Optional[torch.device] = None, ): super().__init__(prob) @@ -109,7 +182,18 @@ def __init__( self.gamma = (min(gamma), max(gamma)) - self.sfield = SmoothField(spatial_size, rand_size, padder, mode, align_corners, self.gamma[0], self.gamma[1]) + self.sfield = SmoothField( + rand_size=rand_size, + pad=pad, + pad_val=1, + low=self.gamma[0], + high=self.gamma[1], + channels=1, + spatial_size=spatial_size, + mode=mode, + align_corners=align_corners, + device=device, + ) def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None @@ -124,7 +208,10 @@ def randomize(self, data: Optional[Any] = None) -> None: if self._do_transform: self.sfield.randomize() - def __call__(self, img: np.ndarray, randomize: bool = True): + def set_mode(self, mode: Union[monai.utils.InterpolateMode, str]) -> None: + self.sfield.set_mode(mode) + + def __call__(self, img: NdarrayOrTensor, randomize: bool = True) -> NdarrayOrTensor: """ Apply the transform to `img`, if `randomize` randomizing the smooth field otherwise reusing the previous. """ @@ -139,44 +226,51 @@ def __call__(self, img: np.ndarray, randomize: bool = True): img_rng = img_max - img_min field = self.sfield() - field, *_ = convert_to_dst_type(field, img) + rfield, *_ = convert_to_dst_type(field, img) - img = (img - img_min) / max(img_rng, 1e-10) # rescale to unit values - img = img ** field # contrast is changed by raising image data to a power, in this case the field + # everything below here is to be computed using the destination type (numpy, tensor, etc.) - out = (img * img_rng) + img_min # rescale back to the original image value range + img = (img - img_min) / (img_rng + 1e-10) # rescale to unit values + img = img ** rfield # contrast is changed by raising image data to a power, in this case the field - out, *_ = convert_to_dst_type(out, img, img.dtype) + out = (img * img_rng) + img_min # rescale back to the original image value range return out class RandSmoothFieldAdjustIntensity(RandomizableTransform): """ - Randomly adjust the intensity of input images by calculating a randomized smooth field for each invocation. This - uses SmoothField internally. + Randomly adjust the intensity of input images by calculating a randomized smooth field for each invocation. + + This uses SmoothField internally to define the adjustment over the image. If `pad` is greater than 0 the + edges of the input volume of that width will be mostly unchanged. Intensity is changed by multiplying the + inputs by the smooth field, so the values of `gamma` should be chosen with this in mind. The default values + of `(0.1, 1.0)` are sensible in that values will not be zeroed out by the field nor multiplied greater than + the original value range. Args: spatial_size: size of input array rand_size: size of the randomized field to start from - padder: optional transform to add padding to the randomized field + pad: number of pixels/voxels along the edges of the field to pad with 1 mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range of intensity multipliers + device: Pytorch device to define field on """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, - spatial_size: Union[Sequence[int], int], - rand_size: Union[Sequence[int], int], - padder: Optional[Transform] = None, - mode: Union[monai.utils.InterpolateMode, str] = monai.utils.InterpolateMode.AREA, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, + mode: Union[InterpolateMode, str] = InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.1, 1.0), + device: Optional[torch.device] = None, ): super().__init__(prob) @@ -188,7 +282,18 @@ def __init__( self.gamma = (min(gamma), max(gamma)) - self.sfield = SmoothField(spatial_size, rand_size, padder, mode, align_corners, self.gamma[0], self.gamma[1]) + self.sfield = SmoothField( + rand_size=rand_size, + pad=pad, + pad_val=1, + low=self.gamma[0], + high=self.gamma[1], + channels=1, + spatial_size=spatial_size, + mode=mode, + align_corners=align_corners, + device=device, + ) def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None @@ -203,7 +308,10 @@ def randomize(self, data: Optional[Any] = None) -> None: if self._do_transform: self.sfield.randomize() - def __call__(self, img: np.ndarray, randomize: bool = True): + def set_mode(self, mode: Union[InterpolateMode, str]) -> None: + self.sfield.set_mode(mode) + + def __call__(self, img: NdarrayOrTensor, randomize: bool = True) -> NdarrayOrTensor: """ Apply the transform to `img`, if `randomize` randomizing the smooth field otherwise reusing the previous. """ @@ -214,13 +322,140 @@ def __call__(self, img: np.ndarray, randomize: bool = True): if not self._do_transform: return img - img_min = img.min() - img_max = img.max() - field = self.sfield() rfield, *_ = convert_to_dst_type(field, img) + # everything below here is to be computed using the destination type (numpy, tensor, etc.) + out = img * rfield - out, *_ = convert_to_dst_type(out, img, img.dtype) return out + + +class RandSmoothDeform(RandomizableTransform): + """ + Deform an image using a random smooth field and Pytorch's grid_sample. + + The amount of deformation is given by `def_range` in fractions of the size of the image. The size of each dimension + of the input image is always defined as 2 regardless of actual image voxel dimensions, that is the coordinates in + every dimension range from -1 to 1. A value of 0.1 means pixels/voxels can be moved by up to 5% of the image's size. + + Args: + spatial_size: input array size to which deformation grid is interpolated + rand_size: size of the randomized field to start from + pad: number of pixels/voxels along the edges of the field to pad with 0 + field_mode: interpolation mode to use when upsampling the deformation field + align_corners: if True align the corners when upsampling field + prob: probability transform is applied + def_range: value of the deformation range in image size fractions, single min/max value or min/max pair + grid_dtype: type for the deformation grid calculated from the field + grid_mode: interpolation mode used for sampling input using deformation grid + grid_padding_mode: padding mode used for sampling input using deformation grid + grid_align_corners: if True align the corners when sampling the deformation grid + device: Pytorch device to define field on + """ + + backend = [TransformBackends.TORCH] + + def __init__( + self, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, + field_mode: Union[InterpolateMode, str] = InterpolateMode.AREA, + align_corners: Optional[bool] = None, + prob: float = 0.1, + def_range: Union[Sequence[float], float] = 1.0, + grid_dtype=torch.float32, + grid_mode: Union[GridSampleMode, str] = GridSampleMode.NEAREST, + grid_padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER, + grid_align_corners: Optional[bool] = False, + device: Optional[torch.device] = None, + ): + super().__init__(prob) + + self.grid_dtype = grid_dtype + self.grid_mode = grid_mode + self.def_range = def_range + self.device = device + self.grid_align_corners = grid_align_corners + self.grid_padding_mode = grid_padding_mode + + if isinstance(def_range, (int, float)): + self.def_range = (-def_range, def_range) + else: + if len(def_range) != 2: + raise ValueError("Argument `def_range` should be a number or pair of numbers.") + + self.def_range = (min(def_range), max(def_range)) + + self.sfield = SmoothField( + spatial_size=spatial_size, + rand_size=rand_size, + pad=pad, + low=self.def_range[0], + high=self.def_range[1], + channels=len(rand_size), + mode=field_mode, + align_corners=align_corners, + device=device, + ) + + grid_space = spatial_size if spatial_size is not None else self.sfield.field.shape[2:] + grid_ranges = [torch.linspace(-1, 1, d) for d in grid_space] + + if pytorch_after(1, 10): + grid = torch.meshgrid(*grid_ranges, indexing="ij") + else: + grid = torch.meshgrid(*grid_ranges) + + self.grid = torch.stack(grid).unsqueeze(0).to(self.device, self.grid_dtype) + + def set_random_state( + self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None + ) -> "Randomizable": + super().set_random_state(seed, state) + self.sfield.set_random_state(seed, state) + return self + + def randomize(self, data: Optional[Any] = None) -> None: + super().randomize(None) + + if self._do_transform: + self.sfield.randomize() + + def set_field_mode(self, mode: Union[monai.utils.InterpolateMode, str]) -> None: + self.sfield.set_mode(mode) + + def set_grid_mode(self, mode: Union[monai.utils.GridSampleMode, str]) -> None: + self.grid_mode = mode + + def __call__( + self, img: NdarrayOrTensor, randomize: bool = True, device: Optional[torch.device] = None + ) -> NdarrayOrTensor: + if randomize: + self.randomize() + + if not self._do_transform: + return img + + device = device if device is not None else self.device + + field = self.sfield() + + dgrid = self.grid + field.to(self.grid_dtype) + dgrid = moveaxis(dgrid, 1, -1) # type: ignore + + img_t = convert_to_tensor(img[None], torch.float32, device) + + out = grid_sample( + input=img_t, + grid=dgrid, + mode=look_up_option(self.grid_mode, GridSampleMode).value, + align_corners=self.grid_align_corners, + padding_mode=look_up_option(self.grid_padding_mode, GridSamplePadMode).value, + ) + + out_t, *_ = convert_to_dst_type(out.squeeze(0), img) + + return out_t diff --git a/monai/transforms/smooth_field/dictionary.py b/monai/transforms/smooth_field/dictionary.py index 43663930ad..c129d14f32 100644 --- a/monai/transforms/smooth_field/dictionary.py +++ b/monai/transforms/smooth_field/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -13,31 +13,44 @@ from typing import Any, Hashable, Mapping, Optional, Sequence, Union import numpy as np +import torch from monai.config import KeysCollection -from monai.transforms.smooth_field.array import RandSmoothFieldAdjustContrast, RandSmoothFieldAdjustIntensity -from monai.transforms.transform import MapTransform, RandomizableTransform, Transform -from monai.utils import InterpolateMode +from monai.config.type_definitions import NdarrayOrTensor +from monai.transforms.smooth_field.array import ( + RandSmoothDeform, + RandSmoothFieldAdjustContrast, + RandSmoothFieldAdjustIntensity, +) +from monai.transforms.transform import MapTransform, RandomizableTransform +from monai.utils import GridSampleMode, GridSamplePadMode, InterpolateMode, ensure_tuple_rep from monai.utils.enums import TransformBackends -__all__ = ["RandSmoothFieldAdjustContrastd", "RandSmoothFieldAdjustIntensityd"] +__all__ = ["RandSmoothFieldAdjustContrastd", "RandSmoothFieldAdjustIntensityd", "RandSmoothDeformd"] + + +InterpolateModeType = Union[InterpolateMode, str] +GridSampleModeType = Union[GridSampleMode, str] class RandSmoothFieldAdjustContrastd(RandomizableTransform, MapTransform): """ - Dictionary version of RandSmoothFieldAdjustContrast. The field is randomized once per invocation by default so the - same field is applied to every selected key. + Dictionary version of RandSmoothFieldAdjustContrast. + + The field is randomized once per invocation by default so the same field is applied to every selected key. The + `mode` parameter specifying interpolation mode for the field can be a single value or a sequence of values with + one for each key in `keys`. Args: keys: key names to apply the augment to spatial_size: size of input arrays, all arrays stated in `keys` must have same dimensions rand_size: size of the randomized field to start from - padder: optional transform to add padding to the randomized field + pad: number of pixels/voxels along the edges of the field to pad with 0 mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range for exponential field - apply_same_field: if True, apply the same field to each key, otherwise randomize individually + device: Pytorch device to define field on """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] @@ -45,28 +58,30 @@ class RandSmoothFieldAdjustContrastd(RandomizableTransform, MapTransform): def __init__( self, keys: KeysCollection, - spatial_size: Union[Sequence[int], int], - rand_size: Union[Sequence[int], int], - padder: Optional[Transform] = None, - mode: Union[InterpolateMode, str] = InterpolateMode.AREA, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, + mode: Union[InterpolateModeType, Sequence[InterpolateModeType]] = InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.5, 4.5), - apply_same_field: bool = True, + device: Optional[torch.device] = None, ): RandomizableTransform.__init__(self, prob) MapTransform.__init__(self, keys) + self.mode = ensure_tuple_rep(mode, len(self.keys)) + self.trans = RandSmoothFieldAdjustContrast( spatial_size=spatial_size, rand_size=rand_size, - padder=padder, - mode=mode, + pad=pad, + mode=self.mode[0], align_corners=align_corners, prob=1.0, gamma=gamma, + device=device, ) - self.apply_same_field = apply_same_field def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None @@ -81,7 +96,7 @@ def randomize(self, data: Optional[Any] = None) -> None: if self._do_transform: self.trans.randomize() - def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Mapping[Hashable, np.ndarray]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Mapping[Hashable, NdarrayOrTensor]: self.randomize() if not self._do_transform: @@ -89,10 +104,8 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Mapping[Hashable, np. d = dict(data) - for key in self.key_iterator(d): - if not self.apply_same_field: - self.randomize() # new field for every key - + for idx, key in enumerate(self.key_iterator(d)): + self.trans.set_mode(self.mode[idx % len(self.mode)]) d[key] = self.trans(d[key], False) return d @@ -100,19 +113,22 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Mapping[Hashable, np. class RandSmoothFieldAdjustIntensityd(RandomizableTransform, MapTransform): """ - Dictionary version of RandSmoothFieldAdjustIntensity. The field is randomized once per invocation by default so - the same field is applied to every selected key. + Dictionary version of RandSmoothFieldAdjustIntensity. + + The field is randomized once per invocation by default so the same field is applied to every selected key. The + `mode` parameter specifying interpolation mode for the field can be a single value or a sequence of values with + one for each key in `keys`. Args: keys: key names to apply the augment to spatial_size: size of input arrays, all arrays stated in `keys` must have same dimensions rand_size: size of the randomized field to start from - padder: optional transform to add padding to the randomized field + pad: number of pixels/voxels along the edges of the field to pad with 0 mode: interpolation mode to use when upsampling align_corners: if True align the corners when upsampling field prob: probability transform is applied gamma: (min, max) range of intensity multipliers - apply_same_field: if True, apply the same field to each key, otherwise randomize individually + device: Pytorch device to define field on """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] @@ -120,28 +136,30 @@ class RandSmoothFieldAdjustIntensityd(RandomizableTransform, MapTransform): def __init__( self, keys: KeysCollection, - spatial_size: Union[Sequence[int], int], - rand_size: Union[Sequence[int], int], - padder: Optional[Transform] = None, - mode: Union[InterpolateMode, str] = InterpolateMode.AREA, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, + mode: Union[InterpolateModeType, Sequence[InterpolateModeType]] = InterpolateMode.AREA, align_corners: Optional[bool] = None, prob: float = 0.1, gamma: Union[Sequence[float], float] = (0.1, 1.0), - apply_same_field: bool = True, + device: Optional[torch.device] = None, ): RandomizableTransform.__init__(self, prob) MapTransform.__init__(self, keys) + self.mode = ensure_tuple_rep(mode, len(self.keys)) + self.trans = RandSmoothFieldAdjustIntensity( spatial_size=spatial_size, rand_size=rand_size, - padder=padder, - mode=mode, + pad=pad, + mode=self.mode[0], align_corners=align_corners, prob=1.0, gamma=gamma, + device=device, ) - self.apply_same_field = apply_same_field def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None @@ -154,7 +172,7 @@ def randomize(self, data: Optional[Any] = None) -> None: super().randomize(None) self.trans.randomize() - def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Mapping[Hashable, np.ndarray]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Mapping[Hashable, NdarrayOrTensor]: self.randomize() if not self._do_transform: @@ -162,10 +180,99 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Mapping[Hashable, np. d = dict(data) - for key in self.key_iterator(d): - if not self.apply_same_field: - self.randomize() # new field for every key - + for idx, key in enumerate(self.key_iterator(d)): + self.trans.set_mode(self.mode[idx % len(self.mode)]) d[key] = self.trans(d[key], False) return d + + +class RandSmoothDeformd(RandomizableTransform, MapTransform): + """ + Dictionary version of RandSmoothDeform. + + The field is randomized once per invocation by default so the same field is applied to every selected key. The + `field_mode` parameter specifying interpolation mode for the field can be a single value or a sequence of values + with one for each key in `keys`. Similarly the `grid_mode` parameter can be one value or one per key. + + Args: + keys: key names to apply the augment to + spatial_size: input array size to which deformation grid is interpolated + rand_size: size of the randomized field to start from + pad: number of pixels/voxels along the edges of the field to pad with 0 + field_mode: interpolation mode to use when upsampling the deformation field + align_corners: if True align the corners when upsampling field + prob: probability transform is applied + def_range: value of the deformation range in image size fractions + grid_dtype: type for the deformation grid calculated from the field + grid_mode: interpolation mode used for sampling input using deformation grid + grid_padding_mode: padding mode used for sampling input using deformation grid + grid_align_corners: if True align the corners when sampling the deformation grid + device: Pytorch device to define field on + """ + + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + + def __init__( + self, + keys: KeysCollection, + spatial_size: Sequence[int], + rand_size: Sequence[int], + pad: int = 0, + field_mode: Union[InterpolateModeType, Sequence[InterpolateModeType]] = InterpolateMode.AREA, + align_corners: Optional[bool] = None, + prob: float = 0.1, + def_range: Union[Sequence[float], float] = 1.0, + grid_dtype=torch.float32, + grid_mode: Union[GridSampleModeType, Sequence[GridSampleModeType]] = GridSampleMode.NEAREST, + grid_padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER, + grid_align_corners: Optional[bool] = False, + device: Optional[torch.device] = None, + ): + RandomizableTransform.__init__(self, prob) + MapTransform.__init__(self, keys) + + self.field_mode = ensure_tuple_rep(field_mode, len(self.keys)) + self.grid_mode = ensure_tuple_rep(grid_mode, len(self.keys)) + + self.trans = RandSmoothDeform( + rand_size=rand_size, + spatial_size=spatial_size, + pad=pad, + field_mode=self.field_mode[0], + align_corners=align_corners, + prob=1.0, + def_range=def_range, + grid_dtype=grid_dtype, + grid_mode=self.grid_mode[0], + grid_padding_mode=grid_padding_mode, + grid_align_corners=grid_align_corners, + device=device, + ) + + def set_random_state( + self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None + ) -> "RandSmoothDeformd": + super().set_random_state(seed, state) + self.trans.set_random_state(seed, state) + return self + + def randomize(self, data: Optional[Any] = None) -> None: + super().randomize(None) + self.trans.randomize() + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Mapping[Hashable, NdarrayOrTensor]: + self.randomize() + + if not self._do_transform: + return data + + d = dict(data) + + for idx, key in enumerate(self.key_iterator(d)): + self.trans.set_field_mode(self.field_mode[idx % len(self.field_mode)]) + self.trans.set_grid_mode(self.grid_mode[idx % len(self.grid_mode)]) + + d[key] = self.trans(d[key], False, self.trans.device) + + return d diff --git a/monai/transforms/spatial/__init__.py b/monai/transforms/spatial/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/spatial/__init__.py +++ b/monai/transforms/spatial/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 8d1558c230..4e5cac2c85 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -13,7 +13,7 @@ https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design """ import warnings -from typing import Any, List, Optional, Sequence, Tuple, Union +from typing import Any, Callable, List, Optional, Sequence, Tuple, Union import numpy as np import torch @@ -33,7 +33,6 @@ create_translate, map_spatial_axes, ) -from monai.transforms.utils_pytorch_numpy_unification import concatenate from monai.utils import ( GridSampleMode, GridSamplePadMode, @@ -77,7 +76,6 @@ "RandAffine", "Rand2DElastic", "Rand3DElastic", - "AddCoordinateChannels", ] RandRange = Optional[Union[Sequence[Union[Tuple[float, float], float]], float]] @@ -702,7 +700,7 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: Args: img: channel first array, must have shape: (num_channels, H[, W, ..., ]), """ - rot90 = torch.rot90 if isinstance(img, torch.Tensor) else np.rot90 + rot90: Callable = torch.rot90 if isinstance(img, torch.Tensor) else np.rot90 # type: ignore out: NdarrayOrTensor = rot90(img, self.k, map_spatial_axes(img.ndim, self.spatial_axes)) out, *_ = convert_data_type(out, dtype=img.dtype) return out @@ -1375,14 +1373,15 @@ def __call__( raise ValueError("Unknown grid.") _device = img.device if isinstance(img, torch.Tensor) else self.device img_t: torch.Tensor + grid_t: torch.Tensor img_t, *_ = convert_data_type(img, torch.Tensor, device=_device, dtype=torch.float32) # type: ignore - grid, *_ = convert_to_dst_type(grid, img_t) + grid_t, *_ = convert_to_dst_type(grid, img_t) # type: ignore if USE_COMPILED: for i, dim in enumerate(img_t.shape[1:]): - grid[i] += (dim - 1.0) / 2.0 - grid = grid[:-1] / grid[-1:] - grid = grid.permute(list(range(grid.ndimension()))[1:] + [0]) + grid_t[i] += (dim - 1.0) / 2.0 + grid_t = grid_t[:-1] / grid_t[-1:] + grid_t = grid_t.permute(list(range(grid_t.ndimension()))[1:] + [0]) _padding_mode = look_up_option( self.padding_mode if padding_mode is None else padding_mode, GridSamplePadMode ).value @@ -1395,21 +1394,21 @@ def __call__( _interp_mode = look_up_option(self.mode if mode is None else mode, GridSampleMode).value out = grid_pull( img_t.unsqueeze(0), - grid.unsqueeze(0), + grid_t.unsqueeze(0), bound=bound, extrapolate=True, interpolation=1 if _interp_mode == "bilinear" else _interp_mode, )[0] else: for i, dim in enumerate(img_t.shape[1:]): - grid[i] = 2.0 * grid[i] / (dim - 1.0) - grid = grid[:-1] / grid[-1:] + grid_t[i] = 2.0 * grid_t[i] / (dim - 1.0) + grid_t = grid_t[:-1] / grid_t[-1:] index_ordering: List[int] = list(range(img_t.ndimension() - 2, -1, -1)) - grid = grid[index_ordering] - grid = grid.permute(list(range(grid.ndimension()))[1:] + [0]) + grid_t = grid_t[index_ordering] + grid_t = grid_t.permute(list(range(grid_t.ndimension()))[1:] + [0]) out = torch.nn.functional.grid_sample( img_t.unsqueeze(0), - grid.unsqueeze(0), + grid_t.unsqueeze(0), mode=self.mode.value if mode is None else GridSampleMode(mode).value, padding_mode=self.padding_mode.value if padding_mode is None else GridSamplePadMode(padding_mode).value, align_corners=True, @@ -2024,49 +2023,6 @@ def __call__( return out -class AddCoordinateChannels(Transform): - """ - Appends additional channels encoding coordinates of the input. Useful when e.g. training using patch-based sampling, - to allow feeding of the patch's location into the network. - - This can be seen as a input-only version of CoordConv: - - Liu, R. et al. An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution, NeurIPS 2018. - """ - - backend = [TransformBackends.TORCH, TransformBackends.NUMPY] - - def __init__(self, spatial_channels: Sequence[int]) -> None: - """ - Args: - spatial_channels: the spatial dimensions that are to have their coordinates encoded in a channel and - appended to the input. E.g., `(1,2,3)` will append three channels to the input, encoding the - coordinates of the input's three spatial dimensions (0 is reserved for the channel dimension). - """ - self.spatial_channels = spatial_channels - - def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: - """ - Args: - img: data to be transformed, assuming `img` is channel first. - """ - if max(self.spatial_channels) > img.ndim - 1: - raise ValueError( - f"input has {img.ndim-1} spatial dimensions, cannot add AddCoordinateChannels channel for " - f"dim {max(self.spatial_channels)}." - ) - if 0 in self.spatial_channels: - raise ValueError("cannot add AddCoordinateChannels channel for dimension 0, as 0 is channel dim.") - - spatial_dims = img.shape[1:] - coord_channels = np.array(np.meshgrid(*tuple(np.linspace(-0.5, 0.5, s) for s in spatial_dims), indexing="ij")) - coord_channels, *_ = convert_to_dst_type(coord_channels, img) # type: ignore - # only keep required dimensions. need to subtract 1 since im will be 0-based - # but user input is 1-based (because channel dim is 0) - coord_channels = coord_channels[[s - 1 for s in self.spatial_channels]] - return concatenate((img, coord_channels), axis=0) - - class GridDistortion(Transform): backend = [TransformBackends.TORCH] diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index e4fc4cf3a9..d1cf70f92c 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -29,7 +29,6 @@ from monai.transforms.croppad.array import CenterSpatialCrop, SpatialPad from monai.transforms.inverse import InvertibleTransform from monai.transforms.spatial.array import ( - AddCoordinateChannels, Affine, AffineGrid, Flip, @@ -123,9 +122,6 @@ "ZoomDict", "RandZoomD", "RandZoomDict", - "AddCoordinateChannelsd", - "AddCoordinateChannelsD", - "AddCoordinateChannelsDict", ] GridSampleModeSequence = Union[Sequence[Union[GridSampleMode, str]], GridSampleMode, str] @@ -1756,34 +1752,6 @@ def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, Nd return d -class AddCoordinateChannelsd(MapTransform): - """ - Dictionary-based wrapper of :py:class:`monai.transforms.AddCoordinateChannels`. - """ - - backend = AddCoordinateChannels.backend - - def __init__(self, keys: KeysCollection, spatial_channels: Sequence[int], allow_missing_keys: bool = False) -> None: - """ - Args: - keys: keys of the corresponding items to be transformed. - See also: :py:class:`monai.transforms.compose.MapTransform` - allow_missing_keys: don't raise exception if key is missing. - spatial_channels: the spatial dimensions that are to have their coordinates encoded in a channel and - appended to the input. E.g., `(1,2,3)` will append three channels to the input, encoding the - coordinates of the input's three spatial dimensions. It is assumed dimension 0 is the channel. - - """ - super().__init__(keys, allow_missing_keys) - self.add_coordinate_channels = AddCoordinateChannels(spatial_channels) - - def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: - d = dict(data) - for key in self.key_iterator(d): - d[key] = self.add_coordinate_channels(d[key]) - return d - - class GridDistortiond(MapTransform): """ Dictionary-based wrapper of :py:class:`monai.transforms.GridDistortion`. @@ -1919,4 +1887,3 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, N RandRotateD = RandRotateDict = RandRotated ZoomD = ZoomDict = Zoomd RandZoomD = RandZoomDict = RandZoomd -AddCoordinateChannelsD = AddCoordinateChannelsDict = AddCoordinateChannelsd diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py index 3b62fc0a31..430e659c95 100644 --- a/monai/transforms/transform.py +++ b/monai/transforms/transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/utility/__init__.py b/monai/transforms/utility/__init__.py index 14ae193634..1e97f89407 100644 --- a/monai/transforms/utility/__init__.py +++ b/monai/transforms/utility/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 84fdc0a78d..a107cf1cb1 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -37,6 +37,7 @@ convert_to_cupy, convert_to_numpy, convert_to_tensor, + deprecated_arg, ensure_tuple, look_up_option, min_version, @@ -56,6 +57,7 @@ "AsChannelFirst", "AsChannelLast", "AddChannel", + "AddCoordinateChannels", "EnsureChannelFirst", "EnsureType", "RepeatChannel", @@ -446,6 +448,8 @@ class ToCupy(Transform): Args: dtype: data type specifier. It is inferred from the input by default. + if not None, must be an argument of `numpy.dtype`, for more details: + https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. wrap_sequence: if `False`, then lists will recursively call this function, default to `True`. E.g., if `False`, `[1, 2]` -> `[array(1), array(2)]`, if `True`, then `[1, 2]` -> `array([1, 2])`. @@ -453,7 +457,7 @@ class ToCupy(Transform): backend = [TransformBackends.TORCH, TransformBackends.NUMPY] - def __init__(self, dtype=None, wrap_sequence: bool = True) -> None: + def __init__(self, dtype: Optional[np.dtype] = None, wrap_sequence: bool = True) -> None: super().__init__() self.dtype = dtype self.wrap_sequence = wrap_sequence @@ -566,7 +570,7 @@ def __init__( a typical example is to print some properties of Nifti image: affine, pixdim, etc. additional_info: user can define callable function to extract additional info from input data. logger_handler: add additional handler to output data: save to file, etc. - add existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html + all the existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html. the handler should have a logging level of at least `INFO`. Raises: @@ -785,7 +789,7 @@ def __call__( if img.shape[0] > 1: data = img[[*select_labels]] else: - where = np.where if isinstance(img, np.ndarray) else torch.where + where: Callable = np.where if isinstance(img, np.ndarray) else torch.where # type: ignore if isinstance(img, np.ndarray) or is_module_ver_at_least(torch, (1, 8, 0)): data = where(in1d(img, select_labels), True, False).reshape(img.shape) # pre pytorch 1.8.0, need to use 1/0 instead of True/False @@ -1006,6 +1010,7 @@ def __init__(self, name: str, *args, **kwargs) -> None: """ super().__init__() + self.name = name transform, _ = optional_import("torchvision.transforms", "0.8.0", min_version, name=name) self.trans = transform(*args, **kwargs) @@ -1194,6 +1199,7 @@ class CuCIM(Transform): def __init__(self, name: str, *args, **kwargs) -> None: super().__init__() + self.name = name self.transform, _ = optional_import("cucim.core.operations.expose.transform", name=name) self.args = args self.kwargs = kwargs @@ -1250,3 +1256,45 @@ def __call__(self, data): if not self._do_transform: return data return super().__call__(data) + + +class AddCoordinateChannels(Transform): + """ + Appends additional channels encoding coordinates of the input. Useful when e.g. training using patch-based sampling, + to allow feeding of the patch's location into the network. + + This can be seen as a input-only version of CoordConv: + + Liu, R. et al. An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution, NeurIPS 2018. + + Args: + spatial_dims: the spatial dimensions that are to have their coordinates encoded in a channel and + appended to the input image. E.g., `(0, 1, 2)` represents `H, W, D` dims and append three channels + to the input image, encoding the coordinates of the input's three spatial dimensions. + + .. deprecated:: 0.8.0 + ``spatial_channels`` is deprecated, use ``spatial_dims`` instead. + + """ + + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + + @deprecated_arg( + name="spatial_channels", new_name="spatial_dims", since="0.8", msg_suffix="please use `spatial_dims` instead." + ) + def __init__(self, spatial_dims: Sequence[int]) -> None: + self.spatial_dims = spatial_dims + + def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: + """ + Args: + img: data to be transformed, assuming `img` is channel first. + """ + if max(self.spatial_dims) > img.ndim - 2 or min(self.spatial_dims) < 0: + raise ValueError(f"`spatial_dims` values must be within [0, {img.ndim - 2}]") + + spatial_size = img.shape[1:] + coord_channels = np.array(np.meshgrid(*tuple(np.linspace(-0.5, 0.5, s) for s in spatial_size), indexing="ij")) + coord_channels, *_ = convert_to_dst_type(coord_channels, img) # type: ignore + coord_channels = coord_channels[list(self.spatial_dims)] + return concatenate((img, coord_channels), axis=0) diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index 082be8dea8..b611d2ed30 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -30,6 +30,7 @@ from monai.transforms.transform import MapTransform, Randomizable, RandomizableTransform from monai.transforms.utility.array import ( AddChannel, + AddCoordinateChannels, AddExtremePointsChannel, AsChannelFirst, AsChannelLast, @@ -61,7 +62,7 @@ ) from monai.transforms.utils import extreme_points_to_image, get_extreme_points from monai.transforms.utils_pytorch_numpy_unification import concatenate -from monai.utils import convert_to_numpy, ensure_tuple, ensure_tuple_rep +from monai.utils import convert_to_numpy, deprecated_arg, ensure_tuple, ensure_tuple_rep from monai.utils.enums import TraceKeys, TransformBackends from monai.utils.type_conversion import convert_to_dst_type @@ -69,6 +70,9 @@ "AddChannelD", "AddChannelDict", "AddChanneld", + "AddCoordinateChannelsD", + "AddCoordinateChannelsDict", + "AddCoordinateChannelsd", "AddExtremePointsChannelD", "AddExtremePointsChannelDict", "AddExtremePointsChanneld", @@ -592,6 +596,8 @@ class ToCupyd(MapTransform): keys: keys of the corresponding items to be transformed. See also: :py:class:`monai.transforms.compose.MapTransform` dtype: data type specifier. It is inferred from the input by default. + if not None, must be an argument of `numpy.dtype`, for more details: + https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. wrap_sequence: if `False`, then lists will recursively call this function, default to `True`. E.g., if `False`, `[1, 2]` -> `[array(1), array(2)]`, if `True`, then `[1, 2]` -> `array([1, 2])`. allow_missing_keys: don't raise exception if key is missing. @@ -600,7 +606,11 @@ class ToCupyd(MapTransform): backend = ToCupy.backend def __init__( - self, keys: KeysCollection, dtype=None, wrap_sequence: bool = True, allow_missing_keys: bool = False + self, + keys: KeysCollection, + dtype: Optional[np.dtype] = None, + wrap_sequence: bool = True, + allow_missing_keys: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.converter = ToCupy(dtype=dtype, wrap_sequence=wrap_sequence) @@ -785,7 +795,7 @@ def __init__( additional info from input data. it also can be a sequence of string, each element corresponds to a key in ``keys``. logger_handler: add additional handler to output data: save to file, etc. - add existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html + all the existing python logging handlers: https://docs.python.org/3/library/logging.handlers.html. the handler should have a logging level of at least `INFO`. allow_missing_keys: don't raise exception if key is missing. @@ -1319,6 +1329,7 @@ def __init__(self, keys: KeysCollection, name: str, allow_missing_keys: bool = F """ super().__init__(keys, allow_missing_keys) + self.name = name self.trans = TorchVision(name, *args, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: @@ -1358,6 +1369,7 @@ def __init__(self, keys: KeysCollection, name: str, allow_missing_keys: bool = F """ MapTransform.__init__(self, keys, allow_missing_keys) + self.name = name self.trans = TorchVision(name, *args, **kwargs) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: @@ -1519,6 +1531,7 @@ class CuCIMd(MapTransform): def __init__(self, keys: KeysCollection, name: str, allow_missing_keys: bool = False, *args, **kwargs) -> None: super().__init__(keys=keys, allow_missing_keys=allow_missing_keys) + self.name = name self.trans = CuCIM(name, *args, **kwargs) def __call__(self, data): @@ -1580,6 +1593,39 @@ def __call__(self, data): return super().__call__(data) +class AddCoordinateChannelsd(MapTransform): + """ + Dictionary-based wrapper of :py:class:`monai.transforms.AddCoordinateChannels`. + + Args: + keys: keys of the corresponding items to be transformed. + See also: :py:class:`monai.transforms.compose.MapTransform` + spatial_dims: the spatial dimensions that are to have their coordinates encoded in a channel and + appended to the input image. E.g., `(0, 1, 2)` represents `H, W, D` dims and append three channels + to the input image, encoding the coordinates of the input's three spatial dimensions. + allow_missing_keys: don't raise exception if key is missing. + + .. deprecated:: 0.8.0 + ``spatial_channels`` is deprecated, use ``spatial_dims`` instead. + + """ + + backend = AddCoordinateChannels.backend + + @deprecated_arg( + name="spatial_channels", new_name="spatial_dims", since="0.8", msg_suffix="please use `spatial_dims` instead." + ) + def __init__(self, keys: KeysCollection, spatial_dims: Sequence[int], allow_missing_keys: bool = False) -> None: + super().__init__(keys, allow_missing_keys) + self.add_coordinate_channels = AddCoordinateChannels(spatial_dims=spatial_dims) + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = dict(data) + for key in self.key_iterator(d): + d[key] = self.add_coordinate_channels(d[key]) + return d + + IdentityD = IdentityDict = Identityd AsChannelFirstD = AsChannelFirstDict = AsChannelFirstd AsChannelLastD = AsChannelLastDict = AsChannelLastd @@ -1618,3 +1664,4 @@ def __call__(self, data): ToDeviceD = ToDeviceDict = ToDeviced CuCIMD = CuCIMDict = CuCIMd RandCuCIMD = RandCuCIMDict = RandCuCIMd +AddCoordinateChannelsD = AddCoordinateChannelsDict = AddCoordinateChannelsd diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index b0553ff8c7..739d98e5c0 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -873,7 +873,7 @@ def generate_spatial_bounding_box( margin: Union[Sequence[int], int] = 0, ) -> Tuple[List[int], List[int]]: """ - generate the spatial bounding box of foreground in the image with start-end positions. + generate the spatial bounding box of foreground in the image with start-end positions (inclusive). Users can define arbitrary function to select expected foreground from the whole image or specified channels. And it can also add margin to every dim of the bounding box. The output format of the coordinates is: @@ -1306,7 +1306,7 @@ def shift_fourier(x: NdarrayOrTensor, spatial_dims: int, n_dims: Optional[int] = dims = tuple(range(-spatial_dims, 0)) k: NdarrayOrTensor if isinstance(x, torch.Tensor): - if hasattr(torch.fft, "fftshift"): + if hasattr(torch.fft, "fftshift"): # `fftshift` is new in torch 1.8.0 k = torch.fft.fftshift(torch.fft.fftn(x, dim=dims), dim=dims) else: # if using old PyTorch, will convert to numpy array and return @@ -1339,7 +1339,7 @@ def inv_shift_fourier(k: NdarrayOrTensor, spatial_dims: int, n_dims: Optional[in dims = tuple(range(-spatial_dims, 0)) out: NdarrayOrTensor if isinstance(k, torch.Tensor): - if hasattr(torch.fft, "ifftshift"): + if hasattr(torch.fft, "ifftshift"): # `ifftshift` is new in torch 1.8.0 out = torch.fft.ifftn(torch.fft.ifftshift(k, dim=dims), dim=dims, norm="backward").real else: # if using old PyTorch, will convert to numpy array and return diff --git a/monai/transforms/utils_create_transform_ims.py b/monai/transforms/utils_create_transform_ims.py index 59d359639b..ab282d5332 100644 --- a/monai/transforms/utils_create_transform_ims.py +++ b/monai/transforms/utils_create_transform_ims.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -102,9 +102,11 @@ RandGibbsNoise, RandHistogramShift, RandKSpaceSpikeNoise, + RandRicianNoise, RandScaleIntensity, RandShiftIntensity, RandStdShiftIntensity, + SavitzkyGolaySmooth, ScaleIntensityRange, ScaleIntensityRangePercentiles, ShiftIntensity, @@ -130,9 +132,11 @@ RandGibbsNoised, RandHistogramShiftd, RandKSpaceSpikeNoised, + RandRicianNoised, RandScaleIntensityd, RandShiftIntensityd, RandStdShiftIntensityd, + SavitzkyGolaySmoothd, ScaleIntensityRanged, ScaleIntensityRangePercentilesd, ShiftIntensityd, @@ -141,7 +145,18 @@ ) from monai.transforms.post.array import KeepLargestConnectedComponent, LabelFilter, LabelToContour from monai.transforms.post.dictionary import AsDiscreted, KeepLargestConnectedComponentd, LabelFilterd, LabelToContourd +from monai.transforms.smooth_field.array import ( + RandSmoothDeform, + RandSmoothFieldAdjustContrast, + RandSmoothFieldAdjustIntensity, +) +from monai.transforms.smooth_field.dictionary import ( + RandSmoothDeformd, + RandSmoothFieldAdjustContrastd, + RandSmoothFieldAdjustIntensityd, +) from monai.transforms.spatial.array import ( + GridDistortion, Rand2DElastic, RandAffine, RandAxisFlip, @@ -151,6 +166,7 @@ Spacing, ) from monai.transforms.spatial.dictionary import ( + GridDistortiond, Rand2DElasticd, RandAffined, RandAxisFlipd, @@ -520,6 +536,10 @@ def create_transform_im( dict(keys=CommonKeys.IMAGE, global_prob=1, prob=1, common_sampling=True, intensity_range=(13, 15)), data, ) + create_transform_im(RandRicianNoise, dict(prob=1.0, mean=1, std=0.5), data) + create_transform_im(RandRicianNoised, dict(keys=CommonKeys.IMAGE, prob=1.0, mean=1, std=0.5), data) + create_transform_im(SavitzkyGolaySmooth, dict(window_length=5, order=1), data) + create_transform_im(SavitzkyGolaySmoothd, dict(keys=CommonKeys.IMAGE, window_length=5, order=1), data) create_transform_im(GibbsNoise, dict(alpha=0.8), data) create_transform_im(GibbsNoised, dict(keys=CommonKeys.IMAGE, alpha=0.8), data) create_transform_im(RandGibbsNoise, dict(prob=1.0, alpha=(0.6, 0.8)), data) @@ -640,8 +660,8 @@ def create_transform_im( create_transform_im(RandScaleCropd, dict(keys=keys, roi_scale=0.4), data) create_transform_im(CenterScaleCrop, dict(roi_scale=0.4), data) create_transform_im(CenterScaleCropd, dict(keys=keys, roi_scale=0.4), data) - create_transform_im(AsDiscrete, dict(to_onehot=2, threshold=10), data, is_post=True, colorbar=True) - create_transform_im(AsDiscreted, dict(keys=CommonKeys.LABEL, to_onehot=2, threshold=10), data, is_post=True) + create_transform_im(AsDiscrete, dict(to_onehot=None, threshold=10), data, is_post=True, colorbar=True) + create_transform_im(AsDiscreted, dict(keys=CommonKeys.LABEL, to_onehot=None, threshold=10), data, is_post=True) create_transform_im(LabelFilter, dict(applied_labels=(1, 2, 3, 4, 5, 6)), data, is_post=True) create_transform_im( LabelFilterd, dict(keys=CommonKeys.LABEL, applied_labels=(1, 2, 3, 4, 5, 6)), data, is_post=True @@ -660,9 +680,55 @@ def create_transform_im( create_transform_im( KeepLargestConnectedComponentd, dict(keys=CommonKeys.LABEL, applied_labels=1), data_binary, is_post=True, ndim=2 ) + create_transform_im( + GridDistortion, dict(num_cells=3, distort_steps=[(1.5,) * 4] * 3, mode="nearest", padding_mode="zeros"), data + ) + create_transform_im( + GridDistortiond, + dict( + keys=keys, num_cells=3, distort_steps=[(1.5,) * 4] * 3, mode=["bilinear", "nearest"], padding_mode="zeros" + ), + data, + ) create_transform_im(RandGridDistortion, dict(num_cells=3, prob=1.0, distort_limit=(-0.1, 0.1)), data) create_transform_im( RandGridDistortiond, dict(keys=keys, num_cells=4, prob=1.0, distort_limit=(-0.2, 0.2), mode=["bilinear", "nearest"]), data, ) + create_transform_im( + RandSmoothFieldAdjustContrast, dict(spatial_size=(217, 217, 217), rand_size=(10, 10, 10), prob=1.0), data + ) + create_transform_im( + RandSmoothFieldAdjustContrastd, + dict(keys=keys, spatial_size=(217, 217, 217), rand_size=(10, 10, 10), prob=1.0), + data, + ) + create_transform_im( + RandSmoothFieldAdjustIntensity, + dict(spatial_size=(217, 217, 217), rand_size=(10, 10, 10), prob=1.0, gamma=(0.5, 4.5)), + data, + ) + create_transform_im( + RandSmoothFieldAdjustIntensityd, + dict(keys=keys, spatial_size=(217, 217, 217), rand_size=(10, 10, 10), prob=1.0, gamma=(0.5, 4.5)), + data, + ) + + create_transform_im( + RandSmoothDeform, + dict(spatial_size=(217, 217, 217), rand_size=(10, 10, 10), prob=1.0, def_range=0.05, grid_mode="blinear"), + data, + ) + create_transform_im( + RandSmoothDeformd, + dict( + keys=keys, + spatial_size=(217, 217, 217), + rand_size=(10, 10, 10), + prob=1.0, + def_range=0.05, + grid_mode="blinear", + ), + data, + ) diff --git a/monai/transforms/utils_pytorch_numpy_unification.py b/monai/transforms/utils_pytorch_numpy_unification.py index 9754981560..25d59ac89a 100644 --- a/monai/transforms/utils_pytorch_numpy_unification.py +++ b/monai/transforms/utils_pytorch_numpy_unification.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -42,7 +42,7 @@ def moveaxis(x: NdarrayOrTensor, src: int, dst: int) -> NdarrayOrTensor: """`moveaxis` for pytorch and numpy, using `permute` for pytorch ver < 1.8""" if isinstance(x, torch.Tensor): - if hasattr(torch, "moveaxis"): + if hasattr(torch, "moveaxis"): # `moveaxis` is new in torch 1.8.0 return torch.moveaxis(x, src, dst) return _moveaxis_with_permute(x, src, dst) # type: ignore if isinstance(x, np.ndarray): @@ -81,32 +81,37 @@ def clip(a: NdarrayOrTensor, a_min, a_max) -> NdarrayOrTensor: return result -def percentile(x: NdarrayOrTensor, q) -> Union[NdarrayOrTensor, float, int]: +def percentile(x: NdarrayOrTensor, q, dim: Optional[int] = None) -> Union[NdarrayOrTensor, float, int]: """`np.percentile` with equivalent implementation for torch. Pytorch uses `quantile`, but this functionality is only available from v1.7. For earlier methods, we calculate it ourselves. This doesn't do interpolation, so is the equivalent of ``numpy.percentile(..., interpolation="nearest")``. + For more details, please refer to: + https://pytorch.org/docs/stable/generated/torch.quantile.html. + https://numpy.org/doc/stable/reference/generated/numpy.percentile.html. Args: x: input data q: percentile to compute (should in range 0 <= q <= 100) + dim: the dim along which the percentiles are computed. default is to compute the percentile + along a flattened version of the array. only work for numpy array or Tensor with PyTorch >= 1.7.0. Returns: Resulting value (scalar) """ if np.isscalar(q): - if not 0 <= q <= 100: + if not 0 <= q <= 100: # type: ignore raise ValueError elif any(q < 0) or any(q > 100): raise ValueError result: Union[NdarrayOrTensor, float, int] if isinstance(x, np.ndarray): - result = np.percentile(x, q) + result = np.percentile(x, q, axis=dim) else: q = torch.tensor(q, device=x.device) - if hasattr(torch, "quantile"): - result = torch.quantile(x, q / 100.0) + if hasattr(torch, "quantile"): # `quantile` is new in torch 1.7.0 + result = torch.quantile(x, q / 100.0, dim=dim) else: # Note that ``kthvalue()`` works one-based, i.e., the first sorted value # corresponds to k=1, not k=0. Thus, we need the `1 +`. @@ -129,7 +134,7 @@ def where(condition: NdarrayOrTensor, x=None, y=None) -> NdarrayOrTensor: if x is not None: result = np.where(condition, x, y) else: - result = np.where(condition) + result = np.where(condition) # type: ignore else: if x is not None: x = torch.as_tensor(x, device=condition.device) @@ -217,7 +222,7 @@ def ravel(x: NdarrayOrTensor): Return a contiguous flattened array/tensor. """ if isinstance(x, torch.Tensor): - if hasattr(torch, "ravel"): + if hasattr(torch, "ravel"): # `ravel` is new in torch 1.8.0 return x.ravel() return x.flatten().contiguous() return np.ravel(x) @@ -263,7 +268,7 @@ def maximum(a: NdarrayOrTensor, b: NdarrayOrTensor) -> NdarrayOrTensor: """ if isinstance(a, torch.Tensor) and isinstance(b, torch.Tensor): # is torch and has torch.maximum (pt>1.6) - if hasattr(torch, "maximum"): + if hasattr(torch, "maximum"): # `maximum` is new in torch 1.7.0 return torch.maximum(a, b) return torch.stack((a, b)).max(dim=0)[0] return np.maximum(a, b) diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index d58fcca32d..0c04680234 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -24,6 +24,7 @@ GridSamplePadMode, InterpolateMode, InverseKeys, + JITMetadataKeys, LossReduction, Method, MetricReduction, diff --git a/monai/utils/aliases.py b/monai/utils/aliases.py index a08dab4f95..0ae79e26ff 100644 --- a/monai/utils/aliases.py +++ b/monai/utils/aliases.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/decorators.py b/monai/utils/decorators.py index 1931d703c9..0856c0fc1a 100644 --- a/monai/utils/decorators.py +++ b/monai/utils/decorators.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py index 4ae5991d9f..a6092c1b63 100644 --- a/monai/utils/deprecate_utils.py +++ b/monai/utils/deprecate_utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,6 +10,7 @@ # limitations under the License. import inspect +import sys import warnings from functools import wraps from types import FunctionType @@ -62,7 +63,7 @@ def deprecated( # if version_val.startswith("0+"): # # version unknown, set version_val to a large value (assuming the latest version) - # version_val = "100" + # version_val = f"{sys.maxsize}" if since is not None and removed is not None and not version_leq(since, removed): raise ValueError(f"since must be less or equal to removed, got since={since}, removed={removed}.") is_not_yet_deprecated = since is not None and version_val != since and version_leq(version_val, since) @@ -144,6 +145,8 @@ def deprecated_arg( msg_suffix: message appended to warning/exception detailing reasons for deprecation and what to use instead. version_val: (used for testing) version to compare since and removed against, default is MONAI version. new_name: name of position or keyword argument to replace the deprecated argument. + if it is specified and the signature of the decorated function has a `kwargs`, the value to the + deprecated argument `name` will be removed. Returns: Decorated callable which warns or raises exception when deprecated argument used. @@ -151,7 +154,7 @@ def deprecated_arg( if version_val.startswith("0+") or not f"{version_val}".strip()[0].isdigit(): # version unknown, set version_val to a large value (assuming the latest version) - version_val = "100" + version_val = f"{sys.maxsize}" if since is not None and removed is not None and not version_leq(since, removed): raise ValueError(f"since must be less or equal to removed, got since={since}, removed={removed}.") is_not_yet_deprecated = since is not None and version_val != since and version_leq(version_val, since) @@ -197,9 +200,13 @@ def _wrapper(*args, **kwargs): # multiple values for new_name using both args and kwargs kwargs.pop(new_name, None) binding = sig.bind(*args, **kwargs).arguments - positional_found = name in binding - kw_found = "kwargs" in binding and name in binding["kwargs"] + kw_found = False + for k, param in sig.parameters.items(): + if param.kind == inspect.Parameter.VAR_KEYWORD and k in binding and name in binding[k]: + kw_found = True + # if the deprecated arg is found in the **kwargs, it should be removed + kwargs.pop(name, None) if positional_found or kw_found: if is_removed: diff --git a/monai/utils/dist.py b/monai/utils/dist.py index beb958a5c8..37536bfe83 100644 --- a/monai/utils/dist.py +++ b/monai/utils/dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/enums.py b/monai/utils/enums.py index c059a4a5e2..08a59c598c 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -266,3 +266,15 @@ class TransformBackends(Enum): TORCH = "torch" NUMPY = "numpy" + + +class JITMetadataKeys(Enum): + """ + Keys stored in the metadata file for saved Torchscript models. Some of these are generated by the routines + and others are optionally provided by users. + """ + + NAME = "name" + TIMESTAMP = "timestamp" + VERSION = "version" + DESCRIPTION = "description" diff --git a/monai/utils/jupyter_utils.py b/monai/utils/jupyter_utils.py index 1f55f2fa61..366d11ebd8 100644 --- a/monai/utils/jupyter_utils.py +++ b/monai/utils/jupyter_utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/misc.py b/monai/utils/misc.py index 1cb5c90244..eae0580696 100644 --- a/monai/utils/misc.py +++ b/monai/utils/misc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -89,7 +89,7 @@ def ensure_tuple(vals: Any) -> Tuple[Any, ...]: Returns a tuple of `vals`. """ if not issequenceiterable(vals): - vals = (vals,) + return (vals,) return tuple(vals) @@ -98,8 +98,8 @@ def ensure_tuple_size(tup: Any, dim: int, pad_val: Any = 0) -> Tuple[Any, ...]: """ Returns a copy of `tup` with `dim` values by either shortened or padded with `pad_val` as necessary. """ - tup = ensure_tuple(tup) + (pad_val,) * dim - return tuple(tup[:dim]) + new_tup = ensure_tuple(tup) + (pad_val,) * dim + return new_tup[:dim] def ensure_tuple_rep(tup: Any, dim: int) -> Tuple[Any, ...]: @@ -251,6 +251,10 @@ def set_determinism( for func in additional_settings: func(seed) + if torch.backends.flags_frozen(): + warnings.warn("PyTorch global flag support of backends is disabled, enable it to set global `cudnn` flags.") + torch.backends.__allow_nonbracketed_mutation_flag = True + if seed is not None: torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False @@ -258,9 +262,9 @@ def set_determinism( torch.backends.cudnn.deterministic = _flag_deterministic torch.backends.cudnn.benchmark = _flag_cudnn_benchmark if use_deterministic_algorithms is not None: - if hasattr(torch, "use_deterministic_algorithms"): + if hasattr(torch, "use_deterministic_algorithms"): # `use_deterministic_algorithms` is new in torch 1.8.0 torch.use_deterministic_algorithms(use_deterministic_algorithms) - elif hasattr(torch, "set_deterministic"): + elif hasattr(torch, "set_deterministic"): # `set_deterministic` is new in torch 1.7.0 torch.set_deterministic(use_deterministic_algorithms) # type: ignore else: warnings.warn("use_deterministic_algorithms=True, but PyTorch version is too old to set the mode.") diff --git a/monai/utils/module.py b/monai/utils/module.py index e475ae1957..c0fc10a7c0 100644 --- a/monai/utils/module.py +++ b/monai/utils/module.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/nvtx.py b/monai/utils/nvtx.py index 0693d6fe5b..691f900c7d 100644 --- a/monai/utils/nvtx.py +++ b/monai/utils/nvtx.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -62,8 +62,15 @@ def __call__(self, obj: Any): # Define the name to be associated to the range if not provided if self.name is None: name = type(obj).__name__ + # If CuCIM or TorchVision transform wrappers are being used, + # append the underlying transform to the name for more clarity + if "CuCIM" in name or "TorchVision" in name: + name = f"{name}_{obj.name}" self.name_counter[name] += 1 - self.name = f"{name}_{self.name_counter[name]}" + if self.name_counter[name] > 1: + self.name = f"{name}_{self.name_counter[name]}" + else: + self.name = name # Define the methods to be wrapped if not provided if self.methods is None: diff --git a/monai/utils/profiling.py b/monai/utils/profiling.py index d7459885fb..8e0742268f 100644 --- a/monai/utils/profiling.py +++ b/monai/utils/profiling.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/state_cacher.py b/monai/utils/state_cacher.py index 2cf0271db2..3e392ab979 100644 --- a/monai/utils/state_cacher.py +++ b/monai/utils/state_cacher.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 6a90d21a09..8686557176 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -59,7 +59,7 @@ def dtype_torch_to_numpy(dtype): def dtype_numpy_to_torch(dtype): """Convert a numpy dtype to its torch equivalent.""" # np dtypes can be given as np.float32 and np.dtype(np.float32) so unify them - dtype = np.dtype(dtype) if isinstance(dtype, type) else dtype + dtype = np.dtype(dtype) if isinstance(dtype, (type, str)) else dtype return look_up_option(dtype, _np_to_torch_dtype) @@ -151,8 +151,8 @@ def convert_to_numpy(data, dtype: DtypeLike = None, wrap_sequence: bool = False) will convert Tensor, Numpy array, float, int, bool to numpy arrays, strings and objects keep the original. for dictionary, list or tuple, convert every item to a numpy array if applicable. dtype: target data type when converting to numpy array. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. """ if isinstance(data, torch.Tensor): data = data.detach().to(dtype=get_equivalent_dtype(dtype, torch.Tensor), device="cpu").numpy() @@ -175,19 +175,19 @@ def convert_to_numpy(data, dtype: DtypeLike = None, wrap_sequence: bool = False) return data -def convert_to_cupy(data, dtype, wrap_sequence: bool = False): +def convert_to_cupy(data, dtype: Optional[np.dtype] = None, wrap_sequence: bool = False): """ Utility to convert the input data to a cupy array. If passing a dictionary, list or tuple, recursively check every item and convert it to cupy array. Args: data: input data can be PyTorch Tensor, numpy array, cupy array, list, dictionary, int, float, bool, str, etc. - Tensor, numpy array, cupy array, float, int, bool are converted to cupy arrays - + Tensor, numpy array, cupy array, float, int, bool are converted to cupy arrays, for dictionary, list or tuple, convert every item to a numpy array if applicable. - dtype: target data type when converting to Cupy array. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + dtype: target data type when converting to Cupy array, tt must be an argument of `numpy.dtype`, + for more details: https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. """ # direct calls @@ -227,8 +227,8 @@ def convert_data_type( dtype: dtype of output data. Converted to correct library type (e.g., `np.float32` is converted to `torch.float32` if output type is `torch.Tensor`). If left blank, it remains unchanged. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. Returns: modified data, orig_type, orig_device diff --git a/monai/visualize/__init__.py b/monai/visualize/__init__.py index 1e3140d3a4..cd980846b3 100644 --- a/monai/visualize/__init__.py +++ b/monai/visualize/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/visualize/class_activation_maps.py b/monai/visualize/class_activation_maps.py index cddd0b1270..b7e433d6fa 100644 --- a/monai/visualize/class_activation_maps.py +++ b/monai/visualize/class_activation_maps.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/visualize/img2tensorboard.py b/monai/visualize/img2tensorboard.py index 619c65f79d..4d4e91c6fd 100644 --- a/monai/visualize/img2tensorboard.py +++ b/monai/visualize/img2tensorboard.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/visualize/occlusion_sensitivity.py b/monai/visualize/occlusion_sensitivity.py index bc2ecc3787..184fa50e61 100644 --- a/monai/visualize/occlusion_sensitivity.py +++ b/monai/visualize/occlusion_sensitivity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/visualize/utils.py b/monai/visualize/utils.py index abdfd5c4b5..63cac7ea35 100644 --- a/monai/visualize/utils.py +++ b/monai/visualize/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/monai/visualize/visualizer.py b/monai/visualize/visualizer.py index bbb01f5c5e..5f19e4f63f 100644 --- a/monai/visualize/visualizer.py +++ b/monai/visualize/visualizer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/pyproject.toml b/pyproject.toml index b53a7640b1..03e9f49ab5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires = [ [tool.black] line-length = 120 -target-version = ['py36', 'py37', 'py38'] +target-version = ['py37', 'py38', 'py39'] include = '\.pyi?$' exclude = ''' ( diff --git a/requirements-dev.txt b/requirements-dev.txt index f47eb14bbd..fc752a7627 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ # Full requirements for developments -r requirements-min.txt -pytorch-ignite==0.4.6 +pytorch-ignite==0.4.7 gdown>=3.6.4 scipy itk>=5.2 diff --git a/requirements-min.txt b/requirements-min.txt index 5db219c840..195f6f49f4 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,5 +1,5 @@ # Requirements for minimal tests -r requirements.txt -setuptools>=50.3.0 +setuptools>=50.3.0,!=60.0.0 coverage>=5.5 parameterized diff --git a/runtests.sh b/runtests.sh index a77f9decd5..70ca8df5c2 100755 --- a/runtests.sh +++ b/runtests.sh @@ -1,6 +1,6 @@ #! /bin/bash -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -38,12 +38,14 @@ doNetTests=false doDryRun=false doZooTests=false doUnitTests=false +doBuild=false doBlackFormat=false doBlackFix=false doIsortFormat=false doIsortFix=false doFlake8Format=false doClangFormat=false +doCopyRight=false doPytypeFormat=false doMypyFormat=false doCleanup=false @@ -55,7 +57,8 @@ PY_EXE=${MONAI_PY_EXE:-$(which python)} function print_usage { echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--flake8] [--clangformat] [--pytype] [--mypy]" - echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--dryrun] [-j number] [--clean] [--help] [--version]" + echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--dryrun] [-j number] [--list_tests]" + echo " [--copyright] [--build] [--clean] [--help] [--version]" echo "" echo "MONAI unit testing utilities." echo "" @@ -86,10 +89,12 @@ function print_usage { echo " -q, --quick : skip long running unit tests and integration tests" echo " -m, --min : only run minimal unit tests which do not require optional packages" echo " --net : perform integration testing" + echo " -b, --build : compile and install the source code folder an editable release." echo " --list_tests : list unit tests and exit" echo "" echo "Misc. options:" echo " --dryrun : display the commands to the screen without running" + echo " --copyright : check whether every source code has a copyright header" echo " -f, --codeformat : shorthand to run all code style and static analysis tests" echo " -c, --clean : clean temporary files from tests and exit" echo " -h, --help : show this help message and exit" @@ -103,7 +108,7 @@ function print_usage { } function check_import { - echo "python: ${PY_EXE}" + echo "Python: ${PY_EXE}" ${cmdPrefix}${PY_EXE} -c "import monai" } @@ -238,6 +243,7 @@ do doFlake8Format=true doPytypeFormat=true doMypyFormat=true + doCopyRight=true ;; --disttests) doDistTests=true @@ -250,6 +256,7 @@ do doBlackFix=true doIsortFormat=true doBlackFormat=true + doCopyRight=true ;; --clangformat) doClangFormat=true @@ -270,6 +277,12 @@ do NUM_PARALLEL=$2 shift ;; + --copyright) + doCopyRight=true + ;; + -b|--build) + doBuild=true + ;; -c|--clean) doCleanup=true ;; @@ -314,6 +327,14 @@ else check_import fi +if [ $doBuild = true ] +then + echo "${separator}${blue}compile and install${noColor}" + # try to compile MONAI cpp + compile_cpp + + echo "${green}done! (to uninstall and clean up, please use \"./runtests.sh --clean\")${noColor}" +fi if [ $doCleanup = true ] then @@ -335,12 +356,33 @@ then exit fi -# try to compile MONAI cpp -compile_cpp - # unconditionally report on the state of monai print_version +if [ $doCopyRight = true ] +then + # check copyright headers + copyright_bad=0 + copyright_all=0 + while read -r fname; do + copyright_all=$((copyright_all + 1)) + if ! grep "http://www.apache.org/licenses/LICENSE-2.0" "$fname" > /dev/null; then + print_error_msg "Missing the license header in file: $fname" + copyright_bad=$((copyright_bad + 1)) + fi + done <<< "$(find "$(pwd)/monai" "$(pwd)/tests" -type f \ + ! -wholename "*_version.py" -and -name "*.py" -or -name "*.cpp" -or -name "*.cu" -or -name "*.h")" + if [[ ${copyright_bad} -eq 0 ]]; + then + echo "${green}Source code copyright headers checked ($copyright_all).${noColor}" + else + echo "Please add the licensing header to the file ($copyright_bad of $copyright_all files)." + echo " See also: https://github.com/Project-MONAI/MONAI/blob/dev/CONTRIBUTING.md#checking-the-coding-style" + echo "" + exit 1 + fi +fi + if [ $doIsortFormat = true ] then @@ -535,7 +577,7 @@ if [ $doUnitTests = true ] then echo "${separator}${blue}unittests${noColor}" torch_validate - ${cmdPrefix}${cmd} ./tests/runner.py -p "test_((?!integration).)" + ${cmdPrefix}${cmd} ./tests/runner.py -p "^(?!test_integration).*(?= 3.6 +python_requires = >= 3.7 # for compiling and develop setup only # no need to specify the versions so that we could # compile for multiple targeted versions. @@ -34,7 +34,7 @@ all = pillow tensorboard gdown>=3.6.4 - pytorch-ignite==0.4.6 + pytorch-ignite==0.4.7 torchvision itk>=5.2 tqdm>=4.47.0 @@ -61,7 +61,7 @@ tensorboard = gdown = gdown>=3.6.4 ignite = - pytorch-ignite==0.4.6 + pytorch-ignite==0.4.7 torchvision = torchvision itk = diff --git a/setup.py b/setup.py index 95087ce06f..83a60129e6 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/__init__.py b/tests/__init__.py index 5093d1f72d..4639a58496 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/clang_format_utils.py b/tests/clang_format_utils.py index 22f86c50b9..1b13ce0ac3 100644 --- a/tests/clang_format_utils.py +++ b/tests/clang_format_utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/hvd_evenly_divisible_all_gather.py b/tests/hvd_evenly_divisible_all_gather.py index 42b2e9530d..f4c39e4e73 100644 --- a/tests/hvd_evenly_divisible_all_gather.py +++ b/tests/hvd_evenly_divisible_all_gather.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/min_tests.py b/tests/min_tests.py index f8e4743244..00f3e49850 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -101,6 +101,7 @@ def run_testsuit(): "test_label_filter", "test_lltm", "test_lmdbdataset", + "test_lmdbdataset_dist", "test_load_image", "test_load_imaged", "test_load_spacing_orientation", diff --git a/tests/ngc_mmar_loading.py b/tests/ngc_mmar_loading.py index c1ed22de5d..a371917e59 100644 --- a/tests/ngc_mmar_loading.py +++ b/tests/ngc_mmar_loading.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/runner.py b/tests/runner.py index b340d60719..7356581365 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_acn_block.py b/tests/test_acn_block.py index 5e2467a565..4c12155fd8 100644 --- a/tests/test_acn_block.py +++ b/tests/test_acn_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_activations.py b/tests/test_activations.py index 92b1e1d945..a67e6f8cb6 100644 --- a/tests/test_activations.py +++ b/tests/test_activations.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_activationsd.py b/tests/test_activationsd.py index 2c522d36ed..557d68de90 100644 --- a/tests/test_activationsd.py +++ b/tests/test_activationsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_adaptors.py b/tests/test_adaptors.py index 9bcd01feb7..3c351d9e20 100644 --- a/tests/test_adaptors.py +++ b/tests/test_adaptors.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_add_channeld.py b/tests/test_add_channeld.py index 8bdd89a4ae..9dc984aff3 100644 --- a/tests/test_add_channeld.py +++ b/tests/test_add_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_add_coordinate_channels.py b/tests/test_add_coordinate_channels.py index 4d779cffff..5a483e25b9 100644 --- a/tests/test_add_coordinate_channels.py +++ b/tests/test_add_coordinate_channels.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,10 +20,10 @@ TESTS, TEST_CASES_ERROR_1, TEST_CASES_ERROR_2 = [], [], [] for p in TEST_NDARRAYS: - TESTS.append([{"spatial_channels": (1, 2, 3)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (4, 3, 3, 3)]) - TESTS.append([{"spatial_channels": (1,)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (2, 3, 3, 3)]) - TEST_CASES_ERROR_1.append([{"spatial_channels": (3,)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) - TEST_CASES_ERROR_2.append([{"spatial_channels": (0, 1, 2)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) + TESTS.append([{"spatial_dims": (0, 1, 2)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (4, 3, 3, 3)]) + TESTS.append([{"spatial_dims": (0,)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (2, 3, 3, 3)]) + TEST_CASES_ERROR_1.append([{"spatial_dims": (2,)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) + TEST_CASES_ERROR_2.append([{"spatial_dims": (-1, 0, 1)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) class TestAddCoordinateChannels(unittest.TestCase): diff --git a/tests/test_add_coordinate_channelsd.py b/tests/test_add_coordinate_channelsd.py index 08d9e62468..c14ff0ba64 100644 --- a/tests/test_add_coordinate_channelsd.py +++ b/tests/test_add_coordinate_channelsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,24 +22,20 @@ for p in TEST_NDARRAYS: TESTS.append( [ - {"spatial_channels": (1, 2, 3), "keys": ["img"]}, + {"spatial_dims": (0, 1, 2), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3, 3)))}, (4, 3, 3, 3), ] ) TESTS.append( - [ - {"spatial_channels": (1,), "keys": ["img"]}, - {"img": p(np.random.randint(0, 2, size=(1, 3, 3, 3)))}, - (2, 3, 3, 3), - ] + [{"spatial_dims": (0,), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3, 3)))}, (2, 3, 3, 3)] ) TEST_CASES_ERROR_1.append( - [{"spatial_channels": (3,), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] + [{"spatial_dims": (2,), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] ) TEST_CASES_ERROR_2.append( - [{"spatial_channels": (0, 1, 2), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] + [{"spatial_dims": (-1, 0, 1), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] ) diff --git a/tests/test_add_extreme_points_channel.py b/tests/test_add_extreme_points_channel.py index 06d26dfdfc..d2c8a627b6 100644 --- a/tests/test_add_extreme_points_channel.py +++ b/tests/test_add_extreme_points_channel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_add_extreme_points_channeld.py b/tests/test_add_extreme_points_channeld.py index acd0ce69ce..39d221596f 100644 --- a/tests/test_add_extreme_points_channeld.py +++ b/tests/test_add_extreme_points_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_adjust_contrast.py b/tests/test_adjust_contrast.py index 80ac61cfea..2f6c4e2259 100644 --- a/tests/test_adjust_contrast.py +++ b/tests/test_adjust_contrast.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_adjust_contrastd.py b/tests/test_adjust_contrastd.py index 1e1c2cf8bc..a7224b643b 100644 --- a/tests/test_adjust_contrastd.py +++ b/tests/test_adjust_contrastd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_adn.py b/tests/test_adn.py index 2130ebc005..2352f5c1e2 100644 --- a/tests/test_adn.py +++ b/tests/test_adn.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_affine.py b/tests/test_affine.py index e5d11e2a82..d681d2941b 100644 --- a/tests/test_affine.py +++ b/tests/test_affine.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_affine_grid.py b/tests/test_affine_grid.py index 27e049843c..6f6364feda 100644 --- a/tests/test_affine_grid.py +++ b/tests/test_affine_grid.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_affine_transform.py b/tests/test_affine_transform.py index ef39c297ce..5170ab4260 100644 --- a/tests/test_affine_transform.py +++ b/tests/test_affine_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_affined.py b/tests/test_affined.py index a7c818fe65..355833b858 100644 --- a/tests/test_affined.py +++ b/tests/test_affined.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_ahnet.py b/tests/test_ahnet.py index f4bfa555fc..dba8eaf72b 100644 --- a/tests/test_ahnet.py +++ b/tests/test_ahnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_alias.py b/tests/test_alias.py index 0895da5743..49f9fa56fe 100644 --- a/tests/test_alias.py +++ b/tests/test_alias.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_apply_filter.py b/tests/test_apply_filter.py index 6bddfb5cf2..ff8480a02f 100644 --- a/tests/test_apply_filter.py +++ b/tests/test_apply_filter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_arraydataset.py b/tests/test_arraydataset.py index f6459cc88c..ee1a92cf97 100644 --- a/tests/test_arraydataset.py +++ b/tests/test_arraydataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_channel_first.py b/tests/test_as_channel_first.py index 918e576011..a2d56295b8 100644 --- a/tests/test_as_channel_first.py +++ b/tests/test_as_channel_first.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_channel_firstd.py b/tests/test_as_channel_firstd.py index 68d33434c1..91086f9299 100644 --- a/tests/test_as_channel_firstd.py +++ b/tests/test_as_channel_firstd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_channel_last.py b/tests/test_as_channel_last.py index 55a7a08676..e6446ab7a6 100644 --- a/tests/test_as_channel_last.py +++ b/tests/test_as_channel_last.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_channel_lastd.py b/tests/test_as_channel_lastd.py index 350f639f3f..a6d94d216a 100644 --- a/tests/test_as_channel_lastd.py +++ b/tests/test_as_channel_lastd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_discrete.py b/tests/test_as_discrete.py index 8cbefbac39..a68e6431ec 100644 --- a/tests/test_as_discrete.py +++ b/tests/test_as_discrete.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_as_discreted.py b/tests/test_as_discreted.py index 7c7cfdf6e5..21825c2d6c 100644 --- a/tests/test_as_discreted.py +++ b/tests/test_as_discreted.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_autoencoder.py b/tests/test_autoencoder.py index 451a93dc01..bed5a198ff 100644 --- a/tests/test_autoencoder.py +++ b/tests/test_autoencoder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_basic_unet.py b/tests/test_basic_unet.py index 1de37b316a..a4f88367dd 100644 --- a/tests/test_basic_unet.py +++ b/tests/test_basic_unet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bending_energy.py b/tests/test_bending_energy.py index f254b9624c..318b1905df 100644 --- a/tests/test_bending_energy.py +++ b/tests/test_bending_energy.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,9 +22,28 @@ TEST_CASES = [ [{}, {"pred": torch.ones((1, 3, 5, 5, 5), device=device)}, 0.0], [{}, {"pred": torch.arange(0, 5, device=device)[None, None, None, None, :].expand(1, 3, 5, 5, 5)}, 0.0], - [{}, {"pred": torch.arange(0, 5, device=device)[None, None, None, None, :].expand(1, 3, 5, 5, 5) ** 2}, 4.0], - [{}, {"pred": torch.arange(0, 5, device=device)[None, None, None, :].expand(1, 3, 5, 5) ** 2}, 4.0], - [{}, {"pred": torch.arange(0, 5, device=device)[None, None, :].expand(1, 3, 5) ** 2}, 4.0], + [ + {"normalize": False}, + {"pred": torch.arange(0, 5, device=device)[None, None, None, None, :].expand(1, 3, 5, 5, 5) ** 2}, + 4.0, + ], + [ + {"normalize": False}, + {"pred": torch.arange(0, 5, device=device)[None, None, None, :].expand(1, 2, 5, 5) ** 2}, + 4.0, + ], + [{"normalize": False}, {"pred": torch.arange(0, 5, device=device)[None, None, :].expand(1, 1, 5) ** 2}, 4.0], + [ + {"normalize": True}, + {"pred": torch.arange(0, 5, device=device)[None, None, None, None, :].expand(1, 3, 5, 5, 5) ** 2}, + 100.0, + ], + [ + {"normalize": True}, + {"pred": torch.arange(0, 5, device=device)[None, None, None, :].expand(1, 2, 5, 5) ** 2}, + 100.0, + ], + [{"normalize": True}, {"pred": torch.arange(0, 5, device=device)[None, None, :].expand(1, 1, 5) ** 2}, 100.0], ] @@ -37,18 +56,24 @@ def test_shape(self, input_param, input_data, expected_val): def test_ill_shape(self): loss = BendingEnergyLoss() # not in 3-d, 4-d, 5-d - with self.assertRaisesRegex(ValueError, ""): + with self.assertRaisesRegex(ValueError, "Expecting 3-d, 4-d or 5-d"): loss.forward(torch.ones((1, 3), device=device)) - with self.assertRaisesRegex(ValueError, ""): - loss.forward(torch.ones((1, 3, 5, 5, 5, 5), device=device)) + with self.assertRaisesRegex(ValueError, "Expecting 3-d, 4-d or 5-d"): + loss.forward(torch.ones((1, 4, 5, 5, 5, 5), device=device)) # spatial_dim < 5 - with self.assertRaisesRegex(ValueError, ""): + with self.assertRaisesRegex(ValueError, "All spatial dimensions"): loss.forward(torch.ones((1, 3, 4, 5, 5), device=device)) - with self.assertRaisesRegex(ValueError, ""): + with self.assertRaisesRegex(ValueError, "All spatial dimensions"): loss.forward(torch.ones((1, 3, 5, 4, 5))) - with self.assertRaisesRegex(ValueError, ""): + with self.assertRaisesRegex(ValueError, "All spatial dimensions"): loss.forward(torch.ones((1, 3, 5, 5, 4))) + # number of vector components unequal to number of spatial dims + with self.assertRaisesRegex(ValueError, "Number of vector components"): + loss.forward(torch.ones((1, 2, 5, 5, 5))) + with self.assertRaisesRegex(ValueError, "Number of vector components"): + loss.forward(torch.ones((1, 2, 5, 5, 5))) + def test_ill_opts(self): pred = torch.rand(1, 3, 5, 5, 5).to(device=device) with self.assertRaisesRegex(ValueError, ""): diff --git a/tests/test_bilateral_approx_cpu.py b/tests/test_bilateral_approx_cpu.py index d55a6ff5b3..b3f4f5c3be 100644 --- a/tests/test_bilateral_approx_cpu.py +++ b/tests/test_bilateral_approx_cpu.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bilateral_approx_cuda.py b/tests/test_bilateral_approx_cuda.py index bc7defdc4e..db34b0ff71 100644 --- a/tests/test_bilateral_approx_cuda.py +++ b/tests/test_bilateral_approx_cuda.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bilateral_precise.py b/tests/test_bilateral_precise.py index 3b8f6194cf..b19369d758 100644 --- a/tests/test_bilateral_precise.py +++ b/tests/test_bilateral_precise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_blend_images.py b/tests/test_blend_images.py index 67e967b89d..6fea53ac30 100644 --- a/tests/test_blend_images.py +++ b/tests/test_blend_images.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_border_pad.py b/tests/test_border_pad.py index 7bd3f36c20..b632ff831f 100644 --- a/tests/test_border_pad.py +++ b/tests/test_border_pad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_border_padd.py b/tests/test_border_padd.py index b48629fc98..e4b8dd20ea 100644 --- a/tests/test_border_padd.py +++ b/tests/test_border_padd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bounding_rect.py b/tests/test_bounding_rect.py index eef8ebec50..a7c2648f1e 100644 --- a/tests/test_bounding_rect.py +++ b/tests/test_bounding_rect.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_bounding_rectd.py b/tests/test_bounding_rectd.py index 4b0e132ca3..47ed854263 100644 --- a/tests/test_bounding_rectd.py +++ b/tests/test_bounding_rectd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cachedataset.py b/tests/test_cachedataset.py index 7f35b99ad5..9d85c711fd 100644 --- a/tests/test_cachedataset.py +++ b/tests/test_cachedataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cachedataset_parallel.py b/tests/test_cachedataset_parallel.py index 96aadd9614..f409e17787 100644 --- a/tests/test_cachedataset_parallel.py +++ b/tests/test_cachedataset_parallel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cachedataset_persistent_workers.py b/tests/test_cachedataset_persistent_workers.py index 6cdd0ada99..4bea0486bc 100644 --- a/tests/test_cachedataset_persistent_workers.py +++ b/tests/test_cachedataset_persistent_workers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cachentransdataset.py b/tests/test_cachentransdataset.py index 13c1e1c68e..99ca0e0c3d 100644 --- a/tests/test_cachentransdataset.py +++ b/tests/test_cachentransdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_distcall.py b/tests/test_call_dist.py similarity index 96% rename from tests/test_distcall.py rename to tests/test_call_dist.py index 1830a85654..bed8289506 100644 --- a/tests/test_distcall.py +++ b/tests/test_call_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cast_to_type.py b/tests/test_cast_to_type.py index d06efb17b5..82daabc4e7 100644 --- a/tests/test_cast_to_type.py +++ b/tests/test_cast_to_type.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -18,9 +18,9 @@ from monai.transforms import CastToType from monai.utils import optional_import from monai.utils.type_conversion import get_equivalent_dtype -from tests.utils import TEST_NDARRAYS +from tests.utils import HAS_CUPY, TEST_NDARRAYS -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") TESTS = [] for p in TEST_NDARRAYS: @@ -45,7 +45,7 @@ def test_type(self, out_dtype, input_data, expected_type): self.assertEqual(result.dtype, get_equivalent_dtype(expected_type, type(result))) @parameterized.expand(TESTS_CUPY) - @unittest.skipUnless(has_cp, "Requires CuPy") + @unittest.skipUnless(HAS_CUPY, "Requires CuPy") def test_type_cupy(self, out_dtype, input_data, expected_type): input_data = cp.asarray(input_data) diff --git a/tests/test_cast_to_typed.py b/tests/test_cast_to_typed.py index 007598c200..4c7623a9e0 100644 --- a/tests/test_cast_to_typed.py +++ b/tests/test_cast_to_typed.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,8 +17,9 @@ from monai.transforms import CastToTyped from monai.utils import optional_import +from tests.utils import HAS_CUPY -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") TEST_CASE_1 = [ {"keys": ["img"], "dtype": np.float64}, @@ -58,7 +59,7 @@ def test_type(self, input_param, input_data, expected_type): self.assertEqual(v.dtype, expected_type[k]) @parameterized.expand(TESTS_CUPY) - @unittest.skipUnless(has_cp, "Requires CuPy") + @unittest.skipUnless(HAS_CUPY, "Requires CuPy") def test_type_cupy(self, input_param, input_data, expected_type): input_data = {k: cp.asarray(v) for k, v in input_data.items()} diff --git a/tests/test_center_scale_crop.py b/tests/test_center_scale_crop.py index 4c5bfc4fac..f22651e3e0 100644 --- a/tests/test_center_scale_crop.py +++ b/tests/test_center_scale_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_center_scale_cropd.py b/tests/test_center_scale_cropd.py index a827d611e6..8aef2dbe5b 100644 --- a/tests/test_center_scale_cropd.py +++ b/tests/test_center_scale_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_center_spatial_crop.py b/tests/test_center_spatial_crop.py index d6a7edb305..09f61be2f1 100644 --- a/tests/test_center_spatial_crop.py +++ b/tests/test_center_spatial_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_center_spatial_cropd.py b/tests/test_center_spatial_cropd.py index be44468987..bdbc1a5031 100644 --- a/tests/test_center_spatial_cropd.py +++ b/tests/test_center_spatial_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_channel_pad.py b/tests/test_channel_pad.py index ebc731c321..bde0f18d83 100644 --- a/tests/test_channel_pad.py +++ b/tests/test_channel_pad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_check_hash.py b/tests/test_check_hash.py index 8009302ad0..5297021540 100644 --- a/tests/test_check_hash.py +++ b/tests/test_check_hash.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_check_missing_files.py b/tests/test_check_missing_files.py index 1134409a66..ecd0b52a63 100644 --- a/tests/test_check_missing_files.py +++ b/tests/test_check_missing_files.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_classes_to_indices.py b/tests/test_classes_to_indices.py index 7c89e3179d..1f39e0f480 100644 --- a/tests/test_classes_to_indices.py +++ b/tests/test_classes_to_indices.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_classes_to_indicesd.py b/tests/test_classes_to_indicesd.py index 0df7490ec5..398620d304 100644 --- a/tests/test_classes_to_indicesd.py +++ b/tests/test_classes_to_indicesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compose.py b/tests/test_compose.py index 28783cad23..e0913f59e1 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compose_get_number_conversions.py b/tests/test_compose_get_number_conversions.py index eb10c7d5ef..fca5bc727d 100644 --- a/tests/test_compose_get_number_conversions.py +++ b/tests/test_compose_get_number_conversions.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compute_confusion_matrix.py b/tests/test_compute_confusion_matrix.py index ef65c474c8..1212715548 100644 --- a/tests/test_compute_confusion_matrix.py +++ b/tests/test_compute_confusion_matrix.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compute_froc.py b/tests/test_compute_froc.py index 70de836dd9..d68f3f7fb4 100644 --- a/tests/test_compute_froc.py +++ b/tests/test_compute_froc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compute_meandice.py b/tests/test_compute_meandice.py index f96563e22e..ad66ed672a 100644 --- a/tests/test_compute_meandice.py +++ b/tests/test_compute_meandice.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compute_regression_metrics.py b/tests/test_compute_regression_metrics.py index 126eab3f07..65ca73a4ec 100644 --- a/tests/test_compute_regression_metrics.py +++ b/tests/test_compute_regression_metrics.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_compute_roc_auc.py b/tests/test_compute_roc_auc.py index 02e2f2b24f..d06eed8740 100644 --- a/tests/test_compute_roc_auc.py +++ b/tests/test_compute_roc_auc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_concat_itemsd.py b/tests/test_concat_itemsd.py index 9c51e1efea..2f98738233 100644 --- a/tests/test_concat_itemsd.py +++ b/tests/test_concat_itemsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_contrastive_loss.py b/tests/test_contrastive_loss.py index b9caecce65..5dce860486 100644 --- a/tests/test_contrastive_loss.py +++ b/tests/test_contrastive_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -61,7 +61,7 @@ def test_result(self, input_param, input_data, expected_val): def test_ill_shape(self): loss = ContrastiveLoss(temperature=0.5, batch_size=1) - with self.assertRaisesRegex(AssertionError, ""): + with self.assertRaisesRegex(ValueError, ""): loss(torch.ones((1, 2, 3)), torch.ones((1, 1, 2, 3))) def test_with_cuda(self): diff --git a/tests/test_convert_data_type.py b/tests/test_convert_data_type.py index 28f9fbd1bd..1818f500f9 100644 --- a/tests/test_convert_data_type.py +++ b/tests/test_convert_data_type.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_convert_to_multi_channel.py b/tests/test_convert_to_multi_channel.py index 4892eae809..b606fee04f 100644 --- a/tests/test_convert_to_multi_channel.py +++ b/tests/test_convert_to_multi_channel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_convert_to_multi_channeld.py b/tests/test_convert_to_multi_channeld.py index 945e07e1cd..7525f8d7e2 100644 --- a/tests/test_convert_to_multi_channeld.py +++ b/tests/test_convert_to_multi_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_convert_to_torchscript.py b/tests/test_convert_to_torchscript.py index a772610a04..a1c1471463 100644 --- a/tests/test_convert_to_torchscript.py +++ b/tests/test_convert_to_torchscript.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -34,6 +34,7 @@ def test_value(self): device="cuda" if torch.cuda.is_available() else "cpu", rtol=1e-3, atol=1e-4, + optimize=None, ) self.assertTrue(isinstance(torchscript_model, torch.nn.Module)) diff --git a/tests/test_convolutions.py b/tests/test_convolutions.py index 97c01dd659..5c1681ad71 100644 --- a/tests/test_convolutions.py +++ b/tests/test_convolutions.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_copy_itemsd.py b/tests/test_copy_itemsd.py index a0a1ad412b..375991cbcc 100644 --- a/tests/test_copy_itemsd.py +++ b/tests/test_copy_itemsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_copy_model_state.py b/tests/test_copy_model_state.py index 438c521479..bc7b116e1f 100644 --- a/tests/test_copy_model_state.py +++ b/tests/test_copy_model_state.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_correct_crop_centers.py b/tests/test_correct_crop_centers.py index dba71f6e8d..50478c7d5d 100644 --- a/tests/test_correct_crop_centers.py +++ b/tests/test_correct_crop_centers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_create_cross_validation_datalist.py b/tests/test_create_cross_validation_datalist.py index e1dc2e522e..3a3e8481ea 100644 --- a/tests/test_create_cross_validation_datalist.py +++ b/tests/test_create_cross_validation_datalist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_create_grid_and_affine.py b/tests/test_create_grid_and_affine.py index cd8d75f63e..d70db45468 100644 --- a/tests/test_create_grid_and_affine.py +++ b/tests/test_create_grid_and_affine.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_crf_cpu.py b/tests/test_crf_cpu.py index 6f9864e934..46da3298bc 100644 --- a/tests/test_crf_cpu.py +++ b/tests/test_crf_cpu.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_crf_cuda.py b/tests/test_crf_cuda.py index 8881b9aec5..ca25fe2de9 100644 --- a/tests/test_crf_cuda.py +++ b/tests/test_crf_cuda.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py index 0bae1f90f3..a9c473a4c6 100644 --- a/tests/test_crop_foreground.py +++ b/tests/test_crop_foreground.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py index 5fa474d6ac..7f1842197c 100644 --- a/tests/test_crop_foregroundd.py +++ b/tests/test_crop_foregroundd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cross_validation.py b/tests/test_cross_validation.py index ee4cb444b2..017fd0243c 100644 --- a/tests/test_cross_validation.py +++ b/tests/test_cross_validation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_csv_dataset.py b/tests/test_csv_dataset.py index d187f4e64d..f288ac4b95 100644 --- a/tests/test_csv_dataset.py +++ b/tests/test_csv_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -14,6 +14,7 @@ import unittest import numpy as np +import pandas as pd from monai.data import CSVDataset from monai.transforms import ToNumpyd @@ -57,6 +58,7 @@ def prepare_csv_file(data, filepath): filepath1 = os.path.join(tempdir, "test_data1.csv") filepath2 = os.path.join(tempdir, "test_data2.csv") filepath3 = os.path.join(tempdir, "test_data3.csv") + filepaths = [filepath1, filepath2, filepath3] prepare_csv_file(test_data1, filepath1) prepare_csv_file(test_data2, filepath2) prepare_csv_file(test_data3, filepath3) @@ -76,7 +78,7 @@ def prepare_csv_file(data, filepath): ) # test multiple CSV files, join tables with kwargs - dataset = CSVDataset([filepath1, filepath2, filepath3], on="subject_id") + dataset = CSVDataset(filepaths, on="subject_id") self.assertDictEqual( {k: round(v, 4) if not isinstance(v, (str, np.bool_)) else v for k, v in dataset[3].items()}, { @@ -102,7 +104,7 @@ def prepare_csv_file(data, filepath): # test selected rows and columns dataset = CSVDataset( - filename=[filepath1, filepath2, filepath3], + src=filepaths, row_indices=[[0, 2], 3], # load row: 0, 1, 3 col_names=["subject_id", "image", "ehr_1", "ehr_7", "meta_1"], ) @@ -120,7 +122,7 @@ def prepare_csv_file(data, filepath): # test group columns dataset = CSVDataset( - filename=[filepath1, filepath2, filepath3], + src=filepaths, row_indices=[1, 3], # load row: 1, 3 col_names=["subject_id", "image", *[f"ehr_{i}" for i in range(11)], "meta_0", "meta_1", "meta_2"], col_groups={"ehr": [f"ehr_{i}" for i in range(11)], "meta12": ["meta_1", "meta_2"]}, @@ -133,9 +135,7 @@ def prepare_csv_file(data, filepath): # test transform dataset = CSVDataset( - filename=[filepath1, filepath2, filepath3], - col_groups={"ehr": [f"ehr_{i}" for i in range(5)]}, - transform=ToNumpyd(keys="ehr"), + src=filepaths, col_groups={"ehr": [f"ehr_{i}" for i in range(5)]}, transform=ToNumpyd(keys="ehr") ) self.assertEqual(len(dataset), 5) expected = [ @@ -151,7 +151,7 @@ def prepare_csv_file(data, filepath): # test default values and dtype dataset = CSVDataset( - filename=[filepath1, filepath2, filepath3], + src=filepaths, col_names=["subject_id", "image", "ehr_1", "ehr_9", "meta_1"], col_types={"image": {"type": str, "default": "No image"}, "ehr_1": {"type": int, "default": 0}}, how="outer", # generate NaN values in this merge mode @@ -161,6 +161,29 @@ def prepare_csv_file(data, filepath): self.assertEqual(type(dataset[-1]["ehr_1"]), int) np.testing.assert_allclose(dataset[-1]["ehr_9"], 3.3537, rtol=1e-2) + # test pre-loaded DataFrame + df = pd.read_csv(filepath1) + dataset = CSVDataset(src=df) + self.assertDictEqual( + {k: round(v, 4) if not isinstance(v, str) else v for k, v in dataset[2].items()}, + { + "subject_id": "s000002", + "label": 4, + "image": "./imgs/s000002.png", + "ehr_0": 3.7725, + "ehr_1": 4.2118, + "ehr_2": 4.6353, + }, + ) + + # test pre-loaded multiple DataFrames, join tables with kwargs + dfs = [pd.read_csv(i) for i in filepaths] + dataset = CSVDataset(src=dfs, on="subject_id") + self.assertEqual(dataset[3]["subject_id"], "s000003") + self.assertEqual(dataset[3]["label"], 1) + self.assertEqual(round(dataset[3]["ehr_0"], 4), 3.3333) + self.assertEqual(dataset[3]["meta_0"], False) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_csv_iterable_dataset.py b/tests/test_csv_iterable_dataset.py index fae8e0ba8d..d6b84074ba 100644 --- a/tests/test_csv_iterable_dataset.py +++ b/tests/test_csv_iterable_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,6 +15,7 @@ import unittest import numpy as np +import pandas as pd from monai.data import CSVIterableDataset, DataLoader from monai.transforms import ToNumpyd @@ -58,6 +59,7 @@ def prepare_csv_file(data, filepath): filepath1 = os.path.join(tempdir, "test_data1.csv") filepath2 = os.path.join(tempdir, "test_data2.csv") filepath3 = os.path.join(tempdir, "test_data3.csv") + filepaths = [filepath1, filepath2, filepath3] prepare_csv_file(test_data1, filepath1) prepare_csv_file(test_data2, filepath2) prepare_csv_file(test_data3, filepath3) @@ -81,18 +83,20 @@ def prepare_csv_file(data, filepath): ) break self.assertEqual(count, 3) + dataset.close() # test reset iterables - dataset.reset(filename=filepath3) + dataset.reset(src=filepath3) count = 0 for i, item in enumerate(dataset): count += 1 if i == 4: self.assertEqual(item["meta_0"], False) self.assertEqual(count, 5) + dataset.close() # test multiple CSV files, join tables with kwargs - dataset = CSVIterableDataset([filepath1, filepath2, filepath3], on="subject_id", shuffle=False) + dataset = CSVIterableDataset(filepaths, on="subject_id", shuffle=False) count = 0 for item in dataset: count += 1 @@ -120,13 +124,11 @@ def prepare_csv_file(data, filepath): }, ) self.assertEqual(count, 5) + dataset.close() # test selected columns and chunk size dataset = CSVIterableDataset( - filename=[filepath1, filepath2, filepath3], - chunksize=2, - col_names=["subject_id", "image", "ehr_1", "ehr_7", "meta_1"], - shuffle=False, + src=filepaths, chunksize=2, col_names=["subject_id", "image", "ehr_1", "ehr_7", "meta_1"], shuffle=False ) count = 0 for item in dataset: @@ -143,10 +145,11 @@ def prepare_csv_file(data, filepath): }, ) self.assertEqual(count, 5) + dataset.close() # test group columns dataset = CSVIterableDataset( - filename=[filepath1, filepath2, filepath3], + src=filepaths, col_names=["subject_id", "image", *[f"ehr_{i}" for i in range(11)], "meta_0", "meta_1", "meta_2"], col_groups={"ehr": [f"ehr_{i}" for i in range(11)], "meta12": ["meta_1", "meta_2"]}, shuffle=False, @@ -161,12 +164,13 @@ def prepare_csv_file(data, filepath): ) np.testing.assert_allclose(item["meta12"], [False, True]) self.assertEqual(count, 5) + dataset.close() # test transform dataset = CSVIterableDataset( chunksize=2, buffer_size=4, - filename=[filepath1, filepath2, filepath3], + src=filepaths, col_groups={"ehr": [f"ehr_{i}" for i in range(5)]}, transform=ToNumpyd(keys="ehr"), shuffle=True, @@ -185,6 +189,7 @@ def prepare_csv_file(data, filepath): self.assertTrue(isinstance(item["ehr"], np.ndarray)) np.testing.assert_allclose(np.around(item["ehr"], 4), exp) self.assertEqual(count, 5) + dataset.close() # test multiple processes loading dataset = CSVIterableDataset(filepath1, transform=ToNumpyd(keys="label"), shuffle=False) @@ -200,6 +205,45 @@ def prepare_csv_file(data, filepath): np.testing.assert_allclose(item["label"], [4]) self.assertListEqual(item["image"], ["./imgs/s000002.png"]) self.assertEqual(count, 3) + dataset.close() + + # test iterable stream + iters = pd.read_csv(filepath1, chunksize=1000) + dataset = CSVIterableDataset(src=iters, shuffle=False) + count = 0 + for item in dataset: + count += 1 + if count == 3: + self.assertDictEqual( + {k: round(v, 4) if not isinstance(v, str) else v for k, v in item.items()}, + { + "subject_id": "s000002", + "label": 4, + "image": "./imgs/s000002.png", + "ehr_0": 3.7725, + "ehr_1": 4.2118, + "ehr_2": 4.6353, + }, + ) + break + self.assertEqual(count, 3) + dataset.close() + + # test multiple iterable streams, join tables with kwargs + iters = [pd.read_csv(i, chunksize=1000) for i in filepaths] + dataset = CSVIterableDataset(src=iters, on="subject_id", shuffle=False) + count = 0 + for item in dataset: + count += 1 + if count == 4: + self.assertEqual(item["subject_id"], "s000003") + self.assertEqual(item["label"], 1) + self.assertEqual(round(item["ehr_0"], 4), 3.3333) + self.assertEqual(item["meta_0"], False) + self.assertEqual(count, 5) + # manually close the pre-loaded iterables instead of `dataset.close()` + for i in iters: + i.close() if __name__ == "__main__": diff --git a/tests/test_csv_saver.py b/tests/test_csv_saver.py index a279599463..6b60de4b0c 100644 --- a/tests/test_csv_saver.py +++ b/tests/test_csv_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cucim_dict_transform.py b/tests/test_cucim_dict_transform.py index 4936375142..f8b54c3147 100644 --- a/tests/test_cucim_dict_transform.py +++ b/tests/test_cucim_dict_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,10 +16,10 @@ from monai.transforms import CuCIMd from monai.utils import optional_import, set_determinism -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda _, has_cut = optional_import("cucim.core.operations.expose.transform") -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") set_determinism(seed=0) @@ -62,7 +62,7 @@ @skip_if_no_cuda -@unittest.skipUnless(has_cp, "CuPy is required.") +@unittest.skipUnless(HAS_CUPY, "CuPy is required.") @unittest.skipUnless(has_cut, "cuCIM transforms are required.") class TestCuCIMDict(unittest.TestCase): @parameterized.expand( diff --git a/tests/test_cucim_transform.py b/tests/test_cucim_transform.py index a6c0084c99..2bf9791bce 100644 --- a/tests/test_cucim_transform.py +++ b/tests/test_cucim_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,10 +16,10 @@ from monai.transforms import CuCIM from monai.utils import optional_import, set_determinism -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda _, has_cut = optional_import("cucim.core.operations.expose.transform") -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") set_determinism(seed=0) @@ -62,7 +62,7 @@ @skip_if_no_cuda -@unittest.skipUnless(has_cp, "CuPy is required.") +@unittest.skipUnless(HAS_CUPY, "CuPy is required.") @unittest.skipUnless(has_cut, "cuCIM transforms are required.") class TestCuCIM(unittest.TestCase): @parameterized.expand( diff --git a/tests/test_cumulative.py b/tests/test_cumulative.py index 90719a936e..12a6a5e5e7 100644 --- a/tests/test_cumulative.py +++ b/tests/test_cumulative.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cumulative_average.py b/tests/test_cumulative_average.py index 90f21fc8c4..4e7e4ff5d9 100644 --- a/tests/test_cumulative_average.py +++ b/tests/test_cumulative_average.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_cumulative_average_dist.py b/tests/test_cumulative_average_dist.py index d58864df44..5de139e9ac 100644 --- a/tests/test_cumulative_average_dist.py +++ b/tests/test_cumulative_average_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_data_stats.py b/tests/test_data_stats.py index 535b28bcf1..65a61ce7ec 100644 --- a/tests/test_data_stats.py +++ b/tests/test_data_stats.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_data_statsd.py b/tests/test_data_statsd.py index 4c33a82b67..1f38db2b05 100644 --- a/tests/test_data_statsd.py +++ b/tests/test_data_statsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dataloader.py b/tests/test_dataloader.py index 035f145c9a..79126b2dbb 100644 --- a/tests/test_dataloader.py +++ b/tests/test_dataloader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 491b777550..f8d4ed2104 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dataset_func.py b/tests/test_dataset_func.py new file mode 100644 index 0000000000..b5871d7de1 --- /dev/null +++ b/tests/test_dataset_func.py @@ -0,0 +1,52 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import tempfile +import unittest + +from monai.data import Dataset, DatasetFunc, load_decathlon_datalist, partition_dataset + + +class TestDatasetFunc(unittest.TestCase): + def test_seg_values(self): + with tempfile.TemporaryDirectory() as tempdir: + # prepare test datalist file + test_data = { + "name": "Spleen", + "description": "Spleen Segmentation", + "labels": {"0": "background", "1": "spleen"}, + "training": [ + {"image": "spleen_19.nii.gz", "label": "spleen_19.nii.gz"}, + {"image": "spleen_31.nii.gz", "label": "spleen_31.nii.gz"}, + ], + "test": ["spleen_15.nii.gz", "spleen_23.nii.gz"], + } + json_str = json.dumps(test_data) + file_path = os.path.join(tempdir, "test_data.json") + with open(file_path, "w") as json_file: + json_file.write(json_str) + + data_list = DatasetFunc( + data=file_path, func=load_decathlon_datalist, data_list_key="training", base_dir=tempdir + ) + # partition dataset for train / validation + data_partition = DatasetFunc( + data=data_list, func=lambda x, **kwargs: partition_dataset(x, **kwargs)[0], num_partitions=2 + ) + dataset = Dataset(data=data_partition, transform=None) + self.assertEqual(dataset[0]["image"], os.path.join(tempdir, "spleen_19.nii.gz")) + self.assertEqual(dataset[0]["label"], os.path.join(tempdir, "spleen_19.nii.gz")) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_dataset_summary.py b/tests/test_dataset_summary.py index 172d4980dd..5569c51a0c 100644 --- a/tests/test_dataset_summary.py +++ b/tests/test_dataset_summary.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,6 +22,15 @@ from monai.utils import set_determinism +def test_collate(batch): + elem = batch[0] + elem_type = type(elem) + if isinstance(elem, np.ndarray): + return np.stack(batch, 0) + elif isinstance(elem, dict): + return elem_type({key: test_collate([d[key] for d in batch]) for key in elem}) + + class TestDatasetSummary(unittest.TestCase): def test_spacing_intensity(self): set_determinism(seed=0) @@ -40,9 +49,12 @@ def test_spacing_intensity(self): {"image": image_name, "label": label_name} for image_name, label_name in zip(train_images, train_labels) ] - dataset = Dataset(data=data_dicts, transform=LoadImaged(keys=["image", "label"])) + dataset = Dataset( + data=data_dicts, transform=LoadImaged(keys=["image", "label"], meta_keys=["test1", "test2"]) + ) - calculator = DatasetSummary(dataset, num_workers=4) + # test **kwargs of `DatasetSummary` for `DataLoader` + calculator = DatasetSummary(dataset, num_workers=4, meta_key="test1", collate_fn=test_collate) target_spacing = calculator.get_target_spacing() self.assertEqual(target_spacing, (1.0, 1.0, 1.0)) @@ -74,7 +86,7 @@ def test_anisotropic_spacing(self): dataset = Dataset(data=data_dicts, transform=LoadImaged(keys=["image", "label"])) - calculator = DatasetSummary(dataset, num_workers=4) + calculator = DatasetSummary(dataset, num_workers=4, meta_key_postfix="meta_dict") target_spacing = calculator.get_target_spacing(anisotropic_threshold=4.0, percentile=20.0) np.testing.assert_allclose(target_spacing, (1.0, 1.0, 1.8)) diff --git a/tests/test_decathlondataset.py b/tests/test_decathlondataset.py index 0756902385..9a785668ac 100644 --- a/tests/test_decathlondataset.py +++ b/tests/test_decathlondataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -47,6 +47,7 @@ def _test_dataset(dataset): transform=transform, section="validation", download=True, + copy_cache=False, ) except (ContentTooShortError, HTTPError, RuntimeError) as e: print(str(e)) diff --git a/tests/test_decollate.py b/tests/test_decollate.py index fc0622c8f0..dc43cfc422 100644 --- a/tests/test_decollate.py +++ b/tests/test_decollate.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_deepedit_transforms.py b/tests/test_deepedit_transforms.py index 391b724da9..aa0a73183d 100644 --- a/tests/test_deepedit_transforms.py +++ b/tests/test_deepedit_transforms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_deepgrow_dataset.py b/tests/test_deepgrow_dataset.py index 147d8e7099..ff8de87b81 100644 --- a/tests/test_deepgrow_dataset.py +++ b/tests/test_deepgrow_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_deepgrow_interaction.py b/tests/test_deepgrow_interaction.py index 016ba17251..b040348b62 100644 --- a/tests/test_deepgrow_interaction.py +++ b/tests/test_deepgrow_interaction.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_deepgrow_transforms.py b/tests/test_deepgrow_transforms.py index 3085309bdc..5a3f55be60 100644 --- a/tests/test_deepgrow_transforms.py +++ b/tests/test_deepgrow_transforms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_delete_itemsd.py b/tests/test_delete_itemsd.py index b7cd104c46..18138119b5 100644 --- a/tests/test_delete_itemsd.py +++ b/tests/test_delete_itemsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_densenet.py b/tests/test_densenet.py index ba4b7afcb4..47f584297e 100644 --- a/tests/test_densenet.py +++ b/tests/test_densenet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 9c7fe4f632..545031321b 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -29,7 +29,7 @@ def test_warning(self): def foo2(): pass - print(foo2()) + foo2() # should not raise any warnings def test_warning_milestone(self): """Test deprecated decorator with `since` and `removed` set for a milestone version""" @@ -172,7 +172,7 @@ def test_arg_warn2(self): """Test deprecated_arg decorator with just `since` set.""" @deprecated_arg("b", since=self.prev_version, version_val=self.test_version) - def afoo2(a, **kwargs): + def afoo2(a, **kw): pass afoo2(1) # ok when no b provided @@ -235,6 +235,19 @@ def afoo4(a, b=None): self.assertRaises(DeprecatedError, lambda: afoo4(1, b=2)) + def test_arg_except3_unknown(self): + """ + Test deprecated_arg decorator raises exception with `removed` set in the past. + with unknown version and kwargs + """ + + @deprecated_arg("b", removed=self.prev_version, version_val="0+untagged.1.g3131155") + def afoo4(a, b=None, **kwargs): + pass + + self.assertRaises(DeprecatedError, lambda: afoo4(1, b=2)) + self.assertRaises(DeprecatedError, lambda: afoo4(1, b=2, c=3)) + def test_replacement_arg(self): """ Test deprecated arg being replaced. @@ -245,10 +258,36 @@ def afoo4(a, b=None): return a self.assertEqual(afoo4(b=2), 2) - # self.assertRaises(DeprecatedError, lambda: afoo4(1, b=2)) self.assertEqual(afoo4(1, b=2), 1) # new name is in use self.assertEqual(afoo4(a=1, b=2), 1) # prefers the new arg + def test_replacement_arg1(self): + """ + Test deprecated arg being replaced with kwargs. + """ + + @deprecated_arg("b", new_name="a", since=self.prev_version, version_val=self.test_version) + def afoo4(a, *args, **kwargs): + return a + + self.assertEqual(afoo4(b=2), 2) + self.assertEqual(afoo4(1, b=2, c=3), 1) # new name is in use + self.assertEqual(afoo4(a=1, b=2, c=3), 1) # prefers the new arg + + def test_replacement_arg2(self): + """ + Test deprecated arg (with a default value) being replaced. + """ + + @deprecated_arg("b", new_name="a", since=self.prev_version, version_val=self.test_version) + def afoo4(a, b=None, **kwargs): + return a, kwargs + + self.assertEqual(afoo4(b=2, c=3), (2, {"c": 3})) + self.assertEqual(afoo4(1, b=2, c=3), (1, {"c": 3})) # new name is in use + self.assertEqual(afoo4(a=1, b=2, c=3), (1, {"c": 3})) # prefers the new arg + self.assertEqual(afoo4(1, 2, c=3), (1, {"c": 3})) # prefers the new positional arg + if __name__ == "__main__": unittest.main() diff --git a/tests/test_detect_envelope.py b/tests/test_detect_envelope.py index 30d6d889eb..f1b9c7ad1a 100644 --- a/tests/test_detect_envelope.py +++ b/tests/test_detect_envelope.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dice_ce_loss.py b/tests/test_dice_ce_loss.py index 66cfb36e99..b11165ca9c 100644 --- a/tests/test_dice_ce_loss.py +++ b/tests/test_dice_ce_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dice_focal_loss.py b/tests/test_dice_focal_loss.py index a69df21693..c611fe4160 100644 --- a/tests/test_dice_focal_loss.py +++ b/tests/test_dice_focal_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dice_loss.py b/tests/test_dice_loss.py index a6d4c987c7..4e45393de6 100644 --- a/tests/test_dice_loss.py +++ b/tests/test_dice_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dints_cell.py b/tests/test_dints_cell.py index e89bb06998..d480235b70 100644 --- a/tests/test_dints_cell.py +++ b/tests/test_dints_cell.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dints_mixop.py b/tests/test_dints_mixop.py index a827fc3207..b686069173 100644 --- a/tests/test_dints_mixop.py +++ b/tests/test_dints_mixop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dints_network.py b/tests/test_dints_network.py index bbdaadbcc4..8be5eb7ccd 100644 --- a/tests/test_dints_network.py +++ b/tests/test_dints_network.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_discriminator.py b/tests/test_discriminator.py index 52b9a10dd5..aa9b9720c4 100644 --- a/tests/test_discriminator.py +++ b/tests/test_discriminator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_divisible_pad.py b/tests/test_divisible_pad.py index bb58668908..f940636fa8 100644 --- a/tests/test_divisible_pad.py +++ b/tests/test_divisible_pad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_divisible_padd.py b/tests/test_divisible_padd.py index 44faeced7b..61fe917421 100644 --- a/tests/test_divisible_padd.py +++ b/tests/test_divisible_padd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_download_and_extract.py b/tests/test_download_and_extract.py index 164fcd723d..f896d4ae93 100644 --- a/tests/test_download_and_extract.py +++ b/tests/test_download_and_extract.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_downsample_block.py b/tests/test_downsample_block.py index d35218c6d7..ac2acb0845 100644 --- a/tests/test_downsample_block.py +++ b/tests/test_downsample_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dvf2ddf.py b/tests/test_dvf2ddf.py index cc3323cf13..d061cca7ff 100644 --- a/tests/test_dvf2ddf.py +++ b/tests/test_dvf2ddf.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dynunet.py b/tests/test_dynunet.py index ca19ea2b47..36ac9d0309 100644 --- a/tests/test_dynunet.py +++ b/tests/test_dynunet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_dynunet_block.py b/tests/test_dynunet_block.py index de3c018d78..1c83552766 100644 --- a/tests/test_dynunet_block.py +++ b/tests/test_dynunet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_efficientnet.py b/tests/test_efficientnet.py index d36157e6fa..0ab383fd56 100644 --- a/tests/test_efficientnet.py +++ b/tests/test_efficientnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -13,6 +13,7 @@ import unittest from typing import TYPE_CHECKING from unittest import skipUnless +from urllib.error import ContentTooShortError, HTTPError import torch from parameterized import parameterized diff --git a/tests/test_ensemble_evaluator.py b/tests/test_ensemble_evaluator.py index 7f63cb6401..c7554e9421 100644 --- a/tests/test_ensemble_evaluator.py +++ b/tests/test_ensemble_evaluator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_ensure_channel_first.py b/tests/test_ensure_channel_first.py index 0fb7759219..dd6168ec75 100644 --- a/tests/test_ensure_channel_first.py +++ b/tests/test_ensure_channel_first.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_ensure_channel_firstd.py b/tests/test_ensure_channel_firstd.py index b5e1abe4ca..9b9043e4ad 100644 --- a/tests/test_ensure_channel_firstd.py +++ b/tests/test_ensure_channel_firstd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_ensure_type.py b/tests/test_ensure_type.py index 80a6204325..f8a6ee30ff 100644 --- a/tests/test_ensure_type.py +++ b/tests/test_ensure_type.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_ensure_typed.py b/tests/test_ensure_typed.py index 4a6582f9d0..cadab9bd56 100644 --- a/tests/test_ensure_typed.py +++ b/tests/test_ensure_typed.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_enum_bound_interp.py b/tests/test_enum_bound_interp.py index f788f8ba17..7607619e7a 100644 --- a/tests/test_enum_bound_interp.py +++ b/tests/test_enum_bound_interp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_eval_mode.py b/tests/test_eval_mode.py index 45c551c209..bc9c97d238 100644 --- a/tests/test_eval_mode.py +++ b/tests/test_eval_mode.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_evenly_divisible_all_gather_dist.py b/tests/test_evenly_divisible_all_gather_dist.py index bf3bd1bacc..1bb3d887a0 100644 --- a/tests/test_evenly_divisible_all_gather_dist.py +++ b/tests/test_evenly_divisible_all_gather_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_factorized_increase.py b/tests/test_factorized_increase.py index 6ef3bb465d..a86f5a2db9 100644 --- a/tests/test_factorized_increase.py +++ b/tests/test_factorized_increase.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_factorized_reduce.py b/tests/test_factorized_reduce.py index 9ed1e8f6ca..d14418233e 100644 --- a/tests/test_factorized_reduce.py +++ b/tests/test_factorized_reduce.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_fg_bg_to_indices.py b/tests/test_fg_bg_to_indices.py index 0d35dd23f8..03eb770d6d 100644 --- a/tests/test_fg_bg_to_indices.py +++ b/tests/test_fg_bg_to_indices.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_fg_bg_to_indicesd.py b/tests/test_fg_bg_to_indicesd.py index 4691526d94..3be795919f 100644 --- a/tests/test_fg_bg_to_indicesd.py +++ b/tests/test_fg_bg_to_indicesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_file_basename.py b/tests/test_file_basename.py index cd1a08afb1..dc8b1316a2 100644 --- a/tests/test_file_basename.py +++ b/tests/test_file_basename.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -74,6 +74,11 @@ def test_value(self): expected = os.path.join(output_tmp, "test", "test_post_8") self.assertEqual(result, expected) + def test_relative_path(self): + output = create_file_basename("", "test.txt", "output", "", makedirs=False) + expected = os.path.join("output", "test", "test") + self.assertEqual(output, expected) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_fill_holes.py b/tests/test_fill_holes.py index 073982e811..9f9dc1fc2e 100644 --- a/tests/test_fill_holes.py +++ b/tests/test_fill_holes.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_fill_holesd.py b/tests/test_fill_holesd.py index 432c60ba74..f7aa9f6108 100644 --- a/tests/test_fill_holesd.py +++ b/tests/test_fill_holesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_flip.py b/tests/test_flip.py index 8547f8aeb4..17cf0d2c39 100644 --- a/tests/test_flip.py +++ b/tests/test_flip.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_flipd.py b/tests/test_flipd.py index 2fa783f8ad..900779f4e0 100644 --- a/tests/test_flipd.py +++ b/tests/test_flipd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_focal_loss.py b/tests/test_focal_loss.py index bec3a6fb84..d8a9c8ab5b 100644 --- a/tests/test_focal_loss.py +++ b/tests/test_focal_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_fourier.py b/tests/test_fourier.py index 488bf0cbf9..e1b5f3089d 100644 --- a/tests/test_fourier.py +++ b/tests/test_fourier.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_fullyconnectednet.py b/tests/test_fullyconnectednet.py index ec91a99c3e..6378ec9718 100644 --- a/tests/test_fullyconnectednet.py +++ b/tests/test_fullyconnectednet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian.py b/tests/test_gaussian.py index b17663652b..461b11d076 100644 --- a/tests/test_gaussian.py +++ b/tests/test_gaussian.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian_filter.py b/tests/test_gaussian_filter.py index 62aea524b8..9d76e44cec 100644 --- a/tests/test_gaussian_filter.py +++ b/tests/test_gaussian_filter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian_sharpen.py b/tests/test_gaussian_sharpen.py index 9130e33656..547febdfaf 100644 --- a/tests/test_gaussian_sharpen.py +++ b/tests/test_gaussian_sharpen.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian_sharpend.py b/tests/test_gaussian_sharpend.py index 4b84eb9c12..d9ef503532 100644 --- a/tests/test_gaussian_sharpend.py +++ b/tests/test_gaussian_sharpend.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian_smooth.py b/tests/test_gaussian_smooth.py index 24ecfb88e8..53f2fc396b 100644 --- a/tests/test_gaussian_smooth.py +++ b/tests/test_gaussian_smooth.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gaussian_smoothd.py b/tests/test_gaussian_smoothd.py index ae358dd59a..839bac81fe 100644 --- a/tests/test_gaussian_smoothd.py +++ b/tests/test_gaussian_smoothd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generalized_dice_loss.py b/tests/test_generalized_dice_loss.py index 9611bef177..f93878a683 100644 --- a/tests/test_generalized_dice_loss.py +++ b/tests/test_generalized_dice_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generalized_wasserstein_dice_loss.py b/tests/test_generalized_wasserstein_dice_loss.py index 5ad946d20d..2c33d365f4 100644 --- a/tests/test_generalized_wasserstein_dice_loss.py +++ b/tests/test_generalized_wasserstein_dice_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generate_label_classes_crop_centers.py b/tests/test_generate_label_classes_crop_centers.py index 0e40750276..4f64aadc26 100644 --- a/tests/test_generate_label_classes_crop_centers.py +++ b/tests/test_generate_label_classes_crop_centers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generate_param_groups.py b/tests/test_generate_param_groups.py index c718f2c729..0b259442ea 100644 --- a/tests/test_generate_param_groups.py +++ b/tests/test_generate_param_groups.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generate_pos_neg_label_crop_centers.py b/tests/test_generate_pos_neg_label_crop_centers.py index b8f2840757..e1d9398fe3 100644 --- a/tests/test_generate_pos_neg_label_crop_centers.py +++ b/tests/test_generate_pos_neg_label_crop_centers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generate_spatial_bounding_box.py b/tests/test_generate_spatial_bounding_box.py index d73b9fafcc..00c9d49724 100644 --- a/tests/test_generate_spatial_bounding_box.py +++ b/tests/test_generate_spatial_bounding_box.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_generator.py b/tests/test_generator.py index b5d846febc..617655f86e 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_get_equivalent_dtype.py b/tests/test_get_equivalent_dtype.py index de2379b15b..fc0867523d 100644 --- a/tests/test_get_equivalent_dtype.py +++ b/tests/test_get_equivalent_dtype.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_get_extreme_points.py b/tests/test_get_extreme_points.py index 269cf63cce..457351b98c 100644 --- a/tests/test_get_extreme_points.py +++ b/tests/test_get_extreme_points.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_get_layers.py b/tests/test_get_layers.py index e6ea810a6b..6109052d1f 100644 --- a/tests/test_get_layers.py +++ b/tests/test_get_layers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_get_package_version.py b/tests/test_get_package_version.py index beddb340ab..c4e15c9d09 100644 --- a/tests/test_get_package_version.py +++ b/tests/test_get_package_version.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gibbs_noise.py b/tests/test_gibbs_noise.py index 2c5e117eaf..3fbe047944 100644 --- a/tests/test_gibbs_noise.py +++ b/tests/test_gibbs_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gibbs_noised.py b/tests/test_gibbs_noised.py index f02052818f..4905300703 100644 --- a/tests/test_gibbs_noised.py +++ b/tests/test_gibbs_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_global_mutual_information_loss.py b/tests/test_global_mutual_information_loss.py index b6defb6aca..1e97122d08 100644 --- a/tests/test_global_mutual_information_loss.py +++ b/tests/test_global_mutual_information_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_globalnet.py b/tests/test_globalnet.py index 32bc58f610..ef0209e397 100644 --- a/tests/test_globalnet.py +++ b/tests/test_globalnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_gmm.py b/tests/test_gmm.py index 641f6b998b..f085dd916c 100644 --- a/tests/test_gmm.py +++ b/tests/test_gmm.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_grid_dataset.py b/tests/test_grid_dataset.py index 3c5c5bd4fa..9c4bcc52ae 100644 --- a/tests/test_grid_dataset.py +++ b/tests/test_grid_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_grid_distortion.py b/tests/test_grid_distortion.py index 09fcb856f3..5e7ccd7c32 100644 --- a/tests/test_grid_distortion.py +++ b/tests/test_grid_distortion.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_grid_distortiond.py b/tests/test_grid_distortiond.py index 55e2e6ad1d..662596f935 100644 --- a/tests/test_grid_distortiond.py +++ b/tests/test_grid_distortiond.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_grid_pull.py b/tests/test_grid_pull.py index 25e1d7c1d8..b0f19d1a00 100644 --- a/tests/test_grid_pull.py +++ b/tests/test_grid_pull.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_checkpoint_loader.py b/tests/test_handler_checkpoint_loader.py index 81a3cdc96d..ec43ed357b 100644 --- a/tests/test_handler_checkpoint_loader.py +++ b/tests/test_handler_checkpoint_loader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_checkpoint_saver.py b/tests/test_handler_checkpoint_saver.py index 86544e5321..1b746184a4 100644 --- a/tests/test_handler_checkpoint_saver.py +++ b/tests/test_handler_checkpoint_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_classification_saver.py b/tests/test_handler_classification_saver.py index e06c6e95f0..a498fa2b5c 100644 --- a/tests/test_handler_classification_saver.py +++ b/tests/test_handler_classification_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_classification_saver_dist.py b/tests/test_handler_classification_saver_dist.py index d9bbe67ecd..e92009d37f 100644 --- a/tests/test_handler_classification_saver_dist.py +++ b/tests/test_handler_classification_saver_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_confusion_matrix.py b/tests/test_handler_confusion_matrix.py index ce02192442..5bddef26af 100644 --- a/tests/test_handler_confusion_matrix.py +++ b/tests/test_handler_confusion_matrix.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_confusion_matrix_dist.py b/tests/test_handler_confusion_matrix_dist.py index 31e853cd00..325a799098 100644 --- a/tests/test_handler_confusion_matrix_dist.py +++ b/tests/test_handler_confusion_matrix_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_decollate_batch.py b/tests/test_handler_decollate_batch.py index d2282a971f..1a43ae295b 100644 --- a/tests/test_handler_decollate_batch.py +++ b/tests/test_handler_decollate_batch.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_early_stop.py b/tests/test_handler_early_stop.py index 4707a8b3cc..36604e5735 100644 --- a/tests/test_handler_early_stop.py +++ b/tests/test_handler_early_stop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_garbage_collector.py b/tests/test_handler_garbage_collector.py index 23ecfbbf37..0350ba62fb 100644 --- a/tests/test_handler_garbage_collector.py +++ b/tests/test_handler_garbage_collector.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_hausdorff_distance.py b/tests/test_handler_hausdorff_distance.py index 038b2e5473..7e38f0ad56 100644 --- a/tests/test_handler_hausdorff_distance.py +++ b/tests/test_handler_hausdorff_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_lr_scheduler.py b/tests/test_handler_lr_scheduler.py index 82a62dce21..b260686315 100644 --- a/tests/test_handler_lr_scheduler.py +++ b/tests/test_handler_lr_scheduler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,7 +10,10 @@ # limitations under the License. import logging +import os +import re import sys +import tempfile import unittest import numpy as np @@ -24,6 +27,8 @@ class TestHandlerLrSchedule(unittest.TestCase): def test_content(self): logging.basicConfig(stream=sys.stdout, level=logging.INFO) data = [0] * 8 + test_lr = 0.1 + gamma = 0.1 # set up engine def _train_func(engine, batch): @@ -41,24 +46,45 @@ def run_validation(engine): net = torch.nn.PReLU() def _reduce_lr_on_plateau(): - optimizer = torch.optim.SGD(net.parameters(), 0.1) + optimizer = torch.optim.SGD(net.parameters(), test_lr) lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=1) handler = LrScheduleHandler(lr_scheduler, step_transform=lambda x: val_engine.state.metrics["val_loss"]) handler.attach(train_engine) - return lr_scheduler + return handler - def _reduce_on_step(): - optimizer = torch.optim.SGD(net.parameters(), 0.1) - lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=2, gamma=0.1) - handler = LrScheduleHandler(lr_scheduler) - handler.attach(train_engine) - return lr_scheduler + with tempfile.TemporaryDirectory() as tempdir: + key_to_handler = "test_log_lr" + key_to_print = "Current learning rate" + filename = os.path.join(tempdir, "test_lr.log") + # test with additional logging handler + file_saver = logging.FileHandler(filename, mode="w") + file_saver.setLevel(logging.INFO) + + def _reduce_on_step(): + optimizer = torch.optim.SGD(net.parameters(), test_lr) + lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=2, gamma=gamma) + handler = LrScheduleHandler(lr_scheduler, name=key_to_handler, logger_handler=file_saver) + handler.attach(train_engine) + handler.logger.setLevel(logging.INFO) + return handler + + schedulers = _reduce_lr_on_plateau(), _reduce_on_step() + + train_engine.run(data, max_epochs=5) + file_saver.close() + schedulers[1].logger.removeHandler(file_saver) - schedulers = _reduce_lr_on_plateau(), _reduce_on_step() + with open(filename) as f: + output_str = f.read() + has_key_word = re.compile(f".*{key_to_print}.*") + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) - train_engine.run(data, max_epochs=5) for scheduler in schedulers: - np.testing.assert_allclose(scheduler._last_lr[0], 0.001) + np.testing.assert_allclose(scheduler.lr_scheduler._last_lr[0], 0.001) if __name__ == "__main__": diff --git a/tests/test_handler_mean_dice.py b/tests/test_handler_mean_dice.py index b505502e8d..f309c7e693 100644 --- a/tests/test_handler_mean_dice.py +++ b/tests/test_handler_mean_dice.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_metric_logger.py b/tests/test_handler_metric_logger.py index 5812605cd7..c3de866c5c 100644 --- a/tests/test_handler_metric_logger.py +++ b/tests/test_handler_metric_logger.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_metrics_saver.py b/tests/test_handler_metrics_saver.py index 17c23be274..1f65eb46dc 100644 --- a/tests/test_handler_metrics_saver.py +++ b/tests/test_handler_metrics_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_metrics_saver_dist.py b/tests/test_handler_metrics_saver_dist.py index 0b65b14886..06dcbafa28 100644 --- a/tests/test_handler_metrics_saver_dist.py +++ b/tests/test_handler_metrics_saver_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_mlflow.py b/tests/test_handler_mlflow.py index 808ebffe33..05b99b0053 100644 --- a/tests/test_handler_mlflow.py +++ b/tests/test_handler_mlflow.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_nvtx.py b/tests/test_handler_nvtx.py index 6723e55892..eeca15ea6f 100644 --- a/tests/test_handler_nvtx.py +++ b/tests/test_handler_nvtx.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_parameter_scheduler.py b/tests/test_handler_parameter_scheduler.py index 63d3f7b064..72742f1956 100644 --- a/tests/test_handler_parameter_scheduler.py +++ b/tests/test_handler_parameter_scheduler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_post_processing.py b/tests/test_handler_post_processing.py index 4b47ece063..89087e1765 100644 --- a/tests/test_handler_post_processing.py +++ b/tests/test_handler_post_processing.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_prob_map_producer.py b/tests/test_handler_prob_map_producer.py index 316cd6f70a..b3f79cf587 100644 --- a/tests/test_handler_prob_map_producer.py +++ b/tests/test_handler_prob_map_producer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_regression_metrics.py b/tests/test_handler_regression_metrics.py index 7bb72dd5d5..c6af76a4db 100644 --- a/tests/test_handler_regression_metrics.py +++ b/tests/test_handler_regression_metrics.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_regression_metrics_dist.py b/tests/test_handler_regression_metrics_dist.py index c336ccf28c..a8b644d550 100644 --- a/tests/test_handler_regression_metrics_dist.py +++ b/tests/test_handler_regression_metrics_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_rocauc.py b/tests/test_handler_rocauc.py index bd32922777..6e2d6be27e 100644 --- a/tests/test_handler_rocauc.py +++ b/tests/test_handler_rocauc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_rocauc_dist.py b/tests/test_handler_rocauc_dist.py index 0905816868..5113911d7c 100644 --- a/tests/test_handler_rocauc_dist.py +++ b/tests/test_handler_rocauc_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_segmentation_saver.py b/tests/test_handler_segmentation_saver.py index 78dea0a68b..3632a98cfc 100644 --- a/tests/test_handler_segmentation_saver.py +++ b/tests/test_handler_segmentation_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_smartcache.py b/tests/test_handler_smartcache.py index 23a6aa7500..ec96d47e3d 100644 --- a/tests/test_handler_smartcache.py +++ b/tests/test_handler_smartcache.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_stats.py b/tests/test_handler_stats.py index 49433195c8..ee0df74002 100644 --- a/tests/test_handler_stats.py +++ b/tests/test_handler_stats.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -45,18 +45,19 @@ def _update_metric(engine): # set up testing handler stats_handler = StatsHandler(name=key_to_handler, logger_handler=log_handler) stats_handler.attach(engine) + stats_handler.logger.setLevel(logging.INFO) engine.run(range(3), max_epochs=2) # check logging output output_str = log_stream.getvalue() log_handler.close() - grep = re.compile(f".*{key_to_handler}.*") has_key_word = re.compile(f".*{key_to_print}.*") - for idx, line in enumerate(output_str.split("\n")): - if grep.match(line): - if idx in [5, 10]: - self.assertTrue(has_key_word.match(line)) + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) def test_loss_print(self): log_stream = StringIO() @@ -74,18 +75,19 @@ def _train_func(engine, batch): # set up testing handler stats_handler = StatsHandler(name=key_to_handler, tag_name=key_to_print, logger_handler=log_handler) stats_handler.attach(engine) + stats_handler.logger.setLevel(logging.INFO) engine.run(range(3), max_epochs=2) # check logging output output_str = log_stream.getvalue() log_handler.close() - grep = re.compile(f".*{key_to_handler}.*") has_key_word = re.compile(f".*{key_to_print}.*") - for idx, line in enumerate(output_str.split("\n")): - if grep.match(line): - if idx in [1, 2, 3, 6, 7, 8]: - self.assertTrue(has_key_word.match(line)) + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) def test_loss_dict(self): log_stream = StringIO() @@ -102,21 +104,22 @@ def _train_func(engine, batch): # set up testing handler stats_handler = StatsHandler( - name=key_to_handler, output_transform=lambda x: {key_to_print: x}, logger_handler=log_handler + name=key_to_handler, output_transform=lambda x: {key_to_print: x[0]}, logger_handler=log_handler ) stats_handler.attach(engine) + stats_handler.logger.setLevel(logging.INFO) engine.run(range(3), max_epochs=2) # check logging output output_str = log_stream.getvalue() log_handler.close() - grep = re.compile(f".*{key_to_handler}.*") has_key_word = re.compile(f".*{key_to_print}.*") - for idx, line in enumerate(output_str.split("\n")): - if grep.match(line): - if idx in [1, 2, 3, 6, 7, 8]: - self.assertTrue(has_key_word.match(line)) + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) def test_loss_file(self): key_to_handler = "test_logging" @@ -136,18 +139,19 @@ def _train_func(engine, batch): # set up testing handler stats_handler = StatsHandler(name=key_to_handler, tag_name=key_to_print, logger_handler=handler) stats_handler.attach(engine) + stats_handler.logger.setLevel(logging.INFO) engine.run(range(3), max_epochs=2) handler.close() stats_handler.logger.removeHandler(handler) with open(filename) as f: output_str = f.read() - grep = re.compile(f".*{key_to_handler}.*") has_key_word = re.compile(f".*{key_to_print}.*") - for idx, line in enumerate(output_str.split("\n")): - if grep.match(line): - if idx in [1, 2, 3, 6, 7, 8]: - self.assertTrue(has_key_word.match(line)) + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) def test_exception(self): # set up engine @@ -190,17 +194,19 @@ def _update_metric(engine): name=key_to_handler, state_attributes=["test1", "test2", "test3"], logger_handler=log_handler ) stats_handler.attach(engine) + stats_handler.logger.setLevel(logging.INFO) engine.run(range(3), max_epochs=2) # check logging output output_str = log_stream.getvalue() log_handler.close() - grep = re.compile(f".*{key_to_handler}.*") has_key_word = re.compile(".*State values.*") - for idx, line in enumerate(output_str.split("\n")): - if grep.match(line) and idx in [5, 10]: - self.assertTrue(has_key_word.match(line)) + content_count = 0 + for line in output_str.split("\n"): + if has_key_word.match(line): + content_count += 1 + self.assertTrue(content_count > 0) if __name__ == "__main__": diff --git a/tests/test_handler_surface_distance.py b/tests/test_handler_surface_distance.py index f6ec0286a8..c990181998 100644 --- a/tests/test_handler_surface_distance.py +++ b/tests/test_handler_surface_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_tb_image.py b/tests/test_handler_tb_image.py index b5d963eedf..d11bbfec59 100644 --- a/tests/test_handler_tb_image.py +++ b/tests/test_handler_tb_image.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_tb_stats.py b/tests/test_handler_tb_stats.py index f0c4d49fd0..eae60f9b09 100644 --- a/tests/test_handler_tb_stats.py +++ b/tests/test_handler_tb_stats.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_handler_validation.py b/tests/test_handler_validation.py index 06f400109d..42ffc8b9eb 100644 --- a/tests/test_handler_validation.py +++ b/tests/test_handler_validation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_hashing.py b/tests/test_hashing.py index ca317a72e8..5a1265bd48 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_hausdorff_distance.py b/tests/test_hausdorff_distance.py index 182c8cdaca..79a2c84b37 100644 --- a/tests/test_hausdorff_distance.py +++ b/tests/test_hausdorff_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_header_correct.py b/tests/test_header_correct.py index 4a8927fa80..aa0a4dde08 100644 --- a/tests/test_header_correct.py +++ b/tests/test_header_correct.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_highresnet.py b/tests/test_highresnet.py index 61af529b63..76c2203431 100644 --- a/tests/test_highresnet.py +++ b/tests/test_highresnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_hilbert_transform.py b/tests/test_hilbert_transform.py index ffe5824034..10aa83293f 100644 --- a/tests/test_hilbert_transform.py +++ b/tests/test_hilbert_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_histogram_normalize.py b/tests/test_histogram_normalize.py index 06fe7e6956..95aa37f26e 100644 --- a/tests/test_histogram_normalize.py +++ b/tests/test_histogram_normalize.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_histogram_normalized.py b/tests/test_histogram_normalized.py index e11ee77da5..7b86a9685f 100644 --- a/tests/test_histogram_normalized.py +++ b/tests/test_histogram_normalized.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_identity.py b/tests/test_identity.py index 172860668c..60134c24a4 100644 --- a/tests/test_identity.py +++ b/tests/test_identity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_identityd.py b/tests/test_identityd.py index 665b7d5d1c..2df74ba2c6 100644 --- a/tests/test_identityd.py +++ b/tests/test_identityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_image_dataset.py b/tests/test_image_dataset.py index 3b3c06c87c..c478f28d13 100644 --- a/tests/test_image_dataset.py +++ b/tests/test_image_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_img2tensorboard.py b/tests/test_img2tensorboard.py index 5d76231356..58c4d3cfab 100644 --- a/tests/test_img2tensorboard.py +++ b/tests/test_img2tensorboard.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_init_reader.py b/tests/test_init_reader.py index 0017c6acee..03a63cc375 100644 --- a/tests/test_init_reader.py +++ b/tests/test_init_reader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_classification_2d.py b/tests/test_integration_classification_2d.py index cafad9dcf0..2c0c9e1f2e 100644 --- a/tests/test_integration_classification_2d.py +++ b/tests/test_integration_classification_2d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_determinism.py b/tests/test_integration_determinism.py index 6c858b7832..97e510d03c 100644 --- a/tests/test_integration_determinism.py +++ b/tests/test_integration_determinism.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_fast_train.py b/tests/test_integration_fast_train.py index b2706dbb47..3522a57342 100644 --- a/tests/test_integration_fast_train.py +++ b/tests/test_integration_fast_train.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_segmentation_3d.py b/tests/test_integration_segmentation_3d.py index 8898bcdbf8..97e339f5bb 100644 --- a/tests/test_integration_segmentation_3d.py +++ b/tests/test_integration_segmentation_3d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_sliding_window.py b/tests/test_integration_sliding_window.py index 0522bf080e..af49e3db77 100644 --- a/tests/test_integration_sliding_window.py +++ b/tests/test_integration_sliding_window.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_stn.py b/tests/test_integration_stn.py index ae00892159..ca067c4d78 100644 --- a/tests/test_integration_stn.py +++ b/tests/test_integration_stn.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_unet_2d.py b/tests/test_integration_unet_2d.py index 88e6d7e795..e60c91968a 100644 --- a/tests/test_integration_unet_2d.py +++ b/tests/test_integration_unet_2d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_workflows.py b/tests/test_integration_workflows.py index 7018c53240..adb6acfcdd 100644 --- a/tests/test_integration_workflows.py +++ b/tests/test_integration_workflows.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_integration_workflows_gan.py b/tests/test_integration_workflows_gan.py index d4b7d99b62..790b222ea0 100644 --- a/tests/test_integration_workflows_gan.py +++ b/tests/test_integration_workflows_gan.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_intensity_stats.py b/tests/test_intensity_stats.py index 6fe8237f00..3479306180 100644 --- a/tests/test_intensity_stats.py +++ b/tests/test_intensity_stats.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_intensity_statsd.py b/tests/test_intensity_statsd.py index 596c80deb5..97d3de80c0 100644 --- a/tests/test_intensity_statsd.py +++ b/tests/test_intensity_statsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_inverse.py b/tests/test_inverse.py index 992a919065..4455009658 100644 --- a/tests/test_inverse.py +++ b/tests/test_inverse.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_inverse_collation.py b/tests/test_inverse_collation.py index d04360a95d..3c293070bb 100644 --- a/tests/test_inverse_collation.py +++ b/tests/test_inverse_collation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_invertd.py b/tests/test_invertd.py index c0a949dd9a..1dd4e2eecf 100644 --- a/tests/test_invertd.py +++ b/tests/test_invertd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_is_supported_format.py b/tests/test_is_supported_format.py index 0008712f96..71a44bd190 100644 --- a/tests/test_is_supported_format.py +++ b/tests/test_is_supported_format.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_iterable_dataset.py b/tests/test_iterable_dataset.py index 74d0f79a3c..2c47a2181e 100644 --- a/tests/test_iterable_dataset.py +++ b/tests/test_iterable_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_k_space_spike_noise.py b/tests/test_k_space_spike_noise.py index 66763f286f..43717aa214 100644 --- a/tests/test_k_space_spike_noise.py +++ b/tests/test_k_space_spike_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_k_space_spike_noised.py b/tests/test_k_space_spike_noised.py index 3fa6a394f3..0230f40b15 100644 --- a/tests/test_k_space_spike_noised.py +++ b/tests/test_k_space_spike_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_keep_largest_connected_component.py b/tests/test_keep_largest_connected_component.py index 5a7fc80e2e..5307830cd3 100644 --- a/tests/test_keep_largest_connected_component.py +++ b/tests/test_keep_largest_connected_component.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_keep_largest_connected_componentd.py b/tests/test_keep_largest_connected_componentd.py index 097787bd3f..94a36feed0 100644 --- a/tests/test_keep_largest_connected_componentd.py +++ b/tests/test_keep_largest_connected_componentd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_filter.py b/tests/test_label_filter.py index b004511265..b782f90441 100644 --- a/tests/test_label_filter.py +++ b/tests/test_label_filter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_filterd.py b/tests/test_label_filterd.py index 19e6f94a0f..d53dc21faf 100644 --- a/tests/test_label_filterd.py +++ b/tests/test_label_filterd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_to_contour.py b/tests/test_label_to_contour.py index e63b581e27..fef40af08d 100644 --- a/tests/test_label_to_contour.py +++ b/tests/test_label_to_contour.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_to_contourd.py b/tests/test_label_to_contourd.py index 922362f1d9..6481e803ba 100644 --- a/tests/test_label_to_contourd.py +++ b/tests/test_label_to_contourd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_to_mask.py b/tests/test_label_to_mask.py index 6c8f935fbc..8f81a8da1a 100644 --- a/tests/test_label_to_mask.py +++ b/tests/test_label_to_mask.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_label_to_maskd.py b/tests/test_label_to_maskd.py index b2073e8ac3..e67b857502 100644 --- a/tests/test_label_to_maskd.py +++ b/tests/test_label_to_maskd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lambda.py b/tests/test_lambda.py index 738c81130d..c187cc979b 100644 --- a/tests/test_lambda.py +++ b/tests/test_lambda.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lambdad.py b/tests/test_lambdad.py index 05ba0ff6bc..30d70f40fb 100644 --- a/tests/test_lambdad.py +++ b/tests/test_lambdad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lesion_froc.py b/tests/test_lesion_froc.py index 4a67c8d0b3..6b4989a9d5 100644 --- a/tests/test_lesion_froc.py +++ b/tests/test_lesion_froc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_list_data_collate.py b/tests/test_list_data_collate.py index eebac69fcf..93b06cc187 100644 --- a/tests/test_list_data_collate.py +++ b/tests/test_list_data_collate.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_list_to_dict.py b/tests/test_list_to_dict.py index c366e8f3bd..ec81310c9f 100644 --- a/tests/test_list_to_dict.py +++ b/tests/test_list_to_dict.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lltm.py b/tests/test_lltm.py index 4186c91246..7633c2fe34 100644 --- a/tests/test_lltm.py +++ b/tests/test_lltm.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lmdbdataset.py b/tests/test_lmdbdataset.py index fbdb651297..b624e5c4e3 100644 --- a/tests/test_lmdbdataset.py +++ b/tests/test_lmdbdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lmdbdataset_dist.py b/tests/test_lmdbdataset_dist.py new file mode 100644 index 0000000000..cad2949dde --- /dev/null +++ b/tests/test_lmdbdataset_dist.py @@ -0,0 +1,72 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import shutil +import tempfile +import unittest + +import numpy as np + +from monai.data import LMDBDataset, json_hashing +from monai.transforms import Transform +from tests.utils import DistCall, DistTestCase, skip_if_windows + + +class _InplaceXform(Transform): + def __call__(self, data): + if data: + data[0] = data[0] + np.pi + else: + data.append(1) + return data + + +@skip_if_windows +class TestMPLMDBDataset(DistTestCase): + def setUp(self): + self.tempdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tempdir) + + @DistCall(nnodes=1, nproc_per_node=1) + def test_mp_cache(self): + items = [[list(range(i))] for i in range(5)] + + ds = LMDBDataset(items, transform=_InplaceXform(), cache_dir=self.tempdir, lmdb_kwargs={"map_size": 10 * 1024}) + self.assertEqual(items, [[[]], [[0]], [[0, 1]], [[0, 1, 2]], [[0, 1, 2, 3]]]) + ds1 = LMDBDataset(items, transform=_InplaceXform(), cache_dir=self.tempdir, lmdb_kwargs={"map_size": 10 * 1024}) + self.assertEqual(list(ds1), list(ds)) + self.assertEqual(items, [[[]], [[0]], [[0, 1]], [[0, 1, 2]], [[0, 1, 2, 3]]]) + + ds = LMDBDataset( + items, + transform=_InplaceXform(), + cache_dir=self.tempdir, + lmdb_kwargs={"map_size": 10 * 1024}, + hash_func=json_hashing, + ) + self.assertEqual(items, [[[]], [[0]], [[0, 1]], [[0, 1, 2]], [[0, 1, 2, 3]]]) + ds1 = LMDBDataset( + items, + transform=_InplaceXform(), + cache_dir=self.tempdir, + lmdb_kwargs={"map_size": 10 * 1024}, + hash_func=json_hashing, + ) + self.assertEqual(list(ds1), list(ds)) + self.assertEqual(items, [[[]], [[0]], [[0, 1]], [[0, 1, 2]], [[0, 1, 2, 3]]]) + + self.assertTrue(isinstance(ds1.info(), dict)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_load_decathlon_datalist.py b/tests/test_load_decathlon_datalist.py index d2113fccfa..91d144d84f 100644 --- a/tests/test_load_decathlon_datalist.py +++ b/tests/test_load_decathlon_datalist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_load_image.py b/tests/test_load_image.py index 849fd9b4e1..3a4d05f012 100644 --- a/tests/test_load_image.py +++ b/tests/test_load_image.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -90,12 +90,21 @@ def get_data(self, _obj): {"image_only": False, "reader": ITKReader(pixel_type=itk.UC)}, "tests/testing_data/CT_DICOM", (16, 16, 4), + (16, 16, 4), ] TEST_CASE_11 = [ {"image_only": False, "reader": "ITKReader", "pixel_type": itk.UC}, "tests/testing_data/CT_DICOM", (16, 16, 4), + (16, 16, 4), +] + +TEST_CASE_12 = [ + {"image_only": False, "reader": "ITKReader", "pixel_type": itk.UC, "reverse_indexing": True}, + "tests/testing_data/CT_DICOM", + (16, 16, 4), + (4, 16, 16), ] @@ -138,8 +147,8 @@ def test_itk_reader(self, input_param, filenames, expected_shape): np.testing.assert_allclose(header["original_affine"], np_diag) self.assertTupleEqual(result.shape, expected_shape) - @parameterized.expand([TEST_CASE_10, TEST_CASE_11]) - def test_itk_dicom_series_reader(self, input_param, filenames, expected_shape): + @parameterized.expand([TEST_CASE_10, TEST_CASE_11, TEST_CASE_12]) + def test_itk_dicom_series_reader(self, input_param, filenames, expected_shape, expected_np_shape): result, header = LoadImage(**input_param)(filenames) self.assertTrue("affine" in header) self.assertEqual(header["filename_or_obj"], f"{Path(filenames)}") @@ -154,8 +163,8 @@ def test_itk_dicom_series_reader(self, input_param, filenames, expected_shape): ] ), ) - self.assertTupleEqual(result.shape, expected_shape) self.assertTupleEqual(tuple(header["spatial_shape"]), expected_shape) + self.assertTupleEqual(result.shape, expected_np_shape) def test_itk_reader_multichannel(self): test_image = np.random.randint(0, 256, size=(256, 224, 3)).astype("uint8") @@ -163,12 +172,14 @@ def test_itk_reader_multichannel(self): filename = os.path.join(tempdir, "test_image.png") itk_np_view = itk.image_view_from_array(test_image, is_vector=True) itk.imwrite(itk_np_view, filename) - result, header = LoadImage(reader=ITKReader())(Path(filename)) + for flag in (False, True): + result, header = LoadImage(reader=ITKReader(reverse_indexing=flag))(Path(filename)) - self.assertTupleEqual(tuple(header["spatial_shape"]), (224, 256)) - np.testing.assert_allclose(result[:, :, 0], test_image[:, :, 0].T) - np.testing.assert_allclose(result[:, :, 1], test_image[:, :, 1].T) - np.testing.assert_allclose(result[:, :, 2], test_image[:, :, 2].T) + self.assertTupleEqual(tuple(header["spatial_shape"]), (224, 256)) + test_image = test_image.transpose(1, 0, 2) + np.testing.assert_allclose(result[:, :, 0], test_image[:, :, 0]) + np.testing.assert_allclose(result[:, :, 1], test_image[:, :, 1]) + np.testing.assert_allclose(result[:, :, 2], test_image[:, :, 2]) def test_load_nifti_multichannel(self): test_image = np.random.randint(0, 256, size=(31, 64, 16, 2)).astype(np.float32) @@ -185,6 +196,8 @@ def test_load_nifti_multichannel(self): self.assertTupleEqual(tuple(nib_header["spatial_shape"]), (16, 64, 31)) self.assertTupleEqual(tuple(nib_image.shape), (16, 64, 31, 2)) + np.testing.assert_allclose(itk_img, nib_image, atol=1e-3, rtol=1e-3) + def test_load_png(self): spatial_size = (256, 224) test_image = np.random.randint(0, 256, size=spatial_size) @@ -241,6 +254,15 @@ def test_my_reader(self): out = LoadImage()("test", reader=_MiniReader(is_compatible=False)) self.assertEqual(out[1]["name"], "my test") + def test_itk_meta(self): + """test metadata from a directory""" + out, meta = LoadImage(reader="ITKReader", pixel_type=itk.UC, series_meta=True)("tests/testing_data/CT_DICOM") + idx = "0008|103e" + label = itk.GDCMImageIO.GetLabelFromTag(idx, "")[1] + val = meta[idx] + expected = "Series Description=Routine Brain " + self.assertEqual(f"{label}={val}", expected) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_load_imaged.py b/tests/test_load_imaged.py index cfe85c7c9c..39885a2cae 100644 --- a/tests/test_load_imaged.py +++ b/tests/test_load_imaged.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_load_spacing_orientation.py b/tests/test_load_spacing_orientation.py index 48aac7ec56..7690adf284 100644 --- a/tests/test_load_spacing_orientation.py +++ b/tests/test_load_spacing_orientation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_loader_semaphore.py b/tests/test_loader_semaphore.py index 85c6d54f35..bbb2d4eef6 100644 --- a/tests/test_loader_semaphore.py +++ b/tests/test_loader_semaphore.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_local_normalized_cross_correlation_loss.py b/tests/test_local_normalized_cross_correlation_loss.py index 31954e727b..8070c27f90 100644 --- a/tests/test_local_normalized_cross_correlation_loss.py +++ b/tests/test_local_normalized_cross_correlation_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_localnet.py b/tests/test_localnet.py index dc680f15f9..1a288fb447 100644 --- a/tests/test_localnet.py +++ b/tests/test_localnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_localnet_block.py b/tests/test_localnet_block.py index f4e857a0fa..d85509344e 100644 --- a/tests/test_localnet_block.py +++ b/tests/test_localnet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_look_up_option.py b/tests/test_look_up_option.py index 60786f2fc5..89fec1b575 100644 --- a/tests/test_look_up_option.py +++ b/tests/test_look_up_option.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lr_finder.py b/tests/test_lr_finder.py index c3a7c83448..78c94d4e41 100644 --- a/tests/test_lr_finder.py +++ b/tests/test_lr_finder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_lr_scheduler.py b/tests/test_lr_scheduler.py index acafc87131..a3e1ea9dd6 100644 --- a/tests/test_lr_scheduler.py +++ b/tests/test_lr_scheduler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_map_binary_to_indices.py b/tests/test_map_binary_to_indices.py index 2d29aa7c0d..bc96231160 100644 --- a/tests/test_map_binary_to_indices.py +++ b/tests/test_map_binary_to_indices.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_map_classes_to_indices.py b/tests/test_map_classes_to_indices.py index ae75b90c16..2f32382f6b 100644 --- a/tests/test_map_classes_to_indices.py +++ b/tests/test_map_classes_to_indices.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_map_label_value.py b/tests/test_map_label_value.py index 5705074c06..0416858a74 100644 --- a/tests/test_map_label_value.py +++ b/tests/test_map_label_value.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_map_label_valued.py b/tests/test_map_label_valued.py index 426ac28836..cf8ca6c8e2 100644 --- a/tests/test_map_label_valued.py +++ b/tests/test_map_label_valued.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_map_transform.py b/tests/test_map_transform.py index 803e699a7d..dd77ccb099 100644 --- a/tests/test_map_transform.py +++ b/tests/test_map_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mask_intensity.py b/tests/test_mask_intensity.py index c2f7d661d6..b6cfe0e10c 100644 --- a/tests/test_mask_intensity.py +++ b/tests/test_mask_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mask_intensityd.py b/tests/test_mask_intensityd.py index c21e26eba6..fe61e7be04 100644 --- a/tests/test_mask_intensityd.py +++ b/tests/test_mask_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_masked_dice_loss.py b/tests/test_masked_dice_loss.py index ce74215023..317da3a316 100644 --- a/tests/test_masked_dice_loss.py +++ b/tests/test_masked_dice_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_masked_inference_wsi_dataset.py b/tests/test_masked_inference_wsi_dataset.py index 576b4c85f4..6cec5b4304 100644 --- a/tests/test_masked_inference_wsi_dataset.py +++ b/tests/test_masked_inference_wsi_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_masked_loss.py b/tests/test_masked_loss.py index b56fcbbcdb..9f28d51aa4 100644 --- a/tests/test_masked_loss.py +++ b/tests/test_masked_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_matshow3d.py b/tests/test_matshow3d.py index d720c18806..83984d1556 100644 --- a/tests/test_matshow3d.py +++ b/tests/test_matshow3d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mean_ensemble.py b/tests/test_mean_ensemble.py index 9136f3a963..b14f6f01d3 100644 --- a/tests/test_mean_ensemble.py +++ b/tests/test_mean_ensemble.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mean_ensembled.py b/tests/test_mean_ensembled.py index b3148fce6d..b5e1569d65 100644 --- a/tests/test_mean_ensembled.py +++ b/tests/test_mean_ensembled.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mednistdataset.py b/tests/test_mednistdataset.py index a833ab75f3..54fb11135a 100644 --- a/tests/test_mednistdataset.py +++ b/tests/test_mednistdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -43,7 +43,9 @@ def _test_dataset(dataset): self.assertTupleEqual(dataset[0]["image"].shape, (1, 64, 64)) try: # will start downloading if testing_dir doesn't have the MedNIST files - data = MedNISTDataset(root_dir=testing_dir, transform=transform, section="test", download=True) + data = MedNISTDataset( + root_dir=testing_dir, transform=transform, section="test", download=True, copy_cache=False + ) except (ContentTooShortError, HTTPError, RuntimeError) as e: print(str(e)) if isinstance(e, RuntimeError): diff --git a/tests/test_milmodel.py b/tests/test_milmodel.py index 9b21d4e2d1..ad04e96c60 100644 --- a/tests/test_milmodel.py +++ b/tests/test_milmodel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mlp.py b/tests/test_mlp.py index b6e78c9a66..6fec5b6854 100644 --- a/tests/test_mlp.py +++ b/tests/test_mlp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_mmar_download.py b/tests/test_mmar_download.py index 7e778fa334..2cae5969db 100644 --- a/tests/test_mmar_download.py +++ b/tests/test_mmar_download.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,7 +22,7 @@ from monai.apps import RemoteMMARKeys, download_mmar, get_model_spec, load_from_mmar from monai.apps.mmars import MODEL_DESC from monai.apps.mmars.mmars import _get_val -from tests.utils import SkipIfBeforePyTorchVersion, skip_if_quick +from tests.utils import skip_if_quick TEST_CASES = [["clara_pt_prostate_mri_segmentation_1"], ["clara_pt_covid19_ct_lesion_segmentation_1"]] TEST_EXTRACT_CASES = [ @@ -104,7 +104,6 @@ class TestMMMARDownload(unittest.TestCase): @parameterized.expand(TEST_CASES) @skip_if_quick - @SkipIfBeforePyTorchVersion((1, 6)) def test_download(self, idx): try: # test model specification diff --git a/tests/test_module_list.py b/tests/test_module_list.py index 3aefaf5e0c..5ec4aa9ff1 100644 --- a/tests/test_module_list.py +++ b/tests/test_module_list.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_multi_scale.py b/tests/test_multi_scale.py index 01a760db72..963824f25e 100644 --- a/tests/test_multi_scale.py +++ b/tests/test_multi_scale.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_net_adapter.py b/tests/test_net_adapter.py index 198de8d142..0d73499a6d 100644 --- a/tests/test_net_adapter.py +++ b/tests/test_net_adapter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_network_consistency.py b/tests/test_network_consistency.py index ccccd9e7f0..419e1202d0 100644 --- a/tests/test_network_consistency.py +++ b/tests/test_network_consistency.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -22,7 +22,7 @@ import monai.networks.nets as nets from monai.utils import set_determinism -extra_test_data_dir = os.environ.get("MONAI_EXTRA_TEST_DATA", None) +extra_test_data_dir = os.environ.get("MONAI_EXTRA_TEST_DATA") TESTS = [] if extra_test_data_dir is not None: @@ -60,8 +60,8 @@ def test_network_consistency(self, net_name, data_path, json_path): json_file.close() # Create model - model = nets.__dict__[net_name](**model_params) - model.load_state_dict(loaded_data["model"]) + model = getattr(nets, net_name)(**model_params) + model.load_state_dict(loaded_data["model"], strict=False) model.eval() in_data = loaded_data["in_data"] diff --git a/tests/test_nifti_endianness.py b/tests/test_nifti_endianness.py index bf0f27b9ca..06b2d803d8 100644 --- a/tests/test_nifti_endianness.py +++ b/tests/test_nifti_endianness.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_nifti_header_revise.py b/tests/test_nifti_header_revise.py index 8d9a1d4f3a..7f917cb0e9 100644 --- a/tests/test_nifti_header_revise.py +++ b/tests/test_nifti_header_revise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_nifti_rw.py b/tests/test_nifti_rw.py index ff7f11e47f..1322fa6a45 100644 --- a/tests/test_nifti_rw.py +++ b/tests/test_nifti_rw.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_nifti_saver.py b/tests/test_nifti_saver.py index 3cbb24c69e..6855a59041 100644 --- a/tests/test_nifti_saver.py +++ b/tests/test_nifti_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_normalize_intensity.py b/tests/test_normalize_intensity.py index 41c6b053ec..5bcee1263b 100644 --- a/tests/test_normalize_intensity.py +++ b/tests/test_normalize_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -41,7 +41,7 @@ TESTS.append( [ p, - {"nonzero": False, "channel_wise": True, "subtrahend": [1, 2, 3]}, + {"nonzero": False, "channel_wise": True, "subtrahend": [1, 2, 3], "dtype": np.float32}, p(np.ones((3, 2, 2))), p(np.array([[[0.0, 0.0], [0.0, 0.0]], [[-1.0, -1.0], [-1.0, -1.0]], [[-2.0, -2.0], [-2.0, -2.0]]])), ] @@ -49,7 +49,7 @@ TESTS.append( [ p, - {"nonzero": True, "channel_wise": True, "subtrahend": [1, 2, 3], "divisor": [0, 0, 2]}, + {"nonzero": True, "channel_wise": True, "subtrahend": [1, 2, 3], "divisor": [0, 0, 2], "dtype": "float32"}, p(np.ones((3, 2, 2))), p(np.array([[[0.0, 0.0], [0.0, 0.0]], [[-1.0, -1.0], [-1.0, -1.0]], [[-1.0, -1.0], [-1.0, -1.0]]])), ] @@ -57,7 +57,7 @@ TESTS.append( [ p, - {"nonzero": True, "channel_wise": False, "subtrahend": 2, "divisor": 0}, + {"nonzero": True, "channel_wise": False, "subtrahend": 2, "divisor": 0, "dtype": torch.float32}, p(np.ones((3, 2, 2))), p(np.ones((3, 2, 2)) * -1.0), ] diff --git a/tests/test_normalize_intensityd.py b/tests/test_normalize_intensityd.py index 60b1d05456..12a39b1b5b 100644 --- a/tests/test_normalize_intensityd.py +++ b/tests/test_normalize_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_npzdictitemdataset.py b/tests/test_npzdictitemdataset.py index 2e86ef29d0..e24a2cfc1f 100644 --- a/tests/test_npzdictitemdataset.py +++ b/tests/test_npzdictitemdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_numpy_reader.py b/tests/test_numpy_reader.py index d84f339e3d..662d22afde 100644 --- a/tests/test_numpy_reader.py +++ b/tests/test_numpy_reader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_nvtx_decorator.py b/tests/test_nvtx_decorator.py index 0955fbb712..e81c72efcf 100644 --- a/tests/test_nvtx_decorator.py +++ b/tests/test_nvtx_decorator.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,18 +17,25 @@ from monai.transforms import ( Compose, + CuCIM, Flip, FlipD, RandAdjustContrast, + RandCuCIM, RandFlip, Randomizable, Rotate90, + ToCupy, + TorchVision, ToTensor, ToTensorD, ) from monai.utils import Range, optional_import +from tests.utils import HAS_CUPY _, has_nvtx = optional_import("torch._C._nvtx", descriptor="NVTX is not installed. Are you sure you have a CUDA build?") +_, has_tvt = optional_import("torchvision.transforms") +_, has_cut = optional_import("cucim.core.operations.expose.transform") TEST_CASE_ARRAY_0 = [np.random.randn(3, 3)] @@ -40,10 +47,12 @@ TEST_CASE_TORCH_0 = [torch.randn(3, 3)] TEST_CASE_TORCH_1 = [torch.randn(3, 10, 10)] +TEST_CASE_WRAPPER = [np.random.randn(3, 10, 10)] + +@unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") class TestNVTXRangeDecorator(unittest.TestCase): @parameterized.expand([TEST_CASE_ARRAY_0, TEST_CASE_ARRAY_1]) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_tranform_array(self, input): transforms = Compose([Range("random flip")(Flip()), Range()(ToTensor())]) # Apply transforms @@ -65,11 +74,10 @@ def test_tranform_array(self, input): self.assertIsInstance(output2, torch.Tensor) self.assertIsInstance(output3, torch.Tensor) np.testing.assert_equal(output.numpy(), output1.numpy()) - np.testing.assert_equal(output.numpy(), output1.numpy()) + np.testing.assert_equal(output.numpy(), output2.numpy()) np.testing.assert_equal(output.numpy(), output3.numpy()) @parameterized.expand([TEST_CASE_DICT_0, TEST_CASE_DICT_1]) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_tranform_dict(self, input): transforms = Compose([Range("random flip dict")(FlipD(keys="image")), Range()(ToTensorD("image"))]) # Apply transforms @@ -94,8 +102,32 @@ def test_tranform_dict(self, input): np.testing.assert_equal(output.numpy(), output2.numpy()) np.testing.assert_equal(output.numpy(), output3.numpy()) + @parameterized.expand([TEST_CASE_WRAPPER]) + @unittest.skipUnless(HAS_CUPY, "Requires CuPy.") + @unittest.skipUnless(has_cut, "Requires cuCIM transforms.") + @unittest.skipUnless(has_tvt, "Requires torchvision transforms.") + def test_wrapper_tranforms(self, input): + transform_list = [ + ToTensor(), + TorchVision(name="RandomHorizontalFlip", p=1.0), + ToCupy(), + CuCIM(name="image_flip", spatial_axis=-1), + RandCuCIM(name="rand_image_rotate_90", prob=1.0, max_k=1, spatial_axis=(-2, -1)), + ] + + transforms = Compose(transform_list) + transforms_range = Compose([Range()(t) for t in transform_list]) + + # Apply transforms + output = transforms(input) + + # Apply transforms with Range + output_r = transforms_range(input) + + # Check the outputs + np.testing.assert_equal(output.get(), output_r.get()) + @parameterized.expand([TEST_CASE_ARRAY_1]) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_tranform_randomized(self, input): # Compose deterministic and randomized transforms transforms = Compose( @@ -136,7 +168,6 @@ def test_tranform_randomized(self, input): break @parameterized.expand([TEST_CASE_TORCH_0, TEST_CASE_TORCH_1]) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_network(self, input): # Create a network model = torch.nn.Sequential(torch.nn.ReLU(), torch.nn.Sigmoid()) @@ -164,7 +195,6 @@ def test_network(self, input): np.testing.assert_equal(output.numpy(), output3.numpy()) @parameterized.expand([TEST_CASE_TORCH_0, TEST_CASE_TORCH_1]) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_loss(self, input): # Create a network and loss model = torch.nn.Sigmoid() @@ -194,7 +224,6 @@ def test_loss(self, input): np.testing.assert_equal(output.numpy(), output2.numpy()) np.testing.assert_equal(output.numpy(), output3.numpy()) - @unittest.skipUnless(has_nvtx, "CUDA is required for NVTX Range!") def test_context_manager(self): model = torch.nn.Sigmoid() loss = torch.nn.BCELoss() diff --git a/tests/test_nvtx_transform.py b/tests/test_nvtx_transform.py index 36a924dd1c..01a069ed8a 100644 --- a/tests/test_nvtx_transform.py +++ b/tests/test_nvtx_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_occlusion_sensitivity.py b/tests/test_occlusion_sensitivity.py index 18da4057ab..ff32f747d4 100644 --- a/tests/test_occlusion_sensitivity.py +++ b/tests/test_occlusion_sensitivity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_one_of.py b/tests/test_one_of.py index 7537062569..29d13d7d0c 100644 --- a/tests/test_one_of.py +++ b/tests/test_one_of.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -12,9 +12,18 @@ import unittest from copy import deepcopy +import numpy as np from parameterized import parameterized -from monai.transforms import InvertibleTransform, OneOf, TraceableTransform, Transform +from monai.transforms import ( + InvertibleTransform, + OneOf, + RandScaleIntensityd, + RandShiftIntensityd, + Resized, + TraceableTransform, + Transform, +) from monai.transforms.compose import Compose from monai.transforms.transform import MapTransform from monai.utils.enums import TraceKeys @@ -139,32 +148,52 @@ def _match(a, b): _match(p, f) @parameterized.expand(TEST_INVERSES) - def test_inverse(self, transform, should_be_ok): + def test_inverse(self, transform, invertible): data = {k: (i + 1) * 10.0 for i, k in enumerate(KEYS)} fwd_data = transform(data) - if not should_be_ok: - with self.assertRaises(RuntimeError): - transform.inverse(fwd_data) - return - - for k in KEYS: - t = fwd_data[TraceableTransform.trace_key(k)][-1] - # make sure the OneOf index was stored - self.assertEqual(t[TraceKeys.CLASS_NAME], OneOf.__name__) - # make sure index exists and is in bounds - self.assertTrue(0 <= t[TraceKeys.EXTRA_INFO]["index"] < len(transform)) + + if invertible: + for k in KEYS: + t = fwd_data[TraceableTransform.trace_key(k)][-1] + # make sure the OneOf index was stored + self.assertEqual(t[TraceKeys.CLASS_NAME], OneOf.__name__) + # make sure index exists and is in bounds + self.assertTrue(0 <= t[TraceKeys.EXTRA_INFO]["index"] < len(transform)) # call the inverse fwd_inv_data = transform.inverse(fwd_data) - for k in KEYS: - # check transform was removed - self.assertTrue( - len(fwd_inv_data[TraceableTransform.trace_key(k)]) < len(fwd_data[TraceableTransform.trace_key(k)]) - ) - # check data is same as original (and different from forward) - self.assertEqual(fwd_inv_data[k], data[k]) - self.assertNotEqual(fwd_inv_data[k], fwd_data[k]) + if invertible: + for k in KEYS: + # check transform was removed + self.assertTrue( + len(fwd_inv_data[TraceableTransform.trace_key(k)]) < len(fwd_data[TraceableTransform.trace_key(k)]) + ) + # check data is same as original (and different from forward) + self.assertEqual(fwd_inv_data[k], data[k]) + self.assertNotEqual(fwd_inv_data[k], fwd_data[k]) + else: + # if not invertible, should not change the data + self.assertDictEqual(fwd_data, fwd_inv_data) + + def test_inverse_compose(self): + transform = Compose( + [ + Resized(keys="img", spatial_size=[100, 100, 100]), + OneOf( + [ + RandScaleIntensityd(keys="img", factors=0.5, prob=1.0), + RandShiftIntensityd(keys="img", offsets=0.5, prob=1.0), + ] + ), + ] + ) + transform.set_random_state(seed=0) + result = transform({"img": np.ones((1, 101, 102, 103))}) + + result = transform.inverse(result) + # invert to the original spatial shape + self.assertTupleEqual(result["img"].shape, (1, 101, 102, 103)) def test_one_of(self): p = OneOf((A(), B(), C()), (1, 2, 1)) diff --git a/tests/test_optim_novograd.py b/tests/test_optim_novograd.py index 35f54d67ae..0cf4c35cb6 100644 --- a/tests/test_optim_novograd.py +++ b/tests/test_optim_novograd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_optional_import.py b/tests/test_optional_import.py index 05584f9f9c..b87ebf8909 100644 --- a/tests/test_optional_import.py +++ b/tests/test_optional_import.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_orientation.py b/tests/test_orientation.py index aa7f33a469..685c977f36 100644 --- a/tests/test_orientation.py +++ b/tests/test_orientation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_orientationd.py b/tests/test_orientationd.py index 452172ce9b..89ecd07b0a 100644 --- a/tests/test_orientationd.py +++ b/tests/test_orientationd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_p3d_block.py b/tests/test_p3d_block.py index b9237cba01..62b7098dcd 100644 --- a/tests/test_p3d_block.py +++ b/tests/test_p3d_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pad_collation.py b/tests/test_pad_collation.py index eda36f4761..a070fc760f 100644 --- a/tests/test_pad_collation.py +++ b/tests/test_pad_collation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_parallel_execution.py b/tests/test_parallel_execution.py index c4115d21ef..6186e73f68 100644 --- a/tests/test_parallel_execution.py +++ b/tests/test_parallel_execution.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_partition_dataset.py b/tests/test_partition_dataset.py index b036cd6827..687cf8df34 100644 --- a/tests/test_partition_dataset.py +++ b/tests/test_partition_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_partition_dataset_classes.py b/tests/test_partition_dataset_classes.py index 3aef47107a..4ed283bdd7 100644 --- a/tests/test_partition_dataset_classes.py +++ b/tests/test_partition_dataset_classes.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_patch_dataset.py b/tests/test_patch_dataset.py index 40e8bbb20a..1796ad4f23 100644 --- a/tests/test_patch_dataset.py +++ b/tests/test_patch_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_patch_wsi_dataset.py b/tests/test_patch_wsi_dataset.py index ca54332ad5..79165df36d 100644 --- a/tests/test_patch_wsi_dataset.py +++ b/tests/test_patch_wsi_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_patchembedding.py b/tests/test_patchembedding.py index 6c9ac78a99..4af2b47ba5 100644 --- a/tests/test_patchembedding.py +++ b/tests/test_patchembedding.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pathology_he_stain.py b/tests/test_pathology_he_stain.py index 7f76c3f03e..7b884315fc 100644 --- a/tests/test_pathology_he_stain.py +++ b/tests/test_pathology_he_stain.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pathology_he_stain_dict.py b/tests/test_pathology_he_stain_dict.py index 2ba2c3f71b..7d6c3ffb75 100644 --- a/tests/test_pathology_he_stain_dict.py +++ b/tests/test_pathology_he_stain_dict.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pathology_prob_nms.py b/tests/test_pathology_prob_nms.py index 879ca88821..3399e33afa 100644 --- a/tests/test_pathology_prob_nms.py +++ b/tests/test_pathology_prob_nms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_persistentdataset.py b/tests/test_persistentdataset.py index 9c66d3d10c..17575c79f7 100644 --- a/tests/test_persistentdataset.py +++ b/tests/test_persistentdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_persistentdataset_dist.py b/tests/test_persistentdataset_dist.py index d45bba03e5..20dcb2c264 100644 --- a/tests/test_persistentdataset_dist.py +++ b/tests/test_persistentdataset_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_phl_cpu.py b/tests/test_phl_cpu.py index 3583c4e996..d479f554b4 100644 --- a/tests/test_phl_cpu.py +++ b/tests/test_phl_cpu.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_phl_cuda.py b/tests/test_phl_cuda.py index 4ba47e4fca..d49a60ecd9 100644 --- a/tests/test_phl_cuda.py +++ b/tests/test_phl_cuda.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pil_reader.py b/tests/test_pil_reader.py index 0a076b581e..0f7792a56c 100644 --- a/tests/test_pil_reader.py +++ b/tests/test_pil_reader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_plot_2d_or_3d_image.py b/tests/test_plot_2d_or_3d_image.py index cfcb145503..2e4adb93e3 100644 --- a/tests/test_plot_2d_or_3d_image.py +++ b/tests/test_plot_2d_or_3d_image.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_png_rw.py b/tests/test_png_rw.py index 265b31b83b..84251b391f 100644 --- a/tests/test_png_rw.py +++ b/tests/test_png_rw.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_png_saver.py b/tests/test_png_saver.py index e807cf2927..d832718643 100644 --- a/tests/test_png_saver.py +++ b/tests/test_png_saver.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_polyval.py b/tests/test_polyval.py index 4ff05bc817..db3bcaca53 100644 --- a/tests/test_polyval.py +++ b/tests/test_polyval.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_prepare_batch_default.py b/tests/test_prepare_batch_default.py index c0d75b1263..8aabb4e9ce 100644 --- a/tests/test_prepare_batch_default.py +++ b/tests/test_prepare_batch_default.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_prepare_batch_extra_input.py b/tests/test_prepare_batch_extra_input.py index 8feb1f9569..79c9a13679 100644 --- a/tests/test_prepare_batch_extra_input.py +++ b/tests/test_prepare_batch_extra_input.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_print_info.py b/tests/test_print_info.py index 64f0b66949..591316884c 100644 --- a/tests/test_print_info.py +++ b/tests/test_print_info.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_print_transform_backends.py b/tests/test_print_transform_backends.py index 4164687f01..2db00fea39 100644 --- a/tests/test_print_transform_backends.py +++ b/tests/test_print_transform_backends.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_probnms.py b/tests/test_probnms.py index 1f1afff5db..aab312c1db 100644 --- a/tests/test_probnms.py +++ b/tests/test_probnms.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_probnmsd.py b/tests/test_probnmsd.py index dc6e6f8211..bb2315487b 100644 --- a/tests/test_probnmsd.py +++ b/tests/test_probnmsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_pytorch_version_after.py b/tests/test_pytorch_version_after.py index 6b5ca6fdb0..68abb9571f 100644 --- a/tests/test_pytorch_version_after.py +++ b/tests/test_pytorch_version_after.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_query_memory.py b/tests/test_query_memory.py index 22c29598fc..cdb44d3eb1 100644 --- a/tests/test_query_memory.py +++ b/tests/test_query_memory.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_adjust_contrast.py b/tests/test_rand_adjust_contrast.py index db408dda42..eaeff70d51 100644 --- a/tests/test_rand_adjust_contrast.py +++ b/tests/test_rand_adjust_contrast.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_adjust_contrastd.py b/tests/test_rand_adjust_contrastd.py index 87a3752b26..e5f1f6099a 100644 --- a/tests/test_rand_adjust_contrastd.py +++ b/tests/test_rand_adjust_contrastd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_affine.py b/tests/test_rand_affine.py index 4cef6b4d44..8de408ab84 100644 --- a/tests/test_rand_affine.py +++ b/tests/test_rand_affine.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_affine_grid.py b/tests/test_rand_affine_grid.py index ade615cd65..60ac40f468 100644 --- a/tests/test_rand_affine_grid.py +++ b/tests/test_rand_affine_grid.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_affined.py b/tests/test_rand_affined.py index e59a345d0d..882b5554e6 100644 --- a/tests/test_rand_affined.py +++ b/tests/test_rand_affined.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_axis_flip.py b/tests/test_rand_axis_flip.py index 1772ef4987..b7c504557f 100644 --- a/tests/test_rand_axis_flip.py +++ b/tests/test_rand_axis_flip.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_axis_flipd.py b/tests/test_rand_axis_flipd.py index 8ccc9b35d7..ff97d5dc1e 100644 --- a/tests/test_rand_axis_flipd.py +++ b/tests/test_rand_axis_flipd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_bias_field.py b/tests/test_rand_bias_field.py index ba755337d4..b3aa8e9174 100644 --- a/tests/test_rand_bias_field.py +++ b/tests/test_rand_bias_field.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_bias_fieldd.py b/tests/test_rand_bias_fieldd.py index b82d435f40..da08cfe053 100644 --- a/tests/test_rand_bias_fieldd.py +++ b/tests/test_rand_bias_fieldd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_coarse_dropout.py b/tests/test_rand_coarse_dropout.py index db26ea3c7a..a05d323277 100644 --- a/tests/test_rand_coarse_dropout.py +++ b/tests/test_rand_coarse_dropout.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_coarse_dropoutd.py b/tests/test_rand_coarse_dropoutd.py index ebb090e378..e54db130a5 100644 --- a/tests/test_rand_coarse_dropoutd.py +++ b/tests/test_rand_coarse_dropoutd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_coarse_shuffle.py b/tests/test_rand_coarse_shuffle.py index 0262fe2b3a..fb7311e5a3 100644 --- a/tests/test_rand_coarse_shuffle.py +++ b/tests/test_rand_coarse_shuffle.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_coarse_shuffled.py b/tests/test_rand_coarse_shuffled.py index ad49c8d02d..fa9c17286d 100644 --- a/tests/test_rand_coarse_shuffled.py +++ b/tests/test_rand_coarse_shuffled.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_crop_by_label_classes.py b/tests/test_rand_crop_by_label_classes.py index c987c3f0fd..11d73df74e 100644 --- a/tests/test_rand_crop_by_label_classes.py +++ b/tests/test_rand_crop_by_label_classes.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_crop_by_label_classesd.py b/tests/test_rand_crop_by_label_classesd.py index e51413a8d0..92780458e0 100644 --- a/tests/test_rand_crop_by_label_classesd.py +++ b/tests/test_rand_crop_by_label_classesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_crop_by_pos_neg_label.py b/tests/test_rand_crop_by_pos_neg_label.py index 42a72ccf2b..f8b8a77a45 100644 --- a/tests/test_rand_crop_by_pos_neg_label.py +++ b/tests/test_rand_crop_by_pos_neg_label.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_crop_by_pos_neg_labeld.py b/tests/test_rand_crop_by_pos_neg_labeld.py index c200b8acac..bff5c0e7fd 100644 --- a/tests/test_rand_crop_by_pos_neg_labeld.py +++ b/tests/test_rand_crop_by_pos_neg_labeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_cucim_dict_transform.py b/tests/test_rand_cucim_dict_transform.py index c084331e0e..cd41b7f49a 100644 --- a/tests/test_rand_cucim_dict_transform.py +++ b/tests/test_rand_cucim_dict_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,10 +16,10 @@ from monai.transforms import RandCuCIMd from monai.utils import optional_import, set_determinism -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda _, has_cut = optional_import("cucim.core.operations.expose.transform") -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") set_determinism(seed=0) @@ -74,7 +74,7 @@ @skip_if_no_cuda -@unittest.skipUnless(has_cp, "CuPy is required.") +@unittest.skipUnless(HAS_CUPY, "CuPy is required.") @unittest.skipUnless(has_cut, "cuCIM transforms are required.") class TestRandCuCIMDict(unittest.TestCase): @parameterized.expand( diff --git a/tests/test_rand_cucim_transform.py b/tests/test_rand_cucim_transform.py index 907bc35e01..0950329833 100644 --- a/tests/test_rand_cucim_transform.py +++ b/tests/test_rand_cucim_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,10 +16,10 @@ from monai.transforms import RandCuCIM from monai.utils import optional_import, set_determinism -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda _, has_cut = optional_import("cucim.core.operations.expose.transform") -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") set_determinism(seed=0) @@ -74,7 +74,7 @@ @skip_if_no_cuda -@unittest.skipUnless(has_cp, "CuPy is required.") +@unittest.skipUnless(HAS_CUPY, "CuPy is required.") @unittest.skipUnless(has_cut, "cuCIM transforms are required.") class TestRandCuCIM(unittest.TestCase): @parameterized.expand( diff --git a/tests/test_rand_deform_grid.py b/tests/test_rand_deform_grid.py index 4725e28339..8a2c8bf6eb 100644 --- a/tests/test_rand_deform_grid.py +++ b/tests/test_rand_deform_grid.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_elastic_2d.py b/tests/test_rand_elastic_2d.py index 22920d0f35..bc23a6c5cb 100644 --- a/tests/test_rand_elastic_2d.py +++ b/tests/test_rand_elastic_2d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_elastic_3d.py b/tests/test_rand_elastic_3d.py index 712049ec1a..d4eed3753f 100644 --- a/tests/test_rand_elastic_3d.py +++ b/tests/test_rand_elastic_3d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_elasticd_2d.py b/tests/test_rand_elasticd_2d.py index 77e6489d50..ead39e5731 100644 --- a/tests/test_rand_elasticd_2d.py +++ b/tests/test_rand_elasticd_2d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_elasticd_3d.py b/tests/test_rand_elasticd_3d.py index 5f8a5f47ed..84bc765ab0 100644 --- a/tests/test_rand_elasticd_3d.py +++ b/tests/test_rand_elasticd_3d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_flip.py b/tests/test_rand_flip.py index df49d60861..b9e9a8c4d6 100644 --- a/tests/test_rand_flip.py +++ b/tests/test_rand_flip.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_flipd.py b/tests/test_rand_flipd.py index c2869537cb..9a92661c59 100644 --- a/tests/test_rand_flipd.py +++ b/tests/test_rand_flipd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_noise.py b/tests/test_rand_gaussian_noise.py index d376add460..1f2adfb9e7 100644 --- a/tests/test_rand_gaussian_noise.py +++ b/tests/test_rand_gaussian_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_noised.py b/tests/test_rand_gaussian_noised.py index 3e578075ae..be1df0f2e6 100644 --- a/tests/test_rand_gaussian_noised.py +++ b/tests/test_rand_gaussian_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_sharpen.py b/tests/test_rand_gaussian_sharpen.py index 4804fc2422..06563a35b6 100644 --- a/tests/test_rand_gaussian_sharpen.py +++ b/tests/test_rand_gaussian_sharpen.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_sharpend.py b/tests/test_rand_gaussian_sharpend.py index 3508ebaa19..ecffa547e0 100644 --- a/tests/test_rand_gaussian_sharpend.py +++ b/tests/test_rand_gaussian_sharpend.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_smooth.py b/tests/test_rand_gaussian_smooth.py index b4d4304b67..d51618be95 100644 --- a/tests/test_rand_gaussian_smooth.py +++ b/tests/test_rand_gaussian_smooth.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gaussian_smoothd.py b/tests/test_rand_gaussian_smoothd.py index 2c80b978f2..e0ef0a8bb5 100644 --- a/tests/test_rand_gaussian_smoothd.py +++ b/tests/test_rand_gaussian_smoothd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gibbs_noise.py b/tests/test_rand_gibbs_noise.py index 15cadea0e2..fe928038da 100644 --- a/tests/test_rand_gibbs_noise.py +++ b/tests/test_rand_gibbs_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_gibbs_noised.py b/tests/test_rand_gibbs_noised.py index ac5fc164e2..8c5e045b90 100644 --- a/tests/test_rand_gibbs_noised.py +++ b/tests/test_rand_gibbs_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_grid_distortion.py b/tests/test_rand_grid_distortion.py index eabe6d34a1..80f19df0db 100644 --- a/tests/test_rand_grid_distortion.py +++ b/tests/test_rand_grid_distortion.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_grid_distortiond.py b/tests/test_rand_grid_distortiond.py index 835f38743c..323848dc0b 100644 --- a/tests/test_rand_grid_distortiond.py +++ b/tests/test_rand_grid_distortiond.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_histogram_shift.py b/tests/test_rand_histogram_shift.py index e38e2ea5f8..c66f7859c6 100644 --- a/tests/test_rand_histogram_shift.py +++ b/tests/test_rand_histogram_shift.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_histogram_shiftd.py b/tests/test_rand_histogram_shiftd.py index 2191e99518..fe8ddf9ffd 100644 --- a/tests/test_rand_histogram_shiftd.py +++ b/tests/test_rand_histogram_shiftd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_k_space_spike_noise.py b/tests/test_rand_k_space_spike_noise.py index 1c9ca9c1d5..645e6ae1ce 100644 --- a/tests/test_rand_k_space_spike_noise.py +++ b/tests/test_rand_k_space_spike_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_k_space_spike_noised.py b/tests/test_rand_k_space_spike_noised.py index 9036166b61..7a6a73b215 100644 --- a/tests/test_rand_k_space_spike_noised.py +++ b/tests/test_rand_k_space_spike_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_lambda.py b/tests/test_rand_lambda.py index bf537883cf..043f44aec4 100644 --- a/tests/test_rand_lambda.py +++ b/tests/test_rand_lambda.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_lambdad.py b/tests/test_rand_lambdad.py index 0a127839b8..854fef8879 100644 --- a/tests/test_rand_lambdad.py +++ b/tests/test_rand_lambdad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rician_noise.py b/tests/test_rand_rician_noise.py index 7ec5fc4dc4..8e2ea1ee3a 100644 --- a/tests/test_rand_rician_noise.py +++ b/tests/test_rand_rician_noise.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rician_noised.py b/tests/test_rand_rician_noised.py index bb5ebbe8c8..05707059bc 100644 --- a/tests/test_rand_rician_noised.py +++ b/tests/test_rand_rician_noised.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rotate.py b/tests/test_rand_rotate.py index 4817e81735..b453b01884 100644 --- a/tests/test_rand_rotate.py +++ b/tests/test_rand_rotate.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rotate90.py b/tests/test_rand_rotate90.py index 8d4e591559..b845944062 100644 --- a/tests/test_rand_rotate90.py +++ b/tests/test_rand_rotate90.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rotate90d.py b/tests/test_rand_rotate90d.py index 3071aa82c8..ded18e430a 100644 --- a/tests/test_rand_rotate90d.py +++ b/tests/test_rand_rotate90d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_rotated.py b/tests/test_rand_rotated.py index fb2038e8c3..23314720c1 100644 --- a/tests/test_rand_rotated.py +++ b/tests/test_rand_rotated.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_scale_crop.py b/tests/test_rand_scale_crop.py index a0c5471ffb..5d6312002f 100644 --- a/tests/test_rand_scale_crop.py +++ b/tests/test_rand_scale_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_scale_cropd.py b/tests/test_rand_scale_cropd.py index 3dc4578f62..5e833fef98 100644 --- a/tests/test_rand_scale_cropd.py +++ b/tests/test_rand_scale_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_scale_intensity.py b/tests/test_rand_scale_intensity.py index c3d18330ea..5aa5c7b964 100644 --- a/tests/test_rand_scale_intensity.py +++ b/tests/test_rand_scale_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_scale_intensityd.py b/tests/test_rand_scale_intensityd.py index 7c2392fded..655bd88ee0 100644 --- a/tests/test_rand_scale_intensityd.py +++ b/tests/test_rand_scale_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_shift_intensity.py b/tests/test_rand_shift_intensity.py index 7f5b278fd0..b4f32a385a 100644 --- a/tests/test_rand_shift_intensity.py +++ b/tests/test_rand_shift_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_shift_intensityd.py b/tests/test_rand_shift_intensityd.py index 5950faac26..10bb5cd71e 100644 --- a/tests/test_rand_shift_intensityd.py +++ b/tests/test_rand_shift_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_spatial_crop.py b/tests/test_rand_spatial_crop.py index 19b1841c6d..8f4bb0fffa 100644 --- a/tests/test_rand_spatial_crop.py +++ b/tests/test_rand_spatial_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_spatial_crop_samples.py b/tests/test_rand_spatial_crop_samples.py index eefe7d0e0a..18fdf38773 100644 --- a/tests/test_rand_spatial_crop_samples.py +++ b/tests/test_rand_spatial_crop_samples.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_spatial_crop_samplesd.py b/tests/test_rand_spatial_crop_samplesd.py index a4e8bdb2e6..b7ceefe542 100644 --- a/tests/test_rand_spatial_crop_samplesd.py +++ b/tests/test_rand_spatial_crop_samplesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_spatial_cropd.py b/tests/test_rand_spatial_cropd.py index edcb61dc99..9e6e86eea2 100644 --- a/tests/test_rand_spatial_cropd.py +++ b/tests/test_rand_spatial_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_std_shift_intensity.py b/tests/test_rand_std_shift_intensity.py index 5b0db09063..fdf386fee4 100644 --- a/tests/test_rand_std_shift_intensity.py +++ b/tests/test_rand_std_shift_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_std_shift_intensityd.py b/tests/test_rand_std_shift_intensityd.py index fbc71721d0..e98d1e3ad3 100644 --- a/tests/test_rand_std_shift_intensityd.py +++ b/tests/test_rand_std_shift_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index eb0e4244f7..dae7f05016 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index f53238d17c..93726f55bb 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_zoom.py b/tests/test_rand_zoom.py index da630853fe..35472024ef 100644 --- a/tests/test_rand_zoom.py +++ b/tests/test_rand_zoom.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rand_zoomd.py b/tests/test_rand_zoomd.py index 89a997e925..a22f2f36f1 100644 --- a/tests/test_rand_zoomd.py +++ b/tests/test_rand_zoomd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_randomizable.py b/tests/test_randomizable.py index 9972bded0f..7445287a12 100644 --- a/tests/test_randomizable.py +++ b/tests/test_randomizable.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_randtorchvisiond.py b/tests/test_randtorchvisiond.py index 2dffe67994..2e96d723ee 100644 --- a/tests/test_randtorchvisiond.py +++ b/tests/test_randtorchvisiond.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_reg_loss_integration.py b/tests/test_reg_loss_integration.py index 2949ee1519..822a056879 100644 --- a/tests/test_reg_loss_integration.py +++ b/tests/test_reg_loss_integration.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -20,7 +20,7 @@ from tests.utils import SkipIfBeforePyTorchVersion TEST_CASES = [ - [BendingEnergyLoss, {}, ["pred"]], + [BendingEnergyLoss, {}, ["pred"], 3], [LocalNormalizedCrossCorrelationLoss, {"kernel_size": 7, "kernel_type": "rectangular"}, ["pred", "target"]], [LocalNormalizedCrossCorrelationLoss, {"kernel_size": 5, "kernel_type": "triangular"}, ["pred", "target"]], [LocalNormalizedCrossCorrelationLoss, {"kernel_size": 3, "kernel_type": "gaussian"}, ["pred", "target"]], @@ -42,7 +42,7 @@ def tearDown(self): @parameterized.expand(TEST_CASES) @SkipIfBeforePyTorchVersion((1, 9)) - def test_convergence(self, loss_type, loss_args, forward_args): + def test_convergence(self, loss_type, loss_args, forward_args, pred_channels=1): """ The goal of this test is to assess if the gradient of the loss function is correct by testing if we can train a one layer neural network @@ -64,7 +64,7 @@ def __init__(self): self.layer = nn.Sequential( nn.Conv3d(in_channels=1, out_channels=1, kernel_size=3, padding=1), nn.ReLU(), - nn.Conv3d(in_channels=1, out_channels=1, kernel_size=3, padding=1), + nn.Conv3d(in_channels=1, out_channels=pred_channels, kernel_size=3, padding=1), ) def forward(self, x): diff --git a/tests/test_regunet.py b/tests/test_regunet.py index 4dd968a1cf..e37ca49538 100644 --- a/tests/test_regunet.py +++ b/tests/test_regunet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_regunet_block.py b/tests/test_regunet_block.py index 9b96875432..3be02ea377 100644 --- a/tests/test_regunet_block.py +++ b/tests/test_regunet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_remove_repeated_channel.py b/tests/test_remove_repeated_channel.py index ebbe6c730c..39b42cc4b0 100644 --- a/tests/test_remove_repeated_channel.py +++ b/tests/test_remove_repeated_channel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_remove_repeated_channeld.py b/tests/test_remove_repeated_channeld.py index 9d4812791e..9db66a6aa0 100644 --- a/tests/test_remove_repeated_channeld.py +++ b/tests/test_remove_repeated_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_repeat_channel.py b/tests/test_repeat_channel.py index e246dd1212..3d74b6479c 100644 --- a/tests/test_repeat_channel.py +++ b/tests/test_repeat_channel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_repeat_channeld.py b/tests/test_repeat_channeld.py index 3b73962bb9..a348f3eea9 100644 --- a/tests/test_repeat_channeld.py +++ b/tests/test_repeat_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_require_pkg.py b/tests/test_require_pkg.py index ff32a322bb..dbe63fff3f 100644 --- a/tests/test_require_pkg.py +++ b/tests/test_require_pkg.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resample_datalist.py b/tests/test_resample_datalist.py index 1d92e431cd..fa120b261e 100644 --- a/tests/test_resample_datalist.py +++ b/tests/test_resample_datalist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resampler.py b/tests/test_resampler.py index af23421ecc..7dfb86a7a9 100644 --- a/tests/test_resampler.py +++ b/tests/test_resampler.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resize.py b/tests/test_resize.py index 65d934afe6..06246b2358 100644 --- a/tests/test_resize.py +++ b/tests/test_resize.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resize_with_pad_or_crop.py b/tests/test_resize_with_pad_or_crop.py index 262cd2ffdb..f81e1d4b08 100644 --- a/tests/test_resize_with_pad_or_crop.py +++ b/tests/test_resize_with_pad_or_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resize_with_pad_or_cropd.py b/tests/test_resize_with_pad_or_cropd.py index 91201bba53..28993a2bf4 100644 --- a/tests/test_resize_with_pad_or_cropd.py +++ b/tests/test_resize_with_pad_or_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resized.py b/tests/test_resized.py index 7d09f13bad..d7374ea930 100644 --- a/tests/test_resized.py +++ b/tests/test_resized.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_resnet.py b/tests/test_resnet.py index 335a5f5869..ffb48125a4 100644 --- a/tests/test_resnet.py +++ b/tests/test_resnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rotate.py b/tests/test_rotate.py index 411fed3d1d..42947e7f72 100644 --- a/tests/test_rotate.py +++ b/tests/test_rotate.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rotate90.py b/tests/test_rotate90.py index 9857b26fe8..9865120688 100644 --- a/tests/test_rotate90.py +++ b/tests/test_rotate90.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rotate90d.py b/tests/test_rotate90d.py index a2a4a27521..ef4bad9419 100644 --- a/tests/test_rotate90d.py +++ b/tests/test_rotate90d.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_rotated.py b/tests/test_rotated.py index 91918513b8..1b759cfef5 100644 --- a/tests/test_rotated.py +++ b/tests/test_rotated.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_saliency_inferer.py b/tests/test_saliency_inferer.py index 416b7170ae..c97bcb7811 100644 --- a/tests/test_saliency_inferer.py +++ b/tests/test_saliency_inferer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_distributed_sampler.py b/tests/test_sampler_dist.py similarity index 97% rename from tests/test_distributed_sampler.py rename to tests/test_sampler_dist.py index 0a439874bd..8b140f3ff8 100644 --- a/tests/test_distributed_sampler.py +++ b/tests/test_sampler_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_save_classificationd.py b/tests/test_save_classificationd.py index 1418bf22ee..c5a8b4705d 100644 --- a/tests/test_save_classificationd.py +++ b/tests/test_save_classificationd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_save_image.py b/tests/test_save_image.py index be77b12b8e..d3671cf830 100644 --- a/tests/test_save_image.py +++ b/tests/test_save_image.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_save_imaged.py b/tests/test_save_imaged.py index f05a83dd9a..d84e582621 100644 --- a/tests/test_save_imaged.py +++ b/tests/test_save_imaged.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_savitzky_golay_filter.py b/tests/test_savitzky_golay_filter.py index fa38659acb..b410a641ea 100644 --- a/tests/test_savitzky_golay_filter.py +++ b/tests/test_savitzky_golay_filter.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_savitzky_golay_smooth.py b/tests/test_savitzky_golay_smooth.py index 0f398bc48f..ac42cf806e 100644 --- a/tests/test_savitzky_golay_smooth.py +++ b/tests/test_savitzky_golay_smooth.py @@ -1,4 +1,4 @@ -# Copyright 2020 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -59,15 +59,9 @@ class TestSavitzkyGolaySmooth(unittest.TestCase): - @parameterized.expand([TEST_CASE_SINGLE_VALUE, TEST_CASE_2D_AXIS_2, TEST_CASE_SINE_SMOOTH]) - def test_value(self, arguments, image, expected_data, atol): - for p in TEST_NDARRAYS: - result = SavitzkyGolaySmooth(**arguments)(p(image.astype(np.float32))) - torch.testing.assert_allclose(result, p(expected_data.astype(np.float32)), rtol=1e-4, atol=atol) - - -class TestSavitzkyGolaySmoothREP(unittest.TestCase): - @parameterized.expand([TEST_CASE_SINGLE_VALUE_REP]) + @parameterized.expand( + [TEST_CASE_SINGLE_VALUE, TEST_CASE_2D_AXIS_2, TEST_CASE_SINE_SMOOTH, TEST_CASE_SINGLE_VALUE_REP] + ) def test_value(self, arguments, image, expected_data, atol): for p in TEST_NDARRAYS: result = SavitzkyGolaySmooth(**arguments)(p(image.astype(np.float32))) diff --git a/tests/test_savitzky_golay_smoothd.py b/tests/test_savitzky_golay_smoothd.py new file mode 100644 index 0000000000..6f0b33f533 --- /dev/null +++ b/tests/test_savitzky_golay_smoothd.py @@ -0,0 +1,72 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import numpy as np +import torch +from parameterized import parameterized + +from monai.transforms import SavitzkyGolaySmoothd +from tests.utils import TEST_NDARRAYS + +# Zero-padding trivial tests + +TEST_CASE_SINGLE_VALUE = [ + {"keys": "img", "window_length": 3, "order": 1}, + np.expand_dims(np.array([1.0]), 0), # Input data: Single value + np.expand_dims(np.array([1 / 3]), 0), # Expected output: With a window length of 3 and polyorder 1 + # output should be equal to mean of 0, 1 and 0 = 1/3 (because input will be zero-padded and a linear fit performed) + 1e-5, # absolute tolerance +] + +TEST_CASE_2D_AXIS_2 = [ + {"keys": "img", "window_length": 3, "order": 1, "axis": 2}, # along axis 2 (second spatial dim) + np.expand_dims(np.ones((2, 3)), 0), + np.expand_dims(np.array([[2 / 3, 1.0, 2 / 3], [2 / 3, 1.0, 2 / 3]]), 0), + 1e-5, # absolute tolerance +] + +# Replicated-padding trivial tests + +TEST_CASE_SINGLE_VALUE_REP = [ + {"keys": "img", "window_length": 3, "order": 1, "mode": "replicate"}, + np.expand_dims(np.array([1.0]), 0), # Input data: Single value + np.expand_dims(np.array([1.0]), 0), # Expected output: With a window length of 3 and polyorder 1 + # output will be equal to mean of [1, 1, 1] = 1 (input will be nearest-neighbour-padded and a linear fit performed) + 1e-5, # absolute tolerance +] + +# Sine smoothing + +TEST_CASE_SINE_SMOOTH = [ + {"keys": "img", "window_length": 3, "order": 1}, + # Sine wave with period equal to savgol window length (windowed to reduce edge effects). + np.expand_dims(np.sin(2 * np.pi * 1 / 3 * np.arange(100)) * np.hanning(100), 0), + # Should be smoothed out to zeros + np.expand_dims(np.zeros(100), 0), + # tolerance chosen by examining output of SciPy.signal.savgol_filter() when provided the above input + 2e-2, # absolute tolerance +] + + +class TestSavitzkyGolaySmoothd(unittest.TestCase): + @parameterized.expand( + [TEST_CASE_SINGLE_VALUE, TEST_CASE_2D_AXIS_2, TEST_CASE_SINE_SMOOTH, TEST_CASE_SINGLE_VALUE_REP] + ) + def test_value(self, arguments, image, expected_data, atol): + for p in TEST_NDARRAYS: + result = SavitzkyGolaySmoothd(**arguments)({"img": p(image.astype(np.float32))})["img"] + torch.testing.assert_allclose(result, p(expected_data.astype(np.float32)), rtol=1e-4, atol=atol) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_scale_intensity.py b/tests/test_scale_intensity.py index 33f4bc759b..b81351ed2e 100644 --- a/tests/test_scale_intensity.py +++ b/tests/test_scale_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_scale_intensity_range.py b/tests/test_scale_intensity_range.py index 2384532776..faddf9001b 100644 --- a/tests/test_scale_intensity_range.py +++ b/tests/test_scale_intensity_range.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_scale_intensity_range_percentiles.py b/tests/test_scale_intensity_range_percentiles.py index 3556c7a8b4..6ccfaf7ba6 100644 --- a/tests/test_scale_intensity_range_percentiles.py +++ b/tests/test_scale_intensity_range_percentiles.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -19,7 +19,7 @@ class TestScaleIntensityRangePercentiles(NumpyImageTestCase2D): def test_scaling(self): - img = self.imt + img = self.imt[0] lower = 10 upper = 99 b_min = 0 @@ -34,7 +34,7 @@ def test_scaling(self): assert_allclose(result, p(expected), rtol=1e-4) def test_relative_scaling(self): - img = self.imt + img = self.imt[0] lower = 10 upper = 99 b_min = 100 @@ -65,6 +65,26 @@ def test_invalid_instantiation(self): self.assertRaises(ValueError, ScaleIntensityRangePercentiles, lower=30, upper=-20, b_min=0, b_max=255) self.assertRaises(ValueError, ScaleIntensityRangePercentiles, lower=30, upper=900, b_min=0, b_max=255) + def test_channel_wise(self): + img = self.imt[0] + lower = 10 + upper = 99 + b_min = 0 + b_max = 255 + scaler = ScaleIntensityRangePercentiles( + lower=lower, upper=upper, b_min=b_min, b_max=b_max, channel_wise=True, dtype=np.uint8 + ) + expected = [] + for c in img: + a_min = np.percentile(c, lower) + a_max = np.percentile(c, upper) + expected.append(((c - a_min) / (a_max - a_min)) * (b_max - b_min) + b_min) + expected = np.stack(expected).astype(np.uint8) + + for p in TEST_NDARRAYS: + result = scaler(p(img)) + assert_allclose(result, p(expected), rtol=1e-4) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_scale_intensity_range_percentilesd.py b/tests/test_scale_intensity_range_percentilesd.py index ac2118d99f..cd0a5f8c35 100644 --- a/tests/test_scale_intensity_range_percentilesd.py +++ b/tests/test_scale_intensity_range_percentilesd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -27,15 +27,14 @@ def test_scaling(self): a_min = np.percentile(img, lower) a_max = np.percentile(img, upper) - expected = (img - a_min) / (a_max - a_min) - expected = (expected * (b_max - b_min)) + b_min + expected = (((img - a_min) / (a_max - a_min)) * (b_max - b_min) + b_min).astype(np.uint8) for p in TEST_NDARRAYS: data = {"img": p(img)} scaler = ScaleIntensityRangePercentilesd( - keys=data.keys(), lower=lower, upper=upper, b_min=b_min, b_max=b_max + keys=data.keys(), lower=lower, upper=upper, b_min=b_min, b_max=b_max, dtype=np.uint8 ) - assert_allclose(p(expected), scaler(data)["img"]) + assert_allclose(p(expected), scaler(data)["img"], rtol=1e-4) def test_relative_scaling(self): img = self.imt @@ -75,6 +74,26 @@ def test_invalid_instantiation(self): s = ScaleIntensityRangePercentilesd(keys=["img"], lower=30, upper=90, b_min=None, b_max=20, relative=True) s(self.imt) + def test_channel_wise(self): + img = self.imt + lower = 10 + upper = 99 + b_min = 0 + b_max = 255 + scaler = ScaleIntensityRangePercentilesd( + keys="img", lower=lower, upper=upper, b_min=b_min, b_max=b_max, channel_wise=True, dtype=np.uint8 + ) + expected = [] + for c in img: + a_min = np.percentile(c, lower) + a_max = np.percentile(c, upper) + expected.append((((c - a_min) / (a_max - a_min)) * (b_max - b_min) + b_min).astype(np.uint8)) + expected = np.stack(expected) + + for p in TEST_NDARRAYS: + data = {"img": p(img)} + assert_allclose(scaler(data)["img"], p(expected), rtol=1e-4) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_scale_intensity_ranged.py b/tests/test_scale_intensity_ranged.py index a4b7f17b04..ffbd3e44c4 100644 --- a/tests/test_scale_intensity_ranged.py +++ b/tests/test_scale_intensity_ranged.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_scale_intensityd.py b/tests/test_scale_intensityd.py index 93449b15e2..42f1527490 100644 --- a/tests/test_scale_intensityd.py +++ b/tests/test_scale_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_se_block.py b/tests/test_se_block.py index 1f515a7fb4..88983a7746 100644 --- a/tests/test_se_block.py +++ b/tests/test_se_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_se_blocks.py b/tests/test_se_blocks.py index e9aed7d9d9..400ee85e7f 100644 --- a/tests/test_se_blocks.py +++ b/tests/test_se_blocks.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_seg_loss_integration.py b/tests/test_seg_loss_integration.py index 98d840afea..f4a0f25267 100644 --- a/tests/test_seg_loss_integration.py +++ b/tests/test_seg_loss_integration.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_segresnet.py b/tests/test_segresnet.py index da23997c95..b7c37f87b9 100644 --- a/tests/test_segresnet.py +++ b/tests/test_segresnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_segresnet_block.py b/tests/test_segresnet_block.py index eb8cc9676b..9bb435ac1d 100644 --- a/tests/test_segresnet_block.py +++ b/tests/test_segresnet_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_select_cross_validation_folds.py b/tests/test_select_cross_validation_folds.py index 6dbd004e71..7693baca80 100644 --- a/tests/test_select_cross_validation_folds.py +++ b/tests/test_select_cross_validation_folds.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_select_itemsd.py b/tests/test_select_itemsd.py index bf63864eb0..ba75a27cff 100644 --- a/tests/test_select_itemsd.py +++ b/tests/test_select_itemsd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_selfattention.py b/tests/test_selfattention.py index 559e86487b..407bee341c 100644 --- a/tests/test_selfattention.py +++ b/tests/test_selfattention.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_senet.py b/tests/test_senet.py index 9aae5e5e54..be7f571e0b 100644 --- a/tests/test_senet.py +++ b/tests/test_senet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_separable_filter.py b/tests/test_separable_filter.py index 0183ab9ef9..167add9556 100644 --- a/tests/test_separable_filter.py +++ b/tests/test_separable_filter.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_set_determinism.py b/tests/test_set_determinism.py index 537aa36676..7d6c54909d 100644 --- a/tests/test_set_determinism.py +++ b/tests/test_set_determinism.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -40,6 +40,8 @@ def test_values(self): self.assertEqual(seed, get_seed()) a = np.random.randint(seed) b = torch.randint(seed, (1,)) + # tset when global flag support is disabled + torch.backends.disable_global_flags() set_determinism(seed=seed) c = np.random.randint(seed) d = torch.randint(seed, (1,)) diff --git a/tests/test_set_visible_devices.py b/tests/test_set_visible_devices.py index b6da879f4b..75cbd6fb0d 100644 --- a/tests/test_set_visible_devices.py +++ b/tests/test_set_visible_devices.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_shift_intensity.py b/tests/test_shift_intensity.py index b73c18b6a5..ecded268ab 100644 --- a/tests/test_shift_intensity.py +++ b/tests/test_shift_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_shift_intensityd.py b/tests/test_shift_intensityd.py index 66aad23b1e..a37aaf4a6b 100644 --- a/tests/test_shift_intensityd.py +++ b/tests/test_shift_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_simple_aspp.py b/tests/test_simple_aspp.py index fbc8cb37d1..9c952bd791 100644 --- a/tests/test_simple_aspp.py +++ b/tests/test_simple_aspp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_simulatedelay.py b/tests/test_simulatedelay.py index 3a4686218e..abf629f5c5 100644 --- a/tests/test_simulatedelay.py +++ b/tests/test_simulatedelay.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_simulatedelayd.py b/tests/test_simulatedelayd.py index 58bd3eb6b8..cbabb68e0f 100644 --- a/tests/test_simulatedelayd.py +++ b/tests/test_simulatedelayd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_skip_connection.py b/tests/test_skip_connection.py index 462acd9242..f523891084 100644 --- a/tests/test_skip_connection.py +++ b/tests/test_skip_connection.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_sliding_window_inference.py b/tests/test_sliding_window_inference.py index c5b941bf3d..3d4ad3151b 100644 --- a/tests/test_sliding_window_inference.py +++ b/tests/test_sliding_window_inference.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_smartcache_patch_wsi_dataset.py b/tests/test_smartcache_patch_wsi_dataset.py index 2150ede51c..73583b5eb1 100644 --- a/tests/test_smartcache_patch_wsi_dataset.py +++ b/tests/test_smartcache_patch_wsi_dataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -45,6 +45,7 @@ "cache_num": 2, "num_init_workers": 1, "num_replace_workers": 1, + "copy_cache": False, }, [ {"image": np.array([[[239]], [[239]], [[239]]], dtype=np.uint8), "label": np.array([[[0]]])}, diff --git a/tests/test_smartcachedataset.py b/tests/test_smartcachedataset.py index f390a9127e..e7d51be63a 100644 --- a/tests/test_smartcachedataset.py +++ b/tests/test_smartcachedataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_smooth_field.py b/tests/test_smooth_field.py index 761cc9e5fa..5849b96167 100644 --- a/tests/test_smooth_field.py +++ b/tests/test_smooth_field.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -10,47 +10,72 @@ # limitations under the License. import unittest +from itertools import product import numpy as np +import torch from parameterized import parameterized -from monai.transforms import RandSmoothFieldAdjustContrastd, RandSmoothFieldAdjustIntensityd +from monai.transforms import RandSmoothDeformd, RandSmoothFieldAdjustContrastd, RandSmoothFieldAdjustIntensityd from tests.utils import TEST_NDARRAYS, assert_allclose, is_tf32_env _rtol = 5e-3 if is_tf32_env() else 1e-4 -INPUT_SHAPE1 = (1, 8, 8) -INPUT_SHAPE2 = (2, 8, 8) +INPUT_SHAPES = ((1, 8, 8), (2, 8, 8), (1, 8, 8, 8)) TESTS_CONTRAST = [] TESTS_INTENSITY = [] +TESTS_DEFORM = [] -for p in TEST_NDARRAYS: - TESTS_CONTRAST += [ - ( - {"keys": ("test",), "spatial_size": INPUT_SHAPE1[1:], "rand_size": (4, 4), "prob": 1.0}, - {"test": p(np.ones(INPUT_SHAPE1, np.float32))}, - {"test": p(np.ones(INPUT_SHAPE1, np.float32))}, - ), +KEY = "test" + +for arr_type, shape in product(TEST_NDARRAYS, INPUT_SHAPES): + in_arr = arr_type(np.ones(shape, np.float32)) + exp_arr = arr_type(np.ones(shape, np.float32)) + rand_size = (4,) * (len(shape) - 1) + + device = torch.device("cpu") + + if isinstance(in_arr, torch.Tensor) and in_arr.get_device() >= 0: + device = torch.device(in_arr.get_device()) + + TESTS_CONTRAST.append( ( - {"keys": ("test",), "spatial_size": INPUT_SHAPE2[1:], "rand_size": (4, 4), "prob": 1.0}, - {"test": p(np.ones(INPUT_SHAPE2, np.float32))}, - {"test": p(np.ones(INPUT_SHAPE2, np.float32))}, - ), - ] + {"keys": (KEY,), "spatial_size": shape[1:], "rand_size": rand_size, "prob": 1.0, "device": device}, + {KEY: in_arr}, + {KEY: exp_arr}, + ) + ) - TESTS_INTENSITY += [ + TESTS_INTENSITY.append( ( - {"keys": ("test",), "spatial_size": INPUT_SHAPE1[1:], "rand_size": (4, 4), "prob": 1.0, "gamma": (1, 1)}, - {"test": p(np.ones(INPUT_SHAPE1, np.float32))}, - {"test": p(np.ones(INPUT_SHAPE1, np.float32))}, - ), + { + "keys": (KEY,), + "spatial_size": shape[1:], + "rand_size": rand_size, + "prob": 1.0, + "device": device, + "gamma": (0.9, 1), + }, + {KEY: in_arr}, + {KEY: exp_arr}, + ) + ) + + TESTS_DEFORM.append( ( - {"keys": ("test",), "spatial_size": INPUT_SHAPE2[1:], "rand_size": (4, 4), "prob": 1.0, "gamma": (1, 1)}, - {"test": p(np.ones(INPUT_SHAPE2, np.float32))}, - {"test": p(np.ones(INPUT_SHAPE2, np.float32))}, - ), - ] + { + "keys": (KEY,), + "spatial_size": shape[1:], + "rand_size": rand_size, + "prob": 1.0, + "device": device, + "def_range": 0.1, + }, + {KEY: in_arr}, + {KEY: exp_arr}, + ) + ) class TestSmoothField(unittest.TestCase): @@ -62,7 +87,18 @@ def test_rand_smooth_field_adjust_contrastd(self, input_param, input_data, expec res = g(input_data) for key, result in res.items(): expected = expected_val[key] - assert_allclose(result, expected, rtol=_rtol, atol=5e-3) + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) + + def test_rand_smooth_field_adjust_contrastd_pad(self): + input_param, input_data, expected_val = TESTS_CONTRAST[0] + + g = RandSmoothFieldAdjustContrastd(pad=1, **input_param) + g.set_random_state(123) + + res = g(input_data) + for key, result in res.items(): + expected = expected_val[key] + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) @parameterized.expand(TESTS_INTENSITY) def test_rand_smooth_field_adjust_intensityd(self, input_param, input_data, expected_val): @@ -72,4 +108,36 @@ def test_rand_smooth_field_adjust_intensityd(self, input_param, input_data, expe res = g(input_data) for key, result in res.items(): expected = expected_val[key] - assert_allclose(result, expected, rtol=_rtol, atol=5e-3) + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) + + def test_rand_smooth_field_adjust_intensityd_pad(self): + input_param, input_data, expected_val = TESTS_INTENSITY[0] + + g = RandSmoothFieldAdjustIntensityd(pad=1, **input_param) + g.set_random_state(123) + + res = g(input_data) + for key, result in res.items(): + expected = expected_val[key] + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) + + @parameterized.expand(TESTS_DEFORM) + def test_rand_smooth_deformd(self, input_param, input_data, expected_val): + g = RandSmoothDeformd(**input_param) + g.set_random_state(123) + + res = g(input_data) + for key, result in res.items(): + expected = expected_val[key] + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) + + def test_rand_smooth_deformd_pad(self): + input_param, input_data, expected_val = TESTS_DEFORM[0] + + g = RandSmoothDeformd(pad=1, **input_param) + g.set_random_state(123) + + res = g(input_data) + for key, result in res.items(): + expected = expected_val[key] + assert_allclose(result, expected, rtol=_rtol, atol=1e-1) diff --git a/tests/test_spacing.py b/tests/test_spacing.py index cd362bccea..ebff25712d 100644 --- a/tests/test_spacing.py +++ b/tests/test_spacing.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_spacingd.py b/tests/test_spacingd.py index 355706f65a..79e759082a 100644 --- a/tests/test_spacingd.py +++ b/tests/test_spacingd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_spatial_crop.py b/tests/test_spatial_crop.py index 84e65d07fb..bf1eb11491 100644 --- a/tests/test_spatial_crop.py +++ b/tests/test_spatial_crop.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_spatial_cropd.py b/tests/test_spatial_cropd.py index 17743124e0..5b16f460fd 100644 --- a/tests/test_spatial_cropd.py +++ b/tests/test_spatial_cropd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_spatial_pad.py b/tests/test_spatial_pad.py index 83a261138e..4cdeb6d64e 100644 --- a/tests/test_spatial_pad.py +++ b/tests/test_spatial_pad.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_spatial_padd.py b/tests/test_spatial_padd.py index 8400bb82cc..762a1145f5 100644 --- a/tests/test_spatial_padd.py +++ b/tests/test_spatial_padd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_split_channel.py b/tests/test_split_channel.py index 38315a102c..75216227e4 100644 --- a/tests/test_split_channel.py +++ b/tests/test_split_channel.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_split_channeld.py b/tests/test_split_channeld.py index 344f206c86..7a34855676 100644 --- a/tests/test_split_channeld.py +++ b/tests/test_split_channeld.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_split_on_grid.py b/tests/test_split_on_grid.py index 8017fcccbd..b1d4cd93c5 100644 --- a/tests/test_split_on_grid.py +++ b/tests/test_split_on_grid.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_split_on_grid_dict.py b/tests/test_split_on_grid_dict.py index 7b96fc4190..778a38da34 100644 --- a/tests/test_split_on_grid_dict.py +++ b/tests/test_split_on_grid_dict.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_squeezedim.py b/tests/test_squeezedim.py index 15ff7e94d6..8403efe836 100644 --- a/tests/test_squeezedim.py +++ b/tests/test_squeezedim.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_squeezedimd.py b/tests/test_squeezedimd.py index 35e7cd5d74..6baf4696a5 100644 --- a/tests/test_squeezedimd.py +++ b/tests/test_squeezedimd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_state_cacher.py b/tests/test_state_cacher.py index 9d7926c0d8..e4164be272 100644 --- a/tests/test_state_cacher.py +++ b/tests/test_state_cacher.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_std_shift_intensity.py b/tests/test_std_shift_intensity.py index 5c16e14c45..55750161ec 100644 --- a/tests/test_std_shift_intensity.py +++ b/tests/test_std_shift_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_std_shift_intensityd.py b/tests/test_std_shift_intensityd.py index 4eb256f1e5..595da5cbc2 100644 --- a/tests/test_std_shift_intensityd.py +++ b/tests/test_std_shift_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_subpixel_upsample.py b/tests/test_subpixel_upsample.py index 05390d231a..0216f164c3 100644 --- a/tests/test_subpixel_upsample.py +++ b/tests/test_subpixel_upsample.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_surface_distance.py b/tests/test_surface_distance.py index 8f09218f57..781c71c23a 100644 --- a/tests/test_surface_distance.py +++ b/tests/test_surface_distance.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_synthetic.py b/tests/test_synthetic.py index 6b08df8b00..fadf6255ff 100644 --- a/tests/test_synthetic.py +++ b/tests/test_synthetic.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_testtimeaugmentation.py b/tests/test_testtimeaugmentation.py index 09a7f1c2ed..cf4772e8bc 100644 --- a/tests/test_testtimeaugmentation.py +++ b/tests/test_testtimeaugmentation.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_thread_buffer.py b/tests/test_thread_buffer.py index 4efc6ee419..04511220f8 100644 --- a/tests/test_thread_buffer.py +++ b/tests/test_thread_buffer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_threadcontainer.py b/tests/test_threadcontainer.py index c33261710a..2419b390fd 100644 --- a/tests/test_threadcontainer.py +++ b/tests/test_threadcontainer.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_threshold_intensity.py b/tests/test_threshold_intensity.py index 075a650ec0..01321f1b0b 100644 --- a/tests/test_threshold_intensity.py +++ b/tests/test_threshold_intensity.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_threshold_intensityd.py b/tests/test_threshold_intensityd.py index a2a9fdcf2b..e0610ebb5b 100644 --- a/tests/test_threshold_intensityd.py +++ b/tests/test_threshold_intensityd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_tile_on_grid.py b/tests/test_tile_on_grid.py index 1a3fc8d44d..08fb5d96fe 100644 --- a/tests/test_tile_on_grid.py +++ b/tests/test_tile_on_grid.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_tile_on_grid_dict.py b/tests/test_tile_on_grid_dict.py index 78b7907805..9f1d67ac29 100644 --- a/tests/test_tile_on_grid_dict.py +++ b/tests/test_tile_on_grid_dict.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_timedcall.py b/tests/test_timedcall_dist.py similarity index 95% rename from tests/test_timedcall.py rename to tests/test_timedcall_dist.py index de10abb8f7..a2b3ae585a 100644 --- a/tests/test_timedcall.py +++ b/tests/test_timedcall_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,7 +17,7 @@ from tests.utils import TimedCall -@TimedCall(seconds=10 if sys.platform == "linux" else 60, force_quit=False) +@TimedCall(seconds=20 if sys.platform == "linux" else 60, force_quit=False) def case_1_seconds(arg=None): time.sleep(1) return "good" if not arg else arg diff --git a/tests/test_to_cupy.py b/tests/test_to_cupy.py index 4235be0580..36edf24f3f 100644 --- a/tests/test_to_cupy.py +++ b/tests/test_to_cupy.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,12 +17,12 @@ from monai.transforms import ToCupy from monai.utils import optional_import -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") -@skipUnless(has_cp, "CuPy is required.") +@skipUnless(HAS_CUPY, "CuPy is required.") class TestToCupy(unittest.TestCase): def test_cupy_input(self): test_data = cp.array([[1, 2], [3, 4]], dtype=cp.float32) diff --git a/tests/test_to_cupyd.py b/tests/test_to_cupyd.py index 58ff1571b0..3e778ae269 100644 --- a/tests/test_to_cupyd.py +++ b/tests/test_to_cupyd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,13 +17,13 @@ from monai.transforms import ToCupyd from monai.utils import optional_import -from tests.utils import skip_if_no_cuda +from tests.utils import HAS_CUPY, skip_if_no_cuda -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") +@skipUnless(HAS_CUPY, "CuPy is required.") class TestToCupyd(unittest.TestCase): - @skipUnless(has_cp, "CuPy is required.") def test_cupy_input(self): test_data = cp.array([[1, 2], [3, 4]]) test_data = cp.rot90(test_data) @@ -33,7 +33,6 @@ def test_cupy_input(self): self.assertTrue(result.flags["C_CONTIGUOUS"]) cp.testing.assert_allclose(result, test_data) - @skipUnless(has_cp, "CuPy is required.") def test_numpy_input(self): test_data = np.array([[1, 2], [3, 4]]) test_data = np.rot90(test_data) @@ -43,7 +42,6 @@ def test_numpy_input(self): self.assertTrue(result.flags["C_CONTIGUOUS"]) cp.testing.assert_allclose(result, test_data) - @skipUnless(has_cp, "CuPy is required.") def test_tensor_input(self): test_data = torch.tensor([[1, 2], [3, 4]]) test_data = test_data.rot90() @@ -53,7 +51,6 @@ def test_tensor_input(self): self.assertTrue(result.flags["C_CONTIGUOUS"]) cp.testing.assert_allclose(result, test_data.numpy()) - @skipUnless(has_cp, "CuPy is required.") @skip_if_no_cuda def test_tensor_cuda_input(self): test_data = torch.tensor([[1, 2], [3, 4]]).cuda() @@ -64,7 +61,6 @@ def test_tensor_cuda_input(self): self.assertTrue(result.flags["C_CONTIGUOUS"]) cp.testing.assert_allclose(result, test_data.cpu().numpy()) - @skipUnless(has_cp, "CuPy is required.") def test_list_tuple(self): test_data = [[1, 2], [3, 4]] result = ToCupyd(keys="img", wrap_sequence=True)({"img": test_data})["img"] diff --git a/tests/test_to_device.py b/tests/test_to_device.py index 9855a353f0..70f1ea8828 100644 --- a/tests/test_to_device.py +++ b/tests/test_to_device.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_to_deviced.py b/tests/test_to_deviced.py index 3b3a7a2e8f..7d075ad365 100644 --- a/tests/test_to_deviced.py +++ b/tests/test_to_deviced.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_to_numpy.py b/tests/test_to_numpy.py index 983e29647c..e1f135a289 100644 --- a/tests/test_to_numpy.py +++ b/tests/test_to_numpy.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,13 +17,13 @@ from monai.transforms import ToNumpy from monai.utils import optional_import -from tests.utils import assert_allclose, skip_if_no_cuda +from tests.utils import HAS_CUPY, assert_allclose, skip_if_no_cuda -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") class TestToNumpy(unittest.TestCase): - @skipUnless(has_cp, "CuPy is required.") + @skipUnless(HAS_CUPY, "CuPy is required.") def test_cupy_input(self): test_data = cp.array([[1, 2], [3, 4]]) test_data = cp.rot90(test_data) @@ -47,7 +47,7 @@ def test_tensor_input(self): test_data = torch.tensor([[1, 2], [3, 4]]) test_data = test_data.rot90() self.assertFalse(test_data.is_contiguous()) - result = ToNumpy()(test_data) + result = ToNumpy(dtype=torch.uint8)(test_data) self.assertTrue(isinstance(result, np.ndarray)) self.assertTrue(result.flags["C_CONTIGUOUS"]) assert_allclose(result, test_data, type_test=False) @@ -73,7 +73,7 @@ def test_list_tuple(self): def test_single_value(self): for test_data in [5, np.array(5), torch.tensor(5)]: - result = ToNumpy()(test_data) + result = ToNumpy(dtype=np.uint8)(test_data) self.assertTrue(isinstance(result, np.ndarray)) assert_allclose(result, np.asarray(test_data), type_test=False) self.assertEqual(result.ndim, 0) diff --git a/tests/test_to_numpyd.py b/tests/test_to_numpyd.py index 0b0b032ef2..ba7cf798ef 100644 --- a/tests/test_to_numpyd.py +++ b/tests/test_to_numpyd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -17,13 +17,13 @@ from monai.transforms import ToNumpyd from monai.utils import optional_import -from tests.utils import assert_allclose, skip_if_no_cuda +from tests.utils import HAS_CUPY, assert_allclose, skip_if_no_cuda -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") class TestToNumpyd(unittest.TestCase): - @skipUnless(has_cp, "CuPy is required.") + @skipUnless(HAS_CUPY, "CuPy is required.") def test_cupy_input(self): test_data = cp.array([[1, 2], [3, 4]]) test_data = cp.rot90(test_data) diff --git a/tests/test_to_onehot.py b/tests/test_to_onehot.py index c3e373955d..c08672bfb2 100644 --- a/tests/test_to_onehot.py +++ b/tests/test_to_onehot.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_to_pil.py b/tests/test_to_pil.py index b4581053c0..0a1351028c 100644 --- a/tests/test_to_pil.py +++ b/tests/test_to_pil.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_to_pild.py b/tests/test_to_pild.py index 3b83fa5258..d00ecf13d4 100644 --- a/tests/test_to_pild.py +++ b/tests/test_to_pild.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_to_tensor.py b/tests/test_to_tensor.py index 18129bc5de..bfc61cdb19 100644 --- a/tests/test_to_tensor.py +++ b/tests/test_to_tensor.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -15,9 +15,9 @@ from parameterized import parameterized from monai.transforms import ToTensor -from tests.utils import TEST_NDARRAYS, assert_allclose, optional_import +from tests.utils import HAS_CUPY, TEST_NDARRAYS, assert_allclose, optional_import -cp, has_cp = optional_import("cupy") +cp, _ = optional_import("cupy") im = [[1, 2], [3, 4]] @@ -47,7 +47,7 @@ def test_single_input(self, test_data): assert_allclose(result, test_data, type_test=False) self.assertEqual(result.ndim, 0) - @unittest.skipUnless(has_cp, "CuPy is required.") + @unittest.skipUnless(HAS_CUPY, "CuPy is required.") def test_cupy(self): test_data = [[1, 2], [3, 4]] cupy_array = cp.ascontiguousarray(cp.asarray(test_data)) diff --git a/tests/test_torchscript_utils.py b/tests/test_torchscript_utils.py new file mode 100644 index 0000000000..b26d41345a --- /dev/null +++ b/tests/test_torchscript_utils.py @@ -0,0 +1,112 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tempfile +import unittest + +import torch + +from monai.config import get_config_values +from monai.data import load_net_with_metadata, save_net_with_metadata +from monai.utils import JITMetadataKeys +from monai.utils.module import pytorch_after + + +class TestModule(torch.nn.Module): + def forward(self, x): + return x + 10 + + +class TestTorchscript(unittest.TestCase): + def test_save_net_with_metadata(self): + """Save a network without metadata to a file.""" + m = torch.jit.script(TestModule()) + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test") + + self.assertTrue(os.path.isfile(f"{tempdir}/test.pt")) + + def test_save_net_with_metadata_ext(self): + """Save a network without metadata to a file.""" + m = torch.jit.script(TestModule()) + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test.zip") + + self.assertTrue(os.path.isfile(f"{tempdir}/test.zip")) + + def test_save_net_with_metadata_with_extra(self): + """Save a network with simple metadata to a file.""" + m = torch.jit.script(TestModule()) + + test_metadata = {"foo": [1, 2], "bar": "string"} + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test", meta_values=test_metadata) + + self.assertTrue(os.path.isfile(f"{tempdir}/test.pt")) + + def test_load_net_with_metadata(self): + """Save then load a network with no metadata or other extra files.""" + m = torch.jit.script(TestModule()) + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test") + _, meta, extra_files = load_net_with_metadata(f"{tempdir}/test.pt") + + del meta[JITMetadataKeys.TIMESTAMP.value] # no way of knowing precisely what this value would be + + self.assertEqual(meta, get_config_values()) + self.assertEqual(extra_files, {}) + + def test_load_net_with_metadata_with_extra(self): + """Save then load a network with basic metadata.""" + m = torch.jit.script(TestModule()) + + test_metadata = {"foo": [1, 2], "bar": "string"} + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test", meta_values=test_metadata) + _, meta, extra_files = load_net_with_metadata(f"{tempdir}/test.pt") + + del meta[JITMetadataKeys.TIMESTAMP.value] # no way of knowing precisely what this value would be + + test_compare = get_config_values() + test_compare.update(test_metadata) + + self.assertEqual(meta, test_compare) + self.assertEqual(extra_files, {}) + + def test_save_load_more_extra_files(self): + """Save then load extra file data from a torchscript file.""" + m = torch.jit.script(TestModule()) + + test_metadata = {"foo": [1, 2], "bar": "string"} + + more_extra_files = {"test.txt": b"This is test data"} + + with tempfile.TemporaryDirectory() as tempdir: + save_net_with_metadata(m, f"{tempdir}/test", meta_values=test_metadata, more_extra_files=more_extra_files) + + self.assertTrue(os.path.isfile(f"{tempdir}/test.pt")) + + _, _, loaded_extra_files = load_net_with_metadata(f"{tempdir}/test.pt", more_extra_files=("test.txt",)) + + if pytorch_after(1, 7): + self.assertEqual(more_extra_files["test.txt"], loaded_extra_files["test.txt"]) + else: + self.assertEqual(more_extra_files["test.txt"].decode(), loaded_extra_files["test.txt"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_torchvision.py b/tests/test_torchvision.py index 8973ad523f..e0844eb4b9 100644 --- a/tests/test_torchvision.py +++ b/tests/test_torchvision.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_torchvision_fc_model.py b/tests/test_torchvision_fc_model.py index cc603e2585..98b300eeac 100644 --- a/tests/test_torchvision_fc_model.py +++ b/tests/test_torchvision_fc_model.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_torchvision_fully_conv_model.py b/tests/test_torchvision_fully_conv_model.py index 444e871c45..34a61ce9fa 100644 --- a/tests/test_torchvision_fully_conv_model.py +++ b/tests/test_torchvision_fully_conv_model.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_torchvisiond.py b/tests/test_torchvisiond.py index 1530691824..4c62c6e41a 100644 --- a/tests/test_torchvisiond.py +++ b/tests/test_torchvisiond.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_traceable_transform.py b/tests/test_traceable_transform.py index b4e9c509f7..bc6aad3a62 100644 --- a/tests/test_traceable_transform.py +++ b/tests/test_traceable_transform.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_train_mode.py b/tests/test_train_mode.py index 1acb443041..231e3854f0 100644 --- a/tests/test_train_mode.py +++ b/tests/test_train_mode.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_transchex.py b/tests/test_transchex.py index e178cb5184..462ce64fd6 100644 --- a/tests/test_transchex.py +++ b/tests/test_transchex.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_transformerblock.py b/tests/test_transformerblock.py index 616e3e7ec9..d6131d010c 100644 --- a/tests/test_transformerblock.py +++ b/tests/test_transformerblock.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_transpose.py b/tests/test_transpose.py index 176fa6f10e..94a5b49c3a 100644 --- a/tests/test_transpose.py +++ b/tests/test_transpose.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_transposed.py b/tests/test_transposed.py index 719efea4c3..14e62eb9da 100644 --- a/tests/test_transposed.py +++ b/tests/test_transposed.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_tversky_loss.py b/tests/test_tversky_loss.py index 57e887d3f0..2bb2409360 100644 --- a/tests/test_tversky_loss.py +++ b/tests/test_tversky_loss.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_unet.py b/tests/test_unet.py index e1dabc4ed0..1ae0094d55 100644 --- a/tests/test_unet.py +++ b/tests/test_unet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_unetr.py b/tests/test_unetr.py index d19ed2ca59..a8c7b7bf88 100644 --- a/tests/test_unetr.py +++ b/tests/test_unetr.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_unetr_block.py b/tests/test_unetr_block.py index 7546918a2c..c0f14c829d 100644 --- a/tests/test_unetr_block.py +++ b/tests/test_unetr_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_upsample_block.py b/tests/test_upsample_block.py index 02dbaacdc6..aa4141aabc 100644 --- a/tests/test_upsample_block.py +++ b/tests/test_upsample_block.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_utils_pytorch_numpy_unification.py b/tests/test_utils_pytorch_numpy_unification.py index c3b1bc259b..4db3056b7b 100644 --- a/tests/test_utils_pytorch_numpy_unification.py +++ b/tests/test_utils_pytorch_numpy_unification.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -16,7 +16,7 @@ from monai.transforms.utils_pytorch_numpy_unification import percentile from monai.utils import set_determinism -from tests.utils import TEST_NDARRAYS, assert_allclose +from tests.utils import TEST_NDARRAYS, SkipIfBeforePyTorchVersion, assert_allclose class TestPytorchNumpyUnification(unittest.TestCase): @@ -42,6 +42,18 @@ def test_fails(self): with self.assertRaises(ValueError): percentile(arr, q) + @SkipIfBeforePyTorchVersion((1, 7)) + def test_dim(self): + q = np.random.randint(0, 100, size=50) + results = [] + for p in TEST_NDARRAYS: + arr = p(np.arange(6).reshape(1, 2, 3).astype(np.float32)) + results.append(percentile(arr, q, dim=1)) + # pre torch 1.7, no `quantile`. Our own method doesn't interpolate, + # so we can only be accurate to 0.5 + atol = 0.5 if not hasattr(torch, "quantile") else 1e-4 + assert_allclose(results[0], results[-1], type_test=False, atol=atol) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_varautoencoder.py b/tests/test_varautoencoder.py index e22a017fc6..95fea8afcb 100644 --- a/tests/test_varautoencoder.py +++ b/tests/test_varautoencoder.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_version_leq.py b/tests/test_version_leq.py index 042a561a90..86fccca9fb 100644 --- a/tests/test_version_leq.py +++ b/tests/test_version_leq.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vis_cam.py b/tests/test_vis_cam.py index 47c116cd5d..2137926424 100644 --- a/tests/test_vis_cam.py +++ b/tests/test_vis_cam.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vis_gradcam.py b/tests/test_vis_gradcam.py index 72385a37a7..acca06d405 100644 --- a/tests/test_vis_gradcam.py +++ b/tests/test_vis_gradcam.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vis_gradcampp.py b/tests/test_vis_gradcampp.py index 5f801dce45..a261b6055b 100644 --- a/tests/test_vis_gradcampp.py +++ b/tests/test_vis_gradcampp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vit.py b/tests/test_vit.py index cdf0888222..870e4010ec 100644 --- a/tests/test_vit.py +++ b/tests/test_vit.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vitautoenc.py b/tests/test_vitautoenc.py index 9e4af61b0a..c45cde68c2 100644 --- a/tests/test_vitautoenc.py +++ b/tests/test_vitautoenc.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vnet.py b/tests/test_vnet.py index 4eba5396b2..add0396bd8 100644 --- a/tests/test_vnet.py +++ b/tests/test_vnet.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vote_ensemble.py b/tests/test_vote_ensemble.py index 08c375d568..79868d4706 100644 --- a/tests/test_vote_ensemble.py +++ b/tests/test_vote_ensemble.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_vote_ensembled.py b/tests/test_vote_ensembled.py index 6059f40bee..e42a57f3b7 100644 --- a/tests/test_vote_ensembled.py +++ b/tests/test_vote_ensembled.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_warp.py b/tests/test_warp.py index c6c79a369a..fde1048bf7 100644 --- a/tests/test_warp.py +++ b/tests/test_warp.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_distributed_weighted_random_sampler.py b/tests/test_weighted_random_sampler_dist.py similarity index 98% rename from tests/test_distributed_weighted_random_sampler.py rename to tests/test_weighted_random_sampler_dist.py index 23574e5121..13404a8acb 100644 --- a/tests/test_distributed_weighted_random_sampler.py +++ b/tests/test_weighted_random_sampler_dist.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_with_allow_missing_keys.py b/tests/test_with_allow_missing_keys.py index 68c5ad30c4..36d5c0c843 100644 --- a/tests/test_with_allow_missing_keys.py +++ b/tests/test_with_allow_missing_keys.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_write_metrics_reports.py b/tests/test_write_metrics_reports.py index 3c93f20144..101e1137b6 100644 --- a/tests/test_write_metrics_reports.py +++ b/tests/test_write_metrics_reports.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index 61eb2d82ce..416a0c11a1 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -28,6 +28,8 @@ has_cucim = has_cucim and hasattr(cucim, "CuImage") _, has_osl = optional_import("openslide") imsave, has_tiff = optional_import("tifffile", name="imsave") +_, has_codec = optional_import("imagecodecs") +has_tiff = has_tiff and has_codec FILE_URL = "https://drive.google.com/uc?id=1sGTKZlJBIz53pfqTxoTqiIQzIoEzHLAe" base_name, extension = FILE_URL.split("id=")[1], ".tiff" @@ -69,6 +71,13 @@ np.array([[[[239]], [[239]], [[239]]], [[[243]], [[243]], [[243]]]]), ] +TEST_CASE_5 = [ + FILE_PATH, + {"location": (HEIGHT - 2, WIDTH - 2), "level": 0, "grid_shape": (1, 1)}, + np.array([[[239, 239], [239, 239]], [[239, 239], [239, 239]], [[237, 237], [237, 237]]]), +] + + TEST_CASE_RGB_0 = [np.ones((3, 2, 2), dtype=np.uint8)] # CHW TEST_CASE_RGB_1 = [np.ones((3, 100, 100), dtype=np.uint8)] # CHW @@ -92,7 +101,7 @@ def save_rgba_tiff(array: np.ndarray, filename: str, mode: str): return filename -@skipUnless(has_cucim or has_osl, "Requires cucim or openslide!") +@skipUnless(has_cucim or has_osl or has_tiff, "Requires cucim, openslide, or tifffile!") def setUpModule(): # noqa: N802 download_url(FILE_URL, FILE_PATH, "5a3cfd4fd725c50578ddb80b517b759f") @@ -104,27 +113,37 @@ class Tests(unittest.TestCase): @parameterized.expand([TEST_CASE_0]) def test_read_whole_image(self, file_path, level, expected_shape): reader = WSIReader(self.backend, level=level) - img_obj = reader.read(file_path) - img = reader.get_data(img_obj)[0] + with reader.read(file_path) as img_obj: + img = reader.get_data(img_obj)[0] self.assertTupleEqual(img.shape, expected_shape) - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand([TEST_CASE_1, TEST_CASE_2, TEST_CASE_5]) def test_read_region(self, file_path, patch_info, expected_img): reader = WSIReader(self.backend) - img_obj = reader.read(file_path) - # Read twice to check multiple calls - img = reader.get_data(img_obj, **patch_info)[0] - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) + with reader.read(file_path) as img_obj: + if self.backend == "tifffile": + with self.assertRaises(ValueError): + reader.get_data(img_obj, **patch_info)[0] + else: + # Read twice to check multiple calls + img = reader.get_data(img_obj, **patch_info)[0] + img2 = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, img2.shape) + self.assertIsNone(assert_array_equal(img, img2)) + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) @parameterized.expand([TEST_CASE_3, TEST_CASE_4]) def test_read_patches(self, file_path, patch_info, expected_img): reader = WSIReader(self.backend) - img_obj = reader.read(file_path) - img = reader.get_data(img_obj, **patch_info)[0] - self.assertTupleEqual(img.shape, expected_img.shape) - self.assertIsNone(assert_array_equal(img, expected_img)) + with reader.read(file_path) as img_obj: + if self.backend == "tifffile": + with self.assertRaises(ValueError): + reader.get_data(img_obj, **patch_info)[0] + else: + img = reader.get_data(img_obj, **patch_info)[0] + self.assertTupleEqual(img.shape, expected_img.shape) + self.assertIsNone(assert_array_equal(img, expected_img)) @parameterized.expand([TEST_CASE_RGB_0, TEST_CASE_RGB_1]) @skipUnless(has_tiff, "Requires tifffile.") @@ -140,8 +159,8 @@ def test_read_rgba(self, img_expected): os.path.join(os.path.dirname(__file__), "testing_data", f"temp_tiff_image_{mode}.tiff"), mode=mode, ) - img_obj = reader.read(file_path) - image[mode], _ = reader.get_data(img_obj) + with reader.read(file_path) as img_obj: + image[mode], _ = reader.get_data(img_obj) self.assertIsNone(assert_array_equal(image["RGB"], img_expected)) self.assertIsNone(assert_array_equal(image["RGBA"], img_expected)) @@ -149,7 +168,10 @@ def test_read_rgba(self, img_expected): @parameterized.expand([TEST_CASE_TRANSFORM_0]) def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): train_transform = Compose( - [LoadImaged(keys=["image"], reader=WSIReader, backend="cuCIM", level=level), ToTensord(keys=["image"])] + [ + LoadImaged(keys=["image"], reader=WSIReader, backend=self.backend, level=level), + ToTensord(keys=["image"]), + ] ) dataset = Dataset([{"image": file_path}], transform=train_transform) data_loader = DataLoader(dataset) diff --git a/tests/test_zipdataset.py b/tests/test_zipdataset.py index 710ca71fc2..f381e0a453 100644 --- a/tests/test_zipdataset.py +++ b/tests/test_zipdataset.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_zoom.py b/tests/test_zoom.py index 9411988a7e..1a7694072e 100644 --- a/tests/test_zoom.py +++ b/tests/test_zoom.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_zoom_affine.py b/tests/test_zoom_affine.py index 49c3c0dcac..3c4bcd302c 100644 --- a/tests/test_zoom_affine.py +++ b/tests/test_zoom_affine.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/test_zoomd.py b/tests/test_zoomd.py index 6231978ca7..87a5cec22b 100644 --- a/tests/test_zoomd.py +++ b/tests/test_zoomd.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/testing_data/cpp_resample_answers.py b/tests/testing_data/cpp_resample_answers.py index 67af152059..93f596619e 100644 --- a/tests/testing_data/cpp_resample_answers.py +++ b/tests/testing_data/cpp_resample_answers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/testing_data/integration_answers.py b/tests/testing_data/integration_answers.py index c5d99bf1ed..99765a2b33 100644 --- a/tests/testing_data/integration_answers.py +++ b/tests/testing_data/integration_answers.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at diff --git a/tests/utils.py b/tests/utils.py index 7ed4ba1745..7e36b289e6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 - 2021 MONAI Consortium +# Copyright (c) MONAI Consortium # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -97,6 +97,10 @@ def test_pretrained_networks(network, input_param, device): return network(**input_param).to(device) except (URLError, HTTPError) as e: raise unittest.SkipTest(e) from e + except RuntimeError as r_error: + if "unexpected EOF" in f"{r_error}": # The file might be corrupted. + raise unittest.SkipTest(f"{r_error}") from r_error + raise def test_is_quick(): @@ -214,6 +218,27 @@ def __call__(self, obj): )(obj) +def has_cupy(): + """ + Returns True if the user has installed a version of cupy. + """ + cp, has_cp = optional_import("cupy") + if not has_cp: + return False + try: # test cupy installation with a basic example + x = cp.arange(6, dtype="f").reshape(2, 3) + y = cp.arange(3, dtype="f") + kernel = cp.ElementwiseKernel( + "float32 x, float32 y", "float32 z", """ if (x - 2 > y) { z = x * y; } else { z = x + y; } """, "my_kernel" + ) + return kernel(x, y)[0, 0] == 0 + except Exception: + return False + + +HAS_CUPY = has_cupy() + + def make_nifti_image(array: NdarrayOrTensor, affine=None): """ Create a temporary nifti image on the disk and return the image name. @@ -350,8 +375,7 @@ def run_process(self, func, local_rank, args, kwargs, results): os.environ["RANK"] = str(self.nproc_per_node * self.node_rank + local_rank) if torch.cuda.is_available(): - os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" - torch.cuda.set_device(int(local_rank)) + torch.cuda.set_device(int(local_rank)) # using device ids from CUDA_VISIBILE_DEVICES dist.init_process_group( backend=self.backend, @@ -406,6 +430,7 @@ def _wrapper(*args, **kwargs): for p in processes: p.join() assert results.get(), "Distributed call failed." + _del_original_func(obj) return _wrapper @@ -487,6 +512,7 @@ def _wrapper(*args, **kwargs): finally: p.join() + _del_original_func(obj) res = None try: res = results.get(block=False) @@ -512,6 +538,15 @@ def _cache_original_func(obj) -> None: _original_funcs[obj.__name__] = obj +def _del_original_func(obj): + """pop the original function from cache.""" + global _original_funcs + _original_funcs.pop(obj.__name__, None) + if torch.cuda.is_available(): # clean up the cached function + torch.cuda.synchronize() + torch.cuda.empty_cache() + + def _call_original_func(name, module, *args, **kwargs): if name not in _original_funcs: _original_module = importlib.import_module(module) # reimport, refresh _original_funcs @@ -600,7 +635,7 @@ def test_script_save(net, *inputs, device=None, rtol=1e-4, atol=0.0): def query_memory(n=2): """ - Find best n idle devices and return a string of device ids. + Find best n idle devices and return a string of device ids using the `nvidia-smi` command. """ bash_string = "nvidia-smi --query-gpu=power.draw,temperature.gpu,memory.used --format=csv,noheader,nounits"