Commit 83625255 authored by Kelvin Rodriguez's avatar Kelvin Rodriguez Committed by jlaura
Browse files

CN refactor + bug fixes (#254)

* removed cn class, bugfixes for jupyter tutorial

* formatting

* Improved cnet generation performence, added cnet masking

* updated notebooks with cnet work

* removed notebook checkpoints

* removed unused notebook

* removed some print statements and a spatial suppression bug

* updated notebooks

* removed functions that were originally in the ControlNetwork class, they are no longer needed after the refactor
parent 2442512f
Loading
Loading
Loading
Loading
+15 −131
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ from autocnet.matcher import subpixel as sp

from plio.io.io_controlnetwork import to_isis, write_filelist


def subpixel_match(cg, cn, threshold=0.9, template_size=19, search_size=53, max_x_shift=1.0, max_y_shift=1.0, **kwargs):

    def subpixel_group(group, threshold=0.9, template_size=19, search_size=53, max_x_shift=1.0, max_y_shift=1.0, **kwargs):
@@ -28,12 +29,17 @@ def subpixel_match(cg, cn,threshold=0.9, template_size=19, search_size=53, max_x
            d_search = sp.clip_roi(d_img, (r.x, r.y), search_size)
            #d_search = cv2.Canny(bytescale(d_search), 50,100)

            xoff,yoff,corr = sp.subpixel_offset(s_template, d_search, **kwargs)
            xoff, yoff, corr = sp.subpixel_offset(
                s_template, d_search, **kwargs)
            offs.append([xoff, yoff, corr])
        df = pd.DataFrame(offs, columns=['x_off', 'y_off', 'corr'], index=group.index)
        df = pd.DataFrame(
            offs, columns=['x_off', 'y_off', 'corr'], index=group.index)
        return df
    gps = cn.data.groupby('point_id').apply(subpixel_group,threshold=0.9,max_x_shift=5, max_y_shift=5,template_size=template_size, search_size=search_size,**kwargs)
    cn.data[['x_off', 'y_off', 'corr']] = gps.reset_index()[['x_off', 'y_off', 'corr']]
    gps = cn.data.groupby('point_id').apply(subpixel_group, threshold=0.9, max_x_shift=5,
                                            max_y_shift=5, template_size=template_size, search_size=search_size, **kwargs)
    cn.data[['x_off', 'y_off', 'corr']] = gps.reset_index()[
        ['x_off', 'y_off', 'corr']]


def identify_potential_overlaps(cg, cn, overlap=True):
    """
@@ -56,7 +62,6 @@ def identify_potential_overlaps(cg, cn, overlap=True):
                        for a new point.
    """


    fc = cg.compute_fully_connected_components()

    candidate_cliques = []
@@ -82,7 +87,8 @@ def identify_potential_overlaps(cg, cn, overlap=True):
        # Determine whether a 'real' lat/lon are to be used and reproject
        if overlap:
            row = p.iloc[0]
            lat, lon = cg.node[row.image_index]['data'].geodata.pixel_to_latlon(row.x, row.y)
            lat, lon = cg.node[row.image_index]['data'].geodata.pixel_to_latlon(
                row.x, row.y)
        else:
            lat, lon = 0, 0

@@ -91,7 +97,6 @@ def identify_potential_overlaps(cg, cn, overlap=True):
        candidate_cliques.append([uncovered, cycle_to_punch])
        idx.append(i)


    candidate_cliques = gpd.GeoDataFrame(candidate_cliques, index=idx,
                                         columns=['candidates', 'subgraph'], geometry=geoms)

@@ -104,7 +109,8 @@ def identify_potential_overlaps(cg, cn, overlap=True):
        """
        cycle_to_punch = group.subgraph.iloc[0]
        subgraph = cg.create_node_subgraph(cycle_to_punch)
        union, _ = subgraph.compute_intersection(cycle_to_punch[0])#.query('overlaps_all == True')
        union, _ = subgraph.compute_intersection(
            cycle_to_punch[0])  # .query('overlaps_all == True')
        intersection = group.intersects(union.unary_union)
        return intersection

@@ -118,128 +124,6 @@ def identify_potential_overlaps(cg, cn, overlap=True):
    else:
        return candidate_cliques.candidates

def deepen_correspondences(cg, cn):
    pass

class ControlNetwork(object):
    measures_keys = ['point_id', 'image_index', 'keypoint_index', 'edge', 'match_idx', 'x', 'y', 'x_off', 'y_off', 'corr', 'valid']

    def __init__(self):
        self._point_id = 0
        self._measure_id = 0
        self.measure_to_point = {}
        self.data = pd.DataFrame(columns=self.measures_keys)

    @classmethod
    def from_candidategraph(cls, matches):
        cls = ControlNetwork()
        for match in matches:
            for idx, row in match.iterrows():
                edge = (row.source_image, row.destination_image)
                source_key = (row.source_image, row.source_idx)
                source_fields = row[['source_x', 'source_y']]
                destin_key = (row.destination_image, row.destination_idx)
                destin_fields = row[['destination_x', 'destination_y']]
                if cls.measure_to_point.get(source_key, None) is not None:
                    tempid = cls.measure_to_point[source_key]
                    cls.add_measure(destin_key, edge, row.name, destin_fields, point_id=tempid)
                elif cls.measure_to_point.get(destin_key, None) is not None:
                    tempid = cls.measure_to_point[destin_key]
                    cls.add_measure(source_key, edge, row.name,  source_fields, point_id=tempid)
                else:
                    cls.add_measure(source_key, edge, row.name,  source_fields)
                    cls.add_measure(destin_key, edge,row.name,  destin_fields)
                    cls._point_id += 1

        cls.data.index.name = 'measure_id'
        return cls

    def add_measure(self, key, edge, match_idx, fields, point_id=None):
        """
        Create a new measure that is coincident to a given point.  This method does not
        create the point if is missing.  When a measure is added to the graph, an associated
        row is added to the measures dataframe.

        Parameters
        ----------
        key : hashable
                  Some hashable id.  In the case of an autocnet graph object the
                  id should be in the form (image_id, match_id)

        point_id : hashable
                   The point to link the node to.  This is most likely an integer, but
                   any hashable should work.
        """
        if key in self.measure_to_point.keys():
            return
        if point_id == None:
            point_id = self._point_id
        self.measure_to_point[key] = point_id
        # The node_id is a composite key (image_id, correspondence_id), so just grab the image
        image_id = key[0]
        match_id = key[1]
        self.data.loc[self._measure_id] = [point_id, image_id, match_id, edge, match_idx, *fields, 0, 0, np.inf, True]
        self._measure_id += 1

    def remove_measure(self, idx):
        self.data = self.data.drop(self.data.index[idx])
        for r in idx:
            self.measure_to_point.pop(r, None)

    def validate_points(self):
        """
        Ensure that all control points currently in the nework are valid.

        Criteria for validity:

          * Singularity: A control point can have one and only one measure from any image

        Returns
        -------
         : pd.Series

        """

        def func(g):
            # One and only one measure constraint
            if g.image_index.duplicated().any():
                return True
            else: return False
        return self.data.groupby('point_id').apply(func)

    def clean_singles(self):
        """
        Take the `data` dataframe and return only those points with
        at least two measures.  This is automatically called before writing
        as functions such as subpixel matching can result in orphaned measures.
        """
        return self.data.groupby('point_id').apply(lambda g: g if len(g) > 1 else None)

    def to_isis(self, outname, serials, olist, *args, **kwargs): #pragma: no cover
        """
        Write the control network out to the ISIS3 control network format.
        """

        if self.validate_points().any() == True:
            warnings.warn('Control Network is not ISIS3 compliant.  Please run the validate_points method on the control network.')
            return

        # Apply the subpixel shift
        self.data.x += self.data.x_off
        self.data.y += self.data.y_off

        to_isis(outname + '.net', self.data.query('valid == True'),
                serials, *args, **kwargs)
        write_filelist(olist, outname + '.lis')

        # Back out the subpixel shift
        self.data.x -= self.data.x_off
        self.data.y -= self.data.y_off

    def to_bal(self):
        """
        Write the control network out to the Bundle Adjustment in the Large
        (BAL) file format.  For more information see:
        http://grail.cs.washington.edu/projects/bal/
        """
def deepen_correspondences(cg, cn):
    pass
+4 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ class Edge(dict, MutableMapping):
        self['destin_mbr'] = None
        self['overlap_latlon_coords'] = None


    def __repr__(self):
        return """
        Source Image Index: {}
@@ -63,10 +64,12 @@ class Edge(dict, MutableMapping):
        Available Masks: {}
        """.format(self.source, self.destination, self.masks)


    def __eq__(self, other):
        return utils.compare_dicts(self.__dict__, other.__dict__) *\
               utils.compare_dicts(self, other)


    @property
    def matches(self):
        if not hasattr(self, '_matches'):
@@ -487,7 +490,7 @@ class Edge(dict, MutableMapping):
            self.costs.loc[mask, 'correlation'] = strengths


    def suppress(self, suppression_func=spf.correlation, clean_keys=[], maskname='suppression', **kwargs):
    def suppress(self, suppression_func=spf.distance, clean_keys=[], maskname='suppression', **kwargs):
        """
        Apply a disc based suppression algorithm to get a good spatial
        distribution of high quality points, where the user defines some
+230 −57

File changed.

Preview size limit exceeded, changes collapsed.

+0 −1
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ from autocnet.io import keypoints as io_keypoints
from autocnet.matcher.add_depth import deepen_correspondences
from autocnet.matcher import cpu_extractor as fe
from autocnet.matcher import cpu_outlier_detector as od
from autocnet.matcher import suppression_funcs as spf
from autocnet.cg.cg import convex_hull_ratio

from autocnet.vis.graph_view import plot_node
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ def distance_ratio(edge, matches, ratio=0.8, single=False):
    return mask


def spatial_suppression(df, bounds, xkey='lon', ykey='lat', k=60, error_k=0.05, nsteps=250):
def spatial_suppression(df, bounds, xkey='x', ykey='y', k=60, error_k=0.05, nsteps=250):
    """
    Apply the spatial suppression algorithm over an arbitrary domain for all of the spatial
    data in the provided data frame.
Loading