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.LsstCameraUtils import chipNameFromPupilCoordsLSST 

15from lsst.sims.coordUtils.LsstCameraUtils import pixelCoordsFromPupilCoordsLSST 

16from lsst.sims.coordUtils.LsstCameraUtils import focalPlaneCoordsFromPupilCoordsLSST 

17from lsst.sims.coordUtils.CameraUtils import focalPlaneCoordsFromPupilCoords 

18 

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

20from lsst.sims.utils import _angularSeparation, arcsecFromRadians 

21 

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

23 "PhoSimAstrometryBase", "PhoSimAstrometryStars", "PhoSimAstrometryGalaxies", 

24 "PhoSimAstrometrySSM", 

25 "CameraCoords", "CameraCoordsLSST"] 

26 

27 

28class AstrometryBase(object): 

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

30 

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 

35 

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

37 longitude and latitude. 

38 

39 All angles are in radians 

40 """ 

41 ra = self.column_by_name('raJ2000') 

42 dec = self.column_by_name('decJ2000') 

43 

44 glon, glat = _galacticFromEquatorial(ra, dec) 

45 

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

47 

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

54 

55 raObs = self.column_by_name('raObserved') 

56 decObs = self.column_by_name('decObserved') 

57 

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

59 obs_metadata=self.obs_metadata) 

60 

61 

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 

69 

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) 

80 

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) 

92 

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) 

102 

103 

104class CameraCoordsLSST(CameraCoords): 

105 

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

112 

113 return chipNameFromPupilCoordsLSST(xPupil, yPupil, 

114 allow_multiple_chips=self.allow_multiple_chips) 

115 

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) 

125 

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) 

133 

134 

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

139 

140 Available getters are: 

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

142 

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

144 to raICRS, decICRS 

145 """ 

146 

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

152 

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) 

160 

161 

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

166 

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 

170 

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

172 and refraction to raICRS, decICRS 

173 """ 

174 

175 def observedStellarCoordinates(self, includeRefraction = True): 

176 """ 

177 Getter which converts mean coordinates in the International Celestial 

178 Reference Frame to observed coordinates. 

179 """ 

180 

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 

185 

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

192 

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) 

196 

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

202 

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 

213 

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

216 

217 

218class AstrometrySSM(AstrometryBase): 

219 """ 

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

221 """ 

222 

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

224 def get_icrsCoordinates(self): 

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

226 

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 

234 

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

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

237 

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

239 def get_observedCoordinates(self): 

240 return self.observedSSMCoordinates(includeRefraction = True) 

241 

242 @cached 

243 def get_skyVelocity(self): 

244 """ 

245 Gets the skyVelocity in radians per day 

246 """ 

247 

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

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

250 

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

252 

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

254 

255 

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

261 

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. 

266 

267 Specifically: 

268 

269 Calculate the displacement between the boresite and the boresite 

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

271 

272 Convert boresite and corrected boresite to Cartesian coordinates. 

273 

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

275 

276 Convert [ra_in, dec_in] into Cartesian coordinates. 

277 

278 Apply the rotation vector to those Cartesian coordinates. 

279 

280 Convert back to ra, dec-like coordinates 

281 

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

283 

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

285 

286 @param [in] obs is an ObservationMetaData 

287 

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

289 

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

291 """ 

292 

293 if len(ra_in) == 0: 

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

295 

296 

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

298 obs_metadata=obs, epoch=2000.0, 

299 includeRefraction=False) 

300 

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

308 

309 self._icrs_to_phosim_rotator = _FieldRotator(precessedRA, precessedDec, 

310 obs._pointingRA, obs._pointingDec) 

311 

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

313 

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

315 

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 

321 

322 Parameters 

323 ---------- 

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

325 

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

327 

328 obs is an ObservationMetaData characterizing the 

329 telescope pointing 

330 

331 Returns 

332 ------- 

333 apparent geocentric RA in radians 

334 

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) 

340 

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

348 

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

350 precessedRA, precessedDec) 

351 

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

353 

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

355 obs_metadata=obs) 

356 

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 

362 

363 Parameters 

364 ---------- 

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

366 

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

368 

369 obs_metadata is an ObservationMetaData characterizing the 

370 telescope pointing 

371 

372 Returns 

373 ------- 

374 apparent geocentric RA in degrees 

375 

376 apparent geocentric Dec in degrees 

377 """ 

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

379 np.radians(decPhoSim), 

380 obs_metadata) 

381 

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

383 

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 

389 

390 Parameters 

391 ---------- 

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

393 

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

395 

396 obs_metadata is an ObservationMetaData characterizing the 

397 telescope pointing 

398 

399 Returns 

400 ------- 

401 raICRS in radians 

402 

403 decICRS in radians 

404 """ 

405 

406 (ra_appGeo, 

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

408 

409 # convert to ICRS coordinates 

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

411 epoch=2000.0) 

412 

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 

418 

419 Parameters 

420 ---------- 

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

422 

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

424 

425 obs_metadata is an ObservationMetaData characterizing the 

426 telescope pointing 

427 

428 Returns 

429 ------- 

430 raICRS in degrees 

431 

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) 

438 

439 

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

446 

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

448 def get_phoSimCoordinates(self): 

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

450 

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

456 

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

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

459 

460 

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

467 

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

469 def get_phoSimCoordinates(self): 

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

471 

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

477 

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) 

482 

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

484 

485 

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

492 

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)