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 zip 

2from builtins import object 

3import re 

4import copy 

5from collections import namedtuple 

6import warnings 

7import astropy.time 

8import astropy.coordinates 

9from astropy._erfa import ErfaWarning 

10import galsim 

11import numpy as np 

12import lsst.geom as LsstGeom 

13from lsst.obs.lsstSim import LsstSimMapper 

14from lsst.sims.utils import arcsecFromRadians 

15from lsst.sims.GalSimInterface.wcsUtils import tanSipWcsFromDetector 

16from lsst.sims.GalSimInterface import GalSimCameraWrapper 

17from lsst.sims.photUtils import PhotometricParameters 

18 

19__all__ = ["GalSimDetector", "make_galsim_detector", "LsstObservatory"] 

20 

21 

22class GalSim_afw_TanSipWCS(galsim.wcs.CelestialWCS): 

23 """ 

24 This class uses methods from lsst.geom and meas_astrom to 

25 fit a TAN-SIP WCS to an afw.cameraGeom.Detector and then wrap 

26 that WCS into something that GalSim can parse. 

27 

28 For documentation on the TAN-SIP WCS see 

29 

30 Shupe and Hook (2008) 

31 http://fits.gsfc.nasa.gov/registry/sip/SIP_distortion_v1_0.pdf 

32 """ 

33 

34 def __init__(self, detectorName, cameraWrapper, obs_metadata, epoch, photParams=None, wcs=None): 

35 """ 

36 @param [in] detectorName is the name of the detector as stored 

37 by afw 

38 

39 @param [in] cameraWrapper is an instantionat of a GalSimCameraWrapper 

40 

41 @param [in] obs_metadata is an instantiation of ObservationMetaData 

42 characterizing the telescope pointing 

43 

44 @param [in] epoch is the epoch in Julian years of the equinox against 

45 which RA and Dec are measured 

46 

47 @param [in] photParams is an instantiation of PhotometricParameters 

48 (it will contain information about gain, exposure time, etc.) 

49 

50 @param [in] wcs is a kwarg that is used by the method _newOrigin(). 

51 The wcs kwarg in this constructor method should not be used by users. 

52 """ 

53 

54 if not isinstance(cameraWrapper, GalSimCameraWrapper): 

55 raise RuntimeError("You must pass GalSim_afw_TanSipWCS an instantiation " 

56 "of GalSimCameraWrapper or one of its daughter " 

57 "classes") 

58 

59 if wcs is None: 

60 self._tanSipWcs = tanSipWcsFromDetector(detectorName, cameraWrapper, obs_metadata, epoch) 

61 else: 

62 self._tanSipWcs = wcs 

63 

64 self.detectorName = detectorName 

65 self.cameraWrapper = cameraWrapper 

66 self.obs_metadata = obs_metadata 

67 self.photParams = photParams 

68 self.epoch = epoch 

69 

70 # this is needed to match the GalSim v1.5 API 

71 self._color = None 

72 

73 self.fitsHeader = self._tanSipWcs.getFitsMetadata() 

74 self.fitsHeader.set("EXTTYPE", "IMAGE") 

75 

76 if self.obs_metadata.bandpass is not None: 

77 if (not isinstance(self.obs_metadata.bandpass, list) and not 

78 isinstance(self.obs_metadata.bandpass, np.ndarray)): 

79 self.fitsHeader.set("FILTER", self.obs_metadata.bandpass) 

80 

81 if self.obs_metadata.mjd is not None: 

82 self.fitsHeader.set("MJD-OBS", self.obs_metadata.mjd.TAI) 

83 mjd_obs = astropy.time.Time(self.obs_metadata.mjd.TAI, format='mjd') 

84 self.fitsHeader.set('DATE-OBS', mjd_obs.isot) 

85 

86 if self.photParams is not None: 

87 exptime = self.photParams.nexp*self.photParams.exptime 

88 self.fitsHeader.set("EXPTIME", exptime) 

89 with warnings.catch_warnings(): 

90 warnings.filterwarnings('ignore', 'ERFA function', ErfaWarning) 

91 mjd_end = mjd_obs + astropy.time.TimeDelta(exptime, format='sec') 

92 self.fitsHeader.set('DATE-END', mjd_end.isot) 

93 

94 # Add pointing information to FITS header. 

95 if self.obs_metadata.pointingRA is not None: 

96 self.fitsHeader.set('RATEL', obs_metadata.pointingRA) 

97 if self.obs_metadata.pointingDec is not None: 

98 self.fitsHeader.set('DECTEL', obs_metadata.pointingDec) 

99 if self.obs_metadata.rotSkyPos is not None: 

100 self.fitsHeader.set('ROTANGLE', obs_metadata.rotSkyPos) 

101 

102 # Add airmass, needed by jointcal. 

103 if self.obs_metadata.OpsimMetaData is not None: 

104 try: 

105 airmass = self.obs_metadata.OpsimMetaData['airmass'] 

106 except KeyError: 

107 pass 

108 else: 

109 self.fitsHeader.set('AIRMASS', airmass) 

110 

111 # Add boilerplate keywords requested by DM. 

112 self.fitsHeader.set('TELESCOP', 'LSST') 

113 self.fitsHeader.set('INSTRUME', 'CAMERA') 

114 self.fitsHeader.set('SIMULATE', True) 

115 self.fitsHeader.set('ORIGIN', 'IMSIM') 

116 observatory = LsstObservatory() 

117 self.fitsHeader.set('OBS-LONG', observatory.getLongitude().asDegrees()) 

118 self.fitsHeader.set('OBS-LAT', observatory.getLatitude().asDegrees()) 

119 self.fitsHeader.set('OBS-ELEV', observatory.getElevation()) 

120 obs_location = observatory.getLocation() 

121 self.fitsHeader.set('OBSGEO-X', obs_location.geocentric[0].value) 

122 self.fitsHeader.set('OBSGEO-Y', obs_location.geocentric[1].value) 

123 self.fitsHeader.set('OBSGEO-Z', obs_location.geocentric[2].value) 

124 

125 self.crpix1 = self.fitsHeader.getScalar("CRPIX1") 

126 self.crpix2 = self.fitsHeader.getScalar("CRPIX2") 

127 

128 self.afw_crpix1 = self.crpix1 

129 self.afw_crpix2 = self.crpix2 

130 

131 self.crval1 = self.fitsHeader.getScalar("CRVAL1") 

132 self.crval2 = self.fitsHeader.getScalar("CRVAL2") 

133 

134 self.origin = galsim.PositionD(x=self.crpix1, y=self.crpix2) 

135 self._color = None 

136 

137 def _radec(self, x, y, color=None): 

138 """ 

139 This is a method required by the GalSim WCS API 

140 

141 Convert pixel coordinates into ra, dec coordinates. 

142 x and y already have crpix1 and crpix2 subtracted from them. 

143 Return ra, dec in radians. 

144 

145 Note: the color arg is ignored. It is only there to 

146 match the GalSim v1.5 API 

147 """ 

148 

149 chipNameList = [self.detectorName] 

150 

151 if type(x) is np.ndarray: 

152 chipNameList = chipNameList * len(x) 

153 

154 ra, dec = self.cameraWrapper._raDecFromPixelCoords(x + self.afw_crpix1, y + self.afw_crpix2, chipNameList, 

155 obs_metadata=self.obs_metadata, 

156 epoch=self.epoch) 

157 

158 if type(x) is np.ndarray: 

159 return (ra, dec) 

160 else: 

161 return (ra[0], dec[0]) 

162 

163 def _xy(self, ra, dec): 

164 """ 

165 This is a method required by the GalSim WCS API 

166 

167 Convert ra, dec in radians into x, y in pixel space with crpix subtracted. 

168 """ 

169 

170 chipNameList = [self.detectorName] 

171 

172 if type(ra) is np.ndarray: 

173 chipNameList = chipNameList * len(ra) 

174 

175 xx, yy = self.cameraWrapper._pixelCoordsFromRaDec(ra=ra, dec=dec, chipName=chipNameList, 

176 obs_metadata=self.obs_metadata, 

177 epoch=self.epoch) 

178 

179 if type(ra) is np.ndarray: 

180 return (xx-self.crpix1, yy-self.crpix2) 

181 else: 

182 return (xx[0]-self.crpix1, yy-self.crpix2) 

183 

184 def _newOrigin(self, origin): 

185 """ 

186 This is a method required by the GalSim WCS API. It returns 

187 a copy of self, but with the pixel-space origin translated to a new 

188 position. 

189 

190 @param [in] origin is an instantiation of a galsim.PositionD representing 

191 the a point in pixel space to which you want to move the origin of the WCS 

192 

193 @param [out] _newWcs is a WCS identical to self, but with the origin 

194 in pixel space moved to the specified origin 

195 """ 

196 _newWcs = GalSim_afw_TanSipWCS.__new__(GalSim_afw_TanSipWCS) 

197 _newWcs.__dict__.update(self.__dict__) 

198 _newWcs.crpix1 = origin.x 

199 _newWcs.crpix2 = origin.y 

200 _newWcs.fitsHeader = copy.deepcopy(self.fitsHeader) 

201 _newWcs.fitsHeader.set('CRPIX1', origin.x) 

202 _newWcs.fitsHeader.set('CRPIX2', origin.y) 

203 return _newWcs 

204 

205 def _writeHeader(self, header, bounds): 

206 for key in self.fitsHeader.getOrderedNames(): 

207 header[key] = self.fitsHeader.getScalar(key) 

208 

209 return header 

210 

211TreeRingInfo = namedtuple('TreeRingInfo', ['center', 'func']) 

212 

213class GalSimDetector(object): 

214 """ 

215 This class stores information about individual detectors for use by the GalSimInterpreter 

216 """ 

217 

218 def __init__(self, detectorName, cameraWrapper, obs_metadata, epoch, photParams=None): 

219 """ 

220 @param [in] detectorName is the name of the detector as stored 

221 by afw 

222 

223 @param [in] cameraWrapper is an instantionat of a GalSimCameraWrapper 

224 

225 @param [in] photParams is an instantiation of the PhotometricParameters class that carries 

226 details about the photometric response of the telescope. 

227 

228 This class will generate its own internal variable self.fileName which is 

229 the name of the detector as it will appear in the output FITS files 

230 """ 

231 

232 if not isinstance(cameraWrapper, GalSimCameraWrapper): 

233 raise RuntimeError("You must pass GalSimDetector an instantiation " 

234 "of GalSimCameraWrapper or one of its daughter " 

235 "classes") 

236 

237 if detectorName not in cameraWrapper.camera: 

238 raise RuntimeError("detectorName needs to be in the camera wrapped by " 

239 "cameraWrapper when instantiating a GalSimDetector\n" 

240 "%s is not in your cameraWrapper.camera" % detectorName) 

241 

242 if photParams is None: 

243 raise RuntimeError("You need to specify an instantiation of PhotometricParameters " + 

244 "when constructing a GalSimDetector") 

245 

246 self._wcs = None # this will be created when it is actually called for 

247 self._name = detectorName 

248 self._cameraWrapper = cameraWrapper 

249 self._obs_metadata = obs_metadata 

250 self._epoch = epoch 

251 self._detector_type = self._cameraWrapper.camera[self._name].getType() 

252 

253 # Default Tree Ring properties, i.e., no tree rings: 

254 self._tree_rings = TreeRingInfo(galsim.PositionD(0, 0), None) 

255 

256 # We are transposing the coordinates because of the difference 

257 # between how DM defines pixel coordinates and how the 

258 # Camera team defines pixel coordinates 

259 bbox = self._cameraWrapper.getBBox(self._name) 

260 self._xMinPix = bbox.getMinX() 

261 self._xMaxPix = bbox.getMaxX() 

262 self._yMinPix = bbox.getMinY() 

263 self._yMaxPix = bbox.getMaxY() 

264 

265 self._bbox = LsstGeom.Box2D(bbox) 

266 

267 centerPupil = self._cameraWrapper.getCenterPupil(self._name) 

268 self._xCenterArcsec = arcsecFromRadians(centerPupil.getX()) 

269 self._yCenterArcsec = arcsecFromRadians(centerPupil.getY()) 

270 

271 centerPixel = self._cameraWrapper.getCenterPixel(self._name) 

272 self._xCenterPix = centerPixel.getX() 

273 self._yCenterPix = centerPixel.getY() 

274 

275 self._xMinArcsec = None 

276 self._yMinArcsec = None 

277 self._xMaxArcsec = None 

278 self._yMaxArcsec = None 

279 

280 for cameraPointPupil in self._cameraWrapper.getCornerPupilList(self._name): 

281 

282 xx = arcsecFromRadians(cameraPointPupil.getX()) 

283 yy = arcsecFromRadians(cameraPointPupil.getY()) 

284 if self._xMinArcsec is None or xx < self._xMinArcsec: 

285 self._xMinArcsec = xx 

286 if self._xMaxArcsec is None or xx > self._xMaxArcsec: 

287 self._xMaxArcsec = xx 

288 if self._yMinArcsec is None or yy < self._yMinArcsec: 

289 self._yMinArcsec = yy 

290 if self._yMaxArcsec is None or yy > self._yMaxArcsec: 

291 self._yMaxArcsec = yy 

292 

293 self._photParams = photParams 

294 self._fileName = self._getFileName() 

295 

296 def _getFileName(self): 

297 """ 

298 Format the name of the detector to add to the name of the FITS file 

299 """ 

300 detectorName = self.name 

301 detectorName = detectorName.replace(',', '') 

302 detectorName = detectorName.replace(':', '') 

303 detectorName = detectorName.replace(' ', '_') 

304 return detectorName 

305 

306 def pixelCoordinatesFromRaDec(self, ra, dec): 

307 """ 

308 Convert RA, Dec into pixel coordinates on this detector 

309 

310 @param [in] ra is a numpy array or a float indicating RA in radians 

311 

312 @param [in] dec is a numpy array or a float indicating Dec in radians 

313 

314 @param [out] xPix is a numpy array indicating the x pixel coordinate 

315 

316 @param [out] yPix is a numpy array indicating the y pixel coordinate 

317 """ 

318 

319 nameList = [self.name] 

320 if type(ra) is np.ndarray: 

321 nameList = nameList*len(ra) 

322 raLocal = ra 

323 decLocal = dec 

324 else: 

325 raLocal = np.array([ra]) 

326 decLocal = np.array([dec]) 

327 

328 xPix, yPix = self._cameraWrapper._pixelCoordsFromRaDec(raLocal, decLocal, chipName=nameList, 

329 obs_metadata=self._obs_metadata, 

330 epoch=self._epoch) 

331 

332 return xPix, yPix 

333 

334 def pixelCoordinatesFromPupilCoordinates(self, xPupil, yPupil): 

335 """ 

336 Convert pupil coordinates into pixel coordinates on this detector 

337 

338 @param [in] xPupil is a numpy array or a float indicating x pupil coordinates 

339 in radians 

340 

341 @param [in] yPupil a numpy array or a float indicating y pupil coordinates 

342 in radians 

343 

344 @param [out] xPix is a numpy array indicating the x pixel coordinate 

345 

346 @param [out] yPix is a numpy array indicating the y pixel coordinate 

347 """ 

348 

349 nameList = [self._name] 

350 if type(xPupil) is np.ndarray: 

351 nameList = nameList*len(xPupil) 

352 xp = xPupil 

353 yp = yPupil 

354 else: 

355 xp = np.array([xPupil]) 

356 yp = np.array([yPupil]) 

357 

358 xPix, yPix = self._cameraWrapper.pixelCoordsFromPupilCoords(xp, yp, nameList, self.obs_metadata) 

359 

360 return xPix, yPix 

361 

362 def containsRaDec(self, ra, dec): 

363 """ 

364 Does a given RA, Dec fall on this detector? 

365 

366 @param [in] ra is a numpy array or a float indicating RA in radians 

367 

368 @param [in] dec is a numpy array or a float indicating Dec in radians 

369 

370 @param [out] answer is an array of booleans indicating whether or not 

371 the corresponding RA, Dec pair falls on this detector 

372 """ 

373 

374 xPix, yPix = self.pixelCoordinatesFromRaDec(ra, dec) 

375 points = [LsstGeom.Point2D(xx, yy) for xx, yy in zip(xPix, yPix)] 

376 answer = [self._bbox.contains(pp) for pp in points] 

377 return answer 

378 

379 def containsPupilCoordinates(self, xPupil, yPupil): 

380 """ 

381 Does a given set of pupil coordinates fall on this detector? 

382 

383 @param [in] xPupil is a numpy array or a float indicating x pupil coordinates 

384 in radians 

385 

386 @param [in] yPupuil is a numpy array or a float indicating y pupil coordinates 

387 in radians 

388 

389 @param [out] answer is an array of booleans indicating whether or not 

390 the corresponding RA, Dec pair falls on this detector 

391 """ 

392 xPix, yPix = self.pixelCoordinatesFromPupilCoordinates(xPupil, yPupil) 

393 points = [LsstGeom.Point2D(xx, yy) for xx, yy in zip(xPix, yPix)] 

394 answer = [self._bbox.contains(pp) for pp in points] 

395 return answer 

396 

397 @property 

398 def xMinPix(self): 

399 """Minimum x pixel coordinate of the detector""" 

400 return self._xMinPix 

401 

402 @xMinPix.setter 

403 def xMinPix(self, value): 

404 raise RuntimeError("You should not be setting xMinPix on the fly; " 

405 "just instantiate a new GalSimDetector") 

406 

407 @property 

408 def xMaxPix(self): 

409 """Maximum x pixel coordinate of the detector""" 

410 return self._xMaxPix 

411 

412 @xMaxPix.setter 

413 def xMaxPix(self, value): 

414 raise RuntimeError("You should not be setting xMaxPix on the fly; " 

415 "just instantiate a new GalSimDetector") 

416 

417 @property 

418 def yMinPix(self): 

419 """Minimum y pixel coordinate of the detector""" 

420 return self._yMinPix 

421 

422 @yMinPix.setter 

423 def yMinPix(self, value): 

424 raise RuntimeError("You should not be setting yMinPix on the fly; " 

425 "just instantiate a new GalSimDetector") 

426 

427 @property 

428 def yMaxPix(self): 

429 """Maximum y pixel coordinate of the detector""" 

430 return self._yMaxPix 

431 

432 @yMaxPix.setter 

433 def yMaxPix(self, value): 

434 raise RuntimeError("You should not be setting yMaxPix on the fly; " 

435 "just instantiate a new GalSimDetector") 

436 

437 @property 

438 def xCenterPix(self): 

439 """Center x pixel coordinate of the detector""" 

440 return self._xCenterPix 

441 

442 @xCenterPix.setter 

443 def xCenterPix(self, value): 

444 raise RuntimeError("You should not be setting xCenterPix on the fly; " 

445 "just instantiate a new GalSimDetector") 

446 

447 @property 

448 def yCenterPix(self): 

449 """Center y pixel coordinate of the detector""" 

450 return self._yCenterPix 

451 

452 @yCenterPix.setter 

453 def yCenterPix(self, value): 

454 raise RuntimeError("You should not be setting yCenterPix on the fly; " 

455 "just instantiate a new GalSimDetector") 

456 

457 @property 

458 def xMaxArcsec(self): 

459 """Maximum x pupil coordinate of the detector in arcseconds""" 

460 return self._xMaxArcsec 

461 

462 @xMaxArcsec.setter 

463 def xMaxArcsec(self, value): 

464 raise RuntimeError("You should not be setting xMaxArcsec on the fly; " 

465 "just instantiate a new GalSimDetector") 

466 

467 @property 

468 def xMinArcsec(self): 

469 """Minimum x pupil coordinate of the detector in arcseconds""" 

470 return self._xMinArcsec 

471 

472 @xMinArcsec.setter 

473 def xMinArcsec(self, value): 

474 raise RuntimeError("You should not be setting xMinArcsec on the fly; " 

475 "just instantiate a new GalSimDetector") 

476 

477 @property 

478 def yMaxArcsec(self): 

479 """Maximum y pupil coordinate of the detector in arcseconds""" 

480 return self._yMaxArcsec 

481 

482 @yMaxArcsec.setter 

483 def yMaxArcsec(self, value): 

484 raise RuntimeError("You should not be setting yMaxArcsec on the fly; " 

485 "just instantiate a new GalSimDetector") 

486 

487 @property 

488 def yMinArcsec(self): 

489 """Minimum y pupil coordinate of the detector in arcseconds""" 

490 return self._yMinArcsec 

491 

492 @yMinArcsec.setter 

493 def yMinArcsec(self, value): 

494 raise RuntimeError("You should not be setting yMinArcsec on the fly; " 

495 "just instantiate a new GalSimDetector") 

496 

497 @property 

498 def xCenterArcsec(self): 

499 """Center x pupil coordinate of the detector in arcseconds""" 

500 return self._xCenterArcsec 

501 

502 @xCenterArcsec.setter 

503 def xCenterArcsec(self, value): 

504 raise RuntimeError("You should not be setting xCenterArcsec on the fly; " 

505 "just instantiate a new GalSimDetector") 

506 

507 @property 

508 def yCenterArcsec(self): 

509 """Center y pupil coordinate of the detector in arcseconds""" 

510 return self._yCenterArcsec 

511 

512 @yCenterArcsec.setter 

513 def yCenterArcsec(self, value): 

514 raise RuntimeError("You should not be setting yCenterArcsec on the fly; " 

515 "just instantiate a new GalSimDetector") 

516 

517 @property 

518 def epoch(self): 

519 """Epoch of the equinox against which RA and Dec are measured in Julian years""" 

520 return self._epoch 

521 

522 @epoch.setter 

523 def epoch(self, value): 

524 raise RuntimeError("You should not be setting epoch on the fly; " 

525 "just instantiate a new GalSimDetector") 

526 

527 @property 

528 def obs_metadata(self): 

529 """ObservationMetaData instantiation describing the telescope pointing""" 

530 return self._obs_metadata 

531 

532 @obs_metadata.setter 

533 def obs_metadata(self, value): 

534 raise RuntimeError("You should not be setting obs_metadata on the fly; " 

535 "just instantiate a new GalSimDetector") 

536 

537 @property 

538 def name(self): 

539 """Name of the detector""" 

540 return self._name 

541 

542 @name.setter 

543 def name(self, value): 

544 raise RuntimeError("You should not be setting name on the fly; " 

545 "just instantiate a new GalSimDetector") 

546 

547 @property 

548 def camera_wrapper(self): 

549 return self._cameraWrapper 

550 

551 @camera_wrapper.setter 

552 def camera_wrapper(self, value): 

553 raise RuntimeError("You should not be setting the camera_wrapper on the fly; " 

554 "just instantiate a new GalSimDetector") 

555 

556 @property 

557 def photParams(self): 

558 """PhotometricParameters instantiation characterizing the detector""" 

559 return self._photParams 

560 

561 @photParams.setter 

562 def photParams(self, value): 

563 raise RuntimeError("You should not be setting photParams on the fly; " 

564 "just instantiate a new GalSimDetector") 

565 

566 @property 

567 def fileName(self): 

568 """Name of the FITS file corresponding to this detector""" 

569 return self._fileName 

570 

571 @fileName.setter 

572 def fileName(self, value): 

573 raise RuntimeError("You should not be setting fileName on the fly; " 

574 "just instantiate a new GalSimDetector") 

575 

576 @property 

577 def wcs(self): 

578 """WCS corresponding to this detector""" 

579 if self._wcs is None: 

580 self._wcs = GalSim_afw_TanSipWCS(self._name, self._cameraWrapper, 

581 self.obs_metadata, self.epoch, 

582 photParams=self.photParams) 

583 

584 if re.match('R[0-9][0-9]_S[0-9][0-9]', self.fileName) is not None: 

585 # This is an LSST camera; format the FITS header to feed through DM code 

586 

587 wcsName = self.fileName 

588 

589 self._wcs.fitsHeader.set("CHIPID", wcsName) 

590 

591 obshistid = 9999 

592 

593 if self.obs_metadata.OpsimMetaData is not None: 

594 if 'obshistID' in self.obs_metadata.OpsimMetaData: 

595 self._wcs.fitsHeader.set("OBSID", 

596 self.obs_metadata.OpsimMetaData['obshistID']) 

597 obshistid = self.obs_metadata.OpsimMetaData['obshistID'] 

598 

599 bp = self.obs_metadata.bandpass 

600 if not isinstance(bp, list) and not isinstance(bp, np.ndarray): 

601 filt_num = {'u': 0, 'g': 1, 'r': 2, 'i': 3, 'z': 4, 'y': 5}[bp] 

602 else: 

603 filt_num = 2 

604 

605 out_name = 'lsst_e_%d_f%d_%s_E000' % (obshistid, filt_num, wcsName) 

606 self._wcs.fitsHeader.set("OUTFILE", out_name) 

607 

608 return self._wcs 

609 

610 @wcs.setter 

611 def wcs(self, value): 

612 raise RuntimeError("You should not be setting wcs on the fly; " 

613 "just instantiate a new GalSimDetector") 

614 

615 @property 

616 def tree_rings(self): 

617 return self._tree_rings 

618 

619 @tree_rings.setter 

620 def tree_rings(self, center_func_tuple): 

621 self._tree_rings = TreeRingInfo(*center_func_tuple) 

622 

623 

624def make_galsim_detector(camera_wrapper, detname, phot_params, 

625 obs_metadata, epoch=2000.0): 

626 """ 

627 Create a GalSimDetector object given the desired detector name. 

628 

629 Parameters 

630 ---------- 

631 camera_wrapper: lsst.sims.GalSimInterface.GalSimCameraWrapper 

632 An object representing the camera being simulated 

633 

634 detname: str 

635 The name of the detector in the LSST focal plane to create, 

636 e.g., "R:2,2 S:1,1". 

637 

638 phot_params: lsst.sims.photUtils.PhotometricParameters 

639 An object containing the physical parameters representing 

640 the photometric properties of the system 

641 

642 obs_metadata: lsst.sims.utils.ObservationMetaData 

643 Characterizing the pointing of the telescope 

644 

645 epoch: float 

646 Representing the Julian epoch against which RA, Dec are 

647 reckoned (default = 2000) 

648 

649 Returns 

650 ------- 

651 GalSimDetector 

652 """ 

653 centerPupil = camera_wrapper.getCenterPupil(detname) 

654 centerPixel = camera_wrapper.getCenterPixel(detname) 

655 

656 translationPupil = camera_wrapper.pupilCoordsFromPixelCoords(centerPixel.getX()+1, 

657 centerPixel.getY()+1, 

658 detname, 

659 obs_metadata) 

660 

661 plateScale = np.sqrt(np.power(translationPupil[0]-centerPupil.getX(), 2) + 

662 np.power(translationPupil[1]-centerPupil.getY(), 2))/np.sqrt(2.0) 

663 

664 plateScale = 3600.0*np.degrees(plateScale) 

665 

666 # make a detector-custom photParams that copies all of the quantities 

667 # in the catalog photParams, except the platescale, which is 

668 # calculated above 

669 params = PhotometricParameters(exptime=phot_params.exptime, 

670 nexp=phot_params.nexp, 

671 effarea=phot_params.effarea, 

672 gain=phot_params.gain, 

673 readnoise=phot_params.readnoise, 

674 darkcurrent=phot_params.darkcurrent, 

675 othernoise=phot_params.othernoise, 

676 platescale=plateScale) 

677 

678 return GalSimDetector(detname, camera_wrapper, 

679 obs_metadata=obs_metadata, epoch=epoch, 

680 photParams=params) 

681 

682 

683class LsstObservatory: 

684 """ 

685 Class to encapsulate an Observatory object and compute the 

686 observatory location information. 

687 """ 

688 def __init__(self): 

689 self.observatory = LsstSimMapper().MakeRawVisitInfoClass().observatory 

690 

691 def getLocation(self): 

692 """ 

693 The LSST observatory location in geocentric coordinates. 

694 

695 Returns 

696 ------- 

697 astropy.coordinates.earth.EarthLocation 

698 """ 

699 return astropy.coordinates.EarthLocation.from_geodetic( 

700 self.observatory.getLongitude().asDegrees(), 

701 self.observatory.getLatitude().asDegrees(), 

702 self.observatory.getElevation()) 

703 

704 def __getattr__(self, attr): 

705 if hasattr(self.observatory, attr): 

706 return getattr(self.observatory, attr)