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 

2import numpy as np 

3import warnings 

4import lsst.geom as geom 

5from lsst.afw.cameraGeom import FIELD_ANGLE, PIXELS, TAN_PIXELS, FOCAL_PLANE 

6from lsst.sims.utils.CodeUtilities import _validate_inputs 

7from lsst.sims.utils import _pupilCoordsFromRaDec, _raDecFromPupilCoords 

8from lsst.sims.utils import radiansFromArcsec 

9 

10__all__ = ["MultipleChipWarning", "getCornerPixels", "_getCornerRaDec", "getCornerRaDec", 

11 "chipNameFromPupilCoords", "chipNameFromRaDec", "_chipNameFromRaDec", 

12 "pixelCoordsFromPupilCoords", "pixelCoordsFromRaDec", "_pixelCoordsFromRaDec", 

13 "focalPlaneCoordsFromPupilCoords", "focalPlaneCoordsFromRaDec", "_focalPlaneCoordsFromRaDec", 

14 "pupilCoordsFromPixelCoords", "pupilCoordsFromFocalPlaneCoords", 

15 "raDecFromPixelCoords", "_raDecFromPixelCoords", 

16 "_validate_inputs_and_chipname"] 

17 

18 

19class MultipleChipWarning(Warning): 

20 """ 

21 A sub-class of Warning emitted when we try to detect the chip that an object falls on and 

22 multiple chips are returned. 

23 """ 

24 pass 

25 

26 

27def _validate_inputs_and_chipname(input_list, input_names, method_name, 

28 chip_name, chipname_can_be_none = True): 

29 """ 

30 This will wrap _validate_inputs, but also reformat chip_name if necessary. 

31 

32 input_list is a list of the inputs passed to a method. 

33 

34 input_name is a list of the variable names associated with 

35 input_list 

36 

37 method_name is the name of the method whose input is being validated. 

38 

39 chip_name is the chip_name variable passed into the calling method. 

40 

41 chipname_can_be_none is a boolean that controls whether or not 

42 chip_name is allowed to be None. 

43 

44 This method will raise a RuntimeError if: 

45 

46 1) the contents of input_list are not all of the same type 

47 2) the contents of input_list are not all floats or numpy arrays 

48 3) the contents of input_list are different lengths (if numpy arrays) 

49 4) chip_name is None and chipname_can_be_none is False 

50 5) chip_name is a list or array of different length than input_list[0] 

51 (if input_list[0] is a list or array) and len(chip_name)>1 

52 

53 This method returns a boolean indicating whether input_list[0] 

54 is a numpy array and a re-casting of chip_name as a list 

55 of length equal to input_list[0] (unless chip_name is None; 

56 then it will leave chip_name untouched) 

57 """ 

58 

59 are_arrays = _validate_inputs(input_list, input_names, method_name) 

60 

61 if chip_name is None and not chipname_can_be_none: 

62 raise RuntimeError("You passed chipName=None to %s" % method_name) 

63 

64 if are_arrays: 

65 n_pts = len(input_list[0]) 

66 else: 

67 n_pts = 1 

68 

69 if isinstance(chip_name, list) or isinstance(chip_name, np.ndarray): 

70 if len(chip_name) > 1 and len(chip_name) != n_pts: 

71 raise RuntimeError("You passed %d chipNames to %s.\n" % (len(chip_name), method_name) + 

72 "You passed %d %s values." % (len(input_list[0]), input_names[0])) 

73 

74 if len(chip_name) == 1 and n_pts > 1: 

75 chip_name_out = [chip_name[0]]*n_pts 

76 else: 

77 chip_name_out = chip_name 

78 

79 return are_arrays, chip_name_out 

80 

81 elif chip_name is None: 

82 return are_arrays, chip_name 

83 else: 

84 return are_arrays, [chip_name]*n_pts 

85 

86 

87def getCornerPixels(detector_name, camera): 

88 """ 

89 Return the pixel coordinates of the corners of a detector. 

90 

91 @param [in] detector_name is the name of the detector in question 

92 

93 @param [in] camera is the afwCameraGeom camera object containing 

94 that detector 

95 

96 @param [out] a list of tuples representing the (x,y) pixel coordinates 

97 of the corners of the detector. Order will be 

98 

99 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)] 

100 """ 

101 

102 det = camera[detector_name] 

103 bbox = det.getBBox() 

104 xmin = bbox.getMinX() 

105 xmax = bbox.getMaxX() 

106 ymin = bbox.getMinY() 

107 ymax = bbox.getMaxY() 

108 return [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)] 

109 

110 

111def getCornerRaDec(detector_name, camera, obs_metadata, epoch=2000.0, 

112 includeDistortion=True): 

113 """ 

114 Return the ICRS RA, Dec values of the corners of the specified 

115 detector in degrees. 

116 

117 @param [in] detector_name is the name of the detector in question 

118 

119 @param [in] camera is the afwCameraGeom camera object containing 

120 that detector 

121 

122 @param [in] obs_metadata is an ObservationMetaData characterizing 

123 the pointing (and orientation) of the telescope. 

124 

125 @param [in] epoch is the mean Julian epoch of the coordinate system 

126 (default is 2000) 

127 

128 @param [in] includeDistortion is a boolean. If True (default), then this method will 

129 convert from pixel coordinates to RA, Dec with optical distortion included. If False, this 

130 method will use TAN_PIXEL coordinates, which are the pixel coordinates with 

131 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

132 details. 

133 

134 @param [out] a list of tuples representing the (RA, Dec) coordinates 

135 of the corners of the detector in degrees. The corners will be 

136 returned in the order 

137 

138 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)] 

139 

140 where (x, y) are pixel coordinates. This will not necessarily 

141 correspond to any order in RAmin, RAmax, DecMin, DecMax, because 

142 of the ambiguity imposed by the rotator angle. 

143 """ 

144 

145 cc = _getCornerRaDec(detector_name, camera, obs_metadata, 

146 epoch=epoch, includeDistortion=includeDistortion) 

147 return [tuple(np.degrees(row)) for row in cc] 

148 

149 

150def _getCornerRaDec(detector_name, camera, obs_metadata, 

151 epoch=2000.0, includeDistortion=True): 

152 """ 

153 Return the ICRS RA, Dec values of the corners of the specified 

154 detector in radians. 

155 

156 @param [in] detector_name is the name of the detector in question 

157 

158 @param [in] camera is the afwCameraGeom camera object containing 

159 that detector 

160 

161 @param [in] obs_metadata is an ObservationMetaData characterizing 

162 the pointing (and orientation) of the telescope. 

163 

164 @param [in] epoch is the mean Julian epoch of the coordinate system 

165 (default is 2000) 

166 

167 @param [in] includeDistortion is a boolean. If True (default), then this method will 

168 convert from pixel coordinates to RA, Dec with optical distortion included. If False, this 

169 method will use TAN_PIXEL coordinates, which are the pixel coordinates with 

170 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

171 details. 

172 

173 @param [out] a list of tuples representing the (RA, Dec) coordinates 

174 of the corners of the detector in radians. The corners will be 

175 returned in the order 

176 

177 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)] 

178 

179 where (x, y) are pixel coordinates. This will not necessarily 

180 correspond to any order in RAmin, RAmax, DecMin, DecMax, because 

181 of the ambiguity imposed by the rotator angle. 

182 """ 

183 

184 cc_pix = getCornerPixels(detector_name, camera) 

185 

186 ra, dec = _raDecFromPixelCoords(np.array([cc[0] for cc in cc_pix]), 

187 np.array([cc[1] for cc in cc_pix]), 

188 [detector_name]*len(cc_pix), 

189 camera=camera, obs_metadata=obs_metadata, 

190 epoch=epoch, 

191 includeDistortion=includeDistortion) 

192 

193 return [(ra[0], dec[0]), (ra[1], dec[1]), (ra[2], dec[2]), (ra[3], dec[3])] 

194 

195 

196def chipNameFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

197 obs_metadata=None, camera=None, 

198 epoch=2000.0, allow_multiple_chips=False): 

199 """ 

200 Return the names of detectors that see the object specified by 

201 (RA, Dec) in degrees. 

202 

203 @param [in] ra in degrees (a numpy array or a float). 

204 In the International Celestial Reference System. 

205 

206 @param [in] dec in degrees (a numpy array or a float). 

207 In the International Celestial Reference System. 

208 

209 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr) 

210 Can be a numpy array or a number or None (default=None). 

211 

212 @param [in] pm_dec is proper motion in dec (arcsec/yr) 

213 Can be a numpy array or a number or None (default=None). 

214 

215 @param [in] parallax is parallax in arcsec 

216 Can be a numpy array or a number or None (default=None). 

217 

218 @param [in] v_rad is radial velocity (km/s) 

219 Can be a numpy array or a number or None (default=None). 

220 

221 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing 

222 

223 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are 

224 measured. Default is 2000. 

225 

226 @param [in] camera is an afw.cameraGeom camera instance characterizing the camera 

227 

228 @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not 

229 this method will allow objects to be visible on more than one chip. If it is 'False' 

230 and an object appears on more than one chip, an exception will be raised. If it is 'True' 

231 and an object falls on more than one chip, it will still only return the first chip in the 

232 list of chips returned. THIS BEHAVIOR SHOULD BE FIXED IN A FUTURE TICKET. 

233 

234 @param [out] a numpy array of chip names 

235 """ 

236 if pm_ra is not None: 

237 pm_ra_out = radiansFromArcsec(pm_ra) 

238 else: 

239 pm_ra_out = None 

240 

241 if pm_dec is not None: 

242 pm_dec_out = radiansFromArcsec(pm_dec) 

243 else: 

244 pm_dec_out = None 

245 

246 if parallax is not None: 

247 parallax_out = radiansFromArcsec(parallax) 

248 else: 

249 parallax_out = None 

250 

251 return _chipNameFromRaDec(np.radians(ra), np.radians(dec), 

252 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

253 parallax=parallax_out, v_rad=v_rad, 

254 obs_metadata=obs_metadata, epoch=epoch, 

255 camera=camera, allow_multiple_chips=allow_multiple_chips) 

256 

257 

258def _chipNameFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

259 obs_metadata=None, camera=None, 

260 epoch=2000.0, allow_multiple_chips=False): 

261 """ 

262 Return the names of detectors that see the object specified by 

263 (RA, Dec) in radians. 

264 

265 @param [in] ra in radians (a numpy array or a float). 

266 In the International Celestial Reference System. 

267 

268 @param [in] dec in radians (a numpy array or a float). 

269 In the International Celestial Reference System. 

270 

271 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) 

272 Can be a numpy array or a number or None (default=None). 

273 

274 @param [in] pm_dec is proper motion in dec (radians/yr) 

275 Can be a numpy array or a number or None (default=None). 

276 

277 @param [in] parallax is parallax in radians 

278 Can be a numpy array or a number or None (default=None). 

279 

280 @param [in] v_rad is radial velocity (km/s) 

281 Can be a numpy array or a number or None (default=None). 

282 

283 

284 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing 

285 

286 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are 

287 measured. Default is 2000. 

288 

289 @param [in] camera is an afw.cameraGeom camera instance characterizing the camera 

290 

291 @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not 

292 this method will allow objects to be visible on more than one chip. If it is 'False' 

293 and an object appears on more than one chip, an exception will be raised. If it is 'True' 

294 and an object falls on more than one chip, it will still only return the first chip in the 

295 list of chips returned. THIS BEHAVIOR SHOULD BE FIXED IN A FUTURE TICKET. 

296 

297 @param [out] the name(s) of the chips on which ra, dec fall (will be a numpy 

298 array if more than one) 

299 """ 

300 

301 are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDec") 

302 

303 if epoch is None: 

304 raise RuntimeError("You need to pass an epoch into chipName") 

305 

306 if obs_metadata is None: 

307 raise RuntimeError("You need to pass an ObservationMetaData into chipName") 

308 

309 if obs_metadata.mjd is None: 

310 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into chipName") 

311 

312 if obs_metadata.rotSkyPos is None: 

313 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into chipName") 

314 

315 xp, yp = _pupilCoordsFromRaDec(ra, dec, 

316 pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, 

317 obs_metadata=obs_metadata, epoch=epoch) 

318 

319 ans = chipNameFromPupilCoords(xp, yp, camera=camera, allow_multiple_chips=allow_multiple_chips) 

320 

321 return ans 

322 

323def chipNameFromPupilCoords(xPupil, yPupil, camera=None, allow_multiple_chips=False): 

324 """ 

325 Return the names of detectors that see the object specified by 

326 (xPupil, yPupil). 

327 

328 @param [in] xPupil is the x pupil coordinate in radians. 

329 Can be either a float or a numpy array. 

330 

331 @param [in] yPupil is the y pupil coordinate in radians. 

332 Can be either a float or a numpy array. 

333 

334 @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not 

335 this method will allow objects to be visible on more than one chip. If it is 'False' 

336 and an object appears on more than one chip, only the first chip will appear in the list of 

337 chipNames and warning will be emitted. If it is 'True' and an object falls on more than one 

338 chip, the resulting chip name will be the string representation of the list of valid chip names. 

339 

340 @param [in] camera is an afwCameraGeom object that specifies the attributes of the camera. 

341 

342 @param [out] a numpy array of chip names 

343 

344 """ 

345 

346 are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoords") 

347 

348 if camera is None: 

349 raise RuntimeError("No camera defined. Cannot run chipName.") 

350 

351 chipNames = [] 

352 

353 if are_arrays: 

354 pupilPointList = [geom.Point2D(x, y) for x, y in zip(xPupil, yPupil)] 

355 else: 

356 pupilPointList = [geom.Point2D(xPupil, yPupil)] 

357 

358 detList = camera.findDetectorsList(pupilPointList, FIELD_ANGLE) 

359 

360 for pt, det in zip(pupilPointList, detList): 

361 if len(det) == 0 or np.isnan(pt.getX()) or np.isnan(pt.getY()): 

362 chipNames.append(None) 

363 else: 

364 name_list = [dd.getName() for dd in det] 

365 if len(name_list) > 1: 

366 if allow_multiple_chips: 

367 chipNames.append(str(name_list)) 

368 else: 

369 warnings.warn("An object has landed on multiple chips. " + 

370 "You asked for this not to happen.\n" + 

371 "We will return only one of the chip names. If you want both, " + 

372 "try re-running with " + 

373 "the kwarg allow_multiple_chips=True.\n" + 

374 "Offending chip names were %s\n" % str(name_list) + 

375 "Offending pupil coordinate point was %.12f %.12f\n" % (pt[0], pt[1]), 

376 category=MultipleChipWarning) 

377 

378 chipNames.append(name_list[0]) 

379 

380 elif len(name_list) == 0: 

381 chipNames.append(None) 

382 else: 

383 chipNames.append(name_list[0]) 

384 

385 if not are_arrays: 

386 return chipNames[0] 

387 

388 return np.array(chipNames) 

389 

390 

391def pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

392 obs_metadata=None, 

393 chipName=None, camera=None, 

394 epoch=2000.0, includeDistortion=True): 

395 """ 

396 Get the pixel positions (or nan if not on a chip) for objects based 

397 on their RA, and Dec (in degrees) 

398 

399 @param [in] ra is in degrees in the International Celestial Reference System. 

400 Can be either a float or a numpy array. 

401 

402 @param [in] dec is in degrees in the International Celestial Reference System. 

403 Can be either a float or a numpy array. 

404 

405 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr) 

406 Can be a numpy array or a number or None (default=None). 

407 

408 @param [in] pm_dec is proper motion in dec (arcsec/yr) 

409 Can be a numpy array or a number or None (default=None). 

410 

411 @param [in] parallax is parallax in arcsec 

412 Can be a numpy array or a number or None (default=None). 

413 

414 @param [in] v_rad is radial velocity (km/s) 

415 Can be a numpy array or a number or None (default=None). 

416 

417 

418 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope 

419 pointing. 

420 

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

422 RA is measured. Default is 2000. 

423 

424 @param [in] chipName designates the names of the chips on which the pixel 

425 coordinates will be reckoned. Can be either single value, an array, or None. 

426 If an array, there must be as many chipNames as there are (RA, Dec) pairs. 

427 If a single value, all of the pixel coordinates will be reckoned on the same 

428 chip. If None, this method will calculate which chip each(RA, Dec) pair actually 

429 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate 

430 chip. Default is None. 

431 

432 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera. 

433 This is an optional argument to be passed to chipName. 

434 

435 @param [in] includeDistortion is a boolean. If True (default), then this method will 

436 return the true pixel coordinates with optical distortion included. If False, this 

437 method will return TAN_PIXEL coordinates, which are the pixel coordinates with 

438 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

439 details. 

440 

441 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate 

442 and the second row is the y pixel coordinate 

443 """ 

444 

445 if pm_ra is not None: 

446 pm_ra_out = radiansFromArcsec(pm_ra) 

447 else: 

448 pm_ra_out = None 

449 

450 if pm_dec is not None: 

451 pm_dec_out = radiansFromArcsec(pm_dec) 

452 else: 

453 pm_dec_out = None 

454 

455 if parallax is not None: 

456 parallax_out = radiansFromArcsec(parallax) 

457 else: 

458 parallax_out = None 

459 

460 return _pixelCoordsFromRaDec(np.radians(ra), np.radians(dec), 

461 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

462 parallax=parallax_out, v_rad=v_rad, 

463 chipName=chipName, camera=camera, 

464 includeDistortion=includeDistortion, 

465 obs_metadata=obs_metadata, epoch=epoch) 

466 

467 

468def _pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

469 obs_metadata=None, 

470 chipName=None, camera=None, 

471 epoch=2000.0, includeDistortion=True): 

472 """ 

473 Get the pixel positions (or nan if not on a chip) for objects based 

474 on their RA, and Dec (in radians) 

475 

476 @param [in] ra is in radians in the International Celestial Reference System. 

477 Can be either a float or a numpy array. 

478 

479 @param [in] dec is in radians in the International Celestial Reference System. 

480 Can be either a float or a numpy array. 

481 

482 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) 

483 Can be a numpy array or a number or None (default=None). 

484 

485 @param [in] pm_dec is proper motion in dec (radians/yr) 

486 Can be a numpy array or a number or None (default=None). 

487 

488 @param [in] parallax is parallax in radians 

489 Can be a numpy array or a number or None (default=None). 

490 

491 @param [in] v_rad is radial velocity (km/s) 

492 Can be a numpy array or a number or None (default=None). 

493 

494 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope 

495 pointing. 

496 

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

498 RA is measured. Default is 2000. 

499 

500 @param [in] chipName designates the names of the chips on which the pixel 

501 coordinates will be reckoned. Can be either single value, an array, or None. 

502 If an array, there must be as many chipNames as there are (RA, Dec) pairs. 

503 If a single value, all of the pixel coordinates will be reckoned on the same 

504 chip. If None, this method will calculate which chip each(RA, Dec) pair actually 

505 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate 

506 chip. Default is None. 

507 

508 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera. 

509 This is an optional argument to be passed to chipName. 

510 

511 @param [in] includeDistortion is a boolean. If True (default), then this method will 

512 return the true pixel coordinates with optical distortion included. If False, this 

513 method will return TAN_PIXEL coordinates, which are the pixel coordinates with 

514 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

515 details. 

516 

517 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate 

518 and the second row is the y pixel coordinate 

519 """ 

520 

521 are_arrays, \ 

522 chipNameList = _validate_inputs_and_chipname([ra, dec], ['ra', 'dec'], 

523 'pixelCoordsFromRaDec', 

524 chipName) 

525 

526 if epoch is None: 

527 raise RuntimeError("You need to pass an epoch into pixelCoordsFromRaDec") 

528 

529 if obs_metadata is None: 

530 raise RuntimeError("You need to pass an ObservationMetaData into pixelCoordsFromRaDec") 

531 

532 if obs_metadata.mjd is None: 

533 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into " 

534 "pixelCoordsFromRaDec") 

535 

536 if obs_metadata.rotSkyPos is None: 

537 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into " 

538 "pixelCoordsFromRaDec") 

539 

540 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, 

541 pm_ra=pm_ra, pm_dec=pm_dec, 

542 parallax=parallax, v_rad=v_rad, 

543 obs_metadata=obs_metadata, epoch=epoch) 

544 

545 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipNameList, camera=camera, 

546 includeDistortion=includeDistortion) 

547 

548 

549def pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=None, 

550 camera=None, includeDistortion=True): 

551 """ 

552 Get the pixel positions (or nan if not on a chip) for objects based 

553 on their pupil coordinates. 

554 

555 @param [in] xPupil is the x pupil coordinates in radians. 

556 Can be either a float or a numpy array. 

557 

558 @param [in] yPupil is the y pupil coordinates in radians. 

559 Can be either a float or a numpy array. 

560 

561 @param [in] chipName designates the names of the chips on which the pixel 

562 coordinates will be reckoned. Can be either single value, an array, or None. 

563 If an array, there must be as many chipNames as there are (RA, Dec) pairs. 

564 If a single value, all of the pixel coordinates will be reckoned on the same 

565 chip. If None, this method will calculate which chip each(RA, Dec) pair actually 

566 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate 

567 chip. Default is None. 

568 

569 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera. 

570 This is an optional argument to be passed to chipName. 

571 

572 @param [in] includeDistortion is a boolean. If True (default), then this method will 

573 return the true pixel coordinates with optical distortion included. If False, this 

574 method will return TAN_PIXEL coordinates, which are the pixel coordinates with 

575 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

576 details. 

577 

578 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate 

579 and the second row is the y pixel coordinate 

580 """ 

581 

582 are_arrays, \ 

583 chipNameList = _validate_inputs_and_chipname([xPupil, yPupil], ["xPupil", "yPupil"], 

584 "pixelCoordsFromPupilCoords", 

585 chipName) 

586 if includeDistortion: 

587 pixelType = PIXELS 

588 else: 

589 pixelType = TAN_PIXELS 

590 

591 if not camera: 

592 raise RuntimeError("Camera not specified. Cannot calculate pixel coordinates.") 

593 

594 if chipNameList is None: 

595 chipNameList = chipNameFromPupilCoords(xPupil, yPupil, camera=camera) 

596 if not isinstance(chipNameList, list) and not isinstance(chipNameList, np.ndarray): 

597 chipNameList = [chipNameList] 

598 

599 fieldToFocal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE) 

600 

601 if are_arrays: 

602 if len(xPupil) == 0: 

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

604 field_point_list = list([geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)]) 

605 focal_point_list = fieldToFocal.applyForward(field_point_list) 

606 

607 transform_dict = {} 

608 xPix = np.nan*np.ones(len(chipNameList), dtype=float) 

609 yPix = np.nan*np.ones(len(chipNameList), dtype=float) 

610 

611 if not isinstance(chipNameList, np.ndarray): 

612 chipNameList = np.array(chipNameList) 

613 chipNameList = chipNameList.astype(str) 

614 

615 for name in np.unique(chipNameList): 

616 if name == 'None': 

617 continue 

618 

619 valid_points = np.where(np.char.find(chipNameList, name)==0) 

620 local_focal_point_list = list([focal_point_list[dex] for dex in valid_points[0]]) 

621 

622 if name not in transform_dict: 

623 transform_dict[name] = camera[name].getTransform(FOCAL_PLANE, pixelType) 

624 

625 focalToPixels = transform_dict[name] 

626 pixPoint_list = focalToPixels.applyForward(local_focal_point_list) 

627 

628 for i_fp, v_dex in enumerate(valid_points[0]): 

629 pixPoint= pixPoint_list[i_fp] 

630 xPix[v_dex] = pixPoint.getX() 

631 yPix[v_dex] = pixPoint.getY() 

632 

633 return np.array([xPix, yPix]) 

634 else: 

635 if chipNameList[0] is None: 

636 return np.array([np.NaN, np.NaN]) 

637 

638 det = camera[chipNameList[0]] 

639 focalToPixels = det.getTransform(FOCAL_PLANE, pixelType) 

640 focalPoint = fieldToFocal.applyForward(geom.Point2D(xPupil, yPupil)) 

641 pixPoint = focalToPixels.applyForward(focalPoint) 

642 return np.array([pixPoint.getX(), pixPoint.getY()]) 

643 

644 

645def pupilCoordsFromPixelCoords(xPix, yPix, chipName, camera=None, 

646 includeDistortion=True): 

647 

648 """ 

649 Convert pixel coordinates into pupil coordinates 

650 

651 @param [in] xPix is the x pixel coordinate of the point. 

652 Can be either a float or a numpy array. 

653 

654 @param [in] yPix is the y pixel coordinate of the point. 

655 Can be either a float or a numpy array. 

656 

657 @param [in] chipName is the name of the chip(s) on which the pixel coordinates 

658 are defined. This can be a list (in which case there should be one chip name 

659 for each (xPix, yPix) coordinate pair), or a single value (in which case, all 

660 of the (xPix, yPix) points will be reckoned on that chip). 

661 

662 @param [in] camera is an afw.CameraGeom.camera object defining the camera 

663 

664 @param [in] includeDistortion is a boolean. If True (default), then this method will 

665 expect the true pixel coordinates with optical distortion included. If False, this 

666 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with 

667 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

668 details. 

669 

670 @param [out] a 2-D numpy array in which the first row is the x pupil coordinate 

671 and the second row is the y pupil coordinate (both in radians) 

672 """ 

673 

674 if camera is None: 

675 raise RuntimeError("You cannot call pupilCoordsFromPixelCoords without specifying a camera") 

676 

677 are_arrays, \ 

678 chipNameList = _validate_inputs_and_chipname([xPix, yPix], ['xPix', 'yPix'], 

679 "pupilCoordsFromPixelCoords", 

680 chipName, 

681 chipname_can_be_none=False) 

682 

683 if includeDistortion: 

684 pixelType = PIXELS 

685 else: 

686 pixelType = TAN_PIXELS 

687 

688 pixel_to_focal_dict = {} 

689 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE) 

690 for name in chipNameList: 

691 if name not in pixel_to_focal_dict and name is not None and name != 'None': 

692 pixel_to_focal_dict[name] = camera[name].getTransform(pixelType, FOCAL_PLANE) 

693 

694 if are_arrays: 

695 xPupilList = [] 

696 yPupilList = [] 

697 

698 for xx, yy, name in zip(xPix, yPix, chipNameList): 

699 if name is None or name == 'None': 

700 xPupilList.append(np.NaN) 

701 yPupilList.append(np.NaN) 

702 else: 

703 focalPoint = pixel_to_focal_dict[name].applyForward(geom.Point2D(xx, yy)) 

704 pupilPoint = focal_to_field.applyForward(focalPoint) 

705 xPupilList.append(pupilPoint.getX()) 

706 yPupilList.append(pupilPoint.getY()) 

707 

708 xPupilList = np.array(xPupilList) 

709 yPupilList = np.array(yPupilList) 

710 

711 return np.array([xPupilList, yPupilList]) 

712 

713 # if not are_arrays 

714 if chipNameList[0] is None or chipNameList[0] == 'None': 

715 return np.array([np.NaN, np.NaN]) 

716 

717 focalPoint = pixel_to_focal_dict[chipNameList[0]].applyForward(geom.Point2D(xPix, yPix)) 

718 pupilPoint = focal_to_field.applyForward(focalPoint) 

719 return np.array([pupilPoint.getX(), pupilPoint.getY()]) 

720 

721 

722def raDecFromPixelCoords(xPix, yPix, chipName, camera=None, 

723 obs_metadata=None, epoch=2000.0, includeDistortion=True): 

724 """ 

725 Convert pixel coordinates into RA, Dec 

726 

727 @param [in] xPix is the x pixel coordinate. It can be either 

728 a float or a numpy array. 

729 

730 @param [in] yPix is the y pixel coordinate. It can be either 

731 a float or a numpy array. 

732 

733 @param [in] chipName is the name of the chip(s) on which the pixel coordinates 

734 are defined. This can be a list (in which case there should be one chip name 

735 for each (xPix, yPix) coordinate pair), or a single value (in which case, all 

736 of the (xPix, yPix) points will be reckoned on that chip). 

737 

738 @param [in] camera is an afw.CameraGeom.camera object defining the camera 

739 

740 @param [in] obs_metadata is an ObservationMetaData defining the pointing 

741 

742 @param [in] epoch is the mean epoch in years of the celestial coordinate system. 

743 Default is 2000. 

744 

745 @param [in] includeDistortion is a boolean. If True (default), then this method will 

746 expect the true pixel coordinates with optical distortion included. If False, this 

747 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with 

748 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

749 details. 

750 

751 @param [out] a 2-D numpy array in which the first row is the RA coordinate 

752 and the second row is the Dec coordinate (both in degrees; in the 

753 International Celestial Reference System) 

754 

755 WARNING: This method does not account for apparent motion due to parallax. 

756 This method is only useful for mapping positions on a theoretical focal plane 

757 to positions on the celestial sphere. 

758 """ 

759 output = _raDecFromPixelCoords(xPix, yPix, chipName, 

760 camera=camera, obs_metadata=obs_metadata, 

761 epoch=epoch, includeDistortion=includeDistortion) 

762 

763 return np.degrees(output) 

764 

765 

766def _raDecFromPixelCoords(xPix, yPix, chipName, camera=None, 

767 obs_metadata=None, epoch=2000.0, includeDistortion=True): 

768 """ 

769 Convert pixel coordinates into RA, Dec 

770 

771 @param [in] xPix is the x pixel coordinate. It can be either 

772 a float or a numpy array. 

773 

774 @param [in] yPix is the y pixel coordinate. It can be either 

775 a float or a numpy array. 

776 

777 @param [in] chipName is the name of the chip(s) on which the pixel coordinates 

778 are defined. This can be a list (in which case there should be one chip name 

779 for each (xPix, yPix) coordinate pair), or a single value (in which case, all 

780 of the (xPix, yPix) points will be reckoned on that chip). 

781 

782 @param [in] camera is an afw.CameraGeom.camera object defining the camera 

783 

784 @param [in] obs_metadata is an ObservationMetaData defining the pointing 

785 

786 @param [in] epoch is the mean epoch in years of the celestial coordinate system. 

787 Default is 2000. 

788 

789 @param [in] includeDistortion is a boolean. If True (default), then this method will 

790 expect the true pixel coordinates with optical distortion included. If False, this 

791 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with 

792 estimated optical distortion removed. See the documentation in afw.cameraGeom for more 

793 details. 

794 

795 @param [out] a 2-D numpy array in which the first row is the RA coordinate 

796 and the second row is the Dec coordinate (both in radians; in the International 

797 Celestial Reference System) 

798 

799 WARNING: This method does not account for apparent motion due to parallax. 

800 This method is only useful for mapping positions on a theoretical focal plane 

801 to positions on the celestial sphere. 

802 """ 

803 

804 are_arrays, \ 

805 chipNameList = _validate_inputs_and_chipname([xPix, yPix], 

806 ['xPix', 'yPix'], 

807 'raDecFromPixelCoords', 

808 chipName, 

809 chipname_can_be_none=False) 

810 

811 if camera is None: 

812 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying a camera") 

813 

814 if epoch is None: 

815 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying an epoch") 

816 

817 if obs_metadata is None: 

818 raise RuntimeError("You cannot call raDecFromPixelCoords without an ObservationMetaData") 

819 

820 if obs_metadata.mjd is None: 

821 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have an mjd") 

822 

823 if obs_metadata.rotSkyPos is None: 

824 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have a rotSkyPos") 

825 

826 xPupilList, yPupilList = pupilCoordsFromPixelCoords(xPix, yPix, chipNameList, 

827 camera=camera, includeDistortion=includeDistortion) 

828 

829 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList, 

830 obs_metadata=obs_metadata, epoch=epoch) 

831 

832 return np.array([raOut, decOut]) 

833 

834 

835def focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

836 obs_metadata=None, epoch=2000.0, camera=None): 

837 """ 

838 Get the focal plane coordinates for all objects in the catalog. 

839 

840 @param [in] ra is in degrees in the International Celestial Reference System. 

841 Can be either a float or a numpy array. 

842 

843 @param [in] dec is in degrees in the International Celestial Reference System. 

844 Can be either a float or a numpy array. 

845 

846 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr) 

847 Can be a numpy array or a number or None (default=None). 

848 

849 @param [in] pm_dec is proper motion in dec (arcsec/yr) 

850 Can be a numpy array or a number or None (default=None). 

851 

852 @param [in] parallax is parallax in arcsec 

853 Can be a numpy array or a number or None (default=None). 

854 

855 @param [in] v_rad is radial velocity (km/s) 

856 Can be a numpy array or a number or None (default=None). 

857 

858 @param [in] obs_metadata is an ObservationMetaData object describing the telescope 

859 pointing (only if specifying RA and Dec rather than pupil coordinates) 

860 

861 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations 

862 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000) 

863 

864 @param [in] camera is an afw.cameraGeom camera object 

865 

866 @param [out] a 2-D numpy array in which the first row is the x 

867 focal plane coordinate and the second row is the y focal plane 

868 coordinate (both in millimeters) 

869 """ 

870 

871 if pm_ra is not None: 

872 pm_ra_out = radiansFromArcsec(pm_ra) 

873 else: 

874 pm_ra_out = None 

875 

876 if pm_dec is not None: 

877 pm_dec_out = radiansFromArcsec(pm_dec) 

878 else: 

879 pm_dec_out = None 

880 

881 if parallax is not None: 

882 parallax_out = radiansFromArcsec(parallax) 

883 else: 

884 parallax_out = None 

885 

886 return _focalPlaneCoordsFromRaDec(np.radians(ra), np.radians(dec), 

887 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

888 parallax=parallax_out, v_rad=v_rad, 

889 obs_metadata=obs_metadata, epoch=epoch, 

890 camera=camera) 

891 

892 

893def _focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

894 obs_metadata=None, epoch=2000.0, camera=None): 

895 """ 

896 Get the focal plane coordinates for all objects in the catalog. 

897 

898 @param [in] ra is in radians in the International Celestial Reference System. 

899 Can be either a float or a numpy array. 

900 

901 @param [in] dec is in radians in the International Celestial Reference System. 

902 Can be either a float or a numpy array. 

903 

904 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr) 

905 Can be a numpy array or a number or None (default=None). 

906 

907 @param [in] pm_dec is proper motion in dec (radians/yr) 

908 Can be a numpy array or a number or None (default=None). 

909 

910 @param [in] parallax is parallax in radians 

911 Can be a numpy array or a number or None (default=None). 

912 

913 @param [in] v_rad is radial velocity (km/s) 

914 Can be a numpy array or a number or None (default=None). 

915 

916 

917 @param [in] obs_metadata is an ObservationMetaData object describing the telescope 

918 pointing (only if specifying RA and Dec rather than pupil coordinates) 

919 

920 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations 

921 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000) 

922 

923 @param [in] camera is an afw.cameraGeom camera object 

924 

925 @param [out] a 2-D numpy array in which the first row is the x 

926 focal plane coordinate and the second row is the y focal plane 

927 coordinate (both in millimeters) 

928 """ 

929 

930 _validate_inputs([ra, dec], ['ra', 'dec'], 'focalPlaneCoordsFromRaDec') 

931 

932 if epoch is None: 

933 raise RuntimeError("You have to specify an epoch to run " 

934 "focalPlaneCoordsFromRaDec") 

935 

936 if obs_metadata is None: 

937 raise RuntimeError("You have to specify an ObservationMetaData to run " 

938 "focalPlaneCoordsFromRaDec") 

939 

940 if obs_metadata.mjd is None: 

941 raise RuntimeError("You need to pass an ObservationMetaData with an " 

942 "mjd into focalPlaneCoordsFromRaDec") 

943 

944 if obs_metadata.rotSkyPos is None: 

945 raise RuntimeError("You need to pass an ObservationMetaData with a " 

946 "rotSkyPos into focalPlaneCoordsFromRaDec") 

947 

948 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, 

949 pm_ra=pm_ra, pm_dec=pm_dec, 

950 parallax=parallax, v_rad=v_rad, 

951 obs_metadata=obs_metadata, 

952 epoch=epoch) 

953 

954 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=camera) 

955 

956 

957def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None): 

958 """ 

959 Get the focal plane coordinates for all objects in the catalog. 

960 

961 @param [in] xPupil the x pupil coordinates in radians. 

962 Can be a float or a numpy array. 

963 

964 @param [in] yPupil the y pupil coordinates in radians. 

965 Can be a float or a numpy array. 

966 

967 @param [in] camera is an afw.cameraGeom camera object 

968 

969 @param [out] a 2-D numpy array in which the first row is the x 

970 focal plane coordinate and the second row is the y focal plane 

971 coordinate (both in millimeters) 

972 """ 

973 

974 are_arrays = _validate_inputs([xPupil, yPupil], 

975 ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords') 

976 

977 if camera is None: 

978 raise RuntimeError("You cannot calculate focal plane coordinates without specifying a camera") 

979 

980 field_to_focal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE) 

981 

982 if are_arrays: 

983 pupil_point_list = [geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)] 

984 focal_point_list = field_to_focal.applyForward(pupil_point_list) 

985 xFocal = np.array([pp.getX() for pp in focal_point_list]) 

986 yFocal = np.array([pp.getY() for pp in focal_point_list]) 

987 

988 return np.array([xFocal, yFocal]) 

989 

990 # if not are_arrays 

991 fpPoint = field_to_focal.applyForward(geom.Point2D(xPupil, yPupil)) 

992 return np.array([fpPoint.getX(), fpPoint.getY()]) 

993 

994 

995def pupilCoordsFromFocalPlaneCoords(xFocal, yFocal, camera=None): 

996 """ 

997 Get the pupil coordinates in radians from the focal plane 

998 coordinates in millimeters 

999 

1000 @param [in] xFocal the x focal plane coordinates in millimeters. 

1001 Can be a float or a numpy array. 

1002 

1003 @param [in] yFocal the y focal plane coordinates in millimeters. 

1004 Can be a float or a numpy array. 

1005 

1006 @param [in] camera is an afw.cameraGeom camera object 

1007 

1008 @param [out] a 2-D numpy array in which the first row is the x 

1009 pupil coordinate and the second row is the y pupil 

1010 coordinate (both in radians) 

1011 """ 

1012 

1013 are_arrays = _validate_inputs([xFocal, yFocal], 

1014 ['xFocal', 'yFocal'], 

1015 'pupilCoordsFromFocalPlaneCoords') 

1016 

1017 if camera is None: 

1018 raise RuntimeError("You cannot calculate pupil coordinates without specifying a camera") 

1019 

1020 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE) 

1021 

1022 if are_arrays: 

1023 focal_point_list = [geom.Point2D(x,y) for x,y in zip(xFocal, yFocal)] 

1024 pupil_point_list = focal_to_field.applyForward(focal_point_list) 

1025 pupil_arr = np.array([[pp.getX(), pp.getY()] 

1026 for pp in pupil_point_list]).transpose() 

1027 is_nan = np.where(np.logical_or(np.isnan(xFocal), np.isnan(yFocal))) 

1028 pupil_arr[0][is_nan] = np.NaN 

1029 pupil_arr[1][is_nan] = np.NaN 

1030 

1031 return pupil_arr 

1032 

1033 # if not are_arrays 

1034 if np.isfinite(xFocal) and np.isfinite(yFocal): 

1035 pupPoint = focal_to_field.applyForward(geom.Point2D(xFocal, yFocal)) 

1036 return np.array([pupPoint.getX(), pupPoint.getY()]) 

1037 

1038 return np.array([np.NaN, np.NaN])