def _nativeLonLatFromRaDec(ra_in, dec_in, obs_metadata): """ Convert the RA and Dec of a star into `native' longitude and latitude. Native longitude and latitude are defined as what RA and Dec would be if the celestial pole were at the location where the telescope is pointing. The transformation is achieved by rotating the vector pointing to the RA and Dec being transformed once about the x axis and once about the z axis. These are the Euler rotations referred to in Section 2.3 of Calabretta and Greisen (2002), A&A 395, p. 1077 Note: RA, and Dec are assumed to be in the International Celestial Reference System. Before calculating native longitude and latitude, this method will convert RA and Dec to observed geocentric coordinates. @param [in] ra is the RA of the star being transformed in radians (in the International Celestial Reference System) @param [in] dec is the Dec of the star being transformed in radians (in the International Celestial Reference System) @param [in] obs_metadata is an ObservationMetaData characterizing the pointing of the telescope. @param [out] lonOut is the native longitude in radians @param [out] latOut is the native latitude in radians """ if not hasattr(ra_in, '__len__'): ra_temp, dec_temp = _observedFromICRS(np.array([ra_in]), np.array([dec_in]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) ra = ra_temp[0] dec = dec_temp[0] else: ra, dec = _observedFromICRS(ra_in, dec_in, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) ra_temp, dec_temp = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) raPointing = ra_temp[0] decPointing = dec_temp[0] return _nativeLonLatFromPointing(ra, dec, raPointing, decPointing)
def controlAltAzFromRaDec(raRad_in, decRad_in, longRad, latRad, mjd): """ Converts RA and Dec to altitude and azimuth @param [in] raRad is the RA in radians (observed geocentric) @param [in] decRad is the Dec in radians (observed geocentric) @param [in] longRad is the longitude of the observer in radians (positive east of the prime meridian) @param [in[ latRad is the latitude of the observer in radians (positive north of the equator) @param [in] mjd is the universal time expressed as an MJD @param [out] altitude in radians @param [out[ azimuth in radians see: http://www.stargazing.net/kepler/altaz.html#twig04 """ obs = utils.ObservationMetaData(mjd=utils.ModifiedJulianDate(UTC=mjd), site=utils.Site(longitude=np.degrees(longRad), latitude=np.degrees(latRad), name='LSST')) if hasattr(raRad_in, '__len__'): raRad, decRad = utils._observedFromICRS(raRad_in, decRad_in, obs_metadata=obs, epoch=2000.0, includeRefraction=True) else: raRad, decRad = utils._observedFromICRS(raRad_in, decRad_in, obs_metadata=obs, epoch=2000.0, includeRefraction=True) lst = utils.calcLmstLast(obs.mjd.UT1, longRad) last = lst[1] haRad = np.radians(last * 15.) - raRad sinDec = np.sin(decRad) cosLat = np.cos(latRad) sinLat = np.sin(latRad) sinAlt = sinDec*sinLat + np.cos(decRad)*cosLat*np.cos(haRad) altRad = np.arcsin(sinAlt) azRad = np.arccos((sinDec - sinAlt*sinLat) / (np.cos(altRad)*cosLat)) azRadOut = np.where(np.sin(haRad) >= 0.0, 2.0 * np.pi - azRad, azRad) if isinstance(altRad, float): return altRad, float(azRadOut) return altRad, azRadOut
def testObservedFromICRS(self): obs = ObservationMetaData(pointingRA=35.0, pointingDec=-45.0, mjd=43572.0) for pmRaList in [self.pm_raList, None]: for pmDecList in [self.pm_decList, None]: for pxList in [self.pxList, None]: for vRadList in [self.v_radList, None]: for includeRefraction in [True, False]: raRad, decRad = utils._observedFromICRS(self.raList, self.decList, pm_ra=pmRaList, pm_dec=pmDecList, parallax=pxList, v_rad=vRadList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) raDeg, decDeg = utils.observedFromICRS(np.degrees(self.raList), np.degrees(self.decList), pm_ra=utils.arcsecFromRadians(pmRaList), pm_dec=utils.arcsecFromRadians(pmDecList), parallax=utils.arcsecFromRadians(pxList), v_rad=vRadList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) dRa = utils.arcsecFromRadians(raRad-np.radians(raDeg)) np.testing.assert_array_almost_equal(dRa, np.zeros(self.nStars), 9) dDec = utils.arcsecFromRadians(decRad-np.radians(decDeg)) np.testing.assert_array_almost_equal(dDec, np.zeros(self.nStars), 9)
def get_phoSimCoordinates(self): ff = self.column_by_name('fitsFiles') # to force the catalog to draw the GalSim images ra = self.column_by_name('raJ2000') dec = self.column_by_name('decJ2000') raObs, decObs = _observedFromICRS(ra, dec, includeRefraction = False, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch) return self._dePrecess(raObs, decObs, self.obs_metadata)
def testObservedFromPupil(self): """ Test conversion from pupil coordinates to observed coordinates """ mjd = ModifiedJulianDate(TAI=53000.0) solarRA, solarDec = solarRaDec(mjd) # to make sure that we are more than 45 degrees from the Sun as required # for _icrsFromObserved to be at all accurate raCenter = solarRA + 100.0 decCenter = solarDec - 30.0 obs = ObservationMetaData(pointingRA=raCenter, pointingDec=decCenter, boundType='circle', boundLength=0.1, rotSkyPos=23.0, mjd=mjd) nSamples = 1000 rng = np.random.RandomState(4453) ra = (rng.random_sample(nSamples) * 0.1 - 0.2) + np.radians(raCenter) dec = (rng.random_sample(nSamples) * 0.1 - 0.2) + np.radians(decCenter) xp, yp = _pupilCoordsFromRaDec(ra, dec, obs_metadata=obs, epoch=2000.0, includeRefraction=True) raObs, decObs = _observedFromICRS(ra, dec, obs_metadata=obs, epoch=2000.0, includeRefraction=True) raObs_test, decObs_test = _observedFromPupilCoords(xp, yp, obs_metadata=obs, epoch=2000.0, includeRefraction=True) dist = arcsecFromRadians(haversine(raObs, decObs, raObs_test, decObs_test)) self.assertLess(dist.max(), 1.0e-6) # test output in degrees raObs_deg, decObs_deg = observedFromPupilCoords(xp, yp, obs_metadata=obs, epoch=2000.0, includeRefraction=True) np.testing.assert_array_almost_equal(raObs_deg, np.degrees(raObs_test), decimal=16) np.testing.assert_array_almost_equal(decObs_deg, np.degrees(decObs_test), decimal=16) # test one-at-a-time input for ii in range(len(raObs)): rr, dd = _observedFromPupilCoords(xp[ii], yp[ii], obs_metadata=obs, epoch=2000.0, includeRefraction=True) self.assertAlmostEqual(rr, raObs_test[ii], 16) self.assertAlmostEqual(dd, decObs_test[ii], 16) rr, dd = observedFromPupilCoords(xp[ii], yp[ii], obs_metadata=obs, epoch=2000.0, includeRefraction=True) self.assertAlmostEqual(rr, raObs_deg[ii], 16) self.assertAlmostEqual(dd, decObs_deg[ii], 16)
def testObservedRaDec(self): """ Test that the mixins provided in Astrometry SSM really do convert ICRS RA, Dec into observed RA, Dec """ dtype = np.dtype([('id', np.int), ('raObserved', np.float), ('decObserved', np.float)]) controlData = np.genfromtxt(self.dbFile, dtype=self.dtype) rng = np.random.RandomState(42) nTests = 5 raList = rng.random_sample(nTests)*2.0*np.pi decList = (rng.random_sample(nTests)-0.5)*np.pi mjdList = rng.random_sample(nTests)*5000.0 + 53850.0 for raPointing, decPointing, mjd in zip(raList, decList, mjdList): obs = ObservationMetaData(pointingRA=raPointing, pointingDec=decPointing, mjd=mjd) cat = SSM_astrometryCat(self.astDB, obs_metadata=obs) with lsst.utils.tests.getTempFilePath('.txt') as catName: cat.write_catalog(catName) testData = np.genfromtxt(catName, dtype=dtype, delimiter=',') self.assertGreater(len(testData), 0) raObservedControl, decObservedControl = _observedFromICRS(controlData['raJ2000'], controlData['decJ2000'], obs_metadata=obs, epoch=2000.0, includeRefraction=True) np.testing.assert_array_almost_equal(raObservedControl, testData['raObserved'], 10) np.testing.assert_array_almost_equal(decObservedControl, testData['decObserved'], 10)
def observedStellarCoordinates(self, includeRefraction=True): """ Getter which converts mean coordinates in the International Celestial Reference Frame to observed coordinates. """ # TODO # are we going to store proper motion in raw radians per year # or in sky motion = cos(dec) * (radians per year) # PAL asks for radians per year inputs pr = self.column_by_name('properMotionRa') # in radians per year pd = self.column_by_name('properMotionDec') # in radians per year px = self.column_by_name('parallax') # in radians rv = self.column_by_name( 'radialVelocity') # in km/s; positive if receding ra = self.column_by_name('raJ2000') dec = self.column_by_name('decJ2000') return _observedFromICRS(ra, dec, pm_ra=pr, pm_dec=pd, parallax=px, v_rad=rv, includeRefraction=includeRefraction, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
def _nativeLonLatFromRaDec(ra_in, dec_in, obs_metadata): """ Convert the RA and Dec of a star into `native' longitude and latitude. Native longitude and latitude are defined as what RA and Dec would be if the celestial pole were at the location where the telescope is pointing. The transformation is achieved by rotating the vector pointing to the RA and Dec being transformed once about the x axis and once about the z axis. These are the Euler rotations referred to in Section 2.3 of Calabretta and Greisen (2002), A&A 395, p. 1077 Note: RA, and Dec are assumed to be in the International Celestial Reference System. Before calculating native longitude and latitude, this method will convert RA and Dec to observed geocentric coordinates. WARNING: This method does not account for apparent motion due to parallax. This method is only useful for mapping positions on a theoretical celestial sphere. @param [in] ra is the RA of the star being transformed in radians (in the International Celestial Reference System) @param [in] dec is the Dec of the star being transformed in radians (in the International Celestial Reference System) @param [in] obs_metadata is an ObservationMetaData characterizing the pointing of the telescope. @param [out] lonOut is the native longitude in radians @param [out] latOut is the native latitude in radians """ ra, dec = _observedFromICRS(ra_in, dec_in, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) raPointing, decPointing = _observedFromICRS(obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) return _nativeLonLatFromPointing(ra, dec, raPointing, decPointing)
def testAltAzFromRaDec(self): """ Test conversion from RA, Dec to Alt, Az """ nSamples = 100 ra = self.rng.random_sample(nSamples)*2.0*np.pi dec = (self.rng.random_sample(nSamples)-0.5)*np.pi lon_rad = 1.467 lat_rad = -0.234 controlAlt, controlAz = controlAltAzFromRaDec(ra, dec, lon_rad, lat_rad, self.mjd) obs = utils.ObservationMetaData(mjd=utils.ModifiedJulianDate(UTC=self.mjd), site=utils.Site(longitude=np.degrees(lon_rad), latitude=np.degrees(lat_rad), name='LSST')) # verify parallactic angle against an expression from # http://www.astro.washington.edu/groups/APO/Mirror.Motions/Feb.2000.Image.Jumps/report.html#Image%20motion%20directions # ra_obs, dec_obs = utils._observedFromICRS(ra, dec, obs_metadata=obs, epoch=2000.0, includeRefraction=True) lmst, last = utils.calcLmstLast(obs.mjd.UT1, lon_rad) hourAngle = np.radians(last * 15.0) - ra_obs controlSinPa = np.sin(hourAngle) * np.cos(lat_rad) / np.cos(controlAlt) testAlt, testAz, testPa = utils._altAzPaFromRaDec(ra, dec, obs) distance = utils.arcsecFromRadians(utils.haversine(controlAz, controlAlt, testAz, testAlt)) self.assertLess(distance.max(), 0.0001) self.assertLess(np.abs(np.sin(testPa) - controlSinPa).max(), self.tolerance) # test non-vectorized version for r, d in zip(ra, dec): controlAlt, controlAz = controlAltAzFromRaDec(r, d, lon_rad, lat_rad, self.mjd) testAlt, testAz, testPa = utils._altAzPaFromRaDec(r, d, obs) lmst, last = utils.calcLmstLast(obs.mjd.UT1, lon_rad) r_obs, dec_obs = utils._observedFromICRS(r, d, obs_metadata=obs, epoch=2000.0, includeRefraction=True) hourAngle = np.radians(last * 15.0) - r_obs controlSinPa = np.sin(hourAngle) * np.cos(lat_rad) / np.cos(controlAlt) distance = utils.arcsecFromRadians(utils.haversine(controlAz, controlAlt, testAz, testAlt)) self.assertLess(distance, 0.0001) self.assertLess(np.abs(np.sin(testPa) - controlSinPa), self.tolerance)
def test_de_precession(self): """ test de-precession by de-precessing a list of RA, Dec and verifying that the distance between the de-precessed points is the same as the distance between the input points. Also verify that the observed boresite gets de-precessed correctly """ rng = np.random.RandomState(12) n_samples = 5 pra = 34.0 pdec = 65.0 obs = ObservationMetaData(pointingRA=pra, pointingDec=pdec, mjd=58324.1) raObs, decObs = _observedFromICRS(np.array([np.radians(pra)]), np.array([np.radians(pdec)]), obs_metadata=obs, epoch=2000.0, includeRefraction=False) ra_list = [] dec_list = [] ra_list.append(raObs[0]) dec_list.append(decObs[0]) for rr, dd in zip( rng.random_sample(n_samples) * 2.0 * np.pi, (rng.random_sample(n_samples) - 0.5) * np.pi): ra_list.append(rr) dec_list.append(dd) ra_list = np.array(ra_list) dec_list = np.array(dec_list) raDecTransformed = PhoSimAstrometryBase()._dePrecess( ra_list, dec_list, obs) dd = arcsecFromRadians( haversine(np.radians(pra), np.radians(pdec), raDecTransformed[0][0], raDecTransformed[1][0])) self.assertLess(dd, 1.0e-6) dd0 = arcsecFromRadians( haversine(raObs[0], decObs[0], np.radians(pra), np.radians(pdec))) self.assertLess(dd, dd0) for ix in range(n_samples + 1): for iy in range(n_samples + 1): if ix != iy: dd1 = arcsecFromRadians( haversine(ra_list[ix], dec_list[ix], ra_list[iy], dec_list[iy])) dd2 = arcsecFromRadians( haversine(raDecTransformed[0][ix], raDecTransformed[1][ix], raDecTransformed[0][iy], raDecTransformed[1][iy])) self.assertAlmostEqual(dd1, dd2, delta=6)
def _dePrecess(self, ra_in, dec_in, obs): """ Transform a set of RA, Dec pairs by subtracting out a rotation which represents the effects of precession, nutation, and aberration. Specifically: Calculate the displacement between the boresite and the boresite corrected for precession, nutation, and aberration (not refraction). Convert boresite and corrected boresite to Cartesian coordinates. Calculate the rotation matrix to go between those Cartesian vectors. Convert [ra_in, dec_in] into Cartesian coordinates. Apply the rotation vector to those Cartesian coordinates. Convert back to ra, dec-like coordinates @param [in] ra_in is a numpy array of RA in radians @param [in] dec_in is a numpy array of Dec in radians @param [in] obs is an ObservationMetaData @param [out] ra_out is a numpy array of de-precessed RA in radians @param [out] dec_out is a numpy array of de-precessed Dec in radians """ if len(ra_in) == 0: return np.array([[], []]) precessedRA, precessedDec = _observedFromICRS(obs._pointingRA, obs._pointingDec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) if (not hasattr(self, '_icrs_to_phosim_rotator') or arcsecFromRadians( _angularSeparation(obs._pointingRA, obs._pointingDec, self._icrs_to_phosim_rotator._ra1, self._icrs_to_phosim_rotator._dec1)) > 1.0e-6 or arcsecFromRadians( _angularSeparation(precessedRA, precessedDec, self._icrs_to_phosim_rotator._ra0, self._icrs_to_phosim_rotator._dec0)) > 1.0e-6): self._icrs_to_phosim_rotator = _FieldRotator( precessedRA, precessedDec, obs._pointingRA, obs._pointingDec) ra_deprecessed, dec_deprecessed = self._icrs_to_phosim_rotator.transform( ra_in, dec_in) return np.array([ra_deprecessed, dec_deprecessed])
def get_phosim_coords(self, ra, dec, obs_metadata): raObs, decObs = _observedFromICRS(ra, dec, includeRefraction=False, obs_metadata=obs_metadata, epoch=2000.0) return self._dePrecess(raObs, decObs, obs_metadata)
def get_observedCoordinates(self): """Getter for observed RA, Dec (i.e. RA and Dec with all effects due to the motion of the Earth and refraction by the atmosphere applied)""" ra = self.column_by_name('raJ2000') dec = self.column_by_name('decJ2000') return _observedFromICRS(ra, dec, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
def observedSSMCoordinates(self, includeRefraction = True): """ Reads in ICRS coordinates from the database. Returns observed coordinates with refraction toggled on or off based on the input boolean includeRefraction """ ra = self.column_by_name('raJ2000') # in radians dec = self.column_by_name('decJ2000') # in radians return _observedFromICRS(ra, dec, includeRefraction=includeRefraction, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
def fovCorners(obs, side_length): """ obs is an ObservationMetaData side_length in arcminutes """ # find the center of the field of view and convert it into "Observed RA, Dec" pointing_lon, pointing_lat = _observedFromICRS(np.array([obs._pointingRA]), np.array([obs._pointingDec ]), obs_metadata=obs, epoch=2000.0) # figure out the length of the diagonal of your square field of view hypotenuse = np.sqrt(2.0 * (side_length / 60.0)**2) half_length = np.radians(0.5 * hypotenuse) # Create a fiducial field of view cetnered on the north pole. # We will take this field of viewand rotate it so that it has # the correct orientation, then translate it down the celestial # sphere to the actual position of your telescope pointing. native_lon_list = np.array([0.0, np.pi / 2.0, np.pi, 1.5 * np.pi]) native_lat_list = np.array([0.5 * np.pi - half_length] * 4) # rotate your field of view to account for the rotation of the sky rot_angle = -1.0 * obs._rotSkyPos + 0.25 * np.pi # the extra 0.25 pi is to align our field # of view so that rotSkyPos=0 puts the # northern edge vertically up (when we # created the field of view, one of the # corners was vertically up) cosRot = np.cos(rot_angle) sinRot = np.sin(rot_angle) rotz = np.array([[cosRot, sinRot, 0.0], [-sinRot, cosRot, 0.0], [0.0, 0.0, 1.0]]) xyz = cartesianFromSpherical(native_lon_list, native_lat_list) rot_xyz = [] for vec in xyz: new_xyz = np.dot(rotz, vec) rot_xyz.append(new_xyz) rot_xyz = np.array(rot_xyz) rot_lon, rot_lat = sphericalFromCartesian(rot_xyz) # translate the field of view down to the actual telescope pointing ra_obs, dec_obs = _lonLatFromNativeLonLat(rot_lon, rot_lat, pointing_lon[0], pointing_lat[0]) return np.degrees( _icrsFromObserved(ra_obs, dec_obs, obs_metadata=obs, epoch=2000.0))
def _dePrecess(self, ra_in, dec_in, obs): """ Transform a set of RA, Dec pairs by subtracting out a rotation which represents the effects of precession, nutation, and aberration. Specifically: Calculate the displacement between the boresite and the boresite corrected for precession, nutation, and aberration (not refraction). Convert boresite and corrected boresite to Cartesian coordinates. Calculate the rotation matrix to go between those Cartesian vectors. Convert [ra_in, dec_in] into Cartesian coordinates. Apply the rotation vector to those Cartesian coordinates. Convert back to ra, dec-like coordinates @param [in] ra_in is a numpy array of RA in radians @param [in] dec_in is a numpy array of Dec in radians @param [in] obs is an ObservationMetaData @param [out] ra_out is a numpy array of de-precessed RA in radians @param [out] dec_out is a numpy array of de-precessed Dec in radians """ if len(ra_in) == 0: return np.array([[], []]) precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) if (not hasattr(self, '_icrs_to_phosim_rotator') or arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec, self._icrs_to_phosim_rotator._ra1, self._icrs_to_phosim_rotator._dec1))>1.0e-6 or arcsecFromRadians(_angularSeparation(precessedRA, precessedDec, self._icrs_to_phosim_rotator._ra0, self._icrs_to_phosim_rotator._dec0))>1.0e-6): self._icrs_to_phosim_rotator = _FieldRotator(precessedRA, precessedDec, obs._pointingRA, obs._pointingDec) ra_deprecessed, dec_deprecessed = self._icrs_to_phosim_rotator.transform(ra_in, dec_in) return np.array([ra_deprecessed, dec_deprecessed])
def _dePrecess(self, ra_in, dec_in, obs_metadata): """ Transform a set of RA, Dec pairs by subtracting out a rotation which represents the effects of precession, nutation, and aberration. Specifically: Calculate the displacement between the boresite and the boresite corrected for precession, nutation, and aberration (not refraction). Convert boresite and corrected boresite to Cartesian coordinates. Calculate the rotation matrix to go between those Cartesian vectors. Convert [ra_in, dec_in] into Cartesian coordinates. Apply the rotation vector to those Cartesian coordinates. Convert back to ra, dec-like coordinates @param [in] ra_in is a numpy array of RA in radians @param [in] dec_in is a numpy array of Dec in radians @param [in] obs_metadata is an ObservationMetaData @param [out] ra_out is a numpy array of de-precessed RA in radians @param [out] dec_out is a numpy array of de-precessed Dec in radians """ if len(ra_in) == 0: return np.array([[], []]) # Calculate the rotation matrix to go from the precessed bore site # to the ICRS bore site xyz_bore = cartesianFromSpherical(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec])) precessedRA, precessedDec = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=False) xyz_precessed = cartesianFromSpherical(precessedRA, precessedDec) rotMat = rotationMatrixFromVectors(xyz_precessed[0], xyz_bore[0]) xyz_list = cartesianFromSpherical(ra_in, dec_in) xyz_de_precessed = np.array([np.dot(rotMat, xx) for xx in xyz_list]) ra_deprecessed, dec_deprecessed = sphericalFromCartesian(xyz_de_precessed) return np.array([ra_deprecessed, dec_deprecessed])
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 _raDecFromNativeLonLat(lon, lat, obs_metadata): """ Transform a star's position in native longitude and latitude into RA and Dec. See the doc string for _nativeLonLatFromRaDec for definitions of native longitude and latitude. @param [in] lon is the native longitude in radians @param [in] lat is the native latitude in radians @param [in] obs_metadata is an ObservationMetaData characterizing the pointing of the telescope @param [out] raOut is the RA of the star in radians (in the International Celestial Reference System) @param [in] decOut is the Dec of the star in radians (in the International Celestial Reference System) Note: Because of its reliance on icrsFromObserved, this method is only accurate at angular distances from the sun of greater than 45 degrees and zenith distances of less than 75 degrees. """ ra_temp, dec_temp = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) raPointing = ra_temp[0] decPointing = dec_temp[0] raObs, decObs = _lonLatFromNativeLonLat(lon, lat, raPointing, decPointing) # convert from observed geocentric coordinates to International Celestial Reference System # coordinates if hasattr(raObs,'__len__'): raOut, decOut = _icrsFromObserved(raObs, decObs, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) else: raOut, decOut = _icrsFromObserved(np.array([raObs]), np.array([decObs]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) if not hasattr(lon, '__len__'): return raOut[0], decOut[0] return raOut, decOut
def test_de_precession(self): """ test de-precession by de-precessing a list of RA, Dec and verifying that the distance between the de-precessed points is the same as the distance between the input points. Also verify that the observed boresite gets de-precessed correctly """ rng = np.random.RandomState(12) n_samples = 5 pra = 34.0 pdec = 65.0 obs = ObservationMetaData(pointingRA=pra, pointingDec=pdec, mjd=58324.1) raObs, decObs = _observedFromICRS(np.array([np.radians(pra)]), np.array([np.radians(pdec)]), obs_metadata=obs, epoch=2000.0, includeRefraction=False) ra_list = [] dec_list = [] ra_list.append(raObs[0]) dec_list.append(decObs[0]) for rr, dd in zip(rng.random_sample(n_samples)*2.0*np.pi, (rng.random_sample(n_samples)-0.5)*np.pi): ra_list.append(rr) dec_list.append(dd) ra_list = np.array(ra_list) dec_list = np.array(dec_list) raDecTransformed = PhoSimAstrometryBase()._dePrecess(ra_list, dec_list, obs) dd = arcsecFromRadians(haversine(np.radians(pra), np.radians(pdec), raDecTransformed[0][0], raDecTransformed[1][0])) self.assertLess(dd, 1.0e-6) dd0 = arcsecFromRadians(haversine(raObs[0], decObs[0], np.radians(pra), np.radians(pdec))) self.assertLess(dd, dd0) for ix in range(n_samples+1): for iy in range(n_samples+1): if ix != iy: dd1 = arcsecFromRadians(haversine(ra_list[ix], dec_list[ix], ra_list[iy], dec_list[iy])) dd2 = arcsecFromRadians(haversine(raDecTransformed[0][ix], raDecTransformed[1][ix], raDecTransformed[0][iy], raDecTransformed[1][iy])) self.assertAlmostEqual(dd1, dd2, delta=6)
def get_phoSimCoordinates(self): """Getter for RA, Dec coordinates expected by PhoSim. These are observed RA, Dec coordinates with the effects of nutation, aberration, and precession subtracted out by the PhosimInputBase._dePrecess() method. This preserves the relative effects of nutation, aberration, and precession while re-aligning the catalog with the boresite RA, Dec so that astrometric solutions make sense.""" ra = self.column_by_name('raJ2000') dec = self.column_by_name('decJ2000') raObs, decObs = _observedFromICRS(ra, dec, includeRefraction = False, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch) return self._dePrecess(raObs, decObs, self.obs_metadata)
def _appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs): """ This method will convert from the 'deprecessed' coordinates expected by PhoSim to apparent geocentric coordinates Parameters ---------- raPhoSim is the PhoSim RA-like coordinate (in radians) decPhoSim is the PhoSim Dec-like coordinate (in radians) obs is an ObservationMetaData characterizing the telescope pointing Returns ------- apparent geocentric RA in radians apparent geocentric Dec in radians """ precessedRA, precessedDec = _observedFromICRS(obs._pointingRA, obs._pointingDec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) if (not hasattr(self, '_phosim_to_icrs_rotator') or arcsecFromRadians( _angularSeparation(obs._pointingRA, obs._pointingDec, self._phosim_to_icrs_rotator._ra0, self._phosim_to_icrs_rotator._dec0)) > 1.0e-6 or arcsecFromRadians( _angularSeparation(precessedRA, precessedDec, self._phosim_to_icrs_rotator._ra1, self._phosim_to_icrs_rotator._dec1)) > 1.0e-6): self._phosim_to_icrs_rotator = _FieldRotator( obs._pointingRA, obs._pointingDec, precessedRA, precessedDec) ra_obs, dec_obs = self._phosim_to_icrs_rotator.transform( raPhoSim, decPhoSim) return _appGeoFromObserved(ra_obs, dec_obs, includeRefraction=False, obs_metadata=obs)
def testObservedRaDec(self): """ Test that the mixins provided in Astrometry SSM really do convert ICRS RA, Dec into observed RA, Dec """ catName = os.path.join(getPackageDir('sims_catUtils'), 'tests', 'scratchSpace', 'ssmAstrometryCat.txt') dtype = np.dtype([('id', np.int), ('raObserved', np.float), ('decObserved', np.float)]) controlData = np.genfromtxt(self.dbFile, dtype=self.dtype) rng = np.random.RandomState(42) nTests = 5 raList = rng.random_sample(nTests) * 2.0 * np.pi decList = (rng.random_sample(nTests) - 0.5) * np.pi mjdList = rng.random_sample(nTests) * 5000.0 + 53850.0 for raPointing, decPointing, mjd in zip(raList, decList, mjdList): obs = ObservationMetaData(pointingRA=raPointing, pointingDec=decPointing, mjd=mjd) cat = SSM_astrometryCat(self.astDB, obs_metadata=obs) cat.write_catalog(catName) testData = np.genfromtxt(catName, dtype=dtype, delimiter=',') self.assertGreater(len(testData), 0) raObservedControl, decObservedControl = _observedFromICRS( controlData['raJ2000'], controlData['decJ2000'], obs_metadata=obs, epoch=2000.0, includeRefraction=True) np.testing.assert_array_almost_equal(raObservedControl, testData['raObserved'], 10) np.testing.assert_array_almost_equal(decObservedControl, testData['decObserved'], 10) if os.path.exists(catName): os.unlink(catName)
def _icrsFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata): """ This method will convert from the 'deprecessed' coordinates expected by PhoSim to ICRS coordinates Parameters ---------- raPhoSim is the PhoSim RA-like coordinate (in radians) decPhoSim is the PhoSim Dec-like coordinate (in radians) obs_metadata is an ObservationMetaData characterizing the telescope pointing Returns ------- raICRS in radians decICRS in radians """ # Calculate the rotation matrix to go from the ICRS bore site to the # precessed bore site xyz_bore = cartesianFromSpherical(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec])) precessedRA, precessedDec = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=False) xyz_precessed = cartesianFromSpherical(precessedRA, precessedDec) rotMat = rotationMatrixFromVectors(xyz_bore[0], xyz_precessed[0]) # apply this rotation matrix to the PhoSim RA, Dec-like coordinates, # transforming back to "Observed" RA and Dec xyz_list = cartesianFromSpherical(raPhoSim, decPhoSim) xyz_obs = np.array([np.dot(rotMat, xx) for xx in xyz_list]) ra_obs, dec_obs = sphericalFromCartesian(xyz_obs) # convert to ICRS coordinates return _icrsFromObserved(ra_obs, dec_obs, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=False)
def _raDecFromNativeLonLat(lon, lat, obs_metadata): """ Transform a star's position in native longitude and latitude into RA and Dec. See the doc string for _nativeLonLatFromRaDec for definitions of native longitude and latitude. @param [in] lon is the native longitude in radians @param [in] lat is the native latitude in radians @param [in] obs_metadata is an ObservationMetaData characterizing the pointing of the telescope @param [out] raOut is the RA of the star in radians (in the International Celestial Reference System) @param [in] decOut is the Dec of the star in radians (in the International Celestial Reference System) Note: Because of its reliance on icrsFromObserved, this method is only accurate at angular distances from the sun of greater than 45 degrees and zenith distances of less than 75 degrees. """ raPointing, decPointing = _observedFromICRS(obs_metadata._pointingRA, obs_metadata._pointingDec, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) raObs, decObs = _lonLatFromNativeLonLat(lon, lat, raPointing, decPointing) # convert from observed geocentric coordinates to International Celestial Reference System # coordinates raOut, decOut = _icrsFromObserved(raObs, decObs, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) return raOut, decOut
def testObservedFromICRS(self): obs = ObservationMetaData(pointingRA=35.0, pointingDec=-45.0, mjd=43572.0) for pmRaList in [self.pm_raList, None]: for pmDecList in [self.pm_decList, None]: for pxList in [self.pxList, None]: for vRadList in [self.v_radList, None]: for includeRefraction in [True, False]: raRad, decRad = utils._observedFromICRS( self.raList, self.decList, pm_ra=pmRaList, pm_dec=pmDecList, parallax=pxList, v_rad=vRadList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) raDeg, decDeg = utils.observedFromICRS( np.degrees(self.raList), np.degrees(self.decList), pm_ra=utils.arcsecFromRadians(pmRaList), pm_dec=utils.arcsecFromRadians(pmDecList), parallax=utils.arcsecFromRadians(pxList), v_rad=vRadList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) dRa = utils.arcsecFromRadians(raRad - np.radians(raDeg)) np.testing.assert_array_almost_equal( dRa, np.zeros(self.nStars), 9) dDec = utils.arcsecFromRadians(decRad - np.radians(decDeg)) np.testing.assert_array_almost_equal( dDec, np.zeros(self.nStars), 9)
def test_stellar_observed_radians(self): """ Test ability to go all the way to observed RA, Dec from PhoSim (this is necessary for the ImSim software that DESC is working on) """ db = testStarsDBObj(driver='sqlite', database=self.db_name) cat = StarTestCatalog(db, obs_metadata=self.obs) with lsst.utils.tests.getTempFilePath('.txt') as cat_name: cat.write_catalog(cat_name) dtype = np.dtype([('raICRS', float), ('decICRS', float), ('raPhoSim', float), ('decPhoSim', float), ('raJ2000', float), ('decJ2000', float), ('pmRA', float), ('pmDec', float), ('parallax', float), ('vRad', float)]) data = np.genfromtxt(cat_name, dtype=dtype) self.assertGreater(len(data), 100) (ra_obs, dec_obs) = _observedFromICRS(data['raJ2000'], data['decJ2000'], obs_metadata=self.obs, pm_ra=data['pmRA'], pm_dec=data['pmDec'], parallax=data['parallax'], v_rad=data['vRad'], includeRefraction=True, epoch=2000.0) (ra_appGeo, dec_appGeo) = PhoSimAstrometryBase._appGeoFromPhoSim(np.radians(data['raPhoSim']), np.radians(data['decPhoSim']), self.obs) (ra_obs_2, dec_obs_2) = _observedFromAppGeo(ra_appGeo, dec_appGeo, obs_metadata=self.obs, includeRefraction=True) dd = arcsecFromRadians(_angularSeparation(ra_obs, dec_obs, ra_obs_2, dec_obs_2)) self.assertLess(dd.max(), 1.0e-5)
def observedStellarCoordinates(self, includeRefraction = True): """ Getter which converts mean coordinates in the International Celestial Reference Frame to observed coordinates. """ # TODO # are we going to store proper motion in raw radians per year # or in sky motion = cos(dec) * (radians per year) # PAL asks for radians per year inputs pr = self.column_by_name('properMotionRa') # in radians per year pd = self.column_by_name('properMotionDec') # in radians per year px = self.column_by_name('parallax') # in radians rv = self.column_by_name('radialVelocity') # in km/s; positive if receding ra = self.column_by_name('raJ2000') dec = self.column_by_name('decJ2000') return _observedFromICRS(ra, dec, pm_ra = pr, pm_dec = pd, parallax = px, v_rad = rv, includeRefraction = includeRefraction, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
def test_stellar_observed_radians(self): """ Test ability to go all the way to observed RA, Dec from PhoSim (this is necessary for the ImSim software that DESC is working on) """ db = testStarsDBObj(driver='sqlite', database=self.db_name) cat = StarTestCatalog(db, obs_metadata=self.obs) with lsst.utils.tests.getTempFilePath('.txt') as cat_name: cat.write_catalog(cat_name) dtype = np.dtype([('raICRS', float), ('decICRS', float), ('raPhoSim', float), ('decPhoSim', float), ('raJ2000', float), ('decJ2000', float), ('pmRA', float), ('pmDec', float), ('parallax', float), ('vRad', float)]) data = np.genfromtxt(cat_name, dtype=dtype) self.assertGreater(len(data), 100) (ra_obs, dec_obs) = _observedFromICRS(data['raJ2000'], data['decJ2000'], obs_metadata=self.obs, pm_ra=data['pmRA'], pm_dec=data['pmDec'], parallax=data['parallax'], v_rad=data['vRad'], includeRefraction=True, epoch=2000.0) (ra_appGeo, dec_appGeo) = PhoSimAstrometryBase._appGeoFromPhoSim( np.radians(data['raPhoSim']), np.radians(data['decPhoSim']), self.obs) (ra_obs_2, dec_obs_2) = _observedFromAppGeo(ra_appGeo, dec_appGeo, obs_metadata=self.obs, includeRefraction=True) dd = arcsecFromRadians( _angularSeparation(ra_obs, dec_obs, ra_obs_2, dec_obs_2)) self.assertLess(dd.max(), 1.0e-5)
def _appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs): """ This method will convert from the 'deprecessed' coordinates expected by PhoSim to apparent geocentric coordinates Parameters ---------- raPhoSim is the PhoSim RA-like coordinate (in radians) decPhoSim is the PhoSim Dec-like coordinate (in radians) obs is an ObservationMetaData characterizing the telescope pointing Returns ------- apparent geocentric RA in radians apparent geocentric Dec in radians """ precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) if (not hasattr(self, '_phosim_to_icrs_rotator') or arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec, self._phosim_to_icrs_rotator._ra0, self._phosim_to_icrs_rotator._dec0))>1.0e-6 or arcsecFromRadians(_angularSeparation(precessedRA, precessedDec, self._phosim_to_icrs_rotator._ra1, self._phosim_to_icrs_rotator._dec1))>1.0e-6): self._phosim_to_icrs_rotator = _FieldRotator(obs._pointingRA, obs._pointingDec, precessedRA, precessedDec) ra_obs, dec_obs = self._phosim_to_icrs_rotator.transform(raPhoSim, decPhoSim) return _appGeoFromObserved(ra_obs, dec_obs, includeRefraction=False, obs_metadata=obs)
def testObservedRaDec(self): """ Test that the mixins provided in Astrometry SSM really do convert ICRS RA, Dec into observed RA, Dec """ catName = os.path.join(getPackageDir('sims_catUtils'), 'tests', 'scratchSpace', 'ssmAstrometryCat.txt') dtype = np.dtype([('id', np.int), ('raObserved', np.float), ('decObserved', np.float)]) controlData = np.genfromtxt(self.dbFile, dtype=self.dtype) np.random.seed(42) nTests = 5 raList = np.random.random_sample(nTests)*2.0*np.pi decList = (np.random.random_sample(nTests)-0.5)*np.pi mjdList = np.random.random_sample(nTests)*5000.0 + 53850.0 for raPointing, decPointing, mjd in zip(raList, decList, mjdList): obs = ObservationMetaData(pointingRA=raPointing, pointingDec=decPointing, mjd=mjd) cat = SSM_astrometryCat(self.astDB, obs_metadata=obs) cat.write_catalog(catName) testData = np.genfromtxt(catName, dtype=dtype, delimiter=',') self.assertGreater(len(testData), 0) raObservedControl, decObservedControl = _observedFromICRS(controlData['raJ2000'], controlData['decJ2000'], obs_metadata=obs, epoch=2000.0, includeRefraction=True) np.testing.assert_array_almost_equal(raObservedControl, testData['raObserved'], 10) np.testing.assert_array_almost_equal(decObservedControl, testData['decObserved'], 10) if os.path.exists(catName): os.unlink(catName)
def _altAzPaFromRaDec(raRad, decRad, obs): """ 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 [out] altitude in radians @param [out] azimuth in radians @param [out] parallactic angle in radians """ raIsArray = False decIsArray = False if isinstance(raRad, np.ndarray): raIsArray = True if isinstance(decRad, np.ndarray): decIsArray = True if raIsArray and not decIsArray: raise RuntimeError('passed numpy array of RA to altAzPaFromRaDec; but only one Dec') if decIsArray and not raIsArray: raise RuntimeError('passed numpy array of Dec to altAzPaFromRaDec; but only one RA') if raIsArray and decIsArray and len(raRad) != len(decRad): raise RuntimeError('in altAzPaFromRaDec length of RA numpy array does not match length of Dec numpy array') if not hasattr(raRad, '__len__'): raObs_temp, decObs_temp = _observedFromICRS(np.array([raRad]), np.array([decRad]), obs_metadata=obs, includeRefraction=True, epoch=2000.0) raObs = raObs_temp[0] decObs = decObs_temp[0] else: raObs, decObs = _observedFromICRS(raRad, decRad, obs_metadata=obs, epoch=2000.0, includeRefraction=True) lst = calcLmstLast(obs.mjd.UT1, obs.site.longitude_rad) last = lst[1] haRad = np.radians(last*15.0) - raObs if isinstance(haRad, np.ndarray): 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 _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 testCardinalDirections(self): """ This unit test verifies that the following conventions hold: if rotSkyPos = 0, then north is +y the camera and east is +x if rotSkyPos = -90, then north is -x on the camera and east is +y if rotSkyPos = 90, then north is +x on the camera and east is -y if rotSkyPos = 180, then north is -y on the camera and east is -x This is consistent with rotSkyPos = rotTelPos - parallacticAngle parallacticAngle is negative when the pointing is east of the meridian. http://www.petermeadows.com/html/parallactic.html rotTelPos is the angle between up on the telescope and up on the camera, where positive rotTelPos goes from north to west (from an email sent to me by LynneJones) I have verified that OpSim follows the rotSkyPos = rotTelPos - paralacticAngle convention. I have verified that altAzPaFromRaDec follows the convention that objects east of the meridian have a negative parallactic angle. (altAzPaFromRaDec uses PALPY under the hood, so it can probably be taken as correct) It will verify this convention for multiple random pointings. """ epoch = 2000.0 mjd = 42350.0 rng = np.random.RandomState(42) raList = rng.random_sample(10) * 360.0 decList = rng.random_sample(10) * 180.0 - 90.0 for rotSkyPos in np.arange(-90.0, 181.0, 90.0): for ra, dec in zip(raList, decList): obs = ObservationMetaData(pointingRA=ra, pointingDec=dec, mjd=mjd, rotSkyPos=rotSkyPos) ra_obs, dec_obs = _observedFromICRS(np.radians([ra]), np.radians([dec]), obs_metadata=obs, epoch=2000.0, includeRefraction=True) # test points that are displaced just to the (E, W, N, S) of the pointing # in observed geocentric RA, Dec; verify that the pupil coordinates # change as expected raTest_obs = ra_obs[0] + np.array([0.01, -0.01, 0.0, 0.0]) decTest_obs = dec_obs[0] + np.array([0.0, 0.0, 0.01, -0.01]) raTest, decTest = _icrsFromObserved(raTest_obs, decTest_obs, obs_metadata=obs, epoch=2000.0, includeRefraction=True) x, y = _pupilCoordsFromRaDec(raTest, decTest, obs_metadata=obs, epoch=epoch) lon, lat = _nativeLonLatFromRaDec(raTest, decTest, obs) rr = np.abs(np.cos(lat) / np.sin(lat)) if np.abs(rotSkyPos) < 0.01: # rotSkyPos == 0 control_x = np.array([1.0 * rr[0], -1.0 * rr[1], 0.0, 0.0]) control_y = np.array([0.0, 0.0, 1.0 * rr[2], -1.0 * rr[3]]) elif np.abs(rotSkyPos + 90.0) < 0.01: # rotSkyPos == -90 control_x = np.array([0.0, 0.0, -1.0 * rr[2], 1.0 * rr[3]]) control_y = np.array([1.0 * rr[0], -1.0 * rr[1], 0.0, 0.0]) elif np.abs(rotSkyPos - 90.0) < 0.01: # rotSkyPos == 90 control_x = np.array([0.0, 0.0, 1.0 * rr[2], -1.0 * rr[3]]) control_y = np.array( [-1.0 * rr[0], +1.0 * rr[1], 0.0, 0.0]) elif np.abs(rotSkyPos - 180.0) < 0.01: # rotSkyPos == 180 control_x = np.array( [-1.0 * rr[0], +1.0 * rr[1], 0.0, 0.0]) control_y = np.array([0.0, 0.0, -1.0 * rr[2], 1.0 * rr[3]]) msg = 'failed on rotSkyPos == %e\n' % rotSkyPos msg += 'control_x %s\n' % str(control_x) msg += 'test_x %s\n' % str(x) msg += 'control_y %s\n' % str(control_y) msg += 'test_y %s\n' % str(y) dx = np.array([ xx / cc if np.abs(cc) > 1.0e-10 else 1.0 - xx for xx, cc in zip(x, control_x) ]) dy = np.array([ yy / cc if np.abs(cc) > 1.0e-10 else 1.0 - yy for yy, cc in zip(y, control_y) ]) self.assertLess(np.abs(dx - np.ones(4)).max(), 0.001, msg=msg) self.assertLess(np.abs(dy - np.ones(4)).max(), 0.001, msg=msg)
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 = 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 test_icrsFromObserved(self): """ Test that _icrsFromObserved really inverts _observedFromAppGeo. In this case, the method is only reliable at distances of more than 45 degrees from the sun and at zenith distances less than 70 degrees. """ numpy.random.seed(412) nSamples = 100 mjd2000 = pal.epb(2000.0) # convert epoch to mjd site = Site(name='LSST') for mjd in (53000.0, 53241.6, 58504.6): for includeRefraction in (True, False): for raPointing in (23.5, 256.9, 100.0): for decPointing in (-12.0, 45.0, 66.8): obs = ObservationMetaData(mjd=mjd, site=site) raZenith, decZenith = _raDecFromAltAz(0.5*numpy.pi, 0.0, obs) obs = ObservationMetaData(pointingRA=raPointing, pointingDec=decPointing, mjd=mjd, site=site) rr = numpy.random.random_sample(nSamples)*numpy.radians(50.0) theta = numpy.random.random_sample(nSamples)*2.0*numpy.pi ra_in = raZenith + rr*numpy.cos(theta) dec_in = decZenith + rr*numpy.sin(theta) # test a round-trip between observedFromICRS and icrsFromObserved ra_obs, dec_obs = _observedFromICRS(ra_in, dec_in, obs_metadata=obs, includeRefraction=includeRefraction, epoch=2000.0) ra_icrs, dec_icrs = _icrsFromObserved(ra_obs, dec_obs, obs_metadata=obs, includeRefraction=includeRefraction, epoch=2000.0) valid_pts = numpy.where(_distanceToSun(ra_in, dec_in, mjd)>0.25*numpy.pi)[0] self.assertGreater(len(valid_pts), 0) distance = arcsecFromRadians(pal.dsepVector(ra_in[valid_pts], dec_in[valid_pts], ra_icrs[valid_pts], dec_icrs[valid_pts])) self.assertLess(distance.max(), 0.01) # test a round-trip between observedFromAppGeo and appGeoFromObserved ra_obs, dec_obs = _observedFromAppGeo(ra_in, dec_in, obs_metadata=obs, includeRefraction=includeRefraction) ra_app, dec_app = _appGeoFromObserved(ra_obs, dec_obs, obs_metadata=obs, includeRefraction=includeRefraction) distance = arcsecFromRadians(pal.dsepVector(ra_in[valid_pts], dec_in[valid_pts], ra_app[valid_pts], dec_app[valid_pts])) self.assertLess(distance.max(), 0.01)
def testAstrometryExceptions(self): """ Test to make sure that stand-alone astrometry methods raise an exception when they are called without the necessary arguments """ obs_metadata = makeObservationMetaData() ra, dec, pm_ra, pm_dec, parallax, v_rad = makeRandomSample() raShort = numpy.array([1.0]) decShort = numpy.array([1.0]) ##########test refractionCoefficients self.assertRaises(RuntimeError, refractionCoefficients) site = obs_metadata.site x, y = refractionCoefficients(site=site) ##########test applyRefraction zd = 0.1 rzd = applyRefraction(zd, x, y) zd = [0.1, 0.2] self.assertRaises(RuntimeError, applyRefraction, zd, x, y) zd = numpy.array([0.1, 0.2]) rzd = applyRefraction(zd, x, y) ##########test _applyPrecession #test without mjd self.assertRaises(RuntimeError, _applyPrecession, ra, dec) #test mismatches self.assertRaises(RuntimeError, _applyPrecession, raShort, dec, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyPrecession, ra, decShort, mjd=ModifiedJulianDate(TAI=52000.0)) #test that it runs _applyPrecession(ra, dec, mjd=ModifiedJulianDate(TAI=52000.0)) ##########test _applyProperMotion raList = list(ra) decList = list(dec) pm_raList = list(pm_ra) pm_decList = list(pm_dec) parallaxList = list(parallax) v_radList = list(v_rad) pm_raShort = numpy.array([pm_ra[0]]) pm_decShort = numpy.array([pm_dec[0]]) parallaxShort = numpy.array([parallax[0]]) v_radShort = numpy.array([v_rad[0]]) #test without mjd self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_dec, parallax, v_rad) #test passing lists self.assertRaises(RuntimeError, _applyProperMotion, raList, dec, pm_ra, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, decList, pm_ra, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_raList, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_decList, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_dec, parallaxList, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_dec, parallax, v_radList, mjd=ModifiedJulianDate(TAI=52000.0)) #test mismatches self.assertRaises(RuntimeError, _applyProperMotion, raShort, dec, pm_ra, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, decShort, pm_ra, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_raShort, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_decShort, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_dec, parallaxShort, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _applyProperMotion, ra, dec, pm_ra, pm_dec, parallax, v_radShort, mjd=ModifiedJulianDate(TAI=52000.0)) #test that it actually runs _applyProperMotion(ra, dec, pm_ra, pm_dec, parallax, v_rad, mjd=ModifiedJulianDate(TAI=52000.0)) _applyProperMotion(ra[0], dec[0], pm_ra[0], pm_dec[0], parallax[0], v_rad[0], mjd=ModifiedJulianDate(TAI=52000.0)) ##########test _appGeoFromICRS #test without mjd self.assertRaises(RuntimeError, _appGeoFromICRS, ra, dec) #test with mismatched ra, dec self.assertRaises(RuntimeError, _appGeoFromICRS, ra, decShort, mjd=ModifiedJulianDate(TAI=52000.0)) self.assertRaises(RuntimeError, _appGeoFromICRS, raShort, dec, mjd=ModifiedJulianDate(TAI=52000.0)) #test that it actually urns test=_appGeoFromICRS(ra, dec, mjd=obs_metadata.mjd) ##########test _observedFromAppGeo #test without obs_metadata self.assertRaises(RuntimeError, _observedFromAppGeo, ra, dec) #test without site dummy=ObservationMetaData(pointingRA=obs_metadata.pointingRA, pointingDec=obs_metadata.pointingDec, mjd=obs_metadata.mjd, site=None) self.assertRaises(RuntimeError, _observedFromAppGeo, ra, dec, obs_metadata=dummy) #test without mjd dummy=ObservationMetaData(pointingRA=obs_metadata.pointingRA, pointingDec=obs_metadata.pointingDec, site=Site(name='LSST')) self.assertRaises(RuntimeError, _observedFromAppGeo, ra, dec, obs_metadata=dummy) #test mismatches dummy=ObservationMetaData(pointingRA=obs_metadata.pointingRA, pointingDec=obs_metadata.pointingDec, mjd=obs_metadata.mjd, site=Site(name='LSST')) self.assertRaises(RuntimeError, _observedFromAppGeo, ra, decShort, obs_metadata=dummy) self.assertRaises(RuntimeError, _observedFromAppGeo, raShort, dec, obs_metadata=dummy) #test that it actually runs test = _observedFromAppGeo(ra, dec, obs_metadata=dummy) ##########test _observedFromICRS #test without epoch self.assertRaises(RuntimeError, _observedFromICRS, ra, dec, obs_metadata=obs_metadata) #test without obs_metadata self.assertRaises(RuntimeError, _observedFromICRS, ra, dec, epoch=2000.0) #test without mjd dummy=ObservationMetaData(pointingRA=obs_metadata.pointingRA, pointingDec=obs_metadata.pointingDec, site=obs_metadata.site) self.assertRaises(RuntimeError, _observedFromICRS, ra, dec, epoch=2000.0, obs_metadata=dummy) #test that it actually runs dummy=ObservationMetaData(pointingRA=obs_metadata.pointingRA, pointingDec=obs_metadata.pointingDec, site=obs_metadata.site, mjd=obs_metadata.mjd) #test mismatches self.assertRaises(RuntimeError, _observedFromICRS, ra, decShort, epoch=2000.0, obs_metadata=dummy) self.assertRaises(RuntimeError, _observedFromICRS, raShort, dec, epoch=2000.0, obs_metadata=dummy) #test that it actually runs test = _observedFromICRS(ra, dec, obs_metadata=dummy, epoch=2000.0)
def _raDecFromPupilCoords(xPupil, yPupil, obs_metadata=None, epoch=None): """ @param [in] xPupil -- pupil coordinates in radians @param [in] yPupil -- pupil coordinates in radians @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) @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) """ 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") if len(xPupil)!=len(yPupil): raise RuntimeError("You passed %d RAs but %d Decs into raDecFromPupilCoords" % \ (len(raObj), len(decObj))) ra_pointing_temp, dec_pointing_temp = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) ra_pointing = ra_pointing_temp[0] dec_pointing = dec_pointing_temp[0] #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. raObs, decObs = palpy.dtp2sVector(x_g, y_g, ra_pointing, dec_pointing) ra_icrs, dec_icrs = _icrsFromObserved(raObs, decObs, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) return np.array([ra_icrs, dec_icrs])
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 _pupilCoordsFromRaDec(ra_in, dec_in, obs_metadata=None, epoch=None): """ 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 a numpy array of RAs in radians (in the International Celestial Reference System) @param [in] dec_in is a numpy array of Decs in radians (in the International Celestial Reference System) @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 (optional; if not provided, this method will try to get it from the db_obj member variable, assuming this method is part of an InstanceCatalog) @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 """ 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 len(ra_in)!=len(dec_in): raise RuntimeError("You passed %d RAs but %d Decs to pupilCoordsFromRaDec" % (len(ra_in), len(dec_in))) 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") theta = obs_metadata._rotSkyPos ra_obs, dec_obs = _observedFromICRS(ra_in, dec_in, obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) ra_pointing_temp, dec_pointing_temp = _observedFromICRS(np.array([obs_metadata._pointingRA]), np.array([obs_metadata._pointingDec]), obs_metadata=obs_metadata, epoch=2000.0, includeRefraction=True) ra_pointing = ra_pointing_temp[0] dec_pointing = dec_pointing_temp[0] #palpy.ds2tp performs the gnomonic projection on ra_in and dec_in #with a tangent point at (pointingRA, pointingDec) # 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 _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 testCardinalDirections(self): """ This unit test verifies that the following conventions hold: if rotSkyPos = 0, then north is +y the camera and east is +x if rotSkyPos = -90, then north is -x on the camera and east is +y if rotSkyPos = 90, then north is +x on the camera and east is -y if rotSkyPos = 180, then north is -y on the camera and east is -x This is consistent with rotSkyPos = rotTelPos - parallacticAngle parallacticAngle is negative when the pointing is east of the meridian. http://www.petermeadows.com/html/parallactic.html rotTelPos is the angle between up on the telescope and up on the camera, where positive rotTelPos goes from north to west (from an email sent to me by LynneJones) I have verified that OpSim follows the rotSkyPos = rotTelPos - paralacticAngle convention. I have verified that altAzPaFromRaDec follows the convention that objects east of the meridian have a negative parallactic angle. (altAzPaFromRaDec uses PALPY under the hood, so it can probably be taken as correct) It will verify this convention for multiple random pointings. """ epoch = 2000.0 mjd = 42350.0 rng = np.random.RandomState(42) raList = rng.random_sample(10) * 360.0 decList = rng.random_sample(10) * 180.0 - 90.0 for rotSkyPos in np.arange(-90.0, 181.0, 90.0): for ra, dec in zip(raList, decList): obs = ObservationMetaData(pointingRA=ra, pointingDec=dec, mjd=mjd, rotSkyPos=rotSkyPos) ra_obs, dec_obs = _observedFromICRS(np.radians([ra]), np.radians([dec]), obs_metadata=obs, epoch=2000.0, includeRefraction=True) # test points that are displaced just to the (E, W, N, S) of the pointing # in observed geocentric RA, Dec; verify that the pupil coordinates # change as expected raTest_obs = ra_obs[0] + np.array([0.01, -0.01, 0.0, 0.0]) decTest_obs = dec_obs[0] + np.array([0.0, 0.0, 0.01, -0.01]) raTest, decTest = _icrsFromObserved(raTest_obs, decTest_obs, obs_metadata=obs, epoch=2000.0, includeRefraction=True) x, y = _pupilCoordsFromRaDec(raTest, decTest, obs_metadata=obs, epoch=epoch) lon, lat = _nativeLonLatFromRaDec(raTest, decTest, obs) rr = np.abs(np.cos(lat) / np.sin(lat)) if np.abs(rotSkyPos) < 0.01: # rotSkyPos == 0 control_x = np.array([1.0 * rr[0], -1.0 * rr[1], 0.0, 0.0]) control_y = np.array([0.0, 0.0, 1.0 * rr[2], -1.0 * rr[3]]) elif np.abs(rotSkyPos + 90.0) < 0.01: # rotSkyPos == -90 control_x = np.array([0.0, 0.0, -1.0 * rr[2], 1.0 * rr[3]]) control_y = np.array([1.0 * rr[0], -1.0 * rr[1], 0.0, 0.0]) elif np.abs(rotSkyPos - 90.0) < 0.01: # rotSkyPos == 90 control_x = np.array([0.0, 0.0, 1.0 * rr[2], -1.0 * rr[3]]) control_y = np.array([-1.0 * rr[0], +1.0 * rr[1], 0.0, 0.0]) elif np.abs(rotSkyPos - 180.0) < 0.01: # rotSkyPos == 180 control_x = np.array([-1.0 * rr[0], +1.0 * rr[1], 0.0, 0.0]) control_y = np.array([0.0, 0.0, -1.0 * rr[2], 1.0 * rr[3]]) msg = 'failed on rotSkyPos == %e\n' % rotSkyPos msg += 'control_x %s\n' % str(control_x) msg += 'test_x %s\n' % str(x) msg += 'control_y %s\n' % str(control_y) msg += 'test_y %s\n' % str(y) dx = np.array([xx / cc if np.abs(cc) > 1.0e-10 else 1.0 - xx for xx, cc in zip(x, control_x)]) dy = np.array([yy / cc if np.abs(cc) > 1.0e-10 else 1.0 - yy for yy, cc in zip(y, control_y)]) self.assertLess(np.abs(dx-np.ones(4)).max(), 0.001, msg=msg) self.assertLess(np.abs(dy-np.ones(4)).max(), 0.001, msg=msg)
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])
def testObservedFromPupil_noRefraction(self): """ Test conversion from pupil coordinates to observed coordinates when includeRefraction=False """ mjd = ModifiedJulianDate(TAI=53000.0) solarRA, solarDec = solarRaDec(mjd) # to make sure that we are more than 45 degrees from the Sun as required # for _icrsFromObserved to be at all accurate raCenter = solarRA + 100.0 decCenter = solarDec - 30.0 obs = ObservationMetaData(pointingRA=raCenter, pointingDec=decCenter, boundType='circle', boundLength=0.1, rotSkyPos=23.0, mjd=mjd) nSamples = 1000 rng = np.random.RandomState(4453) ra = (rng.random_sample(nSamples) * 0.1 - 0.2) + np.radians(raCenter) dec = (rng.random_sample(nSamples) * 0.1 - 0.2) + np.radians(decCenter) xp, yp = _pupilCoordsFromRaDec(ra, dec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) raObs, decObs = _observedFromICRS(ra, dec, obs_metadata=obs, epoch=2000.0, includeRefraction=False) raObs_test, decObs_test = _observedFromPupilCoords( xp, yp, obs_metadata=obs, epoch=2000.0, includeRefraction=False) dist = arcsecFromRadians( haversine(raObs, decObs, raObs_test, decObs_test)) self.assertLess(dist.max(), 1.0e-6) # test output in degrees raObs_deg, decObs_deg = observedFromPupilCoords( xp, yp, obs_metadata=obs, epoch=2000.0, includeRefraction=False) np.testing.assert_array_almost_equal(raObs_deg, np.degrees(raObs_test), decimal=16) np.testing.assert_array_almost_equal(decObs_deg, np.degrees(decObs_test), decimal=16) # test one-at-a-time input for ii in range(len(raObs)): rr, dd = _observedFromPupilCoords(xp[ii], yp[ii], obs_metadata=obs, epoch=2000.0, includeRefraction=False) self.assertAlmostEqual(rr, raObs_test[ii], 16) self.assertAlmostEqual(dd, decObs_test[ii], 16) rr, dd = observedFromPupilCoords(xp[ii], yp[ii], obs_metadata=obs, epoch=2000.0, includeRefraction=False) self.assertAlmostEqual(rr, raObs_deg[ii], 16) self.assertAlmostEqual(dd, decObs_deg[ii], 16)