Commit a6a308ca authored by jay's avatar jay
Browse files

API changes to support server refactor

parent 195ffba8
Loading
Loading
Loading
Loading
+26 −8
Original line number Diff line number Diff line
@@ -94,6 +94,13 @@ class CandidateGraph(nx.Graph):
        if overlaps:
            self.compute_overlaps()

    def __key(self):
        # TODO: This needs to be a real self identifying key
        return 'abcde'

    def __hash__(self):
        return hash(self.__key())

    def __eq__(self, other):
        # Check the nodes
        if sorted(self.nodes()) != sorted(other.nodes()):
@@ -124,6 +131,19 @@ class CandidateGraph(nx.Graph):
        else:
            self._maxsize = MAXSIZE[value]

    @property
    def unmatched_edges(self):
        """
        Returns a list of edges (source, destination) that do not have
        entries in the matches dataframe.
        """
        unmatched = []
        for s, d, e in self.edges(data='data'):
            if len(e.matches) == 0:
                unmatched.append((s,d))

        return unmatched

    @classmethod
    def from_filelist(cls, filelist, basepath=None):
        """
@@ -352,14 +372,14 @@ class CandidateGraph(nx.Graph):
                downsample_amount = math.ceil(total_size / self.maxsize**2)
            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 i, node in self.nodes(data='data'):
            print('Processing {}'.format(node['image_name']))
            node.extract_features_with_tiling(tilesize=tilesize, overlap=overlap, *args, **kwargs)
    def extract_features_with_tiling(self, *args, **kwargs): #pragma: no cover
        """

    def save_features(self, out_path):
        """
        self.apply(Node.extract_features_with_tiling, args=args, **kwargs)

    def save_features(self, out_path):
        """
        Save the features (keypoints and descriptors) for the
        specified nodes.

@@ -369,9 +389,6 @@ class CandidateGraph(nx.Graph):
                   Location of the output file.  If the file exists,
                   features are appended.  Otherwise, the file is created.
        """



        self.apply(Node.save_features, args=(out_path,), on='node')

    def load_features(self, in_path, nodes=[], nfeatures=None, **kwargs):
@@ -388,6 +405,7 @@ class CandidateGraph(nx.Graph):
                of nodes to load features for.  If empty, load features
                for all nodes
        """
        self.apply(Nodes.load_features, args=(in_path, nfeatures), on='node', **kwargs)
        for n in self.nodes:
            if node['node_id'] not in nodes:
                continue
+73 −16
Original line number Diff line number Diff line
@@ -65,10 +65,38 @@ class Node(dict, MutableMapping):
        self['image_path'] = image_path
        self['node_id'] = node_id
        self['hash'] = image_name
        self.descriptors = None
        self.keypoints = pd.DataFrame()
        self.masks = pd.DataFrame()

    @property
    def camera(self):
        if not hasattr(self, '_camera'):
            self._camera = None
        return self._camera
    
    @camera.setter
    def camera(self, camera):
        self._camera = camera

    @property
    def descriptors(self):
        if not hasattr(self, '_descriptors'):
            self._descriptors = None
        return self._descriptors

    @descriptors.setter
    def descriptors(self, desc):
        self._descriptors = desc

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

    @keypoints.setter
    def keypoints(self, kps):
        self._keypoints = kps

    def __repr__(self):
        return """
        NodeID: {}
@@ -158,7 +186,10 @@ class Node(dict, MutableMapping):

    @property
    def nkeypoints(self):
        try:
            return len(self.keypoints)
        except:
            return 0

    def coverage(self):
        """
@@ -285,7 +316,7 @@ class Node(dict, MutableMapping):
        """
        pass

    def extract_features(self, array, xystart=[], *args, **kwargs):
    def extract_features(self, array, xystart=[], camera=None, *args, **kwargs):
        arraysize = array.shape[0] * array.shape[1]

        try:
@@ -296,26 +327,45 @@ class Node(dict, MutableMapping):
        if arraysize > maxsize:
            warnings.warn('Node: {}. Maximum feature extraction array size is {}.  Maximum array size is {}. Please use tiling or downsampling.'.format(self['node_id'], maxsize, arraysize))

        keypoints, descriptors = Node._extract_features(array, *args, **kwargs)
        new_keypoints, new_descriptors = Node._extract_features(array, *args, **kwargs)
        count = len(self.keypoints)
        
        if camera:
            # Project the sift keypoints to the ground
            def func(row, args):
                camera = args[0]
                gnd = getattr(camera, 'imageToGround')(row[1], row[0], 0)
                return gnd
            feats = new_keypoints[['x', 'y']].values
            gnd = np.apply_along_axis(func, 1, feats, args=(camera, ))
            gnd = pd.DataFrame(gnd, columns=['xm', 'ym', 'zm'], index=keypoints.index)
            keypoints = pd.concat([keypoints, gnd], axis=1)

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

        self.keypoints = pd.concat((self.keypoints, keypoints))
        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)
        concat_kps = pd.concat((self.keypoints, new_keypoints))

        descriptor_mask = concat_kps.duplicated()
        descriptor_mask = descriptor_mask[count:]        
        # Removed duplicated and re-index the merged keypoints
        concat_kps.drop_duplicates(inplace=True)
        concat_kps.reset_index(inplace=True, drop=True)
        if self.descriptors is not None:
            self.descriptors = np.concatenate((self.descriptors, descriptors[~descriptor_mask]))
            concat = np.concatenate((self.descriptors, new_descriptors[~descriptor_mask]))
            self.descriptors = concat
        else:
            self.descriptors = descriptors
        #self.descriptors = descriptors
        assert count + number_new == len(self.descriptors)
            self.descriptors = new_descriptors
        self.keypoints = concat_kps
        
        lkps = len(self.keypoints)

        assert lkps == len(self.descriptors)

        if lkps > 0:
            return True

    def extract_features_from_overlaps(self, overlaps=[], downsampling=False, tiling=False, *args, **kwargs):
        # iterate through the overlaps
@@ -342,9 +392,13 @@ class Node(dict, MutableMapping):
                 int(array_size[1] / downsample_amount))
        array = imresize(self.geodata.read_array(**array_read_args), shape, interp=interp)
        self.extract_features(array, *args, **kwargs)

        self.keypoints['x'] *= downsample_amount
        self.keypoints['y'] *= downsample_amount

        if len(self.keypoints) > 0:
            return True

    def extract_features_with_tiling(self, tilesize=1000, overlap=500, *args, **kwargs):
        array_size = self.geodata.raster_size
        slices = utils.tile(array_size, tilesize=tilesize, overlap=overlap)
@@ -353,6 +407,9 @@ class Node(dict, MutableMapping):
            array = self.geodata.read_array(pixels=s)
            self.extract_features(array, xystart, *args, **kwargs)

        if len(self.keypoints) > 0:
            return True

    def load_features(self, in_path, format='npy', **kwargs):
        """
        Load keypoints and descriptors for the given image
+17 −3
Original line number Diff line number Diff line
@@ -47,7 +47,6 @@ def from_hdf(in_path, index=None, keypoints=True, descriptors=True):
    outd = '/descriptors'
    outk = '/keypoints'


    if index is not None:
        index=np.asarray(index)

@@ -69,6 +68,8 @@ def from_hdf(in_path, index=None, keypoints=True, descriptors=True):
            desc = hdf[outd][:]
        if keypoints:
            raw_kps = hdf[outk][:]
    
    if keypoints:
        index = raw_kps['index']
        clean_kps = utils.remove_field_name(raw_kps, 'index')
        columns = clean_kps.dtype.names
@@ -86,7 +87,7 @@ def from_hdf(in_path, index=None, keypoints=True, descriptors=True):
        return desc


def to_hdf(keypoints, descriptors, out_path, key=None):
def to_hdf(out_path, keypoints=None, descriptors=None, key=None):
    """
    Save keypoints and descriptors to HDF at a given out_path at either
    the root or at some arbitrary path given by a key.
@@ -108,20 +109,33 @@ def to_hdf(keypoints, descriptors, out_path, key=None):
    """
    # If the out_path is a string, access the HDF5 file
    if isinstance(out_path, str):
        hdf = io_hdf.HDFDataset(out_path, mode='w')
        hdf = io_hdf.HDFDataset(out_path, mode='a')
    else:
        hdf = out_path

    grps = list(hdf.keys())

    outd = '/descriptors'
    outk = '/keypoints'
    if descriptors is not None:
        # Strip the leading slash
        if outd[1:] in grps:
            del hdf[outd] # Prep to replace

        hdf.create_dataset(outd,
                        data=descriptors,
                        compression=io_hdf.DEFAULT_COMPRESSION,
                        compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE)

    if keypoints is not None:
        if outk[1:] in grps:
            del hdf[outk]

        hdf.create_dataset(outk,
                        data=hdf.df_to_sarray(keypoints.reset_index()),
                        compression=io_hdf.DEFAULT_COMPRESSION,
                        compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE)

    #except:
        #warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name']))