Loading CHANGELOG.md +3 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,9 @@ heading to indicate that only the bug fixes and security fixes are in the bug fi release. --> ## [Unreleased] ### Added - Ability to place points in centroids instead of overlaps. - Ability to find points in centroids focused around a plantary body ## [1.0.2] ### Fixed Loading autocnet/spatial/centroids.py 0 → 100644 +374 −0 Original line number Diff line number Diff line import numpy as np import shapely import math import csmapi import os import logging from sqlalchemy import text from autocnet.cg.cg import create_points_along_line from autocnet.io.db.model import Images, Measures, Overlay, Points, JsonEncoder from autocnet.graph.node import NetworkNode from autocnet.spatial import isis from autocnet.transformation import roi from autocnet.matcher.cpu_extractor import extract_most_interesting from autocnet.transformation.spatial import reproject, og2oc, oc2og import time # Set up logging file log = logging.getLogger(__name__) def get_number_of_points(target_distance_km, latitude, radius): """ Calculate the number of points needed to maintain a certain target distance (in km) around a specific latitude for a target planetary body. This assume that the body is spherical and not ellipsoidal. Parameters ___________ target_distance_km : int The target distance to maintin between points latitude : int the latitude where points are needed radius : int radius of the planetary body Returns _______ num_points : int Number of points needed to maintin target distance """ # Convert to radians target_distance_rad = target_distance_km / radius # Calculate longitudal distance between two points longitudinal_distance = 2 * math.asin(math.sqrt(math.sin(target_distance_rad/2)**2 / (math.cos(math.radians(latitude))**2))) # Calculate the circumference of the planetary body at the given latitude body_circumference = 2 * math.pi * radius * math.cos(math.radians(latitude)) # Calculate points needed num_points = int(body_circumference / longitudinal_distance) + 1 return num_points def winnow_points(polygon, list_of_points): """ This removes any points in a list that arent within a given polygon Parameters ___________ polygon : shapely.geom A shapely geometry object list_of_points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] Returns _______ points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] """ return [(point[0], point[1]) for point in list_of_points if polygon.contains(shapely.geometry.Point(point[0], point[1]))] def find_points_in_centroids(radius, target_distance_in_km, polygon=None, min_lat=None, max_lat=None, longitude_min=-180, longitude_max=180): """ This finds points within a specified geometry. It finds those points be placing points in a centroid pattern around latitude lines (default longitude: -180, 180) where the center is the center of the min_lat/max_lat/min_lon/max_lon Parameters ___________ radius : int radius of the planetary body target_distance_in_km : int The target distance to maintain between points polygon : shapely.geom A shapely geometry object min_lat : int The minimum latitude of the wanted area If polygon is passed, not necessary min_long : int The minimum longitude of the wanted area If polygon is passed, not necessary longitude_min : int The minimum longitde (ex. -180 or 0) longitude_max : int The maximum longitde (ex. 180 or 360) Returns _______ points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] """ # Set up processes if polygon: _, min_lat, _, max_lat = polygon.bounds latitude_intervals = np.arange(max_lat, min_lat, -0.005) points=[] # Itterate through each latitude, decrimenting at -0.005 # TODO: decide if this decrement should be a user input for lat in latitude_intervals: # Calculate the number of points needed to space points along a latitude line every __km num_points = get_number_of_points(target_distance_in_km, lat, radius) p1 = (longitude_min, lat) p2 = (longitude_max, lat) # Create a list of points for latitude lines line_points = [(point[0], point[1]) for point in create_points_along_line(p1, p2, num_points)] # If a polygon was given limit points so that they are only within the polygon if polygon: line_points = winnow_points(polygon, line_points) if len(line_points)!=0: points.extend(line_points) return points def place_points_in_centroids(valid, identifier="autocnet", cam_type="csm", size=71, point_type=2, ncg=None, use_cache=False, **kwargs): """ Place points into an centroid geometry by back-projecting using sensor models. The DEM specified in the config file will be used to calculate point elevations. Parameters ---------- valid : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] identifier: str The tag used to distinguish points laid down by this function. cam_type : str options: {"csm", "isis"} Pick what kind of camera model implementation to use. size : int The amount of pixel around a points initial location to search for an interesting feature to which to shift the point. point_type: int The type of point being placed. Default is pointtype=2, corresponding to free points. ncg: obj An autocnet.graph.network NetworkCandidateGraph instance representing the network to apply this function to use_cache : bool If False (default) this func opens a database session and writes points and measures directly to the respective tables. If True, this method writes messages to the point_insert (defined in ncg.config) redis queue for asynchronous (higher performance) inserts. """ t1 = time.time() points = [] semi_major = ncg.config['spatial']['semimajor_rad'] semi_minor = ncg.config['spatial']['semiminor_rad'] v=shapely.geometry.Point(valid[0], valid[1]) # Get all the images that the point intersects sql_query=text(f"SELECT id FROM images WHERE ST_Intersects('SRID=30100;{v}', geom)") with ncg.session_scope() as session: res = session.execute(sql_query).all() overlap_ids = [id[0] for id in res] # Make the nodes nodes = [] with ncg.session_scope() as session: for id in overlap_ids: res = session.query(Images).filter(Images.id == id).one() nn = NetworkNode(node_id=id, image_path=res.path) nn.parent = ncg nodes.append(nn) lon = v.x lat = v.y log.info(f'Point: {lon, lat}') # Need to get the first node and then convert from lat/lon to image space for reference_index, node in enumerate(nodes): log.debug(f'Starting with reference_index: {reference_index}') # reference_index is the index into the list of measures for the image that is not shifted and is set at the # reference against which all other images are registered. if cam_type == "isis": sample, line = isis.ground_to_image(node["image_path"], lon, lat) if sample == None or line == None: log.info(f'point ({lon}, {lat}) does not project to reference image {node["image_path"]}') continue if cam_type == "csm": lon_og, lat_og = oc2og(lon, lat, semi_major, semi_minor) x, y, z = reproject([lon_og, lat_og, height], semi_major, semi_minor, 'latlon', 'geocent') # The CSM conversion makes the LLA/ECEF conversion explicit gnd = csmapi.EcefCoord(x, y, z) image_coord = node.camera.groundToImage(gnd) sample, line = image_coord.samp, image_coord.line # Extract ORB features in a sub-image around the desired point image_roi = roi.Roi(node.geodata, sample, line, size_x=size, size_y=size) try: if image_roi.variance == 0: log.info(f'Failed to find interesting features in image.') continue except: log.info(f'Failed to find interesting features in image.') continue # Extract the most interesting feature in the search window interesting = extract_most_interesting(image_roi.clipped_array) if interesting is not None: # We have found an interesting feature and have identified the reference point. break log.debug(f'Current reference index: {reference_index}.') if interesting is None: log.info('Unable to find an interesting point, falling back to the a priori pointing') newsample = sample newline = line else: # kps are in the image space with upper left origin and the roi # could be the requested size or smaller if near an image boundary. # So use the roi upper left_x and top_y for the actual origin. left_x, _, top_y, _ = image_roi.image_extent newsample = left_x + interesting.x newline = top_y + interesting.y # Get the updated lat/lon from the feature in the node if cam_type == "isis": try: p = isis.point_info(node["image_path"], newsample, newline, point_type="image") except CalledProcessError as e: if 'Requested position does not project in camera model' in e.stderr: log.debug(node["image_path"]) log.info(f'interesting point ({newsample}, {newline}) does not project back to ground') try: x, y, z = p["BodyFixedCoordinate"].value except: x, y, z = ["BodyFixedCoordinate"] if getattr(p["BodyFixedCoordinate"], "units", "None").lower() == "km": x = x * 1000 y = y * 1000 z = z * 1000 elif cam_type == "csm": image_coord = csmapi.ImageCoord(newline, newsample) pcoord = node.camera.imageToGround(image_coord) # Get the BCEF coordinate from the lon, lat updated_lon_og, updated_lat_og, _ = reproject([pcoord.x, pcoord.y, pcoord.z], semi_major, semi_minor, 'geocent', 'latlon') updated_lon, updated_lat = og2oc(updated_lon_og, updated_lat_og, semi_major, semi_minor) updated_height = ncg.dem.get_height(updated_lat, updated_lon) # Get the BCEF coordinate from the lon, lat x, y, z = reproject([updated_lon_og, updated_lat_og, updated_height], semi_major, semi_minor, 'latlon', 'geocent') updated_lon_og, updated_lat_og, updated_height = reproject([x, y, z], semi_major, semi_minor, 'geocent', 'latlon') updated_lon, updated_lat = og2oc(updated_lon_og, updated_lat_og, semi_major, semi_minor) # Make the point point_geom = shapely.geometry.Point(x, y, z) log.debug(f'Creating point with reference_index: {reference_index}') point = Points(identifier=identifier, apriori=point_geom, adjusted=point_geom, pointtype=point_type, # Would be 3 or 4 for ground cam_type=cam_type, reference_index=reference_index) # Compute ground point to back project into measurtes gnd = csmapi.EcefCoord(x, y, z) for current_index, node in enumerate(nodes): if cam_type == "csm": image_coord = node.camera.groundToImage(gnd) sample, line = image_coord.samp, image_coord.line if cam_type == "isis": # If this try/except fails, then the reference_index could be wrong because the length # of the measures list is different than the length of the nodes list that was used # to find the most interesting feature. if not os.path.exists(node["image_path"]): log.info(f'Unable to find input image {node["image_path"]}') continue try: sample, line = isis.ground_to_image(node["image_path"], updated_lon, updated_lat) except: log.info(f"{node['image_path']} failed ground_to_image. Likely due to being processed incorrectly or is just a bad image that failed campt.") if sample == None or line == None: #except CalledProcessError as e: #except: # CalledProcessError is not catching the ValueError that this try/except is attempting to handle. log.info(f'interesting point ({updated_lon},{updated_lat}) does not project to image {node["image_path"]}') # If the current_index is greater than the reference_index, the change in list size does # not impact the positional index of the reference. If current_index is less than the # reference_index, then the reference_index needs to de-increment by one for each time # a measure fails to be placed. if current_index < reference_index: reference_index -= 1 log.debug('Reference de-incremented.') continue if node.isis_serial: point.measures.append(Measures(sample=sample, line=line, apriorisample=sample, aprioriline=line, imageid=node['node_id'], serial=node.isis_serial, measuretype=3, choosername='place_points_in_centroid')) else: log.info(f"{node['node_id']} serial number is NULL") log.debug(f'Current reference index in code: {reference_index}.') log.debug(f'Current reference index on point: {point.reference_index}') if len(point.measures) >= 2: points.append(point) log.info(f'Able to place {len(points)} points.') if not points: return # Insert the points into the database asynchronously (via redis) or synchronously via the ncg if use_cache: pipeline = ncg.redis_queue.pipeline() msgs = [json.dumps(point.to_dict(_hide=[]), cls=JsonEncoder) for point in points] pipeline.rpush(ncg.point_insert_queue, *msgs) pipeline.execute() # Push log.info('Using the cache') ncg.redis_queue.incr(ncg.point_insert_counter, amount=len(points)) else: with ncg.session_scope() as session: for point in points: session.add(point) t2 = time.time() log.info(f'Total processing time was {t2-t1} seconds.') return No newline at end of file Loading
CHANGELOG.md +3 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,9 @@ heading to indicate that only the bug fixes and security fixes are in the bug fi release. --> ## [Unreleased] ### Added - Ability to place points in centroids instead of overlaps. - Ability to find points in centroids focused around a plantary body ## [1.0.2] ### Fixed Loading
autocnet/spatial/centroids.py 0 → 100644 +374 −0 Original line number Diff line number Diff line import numpy as np import shapely import math import csmapi import os import logging from sqlalchemy import text from autocnet.cg.cg import create_points_along_line from autocnet.io.db.model import Images, Measures, Overlay, Points, JsonEncoder from autocnet.graph.node import NetworkNode from autocnet.spatial import isis from autocnet.transformation import roi from autocnet.matcher.cpu_extractor import extract_most_interesting from autocnet.transformation.spatial import reproject, og2oc, oc2og import time # Set up logging file log = logging.getLogger(__name__) def get_number_of_points(target_distance_km, latitude, radius): """ Calculate the number of points needed to maintain a certain target distance (in km) around a specific latitude for a target planetary body. This assume that the body is spherical and not ellipsoidal. Parameters ___________ target_distance_km : int The target distance to maintin between points latitude : int the latitude where points are needed radius : int radius of the planetary body Returns _______ num_points : int Number of points needed to maintin target distance """ # Convert to radians target_distance_rad = target_distance_km / radius # Calculate longitudal distance between two points longitudinal_distance = 2 * math.asin(math.sqrt(math.sin(target_distance_rad/2)**2 / (math.cos(math.radians(latitude))**2))) # Calculate the circumference of the planetary body at the given latitude body_circumference = 2 * math.pi * radius * math.cos(math.radians(latitude)) # Calculate points needed num_points = int(body_circumference / longitudinal_distance) + 1 return num_points def winnow_points(polygon, list_of_points): """ This removes any points in a list that arent within a given polygon Parameters ___________ polygon : shapely.geom A shapely geometry object list_of_points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] Returns _______ points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] """ return [(point[0], point[1]) for point in list_of_points if polygon.contains(shapely.geometry.Point(point[0], point[1]))] def find_points_in_centroids(radius, target_distance_in_km, polygon=None, min_lat=None, max_lat=None, longitude_min=-180, longitude_max=180): """ This finds points within a specified geometry. It finds those points be placing points in a centroid pattern around latitude lines (default longitude: -180, 180) where the center is the center of the min_lat/max_lat/min_lon/max_lon Parameters ___________ radius : int radius of the planetary body target_distance_in_km : int The target distance to maintain between points polygon : shapely.geom A shapely geometry object min_lat : int The minimum latitude of the wanted area If polygon is passed, not necessary min_long : int The minimum longitude of the wanted area If polygon is passed, not necessary longitude_min : int The minimum longitde (ex. -180 or 0) longitude_max : int The maximum longitde (ex. 180 or 360) Returns _______ points : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] """ # Set up processes if polygon: _, min_lat, _, max_lat = polygon.bounds latitude_intervals = np.arange(max_lat, min_lat, -0.005) points=[] # Itterate through each latitude, decrimenting at -0.005 # TODO: decide if this decrement should be a user input for lat in latitude_intervals: # Calculate the number of points needed to space points along a latitude line every __km num_points = get_number_of_points(target_distance_in_km, lat, radius) p1 = (longitude_min, lat) p2 = (longitude_max, lat) # Create a list of points for latitude lines line_points = [(point[0], point[1]) for point in create_points_along_line(p1, p2, num_points)] # If a polygon was given limit points so that they are only within the polygon if polygon: line_points = winnow_points(polygon, line_points) if len(line_points)!=0: points.extend(line_points) return points def place_points_in_centroids(valid, identifier="autocnet", cam_type="csm", size=71, point_type=2, ncg=None, use_cache=False, **kwargs): """ Place points into an centroid geometry by back-projecting using sensor models. The DEM specified in the config file will be used to calculate point elevations. Parameters ---------- valid : list list of point coordinates in the form [(x1,y1), (x2,y2), ..., (xn, yn)] identifier: str The tag used to distinguish points laid down by this function. cam_type : str options: {"csm", "isis"} Pick what kind of camera model implementation to use. size : int The amount of pixel around a points initial location to search for an interesting feature to which to shift the point. point_type: int The type of point being placed. Default is pointtype=2, corresponding to free points. ncg: obj An autocnet.graph.network NetworkCandidateGraph instance representing the network to apply this function to use_cache : bool If False (default) this func opens a database session and writes points and measures directly to the respective tables. If True, this method writes messages to the point_insert (defined in ncg.config) redis queue for asynchronous (higher performance) inserts. """ t1 = time.time() points = [] semi_major = ncg.config['spatial']['semimajor_rad'] semi_minor = ncg.config['spatial']['semiminor_rad'] v=shapely.geometry.Point(valid[0], valid[1]) # Get all the images that the point intersects sql_query=text(f"SELECT id FROM images WHERE ST_Intersects('SRID=30100;{v}', geom)") with ncg.session_scope() as session: res = session.execute(sql_query).all() overlap_ids = [id[0] for id in res] # Make the nodes nodes = [] with ncg.session_scope() as session: for id in overlap_ids: res = session.query(Images).filter(Images.id == id).one() nn = NetworkNode(node_id=id, image_path=res.path) nn.parent = ncg nodes.append(nn) lon = v.x lat = v.y log.info(f'Point: {lon, lat}') # Need to get the first node and then convert from lat/lon to image space for reference_index, node in enumerate(nodes): log.debug(f'Starting with reference_index: {reference_index}') # reference_index is the index into the list of measures for the image that is not shifted and is set at the # reference against which all other images are registered. if cam_type == "isis": sample, line = isis.ground_to_image(node["image_path"], lon, lat) if sample == None or line == None: log.info(f'point ({lon}, {lat}) does not project to reference image {node["image_path"]}') continue if cam_type == "csm": lon_og, lat_og = oc2og(lon, lat, semi_major, semi_minor) x, y, z = reproject([lon_og, lat_og, height], semi_major, semi_minor, 'latlon', 'geocent') # The CSM conversion makes the LLA/ECEF conversion explicit gnd = csmapi.EcefCoord(x, y, z) image_coord = node.camera.groundToImage(gnd) sample, line = image_coord.samp, image_coord.line # Extract ORB features in a sub-image around the desired point image_roi = roi.Roi(node.geodata, sample, line, size_x=size, size_y=size) try: if image_roi.variance == 0: log.info(f'Failed to find interesting features in image.') continue except: log.info(f'Failed to find interesting features in image.') continue # Extract the most interesting feature in the search window interesting = extract_most_interesting(image_roi.clipped_array) if interesting is not None: # We have found an interesting feature and have identified the reference point. break log.debug(f'Current reference index: {reference_index}.') if interesting is None: log.info('Unable to find an interesting point, falling back to the a priori pointing') newsample = sample newline = line else: # kps are in the image space with upper left origin and the roi # could be the requested size or smaller if near an image boundary. # So use the roi upper left_x and top_y for the actual origin. left_x, _, top_y, _ = image_roi.image_extent newsample = left_x + interesting.x newline = top_y + interesting.y # Get the updated lat/lon from the feature in the node if cam_type == "isis": try: p = isis.point_info(node["image_path"], newsample, newline, point_type="image") except CalledProcessError as e: if 'Requested position does not project in camera model' in e.stderr: log.debug(node["image_path"]) log.info(f'interesting point ({newsample}, {newline}) does not project back to ground') try: x, y, z = p["BodyFixedCoordinate"].value except: x, y, z = ["BodyFixedCoordinate"] if getattr(p["BodyFixedCoordinate"], "units", "None").lower() == "km": x = x * 1000 y = y * 1000 z = z * 1000 elif cam_type == "csm": image_coord = csmapi.ImageCoord(newline, newsample) pcoord = node.camera.imageToGround(image_coord) # Get the BCEF coordinate from the lon, lat updated_lon_og, updated_lat_og, _ = reproject([pcoord.x, pcoord.y, pcoord.z], semi_major, semi_minor, 'geocent', 'latlon') updated_lon, updated_lat = og2oc(updated_lon_og, updated_lat_og, semi_major, semi_minor) updated_height = ncg.dem.get_height(updated_lat, updated_lon) # Get the BCEF coordinate from the lon, lat x, y, z = reproject([updated_lon_og, updated_lat_og, updated_height], semi_major, semi_minor, 'latlon', 'geocent') updated_lon_og, updated_lat_og, updated_height = reproject([x, y, z], semi_major, semi_minor, 'geocent', 'latlon') updated_lon, updated_lat = og2oc(updated_lon_og, updated_lat_og, semi_major, semi_minor) # Make the point point_geom = shapely.geometry.Point(x, y, z) log.debug(f'Creating point with reference_index: {reference_index}') point = Points(identifier=identifier, apriori=point_geom, adjusted=point_geom, pointtype=point_type, # Would be 3 or 4 for ground cam_type=cam_type, reference_index=reference_index) # Compute ground point to back project into measurtes gnd = csmapi.EcefCoord(x, y, z) for current_index, node in enumerate(nodes): if cam_type == "csm": image_coord = node.camera.groundToImage(gnd) sample, line = image_coord.samp, image_coord.line if cam_type == "isis": # If this try/except fails, then the reference_index could be wrong because the length # of the measures list is different than the length of the nodes list that was used # to find the most interesting feature. if not os.path.exists(node["image_path"]): log.info(f'Unable to find input image {node["image_path"]}') continue try: sample, line = isis.ground_to_image(node["image_path"], updated_lon, updated_lat) except: log.info(f"{node['image_path']} failed ground_to_image. Likely due to being processed incorrectly or is just a bad image that failed campt.") if sample == None or line == None: #except CalledProcessError as e: #except: # CalledProcessError is not catching the ValueError that this try/except is attempting to handle. log.info(f'interesting point ({updated_lon},{updated_lat}) does not project to image {node["image_path"]}') # If the current_index is greater than the reference_index, the change in list size does # not impact the positional index of the reference. If current_index is less than the # reference_index, then the reference_index needs to de-increment by one for each time # a measure fails to be placed. if current_index < reference_index: reference_index -= 1 log.debug('Reference de-incremented.') continue if node.isis_serial: point.measures.append(Measures(sample=sample, line=line, apriorisample=sample, aprioriline=line, imageid=node['node_id'], serial=node.isis_serial, measuretype=3, choosername='place_points_in_centroid')) else: log.info(f"{node['node_id']} serial number is NULL") log.debug(f'Current reference index in code: {reference_index}.') log.debug(f'Current reference index on point: {point.reference_index}') if len(point.measures) >= 2: points.append(point) log.info(f'Able to place {len(points)} points.') if not points: return # Insert the points into the database asynchronously (via redis) or synchronously via the ncg if use_cache: pipeline = ncg.redis_queue.pipeline() msgs = [json.dumps(point.to_dict(_hide=[]), cls=JsonEncoder) for point in points] pipeline.rpush(ncg.point_insert_queue, *msgs) pipeline.execute() # Push log.info('Using the cache') ncg.redis_queue.incr(ncg.point_insert_counter, amount=len(points)) else: with ncg.session_scope() as session: for point in points: session.add(point) t2 = time.time() log.info(f'Total processing time was {t2-t1} seconds.') return No newline at end of file