From 494c2dcf6d742aaecb2cb453645820866898b5e1 Mon Sep 17 00:00:00 2001 From: Kirscher Date: Mon, 10 Aug 2026 21:48:31 +0200 Subject: [PATCH] Report a missing nnunetv2 as an optional dependency in nnUNetV2Runner `nnUNetV2Runner.__init__` imports `nnunetv2.configuration` directly, so constructing the runner without nnunetv2 installed raises a bare ModuleNotFoundError rather than MONAI's OptionalImportError. The dataset lookup just above it is wrapped in a broad `except Exception`, which swallows that same ImportError first and logs Dataset with name/ID: 123 cannot be found in the record. ... please check your input_config. so the reported cause is the user's configuration, not the missing package. Decorate the class with `@require_pkg(pkg_name="nnunetv2")`, as is done for other optional dependencies (ITKReader, NibabelReader, ...). The failure now names the package and links the installation docs, and the misleading dataset warning is no longer emitted. Co-Authored-By: Claude Opus 5 Signed-off-by: Kirscher --- monai/apps/nnunet/nnunetv2_runner.py | 3 +- .../test_nnunetv2_runner_optional_import.py | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/apps/nnunet/test_nnunetv2_runner_optional_import.py diff --git a/monai/apps/nnunet/nnunetv2_runner.py b/monai/apps/nnunet/nnunetv2_runner.py index 5d5c82801a..664660f9bb 100644 --- a/monai/apps/nnunet/nnunetv2_runner.py +++ b/monai/apps/nnunet/nnunetv2_runner.py @@ -23,7 +23,7 @@ from monai.apps.nnunet.utils import NNUNETMode as M from monai.apps.nnunet.utils import analyze_data, create_new_data_copy, create_new_dataset_json from monai.bundle import ConfigParser -from monai.utils import ensure_tuple, optional_import +from monai.utils import ensure_tuple, optional_import, require_pkg from monai.utils.misc import run_cmd load_pickle, _ = optional_import("batchgenerators.utilities.file_and_folder_operations", name="load_pickle") @@ -38,6 +38,7 @@ DATASET_ID_FORMAT = r"Dataset[0-9]{3}|[0-9]+" # regex format for a valid nnUnet dataset name +@require_pkg(pkg_name="nnunetv2") class nnUNetV2Runner: # noqa: N801 """ ``nnUNetV2Runner`` provides an interface in MONAI to use `nnU-Net` V2 library to analyze, train, and evaluate diff --git a/tests/apps/nnunet/test_nnunetv2_runner_optional_import.py b/tests/apps/nnunet/test_nnunetv2_runner_optional_import.py new file mode 100644 index 0000000000..05b7c3d472 --- /dev/null +++ b/tests/apps/nnunet/test_nnunetv2_runner_optional_import.py @@ -0,0 +1,58 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.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 __future__ import annotations + +import os +import tempfile +import unittest +from unittest import mock + +from monai.apps.nnunet.nnunetv2_runner import nnUNetV2Runner +from monai.utils import OptionalImportError + + +class TestnnUNetV2RunnerOptionalImport(unittest.TestCase): + """``nnUNetV2Runner`` requires the optional ``nnunetv2`` package to be installed.""" + + def setUp(self) -> None: + self.test_dir = tempfile.TemporaryDirectory() + test_path = self.test_dir.name + self.input_config = { + "dataset_name_or_id": "123", + "dataroot": os.path.join(test_path, "data"), + "datalist": os.path.join(test_path, "lists", "task4.json"), + "work_dir": os.path.join(test_path, "work"), + "nnunet_raw": os.path.join(test_path, "nnUNet_raw"), + "nnunet_preprocessed": os.path.join(test_path, "nnUNet_preprocessed"), + "nnunet_results": os.path.join(test_path, "nnUNet_results"), + } + + def test_missing_nnunetv2_raises_optional_import_error(self) -> None: + """A missing ``nnunetv2`` must be reported as such, not as a bare ``ModuleNotFoundError``.""" + with mock.patch("monai.utils.module.optional_import", return_value=(None, False)): + with self.assertRaises(OptionalImportError) as context: + nnUNetV2Runner(input_config=dict(self.input_config)) + self.assertIn("nnunetv2", str(context.exception)) + + def test_missing_nnunetv2_does_not_warn_about_the_dataset(self) -> None: + """The dataset lookup warning must not fire when the real cause is the missing package.""" + with mock.patch("monai.utils.module.optional_import", return_value=(None, False)): + with self.assertNoLogs("monai.apps.nnunet.nnunetv2_runner", level="WARNING"): + with self.assertRaises(OptionalImportError): + nnUNetV2Runner(input_config=dict(self.input_config)) + + def tearDown(self) -> None: + self.test_dir.cleanup() + + +if __name__ == "__main__": + unittest.main()