22 """Code to convert jointcal's output WCS models to distortion maps that can be
23 used by afw CameraGeom.
31 from lsst.geom import SpherePoint, Point2D, radians
35 """Convert a jointcal `~lsst.afw.geom.SkyWcs` into a distortion model and
36 detector positions (TODO) that can be used by `~lsst.afw.cameraGeom`.
38 Because this code only operates on the WCS, it is independent of the
39 format of the persisted output (e.g. gen2 separate files vs. gen3 bundled
44 wcsList : `list` [`lsst.afw.geom.SkyWcs`]
45 The WCS to use to compute the distortion model from, preferably from
46 multiple visits on the same tract.
47 detectors : `list` [`int`]
48 Detector ids that correspond one-to-one with ``wcsList``.
49 camera : `lsst.afw.cameraGeom.Camera`
50 The camera these WCS were fit for.
52 Number of points to compute the pixel scale at, along the +y axis.
54 def __init__(self, wcsList, detectors, camera, n=100):
75 """Calculate the afw cameraGeom distortion model to be included in an
79 PLACEHOLDER: This may be as simple as running `computePixelScale` and
80 then doing a numpy polynomial fit to it for the cameraGeom input.
81 However, we need to check details of how that distortion model is
83 e.g.: np.polyfit(self.fieldAngle, self.radialScale, poly_degree))
85 raise NotImplementedError(
"not yet!")
88 """Compute the radial and tangential pixel scale by averaging over
89 multiple jointcal WCS models.
91 Also computes the standard deviation and logs any WCS that are
93 The calculations are stored in the ``fieldAngle[s]``,
94 ``radialScale[s]``, and ``tangentialScale[s]`` member variables.
110 self.
loglog.
warn(
"Large stddev in computed field angles between visits (max: %s degree).",
116 self.
loglog.
warn(
"Large stddev in computed radial scales between visits (max: %s arcsec).",
121 self.
loglog.
warn(
"Large stddev in computed tangential scales between visits (max: %s arcsec).",
125 """Compute the radial and tangential pixel scales using the distortion
126 model supplied with the camera.
128 This is designed to be directly comparable with the results of
129 `~CameraModel.computePixelScale`.
134 Detector identifier for the detector_id to use for the calculation.
138 fieldAngle : `numpy.ndarray`
139 Field angles in degrees.
140 radialScale : `numpy.ndarray`
141 Radial direction pixel scales in arcseconds/pixel.
142 tangentialScale : `numpy.ndarray`
143 Tangential direction pixel scales in arcseconds/pixel.
148 iwcToSkyMap = iwcToSkyWcs.getFrameDict().getMapping(
"PIXELS",
"SKY")
149 skyFrame = iwcToSkyWcs.getFrameDict().
getFrame(
"SKY")
152 pixSys = self.
cameracamera[detector_id].makeCameraSys(cameraGeom.PIXELS)
153 pixelsToFocal = self.
cameracamera.getTransform(pixSys, cameraGeom.FOCAL_PLANE)
154 focalToField = self.
cameracamera.getTransform(cameraGeom.FOCAL_PLANE, cameraGeom.FIELD_ANGLE)
157 pixelFrame =
ast.Frame(2,
"Domain=PIXELS")
158 focalFrame =
ast.Frame(2,
"Domain=FOCAL")
161 frameDict.addFrame(
"PIXELS", pixelsToFocal.getMapping(), focalFrame)
162 frameDict.addFrame(
"FOCAL", focalToField.getMapping(), iwcFrame)
163 frameDict.addFrame(
"IWC", iwcToSkyMap, skyFrame)
168 def _computeDetectorPixelScale(self, detector_id, wcs):
169 """Compute pixel scale in radial and tangential directions as a
170 function of field angle.
175 Detector identifier for the detector of this wcs.
176 wcs : `lsst.afw.geom.SkyWcs`
177 Full focal-plane model to compute pixel scale on.
181 fieldAngle : `numpy.ndarray`
182 Field angles in degrees.
183 radialScale : `numpy.ndarray`
184 Radial direction pixel scales in arcseconds/pixel.
185 tangentialScale : `numpy.ndarray`
186 Tangential direction pixel scales in arcseconds/pixel.
190 Pixel scales are calculated from finite differences only along the +y
191 focal plane direction.
193 focalToSky = wcs.getFrameDict().getMapping(
'FOCAL',
'SKY')
194 mmPerPixel = self.
cameracamera[detector_id].getPixelSize()
196 focalToPixels = wcs.getFrameDict().getMapping(
'FOCAL',
'PIXELS')
197 trans = wcs.getTransform()
198 boresight = trans.applyForward(
Point2D(focalToPixels.applyForward([0, 0])))
201 fieldAngle = np.zeros_like(rs)
202 radialScale = np.zeros_like(rs)
203 tangentialScale = np.zeros_like(rs)
204 for i, r
in enumerate(rs):
208 sp2 =
SpherePoint(*focalToSky.applyForward(
Point2D([0, r + mmPerPixel.getY()])), radians)
211 fieldAngle[i] = boresight.separation(sp1).asDegrees()
212 radialScale[i] = sp1.separation(sp2).asArcseconds()
213 tangentialScale[i] = sp1.separation(sp3).asArcseconds()
214 return fieldAngle, radialScale, tangentialScale
A FrameSet whose frames can be referenced by domain name.
Frame is used to represent a coordinate system.
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Point in an unspecified spherical coordinate system.
def computeCameraPixelScale(self, detector_id=30)
def computePixelScale(self)
def computeDistortionModel(self)
def __init__(self, wcsList, detectors, camera, n=100)
def _computeDetectorPixelScale(self, detector_id, wcs)
static Log getLogger(Log const &logger)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
Make a WCS CD matrix.
Point< double, 2 > Point2D