{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Focal Statistics\n", "\n", "In this tutorial we calculate focal statistics and determine hot and cold spots of NDVI (Normalized difference vegetation index) over a time series of satellite images. On its own, NDVI is used to highlight live green vegetation. Its hot and cold spots help determine the growth or loss of plants. In this notebook, we'll see how to:\n", "\n", "- Search for satellite data by item ID using `pystac_client`\n", "- Visualize true color images\n", "- Calculate NDVI\n", "- Smooth images with a mean filter\n", "- Calculate focal statistics of the values within a specified focal neighborhood for each pixel in an input data array\n", "- Identify hot and cold spots in a image, neighborhoods that are significantly different from the rest of the image\n", "\n", "The focus of this notebook is to analyse information for each pixel based on its focal neighborhood kernel. The [xrspatial.focal](https://xarray-spatial.readthedocs.io/en/latest/user_guide/focal.html) module from `xarray-spatial` provides a set of analysis tools performing neighborhood operations that will be used through this tutorial." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import xarray as xr\n", "\n", "import stackstac\n", "import planetary_computer\n", "import pystac_client\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "import xrspatial.multispectral as ms\n", "from xrspatial.convolution import calc_cellsize, circle_kernel, convolution_2d\n", "from xrspatial.focal import mean, focal_stats, hotspots\n", "\n", "from dask.distributed import Client, progress" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Local Dask Cluster\n", "\n", "We'll use a small number of images for this example. We'll parallelize reading the data from Azure Blob Storage using a local Dask \"cluster\" on this single machine." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [] }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2022-08-16 16:30:24,531 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-worker-space/worker-l6lzssem', purging\n", "2022-08-16 16:30:24,532 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-worker-space/worker-h374kdv4', purging\n", "2022-08-16 16:30:24,532 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-worker-space/worker-isifihn_', purging\n", "2022-08-16 16:30:24,532 - distributed.diskutils - INFO - Found stale lock file and directory '/tmp/dask-worker-space/worker-swbsk4a4', purging\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "/proxy/8787/status\n" ] } ], "source": [ "client = Client()\n", "print(f\"/proxy/{client.scheduler_info()['services']['dashboard']}/status\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can access the Dask Dashboard by pasting that URL into the Dask labextension field. See [Scale with Dask](../quickstarts/scale-with-dask.ipynb) for more." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The region of interest is a small area in the Amazon rainforest located in State of Mato Grosso and State of Amazonas, Brazil. In order to calculate NDVI accurately, we found these least cloudy scenes by searching with the [STAC API](https://planetarycomputer.microsoft.com/docs/quickstarts/reading-stac/) and filtering the results." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "catalog = pystac_client.Client.open(\n", " \"https://planetarycomputer.microsoft.com/api/stac/v1\",\n", " modifier=planetary_computer.sign_inplace,\n", ")\n", "\n", "ids = [\n", " \"S2A_MSIL2A_20200616T141741_R010_T20LQR_20200822T232052\",\n", " \"S2B_MSIL2A_20190617T142049_R010_T20LQR_20201006T032921\",\n", " \"S2B_MSIL2A_20180712T142039_R010_T20LQR_20201011T150557\",\n", " \"S2B_MSIL2A_20170727T142039_R010_T20LQR_20210210T153028\",\n", " \"S2A_MSIL2A_20160627T142042_R010_T20LQR_20210211T234456\",\n", "]\n", "search = catalog.search(collections=[\"sentinel-2-l2a\"], ids=ids)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we'll sign the STAC items so we can download the data from blob storage. See [Using Tokens for Data Access](../concepts/sas.ipynb) for more. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.DataArray 'stackstac-6aab09147bacb75bf1d5e6bdb3733648' (time: 5,\n",
       "                                                                band: 4,\n",
       "                                                                y: 227, x: 226)>\n",
       "dask.array<where, shape=(5, 4, 227, 226), dtype=float64, chunksize=(1, 1, 227, 226), chunktype=numpy.ndarray>\n",
       "Coordinates: (12/46)\n",
       "  * time                                     (time) datetime64[ns] 2016-06-28...\n",
       "    id                                       (time) <U54 'S2A_MSIL2A_20160627...\n",
       "  * band                                     (band) <U5 'blue' 'green' ... 'nir'\n",
       "  * x                                        (x) float64 1.362e+06 ... 1.474e+06\n",
       "  * y                                        (y) float64 -9.075e+05 ... -1.02...\n",
       "    s2:granule_id                            (time) <U62 'S2A_OPER_MSI_L2A_TL...\n",
       "    ...                                       ...\n",
       "    title                                    (band) <U20 'Band 2 - Blue - 10m...\n",
       "    proj:transform                           object {0.0, 9100000.0, 10.0, -1...\n",
       "    common_name                              (band) <U5 'blue' 'green' ... 'nir'\n",
       "    center_wavelength                        (band) float64 0.49 0.56 ... 0.842\n",
       "    full_width_half_max                      (band) float64 0.098 ... 0.145\n",
       "    epsg                                     int64 32619\n",
       "Attributes:\n",
       "    spec:        RasterSpec(epsg=32619, bounds=(1361500, -1021000, 1474500, -...\n",
       "    crs:         epsg:32619\n",
       "    transform:   | 500.00, 0.00, 1361500.00|\\n| 0.00,-500.00,-907500.00|\\n| 0...\n",
       "    resolution:  500
" ], "text/plain": [ "\n", "dask.array\n", "Coordinates: (12/46)\n", " * time (time) datetime64[ns] 2016-06-28...\n", " id (time) 0, other=np.nan) # sentinel-2 uses 0 as nodata\n", " .assign_coords(\n", " band=lambda x: x.common_name.rename(\"band\"), # use common names\n", " time=lambda x: x.time.dt.round(\n", " \"D\"\n", " ), # round time to daily for nicer plot labels\n", " )\n", ")\n", "data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets load up the data. This should take about 30 seconds." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7703a122c35249828ba4ebd166d855b3", "version_major": 2, "version_minor": 0 }, "text/plain": [ "VBox()" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "data = data.persist()\n", "progress(data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### True color\n", "\n", "Let's see how the data actually looks by visualizing them with `true_color` function from [xrspatial.multispectral](https://xarray-spatial.readthedocs.io/en/latest/reference/multispectral.html). To hide the label for x and y axes from sub-plots, we can update `rcParams` for `matplotlib.pyplot` as below:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "rc = {\n", " \"axes.spines.left\": False,\n", " \"axes.spines.right\": False,\n", " \"axes.spines.bottom\": False,\n", " \"axes.spines.top\": False,\n", " \"xtick.bottom\": False,\n", " \"xtick.labelbottom\": False,\n", " \"ytick.labelleft\": False,\n", " \"ytick.left\": False,\n", "}\n", "\n", "plt.rcParams.update(rc)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "true_color_aggs = [\n", " ms.true_color(x.sel(band=\"red\"), x.sel(band=\"green\"), x.sel(band=\"blue\"))\n", " for x in data\n", "]\n", "\n", "true_color = xr.concat(true_color_aggs, dim=data.coords[\"time\"])\n", "\n", "# visualize\n", "t = true_color.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "true_color_aggs = [\n", " ms.true_color(x.sel(band=\"red\"), x.sel(band=\"green\"), x.sel(band=\"blue\"))\n", " for x in data\n", "]\n", "\n", "true_color = xr.concat(true_color_aggs, dim=data.coords[\"time\"])\n", "\n", "# visualize\n", "t = true_color.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### NDVI\n", "\n", "NDVI can be calculated with [xarray-spatial](https://xarray-spatial.readthedocs.io/en/latest/reference/_autosummary/xrspatial.multispectral.ndvi.html). We'll compute the NDVI for each year's image." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ndvi_aggs = [ms.ndvi(x.sel(band=\"nir\"), x.sel(band=\"red\")) for x in data]\n", "\n", "ndvi = xr.concat(ndvi_aggs, dim=\"time\")\n", "\n", "ndvi.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5, cmap=\"viridis\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Smoothing Images with Focal Mean\n", "\n", "[`focal.mean`](https://xarray-spatial.readthedocs.io/en/latest/reference/_autosummary/xrspatial.focal.mean.html) can be used to smooth or reduce noises in an image by a mean reduction to each 3x3 window in an image." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "mean_aggs = [mean(ndvi_agg) for ndvi_agg in ndvi_aggs]\n", "\n", "smooth = xr.concat(mean_aggs, dim=\"time\")\n", "\n", "s = smooth.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5, cmap=\"viridis\")" ] }, { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "### Focal statistics\n", "\n", "In this step, we calculate [focal statistics](https://xarray-spatial.readthedocs.io/en/latest/user_guide/focal.html#) using a circular kernel for the NDVI data arrays computed above. By default, seven statistics are computed:\n", "\n", "1. Mean\n", "2. Max\n", "3. Min\n", "4. Range\n", "5. Std\n", "6. Var\n", "7. Sum\n", "\n", "The result of `focal_stats` is a 3D `DataArray` with the newly added `stats` dimension.\n", "The size of the kernel will affect the result of `focal_stats`. We'll use `cacl_cellsize` to determine a good kernel size. In this example, each pixel represents a region of `500m x 500m`, let's consider a circular kernel with a radius of 1.5km, equivalent to 3 times of the input cellsize." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(500.0, 500.0)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cellsize = calc_cellsize(ndvi)\n", "cellsize" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(7, 7)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "kernel = circle_kernel(*cellsize, radius=3 * cellsize[0])\n", "kernel.shape" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 379 ms, sys: 19.3 ms, total: 398 ms\n", "Wall time: 385 ms\n" ] } ], "source": [ "%%time\n", "stats_aggs = [focal_stats(ndvi_agg, kernel) for ndvi_agg in ndvi_aggs]\n", "\n", "stats = xr.concat(stats_aggs, dim=\"time\")" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# transpose the data array so `stats` dimension appears first\n", "stats_t = stats.transpose(\"stats\", \"time\", \"y\", \"x\")\n", "\n", "for stats_img in stats_t:\n", " g = stats_img.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5, cmap=\"viridis\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Custom statistics\n", "\n", "Sometimes you may want to a different statistic than the options provided in `focal_stats`, or you want to apply a custom kernel to your images. That's when you can think about using `convolution.convolution_2d` to calculate, for all inner cells of an array, the 2D convolution of each cell. Convolution is frequently used for image processing, such as smoothing, sharpening, and edge detection of images by eliminating spurious data or enhancing features in the data.\n", "\n", "As an example, let's experiment a horizontal Sobel kernel." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 1, 0, -1],\n", " [ 2, 0, -2],\n", " [ 1, 0, -1]])" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Use Sobel operator\n", "sobel_kernel = np.array([[1, 0, -1], [2, 0, -2], [1, 0, -1]])\n", "\n", "sobel_kernel" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sobel_aggs = [convolution_2d(ndvi_agg, sobel_kernel) for ndvi_agg in ndvi_aggs]\n", "\n", "sobel_agg = xr.concat(sobel_aggs, dim=\"time\")\n", "\n", "sobel_agg.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5, cmap=\"viridis\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hotspots\n", " \n", "We can use the `hotspots` function to identify hot and cold spots in a DataArray. A statistically significant hot spot has a high value and is surrounded by other high values. A cold spot has a low value and is surrounded by other low values features.\n", "\n", "Similar to other focal tools, to identify hot and cold spots we need to provide a kernel as a NumPy ndarary, along with the input data array to be analysed. The output array will have one of 7 possible values for each pixel of the input array:\n", "\n", "- -99 for 99% confidence low value cluster (cold spot)\n", "- -95 for 95% confidence low value cluster (cold spot)\n", "- -90 for 90% confidence low value cluster (cold spot)\n", "- 0 for no significance\n", "- +90 for 90% confidence high value cluster (hot spot)\n", "- +95 for 95% confidence high value cluster (hot spot)\n", "- +99 for 99% confidence high value cluster (hot spot)\n", "\n", "Hotspots are identified using z-scores of all cells in a input raster, which requires a global mean to be defined. Currently, `focal.hotspots` does not perform well in term of finding nan-mean of a Dask-backed data array, so let's use pure numpy-backed version for this." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 114 ms, sys: 3.81 ms, total: 118 ms\n", "Wall time: 112 ms\n" ] } ], "source": [ "%%time\n", "hotspots_aggs = [hotspots(ndvi_agg, kernel) for ndvi_agg in ndvi]\n", "\n", "hotspots_ndvi = xr.concat(hotspots_aggs, dim=\"time\")" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "hotspots_ndvi.plot.imshow(x=\"x\", y=\"y\", col=\"time\", col_wrap=5, cmap=\"viridis\");" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([-99, -95, -90, 0], dtype=int8)" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.unique(hotspots_ndvi)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "None of the results above are positive, so we can see that there only cold spots detected for this region. They expanded mostly from 2017 to 2018 and has been gradually recovered in 2020.\n", "\n", "### Next steps\n", "\n", "To find out more about xarray-spatial focal statistics and other toolsets provided by the library, please visit: https://xarray-spatial.readthedocs.io/en/latest/index.html" ] } ], "metadata": { "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.8.13" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": { "0b7ed5d840164484b268dfa5e3acfc51": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "18f42b8de0564438ba4d2c51b9411c08": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "1bb4be32a7bb41ecb234afbec83b6267": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_c7c02eb799e147c0a1207b149a0bbfb9", "style": "IPY_MODEL_5ee8db6a818a468cb71dd6618741f15d", "value": "
Finished: 53.2s
" } }, "2724b8b23ea140b282bf3298f9ddcf86": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "layout": "IPY_MODEL_3e93b1e6b44e46a9b7afd4441e5facd3" } }, "2ee0412d283e4adf9b6ae5f459546de2": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "331bdc6318154a9e9e046cd9c1ef8563": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_ed5370ef452b46e38b04b6300173c000", "style": "IPY_MODEL_b307a246bdf74a04ab9c8bde55438bd1", "value": "
20 / 20
" } }, "3530508cc8184c0baba1ed6828620779": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_331bdc6318154a9e9e046cd9c1ef8563", "IPY_MODEL_dae56509caab45e88e5f4eefab84d156", "IPY_MODEL_64ba945c00294fad9ddfa024c62739f1" ], "layout": "IPY_MODEL_e8f4ced5cc9e496ca450c33025ac8d02" } }, "3d5f15189b4a43cca689affc6aadcaf6": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_f16c48340c5142cca28413bcbd15a61c", "IPY_MODEL_953c0e5d319b4a81a98a59ccec1edaff", "IPY_MODEL_4e6a9c5683a048e99b9e58d935835777" ], "layout": "IPY_MODEL_82d08efbc82849c987ede036bd4a42bc" } }, "3e93b1e6b44e46a9b7afd4441e5facd3": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "439341ca87724afb8c27d518da570433": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "4c5516116f3c4b769b3c43af2252d060": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_74837e7386ec4b2db1dfdd3418d79354", "style": "IPY_MODEL_0b7ed5d840164484b268dfa5e3acfc51", "value": "
20 / 20
" } }, "4e6a9c5683a048e99b9e58d935835777": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_e4e047da95d347a8b762bad9967e4e65", "style": "IPY_MODEL_ef7fd6301b7a4cf9a50fe099d829550d", "value": "
asset_table_to_reader_and_window-where
" } }, "5ee8db6a818a468cb71dd6618741f15d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "64ba945c00294fad9ddfa024c62739f1": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_cb84fd177c844bd599254ac53ae9467c", "style": "IPY_MODEL_8ac1b1dc658247589be0b85001e3373e", "value": "
asset-table
" } }, "66c9c067f5b2408d9d12a94e2ef8ef09": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_a4b28af88fc348e9a5c82d57aa560c4d", "IPY_MODEL_3d5f15189b4a43cca689affc6aadcaf6", "IPY_MODEL_3530508cc8184c0baba1ed6828620779" ], "layout": "IPY_MODEL_fa7f1d1e40324388b2ef4c60ec8778c4" } }, "672bc429b85a48238149c8d0779a1f05": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "74837e7386ec4b2db1dfdd3418d79354": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "7703a122c35249828ba4ebd166d855b3": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "VBoxModel", "state": { "children": [ "IPY_MODEL_1bb4be32a7bb41ecb234afbec83b6267", "IPY_MODEL_66c9c067f5b2408d9d12a94e2ef8ef09" ], "layout": "IPY_MODEL_e06f3fd2157f49799377293b889fdf1c" } }, "7b53f756d60247ff82e7350886eee319": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "7e17615bfde045aca6537407f5ecbd48": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ProgressStyleModel", "state": { "description_width": "" } }, "813bb444dabb43b3a242ba15b1fd0954": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "82d08efbc82849c987ede036bd4a42bc": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "87802f04e51948509dde74ccc03b7730": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ProgressStyleModel", "state": { "description_width": "" } }, "8ac1b1dc658247589be0b85001e3373e": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "953c0e5d319b4a81a98a59ccec1edaff": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "FloatProgressModel", "state": { "bar_style": "success", "layout": "IPY_MODEL_2ee0412d283e4adf9b6ae5f459546de2", "max": 1, "style": "IPY_MODEL_c9e33bd59457422799b993d8bf2e0a1f", "value": 1 } }, "a4b28af88fc348e9a5c82d57aa560c4d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HBoxModel", "state": { "children": [ "IPY_MODEL_4c5516116f3c4b769b3c43af2252d060", "IPY_MODEL_b3bcddda67ad4184ade4f7463cd09ddd", "IPY_MODEL_e2c1be67d01242f6b0979b0d94a92d15" ], "layout": "IPY_MODEL_7b53f756d60247ff82e7350886eee319" } }, "b307a246bdf74a04ab9c8bde55438bd1": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "b3bcddda67ad4184ade4f7463cd09ddd": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "FloatProgressModel", "state": { "bar_style": "success", "layout": "IPY_MODEL_813bb444dabb43b3a242ba15b1fd0954", "max": 1, "style": "IPY_MODEL_87802f04e51948509dde74ccc03b7730", "value": 1 } }, "c32e5434701d4ff28077a3e9259edc0a": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "c7c02eb799e147c0a1207b149a0bbfb9": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "c9e33bd59457422799b993d8bf2e0a1f": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "ProgressStyleModel", "state": { "description_width": "" } }, "cb84fd177c844bd599254ac53ae9467c": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "dae56509caab45e88e5f4eefab84d156": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "FloatProgressModel", "state": { "bar_style": "success", "layout": "IPY_MODEL_c32e5434701d4ff28077a3e9259edc0a", "max": 1, "style": "IPY_MODEL_7e17615bfde045aca6537407f5ecbd48", "value": 1 } }, "e06f3fd2157f49799377293b889fdf1c": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "e2c1be67d01242f6b0979b0d94a92d15": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_fa0dcf6ce72b4a6a8623f0ad871bd252", "style": "IPY_MODEL_672bc429b85a48238149c8d0779a1f05", "value": "
where
" } }, "e4e047da95d347a8b762bad9967e4e65": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "e8f4ced5cc9e496ca450c33025ac8d02": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "ed5370ef452b46e38b04b6300173c000": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "ef7fd6301b7a4cf9a50fe099d829550d": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "DescriptionStyleModel", "state": { "description_width": "" } }, "f16c48340c5142cca28413bcbd15a61c": { "model_module": "@jupyter-widgets/controls", "model_module_version": "1.5.0", "model_name": "HTMLModel", "state": { "layout": "IPY_MODEL_439341ca87724afb8c27d518da570433", "style": "IPY_MODEL_18f42b8de0564438ba4d2c51b9411c08", "value": "
20 / 20
" } }, "fa0dcf6ce72b4a6a8623f0ad871bd252": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} }, "fa7f1d1e40324388b2ef4c60ec8778c4": { "model_module": "@jupyter-widgets/base", "model_module_version": "1.2.0", "model_name": "LayoutModel", "state": {} } }, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }