From 0f50d2120b6a3dbd656c5c6cb7c4433a54c1898c Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:02:28 +0100 Subject: [PATCH 01/19] Complete replace `flake8` with `ruff` and update rules Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- .github/workflows/pythonapp.yml | 2 +- .github/workflows/weekly-preview.yml | 2 +- pyproject.toml | 24 ++++++++++++++++++-- requirements-dev.txt | 3 --- runtests.sh | 34 +--------------------------- setup.cfg | 30 ------------------------ 6 files changed, 25 insertions(+), 70 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 5fdec1e11e..c60925699f 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -22,7 +22,7 @@ jobs: # - docker-py3-pip- (shared) # - ubuntu py37 pip- # - os-latest-pip- (shared) - flake8-py3: + lint-py3: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index 9f63c5bc90..caab3eb8ce 100644 --- a/.github/workflows/weekly-preview.yml +++ b/.github/workflows/weekly-preview.yml @@ -5,7 +5,7 @@ on: - cron: "0 2 * * 0" # 02:00 of every Sunday jobs: - flake8-py3: + lint-py3: runs-on: ubuntu-latest strategy: matrix: diff --git a/pyproject.toml b/pyproject.toml index f9fab1141a..bc8d2abba7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,12 +39,19 @@ all = true exclude = "monai/bundle/__main__.py" [tool.ruff] -line-length = 133 +line-length = 120 target-version = "py39" [tool.ruff.lint] select = [ - "E", "F", "W", # flake8 + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "C90", # mccabe (complexity) - https://docs.astral.sh/ruff/rules/#mccabe-c90 + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes - https://docs.astral.sh/ruff/rules/#pyflakes-f + "N", # pep8-naming - https://docs.astral.sh/ruff/rules/#pep8-naming-n + "PIE", # flake8-pie - https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "TID", # flake8-tidy-imports - https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w "NPY", # NumPy specific rules "UP", # pyupgrade # "RUF100", # aka yesqa @@ -53,6 +60,19 @@ extend-ignore = [ "E741", # ambiguous variable name "F401", # unused import "NPY002", # numpy-legacy-random + "E203", # whitespace before ':' (pycodestyle) + "E501", # line too long (pycodestyle) + "W503", # line break before binary operator (pycodestyle) + "W504", # line break after binary operator (pycodestyle) + "C408", # unnecessary collection call (flake8-comprehensions) + "N812", # lowercase imported as non lowercase (pep8-naming) + "B023", # function uses loop variable (flake8-bugbear) + "B905", # zip() without an explicit strict= parameter (flake8-bugbear) + "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) + "B907", # missing copyright notice at top of file (flake8-bugbear) + "B908", # missing author, email or maintainer (flake8-bugbear) + "B036", # useless expression (flake8-bugbear) + "E704", # multiple statements on one line (def) (pycodestyle) ] [tool.pytype] diff --git a/requirements-dev.txt b/requirements-dev.txt index 1dc2141cf6..5faf8a7532 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,9 +10,6 @@ tensorboard>=2.12.0 # https://github.com/Project-MONAI/MONAI/issues/7434 scikit-image>=0.19.0 tqdm>=4.47.0 lmdb -flake8>=3.8.1 -flake8-bugbear<=24.2.6 # https://github.com/Project-MONAI/MONAI/issues/7690 -flake8-comprehensions mccabe pep8-naming pycodestyle diff --git a/runtests.sh b/runtests.sh index 18cb0ab73a..eb7a2d6ce0 100755 --- a/runtests.sh +++ b/runtests.sh @@ -43,7 +43,6 @@ doBlackFormat=false doBlackFix=false doIsortFormat=false doIsortFix=false -doFlake8Format=false doPylintFormat=false doRuffFormat=false doRuffFix=false @@ -60,7 +59,7 @@ NUM_PARALLEL=1 PY_EXE=${MONAI_PY_EXE:-$(which python)} function print_usage { - echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--flake8] [--pylint] [--ruff]" + echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--pylint] [--ruff]" echo " [--clangformat] [--precommit] [--pytype] [-j number] [--mypy]" echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]" echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]" @@ -80,7 +79,6 @@ function print_usage { echo " --autofix : format code using \"isort\" and \"black\"" echo " --black : perform \"black\" code format checks" echo " --isort : perform \"isort\" import sort checks" - echo " --flake8 : perform \"flake8\" code format checks" echo " --pylint : perform \"pylint\" code format checks" echo " --ruff : perform \"ruff\" code format checks" echo " --clangformat : format csrc code using \"clang-format\"" @@ -267,7 +265,6 @@ do -f|--codeformat) doBlackFormat=true doIsortFormat=true - doFlake8Format=true # doPylintFormat=true # https://github.com/Project-MONAI/MONAI/issues/7094 doRuffFormat=true doCopyRight=true @@ -299,9 +296,6 @@ do --isort) doIsortFormat=true ;; - --flake8) - doFlake8Format=true - ;; --pylint) doPylintFormat=true ;; @@ -533,32 +527,6 @@ then set -e # enable exit on failure fi - -if [ $doFlake8Format = true ] -then - set +e # disable exit on failure so that diagnostics can be given on failure - echo "${separator}${blue}flake8${noColor}" - - # ensure that the necessary packages for code format testing are installed - if ! is_pip_installed flake8 - then - install_deps - fi - ${cmdPrefix}"${PY_EXE}" -m flake8 --version - - ${cmdPrefix}"${PY_EXE}" -m flake8 "$homedir" --count --statistics - - flake8_status=$? - if [ ${flake8_status} -ne 0 ] - then - print_style_fail_msg - exit ${flake8_status} - else - echo "${green}passed!${noColor}" - fi - set -e # enable exit on failure -fi - if [ $doPylintFormat = true ] then set +e # disable exit on failure so that diagnostics can be given on failure diff --git a/setup.cfg b/setup.cfg index ab03b906c1..59fab0b54d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -179,36 +179,6 @@ pyamg = # segment-anything = # segment-anything @ git+https://github.com/facebookresearch/segment-anything@6fdee8f2727f4506cfbbe553e23b895e27956588#egg=segment-anything -[flake8] -select = B,C,E,F,N,P,T4,W,B9 -max_line_length = 120 -# C408 ignored because we like the dict keyword argument syntax -# E501 is not flexible enough, we're using B950 instead -# N812 lowercase 'torch.nn.functional' imported as non lowercase 'F' -# B023 https://github.com/Project-MONAI/MONAI/issues/4627 -# B028 https://github.com/Project-MONAI/MONAI/issues/5855 -# B907 https://github.com/Project-MONAI/MONAI/issues/5868 -# B908 https://github.com/Project-MONAI/MONAI/issues/6503 -# B036 https://github.com/Project-MONAI/MONAI/issues/7396 -# E704 https://github.com/Project-MONAI/MONAI/issues/7421 -ignore = - E203 - E501 - E741 - W503 - W504 - C408 - N812 - B023 - B905 - B028 - B907 - B908 - B036 - E704 -per_file_ignores = __init__.py: F401, __main__.py: F401 -exclude = *.pyi,.git,.eggs,monai/_version.py,versioneer.py,venv,.venv,_version.py - [isort] known_first_party = monai profile = black From 5a27d2155f78a6240dffb39dc93102f71b8565a9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:05:36 +0000 Subject: [PATCH 02/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/utils/profiling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/utils/profiling.py b/monai/utils/profiling.py index 5c880bbe1f..5eda00459e 100644 --- a/monai/utils/profiling.py +++ b/monai/utils/profiling.py @@ -337,7 +337,7 @@ def profile_iter(self, name, iterable): class _Iterable: - def __iter__(_self): # noqa: B902, N805 pylint: disable=E0213 + def __iter__(_self): # noqa: N805 pylint: disable=E0213 do_iter = True orig_iter = iter(iterable) caller = getframeinfo(stack()[1][0]) From 4925989c2c0964e78c77f495449a76a03271020e Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:08:50 +0100 Subject: [PATCH 03/19] update rules Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bc8d2abba7..ddd4581375 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,17 +62,17 @@ extend-ignore = [ "NPY002", # numpy-legacy-random "E203", # whitespace before ':' (pycodestyle) "E501", # line too long (pycodestyle) - "W503", # line break before binary operator (pycodestyle) - "W504", # line break after binary operator (pycodestyle) + #"W503", # line break before binary operator (pycodestyle) + #"W504", # line break after binary operator (pycodestyle) "C408", # unnecessary collection call (flake8-comprehensions) "N812", # lowercase imported as non lowercase (pep8-naming) "B023", # function uses loop variable (flake8-bugbear) "B905", # zip() without an explicit strict= parameter (flake8-bugbear) "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) - "B907", # missing copyright notice at top of file (flake8-bugbear) - "B908", # missing author, email or maintainer (flake8-bugbear) - "B036", # useless expression (flake8-bugbear) - "E704", # multiple statements on one line (def) (pycodestyle) + #"B907", # missing copyright notice at top of file (flake8-bugbear) + #"B908", # missing author, email or maintainer (flake8-bugbear) + #"B036", # useless expression (flake8-bugbear) + #"E704", # multiple statements on one line (def) (pycodestyle) ] [tool.pytype] From 3d66ac7ea5ec25f2cecc84bc8fd8a9aab552accc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 09:09:42 +0000 Subject: [PATCH 04/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/detection/transforms/box_ops.py | 2 +- monai/auto3dseg/algo_gen.py | 13 ------------- monai/data/box_utils.py | 8 ++++---- monai/fl/client/client_algo.py | 3 --- monai/networks/blocks/feature_pyramid_network.py | 1 - monai/networks/nets/dints.py | 1 - monai/networks/trt_compiler.py | 1 - monai/transforms/traits.py | 4 ---- tests/handlers/test_handler_regression_metrics.py | 2 +- .../test_integration_classification_2d.py | 1 - .../utils/type_conversion/test_convert_data_type.py | 1 - 11 files changed, 6 insertions(+), 31 deletions(-) diff --git a/monai/apps/detection/transforms/box_ops.py b/monai/apps/detection/transforms/box_ops.py index 404854c4c0..6e08a88e59 100644 --- a/monai/apps/detection/transforms/box_ops.py +++ b/monai/apps/detection/transforms/box_ops.py @@ -179,7 +179,7 @@ def flip_boxes( spatial_dims: int = get_spatial_dims(boxes=boxes) spatial_size = ensure_tuple_rep(spatial_size, spatial_dims) if flip_axes is None: - flip_axes = tuple(range(0, spatial_dims)) + flip_axes = tuple(range(spatial_dims)) flip_axes = ensure_tuple(flip_axes) # flip box diff --git a/monai/auto3dseg/algo_gen.py b/monai/auto3dseg/algo_gen.py index 38ac542cff..5ad15c7d7a 100644 --- a/monai/auto3dseg/algo_gen.py +++ b/monai/auto3dseg/algo_gen.py @@ -25,23 +25,18 @@ class Algo: def set_data_stats(self, *args, **kwargs): """Provide dataset (and summaries) so that the model creation can depend on the input datasets.""" - pass def train(self, *args, **kwargs): """Read training/validation data and output a model.""" - pass def predict(self, *args, **kwargs): """Read test data and output model predictions.""" - pass def get_score(self, *args, **kwargs): """Returns the model quality measurement based on training and validation datasets.""" - pass def get_output_path(self, *args, **kwargs): """Returns the algo output paths for scripts location""" - pass class AlgoGen(Randomizable): @@ -70,31 +65,24 @@ class AlgoGen(Randomizable): def set_data_stats(self, *args, **kwargs): # type ignore """Provide dataset summaries/properties so that the generator can be conditioned on the input datasets.""" - pass def set_budget(self, *args, **kwargs): """Provide computational budget so that the generator outputs algorithms that requires reasonable resources.""" - pass def set_score(self, *args, **kwargs): """Feedback from the previously generated algo, the score can be used for new Algo generations.""" - pass def get_data_stats(self, *args, **kwargs): """Get current dataset summaries.""" - pass def get_budget(self, *args, **kwargs): """Get the current computational budget.""" - pass def get_history(self, *args, **kwargs): """Get the previously generated algo.""" - pass def generate(self): """Generate new Algo -- based on data_stats, budget, and history of previous algo generations.""" - pass def run_algo(self, *args, **kwargs): """ @@ -104,4 +92,3 @@ def run_algo(self, *args, **kwargs): implemented separately is preferred to run them. In this case the controller should also report back the scores and the algo history, so that the future ``AlgoGen.generate`` can leverage the information. """ - pass diff --git a/monai/data/box_utils.py b/monai/data/box_utils.py index b09b86b605..68a0435285 100644 --- a/monai/data/box_utils.py +++ b/monai/data/box_utils.py @@ -591,7 +591,7 @@ def convert_box_mode( # check validity of corners spatial_dims = get_spatial_dims(boxes=boxes_t) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): if (corners[spatial_dims + axis] < corners[axis]).sum() > 0: warnings.warn("Given boxes has invalid values. The box size must be non-negative.") @@ -731,7 +731,7 @@ def is_valid_box_values(boxes: NdarrayOrTensor) -> bool: whether ``boxes`` is valid """ spatial_dims = get_spatial_dims(boxes=boxes) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): if (boxes[:, spatial_dims + axis] < boxes[:, axis]).sum() > 0: return False return True @@ -1041,7 +1041,7 @@ def spatial_crop_boxes( # makes sure the bounding boxes are within the patch spatial_dims = get_spatial_dims(boxes=boxes, spatial_size=roi_end) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): boxes_t[:, axis] = boxes_t[:, axis].clamp(min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE) boxes_t[:, axis + spatial_dims] = boxes_t[:, axis + spatial_dims].clamp( min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE @@ -1133,7 +1133,7 @@ def non_max_suppression( # initialize the list of picked indexes pick = [] - idxs = torch.Tensor(list(range(0, boxes_sort.shape[0]))).to(device=boxes_t.device, dtype=torch.long) + idxs = torch.Tensor(list(range(boxes_sort.shape[0]))).to(device=boxes_t.device, dtype=torch.long) # keep looping while some indexes still remain in the indexes list while len(idxs) > 0: diff --git a/monai/fl/client/client_algo.py b/monai/fl/client/client_algo.py index 3dc9f5785d..816312345d 100644 --- a/monai/fl/client/client_algo.py +++ b/monai/fl/client/client_algo.py @@ -34,7 +34,6 @@ def initialize(self, extra: dict | None = None) -> None: Args: extra: optional extra information, e.g. dict of `ExtraItems.CLIENT_NAME` and/or `ExtraItems.APP_ROOT`. """ - pass def finalize(self, extra: dict | None = None) -> None: """ @@ -43,7 +42,6 @@ def finalize(self, extra: dict | None = None) -> None: Args: extra: Dict with additional information that can be provided by the FL system. """ - pass def abort(self, extra: dict | None = None) -> None: """ @@ -53,7 +51,6 @@ def abort(self, extra: dict | None = None) -> None: extra: Dict with additional information that can be provided by the FL system. """ - pass class ClientAlgoStats(BaseClient): diff --git a/monai/networks/blocks/feature_pyramid_network.py b/monai/networks/blocks/feature_pyramid_network.py index 759a4efe0d..96083e7c0d 100644 --- a/monai/networks/blocks/feature_pyramid_network.py +++ b/monai/networks/blocks/feature_pyramid_network.py @@ -85,7 +85,6 @@ def forward(self, results: list[Tensor], x: list[Tensor], names: list[str]): - the extended set of results of the FPN - the extended set of names for the results """ - pass class LastLevelMaxPool(ExtraFPNBlock): diff --git a/monai/networks/nets/dints.py b/monai/networks/nets/dints.py index 18e24373a0..f87fcebea8 100644 --- a/monai/networks/nets/dints.py +++ b/monai/networks/nets/dints.py @@ -627,7 +627,6 @@ def __init__( def forward(self, x): """This function to be implemented by the architecture instances or search spaces.""" - pass class TopologyInstance(TopologyConstruction): diff --git a/monai/networks/trt_compiler.py b/monai/networks/trt_compiler.py index 2df7189ad4..1fee5bfbf2 100644 --- a/monai/networks/trt_compiler.py +++ b/monai/networks/trt_compiler.py @@ -98,7 +98,6 @@ class ShapeError(Exception): Exception class to report errors from setting TRT plan input shapes """ - pass class TRTEngine: diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 45d081f2e6..9c4dce28fc 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -74,7 +74,6 @@ class RandomizableTrait: implementors of MONAI transforms. """ - pass class MultiSampleTrait: @@ -85,7 +84,6 @@ class MultiSampleTrait: of MONAI transforms. """ - pass class ThreadUnsafe: @@ -98,7 +96,6 @@ class ThreadUnsafe: its extensions, where the transform cache is built with multiple threads. """ - pass class ReduceTrait: @@ -109,4 +106,3 @@ class ReduceTrait: as by implementors of MONAI transforms. """ - pass diff --git a/tests/handlers/test_handler_regression_metrics.py b/tests/handlers/test_handler_regression_metrics.py index a3ec9f071a..fdbc82090d 100644 --- a/tests/handlers/test_handler_regression_metrics.py +++ b/tests/handlers/test_handler_regression_metrics.py @@ -70,7 +70,7 @@ def test_compute(self): for batch in batch_dims: for spatial in spatial_dims: for base in base_dims: - mt_fn_obj = mt_fn(**{"save_details": False}) + mt_fn_obj = mt_fn(save_details=False) # create random tensor in_tensor_a1 = torch.rand((batch,) + (base,) * (spatial - 1)).to(device) diff --git a/tests/integration/test_integration_classification_2d.py b/tests/integration/test_integration_classification_2d.py index 88351292b0..c20e4593f3 100644 --- a/tests/integration/test_integration_classification_2d.py +++ b/tests/integration/test_integration_classification_2d.py @@ -236,7 +236,6 @@ def tearDown(self): os.remove(os.path.join(self.data_dir, "best_metric_model.pth")) except FileNotFoundError: warnings.warn("not found best_metric_model.pth, training skipped?") - pass def train_and_infer(self, idx=0): results = [] diff --git a/tests/utils/type_conversion/test_convert_data_type.py b/tests/utils/type_conversion/test_convert_data_type.py index 4c1905b188..08995034d0 100644 --- a/tests/utils/type_conversion/test_convert_data_type.py +++ b/tests/utils/type_conversion/test_convert_data_type.py @@ -74,7 +74,6 @@ class TestTensor(torch.Tensor): __test__ = False # indicate to pytest that this class is not intended for collection - pass class TestConvertDataType(unittest.TestCase): From e68a2792f0b7da81c08f2c6b94ffbf01ed93f4d3 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:13:02 +0100 Subject: [PATCH 05/19] prune Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- pyproject.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ddd4581375..5570650617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,17 +62,11 @@ extend-ignore = [ "NPY002", # numpy-legacy-random "E203", # whitespace before ':' (pycodestyle) "E501", # line too long (pycodestyle) - #"W503", # line break before binary operator (pycodestyle) - #"W504", # line break after binary operator (pycodestyle) "C408", # unnecessary collection call (flake8-comprehensions) "N812", # lowercase imported as non lowercase (pep8-naming) "B023", # function uses loop variable (flake8-bugbear) "B905", # zip() without an explicit strict= parameter (flake8-bugbear) "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) - #"B907", # missing copyright notice at top of file (flake8-bugbear) - #"B908", # missing author, email or maintainer (flake8-bugbear) - #"B036", # useless expression (flake8-bugbear) - #"E704", # multiple statements on one line (def) (pycodestyle) ] [tool.pytype] From 4651c1311860976ad5ecea33f57e587ac82af2a6 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:18:42 +0100 Subject: [PATCH 06/19] max-complexity = 15 Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- pyproject.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5570650617..c1aadfcdba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,15 @@ extend-ignore = [ "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) ] +[tool.ruff.lint.per-file-ignores] +"tests/**" = [ + "B018", + #"C901", +] + +[tool.ruff.lint.mccabe] +max-complexity = 15 + [tool.pytype] # Space-separated list of files or directories to exclude. exclude = ["versioneer.py", "_version.py"] From e1c5fffda33c1814becc246b27cda43a1a6cb268 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:19:11 +0100 Subject: [PATCH 07/19] max-complexity = 20 Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c1aadfcdba..be862f9cbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ extend-ignore = [ ] [tool.ruff.lint.mccabe] -max-complexity = 15 +max-complexity = 20 [tool.pytype] # Space-separated list of files or directories to exclude. From 1588ee1bd59788401fe6a9553e922c6a6b488044 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:20:04 +0100 Subject: [PATCH 08/19] inore Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index be862f9cbd..306ef3f91b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ extend-ignore = [ [tool.ruff.lint.per-file-ignores] "tests/**" = [ "B018", - #"C901", + "C901", ] [tool.ruff.lint.mccabe] From 29675d420d50d3e9946124ef639ea98319adbdce Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:20:44 +0100 Subject: [PATCH 09/19] --unsafe-fixes Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/auto3dseg/bundle_gen.py | 2 +- monai/metrics/loss_metric.py | 2 +- monai/transforms/regularization/array.py | 2 +- monai/transforms/regularization/dictionary.py | 2 +- monai/transforms/traits.py | 1 - monai/utils/deprecate_utils.py | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/monai/apps/auto3dseg/bundle_gen.py b/monai/apps/auto3dseg/bundle_gen.py index 87a612cae2..d053ea37ef 100644 --- a/monai/apps/auto3dseg/bundle_gen.py +++ b/monai/apps/auto3dseg/bundle_gen.py @@ -208,7 +208,7 @@ def _create_cmd(self, train_params: None | dict = None) -> tuple[str, str]: config_files = [] if os.path.isdir(config_dir): for file in sorted(os.listdir(config_dir)): - if file.endswith("yaml") or file.endswith("json"): + if file.endswith(("yaml", "json")): # Python Fire may be confused by single-quoted WindowsPath config_files.append(Path(os.path.join(config_dir, file)).as_posix()) diff --git a/monai/metrics/loss_metric.py b/monai/metrics/loss_metric.py index 3136f42f4a..2ac01489c2 100644 --- a/monai/metrics/loss_metric.py +++ b/monai/metrics/loss_metric.py @@ -19,7 +19,7 @@ from monai.metrics.utils import do_metric_reduction from monai.utils import MetricReduction -from ..config import TensorOrList +from monai.config import TensorOrList from .metric import CumulativeIterationMetric diff --git a/monai/transforms/regularization/array.py b/monai/transforms/regularization/array.py index 445a9340f2..9e80c2a947 100644 --- a/monai/transforms/regularization/array.py +++ b/monai/transforms/regularization/array.py @@ -19,7 +19,7 @@ from monai.data.meta_obj import get_track_meta from monai.utils.type_conversion import convert_to_dst_type, convert_to_tensor -from ..transform import RandomizableTransform +from monai.transforms.transform import RandomizableTransform __all__ = ["MixUp", "CutMix", "CutOut", "Mixer"] diff --git a/monai/transforms/regularization/dictionary.py b/monai/transforms/regularization/dictionary.py index d8815e47b9..fb84ceef06 100644 --- a/monai/transforms/regularization/dictionary.py +++ b/monai/transforms/regularization/dictionary.py @@ -21,7 +21,7 @@ from monai.utils import convert_to_tensor from monai.utils.misc import ensure_tuple -from ..transform import MapTransform, RandomizableTransform +from monai.transforms.transform import MapTransform, RandomizableTransform from .array import CutMix, CutOut, MixUp __all__ = ["MixUpd", "MixUpD", "MixUpDict", "CutMixd", "CutMixD", "CutMixDict", "CutOutd", "CutOutD", "CutOutDict"] diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 9c4dce28fc..a7e1ba3681 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -105,4 +105,3 @@ class ReduceTrait: This interface can be extended from by people adapting transforms to the MONAI framework as well as by implementors of MONAI transforms. """ - diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py index d4f239cd23..b6888505e5 100644 --- a/monai/utils/deprecate_utils.py +++ b/monai/utils/deprecate_utils.py @@ -21,7 +21,7 @@ from monai.utils.module import version_leq -from .. import __version__ +from monai import __version__ __all__ = ["deprecated", "deprecated_arg", "DeprecatedError", "deprecated_arg_default"] T = TypeVar("T", type, Callable) From c8df5077b99d781a425ad96588fb98471bce20b9 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 10:28:50 +0100 Subject: [PATCH 10/19] fix * update Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/metrics/confusion_matrix.py | 4 +++- monai/utils/deprecate_utils.py | 2 +- pyproject.toml | 7 ++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/monai/metrics/confusion_matrix.py b/monai/metrics/confusion_matrix.py index 26ec823081..6c76814247 100644 --- a/monai/metrics/confusion_matrix.py +++ b/monai/metrics/confusion_matrix.py @@ -176,7 +176,9 @@ def get_confusion_matrix(y_pred: torch.Tensor, y: torch.Tensor, include_backgrou return torch.stack([tp, fp, tn, fn], dim=-1) -def compute_confusion_matrix_metric(metric_name: str, confusion_matrix: torch.Tensor) -> torch.Tensor: +def compute_confusion_matrix_metric( + metric_name: str, confusion_matrix: torch.Tensor +) -> torch.Tensor: """ This function is used to compute confusion matrix related metric. diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py index b6888505e5..a6ac87a301 100644 --- a/monai/utils/deprecate_utils.py +++ b/monai/utils/deprecate_utils.py @@ -201,7 +201,7 @@ def _wrapper(*args, **kwargs): # if name is specified and new_name is not specified kwargs[new_name] = kwargs[name] try: - sig.bind(*args, **kwargs).arguments + _ = sig.bind(*args, **kwargs).arguments except TypeError: # multiple values for new_name using both args and kwargs kwargs.pop(new_name, None) diff --git a/pyproject.toml b/pyproject.toml index 306ef3f91b..4af0ab4a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,10 +73,15 @@ extend-ignore = [ "tests/**" = [ "B018", "C901", + "N999", + "N801" +] +"monai/apps/detection/utils/ATSS_matcher.py" = [ + "N999" ] [tool.ruff.lint.mccabe] -max-complexity = 20 +max-complexity = 50 # todo lower this treshold when yesqa id replaced with Ruff's RUF100 [tool.pytype] # Space-separated list of files or directories to exclude. From 9a33e1f9e9a086ef5ce8c46887cf70ab306d4898 Mon Sep 17 00:00:00 2001 From: jirka Date: Tue, 13 Jan 2026 11:58:34 +0100 Subject: [PATCH 11/19] ./runtests.sh --autofix Signed-off-by: jirka Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/fl/client/client_algo.py | 1 - monai/metrics/confusion_matrix.py | 4 +--- monai/metrics/loss_metric.py | 2 +- monai/networks/trt_compiler.py | 1 - monai/transforms/regularization/array.py | 3 +-- monai/transforms/regularization/dictionary.py | 2 +- monai/transforms/traits.py | 3 --- monai/utils/deprecate_utils.py | 3 +-- 8 files changed, 5 insertions(+), 14 deletions(-) diff --git a/monai/fl/client/client_algo.py b/monai/fl/client/client_algo.py index 816312345d..4b60668b07 100644 --- a/monai/fl/client/client_algo.py +++ b/monai/fl/client/client_algo.py @@ -52,7 +52,6 @@ def abort(self, extra: dict | None = None) -> None: """ - class ClientAlgoStats(BaseClient): def get_data_stats(self, extra: dict | None = None) -> ExchangeObject: diff --git a/monai/metrics/confusion_matrix.py b/monai/metrics/confusion_matrix.py index 6c76814247..26ec823081 100644 --- a/monai/metrics/confusion_matrix.py +++ b/monai/metrics/confusion_matrix.py @@ -176,9 +176,7 @@ def get_confusion_matrix(y_pred: torch.Tensor, y: torch.Tensor, include_backgrou return torch.stack([tp, fp, tn, fn], dim=-1) -def compute_confusion_matrix_metric( - metric_name: str, confusion_matrix: torch.Tensor -) -> torch.Tensor: +def compute_confusion_matrix_metric(metric_name: str, confusion_matrix: torch.Tensor) -> torch.Tensor: """ This function is used to compute confusion matrix related metric. diff --git a/monai/metrics/loss_metric.py b/monai/metrics/loss_metric.py index 2ac01489c2..3613576a7d 100644 --- a/monai/metrics/loss_metric.py +++ b/monai/metrics/loss_metric.py @@ -16,10 +16,10 @@ import torch from torch.nn.modules.loss import _Loss +from monai.config import TensorOrList from monai.metrics.utils import do_metric_reduction from monai.utils import MetricReduction -from monai.config import TensorOrList from .metric import CumulativeIterationMetric diff --git a/monai/networks/trt_compiler.py b/monai/networks/trt_compiler.py index 1fee5bfbf2..d54b61d89b 100644 --- a/monai/networks/trt_compiler.py +++ b/monai/networks/trt_compiler.py @@ -99,7 +99,6 @@ class ShapeError(Exception): """ - class TRTEngine: """ An auxiliary class to implement running of TRT optimized engines diff --git a/monai/transforms/regularization/array.py b/monai/transforms/regularization/array.py index 9e80c2a947..6b979e564a 100644 --- a/monai/transforms/regularization/array.py +++ b/monai/transforms/regularization/array.py @@ -17,9 +17,8 @@ import torch from monai.data.meta_obj import get_track_meta -from monai.utils.type_conversion import convert_to_dst_type, convert_to_tensor - from monai.transforms.transform import RandomizableTransform +from monai.utils.type_conversion import convert_to_dst_type, convert_to_tensor __all__ = ["MixUp", "CutMix", "CutOut", "Mixer"] diff --git a/monai/transforms/regularization/dictionary.py b/monai/transforms/regularization/dictionary.py index fb84ceef06..e3e2c4a150 100644 --- a/monai/transforms/regularization/dictionary.py +++ b/monai/transforms/regularization/dictionary.py @@ -18,10 +18,10 @@ from monai.config import KeysCollection from monai.config.type_definitions import NdarrayOrTensor from monai.data.meta_obj import get_track_meta +from monai.transforms.transform import MapTransform, RandomizableTransform from monai.utils import convert_to_tensor from monai.utils.misc import ensure_tuple -from monai.transforms.transform import MapTransform, RandomizableTransform from .array import CutMix, CutOut, MixUp __all__ = ["MixUpd", "MixUpD", "MixUpDict", "CutMixd", "CutMixD", "CutMixDict", "CutOutd", "CutOutD", "CutOutDict"] diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index a7e1ba3681..33ff52015b 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -75,7 +75,6 @@ class RandomizableTrait: """ - class MultiSampleTrait: """ An interface to indicate that the transform has the capability to return multiple samples @@ -85,7 +84,6 @@ class MultiSampleTrait: """ - class ThreadUnsafe: """ A class to denote that the transform will mutate its member variables, @@ -97,7 +95,6 @@ class ThreadUnsafe: """ - class ReduceTrait: """ An interface to indicate that the transform has the capability to reduce multiple samples diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py index a6ac87a301..1249c51919 100644 --- a/monai/utils/deprecate_utils.py +++ b/monai/utils/deprecate_utils.py @@ -19,9 +19,8 @@ from types import FunctionType from typing import Any, TypeVar -from monai.utils.module import version_leq - from monai import __version__ +from monai.utils.module import version_leq __all__ = ["deprecated", "deprecated_arg", "DeprecatedError", "deprecated_arg_default"] T = TypeVar("T", type, Callable) From d7d64610a91bd7aa698e38d73297b3c62c75cb69 Mon Sep 17 00:00:00 2001 From: jirka <6035284+Borda@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:36:02 +0100 Subject: [PATCH 12/19] Deprecate `--flake8` in favor of `--ruff` and update documentation Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- CONTRIBUTING.md | 4 ++-- runtests.sh | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df7f5e336c..c742284bc0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ Please note that, as per PyTorch, MONAI uses American English spelling. This mea ### Preparing pull requests -To ensure the code quality, MONAI relies on several linting tools ([flake8 and its plugins](https://gitlab.com/pycqa/flake8), [black](https://github.com/psf/black), [isort](https://github.com/timothycrosley/isort), [ruff](https://github.com/astral-sh/ruff)), +To ensure the code quality, MONAI relies on several linting tools ([black](https://github.com/psf/black), [isort](https://github.com/timothycrosley/isort), [ruff](https://github.com/astral-sh/ruff)), static type analysis tools ([mypy](https://github.com/python/mypy), [pytype](https://github.com/google/pytype)), as well as a set of unit/integration tests. This section highlights all the necessary preparation steps required before sending a pull request. @@ -51,7 +51,7 @@ To collaborate efficiently, please read through this section and follow them. #### Checking the coding style -Coding style is checked and enforced by flake8, black, isort, and ruff, using [a flake8 configuration](./setup.cfg) similar to [PyTorch's](https://github.com/pytorch/pytorch/blob/master/.flake8). +Coding style is checked and enforced by black, isort, and ruff. Before submitting a pull request, we recommend that all linting should pass, by running the following command locally: ```bash diff --git a/runtests.sh b/runtests.sh index eb7a2d6ce0..f15737f9fd 100755 --- a/runtests.sh +++ b/runtests.sh @@ -81,6 +81,7 @@ function print_usage { echo " --isort : perform \"isort\" import sort checks" echo " --pylint : perform \"pylint\" code format checks" echo " --ruff : perform \"ruff\" code format checks" + echo " --flake8 : perform \"ruff\" code format checks (deprecated alias for --ruff)" echo " --clangformat : format csrc code using \"clang-format\"" echo " --precommit : perform source code format check and fix using \"pre-commit\"" echo "" @@ -302,6 +303,10 @@ do --ruff) doRuffFormat=true ;; + --flake8) + echo "${red}warning: --flake8 is deprecated, please use --ruff instead.${noColor}" + doRuffFormat=true + ;; --precommit) doPrecommit=true ;; From 9dc7607ef021b8ce859ed97a865df75b9f24b446 Mon Sep 17 00:00:00 2001 From: Soumya Snigdha Kundu Date: Tue, 27 Jan 2026 16:21:09 +0530 Subject: [PATCH 13/19] Rename NormalizeLabelsInDatasetd to RemapLabelsToSequentiald and fix label ordering bug (#8680) ### Description Rename `NormalizeLabelsInDatasetd` to `RemapLabelsToSequentiald` to better describe its actual functionality. The old name was confusing as it suggests normalization when it actually remaps arbitrary label values to sequential indices (0, 1, 2, 3, ...). Fixes #7800 ### Bug Fix Fixed a bug where the order of labels in the input dictionary affected the output. Previously, if background appeared first (e.g., `{background: 0, organ1: 1, organ2: 2}`), the transform would skip index 1 and produce `{background: 0, organ1: 2, organ2: 3}`. This was caused by enumerate starting at 1 for all items but skipping background without adjusting the index. The fix excludes background from enumeration and handles it separately. ### Changes - Renamed `NormalizeLabelsInDatasetd` to `RemapLabelsToSequentiald` - Fixed label ordering bug by excluding background from enumeration - Kept `NormalizeLabelsInDatasetd` as deprecated alias for backward compatibility - Enhanced documentation to clearly explain remapping behavior - Added alphabetical sorting for deterministic output ordering - Added tests for deprecated name warning and proper remapping ### Types of changes - [x] Non-breaking change (fix or new feature that would not break existing functionality). - [x] New tests added to cover the changes. - [x] Quick tests passed locally by running `./runtests.sh --quick --unittests --disttests`. - [x] In-line docstrings updated. --------- Signed-off-by: Soumya Snigdha Kundu Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/deepedit/transforms.py | 77 +++++++++++---- .../apps/deepedit/test_deepedit_transforms.py | 95 +++++++++++++++++++ 2 files changed, 156 insertions(+), 16 deletions(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 5af082e2b0..14c37be860 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -24,7 +24,7 @@ from monai.data import MetaTensor from monai.networks.layers import GaussianFilter from monai.transforms.transform import MapTransform, Randomizable, Transform -from monai.utils import min_version, optional_import +from monai.utils import deprecated, min_version, optional_import measure, _ = optional_import("skimage.measure", "0.14.2", min_version) @@ -84,18 +84,44 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.nda return d -class NormalizeLabelsInDatasetd(MapTransform): +class RemapLabelsToSequentiald(MapTransform): + """ + Remap label values from a dataset-specific schema to sequential indices (0, 1, 2, 3, ...). + + This transform takes labels with arbitrary values defined in a label dictionary and remaps them + to a sequential range starting from 1 (with background always set to 0). This is useful for + standardizing labels across different datasets or ensuring labels are in a contiguous range. + + The output label indices are assigned in alphabetical order by label name to ensure + deterministic behavior regardless of input dictionary ordering. + + Args: + keys: The ``keys`` parameter will be used to get and set the actual data item to transform + label_names: Dictionary mapping label names to their current values in the dataset. + For example: {"spleen": 1, "liver": 6, "background": 0} + Will be remapped to: {"background": 0, "liver": 1, "spleen": 2} + (alphabetically sorted, excluding background) + allow_missing_keys: If True, missing keys in the data dictionary will not raise an error + + Example: + >>> transform = RemapLabelsToSequentiald( + ... keys="label", + ... label_names={"liver": 6, "spleen": 1, "background": 0} + ... ) + >>> # Input label has values [0, 1, 6] + >>> # Output label will have values [0, 1, 2] (background=0, liver=1, spleen=2) + >>> # And updates d["label_names"] to {"background": 0, "liver": 1, "spleen": 2} + + Note: + - Background label (if present) is always mapped to 0 + - Non-background labels are mapped to sequential indices 1, 2, 3, ... in alphabetical order + - Undefined labels (not in label_names) will be set to 0 (background) + - The transform updates the data dictionary with a new "label_names" key containing the remapped values + """ def __init__( self, keys: KeysCollection, label_names: dict[str, int] | None = None, allow_missing_keys: bool = False ): - """ - Normalize label values according to label names dictionary - - Args: - keys: The ``keys`` parameter will be used to get and set the actual data item to transform - label_names: all label names - """ super().__init__(keys, allow_missing_keys) self.label_names = label_names or {} @@ -106,13 +132,18 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.nda # Dictionary containing new label numbers new_label_names = {} label = np.zeros(d[key].shape) - # Making sure the range values and number of labels are the same - for idx, (key_label, val_label) in enumerate(self.label_names.items(), start=1): - if key_label != "background": - new_label_names[key_label] = idx - label[d[key] == val_label] = idx - if key_label == "background": - new_label_names["background"] = 0 + + # Sort label names to ensure deterministic ordering (exclude background) + sorted_labels = sorted([(k, v) for k, v in self.label_names.items() if k != "background"]) + + # Always set background to 0 first + if "background" in self.label_names: + new_label_names["background"] = 0 + + # Assign sequential indices to sorted non-background labels + for idx, (key_label, val_label) in enumerate(sorted_labels, start=1): + new_label_names[key_label] = idx + label[d[key] == val_label] = idx d["label_names"] = new_label_names if isinstance(d[key], MetaTensor): @@ -122,6 +153,20 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> dict[Hashable, np.nda return d +@deprecated(since="1.6", removed="1.8", msg_suffix="Use `RemapLabelsToSequentiald` instead.") +class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): + """ + .. deprecated:: 1.6.0 + `NormalizeLabelsInDatasetd` is deprecated and will be removed in version 1.8.0. + Use :class:`RemapLabelsToSequentiald` instead. + + This class is maintained for backward compatibility. Please use RemapLabelsToSequentiald + which better describes the transform's functionality. + """ + + pass + + class SingleLabelSelectiond(MapTransform): def __init__( diff --git a/tests/apps/deepedit/test_deepedit_transforms.py b/tests/apps/deepedit/test_deepedit_transforms.py index 18d6567fd7..db4d872d56 100644 --- a/tests/apps/deepedit/test_deepedit_transforms.py +++ b/tests/apps/deepedit/test_deepedit_transforms.py @@ -25,6 +25,7 @@ FindAllValidSlicesMissingLabelsd, FindDiscrepancyRegionsDeepEditd, NormalizeLabelsInDatasetd, + RemapLabelsToSequentiald, ResizeGuidanceMultipleLabelDeepEditd, SingleLabelSelectiond, SplitPredsLabeld, @@ -282,6 +283,100 @@ def test_correct_results(self, arguments, input_data, expected_result): result = add_fn(input_data) self.assertEqual(len(np.unique(result["label"])), expected_result) + def test_ordering_determinism(self): + """Test that different input ordering produces the same output (alphabetical)""" + # Create a label array with different label values + label = np.array([[[0, 1, 6, 3]]]) # background=0, spleen=1, liver=6, kidney=3 + + # Test case 1: liver first, then kidney, then spleen + data1 = {"label": label.copy()} + transform1 = RemapLabelsToSequentiald( + keys="label", label_names={"liver": 6, "kidney": 3, "spleen": 1, "background": 0} + ) + result1 = transform1(data1) + + # Test case 2: spleen first, then kidney, then liver (different order) + data2 = {"label": label.copy()} + transform2 = RemapLabelsToSequentiald( + keys="label", label_names={"spleen": 1, "kidney": 3, "liver": 6, "background": 0} + ) + result2 = transform2(data2) + + # Both should produce the same output (alphabetically sorted) + # Expected mapping: background=0, kidney=1, liver=2, spleen=3 + np.testing.assert_array_equal(result1["label"], result2["label"]) + + # Verify the actual mapping is alphabetical + expected_output = np.array([[[0, 3, 2, 1]]]) # kidney=1, liver=2, spleen=3, background=0 + np.testing.assert_array_equal(result1["label"], expected_output) + + # Verify label_names is correct + self.assertEqual(result1["label_names"], {"background": 0, "kidney": 1, "liver": 2, "spleen": 3}) + self.assertEqual(result2["label_names"], {"background": 0, "kidney": 1, "liver": 2, "spleen": 3}) + + def test_multiple_labels(self): + """Test with multiple non-background labels""" + label = np.array([[[0, 1, 2, 5]]]) # background, spleen, kidney, liver + data = {"label": label.copy()} + transform = RemapLabelsToSequentiald( + keys="label", label_names={"spleen": 1, "kidney": 2, "liver": 5, "background": 0} + ) + result = transform(data) + + # Expected: background=0, kidney=1, liver=2, spleen=3 (alphabetical) + expected = np.array([[[0, 3, 1, 2]]]) + np.testing.assert_array_equal(result["label"], expected) + self.assertEqual(result["label_names"], {"background": 0, "kidney": 1, "liver": 2, "spleen": 3}) + + def test_deprecated_name_warning(self): + """Test that NormalizeLabelsInDatasetd is properly deprecated. + + The deprecation warning only triggers when MONAI version >= 1.6 (since="1.6"). + This test verifies: + 1. The actual NormalizeLabelsInDatasetd class is marked as deprecated in docstring + 2. The class is a subclass of RemapLabelsToSequentiald + 3. The deprecation mechanism works correctly (tested via version_val simulation) + 4. The actual class functions correctly + """ + import warnings + + from monai.utils import deprecated + + # Verify NormalizeLabelsInDatasetd docstring indicates deprecation + self.assertIn("deprecated", NormalizeLabelsInDatasetd.__doc__.lower()) + self.assertIn("RemapLabelsToSequentiald", NormalizeLabelsInDatasetd.__doc__) + + # Verify NormalizeLabelsInDatasetd is a subclass of RemapLabelsToSequentiald + self.assertTrue(issubclass(NormalizeLabelsInDatasetd, RemapLabelsToSequentiald)) + + # Test the deprecation mechanism using version_val to simulate version 1.6 + # This verifies the @deprecated decorator behavior that NormalizeLabelsInDatasetd uses + @deprecated( + since="1.6", + removed="1.8", + msg_suffix="Use `RemapLabelsToSequentiald` instead.", + version_val="1.6", # Simulate version 1.6 to trigger warning + ) + class DeprecatedNormalizeLabels(RemapLabelsToSequentiald): + pass + + data = {"label": np.array([[[0, 1]]])} + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + transform = DeprecatedNormalizeLabels(keys="label", label_names={"spleen": 1, "background": 0}) + _ = transform(data) + + # Check that a deprecation warning was raised + self.assertEqual(len(w), 1) + self.assertTrue(issubclass(w[0].category, FutureWarning)) + self.assertIn("RemapLabelsToSequentiald", str(w[0].message)) + + # Verify the actual NormalizeLabelsInDatasetd class works correctly + transform_actual = NormalizeLabelsInDatasetd(keys="label", label_names={"spleen": 1, "background": 0}) + result = transform_actual({"label": np.array([[[0, 1]]])}) + self.assertIn("label", result) + class TestResizeGuidanceMultipleLabelCustomd(unittest.TestCase): From 3d5afdf3faf32098eed9bc7bc7cb344388247875 Mon Sep 17 00:00:00 2001 From: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Date: Tue, 27 Jan 2026 12:57:29 +0000 Subject: [PATCH 14/19] Adding What's New Page for 1.5.2 Release (#8720) ### Description This adds the "what's new" page for the minor 1.5.2 release. This commit should probably just be cherry-picked for the release. Branch rules will have to be temporarily broken to allow the PR past blossom as well, since no code is modified here this is safe and it's only the documentation generation that should be checked. ### 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. - [x] Documentation updated, tested `make html` command in the `docs/` folder. --------- Signed-off-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com> Co-authored-by: YunLiu <55491388+KumoLiu@users.noreply.github.com> Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- CHANGELOG.md | 9 ++++++++- docs/source/whatsnew.rst | 1 + docs/source/whatsnew_1_5_1.md | 2 +- docs/source/whatsnew_1_5_2.md | 6 ++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 docs/source/whatsnew_1_5_2.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c6ddd262a..c89d723dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## [1.5.2] - 2026-01-28 + +## What's Changed +### Fixed +* Fix Zip Slip vulnerability in NGC private bundle download (#8682) + ## [1.5.1] - 2025-09-22 ## What's Changed @@ -1261,7 +1267,8 @@ the postprocessing steps should be used before calling the metrics methods [highlights]: https://github.com/Project-MONAI/MONAI/blob/master/docs/source/highlights.md -[Unreleased]: https://github.com/Project-MONAI/MONAI/compare/1.5.1...HEAD +[Unreleased]: https://github.com/Project-MONAI/MONAI/compare/1.5.2...HEAD +[1.5.2]: https://github.com/Project-MONAI/MONAI/compare/1.5.1...1.5.2 [1.5.1]: https://github.com/Project-MONAI/MONAI/compare/1.5.0...1.5.1 [1.5.0]: https://github.com/Project-MONAI/MONAI/compare/1.4.0...1.5.0 [1.4.0]: https://github.com/Project-MONAI/MONAI/compare/1.3.2...1.4.0 diff --git a/docs/source/whatsnew.rst b/docs/source/whatsnew.rst index 0104ca4832..1fa4680055 100644 --- a/docs/source/whatsnew.rst +++ b/docs/source/whatsnew.rst @@ -6,6 +6,7 @@ What's New .. toctree:: :maxdepth: 1 + whatsnew_1_5_2.md whatsnew_1_5_1.md whatsnew_1_5.md whatsnew_1_4.md diff --git a/docs/source/whatsnew_1_5_1.md b/docs/source/whatsnew_1_5_1.md index 928bc97985..6fb1c4af5a 100644 --- a/docs/source/whatsnew_1_5_1.md +++ b/docs/source/whatsnew_1_5_1.md @@ -1,5 +1,5 @@ -# What's new in 1.5.1 🎉🎉 +# What's new in 1.5.1 This is a minor update for MONAI to address security concerns and improve compatibility with the newest PyTorch release. diff --git a/docs/source/whatsnew_1_5_2.md b/docs/source/whatsnew_1_5_2.md new file mode 100644 index 0000000000..7a468956c5 --- /dev/null +++ b/docs/source/whatsnew_1_5_2.md @@ -0,0 +1,6 @@ + +# What's new in 1.5.2 🎉🎉 + +This is a minor update for MONAI to address a security concern. + +- Security fix to address advisory [GHSA-9rg3-9pvr-6p27](https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-9rg3-9pvr-6p27). From b30b7a6be2af7dbaefaf50ebcc613bd3c83f1450 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:21:11 +0000 Subject: [PATCH 15/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/deepedit/transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 14c37be860..ad5e8eaa4c 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -164,7 +164,6 @@ class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): which better describes the transform's functionality. """ - pass class SingleLabelSelectiond(MapTransform): From 40d9512919156e31dff3425135769fb03e48f2a1 Mon Sep 17 00:00:00 2001 From: jirka <6035284+Borda@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:06:50 +0100 Subject: [PATCH 16/19] linting Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/deepedit/transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index ad5e8eaa4c..d2f89d2eea 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -165,7 +165,6 @@ class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): """ - class SingleLabelSelectiond(MapTransform): def __init__( From 2892507c47e9eaa3cb1cf368ff4cc55b99eef185 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:10:42 +0000 Subject: [PATCH 17/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- monai/apps/deepedit/transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 14c37be860..ad5e8eaa4c 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -164,7 +164,6 @@ class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): which better describes the transform's functionality. """ - pass class SingleLabelSelectiond(MapTransform): From 19342089374ec8f305090688abac2e5cde14ba79 Mon Sep 17 00:00:00 2001 From: jirka <6035284+Borda@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:10:55 +0100 Subject: [PATCH 18/19] linting Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/deepedit/transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 14c37be860..ad5e8eaa4c 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -164,7 +164,6 @@ class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): which better describes the transform's functionality. """ - pass class SingleLabelSelectiond(MapTransform): From 385127cf187ad65005c5c756c87bc6289de3d67c Mon Sep 17 00:00:00 2001 From: jirka <6035284+Borda@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:56:53 +0100 Subject: [PATCH 19/19] linting Signed-off-by: jirka <6035284+Borda@users.noreply.github.com> --- monai/apps/deepedit/transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index ad5e8eaa4c..d2f89d2eea 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -165,7 +165,6 @@ class NormalizeLabelsInDatasetd(RemapLabelsToSequentiald): """ - class SingleLabelSelectiond(MapTransform): def __init__(