Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save oeway/3a65504890d044da53bb79d2adbe67cb to your computer and use it in GitHub Desktop.
Save oeway/3a65504890d044da53bb79d2adbe67cb to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"kernelspec": {
"name": "python",
"display_name": "Python (Pyodide)",
"language": "python"
},
"language_info": {
"codemirror_mode": {
"name": "python",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8"
}
},
"nbformat_minor": 4,
"nbformat": 4,
"cells": [
{
"cell_type": "markdown",
"source": "## Collabrative Image Annotation\n\nIn this notebook, we will walk you through a set of tools for building collabrative annotation tool using the following:\n - [ImJoy](https://imjoy.io): [slides](https://aicell.io/slides/imjoy/), [tutorial](https://imjoy.io/docs/#/i2k_tutorial)\n - [Kaibu](https://kaibu.org)\n - [BioEngine](https://aicell.io/project/bioengine/)\n - [elFinder](https://github.com/imjoy-team/elFinder)\n ",
"metadata": {}
},
{
"cell_type": "markdown",
"source": "### Let's first install some libraries",
"metadata": {}
},
{
"cell_type": "code",
"source": "import micropip\nawait micropip.install(['pyodide-http', 'numpy', 'imjoy-rpc', 'requests', 'kaibu-utils'])\n\nimport pyodide_http\npyodide_http.patch_all() # Patch all libraries",
"metadata": {
"trusted": true
},
"outputs": [],
"execution_count": 2
},
{
"cell_type": "markdown",
"source": "### ImJoy plugin\n\nImJoy is a web-based plugin framework for creating user friendly tools. In this notebook, we have integrated ImJoy so you can run ImJoy plugins.\n\nThe follow code shows a hello world example for an ImJoy ",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc import api\n\nasync def setup(): \n await api.alert(\"Hello\")\n \napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"c19e9e04-d64d-4a23-b843-40a676da1efe\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"d00e5ee9-908e-4fd2-ab33-6d7015e0ca4e\"></div>"
},
"metadata": {}
},
{
"execution_count": 3,
"output_type": "execute_result",
"data": {
"text/plain": "<PyodideFuture finished result=[]>"
},
"metadata": {}
}
],
"execution_count": 3
},
{
"cell_type": "markdown",
"source": "### Using Kaibu as an ImJoy plugin\n\nKaibu (https://kaibu.org) is a web app for image annotation, here we will use it for annotating images.\n\nKaibu can be used as an ImJoy plugin, see the example below.\n\nFor more API and example, see here: https://kaibu.org/docs/#/api",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy import api\n\nasync def setup():\n viewer = await api.createWindow(src=\"https://kaibu.org/#/app\")\n await viewer.view_image(\"https://images.proteinatlas.org/61448/1319_C10_2_blue_red_green.jpg\", name=\"example image\")\n await viewer.add_shapes([], name=\"annotation\")\n\napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"c19e9e04-d64d-4a23-b843-40a676da1efe\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"ce004888-4143-4df7-b023-827d6766b993\"></div>"
},
"metadata": {}
},
{
"execution_count": 4,
"output_type": "execute_result",
"data": {
"text/plain": "<_GatheringFuture pending>"
},
"metadata": {}
}
],
"execution_count": 4
},
{
"cell_type": "markdown",
"source": "### Using Kaibu to get annotation\n\nIn the following example, we will use Kaibu to load an image (remote for now), then add an annotation layer and a button to get the annotation.\n\nHere, you will see how we refer to the annotation layer and obtain the annotated polygons, also we used `viewer.add_widgets` to add a button.\n\nTry to click on the annotation layer in the viewer, then use the free draw tool (with the pencil icon) to outline some cells, then click \"Get Annotation\" you will see a new layer added with the converted masks. The mask image (a.k.a label image) is a numpy array, with pixel values representing its object id.",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc import api\nfrom kaibu_utils import fetch_image, features_to_mask, mask_to_features\n\n\nasync def setup():\n viewer = await api.createWindow(src=\"https://kaibu.org/#/app\")\n\n # Add an image layer and annotation layer\n await viewer.view_image(\"https://images.proteinatlas.org/61448/1319_C10_2_blue_red_green.jpg\")\n annotation_layer = await viewer.add_shapes(\n [],\n shape_type=\"polygon\",\n edge_color=\"red\",\n name=\"annotation\",\n )\n async def get_annotation():\n features = await annotation_layer.get_features()\n await api.alert(str(features))\n \n mask = features_to_mask(features, [2048, 2048])\n\n await viewer.view_image(\n mask,\n name=\"mask\"\n )\n\n\n await viewer.add_widget(\n {\n \"_rintf\": True,\n \"name\": \"Control\",\n \"type\": \"control\",\n \"elements\": [\n {\n \"type\": \"button\",\n \"label\": \"Get Annotation\",\n \"callback\": get_annotation,\n }\n ],\n })\n\napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"c19e9e04-d64d-4a23-b843-40a676da1efe\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"a0b3bbd9-d947-45a3-a764-5462fecbe593\"></div>"
},
"metadata": {}
},
{
"execution_count": 9,
"output_type": "execute_result",
"data": {
"text/plain": "<_GatheringFuture pending>"
},
"metadata": {}
}
],
"execution_count": 9
},
{
"cell_type": "markdown",
"source": "### Download an remote image and store it inside elFinder\n\nSo far you have been using the remote image, let's move on to use other ImJoy plugins so you can load your files locally.\n\nHere, elFinder is a in-browser file manager, we will use it to manage our images for annotation.\n\nIn the following code, we first download an image from the human protein atlas, then write it into the elFinder",
"metadata": {}
},
{
"cell_type": "code",
"source": "import requests\nimport io\nfrom imjoy_rpc.utils import open_elfinder, elfinder_listdir\n\n# Download a test image\nresponse = requests.get(\"https://images.proteinatlas.org/61448/1319_C10_2_blue_red_green.jpg\")\ndata = response.content\n\n\n# Write the file to elfinder storage\nwith open_elfinder(\"/home/test-image.png\", \"wb\") as f:\n f.write(data)",
"metadata": {
"trusted": true
},
"outputs": [
{
"ename": "<class 'Exception'>",
"evalue": "Failed to write: , 404",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[11], line 12\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;66;03m# Write the file to elfinder storage\u001b[39;00m\n\u001b[1;32m 11\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m open_elfinder(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/home/test-image.png\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m---> 12\u001b[0m \u001b[43mf\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwrite\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[0;32m/lib/python3.11/site-packages/imjoy_rpc/utils.py:724\u001b[0m, in \u001b[0;36mHTTPFile.write\u001b[0;34m(self, content)\u001b[0m\n\u001b[1;32m 721\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwrite is not supported with mode \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_mode\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 723\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mb\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_mode:\n\u001b[0;32m--> 724\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_upload\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcontent\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 725\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 726\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_upload(content\u001b[38;5;241m.\u001b[39mencode(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_encoding))\n",
"File \u001b[0;32m/lib/python3.11/site-packages/imjoy_rpc/utils.py:819\u001b[0m, in \u001b[0;36mHTTPFile._upload\u001b[0;34m(self, content)\u001b[0m\n\u001b[1;32m 815\u001b[0m req \u001b[38;5;241m=\u001b[39m _sync_xhr_post(\n\u001b[1;32m 816\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_url, content, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mapplication/octet-stream\u001b[39m\u001b[38;5;124m\"\u001b[39m, append\n\u001b[1;32m 817\u001b[0m )\n\u001b[1;32m 818\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m req\u001b[38;5;241m.\u001b[39mstatus \u001b[38;5;241m!=\u001b[39m \u001b[38;5;241m200\u001b[39m:\n\u001b[0;32m--> 819\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFailed to write: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mreq\u001b[38;5;241m.\u001b[39mresponse\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m, \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mreq\u001b[38;5;241m.\u001b[39mstatus\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 821\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial_request:\n\u001b[1;32m 822\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_initial_request \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n",
"\u001b[0;31mException\u001b[0m: Failed to write: , 404"
],
"output_type": "error"
}
],
"execution_count": 11
},
{
"cell_type": "markdown",
"source": "### Now we can show the elFinder as an ImJoy plugin\n\nIn the embedded window, you can see a file manager show up, with a file we just created named `test-image.png`.",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc import api\nasync def setup(): \n fm = await api.createWindow(\n src=\"https://jupyter.imjoy.io/elFinder\"\n )\n\napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"c19e9e04-d64d-4a23-b843-40a676da1efe\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"4b3e1faf-cfd0-4f2f-80e2-ac92e1afe3c9\"></div>"
},
"metadata": {}
},
{
"execution_count": 12,
"output_type": "execute_result",
"data": {
"text/plain": "<_GatheringFuture pending>"
},
"metadata": {}
}
],
"execution_count": 12
},
{
"cell_type": "markdown",
"source": "## Show elFinder as a file dialog\n\nYou can show elFinder in a popup window as a file dialog, e.g. for selecting files or folders.\n\nIn the following example, you can select a file and click \"OK\".",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc import api\n\nasync def setup(): \n fm = await api.showDialog(\n src=\"https://jupyter.imjoy.io/elFinder\"\n )\n selections = await fm.getSelections()\n await api.alert(str(selections))\n \n\napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"22ebe299-9274-4c38-bd77-f57347e0d0c1\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"28717658-78e4-451c-b1bd-728a2fbe40e4\"></div>"
},
"metadata": {}
},
{
"execution_count": 23,
"output_type": "execute_result",
"data": {
"text/plain": "<_GatheringFuture pending>"
},
"metadata": {}
}
],
"execution_count": 23
},
{
"cell_type": "markdown",
"source": "### List directory\n\nYou can use the `elfinder_listdir` to see what's inside the folder, e.g. the folder you selected.",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc.utils import elfinder_listdir\n\nelfinder_listdir('/home')",
"metadata": {
"trusted": true
},
"outputs": [
{
"execution_count": 24,
"output_type": "execute_result",
"data": {
"text/plain": "['test-image.png', 'NewFolder']"
},
"metadata": {}
}
],
"execution_count": 24
},
{
"cell_type": "markdown",
"source": "### Read files from elFinder\n\nTo read the files from elFinder, you can use `open_elfinder`, to parse images, we use the `imageio` module here.\n\nLet's show the elFinder again, so you can drag and drop your own image file in the following elFinder window.",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc import api\nimport imageio\n\nasync def setup(): \n fm = await api.showDialog(\n src=\"https://jupyter.imjoy.io/elFinder\"\n )\n await api.showMessage(\"Please drag and drop an image file to the elFinder window and select it\")\n selections = await fm.getSelections()\n try:\n image_file = open_elfinder(selections[0]['path'], \"rb\")\n img = imageio.v2.imread(image_file) \n api.alert(f\"Image numpy array shape: {img.shape}\")\n except Exception as exp:\n api.alert(f\"Failed to read the image, error: {exp}\")\n\napi.export({\"setup\": setup})",
"metadata": {
"trusted": true
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "window.connectPlugin && window.connectPlugin(\"22ebe299-9274-4c38-bd77-f57347e0d0c1\")"
},
"metadata": {}
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<div id=\"9403a8b6-acb9-48a7-8f8f-3557e5e0a5c9\"></div>"
},
"metadata": {}
},
{
"execution_count": 29,
"output_type": "execute_result",
"data": {
"text/plain": "<_GatheringFuture pending>"
},
"metadata": {}
}
],
"execution_count": 29
},
{
"cell_type": "markdown",
"source": "### Display the image\n\nAs an exercise, add the following code to the above, to display the image:\n\n```python\nviewer = await api.createWindow(src=\"https://kaibu.org/#/app\")\nawait viewer.view_image(img)\n```",
"metadata": {}
},
{
"cell_type": "code",
"source": "",
"metadata": {},
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"source": "## Collabrative Image Annotation with the BioEngine\n\nIn this section, we will show you now you can annotate images collabratively, e.g. sending a link to others, using [the BioEngine server](https://aicell.io/project/bioengine/)([Github](https://github.com/bioimage-io/bioengine)).\n\nTo do that, we need to connect to the BioEngine server, this allows us to create a workspace, similar to a \"Zoom room\", for users to join.",
"metadata": {}
},
{
"cell_type": "code",
"source": "from imjoy_rpc.hypha import login, connect_to_server\nimport uuid\n\nSERVER_URL = \"https://ai.imjoy.io\" # This may change in the future\n\n# Connect to the BioEngine server\nserver = await connect_to_server(\n {\"name\": \"test client\", \"server_url\": SERVER_URL}\n)\ntoken = await server.generate_token()\nworkspace = server.config['workspace']",
"metadata": {
"trusted": true
},
"outputs": [],
"execution_count": 31
},
{
"cell_type": "markdown",
"source": "### Create a sharable link\n\nLet's create a sharable link for others to join the workspace.\n\nClick the link, or share send it to someone to open it.",
"metadata": {}
},
{
"cell_type": "code",
"source": "service_id = 'imjoy-client'\nimjoy_client_url = f\"https://imjoy.io/lite?workspace={workspace}&token={token}&service_id={service_id}\"\nprint(imjoy_client_url)",
"metadata": {
"trusted": true
},
"outputs": [
{
"name": "stdout",
"text": "https://imjoy.io/lite?workspace=93PKeV6X6A27MLWxbLML6E&token=nv7fSfPrPqdKyiYLVM7eBz@imjoy@eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2ltam95LmlvLyIsInN1YiI6Im52N2ZTZlByUHFkS3lpWUxWTTdlQnoiLCJhdWQiOiJodHRwczovL2ltam95LmV1LmF1dGgwLmNvbS9hcGkvdjIvIiwiaWF0IjoxNjk3Njk4ODY2Ljc4OTc2MzcsImV4cCI6MTY5NzcwOTY2Ni43ODk3NjM3LCJzY29wZSI6IjkzUEtlVjZYNkEyN01MV3hiTE1MNkUiLCJwYXJlbnQiOiI5M1BLZVY2WDZBMjdNTFd4YkxNTDZFIiwicGMiOm51bGwsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyIsImh0dHBzOi8vYXBpLmltam95LmlvL3JvbGVzIjpbXSwiaHR0cHM6Ly9hcGkuaW1qb3kuaW8vZW1haWwiOm51bGx9.L7RzHRXND2TouQtjNVb3Cbu6YPOifnr4GFUaN1w04OI&service_id=imjoy-client\n",
"output_type": "stream"
}
],
"execution_count": 32
},
{
"cell_type": "markdown",
"source": "### Controlling remote imjoy clients\n\nWhen multiple people open the link above, they can join a common workspace as an ImJoy client, you can then for example to control every ImJoy client.\n\nLet's send a hello world message to the ones opened the link.",
"metadata": {}
},
{
"cell_type": "code",
"source": "clients = await server.list_services({\"type\": \"imjoy-client\"})\n\nfor client in clients:\n print(\"Found imjoy client: \", client)\n # get the client service\n svc = await server.get_service(client)\n # We can access the imjoy plugin instance via svc.api, the exact same api as you see before.\n await svc.api.alert(\"Hello from workspace host!\")",
"metadata": {
"trusted": true
},
"outputs": [],
"execution_count": 37
},
{
"cell_type": "markdown",
"source": "### Display an image",
"metadata": {}
},
{
"cell_type": "code",
"source": "clients = await server.list_services({\"type\": \"imjoy-client\"})\n\nfor client in clients:\n print(\"Found imjoy client: \", client)\n # get the client service\n svc = await server.get_service(client)\n # We can access the imjoy plugin instance via svc.api, the exact same api as you see before.\n # This means you can also display any ImJoy plugins including Kaibu\n viewer = await svc.api.showDialog(\n name=\"Kaibu\",\n src=\"https://kaibu.org\",\n fullscreen=True\n )\n await viewer.view_image(\"https://images.proteinatlas.org/61448/1319_C10_2_blue_red_green.jpg\")\n await viewer.add_shapes([], name=\"annotation\")\n ",
"metadata": {},
"outputs": [],
"execution_count": null
},
{
"cell_type": "markdown",
"source": "### Going further\n\nWith the above tools and code snippet, you should be now well equiped to build collabrative annotation tools. For example, you can combine the elFinder with the BioEngine to distribute images to multiple people for annotation.",
"metadata": {}
},
{
"cell_type": "markdown",
"source": "## Useful links\n\n - Tutorial for ImJoy: https://imjoy.io/docs/#/i2k_tutorial\n - itk-vtk-viewer (a ImJoy plugin for image visualization supports 3D): https://kitware.github.io/itk-vtk-viewer/docs/imjoy.html\n - vizarr (): https://github.com/hms-dbmi/vizarr\n\n\nAny question, please reach out:\n Wei Ouyang (weio@kth.se)\n",
"metadata": {}
},
{
"cell_type": "code",
"source": "",
"metadata": {},
"outputs": [],
"execution_count": null
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment