diff --git a/.dockerignore b/.dockerignore index 07db66344..2486d5f59 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,6 +14,7 @@ __pycache__ .vscode .webpack node_modules/ +*.log # Environments .env diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 388b54176..00dc0aa11 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,7 +91,7 @@ jobs: run: | rm dist/monai*.tar.gz - - name: Publish distribution to Test PyPI + - name: Publish distribution to Test if: ${{ github.event.inputs.test_py == 'true' }} uses: pypa/gh-action-pypi-publish@master with: diff --git a/Dockerfile b/Dockerfile index f47e9137a..cf02e489e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,12 @@ RUN BUILD_OHIF=false python setup.py bdist_wheel --build-number $(date +'%Y%m%d% FROM ${FINAL_IMAGE} LABEL maintainer="monai.contact@gmail.com" WORKDIR /opt/monailabel +COPY requirements.txt /opt/monailabel/requirements.txt + RUN apt update -y && apt install -y git curl openslide-tools python3 python-is-python3 python3-pip RUN python -m pip install --no-cache-dir pytest torch torchvision torchaudio + COPY --from=build /opt/monailabel/dist/monailabel* /opt/monailabel/dist/ -RUN python -m pip install --no-cache-dir /opt/monailabel/dist/monailabel*.whl +RUN python -m pip install -v --no-cache-dir /opt/monailabel/dist/monailabel*.whl +RUN python -m pip uninstall sam2 -y +RUN python -m pip install -v --no-cache-dir -r /opt/monailabel/requirements.txt diff --git a/README.md b/README.md index 3ae5bbc4d..d58360dd2 100644 --- a/README.md +++ b/README.md @@ -167,12 +167,6 @@ In addition, you can find a table of the basic supported fields, modalities, vie -> [**SAM2**](https://github.com/facebookresearch/sam2/) -> -> By default, SAM2 is included for all the above Apps only when **_python >= 3.10_** -> - **sam_2d**: for any organ or tissue and others over a given slice/2D image. -> - **sam_3d**: to support SAM2 propagation over multiple slices (Radiology/MONAI-Bundle). - # Getting Started with MONAI Label ### MONAI Label requires a few steps to get started: - Step 1: [Install MONAI Label](#step-1-installation) @@ -219,6 +213,19 @@ To install the _**latest features**_ using one of the following options:
docker run --gpus all --rm -ti --ipc=host --net=host projectmonai/monailabel:latest bash
+### SAM-2 + +> By default, [**SAM2**](https://github.com/facebookresearch/sam2/) model is included for all the Apps when **_python >= 3.10_** +> - **sam_2d**: for any organ or tissue and others over a given slice/2D image. +> - **sam_3d**: to support SAM2 propagation over multiple slices (Radiology/MONAI-Bundle). + +If you are using `pip install monailabel` by default it uses [SAM-2](https://huggingface.co/facebook/sam2-hiera-large) models. +
+To use [SAM-2.1](https://huggingface.co/facebook/sam2.1-hiera-large) use one of following options. + - Use monailabel [Docker](https://hub.docker.com/r/projectmonai/monailabel) instead of pip package + - Run monailabel in dev mode (git checkout) + - If you have installed monailabel via pip then uninstall **_sam2_** package `pip uninstall sam2` and then run `pip install -r requirements.txt` or install latest **SAM-2** from it's [github](https://github.com/facebookresearch/sam2/tree/main?tab=readme-ov-file#installation). + ## Step 2 MONAI Label Sample Applications

Radiology

diff --git a/monailabel/config.py b/monailabel/config.py index bf195c7bc..844b6728e 100644 --- a/monailabel/config.py +++ b/monailabel/config.py @@ -8,14 +8,18 @@ # 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. - import os +from importlib.util import find_spec from typing import Any, Dict, List, Optional from pydantic import AnyHttpUrl from pydantic_settings import BaseSettings, SettingsConfigDict +def is_package_installed(name): + return False if find_spec(name) is None else True + + class Settings(BaseSettings): MONAI_LABEL_API_STR: str = "" MONAI_LABEL_PROJECT_NAME: str = "MONAILabel" @@ -98,6 +102,19 @@ class Settings(BaseSettings): MONAI_ZOO_REPO: str = "Project-MONAI/model-zoo/hosting_storage_v1" MONAI_ZOO_AUTH_TOKEN: str = "" + # Refer: https://github.com/facebookresearch/sam2?tab=readme-ov-file#model-description + # Refer: https://huggingface.co/facebook/sam2-hiera-large + MONAI_SAM_MODEL_PT: str = ( + "https://huggingface.co/facebook/sam2.1-hiera-large/resolve/main/sam2.1_hiera_large.pt" + if is_package_installed("SAM-2") + else "https://huggingface.co/facebook/sam2-hiera-large/resolve/main/sam2_hiera_large.pt" + ) + MONAI_SAM_MODEL_CFG: str = ( + "https://huggingface.co/facebook/sam2.1-hiera-large/resolve/main/sam2.1_hiera_l.yaml" + if is_package_installed("SAM-2") + else "https://huggingface.co/facebook/sam2-hiera-large/resolve/main/sam2_hiera_l.yaml" + ) + model_config = SettingsConfigDict( env_file=".env", case_sensitive=True, diff --git a/monailabel/sam2/infer.py b/monailabel/sam2/infer.py index 880e2ae7c..1ddca8ed3 100644 --- a/monailabel/sam2/infer.py +++ b/monailabel/sam2/infer.py @@ -22,6 +22,8 @@ import pylab import schedule import torch +from hydra import initialize_config_dir +from hydra.core.global_hydra import GlobalHydra from monai.transforms import KeepLargestConnectedComponent, LoadImaged from PIL import Image from sam2.build_sam import build_sam2, build_sam2_video_predictor @@ -114,13 +116,21 @@ def __init__( self._config.update(config) # Download PreTrained Model - # https://github.com/facebookresearch/sam2?tab=readme-ov-file#model-description - pt = "sam2.1_hiera_large.pt" - url = f"https://dl.fbaipublicfiles.com/segment_anything_2/092824/{pt}" - self.path = os.path.join(model_dir, f"pretrained_{pt}") - download_file(url, self.path) + pt_url = settings.MONAI_SAM_MODEL_PT + conf_url = settings.MONAI_SAM_MODEL_CFG + sam_pt = pt_url.split("/")[-1] + sam_conf = conf_url.split("/")[-1] + + self.path = os.path.join(model_dir, sam_pt) + self.config_path = os.path.join(model_dir, sam_conf) + + GlobalHydra.instance().clear() + initialize_config_dir(config_dir=model_dir) + + download_file(pt_url, self.path) + download_file(conf_url, self.config_path) + self.config_path = sam_conf - self.config_path = "configs/sam2.1/sam2.1_hiera_l.yaml" self.predictors = {} self.image_cache = {} self.inference_state = None @@ -393,8 +403,8 @@ def main(): force=True, ) - app_name = "pathology" - app_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "sample-apps", app_name)) + app_name = "radiology" + app_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "sample-apps", app_name)) model_dir = os.path.join(app_dir, "model") logger.info(f"Model Dir: {model_dir}") if app_name == "pathology": diff --git a/requirements.txt b/requirements.txt index 3583645c2..4d05c4ea3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,7 +45,7 @@ scikit-learn scipy google-auth==2.29.0 SAM-2 @ git+https://github.com/facebookresearch/sam2.git@c2ec8e14a185632b0a5d8b161928ceb50197eddc ; python_version >= '3.10' - +#sam2>=0.4.1; python_version >= '3.10' # scipy and scikit-learn latest packages are missing on python 3.8 # sudo apt-get install openslide-tools -y diff --git a/sample-apps/endoscopy/main.py b/sample-apps/endoscopy/main.py index cd38984c2..258b5cb20 100644 --- a/sample-apps/endoscopy/main.py +++ b/sample-apps/endoscopy/main.py @@ -58,9 +58,9 @@ def __init__(self, app_dir, studies, conf): print(f" all, {', '.join(configs.keys())}") print("---------------------------------------------------------------------------------------") print("") - exit(-1) + # exit(-1) - models = models.split(",") + models = models.split(",") if models else [] models = [m.strip() for m in models] invalid = [m for m in models if m != "all" and not configs.get(m)] if invalid: @@ -85,7 +85,7 @@ def __init__(self, app_dir, studies, conf): logger.info(f"+++ Using Models: {list(self.models.keys())}") - self.sam = strtobool(conf.get("sam", "true")) + self.sam = strtobool(conf.get("sam2", "true")) super().__init__( app_dir=app_dir, studies=studies, diff --git a/sample-apps/monaibundle/main.py b/sample-apps/monaibundle/main.py index c698d402a..bd6ff807f 100644 --- a/sample-apps/monaibundle/main.py +++ b/sample-apps/monaibundle/main.py @@ -46,7 +46,7 @@ def __init__(self, app_dir, studies, conf): self.epistemic_simulation_size = int(conf.get("epistemic_simulation_size", "5")) self.epistemic_dropout = float(conf.get("epistemic_dropout", "0.2")) - self.sam = strtobool(conf.get("sam", "true")) + self.sam = strtobool(conf.get("sam2", "true")) super().__init__( app_dir=app_dir, studies=studies, diff --git a/sample-apps/pathology/main.py b/sample-apps/pathology/main.py index 095791792..1f11865b0 100644 --- a/sample-apps/pathology/main.py +++ b/sample-apps/pathology/main.py @@ -54,7 +54,7 @@ def __init__(self, app_dir, studies, conf): configs = {k: v for k, v in sorted(configs.items())} - models = conf.get("models", "all") + models = conf.get("models") if not models: print("") print("---------------------------------------------------------------------------------------") @@ -63,9 +63,9 @@ def __init__(self, app_dir, studies, conf): print(f" all, {', '.join(configs.keys())}") print("---------------------------------------------------------------------------------------") print("") - exit(-1) + # exit(-1) - models = models.split(",") + models = models.split(",") if models else [] models = [m.strip() for m in models] invalid = [m for m in models if m != "all" and not configs.get(m)] if invalid: @@ -90,7 +90,7 @@ def __init__(self, app_dir, studies, conf): logger.info(f"+++ Using Models: {list(self.models.keys())}") - self.sam = strtobool(conf.get("sam", "true")) + self.sam = strtobool(conf.get("sam2", "true")) super().__init__( app_dir=app_dir, studies=studies, diff --git a/sample-apps/radiology/main.py b/sample-apps/radiology/main.py index 66ef3e4ad..bb7f8ae18 100644 --- a/sample-apps/radiology/main.py +++ b/sample-apps/radiology/main.py @@ -64,7 +64,7 @@ def __init__(self, app_dir, studies, conf): print("") exit(-1) - models = models.split(",") + models = models.split(",") if models else [] models = [m.strip() for m in models] # Can be configured with --conf scribbles false or true self.scribbles = conf.get("scribbles", "true") == "true" @@ -100,7 +100,7 @@ def __init__(self, app_dir, studies, conf): # Load models from bundle config files, local or released in Model-Zoo, e.g., --conf bundles self.bundles = get_bundle_models(app_dir, conf, conf_key="bundles") if conf.get("bundles") else None - self.sam = strtobool(conf.get("sam", "true")) + self.sam = strtobool(conf.get("sam2", "true")) super().__init__( app_dir=app_dir, studies=studies, diff --git a/setup.cfg b/setup.cfg index 15e24022d..83b3d77e0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,7 +70,8 @@ install_requires = scikit-learn scipy google-auth>=2.29.0 - SAM-2 @ git+https://github.com/facebookresearch/sam2.git@c2ec8e14a185632b0a5d8b161928ceb50197eddc ; python_version >= '3.10' + sam2>=0.4.1; python_version >= '3.10' + #SAM-2 @ git+https://github.com/facebookresearch/sam2.git@c2ec8e14a185632b0a5d8b161928ceb50197eddc ; python_version >= '3.10' [flake8] select = B,C,E,F,N,P,T4,W,B9