Commit 9221d887 authored by Jason R Laura's avatar Jason R Laura Committed by jay
Browse files

Fixes suppression

parent 48c758b5
Loading
Loading
Loading
Loading
+7 −1
Changes for autocnet/__init__.py: 7 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -35,8 +35,11 @@ def cuda(enable=False, gpu=0):

            from autocnet.matcher.cuda_matcher import match
            Edge.match = match

            from autocnet.matcher.cuda_decompose import decompose_and_match
            Edge.decompose_and_match = decompose_and_match
        except Exception:
            print('Failed to enable cuda')
            print('Failed to enable Cuda')
        return

    print('CUDA Disabled')
@@ -46,4 +49,7 @@ def cuda(enable=False, gpu=0):

    from autocnet.matcher.feature_matcher import match
    Edge.match = match

    from autocnet.matcher.cpu_decompose import decompose_and_match
    Edge.decompose_and_match = decompose_and_match
cuda()
+249 −0
Changes for autocnet/matcher/cpu_decompose.py: 249 added lines, 0 removed lines.
Original line number Diff line number Diff line
import numpy as np
from scipy.spatial.distance import cdist

from autocnet.matcher.feature import FlannMatcher
from autocnet.transformation.decompose import coupled_decomposition


def decompose_and_match(self, k=2, maxiteration=3, size=18, buf_dist=3,**kwargs):
    """
    Similar to match, this method first decomposed the image into
    $4^{maxiteration}$ subimages and applys matching between each sub-image.

    This method is potential slower than the standard match due to the
    overhead in matching, but can be significantly more accurate.  The
    increase in accuracy is a function of the total image size.  Suggested
    values for maxiteration are provided below.

    Parameters
    ----------
    k : int
        The number of neighbors to find

    method : {'coupled', 'whole'}
             whether to utilize coupled decomposition
             or match the whole image

    maxiteration : int
                   When using coupled decomposition, the number of recursive
                   divisions to apply.  The total number of resultant
                   sub-images will be 4 ** maxiteration.  Approximate values:

                    | Number of megapixels | maxiteration |
                    |----------------------|--------------|
                    | m < 10               |1-2|
                    | 10 < m < 30          | 3 |
                    | 30 < m < 100         | 4 |
                    | 100 < m < 1000       | 5 |
                    | m > 1000             | 6 |

    size : int
           When using coupled decomposition, the total number of points
           to check in each sub-image to try and find a match.
           Selection of this number is a balance between seeking a
           representative mid-point and computational cost.

    buf_dist : int
               When using coupled decomposition, the distance from the edge of
               the (sub)image a point must be in order to be used as a
               partioning point.  The smaller the distance, the more likely
               percision errors can results in erroneous partitions.
    """
    def mono_matches(a, b, aidx=None, bidx=None):
        """
        Apply the FLANN match_features

        Parameters
        ----------
        a : object
            A node object

        b : object
            A node object

        aidx : iterable
               An index for the descriptors to subset

        bidx : iterable
               An index for the descriptors to subset
        """
        # Subset if requested
        if aidx is not None:
            ad = a.descriptors[aidx]
        else:
            ad = a.descriptors

        if bidx is not None:
            bd = b.descriptors[bidx]
        else:
            bd = b.descriptors

        # Load, train, and match
        fl.add(ad, a.node_id, index=aidx)
        fl.train()
        matches = fl.query(bd, b.node_id, k, index=bidx)
        if self.matches is None:
            self.matches = matches
        else:
            df = self.matches
            self.matches = df.append(matches,
                                     ignore_index=True,
                                     verify_integrity=True)
        fl.clear()

    def func(group):
        ratio = 0.8
        res = [False] * len(group)
        if len(res) == 1:
            return [single]
        if group.iloc[0] < group.iloc[1] * ratio:
            res[0] = True
        return res

    # Grab the original image arrays
    sdata = self.source.get_array()
    ddata = self.destination.get_array()

    ssize = sdata.shape
    dsize = ddata.shape

    # Grab all the available candidate keypoints
    skp = self.source.get_keypoints()
    dkp = self.destination.get_keypoints()

    # Set up the membership arrays
    self.smembership = np.zeros(sdata.shape, dtype=np.int16)
    self.dmembership = np.zeros(ddata.shape, dtype=np.int16)
    self.smembership[:] = -1
    self.dmembership[:] = -1
    pcounter = 0

    # FLANN Matcher
    fl= FlannMatcher()

    for k in range(maxiteration):
        partitions = np.unique(self.smembership)
        for p in partitions:
            sy_part, sx_part = np.where(self.smembership == p)
            dy_part, dx_part = np.where(self.dmembership == p)

            # Get the source extent
            minsy = np.min(sy_part)
            maxsy = np.max(sy_part) + 1
            minsx = np.min(sx_part)
            maxsx = np.max(sx_part) + 1

            # Get the destination extent
            mindy = np.min(dy_part)
            maxdy = np.max(dy_part) + 1
            mindx = np.min(dx_part)
            maxdx = np.max(dx_part) + 1

            # Clip the sub image from the full images
            asub = sdata[minsy:maxsy, minsx:maxsx]
            bsub = ddata[mindy:maxdy, mindx:maxdx]

            # Utilize the FLANN matcher to find a match to approximate a center
            fl.add(self.destination.descriptors, self.destination.node_id)
            fl.train()

            scounter = 0
            decompose = False
            while True:
                sub_skp = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy))
                # Check the size to ensure a valid return
                if len(sub_skp) == 0:
                    break # No valid keypoints in this (sub)image
                if size > len(sub_skp):
                    size = len(sub_skp)
                candidate_idx = np.random.choice(sub_skp.index, size=size, replace=False)
                candidates = self.source.descriptors[candidate_idx]
                matches = fl.query(candidates, self.source.node_id, k=3, index=candidate_idx)

                # Apply Lowe's ratio test to try to find a 'good' starting point
                mask = matches.groupby('source_idx')['distance'].transform(func).astype('bool')
                candidate_matches = matches[mask]
                match_idx = candidate_matches['source_idx']

                # Extract those matches that pass the ratio check
                sub_skp = skp.iloc[match_idx]

                # Check that valid points remain
                if len(sub_skp) == 0:
                    break

                # Locate the candidate closest to the middle of all of the matches
                smx, smy = sub_skp[['x', 'y']].mean()
                mid = np.array([[smx, smy]])
                dists = cdist(mid, sub_skp[['x', 'y']])
                closest = sub_skp.iloc[np.argmin(dists)]
                closest_idx = closest.name
                soriginx, soriginy = closest[['x', 'y']]

                # Grab the corresponding point in the destination
                q = candidate_matches.query('source_idx == {}'.format(closest.name))
                dest_idx = q['destination_idx'].iat[0]
                doriginx = dkp.at[dest_idx, 'x']
                doriginy = dkp.at[dest_idx, 'y']

                if mindy + buf_dist <= doriginy <= maxdy - buf_dist\
                 and mindx + 3 <= doriginx <= maxdx - 3:
                    # Point is good to split on
                    decompose = True
                    break
                else:
                    scounter += 1
                    if scounter >= maxiteration:
                        break

            # Clear the Flann matcher for reuse
            fl.clear()

            # Check that the identified match falls within the (sub)image
            # This catches most bad matches that have passed the ratio check
            if not (buf_dist <= doriginx - mindx <= bsub.shape[1] - buf_dist) or not\
                   (buf_dist <= doriginy - mindy <= bsub.shape[0] - buf_dist):
                   decompose = False

            if decompose:
                # Apply coupled decomposition, shifting the origin to the sub-image
                s_submembership, d_submembership = coupled_decomposition(asub, bsub,
                                                                     sorigin=(soriginx - minsx, soriginy - minsy),
                                                                     dorigin=(doriginx - mindx, doriginy - mindy),
                                                                     **kwargs)

                # Shift the returned membership counters to a set of unique numbers
                s_submembership += pcounter
                d_submembership += pcounter

                # And assign membership
                self.smembership[minsy:maxsy,
                            minsx:maxsx] = s_submembership
                self.dmembership[mindy:maxdy,
                            mindx:maxdx] = d_submembership
                pcounter += 4

    # Now match the decomposed segments to one another
    for p in np.unique(self.smembership):
        sy_part, sx_part = np.where(self.smembership == p)
        dy_part, dx_part = np.where(self.dmembership == p)

        # Get the source extent
        minsy = np.min(sy_part)
        maxsy = np.max(sy_part) + 1
        minsx = np.min(sx_part)
        maxsx = np.max(sx_part) + 1

        # Get the destination extent
        mindy = np.min(dy_part)
        maxdy = np.max(dy_part) + 1
        mindx = np.min(dx_part)
        maxdx = np.max(dx_part) + 1

        # Get the indices of the candidate keypoints within those regions / variables are pulled before decomp.
        sidx = skp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(minsx, maxsx, minsy, maxsy)).index
        didx = dkp.query('x >= {} and x <= {} and y >= {} and y <= {}'.format(mindx, maxdx, mindy, maxdy)).index
        # If the candidates < k, OpenCV throws an error
        if len(sidx) >= k and len(didx) >=k:
            mono_matches(self.source, self.destination, sidx, didx)
            mono_matches(self.destination, self.source, didx, sidx)
+37 −0
Changes for autocnet/matcher/cuda_decompose.py: 37 added lines, 0 removed lines.
Original line number Diff line number Diff line
import cudasift as cs
import numpy as np
import pandas as pd


def decompose_and_match(self, maxiterations=3, ratio=0.9, buf_dist=3, **kwargs):
    sdata = self.source.get_array()
    ddata = self.destination.get_array()

    ret = cs.PyDecomposeAndMatch(sdata, ddata, maxiterations=maxiterations,
                                 ratio=ratio, buf_dist=buf_dist, **kwargs)

    self.smembership = ret[0]
    self.dmembership = ret[1]
    self.source._keypoints = skps = ret[3]
    self.source.descriptors = ret[4]
    self.destination._keypoints = dkps = ret[5]
    self.destination.descriptors = ddesc = ret[6]

    # Parse the matches by decomposed sections into a global matches dataframe
    raw_matches = ret[2]

    columns = ['source_image', 'source_idx', 'destination_image',
               'destination_idx', 'score', 'ambiguity']

    m = np.empty((len(raw_matches), len(columns)))
    m[:,0] = self.source.node_id
    m[:,1] = raw_matches.index
    m[:,2] = self.destination.node_id
    m[:,3] = raw_matches['match']
    m[:,4] = raw_matches['score']
    m[:,5] = raw_matches['ambiguity']

    self.matches = pd.DataFrame(m, columns=columns)

    # Set the ratio mask
    self.masks = ('ratio', self.matches['ambiguity'] <= ratio)
+20 −1
Changes for autocnet/matcher/feature_matcher.py: 20 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -13,6 +13,25 @@ def match(self, k=2, **kwargs):
    k : int
	The number of neighbors to find
    """

    def _add_matches(matches):
        """
        Given a dataframe of matches, either append to an existing
        matches edge attribute or initially populate said attribute.

        Parameters
        ----------
        matches : dataframe
                  A dataframe of matches
        """
        if self.matches is None:
            self.matches = matches
        else:
            df = self.matches
            self.matches = df.append(matches,
                                     ignore_index=True,
                                     verify_integrity=True)

    def mono_matches(a, b, aidx=None, bidx=None):
        """
	    Apply the FLANN match_features
@@ -46,7 +65,7 @@ def match(self, k=2, **kwargs):
        fl.add(ad, a.node_id, index=aidx)
        fl.train()
        matches = fl.query(bd, b.node_id, k, index=bidx)
        self._add_matches(matches)
        _add_matches(matches)
        fl.clear()

    fl = FlannMatcher()
+1 −0
Changes for autocnet/vis/graph_view.py: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -181,6 +181,7 @@ def plot_edge_decomposition(edge, ax=None, clean_keys=[], image_space=100,
        ax.plot((l[0][0], l[1][0]), (l[0][1], l[1][1]), color=color, **line_kwargs)

    return ax

def plot_edge(edge, ax=None, clean_keys=[], image_space=100,
              scatter_kwargs={}, line_kwargs={}, image_kwargs={}):
    """
+2 −2

File changed.

Contains only whitespace changes.