diff --git a/examples/apps/ai_livertumor_seg_app/app.py b/examples/apps/ai_livertumor_seg_app/app.py index acca14d2..662c5fbb 100644 --- a/examples/apps/ai_livertumor_seg_app/app.py +++ b/examples/apps/ai_livertumor_seg_app/app.py @@ -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. @@ -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() diff --git a/examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py b/examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py index d2fccf67..6b5e41c8 100644 --- a/examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py +++ b/examples/apps/ai_livertumor_seg_app/livertumor_seg_operator.py @@ -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. diff --git a/monai/deploy/operators/dicom_series_selector_operator.py b/monai/deploy/operators/dicom_series_selector_operator.py index 54efa671..d088e48a 100644 --- a/monai/deploy/operators/dicom_series_selector_operator.py +++ b/monai/deploy/operators/dicom_series_selector_operator.py @@ -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): @@ -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)}") diff --git a/setup.cfg b/setup.cfg index 361fd924..aec9f6c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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.