{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Approach 1b: Comparing histograms with closest lapse image\n",
    "Instead of comparing images directly (approach 1a), we compare the image histograms using the Kolmogorov-Smirnov test. We expect low \"goodness of fit\" values between the histograms for anomalous images since the appearance of an anomalous object would alter the histogram. Since color information is important (at least for daytime images), we compare the histogram of each color channel individually. Using a linear SVM, we try to classify new images as normal or anomalous.\n",
    "\n",
    "### Advantages compared to approach 1a (pixel-wise comparison)\n",
    "- less sensitive to camera movement\n",
    "- less sensitive to small moving objects or dust particles\n",
    "- almost not sensitive to noise\n",
    "- color information is preserved\n",
    "\n",
    "### Problems\n",
    "- very sensitive to lighting changes\n",
    "- loss of local information (we're only looking at the histogram)\n",
    "- can easily be confused, e.g. by green or brown animals\n",
    "- color information is useless at night"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The autoreload extension is already loaded. To reload it, use:\n",
      "  %reload_ext autoreload\n",
      "Found 2 sessions\n",
      "Session 'Beaver_01' at folder: ./ResizedSessions_NoBackup/VIELAAS_Spring_Session01-VIELAAS_Beaver_01\n",
      "Loaded scans.\n"
     ]
    }
   ],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "from tqdm.notebook import tqdm\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "from py.PlotUtils import plot_roc_curve\n",
    "from py.Dataset import Dataset\n",
    "from py.ImageUtils import display_images, is_daytime\n",
    "\n",
    "DIR = './ResizedSessions_NoBackup' # dataset directory\n",
    "\n",
    "ds = Dataset(DIR)\n",
    "session = ds.create_session(\"beaver_01\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1008588 characters]",
      "text/plain": [
       "<Figure size 1152x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "motion_img = session.get_random_motion_image(day_only=True)\n",
    "before_img, after_img, rel = motion_img.get_closest_lapse_images()\n",
    "comp_img = before_img if rel <= 0.5 else after_img\n",
    "motion_arr = motion_img.read(gray=False)\n",
    "comp_arr = comp_img.read(gray=False)\n",
    "display_images([motion_arr, comp_arr], [motion_img.filename, comp_img.filename])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "hists = [np.histogram(motion_arr[:,:,i], bins=range(256), density=True) for i in range(3)]\n",
    "comp_hists = [np.histogram(comp_arr[:,:,i], bins=range(256), density=True) for i in range(3)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 53896 characters]",
      "text/plain": [
       "<Figure size 1440x360 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from scipy.stats import ks_2samp\n",
    "\n",
    "plt.figure(figsize=(20, 5))\n",
    "for channel, channel_name in enumerate([\"R\", \"G\", \"B\"]):\n",
    "    # calculate Kolmogorov-Smirnov test\n",
    "    ks, p = ks_2samp(comp_hists[channel][0], hists[channel][0])\n",
    "\n",
    "    # plot\n",
    "    plt.subplot(1, 3, channel + 1)\n",
    "    plt.plot(comp_hists[channel][1][:-1], comp_hists[channel][0], color=\"blue\", label=\"Lapse\")\n",
    "    plt.plot(hists[channel][1][:-1], hists[channel][0], color=\"red\", label=\"Motion\")\n",
    "    plt.xlim((-2, 257))\n",
    "    plt.title(f\"{channel_name} (ks = {ks:.3f}, p = {p:.3f})\")\n",
    "    plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Complete test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "8bfb17008cab4431924087fa4fa86a84",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/695 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "results = []\n",
    "\n",
    "for img in tqdm(list(session.generate_motion_images())):\n",
    "    before_img, after_img, rel = img.get_closest_lapse_images()\n",
    "    if rel < 0: # no lapse image found\n",
    "        continue\n",
    "    comp_img = before_img if rel <= 0.5 else after_img\n",
    "    motion_arr = img.read(gray=False)\n",
    "    comp_arr = comp_img.read(gray=False)\n",
    "    motion_daytime = is_daytime(motion_arr)\n",
    "\n",
    "    # don't compare daytime with nighttime images\n",
    "    if motion_daytime != is_daytime(comp_arr):\n",
    "        # choose the other closest lapse image\n",
    "        comp_img = after_img if rel <= 0.5 else before_img\n",
    "        comp_arr = comp_img.read(gray=False)\n",
    "        assert motion_daytime == is_daytime(comp_arr)\n",
    "        # if this assertion fails, a motion image was captured at daytime between two nighttime lapse images or the other way around\n",
    "    assert motion_arr.dtype == \"uint8\" and comp_arr.dtype == \"uint8\"\n",
    "\n",
    "    # Calculate histograms\n",
    "    hists = [np.histogram(motion_arr[:,:,channel], bins=range(256), density=True) for channel in range(3)]\n",
    "    comp_hists = [np.histogram(comp_arr[:,:,channel], bins=range(256), density=True) for channel in range(3)]\n",
    "\n",
    "    # Compare each channel using Kolmogorov-Smirnov test\n",
    "    vals = [list(ks_2samp(comp_hists[channel][0], hists[channel][0])) for channel in range(3)]\n",
    "    results.append([img.filename, *vals])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/kleinsteuber/.local/lib/python3.6/site-packages/numpy/core/_asarray.py:136: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray\n",
      "  return array(a, dtype, copy=False, order=order, subok=True)\n"
     ]
    }
   ],
   "source": [
    "np.save(f\"Cache_NoBackup/approach1b_{session.name}_results.npy\", results)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([['Beaver_01_00134.jpg',\n",
       "        list([0.1803921568627451, 0.0004837347512529537]),\n",
       "        list([0.23921568627450981, 8.215743051351134e-07]),\n",
       "        list([0.11764705882352941, 0.05857104535159017])],\n",
       "       ['Beaver_01_00329.jpg',\n",
       "        list([0.10588235294117647, 0.1146823484510564]),\n",
       "        list([0.19607843137254902, 0.00010568941169931606]),\n",
       "        list([0.11372549019607843, 0.07385998493202764])],\n",
       "       ['Beaver_01_00167.jpg',\n",
       "        list([0.13725490196078433, 0.016301855576781033]),\n",
       "        list([0.13725490196078433, 0.016301855576781033]),\n",
       "        list([0.13725490196078433, 0.016301855576781033])],\n",
       "       ...,\n",
       "       ['Beaver_01_00658.jpg',\n",
       "        list([0.2901960784313726, 7.216496108235626e-10]),\n",
       "        list([0.2901960784313726, 7.216496108235626e-10]),\n",
       "        list([0.2901960784313726, 7.216496108235626e-10])],\n",
       "       ['Beaver_01_00355.jpg',\n",
       "        list([0.21568627450980393, 1.3144022142141512e-05]),\n",
       "        list([0.21568627450980393, 1.3144022142141512e-05]),\n",
       "        list([0.21568627450980393, 1.3144022142141512e-05])],\n",
       "       ['Beaver_01_00202.jpg',\n",
       "        list([0.09411764705882353, 0.20894602299576795]),\n",
       "        list([0.1450980392156863, 0.00924332360259003]),\n",
       "        list([0.03529411764705882, 0.9974408869187625])]], dtype=object)"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "results = np.load(f\"Cache_NoBackup/approach1b_{session.name}_results.npy\", allow_pickle=True)\n",
    "results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "from py.Labels import LABELS\n",
    "\n",
    "normal = []\n",
    "anomalous = []\n",
    "for filename, r, g, b in results:\n",
    "    vals = [r[0], g[0], b[0], r[1], g[1], b[1]] # p values of all channels\n",
    "    img_nr = int(filename[10:-4])\n",
    "    if img_nr > LABELS[session.name][\"max\"]:\n",
    "        continue\n",
    "    elif img_nr in LABELS[session.name][\"normal\"]:\n",
    "        normal.append(vals)\n",
    "    else:\n",
    "        anomalous.append(vals)\n",
    "normal = np.array(normal)\n",
    "anomalous = np.array(anomalous)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3d plot of ks values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 174768 characters]",
      "text/plain": [
       "<Figure size 1152x1152 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(16,16))\n",
    "ax = fig.add_subplot(projection=\"3d\")\n",
    "ax.scatter(anomalous[:,0], anomalous[:,1], anomalous[:,2], label=\"Anomalous\", color=\"red\", marker=\"x\")\n",
    "ax.scatter(normal[:,0], normal[:,1], normal[:,2], label=\"Normal\", color=\"blue\", marker=\"x\")\n",
    "# ax.xscale(\"log\")\n",
    "# ax.yscale(\"log\")\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3d plot of p values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 192904 characters]",
      "text/plain": [
       "<Figure size 1152x1152 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(16,16))\n",
    "ax = fig.add_subplot(projection=\"3d\")\n",
    "ax.scatter(anomalous[:,3], anomalous[:,4], anomalous[:,5], label=\"Anomalous\", color=\"red\", marker=\"x\")\n",
    "ax.scatter(normal[:,3], normal[:,4], normal[:,5], label=\"Normal\", color=\"blue\", marker=\"x\")\n",
    "# ax.xscale(\"log\")\n",
    "# ax.yscale(\"log\")\n",
    "plt.legend()\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 98600 characters]",
      "text/plain": [
       "<Figure size 1080x720 with 6 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(15,10))\n",
    "channels = [\"ks(R)\", \"ks(G)\", \"ks(B)\", \"p(R)\", \"p(G)\", \"p(B)\"]\n",
    "i = 0\n",
    "for c1, c2 in [(0, 1), (0, 2), (1, 2), (3, 4), (3, 5), (4, 5)]:\n",
    "    i += 1\n",
    "    plt.subplot(2, 3, i)\n",
    "    plt.scatter(anomalous[:,c1], anomalous[:,c2], label=\"Anomalous\", color=\"red\", marker=\"x\")\n",
    "    plt.scatter(normal[:,c1], normal[:,c2], label=\"Normal\", color=\"blue\", marker=\"x\")\n",
    "    plt.legend()\n",
    "    plt.title(f\"{channels[c1]} vs {channels[c2]}\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.627884741971111 0.8073281330931875\n"
     ]
    }
   ],
   "source": [
    "normal_means = 1 - np.mean(normal[:,3:6], axis=1)\n",
    "anomalous_means = 1 - np.mean(anomalous[:,3:6], axis=1)\n",
    "print(np.mean(normal_means), np.mean(anomalous_means))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(695, 1)\n",
      "(695,)\n"
     ]
    }
   ],
   "source": [
    "y_anom = np.array(anomalous_means).reshape(-1, 1)\n",
    "\n",
    "y_norm = np.array(normal_means).reshape(-1, 1)\n",
    "\n",
    "X = np.concatenate([y_norm, y_anom])\n",
    "print(X.shape)\n",
    "y = np.concatenate([-np.ones((y_norm.shape[0])), np.ones((y_anom.shape[0]))])\n",
    "print(y.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 27400 characters]",
      "text/plain": [
       "<Figure size 576x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fpr, tpr, thresholds, auc_score = plot_roc_curve(y, X[:,0], f\"Histogram comparison\", savefile=f\"plots/approach1b/roc_curves/{session.name}_pmean\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.6.9 64-bit",
   "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.6.9"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
