From fa1c1af79ef5387434f2a76744f75b5aaca09f0b Mon Sep 17 00:00:00 2001 From: Han123su <107395380+Han123su@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:16:29 +0800 Subject: [PATCH 1/5] Fix RandomWeightedCrop for Integer Weightmap Handling (#8097) Fixes #7949 . ### Description Regardless of the type of `weight map`, random numbers should be kept as floating-point numbers for calculating the sampling location. However, `searchsorted` requires matching data structures. I have modified `convert_to_dst_type` to control converting only the data structure while maintaining the original data type. Additionally, I have included an example with integer weight maps in the test file. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Han123su Signed-off-by: Han123su <107395380+Han123su@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- monai/transforms/utils.py | 3 ++- tests/test_rand_weighted_crop.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 32fffc25f0..e7e1616e13 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -582,7 +582,8 @@ def weighted_patch_samples( if not v[-1] or not isfinite(v[-1]) or v[-1] < 0: # uniform sampling idx = r_state.randint(0, len(v), size=n_samples) else: - r, *_ = convert_to_dst_type(r_state.random(n_samples), v) + r_samples = r_state.random(n_samples) + r, *_ = convert_to_dst_type(r_samples, v, dtype=r_samples.dtype) idx = searchsorted(v, r * v[-1], right=True) # type: ignore idx, *_ = convert_to_dst_type(idx, v, dtype=torch.int) # type: ignore # compensate 'valid' mode diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index 47a8f3bfa2..f509065a56 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -90,6 +90,21 @@ def get_data(ndim): [[63, 37], [31, 43], [66, 20]], ] ) + im = SEG1_2D + weight_map = np.zeros_like(im, dtype=np.int32) + weight_map[0, 30, 20] = 3 + weight_map[0, 45, 44] = 1 + weight_map[0, 60, 50] = 2 + TESTS.append( + [ + "int w 2d", + dict(spatial_size=(10, 12), num_samples=3), + p(im), + q(weight_map), + (1, 10, 12), + [[60, 50], [30, 20], [45, 44]], + ] + ) im = SEG1_3D weight = np.zeros_like(im) weight[0, 5, 30, 17] = 1.1 @@ -149,6 +164,21 @@ def get_data(ndim): [[32, 24, 40], [32, 24, 40], [32, 24, 40]], ] ) + im = SEG1_3D + weight_map = np.zeros_like(im, dtype=np.int32) + weight_map[0, 6, 22, 19] = 4 + weight_map[0, 8, 40, 31] = 2 + weight_map[0, 13, 20, 24] = 3 + TESTS.append( + [ + "int w 3d", + dict(spatial_size=(8, 10, 12), num_samples=3), + p(im), + q(weight_map), + (1, 8, 10, 12), + [[13, 20, 24], [6, 22, 19], [8, 40, 31]], + ] + ) class TestRandWeightedCrop(CropTest): From acfc508ebd35ad46bbccbd19f2eb57d34609f0e7 Mon Sep 17 00:00:00 2001 From: YunLiu <55491388+KumoLiu@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:38:36 +0800 Subject: [PATCH 2/5] Fix dtype bug in ScaleIntensityRangePercentile (#8109) Fixes #8108 ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> --- monai/transforms/intensity/array.py | 2 +- tests/test_scale_intensity_range_percentiles.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 3b813809e4..20000c52c4 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -1411,7 +1411,7 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: else: img_t = self._normalize(img=img_t) - return convert_to_dst_type(img_t, dst=img)[0] + return convert_to_dst_type(img_t, dst=img, dtype=self.dtype)[0] class MaskIntensity(Transform): diff --git a/tests/test_scale_intensity_range_percentiles.py b/tests/test_scale_intensity_range_percentiles.py index 7c3a684a00..a7390efe72 100644 --- a/tests/test_scale_intensity_range_percentiles.py +++ b/tests/test_scale_intensity_range_percentiles.py @@ -14,6 +14,7 @@ import unittest import numpy as np +import torch from monai.transforms.intensity.array import ScaleIntensityRangePercentiles from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, assert_allclose @@ -34,6 +35,7 @@ def test_scaling(self): scaler = ScaleIntensityRangePercentiles(lower=lower, upper=upper, b_min=b_min, b_max=b_max, dtype=np.uint8) for p in TEST_NDARRAYS: result = scaler(p(img)) + self.assertEqual(result.dtype, torch.uint8) assert_allclose(result, p(expected), type_test="tensor", rtol=1e-4) def test_relative_scaling(self): From cac21f6936a2e8d6e4e57e4e958f8e32aae1585e Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Fri, 27 Sep 2024 04:23:47 +0100 Subject: [PATCH 3/5] Cleaning up some very old and now obsolete infrastructure (#8113) ### Description This removes some functions which aren't needed any more. These related to an early idea for doing importation to avoid defining `__all__` components to modules, but this is being done instead as it is more standard. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Eric Kerfoot Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> --- docs/source/networks.rst | 5 -- docs/source/utils.rst | 6 -- monai/__init__.py | 39 +++++---- monai/bundle/scripts.py | 3 +- monai/config/__init__.py | 1 - monai/config/deviceconfig.py | 10 --- monai/engines/evaluator.py | 4 +- monai/engines/trainer.py | 3 +- monai/engines/utils.py | 3 +- monai/engines/workflow.py | 3 +- monai/handlers/checkpoint_loader.py | 3 +- monai/handlers/checkpoint_saver.py | 3 +- monai/handlers/classification_saver.py | 2 +- monai/handlers/decollate_batch.py | 4 +- monai/handlers/earlystop_handler.py | 3 +- monai/handlers/garbage_collector.py | 3 +- monai/handlers/ignite_metric.py | 3 +- monai/handlers/logfile_handler.py | 3 +- monai/handlers/lr_schedule_handler.py | 3 +- monai/handlers/metric_logger.py | 3 +- monai/handlers/metrics_saver.py | 2 +- monai/handlers/mlflow_handler.py | 3 +- monai/handlers/nvtx_handlers.py | 3 +- monai/handlers/parameter_scheduler.py | 3 +- monai/handlers/postprocessing.py | 3 +- monai/handlers/probability_maps.py | 4 +- monai/handlers/smartcache_handler.py | 3 +- monai/handlers/stats_handler.py | 3 +- monai/handlers/tensorboard_handlers.py | 3 +- monai/handlers/trt_handler.py | 3 +- monai/handlers/utils.py | 4 +- monai/handlers/validation_handler.py | 3 +- monai/networks/nets/hovernet.py | 3 +- monai/networks/nets/unet.py | 3 - monai/networks/nets/voxelmorph.py | 5 -- monai/transforms/adaptors.py | 5 -- monai/utils/__init__.py | 7 +- monai/utils/aliases.py | 103 ------------------------ monai/utils/dist.py | 2 +- monai/utils/enums.py | 51 +++++++----- monai/utils/jupyter_utils.py | 2 +- monai/utils/module.py | 60 +------------- tests/min_tests.py | 6 -- tests/test_fastmri_reader.py | 3 +- tests/test_handler_garbage_collector.py | 3 +- 45 files changed, 90 insertions(+), 307 deletions(-) delete mode 100644 monai/utils/aliases.py diff --git a/docs/source/networks.rst b/docs/source/networks.rst index 1810fec49b..64a3a4c9d1 100644 --- a/docs/source/networks.rst +++ b/docs/source/networks.rst @@ -735,14 +735,9 @@ Nets .. autoclass:: VoxelMorphUNet :members: -.. autoclass:: voxelmorphunet - :members: - .. autoclass:: VoxelMorph :members: -.. autoclass:: voxelmorph - Utilities --------- .. automodule:: monai.networks.utils diff --git a/docs/source/utils.rst b/docs/source/utils.rst index fef671e1f8..ae3b476c3e 100644 --- a/docs/source/utils.rst +++ b/docs/source/utils.rst @@ -17,12 +17,6 @@ Module utils :members: -Aliases -------- -.. automodule:: monai.utils.aliases - :members: - - Misc ---- .. automodule:: monai.utils.misc diff --git a/monai/__init__.py b/monai/__init__.py index 3f6b12c407..46f7510915 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -79,28 +79,25 @@ def filter(self, record): category=RuntimeWarning, ) -from .utils.module import load_submodules # noqa: E402 - -# handlers_* have some external decorators the users may not have installed -# *.so files and folder "_C" may not exist when the cpp extensions are not compiled -excludes = "|".join( - [ - "(^(monai.handlers))", - "(^(monai.bundle))", - "(^(monai.fl))", - "((\\.so)$)", - "(^(monai._C))", - "(.*(__main__)$)", - "(.*(video_dataset)$)", - "(.*(nnunet).*$)", - ] -) - -# load directory modules only, skip loading individual files -load_submodules(sys.modules[__name__], False, exclude_pattern=excludes) -# load all modules, this will trigger all export decorations -load_submodules(sys.modules[__name__], True, exclude_pattern=excludes) +from . import ( # noqa: E402 + apps, + auto3dseg, + bundle, + config, + data, + engines, + fl, + handlers, + inferers, + losses, + metrics, + networks, + optimizers, + transforms, + utils, + visualize, +) __all__ = [ "apps", diff --git a/monai/bundle/scripts.py b/monai/bundle/scripts.py index f31ad0e814..4251da0b6f 100644 --- a/monai/bundle/scripts.py +++ b/monai/bundle/scripts.py @@ -34,7 +34,7 @@ from monai.bundle.config_parser import ConfigParser from monai.bundle.utils import DEFAULT_INFERENCE, DEFAULT_METADATA, merge_kv from monai.bundle.workflows import BundleWorkflow, ConfigWorkflow -from monai.config import IgniteInfo, PathLike +from monai.config import PathLike from monai.data import load_net_with_metadata, save_net_with_metadata from monai.networks import ( convert_to_onnx, @@ -45,6 +45,7 @@ save_state, ) from monai.utils import ( + IgniteInfo, check_parent_dir, deprecated_arg, ensure_tuple, diff --git a/monai/config/__init__.py b/monai/config/__init__.py index c814e1f8eb..a83889aee0 100644 --- a/monai/config/__init__.py +++ b/monai/config/__init__.py @@ -14,7 +14,6 @@ from .deviceconfig import ( USE_COMPILED, USE_META_DICT, - IgniteInfo, get_config_values, get_gpu_info, get_optional_config_values, diff --git a/monai/config/deviceconfig.py b/monai/config/deviceconfig.py index a4580c741b..7ac1b29919 100644 --- a/monai/config/deviceconfig.py +++ b/monai/config/deviceconfig.py @@ -45,7 +45,6 @@ "print_debug_info", "USE_COMPILED", "USE_META_DICT", - "IgniteInfo", ] @@ -261,14 +260,5 @@ def print_debug_info(file: TextIO = sys.stdout) -> None: print_gpu_info(file) -class IgniteInfo: - """ - Config information of the PyTorch ignite package. - - """ - - OPT_IMPORT_VERSION = "0.4.4" - - if __name__ == "__main__": print_debug_info() diff --git a/monai/engines/evaluator.py b/monai/engines/evaluator.py index 2c8dfe6b85..523c3dcbf6 100644 --- a/monai/engines/evaluator.py +++ b/monai/engines/evaluator.py @@ -17,14 +17,14 @@ import torch from torch.utils.data import DataLoader -from monai.config import IgniteInfo, KeysCollection +from monai.config import KeysCollection from monai.data import MetaTensor from monai.engines.utils import IterationEvents, default_metric_cmp_fn, default_prepare_batch from monai.engines.workflow import Workflow from monai.inferers import Inferer, SimpleInferer from monai.networks.utils import eval_mode, train_mode from monai.transforms import Transform -from monai.utils import ForwardMode, ensure_tuple, min_version, optional_import +from monai.utils import ForwardMode, IgniteInfo, ensure_tuple, min_version, optional_import from monai.utils.enums import CommonKeys as Keys from monai.utils.enums import EngineStatsKeys as ESKeys from monai.utils.module import look_up_option, pytorch_after diff --git a/monai/engines/trainer.py b/monai/engines/trainer.py index c1364fe015..bbcc9c880b 100644 --- a/monai/engines/trainer.py +++ b/monai/engines/trainer.py @@ -18,13 +18,12 @@ from torch.optim.optimizer import Optimizer from torch.utils.data import DataLoader -from monai.config import IgniteInfo from monai.data import MetaTensor from monai.engines.utils import IterationEvents, default_make_latent, default_metric_cmp_fn, default_prepare_batch from monai.engines.workflow import Workflow from monai.inferers import Inferer, SimpleInferer from monai.transforms import Transform -from monai.utils import AdversarialIterationEvents, AdversarialKeys, GanKeys, min_version, optional_import +from monai.utils import AdversarialIterationEvents, AdversarialKeys, GanKeys, IgniteInfo, min_version, optional_import from monai.utils.enums import CommonKeys as Keys from monai.utils.enums import EngineStatsKeys as ESKeys from monai.utils.module import pytorch_after diff --git a/monai/engines/utils.py b/monai/engines/utils.py index 5339d6965a..11a0000989 100644 --- a/monai/engines/utils.py +++ b/monai/engines/utils.py @@ -18,9 +18,8 @@ import torch import torch.nn as nn -from monai.config import IgniteInfo from monai.transforms import apply_transform -from monai.utils import ensure_tuple, min_version, optional_import +from monai.utils import IgniteInfo, ensure_tuple, min_version, optional_import from monai.utils.enums import CommonKeys, GanKeys if TYPE_CHECKING: diff --git a/monai/engines/workflow.py b/monai/engines/workflow.py index 30622c2b93..3629659db1 100644 --- a/monai/engines/workflow.py +++ b/monai/engines/workflow.py @@ -20,10 +20,9 @@ from torch.utils.data import DataLoader from torch.utils.data.distributed import DistributedSampler -from monai.config import IgniteInfo from monai.engines.utils import IterationEvents, default_metric_cmp_fn, default_prepare_batch from monai.transforms import Decollated -from monai.utils import ensure_tuple, is_scalar, min_version, optional_import +from monai.utils import IgniteInfo, ensure_tuple, is_scalar, min_version, optional_import from .utils import engine_apply_transform diff --git a/monai/handlers/checkpoint_loader.py b/monai/handlers/checkpoint_loader.py index 9a867534a3..f48968ecfd 100644 --- a/monai/handlers/checkpoint_loader.py +++ b/monai/handlers/checkpoint_loader.py @@ -17,9 +17,8 @@ import torch -from monai.config import IgniteInfo from monai.networks.utils import copy_model_state -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") Checkpoint, _ = optional_import("ignite.handlers", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Checkpoint") diff --git a/monai/handlers/checkpoint_saver.py b/monai/handlers/checkpoint_saver.py index 0651c6ff33..2a3a467570 100644 --- a/monai/handlers/checkpoint_saver.py +++ b/monai/handlers/checkpoint_saver.py @@ -17,8 +17,7 @@ from collections.abc import Mapping from typing import TYPE_CHECKING, Any -from monai.config import IgniteInfo -from monai.utils import is_scalar, min_version, optional_import +from monai.utils import IgniteInfo, is_scalar, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") diff --git a/monai/handlers/classification_saver.py b/monai/handlers/classification_saver.py index 831808f4fb..ffcfe3c1fb 100644 --- a/monai/handlers/classification_saver.py +++ b/monai/handlers/classification_saver.py @@ -18,8 +18,8 @@ import torch -from monai.config import IgniteInfo from monai.data import CSVSaver, decollate_batch +from monai.utils import IgniteInfo from monai.utils import ImageMetaKey as Key from monai.utils import evenly_divisible_all_gather, min_version, optional_import, string_list_all_gather diff --git a/monai/handlers/decollate_batch.py b/monai/handlers/decollate_batch.py index ac3aa94145..81415bd56e 100644 --- a/monai/handlers/decollate_batch.py +++ b/monai/handlers/decollate_batch.py @@ -13,10 +13,10 @@ from typing import TYPE_CHECKING -from monai.config import IgniteInfo, KeysCollection +from monai.config import KeysCollection from monai.engines.utils import IterationEvents from monai.transforms import Decollated -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/earlystop_handler.py b/monai/handlers/earlystop_handler.py index 93334bf5c0..0562335192 100644 --- a/monai/handlers/earlystop_handler.py +++ b/monai/handlers/earlystop_handler.py @@ -14,8 +14,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from monai.config import IgniteInfo -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") EarlyStopping, _ = optional_import("ignite.handlers", IgniteInfo.OPT_IMPORT_VERSION, min_version, "EarlyStopping") diff --git a/monai/handlers/garbage_collector.py b/monai/handlers/garbage_collector.py index 3d7e948364..586fa10d33 100644 --- a/monai/handlers/garbage_collector.py +++ b/monai/handlers/garbage_collector.py @@ -14,8 +14,7 @@ import gc from typing import TYPE_CHECKING -from monai.config import IgniteInfo -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import if TYPE_CHECKING: from ignite.engine import Engine, Events diff --git a/monai/handlers/ignite_metric.py b/monai/handlers/ignite_metric.py index 64ded4d5ea..44a5634c42 100644 --- a/monai/handlers/ignite_metric.py +++ b/monai/handlers/ignite_metric.py @@ -18,9 +18,8 @@ import torch from torch.nn.modules.loss import _Loss -from monai.config import IgniteInfo from monai.metrics import CumulativeIterationMetric, LossMetric -from monai.utils import MetricReduction, min_version, optional_import +from monai.utils import IgniteInfo, MetricReduction, min_version, optional_import idist, _ = optional_import("ignite", IgniteInfo.OPT_IMPORT_VERSION, min_version, "distributed") diff --git a/monai/handlers/logfile_handler.py b/monai/handlers/logfile_handler.py index df6ebd34a7..0c44ae47f4 100644 --- a/monai/handlers/logfile_handler.py +++ b/monai/handlers/logfile_handler.py @@ -15,8 +15,7 @@ import os from typing import TYPE_CHECKING -from monai.config import IgniteInfo -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/lr_schedule_handler.py b/monai/handlers/lr_schedule_handler.py index a79722517d..8d90992a84 100644 --- a/monai/handlers/lr_schedule_handler.py +++ b/monai/handlers/lr_schedule_handler.py @@ -17,8 +17,7 @@ from torch.optim.lr_scheduler import ReduceLROnPlateau, _LRScheduler -from monai.config import IgniteInfo -from monai.utils import ensure_tuple, min_version, optional_import +from monai.utils import IgniteInfo, ensure_tuple, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/metric_logger.py b/monai/handlers/metric_logger.py index d59205a021..62cdee6509 100644 --- a/monai/handlers/metric_logger.py +++ b/monai/handlers/metric_logger.py @@ -17,8 +17,7 @@ from threading import RLock from typing import TYPE_CHECKING, Any -from monai.config import IgniteInfo -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import from monai.utils.enums import CommonKeys Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") diff --git a/monai/handlers/metrics_saver.py b/monai/handlers/metrics_saver.py index 88a0926b91..6175b1242a 100644 --- a/monai/handlers/metrics_saver.py +++ b/monai/handlers/metrics_saver.py @@ -14,9 +14,9 @@ from collections.abc import Callable, Sequence from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.data import decollate_batch from monai.handlers.utils import write_metrics_reports +from monai.utils import IgniteInfo from monai.utils import ImageMetaKey as Key from monai.utils import ensure_tuple, min_version, optional_import, string_list_all_gather diff --git a/monai/handlers/mlflow_handler.py b/monai/handlers/mlflow_handler.py index 6d19579d9e..c7e293ea7d 100644 --- a/monai/handlers/mlflow_handler.py +++ b/monai/handlers/mlflow_handler.py @@ -22,8 +22,7 @@ from torch.utils.data import Dataset from monai.apps.utils import get_logger -from monai.config import IgniteInfo -from monai.utils import CommonKeys, ensure_tuple, min_version, optional_import +from monai.utils import CommonKeys, IgniteInfo, ensure_tuple, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") mlflow, _ = optional_import("mlflow", descriptor="Please install mlflow before using MLFlowHandler.") diff --git a/monai/handlers/nvtx_handlers.py b/monai/handlers/nvtx_handlers.py index 38eef6f05b..bd22af0db8 100644 --- a/monai/handlers/nvtx_handlers.py +++ b/monai/handlers/nvtx_handlers.py @@ -16,8 +16,7 @@ from typing import TYPE_CHECKING -from monai.config import IgniteInfo -from monai.utils import ensure_tuple, min_version, optional_import +from monai.utils import IgniteInfo, ensure_tuple, min_version, optional_import _nvtx, _ = optional_import("torch._C._nvtx", descriptor="NVTX is not installed. Are you sure you have a CUDA build?") if TYPE_CHECKING: diff --git a/monai/handlers/parameter_scheduler.py b/monai/handlers/parameter_scheduler.py index d12e6e072c..1ce6193b6d 100644 --- a/monai/handlers/parameter_scheduler.py +++ b/monai/handlers/parameter_scheduler.py @@ -16,8 +16,7 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from monai.config import IgniteInfo -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import if TYPE_CHECKING: from ignite.engine import Engine, Events diff --git a/monai/handlers/postprocessing.py b/monai/handlers/postprocessing.py index c698c84338..541b5924d1 100644 --- a/monai/handlers/postprocessing.py +++ b/monai/handlers/postprocessing.py @@ -14,9 +14,8 @@ from collections.abc import Callable from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.engines.utils import IterationEvents, engine_apply_transform -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/probability_maps.py b/monai/handlers/probability_maps.py index 8a60fcc983..e21bd199f8 100644 --- a/monai/handlers/probability_maps.py +++ b/monai/handlers/probability_maps.py @@ -17,10 +17,10 @@ import numpy as np -from monai.config import DtypeLike, IgniteInfo +from monai.config import DtypeLike from monai.data.folder_layout import FolderLayout from monai.utils import ProbMapKeys, min_version, optional_import -from monai.utils.enums import CommonKeys +from monai.utils.enums import CommonKeys, IgniteInfo Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/smartcache_handler.py b/monai/handlers/smartcache_handler.py index ee043635db..e07e98e541 100644 --- a/monai/handlers/smartcache_handler.py +++ b/monai/handlers/smartcache_handler.py @@ -13,9 +13,8 @@ from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.data import SmartCacheDataset -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/stats_handler.py b/monai/handlers/stats_handler.py index c49fcda819..ab36d19bd1 100644 --- a/monai/handlers/stats_handler.py +++ b/monai/handlers/stats_handler.py @@ -19,8 +19,7 @@ import torch from monai.apps import get_logger -from monai.config import IgniteInfo -from monai.utils import is_scalar, min_version, optional_import +from monai.utils import IgniteInfo, is_scalar, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/tensorboard_handlers.py b/monai/handlers/tensorboard_handlers.py index 7b7e3968fb..44a03710de 100644 --- a/monai/handlers/tensorboard_handlers.py +++ b/monai/handlers/tensorboard_handlers.py @@ -18,8 +18,7 @@ import numpy as np import torch -from monai.config import IgniteInfo -from monai.utils import is_scalar, min_version, optional_import +from monai.utils import IgniteInfo, is_scalar, min_version, optional_import from monai.visualize import plot_2d_or_3d_image Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") diff --git a/monai/handlers/trt_handler.py b/monai/handlers/trt_handler.py index 0e36b59d8c..45e2669f70 100644 --- a/monai/handlers/trt_handler.py +++ b/monai/handlers/trt_handler.py @@ -13,9 +13,8 @@ from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.networks import trt_compile -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/handlers/utils.py b/monai/handlers/utils.py index 0cd31b89c2..b6771f2dcc 100644 --- a/monai/handlers/utils.py +++ b/monai/handlers/utils.py @@ -19,8 +19,8 @@ import numpy as np import torch -from monai.config import IgniteInfo, KeysCollection, PathLike -from monai.utils import ensure_tuple, look_up_option, min_version, optional_import +from monai.config import KeysCollection, PathLike +from monai.utils import IgniteInfo, ensure_tuple, look_up_option, min_version, optional_import idist, _ = optional_import("ignite", IgniteInfo.OPT_IMPORT_VERSION, min_version, "distributed") if TYPE_CHECKING: diff --git a/monai/handlers/validation_handler.py b/monai/handlers/validation_handler.py index 89c7715f42..38dd511aa4 100644 --- a/monai/handlers/validation_handler.py +++ b/monai/handlers/validation_handler.py @@ -13,9 +13,8 @@ from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.engines.evaluator import Evaluator -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") if TYPE_CHECKING: diff --git a/monai/networks/nets/hovernet.py b/monai/networks/nets/hovernet.py index 5f340c9be6..3745b66bb5 100644 --- a/monai/networks/nets/hovernet.py +++ b/monai/networks/nets/hovernet.py @@ -43,7 +43,7 @@ from monai.networks.layers.factories import Conv, Dropout from monai.networks.layers.utils import get_act_layer, get_norm_layer from monai.utils.enums import HoVerNetBranch, HoVerNetMode, InterpolateMode, UpsampleMode -from monai.utils.module import export, look_up_option +from monai.utils.module import look_up_option __all__ = ["HoVerNet", "Hovernet", "HoVernet", "HoVerNet"] @@ -409,7 +409,6 @@ def forward(self, xin: torch.Tensor, short_cuts: list[torch.Tensor]) -> torch.Te return x -@export("monai.networks.nets") class HoVerNet(nn.Module): """HoVerNet model diff --git a/monai/networks/nets/unet.py b/monai/networks/nets/unet.py index 7b16b6c923..eac0ddab39 100644 --- a/monai/networks/nets/unet.py +++ b/monai/networks/nets/unet.py @@ -20,13 +20,10 @@ from monai.networks.blocks.convolutions import Convolution, ResidualUnit from monai.networks.layers.factories import Act, Norm from monai.networks.layers.simplelayers import SkipConnection -from monai.utils import alias, export __all__ = ["UNet", "Unet"] -@export("monai.networks.nets") -@alias("Unet") class UNet(nn.Module): """ Enhanced version of UNet which has residual units implemented with the ResidualUnit class. diff --git a/monai/networks/nets/voxelmorph.py b/monai/networks/nets/voxelmorph.py index 0496cfc8f8..4923b6ad60 100644 --- a/monai/networks/nets/voxelmorph.py +++ b/monai/networks/nets/voxelmorph.py @@ -21,13 +21,10 @@ from monai.networks.blocks.upsample import UpSample from monai.networks.blocks.warp import DVF2DDF, Warp from monai.networks.layers.simplelayers import SkipConnection -from monai.utils import alias, export __all__ = ["VoxelMorphUNet", "voxelmorphunet", "VoxelMorph", "voxelmorph"] -@export("monai.networks.nets") -@alias("voxelmorphunet") class VoxelMorphUNet(nn.Module): """ The backbone network used in VoxelMorph. See :py:class:`monai.networks.nets.VoxelMorph` for more details. @@ -340,8 +337,6 @@ def forward(self, concatenated_pairs: torch.Tensor) -> torch.Tensor: voxelmorphunet = VoxelMorphUNet -@export("monai.networks.nets") -@alias("voxelmorph") class VoxelMorph(nn.Module): """ A re-implementation of VoxelMorph framework for medical image registration as described in diff --git a/monai/transforms/adaptors.py b/monai/transforms/adaptors.py index f5f1a4fc18..5a0c24c7f6 100644 --- a/monai/transforms/adaptors.py +++ b/monai/transforms/adaptors.py @@ -125,12 +125,9 @@ def __call__(self, img, seg): from typing import Callable -from monai.utils import export as _monai_export - __all__ = ["adaptor", "apply_alias", "to_kwargs", "FunctionSignature"] -@_monai_export("monai.transforms") def adaptor(function, outputs, inputs=None): def must_be_types_or_none(variable_name, variable, types): @@ -215,7 +212,6 @@ def _inner(ditems): return _inner -@_monai_export("monai.transforms") def apply_alias(fn, name_map): def _inner(data): @@ -236,7 +232,6 @@ def _inner(data): return _inner -@_monai_export("monai.transforms") def to_kwargs(fn): def _inner(data): diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index 4e36e3cd47..b5b2d7a525 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -11,8 +11,6 @@ from __future__ import annotations -# have to explicitly bring these in here to resolve circular import issues -from .aliases import alias, resolve_name from .component_store import ComponentStore from .decorators import MethodReplacer, RestartGenerator from .deprecate_utils import DeprecatedError, deprecated, deprecated_arg, deprecated_arg_default @@ -40,6 +38,7 @@ GridSamplePadMode, HoVerNetBranch, HoVerNetMode, + IgniteInfo, InterpolateMode, JITMetadataKeys, LazyAttr, @@ -109,12 +108,10 @@ allow_missing_reference, damerau_levenshtein_distance, exact_version, - export, get_full_type_name, get_package_version, get_torch_version_tuple, instantiate, - load_submodules, look_up_option, min_version, optional_import, @@ -153,3 +150,5 @@ get_numpy_dtype_from_string, get_torch_dtype_from_string, ) + +# have to explicitly bring these in here to resolve circular import issues diff --git a/monai/utils/aliases.py b/monai/utils/aliases.py deleted file mode 100644 index 2974eec2eb..0000000000 --- a/monai/utils/aliases.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright (c) MONAI Consortium -# Licensed under the Apache License, Version 2.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. -""" -This module is written for configurable workflow, not currently in use. -""" - -from __future__ import annotations - -import importlib -import inspect -import sys -import threading - -alias_lock = threading.RLock() -GlobalAliases = {} - -__all__ = ["alias", "resolve_name"] - - -def alias(*names): - """ - Stores the decorated function or class in the global aliases table under the given names and as the `__aliases__` - member of the decorated object. This new member will contain all alias names declared for that object. - """ - - def _outer(obj): - for n in names: - with alias_lock: - GlobalAliases[n] = obj - - # set the member list __aliases__ to contain the alias names defined by the decorator for `obj` - obj.__aliases__ = getattr(obj, "__aliases__", ()) + tuple(names) - - return obj - - return _outer - - -def resolve_name(name): - """ - Search for the declaration (function or class) with the given name. This will first search the list of aliases to - see if it was declared with this aliased name, then search treating `name` as a fully qualified name, then search - the loaded modules for one having a declaration with the given name. If no declaration is found, raise ValueError. - - Raises: - ValueError: When the module is not found. - ValueError: When the module does not have the specified member. - ValueError: When multiple modules with the declaration name are found. - ValueError: When no module with the specified member is found. - - """ - # attempt to resolve an alias - with alias_lock: - obj = GlobalAliases.get(name) - - if name in GlobalAliases and obj is None: - raise AssertionError - - # attempt to resolve a qualified name - if obj is None and "." in name: - modname, declname = name.rsplit(".", 1) - - try: - mod = importlib.import_module(modname) - obj = getattr(mod, declname, None) - except ModuleNotFoundError as not_found_err: - raise ValueError(f"Module {modname!r} not found.") from not_found_err - - if obj is None: - raise ValueError(f"Module {modname!r} does not have member {declname!r}.") - - # attempt to resolve a simple name - if obj is None: - # Get all modules having the declaration/import, need to check here that getattr returns something which doesn't - # equate to False since in places __getattr__ returns 0 incorrectly: - # https://github.com/tensorflow/tensorboard/blob/a22566561d2b4fea408755a951ac9eaf3a156f8e/ - # tensorboard/compat/tensorflow_stub/pywrap_tensorflow.py#L35 - mods = [m for m in list(sys.modules.values()) if getattr(m, name, None)] - - if len(mods) > 0: # found modules with this declaration or import - if len(mods) > 1: # found multiple modules, need to determine if ambiguous or just multiple imports - foundmods = set(filter(None, {inspect.getmodule(getattr(m, name)) for m in mods})) # resolve imports - - if len(foundmods) > 1: # found multiple declarations with the same name - modnames = [m.__name__ for m in foundmods] - msg = f"Multiple modules ({modnames!r}) with declaration name {name!r} found, resolution is ambiguous." - raise ValueError(msg) - mods = list(foundmods) - - obj = getattr(mods[0], name) - - if obj is None: - raise ValueError(f"No module with member {name!r} found.") - - return obj diff --git a/monai/utils/dist.py b/monai/utils/dist.py index 2418b43591..c7ff988027 100644 --- a/monai/utils/dist.py +++ b/monai/utils/dist.py @@ -24,7 +24,7 @@ import torch import torch.distributed as dist -from monai.config import IgniteInfo +from monai.utils.enums import IgniteInfo from monai.utils.module import min_version, optional_import idist, has_ignite = optional_import("ignite", IgniteInfo.OPT_IMPORT_VERSION, min_version, "distributed") diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 7838a2e741..900133f2f2 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -15,7 +15,6 @@ from enum import Enum from typing import TYPE_CHECKING -from monai.config import IgniteInfo from monai.utils.module import min_version, optional_import __all__ = [ @@ -61,6 +60,7 @@ "BundleProperty", "BundlePropertyConfig", "AlgoKeys", + "IgniteInfo", ] @@ -89,14 +89,6 @@ def __repr__(self): return self.value -if TYPE_CHECKING: - from ignite.engine import EventEnum -else: - EventEnum, _ = optional_import( - "ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "EventEnum", as_type="base" - ) - - class NumpyPadMode(StrEnum): """ See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html @@ -717,6 +709,35 @@ class AdversarialKeys(StrEnum): DISCRIMINATOR_LOSS = "discriminator_loss" +class OrderingType(StrEnum): + RASTER_SCAN = "raster_scan" + S_CURVE = "s_curve" + RANDOM = "random" + + +class OrderingTransformations(StrEnum): + ROTATE_90 = "rotate_90" + TRANSPOSE = "transpose" + REFLECT = "reflect" + + +class IgniteInfo(StrEnum): + """ + Config information of the PyTorch ignite package. + + """ + + OPT_IMPORT_VERSION = "0.4.4" + + +if TYPE_CHECKING: + from ignite.engine import EventEnum +else: + EventEnum, _ = optional_import( + "ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "EventEnum", as_type="base" + ) + + class AdversarialIterationEvents(EventEnum): """ Keys used to define events as used in the AdversarialTrainer. @@ -733,15 +754,3 @@ class AdversarialIterationEvents(EventEnum): DISCRIMINATOR_LOSS_COMPLETED = "discriminator_loss_completed" DISCRIMINATOR_BACKWARD_COMPLETED = "discriminator_backward_completed" DISCRIMINATOR_MODEL_COMPLETED = "discriminator_model_completed" - - -class OrderingType(StrEnum): - RASTER_SCAN = "raster_scan" - S_CURVE = "s_curve" - RANDOM = "random" - - -class OrderingTransformations(StrEnum): - ROTATE_90 = "rotate_90" - TRANSPOSE = "transpose" - REFLECT = "reflect" diff --git a/monai/utils/jupyter_utils.py b/monai/utils/jupyter_utils.py index 7dcd0e62cd..b1b43a6767 100644 --- a/monai/utils/jupyter_utils.py +++ b/monai/utils/jupyter_utils.py @@ -24,7 +24,7 @@ import numpy as np import torch -from monai.config import IgniteInfo +from monai.utils import IgniteInfo from monai.utils.module import min_version, optional_import try: diff --git a/monai/utils/module.py b/monai/utils/module.py index 78087aef84..3f07dd9e91 100644 --- a/monai/utils/module.py +++ b/monai/utils/module.py @@ -16,15 +16,13 @@ import os import pdb import re -import sys import warnings from collections.abc import Callable, Collection, Hashable, Mapping from functools import partial, wraps from importlib import import_module -from pkgutil import walk_packages from pydoc import locate from re import match -from types import FunctionType, ModuleType +from types import FunctionType from typing import Any, Iterable, cast import torch @@ -43,13 +41,11 @@ "InvalidPyTorchVersionError", "OptionalImportError", "exact_version", - "export", "damerau_levenshtein_distance", "look_up_option", "min_version", "optional_import", "require_pkg", - "load_submodules", "instantiate", "get_full_type_name", "get_package_version", @@ -172,60 +168,6 @@ def damerau_levenshtein_distance(s1: str, s2: str) -> int: return d[string_1_length - 1, string_2_length - 1] -def export(modname): - """ - Make the decorated object a member of the named module. This will also add the object under its aliases if it has - a `__aliases__` member, thus this decorator should be before the `alias` decorator to pick up those names. Alias - names which conflict with package names or existing members will be ignored. - """ - - def _inner(obj): - mod = import_module(modname) - if not hasattr(mod, obj.__name__): - setattr(mod, obj.__name__, obj) - - # add the aliases for `obj` to the target module - for alias in getattr(obj, "__aliases__", ()): - if not hasattr(mod, alias): - setattr(mod, alias, obj) - - return obj - - return _inner - - -def load_submodules( - basemod: ModuleType, load_all: bool = True, exclude_pattern: str = "(.*[tT]est.*)|(_.*)" -) -> tuple[list[ModuleType], list[str]]: - """ - Traverse the source of the module structure starting with module `basemod`, loading all packages plus all files if - `load_all` is True, excluding anything whose name matches `exclude_pattern`. - """ - submodules = [] - err_mod: list[str] = [] - for importer, name, is_pkg in walk_packages( - basemod.__path__, prefix=basemod.__name__ + ".", onerror=err_mod.append - ): - if (is_pkg or load_all) and name not in sys.modules and match(exclude_pattern, name) is None: - try: - mod = import_module(name) - mod_spec = importer.find_spec(name) # type: ignore - if mod_spec and mod_spec.loader: - loader = mod_spec.loader - loader.exec_module(mod) - submodules.append(mod) - except OptionalImportError: - pass # could not import the optional deps., they are ignored - except ImportError as e: - msg = ( - "\nMultiple versions of MONAI may have been installed?\n" - "Please see the installation guide: https://docs.monai.io/en/stable/installation.html\n" - ) # issue project-monai/monai#5193 - raise type(e)(f"{e}\n{msg}").with_traceback(e.__traceback__) from e # raise with modified message - - return submodules, err_mod - - def instantiate(__path: str, __mode: str, **kwargs: Any) -> Any: """ Create an object instance or call a callable object from a class or function represented by ``_path``. diff --git a/tests/min_tests.py b/tests/min_tests.py index f39d3f9843..f5b715e979 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -232,12 +232,6 @@ def run_testsuit(): if __name__ == "__main__": - # testing import submodules - from monai.utils.module import load_submodules - - _, err_mod = load_submodules(sys.modules["monai"], True) - assert not err_mod, f"err_mod={err_mod} not empty" - # testing all modules test_runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) result = test_runner.run(run_testsuit()) diff --git a/tests/test_fastmri_reader.py b/tests/test_fastmri_reader.py index af2eed7db5..06c3954eae 100644 --- a/tests/test_fastmri_reader.py +++ b/tests/test_fastmri_reader.py @@ -17,7 +17,7 @@ from parameterized import parameterized from monai.apps.reconstruction.fastmri_reader import FastMRIReader -from tests.utils import assert_allclose +from tests.utils import SkipIfNoModule, assert_allclose TEST_CASE1 = [ { @@ -64,6 +64,7 @@ ] +@SkipIfNoModule("h5py") class TestMRIUtils(unittest.TestCase): @parameterized.expand([TEST_CASE1, TEST_CASE2]) diff --git a/tests/test_handler_garbage_collector.py b/tests/test_handler_garbage_collector.py index 317eba1b11..4254a73a6b 100644 --- a/tests/test_handler_garbage_collector.py +++ b/tests/test_handler_garbage_collector.py @@ -19,10 +19,9 @@ from ignite.engine import Engine from parameterized import parameterized -from monai.config import IgniteInfo from monai.data import Dataset from monai.handlers import GarbageCollector -from monai.utils import min_version, optional_import +from monai.utils import IgniteInfo, min_version, optional_import Events, has_ignite = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events") From 8546be098daaf6841c39a2748412bbda83929c92 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:39:20 +0100 Subject: [PATCH 4/5] Re-adding load_submodules (#8118) ### Description This restores load_modules for now to resolve importation issues. Doing a two-pass loading process seems to allow some references which normally Python wouldn't permit. The fact that these issues weren't caught in testing is rather strange, and only showed up when symbols in `monai.apps` were references in bundles. I'm not sure if this is all related to circular imports, if parts of MONAI aren't being tested properly, or the way the symbol resolution works within `monai.bundle` should be improved. ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Eric Kerfoot --- monai/__init__.py | 38 +++++++++++++++++++++----------------- monai/utils/__init__.py | 1 + monai/utils/module.py | 36 +++++++++++++++++++++++++++++++++++- tests/min_tests.py | 6 ++++++ 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/monai/__init__.py b/monai/__init__.py index 46f7510915..f6fc8b0646 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -80,25 +80,29 @@ def filter(self, record): ) -from . import ( # noqa: E402 - apps, - auto3dseg, - bundle, - config, - data, - engines, - fl, - handlers, - inferers, - losses, - metrics, - networks, - optimizers, - transforms, - utils, - visualize, +from .utils.module import load_submodules # noqa: E402 + +# handlers_* have some external decorators the users may not have installed +# *.so files and folder "_C" may not exist when the cpp extensions are not compiled +excludes = "|".join( + [ + "(^(monai.handlers))", + "(^(monai.bundle))", + "(^(monai.fl))", + "((\\.so)$)", + "(^(monai._C))", + "(.*(__main__)$)", + "(.*(video_dataset)$)", + "(.*(nnunet).*$)", + ] ) +# load directory modules only, skip loading individual files +load_submodules(sys.modules[__name__], False, exclude_pattern=excludes) + +# load all modules, this will trigger all export decorations +load_submodules(sys.modules[__name__], True, exclude_pattern=excludes) + __all__ = [ "apps", "auto3dseg", diff --git a/monai/utils/__init__.py b/monai/utils/__init__.py index b5b2d7a525..916c1a6c70 100644 --- a/monai/utils/__init__.py +++ b/monai/utils/__init__.py @@ -112,6 +112,7 @@ get_package_version, get_torch_version_tuple, instantiate, + load_submodules, look_up_option, min_version, optional_import, diff --git a/monai/utils/module.py b/monai/utils/module.py index 3f07dd9e91..df5fe873ae 100644 --- a/monai/utils/module.py +++ b/monai/utils/module.py @@ -16,13 +16,15 @@ import os import pdb import re +import sys import warnings from collections.abc import Callable, Collection, Hashable, Mapping from functools import partial, wraps from importlib import import_module +from pkgutil import walk_packages from pydoc import locate from re import match -from types import FunctionType +from types import FunctionType, ModuleType from typing import Any, Iterable, cast import torch @@ -168,6 +170,38 @@ def damerau_levenshtein_distance(s1: str, s2: str) -> int: return d[string_1_length - 1, string_2_length - 1] +def load_submodules( + basemod: ModuleType, load_all: bool = True, exclude_pattern: str = "(.*[tT]est.*)|(_.*)" +) -> tuple[list[ModuleType], list[str]]: + """ + Traverse the source of the module structure starting with module `basemod`, loading all packages plus all files if + `load_all` is True, excluding anything whose name matches `exclude_pattern`. + """ + submodules = [] + err_mod: list[str] = [] + for importer, name, is_pkg in walk_packages( + basemod.__path__, prefix=basemod.__name__ + ".", onerror=err_mod.append + ): + if (is_pkg or load_all) and name not in sys.modules and match(exclude_pattern, name) is None: + try: + mod = import_module(name) + mod_spec = importer.find_spec(name) # type: ignore + if mod_spec and mod_spec.loader: + loader = mod_spec.loader + loader.exec_module(mod) + submodules.append(mod) + except OptionalImportError: + pass # could not import the optional deps., they are ignored + except ImportError as e: + msg = ( + "\nMultiple versions of MONAI may have been installed?\n" + "Please see the installation guide: https://docs.monai.io/en/stable/installation.html\n" + ) # issue project-monai/monai#5193 + raise type(e)(f"{e}\n{msg}").with_traceback(e.__traceback__) from e # raise with modified message + + return submodules, err_mod + + def instantiate(__path: str, __mode: str, **kwargs: Any) -> Any: """ Create an object instance or call a callable object from a class or function represented by ``_path``. diff --git a/tests/min_tests.py b/tests/min_tests.py index f5b715e979..f39d3f9843 100644 --- a/tests/min_tests.py +++ b/tests/min_tests.py @@ -232,6 +232,12 @@ def run_testsuit(): if __name__ == "__main__": + # testing import submodules + from monai.utils.module import load_submodules + + _, err_mod = load_submodules(sys.modules["monai"], True) + assert not err_mod, f"err_mod={err_mod} not empty" + # testing all modules test_runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) result = test_runner.run(run_testsuit()) From 76ef9f40c8da626928238c91eacddc789b0b4530 Mon Sep 17 00:00:00 2001 From: YunLiu <55491388+KumoLiu@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:41:14 +0800 Subject: [PATCH 5/5] Fix IgniteInfo can not import issue (#8121) Fixes #8120 . ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [ ] Breaking change (fix or new feature that would cause existing functionality to change). - [ ] New tests added to cover the changes. - [ ] Integration tests passed locally by running `./runtests.sh -f -u --net --coverage`. - [ ] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [ ] In-line docstrings updated. - [ ] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> --- monai/config/__init__.py | 1 + monai/config/deviceconfig.py | 10 ++++++++++ monai/utils/enums.py | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/monai/config/__init__.py b/monai/config/__init__.py index a83889aee0..c814e1f8eb 100644 --- a/monai/config/__init__.py +++ b/monai/config/__init__.py @@ -14,6 +14,7 @@ from .deviceconfig import ( USE_COMPILED, USE_META_DICT, + IgniteInfo, get_config_values, get_gpu_info, get_optional_config_values, diff --git a/monai/config/deviceconfig.py b/monai/config/deviceconfig.py index 7ac1b29919..05842245ce 100644 --- a/monai/config/deviceconfig.py +++ b/monai/config/deviceconfig.py @@ -23,6 +23,8 @@ import torch import monai +from monai.utils.deprecate_utils import deprecated +from monai.utils.enums import IgniteInfo as _IgniteInfo from monai.utils.module import OptionalImportError, get_package_version, optional_import try: @@ -45,6 +47,7 @@ "print_debug_info", "USE_COMPILED", "USE_META_DICT", + "IgniteInfo", ] @@ -260,5 +263,12 @@ def print_debug_info(file: TextIO = sys.stdout) -> None: print_gpu_info(file) +@deprecated(since="1.4.0", removed="1.6.0", msg_suffix="Please use `monai.utils.enums.IgniteInfo` instead.") +class IgniteInfo: + """Deprecated Import of IgniteInfo enum, which was moved to `monai.utils.enums.IgniteInfo`.""" + + OPT_IMPORT_VERSION = _IgniteInfo.OPT_IMPORT_VERSION + + if __name__ == "__main__": print_debug_info() diff --git a/monai/utils/enums.py b/monai/utils/enums.py index 900133f2f2..1fbf3ffa05 100644 --- a/monai/utils/enums.py +++ b/monai/utils/enums.py @@ -727,7 +727,7 @@ class IgniteInfo(StrEnum): """ - OPT_IMPORT_VERSION = "0.4.4" + OPT_IMPORT_VERSION = "0.4.11" if TYPE_CHECKING: