Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8e57a2e
First commit for forked branch.
ybagdasa Mar 10, 2025
1e02cc5
Deleted the zip file download in inference.py. Updated urls in large_…
ybagdasa Mar 12, 2025
3ac9f28
Implemented unit testing and added sample_data.
ybagdasa Mar 25, 2025
0676427
Applied changes from isort, black, flake in runtests.sh. Ran pytype s…
ybagdasa Mar 25, 2025
f7e9de6
Removed configargparser and added PNG naming instructions in readme.
ybagdasa Mar 25, 2025
0b8f1ef
Merge branch 'dev' into 730-retinalOCT_RPD_segmentation. Upstream
ybagdasa Mar 25, 2025
30dabf9
Moved large_files.yaml into retinalOCT_RPD_Segmentation into bundle r…
ybagdasa Apr 1, 2025
efb5da8
Updated dependencies. Updated depreciated pandas api. Updated install…
ybagdasa Apr 29, 2025
b831e50
Merge from upstream.
ybagdasa Apr 29, 2025
7cd1394
fix isort and update cit for detectron2
yiheng-wang-nv May 16, 2025
d171542
Fixed pre-commit and flake8 errors. Added torchvision to metadata.json.
ybagdasa May 16, 2025
50238f8
Merge branch '730-retinalOCT_RPD_segmentation' of github.com:uw-biome…
ybagdasa May 16, 2025
e271884
fix pre-commit and black checks
yiheng-wang-nv May 19, 2025
db0d0d6
skip part of tests and rrevert changes in UNEST bundles
yiheng-wang-nv May 19, 2025
ea0ef49
Fixed flake8 errors. runtests.sh failing at ptype.
ybagdasa May 19, 2025
992391e
enable different py env for ci tests
yiheng-wang-nv May 29, 2025
52cce77
fix precommit
yiheng-wang-nv May 29, 2025
03525b8
add conda init for cpu test
yiheng-wang-nv May 29, 2025
ee65966
update premerge cpu script
yiheng-wang-nv May 29, 2025
33cc537
add conda init
yiheng-wang-nv May 29, 2025
f767da8
adjust conda init way
yiheng-wang-nv May 29, 2025
a07021c
update init conda env
yiheng-wang-nv May 29, 2025
310742b
Merge branch 'dev' into 730-retinalOCT_RPD_segmentation
yiheng-wang-nv Jun 3, 2025
e74f971
Added docstrings and fixed all flake8 errors.
ybagdasa Jun 6, 2025
d260ea2
Added setuptools==75.8.0 to dependencies to build detectron2 with pyt…
ybagdasa Jun 6, 2025
6160e06
Changed "format" in metadata.json to "magnitude"
ybagdasa Jun 20, 2025
bb88574
Updated required packages in metadata.json.
ybagdasa Jun 20, 2025
d8b3899
Merge branch 'dev' into 730-retinalOCT_RPD_segmentation
yiheng-wang-nv Jun 27, 2025
a707723
Updated README and bundle requirements.
ybagdasa Jul 7, 2025
ca50689
Merge branch '730-retinalOCT_RPD_segmentation' of github.com:uw-biome…
ybagdasa Jul 7, 2025
32a2d10
Merge branch 'dev' into 730-retinalOCT_RPD_segmentation
yiheng-wang-nv Jul 22, 2025
763ccc8
Fixed pre-commit eof line.
ybagdasa Jul 23, 2025
dc0c0b2
Merge branch '730-retinalOCT_RPD_segmentation' of github.com:uw-biome…
ybagdasa Jul 23, 2025
9ba8107
revert py39 changes
yiheng-wang-nv Aug 11, 2025
5faa761
revert py39 changes
yiheng-wang-nv Aug 11, 2025
accbffc
add customized detectron2
yiheng-wang-nv Aug 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions ci/bundle_custom_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@
"maisi_ct_generative",
"cxr_image_synthesis_latent_diffusion_model",
"brain_image_synthesis_latent_diffusion_model",
"retinalOCT_RPD_segmentation",
]

# This list is used for our CI tests to determine whether a bundle contains the preferred files.
# If a bundle does not have any of the preferred files, please add the bundle name into the list.
exclude_verify_preferred_files_list = ["pediatric_abdominal_ct_segmentation", "maisi_ct_generative"]
exclude_verify_preferred_files_list = [
"pediatric_abdominal_ct_segmentation",
"maisi_ct_generative",
"retinalOCT_RPD_segmentation",
]

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

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

# This list is used for our CI tests to determine whether a bundle supports TensorRT export. Related
# test will be employed for bundles in the dict.
Expand Down
4 changes: 4 additions & 0 deletions ci/get_bundle_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

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

special_dependencies_list = ["detectron2"]


def increment_version(version):
"""
Expand Down Expand Up @@ -79,6 +81,8 @@ def get_requirements(bundle, models_path, requirements_file):
if package_key in metadata.keys():
optional_dict = metadata[package_key]
for name, version in optional_dict.items():
if name in special_dependencies_list:
continue
libs.append(f"{name}=={version}")

if len(libs) > 0:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python -m pip install 'git+https://github.com/facebookresearch/detectron2.git@65184fc057d4fab080a98564f6b60fae0b94edc4'
118 changes: 118 additions & 0 deletions ci/unit_tests/test_retinalOCT_RPD_segmentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import glob
import json
import os
import shutil
import subprocess
import sys
import tempfile
import unittest

import pandas as pd
import yaml


class TestRPDInference(unittest.TestCase):
def setUp(self):
print(os.getcwd())
# set the bundle root to the directory the test is being run from.
self.bundle_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", "models", "retinalOCT_RPD_segmentation")
)
# Change the current working directory to bundle_root
os.chdir(self.bundle_root)

# Create a temporary directory for test data
self.test_data_dir = tempfile.mkdtemp()
self.extracted_dir = os.path.join(self.bundle_root, "sample_data")

# create a dummy metadata.json file.
metadata_file = os.path.join(self.test_data_dir, "metadata.json")
metadata = {
"version": "0.0.1",
"schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json",
}
with open(metadata_file, "w") as f:
json.dump(metadata, f)

# create output directory.
self.output_dir = os.path.join(self.test_data_dir, "output")
os.makedirs(self.output_dir)

def tearDown(self):
# Clean up the temporary directory
shutil.rmtree(self.test_data_dir)

def test_inference_run(self):
# Override configuration parameters
override = {
"args": {
"extracted_dir": self.extracted_dir,
"output_dir": self.output_dir,
"run_extract": False,
"create_dataset": True,
"run_inference": True,
"binary_mask": True,
"binary_mask_overlay": True,
"instance_mask_overlay": True,
"dataset_name": "testDataset",
}
}

# Load the original inference.yaml
inference_yaml_path = "configs/inference.yaml"
with open(inference_yaml_path, "r") as f:
inference_yaml = yaml.safe_load(f)

# Modify inference.yaml with override parameters.
inference_yaml["args"].update(override["args"])

# Create a new inference.yaml in the test_data_dir
test_inference_yaml_path = os.path.join(self.test_data_dir, "inference.yaml")
with open(test_inference_yaml_path, "w") as f:
yaml.dump(inference_yaml, f)

# Run the inference command using subprocess
cmd = [
sys.executable, # Use the same Python interpreter
"-m",
"monai.bundle",
"run",
"inference",
"--bundle_root",
self.bundle_root,
"--config_file",
test_inference_yaml_path, # Use the new file
"--meta_file",
os.path.join(self.test_data_dir, "metadata.json"),
]

try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as e:
self.fail(f"Inference command failed: {e}")

# Add assertions to check the output
# Check if output files were created
output_files = os.listdir(self.output_dir)
self.assertTrue(len(output_files) > 0)

# Check for the COCO JSON file
coco_file_found = glob.glob(os.path.join(self.output_dir, "**", "coco_instances_results.json"), recursive=True)
print(coco_file_found)
self.assertTrue(len(coco_file_found) == 6)

# Check for the TIFF files.
tiff_files_found = glob.glob(os.path.join(self.output_dir, "**", "*.tiff"), recursive=True)
self.assertTrue(len(tiff_files_found) == 6)

# Check for the html files.
html_files_found = glob.glob(os.path.join(self.output_dir, "*.html"))
self.assertTrue(len(html_files_found) == 2)

# At least 10 RPD present in sample data
dfvol = pd.read_html(os.path.join(self.output_dir, "dfvol_testDataset.html"))[0]
self.assertTrue(dfvol["dt_instances"].sum().iloc[0] > 10)


if __name__ == "__main__":
unittest.main()
25 changes: 25 additions & 0 deletions models/retinalOCT_RPD_segmentation/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
BSD 2-Clause License

Copyright (c) 2022, uw-biomedical-ml
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 changes: 23 additions & 0 deletions models/retinalOCT_RPD_segmentation/configs/inference.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
imports:
- $import scripts
- $import scripts.inference

args:
run_extract : False
input_dir : "/path/to/data"
extracted_dir : "/path/to/extracted/data"
input_format : "dicom"
create_dataset : True
dataset_name : "my_dataset_name"

output_dir : "/path/to/model/output"
run_inference : True
create_tables : True

# create visuals
binary_mask : False
binary_mask_overlay : True
instance_mask_overlay : False

inference:
- $scripts.inference.main(@args)
146 changes: 146 additions & 0 deletions models/retinalOCT_RPD_segmentation/configs/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{
"schema": "https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/meta_schema_20220324.json",
"version": "0.0.1",
"changelog": {
"0.0.1": "Initial version"
},
"monai_version": "1.5.0",
"pytorch_version": "2.6.0",
"numpy_version": "1.26.4",
"optional_packages_version": {},
"required_packages_version": {
"setuptools": "75.8.0",
"opencv-python-headless": "4.11.0.86",
"pandas": "2.3.0",
"seaborn": "0.13.2",
"scikit-learn": "1.6.1",
"progressbar": "2.5",
"pydicom": "3.0.1",
"fire": "0.7.0",
"torchvision": "0.21.0",
"detectron2": "0.6",
"lxml": "5.4.0",
"pillow": "11.2.1"
},
"name": "retinalOCT_RPD_segmentation",
"task": "Reticular Pseudodrusen (RPD) instance segmentation.",
"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.",
"authors": "Yelena Bagdasarova, Scott Song",
"copyright": "Copyright (c) 2022, uw-biomedical-ml",
"network_data_format": {
"inputs": {
"image": {
"type": "image",
"format": "magnitude",
"modality": "OCT",
"num_channels": 1,
"spatial_shape": [
496,
1024
],
"dtype": "int16",
"value_range": [
0,
256
],
"is_patch_data": false,
"channel_def": {
"0": "image"
}
}
},
"preprocessed_data_sources": {
"vol_file": {
"type": "image",
"format": "magnitude",
"modality": "OCT",
"num_channels": 1,
"spatial_shape": [
496,
1024,
"D"
],
"dtype": "int16",
"value_range": [
0,
256
],
"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. "
},
"dicom_series": {
"type": "image",
"format": "magnitude",
"modality": "OCT",
"SOP class UID": "1.2.840.10008.5.1.4.1.1.77.1.5.4",
"num_channels": 1,
"spatial_shape": [
496,
1024,
"D"
],
"dtype": "int16",
"value_range": [
0,
256
],
"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. "
}
},
"outputs": {
"pred": {
"dtype": "dictionary",
"type": "dictionary",
"format": "COCO",
"modality": "n/a",
"value_range": [
0,
1
],
"num_channels": 1,
"spatial_shape": [
496,
1024
],
"channel_def": {
"0": "RPD"
},
"description": "This output is a JSON file in COCO Instance Segmentation format, containing bounding boxes, segmentation masks, and output probabilities for detected instances."
}
},
"post_processed_outputs": {
"binary segmentation": {
"type": "image",
"format": "TIFF",
"modality": "OCT",
"num_channels": 3,
"spatial_shape": [
496,
1024
],
"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."
},
"binary segmentation overlay": {
"type": "image",
"format": "TIFF",
"modality": "OCT",
"num_channels": 3,
"spatial_shape": [
496,
1024
],
"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."
},
"instance segmentation overlay": {
"type": "image",
"format": "TIFF",
"modality": "OCT",
"num_channels": 3,
"spatial_shape": [
496,
1024
],
"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."
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading