Coverage for python/lsst/sims/coordUtils/CameraUtils.py : 7%

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
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"]
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
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.
32 input_list is a list of the inputs passed to a method.
34 input_name is a list of the variable names associated with
35 input_list
37 method_name is the name of the method whose input is being validated.
39 chip_name is the chip_name variable passed into the calling method.
41 chipname_can_be_none is a boolean that controls whether or not
42 chip_name is allowed to be None.
44 This method will raise a RuntimeError if:
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
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 """
59 are_arrays = _validate_inputs(input_list, input_names, method_name)
61 if chip_name is None and not chipname_can_be_none:
62 raise RuntimeError("You passed chipName=None to %s" % method_name)
64 if are_arrays:
65 n_pts = len(input_list[0])
66 else:
67 n_pts = 1
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]))
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
79 return are_arrays, chip_name_out
81 elif chip_name is None:
82 return are_arrays, chip_name
83 else:
84 return are_arrays, [chip_name]*n_pts
87def getCornerPixels(detector_name, camera):
88 """
89 Return the pixel coordinates of the corners of a detector.
91 @param [in] detector_name is the name of the detector in question
93 @param [in] camera is the afwCameraGeom camera object containing
94 that detector
96 @param [out] a list of tuples representing the (x,y) pixel coordinates
97 of the corners of the detector. Order will be
99 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)]
100 """
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)]
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.
117 @param [in] detector_name is the name of the detector in question
119 @param [in] camera is the afwCameraGeom camera object containing
120 that detector
122 @param [in] obs_metadata is an ObservationMetaData characterizing
123 the pointing (and orientation) of the telescope.
125 @param [in] epoch is the mean Julian epoch of the coordinate system
126 (default is 2000)
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.
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
138 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)]
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 """
145 cc = _getCornerRaDec(detector_name, camera, obs_metadata,
146 epoch=epoch, includeDistortion=includeDistortion)
147 return [tuple(np.degrees(row)) for row in cc]
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.
156 @param [in] detector_name is the name of the detector in question
158 @param [in] camera is the afwCameraGeom camera object containing
159 that detector
161 @param [in] obs_metadata is an ObservationMetaData characterizing
162 the pointing (and orientation) of the telescope.
164 @param [in] epoch is the mean Julian epoch of the coordinate system
165 (default is 2000)
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.
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
177 [(xmin, ymin), (xmin, ymax), (xmax, ymin), (xmax, ymax)]
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 """
184 cc_pix = getCornerPixels(detector_name, camera)
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)
193 return [(ra[0], dec[0]), (ra[1], dec[1]), (ra[2], dec[2]), (ra[3], dec[3])]
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.
203 @param [in] ra in degrees (a numpy array or a float).
204 In the International Celestial Reference System.
206 @param [in] dec in degrees (a numpy array or a float).
207 In the International Celestial Reference System.
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).
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).
215 @param [in] parallax is parallax in arcsec
216 Can be a numpy array or a number or None (default=None).
218 @param [in] v_rad is radial velocity (km/s)
219 Can be a numpy array or a number or None (default=None).
221 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
223 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are
224 measured. Default is 2000.
226 @param [in] camera is an afw.cameraGeom camera instance characterizing the camera
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.
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
241 if pm_dec is not None:
242 pm_dec_out = radiansFromArcsec(pm_dec)
243 else:
244 pm_dec_out = None
246 if parallax is not None:
247 parallax_out = radiansFromArcsec(parallax)
248 else:
249 parallax_out = None
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)
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.
265 @param [in] ra in radians (a numpy array or a float).
266 In the International Celestial Reference System.
268 @param [in] dec in radians (a numpy array or a float).
269 In the International Celestial Reference System.
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).
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).
277 @param [in] parallax is parallax in radians
278 Can be a numpy array or a number or None (default=None).
280 @param [in] v_rad is radial velocity (km/s)
281 Can be a numpy array or a number or None (default=None).
284 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
286 @param [in] epoch is the epoch in Julian years of the equinox against which RA and Dec are
287 measured. Default is 2000.
289 @param [in] camera is an afw.cameraGeom camera instance characterizing the camera
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.
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 """
301 are_arrays = _validate_inputs([ra, dec], ['ra', 'dec'], "chipNameFromRaDec")
303 if epoch is None:
304 raise RuntimeError("You need to pass an epoch into chipName")
306 if obs_metadata is None:
307 raise RuntimeError("You need to pass an ObservationMetaData into chipName")
309 if obs_metadata.mjd is None:
310 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into chipName")
312 if obs_metadata.rotSkyPos is None:
313 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into chipName")
315 xp, yp = _pupilCoordsFromRaDec(ra, dec,
316 pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad,
317 obs_metadata=obs_metadata, epoch=epoch)
319 ans = chipNameFromPupilCoords(xp, yp, camera=camera, allow_multiple_chips=allow_multiple_chips)
321 return ans
323def chipNameFromPupilCoords(xPupil, yPupil, camera=None, allow_multiple_chips=False):
324 """
325 Return the names of detectors that see the object specified by
326 (xPupil, yPupil).
328 @param [in] xPupil is the x pupil coordinate in radians.
329 Can be either a float or a numpy array.
331 @param [in] yPupil is the y pupil coordinate in radians.
332 Can be either a float or a numpy array.
334 @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not
335 this method will allow objects to be visible on more than one chip. If it is 'False'
336 and an object appears on more than one chip, only the first chip will appear in the list of
337 chipNames and warning will be emitted. If it is 'True' and an object falls on more than one
338 chip, the resulting chip name will be the string representation of the list of valid chip names.
340 @param [in] camera is an afwCameraGeom object that specifies the attributes of the camera.
342 @param [out] a numpy array of chip names
344 """
346 are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoords")
348 if camera is None:
349 raise RuntimeError("No camera defined. Cannot run chipName.")
351 chipNames = []
353 if are_arrays:
354 pupilPointList = [geom.Point2D(x, y) for x, y in zip(xPupil, yPupil)]
355 else:
356 pupilPointList = [geom.Point2D(xPupil, yPupil)]
358 detList = camera.findDetectorsList(pupilPointList, FIELD_ANGLE)
360 for pt, det in zip(pupilPointList, detList):
361 if len(det) == 0 or np.isnan(pt.getX()) or np.isnan(pt.getY()):
362 chipNames.append(None)
363 else:
364 name_list = [dd.getName() for dd in det]
365 if len(name_list) > 1:
366 if allow_multiple_chips:
367 chipNames.append(str(name_list))
368 else:
369 warnings.warn("An object has landed on multiple chips. " +
370 "You asked for this not to happen.\n" +
371 "We will return only one of the chip names. If you want both, " +
372 "try re-running with " +
373 "the kwarg allow_multiple_chips=True.\n" +
374 "Offending chip names were %s\n" % str(name_list) +
375 "Offending pupil coordinate point was %.12f %.12f\n" % (pt[0], pt[1]),
376 category=MultipleChipWarning)
378 chipNames.append(name_list[0])
380 elif len(name_list) == 0:
381 chipNames.append(None)
382 else:
383 chipNames.append(name_list[0])
385 if not are_arrays:
386 return chipNames[0]
388 return np.array(chipNames)
391def pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
392 obs_metadata=None,
393 chipName=None, camera=None,
394 epoch=2000.0, includeDistortion=True):
395 """
396 Get the pixel positions (or nan if not on a chip) for objects based
397 on their RA, and Dec (in degrees)
399 @param [in] ra is in degrees in the International Celestial Reference System.
400 Can be either a float or a numpy array.
402 @param [in] dec is in degrees in the International Celestial Reference System.
403 Can be either a float or a numpy array.
405 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr)
406 Can be a numpy array or a number or None (default=None).
408 @param [in] pm_dec is proper motion in dec (arcsec/yr)
409 Can be a numpy array or a number or None (default=None).
411 @param [in] parallax is parallax in arcsec
412 Can be a numpy array or a number or None (default=None).
414 @param [in] v_rad is radial velocity (km/s)
415 Can be a numpy array or a number or None (default=None).
418 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
419 pointing.
421 @param [in] epoch is the epoch in Julian years of the equinox against which
422 RA is measured. Default is 2000.
424 @param [in] chipName designates the names of the chips on which the pixel
425 coordinates will be reckoned. Can be either single value, an array, or None.
426 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
427 If a single value, all of the pixel coordinates will be reckoned on the same
428 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
429 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
430 chip. Default is None.
432 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
433 This is an optional argument to be passed to chipName.
435 @param [in] includeDistortion is a boolean. If True (default), then this method will
436 return the true pixel coordinates with optical distortion included. If False, this
437 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
438 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
439 details.
441 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
442 and the second row is the y pixel coordinate
443 """
445 if pm_ra is not None:
446 pm_ra_out = radiansFromArcsec(pm_ra)
447 else:
448 pm_ra_out = None
450 if pm_dec is not None:
451 pm_dec_out = radiansFromArcsec(pm_dec)
452 else:
453 pm_dec_out = None
455 if parallax is not None:
456 parallax_out = radiansFromArcsec(parallax)
457 else:
458 parallax_out = None
460 return _pixelCoordsFromRaDec(np.radians(ra), np.radians(dec),
461 pm_ra=pm_ra_out, pm_dec=pm_dec_out,
462 parallax=parallax_out, v_rad=v_rad,
463 chipName=chipName, camera=camera,
464 includeDistortion=includeDistortion,
465 obs_metadata=obs_metadata, epoch=epoch)
468def _pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
469 obs_metadata=None,
470 chipName=None, camera=None,
471 epoch=2000.0, includeDistortion=True):
472 """
473 Get the pixel positions (or nan if not on a chip) for objects based
474 on their RA, and Dec (in radians)
476 @param [in] ra is in radians in the International Celestial Reference System.
477 Can be either a float or a numpy array.
479 @param [in] dec is in radians in the International Celestial Reference System.
480 Can be either a float or a numpy array.
482 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr)
483 Can be a numpy array or a number or None (default=None).
485 @param [in] pm_dec is proper motion in dec (radians/yr)
486 Can be a numpy array or a number or None (default=None).
488 @param [in] parallax is parallax in radians
489 Can be a numpy array or a number or None (default=None).
491 @param [in] v_rad is radial velocity (km/s)
492 Can be a numpy array or a number or None (default=None).
494 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
495 pointing.
497 @param [in] epoch is the epoch in Julian years of the equinox against which
498 RA is measured. Default is 2000.
500 @param [in] chipName designates the names of the chips on which the pixel
501 coordinates will be reckoned. Can be either single value, an array, or None.
502 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
503 If a single value, all of the pixel coordinates will be reckoned on the same
504 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
505 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
506 chip. Default is None.
508 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
509 This is an optional argument to be passed to chipName.
511 @param [in] includeDistortion is a boolean. If True (default), then this method will
512 return the true pixel coordinates with optical distortion included. If False, this
513 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
514 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
515 details.
517 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
518 and the second row is the y pixel coordinate
519 """
521 are_arrays, \
522 chipNameList = _validate_inputs_and_chipname([ra, dec], ['ra', 'dec'],
523 'pixelCoordsFromRaDec',
524 chipName)
526 if epoch is None:
527 raise RuntimeError("You need to pass an epoch into pixelCoordsFromRaDec")
529 if obs_metadata is None:
530 raise RuntimeError("You need to pass an ObservationMetaData into pixelCoordsFromRaDec")
532 if obs_metadata.mjd is None:
533 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into "
534 "pixelCoordsFromRaDec")
536 if obs_metadata.rotSkyPos is None:
537 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into "
538 "pixelCoordsFromRaDec")
540 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec,
541 pm_ra=pm_ra, pm_dec=pm_dec,
542 parallax=parallax, v_rad=v_rad,
543 obs_metadata=obs_metadata, epoch=epoch)
545 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipNameList, camera=camera,
546 includeDistortion=includeDistortion)
549def pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=None,
550 camera=None, includeDistortion=True):
551 """
552 Get the pixel positions (or nan if not on a chip) for objects based
553 on their pupil coordinates.
555 @param [in] xPupil is the x pupil coordinates in radians.
556 Can be either a float or a numpy array.
558 @param [in] yPupil is the y pupil coordinates in radians.
559 Can be either a float or a numpy array.
561 @param [in] chipName designates the names of the chips on which the pixel
562 coordinates will be reckoned. Can be either single value, an array, or None.
563 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
564 If a single value, all of the pixel coordinates will be reckoned on the same
565 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
566 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
567 chip. Default is None.
569 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
570 This is an optional argument to be passed to chipName.
572 @param [in] includeDistortion is a boolean. If True (default), then this method will
573 return the true pixel coordinates with optical distortion included. If False, this
574 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
575 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
576 details.
578 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
579 and the second row is the y pixel coordinate
580 """
582 are_arrays, \
583 chipNameList = _validate_inputs_and_chipname([xPupil, yPupil], ["xPupil", "yPupil"],
584 "pixelCoordsFromPupilCoords",
585 chipName)
586 if includeDistortion:
587 pixelType = PIXELS
588 else:
589 pixelType = TAN_PIXELS
591 if not camera:
592 raise RuntimeError("Camera not specified. Cannot calculate pixel coordinates.")
594 if chipNameList is None:
595 chipNameList = chipNameFromPupilCoords(xPupil, yPupil, camera=camera)
596 if not isinstance(chipNameList, list) and not isinstance(chipNameList, np.ndarray):
597 chipNameList = [chipNameList]
599 fieldToFocal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE)
601 if are_arrays:
602 if len(xPupil) == 0:
603 return np.array([[],[]])
604 field_point_list = list([geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)])
605 focal_point_list = fieldToFocal.applyForward(field_point_list)
607 transform_dict = {}
608 xPix = np.nan*np.ones(len(chipNameList), dtype=float)
609 yPix = np.nan*np.ones(len(chipNameList), dtype=float)
611 if not isinstance(chipNameList, np.ndarray):
612 chipNameList = np.array(chipNameList)
613 chipNameList = chipNameList.astype(str)
615 for name in np.unique(chipNameList):
616 if name == 'None':
617 continue
619 valid_points = np.where(np.char.find(chipNameList, name)==0)
620 local_focal_point_list = list([focal_point_list[dex] for dex in valid_points[0]])
622 if name not in transform_dict:
623 transform_dict[name] = camera[name].getTransform(FOCAL_PLANE, pixelType)
625 focalToPixels = transform_dict[name]
626 pixPoint_list = focalToPixels.applyForward(local_focal_point_list)
628 for i_fp, v_dex in enumerate(valid_points[0]):
629 pixPoint= pixPoint_list[i_fp]
630 xPix[v_dex] = pixPoint.getX()
631 yPix[v_dex] = pixPoint.getY()
633 return np.array([xPix, yPix])
634 else:
635 if chipNameList[0] is None:
636 return np.array([np.NaN, np.NaN])
638 det = camera[chipNameList[0]]
639 focalToPixels = det.getTransform(FOCAL_PLANE, pixelType)
640 focalPoint = fieldToFocal.applyForward(geom.Point2D(xPupil, yPupil))
641 pixPoint = focalToPixels.applyForward(focalPoint)
642 return np.array([pixPoint.getX(), pixPoint.getY()])
645def pupilCoordsFromPixelCoords(xPix, yPix, chipName, camera=None,
646 includeDistortion=True):
648 """
649 Convert pixel coordinates into pupil coordinates
651 @param [in] xPix is the x pixel coordinate of the point.
652 Can be either a float or a numpy array.
654 @param [in] yPix is the y pixel coordinate of the point.
655 Can be either a float or a numpy array.
657 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
658 are defined. This can be a list (in which case there should be one chip name
659 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
660 of the (xPix, yPix) points will be reckoned on that chip).
662 @param [in] camera is an afw.CameraGeom.camera object defining the camera
664 @param [in] includeDistortion is a boolean. If True (default), then this method will
665 expect the true pixel coordinates with optical distortion included. If False, this
666 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
667 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
668 details.
670 @param [out] a 2-D numpy array in which the first row is the x pupil coordinate
671 and the second row is the y pupil coordinate (both in radians)
672 """
674 if camera is None:
675 raise RuntimeError("You cannot call pupilCoordsFromPixelCoords without specifying a camera")
677 are_arrays, \
678 chipNameList = _validate_inputs_and_chipname([xPix, yPix], ['xPix', 'yPix'],
679 "pupilCoordsFromPixelCoords",
680 chipName,
681 chipname_can_be_none=False)
683 if includeDistortion:
684 pixelType = PIXELS
685 else:
686 pixelType = TAN_PIXELS
688 pixel_to_focal_dict = {}
689 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
690 for name in chipNameList:
691 if name not in pixel_to_focal_dict and name is not None and name != 'None':
692 pixel_to_focal_dict[name] = camera[name].getTransform(pixelType, FOCAL_PLANE)
694 if are_arrays:
695 xPupilList = []
696 yPupilList = []
698 for xx, yy, name in zip(xPix, yPix, chipNameList):
699 if name is None or name == 'None':
700 xPupilList.append(np.NaN)
701 yPupilList.append(np.NaN)
702 else:
703 focalPoint = pixel_to_focal_dict[name].applyForward(geom.Point2D(xx, yy))
704 pupilPoint = focal_to_field.applyForward(focalPoint)
705 xPupilList.append(pupilPoint.getX())
706 yPupilList.append(pupilPoint.getY())
708 xPupilList = np.array(xPupilList)
709 yPupilList = np.array(yPupilList)
711 return np.array([xPupilList, yPupilList])
713 # if not are_arrays
714 if chipNameList[0] is None or chipNameList[0] == 'None':
715 return np.array([np.NaN, np.NaN])
717 focalPoint = pixel_to_focal_dict[chipNameList[0]].applyForward(geom.Point2D(xPix, yPix))
718 pupilPoint = focal_to_field.applyForward(focalPoint)
719 return np.array([pupilPoint.getX(), pupilPoint.getY()])
722def raDecFromPixelCoords(xPix, yPix, chipName, camera=None,
723 obs_metadata=None, epoch=2000.0, includeDistortion=True):
724 """
725 Convert pixel coordinates into RA, Dec
727 @param [in] xPix is the x pixel coordinate. It can be either
728 a float or a numpy array.
730 @param [in] yPix is the y pixel coordinate. It can be either
731 a float or a numpy array.
733 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
734 are defined. This can be a list (in which case there should be one chip name
735 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
736 of the (xPix, yPix) points will be reckoned on that chip).
738 @param [in] camera is an afw.CameraGeom.camera object defining the camera
740 @param [in] obs_metadata is an ObservationMetaData defining the pointing
742 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
743 Default is 2000.
745 @param [in] includeDistortion is a boolean. If True (default), then this method will
746 expect the true pixel coordinates with optical distortion included. If False, this
747 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
748 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
749 details.
751 @param [out] a 2-D numpy array in which the first row is the RA coordinate
752 and the second row is the Dec coordinate (both in degrees; in the
753 International Celestial Reference System)
755 WARNING: This method does not account for apparent motion due to parallax.
756 This method is only useful for mapping positions on a theoretical focal plane
757 to positions on the celestial sphere.
758 """
759 output = _raDecFromPixelCoords(xPix, yPix, chipName,
760 camera=camera, obs_metadata=obs_metadata,
761 epoch=epoch, includeDistortion=includeDistortion)
763 return np.degrees(output)
766def _raDecFromPixelCoords(xPix, yPix, chipName, camera=None,
767 obs_metadata=None, epoch=2000.0, includeDistortion=True):
768 """
769 Convert pixel coordinates into RA, Dec
771 @param [in] xPix is the x pixel coordinate. It can be either
772 a float or a numpy array.
774 @param [in] yPix is the y pixel coordinate. It can be either
775 a float or a numpy array.
777 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
778 are defined. This can be a list (in which case there should be one chip name
779 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
780 of the (xPix, yPix) points will be reckoned on that chip).
782 @param [in] camera is an afw.CameraGeom.camera object defining the camera
784 @param [in] obs_metadata is an ObservationMetaData defining the pointing
786 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
787 Default is 2000.
789 @param [in] includeDistortion is a boolean. If True (default), then this method will
790 expect the true pixel coordinates with optical distortion included. If False, this
791 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
792 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
793 details.
795 @param [out] a 2-D numpy array in which the first row is the RA coordinate
796 and the second row is the Dec coordinate (both in radians; in the International
797 Celestial Reference System)
799 WARNING: This method does not account for apparent motion due to parallax.
800 This method is only useful for mapping positions on a theoretical focal plane
801 to positions on the celestial sphere.
802 """
804 are_arrays, \
805 chipNameList = _validate_inputs_and_chipname([xPix, yPix],
806 ['xPix', 'yPix'],
807 'raDecFromPixelCoords',
808 chipName,
809 chipname_can_be_none=False)
811 if camera is None:
812 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying a camera")
814 if epoch is None:
815 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying an epoch")
817 if obs_metadata is None:
818 raise RuntimeError("You cannot call raDecFromPixelCoords without an ObservationMetaData")
820 if obs_metadata.mjd is None:
821 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have an mjd")
823 if obs_metadata.rotSkyPos is None:
824 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have a rotSkyPos")
826 xPupilList, yPupilList = pupilCoordsFromPixelCoords(xPix, yPix, chipNameList,
827 camera=camera, includeDistortion=includeDistortion)
829 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList,
830 obs_metadata=obs_metadata, epoch=epoch)
832 return np.array([raOut, decOut])
835def focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
836 obs_metadata=None, epoch=2000.0, camera=None):
837 """
838 Get the focal plane coordinates for all objects in the catalog.
840 @param [in] ra is in degrees in the International Celestial Reference System.
841 Can be either a float or a numpy array.
843 @param [in] dec is in degrees in the International Celestial Reference System.
844 Can be either a float or a numpy array.
846 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr)
847 Can be a numpy array or a number or None (default=None).
849 @param [in] pm_dec is proper motion in dec (arcsec/yr)
850 Can be a numpy array or a number or None (default=None).
852 @param [in] parallax is parallax in arcsec
853 Can be a numpy array or a number or None (default=None).
855 @param [in] v_rad is radial velocity (km/s)
856 Can be a numpy array or a number or None (default=None).
858 @param [in] obs_metadata is an ObservationMetaData object describing the telescope
859 pointing (only if specifying RA and Dec rather than pupil coordinates)
861 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations
862 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000)
864 @param [in] camera is an afw.cameraGeom camera object
866 @param [out] a 2-D numpy array in which the first row is the x
867 focal plane coordinate and the second row is the y focal plane
868 coordinate (both in millimeters)
869 """
871 if pm_ra is not None:
872 pm_ra_out = radiansFromArcsec(pm_ra)
873 else:
874 pm_ra_out = None
876 if pm_dec is not None:
877 pm_dec_out = radiansFromArcsec(pm_dec)
878 else:
879 pm_dec_out = None
881 if parallax is not None:
882 parallax_out = radiansFromArcsec(parallax)
883 else:
884 parallax_out = None
886 return _focalPlaneCoordsFromRaDec(np.radians(ra), np.radians(dec),
887 pm_ra=pm_ra_out, pm_dec=pm_dec_out,
888 parallax=parallax_out, v_rad=v_rad,
889 obs_metadata=obs_metadata, epoch=epoch,
890 camera=camera)
893def _focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
894 obs_metadata=None, epoch=2000.0, camera=None):
895 """
896 Get the focal plane coordinates for all objects in the catalog.
898 @param [in] ra is in radians in the International Celestial Reference System.
899 Can be either a float or a numpy array.
901 @param [in] dec is in radians in the International Celestial Reference System.
902 Can be either a float or a numpy array.
904 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr)
905 Can be a numpy array or a number or None (default=None).
907 @param [in] pm_dec is proper motion in dec (radians/yr)
908 Can be a numpy array or a number or None (default=None).
910 @param [in] parallax is parallax in radians
911 Can be a numpy array or a number or None (default=None).
913 @param [in] v_rad is radial velocity (km/s)
914 Can be a numpy array or a number or None (default=None).
917 @param [in] obs_metadata is an ObservationMetaData object describing the telescope
918 pointing (only if specifying RA and Dec rather than pupil coordinates)
920 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations
921 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000)
923 @param [in] camera is an afw.cameraGeom camera object
925 @param [out] a 2-D numpy array in which the first row is the x
926 focal plane coordinate and the second row is the y focal plane
927 coordinate (both in millimeters)
928 """
930 _validate_inputs([ra, dec], ['ra', 'dec'], 'focalPlaneCoordsFromRaDec')
932 if epoch is None:
933 raise RuntimeError("You have to specify an epoch to run "
934 "focalPlaneCoordsFromRaDec")
936 if obs_metadata is None:
937 raise RuntimeError("You have to specify an ObservationMetaData to run "
938 "focalPlaneCoordsFromRaDec")
940 if obs_metadata.mjd is None:
941 raise RuntimeError("You need to pass an ObservationMetaData with an "
942 "mjd into focalPlaneCoordsFromRaDec")
944 if obs_metadata.rotSkyPos is None:
945 raise RuntimeError("You need to pass an ObservationMetaData with a "
946 "rotSkyPos into focalPlaneCoordsFromRaDec")
948 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec,
949 pm_ra=pm_ra, pm_dec=pm_dec,
950 parallax=parallax, v_rad=v_rad,
951 obs_metadata=obs_metadata,
952 epoch=epoch)
954 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=camera)
957def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None):
958 """
959 Get the focal plane coordinates for all objects in the catalog.
961 @param [in] xPupil the x pupil coordinates in radians.
962 Can be a float or a numpy array.
964 @param [in] yPupil the y pupil coordinates in radians.
965 Can be a float or a numpy array.
967 @param [in] camera is an afw.cameraGeom camera object
969 @param [out] a 2-D numpy array in which the first row is the x
970 focal plane coordinate and the second row is the y focal plane
971 coordinate (both in millimeters)
972 """
974 are_arrays = _validate_inputs([xPupil, yPupil],
975 ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords')
977 if camera is None:
978 raise RuntimeError("You cannot calculate focal plane coordinates without specifying a camera")
980 field_to_focal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE)
982 if are_arrays:
983 pupil_point_list = [geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)]
984 focal_point_list = field_to_focal.applyForward(pupil_point_list)
985 xFocal = np.array([pp.getX() for pp in focal_point_list])
986 yFocal = np.array([pp.getY() for pp in focal_point_list])
988 return np.array([xFocal, yFocal])
990 # if not are_arrays
991 fpPoint = field_to_focal.applyForward(geom.Point2D(xPupil, yPupil))
992 return np.array([fpPoint.getX(), fpPoint.getY()])
995def pupilCoordsFromFocalPlaneCoords(xFocal, yFocal, camera=None):
996 """
997 Get the pupil coordinates in radians from the focal plane
998 coordinates in millimeters
1000 @param [in] xFocal the x focal plane coordinates in millimeters.
1001 Can be a float or a numpy array.
1003 @param [in] yFocal the y focal plane coordinates in millimeters.
1004 Can be a float or a numpy array.
1006 @param [in] camera is an afw.cameraGeom camera object
1008 @param [out] a 2-D numpy array in which the first row is the x
1009 pupil coordinate and the second row is the y pupil
1010 coordinate (both in radians)
1011 """
1013 are_arrays = _validate_inputs([xFocal, yFocal],
1014 ['xFocal', 'yFocal'],
1015 'pupilCoordsFromFocalPlaneCoords')
1017 if camera is None:
1018 raise RuntimeError("You cannot calculate pupil coordinates without specifying a camera")
1020 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
1022 if are_arrays:
1023 focal_point_list = [geom.Point2D(x,y) for x,y in zip(xFocal, yFocal)]
1024 pupil_point_list = focal_to_field.applyForward(focal_point_list)
1025 pupil_arr = np.array([[pp.getX(), pp.getY()]
1026 for pp in pupil_point_list]).transpose()
1027 is_nan = np.where(np.logical_or(np.isnan(xFocal), np.isnan(yFocal)))
1028 pupil_arr[0][is_nan] = np.NaN
1029 pupil_arr[1][is_nan] = np.NaN
1031 return pupil_arr
1033 # if not are_arrays
1034 if np.isfinite(xFocal) and np.isfinite(yFocal):
1035 pupPoint = focal_to_field.applyForward(geom.Point2D(xFocal, yFocal))
1036 return np.array([pupPoint.getX(), pupPoint.getY()])
1038 return np.array([np.NaN, np.NaN])