def test_icrsFromObservedExceptions(self): """ Test that _icrsFromObserved raises exceptions when it is supposed to. """ numpy.random.seed(33) ra_in = numpy.random.random_sample(10) dec_in = numpy.random.random_sample(10) with self.assertRaises(RuntimeError) as context: ra_out, dec_out = _icrsFromObserved(ra_in, dec_in, epoch=2000.0) self.assertEqual(context.exception.args[0], "cannot call icrsFromObserved; obs_metadata is None") obs = ObservationMetaData(pointingRA=23.0, pointingDec=-19.0) with self.assertRaises(RuntimeError) as context: ra_out, dec_out = _icrsFromObserved(ra_in, dec_in, epoch=2000.0, obs_metadata=obs) self.assertEqual(context.exception.args[0], "cannot call icrsFromObserved; obs_metadata.mjd is None") obs = ObservationMetaData(pointingRA=23.0, pointingDec=-19.0, mjd=ModifiedJulianDate(TAI=52344.0)) with self.assertRaises(RuntimeError) as context: ra_out, dec_out = _icrsFromObserved(ra_in, dec_in, obs_metadata=obs) self.assertEqual(context.exception.args[0], "cannot call icrsFromObserved; you have not specified an epoch") with self.assertRaises(RuntimeError) as context: ra_out, dec_out = _icrsFromObserved(ra_in[:3], dec_in, obs_metadata=obs, epoch=2000.0) self.assertEqual(context.exception.args[0], "You passed 3 RAs but 10 Decs to icrsFromObserved")
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 testIcrsFromObserved(self): obs = ObservationMetaData(pointingRA=35.0, pointingDec=-45.0, mjd=43572.0) for includeRefraction in [True, False]: raRad, decRad = utils._icrsFromObserved( self.raList, self.decList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) raDeg, decDeg = utils.icrsFromObserved( np.degrees(self.raList), np.degrees(self.decList), 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 _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 _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 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 _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 testIcrsFromObserved(self): obs = ObservationMetaData(pointingRA=35.0, pointingDec=-45.0, mjd=43572.0) for includeRefraction in [True, False]: raRad, decRad = utils._icrsFromObserved(self.raList, self.decList, obs_metadata=obs, epoch=2000.0, includeRefraction=includeRefraction) raDeg, decDeg = utils.icrsFromObserved(np.degrees(self.raList), np.degrees(self.decList), 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 _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 _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 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 _raDecFromAltAz(altRad, azRad, obs): """ 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 [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 """ altIsArray = False azIsArray = False if isinstance(altRad, np.ndarray): altIsArray = True if isinstance(azRad, np.ndarray): azIsArray = True if altIsArray and not azIsArray: raise RuntimeError('passed a numpy array of alt to raDecFromAltAz, but only one az') if azIsArray and not altIsArray: raise RuntimeError('passed a numpy array of az to raDecFromAltAz, but only one alt') if azIsArray and altIsArray and len(altRad)!=len(azRad): raise RuntimeError('in raDecFromAltAz, length of alt numpy array does not match length of az numpy array') 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 altIsArray: 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 if not hasattr(raObs, '__len__'): raRad, decRad = _icrsFromObserved(np.array([raObs]), np.array([decObs]), obs_metadata=obs, epoch=2000.0, includeRefraction=True) return raRad[0], decRad[0] raRad, decRad = _icrsFromObserved(raObs, decObs, obs_metadata=obs, epoch=2000.0, includeRefraction=True) return raRad, decRad
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 _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 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)