{
 "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": "[encoded data, 23300 characters]\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",
       "            \n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.YouTubeVideo at 0x1042d0430>"
      ]
     },
     "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",
    "## ignore these lines, you do not need to add this if Firefly is pip installed into your PYTHONPATH\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,quitAllFireflyServers"
   ]
  },
  {
   "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://alexbgurvi.ch/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://alexbgurvi.ch/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:5500\n"
     ]
    }
   ],
   "source": [
    "process = spawnFireflyServer(5500)"
   ]
  },
  {
   "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:5500 url in your browser if you'd prefer). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <iframe\n",
       "            width=\"1000\"\n",
       "            height=\"500\"\n",
       "            src=\"http://localhost:5500\"\n",
       "            frameborder=\"0\"\n",
       "            allowfullscreen\n",
       "            \n",
       "        ></iframe>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.lib.display.IFrame at 0x1042b0be0>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "url = \"http://localhost:5500\"\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://alexbgurvi.ch/Firefly/docs/build/html/data_reader/reader.html\">reader documentation</a> or the `reader_tutorial.ipynb` example notebook. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "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": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "datadir is None, defaulting to /Users/agurvich/research/repos/firefly/src/firefly/static/data/Data\n",
      "Make sure each field_array (1) has a field_filter_flag (0), assuming True.\n",
      "Make sure each field_array (1) has a field_colormap_flag (0), assuming True.\n",
      "Make sure each field_array (1) has a field_radius_flag (0), assuming False.\n",
      "PGroup_0 - 8000/8000 particles - 1 tracked fields\n"
     ]
    }
   ],
   "source": [
    "my_arrayReader = ArrayReader(\n",
    "    coords,\n",
    "    fields=fields,\n",
    "    write_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://alexbgurvi.ch/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": "markdown",
   "metadata": {},
   "source": [
    "We've also wrapped this code in the `.sendDataViaFlask` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PGroup_0 - 8000/8000 particles - 1 tracked fields\n",
      "Posting data on port 5500...data posted!\n"
     ]
    }
   ],
   "source": [
    "## make a POST request\n",
    "my_arrayReader.sendDataViaFlask(5500)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quitting 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 quit it using the `firefly.server.quitAllFireflyServers` function. If you supply a process id (which is returned by the `spawnFireflyServer` function) then it will only quit 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 quit 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. \n",
    "\n",
    "The `firefly.server.quitAllFireflyServers` function will print all of the output the Firefly server "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Server output:\n",
      "--------------\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "kill: 90863: No such process\n"
     ]
    }
   ],
   "source": [
    "return_code = quitAllFireflyServers()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Popen: returncode: None args: ['/opt/homebrew/Caskroom/miniconda/base/envs/...>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "process"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "interpreter": {
   "hash": "f534f06944b7101ee418c38585e628955b3f3d62b78942ee260617f70aa005fb"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
