Skip to content

Added support of matching instance level tags, and update liver seg #345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 22 additions & 1 deletion examples/apps/ai_livertumor_seg_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
from monai.deploy.operators.publisher_operator import PublisherOperator

# This is a sample series selection rule in JSON, simply selecting CT series.
# If the study has more than 1 CT series, then all of them will be selected.
# Please see more detail in DICOMSeriesSelectorOperator.
# For list of string values, e.g. "ImageType": ["PRIMARY", "ORIGINAL"], it is a match if all elements
# are all in the multi-value attribute of the DICOM series.

Sample_Rules_Text = """
{
"selections": [
{
"name": "CT Series",
"conditions": {
"Modality": "(?i)CT",
"ImageType": ["PRIMARY", "ORIGINAL"],
"PhotometricInterpretation": "MONOCHROME2"
}
}
]
}
"""


@resource(cpu=1, gpu=1, memory="7Gi")
# pip_packages can be a string that is a path(str) to requirements.txt file or a list of packages.
Expand All @@ -46,7 +67,7 @@ def compose(self):
self._logger.debug(f"Begin {self.compose.__name__}")
# Creates the custom operator(s) as well as SDK built-in operator(s).
study_loader_op = DICOMDataLoaderOperator()
series_selector_op = DICOMSeriesSelectorOperator()
series_selector_op = DICOMSeriesSelectorOperator(rules=Sample_Rules_Text)
series_to_vol_op = DICOMSeriesToVolumeOperator()
# Model specific inference operator, supporting MONAI transforms.
liver_tumor_seg_op = LiverTumorSegOperator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
@md.input("image", Image, IOType.IN_MEMORY)
@md.output("seg_image", Image, IOType.IN_MEMORY)
@md.output("saved_images_folder", DataPath, IOType.DISK)
@md.env(pip_packages=["monai>=0.8.1", "torch>=1.5", "numpy>=1.21", "nibabel"])
@md.env(pip_packages=["monai==0.9.0", "torch>=1.5", "numpy>=1.21", "nibabel"])
class LiverTumorSegOperator(Operator):
"""Performs liver and tumor segmentation using a DL model with an image converted from a DICOM CT series.

Expand Down
19 changes: 15 additions & 4 deletions monai/deploy/operators/dicom_series_selector_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,17 @@ def _select_series(self, attributes: dict, study: DICOMStudy, all_matched=False)
continue
# Try getting the attribute value from Study and current Series prop dict
attr_value = series_attr.get(key, None)
logging.info(f" Series attribute value: {attr_value}")
logging.info(f" Series attribute {key} value: {attr_value}")

# If not found, try the best at the native instance level for string VR
# This is mainly for attributes like ImageType
if not attr_value:
try:
attr_value = [series.get_sop_instances()[0].get_native_sop_instance()[key].repval]
series_attr.update({key: attr_value})
except Exception:
logging.info(f" Attribute {key} not at instance level either.")

if not attr_value:
matched = False
elif isinstance(attr_value, numbers.Number):
Expand All @@ -233,12 +243,13 @@ def _select_series(self, attributes: dict, study: DICOMStudy, all_matched=False)
if re.search(value_to_match, attr_value, re.IGNORECASE):
matched = True
elif isinstance(attr_value, list):
meta_data_set = {str(element).lower() for element in attr_value}
# Assume multi value string attributes
meta_data_list = str(attr_value).lower()
if isinstance(value_to_match, list):
value_set = {str(element).lower() for element in value_to_match}
matched = all(val in meta_data_set for val in value_set)
matched = all(val in meta_data_list for val in value_set)
elif isinstance(value_to_match, (str, numbers.Number)):
matched = str(value_to_match).lower() in meta_data_set
matched = str(value_to_match).lower() in meta_data_list
else:
raise NotImplementedError(f"Not support for matching on this type: {type(value_to_match)}")

Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ max_line_length = 120
ignore =
E203,E305,E402,E501,E721,E741,F821,F841,F999,W503,W504,C408,E302,W291,E303,
# N812 lowercase 'torch.nn.functional' imported as non lowercase 'F'
N812
N812,
B024 #abstract base class, but it has no abstract methods
per_file_ignores =
__init__.py: F401
# Allow using camel case for variable/argument names for the sake of readability.
Expand Down