Commit 576b7402 authored by Kristin Berry's avatar Kristin Berry
Browse files

Merge pull request #26 from jlaura/master

Alterations to outlier detection and initial 3 image support
parents fb6eb862 0cd05e86
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -27,7 +27,7 @@ install:
  - conda info -a

  # Create a virtual env and install dependencies
  - conda create -y -q -n test-env python=$TRAVIS_PYTHON_VERSION nose gdal numpy pillow scipy pandas networkx
  - conda create -y -q -n test-env python=$TRAVIS_PYTHON_VERSION nose gdal numpy pillow scipy pandas networkx scikit-image
  # Activate the env
  - source activate test-env

+0 −2
Original line number Diff line number Diff line
@@ -132,7 +132,6 @@ class IsisStore(object):
        """
        point_sizes = []
        point_messages = []
        print(cnet)
        for pid, point in cnet.groupby('pid'):
            # Instantiate the proto spec
            point_spec = cnf.ControlPointFileEntryV0002()
@@ -146,7 +145,6 @@ class IsisStore(object):

            # A single extend call is cheaper than many add calls to pack points
            measure_iterable = []
            print(point)
            for name, row in point.iterrows():
                measure_spec = point_spec.Measure()
                measure_spec.serialnumber = row.nid
+28 −4
Original line number Diff line number Diff line
import operator
import os

import networkx as nx
@@ -202,9 +203,17 @@ class CandidateGraph(nx.Graph):
                     The identifier for the source node
        destination_key : str
                          The identifier for the destination node

        outlier_algorithm : object
                            An openCV outlier detections algorithm, e.g. cv2.RANSAC
        Returns
        -------
         : tuple
        transformation_matrix : ndarray
                                The 3x3 transformation matrix

        mask : ndarray
               Boolean array of the outliers

           A tuple of the form (transformation matrix, bad entry mask)
           The returned tuple is empty if there is no edge between the source and destination nodes or
           if it exists, but has not been populated with a matches dataframe.
@@ -229,14 +238,20 @@ class CandidateGraph(nx.Graph):

                    source_keypoints.append(src_keypoint)
                    destination_keypoints.append(dest_keypoint)
                return cv2.findHomography(np.array(source_keypoints), np.array(destination_keypoints),
                                          outlier_algorithm, 5.0)
                transformation_matrix, mask = cv2.findHomography(np.array(source_keypoints),
                                                                 np.array(destination_keypoints),
                                                                 outlier_algorithm,
                                                                 5.0)
                mask = mask.astype(bool)
                return transformation_matrix, mask
            else:
                return ('', '')
        else:
            return ('','')

    def to_cnet(self):


    def to_cnet(self, clean_keys=[]):
        """
        Generate a control network (C) object from a graph

@@ -244,11 +259,20 @@ class CandidateGraph(nx.Graph):
        -------
        merged_cnet : C
                      A control network object

        clean_keys : list
                     of strings identifying the masking arrays to use, e.g. ratio, symmetry
        """
        merged_cnet = None

        for source, destination, attributes in self.edges_iter(data=True):
            matches = attributes['matches']

            # Merge all of the masks
            if clean_keys:
                mask = np.array(list(map(operator.mul, *[attributes[i] for i in clean_keys])))
                matches = matches[mask]

            kp1 = self.node[source]['keypoints']
            kp2 = self.node[destination]['keypoints']

+75 −103
Original line number Diff line number Diff line
import cv2
import numpy as np
import pandas as pd
from autocnet.graph.network import CandidateGraph

import numpy as np
from skimage.feature import match_template
from scipy.misc import imresize

FLANN_INDEX_KDTREE = 1  # Algorithm to set centers,
DEFAULT_FLANN_PARAMETERS = dict(algorithm=FLANN_INDEX_KDTREE,
                                trees=3)


def pattern_match(template, image, upsampling=10,
                  func=match_template):
    """
    Call an arbitrary pattern matcher

    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image

    image : ndarray
            The image or sub-image to be searched

    upsampling : int
                 The multiplier to upsample the template and image.

    func : object
           The function to be used to perform the template based matching

    Returns
    -------

    x : float
        The x offset

    y : float
        The y offset

    strength : float
               The strength of the correlation in the range [-1, 1].



    """
    if upsampling < 1:
        raise ValueError

    u_template = imresize(template, (template.shape[0] * upsampling,
                                   template.shape[1] * upsampling),
                        interp='bicubic')

    u_image = imresize(image, (image.shape[0] * upsampling,
                             image.shape[1] * upsampling),
                     interp='bicubic')

    # Find the the upper left origin of the template in the image
    match = func(u_image, u_template)
    y, x = np.unravel_index(np.argmax(match), match.shape)

    # Resample the match back to the native image resolution
    x /= upsampling
    y /= upsampling

    # Offset from the UL origin to the image center
    x += (template.shape[1] / 2)
    y += (template.shape[0] / 2)

    # Compute the offset to adjust the image match point location
    ideal_y = image.shape[0] / 2
    ideal_x = image.shape[1] / 2

    x = ideal_x - x
    y = ideal_y - y

    # Find the maximum correlation
    strength = np.max(match)

    return x, y, strength


class FlannMatcher(object):
    """
    A wrapper to the OpenCV Flann based matcher class that adds
@@ -100,104 +173,3 @@ class FlannMatcher(object):
        return pd.DataFrame(matched, columns=['source_image', 'source_idx',
                                              'destination_image', 'destination_idx',
                                              'distance'])

class OutlierDetector(object):
    """
    A class which contains several outlier detection methods which all return
    True/False masks as pandas data series, which can be used as masks for
    the "matches" pandas dataframe which stores match information for each
    edge of the graph.

    Attributes
    ----------

    """
    def __init__(self):
        pass

    # (query only takes care of literal self-matches on a keypoint basis, not self-matches for the whole image)
    def self_neighbors(self, matches):
        """
        Returns a pandas data series intended to be used as a mask. Each row
        is True if it is not matched to a point in the same image (good) and
        False if it is (bad.)

        Parameters
        ----------
        matches : dataframe
                  the matches dataframe stored along the edge of the graph
                  containing matched points with columns containing:
                  matched image name, query index, train index, and
                  descriptor distance
        Returns
        -------
        : dataseries
          Intended to mask the matches dataframe. True means the row is not matched to a point in the same image
          and false the row is.
        """
        return matches.source_image != matches.destination_image

    def distance_ratio(self, matches, ratio=0.8):
        """
        Compute and return a mask for the matches dataframe stored on each edge of the graph
        using the ratio test and distance_ratio set during initialization.

        Parameters
        ----------
        matches : dataframe
                  the matches dataframe stored along the edge of the graph
                  containing matched points with columns containing:
                  matched image name, query index, train index, and
                  descriptor distance. ***Will only work as expected if matches already has dropped duplicates***

        ratio: float
               the ratio between the first and second-best match distances
               for each keypoint to use as a bound for marking the first keypoint
               as "good."
        Returns
        -------
         : dataseries
           Intended to mask the matches dataframe. Rows are True if the associated keypoint passes
           the ratio test and false otherwise. Keypoints without more than one match are True by
           default, since the ratio test will not work for them.
        """
        #0.8 is Lowe's paper value -- can be changed.
        mask = []
        temp_matches = matches.drop_duplicates() #don't want to deal with duplicates...
        for key, group in temp_matches.groupby('source_idx'):
            #won't work if there's only 1 match for each queryIdx
            if len(group) < 2:
                mask.append(True)
            else:
                if group['distance'].iloc[0] < ratio * group['distance'].iloc[1]: #this means distance _0_ is good and can drop all other distances
                    mask.append(True)
                    for i in range(len(group['distance']-1)):
                        mask.append(False)
                else:
                    for i in range(len(group['distance'])):
                        mask.append(False)
        return pd.Series(mask)

    def mirroring_test(self, matches):
        """
        Compute and return a mask for the matches dataframe on each edge of the graph which
        will keep only entries in which there is both a source -> destination match and a destination ->
        source match.

        Parameters
        ----------
        matches : dataframe
                  the matches dataframe stored along the edge of the graph
                  containing matched points with columns containing:
                  matched image name, query index, train index, and
                  descriptor distance

        Returns
        -------
         : dataseries
           Intended to mask the matches dataframe. Rows are True if the associated keypoint passes
           the mirroring test and false otherwise. That is, if 1->2, 2->1, both rows will be True,
           otherwise, they will be false. Keypoints with only one match will be False. Removes
           duplicate rows.
        """
        return matches.duplicated(keep='first')
+114 −0
Original line number Diff line number Diff line
import numpy as np


def self_neighbors(matches):
    """
    Returns a pandas data series intended to be used as a mask. Each row
    is True if it is not matched to a point in the same image (good) and
    False if it is (bad.)

    Parameters
    ----------
    matches : dataframe
              the matches dataframe stored along the edge of the graph
              containing matched points with columns containing:
              matched image name, query index, train index, and
              descriptor distance
    Returns
    -------
    : dataseries
      Intended to mask the matches dataframe. True means the row is not matched to a point in the same image
      and false the row is.
    """
    return matches.source_image != matches.destination_image


def distance_ratio(matches, ratio=0.8):
    """
    Compute and return a mask for a matches dataframe
    using Lowe's ratio test.
    Lowe (2004) [Lowe2004]_

    Parameters
    ----------
    matches : dataframe
              the matches dataframe stored along the edge of the graph
              containing matched points with columns containing:
              matched image name, query index, train index, and
              descriptor distance.

    ratio : float
            the ratio between the first and second-best match distances
            for each keypoint to use as a bound for marking the first keypoint
            as "good". Default: 0.8
    Returns
    -------
     mask : ndarray
            Intended to mask the matches dataframe. Rows are True if the associated keypoint passes
            the ratio test and false otherwise. Keypoints without more than one match are True by
            default, since the ratio test will not work for them.

    """

    mask = np.zeros(len(matches), dtype=bool)  # Pre-allocate the mask
    counter = 0
    for i, group in matches.groupby('source_idx'):
        group_size = len(group)
        # If we can not perform the ratio check because all matches are symmetrical
        if len(group['destination_idx'].unique()) == 1:
            mask[counter:counter + group_size] = True
            counter += group_size
        else:
            # Otherwise, we can perform the ratio test
            sorted = group.sort_values(by=['distance'])
            unique = sorted['distance'].unique()
            if unique[0] < ratio * unique[1]:
                mask[counter] = True
                mask[counter + 1:counter + group_size] = False
                counter += group_size
            else:
                mask[counter: counter + group_size] = False
                counter += group_size

        '''
        # won't work if there's only 1 match for each queryIdx
        if len(group) < 2:
            mask.append(True)
        else:
            if group['distance'].iloc[0] < ratio * group['distance'].iloc[1]: # this means distance _0_ is good and can drop all other distances
                mask.append(True)
                for i in range(len(group['distance']-1)):
                    mask.append(False)
            else:
                for i in range(len(group['distance'])):
                    mask.append(False)
        '''
    return mask


def mirroring_test(matches):
    """
    Compute and return a mask for the matches dataframe on each edge of the graph which
    will keep only entries in which there is both a source -> destination match and a destination ->
    source match.

    Parameters
    ----------
    matches : dataframe
              the matches dataframe stored along the edge of the graph
              containing matched points with columns containing:
              matched image name, query index, train index, and
              descriptor distance

    Returns
    -------
    duplicates : dataseries
                 Intended to mask the matches dataframe. Rows are True if the associated keypoint passes
                 the mirroring test and false otherwise. That is, if 1->2, 2->1, both rows will be True,
                 otherwise, they will be false. Keypoints with only one match will be False. Removes
                 duplicate rows.
    """
    duplicates = matches.duplicated(keep='first').values
    duplicates.astype(bool, copy=False)
    return duplicates
Loading