Commit 82a8be6f authored by jlaura's avatar jlaura Committed by GitHub
Browse files

Adds the UCL ring matcher to autocnet (#237)

* Adds a CPU baseg ring matching implementation

* Fixes rounding in doctest on new travis systems.

* Fixes rounding in doctest on new travis systems.

* Ammending last commit - typo.

* adds conditional use of OpenCV Sift/Surf

* Updated tutorial for API changes

* Stubs for AutoCNet Service

* Testing autocnet as a service

* Fixes camera identity

* Updates to generalize data structures for autocnet server

* Updates fundamental and adds trifocal properly

* Backing out camera stuff that belongs someplace else

* Updates tests for API changes.

* Updates findFundamentalMat for OpenCV API Change

* Updates for @acpaquette

* Fixes np array comparison tobe canonical and worth with mixed types

* Updates docstring test with spaces causing failures
parent fe960a4f
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -38,14 +38,14 @@ def idealized_camera():
     : ndarray
       (3,4) with diagonal 1
    """
    return np.eye(3, 4)

    i = np.eye(3, 4)
    i[:,-1] = 0
    return i

def estimated_camera_from_f(f):
    """
    Estimate a camera matrix using a fundamental matrix.


    Parameters
    ----------
    f : ndarray
+14 −1
Original line number Diff line number Diff line
@@ -45,9 +45,9 @@ class Edge(dict, MutableMapping):
        self.destination = destination
        self['homography'] = None
        self['fundamental_matrix'] = None
        self.matches = pd.DataFrame()
        self.masks = pd.DataFrame()
        self.subpixel_matches = pd.DataFrame()
        self._matches = pd.DataFrame()
        self['weights'] = {}
        
        self['source_mbr'] = None
@@ -65,6 +65,19 @@ class Edge(dict, MutableMapping):
        return utils.compare_dicts(self.__dict__, other.__dict__) *\
               utils.compare_dicts(self, other)

    @property
    def matches(self):
        if not hasattr(self, '_matches'):
            self._matches = pd.DataFrame()
        return self._matches

    @matches.setter
    def matches(self, value):
        if isinstance(value, pd.DataFrame):
            self._matches = value
        else:
            raise(TypeError)
            
    def match(self, k=2, **kwargs):

        """
+25 −10
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ from autocnet.io import network as io_network
from autocnet.vis.graph_view import plot_graph, cluster_plot
from autocnet.control import control


# The total number of pixels squared that can fit into the keys number of GB of RAM for SIFT.
MAXSIZE = {0: None,
           2: 6250,
@@ -55,7 +54,11 @@ class CandidateGraph(nx.Graph):
         A control network object instantiated by calling generate_cnet.
    ----------
    """
    def __init__(self, *args, basepath=None, **kwargs):

    node_factory = Node
    edge_factory = Edge

    def __init__(self, *args, basepath=None, node_id_map=None, overlaps=False, **kwargs):
        super(CandidateGraph, self).__init__(*args, **kwargs)

        self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime())
@@ -67,21 +70,28 @@ class CandidateGraph(nx.Graph):
                image_path = os.path.join(basepath, i)
            else:
                image_path = i
            n['data'] = Node(image_name=i, image_path=image_path, node_id = self.graph['node_counter'])

            self.graph['node_name_map'][i] = self.graph['node_counter']
            if node_id_map:
                node_id = node_id_map[image_path]
            else:
                node_id = self.graph['node_counter']
                self.graph['node_counter'] += 1

            n['data'] = self.node_factory(image_name=i, image_path=image_path, node_id=node_id)

            self.graph['node_name_map'][i] = node_id

        # Relabel the nodes in place to use integer node ids
        nx.relabel_nodes(self, self.graph['node_name_map'], copy=False)
        for s, d, e in self.edges(data=True):
            if s > d:
                s,d = d,s
            edge = Edge(self.nodes[s]['data'],self.nodes[d]['data'])
            edge = self.edge_factory(self.nodes[s]['data'],self.nodes[d]['data'])
            # Unidrected graph - both representation point at the same data
            self.edges[s,d]['data'] = edge
            self.edges[d,s]['data'] = edge

        if overlaps:
            self.compute_overlaps()

    def __eq__(self, other):
@@ -166,7 +176,7 @@ class CandidateGraph(nx.Graph):
        return cls.from_adjacency(adjacency_dict)

    @classmethod
    def from_adjacency(cls, input_adjacency, basepath=None):
    def from_adjacency(cls, input_adjacency, node_id_map=None, basepath=None, **kwargs):
        """
        Instantiate the class using an adjacency dict or file. The input must contain relative or
        absolute paths to image files.
@@ -191,7 +201,7 @@ class CandidateGraph(nx.Graph):
        """
        if not isinstance(input_adjacency, dict):
            input_adjacency = io_json.read_json(input_adjacency)
        return cls(input_adjacency, basepath=basepath)
        return cls(input_adjacency, basepath=basepath, node_id_map=node_id_map, **kwargs)

    @classmethod
    def from_save(cls, input_file):
@@ -343,7 +353,7 @@ class CandidateGraph(nx.Graph):
            node.extract_features_with_downsampling(downsample_amount, *args, **kwargs)

    def extract_features_with_tiling(self, tilesize=1000, overlap=500, *args, **kwargs): #pragma: no cover
        for node in self.nodes:
        for i, node in self.nodes(data='data'):
            print('Processing {}'.format(node['image_name']))
            node.extract_features_with_tiling(tilesize=tilesize, overlap=overlap, *args, **kwargs)

@@ -1043,8 +1053,13 @@ class CandidateGraph(nx.Graph):
        return True

    def footprints(self):
        geoms = [node.footprint for i, node in self.nodes.data('data')]
        return gpd.GeoDataFrame(geometry=geoms)
        geoms = []
        names = []
        for i, node in self.nodes.data('data'):
            geoms.append(node.footprint)
            names.append(node['image_name'])

        return gpd.GeoDataFrame(names, geometry=geoms)

    def create_control_network(self, clean_keys=[]):
        matches = self.get_matches(clean_keys=clean_keys)
+12 −40
Original line number Diff line number Diff line
@@ -298,15 +298,14 @@ class Node(dict, MutableMapping):

        keypoints, descriptors = Node._extract_features(array, *args, **kwargs)
        count = len(self.keypoints)

        # If this is a tile, push the keypoints to the correct start xy
        if xystart:
            keypoints['x'] += xystart[0]
            keypoints['y'] += xystart[1]

        self.keypoints = pd.concat((self.keypoints, keypoints))
        descriptor_mask = self.keypoints.duplicated()[count:]
        number_new = descriptor_mask.sum()

        descriptor_mask = self.keypoints[count:].duplicated()
        number_new = len(descriptor_mask) - descriptor_mask.sum()
        # Removed duplicated and re-index the merged keypoints
        self.keypoints.drop_duplicates(inplace=True)
        self.keypoints.reset_index(inplace=True, drop=True)
@@ -315,6 +314,8 @@ class Node(dict, MutableMapping):
            self.descriptors = np.concatenate((self.descriptors, descriptors[~descriptor_mask]))
        else:
            self.descriptors = descriptors
        #self.descriptors = descriptors
        assert count + number_new == len(self.descriptors)

    def extract_features_from_overlaps(self, overlaps=[], downsampling=False, tiling=False, *args, **kwargs):
        # iterate through the overlaps
@@ -346,42 +347,13 @@ class Node(dict, MutableMapping):

    def extract_features_with_tiling(self, tilesize=1000, overlap=500, *args, **kwargs):
        array_size = self.geodata.raster_size
        stepsize = tilesize - overlap
        if stepsize < 0:
            raise ValueError('Overlap can not be greater than tilesize.')
        # Compute the tiles
        if tilesize >= array_size[1]:
            ytiles = [(0, array_size[1])]
        else:
            ystarts = range(0, array_size[1], stepsize)
            ystops = range(tilesize, array_size[1], stepsize)
            ytiles = list(zip(ystarts, ystops))
            ytiles.append((ytiles[-1][0] + stepsize, array_size[1]))

        if tilesize >= array_size[0]:
            xtiles = [(0, array_size[0])]
        else:
            xstarts = range(0, array_size[0], stepsize)
            xstops = range(tilesize, array_size[0], stepsize)
            xtiles = list(zip(xstarts, xstops))
            xtiles.append((xtiles[-1][0] + stepsize, array_size[0]))
        tiles = itertools.product(xtiles, ytiles)

        for tile in tiles:
            # xstart, ystart, xcount, ycount
            xstart = tile[0][0]
            ystart = tile[1][0]
            xstop = tile[0][1]
            ystop = tile[1][1]
            pixels = [xstart, ystart,
                      xstop - xstart,
                      ystop - ystart]

            array = self.geodata.read_array(pixels=pixels)
            xystart = [xstart, ystart]
        slices = utils.tile(array_size, tilesize=tilesize, overlap=overlap)
        for s in slices:
            xystart = [s[0], s[1]]
            array = self.geodata.read_array(pixels=s)
            self.extract_features(array, xystart, *args, **kwargs)

    def load_features(self, in_path, format='npy'):
    def load_features(self, in_path, format='npy', **kwargs):
        """
        Load keypoints and descriptors for the given image
        from a HDF file.
@@ -392,12 +364,12 @@ class Node(dict, MutableMapping):
                  PATH to the hdf file or a HDFDataset object handle

        format : {'npy', 'hdf'}
                 The format that the features are stored in.  Default: npy.
        """
        if format == 'npy':
            keypoints, descriptors = io_keypoints.from_npy(in_path)
        elif format == 'hdf':
            keypoints, descriptors = io_keypoints.from_hdf(in_path,
                                                           key=self['image_name'])
            keypoints, descriptors = io_keypoints.from_hdf(in_path, **kwargs)

        self.keypoints = keypoints
        self.descriptors = descriptors
+8 −0
Original line number Diff line number Diff line
@@ -329,3 +329,11 @@ class TestEdge(unittest.TestCase):
        overlap_matches, overlap_mask = e.clean(clean_keys=['overlap'])
        self.assertTrue(expected_mask.equals(overlap_mask))
        self.assertTrue(overlap_matches.equals(e.matches[overlap_mask]))

    def test_bad_matches_type(self):
        with self.assertRaises(TypeError):
            s = node.Node(node_id=0)
            d = node.Node(node_id=1)

            e = edge.Edge(s, d)
            e.matches = ['a', 'b', 'c']
 No newline at end of file
Loading