Unverified Commit 96bc7794 authored by jlaura's avatar jlaura Committed by GitHub
Browse files

Single clean commit for parallel ground (#549)

* Single clean commit for parallel ground

* update for comments

* Updated environment.yml for updated dependencies

* Updates for comments
parent e54ccf36
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -471,7 +471,7 @@ def distribute_points_in_geom(geom, method="classic",

    Returns
    -------
    valid : list
    valid : np.ndarray
            of valid points in the form (x,y) or (lon,lat)

    """
@@ -516,7 +516,7 @@ def distribute_points_in_geom(geom, method="classic",
    # Decision Tree
    if ratio < 0.16 and geom.area < 0.01:
        # Class: Slivers - ignore.
        return []
        return np.array([])
    elif geom.area <= 0.004 and ratio >= 0.25:
        # Single point at the centroid
        valid = single_centroid(geom)
@@ -538,7 +538,7 @@ def distribute_points_in_geom(geom, method="classic",
            valid = point_distribution_func(geom, nspts, ewpts, Session=Session, **kwargs)
    else:
        print('WTF Willy')
    return valid
    return np.array(valid)


def alpha_shape(points, alpha):
+116 −32
Original line number Diff line number Diff line
@@ -44,8 +44,8 @@ from autocnet.graph.node import Node, NetworkNode
from autocnet.io import network as io_network
from autocnet.io.db import controlnetwork as io_controlnetwork
from autocnet.io.db.model import (Images, Keypoints, Matches, Cameras, Points,
                                  Base, Overlay, Edges, Costs, Measures, JsonEncoder,
                                  try_db_creation)
                                  Base, Overlay, Edges, Costs, Measures, CandidateGroundPoints,
                                  JsonEncoder, try_db_creation)
from autocnet.io.db.connection import new_connection, Parent
from autocnet.matcher import subpixel
from autocnet.matcher import cross_instrument_matcher as cim
@@ -1350,7 +1350,10 @@ class NetworkCandidateGraph(CandidateGraph):
                'image': Images,
                'images': Images,
                'i': Images,
                5: Images
                5: Images,
                'candidategroundpoints' : CandidateGroundPoints,
                'candidategroundpoint' : CandidateGroundPoints,
                6: CandidateGroundPoints
            }

    def config_from_file(self, filepath):
@@ -1583,6 +1586,7 @@ class NetworkCandidateGraph(CandidateGraph):
            reapply=False,
            log_dir=None,
            queue=None,
            exclude=None,
            **kwargs):
        """
        A mirror of the apply function from the standard CandidateGraph object. This implementation
@@ -1648,6 +1652,11 @@ class NetworkCandidateGraph(CandidateGraph):
                The processing queue to use. If None (default), use the processing queue from
                the config file.

        Returns
        -------
        job_str : str
                  The string job that is submitted to the job scheduler

        Examples
        --------
        Apply a function to the overlay table omitting those overlay rows that already have
@@ -1679,7 +1688,7 @@ class NetworkCandidateGraph(CandidateGraph):
            # Determine which obj will be called
            if isinstance(on, str):
                onobj = self.apply_iterable_options[on]
            elif isinstance(on, list):
            elif isinstance(on, (list, np.ndarray)):
                onobj = on

            # This method support arbitrary functions. The name needs to be a string for the log name.
@@ -1691,7 +1700,7 @@ class NetworkCandidateGraph(CandidateGraph):
            # Dispatch to either the database object message generator or the autocnet object message generator
            if isinstance(onobj, DeclarativeMeta):
                job_counter = self._push_row_messages(onobj, on, function, walltime, filters, query_string, args, kwargs)
            elif isinstance(onobj, list):
            elif isinstance(onobj, (list, np.ndarray)):
                job_counter = self._push_iterable_message(onobj, function, walltime, args, kwargs)
            elif isinstance(onobj, (nx.classes.reportviews.EdgeView, nx.classes.reportviews.NodeView)):
                job_counter = self._push_obj_messages(onobj, function, walltime, args, kwargs)
@@ -1723,8 +1732,10 @@ class NetworkCandidateGraph(CandidateGraph):
                     time=walltime,
                     partition=queue,
                     output=log_dir+f'/autocnet.{function}-%j')
        submitter.submit(array='1-{}%{}'.format(job_counter,arraychunk), chunksize=chunksize)
        return job_counter
        job_str = submitter.submit(array='1-{}%{}'.format(job_counter,arraychunk), 
                                   chunksize=chunksize, 
                                   exclude=exclude)
        return job_str

    def generic_callback(self, msg):
        """
@@ -1783,7 +1794,8 @@ class NetworkCandidateGraph(CandidateGraph):

        Returns
        -------
        None
        df : pd.DataFrame
             The pandas dataframe that is passed to plio to generate the control network.

        """
        # Read the cnet from the db
@@ -1813,6 +1825,10 @@ class NetworkCandidateGraph(CandidateGraph):
        cnet.to_isis(df, path, targetname=target)
        cnet.write_filelist(fpaths, path=flistpath)
        
        # Even though this method writes, having a non-None return 
        # let's a user work with the data that is passed to plio
        return df

    def update_from_jigsaw(self, path, pointid_func=lambda x: int(x.split('_')[-1])):
        """
        Updates the measures table in the database with data from
@@ -1919,11 +1935,17 @@ class NetworkCandidateGraph(CandidateGraph):
        ----------
        img_path : str
                  absolute path to image

        Returns
        -------
        node.id : int
                  The id of the newly added node. 
        """
        image_name = os.path.basename(img_path)
        node = NetworkNode(image_path=img_path, image_name=image_name)
        node.parent = self
        node.populate_db()
        return node.id
        
    def copy_images(self, newdir):
        """
@@ -2381,11 +2403,92 @@ class NetworkCandidateGraph(CandidateGraph):
        submitter.submit(array='1-{}'.format(job_counter))
        return job_counter

    def distribute_ground_uniform(self, distribute_points_kwargs={}):
        """
        Distribute candidate ground points into the union of the image footprints. This
        function returns a list of 2d nd-arrays where the first element is the longitude
        and the second element is the latitude.
        
        Parameters
        ----------
        distirbute_points_kwargs : dict
                                   Of arguments that are passed on the the
                                   distribute_points_in_geom argument in autocnet.cg.cg
        Returns
        -------
        valid : np.ndarray
                n, 2 array with each row in the form lon, lat
        
        Examples
        --------
        To use this method, one can first define the spacing of ground points in the north-
        south and east-west directions using the `distribute_points_kwargs` keyword argument:
            def ns(x):
                from math import ceil
                return ceil(round(x,1)*3)
            def ew(x):
                from math import ceil
                return ceil(round(x,1)*3)
        
        Next these arguments can be passed in in order to generate the grid of points:
            distribute_points_kwargs = {'nspts_func':ns, 'ewpts_func':ew, 'method':'classic'}
            valid = ncg.distribute_ground_uniform(distribute_points_kwargs=distribute_points_kwargs)
        
        At this point, it is possible to visualize the valid points inside of a Jupyter notebook. This
        is frequently convenient when combined with the `ncg.union` property that displays the unioned
        geometries in the NetworkCandidateGraph.
        Finally, the valid points can be propagated using apply. The code below will use the defined base
        to find the most interesting ground feature in the region of the valid point and write that point
        to the table defined by CandidateGroundPoints (autocnet.io.db.model):
        
            base = 'mc11_oxia_palus_dir_final.cub'
            ncg.apply('matcher.ground.find_most_interesting_ground', on=valid, args=(base,))
        """
        geom  = self.union
        valid = cg.distribute_points_in_geom(geom, **distribute_points_kwargs)
        return valid

    def distribute_ground_density(self, threshold=4, distribute_points_kwargs={}):
        """
        Distribute candidate ground points into overlaps with a number of images greater than or equal 
        to the threshold. This function returns a list of 2d nd-arrays where the first element is the 
        longitude and the second element is the latitude.
        
        Parameters
        ----------
        distirbute_points_kwargs : dict
                                   Of arguments that are passed on the the
                                   distribute_points_in_geom argument in autocnet.cg.cg
        threshold : int
                    Overlaps intersecting threshold images or greater have points placed. 
                    Default 4.
        Returns
        -------
        valid : np.ndarray
                n, 2 array in the form lon, lat

        Examples
        --------
        Usage for `distribute_ground_density` is identical to usage for `distribute_ground_uniform`.
        See Also
        --------
        autocnet.graph.network.NetworkCandidateGraph.distribute_ground_uniform
        """
        valid = []
        with self.session_scope() as session:
            res = session.query(Overlay).filter(func.array_length(Overlay.intersections, 1) >= threshold).all()
            for r in res:
                coords = cg.distribute_points_in_geom(r.geom, **distribute_points_kwargs)
                if len(coords) > 0:
                    valid.append(coords)
        valid = np.vstack(valid)
        return valid

    def subpixel_register_points(self, **kwargs):
        subpixel.subpixel_register_points(self.Session, **kwargs)

    def subpixel_register_point(self, pointid, **kwargs):
        subpixel.subpixel_register_point(self.Session, pointid, **kwarg)
        subpixel.subpixel_register_point(self.Session, pointid, **kwargs)

    def subpixel_regiter_mearure(self, measureid, **kwargs):
        subpixel.subpixel_register_measure(self.Session, measureid, **kwargs)
@@ -2405,23 +2508,4 @@ class NetworkCandidateGraph(CandidateGraph):
                                         self.dem,
                                         nodes,
                                         **kwargs)
    def distribute_ground(self, distribute_points_kwargs={}):
        """
        Distribute candidate ground points into the union of the image footprints. This
        function returns a list of 2d nd-arrays where the first element is the longitude
        and the second element is the latitude.
    
        Parameters
        ----------
        distirbute_points_kwargs : dict
                                   Of arguments that are passed on the the
                                   distribute_points_in_geom argument in autocnet.cg.cg

        Returns
        -------
        valid : list
                of nd-arrays in the form [array([lon, lat]), array([lon, lat])]
        """
        geom  = self.union
        valid = cg.distribute_points_in_geom(geom, **distribute_points_kwargs)
        return valid
+39 −6
Original line number Diff line number Diff line
@@ -246,6 +246,7 @@ class Overlay(BaseMixin, Base):
    points = relationship('Points',
                          primaryjoin='func.ST_Contains(foreign(Overlay.geom), Points.geom).as_comparison(1,2)',
                          backref=backref('overlay', uselist=False),
                          sync_backref=False,
                          viewonly=True,
                          uselist=True)

@@ -303,7 +304,7 @@ class Points(BaseMixin, Base):
    ignore = Column("pointIgnore", Boolean, default=False)
    _apriori = Column("apriori", Geometry('POINTZ', srid=rectangular_srid, dimension=3, spatial_index=False))
    _adjusted = Column("adjusted", Geometry('POINTZ', srid=rectangular_srid, dimension=3, spatial_index=False))
    measures = relationship('Measures', back_populates="point")
    measures = relationship('Measures', order_by="asc(Measures.id)", back_populates="point")
    reference_index = Column("referenceIndex", Integer, default=0)

    @hybrid_property
@@ -365,6 +366,39 @@ class Points(BaseMixin, Base):
    #def subpixel_register(self, Session, pointid, **kwargs):
    #    subpixel.subpixel_register_point(args=(Session, pointid), **kwargs)


class CandidateGroundPoints(BaseMixin, Base):
    __tablename__ = 'candidategroundpoints'
    latitudinal_srid = -1

    id = Column(Integer,primary_key=True, autoincrement=True)
    path = Column(String, nullable=False)
    choosername = Column("ChooserName", String)
    apriorisample = Column(Float)
    aprioriline = Column(Float)
    sample = Column(Float, nullable=False)
    line = Column(Float, nullable=False)
    _geom = Column("geom", Geometry('POINT', srid=latitudinal_srid, dimension=2, spatial_index=True))
    ignore = Column(Boolean, default=False)

    @hybrid_property
    def geom(self):
        try:
            return to_shape(self._geom)
        except:
            return self._geom

    @geom.setter
    def geom(self, newgeom):
        if isinstance(newgeom, osgeo.ogr.Geometry):
            # If an OGR geom, convert to shapely
            newgeom = shapely.wkt.loads(newgeom.ExportToWkt())
        if newgeom is None:
            self._geom = None
        else:
            self._geom = from_shape(newgeom, srid=self.latitudinal_srid)


class MeasureType(enum.IntEnum):
    """
    Enum to enforce measure type for ISIS control networks
@@ -377,8 +411,8 @@ class MeasureType(enum.IntEnum):
class Measures(BaseMixin, Base):
    __tablename__ = 'measures'
    id = Column(Integer,primary_key=True, autoincrement=True)
    pointid = Column(Integer, ForeignKey('points.id'), nullable=False)
    imageid = Column(Integer, ForeignKey('images.id'))
    pointid = Column(Integer, ForeignKey('points.id'), nullable=False, index=True)
    imageid = Column(Integer, ForeignKey('images.id'), index=True)
    serial = Column("serialnumber", String, nullable=False)
    _measuretype = Column("measureType", IntEnum(MeasureType), nullable=False)  # [0,3]  # Enum as above
    ignore = Column("measureIgnore", Boolean, default=False)
@@ -443,7 +477,7 @@ def try_db_creation(engine, config):
    Points.rectangular_srid = rectangular_srid
    Points.semimajor_rad = spatial['semimajor_rad']
    Points.semiminor_rad = spatial['semiminor_rad']
    for cls in [Points, Overlay, Images, Keypoints, Matches]:
    for cls in [Points, Overlay, Images, Keypoints, Matches, CandidateGroundPoints]:
        setattr(cls, 'latitudinal_srid', latitudinal_srid)

    # If the table does not exist, this will create it. This is used in case a
@@ -452,5 +486,4 @@ def try_db_creation(engine, config):
                                     Edges.__table__, Costs.__table__, Matches.__table__,
                                     Cameras.__table__, Points.__table__,
                                     Measures.__table__, Images.__table__,
                                     Keypoints.__table__])
                                     Keypoints.__table__, CandidateGroundPoints.__table__])
 No newline at end of file
+353 −0

File added.

Preview size limit exceeded, changes collapsed.

+10 −27
Original line number Diff line number Diff line
@@ -4,48 +4,40 @@ import numpy as np
from scipy.ndimage.interpolation import zoom


def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, func=cv2.TM_CCORR_NORMED):
def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, metric=cv2.TM_CCORR_NORMED):
    """
    Call an arbitrary pattern matcher using a subpixel approach where a center of gravity using
    the correlation coefficients are used for subpixel alignment.

    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image

    image : ndarray
            The image or sub-image to be searched

    subpixel_size : int
                    An odd integer that defines the window size used to compute
                    the moments

    max_scaler : float
                 The percentage offset to apply to the delta between the maximum
                 correlation and the maximum edge correlation.

    func : object
    metric : object
             The function to be used to perform the template based matching
             Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED}
             In testing the first two options perform significantly better with Apollo data.

    Returns
    -------
    x : float
        The x offset

    y : float
        The y offset

    max_corr : float
               The strength of the correlation in the range [-1, 1].
    """

    result = cv2.matchTemplate(image, template, method=func)
    result = cv2.matchTemplate(image, template, method=metric)

    if func == cv2.TM_SQDIFF or func == cv2.TM_SQDIFF_NORMED:
    if metric == cv2.TM_SQDIFF or metric == cv2.TM_SQDIFF_NORMED:
        y, x = np.unravel_index(np.argmin(result, axis=None), result.shape)
    else:
        y, x = np.unravel_index(np.argmax(result, axis=None), result.shape)
@@ -59,7 +51,7 @@ def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, func

    if area.shape != (subpixel_size+2, subpixel_size+2):
        print("Max correlation is too close to the boundary.")
        return None, None, 0
        return None, None, 0, None

    # Find the max on the edges, scale just like autoreg (but why?)
    edge_max = np.max(np.vstack([area[0], area[-1], area[:,0], area[:,-1]]))
@@ -83,43 +75,34 @@ def pattern_match_autoreg(template, image, subpixel_size=3, max_scaler=0.2, func
    y -= (image.shape[0] / 2) - (template.shape[0] / 2)
    x -= (image.shape[1] / 2) - (template.shape[1] / 2)

    return x, y, max_corr
    return float(x), float(y), float(max_corr), result

def pattern_match(template, image, upsampling=16, metric=cv2.TM_CCOEFF_NORMED, error_check=False):
    """
    Call an arbitrary pattern matcher using a subpixel approach where the template and image
    are upsampled using a third order polynomial.

    Parameters
    ----------
    template : ndarray
               The input search template used to 'query' the destination
               image

    image : ndarray
            The image or sub-image to be searched

    upsampling : int
                 The multiplier to upsample the template and image.

    func : object
           The function to be used to perform the template based matching
           Options: {cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF_NORMED, cv2.TM_SQDIFF_NORMED}
           In testing the first two options perform significantly better with Apollo data.

    error_check : bool
                  If True, also apply a different matcher and test that the values
                  are not too divergent.  Default, False.

    Returns
    -------

    x : float
        The x offset

    y : float
        The y offset

    strength : float
               The strength of the correlation in the range [-1, 1].
    """
Loading