{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Controlling SBA Composer from within a Jupyter notebook\n",
    "\n",
    "> Inspired by: https://github.com/cmoscardi/embedded_d3_example/blob/master/Embedded_D3.ipynb\n",
    "\n",
    "Step 1: Create an `sbaInterface` object in Python, with a `send()` method that mimics the javascript code. The Python sbaInterface object loads the sba-interface.js library and generates the Javascript sbaInterface object. This will open a new SBA Composer window or tab. If a popup-blocker prevents this, do not worry."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# support python 2.7+ and 3\n",
    "from __future__ import unicode_literals\n",
    "from IPython.display import display, Javascript, clear_output\n",
    "from json import dumps as json_encode\n",
    "# the base64 library produces clean output without newlines\n",
    "from base64 import b64encode\n",
    "\n",
    "# SBA Composer page to load: here it is the -dev version and atlas ABA_v3\n",
    "#sbaHost = 'https://scalablebrainatlas.incf.org/composer-dev';\n",
    "sbaHost = 'http://localhost:81/composer';\n",
    "sbaUrl = sbaHost+'/?template=ABA_v3';\n",
    "inSandbox = False;\n",
    "\n",
    "class sbaInterface_class:\n",
    "    def __init__(this,sbaUrl,inSandbox):\n",
    "        # In Javascript, load sbaInterface.js and create a new sbaInterface object\n",
    "        this.channelName = 'sba-interface-sandbox' if inSandbox else None\n",
    "        display(Javascript(\"\"\"\n",
    "            var script = document.createElement('script');\n",
    "            script.src = '{}/../js/sba-interface.js';\n",
    "            script.onload = function() {{\n",
    "              window.sbaInterface = new sbaInterface_class('{}',null,{});\n",
    "            }}\n",
    "            document.head.appendChild(script)\n",
    "        \"\"\".format(sbaHost,sbaUrl,json_encode(this.channelName))))\n",
    "        clear_output()\n",
    "        \n",
    "    \n",
    "    def send(this,sbaCommand):\n",
    "        if this.channelName:\n",
    "          print('Messages from SBA Composer appear in the output area of the cell that contains the sbaInterface_class.')\n",
    "          display(Javascript(\"\"\"\n",
    "            const senderChannel = new BroadcastChannel({});\n",
    "            senderChannel.postMessage({});\n",
    "          \"\"\".format(json_encode(channelName),json_encode(sbaCommand))))\n",
    "        else:\n",
    "          display(Javascript(\"\"\"window.sbaInterface.send({})\"\"\".format(json_encode(sbaCommand))))\n",
    "        # Without clear_output(), sbaCommands are stored as notebook outputs\n",
    "        clear_output() \n",
    "\n",
    "sbaInterface = sbaInterface_class(sbaUrl,inSandbox)\n",
    "if inSandbox:\n",
    "  for i in range(0,10): print()\n",
    "  print('Messages from SBA Composer will appear in this area.');\n",
    "  for i in range(0,10): print()\n",
    "        \n",
    "#class sbaInterface_class:\n",
    "#    def __init__(this,sbaUrl):\n",
    "#        # In Javascript, load sbaInterface.js and create a new sbaInterface object\n",
    "#        display(Javascript(\"\"\"\n",
    "#            var script = document.createElement('script');\n",
    "#            script.src = '{}/../js/sba-interface.js';\n",
    "#            script.onload = function() {{\n",
    "#              window.global_sbaInterface = new sbaInterface_class('{}');\n",
    "#            }}\n",
    "#            document.head.appendChild(script)\n",
    "#        \"\"\".format(sbaHost,sbaUrl)))\n",
    "#        clear_output()\n",
    "#    \n",
    "#    def send(this,sbaCommand):\n",
    "#        display(Javascript(\"\"\"window.global_sbaInterface.send({})\"\"\".format(json_encode(sbaCommand))))\n",
    "#        # Without clear_output(), sbaCommands are stored as notebook outputs\n",
    "#        clear_output() \n",
    "#\n",
    "#sbaInterface = sbaInterface_class(sbaUrl)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Step 2:  In Python, create the command that you wish to send to SBA Composer, and send it. If the SBA Composer window failed to open in step 1, you will see a prompt to open it now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "x3dFileContents = \"\"\"\n",
    "<X3D profile=\"Interchange\" version=\"3.3\" xmlns:xsd=\"http://www.web3d.org/specifications/x3d-3.3.xsd\">\n",
    "  <Scene>\n",
    "    <Shape>\n",
    "      <Appearance><Material diffuseColor=\"#FF0000\"></Material></Appearance>\n",
    "      <Box></Box>\n",
    "    </Shape>\n",
    "  </Scene>\n",
    "</X3D>\n",
    "\"\"\"\n",
    "sbaCommand = {\n",
    "    \"method\": \"Composer.import\",\n",
    "    \"params\": {\n",
    "        \"name\": \"red cube\",\n",
    "        \"mime\": \"model/x3d+xml\",\n",
    "        \"contents\": x3dFileContents\n",
    "    }\n",
    "}\n",
    "\n",
    "# send the command to sbaComposer\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can now repeat step 2 to send more commands to SBA Composer.\n",
    "The command below adds eight markers at the corners of the red cube, with the (1,1,1) corner in yellow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbaCommand = {\n",
    "  \"method\":\"Composer.scatter3d\",\n",
    "  \"params\": {\n",
    "    \"name\": u\"Markers at 8 corners of a cube\",\n",
    "    \"style\": {\n",
    "      \"marker\": {\n",
    "        \"size\": 0.3\n",
    "      }\n",
    "    },\n",
    "    \"markers\":[{\n",
    "      \"coord\":[-1,-1,-1]\n",
    "    },{\n",
    "      \"coord\":[1,-1,-1]\n",
    "    },{\n",
    "      \"coord\":[-1,1,-1]\n",
    "    },{\n",
    "      \"coord\":[1,1,-1]\n",
    "    },{\n",
    "      \"coord\":[-1,-1,1]\n",
    "    },{\n",
    "      \"coord\":[1,-1,1]\n",
    "    },{\n",
    "      \"coord\":[-1,1,1]\n",
    "    },{\n",
    "      \"coord\":[1,1,1],\n",
    "      \"color\":\"#FFFF00\"\n",
    "    }]\n",
    "  }\n",
    "}\n",
    "\n",
    "# send the command to sbaComposer\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sending (binary) file contents to SBA Composer\n",
    "The `Composer.import` command accepts text or binary file content. To transfer binary data to Javascript, bas64-encode it. SBA Composer detects this encoding automatically. The three examples below are all dealing with the same example, a gzipped nifti file. In the first case, the file is downloaded from the web by Python, its contents are sent to SBA Composer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next example assumes that the file is available locally under the name `../sampledata/P56_Annotation_downsample2.nii.gz`. Python reads its contents and passes them on to SBA Composer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"../sampledata/P56_Annotation_downsample2.nii.gz\",\"rb\") as fp:\n",
    "  sbaCommand = {\n",
    "    \"method\":\"Composer.import\",\n",
    "    \"params\": {\n",
    "      \"name\": \"example_read.nii.gz\",\n",
    "      \"encoding\": 'base64',\n",
    "      \"contents\": b64encode(fp.read()).decode('utf-8')\n",
    "    }\n",
    "  }\n",
    "\n",
    "# send the command to sbaComposer\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also send the file to Javascript directly by URL, but keep in mind that this requires that the file is hosted on the same domain as the Jupyter notebook, otherwise you run into <a href=\"https://en.wikipedia.org/wiki/Cross-origin_resource_sharing\">CORS</a> issues."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests, time\n",
    "r = requests.get(sbaHost+'/docs/sampledata/P56_Annotation_downsample2.nii.gz')\n",
    "sbaCommand = {\n",
    "  \"method\":\"Composer.import\",\n",
    "  \"params\": {\n",
    "    \"name\": \"example_requests.nii.gz\",\n",
    "    \"contents\": b64encode(r.content).decode('utf-8')\n",
    "  }\n",
    "}\n",
    "\n",
    "# send the command to sbaComposer\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbaCommand = {\n",
    "  \"method\":\"Composer.import\",\n",
    "  \"params\": {\n",
    "    \"name\": \"example_url.nii.gz\",\n",
    "    \"url\": \"../sampledata/P56_Annotation_downsample2.nii.gz\"\n",
    "  }\n",
    "}\n",
    "\n",
    "# send the command to sbaComposer\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you run into issues, email <a href=\"mailto:feedback@scalablebrainatlas.org\">feedback@scalablebrainatlas.org</a>."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using non-ascii characters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbaCommand = {\n",
    "  \"method\":\"Composer.message\",\n",
    "  \"params\" : {\n",
    "    \"message\":\"Looks like Chinese to me: 你好世界. According to Google translate this means: Hello world.\"\n",
    "  }\n",
    "}\n",
    "\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating a scene with colored brain regions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbaCommand = {\n",
    "  \"method\":\"Composer.scene\",\n",
    "  \"params\" : {\n",
    "    \"name\": \"somatosensory areas\",\n",
    "    \"regions\":{\n",
    "      \"SSp\":[\"#FFFF00\",1],\n",
    "      \"SSs\":[\"#FF0000\",1]\n",
    "    }\n",
    "  }\n",
    "}\n",
    "\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instead of specifying colors directly, they can also be interpolated from a colormap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "sbaCommand = {\n",
    "  \"method\": \"Composer.scene\",\n",
    "  \"params\": {\n",
    "    \"name\": \"somatosensory areas\",\n",
    "    \"regions\": {\n",
    "      \"SSp\":0.1,\n",
    "      \"SSs\":2.7,\n",
    "      \"MOp\":1.5\n",
    "    },\n",
    "    \"colormap\":[[\"000000\",0], [\"000088\",0.1], [\"00FF00\",1.6], [\"FFFFFF\",3.0]]\n",
    "  }\n",
    "}\n",
    "\n",
    "sbaInterface.send(sbaCommand)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
