Skip to content

Commit 5b48048

Browse files
730 retinal oct rpd segmentation (#748)
Fixes # . ### Description This is a proposed segmentation model to add to the model zoo. ### Status Ready for review/work in progress. ### Please ensure all the checkboxes: <!--- Put an `x` in all the boxes that apply, and remove the not applicable items --> - [x] Codeformat tests passed locally by running `./runtests.sh --codeformat`. - [ ] In-line docstrings updated. - [ ] Update `version` and `changelog` in `metadata.json` if changing an existing bundle. - [ x] Please ensure the naming rules in config files meet our requirements (please refer to: `CONTRIBUTING.md`). - [x ] Ensure versions of packages such as `monai`, `pytorch` and `numpy` are correct in `metadata.json`. - [x ] Descriptions should be consistent with the content, such as `eval_metrics` of the provided weights and TorchScript modules. - [x ] Files larger than 25MB are excluded and replaced by providing download links in `large_file.yml`. - [ x] Avoid using path that contains personal information within config files (such as use `/home/your_name/` for `"bundle_root"`). --------- Signed-off-by: Yiheng Wang <[email protected]> Co-authored-by: Yiheng Wang <[email protected]> Co-authored-by: Yiheng Wang <[email protected]>
1 parent 05067dc commit 5b48048

30 files changed

+3047
-3
lines changed

ci/bundle_custom_data.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,16 @@
2323
"maisi_ct_generative",
2424
"cxr_image_synthesis_latent_diffusion_model",
2525
"brain_image_synthesis_latent_diffusion_model",
26+
"retinalOCT_RPD_segmentation",
2627
]
2728

2829
# This list is used for our CI tests to determine whether a bundle contains the preferred files.
2930
# If a bundle does not have any of the preferred files, please add the bundle name into the list.
30-
exclude_verify_preferred_files_list = ["pediatric_abdominal_ct_segmentation", "maisi_ct_generative"]
31+
exclude_verify_preferred_files_list = [
32+
"pediatric_abdominal_ct_segmentation",
33+
"maisi_ct_generative",
34+
"retinalOCT_RPD_segmentation",
35+
]
3136

3237
# This list is used for our CI tests to determine whether a bundle needs to be tested with
3338
# the `verify_export_torchscript` function in `verify_bundle.py`.
@@ -47,6 +52,7 @@
4752
"mednist_ddpm",
4853
"cxr_image_synthesis_latent_diffusion_model",
4954
"brain_image_synthesis_latent_diffusion_model",
55+
"retinalOCT_RPD_segmentation",
5056
]
5157

5258
# This list is used for our CI tests to determine whether a bundle needs to be tested after downloading
@@ -58,7 +64,9 @@
5864
# This dict is used for our CI tests to install required dependencies that cannot be installed by `pip install` directly.
5965
# If a bundle has this kind of dependencies, please add the bundle name (key), and the path of the install script (value)
6066
# into the dict.
61-
install_dependency_dict = {}
67+
install_dependency_dict = {
68+
"retinalOCT_RPD_segmentation": "ci/install_scripts/install_retinalOCT_RPD_segmentation_dependency.sh"
69+
}
6270

6371
# This list is used for our CI tests to determine whether a bundle supports TensorRT export. Related
6472
# test will be employed for bundles in the dict.

ci/get_bundle_requirements.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
ALLOW_MONAI_RC = os.environ.get("ALLOW_MONAI_RC", "false").lower() in ("true", "1", "t", "y", "yes")
2121

22+
special_dependencies_list = ["detectron2"]
23+
2224

2325
def increment_version(version):
2426
"""
@@ -79,6 +81,8 @@ def get_requirements(bundle, models_path, requirements_file):
7981
if package_key in metadata.keys():
8082
optional_dict = metadata[package_key]
8183
for name, version in optional_dict.items():
84+
if name in special_dependencies_list:
85+
continue
8286
libs.append(f"{name}=={version}")
8387

8488
if len(libs) > 0:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
python -m pip install 'git+https://github.com/facebookresearch/detectron2.git@65184fc057d4fab080a98564f6b60fae0b94edc4'
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import glob
2+
import json
3+
import os
4+
import shutil
5+
import subprocess
6+
import sys
7+
import tempfile
8+
import unittest
9+
10+
import pandas as pd
11+
import yaml
12+
13+
14+
class TestRPDInference(unittest.TestCase):
15+
def setUp(self):
16+
print(os.getcwd())
17+
# set the bundle root to the directory the test is being run from.
18+
self.bundle_root = os.path.abspath(
19+
os.path.join(os.path.dirname(__file__), "..", "..", "models", "retinalOCT_RPD_segmentation")
20+
)
21+
# Change the current working directory to bundle_root
22+
os.chdir(self.bundle_root)
23+
24+
# Create a temporary directory for test data
25+
self.test_data_dir = tempfile.mkdtemp()
26+
self.extracted_dir = os.path.join(self.bundle_root, "sample_data")
27+
28+
# create a dummy metadata.json file.
29+
metadata_file = os.path.join(self.test_data_dir, "metadata.json")
30+
metadata = {
31+
"version": "0.0.1",
32+
"schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json",
33+
}
34+
with open(metadata_file, "w") as f:
35+
json.dump(metadata, f)
36+
37+
# create output directory.
38+
self.output_dir = os.path.join(self.test_data_dir, "output")
39+
os.makedirs(self.output_dir)
40+
41+
def tearDown(self):
42+
# Clean up the temporary directory
43+
shutil.rmtree(self.test_data_dir)
44+
45+
def test_inference_run(self):
46+
# Override configuration parameters
47+
override = {
48+
"args": {
49+
"extracted_dir": self.extracted_dir,
50+
"output_dir": self.output_dir,
51+
"run_extract": False,
52+
"create_dataset": True,
53+
"run_inference": True,
54+
"binary_mask": True,
55+
"binary_mask_overlay": True,
56+
"instance_mask_overlay": True,
57+
"dataset_name": "testDataset",
58+
}
59+
}
60+
61+
# Load the original inference.yaml
62+
inference_yaml_path = "configs/inference.yaml"
63+
with open(inference_yaml_path, "r") as f:
64+
inference_yaml = yaml.safe_load(f)
65+
66+
# Modify inference.yaml with override parameters.
67+
inference_yaml["args"].update(override["args"])
68+
69+
# Create a new inference.yaml in the test_data_dir
70+
test_inference_yaml_path = os.path.join(self.test_data_dir, "inference.yaml")
71+
with open(test_inference_yaml_path, "w") as f:
72+
yaml.dump(inference_yaml, f)
73+
74+
# Run the inference command using subprocess
75+
cmd = [
76+
sys.executable, # Use the same Python interpreter
77+
"-m",
78+
"monai.bundle",
79+
"run",
80+
"inference",
81+
"--bundle_root",
82+
self.bundle_root,
83+
"--config_file",
84+
test_inference_yaml_path, # Use the new file
85+
"--meta_file",
86+
os.path.join(self.test_data_dir, "metadata.json"),
87+
]
88+
89+
try:
90+
subprocess.run(cmd, check=True)
91+
except subprocess.CalledProcessError as e:
92+
self.fail(f"Inference command failed: {e}")
93+
94+
# Add assertions to check the output
95+
# Check if output files were created
96+
output_files = os.listdir(self.output_dir)
97+
self.assertTrue(len(output_files) > 0)
98+
99+
# Check for the COCO JSON file
100+
coco_file_found = glob.glob(os.path.join(self.output_dir, "**", "coco_instances_results.json"), recursive=True)
101+
print(coco_file_found)
102+
self.assertTrue(len(coco_file_found) == 6)
103+
104+
# Check for the TIFF files.
105+
tiff_files_found = glob.glob(os.path.join(self.output_dir, "**", "*.tiff"), recursive=True)
106+
self.assertTrue(len(tiff_files_found) == 6)
107+
108+
# Check for the html files.
109+
html_files_found = glob.glob(os.path.join(self.output_dir, "*.html"))
110+
self.assertTrue(len(html_files_found) == 2)
111+
112+
# At least 10 RPD present in sample data
113+
dfvol = pd.read_html(os.path.join(self.output_dir, "dfvol_testDataset.html"))[0]
114+
self.assertTrue(dfvol["dt_instances"].sum().iloc[0] > 10)
115+
116+
117+
if __name__ == "__main__":
118+
unittest.main()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
BSD 2-Clause License
2+
3+
Copyright (c) 2022, uw-biomedical-ml
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions are met:
8+
9+
1. Redistributions of source code must retain the above copyright notice, this
10+
list of conditions and the following disclaimer.
11+
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
imports:
2+
- $import scripts
3+
- $import scripts.inference
4+
5+
args:
6+
run_extract : False
7+
input_dir : "/path/to/data"
8+
extracted_dir : "/path/to/extracted/data"
9+
input_format : "dicom"
10+
create_dataset : True
11+
dataset_name : "my_dataset_name"
12+
13+
output_dir : "/path/to/model/output"
14+
run_inference : True
15+
create_tables : True
16+
17+
# create visuals
18+
binary_mask : False
19+
binary_mask_overlay : True
20+
instance_mask_overlay : False
21+
22+
inference:
23+
- $scripts.inference.main(@args)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
{
2+
"schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json",
3+
"version": "0.0.1",
4+
"changelog": {
5+
"0.0.1": "Initial version"
6+
},
7+
"monai_version": "1.5.0",
8+
"pytorch_version": "2.6.0",
9+
"numpy_version": "1.26.4",
10+
"optional_packages_version": {},
11+
"required_packages_version": {
12+
"setuptools": "75.8.0",
13+
"opencv-python-headless": "4.11.0.86",
14+
"pandas": "2.3.0",
15+
"seaborn": "0.13.2",
16+
"scikit-learn": "1.6.1",
17+
"progressbar": "2.5",
18+
"pydicom": "3.0.1",
19+
"fire": "0.7.0",
20+
"torchvision": "0.21.0",
21+
"detectron2": "0.6",
22+
"lxml": "5.4.0",
23+
"pillow": "11.2.1"
24+
},
25+
"name": "retinalOCT_RPD_segmentation",
26+
"task": "Reticular Pseudodrusen (RPD) instance segmentation.",
27+
"description": "This network detects and segments Reticular Pseudodrusen (RPD) instances in Optical Coherence Tomography (OCT) B-scans which can be presented in a vol or dicom format.",
28+
"authors": "Yelena Bagdasarova, Scott Song",
29+
"copyright": "Copyright (c) 2022, uw-biomedical-ml",
30+
"network_data_format": {
31+
"inputs": {
32+
"image": {
33+
"type": "image",
34+
"format": "magnitude",
35+
"modality": "OCT",
36+
"num_channels": 1,
37+
"spatial_shape": [
38+
496,
39+
1024
40+
],
41+
"dtype": "int16",
42+
"value_range": [
43+
0,
44+
256
45+
],
46+
"is_patch_data": false,
47+
"channel_def": {
48+
"0": "image"
49+
}
50+
}
51+
},
52+
"preprocessed_data_sources": {
53+
"vol_file": {
54+
"type": "image",
55+
"format": "magnitude",
56+
"modality": "OCT",
57+
"num_channels": 1,
58+
"spatial_shape": [
59+
496,
60+
1024,
61+
"D"
62+
],
63+
"dtype": "int16",
64+
"value_range": [
65+
0,
66+
256
67+
],
68+
"description": "The pixel array of each OCT slice is extracted with volreader and the png files saved to <extracted_dir>/<some>/<file>/<name>/<some_file_name>_oct_<DDD>.png on disk, where <DDD> is the slice number and a nested hierarchy of folders is created using the underscores in the original filename. "
69+
},
70+
"dicom_series": {
71+
"type": "image",
72+
"format": "magnitude",
73+
"modality": "OCT",
74+
"SOP class UID": "1.2.840.10008.5.1.4.1.1.77.1.5.4",
75+
"num_channels": 1,
76+
"spatial_shape": [
77+
496,
78+
1024,
79+
"D"
80+
],
81+
"dtype": "int16",
82+
"value_range": [
83+
0,
84+
256
85+
],
86+
"description": "The pixel array of each OCT slice is extracted with pydicom and the png files saved to <extracted_dir>/<SOPInstanceUID>/<SOPInstanceUID>_oct_<DDD>.png on disk, where <DDD> is the slice number. "
87+
}
88+
},
89+
"outputs": {
90+
"pred": {
91+
"dtype": "dictionary",
92+
"type": "dictionary",
93+
"format": "COCO",
94+
"modality": "n/a",
95+
"value_range": [
96+
0,
97+
1
98+
],
99+
"num_channels": 1,
100+
"spatial_shape": [
101+
496,
102+
1024
103+
],
104+
"channel_def": {
105+
"0": "RPD"
106+
},
107+
"description": "This output is a JSON file in COCO Instance Segmentation format, containing bounding boxes, segmentation masks, and output probabilities for detected instances."
108+
}
109+
},
110+
"post_processed_outputs": {
111+
"binary segmentation": {
112+
"type": "image",
113+
"format": "TIFF",
114+
"modality": "OCT",
115+
"num_channels": 3,
116+
"spatial_shape": [
117+
496,
118+
1024
119+
],
120+
"description": "This output is a multi-page TIFF file. Each page of the TIFF image corresponds to a binary segmentation mask for a single OCT slice from the input volume. The segmentation masks are stacked in the same order as the original OCT slices."
121+
},
122+
"binary segmentation overlay": {
123+
"type": "image",
124+
"format": "TIFF",
125+
"modality": "OCT",
126+
"num_channels": 3,
127+
"spatial_shape": [
128+
496,
129+
1024
130+
],
131+
"description": "This output is a multi-page TIFF file. Each page of the TIFF image corresponds to a single OCT slice from the input volume overlayed with the detected binary segmentation mask."
132+
},
133+
"instance segmentation overlay": {
134+
"type": "image",
135+
"format": "TIFF",
136+
"modality": "OCT",
137+
"num_channels": 3,
138+
"spatial_shape": [
139+
496,
140+
1024
141+
],
142+
"description": "This output is a multi-page TIFF file. Each page of the TIFF image corresponds to a single OCT slice from the input volume overlayed with the detected binary segmentation mask."
143+
}
144+
}
145+
}
146+
}
188 KB
Loading

0 commit comments

Comments
 (0)