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/monailabel/config.py b/monailabel/config.py index ea8d1c37e..18518c144 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.strtobool 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..19747f800 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.strtobool import strtobool logger = logging.getLogger(__name__) diff --git a/monailabel/utils/others/generic.py b/monailabel/utils/others/generic.py index a26edec63..def82a554 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 @@ -31,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__) @@ -241,10 +241,6 @@ 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)) - - 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}") 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 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 = ''' ( 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' diff --git a/setup.py b/setup.py index aa4af4408..e5ae765a5 100755 --- a/setup.py +++ b/setup.py @@ -14,13 +14,30 @@ 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. + """ + 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 + 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):