Creating a Deploy App with MONAI Deploy App SDK and MONAI Bundle

This tutorial shows how to create an application for organ segmentation using a PyTorch model that has been trained with MONAI and packaged in the MONAI Bundle format.

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.

DICOM instances received from modalities and Picture Archiving and Communications System (PACS) are often times the whole DICOM study, so 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, and importantly, using the built-in MONAI Bundle Inference Operator to perform inference with the Spleen CT Segmentation PyTorch model in a MONAI Bundle.

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 a NIfTI file to a DICOM series.

To make running this example simpler, the DICOM files and the Spleen CT Segmentation MONAI Bundle, published in MONAI Model Zoo, have been packaged and shared on Google Drive.

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)

  • MonaiBundleInferenceOperator:

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

    • Output(pred): 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 is illustrated below.

%%{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 --|> MonaiBundleInferenceOperator : image...image DICOMSeriesSelectorOperator --|> DICOMSegmentationWriterOperator : study_selected_series_list...study_selected_series_list MonaiBundleInferenceOperator --|> DICOMSegmentationWriterOperator : pred...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 MonaiBundleInferenceOperator { <in>image : IN_MEMORY pred(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.12.0"
!python -c "import numpy" || pip install -q "numpy>=1.21.6"
!python -c "import nibabel" || pip install -q "nibabel>=3.2.1"
!python -c "import pydicom" || pip install -q "pydicom>=2.3.0"
!python -c "import highdicom" || pip install -q "highdicom>=0.18.2"
!python -c "import SimpleITK" || pip install -q "SimpleITK>=2.0.0"
!python -c "import skimage" || pip install -q "scikit-image>=0.17.2"
!python -c "import stl" || pip install -q "numpy-stl>=2.12.0"
!python -c "import trimesh" || pip install -q "trimesh>=3.8.11"

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

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

Download/Extract input and model/bundle files from Google Drive

# Download the test data and MONAI bundle 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 pacakging, to workaround 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.10/site-packages (5.1.0)
Requirement already satisfied: beautifulsoup4 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from gdown) (4.12.3)
Requirement already satisfied: filelock in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from gdown) (3.13.4)
Requirement already satisfied: requests[socks] in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from gdown) (2.31.0)
Requirement already satisfied: tqdm in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from gdown) (4.66.2)
Requirement already satisfied: soupsieve>1.2 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from beautifulsoup4->gdown) (2.5)
Requirement already satisfied: charset-normalizer<4,>=2 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from requests[socks]->gdown) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from requests[socks]->gdown) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from requests[socks]->gdown) (2.2.1)
Requirement already satisfied: certifi>=2017.4.17 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from requests[socks]->gdown) (2024.2.2)
Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages (from requests[socks]->gdown) (1.7.1)
Downloading...
From (original): https://drive.google.com/uc?id=1Uds8mEvdGNYUuvFpTtCQ8gNU97bAPCaQ
From (redirected): https://drive.google.com/uc?id=1Uds8mEvdGNYUuvFpTtCQ8gNU97bAPCaQ&confirm=t&uuid=acdd1e9c-2382-4003-8390-b86e2e103d74
To: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/ai_spleen_seg_bundle_data.zip
100%|██████████████████████████████████████| 79.4M/79.4M [00:00<00:00, 96.9MB/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

Set up environment variables

%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

Set up imports

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

import logging
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
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_bundle_inference_operator import (
    BundleConfigNames,
    IOMapping,
    MonaiBundleInferenceOperator,
)
from monai.deploy.operators.stl_conversion_operator import STLConversionOperator

Determining the Input and Output for the Model Bundle Inference Operator

The App SDK provides a MonaiBundleInferenceOperator class to perform inference with a MONAI Bundle, which is essentially a PyTorch model in TorchScript with additional metadata describing the model network and processing specification. This operator uses the MONAI utilities to parse a MONAI Bundle to automatically instantiate the objects required for input and output processing as well as inference, as such it depends on MONAI transforms, inferers, and in turn their dependencies.

Each Operator class inherits from the base Operator base class, and its input/output properties are specified in the setup function (as opposed to using decorators @inputand @output in Version 0.5 and below).

For the MonaiBundleInferenceOperator class, the input/output need to be defined to match those of the model network, both in name and data type. For the current release, an IOMapping object is used to connect the operator input/output to those of the model network by using the same names. This is likely to change, to be automated, in the future releases once certain limitation in the App SDK is removed.

The Spleen CT Segmentation model network has a named input, called “image”, and the named output called “pred”, and both are of image type, which can all be mapped to the App SDK Image. This piece of information is typically acquired by examining the model metadata network_data_format attribute in the bundle, as seen in this [example] (https://github.com/Project-MONAI/model-zoo/blob/dev/models/spleen_ct_segmentation/configs/metadata.json).

Creating Application class

Our application class would look like below.

It defines App class, inheriting the base Application class.

Objects required for DICOM parsing, series selection, pixel data conversion to volume image, model specific inference, and the AI result specific DICOM Segmentation object writers are created. The execution pipeline, as a Directed Acyclic Graph, is then constructed by connecting these objects through self.add_flow().

class AISpleenSegApp(Application):
    """Demonstrates inference with built-in MONAI Bundle inference operator with DICOM files as input/output

    This application loads a set of DICOM instances, select the appropriate series, converts the series to
    3D volume image, performs inference with the built-in MONAI Bundle inference operator, including pre-processing
    and post-processing, save the segmentation image in a DICOM Seg OID in an instance file, and optionally the
    surface mesh in STL format.

    Pertinent MONAI Bundle:
      https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation

    Execution Time Estimate:
      With a Nvidia GV100 32GB GPU, for an input DICOM Series of 515 instances, the execution time is around
      25 seconds with saving both DICOM Seg and surface mesh STL file, and 15 seconds with DICOM Seg only.
    """

    def __init__(self, *args, **kwargs):
        """Creates an application instance."""
        self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
        super().__init__(*args, **kwargs)

    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."""

        logging.info(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)

        # Create the custom operator(s) as well as SDK built-in operator(s).
        study_loader_op = DICOMDataLoaderOperator(
            self, CountCondition(self, 1), input_folder=app_input_path, name="study_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")

        # Create the inference operator that supports MONAI Bundle and automates the inference.
        # The IOMapping labels match the input and prediction keys in the pre and post processing.
        # The model_name is optional when the app has only one model.
        # The bundle_path argument optionally can be set to an accessible bundle file path in the dev
        # environment, so when the app is packaged into a MAP, the operator can complete the bundle parsing
        # during init.

        config_names = BundleConfigNames(config_names=["inference"])  # Same as the default

        bundle_spleen_seg_op = MonaiBundleInferenceOperator(
            self,
            input_mapping=[IOMapping("image", Image, IOType.IN_MEMORY)],
            output_mapping=[IOMapping("pred", Image, IOType.IN_MEMORY)],
            app_context=app_context,
            bundle_config_names=config_names,
            bundle_path=model_path,
            name="bundle_spleen_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 of DICOM VR LO type, limited to 64 chars.
        # https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
        segment_descriptions = [
            SegmentDescription(
                segment_label="Spleen",
                segmented_property_category=codes.SCT.Organ,
                segmented_property_type=codes.SCT.Spleen,
                algorithm_name="volumetric (3D) segmentation of the spleen from CT image",
                algorithm_family=codes.DCM.ArtificialIntelligence,
                algorithm_version="0.3.2",
            )
        ]

        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="dicom_seg_writer",
        )

        # 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, bundle_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(bundle_spleen_seg_op, dicom_seg_writer, {("pred", "seg_image")})
        # Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
        # uncommenting the following couple lines.
        stl_conversion_op = STLConversionOperator(
            self, output_file=app_output_path.joinpath("stl/spleen.stl"), name="stl_conversion_op"
        )
        self.add_flow(bundle_spleen_seg_op, stl_conversion_op, {("pred", "image")})

        logging.info(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.
Sample_Rules_Text = """
{
    "selections": [
        {
            "name": "CT Series",
            "conditions": {
                "StudyDescription": "(.*?)",
                "Modality": "(?i)CT",
                "SeriesDescription": "(.*?)"
            }
        }
    ]
}
"""

Executing app locally

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

!rm -rf $HOLOSCAN_OUTPUT_PATH
logging.info(f"Begin {__name__}")
AISpleenSegApp().run()
logging.info(f"End {__name__}")
[2024-04-23 16:51:59,898] [INFO] (root) - Begin __main__
[2024-04-23 16:51:59,901] [INFO] (__main__.AISpleenSegApp) - Begin run
[2024-04-23 16:51:59,902] [INFO] (root) - Begin compose
[2024-04-23 16:51:59,909] [INFO] (root) - Parsed args: Namespace(log_level=None, input=None, output=None, model=None, workdir=None, argv=[])
[2024-04-23 16:51:59,915] [INFO] (root) - AppContext object: AppContext(input_path=dcm, output_path=output, model_path=models, workdir=)
[2024-04-23 16:51:59,920] [INFO] (root) - End compose
[info] [gxf_executor.cpp:247] Creating context
[info] [gxf_executor.cpp:1672] Loading extensions from configs...
[info] [gxf_executor.cpp:1842] Activating Graph...
[info] [gxf_executor.cpp:1874] Running Graph...
[info] [gxf_executor.cpp:1876] Waiting for completion...
[2024-04-23 16:51:59,943] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None
2024-04-23 16:51:59.940 INFO  gxf/std/greedy_scheduler.cpp@191: Scheduling 8 entities
[2024-04-23 16:52:00,393] [INFO] (root) - Finding series for Selection named: CT Series
[2024-04-23 16:52:00,394] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291
  # of series: 1
[2024-04-23 16:52:00,395] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2024-04-23 16:52:00,396] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'
[2024-04-23 16:52:00,397] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST
[2024-04-23 16:52:00,397] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:00,398] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'
[2024-04-23 16:52:00,399] [INFO] (root) -     Series attribute Modality value: CT
[2024-04-23 16:52:00,399] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:00,400] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'
[2024-04-23 16:52:00,400] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f
[2024-04-23 16:52:00,401] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:00,402] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2024-04-23 16:52:00,641] [INFO] (root) - Parsing from bundle_path: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models/model/model.ts
[2024-04-23 16:52:03,610] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConversionOperator) - Output will be saved in file output/stl/spleen.stl.
[2024-04-23 16:52:05,137] [INFO] (monai.deploy.operators.stl_conversion_operator.SpatialImage) - 3D image
[2024-04-23 16:52:05,138] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConverter) - Image ndarray shape:(204, 512, 512)
[2024-04-23 16:52:14,942] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:14,943] [INFO] (highdicom.base) - copy attributes of module "Specimen"
[2024-04-23 16:52:14,944] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:14,945] [INFO] (highdicom.base) - copy attributes of module "Patient"
[2024-04-23 16:52:14,946] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"
[2024-04-23 16:52:14,947] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:14,948] [INFO] (highdicom.base) - copy attributes of module "General Study"
[2024-04-23 16:52:14,949] [INFO] (highdicom.base) - copy attributes of module "Patient Study"
[2024-04-23 16:52:14,951] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"
[info] [gxf_executor.cpp:1879] Deactivating Graph...
[info] [gxf_executor.cpp:1887] Graph execution finished.
[2024-04-23 16:52:15,056] [INFO] (__main__.AISpleenSegApp) - End run
[2024-04-23 16:52:15,057] [INFO] (root) - End __main__
2024-04-23 16:52:15.054 INFO  gxf/std/greedy_scheduler.cpp@372: Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.
2024-04-23 16:52:15.055 INFO  gxf/std/greedy_scheduler.cpp@401: Scheduler finished.

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
# Create an application folder
!mkdir -p my_app && rm -rf my_app/*

app.py

%%writefile my_app/app.py

# Copyright 2021-2023 MONAI Consortium
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
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
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_bundle_inference_operator import (
    BundleConfigNames,
    IOMapping,
    MonaiBundleInferenceOperator,
)
from monai.deploy.operators.stl_conversion_operator import STLConversionOperator


class AISpleenSegApp(Application):
    """Demonstrates inference with built-in MONAI Bundle inference operator with DICOM files as input/output

    This application loads a set of DICOM instances, select the appropriate series, converts the series to
    3D volume image, performs inference with the built-in MONAI Bundle inference operator, including pre-processing
    and post-processing, save the segmentation image in a DICOM Seg OID in an instance file, and optionally the
    surface mesh in STL format.

    Pertinent MONAI Bundle:
      https://github.com/Project-MONAI/model-zoo/tree/dev/models/spleen_ct_segmentation

    Execution Time Estimate:
      With a Nvidia GV100 32GB GPU, for an input DICOM Series of 515 instances, the execution time is around
      25 seconds with saving both DICOM Seg and surface mesh STL file, and 15 seconds with DICOM Seg only.
    """

    def __init__(self, *args, **kwargs):
        """Creates an application instance."""
        self._logger = logging.getLogger("{}.{}".format(__name__, type(self).__name__))
        super().__init__(*args, **kwargs)

    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."""

        logging.info(f"Begin {self.compose.__name__}")

        # Use Commandline options over environment variables to init context.
        app_context = Application.init_app_context(self.argv)
        app_input_path = Path(app_context.input_path)
        app_output_path = Path(app_context.output_path)
        model_path = Path(app_context.model_path)

        # Create the custom operator(s) as well as SDK built-in operator(s).
        study_loader_op = DICOMDataLoaderOperator(
            self, CountCondition(self, 1), input_folder=app_input_path, name="study_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")

        # Create the inference operator that supports MONAI Bundle and automates the inference.
        # The IOMapping labels match the input and prediction keys in the pre and post processing.
        # The model_name is optional when the app has only one model.
        # The bundle_path argument optionally can be set to an accessible bundle file path in the dev
        # environment, so when the app is packaged into a MAP, the operator can complete the bundle parsing
        # during init.

        config_names = BundleConfigNames(config_names=["inference"])  # Same as the default

        bundle_spleen_seg_op = MonaiBundleInferenceOperator(
            self,
            input_mapping=[IOMapping("image", Image, IOType.IN_MEMORY)],
            output_mapping=[IOMapping("pred", Image, IOType.IN_MEMORY)],
            app_context=app_context,
            bundle_config_names=config_names,
            bundle_path=model_path,
            name="bundle_spleen_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 of DICOM VR LO type, limited to 64 chars.
        # https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
        segment_descriptions = [
            SegmentDescription(
                segment_label="Spleen",
                segmented_property_category=codes.SCT.Organ,
                segmented_property_type=codes.SCT.Spleen,
                algorithm_name="volumetric (3D) segmentation of the spleen from CT image",
                algorithm_family=codes.DCM.ArtificialIntelligence,
                algorithm_version="0.3.2",
            )
        ]

        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="dicom_seg_writer",
        )

        # 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, bundle_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(bundle_spleen_seg_op, dicom_seg_writer, {("pred", "seg_image")})
        # Create the surface mesh STL conversion operator and add it to the app execution flow, if needed, by
        # uncommenting the following couple lines.
        stl_conversion_op = STLConversionOperator(
            self, output_file=app_output_path.joinpath("stl/spleen.stl"), name="stl_conversion_op"
        )
        self.add_flow(bundle_spleen_seg_op, stl_conversion_op, {("pred", "image")})

        logging.info(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.
Sample_Rules_Text = """
{
    "selections": [
        {
            "name": "CT Series",
            "conditions": {
                "StudyDescription": "(.*?)",
                "Modality": "(?i)CT",
                "SeriesDescription": "(.*?)"
            }
        }
    ]
}
"""

if __name__ == "__main__":
    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

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. The following command demonstrates the use of the options.

!rm -rf $HOLOSCAN_OUTPUT_PATH
!python my_app -i dcm -o output -m models
[2024-04-23 16:52:19,843] [INFO] (root) - Parsed args: Namespace(log_level=None, input=PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/dcm'), output=PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output'), model=PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models'), workdir=None, argv=['my_app', '-i', 'dcm', '-o', 'output', '-m', 'models'])
[2024-04-23 16:52:19,845] [INFO] (root) - AppContext object: AppContext(input_path=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/dcm, output_path=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output, model_path=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models, workdir=)
[2024-04-23 16:52:19,847] [INFO] (root) - End compose
[info] [gxf_executor.cpp:247] Creating context
[info] [gxf_executor.cpp:1672] Loading extensions from configs...
[info] [gxf_executor.cpp:1842] Activating Graph...
[info] [gxf_executor.cpp:1874] Running Graph...
[info] [gxf_executor.cpp:1876] Waiting for completion...
2024-04-23 16:52:19.876 INFO  gxf/std/greedy_scheduler.cpp@191: Scheduling 8 entities
[2024-04-23 16:52:19,877] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None
[2024-04-23 16:52:20,224] [INFO] (root) - Finding series for Selection named: CT Series
[2024-04-23 16:52:20,224] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291
  # of series: 1
[2024-04-23 16:52:20,224] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2024-04-23 16:52:20,224] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'
[2024-04-23 16:52:20,224] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST
[2024-04-23 16:52:20,224] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:20,225] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'
[2024-04-23 16:52:20,225] [INFO] (root) -     Series attribute Modality value: CT
[2024-04-23 16:52:20,225] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:20,225] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'
[2024-04-23 16:52:20,225] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f
[2024-04-23 16:52:20,225] [INFO] (root) - Series attribute string value did not match. Try regEx.
[2024-04-23 16:52:20,225] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239
[2024-04-23 16:52:20,449] [INFO] (root) - Parsing from bundle_path: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models/model/model.ts
[2024-04-23 16:52:26,711] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConversionOperator) - Output will be saved in file /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output/stl/spleen.stl.
[2024-04-23 16:52:28,387] [INFO] (monai.deploy.operators.stl_conversion_operator.SpatialImage) - 3D image
[2024-04-23 16:52:28,388] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConverter) - Image ndarray shape:(204, 512, 512)
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/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 https://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(
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy attributes of module "Specimen"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy attributes of module "Patient"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"
[2024-04-23 16:52:39,441] [INFO] (highdicom.base) - copy attributes of module "General Study"
[2024-04-23 16:52:39,442] [INFO] (highdicom.base) - copy attributes of module "Patient Study"
[2024-04-23 16:52:39,442] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"
2024-04-23 16:52:39.552 INFO  gxf/std/greedy_scheduler.cpp@372: Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.
2024-04-23 16:52:39.552 INFO  gxf/std/greedy_scheduler.cpp@401: Scheduler finished.
[info] [gxf_executor.cpp:1879] Deactivating Graph...
[info] [gxf_executor.cpp:1887] Graph execution finished.
[2024-04-23 16:52:39,552] [INFO] (app.AISpleenSegApp) - End run
!ls output
1.2.826.0.1.3680043.10.511.3.31677856801140848641305221346725457.dcm  stl

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
scikit-image>=0.17.2
numpy-stl>=2.12.0
trimesh>=3.8.11
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
[2024-04-23 16:52:41,994] [INFO] (common) - Downloading CLI manifest file...
[2024-04-23 16:52:42,236] [DEBUG] (common) - Validating CLI manifest file...
[2024-04-23 16:52:42,237] [INFO] (packager.parameters) - Application: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/my_app
[2024-04-23 16:52:42,238] [INFO] (packager.parameters) - Detected application type: Python Module
[2024-04-23 16:52:42,238] [INFO] (packager) - Scanning for models in /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models...
[2024-04-23 16:52:42,238] [DEBUG] (packager) - Model model=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/models/model added.
[2024-04-23 16:52:42,238] [INFO] (packager) - Reading application configuration from /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/my_app/app.yaml...
[2024-04-23 16:52:42,240] [INFO] (packager) - Generating app.json...
[2024-04-23 16:52:42,240] [INFO] (packager) - Generating pkg.json...
[2024-04-23 16:52:42,246] [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.5.1",
    "timeout": 0,
    "version": 1.0,
    "workingDirectory": "/var/holoscan"
}
================ End app.json ================
                 
[2024-04-23 16:52:42,246] [DEBUG] (common) - 
=============== Begin pkg.json ===============
{
    "apiVersion": "1.0.0",
    "applicationRoot": "/opt/holoscan/app",
    "modelRoot": "/opt/holoscan/models",
    "models": {
        "model": "/opt/holoscan/models/model"
    },
    "resources": {
        "cpu": 1,
        "gpu": 1,
        "memory": "1Gi",
        "gpuMemory": "6Gi"
    },
    "version": 1.0,
    "platformConfig": "dgpu"
}
================ End pkg.json ================
                 
[2024-04-23 16:52:42,273] [DEBUG] (packager.builder) - 
========== Begin Dockerfile ==========


FROM nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.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:v2.0.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="2.0.0"
LABEL org.monai.deploy.app-sdk="0.5.1"


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 -f -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

 
# MONAI Deploy

# Copy user-specified MONAI Deploy SDK file
COPY ./monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl /tmp/monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl
RUN pip install /tmp/monai_deploy_app_sdk-0.5.1+20.gb869749.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 ===========

[2024-04-23 16:52:42,273] [INFO] (packager.builder) - 
===============================================================================
Building image for:                 x64-workstation
    Architecture:                   linux/amd64
    Base Image:                     nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.0-dgpu
    Build Image:                    N/A
    Cache:                          Enabled
    Configuration:                  dgpu
    Holoscan SDK Package:           pypi.org
    MONAI Deploy App SDK Package:   /home/mqin/src/monai-deploy-app-sdk/dist/monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl
    gRPC Health Probe:              N/A
    SDK Version:                    2.0.0
    SDK:                            monai-deploy
    Tag:                            my_app-x64-workstation-dgpu-linux-amd64:1.0
    
[2024-04-23 16:52:42,564] [INFO] (common) - Using existing Docker BuildKit builder `holoscan_app_builder`
[2024-04-23 16:52:42,564] [DEBUG] (packager.builder) - Building Holoscan Application Package: tag=my_app-x64-workstation-dgpu-linux-amd64:1.0
#0 building with "holoscan_app_builder" instance using docker-container driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 2.66kB done
#1 DONE 0.1s

#2 [internal] load metadata for nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.0-dgpu
#2 DONE 0.5s

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

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

#5 importing cache manifest from local:13600691502778489948
#5 inferred cache manifest type: application/vnd.oci.image.index.v1+json done
#5 DONE 0.0s

#6 [ 1/21] FROM nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.0-dgpu@sha256:20adbccd2c7b12dfb1798f6953f071631c3b85cd337858a7506f8e420add6d4a
#6 resolve nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.0-dgpu@sha256:20adbccd2c7b12dfb1798f6953f071631c3b85cd337858a7506f8e420add6d4a 0.0s done
#6 DONE 0.1s

#7 importing cache manifest from nvcr.io/nvidia/clara-holoscan/holoscan:v2.0.0-dgpu
#7 inferred cache manifest type: application/vnd.docker.distribution.manifest.list.v2+json done
#7 DONE 0.7s

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

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

#9 [ 7/21] RUN chown -R holoscan /var/holoscan/input
#9 CACHED

#10 [ 8/21] RUN chown -R holoscan /var/holoscan/output
#10 CACHED

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

#12 [ 4/21] RUN groupadd -f -g 1000 holoscan
#12 CACHED

#13 [ 9/21] WORKDIR /var/holoscan
#13 CACHED

#14 [ 2/21] 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
#14 CACHED

#15 [ 6/21] RUN chown -R holoscan /var/holoscan
#15 CACHED

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

#17 [12/21] COPY ./pip/requirements.txt /tmp/requirements.txt
#17 CACHED

#18 [10/21] COPY ./tools /var/holoscan/tools
#18 CACHED

#19 [13/21] RUN pip install --upgrade pip
#19 CACHED

#20 [14/21] RUN pip install --no-cache-dir --user -r /tmp/requirements.txt
#20 0.822 Collecting highdicom>=0.18.2 (from -r /tmp/requirements.txt (line 1))
#20 0.909   Downloading highdicom-0.22.0-py3-none-any.whl.metadata (3.8 kB)
#20 0.940 Collecting monai>=1.0 (from -r /tmp/requirements.txt (line 2))
#20 0.944   Downloading monai-1.3.0-202310121228-py3-none-any.whl.metadata (10 kB)
#20 0.972 Collecting nibabel>=3.2.1 (from -r /tmp/requirements.txt (line 3))
#20 0.976   Downloading nibabel-5.2.1-py3-none-any.whl.metadata (8.8 kB)
#20 0.979 Requirement already satisfied: numpy>=1.21.6 in /usr/local/lib/python3.10/dist-packages (from -r /tmp/requirements.txt (line 4)) (1.23.5)
#20 1.002 Collecting pydicom>=2.3.0 (from -r /tmp/requirements.txt (line 5))
#20 1.007   Downloading pydicom-2.4.4-py3-none-any.whl.metadata (7.8 kB)
#20 1.010 Requirement already satisfied: setuptools>=59.5.0 in /usr/lib/python3/dist-packages (from -r /tmp/requirements.txt (line 6)) (59.6.0)
#20 1.064 Collecting SimpleITK>=2.0.0 (from -r /tmp/requirements.txt (line 7))
#20 1.068   Downloading SimpleITK-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.9 kB)
#20 1.129 Collecting scikit-image>=0.17.2 (from -r /tmp/requirements.txt (line 8))
#20 1.133   Downloading scikit_image-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (14 kB)
#20 1.157 Collecting numpy-stl>=2.12.0 (from -r /tmp/requirements.txt (line 9))
#20 1.161   Downloading numpy_stl-3.1.1-py3-none-any.whl.metadata (16 kB)
#20 1.263 Collecting trimesh>=3.8.11 (from -r /tmp/requirements.txt (line 10))
#20 1.267   Downloading trimesh-4.3.1-py3-none-any.whl.metadata (18 kB)
#20 1.322 Collecting torch>=1.12.0 (from -r /tmp/requirements.txt (line 11))
#20 1.327   Downloading torch-2.2.2-cp310-cp310-manylinux1_x86_64.whl.metadata (26 kB)
#20 1.536 Collecting pillow>=8.3 (from highdicom>=0.18.2->-r /tmp/requirements.txt (line 1))
#20 1.541   Downloading pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl.metadata (9.2 kB)
#20 1.606 Collecting pillow-jpls>=1.0 (from highdicom>=0.18.2->-r /tmp/requirements.txt (line 1))
#20 1.610   Downloading pillow_jpls-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.1 kB)
#20 1.690 Requirement already satisfied: packaging>=17 in /usr/local/lib/python3.10/dist-packages (from nibabel>=3.2.1->-r /tmp/requirements.txt (line 3)) (23.1)
#20 1.855 Collecting scipy>=1.9 (from scikit-image>=0.17.2->-r /tmp/requirements.txt (line 8))
#20 1.860   Downloading scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (60 kB)
#20 1.864      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.6/60.6 kB 231.5 MB/s eta 0:00:00
#20 1.897 Collecting networkx>=2.8 (from scikit-image>=0.17.2->-r /tmp/requirements.txt (line 8))
#20 1.901   Downloading networkx-3.3-py3-none-any.whl.metadata (5.1 kB)
#20 1.945 Collecting imageio>=2.33 (from scikit-image>=0.17.2->-r /tmp/requirements.txt (line 8))
#20 1.950   Downloading imageio-2.34.1-py3-none-any.whl.metadata (4.9 kB)
#20 1.993 Collecting tifffile>=2022.8.12 (from scikit-image>=0.17.2->-r /tmp/requirements.txt (line 8))
#20 1.998   Downloading tifffile-2024.4.18-py3-none-any.whl.metadata (31 kB)
#20 2.013 Collecting lazy-loader>=0.4 (from scikit-image>=0.17.2->-r /tmp/requirements.txt (line 8))
#20 2.017   Downloading lazy_loader-0.4-py3-none-any.whl.metadata (7.6 kB)
#20 2.041 Collecting python-utils>=3.4.5 (from numpy-stl>=2.12.0->-r /tmp/requirements.txt (line 9))
#20 2.044   Downloading python_utils-3.8.2-py2.py3-none-any.whl.metadata (9.7 kB)
#20 2.165 Collecting filelock (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.169   Downloading filelock-3.13.4-py3-none-any.whl.metadata (2.8 kB)
#20 2.189 Collecting typing-extensions>=4.8.0 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.192   Downloading typing_extensions-4.11.0-py3-none-any.whl.metadata (3.0 kB)
#20 2.220 Collecting sympy (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.224   Downloading sympy-1.12-py3-none-any.whl.metadata (12 kB)
#20 2.226 Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch>=1.12.0->-r /tmp/requirements.txt (line 11)) (3.1.3)
#20 2.252 Collecting fsspec (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.257   Downloading fsspec-2024.3.1-py3-none-any.whl.metadata (6.8 kB)
#20 2.274 Collecting nvidia-cuda-nvrtc-cu12==12.1.105 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.278   Downloading nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl.metadata (1.5 kB)
#20 2.293 Collecting nvidia-cuda-runtime-cu12==12.1.105 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.296   Downloading nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl.metadata (1.5 kB)
#20 2.315 Collecting nvidia-cuda-cupti-cu12==12.1.105 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.323   Downloading nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl.metadata (1.6 kB)
#20 2.340 Collecting nvidia-cudnn-cu12==8.9.2.26 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.343   Downloading nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl.metadata (1.6 kB)
#20 2.358 Collecting nvidia-cublas-cu12==12.1.3.1 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.362   Downloading nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl.metadata (1.5 kB)
#20 2.394 Collecting nvidia-cufft-cu12==11.0.2.54 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.399   Downloading nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl.metadata (1.5 kB)
#20 2.419 Collecting nvidia-curand-cu12==10.3.2.106 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.425   Downloading nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl.metadata (1.5 kB)
#20 2.459 Collecting nvidia-cusolver-cu12==11.4.5.107 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.463   Downloading nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl.metadata (1.6 kB)
#20 2.484 Collecting nvidia-cusparse-cu12==12.1.0.106 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.488   Downloading nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl.metadata (1.6 kB)
#20 2.504 Collecting nvidia-nccl-cu12==2.19.3 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.508   Downloading nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl.metadata (1.8 kB)
#20 2.530 Collecting nvidia-nvtx-cu12==12.1.105 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.534   Downloading nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl.metadata (1.7 kB)
#20 2.555 Collecting triton==2.2.0 (from torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.559   Downloading triton-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.4 kB)
#20 2.609 Collecting nvidia-nvjitlink-cu12 (from nvidia-cusolver-cu12==11.4.5.107->torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.612   Downloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)
#20 2.849 Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch>=1.12.0->-r /tmp/requirements.txt (line 11)) (2.1.3)
#20 2.869 Collecting mpmath>=0.19 (from sympy->torch>=1.12.0->-r /tmp/requirements.txt (line 11))
#20 2.873   Downloading mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)
#20 2.917 Downloading highdicom-0.22.0-py3-none-any.whl (825 kB)
#20 2.932    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 825.0/825.0 kB 85.5 MB/s eta 0:00:00
#20 2.939 Downloading monai-1.3.0-202310121228-py3-none-any.whl (1.3 MB)
#20 2.958    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.3/1.3 MB 99.3 MB/s eta 0:00:00
#20 2.963 Downloading nibabel-5.2.1-py3-none-any.whl (3.3 MB)
#20 2.993    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.3/3.3 MB 124.6 MB/s eta 0:00:00
#20 2.999 Downloading pydicom-2.4.4-py3-none-any.whl (1.8 MB)
#20 3.015    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 130.4 MB/s eta 0:00:00
#20 3.020 Downloading SimpleITK-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (52.7 MB)
#20 3.526    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.7/52.7 MB 99.8 MB/s eta 0:00:00
#20 3.533 Downloading scikit_image-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (14.7 MB)
#20 3.910    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14.7/14.7 MB 30.4 MB/s eta 0:00:00
#20 3.916 Downloading numpy_stl-3.1.1-py3-none-any.whl (20 kB)
#20 3.921 Downloading trimesh-4.3.1-py3-none-any.whl (693 kB)
#20 3.928    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 693.8/693.8 kB 179.7 MB/s eta 0:00:00
#20 3.936 Downloading torch-2.2.2-cp310-cp310-manylinux1_x86_64.whl (755.5 MB)
#20 11.35    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 755.5/755.5 MB 118.0 MB/s eta 0:00:00
#20 11.36 Downloading nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl (410.6 MB)
#20 16.03    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 410.6/410.6 MB 117.5 MB/s eta 0:00:00
#20 16.04 Downloading nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (14.1 MB)
#20 16.17    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14.1/14.1 MB 118.4 MB/s eta 0:00:00
#20 16.18 Downloading nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (23.7 MB)
#20 16.45    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23.7/23.7 MB 117.6 MB/s eta 0:00:00
#20 16.46 Downloading nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (823 kB)
#20 16.47    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 823.6/823.6 kB 153.8 MB/s eta 0:00:00
#20 16.49 Downloading nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl (731.7 MB)
#20 24.48    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 731.7/731.7 MB 117.1 MB/s eta 0:00:00
#20 24.49 Downloading nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl (121.6 MB)
#20 25.76    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.6/121.6 MB 117.6 MB/s eta 0:00:00
#20 25.77 Downloading nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl (56.5 MB)
#20 26.35    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.5/56.5 MB 63.6 MB/s eta 0:00:00
#20 26.36 Downloading nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl (124.2 MB)
#20 27.50    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 124.2/124.2 MB 104.0 MB/s eta 0:00:00
#20 27.51 Downloading nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl (196.0 MB)
#20 29.35    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 196.0/196.0 MB 108.0 MB/s eta 0:00:00
#20 29.36 Downloading nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl (166.0 MB)
#20 30.97    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 166.0/166.0 MB 116.7 MB/s eta 0:00:00
#20 30.98 Downloading nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (99 kB)
#20 30.98    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 99.1/99.1 kB 245.1 MB/s eta 0:00:00
#20 30.99 Downloading triton-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (167.9 MB)
#20 32.54    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 167.9/167.9 MB 123.3 MB/s eta 0:00:00
#20 32.54 Downloading imageio-2.34.1-py3-none-any.whl (313 kB)
#20 32.55    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 313.5/313.5 kB 231.8 MB/s eta 0:00:00
#20 32.55 Downloading lazy_loader-0.4-py3-none-any.whl (12 kB)
#20 32.56 Downloading networkx-3.3-py3-none-any.whl (1.7 MB)
#20 32.57    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 135.6 MB/s eta 0:00:00
#20 32.58 Downloading pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl (4.5 MB)
#20 32.62    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 121.7 MB/s eta 0:00:00
#20 32.63 Downloading pillow_jpls-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (305 kB)
#20 32.63    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 305.6/305.6 kB 239.6 MB/s eta 0:00:00
#20 32.64 Downloading python_utils-3.8.2-py2.py3-none-any.whl (27 kB)
#20 32.64 Downloading scipy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (38.6 MB)
#20 33.02    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 38.6/38.6 MB 108.0 MB/s eta 0:00:00
#20 33.02 Downloading tifffile-2024.4.18-py3-none-any.whl (224 kB)
#20 33.03    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 225.0/225.0 kB 110.7 MB/s eta 0:00:00
#20 33.04 Downloading typing_extensions-4.11.0-py3-none-any.whl (34 kB)
#20 33.04 Downloading filelock-3.13.4-py3-none-any.whl (11 kB)
#20 33.05 Downloading fsspec-2024.3.1-py3-none-any.whl (171 kB)
#20 33.06    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 172.0/172.0 kB 77.4 MB/s eta 0:00:00
#20 33.07 Downloading sympy-1.12-py3-none-any.whl (5.7 MB)
#20 33.12    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.7/5.7 MB 117.1 MB/s eta 0:00:00
#20 33.13 Downloading mpmath-1.3.0-py3-none-any.whl (536 kB)
#20 33.14    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.2/536.2 kB 146.2 MB/s eta 0:00:00
#20 33.14 Downloading nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (21.1 MB)
#20 33.34    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 21.1/21.1 MB 117.6 MB/s eta 0:00:00
#20 40.94 Installing collected packages: SimpleITK, mpmath, typing-extensions, trimesh, tifffile, sympy, scipy, pydicom, pillow, nvidia-nvtx-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, nibabel, networkx, lazy-loader, fsspec, filelock, triton, python-utils, pillow-jpls, nvidia-cusparse-cu12, nvidia-cudnn-cu12, imageio, scikit-image, nvidia-cusolver-cu12, numpy-stl, highdicom, torch, monai
#20 94.76 Successfully installed SimpleITK-2.3.1 filelock-3.13.4 fsspec-2024.3.1 highdicom-0.22.0 imageio-2.34.1 lazy-loader-0.4 monai-1.3.0 mpmath-1.3.0 networkx-3.3 nibabel-5.2.1 numpy-stl-3.1.1 nvidia-cublas-cu12-12.1.3.1 nvidia-cuda-cupti-cu12-12.1.105 nvidia-cuda-nvrtc-cu12-12.1.105 nvidia-cuda-runtime-cu12-12.1.105 nvidia-cudnn-cu12-8.9.2.26 nvidia-cufft-cu12-11.0.2.54 nvidia-curand-cu12-10.3.2.106 nvidia-cusolver-cu12-11.4.5.107 nvidia-cusparse-cu12-12.1.0.106 nvidia-nccl-cu12-2.19.3 nvidia-nvjitlink-cu12-12.4.127 nvidia-nvtx-cu12-12.1.105 pillow-10.3.0 pillow-jpls-1.3.2 pydicom-2.4.4 python-utils-3.8.2 scikit-image-0.23.2 scipy-1.13.0 sympy-1.12 tifffile-2024.4.18 torch-2.2.2 trimesh-4.3.1 triton-2.2.0 typing-extensions-4.11.0
#20 DONE 98.0s

#21 [15/21] COPY ./monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl /tmp/monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl
#21 DONE 0.5s

#22 [16/21] RUN pip install /tmp/monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl
#22 0.651 Defaulting to user installation because normal site-packages is not writeable
#22 0.747 Processing /tmp/monai_deploy_app_sdk-0.5.1+20.gb869749.dirty-py3-none-any.whl
#22 0.758 Requirement already satisfied: numpy>=1.21.6 in /usr/local/lib/python3.10/dist-packages (from monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (1.23.5)
#22 0.873 Collecting holoscan~=2.0 (from monai-deploy-app-sdk==0.5.1+20.gb869749.dirty)
#22 0.964   Downloading holoscan-2.0.0-cp310-cp310-manylinux_2_35_x86_64.whl.metadata (6.7 kB)
#22 1.036 Collecting colorama>=0.4.1 (from monai-deploy-app-sdk==0.5.1+20.gb869749.dirty)
#22 1.040   Downloading colorama-0.4.6-py2.py3-none-any.whl.metadata (17 kB)
#22 1.125 Collecting typeguard>=3.0.0 (from monai-deploy-app-sdk==0.5.1+20.gb869749.dirty)
#22 1.129   Downloading typeguard-4.2.1-py3-none-any.whl.metadata (3.7 kB)
#22 1.167 Requirement already satisfied: pip>=20.3 in /home/holoscan/.local/lib/python3.10/site-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (24.0)
#22 1.168 Requirement already satisfied: cupy-cuda12x==12.2 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (12.2.0)
#22 1.169 Requirement already satisfied: cloudpickle==2.2.1 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2.2.1)
#22 1.170 Requirement already satisfied: python-on-whales==0.60.1 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (0.60.1)
#22 1.171 Requirement already satisfied: Jinja2==3.1.3 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (3.1.3)
#22 1.172 Requirement already satisfied: packaging==23.1 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (23.1)
#22 1.172 Requirement already satisfied: pyyaml==6.0 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (6.0)
#22 1.173 Requirement already satisfied: requests==2.31.0 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2.31.0)
#22 1.174 Requirement already satisfied: psutil==5.9.6 in /usr/local/lib/python3.10/dist-packages (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (5.9.6)
#22 1.219 Collecting wheel-axle-runtime<1.0 (from holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty)
#22 1.224   Downloading wheel_axle_runtime-0.0.5-py3-none-any.whl.metadata (7.7 kB)
#22 1.261 Requirement already satisfied: fastrlock>=0.5 in /usr/local/lib/python3.10/dist-packages (from cupy-cuda12x==12.2->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (0.8.2)
#22 1.266 Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from Jinja2==3.1.3->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2.1.3)
#22 1.286 Requirement already satisfied: pydantic<2,>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (1.10.15)
#22 1.287 Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (4.66.2)
#22 1.288 Requirement already satisfied: typer>=0.4.1 in /usr/local/lib/python3.10/dist-packages (from python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (0.12.3)
#22 1.288 Requirement already satisfied: typing-extensions in /home/holoscan/.local/lib/python3.10/site-packages (from python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (4.11.0)
#22 1.297 Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests==2.31.0->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (3.3.2)
#22 1.298 Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests==2.31.0->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (3.7)
#22 1.298 Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests==2.31.0->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2.2.1)
#22 1.299 Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests==2.31.0->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2024.2.2)
#22 1.319 Requirement already satisfied: filelock in /home/holoscan/.local/lib/python3.10/site-packages (from wheel-axle-runtime<1.0->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (3.13.4)
#22 1.341 Requirement already satisfied: click>=8.0.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (8.1.7)
#22 1.342 Requirement already satisfied: shellingham>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (1.5.4)
#22 1.343 Requirement already satisfied: rich>=10.11.0 in /usr/local/lib/python3.10/dist-packages (from typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (13.7.1)
#22 1.380 Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.10/dist-packages (from rich>=10.11.0->typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (3.0.0)
#22 1.380 Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.10/dist-packages (from rich>=10.11.0->typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (2.17.2)
#22 1.400 Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.10/dist-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer>=0.4.1->python-on-whales==0.60.1->holoscan~=2.0->monai-deploy-app-sdk==0.5.1+20.gb869749.dirty) (0.1.2)
#22 1.415 Downloading colorama-0.4.6-py2.py3-none-any.whl (25 kB)
#22 1.437 Downloading holoscan-2.0.0-cp310-cp310-manylinux_2_35_x86_64.whl (33.2 MB)
#22 2.034    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 33.2/33.2 MB 28.4 MB/s eta 0:00:00
#22 2.039 Downloading typeguard-4.2.1-py3-none-any.whl (34 kB)
#22 2.064 Downloading wheel_axle_runtime-0.0.5-py3-none-any.whl (12 kB)
#22 2.573 Installing collected packages: wheel-axle-runtime, typeguard, colorama, holoscan, monai-deploy-app-sdk
#22 3.306 Successfully installed colorama-0.4.6 holoscan-2.0.0 monai-deploy-app-sdk-0.5.1+20.gb869749.dirty typeguard-4.2.1 wheel-axle-runtime-0.0.5
#22 DONE 4.1s

#23 [17/21] COPY ./models  /opt/holoscan/models
#23 DONE 0.3s

#24 [18/21] COPY ./map/app.json /etc/holoscan/app.json
#24 DONE 0.1s

#25 [19/21] COPY ./app.config /var/holoscan/app.yaml
#25 DONE 0.1s

#26 [20/21] COPY ./map/pkg.json /etc/holoscan/pkg.json
#26 DONE 0.1s

#27 [21/21] COPY ./app /opt/holoscan/app
#27 DONE 0.1s

#28 exporting to docker image format
#28 exporting layers
#28 exporting layers 161.2s done
#28 exporting manifest sha256:db72052410ed3875bd2689b115d7ea706f74caa44be88c9b455c9761c991f225 0.0s done
#28 exporting config sha256:4fe495cf55e1086e3b466f4799218faa2b377e58348ae9b51253b56b560295b3 0.0s done
#28 sending tarball
#28 ...

#29 importing to docker
#29 loading layer 414c50de1c1c 557.06kB / 2.97GB
#29 loading layer 414c50de1c1c 103.06MB / 2.97GB 6.1s
#29 loading layer 414c50de1c1c 325.88MB / 2.97GB 10.2s
#29 loading layer 414c50de1c1c 520.85MB / 2.97GB 14.3s
#29 loading layer 414c50de1c1c 719.72MB / 2.97GB 18.4s
#29 loading layer 414c50de1c1c 903.54MB / 2.97GB 22.5s
#29 loading layer 414c50de1c1c 1.11GB / 2.97GB 26.6s
#29 loading layer 414c50de1c1c 1.36GB / 2.97GB 30.7s
#29 loading layer 414c50de1c1c 1.56GB / 2.97GB 34.9s
#29 loading layer 414c50de1c1c 1.81GB / 2.97GB 38.9s
#29 loading layer 414c50de1c1c 1.97GB / 2.97GB 43.1s
#29 loading layer 414c50de1c1c 2.04GB / 2.97GB 50.1s
#29 loading layer 414c50de1c1c 2.17GB / 2.97GB 56.4s
#29 loading layer 414c50de1c1c 2.42GB / 2.97GB 62.5s
#29 loading layer 414c50de1c1c 2.63GB / 2.97GB 66.6s
#29 loading layer 414c50de1c1c 2.79GB / 2.97GB 70.7s
#29 loading layer 2d2337d002b1 32.77kB / 125.83kB
#29 loading layer 05a9e332a339 557.06kB / 67.35MB
#29 loading layer 05a9e332a339 59.60MB / 67.35MB 2.1s
#29 loading layer b466f5b35107 196.61kB / 17.81MB
#29 loading layer 66a2f54a1b71 493B / 493B
#29 loading layer 9b5da571c07c 316B / 316B
#29 loading layer 69c6b6426338 301B / 301B
#29 loading layer bfe49db5ef06 3.33kB / 3.33kB
#29 loading layer b466f5b35107 196.61kB / 17.81MB 5.1s done
#29 loading layer 414c50de1c1c 2.90GB / 2.97GB 85.2s done
#29 loading layer 2d2337d002b1 32.77kB / 125.83kB 8.8s done
#29 loading layer 05a9e332a339 59.60MB / 67.35MB 8.7s done
#29 loading layer 66a2f54a1b71 493B / 493B 3.4s done
#29 loading layer 9b5da571c07c 316B / 316B 2.7s done
#29 loading layer 69c6b6426338 301B / 301B 2.0s done
#29 loading layer bfe49db5ef06 3.33kB / 3.33kB 1.3s done
#29 DONE 85.2s

#28 exporting to docker image format
#28 sending tarball 146.6s done
#28 DONE 307.9s

#30 exporting cache to client directory
#30 preparing build cache for export
#30 writing layer sha256:014cff740c9ec6e9a30d0b859219a700ae880eb385d62095d348f5ea136d6015
#30 writing layer sha256:014cff740c9ec6e9a30d0b859219a700ae880eb385d62095d348f5ea136d6015 done
#30 writing layer sha256:0487800842442c7a031a39e1e1857bc6dae4b4f7e5daf3d625f7a8a4833fb364 done
#30 writing layer sha256:06c6aee94862daf0603783db4e1de6f8524b30ac9fbe0374ab3f1d85b2f76f7f done
#30 writing layer sha256:0a1756432df4a4350712d8ae5c003f1526bd2180800b3ae6301cfc9ccf370254 done
#30 writing layer sha256:0a77dcbd0e648ddc4f8e5230ade8fdb781d99e24fa4f13ca96a360c7f7e6751f done
#30 writing layer sha256:0ec682bf99715a9f88631226f3749e2271b8b9f254528ef61f65ed829984821c done
#30 writing layer sha256:121c2168995bf46d9888919d105fe5a0d03a3640ee73ed00c9081e10794e7c58 0.0s done
#30 writing layer sha256:1c5c3aa9c2c8bfd1b9eb36248f5b6d67b3db73ef43440f9dd897615771974b39 done
#30 writing layer sha256:1f73278b7f17492ce1a8b28b139d54596961596d6790dc20046fa6d5909f3e9c done
#30 writing layer sha256:20d331454f5fb557f2692dfbdbe092c718fd2cb55d5db9d661b62228dacca5c2 done
#30 writing layer sha256:229f01f9a7c2d258af634b5b41c5f5b2998c54c7557b3e3cfec465152ebb0c8b
#30 writing layer sha256:229f01f9a7c2d258af634b5b41c5f5b2998c54c7557b3e3cfec465152ebb0c8b 0.4s done
#30 writing layer sha256:238f69a43816e481f0295995fcf5fe74d59facf0f9f99734c8d0a2fb140630e0
#30 writing layer sha256:238f69a43816e481f0295995fcf5fe74d59facf0f9f99734c8d0a2fb140630e0 done
#30 writing layer sha256:2ad84487f9d4d31cd1e0a92697a5447dd241935253d036b272ef16d31620c1e7 done
#30 writing layer sha256:2f65750928993b5b31fe572d9e085b53853c5a344feeb0e8615898e285a8c256 done
#30 writing layer sha256:3777c6498f08c0400339c243e827d465075b7296eb2526e38d9b01c84f8764d8 done
#30 writing layer sha256:3e3e04011ebdba380ab129f0ee390626cb2a600623815ca756340c18bedb9517 done
#30 writing layer sha256:42619ce4a0c9e54cfd0ee41a8e5f27d58b3f51becabd1ac6de725fbe6c42b14a done
#30 writing layer sha256:49bdc9abf8a437ccff67cc11490ba52c976577992909856a86be872a34d3b950 done
#30 writing layer sha256:4b691ba9f48b41eaa0c754feba8366f1c030464fcbc55eeffa6c86675990933a done
#30 writing layer sha256:4d04a8db404f16c2704fa10739cb6745a0187713a21a6ef0deb34b48629b54c1 done
#30 writing layer sha256:4d6542f3a7f5dc637e92c4ce4f54ad7edbfc4fcf629380921a76a1eaaa042085 0.0s done
#30 writing layer sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 done
#30 writing layer sha256:542bc8c8d18fbc95e6794122c3593a4a693f8ab6dda4460406f4d7b1ae64a2bc done
#30 writing layer sha256:57f244836ad318f9bbb3b29856ae1a5b31038bfbb9b43d2466d51c199eb55041 done
#30 writing layer sha256:5b5b131e0f20db4cb8e568b623a95f8fc16ed1c6b322a9366df70b59a881f24f done
#30 writing layer sha256:5b90d17b5048adcadefd0b1e4dba9a99247a8827a887e1ca042df375c85b518d done
#30 writing layer sha256:62452179df7c18e292f141d4aec29e6aba9ff8270c893731169fc6f41dc07631 done
#30 writing layer sha256:6545b2e110c927b4bf5b0cfdb2ae791686a503dd32470362873ccb4375dcc13c
#30 writing layer sha256:6545b2e110c927b4bf5b0cfdb2ae791686a503dd32470362873ccb4375dcc13c 1.5s done
#30 writing layer sha256:6630c387f5f2115bca2e646fd0c2f64e1f3d5431c2e050abe607633883eda230
#30 writing layer sha256:6630c387f5f2115bca2e646fd0c2f64e1f3d5431c2e050abe607633883eda230 done
#30 writing layer sha256:6661e0146e77a8bcb03edbfda95bf7780c8bb4c4f98bc03a398c88f4b2403d12 done
#30 writing layer sha256:717ebf8c9c66ae393ad01e50dbac4413d7b026b9c97d4d348b22ad17052a1a35 done
#30 writing layer sha256:773c6815e5e7d6855a62f8c5e2fabce3d939ded36c5420f15b54dd7908cdbcfa done
#30 writing layer sha256:7852b73ea931e3a8d3287ee7ef3cf4bad068e44f046583bfc2b81336fb299284 done
#30 writing layer sha256:7f8ec130348bcdac81c295e37fe82b4a6e5e9a3ca980a6343809c561020d82d7 done
#30 writing layer sha256:80885adcad6b5d021bb9f68b6c952018085bb4ce72011bdc0cf7fe8178b5960b done
#30 writing layer sha256:80baedfe7e90864fa675693451fc0755b31995ecc8575aeaf4e4366d5634af45 done
#30 writing layer sha256:82a3436133b2b17bb407c7fe488932aa0ca55411f23ab55c34a6134b287c6a27 done
#30 writing layer sha256:8371d15eb4d69b1d98174dd098b8ddd5c4f19ec6f8d8b67e72dfa9891dc454b4 done
#30 writing layer sha256:85713f9b166b5add777c524ee807f6265d88b967cbeb9f961d6b09bf220c9a65 done
#30 writing layer sha256:8ca81d63d0fea573e7b16da41370b55162415a5c07feac92012b101b79f8d5ae done
#30 writing layer sha256:8fe00505006a09966e763918147ef6ed55bb6695b26e4940c780ee430dc5da8e done
#30 writing layer sha256:90eae6faa5cc5ba62f12c25915cdfb1a7a51abfba0d05cb5818c3f908f4e345f done
#30 writing layer sha256:9205d97d9d3e906698bcc6c42d45727c2fa6ec2622abf953d46778c3b8c78edc done
#30 writing layer sha256:993369dbcc13162a6654d2a3e990b8d8b5f37963564d25710e12764337261ae3 done
#30 writing layer sha256:99e42a4adebadb39bf55bf94bbd9fb8034230ee19b6b0a42e6ff96f2e7794f30 done
#30 writing layer sha256:9ac855545fa90ed2bf3b388fdff9ef06ac9427b0c0fca07c9e59161983d8827e done
#30 writing layer sha256:9d19ee268e0d7bcf6716e6658ee1b0384a71d6f2f9aa1ae2085610cf7c7b316f done
#30 writing layer sha256:9fafbd4203c4fefe007a462e0d2cd4c1c7c41db2cfdc58d212279e1b9b4b230c done
#30 writing layer sha256:a1748eee9d376f97bd19225ba61dfada9986f063f4fc429e435f157abb629fc6 done
#30 writing layer sha256:a251fe5ae6c6d2d5034e4ca88b5dfe5d4827ff90b18e9b143a073232a32bb18d done
#30 writing layer sha256:a3c41b99822f620cfd6e42b3b0760c1fa99ebb77782013146ff5531da4f4064b 0.0s done
#30 writing layer sha256:a68f4e0ec09ec3b78cb4cf8e4511d658e34e7b6f676d7806ad9703194ff17604 done
#30 writing layer sha256:a8e4decc8f7289623b8fd7b9ba1ca555b5a755ebdbf81328d68209f148d9e602 done
#30 writing layer sha256:afde1c269453ce68a0f2b54c1ba8c5ecddeb18a19e5618a4acdef1f0fe3921af done
#30 writing layer sha256:b406feb20a37b8c87ef4f5ef814039e3adc90473d50c366b7d9bb6ded4e94a2e done
#30 writing layer sha256:b48a5fafcaba74eb5d7e7665601509e2889285b50a04b5b639a23f8adc818157 done
#30 writing layer sha256:ba9f7c75e4dd7942b944679995365aab766d3677da2e69e1d74472f471a484dd done
#30 writing layer sha256:bdc13166216ae226fa6976f9ce91f4f259d43972f1e0a9b723e436919534b2f4 done
#30 writing layer sha256:c5d17b776c61f416be379c9d1049e897e197b748dda4284d991324b18fc6c9df 0.0s done
#30 writing layer sha256:c815f0be64eded102822d81e029bd23b0d8d9a0fbfeb492ec0b4b0bc4ee777bf done
#30 writing layer sha256:c98533d2908f36a5e9b52faae83809b3b6865b50e90e2817308acfc64cd3655f done
#30 writing layer sha256:d7da5c5e9a40c476c4b3188a845e3276dedfd752e015ea5113df5af64d4d43f7 done
#30 writing layer sha256:db20521a869adda8244cb64b783c65e1a911efaae0e73ae00e4a34ea6213d6ce done
#30 writing layer sha256:df4fd0ac710d7af949afbc6d25b5b4daf3f0596dabf3dec36fa7ca8fa6e1d049 done
#30 writing layer sha256:e291ddecfbe16b95ee9e90b5e90b1a3d0cfd53dc5e720d6b0f3d28e4a47cf5ac done
#30 writing layer sha256:e8acb678f16bc0c369d5cf9c184f2d3a1c773986816526e5e3e9c0354f7e757f done
#30 writing layer sha256:e9225f7ab6606813ec9acba98a064826ebfd6713a9645a58cd068538af1ecddb done
#30 writing layer sha256:f249faf9663a96b0911a903f8803b11a553c59b698013fb8343492fefdaaea90 done
#30 writing layer sha256:f608e2fbff86e98627b7e462057e7d2416522096d73fe4664b82fe6ce8a4047d done
#30 writing layer sha256:f65d191416580d6c38e3d95eee12377b75a4df548be1492618ce2a8c3c41b99e done
#30 writing layer sha256:fbfd4de480c7037f0604cf64cef29e59cfb27193b257d66d110ff82ec6fc6715
#30 writing layer sha256:fbfd4de480c7037f0604cf64cef29e59cfb27193b257d66d110ff82ec6fc6715 47.3s done
#30 writing layer sha256:fcb10e9f191b92679f1cac7623b152400f374a3e3d90f3d2248bfced02b6bdca
#30 preparing build cache for export 50.0s done
#30 writing layer sha256:fcb10e9f191b92679f1cac7623b152400f374a3e3d90f3d2248bfced02b6bdca 0.0s done
#30 writing config sha256:1af23d9bb67b68807f26cc256ea0ba50bba21a62a52cdfab0bc82567fef2a35e 0.0s done
#30 writing cache manifest sha256:3e6252fe3fb73d49377342ccff9ce009489fdd34a8709b3c2e0e2c2cd1a54372 0.0s done
#30 DONE 50.0s
[2024-04-23 17:00:27,074] [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

!docker image ls | grep {tag_prefix}
my_app-x64-workstation-dgpu-linux-amd64                                                   1.0                 4fe495cf55e1   5 minutes ago       17.9GB

We can choose to display and inspect the MAP manifests by running the container with the show command. Furthermore, we can also extract the manifests and other contents in the MAP by using the extract command while mapping specific folder to the host’s (we know that our MAP is compliant and supports these commands).

Note

The host folder for storing the extracted content must first be created by the user, and if it has been created by Docker on running the container, the folder needs to be deleted and re-created.

!echo "Display manifests and extract MAP contents to the host folder, ./export"
!docker run --rm {tag_prefix}-x64-workstation-dgpu-linux-amd64:1.0 show
!rm -rf `pwd`/export && mkdir -p `pwd`/export
!docker run --rm -v `pwd`/export/:/var/run/holoscan/export/ {tag_prefix}-x64-workstation-dgpu-linux-amd64:1.0 extract
!ls `pwd`/export
Display manifests and extract MAP contents to the host folder, ./export

============================== 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.5.1",
  "timeout": 0,
  "version": 1,
  "workingDirectory": "/var/holoscan"
}

============================== pkg.json ==============================
{
  "apiVersion": "1.0.0",
  "applicationRoot": "/opt/holoscan/app",
  "modelRoot": "/opt/holoscan/models",
  "models": {
    "model": "/opt/holoscan/models/model"
  },
  "resources": {
    "cpu": 1,
    "gpu": 1,
    "memory": "1Gi",
    "gpuMemory": "6Gi"
  },
  "version": 1,
  "platformConfig": "dgpu"
}

2024-04-24 00:00:30 [INFO] Copying application from /opt/holoscan/app to /var/run/holoscan/export/app

2024-04-24 00:00:30 [INFO] Copying application manifest file from /etc/holoscan/app.json to /var/run/holoscan/export/config/app.json
2024-04-24 00:00:30 [INFO] Copying pkg manifest file from /etc/holoscan/pkg.json to /var/run/holoscan/export/config/pkg.json
2024-04-24 00:00:30 [INFO] Copying application configuration from /var/holoscan/app.yaml to /var/run/holoscan/export/config/app.yaml

2024-04-24 00:00:30 [INFO] Copying models from /opt/holoscan/models to /var/run/holoscan/export/models

2024-04-24 00:00:30 [INFO] Copying documentation from /opt/holoscan/docs/ to /var/run/holoscan/export/docs
2024-04-24 00:00:30 [INFO] '/opt/holoscan/docs/' cannot be found.

app  config  models

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
[2024-04-23 17:00:31,900] [INFO] (runner) - Checking dependencies...
[2024-04-23 17:00:31,900] [INFO] (runner) - --> Verifying if "docker" is installed...

[2024-04-23 17:00:31,901] [INFO] (runner) - --> Verifying if "docker-buildx" is installed...

[2024-04-23 17:00:31,901] [INFO] (runner) - --> Verifying if "my_app-x64-workstation-dgpu-linux-amd64:1.0" is available...

[2024-04-23 17:00:31,971] [INFO] (runner) - Reading HAP/MAP manifest...
Preparing to copy...?25lCopying from container - 0B?25hSuccessfully copied 2.56kB to /tmp/tmpxev2hpxc/app.json
Preparing to copy...?25lCopying from container - 0B?25hSuccessfully copied 2.05kB to /tmp/tmpxev2hpxc/pkg.json
[2024-04-23 17:00:32,215] [INFO] (runner) - --> Verifying if "nvidia-ctk" is installed...

[2024-04-23 17:00:32,216] [INFO] (runner) - --> Verifying "nvidia-ctk" version...

[2024-04-23 17:00:32,528] [INFO] (common) - Launching container (6ffaea9917bd) using image 'my_app-x64-workstation-dgpu-linux-amd64:1.0'...
    container name:      fervent_bell
    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:             
    group_add:           44
2024-04-24 00:00:33 [INFO] Launching application python3 /opt/holoscan/app ...

[2024-04-24 00:00:36,780] [INFO] (root) - Parsed args: Namespace(log_level=None, input=None, output=None, model=None, workdir=None, argv=['/opt/holoscan/app'])

[2024-04-24 00:00:36,783] [INFO] (root) - AppContext object: AppContext(input_path=/var/holoscan/input, output_path=/var/holoscan/output, model_path=/opt/holoscan/models, workdir=/var/holoscan)

[2024-04-24 00:00:36,784] [INFO] (root) - End compose

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

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

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

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

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

2024-04-24 00:00:36.818 INFO  gxf/std/greedy_scheduler.cpp@191: Scheduling 8 entities

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

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

[2024-04-24 00:00:36,819] [INFO] (monai.deploy.operators.dicom_data_loader_operator.DICOMDataLoaderOperator) - No or invalid input path from the optional input port: None

[2024-04-24 00:00:37,735] [INFO] (root) - Finding series for Selection named: CT Series

[2024-04-24 00:00:37,735] [INFO] (root) - Searching study, : 1.3.6.1.4.1.14519.5.2.1.7085.2626.822645453932810382886582736291

  # of series: 1

[2024-04-24 00:00:37,735] [INFO] (root) - Working on series, instance UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239

[2024-04-24 00:00:37,735] [INFO] (root) - On attribute: 'StudyDescription' to match value: '(.*?)'

[2024-04-24 00:00:37,735] [INFO] (root) -     Series attribute StudyDescription value: CT ABDOMEN W IV CONTRAST

[2024-04-24 00:00:37,735] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2024-04-24 00:00:37,735] [INFO] (root) - On attribute: 'Modality' to match value: '(?i)CT'

[2024-04-24 00:00:37,735] [INFO] (root) -     Series attribute Modality value: CT

[2024-04-24 00:00:37,735] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2024-04-24 00:00:37,735] [INFO] (root) - On attribute: 'SeriesDescription' to match value: '(.*?)'

[2024-04-24 00:00:37,735] [INFO] (root) -     Series attribute SeriesDescription value: ABD/PANC 3.0 B31f

[2024-04-24 00:00:37,735] [INFO] (root) - Series attribute string value did not match. Try regEx.

[2024-04-24 00:00:37,735] [INFO] (root) - Selected Series, UID: 1.3.6.1.4.1.14519.5.2.1.7085.2626.119403521930927333027265674239

[2024-04-24 00:00:38,147] [INFO] (root) - Parsing from bundle_path: /opt/holoscan/models/model/model.ts

[2024-04-24 00:00:41,393] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConversionOperator) - Output will be saved in file /var/holoscan/output/stl/spleen.stl.

[2024-04-24 00:00:43,045] [INFO] (monai.deploy.operators.stl_conversion_operator.SpatialImage) - 3D image

[2024-04-24 00:00:43,045] [INFO] (monai.deploy.operators.stl_conversion_operator.STLConverter) - Image ndarray shape:(204, 512, 512)

/home/holoscan/.local/lib/python3.10/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 https://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(

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy Image-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy attributes of module "Specimen"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy Patient-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy attributes of module "Patient"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Subject"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy Study-related attributes from dataset "1.3.6.1.4.1.14519.5.2.1.7085.2626.936983343951485811186213470191"

[2024-04-24 00:00:53,817] [INFO] (highdicom.base) - copy attributes of module "General Study"

[2024-04-24 00:00:53,818] [INFO] (highdicom.base) - copy attributes of module "Patient Study"

[2024-04-24 00:00:53,818] [INFO] (highdicom.base) - copy attributes of module "Clinical Trial Study"

2024-04-24 00:00:53.906 INFO  gxf/std/greedy_scheduler.cpp@372: Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock.

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

2024-04-24 00:00:53.908 INFO  gxf/std/greedy_scheduler.cpp@401: Scheduler finished.

[info] [gxf_executor.cpp:1887] Graph execution finished.

[2024-04-24 00:00:53,915] [INFO] (app.AISpleenSegApp) - End run

[2024-04-23 17:00:55,176] [INFO] (common) - Container 'fervent_bell'(6ffaea9917bd) exited.
!ls $HOLOSCAN_OUTPUT_PATH
1.2.826.0.1.3680043.10.511.3.91573472497917482579554686696126426.dcm  stl