Loading autocnet/graph/cluster_submit.py +3 −1 Original line number Diff line number Diff line Loading @@ -213,7 +213,9 @@ def main(): # pragma: no cover args = vars(parse_args()) # set up the logger logging.basicConfig(level=os.environ.get("AUTOCNET_LOGLEVEL", "INFO")) # Get the message # Get the message; 30s timeout should be long enough to handle minor # network issues or congestion without holding onto cluster resources # for an undue amount of time. queue = StrictRedis(host=args['host'], port=args['port'], db=0, socket_timeout=30, socket_connect_timeout=30) manage_messages(args, queue) Loading autocnet/matcher/subpixel.py +26 −4 Original line number Diff line number Diff line Loading @@ -805,6 +805,7 @@ def subpixel_register_point_smart(point, chooser='subpixel_register_point_smart', verbose=False, func=pattern_match, maximum_affine_reprojective_error=1.0, ncg=None): """ Loading Loading @@ -882,7 +883,8 @@ def subpixel_register_point_smart(point, size_y=match_kwargs['image_size'][1]) try: baseline_affine = estimate_local_affine(moving_roi, reference_roi) reference_roi, maximum_affine_reprojective_error) except Exception as e: log.exception(e) m = {'id': measure.id, Loading Loading @@ -1121,6 +1123,7 @@ def validate_candidate_measure(point, node_cache, parameters=[], func=pattern_match, maximum_affine_reprojective_error=1.0, **kwargs): """ Compute the matching distances, matching the reference measure to the measure Loading Loading @@ -1183,7 +1186,9 @@ def validate_candidate_measure(point, buffer=20) try: baseline_affine = estimate_local_affine(moving_roi, reference_roi) baseline_affine = estimate_local_affine(moving_roi, reference_roi, maximum_affine_reprojective_error) except: log.error('Unable to transform image to reference space. Likely too close to the edge of the non-reference image. Setting ignore=True') return [np.inf] * len(parameters) Loading Loading @@ -1220,6 +1225,7 @@ def smart_register_point(point, parameters=[], shared_kwargs={}, valid_reprojection_distance=1.5, maximum_affine_reprojective_error=1.0, ncg=None): """ The entry func for the smart subpixel registration code. This is the user Loading Loading @@ -1256,6 +1262,12 @@ def smart_register_point(point, measures matched from the moving image to the reference image with a distance less than this value in pixels are considered valid. Default: 1.1 maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- measures_to_update : list Loading @@ -1269,12 +1281,22 @@ def smart_register_point(point, if not isinstance(point, Points): point = get_point(ncg, session, point) measure_results, node_cache = subpixel_register_point_smart(point, session, ncg=ncg, parameters=parameters, **shared_kwargs) measure_results, node_cache = subpixel_register_point_smart(point, session, ncg=ncg, parameters=parameters, maximum_affine_reprojective_error=maximum_affine_reprojective_error **shared_kwargs) measures_to_update, measures_to_set_false = decider(measure_results) log.info(f'Found {len(measures_to_update)} measures that found subpixel registration consensus.') # Validate that the new position has consensus for measure in measures_to_update: reprojection_distances = validate_candidate_measure(point, measure, node_cache, parameters=parameters, **shared_kwargs) reprojection_distances = validate_candidate_measure(point, measure, node_cache, parameters=parameters, maximum_affine_reprojective_error=maximum_affine_reprojective_error, **shared_kwargs) log.info(f'Validation Distance Boolean: {np.array(reprojection_distances) < valid_reprojection_distance}') if np.sum(np.array(reprojection_distances) < valid_reprojection_distance) < 2: log.info(f"Measure {measure['id']} failed validation. Setting ignore=True for this measure.") Loading autocnet/spatial/overlap.py +14 −6 Original line number Diff line number Diff line from contextlib import nullcontext import time import logging import warnings import shapely import json Loading Loading @@ -121,8 +120,12 @@ def find_interesting_point(nodes, lon, lat, size=71, **kwargs): # Extract ORB features in a sub-image around the desired point image_roi = roi.Roi(node.geodata, sample, line) try: roi_array = image_roi.clip(size_x=size, size_y=size) # Units are pixels for the array except Exception as e: logging.debug(f'Failed to clip ROI with exception: {e}.') log.warn(f'Unable to clip ROI for {node}.') continue # Check if the image is valid and could be used as the reference if not is_valid_lroc_image(roi_array): log.info('Failed to find interesting features in image due to poor quality image.') Loading Loading @@ -223,7 +226,7 @@ def place_points_in_overlap(overlap, candidate_points = compgeom.distribute_points_in_geom(geom, ratio_size=ratio_size, **distribute_points_kwargs, **kwargs) logging.debug(f'Found {len(candidate_points)} in overlap {overlap.id}.') if not candidate_points.any(): warnings.warn(f'Failed to distribute points in overlap {overlap.id}') log.warn(f'Failed to distribute points in overlap {overlap.id}') return [] log.info(f'Have {len(candidate_points)} potential points to place in overlap {overlap.id}.') Loading Loading @@ -344,8 +347,13 @@ def place_points_in_image(image, continue # Extract ORB features in a sub-image around the desired point try: image_roi = roi.Roi(node.geodata, sample, line) roi_array = image_roi.clip(size_x=size, size_y=size) except Exception as e: log.debug(f'Failed to clip ROI with exception: {e}.') log.warn(f'Unable to clip ROI for {node}.') continue interesting = extract_most_interesting(roi_array) # kps are in the image space with upper left origin and the roi Loading autocnet/transformation/affine.py +26 −19 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ def estimate_affine_from_sensors(reference_image, bcenter_x, bcenter_y, size_x=40, size_y=40): size_y=40, maximum_affine_reprojective_error=1.0): """ Using the a priori sensor model, project corner and center points from the reference_image into the moving_image and use these points to estimate an affine transformation. Loading @@ -54,6 +55,12 @@ def estimate_affine_from_sensors(reference_image, half-height of the subimage used in the affine transformation size_y: int half-width of the subimage used in affine transformation maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- Loading @@ -72,20 +79,9 @@ def estimate_affine_from_sensors(reference_image, base_stopx = int(bcenter_x + size_x) base_stopy = int(bcenter_y + size_y) match_size = reference_image.raster_size # for now, require the entire window resides inside both cubes. # if base_stopx > match_size[0]: # raise Exception(f"Window: {base_stopx} > {match_size[0]}, center: {bcenter_x},{bcenter_y}") # if base_startx < 0: # raise Exception(f"Window: {base_startx} < 0, center: {bcenter_x},{bcenter_y}") # if base_stopy > match_size[1]: # raise Exception(f"Window: {base_stopy} > {match_size[1]}, center: {bcenter_x},{bcenter_y} ") # if base_starty < 0: # raise Exception(f"Window: {base_starty} < 0, center: {bcenter_x},{bcenter_y}") x_coords = [base_startx, base_startx, base_stopx, base_stopx, bcenter_x] y_coords = [base_starty, base_stopy, base_stopy, base_starty, bcenter_y] # Dispatch to the sensor to get the a priori pixel location in the input image lons, lats = reference_image.sensormodel.sampline2lonlat(x_coords, y_coords, allowoutside=True) xs, ys = moving_image.sensormodel.lonlat2sampline(lons, lats, allowoutside=True) Loading @@ -103,13 +99,14 @@ def estimate_affine_from_sensors(reference_image, raise ValueError(f'Unable to find enough points to compute an affine transformation. Found {len(dst_gcps)} points, but need at least 3.') log.debug(f'Number of GCPs for affine estimation: {len(dst_gcps)}') affine = tf.AffineTransform() # Estimate the affine twice. The first time to get an initial estimate # and the second time to drop points with an estimated reprojection # error greater than or equal to 0.1px. # error greater than the passed maximum_affine_reprojective_error. affine.estimate(np.array(base_gcps), np.array(dst_gcps)) residuals = affine.residuals(np.array(base_gcps), np.array(dst_gcps)) mask = residuals <= 1 mask = residuals <= maximum_affine_reprojective_error if len(np.array(base_gcps)[mask]) < 3: logging.info(f'Affine residuals: {residuals}.') raise ValueError(f'Unable to find enough points to compute an affine transformation. Found {len(np.array(dst_gcps)[mask])} points, but need at least 3.') Loading @@ -122,7 +119,7 @@ def estimate_affine_from_sensors(reference_image, return affine def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60, maximum_affine_reprojective_error=1.0): """ Applies the affine transfromation calculated in estimate_affine_from_sensors to the moving region of interest (ROI). Loading @@ -135,7 +132,16 @@ def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): moving_image : autocnet.io.geodataset.AGeoDataset Image that is expected to move around during the matching process, points are projected onto this image to compute an affine size_x: int half-height of the subimage used in the affine transformation size_y: int half-width of the subimage used in affine transformation maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- affine Loading @@ -146,7 +152,8 @@ def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): reference_roi.x, reference_roi.y, size_x=size_x, size_y=size_y) size_y=size_y, maximum_affine_reprojective_error=1.0) # Remove the translation from the transformation. Users of this function should add Loading Loading
autocnet/graph/cluster_submit.py +3 −1 Original line number Diff line number Diff line Loading @@ -213,7 +213,9 @@ def main(): # pragma: no cover args = vars(parse_args()) # set up the logger logging.basicConfig(level=os.environ.get("AUTOCNET_LOGLEVEL", "INFO")) # Get the message # Get the message; 30s timeout should be long enough to handle minor # network issues or congestion without holding onto cluster resources # for an undue amount of time. queue = StrictRedis(host=args['host'], port=args['port'], db=0, socket_timeout=30, socket_connect_timeout=30) manage_messages(args, queue) Loading
autocnet/matcher/subpixel.py +26 −4 Original line number Diff line number Diff line Loading @@ -805,6 +805,7 @@ def subpixel_register_point_smart(point, chooser='subpixel_register_point_smart', verbose=False, func=pattern_match, maximum_affine_reprojective_error=1.0, ncg=None): """ Loading Loading @@ -882,7 +883,8 @@ def subpixel_register_point_smart(point, size_y=match_kwargs['image_size'][1]) try: baseline_affine = estimate_local_affine(moving_roi, reference_roi) reference_roi, maximum_affine_reprojective_error) except Exception as e: log.exception(e) m = {'id': measure.id, Loading Loading @@ -1121,6 +1123,7 @@ def validate_candidate_measure(point, node_cache, parameters=[], func=pattern_match, maximum_affine_reprojective_error=1.0, **kwargs): """ Compute the matching distances, matching the reference measure to the measure Loading Loading @@ -1183,7 +1186,9 @@ def validate_candidate_measure(point, buffer=20) try: baseline_affine = estimate_local_affine(moving_roi, reference_roi) baseline_affine = estimate_local_affine(moving_roi, reference_roi, maximum_affine_reprojective_error) except: log.error('Unable to transform image to reference space. Likely too close to the edge of the non-reference image. Setting ignore=True') return [np.inf] * len(parameters) Loading Loading @@ -1220,6 +1225,7 @@ def smart_register_point(point, parameters=[], shared_kwargs={}, valid_reprojection_distance=1.5, maximum_affine_reprojective_error=1.0, ncg=None): """ The entry func for the smart subpixel registration code. This is the user Loading Loading @@ -1256,6 +1262,12 @@ def smart_register_point(point, measures matched from the moving image to the reference image with a distance less than this value in pixels are considered valid. Default: 1.1 maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- measures_to_update : list Loading @@ -1269,12 +1281,22 @@ def smart_register_point(point, if not isinstance(point, Points): point = get_point(ncg, session, point) measure_results, node_cache = subpixel_register_point_smart(point, session, ncg=ncg, parameters=parameters, **shared_kwargs) measure_results, node_cache = subpixel_register_point_smart(point, session, ncg=ncg, parameters=parameters, maximum_affine_reprojective_error=maximum_affine_reprojective_error **shared_kwargs) measures_to_update, measures_to_set_false = decider(measure_results) log.info(f'Found {len(measures_to_update)} measures that found subpixel registration consensus.') # Validate that the new position has consensus for measure in measures_to_update: reprojection_distances = validate_candidate_measure(point, measure, node_cache, parameters=parameters, **shared_kwargs) reprojection_distances = validate_candidate_measure(point, measure, node_cache, parameters=parameters, maximum_affine_reprojective_error=maximum_affine_reprojective_error, **shared_kwargs) log.info(f'Validation Distance Boolean: {np.array(reprojection_distances) < valid_reprojection_distance}') if np.sum(np.array(reprojection_distances) < valid_reprojection_distance) < 2: log.info(f"Measure {measure['id']} failed validation. Setting ignore=True for this measure.") Loading
autocnet/spatial/overlap.py +14 −6 Original line number Diff line number Diff line from contextlib import nullcontext import time import logging import warnings import shapely import json Loading Loading @@ -121,8 +120,12 @@ def find_interesting_point(nodes, lon, lat, size=71, **kwargs): # Extract ORB features in a sub-image around the desired point image_roi = roi.Roi(node.geodata, sample, line) try: roi_array = image_roi.clip(size_x=size, size_y=size) # Units are pixels for the array except Exception as e: logging.debug(f'Failed to clip ROI with exception: {e}.') log.warn(f'Unable to clip ROI for {node}.') continue # Check if the image is valid and could be used as the reference if not is_valid_lroc_image(roi_array): log.info('Failed to find interesting features in image due to poor quality image.') Loading Loading @@ -223,7 +226,7 @@ def place_points_in_overlap(overlap, candidate_points = compgeom.distribute_points_in_geom(geom, ratio_size=ratio_size, **distribute_points_kwargs, **kwargs) logging.debug(f'Found {len(candidate_points)} in overlap {overlap.id}.') if not candidate_points.any(): warnings.warn(f'Failed to distribute points in overlap {overlap.id}') log.warn(f'Failed to distribute points in overlap {overlap.id}') return [] log.info(f'Have {len(candidate_points)} potential points to place in overlap {overlap.id}.') Loading Loading @@ -344,8 +347,13 @@ def place_points_in_image(image, continue # Extract ORB features in a sub-image around the desired point try: image_roi = roi.Roi(node.geodata, sample, line) roi_array = image_roi.clip(size_x=size, size_y=size) except Exception as e: log.debug(f'Failed to clip ROI with exception: {e}.') log.warn(f'Unable to clip ROI for {node}.') continue interesting = extract_most_interesting(roi_array) # kps are in the image space with upper left origin and the roi Loading
autocnet/transformation/affine.py +26 −19 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ def estimate_affine_from_sensors(reference_image, bcenter_x, bcenter_y, size_x=40, size_y=40): size_y=40, maximum_affine_reprojective_error=1.0): """ Using the a priori sensor model, project corner and center points from the reference_image into the moving_image and use these points to estimate an affine transformation. Loading @@ -54,6 +55,12 @@ def estimate_affine_from_sensors(reference_image, half-height of the subimage used in the affine transformation size_y: int half-width of the subimage used in affine transformation maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- Loading @@ -72,20 +79,9 @@ def estimate_affine_from_sensors(reference_image, base_stopx = int(bcenter_x + size_x) base_stopy = int(bcenter_y + size_y) match_size = reference_image.raster_size # for now, require the entire window resides inside both cubes. # if base_stopx > match_size[0]: # raise Exception(f"Window: {base_stopx} > {match_size[0]}, center: {bcenter_x},{bcenter_y}") # if base_startx < 0: # raise Exception(f"Window: {base_startx} < 0, center: {bcenter_x},{bcenter_y}") # if base_stopy > match_size[1]: # raise Exception(f"Window: {base_stopy} > {match_size[1]}, center: {bcenter_x},{bcenter_y} ") # if base_starty < 0: # raise Exception(f"Window: {base_starty} < 0, center: {bcenter_x},{bcenter_y}") x_coords = [base_startx, base_startx, base_stopx, base_stopx, bcenter_x] y_coords = [base_starty, base_stopy, base_stopy, base_starty, bcenter_y] # Dispatch to the sensor to get the a priori pixel location in the input image lons, lats = reference_image.sensormodel.sampline2lonlat(x_coords, y_coords, allowoutside=True) xs, ys = moving_image.sensormodel.lonlat2sampline(lons, lats, allowoutside=True) Loading @@ -103,13 +99,14 @@ def estimate_affine_from_sensors(reference_image, raise ValueError(f'Unable to find enough points to compute an affine transformation. Found {len(dst_gcps)} points, but need at least 3.') log.debug(f'Number of GCPs for affine estimation: {len(dst_gcps)}') affine = tf.AffineTransform() # Estimate the affine twice. The first time to get an initial estimate # and the second time to drop points with an estimated reprojection # error greater than or equal to 0.1px. # error greater than the passed maximum_affine_reprojective_error. affine.estimate(np.array(base_gcps), np.array(dst_gcps)) residuals = affine.residuals(np.array(base_gcps), np.array(dst_gcps)) mask = residuals <= 1 mask = residuals <= maximum_affine_reprojective_error if len(np.array(base_gcps)[mask]) < 3: logging.info(f'Affine residuals: {residuals}.') raise ValueError(f'Unable to find enough points to compute an affine transformation. Found {len(np.array(dst_gcps)[mask])} points, but need at least 3.') Loading @@ -122,7 +119,7 @@ def estimate_affine_from_sensors(reference_image, return affine def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60, maximum_affine_reprojective_error=1.0): """ Applies the affine transfromation calculated in estimate_affine_from_sensors to the moving region of interest (ROI). Loading @@ -135,7 +132,16 @@ def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): moving_image : autocnet.io.geodataset.AGeoDataset Image that is expected to move around during the matching process, points are projected onto this image to compute an affine size_x: int half-height of the subimage used in the affine transformation size_y: int half-width of the subimage used in affine transformation maximum_affine_reprojective_error: float reprojective errors less than or equal to this value (default 1.0) are masked and removed from affine transform estimations. If less than 3 points are found, affine estimation will fail. This value can be used to loosen the reprojective constraint estimating the affine transform between images. Returns ------- affine Loading @@ -146,7 +152,8 @@ def estimate_local_affine(reference_roi, moving_roi, size_x=60, size_y=60): reference_roi.x, reference_roi.y, size_x=size_x, size_y=size_y) size_y=size_y, maximum_affine_reprojective_error=1.0) # Remove the translation from the transformation. Users of this function should add Loading