Commit 261a67fe authored by Kristin Berry's avatar Kristin Berry
Browse files

Merge pull request #52 from jlaura/master

Fundamental Matrix and misc. cleanup
parents 82e1fdd0 7772d1c2
Loading
Loading
Loading
Loading
+24 −24
Original line number Diff line number Diff line
{"autocnet/examples/Apollo15/AS15-M-0297_SML.png"
 : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0300_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0299_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0296_SML.png"],
 "autocnet/examples/Apollo15/AS15-M-0300_SML.png"
 : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0297_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0299_SML.png"],
 "autocnet/examples/Apollo15/AS15-M-0299_SML.png"
 : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0297_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0300_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0296_SML.png"],
 "autocnet/examples/Apollo15/AS15-M-0295_SML.png"
{"AS15-M-0297_SML.png"
 : ["AS15-M-0298_SML.png",
    "AS15-M-0300_SML.png",
    "AS15-M-0299_SML.png",
    "AS15-M-0296_SML.png"],
 "AS15-M-0300_SML.png"
 : ["AS15-M-0298_SML.png",
    "AS15-M-0297_SML.png",
    "AS15-M-0299_SML.png"],
 "AS15-M-0299_SML.png"
 : ["AS15-M-0298_SML.png",
    "AS15-M-0297_SML.png",
    "AS15-M-0300_SML.png",
    "AS15-M-0296_SML.png"],
 "AS15-M-0295_SML.png"
 : [], 
 "autocnet/examples/Apollo15/AS15-M-0296_SML.png"
 : ["autocnet/examples/Apollo15/AS15-M-0298_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0297_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0299_SML.png"],
 "autocnet/examples/Apollo15/AS15-M-0298_SML.png"
 : ["autocnet/examples/Apollo15/AS15-M-0297_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0300_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0299_SML.png",
    "autocnet/examples/Apollo15/AS15-M-0296_SML.png"]}
 "AS15-M-0296_SML.png"
 : ["AS15-M-0298_SML.png",
    "AS15-M-0297_SML.png",
    "AS15-M-0299_SML.png"],
 "AS15-M-0298_SML.png"
 : ["AS15-M-0297_SML.png",
    "AS15-M-0300_SML.png",
    "AS15-M-0299_SML.png",
    "AS15-M-0296_SML.png"]}
+43 −33
Original line number Diff line number Diff line
from collections import MutableMapping
import warnings
from collections import MutableMapping

import numpy as np
import pandas as pd
from pysal.cg.shapes import Polygon

from autocnet.matcher import subpixel as sp
from autocnet.matcher.homography import Homography
from autocnet.cg.cg import convex_hull_ratio
from autocnet.cg.cg import overlapping_polygon_area
from autocnet.vis.graph_view import plot_edge
from autocnet.matcher import health
from autocnet.matcher import outlier_detector as od
from autocnet.cg.cg import convex_hull_ratio
from autocnet.matcher import subpixel as sp
from autocnet.transformation.transformations import FundamentalMatrix, Homography
from autocnet.vis.graph_view import plot_edge


class Edge(dict, MutableMapping):
@@ -35,10 +36,14 @@ class Edge(dict, MutableMapping):
        self.source = source
        self.destination = destination

        self._homography = None
        self.homography = None
        self.fundamental_matrix = None
        self._subpixel_offsets = None
        self.provenance = {}
        self._pid = 0

        self._observers = set()

        #Subscribe the heatlh observer
        self._health = health.EdgeHealth()

    def __repr__(self):
        return """
@@ -49,34 +54,29 @@ class Edge(dict, MutableMapping):

    @property
    def masks(self):
        mask_lookup = {'fundamental': 'fundamental_matrix'}
        if not hasattr(self, '_masks'):
            self._masks = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)})
            if hasattr(self, 'matches'):
                self._masks = pd.DataFrame(True, columns=['symmetry', 'ratio'],
                                       index=self.matches.index)
            else:
                self._masks = pd.DataFrame()
        # If the mask is coming form another object that tracks
        # state, dynamically draw the mask from the object.
        for c in self._masks.columns:
            if c in mask_lookup:
                self._masks[c] = getattr(self, mask_lookup[c]).mask
        return self._masks

    @masks.setter
    def masks(self, v):
        column_name = v[0]
        boolean_mask = v[1]
        current = self.masks[self._pid]
        current[column_name] = boolean_mask

    @property
    def error(self):
        if not hasattr(self, '_error'):
            self._error = pd.Panel({self._pid: pd.DataFrame(index=self.matches.index)})
        return self._error

    @error.setter
    def error(self, v):
        pass
        self.masks[column_name] = boolean_mask

    @property
    def homography(self):
        return self._homography

    @homography.setter
    def homography(self, v):
        self._homography = v
    def health(self):
        return self._health.health

    def keypoints(self, clean_keys=[]):
        """
@@ -124,6 +124,9 @@ class Edge(dict, MutableMapping):
        else:
            raise AttributeError('Matches have not been computed for this edge')

        all_source_keypoints = self.source.keypoints.iloc[matches['source_idx']]
        all_destin_keypoints = self.destination.keypoints.iloc[matches['destination_idx']]

        if clean_keys:
            matches, mask = self._clean(clean_keys)

@@ -140,8 +143,17 @@ class Edge(dict, MutableMapping):
            mask[mask == True] = fundam_mask
        else:
            mask = fundam_mask
        self.fundamental_matrix = FundamentalMatrix(transformation_matrix,
                                                    all_source_keypoints[['x', 'y']],
                                                    all_destin_keypoints[['x', 'y']],
                                                    mask=mask)

        # Subscribe the health watcher to the fundamental matrix observable
        self.fundamental_matrix.subscribe(self._health.update)
        self.fundamental_matrix._notify_subscribers(self.fundamental_matrix)

        # Set the initial state of the fundamental mask in the masks
        self.masks = ('fundamental', mask)
        self.fundamental_matrix = transformation_matrix

    def compute_homography(self, method='ransac', clean_keys=[], pid=None, **kwargs):
        """
@@ -188,7 +200,7 @@ class Edge(dict, MutableMapping):
        self.homography = Homography(transformation_matrix,
                                     s_keypoints[ransac_mask][['x', 'y']],
                                     d_keypoints[ransac_mask][['x', 'y']],
                                     index=mask[mask == True].index)
                                     mask=mask[mask == True].index)

        # Finalize the array to get custom attrs to propagate
        self.homography.__array_finalize__(self.homography)
@@ -362,9 +374,7 @@ class Edge(dict, MutableMapping):
        mask : series
                    A boolean series to inflate back to the full match set
        """
        if not pid:
            pid = self._pid
        panel = self.masks[pid]
        panel = self.masks
        mask = panel[clean_keys].all(axis=1)
        matches = self.matches[mask]
        return matches, mask
+8 −6
Original line number Diff line number Diff line
@@ -175,7 +175,6 @@ class CandidateGraph(nx.Graph):

        raise NotImplementedError
        self.add_node(self.node_counter, *args, **kwargs)
        #self.node_labels[self.node[self.node_counter]['image_name']] = self.node_counter
        self.node_counter += 1

    def extract_features(self, method='orb', extractor_parameters={}):
@@ -225,7 +224,7 @@ class CandidateGraph(nx.Graph):
    def add_matches(self, matches):
        """
        Adds match data to a node and attributes the data to the
        appropriate edges, e.g. if A-B have a match, edge A-B is attributes
        appropriate edges, e.g. if A-B have a match, edge A-B is attributed
        with the pandas dataframe.

        Parameters
@@ -233,13 +232,16 @@ class CandidateGraph(nx.Graph):
        matches : dataframe
                  The pandas dataframe containing the matches
        """
        edges = self.edges()
        source_groups = matches.groupby('source_image')
        for i, source_group in source_groups:
            for j, dest_group in source_group.groupby('destination_image'):
                source_key = dest_group['source_image'].values[0]
                destination_key = dest_group['destination_image'].values[0]

                if (source_key, destination_key) in edges:
                    edge = self.edge[source_key][destination_key]
                else:
                    edge = self.edge[destination_key][source_key]

                if hasattr(edge, 'matches'):
                    df = edge.matches
+12 −6
Original line number Diff line number Diff line
@@ -15,7 +15,10 @@ class TestCandidateGraph(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json'))
        basepath = get_path('Apollo15')
        cls.graph = network.CandidateGraph.from_adjacency(get_path('three_image_adjacency.json'),
                                                          basepath=basepath)
        cls.disconnected_graph = network.CandidateGraph.from_adjacency(get_path('adjacency.json'))

    def test_get_name(self):
        node_number = self.graph.node_name_map['AS15-M-0297_SML.png']
@@ -35,18 +38,21 @@ class TestCandidateGraph(unittest.TestCase):
            pass

    def test_island_nodes(self):
        self.assertEqual(len(self.graph.island_nodes()), 1)
        self.assertEqual(len(self.disconnected_graph.island_nodes()), 1)

    def test_connected_subgraphs(self):
        subgraph_list = self.graph.connected_subgraphs()
        subgraph_list = self.disconnected_graph.connected_subgraphs()
        self.assertEqual(len(subgraph_list), 2)
        island = self.graph.island_nodes()[0]
        self.assertTrue(island in subgraph_list[1])
        islands = self.disconnected_graph.island_nodes()
        self.assertTrue(islands[0] in subgraph_list[1])

        subgraph_list = self.graph.connected_subgraphs()
        self.assertEqual(len(subgraph_list), 1)


    def test_save_load(self):
        self.graph.save('test_save.cg')
        loaded = self.graph.from_graph('test_save.cg')

        self.assertEqual(self.graph.node[0].nkeypoints, loaded.node[0].nkeypoints)
        self.assertEqual(self.graph.edge[0][1], loaded.edge[0][1])

+29 −0
Original line number Diff line number Diff line


class EdgeHealth(object):
    """
    Storage and computation of the health of a graph edge using the metric:


    """
    def __init__(self):
        self.FundamentalMatrix = 0.0

    @property
    def health(self):
        return self.recompute_health()

    def update(self, *args, **kwargs):
        """
        Pass through called when the observable (model) changes.
        *args and **kwargs are passed through from the observable.
        """
        for a in args:
            if hasattr(self, a.__class__.__name__):
                setattr(self, a.__class__.__name__, a)

    def recompute_health(self):
        """
        Recompute the health of the edge.
        """
        return self.FundamentalMatrix.error.mean()
Loading