Skip to content
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

Implement highdicom seg operator #327

Merged
merged 9 commits into from Aug 18, 2022
51 changes: 39 additions & 12 deletions examples/apps/ai_livertumor_seg_app/app.py
Expand Up @@ -13,9 +13,12 @@

from livertumor_seg_operator import LiverTumorSegOperator

# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
from pydicom.sr.codedict import codes

from monai.deploy.core import Application, resource
from monai.deploy.operators.dicom_data_loader_operator import DICOMDataLoaderOperator
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator, SegmentDescription
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
from monai.deploy.operators.publisher_operator import PublisherOperator
Expand Down Expand Up @@ -46,33 +49,57 @@ def compose(self):
series_selector_op = DICOMSeriesSelectorOperator()
series_to_vol_op = DICOMSeriesToVolumeOperator()
# Model specific inference operator, supporting MONAI transforms.
unetr_seg_op = LiverTumorSegOperator()
liver_tumor_seg_op = LiverTumorSegOperator()

# Create the publisher operator
publisher_op = PublisherOperator()

# Creates DICOM Seg writer with segment label name in a string list
dicom_seg_writer = DICOMSegmentationWriterOperator(
seg_labels=[
"Liver",
"Tumor",
]
)
# Create DICOM Seg writer providing the required segment description for each segment with
# the actual algorithm and the pertinent organ/tissue.
# The segment_label, algorithm_name, and algorithm_version are limited to 64 chars.
# https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
# User can Look up SNOMED CT codes at, e.g.
# https://bioportal.bioontology.org/ontologies/SNOMEDCT

_algorithm_name = "3D segmentation of the liver and tumor from CT image"
_algorithm_family = codes.DCM.ArtificialIntelligence
_algorithm_version = "0.1.0"

segment_descriptions = [
SegmentDescription(
segment_label="Liver",
segmented_property_category=codes.SCT.Organ,
segmented_property_type=codes.SCT.Liver,
algorithm_name=_algorithm_name,
algorithm_family=_algorithm_family,
algorithm_version=_algorithm_version,
),
SegmentDescription(
segment_label="Tumor",
segmented_property_category=codes.SCT.Tumor,
segmented_property_type=codes.SCT.Tumor,
algorithm_name=_algorithm_name,
algorithm_family=_algorithm_family,
algorithm_version=_algorithm_version,
),
]

dicom_seg_writer = DICOMSegmentationWriterOperator(segment_descriptions)
# Create the processing pipeline, by specifying the source and destination operators, and
# ensuring the output from the former matches the input of the latter, in both name and type.
self.add_flow(study_loader_op, series_selector_op, {"dicom_study_list": "dicom_study_list"})
self.add_flow(
series_selector_op, series_to_vol_op, {"study_selected_series_list": "study_selected_series_list"}
)
self.add_flow(series_to_vol_op, unetr_seg_op, {"image": "image"})
self.add_flow(series_to_vol_op, liver_tumor_seg_op, {"image": "image"})
# Add the publishing operator to save the input and seg images for Render Server.
# Note the PublisherOperator has temp impl till a proper rendering module is created.
self.add_flow(unetr_seg_op, publisher_op, {"saved_images_folder": "saved_images_folder"})
self.add_flow(liver_tumor_seg_op, publisher_op, {"saved_images_folder": "saved_images_folder"})
# Note below the dicom_seg_writer requires two inputs, each coming from a source operator.
self.add_flow(
series_selector_op, dicom_seg_writer, {"study_selected_series_list": "study_selected_series_list"}
)
self.add_flow(unetr_seg_op, dicom_seg_writer, {"seg_image": "seg_image"})
self.add_flow(liver_tumor_seg_op, dicom_seg_writer, {"seg_image": "seg_image"})

self._logger.debug(f"End {self.compose.__name__}")

Expand Down
25 changes: 22 additions & 3 deletions examples/apps/ai_spleen_seg_app/app.py
Expand Up @@ -11,11 +11,14 @@

import logging

# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
from pydicom.sr.codedict import codes

from monai.deploy.core import Application, resource
from monai.deploy.core.domain import Image
from monai.deploy.core.io_type import IOType
from monai.deploy.operators.dicom_data_loader_operator import DICOMDataLoaderOperator
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator, SegmentDescription
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
from monai.deploy.operators.monai_bundle_inference_operator import IOMapping, MonaiBundleInferenceOperator
Expand Down Expand Up @@ -56,13 +59,29 @@ def compose(self):
# during init to provide the optional packages info, parsed from the bundle, to the packager
# for it to install the packages in the MAP docker image.
# Setting output IOType to DISK only works only for leaf operators, not the case in this example.
#
# Pertinent MONAI Bundle:
# https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation
bundle_spleen_seg_op = MonaiBundleInferenceOperator(
input_mapping=[IOMapping("image", Image, IOType.IN_MEMORY)],
output_mapping=[IOMapping("pred", Image, IOType.IN_MEMORY)],
)

# Create DICOM Seg writer with segment label name in a string list
dicom_seg_writer = DICOMSegmentationWriterOperator(seg_labels=["Spleen"])
# Create DICOM Seg writer providing the required segment description for each segment with
# the actual algorithm and the pertinent organ/tissue. The segment_label, algorithm_name,
# and algorithm_version are of DICOM VR LO type, limited to 64 chars.
# https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
segment_descriptions = [
SegmentDescription(
segment_label="Spleen",
segmented_property_category=codes.SCT.Organ,
segmented_property_type=codes.SCT.Spleen,
algorithm_name="volumetric (3D) segmentation of the spleen from CT image",
algorithm_family=codes.DCM.ArtificialIntelligence,
algorithm_version="0.1.0",
)
]
dicom_seg_writer = DICOMSegmentationWriterOperator(segment_descriptions=segment_descriptions)

# Create the processing pipeline, by specifying the source and destination operators, and
# ensuring the output from the former matches the input of the latter, in both name and type.
Expand Down
53 changes: 53 additions & 0 deletions examples/apps/ai_unetr_seg_app/app.py
Expand Up @@ -11,10 +11,13 @@

import logging

# Required for setting SegmentDescription attributes. Direct import as this is not part of App SDK package.
from pydicom.sr.codedict import codes
from unetr_seg_operator import UnetrSegOperator

from monai.deploy.core import Application, resource
from monai.deploy.operators.dicom_data_loader_operator import DICOMDataLoaderOperator
from monai.deploy.operators.dicom_seg_writer_operator import DICOMSegmentationWriterOperator, SegmentDescription
from monai.deploy.operators.dicom_series_selector_operator import DICOMSeriesSelectorOperator
from monai.deploy.operators.dicom_series_to_volume_operator import DICOMSeriesToVolumeOperator
from monai.deploy.operators.publisher_operator import PublisherOperator
Expand Down Expand Up @@ -54,6 +57,50 @@ def compose(self):
output_file="stl/multi-organs.stl", keep_largest_connected_component=False
)

# Create DICOM Seg writer providing the required segment description for each segment with
# the actual algorithm and the pertinent organ/tissue.
# The segment_label, algorithm_name, and algorithm_version are limited to 64 chars.
# https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html

_algorithm_name = "3D multi-organ segmentation from CT image"
_algorithm_family = codes.DCM.ArtificialIntelligence
_algorithm_version = "0.1.0"

# List of (Segment name, [Code menaing str]), not including background which is value of 0.
# User must provide correct codes, which can be looked at, e.g.
# https://bioportal.bioontology.org/ontologies/SNOMEDCT
# Alternatively, consult the concept and code dictionaries in PyDicom

organs = [
("Spleen",),
("Right Kidney", "Kidney"),
("Left Kideny", "Kidney"),
("Gallbladder",),
("Esophagus",),
("Liver",),
("Stomach",),
("Aorta",),
("Inferior vena cava", "InferiorVenaCava"),
("Portal and Splenic Veins", "SplenicVein"),
("Pancreas",),
("Right adrenal gland", "AdrenalGland"),
("Left adrenal gland", "AdrenalGland"),
]

segment_descriptions = [
SegmentDescription(
segment_label=organ[0],
segmented_property_category=codes.SCT.Organ,
segmented_property_type=codes.SCT.__getattr__(organ[1] if len(organ) > 1 else organ[0]),
algorithm_name=_algorithm_name,
algorithm_family=_algorithm_family,
algorithm_version=_algorithm_version,
)
for organ in organs
]

dicom_seg_writer = DICOMSegmentationWriterOperator(segment_descriptions)

# Create the processing pipeline, by specifying the source and destination operators, and
# ensuring the output from the former matches the input of the latter, in both name and type.
self.add_flow(study_loader_op, series_selector_op, {"dicom_study_list": "dicom_study_list"})
Expand All @@ -67,6 +114,12 @@ def compose(self):
# Note the PublisherOperator has temp impl till a proper rendering module is created.
self.add_flow(unetr_seg_op, publisher_op, {"saved_images_folder": "saved_images_folder"})

# Note below the dicom_seg_writer requires two inputs, each coming from a source operator.
self.add_flow(
series_selector_op, dicom_seg_writer, {"study_selected_series_list": "study_selected_series_list"}
)
self.add_flow(unetr_seg_op, dicom_seg_writer, {"seg_image": "seg_image"})

self._logger.debug(f"End {self.compose.__name__}")


Expand Down