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

Multiple updates - separated by commit message (#527)

* Adds option (default) to place points using the minimum rotated rectangle.

* Fixes og oc calls to proj to use degrees

* Fixes #542

* subpixel changes that merge Kelvin's unmerged work

* Fixes queue flushes

* Updates for comments on PR

* Fixes #524

* Updates subpixel to support no variance.

* Updates to add union, fixes bugs
parent 07e77059
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -313,8 +313,7 @@ def xy_in_polygon(x,y, geom):
    """
    return geom.contains(Point(x, y))


def distribute_points_classic(geom, nspts, ewpts, **kwargs):
def distribute_points_classic(geom, nspts, ewpts, use_mrr=True, **kwargs):
    """
    This is a decision tree that attempts to perform a
    very simplistic approximation of the shape
@@ -334,13 +333,20 @@ def distribute_points_classic(geom, nspts, ewpts, **kwargs):
            The number of points to attempt to place
            in the E/W (right/left) direction
    
    use_mrr : boolean
              If True (default) compute the minimum rotated rectangle bounding
              the geometry

    Returns
    -------
    valid : list
            of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)]
    """
    original_geom = geom
    if use_mrr:
        geom = geom.minimum_rotated_rectangle
        
    geom_coords = np.column_stack(geom.envelope.exterior.xy)
    geom_coords = np.column_stack(geom.exterior.xy)
    coords = np.array(list(zip(*geom.envelope.exterior.xy))[:-1])

    ll = coords[0]
@@ -371,7 +377,7 @@ def distribute_points_classic(geom, nspts, ewpts, **kwargs):

    points = np.vstack(points)
    # Perform a spatial intersection check to eject points that are not valid
    valid = [p for p in points if xy_in_polygon(p[0], p[1], geom)]
    valid = [p for p in points if xy_in_polygon(p[0], p[1], original_geom)]
    return valid

def distribute_points_new(geom, nspts, ewpts, Session):
@@ -434,7 +440,8 @@ def distribute_points_new(geom, nspts, ewpts, Session):
def distribute_points_in_geom(geom, method="classic",
                              nspts_func=lambda x: ceil(round(x,1)*10),
                              ewpts_func=lambda x: ceil(round(x,1)*5),
                              Session=None):
                              Session=None,
                              **kwargs):
    """
    Given a geometry, attempt a basic classification of the shape.
    RIght now, this simply attempts to determine if the bounding box
@@ -477,6 +484,9 @@ def distribute_points_in_geom(geom, method="classic",
    point_distribution_func = point_funcs[method]

    coords = list(zip(*geom.envelope.exterior.xy))
   

    # This logic is kwarg swapping - need to trace this logic.
    short = np.inf
    long = -np.inf
    shortid = 0
@@ -490,9 +500,11 @@ def distribute_points_in_geom(geom, method="classic",
            long = d
            longid = i
    ratio = short/long
    
    ns = False
    ew = False
    valid = []
    
    # The polygons should be encoded with a lower left origin in counter-clockwise direction.
    # Therefore, if the 'bottom' is the short edge it should be id 0 and modulo 2 == 0.
    if shortid % 2 == 0:
@@ -515,7 +527,7 @@ def distribute_points_in_geom(geom, method="classic",
        if nspts == 1 and ewpts == 1:
            valid = single_centroid(geom)
        else:
            valid = point_distribution_func(geom, nspts, ewpts, Session=Session)
            valid = point_distribution_func(geom, nspts, ewpts, Session=Session, **kwargs)
    elif ew == True:
        # Since this is an LS, we should place these diagonally from the 'lower left' to the 'upper right'
        nspts = ewpts_func(short)
@@ -523,7 +535,7 @@ def distribute_points_in_geom(geom, method="classic",
        if nspts == 1 and ewpts == 1:
            valid = single_centroid(geom)
        else:
            valid = point_distribution_func(geom, nspts, ewpts, Session=Session)
            valid = point_distribution_func(geom, nspts, ewpts, Session=Session, **kwargs)
    else:
        print('WTF Willy')
    return valid
+46 −16
Original line number Diff line number Diff line
@@ -1268,7 +1268,6 @@ class CandidateGraph(nx.Graph):
        """
        return self.controlnetwork.groupby('point_id').apply(lambda g: g if len(g) > 1 else None)


    def to_isis(self, outname, flistpath=None, target="Mars"):  # pragma: no cover
        """
        Write the control network out to the ISIS3 control network format.
@@ -1310,13 +1309,6 @@ class CandidateGraph(nx.Graph):
        cnet.to_isis(df, outname, targetname=target)
        cnet.write_filelist(self.files, path=flistpath)

    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/
        """
        pass

class NetworkCandidateGraph(CandidateGraph):
    node_factory = NetworkNode
@@ -1468,14 +1460,18 @@ class NetworkCandidateGraph(CandidateGraph):
                                       port=conf['port'],
                                       db=0)
        self.processing_queue = conf['processing_queue']
        self.completed_queue = conf['completed_queue']
        self.working_queue = conf['working_queue']

    def empty_queues(self):
    def clear_queues(self):
        """
        Delete all messages from the redis queue. This a convenience method.
        The `redis_queue` object is a redis-py StrictRedis object with API
        documented at: https://redis-py.readthedocs.io/en/latest/#redis.StrictRedis
        """
        return self.redis_queue.flushall()
        queues = [self.processing_queue, self.completed_queue, self.working_queue]
        for q in queues:
            self.redis_queue.delete(q)

    def _execute_sql(self, sql):
        """
@@ -1586,6 +1582,7 @@ class NetworkCandidateGraph(CandidateGraph):
            query_string='',
            reapply=False,
            log_dir=None,
            queue=None,
            **kwargs):
        """
        A mirror of the apply function from the standard CandidateGraph object. This implementation
@@ -1647,6 +1644,10 @@ class NetworkCandidateGraph(CandidateGraph):
        kwargs : dict
                 Of keyword arguments passed to the function being applied

        queue : str
                The processing queue to use. If None (default), use the processing queue from
                the config file.

        Examples
        --------
        Apply a function to the overlay table omitting those overlay rows that already have
@@ -1708,16 +1709,19 @@ class NetworkCandidateGraph(CandidateGraph):
        isisroot = env['ISISROOT']
        isisdata = env['ISISDATA']

        isissetup = f'export ISISROOT={isisroot} && export ISIS3DATA={isisdata} && export ISISDATA={isisdata}'
        isissetup = f'export ISISROOT={isisroot} && export ISISDATA={isisdata}'
        condasetup = f'conda activate {condaenv}'
        job = f'acn_submit -r={rhost} -p={rport} {processing_queue}'
        command = f'{condasetup} && {isissetup} && {job}'

        if queue == None:
            queue = self.config['cluster']['queue']

        submitter = Slurm(command,
                     job_name='AutoCNet',
                     mem_per_cpu=self.config['cluster']['processing_memory'],
                     time=walltime,
                     partition=self.config['cluster']['queue'],
                     partition=queue,
                     output=log_dir+f'/autocnet.{function}-%j')
        submitter.submit(array='1-{}%{}'.format(job_counter,arraychunk), chunksize=chunksize)
        return job_counter
@@ -2217,12 +2221,18 @@ class NetworkCandidateGraph(CandidateGraph):
        llen = self.redis_queue.llen(self.config['redis']['processing_queue'])
        return llen

    def queue_flushdb(self):
    @property
    def union(self):
        """
        Clear the processing queue of any left over jobs from a previous cluster
        job cancellation or hanging jobs.
        The boundary formed by unioning (or merging) all of the input footprints. The result 
        will likely be a multipolygon, likely with holes where data were not collected.

        Returns
        """
        self.redis_queue.flushdb()
        if not hasattr(self, '_union'):
            with self.session_scope() as session:
                self._union = Images.union(session)
        return self._union

    def overlays(self, size_threshold=0):
        """
@@ -2413,3 +2423,23 @@ 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
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ SELECT measures."pointid",
        points."apriori",
        points."adjusted",
        points."pointIgnore",
        points."referenceIndex",
        measures."id",
        measures."serialnumber",
        measures."sample",
+21 −1
Original line number Diff line number Diff line
@@ -222,6 +222,20 @@ class Images(BaseMixin, Base):
        else:
            self._geom = from_shape(newgeom, srid=self.latitudinal_srid)

    @classmethod
    def union(cls, session):
        """
        The boundary formed by unioning (or merging) all of the input footprints. The result 
        will likely be a multipolygon, likely with holes where data were not collected.

        Returns
        -------
          : obj
            A shapely MULTIPOLYGON object
        """
        res = session.query(cls.geom.ST_Union()).one()[0]
        return to_shape(res)

class Overlay(BaseMixin, Base):
    __tablename__ = 'overlay'
    latitudinal_srid = -1
@@ -289,7 +303,8 @@ 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')
    measures = relationship('Measures', back_populates="point")
    reference_index = Column("referenceIndex", Integer, default=0)

    @hybrid_property
    def geom(self):
@@ -385,6 +400,7 @@ class Measures(BaseMixin, Base):
    linesigma = Column(Float)
    weight = Column(Float, default=None)
    rms = Column(Float)
    point = relationship("Points", back_populates="measures")

    @hybrid_property
    def measuretype(self):
@@ -396,6 +412,10 @@ class Measures(BaseMixin, Base):
            v = MeasureType(v)
        self._measuretype = v

    @property
    def reference_index(self):
        return self.point.reference_index

def try_db_creation(engine, config):
    from autocnet.io.db.triggers import valid_point_function, valid_point_trigger, valid_geom_function, valid_geom_trigger, ignore_image_function, ignore_image_trigger

+3 −1
Original line number Diff line number Diff line
@@ -83,7 +83,7 @@ def extract_features(array, extractor_method='sift', extractor_parameters={}):
        keypoints = pd.DataFrame(keypoints, columns=['x', 'y', 'response', 'size',
                                                     'angle', 'octave', 'layer'])

        if descriptors.dtype != np.float32:
        if hasattr(descriptors, 'dtype') and descriptors.dtype != np.float32:
            descriptors = descriptors.astype(np.float32)

    return keypoints, descriptors
@@ -115,6 +115,8 @@ def extract_most_interesting(image, extractor_method='orb', extractor_parameters
                                 extractor_method=extractor_method,
                                 extractor_parameters=extractor_parameters)

    if len(kps) == 0:
        return None
    # Naively assume that the maximum variance is the most unique feature
    vari = np.var(desc, axis=1)
    return kps.iloc[np.argmax(vari)]
Loading