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 if not are_arrays: 

316 ra = np.array([ra]) 

317 dec = np.array([dec]) 

318 

319 xp, yp = _pupilCoordsFromRaDec(ra, dec, 

320 pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad, 

321 obs_metadata=obs_metadata, epoch=epoch) 

322 

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

324 

325 if not are_arrays: 

326 return ans[0] 

327 return ans 

328 

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

330 """ 

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

332 (xPupil, yPupil). 

333 

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

335 Can be either a float or a numpy array. 

336 

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

338 Can be either a float or a numpy array. 

339 

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

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

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

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

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

345 

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

347 

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

349 

350 """ 

351 

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

353 

354 if camera is None: 

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

356 

357 chipNames = [] 

358 

359 if are_arrays: 

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

361 else: 

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

363 

364 detList = camera.findDetectorsList(pupilPointList, FIELD_ANGLE) 

365 

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

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

368 chipNames.append(None) 

369 else: 

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

371 if len(name_list) > 1: 

372 if allow_multiple_chips: 

373 chipNames.append(str(name_list)) 

374 else: 

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

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

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

378 "try re-running with " + 

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

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

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

382 category=MultipleChipWarning) 

383 

384 chipNames.append(name_list[0]) 

385 

386 elif len(name_list) == 0: 

387 chipNames.append(None) 

388 else: 

389 chipNames.append(name_list[0]) 

390 

391 if not are_arrays: 

392 return chipNames[0] 

393 

394 return np.array(chipNames) 

395 

396 

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

398 obs_metadata=None, 

399 chipName=None, camera=None, 

400 epoch=2000.0, includeDistortion=True): 

401 """ 

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

403 on their RA, and Dec (in degrees) 

404 

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

406 Can be either a float or a numpy array. 

407 

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

409 Can be either a float or a numpy array. 

410 

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

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

413 

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

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

416 

417 @param [in] parallax is parallax in arcsec 

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

419 

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

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

422 

423 

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

425 pointing. 

426 

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

428 RA is measured. Default is 2000. 

429 

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

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

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

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

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

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

436 chip. Default is None. 

437 

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

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

440 

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

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

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

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

445 details. 

446 

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

448 and the second row is the y pixel coordinate 

449 """ 

450 

451 if pm_ra is not None: 

452 pm_ra_out = radiansFromArcsec(pm_ra) 

453 else: 

454 pm_ra_out = None 

455 

456 if pm_dec is not None: 

457 pm_dec_out = radiansFromArcsec(pm_dec) 

458 else: 

459 pm_dec_out = None 

460 

461 if parallax is not None: 

462 parallax_out = radiansFromArcsec(parallax) 

463 else: 

464 parallax_out = None 

465 

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

467 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

468 parallax=parallax_out, v_rad=v_rad, 

469 chipName=chipName, camera=camera, 

470 includeDistortion=includeDistortion, 

471 obs_metadata=obs_metadata, epoch=epoch) 

472 

473 

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

475 obs_metadata=None, 

476 chipName=None, camera=None, 

477 epoch=2000.0, includeDistortion=True): 

478 """ 

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

480 on their RA, and Dec (in radians) 

481 

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

483 Can be either a float or a numpy array. 

484 

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

486 Can be either a float or a numpy array. 

487 

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

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

490 

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

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

493 

494 @param [in] parallax is parallax in radians 

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

496 

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

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

499 

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

501 pointing. 

502 

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

504 RA is measured. Default is 2000. 

505 

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

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

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

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

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

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

512 chip. Default is None. 

513 

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

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

516 

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

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

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

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

521 details. 

522 

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

524 and the second row is the y pixel coordinate 

525 """ 

526 

527 are_arrays, \ 

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

529 'pixelCoordsFromRaDec', 

530 chipName) 

531 

532 if epoch is None: 

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

534 

535 if obs_metadata is None: 

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

537 

538 if obs_metadata.mjd is None: 

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

540 "pixelCoordsFromRaDec") 

541 

542 if obs_metadata.rotSkyPos is None: 

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

544 "pixelCoordsFromRaDec") 

545 

546 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, 

547 pm_ra=pm_ra, pm_dec=pm_dec, 

548 parallax=parallax, v_rad=v_rad, 

549 obs_metadata=obs_metadata, epoch=epoch) 

550 

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

552 includeDistortion=includeDistortion) 

553 

554 

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

556 camera=None, includeDistortion=True): 

557 """ 

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

559 on their pupil coordinates. 

560 

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

562 Can be either a float or a numpy array. 

563 

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

565 Can be either a float or a numpy array. 

566 

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

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

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

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

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

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

573 chip. Default is None. 

574 

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

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

577 

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

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

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

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

582 details. 

583 

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

585 and the second row is the y pixel coordinate 

586 """ 

587 

588 are_arrays, \ 

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

590 "pixelCoordsFromPupilCoords", 

591 chipName) 

592 if includeDistortion: 

593 pixelType = PIXELS 

594 else: 

595 pixelType = TAN_PIXELS 

596 

597 if not camera: 

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

599 

600 if chipNameList is None: 

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

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

603 chipNameList = [chipNameList] 

604 

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

606 

607 if are_arrays: 

608 if len(xPupil) == 0: 

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

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

611 focal_point_list = fieldToFocal.applyForward(field_point_list) 

612 

613 transform_dict = {} 

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

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

616 

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

618 chipNameList = np.array(chipNameList) 

619 chipNameList = chipNameList.astype(str) 

620 

621 for name in np.unique(chipNameList): 

622 if name == 'None': 

623 continue 

624 

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

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

627 

628 if name not in transform_dict: 

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

630 

631 focalToPixels = transform_dict[name] 

632 pixPoint_list = focalToPixels.applyForward(local_focal_point_list) 

633 

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

635 pixPoint= pixPoint_list[i_fp] 

636 xPix[v_dex] = pixPoint.getX() 

637 yPix[v_dex] = pixPoint.getY() 

638 

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

640 else: 

641 if chipNameList[0] is None: 

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

643 

644 det = camera[chipNameList[0]] 

645 focalToPixels = det.getTransform(FOCAL_PLANE, pixelType) 

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

647 pixPoint = focalToPixels.applyForward(focalPoint) 

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

649 

650 

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

652 includeDistortion=True): 

653 

654 """ 

655 Convert pixel coordinates into pupil coordinates 

656 

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

658 Can be either a float or a numpy array. 

659 

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

661 Can be either a float or a numpy array. 

662 

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

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

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

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

667 

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

669 

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

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

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

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

674 details. 

675 

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

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

678 """ 

679 

680 if camera is None: 

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

682 

683 are_arrays, \ 

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

685 "pupilCoordsFromPixelCoords", 

686 chipName, 

687 chipname_can_be_none=False) 

688 

689 if includeDistortion: 

690 pixelType = PIXELS 

691 else: 

692 pixelType = TAN_PIXELS 

693 

694 pixel_to_focal_dict = {} 

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

696 for name in chipNameList: 

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

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

699 

700 if are_arrays: 

701 xPupilList = [] 

702 yPupilList = [] 

703 

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

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

706 xPupilList.append(np.NaN) 

707 yPupilList.append(np.NaN) 

708 else: 

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

710 pupilPoint = focal_to_field.applyForward(focalPoint) 

711 xPupilList.append(pupilPoint.getX()) 

712 yPupilList.append(pupilPoint.getY()) 

713 

714 xPupilList = np.array(xPupilList) 

715 yPupilList = np.array(yPupilList) 

716 

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

718 

719 # if not are_arrays 

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

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

722 

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

724 pupilPoint = focal_to_field.applyForward(focalPoint) 

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

726 

727 

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

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

730 """ 

731 Convert pixel coordinates into RA, Dec 

732 

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

734 a float or a numpy array. 

735 

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

737 a float or a numpy array. 

738 

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

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

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

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

743 

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

745 

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

747 

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

749 Default is 2000. 

750 

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

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

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

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

755 details. 

756 

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

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

759 International Celestial Reference System) 

760 

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

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

763 to positions on the celestial sphere. 

764 """ 

765 output = _raDecFromPixelCoords(xPix, yPix, chipName, 

766 camera=camera, obs_metadata=obs_metadata, 

767 epoch=epoch, includeDistortion=includeDistortion) 

768 

769 return np.degrees(output) 

770 

771 

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

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

774 """ 

775 Convert pixel coordinates into RA, Dec 

776 

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

778 a float or a numpy array. 

779 

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

781 a float or a numpy array. 

782 

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

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

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

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

787 

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

789 

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

791 

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

793 Default is 2000. 

794 

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

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

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

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

799 details. 

800 

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

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

803 Celestial Reference System) 

804 

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

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

807 to positions on the celestial sphere. 

808 """ 

809 

810 are_arrays, \ 

811 chipNameList = _validate_inputs_and_chipname([xPix, yPix], 

812 ['xPix', 'yPix'], 

813 'raDecFromPixelCoords', 

814 chipName, 

815 chipname_can_be_none=False) 

816 

817 if camera is None: 

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

819 

820 if epoch is None: 

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

822 

823 if obs_metadata is None: 

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

825 

826 if obs_metadata.mjd is None: 

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

828 

829 if obs_metadata.rotSkyPos is None: 

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

831 

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

833 camera=camera, includeDistortion=includeDistortion) 

834 

835 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList, 

836 obs_metadata=obs_metadata, epoch=epoch) 

837 

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

839 

840 

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

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

843 """ 

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

845 

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

847 Can be either a float or a numpy array. 

848 

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

850 Can be either a float or a numpy array. 

851 

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

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

854 

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

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

857 

858 @param [in] parallax is parallax in arcsec 

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

860 

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

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

863 

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

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

866 

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

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

869 

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

871 

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

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

874 coordinate (both in millimeters) 

875 """ 

876 

877 if pm_ra is not None: 

878 pm_ra_out = radiansFromArcsec(pm_ra) 

879 else: 

880 pm_ra_out = None 

881 

882 if pm_dec is not None: 

883 pm_dec_out = radiansFromArcsec(pm_dec) 

884 else: 

885 pm_dec_out = None 

886 

887 if parallax is not None: 

888 parallax_out = radiansFromArcsec(parallax) 

889 else: 

890 parallax_out = None 

891 

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

893 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

894 parallax=parallax_out, v_rad=v_rad, 

895 obs_metadata=obs_metadata, epoch=epoch, 

896 camera=camera) 

897 

898 

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

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

901 """ 

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

903 

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

905 Can be either a float or a numpy array. 

906 

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

908 Can be either a float or a numpy array. 

909 

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

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

912 

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

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

915 

916 @param [in] parallax is parallax in radians 

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

918 

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

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

921 

922 

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

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

925 

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

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

928 

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

930 

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

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

933 coordinate (both in millimeters) 

934 """ 

935 

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

937 

938 if epoch is None: 

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

940 "focalPlaneCoordsFromRaDec") 

941 

942 if obs_metadata is None: 

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

944 "focalPlaneCoordsFromRaDec") 

945 

946 if obs_metadata.mjd is None: 

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

948 "mjd into focalPlaneCoordsFromRaDec") 

949 

950 if obs_metadata.rotSkyPos is None: 

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

952 "rotSkyPos into focalPlaneCoordsFromRaDec") 

953 

954 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, 

955 pm_ra=pm_ra, pm_dec=pm_dec, 

956 parallax=parallax, v_rad=v_rad, 

957 obs_metadata=obs_metadata, 

958 epoch=epoch) 

959 

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

961 

962 

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

964 """ 

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

966 

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

968 Can be a float or a numpy array. 

969 

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

971 Can be a float or a numpy array. 

972 

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

974 

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

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

977 coordinate (both in millimeters) 

978 """ 

979 

980 are_arrays = _validate_inputs([xPupil, yPupil], 

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

982 

983 if camera is None: 

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

985 

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

987 

988 if are_arrays: 

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

990 focal_point_list = field_to_focal.applyForward(pupil_point_list) 

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

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

993 

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

995 

996 # if not are_arrays 

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

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

999 

1000 

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

1002 """ 

1003 Get the pupil coordinates in radians from the focal plane 

1004 coordinates in millimeters 

1005 

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

1007 Can be a float or a numpy array. 

1008 

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

1010 Can be a float or a numpy array. 

1011 

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

1013 

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

1015 pupil coordinate and the second row is the y pupil 

1016 coordinate (both in radians) 

1017 """ 

1018 

1019 are_arrays = _validate_inputs([xFocal, yFocal], 

1020 ['xFocal', 'yFocal'], 

1021 'pupilCoordsFromFocalPlaneCoords') 

1022 

1023 if camera is None: 

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

1025 

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

1027 

1028 if are_arrays: 

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

1030 pupil_point_list = focal_to_field.applyForward(focal_point_list) 

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

1032 for pp in pupil_point_list]).transpose() 

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

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

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

1036 

1037 return pupil_arr 

1038 

1039 # if not are_arrays 

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

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

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

1043 

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