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

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.LsstCameraUtils import chipNameFromPupilCoordsLSST
15from lsst.sims.coordUtils.LsstCameraUtils import pixelCoordsFromPupilCoordsLSST
16from lsst.sims.coordUtils.LsstCameraUtils import focalPlaneCoordsFromPupilCoordsLSST
17from lsst.sims.coordUtils.CameraUtils import focalPlaneCoordsFromPupilCoords
19from lsst.sims.catUtils.mixins.PhoSimSupport import _FieldRotator
20from lsst.sims.utils import _angularSeparation, arcsecFromRadians
22__all__ = ["AstrometryBase", "AstrometryStars", "AstrometryGalaxies", "AstrometrySSM",
23 "PhoSimAstrometryBase", "PhoSimAstrometryStars", "PhoSimAstrometryGalaxies",
24 "PhoSimAstrometrySSM",
25 "CameraCoords", "CameraCoordsLSST"]
28class AstrometryBase(object):
29 """Collection of astrometry routines that operate on numpy arrays"""
31 @compound('glon', 'glat')
32 def get_galactic_coords(self):
33 """
34 Getter for galactic coordinates, in case the catalog class does not provide that
36 Reads in the ra and dec from the data base and returns columns with galactic
37 longitude and latitude.
39 All angles are in radians
40 """
41 ra = self.column_by_name('raJ2000')
42 dec = self.column_by_name('decJ2000')
44 glon, glat = _galacticFromEquatorial(ra, dec)
46 return np.array([glon, glat])
48 @compound('x_pupil', 'y_pupil')
49 def get_pupilFromSky(self):
50 """
51 Take an input RA and dec from the sky and convert it to coordinates
52 in the pupil.
53 """
55 raObs = self.column_by_name('raObserved')
56 decObs = self.column_by_name('decObserved')
58 return _pupilCoordsFromObserved(raObs, decObs, epoch=self.db_obj.epoch,
59 obs_metadata=self.obs_metadata)
62class CameraCoords(AstrometryBase):
63 """Methods for getting coordinates from the camera object"""
64 camera = None
65 allow_multiple_chips = False # this is a flag which, if true, would allow
66 # chipNameFromPupilCoords to return objects that land on
67 # multiple chips; only the first chip would be
68 # written to the catalog
70 @cached
71 def get_chipName(self):
72 """Get the chip name if there is one for each catalog entry"""
73 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
74 if len(xPupil) == 0:
75 return np.array([])
76 if self.camera is None:
77 raise RuntimeError("No camera defined; cannot find chipName")
78 return chipNameFromPupilCoords(xPupil, yPupil, camera=self.camera,
79 allow_multiple_chips=self.allow_multiple_chips)
81 @compound('xPix', 'yPix')
82 def get_pixelCoordinates(self):
83 """Get the pixel positions (or nan if not on a chip) for all objects in the catalog"""
84 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
85 chipNameList = self.column_by_name('chipName')
86 if len(xPupil) == 0:
87 return np.array([[],[]])
88 if self.camera is None:
89 raise RuntimeError("No camera is defined; cannot calculate pixel coordinates")
90 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipNameList,
91 camera=self.camera)
93 @compound('xFocalPlane', 'yFocalPlane')
94 def get_focalPlaneCoordinates(self):
95 """Get the focal plane coordinates for all objects in the catalog."""
96 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
97 if len(xPupil) == 0:
98 return np.array([[],[]])
99 if self.camera is None:
100 raise RuntimeError("No camera defined. Cannot calculate focal plane coordinates")
101 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=self.camera)
104class CameraCoordsLSST(CameraCoords):
106 @cached
107 def get_chipName(self):
108 """Get the chip name if there is one for each catalog entry"""
109 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
110 if len(xPupil) == 0:
111 return np.array([])
113 return chipNameFromPupilCoordsLSST(xPupil, yPupil,
114 allow_multiple_chips=self.allow_multiple_chips)
116 @compound('xPix', 'yPix')
117 def get_pixelCoordinates(self):
118 """Get the pixel positions (or nan if not on a chip) for all objects in the catalog"""
119 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
120 chipNameList = self.column_by_name('chipName')
121 if len(xPupil) == 0:
122 return np.array([[],[]])
123 return pixelCoordsFromPupilCoordsLSST(xPupil, yPupil, chipName=chipNameList,
124 band=self.obs_metadata.bandpass)
126 @compound('xFocalPlane', 'yFocalPlane')
127 def get_focalPlaneCoordinates(self):
128 """Get the focal plane coordinates for all objects in the catalog."""
129 xPupil, yPupil = (self.column_by_name('x_pupil'), self.column_by_name('y_pupil'))
130 if len(xPupil) == 0:
131 return np.array([[],[]])
132 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, band=self.obs_metadata.bandpass)
135class AstrometryGalaxies(AstrometryBase):
136 """
137 This mixin contains astrometry getters for objects with zero parallax, proper motion, or radial
138 velocity (i.e. extragalactic sources).
140 Available getters are:
141 raICRS, decICRS -- the RA, Dec of the object in the International Celestial Reference System
143 raObserved, decObserved -- the result of applying precession, nutation, aberration, and refraction
144 to raICRS, decICRS
145 """
147 @compound('raICRS', 'decICRS')
148 def get_icrsCoordinates(self):
149 """Getter for RA, Dec in the International Celestial Reference System with effects
150 due to proper motion and radial velocity applied"""
151 return np.array([self.column_by_name('raJ2000'), self.column_by_name('decJ2000')])
153 @compound('raObserved', 'decObserved')
154 def get_observedCoordinates(self):
155 """Getter for observed RA, Dec (i.e. RA and Dec with all effects due to the motion
156 of the Earth and refraction by the atmosphere applied)"""
157 ra = self.column_by_name('raJ2000')
158 dec = self.column_by_name('decJ2000')
159 return _observedFromICRS(ra, dec, obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
162class AstrometryStars(AstrometryBase):
163 """
164 This mixin contains getters for objects with non-zero parallax, proper motion, and radial
165 velocities (i.e. sources in the Milky Way).
167 Available getters are:
168 raICRS, decICRS -- the RA, Dec of the object in the International Celestial Reference System
169 with proper motion and radial velocity applied
171 raObserved, decObserved -- the result of applying precession, nutation, aberration, parallax,
172 and refraction to raICRS, decICRS
173 """
175 def observedStellarCoordinates(self, includeRefraction = True):
176 """
177 Getter which converts mean coordinates in the International Celestial
178 Reference Frame to observed coordinates.
179 """
181 # TODO
182 # are we going to store proper motion in raw radians per year
183 # or in sky motion = cos(dec) * (radians per year)
184 # PAL asks for radians per year inputs
186 pr = self.column_by_name('properMotionRa') # in radians per year
187 pd = self.column_by_name('properMotionDec') # in radians per year
188 px = self.column_by_name('parallax') # in radians
189 rv = self.column_by_name('radialVelocity') # in km/s; positive if receding
190 ra = self.column_by_name('raJ2000')
191 dec = self.column_by_name('decJ2000')
193 return _observedFromICRS(ra, dec, pm_ra = pr, pm_dec = pd, parallax = px, v_rad = rv,
194 includeRefraction = includeRefraction, obs_metadata=self.obs_metadata,
195 epoch=self.db_obj.epoch)
197 @compound('raObserved', 'decObserved')
198 def get_observedCoordinates(self):
199 """Getter for observed RA, Dec (i.e. RA and Dec with all effects due to the motion
200 of the Earth and refraction by the atmosphere applied)"""
201 return self.observedStellarCoordinates()
203 @compound('raICRS', 'decICRS')
204 def get_icrsCoordinates(self):
205 """Getter for RA, Dec in the International Celestial Reference System with effects
206 due to proper motion and radial velocity applied"""
207 ra0 = self.column_by_name('raJ2000')
208 dec0 = self.column_by_name('decJ2000')
209 pr = self.column_by_name('properMotionRa') # in radians per year
210 pd = self.column_by_name('properMotionDec') # in radians per year
211 px = self.column_by_name('parallax') # in radians
212 rv = self.column_by_name('radialVelocity') # in km/s; positive if receding
214 ra_corr, dec_corr = _applyProperMotion(ra0, dec0, pr, pd, px, rv, mjd=self.obs_metadata.mjd)
215 return np.array([ra_corr, dec_corr])
218class AstrometrySSM(AstrometryBase):
219 """
220 This mixin will provide getters for astrometric columns customized to Solar System Object tables
221 """
223 @compound('raICRS', 'decICRS')
224 def get_icrsCoordinates(self):
225 return np.array([self.column_by_name('raJ2000'), self.column_by_name('decJ2000')])
227 def observedSSMCoordinates(self, includeRefraction = True):
228 """
229 Reads in ICRS coordinates from the database. Returns observed coordinates
230 with refraction toggled on or off based on the input boolean includeRefraction
231 """
232 ra = self.column_by_name('raJ2000') # in radians
233 dec = self.column_by_name('decJ2000') # in radians
235 return _observedFromICRS(ra, dec, includeRefraction=includeRefraction,
236 obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch)
238 @compound('raObserved', 'decObserved')
239 def get_observedCoordinates(self):
240 return self.observedSSMCoordinates(includeRefraction = True)
242 @cached
243 def get_skyVelocity(self):
244 """
245 Gets the skyVelocity in radians per day
246 """
248 dradt = self.column_by_name('velRa') # in radians per day (actual sky velocity;
249 # i.e., no need to divide by cos(dec))
251 ddecdt = self.column_by_name('velDec') # in radians per day
253 return np.sqrt(np.power(dradt, 2) + np.power(ddecdt, 2))
256class PhoSimAstrometryBase(object):
257 """
258 This mixin contains the _dePrecess method necessary to create PhoSim
259 images that are astrometrically consistent with their input catalogs.
260 """
262 def _dePrecess(self, ra_in, dec_in, obs):
263 """
264 Transform a set of RA, Dec pairs by subtracting out a rotation
265 which represents the effects of precession, nutation, and aberration.
267 Specifically:
269 Calculate the displacement between the boresite and the boresite
270 corrected for precession, nutation, and aberration (not refraction).
272 Convert boresite and corrected boresite to Cartesian coordinates.
274 Calculate the rotation matrix to go between those Cartesian vectors.
276 Convert [ra_in, dec_in] into Cartesian coordinates.
278 Apply the rotation vector to those Cartesian coordinates.
280 Convert back to ra, dec-like coordinates
282 @param [in] ra_in is a numpy array of RA in radians
284 @param [in] dec_in is a numpy array of Dec in radians
286 @param [in] obs is an ObservationMetaData
288 @param [out] ra_out is a numpy array of de-precessed RA in radians
290 @param [out] dec_out is a numpy array of de-precessed Dec in radians
291 """
293 if len(ra_in) == 0:
294 return np.array([[], []])
297 precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec,
298 obs_metadata=obs, epoch=2000.0,
299 includeRefraction=False)
301 if (not hasattr(self, '_icrs_to_phosim_rotator') or
302 arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec,
303 self._icrs_to_phosim_rotator._ra1,
304 self._icrs_to_phosim_rotator._dec1))>1.0e-6 or
305 arcsecFromRadians(_angularSeparation(precessedRA, precessedDec,
306 self._icrs_to_phosim_rotator._ra0,
307 self._icrs_to_phosim_rotator._dec0))>1.0e-6):
309 self._icrs_to_phosim_rotator = _FieldRotator(precessedRA, precessedDec,
310 obs._pointingRA, obs._pointingDec)
312 ra_deprecessed, dec_deprecessed = self._icrs_to_phosim_rotator.transform(ra_in, dec_in)
314 return np.array([ra_deprecessed, dec_deprecessed])
316 @classmethod
317 def _appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs):
318 """
319 This method will convert from the 'deprecessed' coordinates expected by
320 PhoSim to apparent geocentric coordinates
322 Parameters
323 ----------
324 raPhoSim is the PhoSim RA-like coordinate (in radians)
326 decPhoSim is the PhoSim Dec-like coordinate (in radians)
328 obs is an ObservationMetaData characterizing the
329 telescope pointing
331 Returns
332 -------
333 apparent geocentric RA in radians
335 apparent geocentric Dec in radians
336 """
337 precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec,
338 obs_metadata=obs, epoch=2000.0,
339 includeRefraction=False)
341 if (not hasattr(self, '_phosim_to_icrs_rotator') or
342 arcsecFromRadians(_angularSeparation(obs._pointingRA, obs._pointingDec,
343 self._phosim_to_icrs_rotator._ra0,
344 self._phosim_to_icrs_rotator._dec0))>1.0e-6 or
345 arcsecFromRadians(_angularSeparation(precessedRA, precessedDec,
346 self._phosim_to_icrs_rotator._ra1,
347 self._phosim_to_icrs_rotator._dec1))>1.0e-6):
349 self._phosim_to_icrs_rotator = _FieldRotator(obs._pointingRA, obs._pointingDec,
350 precessedRA, precessedDec)
352 ra_obs, dec_obs = self._phosim_to_icrs_rotator.transform(raPhoSim, decPhoSim)
354 return _appGeoFromObserved(ra_obs, dec_obs, includeRefraction=False,
355 obs_metadata=obs)
357 @classmethod
358 def appGeoFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
359 """
360 This method will convert from the 'deprecessed' coordinates expected by
361 PhoSim to apparent geocentric coordinates
363 Parameters
364 ----------
365 raPhoSim is the PhoSim RA-like coordinate (in degrees)
367 decPhoSim is the PhoSim Dec-like coordinate (in degrees)
369 obs_metadata is an ObservationMetaData characterizing the
370 telescope pointing
372 Returns
373 -------
374 apparent geocentric RA in degrees
376 apparent geocentric Dec in degrees
377 """
378 ra_appGeo, dec_appGeo = self._appGeoFromPhoSim(np.radians(raPhoSim),
379 np.radians(decPhoSim),
380 obs_metadata)
382 return np.degrees(ra_appGeo), np.degrees(dec_appGeo)
384 @classmethod
385 def _icrsFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
386 """
387 This method will convert from the 'deprecessed' coordinates expected by
388 PhoSim to ICRS coordinates
390 Parameters
391 ----------
392 raPhoSim is the PhoSim RA-like coordinate (in radians)
394 decPhoSim is the PhoSim Dec-like coordinate (in radians)
396 obs_metadata is an ObservationMetaData characterizing the
397 telescope pointing
399 Returns
400 -------
401 raICRS in radians
403 decICRS in radians
404 """
406 (ra_appGeo,
407 dec_appGeo) = self._appGeoFromPhoSim(raPhoSim, decPhoSim, obs_metadata)
409 # convert to ICRS coordinates
410 return _icrsFromAppGeo(ra_appGeo, dec_appGeo, mjd=obs_metadata.mjd,
411 epoch=2000.0)
413 @classmethod
414 def icrsFromPhoSim(self, raPhoSim, decPhoSim, obs_metadata):
415 """
416 This method will convert from the 'deprecessed' coordinates expected by
417 PhoSim to ICRS coordinates
419 Parameters
420 ----------
421 raPhoSim is the PhoSim RA-like coordinate (in degrees)
423 decPhoSim is the PhoSim Dec-like coordinate (in degrees)
425 obs_metadata is an ObservationMetaData characterizing the
426 telescope pointing
428 Returns
429 -------
430 raICRS in degrees
432 decICRS in degrees
433 """
434 ra, dec = PhoSimAstrometryBase._icrsFromPhoSim(np.radians(raPhoSim),
435 np.radians(decPhoSim),
436 obs_metadata)
437 return np.degrees(ra), np.degrees(dec)
440class PhoSimAstrometryStars(AstrometryStars, PhoSimAstrometryBase):
441 """
442 This mixin contains the getter method that calculates raPhoSim,
443 decPhoSim (the coordinates necessary for a PhoSim-readable
444 InstanceCatalog) in the case of stellar sources.
445 """
447 @compound('raPhoSim', 'decPhoSim')
448 def get_phoSimCoordinates(self):
449 """Getter for RA, Dec coordinates expected by PhoSim.
451 These are observed RA, Dec coordinates with the effects of nutation, aberration,
452 and precession subtracted out by the PhosimInputBase._dePrecess() method.
453 This preserves the relative effects of nutation, aberration, and precession while
454 re-aligning the catalog with the boresite RA, Dec so that astrometric solutions
455 make sense."""
457 raObs, decObs = self.observedStellarCoordinates(includeRefraction = False)
458 return self._dePrecess(raObs, decObs, self.obs_metadata)
461class PhoSimAstrometryGalaxies(AstrometryGalaxies, PhoSimAstrometryBase):
462 """
463 This mixin contains the getter method that calculates raPhoSim,
464 decPhoSim (the coordinates necessary for a PhoSim-readable
465 InstanceCatalog) in the case of extra-galactic sources.
466 """
468 @compound('raPhoSim', 'decPhoSim')
469 def get_phoSimCoordinates(self):
470 """Getter for RA, Dec coordinates expected by PhoSim.
472 These are observed RA, Dec coordinates with the effects of nutation, aberration,
473 and precession subtracted out by the PhosimInputBase._dePrecess() method.
474 This preserves the relative effects of nutation, aberration, and precession while
475 re-aligning the catalog with the boresite RA, Dec so that astrometric solutions
476 make sense."""
478 ra = self.column_by_name('raJ2000')
479 dec = self.column_by_name('decJ2000')
480 raObs, decObs = _observedFromICRS(ra, dec, includeRefraction = False, obs_metadata=self.obs_metadata,
481 epoch=self.db_obj.epoch)
483 return self._dePrecess(raObs, decObs, self.obs_metadata)
486class PhoSimAstrometrySSM(AstrometrySSM, PhoSimAstrometryBase):
487 """
488 This mixin contains the getter method that calculates raPhoSim,
489 decPhoSim (the coordinates necessary for a PhoSim-readable
490 InstanceCatalog) in the case of solar system sources.
491 """
493 @compound('raPhoSim', 'decPhoSim')
494 def get_phoSimCoordinates(self):
495 raObs, decObs = self.observedSSMCoordinates(includeRefraction = False)
496 return self._dePrecess(raObs, decObs, self.obs_metadata)