{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# AutoCNet Demo Notebook"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This first cell is largely book keeping and environment setup. The second line places a configuration file into the environment that provides URLs, paths, and login information for the services that AutoCNet uses, as well as information about the spatial reference system the project is going to use.\n",
    "\n",
    "Lines 4-6 get the USGS Community Sensor Model plugin loaded and ready for use.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['autocnet_config'] = 'config/sample.yml'\n",
    "os.environ['PROJ_LIB'] = '/home/ladoramkershner/miniconda3/envs/autocnet_local/share/proj' #point to you local environment path\n",
    "\n",
    "import ctypes\n",
    "from ctypes.util import find_library\n",
    "ctypes.CDLL(find_library('usgscsm'))\n",
    "\n",
    "from autocnet.graph.network import NetworkCandidateGraph"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The primary way to use AutoCNet is through the CandidateGraph object. In this demo, the derived, NetworkCandidateGraph is used. This object has an identical interface to the CandidateGraph. The difference lies in where the data are stored. In the CandidateGraph everything is stored in memory and all processing occurs in serial. On the NetworkCandidateGraph, data are stored in a database and processing occurs either in serial or on a compute cluster. \n",
    "\n",
    "Below, the `from_filelist` method is used to perform an initial database populate. A few things are happening here behind the scenes:\n",
    "\n",
    "* The database (name specified in the configuration file) is being created if it does not already exist. This creates all of the tables, relationships, and triggers.\n",
    "* The `images` table is being populated with metadata about the images in the file list including a lat/lon footprint.\n",
    "* The `cameras` table is being populated with a CSM compliant state string.\n",
    "\n",
    "For a one-and-done style project, this cell should be run once."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# First run\n",
    "import glob\n",
    "ncg = NetworkCandidateGraph.from_filelist(glob.glob('/scratch/jlaura/elysium_subset/cal/*.cub'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The cell below is the primary mechanism for accessing an existing project. Here, the `from_database` class method is used. This method makes a spatial query to the `images` table and builds a graph object where the nodes are images and the edges linking nodes indicate that the footprints overlap. This call makes no modifications to the database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# On subsequent runs\n",
    "ncg = NetworkCandidateGraph().from_database()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The database currently contains a populated `images` table and a populated `cameras` table. In the current pipeline style flow, we now need to compute polygons generated by the overlaps of footprints. It is within those polygons that correspondences can be found. We run two SQL queries (found in the `sql` directory).\n",
    "\n",
    "The first query computes the n-wise overlapping polygons and the second query identifies those polygons that contributed to the overlapping geometry. The picture below illustrates what the result looks like.\n",
    "\n",
    "![overlap](overlap.png)\n",
    "\n",
    "Each polygon (A, B, AB, ABC, BC) will be an independent row in the database. The `intersections` column will be populated with an array where the values in the array are the `id`s of the image that contributed to the particular overlap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Run the 2 sql commands in the sql directory of the autocnet repo to compute overlaps and get the overlap arrays populates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once overlaps have been computed, points are placed into the overlapping geometries. This process adds points to the `points` table and measures to the `measures` table. These points and measures are synonymous with the points and measures in an ISIS control network. This code is making use of the `images` and `cameras` tables that have been previously populated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# This block is used to compute the overlapping polygon components and then place points into them.\n",
    "from autocnet.spatial.overlap import place_points_in_overlaps\n",
    "\n",
    "# Place points\n",
    "place_points_in_overlaps(ncg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, the points/measures are converted to be pairwise matches between images. Instead of a point-centric representation where a point has many measures, this representation is image-to-image centric where a pair of images has some set of shared correspondences. This command results in the `matches` table being populated with the pairwise matches between edges. Using the above example, all of the points/measures placed in the overlapping ABC polygon are decomposed into matches between AB, AC, and BC. The data are identical, but the representation has changed.\n",
    "\n",
    "We do this because now we want to use classic computer vision techniques that generally operate best with pairwise representations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# This block converts the points into matches\n",
    "for s, d, e in ncg.edges(data='data'):  # intentionally in a loop so this doesn't spawn a cluster job\n",
    "    e.network_to_matches()\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that a pairwise representation exists, we apply a standard CV technique (computation of the fundamental matrix (F)). This operation updates the `edges` table to add the F matrix and a mask to the `masks` column indicating whether a particular match has been flagged as a blunder by the ransac procedure. This code is making use of the `matches` table that has been previously populated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/jlaura/autocnet/autocnet/transformation/fundamental_matrix.py:310: UserWarning: F Computation Failed.\n",
      "  warnings.warn(\"F Computation Failed.\")\n"
     ]
    }
   ],
   "source": [
    "# This block computes the fundamental matrices\n",
    "ncg.compute_fundamental_matrices(method='ransac', maskname='fundamental')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, the next three cells are a janky heuristic designed to use the F matrix to remove blunders in the `points` and `measures` tables. This is an aggregation step whereby we seek to determine if a measure should be made inactive. This code makes use of the `edges` table (`masks` column) and updates the `measures` table (`active` column).\n",
    "\n",
    "These three cells are not well integrated into the NetworkCandidateGraph and provide the best view of the type of operations happening under the hood in the previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# This block converts the points into matches\n",
    "counters = []\n",
    "for s, d, e in ncg.edges(data='data'):  # intentionally in a loop so this doesn't spawn a cluster job\n",
    "    counters.append(e.mask_to_counter('fundamental'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from collections import Counter\n",
    "from autocnet import Session\n",
    "from autocnet.io.db.model import Measures\n",
    "\n",
    "aggregate = sum(counters, Counter())\n",
    "\n",
    "# Now I need to take the output here and then look in qnet to see wtf is going on. Do we have a threshold here\n",
    "# for blowing away bad stuff? If so, where? I should probably normalize all of these too based on the number of other\n",
    "# images that they exist in. In other words, count/n-images\n",
    "to_pop = []\n",
    "session = Session()\n",
    "for k, v in aggregate.items():\n",
    "    pid = session.query(Measures).filter(Measures.id == k).first().pointid\n",
    "    nimages = len(session.query(Measures).filter(Measures.pointid == pid).all())\n",
    "    outlier_ratio = v / nimages  # This is the metric to test on (maybe?) - the ratio of the # of times the measure is \n",
    "                                  # flagged as bad to the number of measures associated with the point.\n",
    "    # These are rules that are going to need testing / vetting. Are these appropriate values?\n",
    "    if outlier_ratio <= 0.5 or (outlier_ratio <= 0.5 and nimages == 2):\n",
    "        to_pop.append(k)\n",
    "    else:\n",
    "        aggregate[k] = v / len(session.query(Measures).filter(Measures.pointid == pid).all())\n",
    "for k in to_pop:\n",
    "    aggregate.pop(k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "session = Session()\n",
    "make_inactive = list(aggregate.keys())\n",
    "session.query(Measures).filter(Measures.id.in_(make_inactive)).update({'active':False}, synchronize_session='fetch')\n",
    "session.commit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ncg.to_isis('/scratch/jlaura/elysium_subset/demo.net')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
