{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Change Detection Tutorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tutorial for running change detection algorithms. \n",
    "\n",
    "This is done using randomly generated before and after images.\n",
    "\n",
    "\n",
    "Steps: \n",
    "1. generate base DEM\n",
    "    1. create after DEM by filling depressions (use defaults for now)\n",
    "    1. add craters to after image (use defaults for now)\n",
    "    1. hillshade both images (use azi param to control light direction)\n",
    "    1. apply noise to both images\n",
    "    1. apply affine to both images and use after affine on truth data (stick with offset differences for now, as it tends to be the most common registration error)\n",
    "1. run before and after through a CD algorithm \n",
    "1. compute TPR and FPR \n",
    "    * TPR = TP/TP+FP\n",
    "    * FPR = FP/FP+TP\n",
    "    * IF function returns points, TP = point equals to or is contained by set of \"truth\" points/polys 1 pixel buffer. \n",
    "    * ELSE function returns polygons, TP = polygon has >50% intersection with \"truth\" poly. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "\n",
    "import os\n",
    "os.environ['ISISROOT'] = '/usgs/cpkgs/anaconda3_linux/envs/isis3.9.0'\n",
    "\n",
    "from numpy import random\n",
    "from scipy.ndimage.interpolation import zoom\n",
    "import scipy\n",
    "\n",
    "import os\n",
    "import math\n",
    "\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "from scipy.misc import imresize\n",
    "\n",
    "import shapely\n",
    "from shapely.geometry import Point, MultiPolygon, MultiPoint\n",
    "\n",
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.image as mpimg\n",
    "\n",
    "import pandas as pd\n",
    "import geopandas as gpd\n",
    "\n",
    "from autocnet.utils.utils import bytescale, cartesian\n",
    "from autocnet.examples import get_path\n",
    "from autocnet.cg import change_detection as cd\n",
    "from autocnet.cg import cg\n",
    "\n",
    "# used just to produce parameterizations \n",
    "def cartesian_product(*arrays):\n",
    "    la = len(arrays)\n",
    "    dtype = \"object\"\n",
    "    arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)\n",
    "    for i, a in enumerate(np.ix_(*arrays)):\n",
    "        arr[...,i] = a\n",
    "    return arr.reshape(-1, la)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def generate_before_and_after(azi=(190, 190), noise_stddev=(0,0), alpha=1.0, nboulders=5):\n",
    "    # Generate base DEM \n",
    "    before_dem = cd.generate_dem(alpha=alpha)\n",
    "    \n",
    "    # Generate Boulders\n",
    "    before_dem, before_polys, after_dem, after_polys = cd.generate_boulder_field(before_dem, nboulders)\n",
    "\n",
    "    # compute Depressions\n",
    "    after_dem, change_poly = cd.compute_depression(after_dem, alpha=0.3, return_polygon=True)\n",
    "    change_poly = shapely.ops.cascaded_union([change_poly, MultiPolygon(after_polys), MultiPolygon(before_polys)])\n",
    "    \n",
    "    # hillshade\n",
    "    before_image = cd.hillshade(before_dem, azi=azi[0]).mean(axis=2)\n",
    "    after_image = cd.hillshade(after_dem, azi=azi[1]).mean(axis=2)\n",
    "    \n",
    "    # add noise \n",
    "    before_image += np.random.normal(0, noise_stddev[0], before_image.shape)\n",
    "    after_image += np.random.normal(0, noise_stddev[1], after_image.shape)\n",
    "    \n",
    "    # TODO: Add affine distortion \n",
    "    \n",
    "    return before_image, after_image, change_poly\n",
    "\n",
    "\n",
    "def plot_before_after_image(before_image, after_image, change_poly):\n",
    "    diff = (after_image-before_image).astype(float)\n",
    "\n",
    "    fig, axs = plt.subplots(2,2, figsize=(10,10))\n",
    "\n",
    "    axs[0][0].imshow(before_image, cmap=\"bone\")\n",
    "    axs[0][0].set_title('\"Before\" Hillshade')\n",
    "    axs[0][1].imshow(after_image, cmap=\"bone\")\n",
    "    axs[0][1].set_title('\"After\" Hillshade')\n",
    "\n",
    "    axs[1][0].imshow(after_image, cmap='bone')\n",
    "\n",
    "    if isinstance(change_poly, MultiPolygon):\n",
    "        for geom in change_poly.geoms:    \n",
    "            xs, ys = geom.exterior.xy    \n",
    "            axs[1][0].fill(ys, xs, alpha=1, fc='black', ec='none')\n",
    "    else: \n",
    "        xs, ys = change_poly.exterior.xy    \n",
    "        axs[1][0].fill(ys, xs, alpha=1, fc='black', ec='none')\n",
    "    axs[1][0].set_title(\"Change Polygons\")\n",
    "\n",
    "    axs[1][1].imshow(diff, cmap=\"magma\")\n",
    "    axs[1][1].set_title('Diff')\n",
    "    plt.show()\n",
    "    \n",
    "    \n",
    "def compute_cd_metrics(results, function_params, verbose=False):\n",
    "    ny = len(results)\n",
    "    \n",
    "    if verbose: \n",
    "        fig, axs = plt.subplots(ny, 1)\n",
    "        fig.set_figheight(5*ny)\n",
    "        fig.set_figwidth(5)\n",
    "\n",
    "    truth_poly = change_poly.buffer(1)\n",
    "    scoresDf = pd.DataFrame(index=[str(p) for p in function_params], columns=[\"tpr\", \"fpr\", \"geom\"])\n",
    "\n",
    "    for i, coord in enumerate(results):\n",
    "        res = results[i]\n",
    "        df = res[0]\n",
    "        # get true positives and false negatives\n",
    "        true_positives = 0\n",
    "        \n",
    "        if(df.shape[0] is not 0 and isinstance(df.geometry[0], Point)):\n",
    "            # needs to be within one pixel of truth\n",
    "            for p in df.geometry:\n",
    "                p = Point(p.y, p.x)\n",
    "                if p.intersects(truth_poly):\n",
    "                    true_positives+=1\n",
    "        else:\n",
    "            # geopandas likes to swap x,y\n",
    "            sdf = df.geometry.map(lambda polygon: shapely.ops.transform(lambda x, y: (y, x), polygon))\n",
    "            # get true positives and false negatives\n",
    "            true_positives = 0\n",
    "            for geom in sdf.geometry:\n",
    "                for truth_geom in truth_poly.geoms:\n",
    "                    if geom.intersection(truth_geom).area >= geom.area/5:\n",
    "                        true_positives += 1\n",
    "                        break\n",
    "        \n",
    "        print(true_positives, len(truth_poly))\n",
    "        false_positives = len(df) - true_positives \n",
    "        TPR = max(true_positives/len(df) if df.shape[0] is not 0 else np.nan, 1)\n",
    "        FPR = false_positives/len(df) if df.shape[0] is not 0 else np.nan\n",
    "        \n",
    "        scoresDf.loc[str(function_params[i])][\"tpr\"] = TPR\n",
    "        scoresDf.loc[str(function_params[i])][\"fpr\"] = FPR\n",
    "        \n",
    "        if df.shape[0] is 0: \n",
    "            scoresDf.loc[str(function_params[i])][\"geom\"] = None\n",
    "        else:\n",
    "            try:\n",
    "                scoresDf.loc[str(function_params[i])][\"geom\"] = MultiPoint(df.geometry)\n",
    "            except:\n",
    "                scoresDf.loc[str(function_params[i])][\"geom\"] = MultiPolygon(list(df.geometry))\n",
    "\n",
    "        if verbose: \n",
    "            axs[i].set_title(f\"score: {TPR}\")\n",
    "            axs[i].set(xlabel=f\"\", ylabel=f\"\")\n",
    "            axs[i].imshow(after_image, cmap=\"bone\", alpha=0.2)\n",
    "\n",
    "            if isinstance(truth_poly, MultiPolygon):\n",
    "                for geom in truth_poly.geoms:    \n",
    "                    xs, ys = geom.exterior.xy    \n",
    "                    axs[i].fill(ys, xs, alpha=0.5, fc='black', ec='none')\n",
    "            else: \n",
    "                xs, ys = truth_poly.exterior.xy    \n",
    "                axs[i].fill(ys, xs, alpha=0.5, fc='black', ec='none')\n",
    "            \n",
    "            try:\n",
    "                axs[i].scatter(df.x, df.y, c=\"r\", alpha=1, s=1)\n",
    "            except:\n",
    "                df.plot(ax=axs[i])\n",
    "            \n",
    "    if verbose: \n",
    "        plt.show()\n",
    "    \n",
    "    return scoresDf\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate before and after images "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Warning! No geotransform defined. Choosing a standard one! (Top left cell's top let corner at <0,0>; cells are 1x1.)\n",
      "Warning! No geotransform defined. Choosing a standard one! (Top left cell's top let corner at <0,0>; cells are 1x1.)\n",
      "Warning! No geotransform defined. Choosing a standard one! (Top left cell's top let corner at <0,0>; cells are 1x1.)\n",
      "Warning! No geotransform defined. Choosing a standard one! (Top left cell's top let corner at <0,0>; cells are 1x1.)\n",
      "Warning! No geotransform defined. Choosing a standard one! (Top left cell's top let corner at <0,0>; cells are 1x1.)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 760508 characters]\n",
      "text/plain": [
       "<Figure size 720x720 with 4 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "before_image, after_image, change_poly = generate_before_and_after(azi=(190, 215), noise_stddev=(1,2))\n",
    "plot_before_after_image(before_image, after_image, change_poly)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# OKB\n",
    "\n",
    "Simple change detection algorithm which produces an overlay image of change hotspots (i.e. a 2d histogram image of detected change density). This is done by running a ORB feature extractor over a diff or ratio image. \n",
    "\n",
    "**Cons** \n",
    "  1. Fails when change near edges of image \n",
    "  2. Not robust to textured FPs, lighting differences \n",
    "  3. Cannot capture large, uniform, changes \n",
    "  4. Poor automatic change detection, incabable of determining descrete changes \n",
    "  5. If nfeatures is too high, noise in the image begins to be picked up as FPs. \n",
    "\n",
    "**Pros**\n",
    "  1. Simple, fast \n",
    "  2. Good \"quick glance\" change detection "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate CD parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2X1 Matrix\n"
     ]
    }
   ],
   "source": [
    "# Change these based on algorithm \n",
    "\n",
    "nfeatures = [1000, 2000]\n",
    "scalefactor = [1.5]\n",
    "nlevels = [3]\n",
    "function_params = [dict(zip([\"nfeatures\", \"scaleFactor\", \"nlevels\"], ps)) for ps in cartesian_product(nfeatures, scalefactor, nlevels)] \n",
    "\n",
    "print(f\"{len(function_params)}X1 Matrix\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run OKB with Params"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def image_diff_abs(arr1, arr2):\n",
    "     return np.abs(cd.image_diff(arr1, arr2))\n",
    "\n",
    "results = [cd.okubogar_detector(before_image, \n",
    "                                after_image, \n",
    "                                nbins=50, \n",
    "                                extractor_kwargs=params) for params in function_params]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot results "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "984 6\n",
      "1780 6\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 265052 characters]\n",
      "text/plain": [
       "<Figure size 360x720 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>tpr</th>\n",
       "      <th>fpr</th>\n",
       "      <th>geom</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>{'nfeatures': 1000, 'scaleFactor': 1.5, 'nlevels': 3}</th>\n",
       "      <td>1</td>\n",
       "      <td>0.016</td>\n",
       "      <td>(POINT (533 649), POINT (538 494), POINT (455 ...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>{'nfeatures': 2000, 'scaleFactor': 1.5, 'nlevels': 3}</th>\n",
       "      <td>1</td>\n",
       "      <td>0.0198238</td>\n",
       "      <td>(POINT (441 668), POINT (454 668), POINT (458 ...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                   tpr        fpr  \\\n",
       "{'nfeatures': 1000, 'scaleFactor': 1.5, 'nlevel...   1      0.016   \n",
       "{'nfeatures': 2000, 'scaleFactor': 1.5, 'nlevel...   1  0.0198238   \n",
       "\n",
       "                                                                                                 geom  \n",
       "{'nfeatures': 1000, 'scaleFactor': 1.5, 'nlevel...  (POINT (533 649), POINT (538 494), POINT (455 ...  \n",
       "{'nfeatures': 2000, 'scaleFactor': 1.5, 'nlevel...  (POINT (441 668), POINT (454 668), POINT (458 ...  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "okb_results = compute_cd_metrics(results, function_params, True)\n",
    "okb_results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# OKBM\n",
    "\n",
    "Modified version of OKB, apply clustering to the output of OKB in order to identify contiguous globs. \n",
    "\n",
    "Pros: \n",
    "* Less ambiguous that OKB, descrete detectable change  \n",
    "* Better for programmatic change detection  \n",
    "* Good for large contiguous features  \n",
    "\n",
    "Cons: \n",
    "* Does very poorly even with slight registration errors  \n",
    "* Requires several super sensative hyper parameters  \n",
    "* Min_samples requires you to know the smallest change "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate CD Parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1X2 Matrix\n"
     ]
    }
   ],
   "source": [
    "# Change Based on Algorithm\n",
    "nfeatures = [500, 2000]\n",
    "scalefactor = [1.5]\n",
    "nlevels = [3]\n",
    "extractor_params = [dict(zip([\"nfeatures\", \"scaleFactor\", \"nlevels\"], ps)) for ps in cartesian_product(nfeatures, scalefactor, nlevels)] \n",
    "\n",
    "min_samples = [10]\n",
    "max_eps = [20]\n",
    "xi = [.5]\n",
    "eps = [.5]\n",
    "cluster_params =  [dict(zip([\"min_samples\", \"max_eps\", \"eps\", \"xi\"], ps)) for ps in cartesian_product(min_samples, max_eps, eps, xi)] \n",
    "\n",
    "print(f\"{len(cluster_params)}X{len(extractor_params)} Matrix\")\n",
    "function_params = cartesian_product(extractor_params, cluster_params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = [cd.okbm_detector(before_image, \n",
    "                   after_image, \n",
    "                   extractor_kwargs=params[0], cluster_kwargs=params[1]) for params in function_params]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7 6\n",
      "6 6\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 254456 characters]\n",
      "text/plain": [
       "<Figure size 360x720 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>tpr</th>\n",
       "      <th>fpr</th>\n",
       "      <th>geom</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>[{'nfeatures': 500, 'scaleFactor': 1.5, 'nlevels': 3}\\n {'min_samples': 10, 'max_eps': 20, 'eps': 0.5, 'xi': 0.5}]</th>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>(POLYGON ((587 460, 569.25 461.25, 564 462, 55...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>[{'nfeatures': 2000, 'scaleFactor': 1.5, 'nlevels': 3}\\n {'min_samples': 10, 'max_eps': 20, 'eps': 0.5, 'xi': 0.5}]</th>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>(POLYGON ((628 451, 595.5 451.5, 565.5 454.5, ...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                   tpr fpr  \\\n",
       "[{'nfeatures': 500, 'scaleFactor': 1.5, 'nlevel...   1   0   \n",
       "[{'nfeatures': 2000, 'scaleFactor': 1.5, 'nleve...   1   0   \n",
       "\n",
       "                                                                                                 geom  \n",
       "[{'nfeatures': 500, 'scaleFactor': 1.5, 'nlevel...  (POLYGON ((587 460, 569.25 461.25, 564 462, 55...  \n",
       "[{'nfeatures': 2000, 'scaleFactor': 1.5, 'nleve...  (POLYGON ((628 451, 595.5 451.5, 565.5 454.5, ...  "
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "okbm_results = compute_cd_metrics(results, function_params, True)\n",
    "okbm_results"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Blob\n",
    "\n",
    "Detector desgined to find boulders. This works by detecting pairs of light and dark areas and correlates them with the expected sub solar azimuth angle to determine candidate pairs of lit and shadowed areas. Small brightened blobs with a nearby dark blob that are colinear to the subsolar azimuth is expected to be a boulder. The boulder is flagged as a change if it not in the same location in the before image as the after image. \n",
    "\n",
    "Pros: \n",
    "* Good at detecting boulders (though off by a few pixels...)\n",
    "* faster than other algorithms \n",
    "\n",
    "Cons: \n",
    "* Does not detect large change\n",
    "* sensative to bad registration "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generate CD Parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Change Based on Algorithm\n",
    "subtractive = [True]\n",
    "min_sigma = [.25]\n",
    "max_sigma = [.35, 1]\n",
    "num_sigma = [4]\n",
    "threshold = [.3]\n",
    "overlap = [.5]\n",
    "n_neighbors = [2]\n",
    "angle_tolerance = [4]\n",
    "\n",
    "function_params = [dict(zip([\"subtractive\", \"min_sigma\", \"max_sigma\", \"num_sigma\", \"threshold\", \n",
    "                              \"overlap\", \"n_neighbors\", \"angle_tolerance\"],\n",
    "                             ps)) for ps in cartesian_product(subtractive, min_sigma, max_sigma, num_sigma, threshold, \n",
    "                              overlap, n_neighbors, angle_tolerance)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(                    geometry\n",
       "  0     POINT (53.000 615.000)\n",
       "  1    POINT (458.000 707.000)\n",
       "  2    POINT (399.000 276.000)\n",
       "  3    POINT (436.000 719.000)\n",
       "  4    POINT (441.000 704.000)\n",
       "  ..                       ...\n",
       "  287  POINT (589.000 552.000)\n",
       "  288  POINT (545.000 614.000)\n",
       "  289  POINT (608.000 499.000)\n",
       "  290  POINT (557.000 582.000)\n",
       "  291  POINT (511.000 592.000)\n",
       "  \n",
       "  [292 rows x 1 columns],\n",
       "  array([[3, 2, 2, ..., 0, 0, 1],\n",
       "         [4, 0, 2, ..., 0, 0, 0],\n",
       "         [4, 0, 2, ..., 1, 2, 0],\n",
       "         ...,\n",
       "         [5, 0, 0, ..., 4, 4, 0],\n",
       "         [5, 0, 0, ..., 3, 3, 0],\n",
       "         [1, 1, 1, ..., 1, 1, 2]], dtype=uint8)),\n",
       " (                    geometry\n",
       "  0     POINT (53.000 615.000)\n",
       "  1    POINT (458.000 707.000)\n",
       "  2    POINT (399.000 276.000)\n",
       "  3    POINT (436.000 719.000)\n",
       "  4    POINT (423.000 712.000)\n",
       "  ..                       ...\n",
       "  287  POINT (589.000 552.000)\n",
       "  288  POINT (545.000 614.000)\n",
       "  289  POINT (608.000 499.000)\n",
       "  290  POINT (557.000 582.000)\n",
       "  291  POINT (511.000 592.000)\n",
       "  \n",
       "  [292 rows x 1 columns],\n",
       "  array([[3, 2, 2, ..., 0, 0, 1],\n",
       "         [4, 0, 2, ..., 0, 0, 0],\n",
       "         [4, 0, 2, ..., 1, 2, 0],\n",
       "         ...,\n",
       "         [5, 0, 0, ..., 4, 4, 0],\n",
       "         [5, 0, 0, ..., 3, 3, 0],\n",
       "         [1, 1, 1, ..., 1, 1, 2]], dtype=uint8))]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def image_diff_abs(arr1, arr2):\n",
    "     return np.abs(cd.image_diff(arr1, arr2))\n",
    "\n",
    "results = [cd.blob_detector(before_image, \n",
    "                            after_image, \n",
    "                            np.full(before_image.shape, 210),\n",
    "                            **params) for params in function_params]\n",
    "\n",
    "results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "290 6\n",
      "290 6\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 256068 characters]\n",
      "text/plain": [
       "<Figure size 360x720 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>tpr</th>\n",
       "      <th>fpr</th>\n",
       "      <th>geom</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>{'subtractive': 0, 'min_sigma': 0.25, 'max_sigma': 0.35, 'num_sigma': 4, 'threshold': 0.3, 'overlap': 0.5, 'n_neighbors': 2, 'angle_tolerance': 4}</th>\n",
       "      <td>1</td>\n",
       "      <td>0.00684932</td>\n",
       "      <td>(POINT (53 615), POINT (458 707), POINT (399 2...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>{'subtractive': 0, 'min_sigma': 0.25, 'max_sigma': 1.0, 'num_sigma': 4, 'threshold': 0.3, 'overlap': 0.5, 'n_neighbors': 2, 'angle_tolerance': 4}</th>\n",
       "      <td>1</td>\n",
       "      <td>0.00684932</td>\n",
       "      <td>(POINT (53 615), POINT (458 707), POINT (399 2...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                   tpr         fpr  \\\n",
       "{'subtractive': 0, 'min_sigma': 0.25, 'max_sigm...   1  0.00684932   \n",
       "{'subtractive': 0, 'min_sigma': 0.25, 'max_sigm...   1  0.00684932   \n",
       "\n",
       "                                                                                                 geom  \n",
       "{'subtractive': 0, 'min_sigma': 0.25, 'max_sigm...  (POINT (53 615), POINT (458 707), POINT (399 2...  \n",
       "{'subtractive': 0, 'min_sigma': 0.25, 'max_sigm...  (POINT (53 615), POINT (458 707), POINT (399 2...  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "blob_results = compute_cd_metrics(results, function_params, True)\n",
    "blob_results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "AutocnetDev",
   "language": "python",
   "name": "autocnetdev"
  },
  "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.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
