From e3a2bb255a40cd10dab5ddd25185765622503e54 Mon Sep 17 00:00:00 2001 From: Oleksandr Yizchak Sanin Date: Tue, 19 May 2026 00:19:34 +0200 Subject: [PATCH 1/4] Replace BaseException with Exception across codebase (#7401) Catching BaseException inadvertently suppresses KeyboardInterrupt, SystemExit, and GeneratorExit, which should nearly always propagate. All 17 occurrences across monai/ and tests/ are replaced with Exception, which is the appropriate base class for catchable errors. Signed-off-by: Oleksandr Sanin --- monai/__init__.py | 2 +- monai/apps/auto3dseg/data_analyzer.py | 4 ++-- monai/apps/auto3dseg/ensemble_builder.py | 2 +- monai/apps/auto3dseg/utils.py | 2 +- monai/apps/detection/metrics/coco.py | 2 +- monai/apps/nnunet/nnunetv2_runner.py | 4 ++-- monai/config/deviceconfig.py | 2 +- monai/data/__init__.py | 2 +- monai/inferers/inferer.py | 2 +- monai/utils/tf32.py | 4 ++-- tests/apps/detection/networks/test_retinanet.py | 4 ++-- tests/networks/nets/test_resnet.py | 2 +- tests/test_utils.py | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/monai/__init__.py b/monai/__init__.py index d92557a8e1a..45e15ddcd9a 100644 --- a/monai/__init__.py +++ b/monai/__init__.py @@ -131,7 +131,7 @@ def filter(self, record): # workaround related to https://github.com/Project-MONAI/MONAI/issues/7575 if hasattr(torch.cuda.device_count, "cache_clear"): torch.cuda.device_count.cache_clear() -except BaseException: +except Exception: from .utils.misc import MONAIEnvVars if MONAIEnvVars.debug(): diff --git a/monai/apps/auto3dseg/data_analyzer.py b/monai/apps/auto3dseg/data_analyzer.py index 15e56abfea4..30824e200fe 100644 --- a/monai/apps/auto3dseg/data_analyzer.py +++ b/monai/apps/auto3dseg/data_analyzer.py @@ -341,7 +341,7 @@ def _get_all_case_stats( _label_argmax = True # track if label is argmaxed batch_data[self.label_key] = label.to(device) d = summarizer(batch_data) - except BaseException as err: + except Exception as err: if "image_meta_dict" in batch_data.keys(): filename = batch_data["image_meta_dict"][ImageMetaKey.FILENAME_OR_OBJ] else: @@ -357,7 +357,7 @@ def _get_all_case_stats( label = torch.argmax(label, dim=0) if label.shape[0] > 1 else label[0] batch_data[self.label_key] = label.to("cpu") d = summarizer(batch_data) - except BaseException as err: + except Exception as err: logger.info(f"Unable to process data {filename} on {device}. {err}") continue else: diff --git a/monai/apps/auto3dseg/ensemble_builder.py b/monai/apps/auto3dseg/ensemble_builder.py index e574baf7c88..eaf1f14c7ff 100644 --- a/monai/apps/auto3dseg/ensemble_builder.py +++ b/monai/apps/auto3dseg/ensemble_builder.py @@ -216,7 +216,7 @@ def __call__(self, pred_param: dict | None = None) -> list: if "image_save_func" in param: try: ensemble_preds = self.ensemble_pred(preds, sigmoid=sigmoid) - except BaseException: + except Exception: ensemble_preds = self.ensemble_pred([_.to("cpu") for _ in preds], sigmoid=sigmoid) res = img_saver(ensemble_preds) # res is the path to the saved results diff --git a/monai/apps/auto3dseg/utils.py b/monai/apps/auto3dseg/utils.py index fbf9dc101c3..1946bb718a4 100644 --- a/monai/apps/auto3dseg/utils.py +++ b/monai/apps/auto3dseg/utils.py @@ -59,7 +59,7 @@ def import_bundle_algo_history( if best_metric is None: try: best_metric = algo.get_score() - except BaseException: + except Exception: pass is_trained = best_metric is not None diff --git a/monai/apps/detection/metrics/coco.py b/monai/apps/detection/metrics/coco.py index d1347d76b8f..b8de0eb1044 100644 --- a/monai/apps/detection/metrics/coco.py +++ b/monai/apps/detection/metrics/coco.py @@ -541,7 +541,7 @@ def _compute_stats_single_threshold( for save_idx, array_index in enumerate(inds): precision[save_idx] = pr[array_index] th_scores[save_idx] = dt_scores_sorted[array_index] - except BaseException: + except Exception: pass return recall, np.array(precision), np.array(th_scores) diff --git a/monai/apps/nnunet/nnunetv2_runner.py b/monai/apps/nnunet/nnunetv2_runner.py index 98b265cbbba..4828ccb56e0 100644 --- a/monai/apps/nnunet/nnunetv2_runner.py +++ b/monai/apps/nnunet/nnunetv2_runner.py @@ -200,7 +200,7 @@ def __init__( from nnunetv2.utilities.dataset_name_id_conversion import maybe_convert_to_dataset_name self.dataset_name = maybe_convert_to_dataset_name(int(self.dataset_name_or_id)) - except BaseException: + except Exception: logger.warning( f"Dataset with name/ID: {self.dataset_name_or_id} cannot be found in the record. " "Please ignore the message above if you are running the pipeline from a fresh start. " @@ -278,7 +278,7 @@ def convert_dataset(self): num_input_channels=num_input_channels, output_datafolder=raw_data_foldername, ) - except BaseException as err: + except Exception as err: logger.warning(f"Input config may be incorrect. Detail info: error/exception message is:\n {err}") return diff --git a/monai/config/deviceconfig.py b/monai/config/deviceconfig.py index aa1f2a0b536..5800651e59a 100644 --- a/monai/config/deviceconfig.py +++ b/monai/config/deviceconfig.py @@ -120,7 +120,7 @@ def print_config(file=sys.stdout): def _dict_append(in_dict, key, fn): try: in_dict[key] = fn() if callable(fn) else fn - except BaseException: + except Exception: in_dict[key] = "UNKNOWN for given OS" diff --git a/monai/data/__init__.py b/monai/data/__init__.py index 5e367cc2970..971d5121f71 100644 --- a/monai/data/__init__.py +++ b/monai/data/__init__.py @@ -118,7 +118,7 @@ from .wsi_datasets import MaskedPatchWSIDataset, PatchWSIDataset, SlidingPatchWSIDataset from .wsi_reader import BaseWSIReader, CuCIMWSIReader, OpenSlideWSIReader, TiffFileWSIReader, WSIReader -with contextlib.suppress(BaseException): +with contextlib.suppress(Exception): from multiprocessing.reduction import ForkingPickler def _rebuild_meta(cls, storage, dtype, metadata): diff --git a/monai/inferers/inferer.py b/monai/inferers/inferer.py index eea573609c9..ee94b1ebdbe 100644 --- a/monai/inferers/inferer.py +++ b/monai/inferers/inferer.py @@ -545,7 +545,7 @@ def __init__( ) if cache_roi_weight_map and self.roi_weight_map is None: warnings.warn("cache_roi_weight_map=True, but cache is not created. (dynamic roi_size?)") - except BaseException as e: + except Exception as e: raise RuntimeError( f"roi size {self.roi_size}, mode={mode}, sigma_scale={sigma_scale}, device={device}\n" "Seems to be OOM. Please try smaller patch size or mode='constant' instead of mode='gaussian'." diff --git a/monai/utils/tf32.py b/monai/utils/tf32.py index ad5918a34a1..db5e05279e2 100644 --- a/monai/utils/tf32.py +++ b/monai/utils/tf32.py @@ -41,7 +41,7 @@ def has_ampere_or_later() -> bool: major, _ = pynvml.nvmlDeviceGetCudaComputeCapability(handle) if major >= 8: return True - except BaseException: + except Exception: pass finally: pynvml.nvmlShutdown() @@ -71,7 +71,7 @@ def detect_default_tf32() -> bool: may_enable_tf32 = True return may_enable_tf32 - except BaseException: + except Exception: from monai.utils.misc import MONAIEnvVars if MONAIEnvVars.debug(): diff --git a/tests/apps/detection/networks/test_retinanet.py b/tests/apps/detection/networks/test_retinanet.py index 3f4721a755f..eb491361ea6 100644 --- a/tests/apps/detection/networks/test_retinanet.py +++ b/tests/apps/detection/networks/test_retinanet.py @@ -140,7 +140,7 @@ def test_retina_shape(self, model, input_param, input_shape): def test_script(self, model, input_param, input_shape): try: idx = int(self.id().split("test_script_")[-1]) - except BaseException: + except Exception: idx = 0 idx %= 3 # test whether support torchscript @@ -174,7 +174,7 @@ def test_script(self, model, input_param, input_shape): def test_onnx(self, model, input_param, input_shape): try: idx = int(self.id().split("test_onnx_")[-1]) - except BaseException: + except Exception: idx = 0 idx %= 3 # test whether support torchscript diff --git a/tests/networks/nets/test_resnet.py b/tests/networks/nets/test_resnet.py index 371ec896825..241f57c78dc 100644 --- a/tests/networks/nets/test_resnet.py +++ b/tests/networks/nets/test_resnet.py @@ -249,7 +249,7 @@ def tearDown(self): if os.path.exists(self.tmp_ckpt_filename): try: os.remove(self.tmp_ckpt_filename) - except BaseException: + except Exception: pass @parameterized.expand(TEST_CASES) diff --git a/tests/test_utils.py b/tests/test_utils.py index 27af61cefe2..05f7cb88d9e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -241,7 +241,7 @@ def is_tf32_env(): a_full = torch.randn(1024, 1024, dtype=torch.double, device="cuda", generator=g_gpu) b_full = torch.randn(1024, 1024, dtype=torch.double, device="cuda", generator=g_gpu) _tf32_enabled = (a_full.float() @ b_full.float() - a_full @ b_full).abs().max().item() > 0.001 # 0.1713 - except BaseException: + except Exception: pass print(f"tf32 enabled: {_tf32_enabled}") return _tf32_enabled From 37db0adf747f10d304da9d379fe035b5f08e5a5f Mon Sep 17 00:00:00 2001 From: Oleksandr Yizchak Sanin Date: Tue, 19 May 2026 00:48:46 +0200 Subject: [PATCH 2/4] Fix RandGridDistortiond converting entire dict when transform is skipped (#8604) When _do_transform is False, RandGridDistortiond called convert_to_tensor on the entire data dictionary, which converted non-image metadata values (integers, strings) into tensors. This caused AttributeError ('int' object has no attribute 'numel') during DataLoader collation. Only convert the transform's target keys instead of the whole dict, consistent with how the transform handles its keys in the normal (do_transform=True) path. Signed-off-by: Oleksandr Sanin --- monai/transforms/spatial/dictionary.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 51ad0435fc0..6fcb2465f34 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -2305,8 +2305,9 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc d = dict(data) self.randomize(None) if not self._do_transform: - out: dict[Hashable, torch.Tensor] = convert_to_tensor(d, track_meta=get_track_meta()) - return out + for key in self.key_iterator(d): + d[key] = convert_to_tensor(d[key], track_meta=get_track_meta()) + return d first_key: Hashable = self.first_key(d) if first_key == (): From a4cefe7a4465af26b0c5d661012dfa30b004a64e Mon Sep 17 00:00:00 2001 From: Oleksandr Yizchak Sanin Date: Tue, 19 May 2026 09:29:35 +0200 Subject: [PATCH 3/4] DCO Remediation Commit for Oleksandr Yizchak Sanin I, Oleksandr Yizchak Sanin , hereby add my Signed-off-by to this commit: 37db0adf747f10d304da9d379fe035b5f08e5a5f Signed-off-by: Oleksandr Yizchak Sanin From 117d65bd5ac0aef3750305d2cb5327c78ab1ada6 Mon Sep 17 00:00:00 2001 From: Oleksandr Sanin Date: Tue, 19 May 2026 10:24:08 +0200 Subject: [PATCH 4/4] Fix mypy error: add type annotation for convert_to_tensor return value Add explicit dict[Hashable, torch.Tensor] type annotation to the convert_to_tensor call in the first_key == () branch to satisfy mypy's no-any-return check. Signed-off-by: Oleksandr Yizchak Sanin --- monai/transforms/spatial/dictionary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index 6fcb2465f34..543d875c4ef 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -2311,7 +2311,7 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc first_key: Hashable = self.first_key(d) if first_key == (): - out = convert_to_tensor(d, track_meta=get_track_meta()) + out: dict[Hashable, torch.Tensor] = convert_to_tensor(d, track_meta=get_track_meta()) return out if isinstance(d[first_key], MetaTensor) and d[first_key].pending_operations: # type: ignore warnings.warn(f"data['{first_key}'] has pending operations, transform may return incorrect results.")