{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`firefly/ntbks/flask_tutorial.ipynb`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "from IPython.display import IFrame,YouTubeVideo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A recording of this jupyter notebook in action is available at:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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\n",
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"400\"\n",
       "            height=\"300\"\n",
       "            src=\"https://www.youtube.com/embed/OD598z7pqB0\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.YouTubeVideo at 0x11240fb80>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "YouTubeVideo(\"OD598z7pqB0\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import os\n",
    "\n",
    "import requests\n",
    "\n",
    "\n",
    "import sys\n",
    "sys.path.insert(0, '/Users/ageller/VISUALIZATIONS/Firefly')\n",
    "sys.path.insert(0,'/Users/agurvich/research/repos/firefly/src')\n",
    "from firefly.data_reader import ArrayReader\n",
    "from firefly.server import spawnFireflyServer,killAllFireflyServers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial notebook: Sending data to a local Firefly server through Flask\n",
    "A drawback of using `.json` files on disk to pass data between the Python frontend and the Firefly webapp is that these `.json` files can \n",
    "1. take up a lot of unnecessary disk space \n",
    "2. take a long time to read from disk\n",
    "\n",
    "To address these problems, we use Flask to host a webserver and parse data directly from Python at a data upload endpoint. This procedure is detailed in the <a href=\"https://ageller.github.io/Firefly/docs/build/html/server/index.html\">server documentation</a>. From the user's perspective, all they need to do is POST their data to a specific port on their local machine and they will be able to explore their own data without ever having to write a file to disk. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Start the Firefly server as a background process\n",
    "In this tutorial we'll demonstrate how to update the data being shown in a live instance of Firefly running on a local webserver through Flask. Before attempting this tutorial read through the <a href=\"https://ageller.github.io/Firefly/docs/build/html/server/index.html\">server documentation</a> which explains how to specify the listening port and different methods of hosting a Flask Firefly server (here we use the `firefly.server.spawnFireflyServer` function which starts a background process)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Waiting up to 10 seconds for background Firefly server to start...done! Your server is available at - http://localhost:5000\n"
     ]
    }
   ],
   "source": [
    "process = spawnFireflyServer()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Show Firefly in an IFrame\n",
    "IPython allows one to embed webpages into a notebook using an IFrame, we'll take advantage of that to embed Firefly here (you can also visit the localhost:5000 url in your browser if you'd prefer). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"1000\"\n",
       "            height=\"500\"\n",
       "            src=\"http://localhost:5000\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x125609550>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "url = \"http://localhost:5000\"\n",
    "IFrame(url, width=1000, height=500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create some example data and put it into a `firefly.data_reader.Reader` object\n",
    "See the <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/reader.html\">reader documentation</a> or the `reader_tutorial.ipynb` example notebook. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "## let's create some sample data, a grid of points in a 3d cube\n",
    "my_coords = np.linspace(-10,10,20)\n",
    "xs,ys,zs = np.meshgrid(my_coords,my_coords,my_coords)\n",
    "xs,ys,zs = xs.flatten(),ys.flatten(),zs.flatten()\n",
    "coords = np.array([xs,ys,zs]).T\n",
    "\n",
    "## we'll pick some random field values to demonstrate filtering/colormapping\n",
    "fields = np.random.random(size=xs.size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll use an `ArrayReader` here, check out the `reader_tutorial.ipynb` example notebook if this is new for you!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "JSONdir is None, defaulting to /Users/agurvich/research/repos/firefly/src/firefly/static/data/Data\n",
      "Make sure each tracked_array (1) has a tracked_filter_flag (0), assuming True.\n",
      "Make sure each tracked_array (1) has a tracked_colormap_flag (0), assuming True.\n",
      "Outputting: PGroup_0 - 8000/8000 particles - 1 tracked fields\n"
     ]
    }
   ],
   "source": [
    "my_arrayReader = ArrayReader(\n",
    "    coords,\n",
    "    fields=fields,\n",
    "    write_jsons_to_disk=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Send this data to the Flask app\n",
    "The data will be sent to the Firefly server via a POST request, we can do this in python using the `requests` module. One the POST has been made scroll back up to the window above and see the new data (if you don't see new data, it's possible that you've overwritten the default `startup.json` that shipped with Firefly by following some of the other tutorials. That's okay! See the <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/multiple_datasets.html\">multiple datasets documentation</a> or the `multiple_datasets.ipynb` example notebook to learn more about the `startup.json` file. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sending to Firefly 425649\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Response [200]>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## make a POST request to port 5000, supplying the JSON produced by setting \n",
    "##  write_jsons_to_disk=False and calling .dumpToJSON\n",
    "port = 5000\n",
    "print('sending to Firefly', sys.getsizeof(my_arrayReader.JSON))\n",
    "requests.post(f'http://localhost:{port:d}/data_input',json=my_arrayReader.JSON)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We've also wrapped this code in the `.sendDataViaFlask` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Posting...data posted!\n"
     ]
    }
   ],
   "source": [
    "## make a POST request\n",
    "my_arrayReader.sendDataViaFlask()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Killing the Firefly server process when you're done\n",
    "Because the Firefly server was started in the background, the process will persist even when you're done with it. You should make sure to kill it using the `firefly.server.killAllFireflyServers` function. If you supply a process id (which is returned by the `spawnFireflyServer` function) then it will only kill that one process. However, processes are a bit defensive and sometimes we've found they survive the attempt on their life and then hide under a different PID. In which case, it's always safest to just kill all the servers indiscriminately. Generally the two are interchangeable unless you're hosting multiple local servers of Firefly on different ports. This is pretty uncommon/advanced in which case you hopefully know what you're doing. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "killAllFireflyServers()"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "interpreter": {
   "hash": "f534f06944b7101ee418c38585e628955b3f3d62b78942ee260617f70aa005fb"
  },
  "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.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
