{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "import numpy as np\n",
    "import panel as pn\n",
    "\n",
    "hv.extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HoloViews-generated plots generally convey information from Python _to_ a viewer of the plot, but there are also circumstances where information needs to be collected _from_ the viewer and made available for processing in Python:\n",
    "\n",
    "* annotating data with contextual information to aid later interpretation\n",
    "* labeling or tagging data for automated machine-learning or other processing pipelines\n",
    "* indicating regions of interest, outliers, or other semantic information\n",
    "* specifying inputs to a query, command, or simulation\n",
    "* testing sensitivity of analyses to adding, changing, or deleting user-selected data points\n",
    "\n",
    "In such cases, it is important to be able to augment, edit, and annotate datasets and to access those values from Python. To perform these actions, HoloViews provides an ``annotate`` helper using [Bokeh's drawing tools](https://docs.bokeh.org/en/latest/docs/reference/models/tools.html#bokeh.models.tools.PointDrawTool) to make it easy to edit HoloViews Elements and add additional information using an associated table. The `annotate` helper:\n",
    "\n",
    "* Adds plot tools that allow editing and adding new elements to a plot  \n",
    "* Adds table(s) to allow editing the element in a tabular format\n",
    "* Returns a layout of these two components\n",
    "* Makes the edits, annotations, and selections available on a property of the annotate object so that they can be utilized in Python"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basics\n",
    "\n",
    "Let us start by annotating a small set of Points. To do this, we need two things:\n",
    "\n",
    "1. A Points Element to annotate or edit\n",
    "2. An annotator object to collect and store annotations\n",
    "\n",
    "The annotator is a callable object with its own state that can be called to return a Layout consisting of the object to be annotated and an Overlay of editable table(s):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = hv.Points([(0.0, 0.0), (1.0, 1.0), (200000.0, 2000000.0)]).opts(size=10, min_height=500)\n",
    "\n",
    "annotator = hv.annotate.instance()\n",
    "layout = annotator(hv.element.tiles.OSM() * points, annotations=['Label'], name=\"Point Annotations\")\n",
    "\n",
    "print(layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This layout of a DynamicMap (the user-editable Element data) and an Overlay (the user-editable table) lets a user input the required information:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "layout"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we have pre-populated the Element with three points. Each of the points has three bits of information that can be edited using the table: the x location, y location, and a \"Label\",  which was initialized to dummy values when we called `annotate` and asked that there be a `Label` column.  Try clicking on one of the rows and editing the location or the label to anything you like. As long as Python is running and the new location is in the viewport, you should see the dot move when you edit the location, and any labels you entered should be visible in the table.\n",
    "\n",
    "You can also edit the locations graphically using the [PointDraw tool](../reference/streams/bokeh/PointDraw.ipynb) in the toolbar:<img src=\"https://bokeh.pydata.org/en/latest/_images/PointDraw.png\">\n",
    "\n",
    "Once you select that tool, you should be able to click and drag any of the existing points and see the location update in the table. Whether you click on the table or the points, the same object should be selected in each, so that you can see how the graphical and tabular representations relate.\n",
    "\n",
    "The PointDraw tool also allows us to add completely new points; once the tool is selected, just click on the plot above in locations not already containing a point and you can see a new point and a new table row appear ready for editing. You can also delete points by selecting them in the plot then pressing Backspace or Delete (depending on operating system)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the above editing and interaction could have been done if we had simply called `hv.annotate(points, annotations=['Label'])` directly, but instead we first saved an \"instance\" of the annotator object so that we'd also be able to access the resulting data. So, once we are done collecting data from the user, let's use the saved `annotator` object handle to read out the values (by re-evaluating the following line):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "annotator.annotated.dframe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see that you can access the current set of user-provided or user-modified points and their user-provided labels from within Python, ready for any subsequent processing you need to do. \n",
    "\n",
    "We can also access the currently `selected` points, in case we care only about a subset of the points (which will be empty if no points/rows are selected):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "annotator.selected.dframe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configuring the Annotator\n",
    "\n",
    "In addition to managing the list of `annotations`, the `annotate` helper exposes a few additional parameters. Remember like most Param-based objects you can get help about `annotate` parameters using the `hv.help` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.help(hv.annotate)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Annotation types\n",
    "\n",
    "The default annotation type is a string, to allow you to put in arbitrary information that you later process in Python. If you want to enforce a more specific type, you can specify the annotation-value types explicitly using a dictionary mapping from column name to the type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.annotate(points, annotations={'int': int, 'float': float, 'str': str})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This example also shows how to collect multiple columns of information for the same data point.\n",
    "\n",
    "## Types of Annotators\n",
    "\n",
    "Currently only a limited set of Elements may be annotated, which include:\n",
    "\n",
    "* ``Points``/``Scatter``\n",
    "* ``Curve``\n",
    "* ``Path``\n",
    "* ``Polygons``\n",
    "* ``Rectangles``\n",
    "\n",
    "Adding support for new elements, in most cases, requires adding corresponding drawing/edit tools to Bokeh itself. But if you have data of other types, you may still be able to annotate it by casting it to one of the indicated types, collecting the data, then casting it back."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Annotating Curves\n",
    "\n",
    "To allow dragging the vertices of the Curve, the ``Curve`` annotator uses the PointDraw tool in the toolbar: <img src=\"https://bokeh.pydata.org/en/latest/_images/PointDraw.png\">\n",
    "The vertices will appear when the tool is selected or a vertex is selected in the table. Unlike most other annotators the Curve annotator only allows editing the vertices and does not allow adding new ones."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "curve = hv.Curve(np.random.randn(50).cumsum())\n",
    "\n",
    "curve_annotator = hv.annotate.instance()\n",
    "\n",
    "curve_annotator(curve.opts(width=800, height=400, responsive=False), annotations={'Label': str})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To access the data you can make use of the ``annotated`` property on the annotator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "curve_annotator.annotated.dframe().head(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Annotating Rectangles\n",
    "\n",
    "The `Rectangles` annotator behaves very similarly to the Points annotator. It allows adding any number of annotation columns, using Bokeh's `BoxEdit` tool that allows both drawing and moving boxes. To see how to use the BoxEdit tool, refer to the HoloViews [BoxEdit stream reference](../reference/streams/bokeh/BoxEdit.ipynb), but briefly:\n",
    "\n",
    "* Select the `BoxEdit` tool in the toolbar: <img src=\"https://bokeh.pydata.org/en/latest/_images/BoxEdit.png\">\n",
    "* Click and drag on an existing Rectangle to move it\n",
    "* Double click to start drawing a new Rectangle at one corner, and double click to complete the rectangle at the opposite corner\n",
    "* Select a rectangle and press the Backspace or Delete key (depending on OS) to delete it\n",
    "* Edit the box coordinates in the table to resize it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "boxes = hv.Rectangles([(0, 0, 1, 1), (1.5, 1.5, 2.5, 2.5)])\n",
    "\n",
    "box_annotator = hv.annotate.instance()\n",
    "\n",
    "box_annotator(boxes.opts(width=800, height=400, responsive=False), annotations=['Label'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To access the data we can make use of the ``annotated`` property on the annotator instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "box_annotator.annotated.dframe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Annotating paths/polygons\n",
    "\n",
    "Unlike the Points and Boxes annotators, the Path and Polygon annotators allow annotating not just each individual entity but also the vertices that make up the paths and polygons. For more information about using the editing tools associated with this annotator refer to the HoloViews [PolyDraw](../reference/streams/PolyDraw.ipynb) and [PolyEdit](../reference/streams/PolyEdit.ipynb) stream reference guides, but briefly:\n",
    "\n",
    "##### Drawing/Selecting Deleting Paths/Polygons\n",
    "\n",
    "- Select the PolyDraw tool in the toolbar: <img src=\"https://bokeh.pydata.org/en/latest/_images/PolyDraw.png\">\n",
    "- Double click to start a new object, single click to add each vertex, and double-click to complete it.\n",
    "- Delete paths/polygons by selecting and pressing Delete key (OSX) or Backspace key (PC)\n",
    "\n",
    "##### Editing Paths/Polygons\n",
    "\n",
    "- Select the PolyEdit tool in the toolbar: <img src=\"https://bokeh.pydata.org/en/latest/_images/PolyEdit.png\">\n",
    "- Double click a Path/Polygon to start editing\n",
    "- Drag vertices to edit them, delete vertices by selecting them\n",
    "\n",
    "To edit and annotate the vertices, use the draw tool or the first table to select a particular path/polygon and then navigate to the Vertices tab."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "path = hv.Path([hv.Box(0, 0, 1), hv.Ellipse(1, 1, 1)])\n",
    "\n",
    "path_annotator = hv.annotate.instance()\n",
    "\n",
    "path_annotator(path.opts(width=800, height=400, responsive=False), annotations=['Label'], vertex_annotations=['Value'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To access the data we can make use of the iloc method on `Path` objects to access a particular path, and then access the `.data` or convert it to a dataframe:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "path_annotator.annotated.iloc[0].dframe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Composing Annotators\n",
    "\n",
    "Often we will want to add some annotations on top of one or more other elements which provide context, e.g. when annotating an image with a set of `Points`. As long as only one annotation layer is required you can pass an overlay of multiple elements to the `annotate` operation and it will automatically associate the annotator with the layer that supports annotation. Note however that this will error if multiple elements that support annotation are provided. Below we will annotate a two-photon microscopy image with a set of Points, e.g. to mark the location of each cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img = hv.Image(np.load('../assets/twophoton.npz')['Calcium'][..., 0])\n",
    "cells = hv.Points([]).opts(width=500, height=500, responsive=False, padding=0)\n",
    "\n",
    "hv.annotate(img * cells, annotations=['Label'], name=\"Cell Annotator\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Multiple Annotators\n",
    "\n",
    "If you want to work with multiple annotators in the same plot, you can recompose and rearrange the components returned by each `annotate` helper manually, but doing so can get confusing. To simplify working with multiple annotators at the same time, the `annotate` helper provides a special classmethod that allows composing multiple annotators and other elements, e.g. making a set of tiles into a combined layout consisting of all the components:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "point_annotate = hv.annotate.instance()\n",
    "points = hv.Points([(500000, 500000), (1000000, 1000000)]).opts(size=10, color='red', line_color='black')\n",
    "point_layout = point_annotate(points, annotations=['Label'])\n",
    "\n",
    "poly_annotate = hv.annotate.instance()\n",
    "poly_layout = poly_annotate(hv.Polygons([]), annotations=['Label'])\n",
    "\n",
    "hv.annotate.compose(hv.element.tiles.OSM(), point_layout, poly_layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Internals\n",
    "\n",
    "The `annotate` function builds on [Param](https://param.holoviz.org) and [Panel](https://panel.holoviz.org), creating and wrapping Panel `Annotator` panes internally. These objects make it easy to include the annotator in Param-based workflows and trigger actions when parameters change and/or update the annotator in response to external events. The Annotator of a `annotate` instance can be accessed using the `annotator` attribute:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(point_annotate.annotator)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This object can be included directly in a Panel layout, be used to watch for parameter changes, or updated directly. To see the effect of updating directly, uncomment the line below, execute that cell, and then look at the previous plot of Africa, which should get updated with 10 randomly located blue dots."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#point_annotate.annotator.object = hv.Points(np.random.randn(10, 2)*1000000).opts(color='blue')"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
