{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deploying a MedNIST Classifier App with MONAI Deploy App SDK (Prebuilt Model)\n",
    "\n",
    "This tutorial demos the process of packaging up a trained model using MONAI Deploy App SDK into an artifact which can be run as a local program performing inference, a workflow job doing the same, and a Docker containerized workflow execution.\n",
    "\n",
    "In this tutorial, we will use a trained model and implement & package the inference application, executing the application locally.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Clone the github project (the latest version of the main branch only)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cloning into 'source'...\n",
      "remote: Enumerating objects: 212, done.\u001b[K\n",
      "remote: Counting objects: 100% (212/212), done.\u001b[K\n",
      "remote: Compressing objects: 100% (188/188), done.\u001b[K\n",
      "remote: Total 212 (delta 33), reused 79 (delta 7), pack-reused 0\u001b[K\n",
      "Receiving objects: 100% (212/212), 546.28 KiB | 3.50 MiB/s, done.\n",
      "Resolving deltas: 100% (33/33), done.\n"
     ]
    }
   ],
   "source": [
    "!git clone --branch main --depth 1 https://github.com/Project-MONAI/monai-deploy-app-sdk.git source \\\n",
    " && rm -rf source/.git"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mednist_classifier_monaideploy.py\n"
     ]
    }
   ],
   "source": [
    "!ls source/examples/apps/mednist_classifier_monaideploy/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install monai-deploy-app-sdk package"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n",
      "Collecting monai-deploy-app-sdk\n",
      "  Downloading monai_deploy_app_sdk-0.1.0rc2-py3-none-any.whl (113 kB)\n",
      "\u001b[K     |████████████████████████████████| 113 kB 2.6 MB/s eta 0:00:01\n",
      "\u001b[?25hCollecting colorama>=0.4.1\n",
      "  Downloading colorama-0.4.4-py2.py3-none-any.whl (16 kB)\n",
      "Collecting networkx>=2.4\n",
      "  Downloading networkx-2.5.1-py3-none-any.whl (1.6 MB)\n",
      "\u001b[K     |████████████████████████████████| 1.6 MB 12.8 MB/s eta 0:00:01\n",
      "\u001b[?25hCollecting typeguard>=2.12.1\n",
      "  Downloading typeguard-2.12.1-py3-none-any.whl (17 kB)\n",
      "Collecting numpy>=1.17\n",
      "  Downloading numpy-1.19.5-cp36-cp36m-manylinux2010_x86_64.whl (14.8 MB)\n",
      "\u001b[K     |████████████████████████████████| 14.8 MB 9.5 MB/s eta 0:00:011\n",
      "\u001b[?25hCollecting decorator<5,>=4.3\n",
      "  Downloading decorator-4.4.2-py2.py3-none-any.whl (9.2 kB)\n",
      "Installing collected packages: decorator, typeguard, numpy, networkx, colorama, monai-deploy-app-sdk\n",
      "  Attempting uninstall: decorator\n",
      "    Found existing installation: decorator 5.1.0\n",
      "    Uninstalling decorator-5.1.0:\n",
      "      Successfully uninstalled decorator-5.1.0\n",
      "Successfully installed colorama-0.4.4 decorator-4.4.2 monai-deploy-app-sdk-0.1.0rc2 networkx-2.5.1 numpy-1.19.5 typeguard-2.12.1\n"
     ]
    }
   ],
   "source": [
    "!pip install --upgrade monai-deploy-app-sdk"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install necessary packages for the app"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n",
      "Collecting monai\n",
      "  Downloading monai-0.6.0-202107081903-py3-none-any.whl (584 kB)\n",
      "\u001b[K     |████████████████████████████████| 584 kB 2.7 MB/s eta 0:00:01\n",
      "\u001b[?25hCollecting Pillow\n",
      "  Downloading Pillow-8.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB)\n",
      "\u001b[K     |████████████████████████████████| 3.0 MB 23.2 MB/s eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied: torch>=1.5 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from monai) (1.9.1)\n",
      "Requirement already satisfied: numpy>=1.17 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from monai) (1.19.5)\n",
      "Requirement already satisfied: typing_extensions in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from torch>=1.5->monai) (3.10.0.0)\n",
      "Requirement already satisfied: dataclasses in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from torch>=1.5->monai) (0.8)\n",
      "Installing collected packages: Pillow, monai\n",
      "Successfully installed Pillow-8.3.2 monai-0.6.0\n"
     ]
    }
   ],
   "source": [
    "!pip install monai Pillow  # for MONAI transforms and Pillow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download/Extract mednist_classifier_data.zip from Google Drive"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://pypi.org/simple, https://pypi.ngc.nvidia.com\n",
      "Collecting gdown\n",
      "  Downloading gdown-3.13.1.tar.gz (10 kB)\n",
      "  Installing build dependencies ... \u001b[?25ldone\n",
      "\u001b[?25h  Getting requirements to build wheel ... \u001b[?25ldone\n",
      "\u001b[?25h    Preparing wheel metadata ... \u001b[?25ldone\n",
      "\u001b[?25hCollecting filelock\n",
      "  Downloading filelock-3.0.12-py3-none-any.whl (7.6 kB)\n",
      "Requirement already satisfied: requests[socks]>=2.12.0 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from gdown) (2.26.0)\n",
      "Requirement already satisfied: six in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from gdown) (1.16.0)\n",
      "Collecting tqdm\n",
      "  Downloading tqdm-4.62.3-py2.py3-none-any.whl (76 kB)\n",
      "\u001b[K     |████████████████████████████████| 76 kB 2.8 MB/s eta 0:00:01\n",
      "\u001b[?25hRequirement already satisfied: certifi>=2017.4.17 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from requests[socks]>=2.12.0->gdown) (2021.5.30)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from requests[socks]>=2.12.0->gdown) (3.1)\n",
      "Requirement already satisfied: charset-normalizer~=2.0.0 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from requests[socks]>=2.12.0->gdown) (2.0.0)\n",
      "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from requests[socks]>=2.12.0->gdown) (1.26.6)\n",
      "Requirement already satisfied: PySocks!=1.5.7,>=1.5.6 in /home/gbae/miniconda3/envs/mednist/lib/python3.6/site-packages (from requests[socks]>=2.12.0->gdown) (1.7.1)\n",
      "Building wheels for collected packages: gdown\n",
      "  Building wheel for gdown (PEP 517) ... \u001b[?25ldone\n",
      "\u001b[?25h  Created wheel for gdown: filename=gdown-3.13.1-py3-none-any.whl size=9907 sha256=34f13d3a73d5f3f25f15dd69606e75b7d211bb9cc638bc47b82043612514d1f4\n",
      "  Stored in directory: /tmp/pip-ephem-wheel-cache-xtmpuwlo/wheels/6b/ba/3b/57c8250cc9279fb303e8bfa589361cbc58a1afb291475c4ddc\n",
      "Successfully built gdown\n",
      "Installing collected packages: tqdm, filelock, gdown\n",
      "Successfully installed filelock-3.0.12 gdown-3.13.1 tqdm-4.62.3\n",
      "Downloading...\n",
      "From: https://drive.google.com/uc?id=1yJ4P-xMNEfN6lIOq_u6x1eMAq1_MJu-E\n",
      "To: /home/gbae/mednist_app/mednist_classifier_data.zip\n",
      "28.6MB [00:02, 10.3MB/s]\n"
     ]
    }
   ],
   "source": [
    "# Download mednist_classifier_data.zip\n",
    "!pip install gdown \n",
    "!gdown \"https://drive.google.com/uc?id=1yJ4P-xMNEfN6lIOq_u6x1eMAq1_MJu-E\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Archive:  mednist_classifier_data.zip\n",
      " extracting: classifier.zip          \n",
      " extracting: input/AbdomenCT_007000.jpeg  \n"
     ]
    }
   ],
   "source": [
    "# After downloading mednist_classifier_data.zip from the web browser or using gdown,\n",
    "!unzip -o \"mednist_classifier_data.zip\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Package app (creating MAP Docker image)\n",
    "\n",
    "This assumes that nvidia docker is installed in the local machine.\n",
    "\n",
    "Please see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker to install nvidia-docker2.\n",
    "\n",
    "Use `-l DEBUG` option to see progress."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Building MONAI Application Package... Done\n",
      "[2021-09-21 03:07:51,614] [INFO] (app_packager) - Successfully built mednist_app:latest\n"
     ]
    }
   ],
   "source": [
    "!monai-deploy package \"source/examples/apps/mednist_classifier_monaideploy/mednist_classifier_monaideploy.py\" \\\n",
    "    --tag mednist_app:latest \\\n",
    "    --model classifier.zip"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run the app with docker image and input file locally"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Checking dependencies...\n",
      "--> Verifying if \"docker\" is installed...\n",
      "\n",
      "--> Verifying if \"mednist_app:latest\" is available...\n",
      "\n",
      "Checking for MAP \"mednist_app:latest\" locally\n",
      "\"mednist_app:latest\" found.\n",
      "\n",
      "Reading MONAI App Package manifest...\n",
      " > export '/var/run/monai/export/' detected\n",
      "--> Verifying if \"nvidia-docker\" is installed...\n",
      "\n",
      "\u001b[34mGoing to initiate execution of operator LoadPILOperator\u001b[39m\n",
      "\u001b[32mExecuting operator LoadPILOperator \u001b[33m(Process ID: 1, Operator ID: 2000b9d2-156f-4abd-8654-cf60219673ac)\u001b[39m\n",
      "\u001b[34mDone performing execution of operator LoadPILOperator\n",
      "\u001b[39m\n",
      "\u001b[34mGoing to initiate execution of operator MedNISTClassifierOperator\u001b[39m\n",
      "\u001b[32mExecuting operator MedNISTClassifierOperator \u001b[33m(Process ID: 1, Operator ID: 13deb10c-dd13-4af5-8a05-a72c07406c05)\u001b[39m\n",
      "AbdomenCT\n",
      "\u001b[34mDone performing execution of operator MedNISTClassifierOperator\n",
      "\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "!monai-deploy run mednist_app:latest \"input\" \"output\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"AbdomenCT\""
     ]
    }
   ],
   "source": [
    "!cat output/output.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementing and Packaging Application with MONAI Deploy App SDK\n",
    "\n",
    "Based on the Torchscript model(`classifier.zip`), we will implement an application that process an input Jpeg image and write the prediction(classification) result as JSON file(`output.json`).\n",
    "\n",
    "In our inference application, we will define two operators:\n",
    "\n",
    "1. `LoadPILOperator` - Load a JPEG image from the input path and pass the loaded image object to the next operator.\n",
    "    - This Operator does similar job with `LoadImage(image_only=True)` transform in *train_transforms*, but handles only one image.\n",
    "    - **Input**: a file path ([`DataPath`](/modules/_autosummary/monai.deploy.core.domain.DataPath))\n",
    "    - **Output**: an image object in memory ([`Image`](/modules/_autosummary/monai.deploy.core.domain.Image))\n",
    "2. `MedNISTClassifierOperator` - Pre-transform the given image by using MONAI's `Compose` class, feed to the Torchscript model (`classifier.zip`), and write the prediction into JSON file(`output.json`)\n",
    "    - Pre-transforms consist of three transforms -- `AddChannel`, `ScaleIntensity`, and `EnsureType`.\n",
    "    - **Input**: an image object in memory ([`Image`](/modules/_autosummary/monai.deploy.core.domain.Image))\n",
    "    - **Output**: a folder path that the prediction result(`output.json`) would be written ([`DataPath`](/modules/_autosummary/monai.deploy.core.domain.DataPath))\n",
    "\n",
    "The workflow of the application would look like this.\n",
    "\n",
    "<img src=\"https://user-images.githubusercontent.com/1928522/133868503-46671f0a-7741-4f9d-aefa-83e95e9a5f84.png\" alt=\"Workflow\" style=\"width: 600px;margin-left:auto;margin-right:auto;\"/>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setup imports\n",
    "\n",
    "Let's import necessary classes/decorators and define `MEDNIST_CLASSES`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import monai.deploy.core as md\n",
    "from monai.deploy.core import (\n",
    "    Application,\n",
    "    DataPath,\n",
    "    ExecutionContext,\n",
    "    Image,\n",
    "    InputContext,\n",
    "    IOType,\n",
    "    Operator,\n",
    "    OutputContext,\n",
    ")\n",
    "from monai.transforms import AddChannel, Compose, EnsureType, ScaleIntensity\n",
    "\n",
    "MEDNIST_CLASSES = [\"AbdomenCT\", \"BreastMRI\", \"CXR\", \"ChestCT\", \"Hand\", \"HeadCT\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creating Operator classes\n",
    "\n",
    "#### LoadPILOperator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "@md.input(\"image\", DataPath, IOType.DISK)\n",
    "@md.output(\"image\", Image, IOType.IN_MEMORY)\n",
    "@md.env(pip_packages=[\"pillow\"])\n",
    "class LoadPILOperator(Operator):\n",
    "    \"\"\"Load image from the given input (DataPath) and set numpy array to the output (Image).\"\"\"\n",
    "\n",
    "    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):\n",
    "        import numpy as np\n",
    "        from PIL import Image as PILImage\n",
    "\n",
    "        input_path = op_input.get().path\n",
    "        if input_path.is_dir():\n",
    "            input_path = next(input_path.glob(\"*.*\"))  # take the first file\n",
    "\n",
    "        image = PILImage.open(input_path)\n",
    "        image = image.convert(\"L\")  # convert to greyscale image\n",
    "        image_arr = np.asarray(image)\n",
    "\n",
    "        output_image = Image(image_arr)  # create Image domain object with a numpy array\n",
    "        op_output.set(output_image)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### MedNISTClassifierOperator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "@md.input(\"image\", Image, IOType.IN_MEMORY)\n",
    "@md.output(\"output\", DataPath, IOType.DISK)\n",
    "@md.env(pip_packages=[\"monai\"])\n",
    "class MedNISTClassifierOperator(Operator):\n",
    "    \"\"\"Classifies the given image and returns the class name.\"\"\"\n",
    "\n",
    "    @property\n",
    "    def transform(self):\n",
    "        return Compose([AddChannel(), ScaleIntensity(), EnsureType()])\n",
    "\n",
    "    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):\n",
    "        import json\n",
    "\n",
    "        import torch\n",
    "\n",
    "        img = op_input.get().asnumpy()  # (64, 64), uint8\n",
    "        image_tensor = self.transform(img)  # (1, 64, 64), torch.float64\n",
    "        image_tensor = image_tensor[None].float()  # (1, 1, 64, 64), torch.float32\n",
    "\n",
    "        device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "        image_tensor = image_tensor.to(device)\n",
    "\n",
    "        model = context.models.get()  # get a TorchScriptModel object\n",
    "\n",
    "        with torch.no_grad():\n",
    "            outputs = model(image_tensor)\n",
    "\n",
    "        _, output_classes = outputs.max(dim=1)\n",
    "\n",
    "        result = MEDNIST_CLASSES[output_classes[0]]  # get the class name\n",
    "        print(result)\n",
    "\n",
    "        # Get output (folder) path and create the folder if not exists\n",
    "        output_folder = op_output.get().path\n",
    "        output_folder.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "        # Write result to \"output.json\"\n",
    "        output_path = output_folder / \"output.json\"\n",
    "        with open(output_path, \"w\") as fp:\n",
    "            json.dump(result, fp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Creating Application class\n",
    "\n",
    "Our application class would look like below.\n",
    "\n",
    "It defines `App` class inheriting `Application` class.\n",
    "\n",
    "`LoadPILOperator` is connected to `MedNISTClassifierOperator` by using `self.add_flow()` in `compose()` method of `App`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "@md.resource(cpu=1, gpu=1, memory=\"1Gi\")\n",
    "class App(Application):\n",
    "    \"\"\"Application class for the MedNIST classifier.\"\"\"\n",
    "\n",
    "    def compose(self):\n",
    "        load_pil_op = LoadPILOperator()\n",
    "        classifier_op = MedNISTClassifierOperator()\n",
    "\n",
    "        self.add_flow(load_pil_op, classifier_op)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Executing app locally"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can execute the app in the Jupyter notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = App()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34mGoing to initiate execution of operator LoadPILOperator\u001b[39m\n",
      "\u001b[32mExecuting operator LoadPILOperator \u001b[33m(Process ID: 7041, Operator ID: 3aa42bbd-f8dd-4374-98ee-7b614979e75a)\u001b[39m\n",
      "\u001b[34mDone performing execution of operator LoadPILOperator\n",
      "\u001b[39m\n",
      "\u001b[34mGoing to initiate execution of operator MedNISTClassifierOperator\u001b[39m\n",
      "\u001b[32mExecuting operator MedNISTClassifierOperator \u001b[33m(Process ID: 7041, Operator ID: 7ee7dd5e-c042-4245-bb75-15ff064bd838)\u001b[39m\n",
      "AbdomenCT\n",
      "\u001b[34mDone performing execution of operator MedNISTClassifierOperator\n",
      "\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "app.run(input=\"input/AbdomenCT_007000.jpeg\", output=\"output\", model=\"classifier.zip\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"AbdomenCT\""
     ]
    }
   ],
   "source": [
    "!cat output/output.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "Once the application is verified inside Jupyter notebook, we can write the whole application as a file(`mednist_classifier_monaideploy.py`) by concatenating code above, then add the following lines:\n",
    "\n",
    "```python\n",
    "if __name__ == \"__main__\":\n",
    "    App(do_run=True)\n",
    "```\n",
    "\n",
    "The above lines are needed to execute the application code by using `python` interpreter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing mednist_classifier_monaideploy.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile mednist_classifier_monaideploy.py\n",
    "\n",
    "# Copyright 2021 MONAI Consortium\n",
    "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#     http://www.apache.org/licenses/LICENSE-2.0\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License.\n",
    "\n",
    "import monai.deploy.core as md  # 'md' stands for MONAI Deploy (or can use 'core' instead)\n",
    "from monai.deploy.core import (\n",
    "    Application,\n",
    "    DataPath,\n",
    "    ExecutionContext,\n",
    "    Image,\n",
    "    InputContext,\n",
    "    IOType,\n",
    "    Operator,\n",
    "    OutputContext,\n",
    ")\n",
    "from monai.transforms import AddChannel, Compose, EnsureType, ScaleIntensity\n",
    "\n",
    "MEDNIST_CLASSES = [\"AbdomenCT\", \"BreastMRI\", \"CXR\", \"ChestCT\", \"Hand\", \"HeadCT\"]\n",
    "\n",
    "\n",
    "@md.input(\"image\", DataPath, IOType.DISK)\n",
    "@md.output(\"image\", Image, IOType.IN_MEMORY)\n",
    "@md.env(pip_packages=[\"pillow\"])\n",
    "class LoadPILOperator(Operator):\n",
    "    \"\"\"Load image from the given input (DataPath) and set numpy array to the output (Image).\"\"\"\n",
    "\n",
    "    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):\n",
    "        import numpy as np\n",
    "        from PIL import Image as PILImage\n",
    "\n",
    "        input_path = op_input.get().path\n",
    "        if input_path.is_dir():\n",
    "            input_path = next(input_path.glob(\"*.*\"))  # take the first file\n",
    "\n",
    "        image = PILImage.open(input_path)\n",
    "        image = image.convert(\"L\")  # convert to greyscale image\n",
    "        image_arr = np.asarray(image)\n",
    "\n",
    "        output_image = Image(image_arr)  # create Image domain object with a numpy array\n",
    "        op_output.set(output_image)\n",
    "\n",
    "\n",
    "@md.input(\"image\", Image, IOType.IN_MEMORY)\n",
    "@md.output(\"output\", DataPath, IOType.DISK)\n",
    "@md.env(pip_packages=[\"monai\"])\n",
    "class MedNISTClassifierOperator(Operator):\n",
    "    \"\"\"Classifies the given image and returns the class name.\"\"\"\n",
    "\n",
    "    @property\n",
    "    def transform(self):\n",
    "        return Compose([AddChannel(), ScaleIntensity(), EnsureType()])\n",
    "\n",
    "    def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):\n",
    "        import json\n",
    "\n",
    "        import torch\n",
    "\n",
    "        img = op_input.get().asnumpy()  # (64, 64), uint8\n",
    "        image_tensor = self.transform(img)  # (1, 64, 64), torch.float64\n",
    "        image_tensor = image_tensor[None].float()  # (1, 1, 64, 64), torch.float32\n",
    "\n",
    "        device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "        image_tensor = image_tensor.to(device)\n",
    "\n",
    "        model = context.models.get()  # get a TorchScriptModel object\n",
    "\n",
    "        with torch.no_grad():\n",
    "            outputs = model(image_tensor)\n",
    "\n",
    "        _, output_classes = outputs.max(dim=1)\n",
    "\n",
    "        result = MEDNIST_CLASSES[output_classes[0]]  # get the class name\n",
    "        print(result)\n",
    "\n",
    "        # Get output (folder) path and create the folder if not exists\n",
    "        output_folder = op_output.get().path\n",
    "        output_folder.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "        # Write result to \"output.json\"\n",
    "        output_path = output_folder / \"output.json\"\n",
    "        with open(output_path, \"w\") as fp:\n",
    "            json.dump(result, fp)\n",
    "\n",
    "\n",
    "@md.resource(cpu=1, gpu=1, memory=\"1Gi\")\n",
    "class App(Application):\n",
    "    \"\"\"Application class for the MedNIST classifier.\"\"\"\n",
    "\n",
    "    def compose(self):\n",
    "        load_pil_op = LoadPILOperator()\n",
    "        classifier_op = MedNISTClassifierOperator()\n",
    "\n",
    "        self.add_flow(load_pil_op, classifier_op)\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    App(do_run=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this time, let's execute the app in the command line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34mGoing to initiate execution of operator LoadPILOperator\u001b[39m\n",
      "\u001b[32mExecuting operator LoadPILOperator \u001b[33m(Process ID: 8412, Operator ID: 631a82bf-c90e-4217-a17c-831b2c74bc50)\u001b[39m\n",
      "\u001b[34mDone performing execution of operator LoadPILOperator\n",
      "\u001b[39m\n",
      "\u001b[34mGoing to initiate execution of operator MedNISTClassifierOperator\u001b[39m\n",
      "\u001b[32mExecuting operator MedNISTClassifierOperator \u001b[33m(Process ID: 8412, Operator ID: a8fe1121-68bb-463f-bf1c-beff38d4fe86)\u001b[39m\n",
      "AbdomenCT\n",
      "\u001b[34mDone performing execution of operator MedNISTClassifierOperator\n",
      "\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "!python \"mednist_classifier_monaideploy.py\" -i \"input/AbdomenCT_007000.jpeg\" -o output -m \"classifier.zip\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Above command is same with the following command line:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[34mGoing to initiate execution of operator LoadPILOperator\u001b[39m\n",
      "\u001b[32mExecuting operator LoadPILOperator \u001b[33m(Process ID: 8453, Operator ID: 7dec2a01-6d18-4104-b250-5b93d663ba4f)\u001b[39m\n",
      "\u001b[34mDone performing execution of operator LoadPILOperator\n",
      "\u001b[39m\n",
      "\u001b[34mGoing to initiate execution of operator MedNISTClassifierOperator\u001b[39m\n",
      "\u001b[32mExecuting operator MedNISTClassifierOperator \u001b[33m(Process ID: 8453, Operator ID: 5e83dd80-5b19-4c78-9382-3d181640b80c)\u001b[39m\n",
      "AbdomenCT\n",
      "\u001b[34mDone performing execution of operator MedNISTClassifierOperator\n",
      "\u001b[39m\n"
     ]
    }
   ],
   "source": [
    "!monai-deploy exec \"mednist_classifier_monaideploy.py\" -i \"input/AbdomenCT_007000.jpeg\" -o output -m \"classifier.zip\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\"AbdomenCT\""
     ]
    }
   ],
   "source": [
    "!cat output/output.json"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
