{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "585af7dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import lumicks.pylake as lk\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89f509f4",
   "metadata": {},
   "source": [
    "# Correlated stacks\n",
    "\n",
    "[Download this page as a Jupyter notebook](_downloads/1f5d95ae47af02cbce695254f17afcb7/correlatedstacks.ipynb)\n",
    "\n",
    "Bluelake has the ability to export videos from the camera’s. These videos can be opened and sliced using [`CorrelatedStack`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfd1d5bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack = lk.CorrelatedStack(\"wf.tiff\")  # Loading a stack.\n",
    "stack_slice = stack[2:10]  # Grab frame 2 to 9"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7145a48f",
   "metadata": {},
   "source": [
    "You can also slice by time:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c35c6c83",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack_slice = stack[\"2s\":\"10s\"]  # Slice from 2 to 10 seconds from beginning of the stack"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3dbb4f02",
   "metadata": {},
   "source": [
    "You can easily load multiple TIFF files by simply listing them consecutively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee3ee1c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack = lk.CorrelatedStack(\"wf.tiff\", \"wf2.tiff\")  # Loading two tiff files in a single stack."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3493fcb1",
   "metadata": {},
   "source": [
    "You can quickly plot an individual frame using the [`plot()`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack.plot) method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f04d31e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack.plot(frame=0, channel=\"rgb\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e7f10c3",
   "metadata": {},
   "source": [
    "Sometimes a few bright pixels can dominate the image. When this is the case, it may be beneficial to manually set the color limits for each of the channels. This can be accomplished by providing a [`ColorAdjustment`](tutorial/../_api/lumicks.pylake.ColorAdjustment.html#lumicks.pylake.ColorAdjustment) to plotting or export functions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d94d726a",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack.plot(channel=\"red\", adjustment=lk.ColorAdjustment([50, 50, 50], [100, 250, 196]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e67e04b",
   "metadata": {},
   "source": [
    "By default the limits should be provided in absolute values, although percentiles can be used instead for convenience:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1d999f05",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack.plot(channel=\"red\", adjustment=lk.ColorAdjustment([5, 5, 5], [95, 95, 95], mode=\"percentile\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a789648b",
   "metadata": {},
   "source": [
    "Gamma adjustments can be applied in addition to the bounds by supplying an extra argument named `gamma`. For example, a gamma adjustment of `2` to the red channel can be applied as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "123c48f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack.plot(channel=\"red\", adjustment=lk.ColorAdjustment([5, 5, 5], [95, 95, 95], mode=\"percentile\", gamma=[2, 1, 1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "136c8a97",
   "metadata": {},
   "source": [
    "To define the location of the tether between beads, supply the `(x, y)` pixel coordinates of the end points to the [`define_tether()`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack.define_tether) method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c96b13e",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack = stack.define_tether((126, 193), (341, 200))\n",
    "stack.plot()\n",
    "stack.plot_tether(lw=0.7)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c322c2e",
   "metadata": {},
   "source": [
    "Note, after defining a tether location the image is rotated such that the tether is horizontal in the field of view. You can also plot the overlay of the tether location using [`plot_tether(**kwargs)`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack.plot_tether), which also accepts keyword arguments that are passed to [`plt.plot()`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot).\n",
    "\n",
    "You can also spatially crop to select a smaller region of interest:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b5eb76b",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack_roi = stack.crop_by_pixels(45, 420, 150, 245)  # pixel coordinates as x_min, x_max, y_min, y_max\n",
    "stack_roi.plot()  # note: the default channel is \"rgb\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5831e7b2",
   "metadata": {},
   "source": [
    "Alternatively, you can crop directly by slicing the stack:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1de36ee6",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack_roi = stack[:, 150:245, 45:420]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8161d56",
   "metadata": {},
   "source": [
    "Here the first index can be used to select a subset of frames and the second and third indices perform a cropping operation. Note how the axes are switched when compared to [`crop_by_pixels()`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack.crop_by_pixels) to follow the numpy convention (rows and then columns).\n",
    "\n",
    "Cropping can be useful, for instance, after applying color alignment to RGB images as the edges can become corrupted due to interpolation artifacts.\n",
    "\n",
    "You can also plot only a single color channel. Note that here we pass some additional formatting arguments, which are forwarded to [`plt.imshow()`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html#matplotlib.pyplot.imshow):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "de775b4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack_roi.plot(channel=\"red\", cmap=\"magma\", adjustment=lk.ColorAdjustment(550, 800))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3570f4c0",
   "metadata": {},
   "source": [
    "Full color RGB images are automatically reconstructed using the alignment matrices from Bluelake if available. This functionality can be turned off with the optional `align` keyword. Note that the align parameter has to be provided as a keyworded argument (i.e. `align=False`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55d9d7f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack2 = lk.CorrelatedStack(\"wf.tiff\", align=False)\n",
    "stack2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "496e3e2f",
   "metadata": {},
   "source": [
    "You can obtain the image stack data as a [`numpy`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray) array using the [`get_image()`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack.get_image) method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfd1d03d",
   "metadata": {},
   "outputs": [],
   "source": [
    "red_data = stack.get_image(channel=\"red\") # shape = [n_frames, y_pixels, x_pixels]\n",
    "rgb_data = stack.get_image(channel=\"rgb\") # shape = [n_frames, y_pixels, x_pixels, 3 channels]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5d65ba5",
   "metadata": {},
   "source": [
    "If the `channel` argument is not provided, the default behavior is `\"rgb\"` for 3-color images. For single-color images, this argument is ignored as there is only one channel available.\n",
    "\n",
    "Finally, the aligned image stack can also be exported to TIFF format:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4a715ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "stack.export_tiff(\"aligned_stack.tiff\")\n",
    "stack[5:20].export_tiff(\"aligned_short_stack.tiff\") # export a slice of the CorrelatedStack"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a0902fa",
   "metadata": {},
   "source": [
    "## Correlating force with the image stack\n",
    "\n",
    "Quite often, it is interesting to correlate events on the camera’s to `channel` data. To quickly explore the correlation between images in a [`CorrelatedStack`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack) and channel data you can use the following function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8960a2f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Making a plot where force is correlated to images in the stack.\n",
    "stack = lk.CorrelatedStack(\"example.tiff\")\n",
    "stack.plot_correlated(file.force1x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5817c9c8",
   "metadata": {},
   "source": [
    "If the plot is interactive (for example, when `%matplotlib notebook` is used in a Jupyter notebook), you can click on the left graph to select a particular force. The corresponding video frame will then automatically appear on the right.\n",
    "\n",
    "In some cases, additional processing may be needed, and we wish to have the data downsampled over the video frames. This can be done using the function `Slice.downsampled_over` using timestamps obtained from the [`CorrelatedStack`](tutorial/../_api/lumicks.pylake.correlated_stack.CorrelatedStack.html#lumicks.pylake.correlated_stack.CorrelatedStack):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44a50e8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Determine the force trace averaged over frame 2...9.\n",
    "file.force1x.downsampled_over(stack[2:10].frame_timestamp_ranges())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f7da6ed",
   "metadata": {},
   "source": [
    "By default, this averages only over the exposure time of the images in the stack. If you wish to average over the full time range from the start of the scan to the next scan, pass the extra parameter `include_dead_time=True`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a26af5b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "file.force1x.downsampled_over(stack[2:10].frame_timestamp_ranges(include_dead_time=True))"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 5
}