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 if not are_arrays:
316 ra = np.array([ra])
317 dec = np.array([dec])
319 xp, yp = _pupilCoordsFromRaDec(ra, dec,
320 pm_ra=pm_ra, pm_dec=pm_dec, parallax=parallax, v_rad=v_rad,
321 obs_metadata=obs_metadata, epoch=epoch)
323 ans = chipNameFromPupilCoords(xp, yp, camera=camera, allow_multiple_chips=allow_multiple_chips)
325 if not are_arrays:
326 return ans[0]
327 return ans
329def chipNameFromPupilCoords(xPupil, yPupil, camera=None, allow_multiple_chips=False):
330 """
331 Return the names of detectors that see the object specified by
332 (xPupil, yPupil).
334 @param [in] xPupil is the x pupil coordinate in radians.
335 Can be either a float or a numpy array.
337 @param [in] yPupil is the y pupil coordinate in radians.
338 Can be either a float or a numpy array.
340 @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not
341 this method will allow objects to be visible on more than one chip. If it is 'False'
342 and an object appears on more than one chip, only the first chip will appear in the list of
343 chipNames and warning will be emitted. If it is 'True' and an object falls on more than one
344 chip, the resulting chip name will be the string representation of the list of valid chip names.
346 @param [in] camera is an afwCameraGeom object that specifies the attributes of the camera.
348 @param [out] a numpy array of chip names
350 """
352 are_arrays = _validate_inputs([xPupil, yPupil], ['xPupil', 'yPupil'], "chipNameFromPupilCoords")
354 if camera is None:
355 raise RuntimeError("No camera defined. Cannot run chipName.")
357 chipNames = []
359 if are_arrays:
360 pupilPointList = [geom.Point2D(x, y) for x, y in zip(xPupil, yPupil)]
361 else:
362 pupilPointList = [geom.Point2D(xPupil, yPupil)]
364 detList = camera.findDetectorsList(pupilPointList, FIELD_ANGLE)
366 for pt, det in zip(pupilPointList, detList):
367 if len(det) == 0 or np.isnan(pt.getX()) or np.isnan(pt.getY()):
368 chipNames.append(None)
369 else:
370 name_list = [dd.getName() for dd in det]
371 if len(name_list) > 1:
372 if allow_multiple_chips:
373 chipNames.append(str(name_list))
374 else:
375 warnings.warn("An object has landed on multiple chips. " +
376 "You asked for this not to happen.\n" +
377 "We will return only one of the chip names. If you want both, " +
378 "try re-running with " +
379 "the kwarg allow_multiple_chips=True.\n" +
380 "Offending chip names were %s\n" % str(name_list) +
381 "Offending pupil coordinate point was %.12f %.12f\n" % (pt[0], pt[1]),
382 category=MultipleChipWarning)
384 chipNames.append(name_list[0])
386 elif len(name_list) == 0:
387 chipNames.append(None)
388 else:
389 chipNames.append(name_list[0])
391 if not are_arrays:
392 return chipNames[0]
394 return np.array(chipNames)
397def pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
398 obs_metadata=None,
399 chipName=None, camera=None,
400 epoch=2000.0, includeDistortion=True):
401 """
402 Get the pixel positions (or nan if not on a chip) for objects based
403 on their RA, and Dec (in degrees)
405 @param [in] ra is in degrees in the International Celestial Reference System.
406 Can be either a float or a numpy array.
408 @param [in] dec is in degrees in the International Celestial Reference System.
409 Can be either a float or a numpy array.
411 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr)
412 Can be a numpy array or a number or None (default=None).
414 @param [in] pm_dec is proper motion in dec (arcsec/yr)
415 Can be a numpy array or a number or None (default=None).
417 @param [in] parallax is parallax in arcsec
418 Can be a numpy array or a number or None (default=None).
420 @param [in] v_rad is radial velocity (km/s)
421 Can be a numpy array or a number or None (default=None).
424 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
425 pointing.
427 @param [in] epoch is the epoch in Julian years of the equinox against which
428 RA is measured. Default is 2000.
430 @param [in] chipName designates the names of the chips on which the pixel
431 coordinates will be reckoned. Can be either single value, an array, or None.
432 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
433 If a single value, all of the pixel coordinates will be reckoned on the same
434 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
435 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
436 chip. Default is None.
438 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
439 This is an optional argument to be passed to chipName.
441 @param [in] includeDistortion is a boolean. If True (default), then this method will
442 return the true pixel coordinates with optical distortion included. If False, this
443 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
444 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
445 details.
447 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
448 and the second row is the y pixel coordinate
449 """
451 if pm_ra is not None:
452 pm_ra_out = radiansFromArcsec(pm_ra)
453 else:
454 pm_ra_out = None
456 if pm_dec is not None:
457 pm_dec_out = radiansFromArcsec(pm_dec)
458 else:
459 pm_dec_out = None
461 if parallax is not None:
462 parallax_out = radiansFromArcsec(parallax)
463 else:
464 parallax_out = None
466 return _pixelCoordsFromRaDec(np.radians(ra), np.radians(dec),
467 pm_ra=pm_ra_out, pm_dec=pm_dec_out,
468 parallax=parallax_out, v_rad=v_rad,
469 chipName=chipName, camera=camera,
470 includeDistortion=includeDistortion,
471 obs_metadata=obs_metadata, epoch=epoch)
474def _pixelCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
475 obs_metadata=None,
476 chipName=None, camera=None,
477 epoch=2000.0, includeDistortion=True):
478 """
479 Get the pixel positions (or nan if not on a chip) for objects based
480 on their RA, and Dec (in radians)
482 @param [in] ra is in radians in the International Celestial Reference System.
483 Can be either a float or a numpy array.
485 @param [in] dec is in radians in the International Celestial Reference System.
486 Can be either a float or a numpy array.
488 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr)
489 Can be a numpy array or a number or None (default=None).
491 @param [in] pm_dec is proper motion in dec (radians/yr)
492 Can be a numpy array or a number or None (default=None).
494 @param [in] parallax is parallax in radians
495 Can be a numpy array or a number or None (default=None).
497 @param [in] v_rad is radial velocity (km/s)
498 Can be a numpy array or a number or None (default=None).
500 @param [in] obs_metadata is an ObservationMetaData characterizing the telescope
501 pointing.
503 @param [in] epoch is the epoch in Julian years of the equinox against which
504 RA is measured. Default is 2000.
506 @param [in] chipName designates the names of the chips on which the pixel
507 coordinates will be reckoned. Can be either single value, an array, or None.
508 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
509 If a single value, all of the pixel coordinates will be reckoned on the same
510 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
511 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
512 chip. Default is None.
514 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
515 This is an optional argument to be passed to chipName.
517 @param [in] includeDistortion is a boolean. If True (default), then this method will
518 return the true pixel coordinates with optical distortion included. If False, this
519 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
520 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
521 details.
523 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
524 and the second row is the y pixel coordinate
525 """
527 are_arrays, \
528 chipNameList = _validate_inputs_and_chipname([ra, dec], ['ra', 'dec'],
529 'pixelCoordsFromRaDec',
530 chipName)
532 if epoch is None:
533 raise RuntimeError("You need to pass an epoch into pixelCoordsFromRaDec")
535 if obs_metadata is None:
536 raise RuntimeError("You need to pass an ObservationMetaData into pixelCoordsFromRaDec")
538 if obs_metadata.mjd is None:
539 raise RuntimeError("You need to pass an ObservationMetaData with an mjd into "
540 "pixelCoordsFromRaDec")
542 if obs_metadata.rotSkyPos is None:
543 raise RuntimeError("You need to pass an ObservationMetaData with a rotSkyPos into "
544 "pixelCoordsFromRaDec")
546 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec,
547 pm_ra=pm_ra, pm_dec=pm_dec,
548 parallax=parallax, v_rad=v_rad,
549 obs_metadata=obs_metadata, epoch=epoch)
551 return pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=chipNameList, camera=camera,
552 includeDistortion=includeDistortion)
555def pixelCoordsFromPupilCoords(xPupil, yPupil, chipName=None,
556 camera=None, includeDistortion=True):
557 """
558 Get the pixel positions (or nan if not on a chip) for objects based
559 on their pupil coordinates.
561 @param [in] xPupil is the x pupil coordinates in radians.
562 Can be either a float or a numpy array.
564 @param [in] yPupil is the y pupil coordinates in radians.
565 Can be either a float or a numpy array.
567 @param [in] chipName designates the names of the chips on which the pixel
568 coordinates will be reckoned. Can be either single value, an array, or None.
569 If an array, there must be as many chipNames as there are (RA, Dec) pairs.
570 If a single value, all of the pixel coordinates will be reckoned on the same
571 chip. If None, this method will calculate which chip each(RA, Dec) pair actually
572 falls on, and return pixel coordinates for each (RA, Dec) pair on the appropriate
573 chip. Default is None.
575 @param [in] camera is an afwCameraGeom object specifying the attributes of the camera.
576 This is an optional argument to be passed to chipName.
578 @param [in] includeDistortion is a boolean. If True (default), then this method will
579 return the true pixel coordinates with optical distortion included. If False, this
580 method will return TAN_PIXEL coordinates, which are the pixel coordinates with
581 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
582 details.
584 @param [out] a 2-D numpy array in which the first row is the x pixel coordinate
585 and the second row is the y pixel coordinate
586 """
588 are_arrays, \
589 chipNameList = _validate_inputs_and_chipname([xPupil, yPupil], ["xPupil", "yPupil"],
590 "pixelCoordsFromPupilCoords",
591 chipName)
592 if includeDistortion:
593 pixelType = PIXELS
594 else:
595 pixelType = TAN_PIXELS
597 if not camera:
598 raise RuntimeError("Camera not specified. Cannot calculate pixel coordinates.")
600 if chipNameList is None:
601 chipNameList = chipNameFromPupilCoords(xPupil, yPupil, camera=camera)
602 if not isinstance(chipNameList, list) and not isinstance(chipNameList, np.ndarray):
603 chipNameList = [chipNameList]
605 fieldToFocal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE)
607 if are_arrays:
608 if len(xPupil) == 0:
609 return np.array([[],[]])
610 field_point_list = list([geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)])
611 focal_point_list = fieldToFocal.applyForward(field_point_list)
613 transform_dict = {}
614 xPix = np.nan*np.ones(len(chipNameList), dtype=float)
615 yPix = np.nan*np.ones(len(chipNameList), dtype=float)
617 if not isinstance(chipNameList, np.ndarray):
618 chipNameList = np.array(chipNameList)
619 chipNameList = chipNameList.astype(str)
621 for name in np.unique(chipNameList):
622 if name == 'None':
623 continue
625 valid_points = np.where(np.char.find(chipNameList, name)==0)
626 local_focal_point_list = list([focal_point_list[dex] for dex in valid_points[0]])
628 if name not in transform_dict:
629 transform_dict[name] = camera[name].getTransform(FOCAL_PLANE, pixelType)
631 focalToPixels = transform_dict[name]
632 pixPoint_list = focalToPixels.applyForward(local_focal_point_list)
634 for i_fp, v_dex in enumerate(valid_points[0]):
635 pixPoint= pixPoint_list[i_fp]
636 xPix[v_dex] = pixPoint.getX()
637 yPix[v_dex] = pixPoint.getY()
639 return np.array([xPix, yPix])
640 else:
641 if chipNameList[0] is None:
642 return np.array([np.NaN, np.NaN])
644 det = camera[chipNameList[0]]
645 focalToPixels = det.getTransform(FOCAL_PLANE, pixelType)
646 focalPoint = fieldToFocal.applyForward(geom.Point2D(xPupil, yPupil))
647 pixPoint = focalToPixels.applyForward(focalPoint)
648 return np.array([pixPoint.getX(), pixPoint.getY()])
651def pupilCoordsFromPixelCoords(xPix, yPix, chipName, camera=None,
652 includeDistortion=True):
654 """
655 Convert pixel coordinates into pupil coordinates
657 @param [in] xPix is the x pixel coordinate of the point.
658 Can be either a float or a numpy array.
660 @param [in] yPix is the y pixel coordinate of the point.
661 Can be either a float or a numpy array.
663 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
664 are defined. This can be a list (in which case there should be one chip name
665 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
666 of the (xPix, yPix) points will be reckoned on that chip).
668 @param [in] camera is an afw.CameraGeom.camera object defining the camera
670 @param [in] includeDistortion is a boolean. If True (default), then this method will
671 expect the true pixel coordinates with optical distortion included. If False, this
672 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
673 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
674 details.
676 @param [out] a 2-D numpy array in which the first row is the x pupil coordinate
677 and the second row is the y pupil coordinate (both in radians)
678 """
680 if camera is None:
681 raise RuntimeError("You cannot call pupilCoordsFromPixelCoords without specifying a camera")
683 are_arrays, \
684 chipNameList = _validate_inputs_and_chipname([xPix, yPix], ['xPix', 'yPix'],
685 "pupilCoordsFromPixelCoords",
686 chipName,
687 chipname_can_be_none=False)
689 if includeDistortion:
690 pixelType = PIXELS
691 else:
692 pixelType = TAN_PIXELS
694 pixel_to_focal_dict = {}
695 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
696 for name in chipNameList:
697 if name not in pixel_to_focal_dict and name is not None and name != 'None':
698 pixel_to_focal_dict[name] = camera[name].getTransform(pixelType, FOCAL_PLANE)
700 if are_arrays:
701 xPupilList = []
702 yPupilList = []
704 for xx, yy, name in zip(xPix, yPix, chipNameList):
705 if name is None or name == 'None':
706 xPupilList.append(np.NaN)
707 yPupilList.append(np.NaN)
708 else:
709 focalPoint = pixel_to_focal_dict[name].applyForward(geom.Point2D(xx, yy))
710 pupilPoint = focal_to_field.applyForward(focalPoint)
711 xPupilList.append(pupilPoint.getX())
712 yPupilList.append(pupilPoint.getY())
714 xPupilList = np.array(xPupilList)
715 yPupilList = np.array(yPupilList)
717 return np.array([xPupilList, yPupilList])
719 # if not are_arrays
720 if chipNameList[0] is None or chipNameList[0] == 'None':
721 return np.array([np.NaN, np.NaN])
723 focalPoint = pixel_to_focal_dict[chipNameList[0]].applyForward(geom.Point2D(xPix, yPix))
724 pupilPoint = focal_to_field.applyForward(focalPoint)
725 return np.array([pupilPoint.getX(), pupilPoint.getY()])
728def raDecFromPixelCoords(xPix, yPix, chipName, camera=None,
729 obs_metadata=None, epoch=2000.0, includeDistortion=True):
730 """
731 Convert pixel coordinates into RA, Dec
733 @param [in] xPix is the x pixel coordinate. It can be either
734 a float or a numpy array.
736 @param [in] yPix is the y pixel coordinate. It can be either
737 a float or a numpy array.
739 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
740 are defined. This can be a list (in which case there should be one chip name
741 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
742 of the (xPix, yPix) points will be reckoned on that chip).
744 @param [in] camera is an afw.CameraGeom.camera object defining the camera
746 @param [in] obs_metadata is an ObservationMetaData defining the pointing
748 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
749 Default is 2000.
751 @param [in] includeDistortion is a boolean. If True (default), then this method will
752 expect the true pixel coordinates with optical distortion included. If False, this
753 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
754 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
755 details.
757 @param [out] a 2-D numpy array in which the first row is the RA coordinate
758 and the second row is the Dec coordinate (both in degrees; in the
759 International Celestial Reference System)
761 WARNING: This method does not account for apparent motion due to parallax.
762 This method is only useful for mapping positions on a theoretical focal plane
763 to positions on the celestial sphere.
764 """
765 output = _raDecFromPixelCoords(xPix, yPix, chipName,
766 camera=camera, obs_metadata=obs_metadata,
767 epoch=epoch, includeDistortion=includeDistortion)
769 return np.degrees(output)
772def _raDecFromPixelCoords(xPix, yPix, chipName, camera=None,
773 obs_metadata=None, epoch=2000.0, includeDistortion=True):
774 """
775 Convert pixel coordinates into RA, Dec
777 @param [in] xPix is the x pixel coordinate. It can be either
778 a float or a numpy array.
780 @param [in] yPix is the y pixel coordinate. It can be either
781 a float or a numpy array.
783 @param [in] chipName is the name of the chip(s) on which the pixel coordinates
784 are defined. This can be a list (in which case there should be one chip name
785 for each (xPix, yPix) coordinate pair), or a single value (in which case, all
786 of the (xPix, yPix) points will be reckoned on that chip).
788 @param [in] camera is an afw.CameraGeom.camera object defining the camera
790 @param [in] obs_metadata is an ObservationMetaData defining the pointing
792 @param [in] epoch is the mean epoch in years of the celestial coordinate system.
793 Default is 2000.
795 @param [in] includeDistortion is a boolean. If True (default), then this method will
796 expect the true pixel coordinates with optical distortion included. If False, this
797 method will expect TAN_PIXEL coordinates, which are the pixel coordinates with
798 estimated optical distortion removed. See the documentation in afw.cameraGeom for more
799 details.
801 @param [out] a 2-D numpy array in which the first row is the RA coordinate
802 and the second row is the Dec coordinate (both in radians; in the International
803 Celestial Reference System)
805 WARNING: This method does not account for apparent motion due to parallax.
806 This method is only useful for mapping positions on a theoretical focal plane
807 to positions on the celestial sphere.
808 """
810 are_arrays, \
811 chipNameList = _validate_inputs_and_chipname([xPix, yPix],
812 ['xPix', 'yPix'],
813 'raDecFromPixelCoords',
814 chipName,
815 chipname_can_be_none=False)
817 if camera is None:
818 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying a camera")
820 if epoch is None:
821 raise RuntimeError("You cannot call raDecFromPixelCoords without specifying an epoch")
823 if obs_metadata is None:
824 raise RuntimeError("You cannot call raDecFromPixelCoords without an ObservationMetaData")
826 if obs_metadata.mjd is None:
827 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have an mjd")
829 if obs_metadata.rotSkyPos is None:
830 raise RuntimeError("The ObservationMetaData in raDecFromPixelCoords must have a rotSkyPos")
832 xPupilList, yPupilList = pupilCoordsFromPixelCoords(xPix, yPix, chipNameList,
833 camera=camera, includeDistortion=includeDistortion)
835 raOut, decOut = _raDecFromPupilCoords(xPupilList, yPupilList,
836 obs_metadata=obs_metadata, epoch=epoch)
838 return np.array([raOut, decOut])
841def focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
842 obs_metadata=None, epoch=2000.0, camera=None):
843 """
844 Get the focal plane coordinates for all objects in the catalog.
846 @param [in] ra is in degrees in the International Celestial Reference System.
847 Can be either a float or a numpy array.
849 @param [in] dec is in degrees in the International Celestial Reference System.
850 Can be either a float or a numpy array.
852 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (arcsec/yr)
853 Can be a numpy array or a number or None (default=None).
855 @param [in] pm_dec is proper motion in dec (arcsec/yr)
856 Can be a numpy array or a number or None (default=None).
858 @param [in] parallax is parallax in arcsec
859 Can be a numpy array or a number or None (default=None).
861 @param [in] v_rad is radial velocity (km/s)
862 Can be a numpy array or a number or None (default=None).
864 @param [in] obs_metadata is an ObservationMetaData object describing the telescope
865 pointing (only if specifying RA and Dec rather than pupil coordinates)
867 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations
868 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000)
870 @param [in] camera is an afw.cameraGeom camera object
872 @param [out] a 2-D numpy array in which the first row is the x
873 focal plane coordinate and the second row is the y focal plane
874 coordinate (both in millimeters)
875 """
877 if pm_ra is not None:
878 pm_ra_out = radiansFromArcsec(pm_ra)
879 else:
880 pm_ra_out = None
882 if pm_dec is not None:
883 pm_dec_out = radiansFromArcsec(pm_dec)
884 else:
885 pm_dec_out = None
887 if parallax is not None:
888 parallax_out = radiansFromArcsec(parallax)
889 else:
890 parallax_out = None
892 return _focalPlaneCoordsFromRaDec(np.radians(ra), np.radians(dec),
893 pm_ra=pm_ra_out, pm_dec=pm_dec_out,
894 parallax=parallax_out, v_rad=v_rad,
895 obs_metadata=obs_metadata, epoch=epoch,
896 camera=camera)
899def _focalPlaneCoordsFromRaDec(ra, dec, pm_ra=None, pm_dec=None, parallax=None, v_rad=None,
900 obs_metadata=None, epoch=2000.0, camera=None):
901 """
902 Get the focal plane coordinates for all objects in the catalog.
904 @param [in] ra is in radians in the International Celestial Reference System.
905 Can be either a float or a numpy array.
907 @param [in] dec is in radians in the International Celestial Reference System.
908 Can be either a float or a numpy array.
910 @param [in] pm_ra is proper motion in RA multiplied by cos(Dec) (radians/yr)
911 Can be a numpy array or a number or None (default=None).
913 @param [in] pm_dec is proper motion in dec (radians/yr)
914 Can be a numpy array or a number or None (default=None).
916 @param [in] parallax is parallax in radians
917 Can be a numpy array or a number or None (default=None).
919 @param [in] v_rad is radial velocity (km/s)
920 Can be a numpy array or a number or None (default=None).
923 @param [in] obs_metadata is an ObservationMetaData object describing the telescope
924 pointing (only if specifying RA and Dec rather than pupil coordinates)
926 @param [in] epoch is the julian epoch of the mean equinox used for coordinate transformations
927 (in years; only if specifying RA and Dec rather than pupil coordinates; default is 2000)
929 @param [in] camera is an afw.cameraGeom camera object
931 @param [out] a 2-D numpy array in which the first row is the x
932 focal plane coordinate and the second row is the y focal plane
933 coordinate (both in millimeters)
934 """
936 _validate_inputs([ra, dec], ['ra', 'dec'], 'focalPlaneCoordsFromRaDec')
938 if epoch is None:
939 raise RuntimeError("You have to specify an epoch to run "
940 "focalPlaneCoordsFromRaDec")
942 if obs_metadata is None:
943 raise RuntimeError("You have to specify an ObservationMetaData to run "
944 "focalPlaneCoordsFromRaDec")
946 if obs_metadata.mjd is None:
947 raise RuntimeError("You need to pass an ObservationMetaData with an "
948 "mjd into focalPlaneCoordsFromRaDec")
950 if obs_metadata.rotSkyPos is None:
951 raise RuntimeError("You need to pass an ObservationMetaData with a "
952 "rotSkyPos into focalPlaneCoordsFromRaDec")
954 xPupil, yPupil = _pupilCoordsFromRaDec(ra, dec,
955 pm_ra=pm_ra, pm_dec=pm_dec,
956 parallax=parallax, v_rad=v_rad,
957 obs_metadata=obs_metadata,
958 epoch=epoch)
960 return focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=camera)
963def focalPlaneCoordsFromPupilCoords(xPupil, yPupil, camera=None):
964 """
965 Get the focal plane coordinates for all objects in the catalog.
967 @param [in] xPupil the x pupil coordinates in radians.
968 Can be a float or a numpy array.
970 @param [in] yPupil the y pupil coordinates in radians.
971 Can be a float or a numpy array.
973 @param [in] camera is an afw.cameraGeom camera object
975 @param [out] a 2-D numpy array in which the first row is the x
976 focal plane coordinate and the second row is the y focal plane
977 coordinate (both in millimeters)
978 """
980 are_arrays = _validate_inputs([xPupil, yPupil],
981 ['xPupil', 'yPupil'], 'focalPlaneCoordsFromPupilCoords')
983 if camera is None:
984 raise RuntimeError("You cannot calculate focal plane coordinates without specifying a camera")
986 field_to_focal = camera.getTransformMap().getTransform(FIELD_ANGLE, FOCAL_PLANE)
988 if are_arrays:
989 pupil_point_list = [geom.Point2D(x,y) for x,y in zip(xPupil, yPupil)]
990 focal_point_list = field_to_focal.applyForward(pupil_point_list)
991 xFocal = np.array([pp.getX() for pp in focal_point_list])
992 yFocal = np.array([pp.getY() for pp in focal_point_list])
994 return np.array([xFocal, yFocal])
996 # if not are_arrays
997 fpPoint = field_to_focal.applyForward(geom.Point2D(xPupil, yPupil))
998 return np.array([fpPoint.getX(), fpPoint.getY()])
1001def pupilCoordsFromFocalPlaneCoords(xFocal, yFocal, camera=None):
1002 """
1003 Get the pupil coordinates in radians from the focal plane
1004 coordinates in millimeters
1006 @param [in] xFocal the x focal plane coordinates in millimeters.
1007 Can be a float or a numpy array.
1009 @param [in] yFocal the y focal plane coordinates in millimeters.
1010 Can be a float or a numpy array.
1012 @param [in] camera is an afw.cameraGeom camera object
1014 @param [out] a 2-D numpy array in which the first row is the x
1015 pupil coordinate and the second row is the y pupil
1016 coordinate (both in radians)
1017 """
1019 are_arrays = _validate_inputs([xFocal, yFocal],
1020 ['xFocal', 'yFocal'],
1021 'pupilCoordsFromFocalPlaneCoords')
1023 if camera is None:
1024 raise RuntimeError("You cannot calculate pupil coordinates without specifying a camera")
1026 focal_to_field = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
1028 if are_arrays:
1029 focal_point_list = [geom.Point2D(x,y) for x,y in zip(xFocal, yFocal)]
1030 pupil_point_list = focal_to_field.applyForward(focal_point_list)
1031 pupil_arr = np.array([[pp.getX(), pp.getY()]
1032 for pp in pupil_point_list]).transpose()
1033 is_nan = np.where(np.logical_or(np.isnan(xFocal), np.isnan(yFocal)))
1034 pupil_arr[0][is_nan] = np.NaN
1035 pupil_arr[1][is_nan] = np.NaN
1037 return pupil_arr
1039 # if not are_arrays
1040 if np.isfinite(xFocal) and np.isfinite(yFocal):
1041 pupPoint = focal_to_field.applyForward(geom.Point2D(xFocal, yFocal))
1042 return np.array([pupPoint.getX(), pupPoint.getY()])
1044 return np.array([np.NaN, np.NaN])