Executing app locally

Executing and Inspecting App in Jupyter Notebook

You can define Operators and Application in Jupyter Notebook.

from typing import Any, List, Union

import monai.deploy.core as md  # 'md' stands for MONAI Deploy (or can use 'core' instead)
from monai.deploy.core import (Application, DataPath, ExecutionContext, Image,
                               InputContext, IOType, Operator, OutputContext)

# @md.input("", DataPath, IOType.DISK)
@md.output("list", List[Union[str,int]], IOType.IN_MEMORY)
@md.env(pip_packages=["numpy"])
class TaskA(Operator):
    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
        input_path = op_input.get().path  # input path (current path). not used here.
        model_path = context.models.get().path  # model path. This would be "" if the path (default: 'models') doesn't exist.

        op_output.set(["A", 1])

@md.input("list", List[Union[str,int]], IOType.IN_MEMORY)
# @md.output("", DataPath, IOType.DISK)
@md.env(pip_packages=["scikit-image"])
class TaskB(Operator):
    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
        input_data = op_input.get()
        print(input_data)
        
        output_path = op_output.get().path  #  output path (current path). not used here.

@md.env(pip_packages=["Pillow"])
class App(Application):
    def compose(self):
        taskA = TaskA()
        taskB = TaskB()
        self.add_flow(taskA, taskB)

Once an Application class (App) is defined, you can instentiate the application and execute with Application.run() method.

At least one parameter (whose value is not None or "") needs to be specified to execute the application inside Jupyter Notebook.

Since the above example doesn’t use input or output paths, we specify "." to locate current path. (If we specify other path for output and the path doesn’t exist, the output path would be created during the execution.)

app = App()
print(app.context)  # print app context
app.run(input=".", output=".")  # override 'input_path' and 'output_path' in app.context
AppContext(graph=nx_digraph, input_path=input, output_path=output, model_path=models, workdir=, datastore=memory, executor=single_process_executor, resource=Resource(cpu=0, memory=0, gpu=0))
Going to initiate execution of operator TaskA
Executing operator TaskA (Process ID: 9471, Operator ID: ab200dcf-833b-40f0-a61c-884793b2d9b4)
Done performing execution of operator TaskA

Going to initiate execution of operator TaskB
Executing operator TaskB (Process ID: 9471, Operator ID: e2db0e3f-76a1-43c1-bb60-26fd7500db22)
['A', 1]
Done performing execution of operator TaskB

app.context  # 'input_path' and 'output_path' are now overwritten
AppContext(graph=nx_digraph, input_path=., output_path=., model_path=models, workdir=, datastore=memory, executor=single_process_executor, resource=Resource(cpu=0, memory=0, gpu=0))

You can access Graph object through Application.graph

graph = app.graph
print(f"root_operators: {list(graph.get_root_operators())}")
print(f"operators: {list(graph.get_operators())}")
worklist = list(graph.gen_worklist())
print(f"worklist: {worklist}")
print(f"graph.is_root(worklist[0]): {graph.is_root(worklist[0])}")
print(f"graph.is_leaf(worklist[1]): {graph.is_leaf(worklist[1])}")
root_operators: [<__main__.TaskA object at 0x7fc749451358>]
operators: [<__main__.TaskA object at 0x7fc749451358>, <__main__.TaskB object at 0x7fc749451438>]
worklist: [<__main__.TaskA object at 0x7fc749451358>, <__main__.TaskB object at 0x7fc749451438>]
graph.is_root(worklist[0]): True
graph.is_leaf(worklist[1]): True

Executing Application through CLI

Once the application is verified inside Jupyter notebook, we can write the whole application as a file(app.py) with the following lines at the end of the file:

if __name__ == "__main__":
    App(do_run=True)

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

%%writefile app.py
from typing import Any, List, Union

import monai.deploy.core as md  # 'md' stands for MONAI Deploy (or can use 'core' instead)
from monai.deploy.core import (Application, DataPath, ExecutionContext, Image,
                               InputContext, IOType, Operator, OutputContext)

# @md.input("", DataPath, IOType.DISK)
@md.output("list", List[Union[str,int]], IOType.IN_MEMORY)
@md.env(pip_packages=["numpy"])
class TaskA(Operator):
    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
        input_path = op_input.get().path  # input path (current path). not used here.
        model_path = context.models.get().path  # model path. This would be "" if the path (default: 'models') doesn't exist.

        op_output.set(["A", 1])

@md.input("list", List[Union[str,int]], IOType.IN_MEMORY)
# @md.output("", DataPath, IOType.DISK)
@md.env(pip_packages=["scikit-image"])
class TaskB(Operator):
    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
        input_data = op_input.get()
        print(input_data)
        
        output_path = op_output.get().path  #  output path (current path). not used here.

@md.env(pip_packages=["Pillow"])
class App(Application):
    def compose(self):
        taskA = TaskA()
        taskB = TaskB()
        self.add_flow(taskA, taskB)

if __name__ == "__main__":
    App(do_run=True)
Overwriting app.py

You can load the Application object from file through monai.deploy.utils.importutil.get_application() method.

For the Application object, you can get the package information through Application.get_package_info() method

import json
from monai.deploy.utils.importutil import get_application
app = get_application("app.py")  # the parameter can be a main application file or an application folder
print(json.dumps(app.get_package_info(), indent=2))
{
  "app-name": "App",
  "app-version": "0.0.0",
  "sdk-version": "0.1.0-rc.2+1.g217d6b1.dirty",
  "command": "python3 -u /opt/monai/app/app.py",
  "resource": {
    "cpu": 0,
    "gpu": 0,
    "memory": "0Mi"
  },
  "models": [],
  "pip-packages": [
    "pillow",
    "numpy",
    "scikit-image"
  ]
}

Let’s run the application through CLI

!python app.py --input . --output .
Going to initiate execution of operator TaskA
Executing operator TaskA (Process ID: 9567, Operator ID: cc8b6114-7bab-4d80-9e82-0c6c42493986)
Done performing execution of operator TaskA

Going to initiate execution of operator TaskB
Executing operator TaskB (Process ID: 9567, Operator ID: acd67d8b-fb1a-4642-aa65-a32a411f7174)
['A', 1]
Done performing execution of operator TaskB

monai-deploy exec

Above command is same with the following command line:

!monai-deploy exec app.py --input . --output .
Going to initiate execution of operator TaskA
Executing operator TaskA (Process ID: 9724, Operator ID: 2c697da1-831f-49ce-84b1-6451413ce367)
Done performing execution of operator TaskA

Going to initiate execution of operator TaskB
Executing operator TaskB (Process ID: 9724, Operator ID: a1c07a95-a5fd-4d58-809e-e4f2fbc101ce)
['A', 1]
Done performing execution of operator TaskB

monai-deploy exec --help

Output:

usage: monai-deploy exec [-h] [-l {DEBUG,INFO,WARN,ERROR,CRITICAL}]
                         [--input INPUT] [--output OUTPUT] [--model MODEL]
                         [--workdir WORKDIR] [--graph {nx_digraph}]
                         [--datastore {memory}]
                         [--executor {single_process_executor}]
                         [remaining [remaining ...]]

positional arguments:
  remaining

optional arguments:
  -h, --help            show this help message and exit
  -l {DEBUG,INFO,WARN,ERROR,CRITICAL}, --log-level {DEBUG,INFO,WARN,ERROR,CRITICAL}
                        Set the logging level (default: INFO)
  --input INPUT, -i INPUT
                        Path to input folder/file (default: input)
  --output OUTPUT, -o OUTPUT
                        Path to output folder (default: output)
  --model MODEL, -m MODEL
                        Path to model(s) folder/file (default: models)
  --workdir WORKDIR, -w WORKDIR
                        Path to workspace folder (default: A temporary
                        '.monai_workdir' folder in the current folder)
  --graph {nx_digraph}  Set Graph engine (default: nx_digraph)
  --datastore {memory}  Set Datastore (default: memory)
  --executor {single_process_executor}
                        Set Executor (default: single_process_executor)