From 6ef6d5a7eae367b3c24a0a412f23774b59315533 Mon Sep 17 00:00:00 2001 From: samncorn Date: Fri, 9 Feb 2024 13:41:25 -0600 Subject: [PATCH 1/6] Added check for existence of default camera footprint file. --- src/sorcha/modules/PPFootprintFilter.py | 129 +++++++++++++++++++++--- 1 file changed, 117 insertions(+), 12 deletions(-) diff --git a/src/sorcha/modules/PPFootprintFilter.py b/src/sorcha/modules/PPFootprintFilter.py index ddebd62f..461e8b4a 100644 --- a/src/sorcha/modules/PPFootprintFilter.py +++ b/src/sorcha/modules/PPFootprintFilter.py @@ -23,11 +23,34 @@ import matplotlib.pyplot as plt import sys import pkg_resources +from numba import njit deg2rad = np.radians sin = np.sin cos = np.cos +logger = logging.getLogger( __name__ ) + +@njit +def segmented_area( vertices, test_point): + """ return the area of a polygon defined by the given vertices and a test point. + If the test point is inside the polygon, andv the polygon is convex, the area will be correct. + Otherwise the are will be incorrect. + + Useful for testing whether a point is inside a polygon known to be convex. + + vertices should be ordered, either clockwise or counterclockwise. + """ + n = len(vertices) + area = 0.0 + for i in range( n ): # indexes at -1, which is necessary to hit all triangles + x1 = vertices[i-1, 0] - test_point[0] + y1 = vertices[i-1, 1] - test_point[1] + x2 = vertices[i, 0] - test_point[0] + y2 = vertices[i, 1] - test_point[1] + area += 0.5*np.abs( x1*y2 - x2*y1 ) + + return area def distToSegment(points, x0, y0, x1, y1): """Compute the distance from each point to the line segment defined by @@ -72,6 +95,83 @@ def distToSegment(points, x0, y0, x1, y1): # Compute the distances to the closest points on the line segment. return np.sqrt((points[0] - proj_x) * (points[0] - proj_x) + (points[1] - proj_y) * (points[1] - proj_y)) +# ============================================================================== +# coordinate transforms +# ============================================================================== + + +def radec_to_tangent_plane(ra, dec, field_ra, field_dec): + """ + Converts ra and dec to xy on the plane tangent to image center, in the 2-d coordinate system where y is aligned with the meridian. + + Parameters: + ----------- + ra (float/array of floats): observation Right Ascension, radians. + + dec (float/array of floats): observation Declination, radians. + + fieldra (float/array of floats): field pointing Right Ascension, radians. + + fielddec (float/array of floats): field pointing Declination, radians. + + fieldID (float/array of floats): Field ID, optional. + + Returns: + ---------- + x, y (float/array of floats): Coordinates on the focal plane, radians projected + to the plane tangent to the unit sphere. + + """ + + # convert to cartesian coordiantes on unit sphere + observation_vectors = np.array([cos(ra) * np.cos(dec), sin(ra) * np.cos(dec), sin(dec)]) # x # y # z + + field_vectors = np.array( + [cos(field_ra) * np.cos(field_dec), sin(field_ra) * np.cos(field_dec), sin(field_dec)] + ) + + # make the basis vectors for the fields of view + # the "x" basis is easy, 90 d rotation of the x, y components + focalx = np.zeros(field_vectors.shape) + focalx[0] = -field_vectors[1] + focalx[1] = field_vectors[0] + + # "y" by taking cross product of field vector and "x" + focaly = np.cross(field_vectors, focalx, axis=0) + + # normalize + focalx /= np.linalg.norm(focalx, axis=0) + focaly /= np.linalg.norm(focaly, axis=0) + + # extend observation vectors to plane tangent to field pointings + k = 1.0 / np.sum(field_vectors * observation_vectors, axis=0) + observation_vectors *= k + observation_vectors -= field_vectors + + # get observation vectors as combinations of focal vectors + x = np.sum(observation_vectors * focalx, axis=0) + y = np.sum(observation_vectors * focaly, axis=0) + + return x, y + + +def radec_to_focal_plane(ra, dec, field_ra, field_dec, field_rot): + # convert ra, dec to points on focal plane, x pointing to celestial north + x, y = radec_to_tangent_plane(ra, dec, field_ra, field_dec) + # rotate focal plane to align with detectors + xy = x + 1.0j * y + xy *= np.exp(1.0j * field_rot) # which direction to rotate? + + x = np.real(xy) + y = np.imag(xy) + + return x, y + + +# ============================================================================== +# detector class +# ============================================================================== + class Detector: """Detector class""" @@ -148,12 +248,10 @@ def ison(self, point, ϵ=10.0 ** (-11), edge_thresh=None, plot=False): selectedidx : array Indices of points in point array that fall on the sensor. """ - pplogger = logging.getLogger(__name__) - # points needs to be shape 2,n # if single point, needs to be an array of single element arrays if len(point.shape) != 2 or point.shape[0] != 2: - pplogger.error(f"ERROR: Detector.ison invalid array {point.shape}") + logger.error(f"ERROR: Detector.ison invalid array {point.shape}") sys.exit(f"ERROR: Detector.ison invalid array {point.shape}") # check whether point is in circle bounding the detector @@ -172,7 +270,7 @@ def ison(self, point, ϵ=10.0 ** (-11), edge_thresh=None, plot=False): elif self.units == "radians" or self.units == "rad": edge_thresh = np.radians(edge_thresh / 3600.0) else: - pplogger.error(f"ERROR: Detector.ison unable to convert edge_thresh to {self.units}") + logger.error(f"ERROR: Detector.ison unable to convert edge_thresh to {self.units}") sys.exit(f"ERROR: Detector.ison unable to convert edge_thresh to {self.units}") n = len(self.x) @@ -428,16 +526,23 @@ def __init__(self, path=None, detectorName="detector"): # the center of the camera should be the origin # if the user doesn't provide their own version of the footprint, # we'll use the default LSST version that comes included. - pplogger = logging.getLogger(__name__) - if path: - allcornersdf = pd.read_csv(path) - pplogger.info(f"Using CCD Detector file: {path}") + try: + allcornersdf = pd.read_csv(path) + logger.info(f"Using CCD Detector file: {path}") + except IOError: + logger.error(f'Provided detector footprint file does not exist.') + sys.exit(1) + else: - default_camera_config_file = "data/LSST_detector_corners_100123.csv" - stream = pkg_resources.resource_stream(__name__, default_camera_config_file) - pplogger.info(f"Using built-in CCD Detector file: {default_camera_config_file}") - allcornersdf = pd.read_csv(stream) + try: + default_camera_config_file = "data/LSST_detector_corners_100123.csv" + stream = pkg_resources.resource_stream(__name__, default_camera_config_file) + logger.info(f"Using built-in CCD Detector file: {default_camera_config_file}") + allcornersdf = pd.read_csv(stream) + except IOError: + logger.error( f'Error loading default camera footprint, exiting ...' ) + sys.exit(1) # build dictionary of detectorName:[list_of_inds] det_to_inds = {} From 9766fda060db092c3fdd7ecc12ec3124c08803f8 Mon Sep 17 00:00:00 2001 From: samncorn Date: Fri, 9 Feb 2024 14:16:34 -0600 Subject: [PATCH 2/6] Fixed call to non-existent square root function --- src/sorcha/modules/PPAddUncertainties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sorcha/modules/PPAddUncertainties.py b/src/sorcha/modules/PPAddUncertainties.py index 67acb530..14dac900 100755 --- a/src/sorcha/modules/PPAddUncertainties.py +++ b/src/sorcha/modules/PPAddUncertainties.py @@ -272,7 +272,7 @@ def calcAstrometricUncertainty( error_rand = calcRandomAstrometricErrorPerCoord(FWHMeff, SNR, astErrCoeff) # random astrometric error for nvisit observations if nvisit > 1: - error_rand = error_rand / sqrt(nvisit) + error_rand = error_rand / np.sqrt(nvisit) # add systematic error floor: astrom_error = np.sqrt(error_sys * error_sys + error_rand * error_rand) From a295b26f22095d758c66e9eecf58a62d19abd61d Mon Sep 17 00:00:00 2001 From: samncorn Date: Fri, 9 Feb 2024 14:18:59 -0600 Subject: [PATCH 3/6] formatting --- src/sorcha/modules/PPFootprintFilter.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/sorcha/modules/PPFootprintFilter.py b/src/sorcha/modules/PPFootprintFilter.py index 461e8b4a..490bca3c 100644 --- a/src/sorcha/modules/PPFootprintFilter.py +++ b/src/sorcha/modules/PPFootprintFilter.py @@ -29,13 +29,14 @@ sin = np.sin cos = np.cos -logger = logging.getLogger( __name__ ) +logger = logging.getLogger(__name__) + @njit -def segmented_area( vertices, test_point): - """ return the area of a polygon defined by the given vertices and a test point. +def segmented_area(vertices, test_point): + """return the area of a polygon defined by the given vertices and a test point. If the test point is inside the polygon, andv the polygon is convex, the area will be correct. - Otherwise the are will be incorrect. + Otherwise the are will be incorrect. Useful for testing whether a point is inside a polygon known to be convex. @@ -43,15 +44,16 @@ def segmented_area( vertices, test_point): """ n = len(vertices) area = 0.0 - for i in range( n ): # indexes at -1, which is necessary to hit all triangles - x1 = vertices[i-1, 0] - test_point[0] - y1 = vertices[i-1, 1] - test_point[1] + for i in range(n): # indexes at -1, which is necessary to hit all triangles + x1 = vertices[i - 1, 0] - test_point[0] + y1 = vertices[i - 1, 1] - test_point[1] x2 = vertices[i, 0] - test_point[0] y2 = vertices[i, 1] - test_point[1] - area += 0.5*np.abs( x1*y2 - x2*y1 ) + area += 0.5 * np.abs(x1 * y2 - x2 * y1) return area + def distToSegment(points, x0, y0, x1, y1): """Compute the distance from each point to the line segment defined by the points (x0, y0) and (x1, y1). Returns the distance in the same @@ -95,6 +97,7 @@ def distToSegment(points, x0, y0, x1, y1): # Compute the distances to the closest points on the line segment. return np.sqrt((points[0] - proj_x) * (points[0] - proj_x) + (points[1] - proj_y) * (points[1] - proj_y)) + # ============================================================================== # coordinate transforms # ============================================================================== @@ -531,9 +534,9 @@ def __init__(self, path=None, detectorName="detector"): allcornersdf = pd.read_csv(path) logger.info(f"Using CCD Detector file: {path}") except IOError: - logger.error(f'Provided detector footprint file does not exist.') + logger.error(f"Provided detector footprint file does not exist.") sys.exit(1) - + else: try: default_camera_config_file = "data/LSST_detector_corners_100123.csv" @@ -541,7 +544,7 @@ def __init__(self, path=None, detectorName="detector"): logger.info(f"Using built-in CCD Detector file: {default_camera_config_file}") allcornersdf = pd.read_csv(stream) except IOError: - logger.error( f'Error loading default camera footprint, exiting ...' ) + logger.error(f"Error loading default camera footprint, exiting ...") sys.exit(1) # build dictionary of detectorName:[list_of_inds] From 73f68f5597b10af276d26550c3a75abff58eedf4 Mon Sep 17 00:00:00 2001 From: samncorn Date: Fri, 9 Feb 2024 17:59:42 -0600 Subject: [PATCH 4/6] cleaned up branch --- src/sorcha/modules/PPFootprintFilter.py | 102 ------------------------ 1 file changed, 102 deletions(-) diff --git a/src/sorcha/modules/PPFootprintFilter.py b/src/sorcha/modules/PPFootprintFilter.py index 490bca3c..e28857b8 100644 --- a/src/sorcha/modules/PPFootprintFilter.py +++ b/src/sorcha/modules/PPFootprintFilter.py @@ -31,29 +31,6 @@ logger = logging.getLogger(__name__) - -@njit -def segmented_area(vertices, test_point): - """return the area of a polygon defined by the given vertices and a test point. - If the test point is inside the polygon, andv the polygon is convex, the area will be correct. - Otherwise the are will be incorrect. - - Useful for testing whether a point is inside a polygon known to be convex. - - vertices should be ordered, either clockwise or counterclockwise. - """ - n = len(vertices) - area = 0.0 - for i in range(n): # indexes at -1, which is necessary to hit all triangles - x1 = vertices[i - 1, 0] - test_point[0] - y1 = vertices[i - 1, 1] - test_point[1] - x2 = vertices[i, 0] - test_point[0] - y2 = vertices[i, 1] - test_point[1] - area += 0.5 * np.abs(x1 * y2 - x2 * y1) - - return area - - def distToSegment(points, x0, y0, x1, y1): """Compute the distance from each point to the line segment defined by the points (x0, y0) and (x1, y1). Returns the distance in the same @@ -97,85 +74,6 @@ def distToSegment(points, x0, y0, x1, y1): # Compute the distances to the closest points on the line segment. return np.sqrt((points[0] - proj_x) * (points[0] - proj_x) + (points[1] - proj_y) * (points[1] - proj_y)) - -# ============================================================================== -# coordinate transforms -# ============================================================================== - - -def radec_to_tangent_plane(ra, dec, field_ra, field_dec): - """ - Converts ra and dec to xy on the plane tangent to image center, in the 2-d coordinate system where y is aligned with the meridian. - - Parameters: - ----------- - ra (float/array of floats): observation Right Ascension, radians. - - dec (float/array of floats): observation Declination, radians. - - fieldra (float/array of floats): field pointing Right Ascension, radians. - - fielddec (float/array of floats): field pointing Declination, radians. - - fieldID (float/array of floats): Field ID, optional. - - Returns: - ---------- - x, y (float/array of floats): Coordinates on the focal plane, radians projected - to the plane tangent to the unit sphere. - - """ - - # convert to cartesian coordiantes on unit sphere - observation_vectors = np.array([cos(ra) * np.cos(dec), sin(ra) * np.cos(dec), sin(dec)]) # x # y # z - - field_vectors = np.array( - [cos(field_ra) * np.cos(field_dec), sin(field_ra) * np.cos(field_dec), sin(field_dec)] - ) - - # make the basis vectors for the fields of view - # the "x" basis is easy, 90 d rotation of the x, y components - focalx = np.zeros(field_vectors.shape) - focalx[0] = -field_vectors[1] - focalx[1] = field_vectors[0] - - # "y" by taking cross product of field vector and "x" - focaly = np.cross(field_vectors, focalx, axis=0) - - # normalize - focalx /= np.linalg.norm(focalx, axis=0) - focaly /= np.linalg.norm(focaly, axis=0) - - # extend observation vectors to plane tangent to field pointings - k = 1.0 / np.sum(field_vectors * observation_vectors, axis=0) - observation_vectors *= k - observation_vectors -= field_vectors - - # get observation vectors as combinations of focal vectors - x = np.sum(observation_vectors * focalx, axis=0) - y = np.sum(observation_vectors * focaly, axis=0) - - return x, y - - -def radec_to_focal_plane(ra, dec, field_ra, field_dec, field_rot): - # convert ra, dec to points on focal plane, x pointing to celestial north - x, y = radec_to_tangent_plane(ra, dec, field_ra, field_dec) - # rotate focal plane to align with detectors - xy = x + 1.0j * y - xy *= np.exp(1.0j * field_rot) # which direction to rotate? - - x = np.real(xy) - y = np.imag(xy) - - return x, y - - -# ============================================================================== -# detector class -# ============================================================================== - - class Detector: """Detector class""" From 6a8407f6d602843c33d4cb9a88de521db19715c1 Mon Sep 17 00:00:00 2001 From: Meg Schwamb Date: Sat, 10 Feb 2024 09:12:31 +0000 Subject: [PATCH 5/6] Update PPFootprintFilter.py Removing numba import since it's not used currently --- src/sorcha/modules/PPFootprintFilter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sorcha/modules/PPFootprintFilter.py b/src/sorcha/modules/PPFootprintFilter.py index e28857b8..f9eed631 100644 --- a/src/sorcha/modules/PPFootprintFilter.py +++ b/src/sorcha/modules/PPFootprintFilter.py @@ -23,7 +23,6 @@ import matplotlib.pyplot as plt import sys import pkg_resources -from numba import njit deg2rad = np.radians sin = np.sin From 27faae0d3089224f75837fa95fa5bbb4930d0333 Mon Sep 17 00:00:00 2001 From: Meg Schwamb Date: Sat, 10 Feb 2024 09:18:42 +0000 Subject: [PATCH 6/6] black footprint filter black footprint filter --- src/sorcha/modules/PPFootprintFilter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sorcha/modules/PPFootprintFilter.py b/src/sorcha/modules/PPFootprintFilter.py index f9eed631..ac6c0611 100644 --- a/src/sorcha/modules/PPFootprintFilter.py +++ b/src/sorcha/modules/PPFootprintFilter.py @@ -30,6 +30,7 @@ logger = logging.getLogger(__name__) + def distToSegment(points, x0, y0, x1, y1): """Compute the distance from each point to the line segment defined by the points (x0, y0) and (x1, y1). Returns the distance in the same @@ -73,6 +74,7 @@ def distToSegment(points, x0, y0, x1, y1): # Compute the distances to the closest points on the line segment. return np.sqrt((points[0] - proj_x) * (points[0] - proj_x) + (points[1] - proj_y) * (points[1] - proj_y)) + class Detector: """Detector class"""