{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[<img src=\"http://holoviews.org/_static/logo.png\" style=\"height:50px;display:inline\"></img>](https://holoviews.holoviz.org)[<img src=\"https://panel.holoviz.org/_static/logo_stacked.png\" style=\"height:50px;display:inline\"></img>](https://panel.holoviz.org)\n",
    "\n",
    "# Timeseries Image Analysis with HoloViews and Panel \n",
    "\n",
    "The purpose of this notebook is to illustrate how you can **make an interactive and responsive tool for timeseries analysis of images** by combining a Panel `IntSlider`, some `@pn.depends` annotated plotting functions and a few HoloViews `DynamicMap`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dependencies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import holoviews as hv\n",
    "import hvplot.pandas\n",
    "import panel as pn\n",
    "\n",
    "hv.extension('bokeh')\n",
    "pn.extension(sizing_mode=\"stretch_width\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_ts_data(n_timesteps):\n",
    "    data = pd.DataFrame(\n",
    "        {\n",
    "            \"a\": np.random.normal(size=(n_timesteps,)),\n",
    "            \"b\": np.random.normal(size=(n_timesteps,)),\n",
    "            \"c\": np.random.normal(size=(n_timesteps,)),\n",
    "        },\n",
    "        index=pd.Index(np.arange(n_timesteps), name=\"time\", )\n",
    "    )\n",
    "    return data\n",
    "\n",
    "ts_data = make_ts_data(1000)\n",
    "ts_data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "HEIGHT=300"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_a = ts_data.hvplot(y=\"a\", responsive=True, height=HEIGHT)\n",
    "plot_b = ts_data.hvplot(y=\"b\", responsive=True, height=HEIGHT)\n",
    "plot_c = ts_data.hvplot(y=\"c\", responsive=True, height=HEIGHT)\n",
    "\n",
    "plot_a + plot_b + plot_c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_image(frame):\n",
    "    return hv.Image(np.random.normal(size=(100, 100))).opts(height=HEIGHT, responsive=True)\n",
    "\n",
    "get_image(100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_vline(frame):\n",
    "    return hv.VLine(frame).opts(color=\"red\")\n",
    "\n",
    "get_vline(0.5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## App\n",
    "\n",
    "### Bar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app_bar = pn.Row(\n",
    "    pn.pane.Markdown(\"## TimeSeries Image Analysis - POC\", style={\"color\": \"white\"}, width=500, sizing_mode=\"fixed\", margin=(10,5,10,15)), \n",
    "    pn.Spacer(),\n",
    "    pn.pane.PNG(\"http://holoviews.org/_static/logo.png\", height=50, sizing_mode=\"fixed\", align=\"center\"),\n",
    "    pn.pane.PNG(\"https://panel.holoviz.org/_static/logo_horizontal.png\", height=50, sizing_mode=\"fixed\", align=\"center\"),\n",
    "    background=\"black\",\n",
    ")\n",
    "app_bar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dynamic Plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "frame_slider = pn.widgets.IntSlider(name=\"Time\", value=25, start=0, end=999)\n",
    "\n",
    "@pn.depends(frame=frame_slider)\n",
    "def image(frame):\n",
    "    return get_image(frame)\n",
    "\n",
    "@pn.depends(frame=frame_slider)\n",
    "def vline(frame):\n",
    "    return get_vline(frame)\n",
    "\n",
    "vline_dmap = hv.DynamicMap(vline)\n",
    "img_dmap = hv.DynamicMap(image)\n",
    "\n",
    "plots = ((plot_a + plot_b + plot_c) * vline_dmap).cols(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Layout"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "app = pn.Column(\n",
    "    app_bar,\n",
    "    pn.Spacer(height=10),\n",
    "    frame_slider,\n",
    "    pn.Row(\n",
    "        plots,\n",
    "        pn.Column(\n",
    "            pn.Spacer(height=20),\n",
    "            img_dmap,\n",
    "        ),\n",
    "    ),\n",
    ")\n",
    "app"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You are now ready to serve the app to your users via `panel serve dynamic_timeseries_image_analysis.ipynb`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Notes\n",
    "\n",
    "The `plots = ((plot_a + plot_b + plot_c) * vline_dmap).cols(1)` line is essential for making the app fast and responsive. Initially the `plot_a + plot_b + plot_c` where regenerated together with the `vline` everytime the `frame_slider.value` was changed. That made the app slower because it had to recalculate and retransfer much more data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## App\n",
    "\n",
    "Lets wrap it into nice template that can be served via `panel serve dynamic_timeseries_image_analysis.ipynb`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.template.FastListTemplate(\n",
    "    site=\"Panel\", title=\"Dynamic Timeseries Image Analysis\", \n",
    "    main=[\n",
    "        \"The purpose of this app is to illustrate how you can **make an interactive and responsive tool for timeseries analysis of images** by combining a Panel `IntSlider`, some `@pn.depends` annotated plotting functions and a few HoloViews `DynamicMap`.\",\n",
    "        *app[2:]]\n",
    ").servable();"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
