22"""Code to convert jointcal's output WCS models to distortion maps that can be
23used by afw CameraGeom.
25__all__ = [
"CameraModel"]
33from lsst.geom import SpherePoint, Point2D, radians
35_LOG = logging.getLogger(__name__)
39 """Convert a jointcal `~lsst.afw.geom.SkyWcs` into a distortion model and
42 Because this code only operates on the WCS, it is independent of the
43 format of the persisted output (e.g. gen2 separate files vs. gen3 bundled
49 The WCS to use to compute the distortion model
from, preferably
from
50 multiple visits on the same tract.
51 detectors : `list` [`int`]
52 Detector ids that correspond one-to-one
with ``wcsList``.
54 The camera these WCS were fit
for.
56 Number of points to compute the pixel scale at, along the +y axis.
58 def __init__(self, wcsList, detectors, camera, n=100):
76 self.
log = _LOG.getChild(
"CameraModel")
79 """Calculate the afw cameraGeom distortion model to be included in an
82 PLACEHOLDER: This may be as simple
as running `computePixelScale`
and
83 then doing a numpy polynomial fit to it
for the cameraGeom input.
84 However, we need to check details of how that distortion model
is
85 stored
in a Camera. e.g.:
88 raise NotImplementedError(
"not yet!")
91 """Compute the radial and tangential pixel scale by averaging over
92 multiple jointcal WCS models.
94 Also computes the standard deviation and logs any WCS that are
96 The calculations are stored
in the ``fieldAngle[s]``,
97 ``radialScale[s]``,
and ``tangentialScale[s]`` member variables.
113 self.
log.warning(
"Large stddev in computed field angles between visits (max: %s degree).",
119 self.
log.warning(
"Large stddev in computed radial scales between visits (max: %s arcsec).",
124 self.
log.warning(
"Large stddev in computed tangential scales between visits (max: %s arcsec).",
128 """Compute the radial and tangential pixel scales using the distortion
129 model supplied with the camera.
131 This
is designed to be directly comparable
with the results of
132 `~CameraModel.computePixelScale`.
137 Detector identifier
for the detector_id to use
for the calculation.
141 fieldAngle : `numpy.ndarray`
142 Field angles
in degrees.
143 radialScale : `numpy.ndarray`
144 Radial direction pixel scales
in arcseconds/pixel.
145 tangentialScale : `numpy.ndarray`
146 Tangential direction pixel scales
in arcseconds/pixel.
151 iwcToSkyMap = iwcToSkyWcs.getFrameDict().getMapping(
"PIXELS",
"SKY")
152 skyFrame = iwcToSkyWcs.getFrameDict().getFrame(
"SKY")
155 pixSys = self.
camera[detector_id].makeCameraSys(cameraGeom.PIXELS)
156 pixelsToFocal = self.
camera.getTransform(pixSys, cameraGeom.FOCAL_PLANE)
157 focalToField = self.
camera.getTransform(cameraGeom.FOCAL_PLANE, cameraGeom.FIELD_ANGLE)
160 pixelFrame =
ast.Frame(2,
"Domain=PIXELS")
161 focalFrame =
ast.Frame(2,
"Domain=FOCAL")
164 frameDict.addFrame(
"PIXELS", pixelsToFocal.getMapping(), focalFrame)
165 frameDict.addFrame(
"FOCAL", focalToField.getMapping(), iwcFrame)
166 frameDict.addFrame(
"IWC", iwcToSkyMap, skyFrame)
171 def _computeDetectorPixelScale(self, detector_id, wcs):
172 """Compute pixel scale in radial and tangential directions as a
173 function of field angle.
178 Detector identifier for the detector of this wcs.
180 Full focal-plane model to compute pixel scale on.
184 fieldAngle : `numpy.ndarray`
185 Field angles
in degrees.
186 radialScale : `numpy.ndarray`
187 Radial direction pixel scales
in arcseconds/pixel.
188 tangentialScale : `numpy.ndarray`
189 Tangential direction pixel scales
in arcseconds/pixel.
193 Pixel scales are calculated
from finite differences only along the +y
194 focal plane direction.
196 focalToSky = wcs.getFrameDict().getMapping('FOCAL',
'SKY')
197 mmPerPixel = self.
camera[detector_id].getPixelSize()
199 focalToPixels = wcs.getFrameDict().getMapping(
'FOCAL',
'PIXELS')
200 trans = wcs.getTransform()
201 boresight = trans.applyForward(Point2D(focalToPixels.applyForward([0, 0])))
204 fieldAngle = np.zeros_like(rs)
205 radialScale = np.zeros_like(rs)
206 tangentialScale = np.zeros_like(rs)
207 for i, r
in enumerate(rs):
209 sp1 =
SpherePoint(*focalToSky.applyForward(Point2D([0, r])), radians)
211 sp2 =
SpherePoint(*focalToSky.applyForward(Point2D([0, r + mmPerPixel.getY()])), radians)
213 sp3 =
SpherePoint(*focalToSky.applyForward(Point2D([mmPerPixel.getX(), r])), radians)
214 fieldAngle[i] = boresight.separation(sp1).asDegrees()
215 radialScale[i] = sp1.separation(sp2).asArcseconds()
216 tangentialScale[i] = sp1.separation(sp3).asArcseconds()
217 return fieldAngle, radialScale, tangentialScale
A FrameSet whose frames can be referenced by domain name.
Frame is used to represent a coordinate system.
An immutable representation of a camera.
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)
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.