Commit 0bef87c5 authored by Jay's avatar Jay
Browse files

merging CUDA stuff into saving

parents 6c193120 7afe625e
Loading
Loading
Loading
Loading
+43 −8
Changes for autocnet/__init__.py: 43 added lines, 8 removed lines.
Original line number Diff line number Diff line
import os
import autocnet

__version__ = "0.1.0"

def get_data(filename):
    packagdir = autocnet.__path__[0]
    dirname = os.path.join(os.path.dirname(packagdir), 'data')
    fullname = os.path.join(dirname, filename)
    return fullname

import autocnet.examples
import autocnet.camera
import autocnet.cg
@@ -18,3 +10,46 @@ import autocnet.matcher
import autocnet.transformation
import autocnet.utils
import autocnet.utils

__version__ = "0.1.0"

def get_data(filename):
    packagdir = autocnet.__path__[0]
    dirname = os.path.join(os.path.dirname(packagdir), 'data')
    fullname = os.path.join(dirname, filename)
    return fullname

def cuda(enable=False, gpu=0):
    # Classes/Methods that can vary if GPU is available
    from autocnet.graph.node import Node
    from autocnet.graph.edge import Edge
    if enable:
        print('Enabling CUDA')
        try:
            import cudasift as cs
            cs.PyInitCuda(gpu)

            # Here is where the GPU methods get patched into the class
            from autocnet.matcher.cuda_extractor import extract_features
            Node._extract_features = staticmethod(extract_features)

            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')
        return

    print('CUDA Disabled')
    # Here is where the CPU methods get patched into the class
    from autocnet.matcher.feature_extractor import extract_features
    Node._extract_features = staticmethod(extract_features)

    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()
+36 −61
Changes for autocnet/graph/edge.py: 36 added lines, 61 removed lines.
Original line number Diff line number Diff line
@@ -6,13 +6,12 @@ import pandas as pd

from scipy.spatial.distance import cdist

import autocnet
from autocnet.utils import utils
from autocnet.matcher import health
from autocnet.matcher import outlier_detector as od
from autocnet.matcher import suppression_funcs as spf
from autocnet.matcher import subpixel as sp
from autocnet.matcher.feature import FlannMatcher
from autocnet.transformation.decompose import coupled_decomposition
from autocnet.transformation.transformations import FundamentalMatrix, Homography
from autocnet.vis.graph_view import plot_edge, plot_node, plot_edge_decomposition
from autocnet.cg import cg
@@ -74,9 +73,13 @@ class Edge(dict, MutableMapping):
        # state, dynamically draw the mask from the object.
        for c in self._masks.columns:
            if c in mask_lookup:
                try:
                    truncated_mask = getattr(self, mask_lookup[c]).mask
                    self._masks[c] = False
                    self._masks[c].iloc[truncated_mask.index] = truncated_mask
                except Exception:
                    #TODO: Get rid of state
                    pass
        return self._masks

    @masks.setter
@@ -321,8 +324,16 @@ class Edge(dict, MutableMapping):
            if len(sidx) >= k and len(didx) >=k:
                mono_matches(self.source, self.destination, sidx, didx)
                mono_matches(self.destination, self.source, didx, sidx)
=======
    @property
    def health(self):
        return self._health.health

    def decompose_and_match(*args, **kwargs):
        pass

    def match(self, k=2, **kwargs):

        """
        Given two sets of descriptors, utilize a FLANN (Approximate Nearest
        Neighbor KDTree) matcher to find the k nearest matches.  Nearness is
@@ -335,65 +346,7 @@ class Edge(dict, MutableMapping):
        k : int
            The number of neighbors to find
        """
        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)
            self._add_matches(matches)
            fl.clear()

        fl = FlannMatcher()
        mono_matches(self.source, self.destination)
        mono_matches(self.destination, self.source)



    def _add_matches(self, 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)
        pass

    def symmetry_check(self):
        if hasattr(self, 'matches'):
@@ -767,3 +720,25 @@ class Edge(dict, MutableMapping):
            raise AttributeError('Matches have not been computed for this edge')
        voronoi = cg.vor(self, clean_keys, **kwargs)
        self.matches = pd.concat([self.matches, voronoi[1]['vor_weights']], axis=1)

    def decompose(self, maxiterations=3):
        """
        Apply coupled decomposition to the images and
        match identified sub-images

        Parameters
        ----------
        maxiterations : int
                        The number of iterations. Appropriate values:

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


        """
        pass
+5 −5
Changes for autocnet/graph/network.py: 5 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -205,7 +205,7 @@ class CandidateGraph(nx.Graph):

        raise NotImplementedError

    def extract_features(self, method='orb', extractor_parameters={}):
    def extract_features(self, *args, **kwargs):
        """
        Extracts features from each image in the graph and uses the result to assign the
        node attributes for 'handle', 'image', 'keypoints', and 'descriptors'.
@@ -223,8 +223,7 @@ class CandidateGraph(nx.Graph):
        """
        for i, node in self.nodes_iter(data=True):
            image = node.get_array()
            node.extract_features(image, method=method,
                                  extractor_parameters=extractor_parameters)
            node.extract_features(image, *args, **kwargs),

    def save_features(self, out_path, nodes=[]):
        """
@@ -284,7 +283,7 @@ class CandidateGraph(nx.Graph):

        hdf = None

    def match_features(self, *args, **kwargs):
    def match(self, *args, **kwargs):
        """
        For all connected edges in the graph, apply feature matching

@@ -294,7 +293,8 @@ class CandidateGraph(nx.Graph):
        """
        self.apply_func_to_edges('match', *args, **kwargs)

    def decompose_and_match_features(self, *args, **kwargs):

    def decompose_and_match(self, *args, **kwargs):
        """
        For all edges in the graph, apply coupled decomposition followed by
        feature matching.
+6 −2
Changes for autocnet/graph/node.py: 6 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -261,7 +261,8 @@ class Node(dict, MutableMapping):

        return keypoints

    def extract_features(self, array, **kwargs):
    @staticmethod
    def _extract_features(*args, **kwargs):
        """
        Extract features for the node

@@ -273,7 +274,10 @@ class Node(dict, MutableMapping):
                 kwargs passed to autocnet.feature_extractor.extract_features

        """
        self._keypoints, self._descriptors = fe.extract_features(array, **kwargs)
        pass

    def extract_features(self, *args, **kwargs):
        self._keypoints, self.descriptors = Node._extract_features(*args, **kwargs)

    def load_features(self, in_path):
        """
+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)
Loading