def _angularSeparation(long1, lat1, long2, lat2): """ Angular separation between two points in radians Parameters ---------- long1 is the first longitudinal coordinate in radians lat1 is the first latitudinal coordinate in radians long2 is the second longitudinal coordinate in radians lat2 is the second latitudinal coordinate in radians Returns ------- The angular separation between the two points in radians Calculated based on the haversine formula From http://en.wikipedia.org/wiki/Haversine_formula """ are_arrays_1 = _validate_inputs([long1, lat1], ['long1', 'lat1'], 'angularSeparation') are_arrays_2 = _validate_inputs([long2, lat2], ['long2', 'lat2'], 'angularSeparation') # The code below is necessary because the call to np.radians() in # angularSeparation() will automatically convert floats # into length 1 numpy arrays, confusing validate_inputs() if are_arrays_1 and len(long1) == 1: are_arrays_1 = False long1 = long1[0] lat1 = lat1[0] if are_arrays_2 and len(long2) == 1: are_arrays_2 = False long2 = long2[0] lat2 = lat2[0] if are_arrays_1 and are_arrays_2: if len(long1) != len(long2): raise RuntimeError("You need to pass arrays of the same length " "as arguments to angularSeparation()") t1 = np.sin(lat2/2.0 - lat1/2.0)**2 t2 = np.cos(lat1)*np.cos(lat2)*np.sin(long2/2.0 - long1/2.0)**2 _sum = t1 + t2 if isinstance(_sum, numbers.Number): if _sum<0.0: _sum = 0.0 else: _sum = np.where(_sum<0.0, 0.0, _sum) return 2.0*np.arcsin(np.sqrt(_sum))
def test_validate_inputs(self): """ test that _validate_inputs returns expected values """ # test that it correctly identifies numpy array inputs self.assertTrue( _validate_inputs( [np.array([1, 2]), np.array([3, 4])], ['a', 'b'], 'dummy_method')) # test that it correctly identifies inputs that are numbers self.assertFalse(_validate_inputs([1, 2, 3], ['a', 'b', 'c'], 'dummy')) # test that the correct exception is raised when you pass in # a number a numpy array with self.assertRaises(RuntimeError) as ee: _validate_inputs([1, np.array([2, 3])], ['a', 'b'], 'dummy') self.assertIn("and the same type", ee.exception.args[0]) # test that the correct exception is raised when you pass in # numpy arrays of different length with self.assertRaises(RuntimeError) as ee: _validate_inputs( [np.array([1, 2]), np.array([1, 2, 3])], ['a', 'b'], 'dummy') self.assertIn("same length", ee.exception.args[0]) # test that an exception is raised if lists (rather than numpy # arrays) are passed in with self.assertRaises(RuntimeError) as ee: _validate_inputs([[1, 2], [3, 4]], ['a', 'b'], 'dummy') self.assertIn("either a number or a numpy array", ee.exception.args[0])
def _raDecFromPupilCoords(xPupil, yPupil, obs_metadata=None, includeRefraction=True, epoch=2000.0): """ @param [in] xPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] yPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] obs_metadata -- an instantiation of ObservationMetaData characterizing the state of the telescope @param[in] includeRefraction -- a boolean which controls the effects of refraction (refraction is used when finding the observed coordinates of the boresite specified by obs_metadata) @param [in] epoch -- julian epoch of the mean equinox used for the coordinate transformations (in years; defaults to 2000) @param [out] a 2-D numpy array in which the first row is RA and the second row is Dec (both in radians; both in the International Celestial Reference System) WARNING: This method does not account for apparent motion due to parallax. This method is only useful for mapping positions on a theoretical focal plane to positions on the celestial sphere. """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "raDecFromPupilCoords") if obs_metadata is None: raise RuntimeError("Cannot call raDecFromPupilCoords without obs_metadata") if epoch is None: raise RuntimeError("Cannot call raDecFromPupilCoords; epoch is None") if obs_metadata.rotSkyPos is None: raise RuntimeError("Cannot call raDecFromPupilCoords without rotSkyPos " + "in obs_metadata") if obs_metadata.pointingRA is None or obs_metadata.pointingDec is None: raise RuntimeError("Cannot call raDecFromPupilCoords " + "without pointingRA, pointingDec in obs_metadata") if obs_metadata.mjd is None: raise RuntimeError("Cannot calculate RA, Dec without mjd " + "in obs_metadata") raObs, decObs = _observedFromPupilCoords(xPupil, yPupil, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) ra_icrs, dec_icrs = _icrsFromObserved(raObs, decObs, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) return np.array([ra_icrs, dec_icrs])
def _validate_inputs_and_chipname(input_list, input_names, method_name, chip_name, chipname_can_be_none = True): """ This will wrap _validate_inputs, but also reformat chip_name if necessary. input_list is a list of the inputs passed to a method. input_name is a list of the variable names associated with input_list method_name is the name of the method whose input is being validated. chip_name is the chip_name variable passed into the calling method. chipname_can_be_none is a boolean that controls whether or not chip_name is allowed to be None. This method will raise a RuntimeError if: 1) the contents of input_list are not all of the same type 2) the contents of input_list are not all floats or numpy arrays 3) the contents of input_list are different lengths (if numpy arrays) 4) chip_name is None and chipname_can_be_none is False 5) chip_name is a list or array of different length than input_list[0] (if input_list[0] is a list or array) and len(chip_name)>1 This method returns a boolean indicating whether input_list[0] is a numpy array and a re-casting of chip_name as a list of length equal to input_list[0] (unless chip_name is None; then it will leave chip_name untouched) """ are_arrays = _validate_inputs(input_list, input_names, method_name) if chip_name is None and not chipname_can_be_none: raise RuntimeError("You passed chipName=None to %s" % method_name) if are_arrays: n_pts = len(input_list[0]) else: n_pts = 1 if isinstance(chip_name, list) or isinstance(chip_name, np.ndarray): if len(chip_name) > 1 and len(chip_name) != n_pts: raise RuntimeError("You passed %d chipNames to %s.\n" % (len(chip_name), method_name) + "You passed %d %s values." % (len(input_list[0]), input_names[0])) if len(chip_name) == 1 and n_pts > 1: chip_name_out = [chip_name[0]]*n_pts else: chip_name_out = chip_name return are_arrays, chip_name_out elif chip_name is None: return are_arrays, chip_name else: return are_arrays, [chip_name]*n_pts
def _raDecFromAltAz(altRad, azRad, obs, includeRefraction=True): """ Convert altitude and azimuth to RA and Dec @param [in] altRad is the altitude in radians. Can be a numpy array or a single value. @param [in] azRad is the azimuth in radians. Cant be a numpy array or a single value. @param [in] obs is an ObservationMetaData characterizing the site of the telescope and the MJD of the observation @param [in] includeRefraction is a boolean that turns refraction on and off (default True) @param [out] RA in radians (in the International Celestial Reference System) @param [out] Dec in radians (in the International Celestial Reference System) Note: This method is only accurate to within 0.01 arcsec near azimuth = 0 or pi """ are_arrays = _validate_inputs([altRad, azRad], ['altRad', 'azRad'], "raDecFromAltAz") lst = calcLmstLast(obs.mjd.UT1, obs.site.longitude_rad) last = lst[1] sinAlt = np.sin(altRad) cosLat = np.cos(obs.site.latitude_rad) sinLat = np.sin(obs.site.latitude_rad) decObs = np.arcsin(sinLat * sinAlt + cosLat * np.cos(altRad) * np.cos(azRad)) costheta = (sinAlt - np.sin(decObs) * sinLat) / (np.cos(decObs) * cosLat) if are_arrays: haRad0 = np.arccos(costheta) # Make sure there were no NaNs nanSpots = np.where(np.isnan(haRad0))[0] if np.size(nanSpots) > 0: haRad0[nanSpots] = 0.5 * np.pi * \ (1.0 - np.sign(costheta[nanSpots])) else: haRad0 = np.arccos(costheta) if np.isnan(haRad0): if np.sign(costheta) > 0.0: haRad0 = 0.0 else: haRad0 = np.pi haRad = np.where(np.sin(azRad) >= 0.0, -1.0 * haRad0, haRad0) raObs = np.radians(last * 15.) - haRad raRad, decRad = _icrsFromObserved(raObs, decObs, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) return raRad, decRad
def _raDecFromAltAz(altRad, azRad, obs, includeRefraction=True): """ Convert altitude and azimuth to RA and Dec @param [in] altRad is the altitude in radians. Can be a numpy array or a single value. @param [in] azRad is the azimuth in radians. Cant be a numpy array or a single value. @param [in] obs is an ObservationMetaData characterizing the site of the telescope and the MJD of the observation @param [in] includeRefraction is a boolean that turns refraction on and off (default True) @param [out] RA in radians (in the International Celestial Reference System) @param [out] Dec in radians (in the International Celestial Reference System) Note: This method is only accurate to within 0.01 arcsec near azimuth = 0 or pi """ with np.errstate(invalid='ignore', divide='ignore'): are_arrays = _validate_inputs( [altRad, azRad], ['altRad', 'azRad'], "raDecFromAltAz") lst = calcLmstLast(obs.mjd.UT1, obs.site.longitude_rad) last = lst[1] sinAlt = np.sin(altRad) cosLat = np.cos(obs.site.latitude_rad) sinLat = np.sin(obs.site.latitude_rad) decObs = np.arcsin(sinLat * sinAlt + cosLat * np.cos(altRad) * np.cos(azRad)) costheta = (sinAlt - np.sin(decObs) * sinLat) / (np.cos(decObs) * cosLat) if are_arrays: haRad0 = np.arccos(costheta) # Make sure there were no NaNs nanSpots = np.where(np.isnan(haRad0))[0] if np.size(nanSpots) > 0: haRad0[nanSpots] = 0.5 * np.pi * \ (1.0 - np.sign(costheta[nanSpots])) else: haRad0 = np.arccos(costheta) if np.isnan(haRad0): if np.sign(costheta) > 0.0: haRad0 = 0.0 else: haRad0 = np.pi haRad = np.where(np.sin(azRad) >= 0.0, -1.0 * haRad0, haRad0) raObs = np.radians(last * 15.) - haRad raRad, decRad = _icrsFromObserved(raObs, decObs, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) return raRad, decRad
def _appGeoFromObserved(ra, dec, includeRefraction=True, wavelength=0.5, obs_metadata=None): """ Convert observed (RA, Dec) to apparent geocentric (RA, Dec). More specifically: undo the effects of refraction and diurnal aberration. Note: This method is only accurate at zenith distances less than ~ 75 degrees. This method works in radians. @param [in] ra is observed RA (radians). Can be a numpy array or a number. @param [in] dec is observed Dec (radians). Can be a numpy array or a number. @param [in] includeRefraction is a boolean to turn refraction on and off @param [in] wavelength is effective wavelength in microns (default: 0.5) @param [in] obs_metadata is an ObservationMetaData characterizing the observation. @param [out] a 2-D numpy array in which the first row is the apparent geocentric RA and the second row is the apparentGeocentric Dec (both in radians) """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "appGeoFromObserved") if obs_metadata is None: raise RuntimeError( "Cannot call appGeoFromObserved without an obs_metadata") if obs_metadata.site is None: raise RuntimeError( "Cannot call appGeoFromObserved: obs_metadata has no site info") if obs_metadata.mjd is None: raise RuntimeError( "Cannot call appGeoFromObserved: obs_metadata has no mjd") obsPrms = _calculateObservatoryParameters(obs_metadata, wavelength, includeRefraction) if are_arrays: raOut, decOut = palpy.oapqkVector('r', ra, dec, obsPrms) else: raOut, decOut = palpy.oapqk('r', ra, dec, obsPrms) return np.array([raOut, decOut])
def _icrsFromAppGeo(ra, dec, epoch=2000.0, mjd=None): """ Convert the apparent geocentric position in (RA, Dec) to the mean position in the International Celestial Reference System (ICRS) This method undoes the effects of precession, annual aberration, and nutation. It is meant for mapping pointing RA and Dec (which presumably include the above effects) back to mean ICRS RA and Dec so that the user knows how to query a database of mean RA and Decs for objects observed at a given telescope pointing. WARNING: This method does not account for apparent motion due to parallax. This means it should not be used to invert the ICRS-to-apparent geocentric transformation for actual celestial objects. This method is only useful for mapping positions on a theoretical celestial sphere. This method works in radians. @param [in] ra in radians (apparent geocentric). Can be a numpy array or a number. @param [in] dec in radians (apparent geocentric). Can be a numpy array or a number. @param [in] epoch is the julian epoch (in years) of the equinox against which to measure RA (default: 2000.0) @param [in] mjd is an instantiation of the ModifiedJulianDate class representing the date of the observation @param [out] a 2-D numpy array in which the first row is the mean ICRS RA and the second row is the mean ICRS Dec (both in radians) """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "icrsFromAppGeo") # Define star independent mean to apparent place parameters # palpy.mappa calculates the star-independent parameters # needed to correct RA and Dec # e.g the Earth barycentric and heliocentric position and velocity, # the precession-nutation matrix, etc. # # arguments of palpy.mappa are: # epoch of mean equinox to be used (Julian) # # date (MJD) params = palpy.mappa(epoch, mjd.TDB) if are_arrays: raOut, decOut = palpy.ampqkVector(ra, dec, params) else: raOut, decOut = palpy.ampqk(ra, dec, params) return np.array([raOut, decOut])
def _altAzPaFromRaDec(raRad, decRad, obs, includeRefraction=True): """ Convert RA, Dec, longitude, latitude and MJD into altitude, azimuth and parallactic angle using PALPY @param [in] raRad is RA in radians. Can be a numpy array or a single value. Assumed to be in the International Celestial Reference System. @param [in] decRad is Dec in radians. Can be a numpy array or a single value. Assumed to be in the International Celestial Reference System. @param [in] obs is an ObservationMetaData characterizing the site of the telescope and the MJD of the observation @param [in] includeRefraction is a boolean that turns refraction on and off (default True) @param [out] altitude in radians @param [out] azimuth in radians @param [out] parallactic angle in radians WARNING: This method does not account for apparent motion due to parallax. This method is only useful for mapping positions on a theoretical focal plan to positions on the celestial sphere. """ are_arrays = _validate_inputs([raRad, decRad], ['ra', 'dec'], "altAzPaFromRaDec") raObs, decObs = \ _observedFromICRS(raRad, decRad, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) lst = calcLmstLast(obs.mjd.UT1, obs.site.longitude_rad) last = lst[1] haRad = np.radians(last * 15.0) - raObs if are_arrays: az, azd, azdd, \ alt, altd, altdd, \ pa, pad, padd = palpy.altazVector( haRad, decObs, obs.site.latitude_rad) else: az, azd, azdd, \ alt, altd, altdd, \ pa, pad, padd = palpy.altaz(haRad, decObs, obs.site.latitude_rad) return alt, az, pa
def chipNameFromPupilCoordsLSST(xPupil, yPupil, allow_multiple_chips=False): """ Return the names of LSST detectors that see the object specified by either (xPupil, yPupil). @param [in] xPupil is the x pupil coordinate in radians. Must be a numpy array. @param [in] yPupil is the y pupil coordinate in radians. Must be a numpy array. @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames and warning will be emitted. If it is 'True' and an object falls on more than one chip, a list of chipNames will appear for that object. @param [out] a numpy array of chip names """ global _lsst_pupil_coord_map if _lsst_pupil_coord_map is None: _build_lsst_pupil_coord_map() are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoordsLSST") if not are_arrays: raise RuntimeError("Pupil coordinates passed to chipNameFromPupilCoordsLSST must be in numpy arrays") cameraPointList = [afwGeom.Point2D(x, y) for x, y in zip(xPupil, yPupil)] # Loop through every point being considered. For each point, assemble a list of detectors # whose centers are within 1.1 detector radii of the point. These are the detectors on which # the point could be located. Store that list of possible detectors as a row in valid_detctors, # which will be passed to _findDetectorsListLSST() valid_detectors = [] for xx, yy in zip(xPupil, yPupil): possible_dexes = np.where(np.sqrt(np.power(xx-_lsst_pupil_coord_map['xx'], 2) + np.power(yy-_lsst_pupil_coord_map['yy'], 2))/_lsst_pupil_coord_map['dp'] < 1.1) local_valid = [lsst_camera()[_lsst_pupil_coord_map['name'][ii]] for ii in possible_dexes[0]] valid_detectors.append(local_valid) nameList = _findDetectorsListLSST(cameraPointList, valid_detectors, allow_multiple_chips=allow_multiple_chips) return nameList
def pupilCoordsFromFocalPlaneCoords(xFocal, yFocal, camera=None): """ Get the pupil coordinates in radians from the focal plane coordinates in millimeters @param [in] xFocal the x focal plane coordinates in millimeters. Can be a float or a numpy array. @param [in] yFocal the y focal plane coordinates in millimeters. Can be a float or a numpy array. @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x pupil coordinate and the second row is the y pupil coordinate (both in radians) """ are_arrays = _validate_inputs([xFocal, yFocal], ['xFocal', 'yFocal'], 'pupilCoordsFromFocalPlaneCoords') if camera is None: raise RuntimeError( "You cannot calculate pupil coordinates without specifying a camera" ) focal_to_field = camera.getTransformMap().getTransform( FOCAL_PLANE, FIELD_ANGLE) if are_arrays: focal_point_list = [geom.Point2D(x, y) for x, y in zip(xFocal, yFocal)] pupil_point_list = focal_to_field.applyForward(focal_point_list) pupil_arr = np.array([[pp.getX(), pp.getY()] for pp in pupil_point_list]).transpose() is_nan = np.where(np.logical_or(np.isnan(xFocal), np.isnan(yFocal))) pupil_arr[0][is_nan] = np.NaN pupil_arr[1][is_nan] = np.NaN return pupil_arr # if not are_arrays if np.isfinite(xFocal) and np.isfinite(yFocal): pupPoint = focal_to_field.applyForward(geom.Point2D(xFocal, yFocal)) return np.array([pupPoint.getX(), pupPoint.getY()]) return np.array([np.NaN, np.NaN])
def pupilCoordsFromFocalPlaneCoords(xFocal, yFocal, camera=None): """ Get the pupil coordinates in radians from the focal plane coordinates in millimeters @param [in] xFocal the x focal plane coordinates in millimeters. Can be a float or a numpy array. @param [in] yFocal the y focal plane coordinates in millimeters. Can be a float or a numpy array. @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x pupil coordinate and the second row is the y pupil coordinate (both in radians) """ are_arrays = _validate_inputs([xFocal, yFocal], ['xFocal', 'yFocal'], 'pupilCoordsFromFocalPlaneCoords') if camera is None: raise RuntimeError("You cannot calculate pupil coordinates without specifying a camera") focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE) if are_arrays: focal_point_list = [geom.Point2D(x,y) for x,y in zip(xFocal, yFocal)] pupil_point_list = focal_to_field.applyForward(focal_point_list) pupil_arr = np.array([[pp.getX(), pp.getY()] for pp in pupil_point_list]).transpose() is_nan = np.where(np.logical_or(np.isnan(xFocal), np.isnan(yFocal))) pupil_arr[0][is_nan] = np.NaN pupil_arr[1][is_nan] = np.NaN return pupil_arr # if not are_arrays if np.isfinite(xFocal) and np.isfinite(yFocal): pupPoint = focal_to_field.applyForward(geom.Point2D(xFocal, yFocal)) return np.array([pupPoint.getX(), pupPoint.getY()]) return np.array([np.NaN, np.NaN])
def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None): """ Get the focal plane coordinates for all objects in the catalog. @param [in] xPupil the x pupil coordinates in radians. Can be a float or a numpy array. @param [in] yPupil the y pupil coordinates in radians. Can be a float or a numpy array. @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x focal plane coordinate and the second row is the y focal plane coordinate (both in millimeters) """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords') if camera is None: raise RuntimeError( "You cannot calculate focal plane coordinates without specifying a camera" ) if are_arrays: xPix = [] yPix = [] for x, y in zip(xPupil, yPupil): cp = camera.makeCameraPoint(afwGeom.Point2D(x, y), PUPIL) fpPoint = camera.transform(cp, FOCAL_PLANE).getPoint() xPix.append(fpPoint.getX()) yPix.append(fpPoint.getY()) return np.array([xPix, yPix]) # if not are_arrays cp = camera.makeCameraPoint(afwGeom.Point2D(xPupil, yPupil), PUPIL) fpPoint = camera.transform(cp, FOCAL_PLANE).getPoint() return np.array([fpPoint.getX(), fpPoint.getY()])
def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None): """ Get the focal plane coordinates for all objects in the catalog. @param [in] xPupil the x pupil coordinates in radians. Can be a float or a numpy array. @param [in] yPupil the y pupil coordinates in radians. Can be a float or a numpy array. @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x focal plane coordinate and the second row is the y focal plane coordinate (both in millimeters) """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords') if camera is None: raise RuntimeError( "You cannot calculate focal plane coordinates without specifying a camera" ) field_to_focal = camera.getTransformMap().getTransform( FIELD_ANGLE, FOCAL_PLANE) if are_arrays: pupil_point_list = [geom.Point2D(x, y) for x, y in zip(xPupil, yPupil)] focal_point_list = field_to_focal.applyForward(pupil_point_list) xFocal = np.array([pp.getX() for pp in focal_point_list]) yFocal = np.array([pp.getY() for pp in focal_point_list]) return np.array([xFocal, yFocal]) # if not are_arrays fpPoint = field_to_focal.applyForward(geom.Point2D(xPupil, yPupil)) return np.array([fpPoint.getX(), fpPoint.getY()])
def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None): """ Get the focal plane coordinates for all objects in the catalog. @param [in] xPupil the x pupil coordinates in radians. Can be a float or a numpy array. @param [in] yPupil the y pupil coordinates in radians. Can be a float or a numpy array. @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x focal plane coordinate and the second row is the y focal plane coordinate (both in millimeters) """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords') if camera is None: raise RuntimeError("You cannot calculate focal plane coordinates without specifying a camera") field_to_focal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE) if are_arrays: pupil_point_list = [geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)] focal_point_list = field_to_focal.applyForward(pupil_point_list) xFocal = np.array([pp.getX() for pp in focal_point_list]) yFocal = np.array([pp.getY() for pp in focal_point_list]) return np.array([xFocal, yFocal]) # if not are_arrays fpPoint = field_to_focal.applyForward(geom.Point2D(xPupil, yPupil)) return np.array([fpPoint.getX(), fpPoint.getY()])
def _focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, obs_metadata=None, epoch=2000.0, camera=None): """ Get the focal plane coordinates for all objects in the catalog. @param [in] ra is in radians in the International Celestial Reference System. Can be either a float or a numpy array. @param [in] dec is in radians in the International Celestial Reference System. Can be either a float or a numpy array. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] obs_metadata is an ObservationMetaData object describing the telescope pointing (only if specifying RA and Dec rather than pupil coordinates) @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000) @param [in] camera is an afw.cameraGeom camera object @param [out] a 2-D numpy array in which the first row is the x focal plane coordinate and the second row is the y focal plane coordinate (both in millimeters) """ _validate_inputs([ra, dec], ['ra', 'dec'], 'focalPlaneCoordsFromRaDec') if epoch is None: raise RuntimeError("You have to specify an epoch to run " "focalPlaneCoordsFromRaDec") if obs_metadata is None: raise RuntimeError("You have to specify an ObservationMetaData to run " "focalPlaneCoordsFromRaDec") if obs_metadata.mjd is None: raise RuntimeError("You need to pass an ObservationMetaData with an " "mjd into focalPlaneCoordsFromRaDec") if obs_metadata.rotSkyPos is None: raise RuntimeError("You need to pass an ObservationMetaData with a " "rotSkyPos into focalPlaneCoordsFromRaDec") xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch) return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=camera)
def _rawPupilCoordsFromObserved(ra_obs, dec_obs, ra0, dec0, rotSkyPos): """ Convert Observed RA, Dec into pupil coordinates Parameters ---------- ra_obs is the observed RA in radians dec_obs is the observed Dec in radians ra0 is the RA of the boresite in radians dec0 is the Dec of the boresite in radians rotSkyPos is in radians Returns -------- A numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_obs, dec_obs], ['ra_obs', 'dec_obs'], "pupilCoordsFromObserved") theta = -1.0 * rotSkyPos ra_pointing = ra0 dec_pointing = dec0 # palpy.ds2tp performs the gnomonic projection on ra_in and dec_in # with a tangent point at (pointingRA, pointingDec) # if not are_arrays: try: x, y = palpy.ds2tp(ra_obs, dec_obs, ra_pointing, dec_pointing) except: x = np.NaN y = np.NaN else: try: x, y = palpy.ds2tpVector(ra_obs, dec_obs, ra_pointing, dec_pointing) except: # apparently, one of your ra/dec values was improper; we will have to do this # element-wise, putting NaN in the place of the bad values x = [] y = [] for rr, dd in zip(ra_obs, dec_obs): try: xx, yy = palpy.ds2tp(rr, dd, ra_pointing, dec_pointing) except: xx = np.NaN yy = np.NaN x.append(xx) y.append(yy) x = np.array(x) y = np.array(y) # rotate the result by rotskypos (rotskypos being "the angle of the sky relative to # camera coordinates" according to phoSim documentation) to account for # the rotation of the focal plane about the telescope pointing x_out = x * np.cos(theta) - y * np.sin(theta) y_out = x * np.sin(theta) + y * np.cos(theta) return np.array([x_out, y_out])
def _pupilCoordsFromRaDec(ra_in, dec_in, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, includeRefraction=True, obs_metadata=None, epoch=2000.0): """ Take an input RA and dec from the sky and convert it to coordinates on the focal plane. This uses PAL's gnomonic projection routine which assumes that the focal plane is perfectly flat. The output is in Cartesian coordinates, assuming that the Celestial Sphere is a unit sphere. The RA, Dec accepted by this method are in the International Celestial Reference System. Before applying the gnomonic projection, this method transforms those RA, Dec into observed geocentric coordinates, applying the effects of precession, nutation, aberration, parallax and refraction. This is done, because the gnomonic projection ought to be applied to what observers actually see, rather than the idealized, above-the-atmosphere coordinates represented by the ICRS. @param [in] ra_in is in radians (ICRS). Can be either a numpy array or a number. @param [in] dec_in is in radians (ICRS). Can be either a numpy array or a number. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] includeRefraction is a boolean controlling the application of refraction. @param [in] obs_metadata is an ObservationMetaData instantiation characterizing the telescope location and pointing. @param [in] epoch is the epoch of mean ra and dec in julian years (default=2000.0) @param [out] returns a numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_in, dec_in], ['ra_in', 'dec_in'], "pupilCoordsFromRaDec") if obs_metadata is None: raise RuntimeError("Cannot call pupilCoordsFromRaDec without obs_metadata") if obs_metadata.mjd is None: raise RuntimeError("Cannot call pupilCoordsFromRaDec; obs_metadata.mjd is None") if epoch is None: raise RuntimeError("Cannot call pupilCoordsFromRaDec; epoch is None") if obs_metadata.rotSkyPos is None: # there is no observation meta data on which to base astrometry raise RuntimeError("Cannot calculate [x,y]_focal_nominal without obs_metadata.rotSkyPos") if obs_metadata.pointingRA is None or obs_metadata.pointingDec is None: raise RuntimeError("Cannot calculate [x,y]_focal_nominal without pointingRA and Dec in obs_metadata") ra_obs, dec_obs = _observedFromICRS(ra_in, dec_in, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) return _pupilCoordsFromObserved(ra_obs, dec_obs, obs_metadata, epoch=epoch, includeRefraction=includeRefraction)
def _validate_inputs_and_chipname(input_list, input_names, method_name, chip_name, chipname_can_be_none=True): """ This will wrap _validate_inputs, but also reformat chip_name if necessary. input_list is a list of the inputs passed to a method. input_name is a list of the variable names associated with input_list method_name is the name of the method whose input is being validated. chip_name is the chip_name variable passed into the calling method. chipname_can_be_none is a boolean that controls whether or not chip_name is allowed to be None. This method will raise a RuntimeError if: 1) the contents of input_list are not all of the same type 2) the contents of input_list are not all floats or numpy arrays 3) the contents of input_list are different lengths (if numpy arrays) 4) chip_name is None and chipname_can_be_none is False 5) chip_name is a list or array of different length than input_list[0] (if input_list[0] is a list or array) and len(chip_name)>1 This method returns a boolean indicating whether input_list[0] is a numpy array and a re-casting of chip_name as a list of length equal to input_list[0] (unless chip_name is None; then it will leave chip_name untouched) """ are_arrays = _validate_inputs(input_list, input_names, method_name) if chip_name is None and not chipname_can_be_none: raise RuntimeError("You passed chipName=None to %s" % method_name) if are_arrays: n_pts = len(input_list[0]) else: n_pts = 1 if isinstance(chip_name, list) or isinstance(chip_name, np.ndarray): if len(chip_name) > 1 and len(chip_name) != n_pts: raise RuntimeError("You passed %d chipNames to %s.\n" % (len(chip_name), method_name) + "You passed %d %s values." % (len(input_list[0]), input_names[0])) if len(chip_name) == 1 and n_pts > 1: chip_name_out = [chip_name[0]] * n_pts else: chip_name_out = chip_name return are_arrays, chip_name_out elif chip_name is None: return are_arrays, chip_name else: return are_arrays, [chip_name] * n_pts
def _observedFromPupilCoords(xPupil, yPupil, obs_metadata=None, includeRefraction=True, epoch=2000.0): """ Convert pupil coordinates into observed (RA, Dec) @param [in] xPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] yPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] obs_metadata -- an instantiation of ObservationMetaData characterizing the state of the telescope @param [in] epoch -- julian epoch of the mean equinox used for the coordinate transformations (in years; defaults to 2000) @param[in] includeRefraction -- a boolean which controls the effects of refraction (refraction is used when finding the observed coordinates of the boresite specified by obs_metadata) @param [out] a 2-D numpy array in which the first row is observed RA and the second row is observed Dec (both in radians). Note: these are not ICRS coordinates. These are RA and Dec-like coordinates resulting from applying precession, nutation, diurnal aberration and annual aberration on top of ICRS coordinates. WARNING: This method does not account for apparent motion due to parallax. This method is only useful for mapping positions on a theoretical focal plane to positions on the celestial sphere. """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "observedFromPupilCoords") if obs_metadata is None: raise RuntimeError("Cannot call observedFromPupilCoords without obs_metadata") if epoch is None: raise RuntimeError("Cannot call observedFromPupilCoords; epoch is None") if obs_metadata.rotSkyPos is None: raise RuntimeError("Cannot call observedFromPupilCoords without rotSkyPos " + "in obs_metadata") if obs_metadata.pointingRA is None or obs_metadata.pointingDec is None: raise RuntimeError("Cannot call observedFromPupilCoords " + "without pointingRA, pointingDec in obs_metadata") if obs_metadata.mjd is None: raise RuntimeError("Cannot calculate RA, Dec without mjd " + "in obs_metadata") ra_pointing, dec_pointing = _observedFromICRS(obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) # This is the same as theta in pupilCoordsFromRaDec, except without the minus sign. # This is because we will be reversing the rotation performed in that other method. theta = obs_metadata._rotSkyPos x_g = xPupil*np.cos(theta) - yPupil*np.sin(theta) y_g = xPupil*np.sin(theta) + yPupil*np.cos(theta) # x_g and y_g are now the x and y coordinates # can now use the PALPY method palDtp2s to convert to RA, Dec. if are_arrays: raObs, decObs = palpy.dtp2sVector(x_g, y_g, ra_pointing, dec_pointing) else: raObs, decObs = palpy.dtp2s(x_g, y_g, ra_pointing, dec_pointing) return raObs, decObs
def _raDecFromPupilCoords(xPupil, yPupil, obs_metadata=None, epoch=2000.0): """ @param [in] xPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] yPupil -- pupil coordinates in radians. Can be a numpy array or a number. @param [in] obs_metadata -- an instantiation of ObservationMetaData characterizing the state of the telescope @param [in] epoch -- julian epoch of the mean equinox used for the coordinate transformations (in years; defaults to 2000) @param [out] a 2-D numpy array in which the first row is RA and the second row is Dec (both in radians; both in the International Celestial Reference System) WARNING: This method does not account for apparent motion due to parallax. This method is only useful for mapping positions on a theoretical focal plane to positions on the celestial sphere. """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "raDecFromPupilCoords") if obs_metadata is None: raise RuntimeError( "Cannot call raDecFromPupilCoords without obs_metadata") if epoch is None: raise RuntimeError("Cannot call raDecFromPupilCoords; epoch is None") if obs_metadata.rotSkyPos is None: raise RuntimeError( "Cannot call raDecFromPupilCoords without rotSkyPos " + "in obs_metadata") if obs_metadata.pointingRA is None or obs_metadata.pointingDec is None: raise RuntimeError("Cannot call raDecFromPupilCoords " + "without pointingRA, pointingDec in obs_metadata") if obs_metadata.mjd is None: raise RuntimeError("Cannot calculate x_pupil, y_pupil without mjd " + "in obs_metadata") ra_pointing, dec_pointing = _observedFromICRS(obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=True) # This is the same as theta in pupilCoordsFromRaDec, except without the minus sign. # This is because we will be reversing the rotation performed in that other method. theta = -1.0 * obs_metadata._rotSkyPos x_g = xPupil * np.cos(theta) - yPupil * np.sin(theta) y_g = xPupil * np.sin(theta) + yPupil * np.cos(theta) x_g *= -1.0 # x_g and y_g are now the x and y coordinates # can now use the PALPY method palDtp2s to convert to RA, Dec. if are_arrays: raObs, decObs = palpy.dtp2sVector(x_g, y_g, ra_pointing, dec_pointing) else: raObs, decObs = palpy.dtp2s(x_g, y_g, ra_pointing, dec_pointing) ra_icrs, dec_icrs = _icrsFromObserved(raObs, decObs, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=True) return np.array([ra_icrs, dec_icrs])
def _chipNameFromRaDecLSST(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, obs_metadata=None, epoch=2000.0, allow_multiple_chips=False, band='r'): """ Return the names of detectors on the LSST camera that see the object specified by (RA, Dec) in radians. @param [in] ra in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] dec in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are measured. Default is 2000. @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames but NO WARNING WILL BE EMITTED. If it is 'True' and an object falls on more than one chip, a list of chipNames will appear for that object. @param [in] band is the filter we are simulating (Default=r) @param [out] the name(s) of the chips on which ra, dec fall (will be a numpy array if more than one) """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDecLSST") if epoch is None: raise RuntimeError("You need to pass an epoch into chipName") if obs_metadata is None: raise RuntimeError( "You need to pass an ObservationMetaData into chipName") if obs_metadata.mjd is None: raise RuntimeError( "You need to pass an ObservationMetaData with an mjd into chipName" ) if obs_metadata.rotSkyPos is None: raise RuntimeError( "You need to pass an ObservationMetaData with a rotSkyPos into chipName" ) xp, yp = _pupilCoordsFromRaDec(ra, dec, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch) return chipNameFromPupilCoordsLSST( xp, yp, allow_multiple_chips=allow_multiple_chips, band=band)
def _rawPupilCoordsFromObserved(ra_obs, dec_obs, ra0, dec0, rotSkyPos): """ Convert Observed RA, Dec into pupil coordinates Parameters ---------- ra_obs is the observed RA in radians dec_obs is the observed Dec in radians ra0 is the RA of the boresite in radians dec0 is the Dec of the boresite in radians rotSkyPos is in radians Returns -------- A numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_obs, dec_obs], ['ra_obs', 'dec_obs'], "pupilCoordsFromObserved") theta = -1.0*rotSkyPos ra_pointing = ra0 dec_pointing = dec0 # palpy.ds2tp performs the gnomonic projection on ra_in and dec_in # with a tangent point at (pointingRA, pointingDec) # if not are_arrays: try: x, y = palpy.ds2tp(ra_obs, dec_obs, ra_pointing, dec_pointing) except: x = np.NaN y = np.NaN else: try: x, y = palpy.ds2tpVector(ra_obs, dec_obs, ra_pointing, dec_pointing) except: # apparently, one of your ra/dec values was improper; we will have to do this # element-wise, putting NaN in the place of the bad values x = [] y = [] for rr, dd in zip(ra_obs, dec_obs): try: xx, yy = palpy.ds2tp(rr, dd, ra_pointing, dec_pointing) except: xx = np.NaN yy = np.NaN x.append(xx) y.append(yy) x = np.array(x) y = np.array(y) # rotate the result by rotskypos (rotskypos being "the angle of the sky relative to # camera coordinates" according to phoSim documentation) to account for # the rotation of the focal plane about the telescope pointing x_out = x*np.cos(theta) - y*np.sin(theta) y_out = x*np.sin(theta) + y*np.cos(theta) return np.array([x_out, y_out])
def _chipNameFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, obs_metadata=None, camera=None, epoch=2000.0, allow_multiple_chips=False): """ Return the names of detectors that see the object specified by (RA, Dec) in radians. @param [in] ra in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] dec in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are measured. Default is 2000. @param [in] camera is an afw.cameraGeom camera instance characterizing the camera @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, an exception will be raised. If it is 'True' and an object falls on more than one chip, it will still only return the first chip in the list of chips returned. THIS BEHAVIOR SHOULD BE FIXED IN A FUTURE TICKET. @param [out] the name(s) of the chips on which ra, dec fall (will be a numpy array if more than one) """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDec") if epoch is None: raise RuntimeError("You need to pass an epoch into chipName") if obs_metadata is None: raise RuntimeError("You need to pass an ObservationMetaData into chipName") if obs_metadata.mjd is None: raise RuntimeError("You need to pass an ObservationMetaData with an mjd into chipName") if obs_metadata.rotSkyPos is None: raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into chipName") if not are_arrays: ra = np.array([ra]) dec = np.array([dec]) xp, yp = _pupilCoordsFromRaDec(ra, dec, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch) ans = chipNameFromPupilCoords(xp, yp, camera=camera, allow_multiple_chips=allow_multiple_chips) if not are_arrays: return ans[0] return ans
def _chipNameFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, obs_metadata=None, camera=None, epoch=2000.0, allow_multiple_chips=False): """ Return the names of detectors that see the object specified by (RA, Dec) in radians. @param [in] ra in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] dec in radians (a numpy array or a float). In the International Celestial Reference System. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are measured. Default is 2000. @param [in] camera is an afw.cameraGeom camera instance characterizing the camera @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, an exception will be raised. If it is 'True' and an object falls on more than one chip, it will still only return the first chip in the list of chips returned. THIS BEHAVIOR SHOULD BE FIXED IN A FUTURE TICKET. @param [out] the name(s) of the chips on which ra, dec fall (will be a numpy array if more than one) """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDec") if epoch is None: raise RuntimeError("You need to pass an epoch into chipName") if obs_metadata is None: raise RuntimeError( "You need to pass an ObservationMetaData into chipName") if obs_metadata.mjd is None: raise RuntimeError( "You need to pass an ObservationMetaData with an mjd into chipName" ) if obs_metadata.rotSkyPos is None: raise RuntimeError( "You need to pass an ObservationMetaData with a rotSkyPos into chipName" ) if not are_arrays: ra = np.array([ra]) dec = np.array([dec]) xp, yp = _pupilCoordsFromRaDec(ra, dec, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch) ans = chipNameFromPupilCoords(xp, yp, camera=camera, allow_multiple_chips=allow_multiple_chips) if not are_arrays: return ans[0] return ans
def _observedFromAppGeo(ra, dec, includeRefraction=True, altAzHr=False, wavelength=0.5, obs_metadata=None): """ Convert apparent geocentric (RA, Dec) to observed (RA, Dec). More specifically: apply refraction and diurnal aberration. This method works in radians. @param [in] ra is geocentric apparent RA (radians). Can be a numpy array or a number. @param [in] dec is geocentric apparent Dec (radians). Can be a numpy array or a number. @param [in] includeRefraction is a boolean to turn refraction on and off @param [in] altAzHr is a boolean indicating whether or not to return altitude and azimuth @param [in] wavelength is effective wavelength in microns (default: 0.5) @param [in] obs_metadata is an ObservationMetaData characterizing the observation. @param [out] a 2-D numpy array in which the first row is the observed RA and the second row is the observed Dec (both in radians) @param [out] a 2-D numpy array in which the first row is the altitude and the second row is the azimuth (both in radians). Only returned if altAzHr == True. """ are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "observedFromAppGeo") if obs_metadata is None: raise RuntimeError( "Cannot call observedFromAppGeo without an obs_metadata") if obs_metadata.site is None: raise RuntimeError( "Cannot call observedFromAppGeo: obs_metadata has no site info") if obs_metadata.mjd is None: raise RuntimeError( "Cannot call observedFromAppGeo: obs_metadata has no mjd") obsPrms = _calculateObservatoryParameters(obs_metadata, wavelength, includeRefraction) # palpy.aopqk does an apparent to observed place # correction # # it corrects for diurnal aberration and refraction # (using a fast algorithm for refraction in the case of # a small zenith distance and a more rigorous algorithm # for a large zenith distance) # if are_arrays: azimuth, zenith, hourAngle, decOut, raOut = palpy.aopqkVector( ra, dec, obsPrms) else: azimuth, zenith, hourAngle, decOut, raOut = palpy.aopqk( ra, dec, obsPrms) # # Note: this is a choke point. Even the vectorized version of aopqk # is expensive (it takes about 0.006 seconds per call) # # Actually, this is only a choke point if you are dealing with zenith # distances of greater than about 70 degrees if altAzHr: # # palpy.de2h converts equatorial to horizon coordinates # if are_arrays: az, alt = palpy.de2hVector(hourAngle, decOut, obs_metadata.site.latitude_rad) else: az, alt = palpy.de2h(hourAngle, decOut, obs_metadata.site.latitude_rad) return np.array([raOut, decOut]), np.array([alt, az]) return np.array([raOut, decOut])
def chipNameFromPupilCoords(xPupil, yPupil, camera=None, allow_multiple_chips=False): """ Return the names of detectors that see the object specified by (xPupil, yPupil). @param [in] xPupil is the x pupil coordinate in radians. Can be either a float or a numpy array. @param [in] yPupil is the y pupil coordinate in radians. Can be either a float or a numpy array. @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames and warning will be emitted. If it is 'True' and an object falls on more than one chip, the resulting chip name will be the string representation of the list of valid chip names. @param [in] camera is an afwCameraGeom object that specifies the attributes of the camera. @param [out] a numpy array of chip names """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoords") if camera is None: raise RuntimeError("No camera defined. Cannot run chipName.") chipNames = [] if are_arrays: cameraPointList = [ afwGeom.Point2D(x, y) for x, y in zip(xPupil, yPupil) ] else: cameraPointList = [afwGeom.Point2D(xPupil, yPupil)] detList = camera.findDetectorsList(cameraPointList, PUPIL) for pt, det in zip(cameraPointList, detList): if len(det) == 0 or np.isnan(pt.getX()) or np.isnan(pt.getY()): chipNames.append(None) else: name_list = [dd.getName() for dd in det] if len(name_list) > 1: if allow_multiple_chips: chipNames.append(str(name_list)) else: warnings.warn( "An object has landed on multiple chips. " + "You asked for this not to happen.\n" + "We will return only one of the chip names. If you want both, " + "try re-running with " + "the kwarg allow_multiple_chips=True.\n" + "Offending chip names were %s\n" % str(name_list) + "Offending pupil coordinate point was %.12f %.12f\n" % (pt[0], pt[1]), category=MultipleChipWarning) chipNames.append(name_list[0]) elif len(name_list) == 0: chipNames.append(None) else: chipNames.append(name_list[0]) if not are_arrays: return chipNames[0] return np.array(chipNames)
def chipNameFromPupilCoords(xPupil, yPupil, camera=None, allow_multiple_chips=False): """ Return the names of detectors that see the object specified by (xPupil, yPupil). @param [in] xPupil is the x pupil coordinate in radians. Can be either a float or a numpy array. @param [in] yPupil is the y pupil coordinate in radians. Can be either a float or a numpy array. @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames and warning will be emitted. If it is 'True' and an object falls on more than one chip, the resulting chip name will be the string representation of the list of valid chip names. @param [in] camera is an afwCameraGeom object that specifies the attributes of the camera. @param [out] a numpy array of chip names """ are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoords") if camera is None: raise RuntimeError("No camera defined. Cannot run chipName.") chipNames = [] if are_arrays: pupilPointList = [geom.Point2D(x, y) for x, y in zip(xPupil, yPupil)] else: pupilPointList = [geom.Point2D(xPupil, yPupil)] detList = camera.findDetectorsList(pupilPointList, FIELD_ANGLE) for pt, det in zip(pupilPointList, detList): if len(det) == 0 or np.isnan(pt.getX()) or np.isnan(pt.getY()): chipNames.append(None) else: name_list = [dd.getName() for dd in det] if len(name_list) > 1: if allow_multiple_chips: chipNames.append(str(name_list)) else: warnings.warn("An object has landed on multiple chips. " + "You asked for this not to happen.\n" + "We will return only one of the chip names. If you want both, " + "try re-running with " + "the kwarg allow_multiple_chips=True.\n" + "Offending chip names were %s\n" % str(name_list) + "Offending pupil coordinate point was %.12f %.12f\n" % (pt[0], pt[1]), category=MultipleChipWarning) chipNames.append(name_list[0]) elif len(name_list) == 0: chipNames.append(None) else: chipNames.append(name_list[0]) if not are_arrays: return chipNames[0] return np.array(chipNames)
def chipNameFromPupilCoordsLSST(xPupil_in, yPupil_in, allow_multiple_chips=False, band='r'): """ Return the names of LSST detectors that see the object specified by either (xPupil, yPupil). @param [in] xPupil_in is the x pupil coordinate in radians. Must be a numpy array. @param [in] yPupil_in is the y pupil coordinate in radians. Must be a numpy array. @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not this method will allow objects to be visible on more than one chip. If it is 'False' and an object appears on more than one chip, only the first chip will appear in the list of chipNames and warning will be emitted. If it is 'True' and an object falls on more than one chip, a list of chipNames will appear for that object. @param[in] band is the bandpass being simulated (default='r') @param [out] a numpy array of chip names """ if (not hasattr(chipNameFromPupilCoordsLSST, '_focal_map') or not hasattr(chipNameFromPupilCoordsLSST, '_detector_arr') or len(chipNameFromPupilCoordsLSST._detector_arr) == 0): focal_map = _build_lsst_focal_coord_map() chipNameFromPupilCoordsLSST._focal_map = focal_map camera = lsst_camera() detector_arr = np.zeros(len(focal_map['name']), dtype=object) for ii in range(len(focal_map['name'])): detector_arr[ii] = camera[focal_map['name'][ii]] chipNameFromPupilCoordsLSST._detector_arr = detector_arr # build a Box2D that contains all of the detectors in the camera focal_to_field = camera.getTransformMap().getTransform( FOCAL_PLANE, FIELD_ANGLE) focal_bbox = camera.getFpBBox() focal_corners = focal_bbox.getCorners() camera_bbox = geom.Box2D() x_focal_max = None x_focal_min = None y_focal_max = None y_focal_min = None for cc in focal_corners: xx = cc.getX() yy = cc.getY() if x_focal_max is None or xx > x_focal_max: x_focal_max = xx if x_focal_min is None or xx < x_focal_min: x_focal_min = xx if y_focal_max is None or yy > y_focal_max: y_focal_max = yy if y_focal_min is None or yy < y_focal_min: y_focal_min = yy chipNameFromPupilCoordsLSST._x_focal_center = 0.5 * (x_focal_max + x_focal_min) chipNameFromPupilCoordsLSST._y_focal_center = 0.5 * (y_focal_max + y_focal_min) radius_sq_max = None for cc in focal_corners: xx = cc.getX() yy = cc.getY() radius_sq = ( (xx - chipNameFromPupilCoordsLSST._x_focal_center)**2 + (yy - chipNameFromPupilCoordsLSST._y_focal_center)**2) if radius_sq_max is None or radius_sq > radius_sq_max: radius_sq_max = radius_sq chipNameFromPupilCoordsLSST._camera_focal_radius_sq = radius_sq_max * 1.1 are_arrays = _validate_inputs([xPupil_in, yPupil_in], ['xPupil_in', 'yPupil_in'], "chipNameFromPupilCoordsLSST") if not are_arrays: xPupil_in = np.array([xPupil_in]) yPupil_in = np.array([yPupil_in]) xFocal, yFocal = focalPlaneCoordsFromPupilCoordsLSST(xPupil_in, yPupil_in, band=band) radius_sq_list = ( (xFocal - chipNameFromPupilCoordsLSST._x_focal_center)**2 + (yFocal - chipNameFromPupilCoordsLSST._y_focal_center)**2) with np.errstate(invalid='ignore'): good_radii = np.where(radius_sq_list < chipNameFromPupilCoordsLSST. _camera_focal_radius_sq) if len(good_radii[0]) == 0: return np.array([None] * len(xPupil_in)) xFocal_good = xFocal[good_radii] yFocal_good = yFocal[good_radii] ############################################################ # in the code below, we will only consider those points which # passed the 'good_radii' test above; the other points will # be added in with chipName == None at the end # focalPointList = [ geom.Point2D(xFocal[i_pt], yFocal[i_pt]) for i_pt in good_radii[0] ] # Loop through every detector on the camera. For each detector, assemble a list of points # whose centers are within 1.1 detector radii of the center of the detector. x_cam_list = chipNameFromPupilCoordsLSST._focal_map['xx'] y_cam_list = chipNameFromPupilCoordsLSST._focal_map['yy'] rrsq_lim_list = (1.1 * chipNameFromPupilCoordsLSST._focal_map['dp'])**2 possible_points = [] for i_chip, (x_cam, y_cam, rrsq_lim) in \ enumerate(zip(x_cam_list, y_cam_list, rrsq_lim_list)): local_possible_pts = np.where(((xFocal_good - x_cam)**2 + (yFocal_good - y_cam)**2) < rrsq_lim)[0] possible_points.append(local_possible_pts) nameList_good = _findDetectorsListLSST( focalPointList, chipNameFromPupilCoordsLSST._detector_arr, possible_points, allow_multiple_chips=allow_multiple_chips) #################################################################### # initialize output as an array of Nones, effectively adding back in # the points which failed the initial radius cut nameList = np.array([None] * len(xPupil_in)) nameList[good_radii] = nameList_good if not are_arrays: return nameList[0] return nameList
def _icrsFromObserved(ra, dec, obs_metadata=None, epoch=None, includeRefraction=True): """ Convert observed RA, Dec into mean International Celestial Reference Frame (ICRS) RA, Dec. This method undoes the effects of precession, nutation, aberration (annual and diurnal), and refraction. It is meant so that users can take pointing RA and Decs, which will be in the observed reference system, and transform them into ICRS for purposes of querying database tables (likely to contain mean ICRS RA, Dec) for objects visible from a given pointing. Note: This method is only accurate at angular distances from the sun of greater than 45 degrees and zenith distances of less than 75 degrees. WARNING: This method does not account for apparent motion due to parallax. This means it should not be used to invert the ICRS-to-observed coordinates transformation for actual celestial objects. This method is only useful for mapping positions on a theoretical celestial sphere. This method works in radians. @param [in] ra is the observed RA in radians. Can be a numpy array or a number. @param [in] dec is the observed Dec in radians. Can be a numpy array or a number. @param [in] obs_metadata is an ObservationMetaData object describing the telescope pointing. @param [in] epoch is the julian epoch (in years) against which the mean equinoxes are measured. @param [in] includeRefraction toggles whether or not to correct for refraction @param [out] a 2-D numpy array in which the first row is the mean ICRS RA and the second row is the mean ICRS Dec (both in radians) """ _validate_inputs([ra, dec], ['ra', 'dec'], "icrsFromObserved") if obs_metadata is None: raise RuntimeError( "Cannot call icrsFromObserved; obs_metadata is None") if obs_metadata.mjd is None: raise RuntimeError( "Cannot call icrsFromObserved; obs_metadata.mjd is None") if epoch is None: raise RuntimeError( "Cannot call icrsFromObserved; you have not specified an epoch") ra_app, dec_app = _appGeoFromObserved(ra, dec, obs_metadata=obs_metadata, includeRefraction=includeRefraction) ra_icrs, dec_icrs = _icrsFromAppGeo(ra_app, dec_app, epoch=epoch, mjd=obs_metadata.mjd) return np.array([ra_icrs, dec_icrs])
def _pupilCoordsFromObserved(ra_obs, dec_obs, obs_metadata, epoch=2000.0, includeRefraction=True): """ Convert Observed RA, Dec into pupil coordinates Parameters ---------- ra_obs is the observed RA in radians dec_obs is the observed Dec in radians obs_metadata is an ObservationMetaData characterizing the telescope location and pointing epoch is the epoch of the mean RA and Dec in julian years (default=2000.0) includeRefraction is a boolean controlling the application of refraction. Returns -------- A numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_obs, dec_obs], ['ra_obs', 'dec_obs'], "pupilCoordsFromObserved") if obs_metadata.rotSkyPos is None: raise RuntimeError("Cannot call pupilCoordsFromObserved; " "rotSkyPos is None") theta = obs_metadata._rotSkyPos ra_pointing, dec_pointing = _observedFromICRS( obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) # palpy.ds2tp performs the gnomonic projection on ra_in and dec_in # with a tangent point at (pointingRA, pointingDec) # if not are_arrays: try: x, y = palpy.ds2tp(ra_obs, dec_obs, ra_pointing, dec_pointing) except: x = np.NaN y = np.NaN else: try: x, y = palpy.ds2tpVector(ra_obs, dec_obs, ra_pointing, dec_pointing) except: # apparently, one of your ra/dec values was improper; we will have to do this # element-wise, putting NaN in the place of the bad values x = [] y = [] for rr, dd in zip(ra_obs, dec_obs): try: xx, yy = palpy.ds2tp(rr, dd, ra_pointing, dec_pointing) except: xx = np.NaN yy = np.NaN x.append(xx) y.append(yy) x = np.array(x) y = np.array(y) # The extra negative sign on x_out comes from the following: # The Gnomonic projection as calculated by palpy is such that, # if north is in the +y direction, then west is in the -x direction, # which is the opposite of the behavior we want (I do not know how to # express this analytically; I have just confirmed it numerically) x *= -1.0 # rotate the result by rotskypos (rotskypos being "the angle of the sky relative to # camera coordinates" according to phoSim documentation) to account for # the rotation of the focal plane about the telescope pointing x_out = x * np.cos(theta) - y * np.sin(theta) y_out = x * np.sin(theta) + y * np.cos(theta) return np.array([x_out, y_out])
def _appGeoFromICRS(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, epoch=2000.0, mjd=None): """ Convert the mean position (RA, Dec) in the International Celestial Reference System (ICRS) to the mean apparent geocentric position units: ra (radians), dec (radians), pm_ra (radians/year), pm_dec (radians/year), parallax (radians), v_rad (km/sec; positive if receding), epoch (Julian years) @param [in] ra in radians (ICRS). Can be a numpy array or a number. @param [in] dec in radians (ICRS). Can be a numpy array or a number. @param [in] pm_ra is ra proper motion multiplied by cos(Dec) in radians/year. Can be a numpy array or a number or None. @param [in] pm_dec is dec proper motion in radians/year. Can be a numpy array or a number or None. @param [in] parallax in radians. Can be a numpy array or a number or None. @param [in] v_rad is radial velocity in km/sec (positive if the object is receding). Can be a numpy array or a number or None. @param [in] epoch is the julian epoch (in years) of the equinox against which to measure RA (default: 2000.0) @param [in] mjd is an instantiation of the ModifiedJulianDate class representing the date of the observation @param [out] a 2-D numpy array in which the first row is the apparent geocentric RAand the second row is the apparent geocentric Dec (both in radians) """ if mjd is None: raise RuntimeError("cannot call appGeoFromICRS; mjd is None") include_px = False if (pm_ra is not None or pm_dec is not None or v_rad is not None or parallax is not None): include_px = True if isinstance(ra, np.ndarray): fill_value = np.zeros(len(ra), dtype=float) else: fill_value = 0.0 if pm_ra is None: pm_ra = fill_value if pm_dec is None: pm_dec = fill_value if v_rad is None: v_rad = fill_value if parallax is None: parallax = fill_value are_arrays = _validate_inputs( [ra, dec, pm_ra, pm_dec, v_rad, parallax], ['ra', 'dec', 'pm_ra', 'pm_dec', 'v_rad', 'parallax'], "appGeoFromICRS") else: are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "appGeoFromICRS") # Define star independent mean to apparent place parameters # palpy.mappa calculates the star-independent parameters # needed to correct RA and Dec # e.g the Earth barycentric and heliocentric position and velocity, # the precession-nutation matrix, etc. # # arguments of palpy.mappa are: # epoch of mean equinox to be used (Julian) # # date (MJD) prms = palpy.mappa(epoch, mjd.TDB) # palpy.mapqk does a quick mean to apparent place calculation using # the output of palpy.mappa # # Taken from the palpy source code (palMap.c which calls both palMappa and palMapqk): # The accuracy is sub-milliarcsecond, limited by the # precession-nutation model (see palPrenut for details). if include_px: # because PAL and ERFA expect proper motion in terms of "coordinate # angle; not true angle" (as stated in erfa/starpm.c documentation) pm_ra_corrected = pm_ra / np.cos(dec) if are_arrays: if include_px: raOut, decOut = palpy.mapqkVector(ra, dec, pm_ra_corrected, pm_dec, arcsecFromRadians(parallax), v_rad, prms) else: raOut, decOut = palpy.mapqkzVector(ra, dec, prms) else: if include_px: raOut, decOut = palpy.mapqk(ra, dec, pm_ra_corrected, pm_dec, arcsecFromRadians(parallax), v_rad, prms) else: raOut, decOut = palpy.mapqkz(ra, dec, prms) return np.array([raOut, decOut])
def _pupilCoordsFromRaDec(ra_in, dec_in, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, includeRefraction=True, obs_metadata=None, epoch=2000.0): """ Take an input RA and dec from the sky and convert it to coordinates on the focal plane. This uses PAL's gnomonic projection routine which assumes that the focal plane is perfectly flat. The output is in Cartesian coordinates, assuming that the Celestial Sphere is a unit sphere. The RA, Dec accepted by this method are in the International Celestial Reference System. Before applying the gnomonic projection, this method transforms those RA, Dec into observed geocentric coordinates, applying the effects of precession, nutation, aberration, parallax and refraction. This is done, because the gnomonic projection ought to be applied to what observers actually see, rather than the idealized, above-the-atmosphere coordinates represented by the ICRS. @param [in] ra_in is in radians (ICRS). Can be either a numpy array or a number. @param [in] dec_in is in radians (ICRS). Can be either a numpy array or a number. @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] pm_dec is proper motion in dec (radians/yr) Can be a numpy array or a number or None (default=None). @param [in] parallax is parallax in radians Can be a numpy array or a number or None (default=None). @param [in] v_rad is radial velocity (km/s) Can be a numpy array or a number or None (default=None). @param [in] includeRefraction is a boolean controlling the application of refraction. @param [in] obs_metadata is an ObservationMetaData instantiation characterizing the telescope location and pointing. @param [in] epoch is the epoch of mean ra and dec in julian years (default=2000.0) @param [out] returns a numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_in, dec_in], ['ra_in', 'dec_in'], "pupilCoordsFromRaDec") if obs_metadata is None: raise RuntimeError( "Cannot call pupilCoordsFromRaDec without obs_metadata") if obs_metadata.mjd is None: raise RuntimeError( "Cannot call pupilCoordsFromRaDec; obs_metadata.mjd is None") if epoch is None: raise RuntimeError("Cannot call pupilCoordsFromRaDec; epoch is None") if obs_metadata.rotSkyPos is None: # there is no observation meta data on which to base astrometry raise RuntimeError( "Cannot calculate [x,y]_focal_nominal without obs_metadata.rotSkyPos" ) if obs_metadata.pointingRA is None or obs_metadata.pointingDec is None: raise RuntimeError( "Cannot calculate [x,y]_focal_nominal without pointingRA and Dec in obs_metadata" ) ra_obs, dec_obs = _observedFromICRS(ra_in, dec_in, pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) return _pupilCoordsFromObserved(ra_obs, dec_obs, obs_metadata, epoch=epoch, includeRefraction=includeRefraction)
def _pupilCoordsFromObserved(ra_obs, dec_obs, obs_metadata, epoch=2000.0, includeRefraction=True): """ Convert Observed RA, Dec into pupil coordinates Parameters ---------- ra_obs is the observed RA in radians dec_obs is the observed Dec in radians obs_metadata is an ObservationMetaData characterizing the telescope location and pointing epoch is the epoch of the mean RA and Dec in julian years (default=2000.0) includeRefraction is a boolean controlling the application of refraction. Returns -------- A numpy array whose first row is the x coordinate on the pupil in radians and whose second row is the y coordinate in radians """ are_arrays = _validate_inputs([ra_obs, dec_obs], ['ra_obs', 'dec_obs'], "pupilCoordsFromObserved") if obs_metadata.rotSkyPos is None: raise RuntimeError("Cannot call pupilCoordsFromObserved; " "rotSkyPos is None") theta = -1.0*obs_metadata._rotSkyPos ra_pointing, dec_pointing = _observedFromICRS(obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=epoch, includeRefraction=includeRefraction) # palpy.ds2tp performs the gnomonic projection on ra_in and dec_in # with a tangent point at (pointingRA, pointingDec) # if not are_arrays: try: x, y = palpy.ds2tp(ra_obs, dec_obs, ra_pointing, dec_pointing) except: x = np.NaN y = np.NaN else: try: x, y = palpy.ds2tpVector(ra_obs, dec_obs, ra_pointing, dec_pointing) except: # apparently, one of your ra/dec values was improper; we will have to do this # element-wise, putting NaN in the place of the bad values x = [] y = [] for rr, dd in zip(ra_obs, dec_obs): try: xx, yy = palpy.ds2tp(rr, dd, ra_pointing, dec_pointing) except: xx = np.NaN yy = np.NaN x.append(xx) y.append(yy) x = np.array(x) y = np.array(y) # rotate the result by rotskypos (rotskypos being "the angle of the sky relative to # camera coordinates" according to phoSim documentation) to account for # the rotation of the focal plane about the telescope pointing x_out = x*np.cos(theta) - y*np.sin(theta) y_out = x*np.sin(theta) + y*np.cos(theta) return np.array([x_out, y_out])