{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Interactive Microscopy Control with ImJoy and MicroManager\n",
    "\n",
    "Author: [Wei OUYANG](https://oeway.github.io/)\n",
    "\n",
    "[ImJoy](https://imjoy.io) is a web framework for building interactive analysis tools. You can also use it to build easy-to-use and interactive data acquisition tool together with MicroManager.\n",
    "\n",
    "In this tutorial notebook, we will go through the steps for using ImJoy plugins with MicroManager to control your microscope interactively. \n",
    "\n",
    "Here is a outline of this tutorial:\n",
    "1. preparation\n",
    "1. Acquire an image and display it with matplotlib\n",
    "1. Acquire and display images continuously with matplotlib\n",
    "1. Build your first ImJoy plugin\n",
    "1. Snap an image in the ImJoy plugin\n",
    "1. Visualize the image with the itk-vtk-viewer plugin\n",
    "1. Use a dedicated UI plugin with MicroManager\n",
    "1. Deploy your plugin to Github and share it\n",
    "1. Additional Resources"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preparation\n",
    "\n",
    "Skip this step if you are running this notebook on Binder, the following preparation steps only concerns your local installation of MicroManager.\n",
    "\n",
    "You will be able to follow this tutorial in a Jupyter notebook on the computer with Micro-Manager.\n",
    "\n",
    "Importantly, MicroManager (the python binding) exposes full access of your microscope to the python scripting interface, please be careful that some commands (e.g. moving the stage) may damage your hardware. Although this tutorial only involves camera control which is safe, we still recommend to disconnect your hardware and start Micro-Manager with the simulated demo devices for exploration, and only connect the hardware when you fully understand the scripts.\n",
    "\n",
    "\n",
    "1. Install Pymmcore, ImJoy and [ImJoy Jupyter Extension](https://github.com/imjoy-team/imjoy-jupyter-extension) by run `pip install pymmcore imjoy imjoy-jupyter-extension`, then start or restart your Jupyter notebook server by using `jupyter notebook` command.\n",
    "2. When you open this Jupyter notebook, make sure you see an ImJoy icon in the toolbar in opened notebooks.\n",
    "3. If you don't have Micro-Manager installed, download the lastest version of [micro-manager 2.0](https://micro-manager.org/wiki/Micro-Manager_Nightly_Builds)\n",
    "4. Run Micro-Manager, select tools-options, and check the box that says Run server on port 4827 (you only need to do this once)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Skip this cell if you are running this notebook on Binder\n",
    "!pip install pymmcore imjoy imjoy-jupyter-extension"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set the path to your micromanager installation and config file\n",
    "\n",
    "After installation, please set the path to your micromanager installation folder and a config file.\n",
    "\n",
    "If you are running on Binder, you can keep the values as is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "MM_DIR = \"./mmcore\"\n",
    "MM_CONFIG_FILE = os.path.join(MM_DIR, \"MMConfig_demo.cfg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Acquire an image and display it with matplotlib\n",
    "\n",
    "By calling `core.snapImage()` we can control micromanager to acquire image and use `core.getImage()` to fetch the image data.\n",
    "\n",
    "In a notebook, we can use matplotlib function `plt.imshow` to visualize the image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7f8f9ef74450>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from imjoy import api\n",
    "import pymmcore\n",
    "import os.path\n",
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "mmc = pymmcore.CMMCore()\n",
    "mmc.setDeviceAdapterSearchPaths([MM_DIR])\n",
    "mmc.loadSystemConfiguration(MM_CONFIG_FILE)\n",
    "mmc.snapImage()\n",
    "image = mmc.getImage()\n",
    "plt.imshow(image)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Acquire and display images continuously\n",
    "Since we are doing microscopy imaging with the microscope, it's important to be able to see a live stream, for example, for finding a field of view.\n",
    "\n",
    "Jupyter notebook has little support for visualizing real-time data itself, but we can try to achieve live update by repeatitively clear the plot and draw again."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import clear_output\n",
    "\n",
    "for i in range(10):\n",
    "    clear_output(wait=True)\n",
    "    plt.figure()\n",
    "    plt.title(i)\n",
    "    mmc.snapImage()\n",
    "    image = mmc.getImage()\n",
    "    plt.imshow(image)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While we can see the live stream, it provides litte interactivity, for example, if we want to do contrast stretching, we will have to stop the stream and perhaps combine with some jupyter widgets to achieve it.\n",
    "\n",
    "On the other hand, ImJoy is designed for providing this type of application. In the following section, you will see how we can achieve this by building an ImJoy plugin.\n",
    "\n",
    "## Build your first ImJoy plugin\n",
    "\n",
    "Let's start by making a \"hello world\" plugin example with ImJoy.\n",
    "\n",
    "An ImJoy plugin is a class defines at least two functions `setup` and `run`. In the `setup` function we put preparation or initialization code and the `run` function is an entrypoint when the user starts the plugin. As an example, we do nothing in the `setup` function and popup a hello world message in the `run` function.\n",
    "\n",
    "Importantly, you need to export your plugin by running `api.export(ImJoyPlugin())` to register the plugin to the ImJoy core (running in the browser with the notebook page).\n",
    "\n",
    "Now run the following cell.\n",
    "\n",
    "If you see a popup message saying \"hello world\", congrats that you have build your first ImJoy plugin!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "window.connectPlugin && window.connectPlugin()"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div id=\"19ed77ca-10c4-4dcb-a00e-a95b0b650907\"></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from imjoy import api\n",
    "\n",
    "class ImJoyPlugin():\n",
    "    '''Defines an ImJoy plugin'''\n",
    "    async def setup(self):\n",
    "        '''for initialization'''\n",
    "        pass\n",
    "\n",
    "    async def run(self, ctx):\n",
    "        '''called when the user run this plugin'''\n",
    "        \n",
    "        # show a popup message\n",
    "        await api.alert(\"hello world\")\n",
    "\n",
    "# register the plugin to the imjoy core\n",
    "api.export(ImJoyPlugin())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: if the `async` and `await` keywords are new to you, you may want to learn about an imporant programing style called \"asynchronous programming\". It's basically a cheap way to achieve parallelizatin in a single thread, and Python3 provides [asyncio API](https://docs.python.org/3/library/asyncio-task.html) for it. With the async/await syntax, you can write async code as you usually do with your other synchronous code.\n",
    "\n",
    "Don't worry if you don't fully understand asynchronous programming. For now you can treat it the same as regular python programming, but remember the following simplified rules:\n",
    "1. it is recommended to add `await` before every ImJoy api call except `api.export`, e.g.: do `await api.alert(\"hello\")`.\n",
    "2. if you used `await` in a function, then you have to also add `async def` to define the function."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Snap an image in the ImJoy plugin\n",
    "\n",
    "Now let's define a function for acquire images with Pycro-Manager and call it `snap_image()`. Add this function into the plugin class and use it in the `run` function.\n",
    "\n",
    "Run the fullowing cell, you should see a message if you acquired an image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "window.connectPlugin && window.connectPlugin()"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div id=\"7068bf8a-1967-4769-aea6-0df83709d2ec\"></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from imjoy import api\n",
    "\n",
    "class MyMicroscope():\n",
    "    '''Defines a Microscope plugin'''\n",
    "    async def setup(self):\n",
    "        '''initialize pymmcore '''\n",
    "        mmc = pymmcore.CMMCore()\n",
    "        mmc.setDeviceAdapterSearchPaths([MM_DIR])\n",
    "        mmc.loadSystemConfiguration(MM_CONFIG_FILE)\n",
    "        self.mmc = mmc\n",
    "    \n",
    "    def snap_image(self):\n",
    "        '''snape an image with the micromanager bridge and return it as a numpy array'''\n",
    "        self.mmc.snapImage()\n",
    "        image_array = self.mmc.getImage()\n",
    "        # for display, we can scale the image into the range of 0~255\n",
    "        image_array = (image_array/image_array.max()*255).astype('uint8')\n",
    "        return image_array\n",
    "\n",
    "    async def run(self, ctx):\n",
    "        '''acquire one image and notify the user'''\n",
    "        img = self.snap_image()\n",
    "        # show a popup message\n",
    "        await api.alert(\"Acquired an image (size={}) with Micro-Manager\".format(img.shape))\n",
    "\n",
    "# register the plugin to the imjoy core\n",
    "api.export(MyMicroscope())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualize the image with the itk-vtk-viewer plugin\n",
    "\n",
    "To show the images, we can use another ImJoy plugin called itk-vtk-viewer which provide rich featuers including color map, contrast stretching, scaling. It can be used directly via this link: https://oeway.github.io/itk-vtk-viewer/ as standalone web app, but also available as an ImJoy plugin.\n",
    "\n",
    "To use it, you can run `viewer = await api.showDialog(src=\"https://oeway.github.io/itk-vtk-viewer/\")` to create a viewer. The returned `viewer` object contains a set of API functions exported by the itk-vtk-viewer plugin, and we will call `viewer.imshow()` for displaying images where `imshow` is one of the API functions.\n",
    "\n",
    "Note that we need to add `await` before `api.showDialog`, but also all the returned API functions including `imshow()`.\n",
    "\n",
    "In the following plugin, we call `snape_image` and `viewer.imshow` in a for loop inside the `run` function, to continuously display the image.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "window.connectPlugin && window.connectPlugin()"
      ],
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div id=\"0051988c-0c91-4058-99ec-dfdad327d48c\"></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from imjoy import api\n",
    "import pymmcore\n",
    "import os.path\n",
    "\n",
    "MM_DIR = \"./mmcore\"\n",
    "MM_CONFIG_FILE = os.path.join(MM_DIR, \"MMConfig_demo.cfg\")\n",
    "\n",
    "mmc = pymmcore.CMMCore()\n",
    "mmc.setDeviceAdapterSearchPaths([MM_DIR])\n",
    "mmc.loadSystemConfiguration(MM_CONFIG_FILE)\n",
    "mmcore = mmc\n",
    "\n",
    "class MyMicroscope():\n",
    "    def setup(self):\n",
    "        self._core = mmcore\n",
    "        self._core.setExposure(float(10))\n",
    "        exposure = self._core.getExposure()\n",
    "        api.showMessage('MMcore loaded, exposure: ' + str(exposure))\n",
    "    \n",
    "    def snapImage(self):\n",
    "        self._core.snapImage()\n",
    "        image_array = self._core.getImage()\n",
    "        image_array = (image_array/image_array.max()*255).astype('uint8')\n",
    "        return image_array\n",
    "\n",
    "    async def run(self, ctx):\n",
    "        viewer = await api.showDialog(type=\"itk-vtk-viewer\",\n",
    "                                      src=\"https://oeway.github.io/itk-vtk-viewer/\")\n",
    "        api.showMessage('Acquiring 30 images')\n",
    "        for i in range(30):\n",
    "            await viewer.imshow(self.snapImage())\n",
    "        api.showMessage('Done.')\n",
    "        \n",
    "api.export(MyMicroscope())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The itk-vtk-viewer plugin provides rich features for inspecting the displayed image, but it does not provide features to control the microscope.\n",
    "\n",
    "## Use a dedicated UI plugin with Micro-Manager\n",
    "\n",
    "ImJoy allows developers build custom plugins and can be easily used later in another plugin. For example, we can add buttons to snap image, provide options to change exposure in a custom UI plugin.\n",
    "\n",
    "For working with Micro-Manager, we made a dedicated UI plugin called \"PycroCam\" which can be referred via https://gist.github.com/oeway/f59c1d1c49c94a831e5e21ba4c6111dd. If you are interested in how to make such a plugin, cick the link and you will see the plugin source code in HTML, Javascript and CSS.\n",
    "\n",
    "For this tutorial, we will focuse on using such a plugin with Micro-Manager and it's as easy as calling `pycrocam = await api.createWindow(src=\"https://gist.github.com/oeway/f59c1d1c49c94a831e5e21ba4c6111dd\", data={...})`.\n",
    "\n",
    "Slightly different from the above example where we create a window via `api.createWindow` and we use the returned `viewer` object to access API functions such as `imshow`. In this example, we will directly pass a set of Micro-Manager core api functions to the `PycroCam` plugin so we can directly control the microscope within the plugin.\n",
    "\n",
    "In the following `run` function, you will see that we first construct a dictionary (named `mmcore_api`) with a set of functions required by the plugin including `snapImage`, `getImage` and `setExposure`. Then we pass the dictionary into `api.createWindow()` as a keyword `data`, specifically, `data={'mmcore': mmcore_api}`. \n",
    "\n",
    "\n",
    "Run the following cell, and you will see the PycroCam UI with snap and live buttons, set exposure and binning. In addition you can click the \"Device Properties\" which will popup a device property browser. Just like the one in Micro-Manager itself, you can change almost any property with that."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "from imjoy import api\n",
    "import numpy as np\n",
    "import pymmcore\n",
    "import os.path\n",
    "\n",
    "MM_DIR = \"./mmcore\"\n",
    "MM_CONFIG_FILE = os.path.join(MM_DIR, \"MMConfig_demo.cfg\")\n",
    "\n",
    "\n",
    "class MyMicroscope():\n",
    "    async def setup(self):\n",
    "        self.mmc = pymmcore.CMMCore()\n",
    "        self.mmc.setDeviceAdapterSearchPaths([MM_DIR])\n",
    "        self.mmc.loadSystemConfiguration(MM_CONFIG_FILE)\n",
    "\n",
    "        exposure = self.mmc.getExposure()\n",
    "        api.showMessage('MMcore loaded, exposure: ' + str(exposure))\n",
    "\n",
    "    def snap_image(self):\n",
    "        if self.mmc.isSequenceRunning():\n",
    "            self.mmc.stopSequenceAcquisition()\n",
    "        self.mmc.snapImage()\n",
    "        image_array = self.mmc.getImage()\n",
    "        image_array = (image_array/image_array.max()*255).astype('uint8')\n",
    "        return image_array\n",
    "    \n",
    "    def get_image(self):\n",
    "        # we can also check remaining with getRemainingImageCount()\n",
    "        image_array = self.mmc.getImage()\n",
    "        image_array = (image_array/image_array.max()*255).astype('uint8')\n",
    "        return image_array\n",
    "\n",
    "    def get_device_properties(self):\n",
    "        devices = self.mmc.getLoadedDevices()\n",
    "        device_items = []\n",
    "        for device in devices:\n",
    "            props = self.mmc.getDevicePropertyNames(device)\n",
    "            property_items = []\n",
    "            for prop in props:\n",
    "                value = self.mmc.getProperty(device, prop)\n",
    "                is_read_only = self.mmc.isPropertyReadOnly(device, prop)\n",
    "                if self.mmc.hasPropertyLimits(device, prop):\n",
    "                    lower = self.mmc.getPropertyLowerLimit(device, prop)\n",
    "                    upper = self.mmc.getPropertyUpperLimit(device, prop)\n",
    "                    allowed = {\"type\": \"range\", \"min\": lower, \"max\": upper, \"readOnly\": is_read_only}\n",
    "                else:\n",
    "                    allowed = self.mmc.getAllowedPropertyValues(device, prop)\n",
    "                    allowed = {\"type\": \"enum\", \"options\": allowed, \"readOnly\": is_read_only}\n",
    "                property_items.append({\"device\": device, \"name\": prop, \"value\": value, \"allowed\": allowed})\n",
    "                # print('===>', device, prop, value, allowed)\n",
    "            if len(property_items) > 0:\n",
    "                device_items.append({\"name\": device, \"value\": \"{} properties\".format(len(props)), \"items\": property_items})\n",
    "        return device_items\n",
    "\n",
    "    async def run(self, ctx):\n",
    "        mmcore_api = {\n",
    "            \"_rintf\": True,\n",
    "            \"snapImage\": self.snap_image,\n",
    "            \"getImage\": self.get_image,\n",
    "            \"getDeviceProperties\": self.get_device_properties,\n",
    "            \"getCameraDevice\": self.mmc.getCameraDevice,\n",
    "            \"setCameraDevice\": self.mmc.setCameraDevice,\n",
    "            \"startContinuousSequenceAcquisition\": self.mmc.startContinuousSequenceAcquisition,\n",
    "            \"stopSequenceAcquisition\": self.mmc.stopSequenceAcquisition,\n",
    "            \"setExposure\": self.mmc.setExposure,\n",
    "            \"getExposure\": self.mmc.getExposure,\n",
    "            \"setProperty\": self.mmc.setProperty,\n",
    "            \"getProperty\": self.mmc.getProperty\n",
    "        }\n",
    "        viewer = await api.createWindow(src=\"https://gist.github.com/oeway/f59c1d1c49c94a831e5e21ba4c6111dd\", data={'mmcore': mmcore_api})\n",
    "\n",
    "api.export(MyMicroscope())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.7.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}