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 __future__ import division 

2from builtins import zip 

3from builtins import range 

4import numpy as np 

5import numbers 

6import lsst.geom as geom 

7from lsst.afw.cameraGeom import FIELD_ANGLE, FOCAL_PLANE, PIXELS 

8from lsst.afw.cameraGeom import DetectorType 

9from lsst.sims.coordUtils import lsst_camera 

10from lsst.sims.coordUtils import focalPlaneCoordsFromPupilCoords 

11from lsst.sims.coordUtils import LsstZernikeFitter 

12from lsst.sims.coordUtils import pupilCoordsFromPixelCoords, pixelCoordsFromPupilCoords 

13from lsst.sims.coordUtils import pupilCoordsFromFocalPlaneCoords 

14from lsst.sims.coordUtils import pupilCoordsFromPixelCoords 

15from lsst.sims.utils import _pupilCoordsFromRaDec 

16from lsst.sims.utils import _raDecFromPupilCoords 

17from lsst.sims.coordUtils import getCornerPixels, _validate_inputs_and_chipname 

18from lsst.sims.utils.CodeUtilities import _validate_inputs 

19from lsst.sims.utils import radiansFromArcsec 

20 

21 

22__all__ = ["focalPlaneCoordsFromPupilCoordsLSST", 

23 "pupilCoordsFromFocalPlaneCoordsLSST", 

24 "chipNameFromPupilCoordsLSST", 

25 "_chipNameFromRaDecLSST", "chipNameFromRaDecLSST", 

26 "pixelCoordsFromPupilCoordsLSST", 

27 "pupilCoordsFromPixelCoordsLSST", 

28 "_pixelCoordsFromRaDecLSST", "pixelCoordsFromRaDecLSST", 

29 "_raDecFromPixelCoordsLSST", "raDecFromPixelCoordsLSST", 

30 "clean_up_lsst_camera"] 

31 

32def clean_up_lsst_camera(): 

33 """ 

34 Delete member objects associated with the methods below 

35 """ 

36 if hasattr(focalPlaneCoordsFromPupilCoordsLSST, '_z_fitter'): 

37 del focalPlaneCoordsFromPupilCoordsLSST._z_fitter 

38 if hasattr(pupilCoordsFromFocalPlaneCoordsLSST, '_z_fitter'): 

39 del pupilCoordsFromFocalPlaneCoordsLSST._z_fitter 

40 if hasattr(chipNameFromPupilCoordsLSST, '_detector_arr'): 

41 del chipNameFromPupilCoordsLSST._detector_arr 

42 if hasattr(lsst_camera, '_lsst_camera'): 

43 del lsst_camera._lsst_camera 

44 

45def focalPlaneCoordsFromPupilCoordsLSST(xPupil, yPupil, band='r'): 

46 """ 

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

48 

49 Parameters 

50 ---------- 

51 xPupil -- the x pupil coordinates in radians. 

52 Can be a float or a numpy array. 

53 

54 yPupil -- the y pupil coordinates in radians. 

55 Can be a float or a numpy array. 

56 

57 band -- the filter being simulated (default='r') 

58 

59 Returns 

60 -------- 

61 a 2-D numpy array in which the first row is the x 

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

63 coordinate (both in millimeters) 

64 """ 

65 

66 if not hasattr(focalPlaneCoordsFromPupilCoordsLSST, '_z_fitter'): 

67 focalPlaneCoordsFromPupilCoordsLSST._z_fitter = LsstZernikeFitter() 

68 

69 if isinstance(xPupil, numbers.Number): 

70 if np.isnan(xPupil) or np.isnan(yPupil): 

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

72 

73 z_fitter = focalPlaneCoordsFromPupilCoordsLSST._z_fitter 

74 x_f0, y_f0 = focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=lsst_camera()) 

75 dx, dy = z_fitter.dxdy(x_f0, y_f0, band) 

76 

77 if not isinstance(xPupil, numbers.Number): 

78 nan_dex = np.where(np.logical_or(np.isnan(xPupil), np.isnan(yPupil))) 

79 x_f0[nan_dex] = np.NaN 

80 y_f0[nan_dex] = np.NaN 

81 

82 return np.array([x_f0+dx, y_f0+dy]) 

83 

84 

85def pupilCoordsFromFocalPlaneCoordsLSST(xmm, ymm, band='r'): 

86 """ 

87 Convert mm on the focal plane to radians on the pupil. 

88 

89 Note: round-tripping through focalPlaneCoordsFromPupilCoordsLSST 

90 and pupilCoordsFromFocalPlaneCoordsLSST introduces a residual 

91 of up to 2.18e-6 mm that accumulates with each round trip. 

92 

93 Parameters 

94 ---------- 

95 xmm -- x coordinate in millimeters on the focal plane 

96 

97 ymm -- y coordinate in millimeters on the focal plane 

98 

99 band -- the filter we are simulating (default='r') 

100 

101 Returns 

102 ------- 

103 a 2-D numpy array in which the first row is the x 

104 pupil coordinate and the second row is the y pupil 

105 coordinate (both in radians) 

106 """ 

107 if not hasattr(pupilCoordsFromFocalPlaneCoordsLSST, '_z_fitter'): 

108 pupilCoordsFromFocalPlaneCoordsLSST._z_fitter = LsstZernikeFitter() 

109 

110 if isinstance(xmm, numbers.Number): 

111 if np.isnan(xmm) or np.isnan(ymm): 

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

113 

114 z_fitter = pupilCoordsFromFocalPlaneCoordsLSST._z_fitter 

115 dx, dy = z_fitter.dxdy_inverse(xmm, ymm, band) 

116 x_f1 = xmm + dx 

117 y_f1 = ymm + dy 

118 xp, yp = pupilCoordsFromFocalPlaneCoords(x_f1, y_f1, camera=lsst_camera()) 

119 

120 if not isinstance(xmm, numbers.Number): 

121 nan_dex = np.where(np.logical_or(np.isnan(xmm), np.isnan(ymm))) 

122 xp[nan_dex] = np.NaN 

123 yp[nan_dex] = np.NaN 

124 

125 return np.array([xp, yp]) 

126 

127 

128def _build_lsst_focal_coord_map(): 

129 """ 

130 Build a map of focal plane coordinates on the LSST focal plane. 

131 Returns _lsst_focal_coord_map, which is a dict. 

132 _lsst_focal_coord_map['name'] contains a list of the names of each chip in the lsst camera 

133 _lsst_focal_coord_map['xx'] contains the x focal plane coordinate of the center of each chip (mm) 

134 _lsst_focal_coord_map['yy'] contains the y focal plane coordinate of the center of each chip (mm) 

135 _lsst_focal_coord_map['dp'] contains the radius (in mm) of the circle containing each chip 

136 """ 

137 

138 camera = lsst_camera() 

139 

140 name_list = [] 

141 x_pix_list = [] 

142 y_pix_list = [] 

143 x_mm_list = [] 

144 y_mm_list = [] 

145 n_chips = 0 

146 for chip in camera: 

147 chip_name = chip.getName() 

148 pixels_to_focal = chip.getTransform(PIXELS, FOCAL_PLANE) 

149 n_chips += 1 

150 corner_list = getCornerPixels(chip_name, lsst_camera()) 

151 for corner in corner_list: 

152 x_pix_list.append(corner[0]) 

153 y_pix_list.append(corner[1]) 

154 pixel_pt = geom.Point2D(corner[0], corner[1]) 

155 focal_pt = pixels_to_focal.applyForward(pixel_pt) 

156 x_mm_list.append(focal_pt.getX()) 

157 y_mm_list.append(focal_pt.getY()) 

158 name_list.append(chip_name) 

159 

160 x_pix_list = np.array(x_pix_list) 

161 y_pix_list = np.array(y_pix_list) 

162 x_mm_list = np.array(x_mm_list) 

163 y_mm_list = np.array(y_mm_list) 

164 

165 center_x = np.zeros(n_chips, dtype=float) 

166 center_y = np.zeros(n_chips, dtype=float) 

167 extent = np.zeros(n_chips, dtype=float) 

168 final_name = [] 

169 for ix_ct in range(n_chips): 

170 ix = ix_ct*4 

171 chip_name = name_list[ix] 

172 xx = 0.25*(x_mm_list[ix] + x_mm_list[ix+1] + 

173 x_mm_list[ix+2] + x_mm_list[ix+3]) 

174 

175 yy = 0.25*(y_mm_list[ix] + y_mm_list[ix+1] + 

176 y_mm_list[ix+2] + y_mm_list[ix+3]) 

177 

178 dx = 0.25*np.array([np.sqrt(np.power(xx-x_mm_list[ix+ii], 2) + 

179 np.power(yy-y_mm_list[ix+ii], 2)) for ii in range(4)]).sum() 

180 

181 center_x[ix_ct] = xx 

182 center_y[ix_ct] = yy 

183 extent[ix_ct] = dx 

184 final_name.append(chip_name) 

185 

186 final_name = np.array(final_name) 

187 

188 lsst_focal_coord_map = {} 

189 lsst_focal_coord_map['name'] = final_name 

190 lsst_focal_coord_map['xx'] = center_x 

191 lsst_focal_coord_map['yy'] = center_y 

192 lsst_focal_coord_map['dp'] = extent 

193 return lsst_focal_coord_map 

194 

195 

196def _findDetectorsListLSST(focalPointList, detectorList, possible_points, 

197 allow_multiple_chips=False): 

198 """!Find the detectors that cover a list of points specified by x and y coordinates in any system 

199 

200 This is based one afw.camerGeom.camera.findDetectorsList. It has been optimized for the LSST 

201 camera in the following way: 

202 

203 - it accepts a limited list of detectors to check in advance (this list should be 

204 constructed by comparing the pupil coordinates in question and comparing to the 

205 pupil coordinates of the center of each detector) 

206 

207 - it will stop looping through detectors one it has found one that is correct (the LSST 

208 camera does not allow an object to fall on more than one detector) 

209 

210 @param[in] focalPointList a list of points in FOCAL_PLANE coordinates 

211 

212 @param[in] detectorList is a list of the afwCameraGeom detector objects being considered 

213 

214 @param[in] possible_points is a list of lists. possible_points[ii] is a list of integers 

215 corresponding to the indices in focalPointList of the pupilPoints that may be on detectorList[ii]. 

216 

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

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

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

220 chipNames but NO WARNING WILL BE EMITTED. If it is 'True' and an object falls on more than one 

221 chip, a list of chipNames will appear for that object. 

222 

223 @return outputNameList is a numpy array of the names of the detectors 

224 """ 

225 # transform the points to the native coordinate system 

226 # 

227 # The conversion to a numpy array looks a little clunky. 

228 # The problem, if you do the naive thing (nativePointList = np.array(lsst_camera().....), 

229 # the conversion to a numpy array gets passed down to the contents of nativePointList 

230 # and they end up in a form that the afwCameraGeom code does not know how to handle 

231 nativePointList = np.zeros(len(focalPointList), dtype=object) 

232 for ii in range(len(focalPointList)): 

233 nativePointList[ii] = focalPointList[ii] 

234 

235 # initialize output and some caching lists 

236 outputNameList = [None]*len(focalPointList) 

237 chip_has_found = np.array([-1]*len(focalPointList)) 

238 unfound_pts = len(chip_has_found) 

239 

240 # Figure out if any of these (RA, Dec) pairs could be 

241 # on more than one chip. This is possible on the 

242 # wavefront sensors, since adjoining wavefront sensors 

243 # are kept one in focus, one out of focus. 

244 # See figure 2 of arXiv:1506.04839v2 

245 # (This might actually be a bug in obs_lsstSim 

246 # I opened DM-8075 on 25 October 2016 to investigate) 

247 could_be_multiple = [False]*len(focalPointList) 

248 if allow_multiple_chips: 

249 for ipt in range(len(focalPointList)): 

250 for det in detectorList[ipt]: 

251 if det.getType() == DetectorType.WAVEFRONT: 

252 could_be_multiple[ipt] = True 

253 

254 # t_assemble_list = 0.0 

255 # loop over detectors 

256 for i_detector, detector in enumerate(detectorList): 

257 if len(possible_points[i_detector]) == 0: 

258 continue 

259 

260 if unfound_pts <= 0: 

261 if unfound_pts<0: 

262 raise RuntimeError("Somehow, unfound_pts = %d in _findDetectorsListLSST" % unfound_pts) 

263 # we have already found all of the (RA, Dec) pairs 

264 for ix, name in enumerate(outputNameList): 

265 if isinstance(name, list): 

266 outputNameList[ix] = str(name) 

267 return np.array(outputNameList) 

268 

269 # find all of the pupil points that could be on this detector 

270 valid_pt_dexes = possible_points[i_detector][np.where(chip_has_found[possible_points[i_detector]]<0)] 

271 

272 if len(valid_pt_dexes) > 0: 

273 valid_pt_list = nativePointList[valid_pt_dexes] 

274 transform = detector.getTransform(FOCAL_PLANE, PIXELS) 

275 detectorPointList = transform.applyForward(valid_pt_list) 

276 

277 box = geom.Box2D(detector.getBBox()) 

278 for ix, pt in zip(valid_pt_dexes, detectorPointList): 

279 if box.contains(pt): 

280 if not could_be_multiple[ix]: 

281 # because this (RA, Dec) pair is not marked as could_be_multiple, 

282 # the fact that this (RA, Dec) pair is on the current chip 

283 # means this (RA, Dec) pair no longer needs to be considered. 

284 # You can set chip_has_found[ix] to unity. 

285 outputNameList[ix] = detector.getName() 

286 chip_has_found[ix] = 1 

287 unfound_pts -= 1 

288 else: 

289 # Since this (RA, Dec) pair has been makred could_be_multiple, 

290 # finding this (RA, Dec) pair on the chip does not remove the 

291 # (RA, Dec) pair from contention. 

292 if outputNameList[ix] is None: 

293 outputNameList[ix] = detector.getName() 

294 elif isinstance(outputNameList[ix], list): 

295 outputNameList[ix].append(detector.getName()) 

296 else: 

297 outputNameList[ix] = [outputNameList[ix], detector.getName()] 

298 

299 # convert entries corresponding to multiple chips into strings 

300 # (i.e. [R:2,2 S:0,0, R:2,2 S:0,1] becomes `[R:2,2 S:0,0, R:2,2 S:0,1]`) 

301 for ix, name in enumerate(outputNameList): 

302 if isinstance(name, list): 

303 outputNameList[ix] = str(name) 

304 

305 # print('t_assemble %.2e' % t_assemble_list) 

306 

307 return np.array(outputNameList) 

308 

309 

310def chipNameFromPupilCoordsLSST(xPupil_in, yPupil_in, allow_multiple_chips=False, band='r'): 

311 """ 

312 Return the names of LSST detectors that see the object specified by 

313 either (xPupil, yPupil). 

314 

315 @param [in] xPupil_in is the x pupil coordinate in radians. 

316 Must be a numpy array. 

317 

318 @param [in] yPupil_in is the y pupil coordinate in radians. 

319 Must be a numpy array. 

320 

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

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

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

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

325 chip, a list of chipNames will appear for that object. 

326 

327 @param[in] band is the bandpass being simulated (default='r') 

328 

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

330 

331 """ 

332 if (not hasattr(chipNameFromPupilCoordsLSST, '_focal_map') or 

333 not hasattr(chipNameFromPupilCoordsLSST, '_detector_arr') or 

334 len(chipNameFromPupilCoordsLSST._detector_arr) == 0): 

335 focal_map = _build_lsst_focal_coord_map() 

336 chipNameFromPupilCoordsLSST._focal_map = focal_map 

337 camera = lsst_camera() 

338 detector_arr = np.zeros(len(focal_map['name']), dtype=object) 

339 for ii in range(len(focal_map['name'])): 

340 detector_arr[ii] = camera[focal_map['name'][ii]] 

341 

342 chipNameFromPupilCoordsLSST._detector_arr = detector_arr 

343 

344 # build a Box2D that contains all of the detectors in the camera 

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

346 focal_bbox = camera.getFpBBox() 

347 focal_corners = focal_bbox.getCorners() 

348 camera_bbox = geom.Box2D() 

349 x_focal_max = None 

350 x_focal_min = None 

351 y_focal_max = None 

352 y_focal_min = None 

353 for cc in focal_corners: 

354 xx = cc.getX() 

355 yy = cc.getY() 

356 if x_focal_max is None or xx > x_focal_max: 

357 x_focal_max = xx 

358 if x_focal_min is None or xx < x_focal_min: 

359 x_focal_min = xx 

360 if y_focal_max is None or yy > y_focal_max: 

361 y_focal_max = yy 

362 if y_focal_min is None or yy < y_focal_min: 

363 y_focal_min = yy 

364 

365 chipNameFromPupilCoordsLSST._x_focal_center = 0.5*(x_focal_max+x_focal_min) 

366 chipNameFromPupilCoordsLSST._y_focal_center = 0.5*(y_focal_max+y_focal_min) 

367 

368 radius_sq_max = None 

369 for cc in focal_corners: 

370 xx = cc.getX() 

371 yy = cc.getY() 

372 radius_sq = ((xx-chipNameFromPupilCoordsLSST._x_focal_center)**2 + 

373 (yy-chipNameFromPupilCoordsLSST._y_focal_center)**2) 

374 if radius_sq_max is None or radius_sq > radius_sq_max: 

375 radius_sq_max = radius_sq 

376 

377 chipNameFromPupilCoordsLSST._camera_focal_radius_sq = radius_sq_max*1.1 

378 

379 are_arrays = _validate_inputs([xPupil_in, yPupil_in], ['xPupil_in', 'yPupil_in'], 

380 "chipNameFromPupilCoordsLSST") 

381 

382 if not are_arrays: 

383 xPupil_in = np.array([xPupil_in]) 

384 yPupil_in = np.array([yPupil_in]) 

385 

386 xFocal, yFocal = focalPlaneCoordsFromPupilCoordsLSST(xPupil_in, yPupil_in, band=band) 

387 

388 radius_sq_list = ((xFocal-chipNameFromPupilCoordsLSST._x_focal_center)**2 + 

389 (yFocal-chipNameFromPupilCoordsLSST._y_focal_center)**2) 

390 

391 with np.errstate(invalid='ignore'): 

392 good_radii = np.where(radius_sq_list<chipNameFromPupilCoordsLSST._camera_focal_radius_sq) 

393 

394 if len(good_radii[0]) == 0: 

395 return np.array([None]*len(xPupil_in)) 

396 

397 xFocal_good = xFocal[good_radii] 

398 yFocal_good = yFocal[good_radii] 

399 

400 ############################################################ 

401 # in the code below, we will only consider those points which 

402 # passed the 'good_radii' test above; the other points will 

403 # be added in with chipName == None at the end 

404 # 

405 focalPointList = [geom.Point2D(xFocal[i_pt], yFocal[i_pt]) 

406 for i_pt in good_radii[0]] 

407 

408 # Loop through every detector on the camera. For each detector, assemble a list of points 

409 # whose centers are within 1.1 detector radii of the center of the detector. 

410 

411 x_cam_list = chipNameFromPupilCoordsLSST._focal_map['xx'] 

412 y_cam_list = chipNameFromPupilCoordsLSST._focal_map['yy'] 

413 rrsq_lim_list = (1.1*chipNameFromPupilCoordsLSST._focal_map['dp'])**2 

414 

415 possible_points = [] 

416 for i_chip, (x_cam, y_cam, rrsq_lim) in \ 

417 enumerate(zip(x_cam_list, y_cam_list, rrsq_lim_list)): 

418 

419 local_possible_pts = np.where(((xFocal_good - x_cam)**2 + 

420 (yFocal_good - y_cam)**2) < rrsq_lim)[0] 

421 

422 possible_points.append(local_possible_pts) 

423 

424 nameList_good = _findDetectorsListLSST(focalPointList, 

425 chipNameFromPupilCoordsLSST._detector_arr, 

426 possible_points, 

427 allow_multiple_chips=allow_multiple_chips) 

428 

429 #################################################################### 

430 # initialize output as an array of Nones, effectively adding back in 

431 # the points which failed the initial radius cut 

432 nameList = np.array([None]*len(xPupil_in)) 

433 

434 nameList[good_radii] = nameList_good 

435 

436 if not are_arrays: 

437 return nameList[0] 

438 

439 return nameList 

440 

441 

442def _chipNameFromRaDecLSST(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

443 obs_metadata=None, epoch=2000.0, allow_multiple_chips=False, 

444 band='r'): 

445 """ 

446 Return the names of detectors on the LSST camera that see the object specified by 

447 (RA, Dec) in radians. 

448 

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

450 In the International Celestial Reference System. 

451 

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

453 In the International Celestial Reference System. 

454 

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

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

457 

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

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

460 

461 @param [in] parallax is parallax in radians 

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

463 

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

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

466 

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

468 

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

470 measured. Default is 2000. 

471 

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

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

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

475 chipNames but NO WARNING WILL BE EMITTED. If it is 'True' and an object falls on more than one 

476 chip, a list of chipNames will appear for that object. 

477 

478 @param [in] band is the filter we are simulating (Default=r) 

479 

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

481 array if more than one) 

482 """ 

483 

484 are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDecLSST") 

485 

486 if epoch is None: 

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

488 

489 if obs_metadata is None: 

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

491 

492 if obs_metadata.mjd is None: 

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

494 

495 if obs_metadata.rotSkyPos is None: 

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

497 

498 xp, yp = _pupilCoordsFromRaDec(ra, dec, 

499 pm_ra=pm_ra, pm_dec=pm_dec, 

500 parallax=parallax, v_rad=v_rad, 

501 obs_metadata=obs_metadata, epoch=epoch) 

502 

503 return chipNameFromPupilCoordsLSST(xp, yp, allow_multiple_chips=allow_multiple_chips, 

504 band=band) 

505 

506 

507def chipNameFromRaDecLSST(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

508 obs_metadata=None, epoch=2000.0, allow_multiple_chips=False, 

509 band='r'): 

510 """ 

511 Return the names of detectors on the LSST camera that see the object specified by 

512 (RA, Dec) in degrees. 

513 

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

515 In the International Celestial Reference System. 

516 

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

518 In the International Celestial Reference System. 

519 

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

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

522 

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

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

525 

526 @param [in] parallax is parallax in arcsec 

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

528 

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

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

531 

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

533 

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

535 measured. Default is 2000. 

536 

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

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

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

540 chipNames but NO WARNING WILL BE EMITTED. If it is 'True' and an object falls on more than one 

541 chip, a list of chipNames will appear for that object. 

542 

543 @param [in] band is the filter that we are simulating (Default=r) 

544 

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

546 array if more than one) 

547 """ 

548 if pm_ra is not None: 

549 pm_ra_out = radiansFromArcsec(pm_ra) 

550 else: 

551 pm_ra_out = None 

552 

553 if pm_dec is not None: 

554 pm_dec_out = radiansFromArcsec(pm_dec) 

555 else: 

556 pm_dec_out = None 

557 

558 if parallax is not None: 

559 parallax_out = radiansFromArcsec(parallax) 

560 else: 

561 parallax_out = None 

562 

563 return _chipNameFromRaDecLSST(np.radians(ra), np.radians(dec), 

564 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

565 parallax=parallax_out, v_rad=v_rad, 

566 obs_metadata=obs_metadata, epoch=epoch, 

567 allow_multiple_chips=allow_multiple_chips, 

568 band=band) 

569 

570 

571def pupilCoordsFromPixelCoordsLSST(xPix, yPix, chipName=None, band="r", 

572 includeDistortion=True): 

573 """ 

574 Convert pixel coordinates into radians on the pupil 

575 

576 Parameters 

577 ---------- 

578 xPix -- the x pixel coordinate 

579 

580 yPix -- the y pixel coordinate 

581 

582 chipName -- the name(s) of the chips on which xPix, yPix are reckoned 

583 

584 band -- the filter we are simulating (default=r) 

585 

586 includeDistortion -- a boolean which turns on or off optical 

587 distortions (default=True) 

588 

589 Returns 

590 ------- 

591 a 2-D numpy array in which the first row is the x 

592 pupil coordinate and the second row is the y pupil 

593 coordinate (both in radians) 

594 """ 

595 

596 if not includeDistortion: 

597 return pupilCoordsFromPixelCoords(xPix, yPix, chipName=chipName, 

598 camera=lsst_camera(), 

599 includeDistortion=includeDistortion) 

600 

601 are_arrays, \ 

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

603 "pupilCoordsFromPixelCoords", 

604 chipName, 

605 chipname_can_be_none=False) 

606 

607 pixel_to_focal_dict = {} 

608 camera = lsst_camera() 

609 name_to_int = {} 

610 name_to_int[None] = 0 

611 name_to_int['None'] = 0 

612 ii = 1 

613 

614 if are_arrays: 

615 chip_name_int = np.zeros(len(xPix), dtype=int) 

616 

617 have_transform = set() 

618 for i_obj, name in enumerate(chipNameList): 

619 if name not in have_transform and name is not None and name != 'None': 

620 pixel_to_focal_dict[name] = camera[name].getTransform(PIXELS, FOCAL_PLANE) 

621 name_to_int[name] = ii 

622 have_transform.add(name) 

623 ii += 1 

624 

625 if are_arrays: 

626 chip_name_int[i_obj] = name_to_int[name] 

627 

628 if are_arrays: 

629 x_f = np.zeros(len(xPix), dtype=float) 

630 y_f = np.zeros(len(yPix), dtype=float) 

631 

632 for name in name_to_int: 

633 local_int = name_to_int[name] 

634 local_valid = np.where(chip_name_int==local_int) 

635 if len(local_valid[0]) == 0: 

636 continue 

637 

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

639 x_f[local_valid] = np.NaN 

640 y_f[local_valid] = np.NaN 

641 continue 

642 

643 pixel_pt_arr = [geom.Point2D(xPix[ii], yPix[ii]) 

644 for ii in local_valid[0]] 

645 

646 focal_pt_arr = pixel_to_focal_dict[name].applyForward(pixel_pt_arr) 

647 focal_coord_arr = np.array([[pt.getX(), pt.getY()] 

648 for pt in focal_pt_arr]).transpose() 

649 x_f[local_valid] = focal_coord_arr[0] 

650 y_f[local_valid] = focal_coord_arr[1] 

651 

652 else: 

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

654 x_f = np.NaN 

655 y_f = np.NaN 

656 else: 

657 pixel_pt = geom.Point2D(xPix, yPix) 

658 focal_pt = pixel_to_focal_dict[chipNameList[0]].applyForward(pixel_pt) 

659 x_f = focal_pt.getX() 

660 y_f = focal_pt.getY() 

661 

662 return pupilCoordsFromFocalPlaneCoordsLSST(x_f, y_f, band=band) 

663 

664 

665def pixelCoordsFromPupilCoordsLSST(xPupil, yPupil, chipName=None, band="r", 

666 includeDistortion=True): 

667 """ 

668 Convert radians on the pupil into pixel coordinates. 

669 

670 Parameters 

671 ---------- 

672 xPupil -- is the x coordinate on the pupil in radians 

673 

674 yPupil -- is the y coordinate on the pupil in radians 

675 

676 chipName -- designates the names of the chips on which the pixel 

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

678 If an array, there must be as many chipNames as there are (xPupil, yPupil) pairs. 

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

680 chip. If None, this method will calculate which chip each(xPupil, yPupil) pair 

681 actually falls on, and return pixel coordinates for each (xPupil, yPupil) pair on 

682 the appropriate chip. Default is None. 

683 

684 band -- the filter we are simulating (default=r) 

685 

686 includeDistortion -- a boolean which turns on and off optical distortions 

687 (default=True) 

688 

689 Returns 

690 ------- 

691 a 2-D numpy array in which the first row is the x pixel coordinate 

692 and the second row is the y pixel coordinate 

693 """ 

694 

695 if not includeDistortion: 

696 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipName, 

697 camera=lsst_camera(), 

698 includeDistortion=includeDistortion) 

699 

700 are_arrays, \ 

701 chipNameList = _validate_inputs_and_chipname([xPupil, yPupil], 

702 ['xPupil', 'yPupil'], 

703 'pixelCoordsFromPupilCoordsLSST', 

704 chipName) 

705 

706 if chipNameList is None: 

707 chipNameList = chipNameFromPupilCoordsLSST(xPupil, yPupil) 

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

709 chipNameList = np.array([chipNameList]) 

710 else: 

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

712 chipNameList = np.array([chipNameList]) 

713 elif isinstance(chipNameList, list): 

714 chipNameList = np.array(chipNameList) 

715 

716 x_f, y_f = focalPlaneCoordsFromPupilCoordsLSST(xPupil, yPupil, band=band) 

717 

718 if are_arrays: 

719 

720 has_transform = set() 

721 focal_to_pixel_dict = {} 

722 chip_name_int = np.zeros(len(x_f), dtype=int) 

723 name_to_int = {} 

724 name_to_int[None] = 0 

725 name_to_int['None'] = 0 

726 ii = 1 

727 for i_obj, chip_name in enumerate(chipNameList): 

728 if chip_name not in has_transform and chip_name is not None and chip_name != 'None': 

729 has_transform.add(chip_name) 

730 focal_to_pixel_dict[chip_name] = lsst_camera()[chip_name].getTransform(FOCAL_PLANE, PIXELS) 

731 name_to_int[chip_name] = ii 

732 ii += 1 

733 

734 chip_name_int[i_obj] = name_to_int[chip_name] 

735 

736 x_pix = np.NaN*np.ones(len(x_f), dtype=float) 

737 y_pix = np.NaN*np.ones(len(x_f), dtype=float) 

738 

739 for chip_name in has_transform: 

740 if chip_name == 'None' or chip_name is None: 

741 continue 

742 

743 local_int = name_to_int[chip_name] 

744 local_valid = np.where(chip_name_int == local_int) 

745 if len(local_valid[0]) == 0: 

746 continue 

747 focal_pt_arr = [geom.Point2D(x_f[ii], y_f[ii]) 

748 for ii in local_valid[0]] 

749 pixel_pt_arr = focal_to_pixel_dict[chip_name].applyForward(focal_pt_arr) 

750 pixel_coord_arr = np.array([[pp.getX(), pp.getY()] 

751 for pp in pixel_pt_arr]).transpose() 

752 

753 x_pix[local_valid] = pixel_coord_arr[0] 

754 y_pix[local_valid] = pixel_coord_arr[1] 

755 else: 

756 chip_name = chipNameList[0] 

757 if chip_name is None: 

758 x_pix = np.NaN 

759 y_pix = np.NaN 

760 else: 

761 det = lsst_camera()[chip_name] 

762 focal_to_pixels = det.getTransform(FOCAL_PLANE, PIXELS) 

763 focal_pt = geom.Point2D(x_f, y_f) 

764 pixel_pt = focal_to_pixels.applyForward(focal_pt) 

765 x_pix= pixel_pt.getX() 

766 y_pix = pixel_pt.getY() 

767 

768 return np.array([x_pix, y_pix]) 

769 

770 

771def _pixelCoordsFromRaDecLSST(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

772 obs_metadata=None, 

773 chipName=None, camera=None, 

774 epoch=2000.0, includeDistortion=True, 

775 band='r'): 

776 """ 

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

778 on their RA, and Dec (in radians) 

779 

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

781 Can be either a float or a numpy array. 

782 

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

784 Can be either a float or a numpy array. 

785 

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

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

788 

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

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

791 

792 @param [in] parallax is parallax in radians 

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

794 

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

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

797 

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

799 pointing. 

800 

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

802 RA is measured. Default is 2000. 

803 

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

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

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

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

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

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

810 chip. Default is None. 

811 

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

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

814 

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

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

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

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

819 details. 

820 

821 @param [in] band is the filter we are simulating ('u', 'g', 'r', etc.) Default='r' 

822 

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

824 and the second row is the y pixel coordinate 

825 """ 

826 

827 if epoch is None: 

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

829 

830 if obs_metadata is None: 

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

832 

833 if obs_metadata.mjd is None: 

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

835 "pixelCoordsFromRaDec") 

836 

837 if obs_metadata.rotSkyPos is None: 

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

839 "pixelCoordsFromRaDec") 

840 

841 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec, 

842 pm_ra=pm_ra, pm_dec=pm_dec, 

843 parallax=parallax, v_rad=v_rad, 

844 obs_metadata=obs_metadata, epoch=epoch) 

845 

846 return pixelCoordsFromPupilCoordsLSST(xPupil, yPupil, chipName=chipName, band=band, 

847 includeDistortion=includeDistortion) 

848 

849 

850def pixelCoordsFromRaDecLSST(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None, 

851 obs_metadata=None, chipName=None, 

852 epoch=2000.0, includeDistortion=True, 

853 band='r'): 

854 """ 

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

856 on their RA, and Dec (in degrees) 

857 

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

859 Can be either a float or a numpy array. 

860 

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

862 Can be either a float or a numpy array. 

863 

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

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

866 

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

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

869 

870 @param [in] parallax is parallax in arcsec 

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

872 

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

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

875 

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

877 pointing. 

878 

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

880 RA is measured. Default is 2000. 

881 

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

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

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

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

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

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

888 chip. Default is None. 

889 

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

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

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

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

894 details. 

895 

896 @param [in] band is the filter we are simulating ('u', 'g', 'r', etc.) Default='r' 

897 

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

899 and the second row is the y pixel coordinate 

900 """ 

901 

902 if pm_ra is not None: 

903 pm_ra_out = radiansFromArcsec(pm_ra) 

904 else: 

905 pm_ra_out = None 

906 

907 if pm_dec is not None: 

908 pm_dec_out = radiansFromArcsec(pm_dec) 

909 else: 

910 pm_dec_out = None 

911 

912 if parallax is not None: 

913 parallax_out = radiansFromArcsec(parallax) 

914 else: 

915 parallax_out = None 

916 

917 return _pixelCoordsFromRaDecLSST(np.radians(ra), np.radians(dec), 

918 pm_ra=pm_ra_out, pm_dec=pm_dec_out, 

919 parallax=parallax_out, v_rad=v_rad, 

920 chipName=chipName, obs_metadata=obs_metadata, 

921 epoch=2000.0, includeDistortion=includeDistortion, 

922 band=band) 

923 

924 

925def _raDecFromPixelCoordsLSST(xPix, yPix, chipName, band='r', 

926 obs_metadata=None, epoch=2000.0, 

927 includeDistortion=True): 

928 """ 

929 Convert pixel coordinates into RA, Dec 

930 

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

932 a float or a numpy array. 

933 

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

935 a float or a numpy array. 

936 

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

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

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

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

941 

942 @param [in] band is the filter we are simulating (default=r) 

943 

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

945 

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

947 Default is 2000. 

948 

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

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

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

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

953 details. 

954 

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

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

957 Celestial Reference System) 

958 

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

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

961 to positions on the celestial sphere. 

962 """ 

963 

964 are_arrays, \ 

965 chipNameList = _validate_inputs_and_chipname([xPix, yPix], 

966 ['xPix', 'yPix'], 

967 'raDecFromPixelCoords', 

968 chipName, 

969 chipname_can_be_none=False) 

970 

971 if epoch is None: 

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

973 

974 if obs_metadata is None: 

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

976 

977 if obs_metadata.mjd is None: 

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

979 

980 if obs_metadata.rotSkyPos is None: 

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

982 

983 xPupilList, yPupilList = pupilCoordsFromPixelCoordsLSST(xPix, yPix, 

984 chipNameList, 

985 band=band, 

986 includeDistortion=includeDistortion) 

987 

988 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList, 

989 obs_metadata=obs_metadata, epoch=epoch) 

990 

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

992 

993 

994 

995def raDecFromPixelCoordsLSST(xPix, yPix, chipName, band='r', 

996 obs_metadata=None, epoch=2000.0, 

997 includeDistortion=True): 

998 """ 

999 Convert pixel coordinates into RA, Dec 

1000 

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

1002 a float or a numpy array. 

1003 

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

1005 a float or a numpy array. 

1006 

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

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

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

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

1011 

1012 @param [in] band is the filter we are simulating (default=r) 

1013 

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

1015 

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

1017 Default is 2000. 

1018 

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

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

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

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

1023 details. 

1024 

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

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

1027 Celestial Reference System) 

1028 

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

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

1031 to positions on the celestial sphere. 

1032 """ 

1033 output = _raDecFromPixelCoordsLSST(xPix, yPix, chipName, band=band, 

1034 obs_metadata=obs_metadata, 

1035 epoch=epoch, 

1036 includeDistortion=includeDistortion) 

1037 

1038 return np.degrees(output)