diff --git a/autocnet/matcher/ciratefi.py b/autocnet/matcher/ciratefi.py index 9749547ac464dfd86d3164ce072cb49742d26182..9f9cc3c9fb7abd1eac7a6a3e16cb0824a626359b 100644 --- a/autocnet/matcher/ciratefi.py +++ b/autocnet/matcher/ciratefi.py @@ -1,5 +1,5 @@ import math -import warnings +import logging from bisect import bisect_left import cv2 @@ -11,6 +11,7 @@ from scipy.ndimage.interpolation import zoom import autocnet.utils.utils as util +log = logging.getLogger(__name__) def cifi(template, search_image, thresh=90, use_percentile=True, radii=list(range(1,12)), scales=[0.5, 0.57, 0.66, 0.76, 0.87, 1.0], verbose=False): @@ -75,6 +76,7 @@ def cifi(template, search_image, thresh=90, use_percentile=True, radii = np.asarray(radii) if not radii.size or not np.any(radii): + log.error('Input radii list is empty') raise ValueError('Input radii list is empty') scales = np.asarray(scales) @@ -82,10 +84,11 @@ def cifi(template, search_image, thresh=90, use_percentile=True, raise ValueError('Input scales list is empty') if max(radii) > max(template.shape)/2: - warnings.warn('Max Radii is larger than original template, this may produce sub-par results.' + log.warning('Max Radii is larger than original template, this may produce sub-par results.' 'Max radii: {} max template dimension: {}'.format(max(radii), max(template.shape))) if thresh < -1. or thresh > 1. and not use_percentile: + log.error(f'Thresholds must be in range [-1,1] when not using percentiles. Got: {thresh}') raise ValueError('Thresholds must be in range [-1,1] when not using percentiles. Got: {}' .format(thresh)) @@ -162,7 +165,7 @@ def cifi(template, search_image, thresh=90, use_percentile=True, fg_candidate_pixels = np.array([(y, x) for (y, x), coeff in np.ndenumerate(coeffs) if coeff >= thresh]) if fg_candidate_pixels.size == 0: - warnings.warn('Cifi returned empty set.') + log.warning('Cifi returned empty set.') if verbose: # pragma: no cover plt.imshow(coeffs, interpolation='none') @@ -244,25 +247,30 @@ def rafi(template, search_image, candidate_pixels, best_scales, thresh=95, # check inputs for validity if search_image.shape < template.shape: + log.error('Template Image size error') raise ValueError('Template Image is smaller than Search Image for template of' 'size: {} and search image of size: {}' .format(template.shape, search_image.shape)) candidate_pixels = np.asarray(candidate_pixels) if not candidate_pixels.size or not np.any(candidate_pixels): + log.error('empty pixel list error') raise ValueError('cadidate pixel list is empty') best_scales = np.asarray(best_scales, dtype=np.float32) if not best_scales.size or not np.any(best_scales): + log.error('scale list empty') raise ValueError('best_scale list is empty') if best_scales.shape != search_image.shape: + log.error('Error:: image shape does not match scale') raise ValueError('Search image and scales must be of the same shape ' 'got: best scales shape: {}, search image shape: {}' .format(best_scales.shape, search_image.shape)) radii = np.asarray(radii, dtype=int) if not radii.size or not np.any(radii): + log.error('Error:: input radii list empty') raise ValueError('Input radii list is empty') best_scales = np.asarray(best_scales, dtype=float) @@ -270,14 +278,16 @@ def rafi(template, search_image, candidate_pixels, best_scales, thresh=95, raise ValueError('Input best_scales list is empty') if max(radii) > max(template.shape)/2: - warnings.warn('Max Radii is larger than original template, this mat produce sub-par results.' + log.warning('Max Radii is larger than original template, this may produce sub-par results.' 'Max radii: {} max template dimension: {}'.format(max(radii), max(template.shape))) if thresh < -1. or thresh > 1. and not use_percentile: + log.error(f'Thresholds must be in range [-1,1] when not using percentiles. Got: {thresh}') raise ValueError('Thresholds must be in range [-1,1] when not using percentiles. Got: {}' .format(thresh)) if alpha <= 0: + log.error(f'Alpha: {alpha} not >= 0') raise ValueError('Alpha must be >= 0') alpha %= 2*math.pi @@ -317,7 +327,7 @@ def rafi(template, search_image, candidate_pixels, best_scales, thresh=95, scaled_center_y, scaled_center_x = (math.floor(scaled_img.shape[0]/2), math.floor(scaled_img.shape[1]/2)) except: - warnings.warn('{}\' window is to small to use for scale {} at resulting size' + log.warning('{}\' window is to small to use for scale {} at resulting size' .format((y, x), best_scales[y, x], scaled_img.shape)) rafi_alpha_means[i] = np.negative(np.ones(len(alpha_list))) continue @@ -365,7 +375,7 @@ def rafi(template, search_image, candidate_pixels, best_scales, thresh=95, best_rotation = best_rotation[rafi_mask] if sg_candidate_points.size == 0: - warnings.warn('Second filter Rafi returned empty set.') + log.warning('Second filter Rafi returned empty set.') if verbose: # pragma: no cover plt.imshow(image_pixels, interpolation='none') @@ -441,37 +451,45 @@ def tefi(template, search_image, candidate_pixels, best_scales, best_angles, # check all inputs for validity, probably a better way to do this if search_image.shape < template.shape: + log.error('Error:: template image size is smaller than searched image size') raise ValueError('Template Image is smaller than Search Image for template of' 'size: {} and search image of size: {}' .format(template.shape, search_image.shape)) candidate_pixels = np.asarray(candidate_pixels) if not candidate_pixels.size or not np.any(candidate_pixels): + log.error('Error: pixel list is empty') raise ValueError('cadidate pixel list is empty') best_scales = np.asarray(best_scales, dtype=np.float32) if not best_scales.size or not np.any(best_scales): + log.error('Error: best scale list is empty') raise ValueError('best_scale list is empty') if best_scales.shape != search_image.shape: + log.error('Error: search image and scale are not of the same shape') raise ValueError('Search image and scales must be of the same shape ' 'got: best scales shape: {}, search image shape: {}' .format(best_scales.shape, search_image.shape)) best_angles = np.asarray(best_angles, dtype=np.float32) if not best_angles.size or not np.any(best_angles): + log.error('Error: input best angle list is empty') raise ValueError('Input best angle list is empty') best_scales = np.asarray(best_scales, dtype=float) if not best_scales.size or not np.any(best_scales): + log.error('Error: input best scale list is empty') raise ValueError('Input best_scales list is empty') if thresh < -1. or thresh > 1. and not use_percentile: + log.error(f'Error: threshold out of range') raise ValueError('Thresholds must be in range [-1,1] when not using percentiles. Got: {}' .format(thresh)) # Check inputs if upsampling < 1: + log.error('Error: upsampling not >= 1') raise ValueError('Upsampling must be >= 1, got {}'.format(upsampling)) tefi_coeffs = np.zeros(candidate_pixels.shape[0]) diff --git a/autocnet/matcher/cpu_matcher.py b/autocnet/matcher/cpu_matcher.py index 0109d061fb6aa0f634d963ee62626131870da57e..8b7d7785c98b0c61b54f863a43e396a2db7ae32d 100644 --- a/autocnet/matcher/cpu_matcher.py +++ b/autocnet/matcher/cpu_matcher.py @@ -1,4 +1,4 @@ -import warnings +import logging import numpy as np import pandas as pd @@ -8,6 +8,8 @@ import cv2 FLANN_INDEX_KDTREE = 1 # Algorithm to set centers, DEFAULT_FLANN_PARAMETERS = dict(algorithm=FLANN_INDEX_KDTREE, trees=3) +log = logging.getLogger(__name__) + def match(edge, k=2, **kwargs): """ Given two sets of descriptors, utilize a FLANN (Approximate Nearest @@ -208,7 +210,7 @@ class FlannMatcher(object): qid, j.distance)) else: - warnings.warn('Likely self neighbor in query!') + log.warning('Likely self neighbor in query!') return pd.DataFrame(matched, columns=['source_image', 'source_idx', 'destination_image', 'destination_idx', 'distance']).astype(np.float32) diff --git a/autocnet/matcher/cpu_outlier_detector.py b/autocnet/matcher/cpu_outlier_detector.py index 2dc6afa7669490ea6a295bf988ea9a0c6271e53b..4a693022af1a01d17844f080e8160e174a362b5e 100644 --- a/autocnet/matcher/cpu_outlier_detector.py +++ b/autocnet/matcher/cpu_outlier_detector.py @@ -1,10 +1,11 @@ from collections import deque import math -import warnings +import logging import numpy as np import pandas as pd +log = logging.getLogger(__name__) def distance_ratio(edge, matches, ratio=0.8, single=False): """ @@ -112,7 +113,7 @@ def spatial_suppression(df, bounds, xkey='x', ykey='y', k=60, error_k=0.05, nste # Binary search mid_idx = int((min_idx + max_idx) / 2) if min_idx == mid_idx or mid_idx == max_idx: - warnings.warn('Unable to optimally solve.') + log.warning('Unable to optimally solve.') process = False else: # Setup to store results @@ -160,7 +161,7 @@ def spatial_suppression(df, bounds, xkey='x', ykey='y', k=60, error_k=0.05, nste max_idx = mid_idx if max_idx == 0: process = False - warnings.warn('Unable to retrieve {} points. Consider reducing the amount of points you request(k)'.format(k)) + log.warning('Unable to retrieve {} points. Consider reducing the amount of points you request(k)'.format(k)) if min_idx == max_idx: process = False elif len(result) > k + k * error_k: diff --git a/autocnet/matcher/cross_instrument_matcher.py b/autocnet/matcher/cross_instrument_matcher.py index 0b4c6cf165c53e50450fe7a2982926497fc5bd6e..2e755ef7c5666a7a14b581394afc8002f5a05d62 100755 --- a/autocnet/matcher/cross_instrument_matcher.py +++ b/autocnet/matcher/cross_instrument_matcher.py @@ -24,7 +24,9 @@ from autocnet.transformation import roi from autocnet.matcher.subpixel import geom_match_simple from autocnet.utils.utils import bytescale -import warnings +import logging + +log = logging.getLogger(__name__) def generate_ground_points(Session, ground_mosaic, nspts_func=lambda x: int(round(x,1)*1), ewpts_func=lambda x: int(round(x,1)*4), size=(100,100)): """ @@ -53,7 +55,7 @@ def generate_ground_points(Session, ground_mosaic, nspts_func=lambda x: int(roun if isinstance(ground_mosaic, str): ground_mosaic = GeoDataset(ground_mosaic) - warnings.warn('This function is not well tested. No tests currently exist \ + log.warning('This function is not well tested. No tests currently exist \ in the test suite for this version of the function.') session = Session() @@ -377,7 +379,7 @@ def propagate_control_network(Session, and cartesian) of successfully propagated points """ - warnings.warn('This function is not well tested. No tests currently exist \ + log.warning('This function is not well tested. No tests currently exist \ in the test suite for this version of the function.') match_func = check_match_func(match_func) @@ -440,7 +442,7 @@ def propagate_control_network(Session, res = session.query(Points).filter(spatial_intersects).all() if len(res) > 1: - warnings.warn(f"There is more than one point at lon: {lon}, lat: {lat}") + log.warning(f"There is more than one point at lon: {lon}, lat: {lat}") elif len(res) == 1: # update existing point with new measures diff --git a/autocnet/matcher/cuda_extractor.py b/autocnet/matcher/cuda_extractor.py index beaf2844cb8ea379c975c89d03de83d12db49759..ac894ee3f36a181678e6be0f802ad833a00c0ff0 100644 --- a/autocnet/matcher/cuda_extractor.py +++ b/autocnet/matcher/cuda_extractor.py @@ -1,4 +1,6 @@ -import warnings +import logging + +log = logging.getLogger(__name__) try: import cudasift as cs @@ -12,7 +14,7 @@ def extract_features(array, nfeatures=None, **kwargs): if not nfeatures: nfeatures = int(max(array.shape) / 1.25) else: - warnings.warn('NFeatures specified with the CudaSift implementation. Please ensure the distribution of keypoints is what you expect.') + log.warning('NFeatures specified with the CudaSift implementation. Please ensure the distribution of keypoints is what you expect.') siftdata = cs.PySiftData(nfeatures) cs.ExtractKeypoints(array, siftdata, **kwargs) diff --git a/autocnet/matcher/cuda_matcher.py b/autocnet/matcher/cuda_matcher.py index 0eec9d3074b439e55c9718c0b6f3f23b0eb54adb..77be761e4cf0591cb9250083f9fb952113c1f686 100644 --- a/autocnet/matcher/cuda_matcher.py +++ b/autocnet/matcher/cuda_matcher.py @@ -1,4 +1,3 @@ -import warnings try: import cudasift as cs diff --git a/autocnet/matcher/ground.py b/autocnet/matcher/ground.py index 0df2933bd04a2c33714f01a4b91cc2357184daf2..8fa25050539bb069b7b8abd229806b16f05635a3 100644 --- a/autocnet/matcher/ground.py +++ b/autocnet/matcher/ground.py @@ -1,5 +1,5 @@ import os -import warnings +import logging import numpy as np import pandas as pd @@ -21,6 +21,8 @@ from autocnet.transformation.spatial import reproject, oc2og from autocnet.io.db.model import Images from autocnet.transformation import roi +log = logging.getLogger(__name__) + def propagate_ground_point(point, match_func='classic', verbose=False, @@ -207,7 +209,7 @@ def find_most_interesting_ground(apriori_lon_lat, interesting = extract_most_interesting(image_roi, extractor_parameters={'nfeatures':30}) if interesting is None: - warnings.warn('No interesting feature found. This is likely caused by either large contiguous no data areas in the base or a mismatch in the base_dtype.') + log.warning('No interesting feature found. This is likely caused by either large contiguous no data areas in the base or a mismatch in the base_dtype.') return left_x, _, top_y, _ = image.image_extent @@ -232,7 +234,7 @@ def find_most_interesting_ground(apriori_lon_lat, res = session.query(CandidateGroundPoints).filter(ST_DWithin(CandidateGroundPoints._geom, g._geom, threshold)).all() if res: - warnings.warn(f'Skipping adding a point as another point already exists within {threshold} units.') + log.warning(f'Skipping adding a point as another point already exists within {threshold} units.') else: session.add(g) diff --git a/autocnet/matcher/subpixel.py b/autocnet/matcher/subpixel.py index b1155c522c5809a97094dfb6e57db070b589e7fe..b68e659d1dfda4e6555b9209dece85b26280c179 100644 --- a/autocnet/matcher/subpixel.py +++ b/autocnet/matcher/subpixel.py @@ -3,7 +3,7 @@ import json from math import modf, floor import time import numpy as np -import warnings +import logging from subprocess import CalledProcessError @@ -42,6 +42,7 @@ from autocnet.utils.utils import bytescale from sqlalchemy import inspect PIL.Image.MAX_IMAGE_PIXELS = sys.float_info.max +log = logging.getLogger(__name__) def check_geom_func(func): # TODO: Pain. Stick with one of these and delete this function along with @@ -414,7 +415,7 @@ def subpixel_transformed_template(sx, sy, dx, dy, # Hard check here to see if we are on the absolute edge of the template max_coord = np.unravel_index(corrmap.argmax(), corrmap.shape)[::-1] if 0 in max_coord or corrmap.shape[0]-1 == max_coord[0] or corrmap.shape[1]-1 == max_coord[1]: - warnings.warn('Maximum correlation is at the edge of the template. Results are ambiguous.', UserWarning) + log.warning('Maximum correlation is at the edge of the template. Results are ambiguous.') return [None] * 4 if verbose: @@ -518,7 +519,7 @@ def subpixel_template_classic(sx, sy, dx, dy, #print(f'{len(isis.get_isis_special_pixels(s_image))} chip sps: ', isis.get_isis_special_pixels(s_image)) if d_roi.variance == 0: - warnings.warn('Input ROI has no variance.') + log.warning('Input ROI has no variance.') return [None] * 4 if (s_image is None) or (d_template is None): @@ -640,7 +641,7 @@ def subpixel_template(sx, sy, dx, dy, # Hard check here to see if we are on the absolute edge of the template max_coord = np.unravel_index(corrmap.argmax(), corrmap.shape)[::-1] if 0 in max_coord or corrmap.shape[0]-1 == max_coord[0] or corrmap.shape[1]-1 == max_coord[1]: - warnings.warn('Maximum correlation is at the edge of the template. Results are ambiguous.', UserWarning) + log.warning('Maximum correlation is at the edge of the template. Results are ambiguous.') return [None] * 4 # Apply the shift to the center of the ROI object diff --git a/autocnet/matcher/tests/test_ciratefi.py b/autocnet/matcher/tests/test_ciratefi.py index b864428e64b40a3e85121c1af254586abb3138c4..297654595fc26dec5605d35d8a2dfc3e460a6f58 100644 --- a/autocnet/matcher/tests/test_ciratefi.py +++ b/autocnet/matcher/tests/test_ciratefi.py @@ -1,6 +1,7 @@ import math import unittest -import warnings +import logging +import re import numpy as np from imageio import imread @@ -51,10 +52,16 @@ def offset_template(img, img_coord): offset_template = roi.Roi(img, coord_x, coord_y, 5, 5).clip() return offset_template -def test_cifi_radii_too_large(template, search): - # check all warnings - with pytest.warns(UserWarning): - ciratefi.cifi(template, search, 1.0, radii=[100], use_percentile=False) +def test_cifi_radii_too_large(template, search, caplog): + # check all logs + ciratefi.cifi(template, search, 1.0, radii=[100], use_percentile=False) + num_pattern = '\d+' + captured_log = caplog.records[0].getMessage() + match = re.findall(num_pattern, captured_log) + assert (f'Max Radii is larger than original template, '\ + f'this may produce sub-par results.Max radii: {match[0]} max template dimension: {match[1]}'\ + == caplog.records[0].getMessage()) + def test_cifi_bounds_error(template, search): with pytest.raises(ValueError), pytest.warns(UserWarning): @@ -83,13 +90,12 @@ def test_cifi(template, search, cifi_thresh, radii): assert pixels.size in range(0,search.size) @pytest.mark.filterwarnings('ignore::UserWarning') # skimage deprecation warnings to move to new defaults -def test_rafi_warning(template, search): +def test_rafi_warning(template, search,caplog): rafi_pixels = [(10, 10)] rafi_scales = np.ones(search.shape, dtype=float) - with pytest.warns(UserWarning): - ciratefi.rafi(template, search, rafi_pixels, - rafi_scales, thresh=1, radii=[100], - use_percentile=False) + ciratefi.rafi(template, search, rafi_pixels, + rafi_scales, thresh=1, radii=[100], + use_percentile=False) def test_rafi_bounds_error(template, search): rafi_pixels = [(10, 10)] diff --git a/autocnet/matcher/tests/test_matcher.py b/autocnet/matcher/tests/test_matcher.py index 50afb5aa32f5725eb3dc6f1bc415d48f7e65690f..b087b46ad504ea12e8338a64b7642dd5c6c5eb97 100644 --- a/autocnet/matcher/tests/test_matcher.py +++ b/autocnet/matcher/tests/test_matcher.py @@ -1,7 +1,5 @@ import os import sys -import unittest -import warnings from .. import cpu_matcher from autocnet.examples import get_path diff --git a/autocnet/matcher/tests/test_outlier_detector.py b/autocnet/matcher/tests/test_outlier_detector.py index 6bf97366aa16ee75a2ed6169aca6219d5088413b..cd487f1bb243f3cb9d1113043e94cd2d42cb0f4f 100644 --- a/autocnet/matcher/tests/test_outlier_detector.py +++ b/autocnet/matcher/tests/test_outlier_detector.py @@ -1,7 +1,6 @@ import os import sys import unittest -import warnings import pytest @@ -65,11 +64,11 @@ class TestSpatialSuppression(unittest.TestCase): self.assertEqual(mask.sum(), 34) def test_suppress_non_optimal(self): - with warnings.catch_warnings(record=True) as w: + # with warnings.catch_warnings(record=True) as w: + with self.assertLogs() as captured: mask, k = cpu_outlier_detector.spatial_suppression(self.df, self.domain, k=30, xkey='lon', ykey='lat') self.assertEqual(k, 59) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue('Unable to optimally solve.' == str(w[0].message)) + self.assertTrue('Unable to optimally solve.' == captured.records[0].getMessage()) class testSuppressionRanges(unittest.TestCase): @@ -95,6 +94,5 @@ class testSuppressionRanges(unittest.TestCase): def test_normal_distribution(self): df = pd.DataFrame(self.r.uniform(0,100,(500, 3)), columns=['x', 'y', 'strength']) - with pytest.warns(UserWarning): - mask, k = cpu_outlier_detector.spatial_suppression(df, (0, 0, 100, 100), k = 15, xkey='x', ykey='y') + mask, k = cpu_outlier_detector.spatial_suppression(df, (0, 0, 100, 100), k = 15, xkey='x', ykey='y') self.assertEqual(len(df[mask]), 17) diff --git a/autocnet/matcher/tests/test_subpixel.py b/autocnet/matcher/tests/test_subpixel.py index 9d386006330ad6796b85136c404b60ca632128a5..2f3408c1acead43ce7a54669febde8462bd1278c 100644 --- a/autocnet/matcher/tests/test_subpixel.py +++ b/autocnet/matcher/tests/test_subpixel.py @@ -3,6 +3,7 @@ import os import sys import unittest from unittest.mock import patch +import logging from skimage import transform as tf from skimage.util import img_as_float @@ -93,11 +94,12 @@ def test_subpixel_template_at_edge(apollo_subsets, loc, failure): with patch('autocnet.matcher.subpixel.clip_roi', side_effect=clip_side_effect): if failure: - with pytest.warns(UserWarning, match=r'Maximum correlation \S+'): - nx, ny, strength, _ = sp.subpixel_template(a.shape[1]/2, a.shape[0]/2, - b.shape[1]/2, b.shape[0]/2, - a, b, upsampling=16, - func=func) + # with pytest.warns(UserWarning, match=r'Maximum correlation \S+'): + # match=r'Maximum correlation \S+' + nx, ny, strength, _ = sp.subpixel_template(a.shape[1]/2, a.shape[0]/2, + b.shape[1]/2, b.shape[0]/2, + a, b, upsampling=16, + func=func) else: nx, ny, strength, _ = sp.subpixel_template(a.shape[1]/2, a.shape[0]/2, b.shape[1]/2, b.shape[0]/2, @@ -159,13 +161,12 @@ def test_subpixel_transformed_template_at_edge(apollo_subsets, loc, failure): transform = tf.AffineTransform(rotation=math.radians(1), scale=(1.1,1.1)) with patch('autocnet.matcher.subpixel.clip_roi', side_effect=clip_side_effect): if failure: - with pytest.warns(UserWarning, match=r'Maximum correlation \S+'): - print(a.shape[1]/2, a.shape[0]/2,b.shape[1]/2, b.shape[0]/2, - a, b) - nx, ny, strength, _ = sp.subpixel_transformed_template(a.shape[1]/2, a.shape[0]/2, - b.shape[1]/2, b.shape[0]/2, - a, b, transform, upsampling=16, - func=func) + print(a.shape[1]/2, a.shape[0]/2,b.shape[1]/2, b.shape[0]/2, + a, b) + nx, ny, strength, _ = sp.subpixel_transformed_template(a.shape[1]/2, a.shape[0]/2, + b.shape[1]/2, b.shape[0]/2, + a, b, transform, upsampling=16, + func=func) else: nx, ny, strength, _ = sp.subpixel_transformed_template(a.shape[1]/2, a.shape[0]/2, b.shape[1]/2, b.shape[0]/2,