Creating a Segmentation App with MONAI Deploy App SDK

This tutorial shows how to create an organ segmentation application for a PyTorch model that has been trained with MONAI. Please note that this one does not require the model be a MONAI Bundle.

Deploying AI models requires the integration with clinical imaging network, even if just in a for-research-use setting. This means that the AI deploy application will need to support standards-based imaging protocols, and specifically for Radiological imaging, DICOM protocol.

Typically, DICOM network communication, either in DICOM TCP/IP network protocol or DICOMWeb, would be handled by DICOM devices or services, e.g. MONAI Deploy Informatics Gateway, so the deploy application itself would only need to use DICOM Part 10 files as input and save the AI result in DICOM Part10 file(s). For segmentation use cases, the DICOM instance file for AI results could be a DICOM Segmentation object or a DICOM RT Structure Set, and for classification, DICOM Structure Report and/or DICOM Encapsulated PDF.

During model training, input and label images are typically in non-DICOM volumetric image format, e.g., NIfTI and PNG, converted from a specific DICOM study series. Furthermore, the voxel spacings most likely have been re-sampled to be uniform for all images. When integrated with imaging networks and receiving DICOM instances from modalities and Picture Archiving and Communications System, PACS, an AI deploy application has to deal with a whole DICOM study with multiple series, whose images’ spacing may not be the same as expected by the trained model. To address these cases consistently and efficiently, MONAI Deploy Application SDK provides classes, called operators, to parse DICOM studies, select specific series with application-defined rules, and convert the selected DICOM series into domain-specific image format along with meta-data representing the pertinent DICOM attributes. The image is then further processed in the pre-processing stage to normalize spacing, orientation, intensity, etc., before pixel data as Tensors are used for inference.

In the following sections, we will demonstrate how to create a MONAI Deploy application package using the MONAI Deploy App SDK.

Note

For local testing, if there is a lack of DICOM Part 10 files, one can use open source programs, e.g. 3D Slicer, to convert NIfTI to DICOM files.

Creating Operators and connecting them in Application class

We will implement an application that consists of five Operators:

  • DICOMDataLoaderOperator:

    • Input(dicom_files): a folder path (Path)

    • Output(dicom_study_list): a list of DICOM studies in memory (List[DICOMStudy])

  • DICOMSeriesSelectorOperator:

    • Input(dicom_study_list): a list of DICOM studies in memory (List[DICOMStudy])

    • Input(selection_rules): a selection rule (Dict)

    • Output(study_selected_series_list): a DICOM series object in memory (StudySelectedSeries)

  • DICOMSeriesToVolumeOperator:

    • Input(study_selected_series_list): a DICOM series object in memory (StudySelectedSeries)

    • Output(image): an image object in memory (Image)

  • SpleenSegOperator:

    • Input(image): an image object in memory (Image)

    • Output(seg_image): an image object in memory (Image)

  • DICOMSegmentationWriterOperator:

    • Input(seg_image): a segmentation image object in memory (Image)

    • Input(study_selected_series_list): a DICOM series object in memory (StudySelectedSeries)

    • Output(dicom_seg_instance): a file path (Path)

Note

The DICOMSegmentationWriterOperator needs both the segmentation image as well as the original DICOM series meta-data in order to use the patient demographics and the DICOM Study level attributes.

The workflow of the application would look like this.

%%{init: {"theme": "base", "themeVariables": { "fontSize": "16px"}} }%% classDiagram direction TB DICOMDataLoaderOperator --|> DICOMSeriesSelectorOperator : dicom_study_list...dicom_study_list DICOMSeriesSelectorOperator --|> DICOMSeriesToVolumeOperator : study_selected_series_list...study_selected_series_list DICOMSeriesToVolumeOperator --|> SpleenSegOperator : image...image DICOMSeriesSelectorOperator --|> DICOMSegmentationWriterOperator : study_selected_series_list...study_selected_series_list SpleenSegOperator --|> DICOMSegmentationWriterOperator : seg_image...seg_image class DICOMDataLoaderOperator { <in>dicom_files : DISK dicom_study_list(out) IN_MEMORY } class DICOMSeriesSelectorOperator { <in>dicom_study_list : IN_MEMORY <in>selection_rules : IN_MEMORY study_selected_series_list(out) IN_MEMORY } class DICOMSeriesToVolumeOperator { <in>study_selected_series_list : IN_MEMORY image(out) IN_MEMORY } class SpleenSegOperator { <in>image : IN_MEMORY seg_image(out) IN_MEMORY } class DICOMSegmentationWriterOperator { <in>seg_image : IN_MEMORY <in>study_selected_series_list : IN_MEMORY dicom_seg_instance(out) DISK }

Setup environment

# Install MONAI and other necessary image processing packages for the application
!python -c "import monai" || pip install --upgrade -q "monai"
!python -c "import torch" || pip install -q "torch>=1.10.2"
!python -c "import numpy" || pip install -q "numpy>=1.21"
!python -c "import nibabel" || pip install -q "nibabel>=3.2.1"
!python -c "import pydicom" || pip install -q "pydicom>=1.4.2"
!python -c "import highdicom" || pip install -q "highdicom>=0.18.2"
!python -c "import SimpleITK" || pip install -q "SimpleITK>=2.0.0"

# Install MONAI Deploy App SDK package
!python -c "import monai.deploy" || pip install --upgrade "monai-deploy-app-sdk"

Note: you may need to restart the Jupyter kernel to use the updated packages.

Download/Extract ai_spleen_bundle_data from Google Drive

# Download ai_spleen_bundle_data test data zip file
!pip install gdown 
!gdown "https://drive.google.com/uc?id=1Uds8mEvdGNYUuvFpTtCQ8gNU97bAPCaQ"

# After downloading ai_spleen_bundle_data zip file from the web browser or using gdown,
!unzip -o "ai_spleen_seg_bundle_data.zip"

# Need to copy the model.ts file to its own clean subfolder for packaging, to work around an issue in the Packager
models_folder = "models"
!rm -rf {models_folder} && mkdir -p {models_folder}/model && cp model.ts {models_folder}/model && ls {models_folder}/model
Requirement already satisfied: gdown in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (4.7.1)
Requirement already satisfied: filelock in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from gdown) (3.12.2)
Requirement already satisfied: requests[socks] in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from gdown) (2.31.0)
Requirement already satisfied: six in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from gdown) (1.16.0)
Requirement already satisfied: tqdm in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from gdown) (4.66.1)
Requirement already satisfied: beautifulsoup4 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from gdown) (4.12.2)
Requirement already satisfied: soupsieve>1.2 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from beautifulsoup4->gdown) (2.4.1)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from requests[socks]->gdown) (3.2.0)
Requirement already satisfied: idna<4,>=2.5 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from requests[socks]->gdown) (3.4)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from requests[socks]->gdown) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from requests[socks]->gdown) (2023.7.22)
Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages (from requests[socks]->gdown) (1.7.1)
Downloading...
From (uriginal): https://drive.google.com/uc?id=1Uds8mEvdGNYUuvFpTtCQ8gNU97bAPCaQ
From (redirected): https://drive.google.com/uc?id=1Uds8mEvdGNYUuvFpTtCQ8gNU97bAPCaQ&confirm=t&uuid=583abdda-51b2-449f-b609-992374b4ac1a
To: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/ai_spleen_seg_bundle_data.zip
100%|██████████████████████████████████████| 79.4M/79.4M [00:01<00:00, 56.1MB/s]
Archive:  ai_spleen_seg_bundle_data.zip
  inflating: dcm/1-001.dcm           
  inflating: dcm/1-002.dcm           
  inflating: dcm/1-003.dcm           
  inflating: dcm/1-004.dcm           
  inflating: dcm/1-005.dcm           
  inflating: dcm/1-006.dcm           
  inflating: dcm/1-007.dcm           
  inflating: dcm/1-008.dcm           
  inflating: dcm/1-009.dcm           
  inflating: dcm/1-010.dcm           
  inflating: dcm/1-011.dcm           
  inflating: dcm/1-012.dcm           
  inflating: dcm/1-013.dcm           
  inflating: dcm/1-014.dcm           
  inflating: dcm/1-015.dcm           
  inflating: dcm/1-016.dcm           
  inflating: dcm/1-017.dcm           
  inflating: dcm/1-018.dcm           
  inflating: dcm/1-019.dcm           
  inflating: dcm/1-020.dcm           
  inflating: dcm/1-021.dcm           
  inflating: dcm/1-022.dcm           
  inflating: dcm/1-023.dcm           
  inflating: dcm/1-024.dcm           
  inflating: dcm/1-025.dcm           
  inflating: dcm/1-026.dcm           
  inflating: dcm/1-027.dcm           
  inflating: dcm/1-028.dcm           
  inflating: dcm/1-029.dcm           
  inflating: dcm/1-030.dcm           
  inflating: dcm/1-031.dcm           
  inflating: dcm/1-032.dcm           
  inflating: dcm/1-033.dcm           
  inflating: dcm/1-034.dcm           
  inflating: dcm/1-035.dcm           
  inflating: dcm/1-036.dcm           
  inflating: dcm/1-037.dcm           
  inflating: dcm/1-038.dcm           
  inflating: dcm/1-039.dcm           
  inflating: dcm/1-040.dcm           
  inflating: dcm/1-041.dcm           
  inflating: dcm/1-042.dcm           
  inflating: dcm/1-043.dcm           
  inflating: dcm/1-044.dcm           
  inflating: dcm/1-045.dcm           
  inflating: dcm/1-046.dcm           
  inflating: dcm/1-047.dcm           
  inflating: dcm/1-048.dcm           
  inflating: dcm/1-049.dcm           
  inflating: dcm/1-050.dcm           
  inflating: dcm/1-051.dcm           
  inflating: dcm/1-052.dcm           
  inflating: dcm/1-053.dcm           
  inflating: dcm/1-054.dcm           
  inflating: dcm/1-055.dcm           
  inflating: dcm/1-056.dcm           
  inflating: dcm/1-057.dcm           
  inflating: dcm/1-058.dcm           
  inflating: dcm/1-059.dcm           
  inflating: dcm/1-060.dcm           
  inflating: dcm/1-061.dcm           
  inflating: dcm/1-062.dcm           
  inflating: dcm/1-063.dcm           
  inflating: dcm/1-064.dcm           
  inflating: dcm/1-065.dcm           
  inflating: dcm/1-066.dcm           
  inflating: dcm/1-067.dcm           
  inflating: dcm/1-068.dcm           
  inflating: dcm/1-069.dcm           
  inflating: dcm/1-070.dcm           
  inflating: dcm/1-071.dcm           
  inflating: dcm/1-072.dcm           
  inflating: dcm/1-073.dcm           
  inflating: dcm/1-074.dcm           
  inflating: dcm/1-075.dcm           
  inflating: dcm/1-076.dcm           
  inflating: dcm/1-077.dcm           
  inflating: dcm/1-078.dcm           
  inflating: dcm/1-079.dcm           
  inflating: dcm/1-080.dcm           
  inflating: dcm/1-081.dcm           
  inflating: dcm/1-082.dcm           
  inflating: dcm/1-083.dcm           
  inflating: dcm/1-084.dcm           
  inflating: dcm/1-085.dcm           
  inflating: dcm/1-086.dcm           
  inflating: dcm/1-087.dcm           
  inflating: dcm/1-088.dcm           
  inflating: dcm/1-089.dcm           
  inflating: dcm/1-090.dcm           
  inflating: dcm/1-091.dcm           
  inflating: dcm/1-092.dcm           
  inflating: dcm/1-093.dcm           
  inflating: dcm/1-094.dcm           
  inflating: dcm/1-095.dcm           
  inflating: dcm/1-096.dcm           
  inflating: dcm/1-097.dcm           
  inflating: dcm/1-098.dcm           
  inflating: dcm/1-099.dcm           
  inflating: dcm/1-100.dcm           
  inflating: dcm/1-101.dcm           
  inflating: dcm/1-102.dcm           
  inflating: dcm/1-103.dcm           
  inflating: dcm/1-104.dcm           
  inflating: dcm/1-105.dcm           
  inflating: dcm/1-106.dcm           
  inflating: dcm/1-107.dcm           
  inflating: dcm/1-108.dcm           
  inflating: dcm/1-109.dcm           
  inflating: dcm/1-110.dcm           
  inflating: dcm/1-111.dcm           
  inflating: dcm/1-112.dcm           
  inflating: dcm/1-113.dcm           
  inflating: dcm/1-114.dcm           
  inflating: dcm/1-115.dcm           
  inflating: dcm/1-116.dcm           
  inflating: dcm/1-117.dcm           
  inflating: dcm/1-118.dcm           
  inflating: dcm/1-119.dcm           
  inflating: dcm/1-120.dcm           
  inflating: dcm/1-121.dcm           
  inflating: dcm/1-122.dcm           
  inflating: dcm/1-123.dcm           
  inflating: dcm/1-124.dcm           
  inflating: dcm/1-125.dcm           
  inflating: dcm/1-126.dcm           
  inflating: dcm/1-127.dcm           
  inflating: dcm/1-128.dcm           
  inflating: dcm/1-129.dcm           
  inflating: dcm/1-130.dcm           
  inflating: dcm/1-131.dcm           
  inflating: dcm/1-132.dcm           
  inflating: dcm/1-133.dcm           
  inflating: dcm/1-134.dcm           
  inflating: dcm/1-135.dcm           
  inflating: dcm/1-136.dcm           
  inflating: dcm/1-137.dcm           
  inflating: dcm/1-138.dcm           
  inflating: dcm/1-139.dcm           
  inflating: dcm/1-140.dcm           
  inflating: dcm/1-141.dcm           
  inflating: dcm/1-142.dcm           
  inflating: dcm/1-143.dcm           
  inflating: dcm/1-144.dcm           
  inflating: dcm/1-145.dcm           
  inflating: dcm/1-146.dcm           
  inflating: dcm/1-147.dcm           
  inflating: dcm/1-148.dcm           
  inflating: dcm/1-149.dcm           
  inflating: dcm/1-150.dcm           
  inflating: dcm/1-151.dcm           
  inflating: dcm/1-152.dcm           
  inflating: dcm/1-153.dcm           
  inflating: dcm/1-154.dcm           
  inflating: dcm/1-155.dcm           
  inflating: dcm/1-156.dcm           
  inflating: dcm/1-157.dcm           
  inflating: dcm/1-158.dcm           
  inflating: dcm/1-159.dcm           
  inflating: dcm/1-160.dcm           
  inflating: dcm/1-161.dcm           
  inflating: dcm/1-162.dcm           
  inflating: dcm/1-163.dcm           
  inflating: dcm/1-164.dcm           
  inflating: dcm/1-165.dcm           
  inflating: dcm/1-166.dcm           
  inflating: dcm/1-167.dcm           
  inflating: dcm/1-168.dcm           
  inflating: dcm/1-169.dcm           
  inflating: dcm/1-170.dcm           
  inflating: dcm/1-171.dcm           
  inflating: dcm/1-172.dcm           
  inflating: dcm/1-173.dcm           
  inflating: dcm/1-174.dcm           
  inflating: dcm/1-175.dcm           
  inflating: dcm/1-176.dcm           
  inflating: dcm/1-177.dcm           
  inflating: dcm/1-178.dcm           
  inflating: dcm/1-179.dcm           
  inflating: dcm/1-180.dcm           
  inflating: dcm/1-181.dcm           
  inflating: dcm/1-182.dcm           
  inflating: dcm/1-183.dcm           
  inflating: dcm/1-184.dcm           
  inflating: dcm/1-185.dcm           
  inflating: dcm/1-186.dcm           
  inflating: dcm/1-187.dcm           
  inflating: dcm/1-188.dcm           
  inflating: dcm/1-189.dcm           
  inflating: dcm/1-190.dcm           
  inflating: dcm/1-191.dcm           
  inflating: dcm/1-192.dcm           
  inflating: dcm/1-193.dcm           
  inflating: dcm/1-194.dcm           
  inflating: dcm/1-195.dcm           
  inflating: dcm/1-196.dcm           
  inflating: dcm/1-197.dcm           
  inflating: dcm/1-198.dcm           
  inflating: dcm/1-199.dcm           
  inflating: dcm/1-200.dcm           
  inflating: dcm/1-201.dcm           
  inflating: dcm/1-202.dcm           
  inflating: dcm/1-203.dcm           
  inflating: dcm/1-204.dcm           
  inflating: model.ts                
model.ts
%env HOLOSCAN_INPUT_PATH dcm
%env HOLOSCAN_MODEL_PATH {models_folder}
%env HOLOSCAN_OUTPUT_PATH output
env: HOLOSCAN_INPUT_PATH=dcm
env: HOLOSCAN_MODEL_PATH=models
env: HOLOSCAN_OUTPUT_PATH=output

Setup imports

Let’s import necessary classes/decorators to define Application and Operator.

import logging
from numpy import uint8  # Needed if SaveImaged is enabled
from pathlib import Path

# 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.conditions import CountCondition
from monai.deploy.core import AppContext, Application, ConditionType, Fragment, Operator, OperatorSpec
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, 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_seg_inference_operator import InfererType, InMemImageReader, MonaiSegInferenceOperator

from monai.transforms import (
    Activationsd,
    AsDiscreted,
    Compose,
    EnsureChannelFirstd,
    EnsureTyped,
    Invertd,
    LoadImaged,
    Orientationd,
    SaveImaged,
    ScaleIntensityRanged,
    Spacingd,
)

Creating Model Specific Inference Operator classes

Each Operator class inherits the base Operator class. The input/output properties are specified by implementing the setup() method, and the business logic implemented in the compute() method.

The App SDK provides a MonaiSegInferenceOperator class to perform segmentation prediction with a Torch Script model. For consistency, this class uses MONAI dictionary-based transforms, as Compose object, for pre and post transforms. The model-specific inference operator will then only need to create the pre and post transform Compose based on what has been used in the model during training and validation. Note that for deploy application, ignite is not needed nor supported.

SpleenSegOperator

The SpleenSegOperator gets as input an in-memory Image object that has been converted from a DICOM CT series by the preceding DICOMSeriesToVolumeOperator, and as output in-memory segmentation Image object.

The pre_process function creates the pre-transforms Compose object. For LoadImage, a specialized InMemImageReader, derived from MONAI ImageReader, is used to convert the in-memory pixel data and return the numpy array as well as the meta-data. Also, the DICOM input pixel spacings are often not the same as expected by the model, so the Spacingd transform must be used to re-sample the image with the expected spacing.

The post_process function creates the post-transform Compose object. The SaveImageD transform class is used to save the segmentation mask as NIfTI image file, which is optional as the in-memory mask image will be passed down to the DICOM Segmentation writer for creating a DICOM Segmentation instance. The Invertd must also be used to revert the segmentation image’s orientation and spacing to be the same as the input.

When the MonaiSegInferenceOperator object is created, the ROI size is specified, as well as the transform Compose objects. Furthermore, the dataset image key names are set accordingly.

Loading of the model and performing the prediction are encapsulated in the MonaiSegInferenceOperator and other SDK classes. Once the inference is completed, the segmentation Image object is created and set to the output by the SpleenSegOperator.

class SpleenSegOperator(Operator):
    """Performs Spleen segmentation with a 3D image converted from a DICOM CT series.
    """

    DEFAULT_OUTPUT_FOLDER = Path.cwd() / "output/saved_images_folder"

    def __init__(
        self,
        fragment: Fragment,
        *args,
        app_context: AppContext,
        model_path: Path,
        output_folder: Path = DEFAULT_OUTPUT_FOLDER,
        **kwargs,
    ):

        self.logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
        self._input_dataset_key = "image"
        self._pred_dataset_key = "pred"

        self.model_path = model_path
        self.output_folder = output_folder
        self.output_folder.mkdir(parents=True, exist_ok=True)
        self.app_context = app_context
        self.input_name_image = "image"
        self.output_name_seg = "seg_image"
        self.output_name_saved_images_folder = "saved_images_folder"

        # The base class has an attribute called fragment to hold the reference to the fragment object
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input(self.input_name_image)
        spec.output(self.output_name_seg)
        spec.output(self.output_name_saved_images_folder).condition(
            ConditionType.NONE
        )  # Output not requiring a receiver

    def compute(self, op_input, op_output, context):
        input_image = op_input.receive(self.input_name_image)
        if not input_image:
            raise ValueError("Input image is not found.")

        # This operator gets an in-memory Image object, so a specialized ImageReader is needed.
        _reader = InMemImageReader(input_image)

        pre_transforms = self.pre_process(_reader, str(self.output_folder))
        post_transforms = self.post_process(pre_transforms, str(self.output_folder))

        # Delegates inference and saving output to the built-in operator.
        infer_operator = MonaiSegInferenceOperator(
            self.fragment,
            roi_size=(
                96,
                96,
                96,
            ),
            pre_transforms=pre_transforms,
            post_transforms=post_transforms,
            overlap=0.6,
            app_context=self.app_context,
            model_name="",
            inferer=InfererType.SLIDING_WINDOW,
            sw_batch_size=4,
            model_path=self.model_path,
            name="monai_seg_inference_op",
        )

        # Setting the keys used in the dictionary based transforms may change.
        infer_operator.input_dataset_key = self._input_dataset_key
        infer_operator.pred_dataset_key = self._pred_dataset_key

        # Now emit data to the output ports of this operator
        op_output.emit(infer_operator.compute_impl(input_image, context), self.output_name_seg)
        op_output.emit(self.output_folder, self.output_name_saved_images_folder)

    def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
        """Composes transforms for preprocessing input before predicting on a model."""

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        my_key = self._input_dataset_key

        return Compose(
            [
                LoadImaged(keys=my_key, reader=img_reader),
                EnsureChannelFirstd(keys=my_key),
                # The SaveImaged transform can be commented out to save 5 seconds.
                # Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
                SaveImaged(
                    keys=my_key,
                    output_dir=out_dir,
                    output_postfix="",
                    resample=False,
                    output_ext=".nii",
                ),
                Orientationd(keys=my_key, axcodes="RAS"),
                Spacingd(keys=my_key, pixdim=[1.5, 1.5, 2.9], mode=["bilinear"]),
                ScaleIntensityRanged(keys=my_key, a_min=-57, a_max=164, b_min=0.0, b_max=1.0, clip=True),
                EnsureTyped(keys=my_key),
            ]
        )

    def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_output") -> Compose:
        """Composes transforms for postprocessing the prediction results."""

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        pred_key = self._pred_dataset_key

        return Compose(
            [
                Activationsd(keys=pred_key, softmax=True),
                Invertd(
                    keys=pred_key,
                    transform=pre_transforms,
                    orig_keys=self._input_dataset_key,
                    nearest_interp=False,
                    to_tensor=True,
                ),
                AsDiscreted(keys=pred_key, argmax=True),
                # The SaveImaged transform can be commented out to save 5 seconds.
                # Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
                SaveImaged(
                    keys=pred_key,
                    output_dir=out_dir,
                    output_postfix="seg",
                    output_dtype=uint8,
                    resample=False,
                    output_ext=".nii",
                ),
            ]
        )

Creating Application class

Our application class would look like below.

It defines App class, inheriting the base Application class.

The base class method, compose, is overridden. Objects required for DICOM parsing, series selection, pixel data conversion to volume image, and segmentation instance creation are created, so is the model-specific SpleenSegOperator. The execution pipeline, as a Directed Acyclic Graph (DAG), is created by connecting these objects through the add_flow method.

class AISpleenSegApp(Application):
    def __init__(self, *args, **kwargs):
        """Creates an application instance."""

        super().__init__(*args, **kwargs)
        self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))

    def run(self, *args, **kwargs):
        # This method calls the base class to run. Can be omitted if simply calling through.
        self._logger.info(f"Begin {self.run.__name__}")
        super().run(*args, **kwargs)
        self._logger.info(f"End {self.run.__name__}")

    def compose(self):
        """Creates the app specific operators and chain them up in the processing DAG."""

        self._logger.debug(f"Begin {self.compose.__name__}")
        app_context = Application.init_app_context({})  # Do not pass argv in Jupyter Notebook
        app_input_path = Path(app_context.input_path)
        app_output_path = Path(app_context.output_path)
        model_path = Path(app_context.model_path)

        self._logger.info(f"App input and output path: {app_input_path}, {app_output_path}")

        # instantiates the SDK built-in operator(s).
        study_loader_op = DICOMDataLoaderOperator(
            self, CountCondition(self, 1), input_folder=app_input_path, name="dcm_loader_op"
        )
        series_selector_op = DICOMSeriesSelectorOperator(self, rules=Sample_Rules_Text, name="series_selector_op")
        series_to_vol_op = DICOMSeriesToVolumeOperator(self, name="series_to_vol_op")

        # Model specific inference operator, supporting MONAI transforms.
        spleen_seg_op = SpleenSegOperator(self, app_context=app_context, model_path=model_path, name="seg_op")

        # 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 Spleen from a CT series"
        _algorithm_family = codes.DCM.ArtificialIntelligence
        _algorithm_version = "0.1.0"

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

        custom_tags = {"SeriesDescription": "AI generated Seg, not for clinical use."}

        dicom_seg_writer = DICOMSegmentationWriterOperator(
            self,
            segment_descriptions=segment_descriptions,
            custom_tags=custom_tags,
            output_folder=app_output_path,
            name="dcm_seg_writer_op",
        )

        # 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, spleen_seg_op, {("image", "image")})

        # 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(spleen_seg_op, dicom_seg_writer, {("seg_image", "seg_image")})

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


# 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": {
                "StudyDescription": "(.*?)",
                "Modality": "(?i)CT",
                "SeriesDescription": "(.*?)",
                "ImageType": ["PRIMARY", "ORIGINAL"]
            }
        }
    ]
}
"""

Executing app locally

We can execute the app in Jupyter notebook. Note that the DICOM files of the CT Abdomen series must be present in the dcm folder and the TorchScript, model.ts, in the folder pointed to by the environment variables.

!rm -rf $HOLOSCAN_OUTPUT_PATH
app = AISpleenSegApp()
app.run()
[2023-08-30 00:58:15,457] [INFO] (root) - Parsed args: Namespace(argv=[], input=None, log_level=None, model=None, output=None, workdir=None)
[2023-08-30 00:58:15,465] [INFO] (root) - AppContext object: AppContext(input_path=dcm, output_path=output, model_path=models, workdir=)
[2023-08-30 00:58:15,466] [INFO] (__main__.AISpleenSegApp) - App input and output path: dcm, output
[info] [gxf_executor.cpp:210] Creating context
[info] [gxf_executor.cpp:1595] Loading extensions from configs...
[info] [gxf_executor.cpp:1741] Activating Graph...
[info] [gxf_executor.cpp:1771] Running Graph...
[info] [gxf_executor.cpp:1773] Waiting for completion...
[info] [gxf_executor.cpp:1774] Graph execution waiting. Fragment: 
[info] [greedy_scheduler.cpp:190] Scheduling 6 entities
[2023-08-30 00:58:15,557] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None
[2023-08-30 00:58:15,886] [INFO] (root) - Finding series for Selection named: CT Series
[2023-08-30 00:58:15,887] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291
  # of series: 1
[2023-08-30 00:58:15,888] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2023-08-30 00:58:15,889] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'
[2023-08-30 00:58:15,889] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST
[2023-08-30 00:58:15,890] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:15,891] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'
[2023-08-30 00:58:15,891] [INFO] (root) -     Series attribute Modality value: CT
[2023-08-30 00:58:15,892] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:15,892] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'
[2023-08-30 00:58:15,893] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f
[2023-08-30 00:58:15,893] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:15,894] [INFO] (root) - On attribute: 'ImageType' to match value: ['PRIMARY', 'ORIGINAL']
[2023-08-30 00:58:15,894] [INFO] (root) -     Series attribute ImageType value: None
[2023-08-30 00:58:15,895] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/monai/utils/deprecate_utils.py:321: FutureWarning: monai.transforms.io.dictionary LoadImaged.__init__:image_only: Current default value of argument `image_only=False` has been deprecated since version 1.1. It will be changed to `image_only=True` in version 1.3.
  warn_deprecated(argname, msg, warning_category)
[2023-08-30 00:58:16,110] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Converted Image object metadata:
[2023-08-30 00:58:16,111] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239, type <class 'str'>
[2023-08-30 00:58:16,112] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDate: 20090831, type <class 'str'>
[2023-08-30 00:58:16,112] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesTime: 101721.452, type <class 'str'>
[2023-08-30 00:58:16,113] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Modality: CT, type <class 'str'>
[2023-08-30 00:58:16,114] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDescription: ABD/PANC 3.0 B31f, type <class 'str'>
[2023-08-30 00:58:16,115] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - PatientPosition: HFS, type <class 'str'>
[2023-08-30 00:58:16,116] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesNumber: 8, type <class 'int'>
[2023-08-30 00:58:16,116] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_pixel_spacing: 0.7890625, type <class 'float'>
[2023-08-30 00:58:16,117] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_pixel_spacing: 0.7890625, type <class 'float'>
[2023-08-30 00:58:16,117] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_pixel_spacing: 1.5, type <class 'float'>
[2023-08-30 00:58:16,118] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_direction_cosine: [1.0, 0.0, 0.0], type <class 'list'>
[2023-08-30 00:58:16,119] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_direction_cosine: [0.0, 1.0, 0.0], type <class 'list'>
[2023-08-30 00:58:16,119] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_direction_cosine: [0.0, 0.0, 1.0], type <class 'list'>
[2023-08-30 00:58:16,120] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - dicom_affine_transform: [[   0.7890625    0.           0.        -197.60547  ]
 [   0.           0.7890625    0.        -398.60547  ]
 [   0.           0.           1.5       -383.       ]
 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>
[2023-08-30 00:58:16,121] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - nifti_affine_transform: [[  -0.7890625   -0.          -0.         197.60547  ]
 [  -0.          -0.7890625   -0.         398.60547  ]
 [   0.           0.           1.5       -383.       ]
 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>
[2023-08-30 00:58:16,122] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291, type <class 'str'>
[2023-08-30 00:58:16,123] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyID: , type <class 'str'>
[2023-08-30 00:58:16,123] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDate: 20090831, type <class 'str'>
[2023-08-30 00:58:16,124] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyTime: 095948.599, type <class 'str'>
[2023-08-30 00:58:16,124] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDescription: CT ABDOMEN W IV CONTRAST, type <class 'str'>
[2023-08-30 00:58:16,125] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - AccessionNumber: 5471978513296937, type <class 'str'>
[2023-08-30 00:58:16,125] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - selection_name: CT Series, type <class 'str'>
2023-08-30 00:58:16,933 INFO image_writer.py:197 - writing: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626.nii
2023-08-30 00:58:23,738 INFO image_writer.py:197 - writing: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626_seg.nii
[2023-08-30 00:58:25,340] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image numpy array shaped: (204, 512, 512)
[2023-08-30 00:58:25,348] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image pixel max value: 1
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/highdicom/valuerep.py:54: UserWarning: The string "C3N-00198" is unlikely to represent the intended person name since it contains only a single component. Construct a person name according to the format in described in http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#sect_6.2.1.2, or, in pydicom 2.2.0 or later, use the pydicom.valuerep.PersonName.from_named_components() method to construct the person name correctly. If a single-component name is really intended, add a trailing caret character to disambiguate the name.
  warnings.warn(
[2023-08-30 00:58:27,813] [INFO] (highdicom.seg.sop) - add plane #0 for segment #1
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR UL.
  warnings.warn(msg)
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR US.
  warnings.warn(msg)
[2023-08-30 00:58:27,816] [INFO] (highdicom.seg.sop) - add plane #1 for segment #1
[2023-08-30 00:58:27,818] [INFO] (highdicom.seg.sop) - add plane #2 for segment #1
[2023-08-30 00:58:27,819] [INFO] (highdicom.seg.sop) - add plane #3 for segment #1
[2023-08-30 00:58:27,820] [INFO] (highdicom.seg.sop) - add plane #4 for segment #1
[2023-08-30 00:58:27,821] [INFO] (highdicom.seg.sop) - add plane #5 for segment #1
[2023-08-30 00:58:27,822] [INFO] (highdicom.seg.sop) - add plane #6 for segment #1
[2023-08-30 00:58:27,823] [INFO] (highdicom.seg.sop) - add plane #7 for segment #1
[2023-08-30 00:58:27,825] [INFO] (highdicom.seg.sop) - add plane #8 for segment #1
[2023-08-30 00:58:27,826] [INFO] (highdicom.seg.sop) - add plane #9 for segment #1
[2023-08-30 00:58:27,827] [INFO] (highdicom.seg.sop) - add plane #10 for segment #1
[2023-08-30 00:58:27,828] [INFO] (highdicom.seg.sop) - add plane #11 for segment #1
[2023-08-30 00:58:27,829] [INFO] (highdicom.seg.sop) - add plane #12 for segment #1
[2023-08-30 00:58:27,830] [INFO] (highdicom.seg.sop) - add plane #13 for segment #1
[2023-08-30 00:58:27,833] [INFO] (highdicom.seg.sop) - add plane #14 for segment #1
[2023-08-30 00:58:27,834] [INFO] (highdicom.seg.sop) - add plane #15 for segment #1
[2023-08-30 00:58:27,836] [INFO] (highdicom.seg.sop) - add plane #16 for segment #1
[2023-08-30 00:58:27,837] [INFO] (highdicom.seg.sop) - add plane #17 for segment #1
[2023-08-30 00:58:27,838] [INFO] (highdicom.seg.sop) - add plane #18 for segment #1
[2023-08-30 00:58:27,839] [INFO] (highdicom.seg.sop) - add plane #19 for segment #1
[2023-08-30 00:58:27,841] [INFO] (highdicom.seg.sop) - add plane #20 for segment #1
[2023-08-30 00:58:27,842] [INFO] (highdicom.seg.sop) - add plane #21 for segment #1
[2023-08-30 00:58:27,843] [INFO] (highdicom.seg.sop) - add plane #22 for segment #1
[2023-08-30 00:58:27,845] [INFO] (highdicom.seg.sop) - add plane #23 for segment #1
[2023-08-30 00:58:27,846] [INFO] (highdicom.seg.sop) - add plane #24 for segment #1
[2023-08-30 00:58:27,848] [INFO] (highdicom.seg.sop) - add plane #25 for segment #1
[2023-08-30 00:58:27,849] [INFO] (highdicom.seg.sop) - add plane #26 for segment #1
[2023-08-30 00:58:27,850] [INFO] (highdicom.seg.sop) - add plane #27 for segment #1
[2023-08-30 00:58:27,851] [INFO] (highdicom.seg.sop) - add plane #28 for segment #1
[2023-08-30 00:58:27,853] [INFO] (highdicom.seg.sop) - add plane #29 for segment #1
[2023-08-30 00:58:27,854] [INFO] (highdicom.seg.sop) - add plane #30 for segment #1
[2023-08-30 00:58:27,856] [INFO] (highdicom.seg.sop) - add plane #31 for segment #1
[2023-08-30 00:58:27,857] [INFO] (highdicom.seg.sop) - add plane #32 for segment #1
[2023-08-30 00:58:27,858] [INFO] (highdicom.seg.sop) - add plane #33 for segment #1
[2023-08-30 00:58:27,860] [INFO] (highdicom.seg.sop) - add plane #34 for segment #1
[2023-08-30 00:58:27,861] [INFO] (highdicom.seg.sop) - add plane #35 for segment #1
[2023-08-30 00:58:27,863] [INFO] (highdicom.seg.sop) - add plane #36 for segment #1
[2023-08-30 00:58:27,864] [INFO] (highdicom.seg.sop) - add plane #37 for segment #1
[2023-08-30 00:58:27,866] [INFO] (highdicom.seg.sop) - add plane #38 for segment #1
[2023-08-30 00:58:27,867] [INFO] (highdicom.seg.sop) - add plane #39 for segment #1
[2023-08-30 00:58:27,869] [INFO] (highdicom.seg.sop) - add plane #40 for segment #1
[2023-08-30 00:58:27,871] [INFO] (highdicom.seg.sop) - add plane #41 for segment #1
[2023-08-30 00:58:27,872] [INFO] (highdicom.seg.sop) - add plane #42 for segment #1
[2023-08-30 00:58:27,874] [INFO] (highdicom.seg.sop) - add plane #43 for segment #1
[2023-08-30 00:58:27,875] [INFO] (highdicom.seg.sop) - add plane #44 for segment #1
[2023-08-30 00:58:27,877] [INFO] (highdicom.seg.sop) - add plane #45 for segment #1
[2023-08-30 00:58:27,879] [INFO] (highdicom.seg.sop) - add plane #46 for segment #1
[2023-08-30 00:58:27,881] [INFO] (highdicom.seg.sop) - add plane #47 for segment #1
[2023-08-30 00:58:27,882] [INFO] (highdicom.seg.sop) - add plane #48 for segment #1
[2023-08-30 00:58:27,884] [INFO] (highdicom.seg.sop) - add plane #49 for segment #1
[2023-08-30 00:58:27,886] [INFO] (highdicom.seg.sop) - add plane #50 for segment #1
[2023-08-30 00:58:27,888] [INFO] (highdicom.seg.sop) - add plane #51 for segment #1
[2023-08-30 00:58:27,890] [INFO] (highdicom.seg.sop) - add plane #52 for segment #1
[2023-08-30 00:58:27,892] [INFO] (highdicom.seg.sop) - add plane #53 for segment #1
[2023-08-30 00:58:27,894] [INFO] (highdicom.seg.sop) - add plane #54 for segment #1
[2023-08-30 00:58:27,896] [INFO] (highdicom.seg.sop) - add plane #55 for segment #1
[2023-08-30 00:58:27,898] [INFO] (highdicom.seg.sop) - add plane #56 for segment #1
[2023-08-30 00:58:27,900] [INFO] (highdicom.seg.sop) - add plane #57 for segment #1
[2023-08-30 00:58:27,903] [INFO] (highdicom.seg.sop) - add plane #58 for segment #1
[2023-08-30 00:58:27,908] [INFO] (highdicom.seg.sop) - add plane #59 for segment #1
[2023-08-30 00:58:27,912] [INFO] (highdicom.seg.sop) - add plane #60 for segment #1
[2023-08-30 00:58:27,916] [INFO] (highdicom.seg.sop) - add plane #61 for segment #1
[2023-08-30 00:58:27,918] [INFO] (highdicom.seg.sop) - add plane #62 for segment #1
[2023-08-30 00:58:27,921] [INFO] (highdicom.seg.sop) - add plane #63 for segment #1
[2023-08-30 00:58:27,923] [INFO] (highdicom.seg.sop) - add plane #64 for segment #1
[2023-08-30 00:58:27,925] [INFO] (highdicom.seg.sop) - add plane #65 for segment #1
[2023-08-30 00:58:27,927] [INFO] (highdicom.seg.sop) - add plane #66 for segment #1
[2023-08-30 00:58:27,929] [INFO] (highdicom.seg.sop) - add plane #67 for segment #1
[2023-08-30 00:58:27,931] [INFO] (highdicom.seg.sop) - add plane #68 for segment #1
[2023-08-30 00:58:27,934] [INFO] (highdicom.seg.sop) - add plane #69 for segment #1
[2023-08-30 00:58:27,936] [INFO] (highdicom.seg.sop) - add plane #70 for segment #1
[2023-08-30 00:58:27,938] [INFO] (highdicom.seg.sop) - add plane #71 for segment #1
[2023-08-30 00:58:27,940] [INFO] (highdicom.seg.sop) - add plane #72 for segment #1
[2023-08-30 00:58:27,942] [INFO] (highdicom.seg.sop) - add plane #73 for segment #1
[2023-08-30 00:58:27,944] [INFO] (highdicom.seg.sop) - add plane #74 for segment #1
[2023-08-30 00:58:27,946] [INFO] (highdicom.seg.sop) - add plane #75 for segment #1
[2023-08-30 00:58:27,948] [INFO] (highdicom.seg.sop) - add plane #76 for segment #1
[2023-08-30 00:58:27,950] [INFO] (highdicom.seg.sop) - add plane #77 for segment #1
[2023-08-30 00:58:27,952] [INFO] (highdicom.seg.sop) - add plane #78 for segment #1
[2023-08-30 00:58:27,956] [INFO] (highdicom.seg.sop) - add plane #79 for segment #1
[2023-08-30 00:58:27,957] [INFO] (highdicom.seg.sop) - add plane #80 for segment #1
[2023-08-30 00:58:27,959] [INFO] (highdicom.seg.sop) - add plane #81 for segment #1
[2023-08-30 00:58:27,960] [INFO] (highdicom.seg.sop) - add plane #82 for segment #1
[2023-08-30 00:58:27,962] [INFO] (highdicom.seg.sop) - add plane #83 for segment #1
[2023-08-30 00:58:27,964] [INFO] (highdicom.seg.sop) - add plane #84 for segment #1
[2023-08-30 00:58:27,965] [INFO] (highdicom.seg.sop) - add plane #85 for segment #1
[2023-08-30 00:58:27,967] [INFO] (highdicom.seg.sop) - add plane #86 for segment #1
[2023-08-30 00:58:27,968] [INFO] (highdicom.seg.sop) - add plane #87 for segment #1
[2023-08-30 00:58:28,028] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:28,029] [INFO] (highdicom.base) - copy attributes of module "Specimen"
[2023-08-30 00:58:28,030] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:28,031] [INFO] (highdicom.base) - copy attributes of module "Patient"
[2023-08-30 00:58:28,032] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"
[2023-08-30 00:58:28,033] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:28,034] [INFO] (highdicom.base) - copy attributes of module "General Study"
[2023-08-30 00:58:28,035] [INFO] (highdicom.base) - copy attributes of module "Patient Study"
[2023-08-30 00:58:28,036] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"
[info] [greedy_scheduler.cpp:369] Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.
[info] [greedy_scheduler.cpp:398] Scheduler finished.
[info] [gxf_executor.cpp:1783] Graph execution deactivating. Fragment: 
[info] [gxf_executor.cpp:1784] Deactivating Graph...
[2023-08-30 00:58:28,138] [INFO] (__main__.AISpleenSegApp) - End run
[info] [gxf_executor.cpp:1787] Graph execution finished. Fragment: 

Once the application is verified inside Jupyter notebook, we can write the above Python code into Python files in an application folder.

The application folder structure would look like below:

my_app
├── __main__.py
├── app.py
└── spleen_seg_operator.py

Note

We can create a single application Python file (such as spleen_app.py) that includes the content of the files, instead of creating multiple files. You will see such an example in MedNist Classifier Tutorial.

# Create an application folder
!mkdir -p my_app && rm -rf my_app/*

spleen_seg_operator.py

%%writefile my_app/spleen_seg_operator.py
import logging

from numpy import uint8
from pathlib import Path

from monai.deploy.core import AppContext, ConditionType, Fragment, Operator, OperatorSpec
from monai.deploy.operators.monai_seg_inference_operator import InfererType, InMemImageReader, MonaiSegInferenceOperator
from monai.transforms import (
    Activationsd,
    AsDiscreted,
    Compose,
    EnsureChannelFirstd,
    EnsureTyped,
    Invertd,
    LoadImaged,
    Orientationd,
    SaveImaged,
    ScaleIntensityRanged,
    Spacingd,
)

class SpleenSegOperator(Operator):
    """Performs Spleen segmentation with a 3D image converted from a DICOM CT series.
    """

    DEFAULT_OUTPUT_FOLDER = Path.cwd() / "output/saved_images_folder"

    def __init__(
        self,
        fragment: Fragment,
        *args,
        app_context: AppContext,
        model_path: Path,
        output_folder: Path = DEFAULT_OUTPUT_FOLDER,
        **kwargs,
    ):

        self.logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
        self._input_dataset_key = "image"
        self._pred_dataset_key = "pred"

        self.model_path = model_path
        self.output_folder = output_folder
        self.output_folder.mkdir(parents=True, exist_ok=True)
        self.app_context = app_context
        self.input_name_image = "image"
        self.output_name_seg = "seg_image"
        self.output_name_saved_images_folder = "saved_images_folder"

        # The base class has an attribute called fragment to hold the reference to the fragment object
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input(self.input_name_image)
        spec.output(self.output_name_seg)
        spec.output(self.output_name_saved_images_folder).condition(
            ConditionType.NONE
        )  # Output not requiring a receiver

    def compute(self, op_input, op_output, context):
        input_image = op_input.receive(self.input_name_image)
        if not input_image:
            raise ValueError("Input image is not found.")

        # This operator gets an in-memory Image object, so a specialized ImageReader is needed.
        _reader = InMemImageReader(input_image)

        pre_transforms = self.pre_process(_reader, str(self.output_folder))
        post_transforms = self.post_process(pre_transforms, str(self.output_folder))

        # Delegates inference and saving output to the built-in operator.
        infer_operator = MonaiSegInferenceOperator(
            self.fragment,
            roi_size=(
                96,
                96,
                96,
            ),
            pre_transforms=pre_transforms,
            post_transforms=post_transforms,
            overlap=0.6,
            app_context=self.app_context,
            model_name="",
            inferer=InfererType.SLIDING_WINDOW,
            sw_batch_size=4,
            model_path=self.model_path,
            name="monai_seg_inference_op",
        )

        # Setting the keys used in the dictionary based transforms may change.
        infer_operator.input_dataset_key = self._input_dataset_key
        infer_operator.pred_dataset_key = self._pred_dataset_key

        # Now emit data to the output ports of this operator
        op_output.emit(infer_operator.compute_impl(input_image, context), self.output_name_seg)
        op_output.emit(self.output_folder, self.output_name_saved_images_folder)

    def pre_process(self, img_reader, out_dir: str = "./input_images") -> Compose:
        """Composes transforms for preprocessing input before predicting on a model."""

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        my_key = self._input_dataset_key

        return Compose(
            [
                LoadImaged(keys=my_key, reader=img_reader),
                EnsureChannelFirstd(keys=my_key),
                # The SaveImaged transform can be commented out to save 5 seconds.
                # Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
                SaveImaged(
                    keys=my_key,
                    output_dir=out_dir,
                    output_postfix="",
                    resample=False,
                    output_ext=".nii",
                ),
                Orientationd(keys=my_key, axcodes="RAS"),
                Spacingd(keys=my_key, pixdim=[1.5, 1.5, 2.9], mode=["bilinear"]),
                ScaleIntensityRanged(keys=my_key, a_min=-57, a_max=164, b_min=0.0, b_max=1.0, clip=True),
                EnsureTyped(keys=my_key),
            ]
        )

    def post_process(self, pre_transforms: Compose, out_dir: str = "./prediction_output") -> Compose:
        """Composes transforms for postprocessing the prediction results."""

        Path(out_dir).mkdir(parents=True, exist_ok=True)
        pred_key = self._pred_dataset_key

        return Compose(
            [
                Activationsd(keys=pred_key, softmax=True),
                Invertd(
                    keys=pred_key,
                    transform=pre_transforms,
                    orig_keys=self._input_dataset_key,
                    nearest_interp=False,
                    to_tensor=True,
                ),
                AsDiscreted(keys=pred_key, argmax=True),
                # The SaveImaged transform can be commented out to save 5 seconds.
                # Uncompress NIfTI file, nii, is used favoring speed over size, but can be changed to nii.gz
                SaveImaged(
                    keys=pred_key,
                    output_dir=out_dir,
                    output_postfix="seg",
                    output_dtype=uint8,
                    resample=False,
                    output_ext=".nii",
                ),
            ]
        )
Writing my_app/spleen_seg_operator.py

app.py

%%writefile my_app/app.py
import logging
from pathlib import Path

from spleen_seg_operator import SpleenSegOperator

from pydicom.sr.codedict import codes  # Required for setting SegmentDescription attributes.

from monai.deploy.conditions import CountCondition
from monai.deploy.core import AppContext, Application
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.stl_conversion_operator import STLConversionOperator

class AISpleenSegApp(Application):
    def __init__(self, *args, **kwargs):
        """Creates an application instance."""

        super().__init__(*args, **kwargs)
        self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))

    def run(self, *args, **kwargs):
        # This method calls the base class to run. Can be omitted if simply calling through.
        self._logger.info(f"Begin {self.run.__name__}")
        super().run(*args, **kwargs)
        self._logger.info(f"End {self.run.__name__}")

    def compose(self):
        """Creates the app specific operators and chain them up in the processing DAG."""

        # Use Commandline options over environment variables to init context.
        app_context = Application.init_app_context(self.argv)
        self._logger.debug(f"Begin {self.compose.__name__}")
        app_input_path = Path(app_context.input_path)
        app_output_path = Path(app_context.output_path)
        model_path = Path(app_context.model_path)

        self._logger.info(f"App input and output path: {app_input_path}, {app_output_path}")

        # instantiates the SDK built-in operator(s).
        study_loader_op = DICOMDataLoaderOperator(
            self, CountCondition(self, 1), input_folder=app_input_path, name="dcm_loader_op"
        )
        series_selector_op = DICOMSeriesSelectorOperator(self, rules=Sample_Rules_Text, name="series_selector_op")
        series_to_vol_op = DICOMSeriesToVolumeOperator(self, name="series_to_vol_op")

        # Model specific inference operator, supporting MONAI transforms.
        spleen_seg_op = SpleenSegOperator(self, app_context=app_context, model_path=model_path, name="seg_op")

        # 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 Spleen from a CT series"
        _algorithm_family = codes.DCM.ArtificialIntelligence
        _algorithm_version = "0.1.0"

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

        custom_tags = {"SeriesDescription": "AI generated Seg, not for clinical use."}

        dicom_seg_writer = DICOMSegmentationWriterOperator(
            self,
            segment_descriptions=segment_descriptions,
            custom_tags=custom_tags,
            output_folder=app_output_path,
            name="dcm_seg_writer_op",
        )

        # 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, spleen_seg_op, {("image", "image")})

        # 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(spleen_seg_op, dicom_seg_writer, {("seg_image", "seg_image")})

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


# 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": {
                "StudyDescription": "(.*?)",
                "Modality": "(?i)CT",
                "SeriesDescription": "(.*?)",
                "ImageType": ["PRIMARY", "ORIGINAL"]
            }
        }
    ]
}
"""

if __name__ == "__main__":
    # Creates the app and test it standalone.
    AISpleenSegApp().run()
Writing my_app/app.py
if __name__ == "__main__":
    AISpleenSegApp().run()

The above lines are needed to execute the application code by using python interpreter.

__main__.py

__main__.py is needed for MONAI Application Packager to detect the main application code (app.py) when the application is executed with the application folder path (e.g., python simple_imaging_app).

%%writefile my_app/__main__.py
from app import AISpleenSegApp

if __name__ == "__main__":
    AISpleenSegApp().run()
Writing my_app/__main__.py
!ls my_app
app.py	__main__.py  spleen_seg_operator.py

In this time, let’s execute the app in the command line.

Note

Since the environment variables have been set and contain the correct paths, it is not necessary to provide the command line options on running the application.

!rm -rf $HOLOSCAN_OUTPUT_PATH
!python my_app
[2023-08-30 00:58:35,056] [INFO] (root) - Parsed args: Namespace(argv=['my_app'], input=None, log_level=None, model=None, output=None, workdir=None)
[2023-08-30 00:58:35,058] [INFO] (root) - AppContext object: AppContext(input_path=dcm, output_path=output, model_path=models, workdir=)
[2023-08-30 00:58:35,058] [INFO] (app.AISpleenSegApp) - App input and output path: dcm, output
[info] [gxf_executor.cpp:210] Creating context
[info] [gxf_executor.cpp:1595] Loading extensions from configs...
[info] [gxf_executor.cpp:1741] Activating Graph...
[info] [gxf_executor.cpp:1771] Running Graph...
[info] [gxf_executor.cpp:1773] Waiting for completion...
[info] [gxf_executor.cpp:1774] Graph execution waiting. Fragment: 
[info] [greedy_scheduler.cpp:190] Scheduling 6 entities
[2023-08-30 00:58:35,114] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None
[2023-08-30 00:58:35,624] [INFO] (root) - Finding series for Selection named: CT Series
[2023-08-30 00:58:35,624] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291
  # of series: 1
[2023-08-30 00:58:35,624] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2023-08-30 00:58:35,625] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'
[2023-08-30 00:58:35,625] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST
[2023-08-30 00:58:35,625] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:35,625] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'
[2023-08-30 00:58:35,625] [INFO] (root) -     Series attribute Modality value: CT
[2023-08-30 00:58:35,625] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:35,625] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'
[2023-08-30 00:58:35,625] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f
[2023-08-30 00:58:35,625] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2023-08-30 00:58:35,625] [INFO] (root) - On attribute: 'ImageType' to match value: ['PRIMARY', 'ORIGINAL']
[2023-08-30 00:58:35,625] [INFO] (root) -     Series attribute ImageType value: None
[2023-08-30 00:58:35,625] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/monai/utils/deprecate_utils.py:321: FutureWarning: monai.transforms.io.dictionary LoadImaged.__init__:image_only: Current default value of argument `image_only=False` has been deprecated since version 1.1. It will be changed to `image_only=True` in version 1.3.
  warn_deprecated(argname, msg, warning_category)
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Converted Image object metadata:
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDate: 20090831, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesTime: 101721.452, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Modality: CT, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDescription: ABD/PANC 3.0 B31f, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - PatientPosition: HFS, type <class 'str'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesNumber: 8, type <class 'int'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_pixel_spacing: 0.7890625, type <class 'float'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_pixel_spacing: 0.7890625, type <class 'float'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_pixel_spacing: 1.5, type <class 'float'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_direction_cosine: [1.0, 0.0, 0.0], type <class 'list'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_direction_cosine: [0.0, 1.0, 0.0], type <class 'list'>
[2023-08-30 00:58:35,841] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_direction_cosine: [0.0, 0.0, 1.0], type <class 'list'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - dicom_affine_transform: [[   0.7890625    0.           0.        -197.60547  ]
 [   0.           0.7890625    0.        -398.60547  ]
 [   0.           0.           1.5       -383.       ]
 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - nifti_affine_transform: [[  -0.7890625   -0.          -0.         197.60547  ]
 [  -0.          -0.7890625   -0.         398.60547  ]
 [   0.           0.           1.5       -383.       ]
 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291, type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyID: , type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDate: 20090831, type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyTime: 095948.599, type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDescription: CT ABDOMEN W IV CONTRAST, type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - AccessionNumber: 5471978513296937, type <class 'str'>
[2023-08-30 00:58:35,842] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - selection_name: CT Series, type <class 'str'>
2023-08-30 00:58:36,678 INFO image_writer.py:197 - writing: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626.nii
2023-08-30 00:58:42,872 INFO image_writer.py:197 - writing: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626_seg.nii
[2023-08-30 00:58:44,629] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image numpy array shaped: (204, 512, 512)
[2023-08-30 00:58:44,635] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image pixel max value: 1
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/highdicom/valuerep.py:54: UserWarning: The string "C3N-00198" is unlikely to represent the intended person name since it contains only a single component. Construct a person name according to the format in described in http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#sect_6.2.1.2, or, in pydicom 2.2.0 or later, use the pydicom.valuerep.PersonName.from_named_components() method to construct the person name correctly. If a single-component name is really intended, add a trailing caret character to disambiguate the name.
  warnings.warn(
[2023-08-30 00:58:47,369] [INFO] (highdicom.seg.sop) - add plane #0 for segment #1
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR UL.
  warnings.warn(msg)
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR US.
  warnings.warn(msg)
[2023-08-30 00:58:47,371] [INFO] (highdicom.seg.sop) - add plane #1 for segment #1
[2023-08-30 00:58:47,372] [INFO] (highdicom.seg.sop) - add plane #2 for segment #1
[2023-08-30 00:58:47,373] [INFO] (highdicom.seg.sop) - add plane #3 for segment #1
[2023-08-30 00:58:47,374] [INFO] (highdicom.seg.sop) - add plane #4 for segment #1
[2023-08-30 00:58:47,374] [INFO] (highdicom.seg.sop) - add plane #5 for segment #1
[2023-08-30 00:58:47,375] [INFO] (highdicom.seg.sop) - add plane #6 for segment #1
[2023-08-30 00:58:47,376] [INFO] (highdicom.seg.sop) - add plane #7 for segment #1
[2023-08-30 00:58:47,377] [INFO] (highdicom.seg.sop) - add plane #8 for segment #1
[2023-08-30 00:58:47,378] [INFO] (highdicom.seg.sop) - add plane #9 for segment #1
[2023-08-30 00:58:47,378] [INFO] (highdicom.seg.sop) - add plane #10 for segment #1
[2023-08-30 00:58:47,379] [INFO] (highdicom.seg.sop) - add plane #11 for segment #1
[2023-08-30 00:58:47,380] [INFO] (highdicom.seg.sop) - add plane #12 for segment #1
[2023-08-30 00:58:47,381] [INFO] (highdicom.seg.sop) - add plane #13 for segment #1
[2023-08-30 00:58:47,382] [INFO] (highdicom.seg.sop) - add plane #14 for segment #1
[2023-08-30 00:58:47,382] [INFO] (highdicom.seg.sop) - add plane #15 for segment #1
[2023-08-30 00:58:47,383] [INFO] (highdicom.seg.sop) - add plane #16 for segment #1
[2023-08-30 00:58:47,384] [INFO] (highdicom.seg.sop) - add plane #17 for segment #1
[2023-08-30 00:58:47,385] [INFO] (highdicom.seg.sop) - add plane #18 for segment #1
[2023-08-30 00:58:47,385] [INFO] (highdicom.seg.sop) - add plane #19 for segment #1
[2023-08-30 00:58:47,386] [INFO] (highdicom.seg.sop) - add plane #20 for segment #1
[2023-08-30 00:58:47,387] [INFO] (highdicom.seg.sop) - add plane #21 for segment #1
[2023-08-30 00:58:47,387] [INFO] (highdicom.seg.sop) - add plane #22 for segment #1
[2023-08-30 00:58:47,388] [INFO] (highdicom.seg.sop) - add plane #23 for segment #1
[2023-08-30 00:58:47,389] [INFO] (highdicom.seg.sop) - add plane #24 for segment #1
[2023-08-30 00:58:47,390] [INFO] (highdicom.seg.sop) - add plane #25 for segment #1
[2023-08-30 00:58:47,390] [INFO] (highdicom.seg.sop) - add plane #26 for segment #1
[2023-08-30 00:58:47,391] [INFO] (highdicom.seg.sop) - add plane #27 for segment #1
[2023-08-30 00:58:47,392] [INFO] (highdicom.seg.sop) - add plane #28 for segment #1
[2023-08-30 00:58:47,393] [INFO] (highdicom.seg.sop) - add plane #29 for segment #1
[2023-08-30 00:58:47,393] [INFO] (highdicom.seg.sop) - add plane #30 for segment #1
[2023-08-30 00:58:47,394] [INFO] (highdicom.seg.sop) - add plane #31 for segment #1
[2023-08-30 00:58:47,395] [INFO] (highdicom.seg.sop) - add plane #32 for segment #1
[2023-08-30 00:58:47,396] [INFO] (highdicom.seg.sop) - add plane #33 for segment #1
[2023-08-30 00:58:47,397] [INFO] (highdicom.seg.sop) - add plane #34 for segment #1
[2023-08-30 00:58:47,398] [INFO] (highdicom.seg.sop) - add plane #35 for segment #1
[2023-08-30 00:58:47,399] [INFO] (highdicom.seg.sop) - add plane #36 for segment #1
[2023-08-30 00:58:47,400] [INFO] (highdicom.seg.sop) - add plane #37 for segment #1
[2023-08-30 00:58:47,401] [INFO] (highdicom.seg.sop) - add plane #38 for segment #1
[2023-08-30 00:58:47,401] [INFO] (highdicom.seg.sop) - add plane #39 for segment #1
[2023-08-30 00:58:47,402] [INFO] (highdicom.seg.sop) - add plane #40 for segment #1
[2023-08-30 00:58:47,403] [INFO] (highdicom.seg.sop) - add plane #41 for segment #1
[2023-08-30 00:58:47,403] [INFO] (highdicom.seg.sop) - add plane #42 for segment #1
[2023-08-30 00:58:47,404] [INFO] (highdicom.seg.sop) - add plane #43 for segment #1
[2023-08-30 00:58:47,404] [INFO] (highdicom.seg.sop) - add plane #44 for segment #1
[2023-08-30 00:58:47,405] [INFO] (highdicom.seg.sop) - add plane #45 for segment #1
[2023-08-30 00:58:47,405] [INFO] (highdicom.seg.sop) - add plane #46 for segment #1
[2023-08-30 00:58:47,406] [INFO] (highdicom.seg.sop) - add plane #47 for segment #1
[2023-08-30 00:58:47,407] [INFO] (highdicom.seg.sop) - add plane #48 for segment #1
[2023-08-30 00:58:47,407] [INFO] (highdicom.seg.sop) - add plane #49 for segment #1
[2023-08-30 00:58:47,408] [INFO] (highdicom.seg.sop) - add plane #50 for segment #1
[2023-08-30 00:58:47,409] [INFO] (highdicom.seg.sop) - add plane #51 for segment #1
[2023-08-30 00:58:47,410] [INFO] (highdicom.seg.sop) - add plane #52 for segment #1
[2023-08-30 00:58:47,410] [INFO] (highdicom.seg.sop) - add plane #53 for segment #1
[2023-08-30 00:58:47,411] [INFO] (highdicom.seg.sop) - add plane #54 for segment #1
[2023-08-30 00:58:47,411] [INFO] (highdicom.seg.sop) - add plane #55 for segment #1
[2023-08-30 00:58:47,412] [INFO] (highdicom.seg.sop) - add plane #56 for segment #1
[2023-08-30 00:58:47,413] [INFO] (highdicom.seg.sop) - add plane #57 for segment #1
[2023-08-30 00:58:47,413] [INFO] (highdicom.seg.sop) - add plane #58 for segment #1
[2023-08-30 00:58:47,414] [INFO] (highdicom.seg.sop) - add plane #59 for segment #1
[2023-08-30 00:58:47,414] [INFO] (highdicom.seg.sop) - add plane #60 for segment #1
[2023-08-30 00:58:47,415] [INFO] (highdicom.seg.sop) - add plane #61 for segment #1
[2023-08-30 00:58:47,415] [INFO] (highdicom.seg.sop) - add plane #62 for segment #1
[2023-08-30 00:58:47,416] [INFO] (highdicom.seg.sop) - add plane #63 for segment #1
[2023-08-30 00:58:47,417] [INFO] (highdicom.seg.sop) - add plane #64 for segment #1
[2023-08-30 00:58:47,417] [INFO] (highdicom.seg.sop) - add plane #65 for segment #1
[2023-08-30 00:58:47,418] [INFO] (highdicom.seg.sop) - add plane #66 for segment #1
[2023-08-30 00:58:47,418] [INFO] (highdicom.seg.sop) - add plane #67 for segment #1
[2023-08-30 00:58:47,419] [INFO] (highdicom.seg.sop) - add plane #68 for segment #1
[2023-08-30 00:58:47,420] [INFO] (highdicom.seg.sop) - add plane #69 for segment #1
[2023-08-30 00:58:47,420] [INFO] (highdicom.seg.sop) - add plane #70 for segment #1
[2023-08-30 00:58:47,421] [INFO] (highdicom.seg.sop) - add plane #71 for segment #1
[2023-08-30 00:58:47,421] [INFO] (highdicom.seg.sop) - add plane #72 for segment #1
[2023-08-30 00:58:47,422] [INFO] (highdicom.seg.sop) - add plane #73 for segment #1
[2023-08-30 00:58:47,423] [INFO] (highdicom.seg.sop) - add plane #74 for segment #1
[2023-08-30 00:58:47,423] [INFO] (highdicom.seg.sop) - add plane #75 for segment #1
[2023-08-30 00:58:47,424] [INFO] (highdicom.seg.sop) - add plane #76 for segment #1
[2023-08-30 00:58:47,424] [INFO] (highdicom.seg.sop) - add plane #77 for segment #1
[2023-08-30 00:58:47,425] [INFO] (highdicom.seg.sop) - add plane #78 for segment #1
[2023-08-30 00:58:47,426] [INFO] (highdicom.seg.sop) - add plane #79 for segment #1
[2023-08-30 00:58:47,426] [INFO] (highdicom.seg.sop) - add plane #80 for segment #1
[2023-08-30 00:58:47,427] [INFO] (highdicom.seg.sop) - add plane #81 for segment #1
[2023-08-30 00:58:47,428] [INFO] (highdicom.seg.sop) - add plane #82 for segment #1
[2023-08-30 00:58:47,429] [INFO] (highdicom.seg.sop) - add plane #83 for segment #1
[2023-08-30 00:58:47,430] [INFO] (highdicom.seg.sop) - add plane #84 for segment #1
[2023-08-30 00:58:47,430] [INFO] (highdicom.seg.sop) - add plane #85 for segment #1
[2023-08-30 00:58:47,431] [INFO] (highdicom.seg.sop) - add plane #86 for segment #1
[2023-08-30 00:58:47,432] [INFO] (highdicom.seg.sop) - add plane #87 for segment #1
[2023-08-30 00:58:47,528] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:47,528] [INFO] (highdicom.base) - copy attributes of module "Specimen"
[2023-08-30 00:58:47,528] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:47,528] [INFO] (highdicom.base) - copy attributes of module "Patient"
[2023-08-30 00:58:47,529] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"
[2023-08-30 00:58:47,529] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2023-08-30 00:58:47,529] [INFO] (highdicom.base) - copy attributes of module "General Study"
[2023-08-30 00:58:47,529] [INFO] (highdicom.base) - copy attributes of module "Patient Study"
[2023-08-30 00:58:47,529] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"
[info] [greedy_scheduler.cpp:369] Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.
[info] [greedy_scheduler.cpp:398] Scheduler finished.
[info] [gxf_executor.cpp:1783] Graph execution deactivating. Fragment: 
[info] [gxf_executor.cpp:1784] Deactivating Graph...
[info] [gxf_executor.cpp:1787] Graph execution finished. Fragment: 
[2023-08-30 00:58:47,626] [INFO] (app.AISpleenSegApp) - End run
[info] [gxf_executor.cpp:229] Destroying context
!ls -R $HOLOSCAN_OUTPUT_PATH
output:
1.2.826.0.1.3680043.10.511.3.70000117896150756142576971005940679.dcm
saved_images_folder

output/saved_images_folder:
1.3.6.1.4.1.14519.5.2.1.7085.2626

output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626:
1.3.6.1.4.1.14519.5.2.1.7085.2626.nii
1.3.6.1.4.1.14519.5.2.1.7085.2626_seg.nii

Packaging app

Let’s package the app with MONAI Application Packager.

In this version of the App SDK, we need to write out the configuration yaml file as well as the package requirements file, in the application folder.

%%writefile my_app/app.yaml
%YAML 1.2
---
application:
  title: MONAI Deploy App Package - MONAI Bundle AI App
  version: 1.0
  inputFormats: ["file"]
  outputFormats: ["file"]

resources:
  cpu: 1
  gpu: 1
  memory: 1Gi
  gpuMemory: 6Gi
Writing my_app/app.yaml
%%writefile my_app/requirements.txt
highdicom>=0.18.2
monai>=1.0
nibabel>=3.2.1
numpy>=1.21.6
pydicom>=2.3.0
setuptools>=59.5.0 # for pkg_resources
SimpleITK>=2.0.0
torch>=1.12.0
Writing my_app/requirements.txt

Now we can use the CLI package command to build the MONAI Application Package (MAP) container image based on a supported base image.

Note

Building a MONAI Application Package (Docker image) can take time. Use -l DEBUG option to see the progress.

tag_prefix = "my_app"

!monai-deploy package my_app -m {models_folder} -c my_app/app.yaml -t {tag_prefix}:1.0 --platform x64-workstation -l DEBUG
[2023-08-30 00:59:31,113] [INFO] (packager.parameters) - Application: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/my_app
[2023-08-30 00:59:31,114] [INFO] (packager.parameters) - Detected application type: Python Module
[2023-08-30 00:59:31,114] [INFO] (packager) - Scanning for models in {models_path}...
[2023-08-30 00:59:31,115] [DEBUG] (packager) - Model model=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models/model added.
[2023-08-30 00:59:31,115] [INFO] (packager) - Reading application configuration from /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/my_app/app.yaml...
[2023-08-30 00:59:31,117] [INFO] (packager) - Generating app.json...
[2023-08-30 00:59:31,117] [INFO] (packager) - Generating pkg.json...
[2023-08-30 00:59:31,118] [DEBUG] (common) - 
=============== Begin app.json ===============
{
    "apiVersion": "1.0.0",
    "command": "[\"python3\", \"/opt/holoscan/app\"]",
    "environment": {
        "HOLOSCAN_APPLICATION": "/opt/holoscan/app",
        "HOLOSCAN_INPUT_PATH": "input/",
        "HOLOSCAN_OUTPUT_PATH": "output/",
        "HOLOSCAN_WORKDIR": "/var/holoscan",
        "HOLOSCAN_MODEL_PATH": "/opt/holoscan/models",
        "HOLOSCAN_CONFIG_PATH": "/var/holoscan/app.yaml",
        "HOLOSCAN_APP_MANIFEST_PATH": "/etc/holoscan/app.json",
        "HOLOSCAN_PKG_MANIFEST_PATH": "/etc/holoscan/pkg.json",
        "HOLOSCAN_DOCS_PATH": "/opt/holoscan/docs",
        "HOLOSCAN_LOGS_PATH": "/var/holoscan/logs"
    },
    "input": {
        "path": "input/",
        "formats": null
    },
    "liveness": null,
    "output": {
        "path": "output/",
        "formats": null
    },
    "readiness": null,
    "sdk": "monai-deploy",
    "sdkVersion": "0.6.0",
    "timeout": 0,
    "version": 1.0,
    "workingDirectory": "/var/holoscan"
}
================ End app.json ================
                 
[2023-08-30 00:59:31,118] [DEBUG] (common) - 
=============== Begin pkg.json ===============
{
    "apiVersion": "1.0.0",
    "applicationRoot": "/opt/holoscan/app",
    "modelRoot": "/opt/holoscan/models",
    "models": {
        "model": "/opt/holoscan/models"
    },
    "resources": {
        "cpu": 1,
        "gpu": 1,
        "memory": "1Gi",
        "gpuMemory": "6Gi"
    },
    "version": 1.0
}
================ End pkg.json ================
                 
[2023-08-30 00:59:31,179] [DEBUG] (packager.builder) - 
========== Begin Dockerfile ==========


FROM nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu

ENV DEBIAN_FRONTEND=noninteractive
ENV TERM=xterm-256color

ARG UNAME
ARG UID
ARG GID

RUN mkdir -p /etc/holoscan/ \
        && mkdir -p /opt/holoscan/ \
        && mkdir -p /var/holoscan \
        && mkdir -p /opt/holoscan/app \
        && mkdir -p /var/holoscan/input \
        && mkdir -p /var/holoscan/output

LABEL base="nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu"
LABEL tag="my_app:1.0"
LABEL org.opencontainers.image.title="MONAI Deploy App Package - MONAI Bundle AI App"
LABEL org.opencontainers.image.version="1.0"
LABEL org.nvidia.holoscan="0.6.0"

ENV HOLOSCAN_ENABLE_HEALTH_CHECK=true
ENV HOLOSCAN_INPUT_PATH=/var/holoscan/input
ENV HOLOSCAN_OUTPUT_PATH=/var/holoscan/output
ENV HOLOSCAN_WORKDIR=/var/holoscan
ENV HOLOSCAN_APPLICATION=/opt/holoscan/app
ENV HOLOSCAN_TIMEOUT=0
ENV HOLOSCAN_MODEL_PATH=/opt/holoscan/models
ENV HOLOSCAN_DOCS_PATH=/opt/holoscan/docs
ENV HOLOSCAN_CONFIG_PATH=/var/holoscan/app.yaml
ENV HOLOSCAN_APP_MANIFEST_PATH=/etc/holoscan/app.json
ENV HOLOSCAN_PKG_MANIFEST_PATH=/etc/holoscan/pkg.json
ENV HOLOSCAN_LOGS_PATH=/var/holoscan/logs
ENV PATH=/root/.local/bin:/opt/nvidia/holoscan:$PATH
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/libtorch/1.13.1/lib/:/opt/nvidia/holoscan/lib

RUN apt-get update \
    && apt-get install -y curl jq \
    && rm -rf /var/lib/apt/lists/*

ENV PYTHONPATH="/opt/holoscan/app:$PYTHONPATH"



RUN groupadd -g $GID $UNAME
RUN useradd -rm -d /home/$UNAME -s /bin/bash -g $GID -G sudo -u $UID $UNAME
RUN chown -R holoscan /var/holoscan 
RUN chown -R holoscan /var/holoscan/input 
RUN chown -R holoscan /var/holoscan/output 

# Set the working directory
WORKDIR /var/holoscan

# Copy HAP/MAP tool script
COPY ./tools /var/holoscan/tools
RUN chmod +x /var/holoscan/tools


# Copy gRPC health probe

USER $UNAME

ENV PATH=/root/.local/bin:/home/holoscan/.local/bin:/opt/nvidia/holoscan:$PATH

COPY ./pip/requirements.txt /tmp/requirements.txt

RUN pip install --upgrade pip
RUN pip install --no-cache-dir --user -r /tmp/requirements.txt

# Install Holoscan from PyPI org
RUN pip install holoscan==0.6.0


# Copy user-specified MONAI Deploy SDK file
COPY ./monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl /tmp/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl
RUN pip install /tmp/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl




COPY ./models  /opt/holoscan/models

COPY ./map/app.json /etc/holoscan/app.json
COPY ./app.config /var/holoscan/app.yaml
COPY ./map/pkg.json /etc/holoscan/pkg.json

COPY ./app /opt/holoscan/app

ENTRYPOINT ["/var/holoscan/tools"]
=========== End Dockerfile ===========

[2023-08-30 00:59:31,179] [INFO] (packager.builder) - 
===============================================================================
Building image for:                 x64-workstation
    Architecture:                   linux/amd64
    Base Image:                     nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu
    Build Image:                    N/A  
    Cache:                          Enabled
    Configuration:                  dgpu
    Holoiscan SDK Package:          pypi.org
    MONAI Deploy App SDK Package:   /home/mqin/src/monai-deploy-app-sdk/dist/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl
    gRPC Health Probe:              N/A
    SDK Version:                    0.6.0
    SDK:                            monai-deploy
    Tag:                            my_app-x64-workstation-dgpu-linux-amd64:1.0
    
[2023-08-30 00:59:31,807] [INFO] (common) - Using existing Docker BuildKit builder `holoscan_app_builder`
[2023-08-30 00:59:31,808] [DEBUG] (packager.builder) - Building Holoscan Application Package: tag=my_app-x64-workstation-dgpu-linux-amd64:1.0
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.67kB done
#1 DONE 0.1s

#2 [internal] load .dockerignore
#2 transferring context: 1.79kB 0.0s done
#2 DONE 0.1s

#3 [internal] load metadata for nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu
#3 DONE 0.6s

#4 [internal] load build context
#4 DONE 0.0s

#5 importing cache manifest from local:8636426000862419753
#5 DONE 0.0s

#6 [ 1/22] FROM nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu@sha256:9653f80f241fd542f25afbcbcf7a0d02ed7e5941c79763e69def5b1e6d9fb7bc
#6 resolve nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu@sha256:9653f80f241fd542f25afbcbcf7a0d02ed7e5941c79763e69def5b1e6d9fb7bc 0.0s done
#6 DONE 0.0s

#7 importing cache manifest from nvcr.io/nvidia/clara-holoscan/holoscan:v0.6.0-dgpu
#7 DONE 0.9s

#4 [internal] load build context
#4 transferring context: 19.57MB 0.1s done
#4 DONE 0.2s

#8 [ 7/22] RUN chown -R holoscan /var/holoscan/input
#8 CACHED

#9 [12/22] COPY ./pip/requirements.txt /tmp/requirements.txt
#9 CACHED

#10 [10/22] COPY ./tools /var/holoscan/tools
#10 CACHED

#11 [14/22] RUN pip install --no-cache-dir --user -r /tmp/requirements.txt
#11 CACHED

#12 [ 5/22] RUN useradd -rm -d /home/holoscan -s /bin/bash -g 1000 -G sudo -u 1000 holoscan
#12 CACHED

#13 [13/22] RUN pip install --upgrade pip
#13 CACHED

#14 [ 6/22] RUN chown -R holoscan /var/holoscan
#14 CACHED

#15 [ 9/22] WORKDIR /var/holoscan
#15 CACHED

#16 [ 4/22] RUN groupadd -g 1000 holoscan
#16 CACHED

#17 [ 3/22] RUN apt-get update     && apt-get install -y curl jq     && rm -rf /var/lib/apt/lists/*
#17 CACHED

#18 [ 8/22] RUN chown -R holoscan /var/holoscan/output
#18 CACHED

#19 [ 2/22] RUN mkdir -p /etc/holoscan/         && mkdir -p /opt/holoscan/         && mkdir -p /var/holoscan         && mkdir -p /opt/holoscan/app         && mkdir -p /var/holoscan/input         && mkdir -p /var/holoscan/output
#19 CACHED

#20 [11/22] RUN chmod +x /var/holoscan/tools
#20 CACHED

#21 [15/22] RUN pip install holoscan==0.6.0
#21 CACHED

#22 [16/22] COPY ./monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl /tmp/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl
#22 DONE 0.3s

#23 [17/22] RUN pip install /tmp/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl
#23 0.878 Defaulting to user installation because normal site-packages is not writeable
#23 0.949 Processing /tmp/monai_deploy_app_sdk-0.5.1+22.g029f8bc.dirty-py3-none-any.whl
#23 1.347 Collecting numpy>=1.21.6 (from monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.347   Obtaining dependency information for numpy>=1.21.6 from https://files.pythonhosted.org/packages/98/5d/5738903efe0ecb73e51eb44feafba32bdba2081263d40c5043568ff60faf/numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
#23 1.394   Downloading numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.6 kB)
#23 1.409 Requirement already satisfied: networkx>=2.4 in /home/holoscan/.local/lib/python3.8/site-packages (from monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (3.1)
#23 1.411 Requirement already satisfied: holoscan>=0.5.0 in /home/holoscan/.local/lib/python3.8/site-packages (from monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (0.6.0)
#23 1.475 Collecting colorama>=0.4.1 (from monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.484   Downloading colorama-0.4.6-py2.py3-none-any.whl (25 kB)
#23 1.557 Collecting typeguard>=3.0.0 (from monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.557   Obtaining dependency information for typeguard>=3.0.0 from https://files.pythonhosted.org/packages/84/99/bfa960dcc0386e240f823f7f4b1b028a18126a72216febf892f84b872444/typeguard-4.1.3-py3-none-any.whl.metadata
#23 1.566   Downloading typeguard-4.1.3-py3-none-any.whl.metadata (3.7 kB)
#23 1.634 Collecting cloudpickle~=2.2 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.642   Downloading cloudpickle-2.2.1-py3-none-any.whl (25 kB)
#23 1.738 Collecting python-on-whales~=0.60 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.738   Obtaining dependency information for python-on-whales~=0.60 from https://files.pythonhosted.org/packages/b1/3b/84494b632d8964e51cb06db89988f2155e1ea62537ba0d70d974fc2a8967/python_on_whales-0.64.2-py3-none-any.whl.metadata
#23 1.751   Downloading python_on_whales-0.64.2-py3-none-any.whl.metadata (16 kB)
#23 1.825 Collecting Jinja2~=3.1 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.835   Downloading Jinja2-3.1.2-py3-none-any.whl (133 kB)
#23 1.854      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.1/133.1 kB 8.7 MB/s eta 0:00:00
#23 1.914 Collecting packaging~=23.1 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 1.923   Downloading packaging-23.1-py3-none-any.whl (48 kB)
#23 1.936      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.9 MB/s eta 0:00:00
#23 2.019 Collecting pyyaml~=6.0 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.019   Obtaining dependency information for pyyaml~=6.0 from https://files.pythonhosted.org/packages/c8/6b/6600ac24725c7388255b2f5add93f91e58a5d7efaf4af244fdbcc11a541b/PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
#23 2.035   Downloading PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.1 kB)
#23 2.116 Collecting requests~=2.28 (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.117   Obtaining dependency information for requests~=2.28 from https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl.metadata
#23 2.125   Downloading requests-2.31.0-py3-none-any.whl.metadata (4.6 kB)
#23 2.134 Requirement already satisfied: pip>=20.2 in /home/holoscan/.local/lib/python3.8/site-packages (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (23.2.1)
#23 2.135 Requirement already satisfied: wheel-axle-runtime<1.0 in /home/holoscan/.local/lib/python3.8/site-packages (from holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (0.0.4)
#23 2.253 Collecting importlib-metadata>=3.6 (from typeguard>=3.0.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.254   Obtaining dependency information for importlib-metadata>=3.6 from https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl.metadata
#23 2.262   Downloading importlib_metadata-6.8.0-py3-none-any.whl.metadata (5.1 kB)
#23 2.317 Collecting typing-extensions>=4.7.0 (from typeguard>=3.0.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.318   Obtaining dependency information for typing-extensions>=4.7.0 from https://files.pythonhosted.org/packages/ec/6b/63cc3df74987c36fe26157ee12e09e8f9db4de771e0f3404263117e75b95/typing_extensions-4.7.1-py3-none-any.whl.metadata
#23 2.331   Downloading typing_extensions-4.7.1-py3-none-any.whl.metadata (3.1 kB)
#23 2.365 Requirement already satisfied: zipp>=0.5 in /home/holoscan/.local/lib/python3.8/site-packages (from importlib-metadata>=3.6->typeguard>=3.0.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (3.16.2)
#23 2.458 Collecting MarkupSafe>=2.0 (from Jinja2~=3.1->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.459   Obtaining dependency information for MarkupSafe>=2.0 from https://files.pythonhosted.org/packages/de/e2/32c14301bb023986dff527a49325b6259cab4ebb4633f69de54af312fc45/MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
#23 2.467   Downloading MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.0 kB)
#23 2.706 Collecting pydantic!=2.0.*,<3,>=1.5 (from python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.707   Obtaining dependency information for pydantic!=2.0.*,<3,>=1.5 from https://files.pythonhosted.org/packages/82/06/fafdc75e48b248eff364b4249af4bcc6952225e8f20e8205820afc66e88e/pydantic-2.3.0-py3-none-any.whl.metadata
#23 2.720   Downloading pydantic-2.3.0-py3-none-any.whl.metadata (148 kB)
#23 2.734      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 148.8/148.8 kB 14.8 MB/s eta 0:00:00
#23 2.832 Collecting tqdm (from python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.832   Obtaining dependency information for tqdm from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl.metadata
#23 2.840   Downloading tqdm-4.66.1-py3-none-any.whl.metadata (57 kB)
#23 2.856      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 57.6/57.6 kB 4.5 MB/s eta 0:00:00
#23 2.926 Collecting typer>=0.4.1 (from python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 2.933   Downloading typer-0.9.0-py3-none-any.whl (45 kB)
#23 2.945      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 45.9/45.9 kB 4.4 MB/s eta 0:00:00
#23 3.107 Collecting charset-normalizer<4,>=2 (from requests~=2.28->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 3.107   Obtaining dependency information for charset-normalizer<4,>=2 from https://files.pythonhosted.org/packages/cb/e7/5e43745003bf1f90668c7be23fc5952b3a2b9c2558f16749411c18039b36/charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
#23 3.115   Downloading charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (31 kB)
#23 3.166 Collecting idna<4,>=2.5 (from requests~=2.28->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 3.174   Downloading idna-3.4-py3-none-any.whl (61 kB)
#23 3.186      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.5/61.5 kB 6.2 MB/s eta 0:00:00
#23 3.254 Collecting urllib3<3,>=1.21.1 (from requests~=2.28->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 3.254   Obtaining dependency information for urllib3<3,>=1.21.1 from https://files.pythonhosted.org/packages/9b/81/62fd61001fa4b9d0df6e31d47ff49cfa9de4af03adecf339c7bc30656b37/urllib3-2.0.4-py3-none-any.whl.metadata
#23 3.264   Downloading urllib3-2.0.4-py3-none-any.whl.metadata (6.6 kB)
#23 3.326 Collecting certifi>=2017.4.17 (from requests~=2.28->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 3.326   Obtaining dependency information for certifi>=2017.4.17 from https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl.metadata
#23 3.334   Downloading certifi-2023.7.22-py3-none-any.whl.metadata (2.2 kB)
#23 3.352 Requirement already satisfied: filelock in /home/holoscan/.local/lib/python3.8/site-packages (from wheel-axle-runtime<1.0->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty) (3.12.2)
#23 3.415 Collecting annotated-types>=0.4.0 (from pydantic!=2.0.*,<3,>=1.5->python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 3.415   Obtaining dependency information for annotated-types>=0.4.0 from https://files.pythonhosted.org/packages/d8/f0/a2ee543a96cc624c35a9086f39b1ed2aa403c6d355dfe47a11ee5c64a164/annotated_types-0.5.0-py3-none-any.whl.metadata
#23 3.423   Downloading annotated_types-0.5.0-py3-none-any.whl.metadata (11 kB)
#23 4.197 Collecting pydantic-core==2.6.3 (from pydantic!=2.0.*,<3,>=1.5->python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 4.198   Obtaining dependency information for pydantic-core==2.6.3 from https://files.pythonhosted.org/packages/b0/88/43c79099fe0bcf6680c0782eb1b08069f024a08e114121b6704c9b26355a/pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
#23 4.208   Downloading pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.5 kB)
#23 4.301 Collecting click<9.0.0,>=7.1.1 (from typer>=0.4.1->python-on-whales~=0.60->holoscan>=0.5.0->monai-deploy-app-sdk==0.5.1+22.g029f8bc.dirty)
#23 4.301   Obtaining dependency information for click<9.0.0,>=7.1.1 from https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl.metadata
#23 4.309   Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
#23 4.455 Downloading numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.3 MB)
#23 4.699    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17.3/17.3 MB 57.3 MB/s eta 0:00:00
#23 4.709 Downloading typeguard-4.1.3-py3-none-any.whl (33 kB)
#23 4.725 Downloading importlib_metadata-6.8.0-py3-none-any.whl (22 kB)
#23 4.748 Downloading python_on_whales-0.64.2-py3-none-any.whl (104 kB)
#23 4.763    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 104.9/104.9 kB 9.1 MB/s eta 0:00:00
#23 4.776 Downloading PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (736 kB)
#23 4.803    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 736.6/736.6 kB 32.5 MB/s eta 0:00:00
#23 4.812 Downloading requests-2.31.0-py3-none-any.whl (62 kB)
#23 4.823    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.6/62.6 kB 6.3 MB/s eta 0:00:00
#23 4.836 Downloading typing_extensions-4.7.1-py3-none-any.whl (33 kB)
#23 4.867 Downloading certifi-2023.7.22-py3-none-any.whl (158 kB)
#23 4.879    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 158.3/158.3 kB 15.7 MB/s eta 0:00:00
#23 4.896 Downloading charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (199 kB)
#23 4.909    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 199.1/199.1 kB 19.5 MB/s eta 0:00:00
#23 4.917 Downloading MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (25 kB)
#23 4.944 Downloading pydantic-2.3.0-py3-none-any.whl (374 kB)
#23 4.958    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 374.5/374.5 kB 31.9 MB/s eta 0:00:00
#23 4.982 Downloading pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.9 MB)
#23 5.022    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.9/1.9 MB 51.7 MB/s eta 0:00:00
#23 5.032 Downloading urllib3-2.0.4-py3-none-any.whl (123 kB)
#23 5.047    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 123.9/123.9 kB 10.8 MB/s eta 0:00:00
#23 5.061 Downloading tqdm-4.66.1-py3-none-any.whl (78 kB)
#23 5.074    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.3/78.3 kB 7.8 MB/s eta 0:00:00
#23 5.082 Downloading annotated_types-0.5.0-py3-none-any.whl (11 kB)
#23 5.098 Downloading click-8.1.7-py3-none-any.whl (97 kB)
#23 5.109    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 10.0 MB/s eta 0:00:00
#23 5.502 Installing collected packages: urllib3, typing-extensions, tqdm, pyyaml, packaging, numpy, MarkupSafe, importlib-metadata, idna, colorama, cloudpickle, click, charset-normalizer, certifi, typer, typeguard, requests, pydantic-core, Jinja2, annotated-types, pydantic, python-on-whales, monai-deploy-app-sdk
#23 8.455 Successfully installed Jinja2-3.1.2 MarkupSafe-2.1.3 annotated-types-0.5.0 certifi-2023.7.22 charset-normalizer-3.2.0 click-8.1.7 cloudpickle-2.2.1 colorama-0.4.6 idna-3.4 importlib-metadata-6.8.0 monai-deploy-app-sdk-0.5.1+22.g029f8bc.dirty numpy-1.24.4 packaging-23.1 pydantic-2.3.0 pydantic-core-2.6.3 python-on-whales-0.64.2 pyyaml-6.0.1 requests-2.31.0 tqdm-4.66.1 typeguard-4.1.3 typer-0.9.0 typing-extensions-4.7.1 urllib3-2.0.4
#23 DONE 9.2s

#24 [18/22] COPY ./models  /opt/holoscan/models
#24 DONE 0.2s

#25 [19/22] COPY ./map/app.json /etc/holoscan/app.json
#25 DONE 0.1s

#26 [20/22] COPY ./app.config /var/holoscan/app.yaml
#26 DONE 0.1s

#27 [21/22] COPY ./map/pkg.json /etc/holoscan/pkg.json
#27 DONE 0.1s

#28 [22/22] COPY ./app /opt/holoscan/app
#28 DONE 0.1s

#29 exporting to docker image format
#29 exporting layers
#29 exporting layers 3.9s done
#29 exporting manifest sha256:f5c67a40a66fa6ccdc2781d51051812f7717c694918f109890a1198817c43f22 0.0s done
#29 exporting config sha256:17be984e3846abefdab5e9fc77950d42632ca87ff47628da66e67a2dff7ebd81 0.0s done
#29 sending tarball
#29 ...

#30 importing to docker
#30 DONE 2.8s

#29 exporting to docker image format
#29 sending tarball 62.0s done
#29 DONE 66.0s

#31 exporting content cache
#31 preparing build cache for export
#31 writing layer sha256:0709800848b4584780b40e7e81200689870e890c38b54e96b65cd0a3b1942f2d
#31 writing layer sha256:0709800848b4584780b40e7e81200689870e890c38b54e96b65cd0a3b1942f2d 0.0s done
#31 writing layer sha256:0ce020987cfa5cd1654085af3bb40779634eb3d792c4a4d6059036463ae0040d done
#31 writing layer sha256:0f4bc5775dfef844ad94316d6cba08f7430019a5986278e18978fdf8fd6370d0 0.0s done
#31 writing layer sha256:0f65089b284381bf795d15b1a186e2a8739ea957106fa526edef0d738e7cda70 done
#31 writing layer sha256:12a47450a9f9cc5d4edab65d0f600dbbe8b23a1663b0b3bb2c481d40e074b580 done
#31 writing layer sha256:1de965777e2e37c7fabe00bdbf3d0203ca83ed30a71a5479c3113fe4fc48c4bb done
#31 writing layer sha256:1e6d878a29f0eee28390766120813fdf36893f516bcc029e698cd941eeb79616 0.0s done
#31 writing layer sha256:24b5aa2448e920814dd67d7d3c0169b2cdacb13c4048d74ded3b4317843b13ff done
#31 writing layer sha256:2789e1f0e19719b047679b4b490cab1edb9e151cd286aed22df08022c249f040
#31 writing layer sha256:2789e1f0e19719b047679b4b490cab1edb9e151cd286aed22df08022c249f040 1.0s done
#31 writing layer sha256:2d42104dbf0a7cc962b791f6ab4f45a803f8a36d296f996aca180cfb2f3e30d0 done
#31 writing layer sha256:2fa1ce4fa3fec6f9723380dc0536b7c361d874add0baaddc4bbf2accac82d2ff done
#31 writing layer sha256:38794be1b5dc99645feabf89b22cd34fb5bdffb5164ad920e7df94f353efe9c0 done
#31 writing layer sha256:38f963dc57c1e7b68a738fe39ed9f9345df7188111a047e2163a46648d7f1d88 done
#31 writing layer sha256:3e7e4c9bc2b136814c20c04feb4eea2b2ecf972e20182d88759931130cfb4181 done
#31 writing layer sha256:3fd77037ad585442cd82d64e337f49a38ddba50432b2a1e563a48401d25c79e6 done
#31 writing layer sha256:41814ed91034b30ac9c44dfc604a4bade6138005ccf682372c02e0bead66dbc0 done
#31 writing layer sha256:45893188359aca643d5918c9932da995364dc62013dfa40c075298b1baabece3 done
#31 writing layer sha256:49bc651b19d9e46715c15c41b7c0daa007e8e25f7d9518f04f0f06592799875a done
#31 writing layer sha256:4c12db5118d8a7d909e4926d69a2192d2b3cd8b110d49c7504a4f701258c1ccc done
#31 writing layer sha256:4cc43a803109d6e9d1fd35495cef9b1257035f5341a2db54f7a1940815b6cc65 done
#31 writing layer sha256:4d32b49e2995210e8937f0898327f196d3fcc52486f0be920e8b2d65f150a7ab done
#31 writing layer sha256:4d6fe980bad9cd7b2c85a478c8033cae3d098a81f7934322fb64658b0c8f9854 done
#31 writing layer sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 done
#31 writing layer sha256:50b2500ad4a5ad2f73d71f4dedecabff852c74ea78a97dab0fc86b2ed44ddc77 done
#31 writing layer sha256:5150182f1ff123399b300ca469e00f6c4d82e1b9b72652fb8ee7eab370245236 done
#31 writing layer sha256:595c38fa102c61c3dda19bdab70dcd26a0e50465b986d022a84fa69023a05d0f done
#31 writing layer sha256:59d451175f6950740e26d38c322da0ef67cb59da63181eb32996f752ba8a2f17 done
#31 writing layer sha256:5ad1f2004580e415b998124ea394e9d4072a35d70968118c779f307204d6bd17 done
#31 writing layer sha256:5e2c1cbc09286c26c04d5b4257b11940ecdb161330319d54feadc7ef9a8dc8f6 done
#31 writing layer sha256:62598eafddf023e7f22643485f4321cbd51ff7eee743b970db12454fd3c8c675 done
#31 writing layer sha256:63d7e616a46987136f4cc9eba95db6f6327b4854cfe3c7e20fed6db0c966e380 done
#31 writing layer sha256:6939d591a6b09b14a437e5cd2d6082a52b6d76bec4f72d960440f097721da34f done
#31 writing layer sha256:698318e5a60e5e0d48c45bf992f205a9532da567fdfe94bd59be2e192975dd6f done
#31 writing layer sha256:6ddc1d0f91833b36aac1c6f0c8cea005c87d94bab132d46cc06d9b060a81cca3 done
#31 writing layer sha256:74ac1f5a47c0926bff1e997bb99985a09926f43bd0895cb27ceb5fa9e95f8720 done
#31 writing layer sha256:7577973918dd30e764733a352a93f418000bc3181163ca451b2307492c1a6ba9 done
#31 writing layer sha256:886c886d8a09d8befb92df75dd461d4f97b77d7cff4144c4223b0d2f6f2c17f2 done
#31 writing layer sha256:8a7451db9b4b817b3b33904abddb7041810a4ffe8ed4a034307d45d9ae9b3f2a done
#31 writing layer sha256:916f4054c6e7f10de4fd7c08ffc75fa23ebecca4eceb8183cb1023b33b1696c9 done
#31 writing layer sha256:9463aa3f56275af97693df69478a2dc1d171f4e763ca6f7b6f370a35e605c154 done
#31 writing layer sha256:955fd173ed884230c2eded4542d10a97384b408537be6bbb7c4ae09ccd6fb2d0 done
#31 writing layer sha256:9c42a4ee99755f441251e6043b2cbba16e49818a88775e7501ec17e379ce3cfd done
#31 writing layer sha256:9c63be0a86e3dc4168db3814bf464e40996afda0031649d9faa8ff7568c3154f done
#31 writing layer sha256:9e04bda98b05554953459b5edef7b2b14d32f1a00b979a23d04b6eb5c191e66b done
#31 writing layer sha256:a4a0c690bc7da07e592514dccaa26098a387e8457f69095e922b6d73f7852502 done
#31 writing layer sha256:a4aafbc094d78a85bef41036173eb816a53bcd3e2564594a32f542facdf2aba6 done
#31 writing layer sha256:ae36a4d38b76948e39a5957025c984a674d2de18ce162a8caaa536e6f06fccea done
#31 writing layer sha256:b2fa40114a4a0725c81b327df89c0c3ed5c05ca9aa7f1157394d5096cf5460ce done
#31 writing layer sha256:b48a5fafcaba74eb5d7e7665601509e2889285b50a04b5b639a23f8adc818157 done
#31 writing layer sha256:c657dd855c8726b050f2b5bd6f4999883fff6803fe9f22add96f6d3ff89cd477 done
#31 writing layer sha256:c86976a083599e36a6441f36f553627194d05ea82bb82a78682e718fe62fccf6 done
#31 writing layer sha256:cb506fbdedc817e3d074f609e2edbf9655aacd7784610a1bbac52f2d7be25438 done
#31 writing layer sha256:d2a6fe65a1f84edb65b63460a75d1cac1aa48b72789006881b0bcfd54cd01ffd done
#31 writing layer sha256:d2cafa18c788d3e44592cf8dcabf80e138db8389aa89e765550691199861d4fe 0.0s done
#31 writing layer sha256:d6a198fd2a224cb803248e86953a164439f1a64889df0861dc5cc7eef4c66664 0.0s done
#31 writing layer sha256:d8d16d6af76dc7c6b539422a25fdad5efb8ada5a8188069fcd9d113e3b783304
#31 writing layer sha256:d8d16d6af76dc7c6b539422a25fdad5efb8ada5a8188069fcd9d113e3b783304 done
#31 writing layer sha256:ddc2ade4f6fe866696cb638c8a102cb644fa842c2ca578392802b3e0e5e3bcb7 done
#31 writing layer sha256:e2cfd7f6244d6f35befa6bda1caa65f1786cecf3f00ef99d7c9a90715ce6a03c done
#31 writing layer sha256:e3d62e9dfa6b71c784d14517790438fabbc4adfca340fc7e2c2fa3ae76eb6917 0.0s done
#31 writing layer sha256:e42e7ccc889dd8eabf5148a4e91eb843e32688cf109fa7c074d87862f8da5da0
#31 writing layer sha256:e42e7ccc889dd8eabf5148a4e91eb843e32688cf109fa7c074d87862f8da5da0 0.4s done
#31 writing layer sha256:e94a4481e9334ff402bf90628594f64a426672debbdfb55f1290802e52013907 done
#31 writing layer sha256:eaf45e9f32d1f5a9983945a1a9f8dedbb475bc0f578337610e00b4dedec87c20 done
#31 writing layer sha256:eb411bef39c013c9853651e68f00965dbd826d829c4e478884a2886976e9c989 done
#31 writing layer sha256:edfe4a95eb6bd3142aeda941ab871ffcc8c19cf50c33561c210ba8ead2424759 done
#31 writing layer sha256:ef4466d6f927d29d404df9c5af3ef5733c86fa14e008762c90110b963978b1e7 done
#31 writing layer sha256:f346e3ecdf0bee048fa1e3baf1d3128ff0283b903f03e97524944949bd8882e5 done
#31 writing layer sha256:f3f9a00a1ce9aadda250aacb3e66a932676badc5d8519c41517fdf7ea14c13ed done
#31 writing layer sha256:f7a50dafd51c2bcaad0ede31fbf29c38fe66776ade008a7fbdb07dba39de7f97 done
#31 writing layer sha256:fd849d9bd8889edd43ae38e9f21a912430c8526b2c18f3057a3b2cd74eb27b31 done
#31 writing config sha256:b3ecef29d7ecd014606ec1a5bc12ec4a44ab5cdaedc016d07e70d0bebbaf471a 0.0s done
#31 preparing build cache for export 2.2s done
#31 writing manifest sha256:41770de5232f0f9ed1171d161204bcf55abf5a318aa784a38c8de9e07d4b7c3d 0.0s done
#31 DONE 2.2s
[2023-08-30 01:00:53,452] [INFO] (packager) - Build Summary:

Platform: x64-workstation/dgpu
    Status:     Succeeded
    Docker Tag: my_app-x64-workstation-dgpu-linux-amd64:1.0
    Tarball:    None

We can see that the MAP Docker image is created.

We can choose to display and inspect the MAP manifests by running the container with the show command, as well as extracting the manifests and other contents in the MAP by using the extract command, but not demonstrated in this example.

!docker image ls | grep {tag_prefix}
my_app-x64-workstation-dgpu-linux-amd64                   1.0                        17be984e3846   About a minute ago   15.4GB

Executing packaged app locally

The packaged app can be run locally through MONAI Application Runner.

# Clear the output folder and run the MAP. The input is expected to be a folder.
!rm -rf $HOLOSCAN_OUTPUT_PATH
!monai-deploy run -i $HOLOSCAN_INPUT_PATH -o $HOLOSCAN_OUTPUT_PATH my_app-x64-workstation-dgpu-linux-amd64:1.0
[2023-08-30 01:00:57,619] [INFO] (runner) - Checking dependencies...
[2023-08-30 01:00:57,619] [INFO] (runner) - --> Verifying if "docker" is installed...

[2023-08-30 01:00:57,620] [INFO] (runner) - --> Verifying if "docker-buildx" is installed...

[2023-08-30 01:00:57,620] [INFO] (runner) - --> Verifying if "my_app-x64-workstation-dgpu-linux-amd64:1.0" is available...

[2023-08-30 01:00:57,670] [INFO] (runner) - Reading HAP/MAP manifest...
Preparing to copy...?25lCopying from container - 0B?25hSuccessfully copied 2.56kB to /tmp/tmpah05b7ex/app.json
Preparing to copy...?25lCopying from container - 0B?25hSuccessfully copied 2.05kB to /tmp/tmpah05b7ex/pkg.json
[2023-08-30 01:00:58,237] [INFO] (runner) - --> Verifying if "nvidia-ctk" is installed...

[2023-08-30 01:00:58,414] [INFO] (common) - Launching container (8ca19e2ad332) using image 'my_app-x64-workstation-dgpu-linux-amd64:1.0'...
    container name:      peaceful_goldstine
    host name:           mingq-dt
    network:             host
    user:                1000:1000
    ulimits:             memlock=-1:-1, stack=67108864:67108864
    cap_add:             CAP_SYS_PTRACE
    ipc mode:            host
    shared memory size:  67108864
    devices:             
2023-08-30 08:00:59 [INFO] Launching application python3 /opt/holoscan/app ...

[2023-08-30 08:01:03,315] [INFO] (root) - Parsed args: Namespace(argv=['/opt/holoscan/app'], input=None, log_level=None, model=None, output=None, workdir=None)

[2023-08-30 08:01:03,318] [INFO] (root) - AppContext object: AppContext(input_path=/var/holoscan/input, output_path=/var/holoscan/output, model_path=/opt/holoscan/models, workdir=/var/holoscan)

[2023-08-30 08:01:03,318] [INFO] (app.AISpleenSegApp) - App input and output path: /var/holoscan/input, /var/holoscan/output

[info] [app_driver.cpp:1025] Launching the driver/health checking service

[info] [gxf_executor.cpp:210] Creating context

[info] [server.cpp:73] Health checking server listening on 0.0.0.0:8777

[info] [gxf_executor.cpp:1595] Loading extensions from configs...

[info] [gxf_executor.cpp:1741] Activating Graph...

[info] [gxf_executor.cpp:1771] Running Graph...

[info] [gxf_executor.cpp:1773] Waiting for completion...

[info] [gxf_executor.cpp:1774] Graph execution waiting. Fragment: 

[info] [greedy_scheduler.cpp:190] Scheduling 6 entities

[2023-08-30 08:01:03,434] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None

[2023-08-30 08:01:04,283] [INFO] (root) - Finding series for Selection named: CT Series

[2023-08-30 08:01:04,284] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291

  # of series: 1

[2023-08-30 08:01:04,284] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239

[2023-08-30 08:01:04,284] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'

[2023-08-30 08:01:04,284] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST

[2023-08-30 08:01:04,284] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2023-08-30 08:01:04,284] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'

[2023-08-30 08:01:04,284] [INFO] (root) -     Series attribute Modality value: CT

[2023-08-30 08:01:04,284] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2023-08-30 08:01:04,284] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'

[2023-08-30 08:01:04,284] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f

[2023-08-30 08:01:04,284] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2023-08-30 08:01:04,284] [INFO] (root) - On attribute: 'ImageType' to match value: ['PRIMARY', 'ORIGINAL']

[2023-08-30 08:01:04,284] [INFO] (root) -     Series attribute ImageType value: None

[2023-08-30 08:01:04,285] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239

/home/holoscan/.local/lib/python3.8/site-packages/monai/utils/deprecate_utils.py:321: FutureWarning: monai.transforms.io.dictionary LoadImaged.__init__:image_only: Current default value of argument `image_only=False` has been deprecated since version 1.1. It will be changed to `image_only=True` in version 1.3.

  warn_deprecated(argname, msg, warning_category)

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Converted Image object metadata:

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDate: 20090831, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesTime: 101721.452, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Modality: CT, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesDescription: ABD/PANC 3.0 B31f, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - PatientPosition: HFS, type <class 'str'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - SeriesNumber: 8, type <class 'int'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_pixel_spacing: 0.7890625, type <class 'float'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_pixel_spacing: 0.7890625, type <class 'float'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_pixel_spacing: 1.5, type <class 'float'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - row_direction_cosine: [1.0, 0.0, 0.0], type <class 'list'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - col_direction_cosine: [0.0, 1.0, 0.0], type <class 'list'>

[2023-08-30 08:01:04,713] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - depth_direction_cosine: [0.0, 0.0, 1.0], type <class 'list'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - dicom_affine_transform: [[   0.7890625    0.           0.        -197.60547  ]

 [   0.           0.7890625    0.        -398.60547  ]

 [   0.           0.           1.5       -383.       ]

 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - nifti_affine_transform: [[  -0.7890625   -0.          -0.         197.60547  ]

 [  -0.          -0.7890625   -0.         398.60547  ]

 [   0.           0.           1.5       -383.       ]

 [   0.           0.           0.           1.       ]], type <class 'numpy.ndarray'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyInstanceUID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291, type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyID: , type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDate: 20090831, type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyTime: 095948.599, type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - StudyDescription: CT ABDOMEN W IV CONTRAST, type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - AccessionNumber: 5471978513296937, type <class 'str'>

[2023-08-30 08:01:04,714] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - selection_name: CT Series, type <class 'str'>

2023-08-30 08:01:05,687 INFO image_writer.py:197 - writing: /var/holoscan/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626.nii

2023-08-30 08:01:16,263 INFO image_writer.py:197 - writing: /var/holoscan/output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626/1.3.6.1.4.1.14519.5.2.1.7085.2626_seg.nii

[2023-08-30 08:01:17,894] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image numpy array shaped: (204, 512, 512)

[2023-08-30 08:01:17,935] [INFO] (monai.deploy.operators.monai_seg_inference_operator.MonaiSegInferenceOperator) - Output Seg image pixel max value: 1

/home/holoscan/.local/lib/python3.8/site-packages/highdicom/valuerep.py:54: UserWarning: The string "C3N-00198" is unlikely to represent the intended person name since it contains only a single component. Construct a person name according to the format in described in http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#sect_6.2.1.2, or, in pydicom 2.2.0 or later, use the pydicom.valuerep.PersonName.from_named_components() method to construct the person name correctly. If a single-component name is really intended, add a trailing caret character to disambiguate the name.

  warnings.warn(

[2023-08-30 08:01:20,578] [INFO] (highdicom.seg.sop) - add plane #0 for segment #1

/home/holoscan/.local/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR UL.

  warnings.warn(msg)

/home/holoscan/.local/lib/python3.8/site-packages/pydicom/valuerep.py:443: UserWarning: A value of type 'int64' cannot be assigned to a tag with VR US.

  warnings.warn(msg)

[2023-08-30 08:01:20,582] [INFO] (highdicom.seg.sop) - add plane #1 for segment #1

[2023-08-30 08:01:20,583] [INFO] (highdicom.seg.sop) - add plane #2 for segment #1

[2023-08-30 08:01:20,584] [INFO] (highdicom.seg.sop) - add plane #3 for segment #1

[2023-08-30 08:01:20,585] [INFO] (highdicom.seg.sop) - add plane #4 for segment #1

[2023-08-30 08:01:20,586] [INFO] (highdicom.seg.sop) - add plane #5 for segment #1

[2023-08-30 08:01:20,587] [INFO] (highdicom.seg.sop) - add plane #6 for segment #1

[2023-08-30 08:01:20,587] [INFO] (highdicom.seg.sop) - add plane #7 for segment #1

[2023-08-30 08:01:20,588] [INFO] (highdicom.seg.sop) - add plane #8 for segment #1

[2023-08-30 08:01:20,589] [INFO] (highdicom.seg.sop) - add plane #9 for segment #1

[2023-08-30 08:01:20,590] [INFO] (highdicom.seg.sop) - add plane #10 for segment #1

[2023-08-30 08:01:20,591] [INFO] (highdicom.seg.sop) - add plane #11 for segment #1

[2023-08-30 08:01:20,591] [INFO] (highdicom.seg.sop) - add plane #12 for segment #1

[2023-08-30 08:01:20,592] [INFO] (highdicom.seg.sop) - add plane #13 for segment #1

[2023-08-30 08:01:20,593] [INFO] (highdicom.seg.sop) - add plane #14 for segment #1

[2023-08-30 08:01:20,594] [INFO] (highdicom.seg.sop) - add plane #15 for segment #1

[2023-08-30 08:01:20,595] [INFO] (highdicom.seg.sop) - add plane #16 for segment #1

[2023-08-30 08:01:20,596] [INFO] (highdicom.seg.sop) - add plane #17 for segment #1

[2023-08-30 08:01:20,597] [INFO] (highdicom.seg.sop) - add plane #18 for segment #1

[2023-08-30 08:01:20,598] [INFO] (highdicom.seg.sop) - add plane #19 for segment #1

[2023-08-30 08:01:20,599] [INFO] (highdicom.seg.sop) - add plane #20 for segment #1

[2023-08-30 08:01:20,600] [INFO] (highdicom.seg.sop) - add plane #21 for segment #1

[2023-08-30 08:01:20,601] [INFO] (highdicom.seg.sop) - add plane #22 for segment #1

[2023-08-30 08:01:20,602] [INFO] (highdicom.seg.sop) - add plane #23 for segment #1

[2023-08-30 08:01:20,603] [INFO] (highdicom.seg.sop) - add plane #24 for segment #1

[2023-08-30 08:01:20,604] [INFO] (highdicom.seg.sop) - add plane #25 for segment #1

[2023-08-30 08:01:20,605] [INFO] (highdicom.seg.sop) - add plane #26 for segment #1

[2023-08-30 08:01:20,606] [INFO] (highdicom.seg.sop) - add plane #27 for segment #1

[2023-08-30 08:01:20,607] [INFO] (highdicom.seg.sop) - add plane #28 for segment #1

[2023-08-30 08:01:20,608] [INFO] (highdicom.seg.sop) - add plane #29 for segment #1

[2023-08-30 08:01:20,609] [INFO] (highdicom.seg.sop) - add plane #30 for segment #1

[2023-08-30 08:01:20,610] [INFO] (highdicom.seg.sop) - add plane #31 for segment #1

[2023-08-30 08:01:20,611] [INFO] (highdicom.seg.sop) - add plane #32 for segment #1

[2023-08-30 08:01:20,612] [INFO] (highdicom.seg.sop) - add plane #33 for segment #1

[2023-08-30 08:01:20,613] [INFO] (highdicom.seg.sop) - add plane #34 for segment #1

[2023-08-30 08:01:20,614] [INFO] (highdicom.seg.sop) - add plane #35 for segment #1

[2023-08-30 08:01:20,615] [INFO] (highdicom.seg.sop) - add plane #36 for segment #1

[2023-08-30 08:01:20,615] [INFO] (highdicom.seg.sop) - add plane #37 for segment #1

[2023-08-30 08:01:20,616] [INFO] (highdicom.seg.sop) - add plane #38 for segment #1

[2023-08-30 08:01:20,617] [INFO] (highdicom.seg.sop) - add plane #39 for segment #1

[2023-08-30 08:01:20,618] [INFO] (highdicom.seg.sop) - add plane #40 for segment #1

[2023-08-30 08:01:20,619] [INFO] (highdicom.seg.sop) - add plane #41 for segment #1

[2023-08-30 08:01:20,620] [INFO] (highdicom.seg.sop) - add plane #42 for segment #1

[2023-08-30 08:01:20,621] [INFO] (highdicom.seg.sop) - add plane #43 for segment #1

[2023-08-30 08:01:20,622] [INFO] (highdicom.seg.sop) - add plane #44 for segment #1

[2023-08-30 08:01:20,623] [INFO] (highdicom.seg.sop) - add plane #45 for segment #1

[2023-08-30 08:01:20,624] [INFO] (highdicom.seg.sop) - add plane #46 for segment #1

[2023-08-30 08:01:20,625] [INFO] (highdicom.seg.sop) - add plane #47 for segment #1

[2023-08-30 08:01:20,626] [INFO] (highdicom.seg.sop) - add plane #48 for segment #1

[2023-08-30 08:01:20,627] [INFO] (highdicom.seg.sop) - add plane #49 for segment #1

[2023-08-30 08:01:20,628] [INFO] (highdicom.seg.sop) - add plane #50 for segment #1

[2023-08-30 08:01:20,629] [INFO] (highdicom.seg.sop) - add plane #51 for segment #1

[2023-08-30 08:01:20,630] [INFO] (highdicom.seg.sop) - add plane #52 for segment #1

[2023-08-30 08:01:20,631] [INFO] (highdicom.seg.sop) - add plane #53 for segment #1

[2023-08-30 08:01:20,632] [INFO] (highdicom.seg.sop) - add plane #54 for segment #1

[2023-08-30 08:01:20,633] [INFO] (highdicom.seg.sop) - add plane #55 for segment #1

[2023-08-30 08:01:20,633] [INFO] (highdicom.seg.sop) - add plane #56 for segment #1

[2023-08-30 08:01:20,635] [INFO] (highdicom.seg.sop) - add plane #57 for segment #1

[2023-08-30 08:01:20,635] [INFO] (highdicom.seg.sop) - add plane #58 for segment #1

[2023-08-30 08:01:20,636] [INFO] (highdicom.seg.sop) - add plane #59 for segment #1

[2023-08-30 08:01:20,637] [INFO] (highdicom.seg.sop) - add plane #60 for segment #1

[2023-08-30 08:01:20,638] [INFO] (highdicom.seg.sop) - add plane #61 for segment #1

[2023-08-30 08:01:20,639] [INFO] (highdicom.seg.sop) - add plane #62 for segment #1

[2023-08-30 08:01:20,640] [INFO] (highdicom.seg.sop) - add plane #63 for segment #1

[2023-08-30 08:01:20,641] [INFO] (highdicom.seg.sop) - add plane #64 for segment #1

[2023-08-30 08:01:20,642] [INFO] (highdicom.seg.sop) - add plane #65 for segment #1

[2023-08-30 08:01:20,643] [INFO] (highdicom.seg.sop) - add plane #66 for segment #1

[2023-08-30 08:01:20,644] [INFO] (highdicom.seg.sop) - add plane #67 for segment #1

[2023-08-30 08:01:20,818] [INFO] (highdicom.seg.sop) - add plane #68 for segment #1

[2023-08-30 08:01:20,819] [INFO] (highdicom.seg.sop) - add plane #69 for segment #1

[2023-08-30 08:01:20,820] [INFO] (highdicom.seg.sop) - add plane #70 for segment #1

[2023-08-30 08:01:20,821] [INFO] (highdicom.seg.sop) - add plane #71 for segment #1

[2023-08-30 08:01:20,822] [INFO] (highdicom.seg.sop) - add plane #72 for segment #1

[2023-08-30 08:01:20,822] [INFO] (highdicom.seg.sop) - add plane #73 for segment #1

[2023-08-30 08:01:20,823] [INFO] (highdicom.seg.sop) - add plane #74 for segment #1

[2023-08-30 08:01:20,824] [INFO] (highdicom.seg.sop) - add plane #75 for segment #1

[2023-08-30 08:01:20,825] [INFO] (highdicom.seg.sop) - add plane #76 for segment #1

[2023-08-30 08:01:20,826] [INFO] (highdicom.seg.sop) - add plane #77 for segment #1

[2023-08-30 08:01:20,826] [INFO] (highdicom.seg.sop) - add plane #78 for segment #1

[2023-08-30 08:01:20,827] [INFO] (highdicom.seg.sop) - add plane #79 for segment #1

[2023-08-30 08:01:20,828] [INFO] (highdicom.seg.sop) - add plane #80 for segment #1

[2023-08-30 08:01:20,828] [INFO] (highdicom.seg.sop) - add plane #81 for segment #1

[2023-08-30 08:01:20,829] [INFO] (highdicom.seg.sop) - add plane #82 for segment #1

[2023-08-30 08:01:20,830] [INFO] (highdicom.seg.sop) - add plane #83 for segment #1

[2023-08-30 08:01:20,831] [INFO] (highdicom.seg.sop) - add plane #84 for segment #1

[2023-08-30 08:01:20,832] [INFO] (highdicom.seg.sop) - add plane #85 for segment #1

[2023-08-30 08:01:20,832] [INFO] (highdicom.seg.sop) - add plane #86 for segment #1

[2023-08-30 08:01:20,833] [INFO] (highdicom.seg.sop) - add plane #87 for segment #1

[2023-08-30 08:01:20,936] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2023-08-30 08:01:20,936] [INFO] (highdicom.base) - copy attributes of module "Specimen"

[2023-08-30 08:01:20,937] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2023-08-30 08:01:20,937] [INFO] (highdicom.base) - copy attributes of module "Patient"

[2023-08-30 08:01:20,937] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"

[2023-08-30 08:01:20,937] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2023-08-30 08:01:20,937] [INFO] (highdicom.base) - copy attributes of module "General Study"

[2023-08-30 08:01:20,938] [INFO] (highdicom.base) - copy attributes of module "Patient Study"

[2023-08-30 08:01:20,938] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"

[info] [greedy_scheduler.cpp:369] Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.

[info] [greedy_scheduler.cpp:398] Scheduler finished.

[info] [gxf_executor.cpp:1783] Graph execution deactivating. Fragment: 

[info] [gxf_executor.cpp:1784] Deactivating Graph...

[info] [gxf_executor.cpp:1787] Graph execution finished. Fragment: 

[2023-08-30 08:01:21,087] [INFO] (app.AISpleenSegApp) - End run

[info] [gxf_executor.cpp:229] Destroying context

[2023-08-30 01:01:23,201] [INFO] (common) - Container 'peaceful_goldstine'(8ca19e2ad332) exited.
!ls -R $HOLOSCAN_OUTPUT_PATH
output:
1.2.826.0.1.3680043.10.511.3.11368427294546636595990283269631758.dcm
saved_images_folder

output/saved_images_folder:
1.3.6.1.4.1.14519.5.2.1.7085.2626

output/saved_images_folder/1.3.6.1.4.1.14519.5.2.1.7085.2626:
1.3.6.1.4.1.14519.5.2.1.7085.2626.nii
1.3.6.1.4.1.14519.5.2.1.7085.2626_seg.nii