Commit 6c193120 authored by Jay's avatar Jay
Browse files

Moving class attributes to the __dict__ inline with networkx syntax to support easier serialization

parent c5ff01ef
Loading
Loading
Loading
Loading
+13 −42
Changes for autocnet/graph/edge.py: 13 added lines, 42 removed lines.
Original line number Diff line number Diff line
@@ -30,11 +30,6 @@ class Edge(dict, MutableMapping):
    masks : set
            A list of the available masking arrays

    provenance : dict
                 With key equal to an autoincrementing integer and value
                 equal to a dict of parameters used to generate this
                 realization.

    weight : dict
             Dictionary with two keys overlap_area, and overlap_percn
             overlap_area returns the area overlaped by both images
@@ -44,19 +39,10 @@ class Edge(dict, MutableMapping):
    def __init__(self, source=None, destination=None):
        self.source = source
        self.destination = destination

        self.homography = None
        self.fundamental_matrix = None
        self.matches = None
        self._subpixel_offsets = None

        self.provenance = {}
        self.weight = {}

        self._observers = set()

        # Subscribe the heatlh observer
        self._health = health.EdgeHealth()
        self['weight'] = {}

    def __repr__(self):
        return """
@@ -69,8 +55,7 @@ class Edge(dict, MutableMapping):
        attribute_dict = {'source': self.source,
                          'destination': self.destination,
                          'masks': self.masks,
                          'provenance': self.provenance,
                          'weight': self.weight}
                          'weight': self['weight']}
        if item in attribute_dict.keys():
            return attribute_dict[item]
        else:
@@ -78,8 +63,7 @@ class Edge(dict, MutableMapping):

    @property
    def masks(self):
        mask_lookup = {'fundamental': 'fundamental_matrix',
                       'ratio': 'distance_ratio'}
        mask_lookup = {'fundamental': 'fundamental_matrix'}
        if not hasattr(self, '_masks'):
            if self.matches is not None:
                self._masks = pd.DataFrame(True, columns=['symmetry'],
@@ -101,10 +85,6 @@ class Edge(dict, MutableMapping):
        boolean_mask = v[1]
        self.masks[column_name] = boolean_mask

    @property
    def health(self):
        return self._health.health

    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
@@ -179,9 +159,9 @@ class Edge(dict, MutableMapping):
                bd = b.descriptors

            # Load, train, and match
            fl.add(ad, a.node_id, index=aidx)
            fl.add(ad, a['node_id'], index=aidx)
            fl.train()
            matches = fl.query(bd, b.node_id, k, index=bidx)
            matches = fl.query(bd, b['node_id'], k, index=bidx)
            self._add_matches(matches)
            fl.clear()

@@ -238,7 +218,7 @@ class Edge(dict, MutableMapping):
                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.add(self.destination.descriptors, self.destination['node_id'])
                fl.train()

                scounter = 0
@@ -252,7 +232,7 @@ class Edge(dict, MutableMapping):
                        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)
                    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')
@@ -385,9 +365,9 @@ class Edge(dict, MutableMapping):
                bd = b.descriptors

            # Load, train, and match
            fl.add(ad, a.node_id, index=aidx)
            fl.add(ad, a['node_id'], index=aidx)
            fl.train()
            matches = fl.query(bd, b.node_id, k, index=bidx)
            matches = fl.query(bd, b['node_id'], k, index=bidx)
            self._add_matches(matches)
            fl.clear()

@@ -424,16 +404,9 @@ class Edge(dict, MutableMapping):

    def ratio_check(self, clean_keys=[], **kwargs):
        if hasattr(self, 'matches'):

            matches, mask = self.clean(clean_keys)

            self.distance_ratio = od.DistanceRatio(matches)
            self.distance_ratio.compute(mask=mask, **kwargs)

            # Setup to be notified
            self.distance_ratio._notify_subscribers(self.distance_ratio)

            self.masks = ('ratio', self.distance_ratio.mask)
            distance_mask = od.distance_ratio(matches, **kwargs)
            self.masks = ('ratio', distance_mask)
        else:
            raise AttributeError('No matches have been computed for this edge.')

@@ -480,7 +453,6 @@ class Edge(dict, MutableMapping):
        mask[mask] = self.fundamental_matrix.mask

        # Subscribe the health watcher to the fundamental matrix observable
        self.fundamental_matrix.subscribe(self._health.update)
        self.fundamental_matrix._notify_subscribers(self.fundamental_matrix)

        # Set the initial state of the fundamental mask in the masks
@@ -737,8 +709,8 @@ class Edge(dict, MutableMapping):

        overlapinfo = cg.two_poly_overlap(poly1, poly2)

        self.weight['overlap_area'] = overlapinfo[1]
        self.weight['overlap_percn'] = overlapinfo[0]
        self['weight']['overlap_area'] = overlapinfo[1]
        self['weight']['overlap_percn'] = overlapinfo[0]

    def coverage(self, clean_keys = []):
        """
@@ -795,4 +767,3 @@ 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)
+11 −12
Changes for autocnet/graph/network.py: 11 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -21,10 +21,9 @@ class CandidateGraph(nx.Graph):
    """
    A NetworkX derived directed graph to store candidate overlap images.

    Parameters
    Attributes
    ----------

    Attributes
    node_counter : int
                   The number of nodes in the graph.
    node_name_map : dict
@@ -43,21 +42,21 @@ class CandidateGraph(nx.Graph):

    def __init__(self, *args, basepath=None, **kwargs):
        super(CandidateGraph, self).__init__(*args, **kwargs)
        self.node_counter = 0
        self.graph['node_counter'] = 0
        node_labels = {}
        self.node_name_map = {}
        self.graph['node_name_map'] = {}

        for node_name in self.nodes():
            image_name = os.path.basename(node_name)
            image_path = node_name
            # Replace the default attr dict with a Node object
            self.node[node_name] = Node(image_name, image_path, self.node_counter)
            self.node[node_name] = Node(image_name, image_path, self.graph['node_counter'])

            # fill the dictionary used for relabelling nodes with relative path keys
            node_labels[node_name] = self.node_counter
            node_labels[node_name] = self.graph['node_counter']
            # fill the dictionary used for mapping base name to node index
            self.node_name_map[self.node[node_name].image_name] = self.node_counter
            self.node_counter += 1
            self.graph['node_name_map'][self.node[node_name]['image_name']] = self.graph['node_counter']
            self.graph['node_counter'] += 1

        nx.relabel_nodes(self, node_labels, copy=False)

@@ -68,8 +67,8 @@ class CandidateGraph(nx.Graph):
            e.source = self.node[s]
            e.destination = self.node[d]

        self.creationdate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        self.graph['creationdate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime())

    @classmethod
    def from_graph(cls, graph):
@@ -175,7 +174,7 @@ class CandidateGraph(nx.Graph):
        """
        Update the last modified date attribute.
        """
        self.modifieddate = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        self.graph['modifieddate'] = strftime("%Y-%m-%d %H:%M:%S", gmtime())

    def get_name(self, node_index):
        """
@@ -193,7 +192,7 @@ class CandidateGraph(nx.Graph):


        """
        return self.node[node_index].image_name
        return self.node[node_index]['image_name']

    def add_image(self, *args, **kwargs):
        """
+16 −14
Changes for autocnet/graph/node.py: 16 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -58,9 +58,10 @@ class Node(dict, MutableMapping):
    """

    def __init__(self, image_name=None, image_path=None, node_id=None):
        self.image_name = image_name
        self.image_path = image_path
        self.node_id = node_id
        self['image_name'] = image_name
        self['image_path'] = image_path
        self['node_id'] = node_id
        self['hash'] = self['image_name']  #TODO: Repalce with farmhash
        self._mask_arrays = {}
        self.point_to_correspondence = defaultdict(set)
        self.point_to_correspondence_df = None
@@ -73,9 +74,10 @@ class Node(dict, MutableMapping):
        Number Keypoints: {}
        Available Masks : {}
        Type: {}
        """.format(self.node_id, self.image_name, self.image_path,
        """.format(self['node_id'], self['image_name'], self['image_path'],
                   self.nkeypoints, self.masks, self.__class__)

    """
    def __getitem__(self, item):
        attribute_dict = {'image_name': self.image_name,
                          'image_path': self.image_path,
@@ -89,11 +91,11 @@ class Node(dict, MutableMapping):
            return attribute_dict[item]
        else:
            return super(Node, self).__getitem__(item)

    """
    @property
    def geodata(self):
        if not getattr(self, '_geodata', None) and self.image_path is not None:
            self._geodata = GeoDataset(self.image_path)
        if not getattr(self, '_geodata', None) and self['image_path'] is not None:
            self._geodata = GeoDataset(self['image_path'])
            return self._geodata
        if hasattr(self, '_geodata'):
            return self._geodata
@@ -133,7 +135,7 @@ class Node(dict, MutableMapping):
        """
        if not hasattr(self, '_isis_serial'):
            try:
                self._isis_serial = generate_serial_number(self.image_path)
                self._isis_serial = generate_serial_number(self['image_path'])
            except:
                self._isis_serial = None
        return self._isis_serial
@@ -288,8 +290,8 @@ class Node(dict, MutableMapping):
        else:
            hdf = in_path

        self._descriptors = hdf['{}/descriptors'.format(self.image_name)][:]
        raw_kps = hdf['{}/keypoints'.format(self.image_name)][:]
        self._descriptors = hdf['{}/descriptors'.format(self['image_name'])][:]
        raw_kps = hdf['{}/keypoints'.format(self['image_name'])][:]
        index = raw_kps['index']
        clean_kps = utils.remove_field_name(raw_kps, 'index')
        columns = clean_kps.dtype.names
@@ -329,16 +331,16 @@ class Node(dict, MutableMapping):
            hdf = out_path

        try:
            hdf.create_dataset('{}/descriptors'.format(self.image_name),
            hdf.create_dataset('{}/descriptors'.format(self['image_name']),
                               data=self._descriptors,
                               compression=io_hdf.DEFAULT_COMPRESSION,
                               compression_opts=io_hdf.DEFAULT_COMPRESSION_VALUE)
            hdf.create_dataset('{}/keypoints'.format(self.image_name),
            hdf.create_dataset('{}/keypoints'.format(self['image_name']),
                               data=hdf.df_to_sarray(self._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))
            warnings.warn('Descriptors for the node {} are already stored'.format(self['image_name']))

        # If the out_path is a string, assume this method is being called as a singleton
        # and close the hdf file gracefully.  If an object, let the instantiator of the
@@ -357,7 +359,7 @@ class Node(dict, MutableMapping):
        deepen : bool
                 If True, attempt to punch matches through to all incident edges.  Default: False
        """
        node = self.node_id
        node = self['node_id']
        # Get the edges incident to the current node
        incident_edges = set(cg.edges(node)).intersection(set(cg.edges()))

+2 −0
Changes for autocnet/matcher/feature_extractor.py: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -77,4 +77,6 @@ def extract_features(array, method='orb', extractor_parameters={}):
        if descriptors.dtype != np.float32:
            descriptors = descriptors.astype(np.float32)

        descriptors = pd.DataFrame(descriptors)                            

    return keypoints, descriptors
+11 −71
Changes for autocnet/matcher/outlier_detector.py: 11 added lines, 71 removed lines.
Original line number Diff line number Diff line
@@ -7,52 +7,7 @@ import pandas as pd

from autocnet.utils.observable import Observable


class DistanceRatio(Observable):

    """
    A stateful object to store ratio test results and provenance.

    Attributes
    ----------

    nvalid : int
             The number of valid entries in the mask

    mask : series
           Pandas boolean series indexed by the match id

    matches : dataframe
              The matches dataframe from an edge.  This dataframe
              must have 'source_idx' and 'distance' columns.

    single : bool
             If True, then single entries in the distance ratio
             mask are assumed to have passed the ratio test.  Else
             False.

    References
    ----------
    [Lowe2004]_

    """

    def __init__(self, matches):

        self._action_stack = deque(maxlen=10)
        self._current_action_stack = 0
        self._observers = set()
        self.matches = matches
        self.mask = None
        self.clean_keys = None
        self.single = None
        self.attrs = ['mask', 'ratio', 'clean_keys', 'single']

    @property
    def nvalid(self):
        return self.mask.sum()

    def compute(self, ratio=0.8, mask=None, mask_name=None, single=False):
def distance_ratio(matches, ratio=0.8, single=False):
    """
    Compute and return a mask for a matches dataframe
    using Lowe's ratio test.  If keypoints have a single
@@ -65,15 +20,15 @@ class DistanceRatio(Observable):
            for each keypoint to use as a bound for marking the first keypoint
            as "good". Default: 0.8

        mask : series
               A pandas boolean series to initially mask the matches array

        mask_name : list or str
                    An arbitrary mask name for provenance tracking

    single : bool
             If True, points with only a single entry are included (True)
             in the result mask, else False.

    Returns
    -------
    mask : pd.dataframe
           A Pandas DataFrame mask for the matches with those failing the
           ratio test set to False.
    """
    def func(group):
        res = [False] * len(group)
@@ -83,27 +38,12 @@ class DistanceRatio(Observable):
            res[0] = True
        return res

        if mask is not None:
            self.mask = mask.copy()
            mask_s = self.matches[mask].groupby('source_idx')['distance'].transform(func).astype('bool')
    mask_s = matches.groupby('source_idx')['distance'].transform(func).astype('bool')
    single = True
            mask_d = self.matches[mask].groupby('destination_idx')['distance'].transform(func).astype('bool')
            self.mask[mask] = mask_s & mask_d
        else:
            mask_s = self.matches.groupby('source_idx')['distance'].transform(func).astype('bool')
            single = True
            mask_d = self.matches.groupby('destination_idx')['distance'].transform(func).astype('bool')

            self.mask = mask_s & mask_d
    mask_d = matches.groupby('destination_idx')['distance'].transform(func).astype('bool')
    mask = mask_s & mask_d

        state_package = {'ratio': ratio,
                         'mask': self.mask.copy(),
                         'clean_keys': mask_name,
                         'single': single
                         }

        self._action_stack.append(state_package)
        self._current_action_stack = len(self._action_stack) - 1
    return mask


class SpatialSuppression(Observable):
Loading