Commit 9ee4aae2 authored by Jay's avatar Jay Committed by jay
Browse files

Leverage mutable mapping to get visualization of graph object.

parent e70c7392
Loading
Loading
Loading
Loading
+7 −14
Original line number Diff line number Diff line
from collections import MutableMapping
import os
import warnings

@@ -20,7 +21,7 @@ from autocnet.vis.graph_view import plot_node, plot_edge, plot_graph
from autocnet.utils.isis_serial_numbers import generate_serial_number


class Edge(object):
class Edge(dict, MutableMapping):
    """
    Attributes
    ----------
@@ -32,9 +33,10 @@ class Edge(object):
            A list of the available masking arrays
    """

    def __init__(self, source, destination):
    def __init__(self, source=None, destination=None):
        self.source = source
        self.destination = destination

        self._masks = set()
        self._mask_arrays = {}
        self._homography = None
@@ -331,12 +333,8 @@ class Edge(object):
    def plot(self, ax=None, clean_keys=[], **kwargs):
        return plot_edge(self, ax=ax, clean_keys=clean_keys, **kwargs)

    def update(self, *args):
        # Added for NetworkX
        pass


class Node(object):
class Node(dict, MutableMapping):
    """
    Attributes
    ----------
@@ -361,7 +359,7 @@ class Node(object):
                  ISIS compatible serial number
    """

    def __init__(self, image_name, image_path):
    def __init__(self, image_name=None, image_path=None):
        self.image_name = image_name
        self.image_path = image_path
        self._masks = set()
@@ -476,10 +474,6 @@ class Node(object):
                self._isis_serial = None
        return self._isis_serial

    def update(self, *args):
        # Empty pass method to get NetworkX to accept a non-dict
        pass


class CandidateGraph(nx.Graph):
    """
@@ -497,6 +491,7 @@ class CandidateGraph(nx.Graph):

    ----------
    """
    edge_attr_dict_factory = Edge

    def __init__(self,*args, basepath=None, **kwargs):
        super(CandidateGraph, self).__init__(*args, **kwargs)
@@ -928,7 +923,6 @@ class CandidateGraph(nx.Graph):

    # TODO: The Edge object requires a get method in order to be plottable, probably Node as well.
    # This is a function of being a dict in NetworkX
    '''
    def plot(self, ax=None, **kwargs):
        """
        Plot the graph object
@@ -944,4 +938,3 @@ class CandidateGraph(nx.Graph):
           A MatPlotLib axes object
        """
        return plot_graph(self, ax=ax,  **kwargs)
    '''
 No newline at end of file
+24 −18
Original line number Diff line number Diff line
%% Cell type:code id: tags:

``` python
import os
import sys
sys.path.insert(0, os.path.abspath('..'))

from autocnet.examples import get_path
from autocnet.graph.network import CandidateGraph
from autocnet.matcher.matcher import FlannMatcher

from IPython.display import display

%pylab qt4
```

%% Output

    Populating the interactive namespace from numpy and matplotlib

%% Cell type:markdown id: tags:

## Generate a 2 image adjacenecy graph

%% Cell type:code id: tags:

``` python
#Point to the adjacency Graph
adjacency = get_path('two_image_adjacency.json')
basepath = get_path('Apollo15')
cg = CandidateGraph.from_adjacency(adjacency, basepath=basepath)

#Apply SIFT to extract features
cg.extract_features(method='sift')

#Match
cg.match_features(k=5)

#Apply outlier detection
cg.symmetry_checks()
cg.ratio_checks()

#Compute a homography and apply RANSAC
cg.compute_homographies(clean_keys=['ratio', 'symmetry'], ransacReprojThreshold=2.5)
```

%% Cell type:markdown id: tags:

### The graph object:
The underlying data structure is a graph, where each node is an image and each edge is the connectivity between nodes.  Nodes and Edges are classes with associated attributes and methods.  This notebook primarily focuses on the plotting functionality on the graph (and graph components).

In these notebooks, the graph object is being stored in the variable `cg`.  Access to nodes and edges is positional.

  * To access a node in the graph:  `cg[node_idx]`, e.g. `cg[0]`.
  * To access an edge in the graph: `cg[source_idx][destination_idx]`, e.g. `cg[0][1]`


%% Cell type:markdown id: tags:

## Plot the graph

%% Cell type:code id: tags:

``` python
cg.plot()
```

%% Output

    /Users/jlaura/anaconda3/envs/autocnet/lib/python3.5/site-packages/matplotlib/collections.py:650: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
      if self._edgecolors_original != str('face'):

    <matplotlib.axes._subplots.AxesSubplot at 0x123a76208>

%% Cell type:markdown id: tags:

## Plot features at an individual node, e.g. a single image

%% Cell type:markdown id: tags:

All defaults are used here.

%% Cell type:code id: tags:

``` python
cg.node[1].plot()
```

%% Output

    <matplotlib.axes._subplots.AxesSubplot at 0x106a721d0>

%% Cell type:markdown id: tags:

This example specifies a plotting layout, passing in an axis object and passes along a color.  All the MatPlotLib plotting arguments are supported.

%% Cell type:code id: tags:

``` python
ax1 = plt.subplot(1,1,1)
ax = cg.node[0].plot(ax=ax1, color='y')
```

%% Cell type:markdown id: tags:

## Plotting Matches on an Edge
The plotting capability on a given node is limited to a single image; one can envision the node as being the image with all associated metadata and derived information.  The edge represents the overlap between images and resultant shared information, e.g. point correspondences, a homography, etc.

%% Cell type:markdown id: tags:

#### Plot the matches between an edge using two outlier detector masks
To get a rough idea of what a 'good' results should be, we should see no, or few, lines which intersect.

%% Cell type:code id: tags:

``` python
fig, ax = plt.subplots(1,1)
ax = cg.edge[0][1].plot(clean_keys=['ratio', 'symmetry'], ax=ax)
```

%% Cell type:markdown id: tags:

#### Now plot with the added, ransac computed mask

%% Cell type:code id: tags:

``` python
cg.edge[0][1].plot
```

%% Cell type:code id: tags:

``` python
cg.edge[0][1].plot(clean_keys=['ratio', 'symmetry'], line_kwargs={'linewidth':0})
```

%% Output

    /Users/jlaura/anaconda3/envs/autocnet/lib/python3.5/site-packages/matplotlib/collections.py:590: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
      if self._edgecolors == str('face'):

    <matplotlib.axes._subplots.AxesSubplot at 0x135ac9f98>

%% Cell type:markdown id: tags:

## Compute Coverage Metric
We compute a coverage metric by utilizing the homography to project the destination image corner pixel coordinates into the source image and computing the intersection.  This is a rough estimate that is as good (or poor) as the homography.

%% Cell type:code id: tags:

``` python
#Ideal coverage would be 1.0
cg.edge[0][1].coverage_ratio(clean_keys=['ransac'])
```

%% Output

    /Users/jlaura/anaconda3/envs/autocnet/lib/python3.5/site-packages/matplotlib/collections.py:590: FutureWarning: elementwise comparison failed; returning scalar instead, but in the future will perform elementwise comparison
      if self._edgecolors == str('face'):

    0.31345856142763456

%% Cell type:markdown id: tags:

The above suggests that the quality is a function of the homography.  Just how good is the homography?  We can use the determinant (something near 1 is bad), the condition (a very large number, e.g. $10^15$ is bad), or the RMSE (reported in the x and y directions).

%% Cell type:code id: tags:

``` python
H = cg.edge[0][1].homography
print('Not zero is good:', H.determinant)
print('Not huge is good: ', H.condition)
print('Shifts less than one pixel in all directions are good:', H.rmse)
```

%% Cell type:markdown id: tags:

## Viewing Keypoint Information
Here we want to explore the attributes of the keypoints, using the masking information, e.g. the outlier detection methods.  The question is, what are the characteristics of those keypoints that have made it through the outlier detection.

%% Cell type:code id: tags:

``` python
skp, dkp = cg.edge[0][1].keypoints(clean_keys=['ratio', 'symmetry', 'ransac'])
display(skp)
display(dkp)
```

%% Cell type:markdown id: tags:

## Subpixel Register
We suggest only subpixel registering 'good' candidate matches as the subpixel registration process can be time consuming.

%% Cell type:code id: tags:

``` python
cg.edge[0][1].compute_subpixel_offset(clean_keys=['ransac', 'symmetry', 'ratio'])
```

%% Cell type:code id: tags:

``` python
cg.edge[0][1].plot(clean_keys=['subpixel'])
```

%% Cell type:code id: tags:

``` python
subp = cg.edge[0][1]._mask_arrays['subpixel']
print(cg.edge[0][1].subpixel_offsets.iloc[subp])
```

%% Cell type:code id: tags:

``` python
cg.to_cnet?
```

%% Cell type:code id: tags:

``` python
```