Hide keyboard shortcuts

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 

6 

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 

15 

16from lsst.sims.catUtils.mixins.PhoSimSupport import _FieldRotator 

17from lsst.sims.utils import _angularSeparation, arcsecFromRadians 

18 

19__all__ = ["AstrometryBase", "AstrometryStars", "AstrometryGalaxies", "AstrometrySSM", 

20 "PhoSimAstrometryBase", "PhoSimAstrometryStars", "PhoSimAstrometryGalaxies", 

21 "PhoSimAstrometrySSM", 

22 "CameraCoords"] 

23 

24 

25class AstrometryBase(object): 

26 """Collection of astrometry routines that operate on numpy arrays""" 

27 

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 

32 

33 Reads in the ra and dec from the data base and returns columns with galactic 

34 longitude and latitude. 

35 

36 All angles are in radians 

37 """ 

38 ra = self.column_by_name('raJ2000') 

39 dec = self.column_by_name('decJ2000') 

40 

41 glon, glat = _galacticFromEquatorial(ra, dec) 

42 

43 return np.array([glon, glat]) 

44 

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 """ 

51 

52 raObs = self.column_by_name('raObserved') 

53 decObs = self.column_by_name('decObserved') 

54 

55 return _pupilCoordsFromObserved(raObs, decObs, epoch=self.db_obj.epoch, 

56 obs_metadata=self.obs_metadata) 

57 

58 

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 

66 

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) 

77 

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) 

89 

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) 

99 

100 

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). 

105 

106 Available getters are: 

107 raICRS, decICRS -- the RA, Dec of the object in the International Celestial Reference System 

108 

109 raObserved, decObserved -- the result of applying precession, nutation, aberration, and refraction 

110 to raICRS, decICRS 

111 """ 

112 

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')]) 

118 

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) 

126 

127 

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). 

132 

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 

136 

137 raObserved, decObserved -- the result of applying precession, nutation, aberration, parallax, 

138 and refraction to raICRS, decICRS 

139 """ 

140 

141 def observedStellarCoordinates(self, includeRefraction = True): 

142 """ 

143 Getter which converts mean coordinates in the International Celestial 

144 Reference Frame to observed coordinates. 

145 """ 

146 

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 

151 

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') 

158 

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) 

162 

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() 

168 

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 

179 

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]) 

182 

183 

184class AstrometrySSM(AstrometryBase): 

185 """ 

186 This mixin will provide getters for astrometric columns customized to Solar System Object tables 

187 """ 

188 

189 @compound('raICRS', 'decICRS') 

190 def get_icrsCoordinates(self): 

191 return np.array([self.column_by_name('raJ2000'), self.column_by_name('decJ2000')]) 

192 

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 

200 

201 return _observedFromICRS(ra, dec, includeRefraction=includeRefraction, 

202 obs_metadata=self.obs_metadata, epoch=self.db_obj.epoch) 

203 

204 @compound('raObserved', 'decObserved') 

205 def get_observedCoordinates(self): 

206 return self.observedSSMCoordinates(includeRefraction = True) 

207 

208 @cached 

209 def get_skyVelocity(self): 

210 """ 

211 Gets the skyVelocity in radians per day 

212 """ 

213 

214 dradt = self.column_by_name('velRa') # in radians per day (actual sky velocity; 

215 # i.e., no need to divide by cos(dec)) 

216 

217 ddecdt = self.column_by_name('velDec') # in radians per day 

218 

219 return np.sqrt(np.power(dradt, 2) + np.power(ddecdt, 2)) 

220 

221 

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 """ 

227 

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. 

232 

233 Specifically: 

234 

235 Calculate the displacement between the boresite and the boresite 

236 corrected for precession, nutation, and aberration (not refraction). 

237 

238 Convert boresite and corrected boresite to Cartesian coordinates. 

239 

240 Calculate the rotation matrix to go between those Cartesian vectors. 

241 

242 Convert [ra_in, dec_in] into Cartesian coordinates. 

243 

244 Apply the rotation vector to those Cartesian coordinates. 

245 

246 Convert back to ra, dec-like coordinates 

247 

248 @param [in] ra_in is a numpy array of RA in radians 

249 

250 @param [in] dec_in is a numpy array of Dec in radians 

251 

252 @param [in] obs is an ObservationMetaData 

253 

254 @param [out] ra_out is a numpy array of de-precessed RA in radians 

255 

256 @param [out] dec_out is a numpy array of de-precessed Dec in radians 

257 """ 

258 

259 if len(ra_in) == 0: 

260 return np.array([[], []]) 

261 

262 

263 precessedRA, precessedDec = _observedFromICRS(obs._pointingRA,obs._pointingDec, 

264 obs_metadata=obs, epoch=2000.0, 

265 includeRefraction=False) 

266 

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): 

274 

275 self._icrs_to_phosim_rotator = _FieldRotator(precessedRA, precessedDec, 

276 obs._pointingRA, obs._pointingDec) 

277 

278 ra_deprecessed, dec_deprecessed = self._icrs_to_phosim_rotator.transform(ra_in, dec_in) 

279 

280 return np.array([ra_deprecessed, dec_deprecessed]) 

281 

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 

287 

288 Parameters 

289 ---------- 

290 raPhoSim is the PhoSim RA-like coordinate (in radians) 

291 

292 decPhoSim is the PhoSim Dec-like coordinate (in radians) 

293 

294 obs is an ObservationMetaData characterizing the 

295 telescope pointing 

296 

297 Returns 

298 ------- 

299 apparent geocentric RA in radians 

300 

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) 

306 

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): 

314 

315 self._phosim_to_icrs_rotator = _FieldRotator(obs._pointingRA, obs._pointingDec, 

316 precessedRA, precessedDec) 

317 

318 ra_obs, dec_obs = self._phosim_to_icrs_rotator.transform(raPhoSim, decPhoSim) 

319 

320 return _appGeoFromObserved(ra_obs, dec_obs, includeRefraction=False, 

321 obs_metadata=obs) 

322 

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 

328 

329 Parameters 

330 ---------- 

331 raPhoSim is the PhoSim RA-like coordinate (in degrees) 

332 

333 decPhoSim is the PhoSim Dec-like coordinate (in degrees) 

334 

335 obs_metadata is an ObservationMetaData characterizing the 

336 telescope pointing 

337 

338 Returns 

339 ------- 

340 apparent geocentric RA in degrees 

341 

342 apparent geocentric Dec in degrees 

343 """ 

344 ra_appGeo, dec_appGeo = self._appGeoFromPhoSim(np.radians(raPhoSim), 

345 np.radians(decPhoSim), 

346 obs_metadata) 

347 

348 return np.degrees(ra_appGeo), np.degrees(dec_appGeo) 

349 

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 

355 

356 Parameters 

357 ---------- 

358 raPhoSim is the PhoSim RA-like coordinate (in radians) 

359 

360 decPhoSim is the PhoSim Dec-like coordinate (in radians) 

361 

362 obs_metadata is an ObservationMetaData characterizing the 

363 telescope pointing 

364 

365 Returns 

366 ------- 

367 raICRS in radians 

368 

369 decICRS in radians 

370 """ 

371 

372 (ra_appGeo, 

373 dec_appGeo) = self._appGeoFromPhoSim(raPhoSim, decPhoSim, obs_metadata) 

374 

375 # convert to ICRS coordinates 

376 return _icrsFromAppGeo(ra_appGeo, dec_appGeo, mjd=obs_metadata.mjd, 

377 epoch=2000.0) 

378 

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 

384 

385 Parameters 

386 ---------- 

387 raPhoSim is the PhoSim RA-like coordinate (in degrees) 

388 

389 decPhoSim is the PhoSim Dec-like coordinate (in degrees) 

390 

391 obs_metadata is an ObservationMetaData characterizing the 

392 telescope pointing 

393 

394 Returns 

395 ------- 

396 raICRS in degrees 

397 

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) 

404 

405 

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 """ 

412 

413 @compound('raPhoSim', 'decPhoSim') 

414 def get_phoSimCoordinates(self): 

415 """Getter for RA, Dec coordinates expected by PhoSim. 

416 

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.""" 

422 

423 raObs, decObs = self.observedStellarCoordinates(includeRefraction = False) 

424 return self._dePrecess(raObs, decObs, self.obs_metadata) 

425 

426 

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 """ 

433 

434 @compound('raPhoSim', 'decPhoSim') 

435 def get_phoSimCoordinates(self): 

436 """Getter for RA, Dec coordinates expected by PhoSim. 

437 

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.""" 

443 

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) 

448 

449 return self._dePrecess(raObs, decObs, self.obs_metadata) 

450 

451 

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 """ 

458 

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)