Commit a621692d authored by Jay's avatar Jay Committed by jay
Browse files

Reworked ratio testing to support n-images and return numpy boolean arrays.

parent ef8e83c6
Loading
Loading
Loading
Loading
+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
+11 −1
Original line number Diff line number Diff line
import operator
import os

import networkx as nx
@@ -236,7 +237,7 @@ class CandidateGraph(nx.Graph):
        else:
            return ('','')

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

@@ -244,11 +245,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']

+1 −101
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ def pattern_match(template, image, upsampling=10,

    return x, y, strength


class FlannMatcher(object):
    """
    A wrapper to the OpenCV Flann based matcher class that adds
@@ -168,104 +169,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')
+113 −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] = False
            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
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ class TestFeatureExtractor(unittest.TestCase):
    def test_extract_features(self):
        features = feature_extractor.extract_features(self.data_array, self.parameters)
        self.assertEquals(len(features), 2)
        self.assertEqual(len(features[0]), 11)  # OpenCV +1 to
        self.assertIn(len(features[0]), range(8,11))
        self.assertIsInstance(features[0][0], type(cv2.KeyPoint()))
        self.assertIsInstance(features[1][0], np.ndarray)
Loading