Coverage for python/lsst/sims/catUtils/mixins/AstrometryMixin.py : 33%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from builtins import object
2import numpy as np
3from lsst.sims.catalogs.decorators import compound, cached
4from lsst.sims.utils import _galacticFromEquatorial, sphericalFromCartesian, \
5 cartesianFromSpherical
7from lsst.sims.utils import _applyProperMotion
8from lsst.sims.utils import _observedFromICRS, _pupilCoordsFromRaDec
9from lsst.sims.utils import _appGeoFromObserved
10from lsst.sims.utils import _icrsFromAppGeo
11from lsst.sims.utils import _pupilCoordsFromObserved
12from lsst.sims.utils import rotationMatrixFromVectors
13from lsst.sims.coordUtils.CameraUtils import chipNameFromPupilCoords, pixelCoordsFromPupilCoords
14from lsst.sims.coordUtils.CameraUtils import focalPlaneCoordsFromPupilCoords
16from lsst.sims.catUtils.mixins.PhoSimSupport import _FieldRotator
17from lsst.sims.utils import _angularSeparation, arcsecFromRadians
19__all__ = ["AstrometryBase", "AstrometryStars", "AstrometryGalaxies", "AstrometrySSM",
20 "PhoSimAstrometryBase", "PhoSimAstrometryStars", "PhoSimAstrometryGalaxies",
21 "PhoSimAstrometrySSM",
22 "CameraCoords"]
25class AstrometryBase(object):
26 """Collection of astrometry routines that operate on numpy arrays"""
28 @compound('glon', 'glat')
29 def get_galactic_coords(self):
30 """
31 Getter for galactic coordinates, in case the catalog class does not provide that
33 Reads in the ra and dec from the data base and returns columns with galactic
34 longitude and latitude.
36 All angles are in radians
37 """
38 ra = self.column_by_name('raJ2000')
39 dec = self.column_by_name('decJ2000')
41 glon, glat = _galacticFromEquatorial(ra, dec)
43 return np.array([glon, glat])
45 @compound('x_pupil', 'y_pupil')
46 def get_pupilFromSky(self):
47 """
48 Take an input RA and dec from the sky and convert it to coordinates
49 in the pupil.
50 """
52 raObs = self.column_by_name('raObserved')
53 decObs = self.column_by_name('decObserved')
55 return _pupilCoordsFromObserved(raObs, decObs, epoch=self.db_obj.epoch,
56 obs_metadata=self.obs_metadata)
59class CameraCoords(AstrometryBase):
60 """Methods for getting coordinates from the camera object"""
61 camera = None
62 allow_multiple_chips = False # this is a flag which, if true, would allow
63 # chipNameFromPupilCoords to return objects that land on
64 # multiple chips; only the first chip would be
65 # written to the catalog
67 @cached
68 def get_chipName(self):
69 """Get the chip name if there is one for each catalog entry"""
70 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
71 if len(xPupil) == 0:
72 return np.array([])
73 if self.camera is None:
74 raise RuntimeError("No camera defined; cannot find chipName")
75 return chipNameFromPupilCoords(xPupil, yPupil, camera=self.camera,
76 allow_multiple_chips=self.allow_multiple_chips)
78 @compound('xPix', 'yPix')
79 def get_pixelCoordinates(self):
80 """Get the pixel positions (or nan if not on a chip) for all objects in the catalog"""
81 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
82 chipNameList = self.column_by_name('chipName')
83 if len(xPupil) == 0:
84 return np.array([[],[]])
85 if self.camera is None:
86 raise RuntimeError("No camera is defined; cannot calculate pixel coordinates")
87 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipNameList,
88 camera=self.camera)
90 @compound('xFocalPlane', 'yFocalPlane')
91 def get_focalPlaneCoordinates(self):
92 """Get the focal plane coordinates for all objects in the catalog."""
93 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
94 if len(xPupil) == 0:
95 return np.array([[],[]])
96 if self.camera is None:
97 raise RuntimeError("No camera defined. Cannot calculate focal plane coordinates")
98 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=self.camera)
101class AstrometryGalaxies(AstrometryBase):
102 """
103 This mixin contains astrometry getters for objects with zero parallax, proper motion, or radial
104 velocity (i.e. extragalactic sources).
106 Available getters are:
107 raICRS, decICRS -- the RA, Dec of the object in the International Celestial Reference System
109 raObserved, decObserved -- the result of applying precession, nutation, aberration, and refraction
110 to raICRS, decICRS
111 """
113 @compound('raICRS', 'decICRS')
114 def get_icrsCoordinates(self):
115 """Getter for RA, Dec in the International Celestial Reference System with effects
116 due to proper motion and radial velocity applied"""
117 return np.array([self.column_by_name('raJ2000'), self.column_by_name('decJ2000')])
119 @compound('raObserved', 'decObserved')
120 def get_observedCoordinates(self):
121 """Getter for observed RA, Dec (i.e. RA and Dec with all effects due to the motion
122 of the Earth and refraction by the atmosphere applied)"""
123 ra = self.column_by_name('raJ2000')
124 dec = self.column_by_name('decJ2000')
125 return _observedFromICRS(ra, dec, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
128class AstrometryStars(AstrometryBase):
129 """
130 This mixin contains getters for objects with non-zero parallax, proper motion, and radial
131 velocities (i.e. sources in the Milky Way).
133 Available getters are:
134 raICRS, decICRS -- the RA, Dec of the object in the International Celestial Reference System
135 with proper motion and radial velocity applied
137 raObserved, decObserved -- the result of applying precession, nutation, aberration, parallax,
138 and refraction to raICRS, decICRS
139 """
141 def observedStellarCoordinates(self, includeRefraction = True):
142 """
143 Getter which converts mean coordinates in the International Celestial
144 Reference Frame to observed coordinates.
145 """
147 # TODO
148 # are we going to store proper motion in raw radians per year
149 # or in sky motion = cos(dec) * (radians per year)
150 # PAL asks for radians per year inputs
152 pr = self.column_by_name('properMotionRa') # in radians per year
153 pd = self.column_by_name('properMotionDec') # in radians per year
154 px = self.column_by_name('parallax') # in radians
155 rv = self.column_by_name('radialVelocity') # in km/s; positive if receding
156 ra = self.column_by_name('raJ2000')
157 dec = self.column_by_name('decJ2000')
159 return _observedFromICRS(ra, dec, pm_ra = pr, pm_dec = pd, parallax = px, v_rad = rv,
160 includeRefraction = includeRefraction, obs_metadata=self.obs_metadata,
161 epoch=self.db_obj.epoch)
163 @compound('raObserved', 'decObserved')
164 def get_observedCoordinates(self):
165 """Getter for observed RA, Dec (i.e. RA and Dec with all effects due to the motion
166 of the Earth and refraction by the atmosphere applied)"""
167 return self.observedStellarCoordinates()
169 @compound('raICRS', 'decICRS')
170 def get_icrsCoordinates(self):
171 """Getter for RA, Dec in the International Celestial Reference System with effects
172 due to proper motion and radial velocity applied"""
173 ra0 = self.column_by_name('raJ2000')
174 dec0 = self.column_by_name('decJ2000')
175 pr = self.column_by_name('properMotionRa') # in radians per year
176 pd = self.column_by_name('properMotionDec') # in radians per year
177 px = self.column_by_name('parallax') # in radians
178 rv = self.column_by_name('radialVelocity') # in km/s; positive if receding
180 ra_corr, dec_corr = _applyProperMotion(ra0, dec0, pr, pd, px, rv, mjd=self.obs_metadata.mjd)
181 return np.array([ra_corr, dec_corr])
184class AstrometrySSM(AstrometryBase):
185 """
186 This mixin will provide getters for astrometric columns customized to Solar System Object tables
187 """
189 @compound('raICRS', 'decICRS')
190 def get_icrsCoordinates(self):
191 return np.array([self.column_by_name('raJ2000'), self.column_by_name('decJ2000')])
193 def observedSSMCoordinates(self, includeRefraction = True):
194 """
195 Reads in ICRS coordinates from the database. Returns observed coordinates
196 with refraction toggled on or off based on the input boolean includeRefraction
197 """
198 ra = self.column_by_name('raJ2000') # in radians
199 dec = self.column_by_name('decJ2000') # in radians
201 return _observedFromICRS(ra, dec, includeRefraction=includeRefraction,
202 obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
204 @compound('raObserved', 'decObserved')
205 def get_observedCoordinates(self):
206 return self.observedSSMCoordinates(includeRefraction = True)
208 @cached
209 def get_skyVelocity(self):
210 """
211 Gets the skyVelocity in radians per day
212 """
214 dradt = self.column_by_name('velRa') # in radians per day (actual sky velocity;
215 # i.e., no need to divide by cos(dec))
217 ddecdt = self.column_by_name('velDec') # in radians per day
219 return np.sqrt(np.power(dradt, 2) + np.power(ddecdt, 2))
222class PhoSimAstrometryBase(object):
223 """
224 This mixin contains the _dePrecess method necessary to create PhoSim
225 images that are astrometrically consistent with their input catalogs.
226 """
228 def _dePrecess(self, ra_in, dec_in, obs):
229 """
230 Transform a set of RA, Dec pairs by subtracting out a rotation
231 which represents the effects of precession, nutation, and aberration.
233 Specifically:
235 Calculate the displacement between the boresite and the boresite
236 corrected for precession, nutation, and aberration (not refraction).
238 Convert boresite and corrected boresite to Cartesian coordinates.
240 Calculate the rotation matrix to go between those Cartesian vectors.
242 Convert [ra_in, dec_in] into Cartesian coordinates.
244 Apply the rotation vector to those Cartesian coordinates.
246 Convert back to ra, dec-like coordinates
248 @param [in] ra_in is a numpy array of RA in radians
250 @param [in] dec_in is a numpy array of Dec in radians
252 @param [in] obs is an ObservationMetaData
254 @param [out] ra_out is a numpy array of de-precessed RA in radians
256 @param [out] dec_out is a numpy array of de-precessed Dec in radians
257 """
259 if len(ra_in) == 0:
260 return np.array([[], []])
263 precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec,
264 obs_metadata=obs, epoch=2000.0,
265 includeRefraction=False)
267 if (not hasattr(self, '_icrs_to_phosim_rotator') or
268 arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec,
269 self._icrs_to_phosim_rotator._ra1,
270 self._icrs_to_phosim_rotator._dec1))>1.0e-6 or
271 arcsecFromRadians(_angularSeparation(precessedRA, precessedDec,
272 self._icrs_to_phosim_rotator._ra0,
273 self._icrs_to_phosim_rotator._dec0))>1.0e-6):
275 self._icrs_to_phosim_rotator = _FieldRotator(precessedRA, precessedDec,
276 obs._pointingRA, obs._pointingDec)
278 ra_deprecessed, dec_deprecessed = self._icrs_to_phosim_rotator.transform(ra_in, dec_in)
280 return np.array([ra_deprecessed, dec_deprecessed])
282 @classmethod
283 def _appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs):
284 """
285 This method will convert from the 'deprecessed' coordinates expected by
286 PhoSim to apparent geocentric coordinates
288 Parameters
289 ----------
290 raPhoSim is the PhoSim RA-like coordinate (in radians)
292 decPhoSim is the PhoSim Dec-like coordinate (in radians)
294 obs is an ObservationMetaData characterizing the
295 telescope pointing
297 Returns
298 -------
299 apparent geocentric RA in radians
301 apparent geocentric Dec in radians
302 """
303 precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec,
304 obs_metadata=obs, epoch=2000.0,
305 includeRefraction=False)
307 if (not hasattr(self, '_phosim_to_icrs_rotator') or
308 arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec,
309 self._phosim_to_icrs_rotator._ra0,
310 self._phosim_to_icrs_rotator._dec0))>1.0e-6 or
311 arcsecFromRadians(_angularSeparation(precessedRA, precessedDec,
312 self._phosim_to_icrs_rotator._ra1,
313 self._phosim_to_icrs_rotator._dec1))>1.0e-6):
315 self._phosim_to_icrs_rotator = _FieldRotator(obs._pointingRA, obs._pointingDec,
316 precessedRA, precessedDec)
318 ra_obs, dec_obs = self._phosim_to_icrs_rotator.transform(raPhoSim, decPhoSim)
320 return _appGeoFromObserved(ra_obs, dec_obs, includeRefraction=False,
321 obs_metadata=obs)
323 @classmethod
324 def appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
325 """
326 This method will convert from the 'deprecessed' coordinates expected by
327 PhoSim to apparent geocentric coordinates
329 Parameters
330 ----------
331 raPhoSim is the PhoSim RA-like coordinate (in degrees)
333 decPhoSim is the PhoSim Dec-like coordinate (in degrees)
335 obs_metadata is an ObservationMetaData characterizing the
336 telescope pointing
338 Returns
339 -------
340 apparent geocentric RA in degrees
342 apparent geocentric Dec in degrees
343 """
344 ra_appGeo, dec_appGeo = self._appGeoFromPhoSim(np.radians(raPhoSim),
345 np.radians(decPhoSim),
346 obs_metadata)
348 return np.degrees(ra_appGeo), np.degrees(dec_appGeo)
350 @classmethod
351 def _icrsFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
352 """
353 This method will convert from the 'deprecessed' coordinates expected by
354 PhoSim to ICRS coordinates
356 Parameters
357 ----------
358 raPhoSim is the PhoSim RA-like coordinate (in radians)
360 decPhoSim is the PhoSim Dec-like coordinate (in radians)
362 obs_metadata is an ObservationMetaData characterizing the
363 telescope pointing
365 Returns
366 -------
367 raICRS in radians
369 decICRS in radians
370 """
372 (ra_appGeo,
373 dec_appGeo) = self._appGeoFromPhoSim(raPhoSim, decPhoSim, obs_metadata)
375 # convert to ICRS coordinates
376 return _icrsFromAppGeo(ra_appGeo, dec_appGeo, mjd=obs_metadata.mjd,
377 epoch=2000.0)
379 @classmethod
380 def icrsFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
381 """
382 This method will convert from the 'deprecessed' coordinates expected by
383 PhoSim to ICRS coordinates
385 Parameters
386 ----------
387 raPhoSim is the PhoSim RA-like coordinate (in degrees)
389 decPhoSim is the PhoSim Dec-like coordinate (in degrees)
391 obs_metadata is an ObservationMetaData characterizing the
392 telescope pointing
394 Returns
395 -------
396 raICRS in degrees
398 decICRS in degrees
399 """
400 ra, dec = PhoSimAstrometryBase._icrsFromPhoSim(np.radians(raPhoSim),
401 np.radians(decPhoSim),
402 obs_metadata)
403 return np.degrees(ra), np.degrees(dec)
406class PhoSimAstrometryStars(AstrometryStars, PhoSimAstrometryBase):
407 """
408 This mixin contains the getter method that calculates raPhoSim,
409 decPhoSim (the coordinates necessary for a PhoSim-readable
410 InstanceCatalog) in the case of stellar sources.
411 """
413 @compound('raPhoSim', 'decPhoSim')
414 def get_phoSimCoordinates(self):
415 """Getter for RA, Dec coordinates expected by PhoSim.
417 These are observed RA, Dec coordinates with the effects of nutation, aberration,
418 and precession subtracted out by the PhosimInputBase._dePrecess() method.
419 This preserves the relative effects of nutation, aberration, and precession while
420 re-aligning the catalog with the boresite RA, Dec so that astrometric solutions
421 make sense."""
423 raObs, decObs = self.observedStellarCoordinates(includeRefraction = False)
424 return self._dePrecess(raObs, decObs, self.obs_metadata)
427class PhoSimAstrometryGalaxies(AstrometryGalaxies, PhoSimAstrometryBase):
428 """
429 This mixin contains the getter method that calculates raPhoSim,
430 decPhoSim (the coordinates necessary for a PhoSim-readable
431 InstanceCatalog) in the case of extra-galactic sources.
432 """
434 @compound('raPhoSim', 'decPhoSim')
435 def get_phoSimCoordinates(self):
436 """Getter for RA, Dec coordinates expected by PhoSim.
438 These are observed RA, Dec coordinates with the effects of nutation, aberration,
439 and precession subtracted out by the PhosimInputBase._dePrecess() method.
440 This preserves the relative effects of nutation, aberration, and precession while
441 re-aligning the catalog with the boresite RA, Dec so that astrometric solutions
442 make sense."""
444 ra = self.column_by_name('raJ2000')
445 dec = self.column_by_name('decJ2000')
446 raObs, decObs = _observedFromICRS(ra, dec, includeRefraction = False, obs_metadata=self.obs_metadata,
447 epoch=self.db_obj.epoch)
449 return self._dePrecess(raObs, decObs, self.obs_metadata)
452class PhoSimAstrometrySSM(AstrometrySSM, PhoSimAstrometryBase):
453 """
454 This mixin contains the getter method that calculates raPhoSim,
455 decPhoSim (the coordinates necessary for a PhoSim-readable
456 InstanceCatalog) in the case of solar system sources.
457 """
459 @compound('raPhoSim', 'decPhoSim')
460 def get_phoSimCoordinates(self):
461 raObs, decObs = self.observedSSMCoordinates(includeRefraction = False)
462 return self._dePrecess(raObs, decObs, self.obs_metadata)