Creating a Simple Image Processing App with MONAI Deploy App SDK#

This tutorial shows how a simple image processing application can be created with MONAI Deploy App SDK.

Creating Operators and connecting them in Application class#

We will implement an application that consists of three Operators:

  • SobelOperator: Apply a Sobel edge detector.

    • Input: a file path (Path)

    • Output: an image object in memory (Image)

  • MedianOperator: Apply a Median filter for noise reduction.

    • Input: an image object in memory (Image)

    • Output: an image object in memory (Image)

  • GaussianOperator: Apply a Gaussian filter for smoothening.

    • Input: an image object in memory (Image)

    • Output: a file path (Path)

The workflow of the application would look like this.

        %%{init: {"theme": "base", "themeVariables": { "fontSize": "16px"}} }%%

classDiagram
    direction LR

    SobelOperator --|> MedianOperator : image...image
    MedianOperator --|> GaussianOperator : image...image

    class SobelOperator {
        <in>image : Path
        image(out) : IN_MEMORY
    }
    class MedianOperator {
        <in>image : IN_MEMORY
        image(out) : IN_MEMORY
    }
    class GaussianOperator {
        <in>image : IN_MEMORY
        image(out) : Path
    }
    

Setup environment#

# Install necessary image loading/processing packages for the application
!python -c "import PIL" || pip install -q "Pillow"
!python -c "import skimage" || pip install -q "scikit-image"
!python -c "import matplotlib" || pip install -q "matplotlib"
%matplotlib inline

# Install MONAI Deploy App SDK package
!python -c "import monai.deploy" || pip install -q "monai-deploy-app-sdk"
<frozen importlib._bootstrap_external>:1184: FutureWarning: The cuda.cudart module is deprecated and will be removed in a future release, please switch to use the cuda.bindings.runtime module instead.
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages/monai/deploy/utils/importutil.py:20: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  import pkg_resources

Download test input#

We will use a test input from the following.

Case courtesy of Dr Bruno Di Muzio, Radiopaedia.org. From the case rID: 41113

test_input_folder = "/tmp/simple_app"
test_input_path = test_input_folder + "/normal-brain-mri-4.png"

!python -c "import wget" || pip install -q "wget"
!mkdir -p {test_input_folder}

from skimage import io
import wget


wget.download("https://user-images.githubusercontent.com/1928522/133383228-2357d62d-316c-46ad-af8a-359b56f25c87.png", test_input_path)

print(f"Test input file path: {test_input_path!r}")

test_image = io.imread(test_input_path)
io.imshow(test_image)
Test input file path: '/tmp/simple_app/normal-brain-mri-4.png'
/tmp/ipykernel_1107452/2727006292.py:16: FutureWarning: `imshow` is deprecated since version 0.25 and will be removed in version 0.27. Please use `matplotlib`, `napari`, etc. to visualize images.
  io.imshow(test_image)
<matplotlib.image.AxesImage at 0x7c2391a03400>
../../_images/3092c5062aad057632fa6b3e680ecd32c3cfc740c39af9d95e120c79106f8a2e.png

Set up environment variables#

The application uses well-known environment variables for the input/output data path, working dir, as well as AI model file path if applicable. Defaults are used if these environment variable are absent.

In this example, only the input data path and output path need to be set.

output_path = "output"
%env HOLOSCAN_INPUT_FOLDER {test_input_folder}
%env HOLOSCAN_INPUT_PATH {test_input_path}
%env HOLOSCAN_OUTPUT_PATH {output_path}
%ls $HOLOSCAN_INPUT_PATH
env: HOLOSCAN_INPUT_FOLDER=/tmp/simple_app
env: HOLOSCAN_INPUT_PATH=/tmp/simple_app/normal-brain-mri-4.png
env: HOLOSCAN_OUTPUT_PATH=output
/tmp/simple_app/normal-brain-mri-4.png

Setup imports#

Let’s import necessary classes/decorators to define the application and operators.

from pathlib import Path

from monai.deploy.conditions import CountCondition
from monai.deploy.core import AppContext, Application, ConditionType, Fragment, Operator, OperatorSpec
<frozen importlib._bootstrap_external>:1184: FutureWarning: The cuda.cudart module is deprecated and will be removed in a future release, please switch to use the cuda.bindings.runtime module instead.
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages/monai/deploy/utils/importutil.py:20: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  import pkg_resources

Creating Operator classes#

Each Operator class inherits from the Operator class, with the input and output ports of the operator specified using the setup method. Business logic would be implemented in the compute method.

Note

  • the way to specify operator input and output in this version of the App SDK is different from versions, up to and including V0.5, where Python decorators are used. Decorator support will be re-introduced in future releases

  • the first operator(SobelOperator)’s input and the last operator(GaussianOperator)’s output are data paths, which are not data types supported by operator ports but as object can be used as optional input and output. In the example, these paths are passed in as arguments to the constructor and the operator classes have defined logic on using the paths, e.g. reading from or writing to the path. The application class is responsible for setting the path by parsing the well-known environment variables

SobelOperator#

SobelOperator is the first operator (the root operator in the workflow graph). It reads from the input file/folder path, which is passed in as an argument on the constructor and assigned to an attribute.

Once loaded and processed, the image data (as a Numpy array) is set to the output (op_output.emit(value, label)).

class SobelOperator(Operator):
    """This Operator implements a Sobel edge detector.

    It has the following input and output:
        single input:
          a image file, first one found in the input folder
        single output:
          array object in memory
    """

    DEFAULT_INPUT_FOLDER = Path.cwd() / "input"

    def __init__(self, fragment: Fragment, *args, input_path: Path, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): An instance of the Application class which is derived from Fragment
            input_path (Path): The path of the input image file or folder containing the image file
        """
        self.index = 0

        # May want to validate the path, but it should really be validated when the compute function is called, also,
        # when file path as input is supported in the operator or execution context, input_folder needs not an attribute.
        self.input_path = (
            input_path if input_path else SobelOperator.DEFAULT_INPUT_FOLDER
        )

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.output("out1")

    def compute(self, op_input, op_output, context):
        from skimage import filters, io

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")

        # Ideally the op_input or execution context should provide the file path
        # to read data from, for operators that are file input based.
        # For now, use a temporary way to get input path. e.g. value set on init
        input_path = self.input_path
        print(f"Input from: {input_path}, whose absolute path: {input_path.absolute()}")
        if input_path.is_dir():
            input_path = next(input_path.glob("*.*"))  # take the first file

        data_in = io.imread(input_path)[:, :, :3]  # discard alpha channel if exists
        data_out = filters.sobel(data_in)

        op_output.emit(data_out, "out1")

MedianOperator#

MedianOperator is a middle operator that accepts data from SobelOperator and passes the processed image data to GaussianOperator.

Its input data type is image in Numpy array. Once received at the input (op_input.receive(label)), the image is transformed and set to the output (op_output.emit(value, label)).

class MedianOperator(Operator):
    """This Operator implements a noise reduction.

    The algorithm is based on the median operator.
    It ingests a single input and provides a single output, both are in-memory image arrays
    """

    # Define __init__ method with super().__init__() if you want to override the default behavior.
    def __init__(self, fragment: Fragment, *args, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): The instance of Application class which is derived from Fragment
        """

        self.index = 0

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input("in1")
        spec.output("out1")

    def compute(self, op_input, op_output, context):
        from skimage.filters import median

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")
        data_in = op_input.receive("in1")
        data_out = median(data_in)
        op_output.emit(data_out, "out1")

GaussianOperator#

GaussianOperator is the last operator (a leaf operator in the workflow graph) and saves the processed image to a file, whose path is provided via an argument on the constructor.

This operator can also output the image in Numpy array in memory without requiring a receiver for it. This can be set up by using the optional output condition in the function setup.

class GaussianOperator(Operator):
    """This Operator implements a smoothening based on Gaussian.

    It has the following input and output:
        single input:
          an image array object
        single output:
          an image array object, without enforcing a downstream receiver

    Besides, this operator also saves the image file in the given output folder.
    """

    DEFAULT_OUTPUT_FOLDER = Path.cwd() / "output"

    def __init__(self, fragment: Fragment, *args, output_folder: Path, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): The instance of Application class which is derived from Fragment
            output_folder (Path): The folder to save the output file.
        """
        self.output_folder = output_folder if output_folder else GaussianOperator.DEFAULT_OUTPUT_FOLDER
        self.index = 0

        # If `self.sigma_default` is set here (e.g., `self.sigma_default = 0.2`), then
        # the default value by `param()` in `setup()` will be ignored.
        # (you can just call `spec.param("sigma_default")` in `setup()` to use the
        # default value)
        self.sigma_default = 0.2
        self.channel_axis = 2

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input("in1")
        spec.output("out1").condition(ConditionType.NONE)  # Condition is for no or not-ready receiver ports.
        spec.param("sigma_default", 0.2)
        spec.param("channel_axis", 2)

    def compute(self, op_input, op_output, context):
        from skimage.filters import gaussian
        from skimage.io import imsave
        import numpy as np

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")

        data_in = op_input.receive("in1")
        data_out = gaussian(data_in, sigma=self.sigma_default, channel_axis=self.channel_axis)

        # Make sure the data type is what PIL Image can support, as the imsave function calls PIL Image fromarray()
        # Some details can be found at https://stackoverflow.com/questions/55319949/pil-typeerror-cannot-handle-this-data-type
        print(f"Data type of output: {type(data_out)!r}, max = {np.max(data_out)!r}")
        if np.max(data_out) <= 1:
            data_out = (data_out*255).astype(np.uint8)
        print(f"Data type of output post conversion: {type(data_out)!r}, max = {np.max(data_out)!r}")

        # For now, use attribute of self to find the output path.
        self.output_folder.mkdir(parents=True, exist_ok=True)
        output_path = self.output_folder / "final_output.png"
        imsave(output_path, data_out)

        op_output.emit(data_out, "out1")

Creating Application class#

Our application class would look like below.

It defines App class, inheriting Application class.

In compose() method, objects of SobelOperator, MedianOperator, and GaussianOperator classes are created and connected through self.add_flow().

add_flow(source_op, destination_op, io_map=None)

io_map is a dictionary of mapping from the source operator’s label to the destination operator’s label(s) and its type is Set[Tuple[str, str]].

We can skip specifying io_map if both the number of source_op’s outputs and the number of destination_op’s inputs are one so self.add_flow(sobel_op, median_op) is same with self.add_flow(sobel_op, median_op, {"image": "image"}) or self.add_flow(sobel_op, median_op, {"image": {"image"}}).

class App(Application):
    """This is a very basic application.

    This showcases the MONAI Deploy application framework.
    """

    # App's name. <class name>('App') if not specified.
    name = "simple_imaging_app"
    # App's description. <class docstring> if not specified.
    description = "This is a very simple application."
    # App's version. <git version tag> or '0.0.0' if not specified.
    version = "0.1.0"

    def compose(self):
        """This application has three operators.

        Each operator has a single input and a single output port.
        Each operator performs some kind of image processing function.
        """
        app_context = Application.init_app_context({})  # Do not pass argv in Jupyter notebook
        sample_data_path = Path(app_context.input_path)
        output_data_path = Path(app_context.output_path)
        print(f"sample_data_path: {sample_data_path}")

        # Please note that the Application object, self, is passed as the first positional argument
        # and the others as kwargs.
        # Also note the CountCondition of 1 on the first operator, indicating to the application executor
        # to invoke this operator, hence the pipeline, only once.
        sobel_op = SobelOperator(self, CountCondition(self, 1), input_path=sample_data_path, name="sobel_op")
        median_op = MedianOperator(self, name="median_op")
        gaussian_op = GaussianOperator(self, output_folder=output_data_path, name="gaussian_op")
        self.add_flow(
            sobel_op,
            median_op,
            {
                ("out1", "in1"),
            },
        )
        self.add_flow(
            median_op,
            gaussian_op,
            {
                (
                    "out1",
                    "in1",
                )
            },
        )  # Using port name is optional for single port cases


if __name__ == "__main__":
    print("The statement, App().run(), is needed when this is run directly by the interpreter.")
    # App().run()
The statement, App().run(), is needed when this is run directly by the interpreter.

Executing app locally#

We can execute the app in the Jupyter notebook.

!rm -rf {output_path}
App().run()
[info] [fragment.cpp:969] Loading extensions from configs...
[2025-08-04 15:38:13,280] [INFO] (root) - Parsed args: Namespace(log_level=None, input=None, output=None, model=None, workdir=None, triton_server_netloc=None, argv=[])
[2025-08-04 15:38:13,288] [INFO] (root) - AppContext object: AppContext(input_path=/tmp/simple_app/normal-brain-mri-4.png, output_path=output, model_path=models, workdir=), triton_server_netloc=
sample_data_path: /tmp/simple_app/normal-brain-mri-4.png
[info] [gxf_executor.cpp:344] Creating context
[info] [gxf_executor.cpp:2508] Activating Graph...
[info] [gxf_executor.cpp:2579] Running Graph...
[info] [gxf_executor.cpp:2581] Waiting for completion...
[info] [greedy_scheduler.cpp:191] Scheduling 3 entities
Number of times operator sobel_op whose class is defined in __main__ called: 1
Input from: /tmp/simple_app/normal-brain-mri-4.png, whose absolute path: /tmp/simple_app/normal-brain-mri-4.png
Number of times operator median_op whose class is defined in __main__ called: 1
Number of times operator gaussian_op whose class is defined in __main__ called: 1
Data type of output: <class 'numpy.ndarray'>, max = np.float64(0.35821119421406195)
Data type of output post conversion: <class 'numpy.ndarray'>, max = np.uint8(91)
[info] [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] [greedy_scheduler.cpp:401] Scheduler finished.
[info] [gxf_executor.cpp:2588] Deactivating Graph...
[info] [gxf_executor.cpp:2597] Graph execution finished.
[info] [gxf_executor.cpp:379] Destroying context
!ls {output_path}
final_output.png
output_image_path = output_path + "/final_output.png"
output_image = io.imread(output_image_path)
io.imshow(output_image)
/tmp/ipykernel_1107452/1643627018.py:3: FutureWarning: `imshow` is deprecated since version 0.25 and will be removed in version 0.27. Please use `matplotlib`, `napari`, etc. to visualize images.
  io.imshow(output_image)
<matplotlib.image.AxesImage at 0x7c229edc0880>
../../_images/d6295ad52a7f6da3b3a61b7651288c84afca8c47bf7cbd936d2c5eecdb4ba2ae.png

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:

simple_imaging_app
├── __main__.py
├── app.py
├── gaussian_operator.py
├── median_operator.py
└── sobel_operator.py

Note

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

# Create an application folder
!mkdir -p simple_imaging_app

sobel_operator.py#

%%writefile simple_imaging_app/sobel_operator.py

from pathlib import Path
from monai.deploy.core import Fragment, Operator, OperatorSpec

class SobelOperator(Operator):
    """This Operator implements a Sobel edge detector.

    It has the following input and output:
        single input:
          a image file, first one found in the input folder
        single output:
          array object in memory
    """

    DEFAULT_INPUT_FOLDER = Path.cwd() / "input"

    def __init__(self, fragment: Fragment, *args, input_path: Path, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): An instance of the Application class which is derived from Fragment
            input_path (Path): The path of the input image file or folder containing the image file
        """
        self.index = 0

        # May want to validate the path, but it should really be validated when the compute function is called, also,
        # when file path as input is supported in the operator or execution context, input_folder needs not an attribute.
        self.input_path = (
            input_path if input_path else SobelOperator.DEFAULT_INPUT_FOLDER
        )

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.output("out1")

    def compute(self, op_input, op_output, context):
        from skimage import filters, io

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")

        # Ideally the op_input or execution context should provide the file path
        # to read data from, for operators that are file input based.
        # For now, use a temporary way to get input path. e.g. value set on init
        input_path = self.input_path
        print(f"Input from: {input_path}, whose absolute path: {input_path.absolute()}")
        if input_path.is_dir():
            input_path = next(input_path.glob("*.*"))  # take the first file

        data_in = io.imread(input_path)[:, :, :3]  # discard alpha channel if exists
        data_out = filters.sobel(data_in)

        op_output.emit(data_out, "out1")
Overwriting simple_imaging_app/sobel_operator.py

median_operator.py#

%%writefile simple_imaging_app/median_operator.py
from monai.deploy.core import Fragment, Operator, OperatorSpec


# Decorator support is not available in this version of the SDK, to be re-introduced later
# @md.env(pip_packages=["scikit-image >= 0.17.2"])
class MedianOperator(Operator):
    """This Operator implements a noise reduction.

    The algorithm is based on the median operator.
    It ingests a single input and provides a single output, both are in-memory image arrays
    """

    # Define __init__ method with super().__init__() if you want to override the default behavior.
    def __init__(self, fragment: Fragment, *args, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): The instance of Application class which is derived from Fragment
        """

        self.index = 0

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input("in1")
        spec.output("out1")

    def compute(self, op_input, op_output, context):
        from skimage.filters import median

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")
        data_in = op_input.receive("in1")
        data_out = median(data_in)
        op_output.emit(data_out, "out1")
Overwriting simple_imaging_app/median_operator.py

gaussian_operator.py#

%%writefile simple_imaging_app/gaussian_operator.py
from pathlib import Path

from monai.deploy.core import ConditionType, Fragment, Operator, OperatorSpec


# Decorator support is not available in this version of the SDK, to be re-introduced later
# @md.env(pip_packages=["scikit-image >= 0.17.2"])
class GaussianOperator(Operator):
    """This Operator implements a smoothening based on Gaussian.

    It has the following input and output:
        single input:
          an image array object
        single output:
          an image array object, without enforcing a downstream receiver

    Besides, this operator also saves the image file in the given output folder.
    """

    DEFAULT_OUTPUT_FOLDER = Path.cwd() / "output"

    def __init__(self, fragment: Fragment, *args, output_folder: Path, **kwargs):
        """Create an instance to be part of the given application (fragment).

        Args:
            fragment (Fragment): The instance of Application class which is derived from Fragment
            output_folder (Path): The folder to save the output file.
        """
        self.output_folder = output_folder if output_folder else GaussianOperator.DEFAULT_OUTPUT_FOLDER
        self.index = 0

        # If `self.sigma_default` is set here (e.g., `self.sigma_default = 0.2`), then
        # the default value by `param()` in `setup()` will be ignored.
        # (you can just call `spec.param("sigma_default")` in `setup()` to use the
        # default value)
        self.sigma_default = 0.2
        self.channel_axis = 2

        # Need to call the base class constructor last
        super().__init__(fragment, *args, **kwargs)

    def setup(self, spec: OperatorSpec):
        spec.input("in1")
        spec.output("out1").condition(ConditionType.NONE)  # Condition is for no or not-ready receiver ports.
        spec.param("sigma_default", 0.2)
        spec.param("channel_axis", 2)

    def compute(self, op_input, op_output, context):
        from skimage.filters import gaussian
        from skimage.io import imsave
        import numpy as np

        self.index += 1
        print(f"Number of times operator {self.name} whose class is defined in {__name__} called: {self.index}")

        data_in = op_input.receive("in1")
        data_out = gaussian(data_in, sigma=self.sigma_default, channel_axis=self.channel_axis)

        # Make sure the data type is what PIL Image can support, as the imsave function calls PIL Image fromarray()
        # Some details can be found at https://stackoverflow.com/questions/55319949/pil-typeerror-cannot-handle-this-data-type
        print(f"Data type of output: {type(data_out)!r}, max = {np.max(data_out)!r}")
        if np.max(data_out) <= 1:
            data_out = (data_out * 255).astype(np.uint8)
        print(f"Data type of output post conversion: {type(data_out)!r}, max = {np.max(data_out)!r}")

        # For now, use attribute of self to find the output path.
        self.output_folder.mkdir(parents=True, exist_ok=True)
        output_path = self.output_folder / "final_output.png"
        imsave(output_path, data_out)

        op_output.emit(data_out, "out1")
Overwriting simple_imaging_app/gaussian_operator.py

app.py#

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

from gaussian_operator import GaussianOperator
from median_operator import MedianOperator
from sobel_operator import SobelOperator

from monai.deploy.conditions import CountCondition
from monai.deploy.core import Application


# Decorator support is not available in this version of the SDK, to be re-introduced later
# @resource(cpu=1)
class App(Application):
    """This is a very basic application.

    This showcases the MONAI Deploy application framework.
    """

    # App's name. <class name>('App') if not specified.
    name = "simple_imaging_app"
    # App's description. <class docstring> if not specified.
    description = "This is a very simple application."
    # App's version. <git version tag> or '0.0.0' if not specified.
    version = "0.1.0"

    def compose(self):
        """This application has three operators.

        Each operator has a single input and a single output port.
        Each operator performs some kind of image processing function.
        """
        # Use Commandline options over environment variables to init context.
        app_context = Application.init_app_context(self.argv)
        sample_data_path = Path(app_context.input_path)
        output_data_path = Path(app_context.output_path)
        logging.info(f"sample_data_path: {sample_data_path}")

        # Please note that the Application object, self, is passed as the first positional argument
        # and the others as kwargs.
        # Also note the CountCondition of 1 on the first operator, indicating to the application executor
        # to invoke this operator, hence the pipeline, only once.
        sobel_op = SobelOperator(self, CountCondition(self, 1), input_path=sample_data_path, name="sobel_op")
        median_op = MedianOperator(self, name="median_op")
        gaussian_op = GaussianOperator(self, output_folder=output_data_path, name="gaussian_op")
        self.add_flow(
            sobel_op,
            median_op,
            {
                ("out1", "in1"),
            },
        )
        self.add_flow(
            median_op,
            gaussian_op,
            {
                (
                    "out1",
                    "in1",
                )
            },
        )


if __name__ == "__main__":
    logging.info(f"Begin {__name__}")
    App().run()
    logging.info(f"End {__name__}")
Overwriting simple_imaging_app/app.py
if __name__ == "__main__":
    App().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 simple_imaging_app/__main__.py
from app import App

if __name__ == "__main__":
    App().run()
Overwriting simple_imaging_app/__main__.py
!ls simple_imaging_app
app.py	  gaussian_operator.py	median_operator.py  requirements.txt
app.yaml  __main__.py		__pycache__	    sobel_operator.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, though the following demonstrates the use of the options.

!rm -rf {output_path}
!python simple_imaging_app -i {test_input_folder} -o {output_path} -l DEBUG
<frozen importlib._bootstrap_external>:1184: FutureWarning: The cuda.cudart module is deprecated and will be removed in a future release, please switch to use the cuda.bindings.runtime module instead.
/home/mqin/src/monai-deploy-app-sdk/.venv/lib/python3.10/site-packages/monai/deploy/utils/importutil.py:20: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  import pkg_resources
[info] [fragment.cpp:969] Loading extensions from configs...
[2025-08-04 15:38:19,053] [INFO] (root) - Parsed args: Namespace(log_level='DEBUG', input=PosixPath('/tmp/simple_app'), output=PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output'), model=None, workdir=None, triton_server_netloc=None, argv=['simple_imaging_app', '-i', '/tmp/simple_app', '-o', 'output', '-l', 'DEBUG'])
[2025-08-04 15:38:19,054] [INFO] (root) - AppContext object: AppContext(input_path=/tmp/simple_app, output_path=/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/output, model_path=models, workdir=), triton_server_netloc=
[2025-08-04 15:38:19,055] [INFO] (root) - sample_data_path: /tmp/simple_app
[info] [gxf_executor.cpp:344] Creating context
[info] [gxf_executor.cpp:2508] Activating Graph...
[info] [gxf_executor.cpp:2579] Running Graph...
[info] [gxf_executor.cpp:2581] Waiting for completion...
[info] [greedy_scheduler.cpp:191] Scheduling 3 entities
Number of times operator sobel_op whose class is defined in sobel_operator called: 1
Input from: /tmp/simple_app, whose absolute path: /tmp/simple_app
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'IHDR' 16 13
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'sRGB' 41 1
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'gAMA' 54 4
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'pHYs' 70 9
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'IDAT' 91 65445
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'IHDR' 16 13
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'sRGB' 41 1
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'gAMA' 54 4
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'pHYs' 70 9
[2025-08-04 15:38:19,178] [DEBUG] (PIL.PngImagePlugin) - STREAM b'IDAT' 91 65445
Number of times operator median_op whose class is defined in median_operator called: 1
Number of times operator gaussian_op whose class is defined in gaussian_operator called: 1
Data type of output: <class 'numpy.ndarray'>, max = np.float64(0.35821119421406195)
Data type of output post conversion: <class 'numpy.ndarray'>, max = np.uint8(91)
[2025-08-04 15:38:19,419] [DEBUG] (PIL.Image) - Importing AvifImagePlugin
[2025-08-04 15:38:19,421] [DEBUG] (PIL.Image) - Importing BlpImagePlugin
[2025-08-04 15:38:19,422] [DEBUG] (PIL.Image) - Importing BmpImagePlugin
[2025-08-04 15:38:19,422] [DEBUG] (PIL.Image) - Importing BufrStubImagePlugin
[2025-08-04 15:38:19,422] [DEBUG] (PIL.Image) - Importing CurImagePlugin
[2025-08-04 15:38:19,422] [DEBUG] (PIL.Image) - Importing DcxImagePlugin
[2025-08-04 15:38:19,422] [DEBUG] (PIL.Image) - Importing DdsImagePlugin
[2025-08-04 15:38:19,425] [DEBUG] (PIL.Image) - Importing EpsImagePlugin
[2025-08-04 15:38:19,426] [DEBUG] (PIL.Image) - Importing FitsImagePlugin
[2025-08-04 15:38:19,426] [DEBUG] (PIL.Image) - Importing FliImagePlugin
[2025-08-04 15:38:19,426] [DEBUG] (PIL.Image) - Importing FpxImagePlugin
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Image: failed to import FpxImagePlugin: No module named 'olefile'
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Importing FtexImagePlugin
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Importing GbrImagePlugin
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Importing GifImagePlugin
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Importing GribStubImagePlugin
[2025-08-04 15:38:19,427] [DEBUG] (PIL.Image) - Importing Hdf5StubImagePlugin
[2025-08-04 15:38:19,428] [DEBUG] (PIL.Image) - Importing IcnsImagePlugin
[2025-08-04 15:38:19,428] [DEBUG] (PIL.Image) - Importing IcoImagePlugin
[2025-08-04 15:38:19,429] [DEBUG] (PIL.Image) - Importing ImImagePlugin
[2025-08-04 15:38:19,429] [DEBUG] (PIL.Image) - Importing ImtImagePlugin
[2025-08-04 15:38:19,430] [DEBUG] (PIL.Image) - Importing IptcImagePlugin
[2025-08-04 15:38:19,430] [DEBUG] (PIL.Image) - Importing JpegImagePlugin
[2025-08-04 15:38:19,430] [DEBUG] (PIL.Image) - Importing Jpeg2KImagePlugin
[2025-08-04 15:38:19,430] [DEBUG] (PIL.Image) - Importing McIdasImagePlugin
[2025-08-04 15:38:19,430] [DEBUG] (PIL.Image) - Importing MicImagePlugin
[2025-08-04 15:38:19,431] [DEBUG] (PIL.Image) - Image: failed to import MicImagePlugin: No module named 'olefile'
[2025-08-04 15:38:19,431] [DEBUG] (PIL.Image) - Importing MpegImagePlugin
[2025-08-04 15:38:19,431] [DEBUG] (PIL.Image) - Importing MpoImagePlugin
[2025-08-04 15:38:19,433] [DEBUG] (PIL.Image) - Importing MspImagePlugin
[2025-08-04 15:38:19,433] [DEBUG] (PIL.Image) - Importing PalmImagePlugin
[2025-08-04 15:38:19,433] [DEBUG] (PIL.Image) - Importing PcdImagePlugin
[2025-08-04 15:38:19,434] [DEBUG] (PIL.Image) - Importing PcxImagePlugin
[2025-08-04 15:38:19,434] [DEBUG] (PIL.Image) - Importing PdfImagePlugin
[2025-08-04 15:38:19,439] [DEBUG] (PIL.Image) - Importing PixarImagePlugin
[2025-08-04 15:38:19,439] [DEBUG] (PIL.Image) - Importing PngImagePlugin
[2025-08-04 15:38:19,439] [DEBUG] (PIL.Image) - Importing PpmImagePlugin
[2025-08-04 15:38:19,439] [DEBUG] (PIL.Image) - Importing PsdImagePlugin
[2025-08-04 15:38:19,440] [DEBUG] (PIL.Image) - Importing QoiImagePlugin
[2025-08-04 15:38:19,440] [DEBUG] (PIL.Image) - Importing SgiImagePlugin
[2025-08-04 15:38:19,440] [DEBUG] (PIL.Image) - Importing SpiderImagePlugin
[2025-08-04 15:38:19,440] [DEBUG] (PIL.Image) - Importing SunImagePlugin
[2025-08-04 15:38:19,441] [DEBUG] (PIL.Image) - Importing TgaImagePlugin
[2025-08-04 15:38:19,441] [DEBUG] (PIL.Image) - Importing TiffImagePlugin
[2025-08-04 15:38:19,441] [DEBUG] (PIL.Image) - Importing WebPImagePlugin
[2025-08-04 15:38:19,442] [DEBUG] (PIL.Image) - Importing WmfImagePlugin
[2025-08-04 15:38:19,442] [DEBUG] (PIL.Image) - Importing XbmImagePlugin
[2025-08-04 15:38:19,443] [DEBUG] (PIL.Image) - Importing XpmImagePlugin
[2025-08-04 15:38:19,443] [DEBUG] (PIL.Image) - Importing XVThumbImagePlugin
[info] [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] [greedy_scheduler.cpp:401] Scheduler finished.
[info] [gxf_executor.cpp:2588] Deactivating Graph...
[info] [gxf_executor.cpp:2597] Graph execution finished.
[info] [gxf_executor.cpp:379] Destroying context
#output_image_path was set as before, output_image_path = output_path + "/final_output.png"
output_image = io.imread(output_image_path)
io.imshow(output_image)
/tmp/ipykernel_1107452/3197869135.py:3: FutureWarning: `imshow` is deprecated since version 0.25 and will be removed in version 0.27. Please use `matplotlib`, `napari`, etc. to visualize images.
  io.imshow(output_image)
<matplotlib.image.AxesImage at 0x7c229c0e8490>
../../_images/d6295ad52a7f6da3b3a61b7651288c84afca8c47bf7cbd936d2c5eecdb4ba2ae.png

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 simple_imaging_app/app.yaml
%YAML 1.2
---
application:
  title: MONAI Deploy App Package - Simple Imaging App
  version: 1.0
  inputFormats: ["file"]
  outputFormats: ["file"]

resources:
  cpu: 1
  gpu: 1
  memory: 1Gi
  gpuMemory: 1Gi
Overwriting simple_imaging_app/app.yaml
%%writefile simple_imaging_app/requirements.txt
scikit-image
setuptools>=59.5.0 # for pkg_resources
Overwriting simple_imaging_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.

tag_prefix = "simple_imaging_app"

!monai-deploy package simple_imaging_app -c simple_imaging_app/app.yaml -t {tag_prefix}:1.0 --platform x86_64 -l DEBUG
[2025-08-04 15:38:21,416] [INFO] (common) - Downloading CLI manifest file...
[2025-08-04 15:38:21,523] [DEBUG] (common) - Validating CLI manifest file...
[2025-08-04 15:38:21,524] [INFO] (packager.parameters) - Application: /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app
[2025-08-04 15:38:21,524] [INFO] (packager.parameters) - Detected application type: Python Module
[2025-08-04 15:38:21,524] [INFO] (packager) - Reading application configuration from /home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app/app.yaml...
[2025-08-04 15:38:21,528] [INFO] (packager) - Generating app.json...
[2025-08-04 15:38:21,528] [INFO] (packager) - Generating pkg.json...
[2025-08-04 15:38:21,533] [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 ================
                 
[2025-08-04 15:38:21,534] [DEBUG] (common) - 
=============== Begin pkg.json ===============
{
    "apiVersion": "1.0.0",
    "applicationRoot": "/opt/holoscan/app",
    "modelRoot": "/opt/holoscan/models",
    "models": {},
    "resources": {
        "cpu": 1,
        "gpu": 1,
        "memory": "1Gi",
        "gpuMemory": "1Gi"
    },
    "version": 1.0,
    "platformConfig": "dgpu"
}
================ End pkg.json ================
                 
[2025-08-04 15:38:21,539] [DEBUG] (packager.builder) - ================ Begin requirements.txt ================
[2025-08-04 15:38:21,539] [DEBUG] (packager.builder) -   scikit-image
[2025-08-04 15:38:21,539] [DEBUG] (packager.builder) -   setuptools>=59.5.0 # for pkg_resources
[2025-08-04 15:38:21,539] [DEBUG] (packager.builder) -   
[2025-08-04 15:38:21,539] [DEBUG] (packager.builder) - ================ End requirements.txt ==================
[2025-08-04 15:38:21,541] [DEBUG] (packager.builder) - 
========== Begin Build Parameters ==========
{'add_hosts': None,
 'additional_lib_paths': '',
 'app_config_file_path': PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app/app.yaml'),
 'app_dir': PosixPath('/opt/holoscan/app'),
 'app_json': '/etc/holoscan/app.json',
 'application': PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app'),
 'application_directory': PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app'),
 'application_type': 'PythonModule',
 'build_cache': PosixPath('/home/mqin/.holoscan_build_cache'),
 'cmake_args': '',
 'command': '["python3", "/opt/holoscan/app"]',
 'command_filename': 'simple_imaging_app',
 'config_file_path': PosixPath('/var/holoscan/app.yaml'),
 'docs_dir': PosixPath('/opt/holoscan/docs'),
 'full_input_path': PosixPath('/var/holoscan/input'),
 'full_output_path': PosixPath('/var/holoscan/output'),
 'gid': 1000,
 'holoscan_sdk_version': '3.5.0',
 'includes': [],
 'input_data': None,
 'input_dir': 'input/',
 'lib_dir': PosixPath('/opt/holoscan/lib'),
 'logs_dir': PosixPath('/var/holoscan/logs'),
 'models_dir': PosixPath('/opt/holoscan/models'),
 'monai_deploy_app_sdk_version': '0.5.1',
 'no_cache': False,
 'output_dir': 'output/',
 'pip_packages': None,
 'pkg_json': '/etc/holoscan/pkg.json',
 'requirements_file_path': PosixPath('/home/mqin/src/monai-deploy-app-sdk/notebooks/tutorials/simple_imaging_app/requirements.txt'),
 'sdk': <SdkType.MonaiDeploy: 'monai-deploy'>,
 'sdk_type': 'monai-deploy',
 'tarball_output': None,
 'timeout': 0,
 'title': 'MONAI Deploy App Package - Simple Imaging App',
 'uid': 1000,
 'username': 'holoscan',
 'version': 1.0,
 'working_dir': PosixPath('/var/holoscan')}
=========== End Build Parameters ===========

[2025-08-04 15:38:21,541] [DEBUG] (packager.builder) - 
========== Begin Platform Parameters ==========
{'base_image': 'nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04',
 'build_image': None,
 'cuda_deb_arch': 'x86_64',
 'custom_base_image': False,
 'custom_holoscan_sdk': False,
 'custom_monai_deploy_sdk': True,
 'gpu_type': 'dgpu',
 'holoscan_deb_arch': 'amd64',
 'holoscan_sdk_file': '3.5.0',
 'holoscan_sdk_filename': '3.5.0',
 'monai_deploy_sdk_file': PosixPath('/home/mqin/src/monai-deploy-app-sdk/dist/monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl'),
 'monai_deploy_sdk_filename': 'monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl',
 'tag': 'simple_imaging_app:1.0',
 'target_arch': 'x86_64'}
=========== End Platform Parameters ===========

[2025-08-04 15:38:21,565] [DEBUG] (packager.builder) - 
========== Begin Dockerfile ==========

ARG GPU_TYPE=dgpu




FROM nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04 AS base

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




# FROM base AS mofed-installer
# ARG MOFED_VERSION=23.10-2.1.3.1

# # In a container, we only need to install the user space libraries, though the drivers are still
# # needed on the host.
# # Note: MOFED's installation is not easily portable, so we can't copy the output of this stage
# # to our final stage, but must inherit from it. For that reason, we keep track of the build/install
# # only dependencies in the `MOFED_DEPS` variable (parsing the output of `--check-deps-only`) to
# # remove them in that same layer, to ensure they are not propagated in the final image.
# WORKDIR /opt/nvidia/mofed
# ARG MOFED_INSTALL_FLAGS="--dpdk --with-mft --user-space-only --force --without-fw-update"
# RUN UBUNTU_VERSION=$(cat /etc/lsb-release | grep DISTRIB_RELEASE | cut -d= -f2) \
#     && OFED_PACKAGE="MLNX_OFED_LINUX-${MOFED_VERSION}-ubuntu${UBUNTU_VERSION}-$(uname -m)" \
#     && curl -S -# -o ${OFED_PACKAGE}.tgz -L \
#         https://www.mellanox.com/downloads/ofed/MLNX_OFED-${MOFED_VERSION}/${OFED_PACKAGE}.tgz \
#     && tar xf ${OFED_PACKAGE}.tgz \
#     && MOFED_INSTALLER=$(find . -name mlnxofedinstall -type f -executable -print) \
#     && MOFED_DEPS=$(${MOFED_INSTALLER} ${MOFED_INSTALL_FLAGS} --check-deps-only 2>/dev/null | tail -n1 |  cut -d' ' -f3-) \
#     && apt-get update \
#     && apt-get install --no-install-recommends -y ${MOFED_DEPS} \
#     && ${MOFED_INSTALLER} ${MOFED_INSTALL_FLAGS} \
#     && rm -r * \
#     && apt-get remove -y ${MOFED_DEPS} && apt-get autoremove -y \
#     && rm -rf /var/lib/apt/lists/*

FROM base AS release
ENV DEBIAN_FRONTEND=noninteractive
ENV TERM=xterm-256color

ARG GPU_TYPE
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/cuda:12.8.1-runtime-ubuntu24.04"
LABEL tag="simple_imaging_app:1.0"
LABEL org.opencontainers.image.title="MONAI Deploy App Package - Simple Imaging App"
LABEL org.opencontainers.image.version="1.0"
LABEL org.nvidia.holoscan="3.5.0"

LABEL org.monai.deploy.app-sdk="0.5.1"

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 HOLOSCAN_VERSION=3.5.0









# If torch is installed, we can skip installing Python
ENV PYTHON_VERSION=3.12.3-*
ENV PYTHON_PIP_VERSION=24.0+dfsg-*

RUN apt update \
    && apt-get install -y --no-install-recommends --no-install-suggests \
        python3-minimal=${PYTHON_VERSION} \
        libpython3-stdlib=${PYTHON_VERSION} \
        python3=${PYTHON_VERSION} \
        python3-venv=${PYTHON_VERSION} \
        python3-pip=${PYTHON_PIP_VERSION} \
    && rm -rf /var/lib/apt/lists/*






RUN if id "ubuntu" >/dev/null 2>&1; then touch /var/mail/ubuntu && chown ubuntu /var/mail/ubuntu && userdel -r ubuntu; fi
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 && \
    chown -R holoscan /var/holoscan/input && \
    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

# Remove EXTERNALLY-MANAGED directory
RUN rm -rf /usr/lib/python3.12/EXTERNALLY-MANAGED

# Set the working directory
WORKDIR /var/holoscan

USER $UNAME

ENV PATH=/home/${UNAME}/.local/bin:/opt/nvidia/holoscan/bin:$PATH
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/${UNAME}/.local/lib/python3.10/site-packages/holoscan/lib

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

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


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



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

[2025-08-04 15:38:21,872] [INFO] (common) - Using existing Docker BuildKit builder `holoscan_app_builder`
[2025-08-04 15:38:21,872] [DEBUG] (packager.builder) - Building Holoscan Application Package: tag=simple_imaging_app-x64-workstation-dgpu-linux-amd64:1.0
[2025-08-04 15:38:21,872] [INFO] (packager.builder) - 
===============================================================================
Building image for:                 x64-workstation
    Architecture:                   linux/amd64
    Base Image:                     nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04
    Build Image:                    N/A
    Cache:                          Enabled
    Configuration:                  dgpu
    Holoscan SDK Package:           3.5.0
    MONAI Deploy App SDK Package:   /home/mqin/src/monai-deploy-app-sdk/dist/monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl
    gRPC Health Probe:              N/A
    SDK Version:                    3.5.0
    SDK:                            monai-deploy
    Tag:                            simple_imaging_app-x64-workstation-dgpu-linux-amd64:1.0
    Included features/dependencies: N/A
    
#0 building with "holoscan_app_builder" instance using docker-container driver

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

#2 [auth] nvidia/cuda:pull token for nvcr.io
#2 DONE 0.0s

#3 [internal] load metadata for nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04
#3 DONE 0.5s

#4 [internal] load .dockerignore
#4 transferring context: 1.80kB done
#4 DONE 0.1s

#5 importing cache manifest from nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04
#5 ...

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

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

#5 importing cache manifest from nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04
#5 inferred cache manifest type: application/vnd.docker.distribution.manifest.list.v2+json done
#5 DONE 0.4s

#8 [base 1/2] FROM nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04@sha256:ebef3c171eeef0298e4eb2e4be843105edf3b8b0ac45e0b43acee358e8046867
#8 resolve nvcr.io/nvidia/cuda:12.8.1-runtime-ubuntu24.04@sha256:ebef3c171eeef0298e4eb2e4be843105edf3b8b0ac45e0b43acee358e8046867 0.1s done
#8 DONE 0.1s

#6 [internal] load build context
#6 transferring context: 175.02kB 0.0s done
#6 DONE 0.1s

#9 [release  2/20] RUN apt update     && apt-get install -y --no-install-recommends --no-install-suggests         python3-minimal=3.12.3-*         libpython3-stdlib=3.12.3-*         python3=3.12.3-*         python3-venv=3.12.3-*         python3-pip=24.0+dfsg-*     && rm -rf /var/lib/apt/lists/*
#9 CACHED

#10 [release  3/20] RUN if id "ubuntu" >/dev/null 2>&1; then touch /var/mail/ubuntu && chown ubuntu /var/mail/ubuntu && userdel -r ubuntu; fi
#10 CACHED

#11 [release 10/20] RUN rm -rf /usr/lib/python3.12/EXTERNALLY-MANAGED
#11 CACHED

#12 [release 12/20] COPY ./pip/requirements.txt /tmp/requirements.txt
#12 CACHED

#13 [release  8/20] COPY ./tools /var/holoscan/tools
#13 CACHED

#14 [release  9/20] RUN chmod +x /var/holoscan/tools
#14 CACHED

#15 [release  4/20] RUN groupadd -f -g 1000 holoscan
#15 CACHED

#16 [release 11/20] WORKDIR /var/holoscan
#16 CACHED

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

#18 [release  1/20] 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
#18 CACHED

#19 [release  6/20] RUN chown -R holoscan /var/holoscan &&     chown -R holoscan /var/holoscan/input &&     chown -R holoscan /var/holoscan/output
#19 CACHED

#20 [release  7/20] WORKDIR /var/holoscan
#20 CACHED

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

#22 [base 2/2] RUN apt-get update     && apt-get install -y --no-install-recommends --no-install-suggests         curl         jq     && rm -rf /var/lib/apt/lists/*
#22 CACHED

#23 [release 14/20] RUN pip install --no-cache-dir --user -r /tmp/requirements.txt
#23 CACHED

#24 [release 15/20] COPY ./monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl /tmp/monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl
#24 DONE 0.1s

#25 [release 16/20] RUN pip install /tmp/monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl
#25 0.723 Defaulting to user installation because normal site-packages is not writeable
#25 0.810 Processing /tmp/monai_deploy_app_sdk-0.5.1+43.gdec9305.dirty-py3-none-any.whl
#25 0.817 Requirement already satisfied: numpy>=1.21.6 in /home/holoscan/.local/lib/python3.12/site-packages (from monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty) (2.3.2)
#25 1.003 Collecting holoscan~=3.0 (from monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.045   Downloading holoscan-3.5.0-cp312-cp312-manylinux_2_35_x86_64.whl.metadata (6.5 kB)
#25 1.107 Collecting holoscan-cli~=3.0 (from monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.115   Downloading holoscan_cli-3.5.0-py3-none-any.whl.metadata (4.0 kB)
#25 1.199 Collecting colorama>=0.4.1 (from monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.205   Downloading colorama-0.4.6-py2.py3-none-any.whl.metadata (17 kB)
#25 1.300 Collecting tritonclient>=2.53.0 (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.307   Downloading tritonclient-2.59.0-py3-none-manylinux1_x86_64.whl.metadata (2.8 kB)
#25 1.397 Collecting typeguard>=3.0.0 (from monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.404   Downloading typeguard-4.4.4-py3-none-any.whl.metadata (3.3 kB)
#25 1.421 Requirement already satisfied: pip>22.0.2 in /home/holoscan/.local/lib/python3.12/site-packages (from holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty) (25.2)
#25 1.478 Collecting cupy-cuda12x<14.0,>=12.2 (from holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.484   Downloading cupy_cuda12x-13.5.1-cp312-cp312-manylinux2014_x86_64.whl.metadata (2.4 kB)
#25 1.548 Collecting cloudpickle<4.0,>=3.0 (from holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.553   Downloading cloudpickle-3.1.1-py3-none-any.whl.metadata (7.1 kB)
#25 1.572 Requirement already satisfied: pillow>=11.2 in /home/holoscan/.local/lib/python3.12/site-packages (from holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty) (11.3.0)
#25 1.662 Collecting wheel-axle-runtime<1.0 (from holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.672   Downloading wheel_axle_runtime-0.0.6-py3-none-any.whl.metadata (8.1 kB)
#25 1.769 Collecting fastrlock>=0.5 (from cupy-cuda12x<14.0,>=12.2->holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.775   Downloading fastrlock-0.8.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl.metadata (7.7 kB)
#25 1.843 Collecting Jinja2<4.0.0,>=3.1.6 (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 1.856   Downloading jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
#25 1.875 Requirement already satisfied: packaging<26.0,>=25.0 in /home/holoscan/.local/lib/python3.12/site-packages (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty) (25.0)
#25 2.017 Collecting psutil<8.0,>=7.0.0 (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.020   Downloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (22 kB)
#25 2.110 Collecting python-on-whales>=0.77.0 (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.116   Downloading python_on_whales-0.78.0-py3-none-any.whl.metadata (18 kB)
#25 2.198 Collecting pyyaml<7.0,>=6.0 (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.203   Downloading PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.1 kB)
#25 2.313 Collecting requests<3.0,>=2.32 (from holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.320   Downloading requests-2.32.4-py3-none-any.whl.metadata (4.9 kB)
#25 2.429 Collecting MarkupSafe>=2.0 (from Jinja2<4.0.0,>=3.1.6->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.433   Downloading MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.0 kB)
#25 2.578 Collecting charset_normalizer<4,>=2 (from requests<3.0,>=2.32->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.584   Downloading charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (35 kB)
#25 2.645 Collecting idna<4,>=2.5 (from requests<3.0,>=2.32->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.653   Downloading idna-3.10-py3-none-any.whl.metadata (10 kB)
#25 2.748 Collecting urllib3<3,>=1.21.1 (from requests<3.0,>=2.32->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.752   Downloading urllib3-2.5.0-py3-none-any.whl.metadata (6.5 kB)
#25 2.823 Collecting certifi>=2017.4.17 (from requests<3.0,>=2.32->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.828   Downloading certifi-2025.8.3-py3-none-any.whl.metadata (2.4 kB)
#25 2.915 Collecting filelock (from wheel-axle-runtime<1.0->holoscan~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 2.920   Downloading filelock-3.18.0-py3-none-any.whl.metadata (2.9 kB)
#25 3.134 Collecting pydantic!=2.0.*,<3,>=2 (from python-on-whales>=0.77.0->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 3.138   Downloading pydantic-2.11.7-py3-none-any.whl.metadata (67 kB)
#25 3.225 Collecting typing-extensions (from python-on-whales>=0.77.0->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 3.232   Downloading typing_extensions-4.14.1-py3-none-any.whl.metadata (3.0 kB)
#25 3.296 Collecting annotated-types>=0.6.0 (from pydantic!=2.0.*,<3,>=2->python-on-whales>=0.77.0->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 3.302   Downloading annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
#25 4.305 Collecting pydantic-core==2.33.2 (from pydantic!=2.0.*,<3,>=2->python-on-whales>=0.77.0->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 4.311   Downloading pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.8 kB)
#25 4.365 Collecting typing-inspection>=0.4.0 (from pydantic!=2.0.*,<3,>=2->python-on-whales>=0.77.0->holoscan-cli~=3.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 4.371   Downloading typing_inspection-0.4.1-py3-none-any.whl.metadata (2.6 kB)
#25 4.570 Collecting python-rapidjson>=0.9.1 (from tritonclient>=2.53.0->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 4.575   Downloading python_rapidjson-1.21-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (23 kB)
#25 5.431 Collecting aiohttp<4.0.0,>=3.8.1 (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 5.434   Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.7 kB)
#25 5.542 Collecting cuda-python (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 5.550   Downloading cuda_python-12.9.0-py3-none-any.whl.metadata (4.6 kB)
#25 5.888 Collecting geventhttpclient>=2.3.3 (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 5.894   Downloading geventhttpclient-2.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (8.4 kB)
#25 7.229 Collecting grpcio<1.68,>=1.63.0 (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.236   Downloading grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.9 kB)
#25 7.565 Collecting protobuf<6.0dev,>=5.26.1 (from tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.569   Downloading protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl.metadata (592 bytes)
#25 7.640 Collecting aiohappyeyeballs>=2.5.0 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.644   Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)
#25 7.693 Collecting aiosignal>=1.4.0 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.699   Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
#25 7.772 Collecting attrs>=17.3.0 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.778   Downloading attrs-25.3.0-py3-none-any.whl.metadata (10 kB)
#25 7.962 Collecting frozenlist>=1.1.1 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 7.965   Downloading frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (18 kB)
#25 8.482 Collecting multidict<7.0,>=4.5 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 8.486   Downloading multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
#25 8.618 Collecting propcache>=0.2.0 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 8.623   Downloading propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)
#25 9.226 Collecting yarl<2.0,>=1.17.0 (from aiohttp<4.0.0,>=3.8.1->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 9.230   Downloading yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (73 kB)
#25 9.582 Collecting gevent (from geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 9.587   Downloading gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (13 kB)
#25 9.708 Collecting brotli (from geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 9.715   Downloading Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.5 kB)
#25 9.805 Collecting cuda-bindings~=12.9.0 (from cuda-python->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 9.811   Downloading cuda_bindings-12.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.1 kB)
#25 10.12 Collecting greenlet>=3.2.2 (from gevent->geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 10.13   Downloading greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (4.1 kB)
#25 10.19 Collecting zope.event (from gevent->geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 10.19   Downloading zope_event-5.1.1-py3-none-any.whl.metadata (5.0 kB)
#25 10.43 Collecting zope.interface (from gevent->geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 10.43   Downloading zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (44 kB)
#25 10.80 Collecting setuptools>=75.8.2 (from zope.event->gevent->geventhttpclient>=2.3.3->tritonclient[all]>=2.53.0->monai-deploy-app-sdk==0.5.1+43.gdec9305.dirty)
#25 10.80   Downloading setuptools-80.9.0-py3-none-any.whl.metadata (6.6 kB)
#25 10.84 Downloading holoscan-3.5.0-cp312-cp312-manylinux_2_35_x86_64.whl (40.6 MB)
#25 13.21    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.6/40.6 MB 17.2 MB/s  0:00:02
#25 13.21 Downloading cloudpickle-3.1.1-py3-none-any.whl (20 kB)
#25 13.23 Downloading cupy_cuda12x-13.5.1-cp312-cp312-manylinux2014_x86_64.whl (113.1 MB)
#25 18.10    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 113.1/113.1 MB 23.3 MB/s  0:00:04
#25 18.11 Downloading holoscan_cli-3.5.0-py3-none-any.whl (73 kB)
#25 18.13 Downloading jinja2-3.1.6-py3-none-any.whl (134 kB)
#25 18.17 Downloading psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (277 kB)
#25 18.21 Downloading PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (767 kB)
#25 18.28    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 767.5/767.5 kB 9.6 MB/s  0:00:00
#25 18.29 Downloading requests-2.32.4-py3-none-any.whl (64 kB)
#25 18.32 Downloading charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (148 kB)
#25 18.36 Downloading idna-3.10-py3-none-any.whl (70 kB)
#25 18.39 Downloading urllib3-2.5.0-py3-none-any.whl (129 kB)
#25 18.42 Downloading wheel_axle_runtime-0.0.6-py3-none-any.whl (14 kB)
#25 18.44 Downloading certifi-2025.8.3-py3-none-any.whl (161 kB)
#25 18.48 Downloading colorama-0.4.6-py2.py3-none-any.whl (25 kB)
#25 18.51 Downloading fastrlock-0.8.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl (53 kB)
#25 18.55 Downloading MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (23 kB)
#25 18.58 Downloading python_on_whales-0.78.0-py3-none-any.whl (118 kB)
#25 18.62 Downloading pydantic-2.11.7-py3-none-any.whl (444 kB)
#25 18.68 Downloading pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB)
#25 18.77    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 23.0 MB/s  0:00:00
#25 18.78 Downloading annotated_types-0.7.0-py3-none-any.whl (13 kB)
#25 18.80 Downloading tritonclient-2.59.0-py3-none-manylinux1_x86_64.whl (14.5 MB)
#25 19.69    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 14.5/14.5 MB 16.1 MB/s  0:00:00
#25 19.70 Downloading python_rapidjson-1.21-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (1.7 MB)
#25 20.12    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 5.2 MB/s  0:00:00
#25 20.13 Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB)
#25 20.21    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 22.2 MB/s  0:00:00
#25 20.22 Downloading grpcio-1.67.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.9 MB)
#25 20.44    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.9/5.9 MB 27.2 MB/s  0:00:00
#25 20.44 Downloading multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (256 kB)
#25 20.50 Downloading protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl (319 kB)
#25 20.54 Downloading yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (355 kB)
#25 20.57 Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)
#25 20.59 Downloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
#25 20.62 Downloading attrs-25.3.0-py3-none-any.whl (63 kB)
#25 20.64 Downloading frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (241 kB)
#25 20.67 Downloading geventhttpclient-2.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (114 kB)
#25 20.69 Downloading propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (224 kB)
#25 20.72 Downloading typeguard-4.4.4-py3-none-any.whl (34 kB)
#25 20.75 Downloading typing_extensions-4.14.1-py3-none-any.whl (43 kB)
#25 20.77 Downloading typing_inspection-0.4.1-py3-none-any.whl (14 kB)
#25 20.79 Downloading Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.9 MB)
#25 21.08    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.9/2.9 MB 9.9 MB/s  0:00:00
#25 21.08 Downloading cuda_python-12.9.0-py3-none-any.whl (7.5 kB)
#25 21.10 Downloading cuda_bindings-12.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.2 MB)
#25 21.48    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.2/12.2 MB 33.0 MB/s  0:00:00
#25 21.48 Downloading filelock-3.18.0-py3-none-any.whl (16 kB)
#25 21.51 Downloading gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
#25 21.59    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 23.6 MB/s  0:00:00
#25 21.60 Downloading greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (605 kB)
#25 21.63    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 605.5/605.5 kB 13.7 MB/s  0:00:00
#25 21.64 Downloading zope_event-5.1.1-py3-none-any.whl (7.0 kB)
#25 21.66 Downloading setuptools-80.9.0-py3-none-any.whl (1.2 MB)
#25 21.70    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 25.6 MB/s  0:00:00
#25 21.71 Downloading zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (264 kB)
#25 22.45 Installing collected packages: fastrlock, cuda-bindings, brotli, urllib3, typing-extensions, setuptools, pyyaml, python-rapidjson, psutil, protobuf, propcache, multidict, MarkupSafe, idna, grpcio, greenlet, frozenlist, filelock, cupy-cuda12x, cuda-python, colorama, cloudpickle, charset_normalizer, certifi, attrs, annotated-types, aiohappyeyeballs, zope.interface, zope.event, yarl, wheel-axle-runtime, typing-inspection, typeguard, tritonclient, requests, pydantic-core, Jinja2, aiosignal, pydantic, holoscan, gevent, aiohttp, python-on-whales, geventhttpclient, holoscan-cli, monai-deploy-app-sdk
#25 29.97 
#25 29.99 Successfully installed Jinja2-3.1.6 MarkupSafe-3.0.2 aiohappyeyeballs-2.6.1 aiohttp-3.12.15 aiosignal-1.4.0 annotated-types-0.7.0 attrs-25.3.0 brotli-1.1.0 certifi-2025.8.3 charset_normalizer-3.4.2 cloudpickle-3.1.1 colorama-0.4.6 cuda-bindings-12.9.0 cuda-python-12.9.0 cupy-cuda12x-13.5.1 fastrlock-0.8.3 filelock-3.18.0 frozenlist-1.7.0 gevent-25.5.1 geventhttpclient-2.3.4 greenlet-3.2.3 grpcio-1.67.1 holoscan-3.5.0 holoscan-cli-3.5.0 idna-3.10 monai-deploy-app-sdk-0.5.1+43.gdec9305.dirty multidict-6.6.3 propcache-0.3.2 protobuf-5.29.5 psutil-7.0.0 pydantic-2.11.7 pydantic-core-2.33.2 python-on-whales-0.78.0 python-rapidjson-1.21 pyyaml-6.0.2 requests-2.32.4 setuptools-80.9.0 tritonclient-2.59.0 typeguard-4.4.4 typing-extensions-4.14.1 typing-inspection-0.4.1 urllib3-2.5.0 wheel-axle-runtime-0.0.6 yarl-1.20.1 zope.event-5.1.1 zope.interface-7.2
#25 DONE 32.5s

#26 [release 17/20] COPY ./map/app.json /etc/holoscan/app.json
#26 DONE 0.2s

#27 [release 18/20] COPY ./app.config /var/holoscan/app.yaml
#27 DONE 0.1s

#28 [release 19/20] COPY ./map/pkg.json /etc/holoscan/pkg.json
#28 DONE 0.1s

#29 [release 20/20] COPY ./app /opt/holoscan/app
#29 DONE 0.1s

#30 exporting to docker image format
#30 exporting layers
#30 exporting layers 24.5s done
#30 exporting manifest sha256:8463f7c8169a9c5582d2544c7b5488ebcc0742fb8910556c55e67f2e76daff1d 0.0s done
#30 exporting config sha256:4bd3a12080424a6e1ffa642b311065054fbd836d8da5c4973f79dc5650239c9c 0.0s done
#30 sending tarball
#30 ...

#31 importing to docker
#31 loading layer dd50deb6b8c3 32.77kB / 143.73kB
#31 loading layer 244a0631f811 557.06kB / 420.22MB
#31 loading layer 244a0631f811 171.57MB / 420.22MB 2.1s
#31 loading layer 244a0631f811 233.96MB / 420.22MB 4.1s
#31 loading layer 244a0631f811 347.05MB / 420.22MB 8.3s
#31 loading layer 244a0631f811 388.83MB / 420.22MB 10.4s
#31 loading layer d88742eeac5f 491B / 491B
#31 loading layer 5113dec238ff 312B / 312B
#31 loading layer 313a25434b4f 293B / 293B
#31 loading layer 3db7dfb31943 3.18kB / 3.18kB
#31 loading layer 5113dec238ff 312B / 312B 0.6s done
#31 loading layer dd50deb6b8c3 143.73kB / 143.73kB 13.1s done
#31 loading layer 244a0631f811 420.22MB / 420.22MB 13.0s done
#31 loading layer d88742eeac5f 491B / 491B 0.7s done
#31 loading layer 313a25434b4f 293B / 293B 0.5s done
#31 loading layer 3db7dfb31943 3.18kB / 3.18kB 0.5s done
#31 DONE 13.1s

#30 exporting to docker image format
#30 sending tarball 28.3s done
#30 DONE 52.8s

#32 exporting cache to client directory
#32 preparing build cache for export
#32 writing layer sha256:0485e1ad7d46995100cf1b77f4ca14706112dd0c6ec89a4b27417fb530d97bc1
#32 writing layer sha256:0485e1ad7d46995100cf1b77f4ca14706112dd0c6ec89a4b27417fb530d97bc1 done
#32 writing layer sha256:05ec76e31584ec109785cc7045bd88df0240411233c2fcdad66b621c662034c0 done
#32 writing layer sha256:071ee33ea2913f8a44d367ce1fac3948d244a01a0e58677d20e3d1b11881f63a done
#32 writing layer sha256:07b18ae07d19d6686837c9bd3bd53d6ed40e51e8a0f7664bfc375f0c05aebef0 done
#32 writing layer sha256:273c1c26f5df2b6b2c519a896c320790e35a280ca9e10f96cdb52e6499393b90 done
#32 writing layer sha256:398182656c471d6ecca3c2d6d30e97193b40ffc8028a94515093960322f3d64e done
#32 writing layer sha256:3d6ab8c799cda2f4c6a6277b0e24dd2231c5de83b0316968b7cce81156bb8be0 done
#32 writing layer sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 done
#32 writing layer sha256:545a3ada5b6bc612a11c13a659775d67eeda5a61615e7f49c76ecd24adcad626 done
#32 writing layer sha256:5a7813e071bfadf18aaa6ca8318be4824a9b6297b3240f2cc84c1db6f4113040 done
#32 writing layer sha256:7209097bfb98d6f8b422984480f1fddead5ea62f8900ff6b6548e060b71aca76 done
#32 writing layer sha256:73389fbd088f5ed5d9fd258baced59de092978b4f483920ea6d074522a105119 done
#32 writing layer sha256:80fd714bb5870ea96d140670d0a239743b1046fa441c04200c67fe981b0caeab 0.0s done
#32 writing layer sha256:882067111f43dcefcfb9978ba194a78f79bf241e8fda47517aa249574d08d23e done
#32 writing layer sha256:974303ed337115258b92b96ca102ac94aa7c61086f2f72c81be9f175c9c82b51 0.0s done
#32 writing layer sha256:9d7b7f0b8d0d1b0dc0d04230b9af8705ef56d7881728bc8439d94f34e2b346fa done
#32 writing layer sha256:9fd1c058058a8f41e47faaaa6bb495e8a9ce41bbfdf7e23e8a21eead352adfa6
#32 writing layer sha256:9fd1c058058a8f41e47faaaa6bb495e8a9ce41bbfdf7e23e8a21eead352adfa6 6.9s done
#32 writing layer sha256:a102f36d092c0e9e0bef8c97854f606af9156aa36ab408e6fa4b88e27124a7e6
#32 writing layer sha256:a102f36d092c0e9e0bef8c97854f606af9156aa36ab408e6fa4b88e27124a7e6 done
#32 writing layer sha256:a7e0585aa1334f827ee888d93df571cf5ca476e64f9d6fc41514d685358b50a1 done
#32 writing layer sha256:b8641f6fe7613397c5a2b24da0b0a541c2511ef1e0347be3cb2139ff622a0f2c done
#32 writing layer sha256:c4244b43c83afc0ad4d17b1f92e5609c1ecf54bf2a9623d2311082796c9b3c43 done
#32 writing layer sha256:c5b0ebd4c7a351c4b972af1cfebad9e226e1f3e151dada98a607f838751c4bfa done
#32 writing layer sha256:cbb9175a9bc5f6553f8c0c5025ea9521898b8a3956ee24798dc35c24c6185053 done
#32 writing layer sha256:cde14b3f73510edb711cb4c12549cb989f8979805cb15be6701a93315d22cc34 done
#32 writing layer sha256:ceabffc6cbe26490efdf617518f6ab6ce96ccde03b71ca66d41178acc1c6a66e 0.0s done
#32 writing layer sha256:d14c0c9ac374a3716bbffda1b99c321e0da9112a55c52b6092fbb69c06c4f08c done
#32 writing layer sha256:d60ffef5907c5ba8b9963a4c1142a7a037158ecd23dfe8fb551921f9b8f30f44 0.0s done
#32 writing layer sha256:dbfcba9170906c6797805ed009bf57df0d4f0e390e91470abfacd94c7d8e3cbc 0.0s done
#32 writing layer sha256:dc8ef917f4d947ac946de58bbd3e597df3eccf6d3114660dc1d10ac65d4c9292 done
#32 writing config sha256:19737b03ef69c29c1bf50fc153b400bc7ff0c1d5edf8419c3aff9c3d09d03498 0.0s done
#32 preparing build cache for export 7.3s done
#32 writing cache manifest sha256:4c9607b4a8cb04c94c01a17b37dd23a78c8599f3ae4740c0e02b92beb4fc33e1 0.0s done
#32 DONE 7.3s
[2025-08-04 15:39:57,397] [INFO] (packager) - Build Summary:

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

Note

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

We can see that the MAP Docker image is created.

!docker image ls | grep {tag_prefix}
simple_imaging_app-x64-workstation-dgpu-linux-amd64                           1.0                            4bd3a1208042   About a minute ago   4.57GB

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.0,
  "workingDirectory": "/var/holoscan"
}

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

2025-08-04 22:40:00 [INFO] Copying application from /opt/holoscan/app to /var/run/holoscan/export/app

2025-08-04 22:40:00 [INFO] Copying application manifest file from /etc/holoscan/app.json to /var/run/holoscan/export/config/app.json
2025-08-04 22:40:00 [INFO] Copying pkg manifest file from /etc/holoscan/pkg.json to /var/run/holoscan/export/config/pkg.json
2025-08-04 22:40:00 [INFO] Copying application configuration from /var/holoscan/app.yaml to /var/run/holoscan/export/config/app.yaml

2025-08-04 22:40:00 [INFO] Copying models from /opt/holoscan/models to /var/run/holoscan/export/models
2025-08-04 22:40:00 [INFO] '/opt/holoscan/models' cannot be found.

2025-08-04 22:40:00 [INFO] Copying documentation from /opt/holoscan/docs/ to /var/run/holoscan/export/docs
2025-08-04 22:40:00 [INFO] '/opt/holoscan/docs/' cannot be found.

app  config

Executing packaged app locally#

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

# Clear the output folder and run the MAP container. The input is expected to be a folder
!rm -rf {output_path}
!monai-deploy run -i {test_input_folder} -o {output_path} {tag_prefix}-x64-workstation-dgpu-linux-amd64:1.0
[2025-08-04 15:40:01,449] [INFO] (runner) - Checking dependencies...
[2025-08-04 15:40:01,449] [INFO] (runner) - --> Verifying if "docker" is installed...

[2025-08-04 15:40:01,450] [INFO] (runner) - --> Verifying if "docker-buildx" is installed...

[2025-08-04 15:40:01,450] [INFO] (runner) - --> Verifying if "simple_imaging_app-x64-workstation-dgpu-linux-amd64:1.0" is available...

[2025-08-04 15:40:01,531] [INFO] (runner) - Reading HAP/MAP manifest...
Successfully copied 2.56kB to /tmp/tmpi4feeooh/app.json
Successfully copied 2.05kB to /tmp/tmpi4feeooh/pkg.json
0d07c2553a1873004f7935ef62b057b0f4c1e3a8600aba9a165c6e088b4bd12c
[2025-08-04 15:40:01,817] [INFO] (runner) - --> Verifying if "nvidia-ctk" is installed...

[2025-08-04 15:40:01,817] [INFO] (runner) - --> Verifying "nvidia-ctk" version...

[2025-08-04 15:40:02,143] [INFO] (common) - Launching container (8ae9dbe1a971) using image 'simple_imaging_app-x64-workstation-dgpu-linux-amd64:1.0'...
    container name:      objective_galois
    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
2025-08-04 22:40:02 [INFO] Launching application python3 /opt/holoscan/app ...

/home/holoscan/.local/lib/python3.12/site-packages/monai/deploy/utils/importutil.py:20: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.

  import pkg_resources

[info] [fragment.cpp:969] Loading extensions from configs...

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

[2025-08-04 22:40:03,369] [INFO] (root) - Parsed args: Namespace(log_level=None, input=None, output=None, model=None, workdir=None, triton_server_netloc=None, argv=['/opt/holoscan/app'])

[2025-08-04 22:40:03,369] [INFO] (root) - AppContext object: AppContext(input_path=/var/holoscan/input, output_path=/var/holoscan/output, model_path=/opt/holoscan/models, workdir=/var/holoscan), triton_server_netloc=

[2025-08-04 22:40:03,370] [INFO] (root) - sample_data_path: /var/holoscan/input

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

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

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

[info] [greedy_scheduler.cpp:191] Scheduling 3 entities

[info] [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] [greedy_scheduler.cpp:401] Scheduler finished.

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

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

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

Number of times operator sobel_op whose class is defined in sobel_operator called: 1

Input from: /var/holoscan/input, whose absolute path: /var/holoscan/input

Number of times operator median_op whose class is defined in median_operator called: 1

Number of times operator gaussian_op whose class is defined in gaussian_operator called: 1

Data type of output: <class 'numpy.ndarray'>, max = np.float64(0.35821119421406195)

Data type of output post conversion: <class 'numpy.ndarray'>, max = np.uint8(91)

2025-08-04 22:40:04 [INFO] Application exited with 0.

[2025-08-04 15:40:04,629] [INFO] (common) - Container 'objective_galois'(8ae9dbe1a971) exited with code 0.
#output_image_path was set as before, output_image_path = output_path + "/final_output.png"
output_image = io.imread(output_image_path)
io.imshow(output_image)
/tmp/ipykernel_1107452/3197869135.py:3: FutureWarning: `imshow` is deprecated since version 0.25 and will be removed in version 0.27. Please use `matplotlib`, `napari`, etc. to visualize images.
  io.imshow(output_image)
<matplotlib.image.AxesImage at 0x7c229c15c430>
../../_images/d6295ad52a7f6da3b3a61b7651288c84afca8c47bf7cbd936d2c5eecdb4ba2ae.png