From 47be4b3e61e3b8652e203fa30b85b2e6c2aa975a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:23:12 +0000 Subject: [PATCH 1/8] Initial plan From 0798db3276513092e6db390c921e1fec30ea47a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:24:55 +0000 Subject: [PATCH 2/8] Remove Python 3.9 support from CI workflows and update target versions Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- .github/workflows/pythonapp.yml | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 52eff10fa..5400e13b4 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -32,7 +32,7 @@ jobs: strategy: matrix: os: [windows-latest, ubuntu-latest] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v6 - name: Set up Python @@ -54,7 +54,7 @@ jobs: MONAI_ZOO_AUTH_TOKEN: ${{ github.token }} strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v6 - name: Set up Python @@ -88,7 +88,7 @@ jobs: MONAI_ZOO_AUTH_TOKEN: ${{ github.token }} strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v6 with: diff --git a/pyproject.toml b/pyproject.toml index 2e0b24c58..b9adf875c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ requires = [ [tool.black] line-length = 120 -target-version = ['py36', 'py37', 'py38'] +target-version = ['py310', 'py311', 'py312'] include = '\.pyi?$' exclude = ''' ( From b088ae0b54c0d6d9637aac91c32bf5cfe064b75e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:30:42 +0000 Subject: [PATCH 3/8] Remove deprecated distutils usage for Python 3.12+ compatibility Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- monailabel/config.py | 3 ++- monailabel/utils/others/class_utils.py | 2 +- monailabel/utils/others/generic.py | 20 ++++++++++++++++++-- setup.py | 17 ++++++++++++++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/monailabel/config.py b/monailabel/config.py index ea8d1c37e..583bdf142 100644 --- a/monailabel/config.py +++ b/monailabel/config.py @@ -9,13 +9,14 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -from distutils.util import strtobool from importlib.metadata import distributions from typing import Any, Dict, List, Optional from pydantic import AnyHttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict +from monailabel.utils.others.generic import strtobool + def is_package_installed(name): return name in (x.metadata.get("Name") for x in distributions() if x.metadata is not None) diff --git a/monailabel/utils/others/class_utils.py b/monailabel/utils/others/class_utils.py index 0b92f5568..eb0f9c146 100644 --- a/monailabel/utils/others/class_utils.py +++ b/monailabel/utils/others/class_utils.py @@ -15,10 +15,10 @@ import logging import os import sys -from distutils.util import strtobool from typing import List from monailabel.interfaces.exception import MONAILabelError, MONAILabelException +from monailabel.utils.others.generic import strtobool logger = logging.getLogger(__name__) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index a26edec63..5abb90220 100644 --- a/monailabel/utils/others/generic.py +++ b/monailabel/utils/others/generic.py @@ -9,7 +9,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import distutils.util import hashlib import json import logging @@ -242,7 +241,24 @@ def _list_files(d, ext): def strtobool(s): - return False if s is None else s if isinstance(s, bool) else bool(distutils.util.strtobool(s)) + """Convert a string representation of truth to true or false. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Returns the input if + already a bool. Returns False if None. + """ + if s is None: + return False + if isinstance(s, bool): + return s + + val = str(s).lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return True + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return False + else: + raise ValueError(f"invalid truth value {s!r}") def is_openslide_supported(name): diff --git a/setup.py b/setup.py index aa4af4408..844d6fb20 100755 --- a/setup.py +++ b/setup.py @@ -14,13 +14,28 @@ import os import platform import subprocess -from distutils.util import strtobool from setuptools import find_packages, setup import versioneer +def strtobool(val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return 1 + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return 0 + else: + raise ValueError(f"invalid truth value {val!r}") + + def recursive_files(directory, prefix): paths = [] for path, _, filenames in os.walk(directory): From c85e8027352c740be8a2454d7c78a1cd7dc9760e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:31:56 +0000 Subject: [PATCH 4/8] Fix flake8 whitespace issues in docstrings Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- monailabel/utils/others/generic.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index 5abb90220..caa906bd0 100644 --- a/monailabel/utils/others/generic.py +++ b/monailabel/utils/others/generic.py @@ -242,7 +242,7 @@ def _list_files(d, ext): def strtobool(s): """Convert a string representation of truth to true or false. - + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values are 'n', 'no', 'f', 'false', 'off', and '0'. Returns the input if already a bool. Returns False if None. @@ -251,7 +251,7 @@ def strtobool(s): return False if isinstance(s, bool): return s - + val = str(s).lower() if val in ('y', 'yes', 't', 'true', 'on', '1'): return True diff --git a/setup.py b/setup.py index 844d6fb20..7bffe291f 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def strtobool(val): """Convert a string representation of truth to true (1) or false (0). - + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 'val' is anything else. From 6b0e3bedf65caf333a59e7bd0fff226a56ff3b79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:33:22 +0000 Subject: [PATCH 5/8] Update CVAT plugin runtime to Python 3.10 Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- plugins/cvat/endoscopy/deepedit.yaml | 2 +- plugins/cvat/endoscopy/inbody.yaml | 2 +- plugins/cvat/endoscopy/tooltracking.yaml | 2 +- plugins/cvat/pathology/deepedit_nuclei.yaml | 2 +- plugins/cvat/pathology/nuclick.yaml | 2 +- plugins/cvat/pathology/segmentation_nuclei.yaml | 2 +- plugins/cvat/sam2/interactor.yaml | 2 +- plugins/cvat/sam2/tracker.yaml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/cvat/endoscopy/deepedit.yaml b/plugins/cvat/endoscopy/deepedit.yaml index 494e8a37a..37c95e186 100644 --- a/plugins/cvat/endoscopy/deepedit.yaml +++ b/plugins/cvat/endoscopy/deepedit.yaml @@ -23,7 +23,7 @@ metadata: spec: description: A pre-trained DeepEdit model for interactive model for Endoscopy - runtime: 'python:3.8' + runtime: 'python:3.10' handler: interactor:handler eventTimeout: 30s diff --git a/plugins/cvat/endoscopy/inbody.yaml b/plugins/cvat/endoscopy/inbody.yaml index 48586b49c..20e33661b 100644 --- a/plugins/cvat/endoscopy/inbody.yaml +++ b/plugins/cvat/endoscopy/inbody.yaml @@ -24,7 +24,7 @@ metadata: spec: description: A pre-trained classification model for Endoscopy to flag if image follows InBody or OutBody - runtime: 'python:3.8' + runtime: 'python:3.10' handler: detector:handler eventTimeout: 30s diff --git a/plugins/cvat/endoscopy/tooltracking.yaml b/plugins/cvat/endoscopy/tooltracking.yaml index 1cac8673e..f890aecd0 100644 --- a/plugins/cvat/endoscopy/tooltracking.yaml +++ b/plugins/cvat/endoscopy/tooltracking.yaml @@ -23,7 +23,7 @@ metadata: spec: description: A pre-trained tool tracking model for Endoscopy - runtime: 'python:3.8' + runtime: 'python:3.10' handler: detector:handler eventTimeout: 30s diff --git a/plugins/cvat/pathology/deepedit_nuclei.yaml b/plugins/cvat/pathology/deepedit_nuclei.yaml index 3084c67e2..dc0260a86 100644 --- a/plugins/cvat/pathology/deepedit_nuclei.yaml +++ b/plugins/cvat/pathology/deepedit_nuclei.yaml @@ -23,7 +23,7 @@ metadata: spec: description: A pre-trained interaction/deepedit model for Pathology - runtime: 'python:3.8' + runtime: 'python:3.10' handler: detector:handler eventTimeout: 30s diff --git a/plugins/cvat/pathology/nuclick.yaml b/plugins/cvat/pathology/nuclick.yaml index 82976e241..242c7997c 100644 --- a/plugins/cvat/pathology/nuclick.yaml +++ b/plugins/cvat/pathology/nuclick.yaml @@ -25,7 +25,7 @@ metadata: spec: description: A pre-trained NuClick model for interactive cell segmentation for Pathology - runtime: 'python:3.8' + runtime: 'python:3.10' handler: interactor:handler eventTimeout: 30s diff --git a/plugins/cvat/pathology/segmentation_nuclei.yaml b/plugins/cvat/pathology/segmentation_nuclei.yaml index f42a8a661..3a92a2866 100644 --- a/plugins/cvat/pathology/segmentation_nuclei.yaml +++ b/plugins/cvat/pathology/segmentation_nuclei.yaml @@ -27,7 +27,7 @@ metadata: spec: description: A pre-trained semantic segmentation model for Pathology - runtime: 'python:3.8' + runtime: 'python:3.10' handler: detector:handler eventTimeout: 30s diff --git a/plugins/cvat/sam2/interactor.yaml b/plugins/cvat/sam2/interactor.yaml index 6787c300e..b1dc7c9a6 100644 --- a/plugins/cvat/sam2/interactor.yaml +++ b/plugins/cvat/sam2/interactor.yaml @@ -24,7 +24,7 @@ metadata: spec: description: A pre-trained SAM2 model for interactive model - runtime: 'python:3.8' + runtime: 'python:3.10' handler: interactor:handler eventTimeout: 30s diff --git a/plugins/cvat/sam2/tracker.yaml b/plugins/cvat/sam2/tracker.yaml index c6842f9bf..1ebba9d77 100644 --- a/plugins/cvat/sam2/tracker.yaml +++ b/plugins/cvat/sam2/tracker.yaml @@ -19,7 +19,7 @@ metadata: spec: description: A pre-trained SAM2 model for tracking model - runtime: 'python:3.8' + runtime: 'python:3.10' handler: tracker:handler eventTimeout: 30s From 5bc06c623957459776114a1f19571035e9b1c005 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:35:25 +0000 Subject: [PATCH 6/8] Improve strtobool type safety with proper error handling Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- monailabel/utils/others/generic.py | 4 +++- setup.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index caa906bd0..2c5e076a0 100644 --- a/monailabel/utils/others/generic.py +++ b/monailabel/utils/others/generic.py @@ -251,8 +251,10 @@ def strtobool(s): return False if isinstance(s, bool): return s + if not isinstance(s, str): + raise TypeError(f"strtobool expects a string or bool, got {type(s).__name__}: {s!r}") - val = str(s).lower() + val = s.lower() if val in ('y', 'yes', 't', 'true', 'on', '1'): return True elif val in ('n', 'no', 'f', 'false', 'off', '0'): diff --git a/setup.py b/setup.py index 7bffe291f..e5ae765a5 100755 --- a/setup.py +++ b/setup.py @@ -27,6 +27,8 @@ def strtobool(val): are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 'val' is anything else. """ + if not isinstance(val, str): + raise TypeError(f"strtobool expects a string, got {type(val).__name__}") val = val.lower() if val in ('y', 'yes', 't', 'true', 'on', '1'): return 1 From ec0d5c8390011ec62539a979c44be6e7b57409de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 18:17:59 +0000 Subject: [PATCH 7/8] Add SimpleITK as explicit dependency to fix CI test failures Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- requirements.txt | 1 + setup.cfg | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index d57ae0d0c..3d36bcecd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,6 +46,7 @@ urllib3==2.6.0 scikit-learn scipy google-auth==2.29.0 +SimpleITK>=2.2.0 SAM-2 @ git+https://github.com/facebookresearch/sam2.git@c2ec8e14a185632b0a5d8b161928ceb50197eddc ; python_version >= '3.10' #sam2>=0.4.1; python_version >= '3.10' diff --git a/setup.cfg b/setup.cfg index 8f81210c1..e18b674b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,6 +70,7 @@ install_requires = scikit-learn scipy google-auth>=2.29.0 + SimpleITK>=2.2.0 sam2>=0.4.1; python_version >= '3.10' #SAM-2 @ git+https://github.com/facebookresearch/sam2.git@c2ec8e14a185632b0a5d8b161928ceb50197eddc ; python_version >= '3.10' From 6843287377cdcdc7ec7b16c68f84bf1ea36c18da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 20:05:34 +0000 Subject: [PATCH 8/8] Fix circular import by moving strtobool to standalone module Co-authored-by: bhashemian <3968947+bhashemian@users.noreply.github.com> --- monailabel/config.py | 2 +- monailabel/utils/others/class_utils.py | 2 +- monailabel/utils/others/generic.py | 24 +--------------- monailabel/utils/others/strtobool.py | 38 ++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 monailabel/utils/others/strtobool.py diff --git a/monailabel/config.py b/monailabel/config.py index 583bdf142..18518c144 100644 --- a/monailabel/config.py +++ b/monailabel/config.py @@ -15,7 +15,7 @@ from pydantic import AnyHttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict -from monailabel.utils.others.generic import strtobool +from monailabel.utils.others.strtobool import strtobool def is_package_installed(name): diff --git a/monailabel/utils/others/class_utils.py b/monailabel/utils/others/class_utils.py index eb0f9c146..19747f800 100644 --- a/monailabel/utils/others/class_utils.py +++ b/monailabel/utils/others/class_utils.py @@ -18,7 +18,7 @@ from typing import List from monailabel.interfaces.exception import MONAILabelError, MONAILabelException -from monailabel.utils.others.generic import strtobool +from monailabel.utils.others.strtobool import strtobool logger = logging.getLogger(__name__) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index 2c5e076a0..def82a554 100644 --- a/monailabel/utils/others/generic.py +++ b/monailabel/utils/others/generic.py @@ -30,6 +30,7 @@ from monailabel.config import settings from monailabel.utils.others.modelzoo_list import MAINTAINED_BUNDLES +from monailabel.utils.others.strtobool import strtobool # noqa: F401 logger = logging.getLogger(__name__) @@ -240,29 +241,6 @@ def _list_files(d, ext): ] -def strtobool(s): - """Convert a string representation of truth to true or false. - - True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values - are 'n', 'no', 'f', 'false', 'off', and '0'. Returns the input if - already a bool. Returns False if None. - """ - if s is None: - return False - if isinstance(s, bool): - return s - if not isinstance(s, str): - raise TypeError(f"strtobool expects a string or bool, got {type(s).__name__}: {s!r}") - - val = s.lower() - if val in ('y', 'yes', 't', 'true', 'on', '1'): - return True - elif val in ('n', 'no', 'f', 'false', 'off', '0'): - return False - else: - raise ValueError(f"invalid truth value {s!r}") - - def is_openslide_supported(name): ext = file_ext(name) supported_ext = (".bif", ".mrxs", ".ndpi", ".scn", ".svs", ".svslide", ".tif", ".tiff", ".vms", ".vmu") diff --git a/monailabel/utils/others/strtobool.py b/monailabel/utils/others/strtobool.py new file mode 100644 index 000000000..2741bf638 --- /dev/null +++ b/monailabel/utils/others/strtobool.py @@ -0,0 +1,38 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.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. + +"""Standalone utility function for string to boolean conversion. + +This module exists separately to avoid circular import dependencies. +""" + + +def strtobool(s): + """Convert a string representation of truth to true or false. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Returns the input if + already a bool. Returns False if None. + """ + if s is None: + return False + if isinstance(s, bool): + return s + if not isinstance(s, str): + raise TypeError(f"strtobool expects a string or bool, got {type(s).__name__}: {s!r}") + + val = s.lower() + if val in ('y', 'yes', 't', 'true', 'on', '1'): + return True + elif val in ('n', 'no', 'f', 'false', 'off', '0'): + return False + else: + raise ValueError(f"invalid truth value {s!r}")