Coverage for python/lsst/sims/coordUtils/LsstCameraUtils.py : 6%

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
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"]
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
45def focalPlaneCoordsFromPupilCoordsLSST(xPupil, yPupil, band='r'):
46 """
47 Get the focal plane coordinates for all objects in the catalog.
49 Parameters
50 ----------
51 xPupil -- the x pupil coordinates in radians.
52 Can be a float or a numpy array.
54 yPupil -- the y pupil coordinates in radians.
55 Can be a float or a numpy array.
57 band -- the filter being simulated (default='r')
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 """
66 if not hasattr(focalPlaneCoordsFromPupilCoordsLSST, '_z_fitter'):
67 focalPlaneCoordsFromPupilCoordsLSST._z_fitter = LsstZernikeFitter()
69 if isinstance(xPupil, numbers.Number):
70 if np.isnan(xPupil) or np.isnan(yPupil):
71 return np.array([np.NaN, np.NaN])
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)
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
82 return np.array([x_f0+dx, y_f0+dy])
85def pupilCoordsFromFocalPlaneCoordsLSST(xmm, ymm, band='r'):
86 """
87 Convert mm on the focal plane to radians on the pupil.
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.
93 Parameters
94 ----------
95 xmm -- x coordinate in millimeters on the focal plane
97 ymm -- y coordinate in millimeters on the focal plane
99 band -- the filter we are simulating (default='r')
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()
110 if isinstance(xmm, numbers.Number):
111 if np.isnan(xmm) or np.isnan(ymm):
112 return np.array([np.NaN, np.NaN])
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())
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
125 return np.array([xp, yp])
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 """
138 camera = lsst_camera()
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)
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)
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])
175 yy = 0.25*(y_mm_list[ix] + y_mm_list[ix+1] +
176 y_mm_list[ix+2] + y_mm_list[ix+3])
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()
181 center_x[ix_ct] = xx
182 center_y[ix_ct] = yy
183 extent[ix_ct] = dx
184 final_name.append(chip_name)
186 final_name = np.array(final_name)
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
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
200 This is based one afw.camerGeom.camera.findDetectorsList. It has been optimized for the LSST
201 camera in the following way:
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)
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)
210 @param[in] focalPointList a list of points in FOCAL_PLANE coordinates
212 @param[in] detectorList is a list of the afwCameraGeom detector objects being considered
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].
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.
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]
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)
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
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
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)
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)]
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)
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()]
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)
305 # print('t_assemble %.2e' % t_assemble_list)
307 return np.array(outputNameList)
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).
315 @param [in] xPupil_in is the x pupil coordinate in radians.
316 Must be a numpy array.
318 @param [in] yPupil_in is the y pupil coordinate in radians.
319 Must be a numpy array.
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.
327 @param[in] band is the bandpass being simulated (default='r')
329 @param [out] a numpy array of chip names
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]]
342 chipNameFromPupilCoordsLSST._detector_arr = detector_arr
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
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)
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
377 chipNameFromPupilCoordsLSST._camera_focal_radius_sq = radius_sq_max*1.1
379 are_arrays = _validate_inputs([xPupil_in, yPupil_in], ['xPupil_in', 'yPupil_in'],
380 "chipNameFromPupilCoordsLSST")
382 if not are_arrays:
383 xPupil_in = np.array([xPupil_in])
384 yPupil_in = np.array([yPupil_in])
386 xFocal, yFocal = focalPlaneCoordsFromPupilCoordsLSST(xPupil_in, yPupil_in, band=band)
388 radius_sq_list = ((xFocal-chipNameFromPupilCoordsLSST._x_focal_center)**2 +
389 (yFocal-chipNameFromPupilCoordsLSST._y_focal_center)**2)
391 with np.errstate(invalid='ignore'):
392 good_radii = np.where(radius_sq_list<chipNameFromPupilCoordsLSST._camera_focal_radius_sq)
394 if len(good_radii[0]) == 0:
395 return np.array([None]*len(xPupil_in))
397 xFocal_good = xFocal[good_radii]
398 yFocal_good = yFocal[good_radii]
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]]
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.
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
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)):
419 local_possible_pts = np.where(((xFocal_good - x_cam)**2 +
420 (yFocal_good - y_cam)**2) < rrsq_lim)[0]
422 possible_points.append(local_possible_pts)
424 nameList_good = _findDetectorsListLSST(focalPointList,
425 chipNameFromPupilCoordsLSST._detector_arr,
426 possible_points,
427 allow_multiple_chips=allow_multiple_chips)
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))
434 nameList[good_radii] = nameList_good
436 if not are_arrays:
437 return nameList[0]
439 return nameList
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.
449 @param [in] ra in radians (a numpy array or a float).
450 In the International Celestial Reference System.
452 @param [in] dec in radians (a numpy array or a float).
453 In the International Celestial Reference System.
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).
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).
461 @param [in] parallax is parallax in radians
462 Can be a numpy array or a number or None (default=None).
464 @param [in] v_rad is radial velocity (km/s)
465 Can be a numpy array or a number or None (default=None).
467 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
469 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are
470 measured. Default is 2000.
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.
478 @param [in] band is the filter we are simulating (Default=r)
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 """
484 are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDecLSST")
486 if epoch is None:
487 raise RuntimeError("You need to pass an epoch into chipName")
489 if obs_metadata is None:
490 raise RuntimeError("You need to pass an ObservationMetaData into chipName")
492 if obs_metadata.mjd is None:
493 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into chipName")
495 if obs_metadata.rotSkyPos is None:
496 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into chipName")
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)
503 return chipNameFromPupilCoordsLSST(xp, yp, allow_multiple_chips=allow_multiple_chips,
504 band=band)
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.
514 @param [in] ra in degrees (a numpy array or a float).
515 In the International Celestial Reference System.
517 @param [in] dec in degrees (a numpy array or a float).
518 In the International Celestial Reference System.
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).
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).
526 @param [in] parallax is parallax in arcsec
527 Can be a numpy array or a number or None (default=None).
529 @param [in] v_rad is radial velocity (km/s)
530 Can be a numpy array or a number or None (default=None).
532 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
534 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are
535 measured. Default is 2000.
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.
543 @param [in] band is the filter that we are simulating (Default=r)
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
553 if pm_dec is not None:
554 pm_dec_out = radiansFromArcsec(pm_dec)
555 else:
556 pm_dec_out = None
558 if parallax is not None:
559 parallax_out = radiansFromArcsec(parallax)
560 else:
561 parallax_out = None
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)
571def pupilCoordsFromPixelCoordsLSST(xPix, yPix, chipName=None, band="r",
572 includeDistortion=True):
573 """
574 Convert pixel coordinates into radians on the pupil
576 Parameters
577 ----------
578 xPix -- the x pixel coordinate
580 yPix -- the y pixel coordinate
582 chipName -- the name(s) of the chips on which xPix, yPix are reckoned
584 band -- the filter we are simulating (default=r)
586 includeDistortion -- a boolean which turns on or off optical
587 distortions (default=True)
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 """
596 if not includeDistortion:
597 return pupilCoordsFromPixelCoords(xPix, yPix, chipName=chipName,
598 camera=lsst_camera(),
599 includeDistortion=includeDistortion)
601 are_arrays, \
602 chipNameList = _validate_inputs_and_chipname([xPix, yPix], ['xPix', 'yPix'],
603 "pupilCoordsFromPixelCoords",
604 chipName,
605 chipname_can_be_none=False)
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
614 if are_arrays:
615 chip_name_int = np.zeros(len(xPix), dtype=int)
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
625 if are_arrays:
626 chip_name_int[i_obj] = name_to_int[name]
628 if are_arrays:
629 x_f = np.zeros(len(xPix), dtype=float)
630 y_f = np.zeros(len(yPix), dtype=float)
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
638 if name is None or name == 'None':
639 x_f[local_valid] = np.NaN
640 y_f[local_valid] = np.NaN
641 continue
643 pixel_pt_arr = [geom.Point2D(xPix[ii], yPix[ii])
644 for ii in local_valid[0]]
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]
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()
662 return pupilCoordsFromFocalPlaneCoordsLSST(x_f, y_f, band=band)
665def pixelCoordsFromPupilCoordsLSST(xPupil, yPupil, chipName=None, band="r",
666 includeDistortion=True):
667 """
668 Convert radians on the pupil into pixel coordinates.
670 Parameters
671 ----------
672 xPupil -- is the x coordinate on the pupil in radians
674 yPupil -- is the y coordinate on the pupil in radians
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.
684 band -- the filter we are simulating (default=r)
686 includeDistortion -- a boolean which turns on and off optical distortions
687 (default=True)
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 """
695 if not includeDistortion:
696 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipName,
697 camera=lsst_camera(),
698 includeDistortion=includeDistortion)
700 are_arrays, \
701 chipNameList = _validate_inputs_and_chipname([xPupil, yPupil],
702 ['xPupil', 'yPupil'],
703 'pixelCoordsFromPupilCoordsLSST',
704 chipName)
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)
716 x_f, y_f = focalPlaneCoordsFromPupilCoordsLSST(xPupil, yPupil, band=band)
718 if are_arrays:
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
734 chip_name_int[i_obj] = name_to_int[chip_name]
736 x_pix = np.NaN*np.ones(len(x_f), dtype=float)
737 y_pix = np.NaN*np.ones(len(x_f), dtype=float)
739 for chip_name in has_transform:
740 if chip_name == 'None' or chip_name is None:
741 continue
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()
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()
768 return np.array([x_pix, y_pix])
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)
780 @param [in] ra is in radians in the International Celestial Reference System.
781 Can be either a float or a numpy array.
783 @param [in] dec is in radians in the International Celestial Reference System.
784 Can be either a float or a numpy array.
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).
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).
792 @param [in] parallax is parallax in radians
793 Can be a numpy array or a number or None (default=None).
795 @param [in] v_rad is radial velocity (km/s)
796 Can be a numpy array or a number or None (default=None).
798 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
799 pointing.
801 @param [in] epoch is the epoch in Julian years of the equinox against which
802 RA is measured. Default is 2000.
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.
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.
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.
821 @param [in] band is the filter we are simulating ('u', 'g', 'r', etc.) Default='r'
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 """
827 if epoch is None:
828 raise RuntimeError("You need to pass an epoch into pixelCoordsFromRaDec")
830 if obs_metadata is None:
831 raise RuntimeError("You need to pass an ObservationMetaData into pixelCoordsFromRaDec")
833 if obs_metadata.mjd is None:
834 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into "
835 "pixelCoordsFromRaDec")
837 if obs_metadata.rotSkyPos is None:
838 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into "
839 "pixelCoordsFromRaDec")
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)
846 return pixelCoordsFromPupilCoordsLSST(xPupil, yPupil, chipName=chipName, band=band,
847 includeDistortion=includeDistortion)
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)
858 @param [in] ra is in degrees in the International Celestial Reference System.
859 Can be either a float or a numpy array.
861 @param [in] dec is in degrees in the International Celestial Reference System.
862 Can be either a float or a numpy array.
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).
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).
870 @param [in] parallax is parallax in arcsec
871 Can be a numpy array or a number or None (default=None).
873 @param [in] v_rad is radial velocity (km/s)
874 Can be a numpy array or a number or None (default=None).
876 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
877 pointing.
879 @param [in] epoch is the epoch in Julian years of the equinox against which
880 RA is measured. Default is 2000.
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.
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.
896 @param [in] band is the filter we are simulating ('u', 'g', 'r', etc.) Default='r'
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 """
902 if pm_ra is not None:
903 pm_ra_out = radiansFromArcsec(pm_ra)
904 else:
905 pm_ra_out = None
907 if pm_dec is not None:
908 pm_dec_out = radiansFromArcsec(pm_dec)
909 else:
910 pm_dec_out = None
912 if parallax is not None:
913 parallax_out = radiansFromArcsec(parallax)
914 else:
915 parallax_out = None
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)
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
931 @param [in] xPix is the x pixel coordinate. It can be either
932 a float or a numpy array.
934 @param [in] yPix is the y pixel coordinate. It can be either
935 a float or a numpy array.
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).
942 @param [in] band is the filter we are simulating (default=r)
944 @param [in] obs_metadata is an ObservationMetaData defining the pointing
946 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
947 Default is 2000.
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.
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)
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 """
964 are_arrays, \
965 chipNameList = _validate_inputs_and_chipname([xPix, yPix],
966 ['xPix', 'yPix'],
967 'raDecFromPixelCoords',
968 chipName,
969 chipname_can_be_none=False)
971 if epoch is None:
972 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying an epoch")
974 if obs_metadata is None:
975 raise RuntimeError("You cannot call raDecFromPixelCoords without an ObservationMetaData")
977 if obs_metadata.mjd is None:
978 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have an mjd")
980 if obs_metadata.rotSkyPos is None:
981 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have a rotSkyPos")
983 xPupilList, yPupilList = pupilCoordsFromPixelCoordsLSST(xPix, yPix,
984 chipNameList,
985 band=band,
986 includeDistortion=includeDistortion)
988 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList,
989 obs_metadata=obs_metadata, epoch=epoch)
991 return np.array([raOut, decOut])
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
1001 @param [in] xPix is the x pixel coordinate. It can be either
1002 a float or a numpy array.
1004 @param [in] yPix is the y pixel coordinate. It can be either
1005 a float or a numpy array.
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).
1012 @param [in] band is the filter we are simulating (default=r)
1014 @param [in] obs_metadata is an ObservationMetaData defining the pointing
1016 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
1017 Default is 2000.
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.
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)
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)
1038 return np.degrees(output)