From 5c8fb69c0289448c2503e3e6d4f8ee0b2fca95de Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Thu, 7 Sep 2023 16:33:13 +0800 Subject: [PATCH 1/9] update deepedit Signed-off-by: Yiheng Wang --- .../test_spleen_deepedit_annotation.py | 4 +- .../configs/inference.json | 7 +++- .../configs/metadata.json | 6 ++- .../scripts/transforms.py | 38 +++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 models/spleen_deepedit_annotation/scripts/transforms.py diff --git a/ci/unit_tests/test_spleen_deepedit_annotation.py b/ci/unit_tests/test_spleen_deepedit_annotation.py index c4df47a6..a39bc7f8 100644 --- a/ci/unit_tests/test_spleen_deepedit_annotation.py +++ b/ci/unit_tests/test_spleen_deepedit_annotation.py @@ -11,6 +11,7 @@ import os import shutil +import sys import tempfile import unittest @@ -109,6 +110,7 @@ def test_eval_config(self, override): @parameterized.expand([TEST_CASE_2]) def test_infer_config(self, override): override["dataset_dir"] = self.dataset_dir + override["use_click"] = False bundle_root = override["bundle_root"] inferrer = ConfigWorkflow( @@ -128,7 +130,7 @@ def test_infer_click_config(self, override): "dataset#data" ] = "$[{'image': i, 'background': [], 'spleen': [[6, 6, 6], [8, 8, 8]]} for i in @datalist]" bundle_root = override["bundle_root"] - print(override) + sys.path = [bundle_root] + sys.path inferrer = ConfigWorkflow( workflow="infer", diff --git a/models/spleen_deepedit_annotation/configs/inference.json b/models/spleen_deepedit_annotation/configs/inference.json index 9050de49..6a26df23 100644 --- a/models/spleen_deepedit_annotation/configs/inference.json +++ b/models/spleen_deepedit_annotation/configs/inference.json @@ -19,7 +19,7 @@ ], "number_intensity_ch": 1, "device": "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')", - "use_click": false, + "use_click": true, "network_def": { "_target_": "DynUNet", "spatial_dims": 3, @@ -101,6 +101,11 @@ } ], "deepedit_transforms": [ + { + "_target_": "scripts.transforms.OrientationGuidanceMultipleLabelDeepEditd", + "ref_image": "image", + "label_names": "@label_names" + }, { "_target_": "AddGuidanceFromPointsDeepEditd", "ref_image": "image", diff --git a/models/spleen_deepedit_annotation/configs/metadata.json b/models/spleen_deepedit_annotation/configs/metadata.json index 7083d0ff..d4d46cd3 100644 --- a/models/spleen_deepedit_annotation/configs/metadata.json +++ b/models/spleen_deepedit_annotation/configs/metadata.json @@ -1,7 +1,8 @@ { "schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json", - "version": "0.4.8", + "version": "0.4.9", "changelog": { + "0.4.9": "fix orientation issue on clicks", "0.4.8": "Add infer transforms to manage clicks from viewer", "0.4.7": "fix the wrong GPU index issue of multi-node", "0.4.6": "update to use rc7 which solves dynunet issue", @@ -31,7 +32,8 @@ "optional_packages_version": { "itk": "5.3.0", "pytorch-ignite": "0.4.9", - "scikit-image": "0.19.3" + "scikit-image": "0.19.3", + "einops": "0.6.1" }, "name": "Spleen DeepEdit annotation", "task": "Decathlon spleen segmentation", diff --git a/models/spleen_deepedit_annotation/scripts/transforms.py b/models/spleen_deepedit_annotation/scripts/transforms.py new file mode 100644 index 00000000..3372f75b --- /dev/null +++ b/models/spleen_deepedit_annotation/scripts/transforms.py @@ -0,0 +1,38 @@ +from typing import Dict + +import numpy as np +from einops import rearrange +from monai.transforms.transform import Transform + + +class OrientationGuidanceMultipleLabelDeepEditd(Transform): + def __init__(self, ref_image="image", label_names=None): + """ + Convert the guidance to the RAS orientation + """ + self.ref_image = ref_image + self.label_names = label_names + + def transform_points(self, point, affine): + """transform point to the coordinates of the transformed image + point: numpy array [bs, N, 3] + """ + bs, N = point.shape[:2] + point = np.concatenate((point, np.ones((bs, N, 1))), axis=-1) + point = rearrange(point, "b n d -> d (b n)") + point = affine @ point + point = rearrange(point, "d (b n)-> b n d", b=bs)[:, :, :3] + return point + + def __call__(self, data): + d: Dict = dict(data) + for key_label in self.label_names.keys(): + points = d.get(key_label, []) + if len(points) < 1: + continue + reoriented_points = self.transform_points( + np.array(points)[None], + np.linalg.inv(d[self.ref_image].meta["affine"].numpy()) @ d[self.ref_image].meta["original_affine"], + ) + d[key_label] = reoriented_points[0] + return d From 3bf169e5460d3df696e6750b1887648e4b96e93e Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Thu, 7 Sep 2023 16:40:42 +0800 Subject: [PATCH 2/9] fix flake8 issue Signed-off-by: Yiheng Wang --- models/spleen_deepedit_annotation/scripts/transforms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/spleen_deepedit_annotation/scripts/transforms.py b/models/spleen_deepedit_annotation/scripts/transforms.py index 3372f75b..3e85f5ac 100644 --- a/models/spleen_deepedit_annotation/scripts/transforms.py +++ b/models/spleen_deepedit_annotation/scripts/transforms.py @@ -17,8 +17,8 @@ def transform_points(self, point, affine): """transform point to the coordinates of the transformed image point: numpy array [bs, N, 3] """ - bs, N = point.shape[:2] - point = np.concatenate((point, np.ones((bs, N, 1))), axis=-1) + bs, n = point.shape[:2] + point = np.concatenate((point, np.ones((bs, n, 1))), axis=-1) point = rearrange(point, "b n d -> d (b n)") point = affine @ point point = rearrange(point, "d (b n)-> b n d", b=bs)[:, :, :3] From 358362abf32cf309c2b63f515e69d4969d31543c Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Thu, 7 Sep 2023 22:56:48 +0800 Subject: [PATCH 3/9] remove use click info Signed-off-by: Yiheng Wang --- .../configs/inference.json | 17 +---------------- .../spleen_deepedit_annotation/docs/README.md | 13 +------------ 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/models/spleen_deepedit_annotation/configs/inference.json b/models/spleen_deepedit_annotation/configs/inference.json index 6a26df23..99cb7917 100644 --- a/models/spleen_deepedit_annotation/configs/inference.json +++ b/models/spleen_deepedit_annotation/configs/inference.json @@ -19,7 +19,6 @@ ], "number_intensity_ch": 1, "device": "$torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')", - "use_click": true, "network_def": { "_target_": "DynUNet", "spatial_dims": 3, @@ -86,20 +85,6 @@ "clip": true } ], - "auto_seg_transforms": [ - { - "_target_": "Resized", - "keys": "image", - "spatial_size": "@spatial_size", - "mode": "area" - }, - { - "_target_": "DiscardAddGuidanced", - "keys": "image", - "label_names": "@label_names", - "number_intensity_ch": "@number_intensity_ch" - } - ], "deepedit_transforms": [ { "_target_": "scripts.transforms.OrientationGuidanceMultipleLabelDeepEditd", @@ -138,7 +123,7 @@ ], "preprocessing": { "_target_": "Compose", - "transforms": "$@preprocessing_transforms + (@deepedit_transforms if @use_click else @auto_seg_transforms) + @extra_transforms" + "transforms": "$@preprocessing_transforms + @deepedit_transforms + @extra_transforms" }, "dataset": { "_target_": "Dataset", diff --git a/models/spleen_deepedit_annotation/docs/README.md b/models/spleen_deepedit_annotation/docs/README.md index b02dc1c7..d6a2fa1e 100644 --- a/models/spleen_deepedit_annotation/docs/README.md +++ b/models/spleen_deepedit_annotation/docs/README.md @@ -118,22 +118,11 @@ python -m monai.bundle run --config_file "['configs/train.json','configs/evaluat #### Execute inference: - -For automatic inference mode: - - ``` python -m monai.bundle run --config_file configs/inference.json ``` -For interactive segmentation mode, in which the user provides clicks, set the **use_click** flag to true: - - -``` -python -m monai.bundle run --config_file configs/inference.json --use_click true -``` - -Clicks should be added to the data dictionary that is passed to the preprocessing transforms. The add keys are defined in `label_names` in `configs/inference.json`, and the corresponding values are the point coordinates. The following is an example of a data dictionary: +Optionally, clicks can be added to the data dictionary that is passed to the preprocessing transforms. The add keys are defined in `label_names` in `configs/inference.json`, and the corresponding values are the point coordinates. The following is an example of a data dictionary: ``` {"image": "example.nii.gz", "background": [], "spleen": [[I1, J1, K1], [I2, J2, K2]]} From 221251f54f8b174e676a479343178bcd76140043 Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Thu, 7 Sep 2023 23:06:28 +0800 Subject: [PATCH 4/9] update unittest Signed-off-by: Yiheng Wang --- ci/unit_tests/test_spleen_deepedit_annotation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ci/unit_tests/test_spleen_deepedit_annotation.py b/ci/unit_tests/test_spleen_deepedit_annotation.py index a39bc7f8..48e2ff3f 100644 --- a/ci/unit_tests/test_spleen_deepedit_annotation.py +++ b/ci/unit_tests/test_spleen_deepedit_annotation.py @@ -110,7 +110,6 @@ def test_eval_config(self, override): @parameterized.expand([TEST_CASE_2]) def test_infer_config(self, override): override["dataset_dir"] = self.dataset_dir - override["use_click"] = False bundle_root = override["bundle_root"] inferrer = ConfigWorkflow( @@ -125,7 +124,6 @@ def test_infer_config(self, override): @parameterized.expand([TEST_CASE_2]) def test_infer_click_config(self, override): override["dataset_dir"] = self.dataset_dir - override["use_click"] = True override[ "dataset#data" ] = "$[{'image': i, 'background': [], 'spleen': [[6, 6, 6], [8, 8, 8]]} for i in @datalist]" From 02bdd83a6c417e020d97e8b7123a6ba3ec8747d0 Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Fri, 8 Sep 2023 11:09:41 +0800 Subject: [PATCH 5/9] bypass pipenv lock Signed-off-by: Yiheng Wang --- ci/run_premerge_cpu.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/run_premerge_cpu.sh b/ci/run_premerge_cpu.sh index 8215cdc4..4c15b5af 100755 --- a/ci/run_premerge_cpu.sh +++ b/ci/run_premerge_cpu.sh @@ -31,7 +31,7 @@ fi init_pipenv() { echo "initializing pip environment: $1" - pipenv install --python=3.8 -r $1 + pipenv install --skip-lock --pre --python=3.8 -r $1 export PYTHONPATH=$PWD } @@ -65,7 +65,7 @@ verify_bundle() { requirements=$(pipenv run python $(pwd)/ci/get_bundle_requirements.py --b "$bundle") if [ ! -z "$requirements" ]; then echo "install required libraries for bundle: $bundle" - pipenv install -r "$requirements" + pipenv install --skip-lock --pre -r "$requirements" fi # verify bundle pipenv run python $(pwd)/ci/verify_bundle.py -b "$bundle" -m "min" # min tests on cpu From 79c1885e788006d056d6417060f9f081cb24f746 Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Fri, 8 Sep 2023 15:05:12 +0800 Subject: [PATCH 6/9] test to use pip replace pipenv for premerge cpu test Signed-off-by: Yiheng Wang --- ci/run_premerge_cpu.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ci/run_premerge_cpu.sh b/ci/run_premerge_cpu.sh index 4c15b5af..54f36065 100755 --- a/ci/run_premerge_cpu.sh +++ b/ci/run_premerge_cpu.sh @@ -31,7 +31,7 @@ fi init_pipenv() { echo "initializing pip environment: $1" - pipenv install --skip-lock --pre --python=3.8 -r $1 + pipenv install --python=3.8 -r $1 export PYTHONPATH=$PWD } @@ -45,7 +45,7 @@ remove_pipenv() { verify_bundle() { echo 'Run verify bundle...' - init_pipenv requirements.txt + pip install -r requirements.txt head_ref=$(git rev-parse HEAD) git fetch origin dev $head_ref # achieve all changed files in 'models' @@ -53,30 +53,30 @@ verify_bundle() { if [ ! -z "$changes" ] then # get all changed bundles - bundle_list=$(pipenv run python $(pwd)/ci/get_changed_bundle.py --f "$changes") + bundle_list=$(python $(pwd)/ci/get_changed_bundle.py --f "$changes") if [ ! -z "$bundle_list" ] then - pipenv run python $(pwd)/ci/prepare_schema.py --l "$bundle_list" + python $(pwd)/ci/prepare_schema.py --l "$bundle_list" echo $bundle_list for bundle in $bundle_list; do - init_pipenv requirements-dev.txt + pip install -r requirements-dev.txt # get required libraries according to the bundle's metadata file - requirements=$(pipenv run python $(pwd)/ci/get_bundle_requirements.py --b "$bundle") + requirements=$(python $(pwd)/ci/get_bundle_requirements.py --b "$bundle") if [ ! -z "$requirements" ]; then echo "install required libraries for bundle: $bundle" - pipenv install --skip-lock --pre -r "$requirements" + pip install -r "$requirements" fi # verify bundle - pipenv run python $(pwd)/ci/verify_bundle.py -b "$bundle" -m "min" # min tests on cpu - remove_pipenv + python $(pwd)/ci/verify_bundle.py -b "$bundle" -m "min" # min tests on cpu + # remove_pipenv done else echo "this pull request does not change any bundles, skip verify." fi else echo "this pull request does not change any files in 'models', skip verify." - remove_pipenv + # remove_pipenv fi } From 0658e14878c09f480f93dd7d2c94cc8715e8ccb5 Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Fri, 8 Sep 2023 15:15:51 +0800 Subject: [PATCH 7/9] fully remove pipenv Signed-off-by: Yiheng Wang --- ci/run_premerge_cpu.sh | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/ci/run_premerge_cpu.sh b/ci/run_premerge_cpu.sh index 54f36065..1b362a8c 100755 --- a/ci/run_premerge_cpu.sh +++ b/ci/run_premerge_cpu.sh @@ -29,20 +29,6 @@ elif [[ $# -gt 1 ]]; then exit 1 fi -init_pipenv() { - echo "initializing pip environment: $1" - pipenv install --python=3.8 -r $1 - export PYTHONPATH=$PWD -} - -remove_pipenv() { - echo "removing pip environment" - pipenv --rm - rm Pipfile Pipfile.lock - pipenv --clear - df -h -} - verify_bundle() { echo 'Run verify bundle...' pip install -r requirements.txt @@ -69,14 +55,12 @@ verify_bundle() { fi # verify bundle python $(pwd)/ci/verify_bundle.py -b "$bundle" -m "min" # min tests on cpu - # remove_pipenv done else echo "this pull request does not change any bundles, skip verify." fi else echo "this pull request does not change any files in 'models', skip verify." - # remove_pipenv fi } From 0a5ffbfe5d20c59795592d57db3de5740b0f1c3b Mon Sep 17 00:00:00 2001 From: Yiheng Wang Date: Fri, 8 Sep 2023 16:22:37 +0800 Subject: [PATCH 8/9] add download changes Signed-off-by: Yiheng Wang --- ci/download_latest_bundle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/download_latest_bundle.py b/ci/download_latest_bundle.py index 969fb53a..128cf747 100644 --- a/ci/download_latest_bundle.py +++ b/ci/download_latest_bundle.py @@ -20,7 +20,8 @@ def download_latest_bundle(bundle_name: str, models_path: str, download_path: str): model_info_path = os.path.join(models_path, "model_info.json") version = get_latest_version(bundle_name=bundle_name, model_info_path=model_info_path) - download(name=bundle_name, source="github", version=version, bundle_dir=download_path) + + download(name=bundle_name, source="monaihosting", version=version, bundle_dir=download_path) if __name__ == "__main__": From 55c6fe7b118a3f68447a86bdd2de61d8e71c1f5d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 08:22:51 +0000 Subject: [PATCH 9/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ci/download_latest_bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/download_latest_bundle.py b/ci/download_latest_bundle.py index 128cf747..ca5c3f7e 100644 --- a/ci/download_latest_bundle.py +++ b/ci/download_latest_bundle.py @@ -20,7 +20,7 @@ def download_latest_bundle(bundle_name: str, models_path: str, download_path: str): model_info_path = os.path.join(models_path, "model_info.json") version = get_latest_version(bundle_name=bundle_name, model_info_path=model_info_path) - + download(name=bundle_name, source="monaihosting", version=version, bundle_dir=download_path)