22 __all__ = [
"FitAffineWcsTask",
"FitAffineWcsConfig",
"TransformedSkyWcsMaker"]
27 from scipy.optimize
import least_squares
31 from lsst.geom import Point2D, degrees, arcseconds, radians
34 from lsst.utils.timer
import timeMethod
36 from .makeMatchStatistics
import makeMatchStatisticsInRadians
37 from .setMatchDistance
import setMatchDistance
40 def _chiFunc(x, refPoints, srcPixels, wcsMaker):
41 """Function to minimize to fit the shift and rotation in the WCS.
46 Current fit values to test. Float values in array are:
48 - ``bearingTo``: Direction to move the wcs coord in.
49 - ``separation``: Distance along sphere to move wcs coord in.
50 - ``affine0,0``: [0, 0] value of the 2x2 affine transform matrix.
51 - ``affine0,1``: [0, 1] value of the 2x2 affine transform matrix.
52 - ``affine1,0``: [1, 0] value of the 2x2 affine transform matrix.
53 - ``affine1,1``: [1, 1] value of the 2x2 affine transform matrix.
54 refPoints : `list` of `lsst.afw.geom.SpherePoint`
55 Reference object on Sky locations.
56 srcPixels : `list` of `lsst.geom.Point2D`
57 Source object positions on the pixels.
58 wcsMaker : `TransformedSkyWcsMaker`
59 Container class for producing the updated Wcs.
63 outputSeparations : `list` of `float`
64 Separation between predicted source location and reference location in
67 wcs = wcsMaker.makeWcs(x[:2], x[2:].reshape((2, 2)))
69 outputSeparations = []
72 for ref, src
in zip(refPoints, srcPixels):
73 skySep = ref.getTangentPlaneOffset(wcs.pixelToSky(src))
74 outputSeparations.append(skySep[0].asArcseconds())
75 outputSeparations.append(skySep[1].asArcseconds())
76 xySep = src - wcs.skyToPixel(ref)
79 outputSeparations.append(
80 xySep[0] * wcs.getPixelScale(src).asArcseconds())
81 outputSeparations.append(
82 xySep[1] * wcs.getPixelScale(src).asArcseconds())
84 return outputSeparations
91 """Config for FitTanSipWcsTask."""
96 """Fit a TAN-SIP WCS given a list of reference object/source matches.
98 This WCS fitter should be used on top of a cameraGeom distortion model as
99 the model assumes that only a shift the WCS center position and a small
100 affine transform are required.
102 ConfigClass = FitAffineWcsConfig
103 _DefaultName =
"fitAffineWcs"
113 """Fit a simple Affine transform with a shift to the matches and update
116 This method assumes that the distortion model of the telescope is
117 applied correctly and is accurate with only a slight rotation,
118 rotation, and "squish" required to fit to the reference locations.
122 matches : `list` of `lsst.afw.table.ReferenceMatch`
123 The following fields are read:
125 - match.first (reference object) coord
126 - match.second (source) centroid
128 The following fields are written:
130 - match.first (reference object) centroid,
131 - match.second (source) centroid
132 - match.distance (on sky separation, in radians)
134 initWcs : `lsst.afw.geom.SkyWcs`
136 bbox : `lsst.geom.Box2I`
137 Ignored; present for consistency with FitSipDistortionTask.
138 refCat : `lsst.afw.table.SimpleCatalog`
139 reference object catalog, or None.
140 If provided then all centroids are updated with the new WCS,
141 otherwise only the centroids for ref objects in matches are
142 updated. Required fields are "centroid_x", "centroid_y",
143 "coord_ra", and "coord_dec".
144 sourceCat : `lsst.afw.table.SourceCatalog`
145 source catalog, or None.
146 If provided then coords are updated with the new WCS;
147 otherwise only the coords for sources in matches are updated.
148 Required fields are "slot_Centroid_x", "slot_Centroid_y", and
149 "coord_ra", and "coord_dec".
150 exposure : `lsst.afw.image.Exposure`
151 Ignored; present for consistency with FitSipDistortionTask.
155 result : `lsst.pipe.base.Struct`
156 with the following fields:
158 - ``wcs`` : the fit WCS (`lsst.afw.geom.SkyWcs`)
159 - ``scatterOnSky`` : median on-sky separation between reference
160 objects and sources in "matches" (`lsst.afw.geom.Angle`)
172 for match
in matches:
173 refCoord = match.first.getCoord()
174 refPoints.append(refCoord)
175 srcCentroid = match.second.getCentroid()
176 srcPixels.append(srcCentroid)
177 srcCoord = initWcs.pixelToSky(srcCentroid)
178 deltaLong, deltaLat = srcCoord.getTangentPlaneOffset(refCoord)
179 offsetLong += deltaLong.asArcseconds()
180 offsetLat += deltaLat.asArcseconds()
181 offsetLong /= len(srcPixels)
182 offsetLat /= len(srcPixels)
183 offsetDist = np.sqrt(offsetLong ** 2 + offsetLat ** 2)
185 offsetDir = np.degrees(np.arccos(offsetLong / offsetDist))
188 offsetDir *= np.sign(offsetLat)
189 self.log.
debug(
"Initial shift guess: Direction: %.3f, Dist %.3f...",
190 offsetDir, offsetDist)
198 x0=[offsetDir, offsetDist, 1., 1e-8, 1e-8, 1.],
199 args=(refPoints, srcPixels, wcsMaker),
201 bounds=[[-360, -np.inf, -np.inf, -np.inf, -np.inf, -np.inf],
202 [360, np.inf, np.inf, np.inf, np.inf, np.inf]],
206 self.log.
debug(
"Best fit: Direction: %.3f, Dist: %.3f, "
207 "Affine matrix: [[%.6f, %.6f], [%.6f, %.6f]]...",
209 fit.x[2], fit.x[3], fit.x[4], fit.x[5])
211 wcs = wcsMaker.makeWcs(fit.x[:2], fit.x[2:].reshape((2, 2)))
214 if refCat
is not None:
215 self.log.
debug(
"Updating centroids in refCat")
218 self.log.
warning(
"Updating reference object centroids in match list; refCat is None")
221 refList=[match.first
for match
in matches])
223 if sourceCat
is not None:
224 self.log.
debug(
"Updating coords in sourceCat")
227 self.log.
warning(
"Updating source coords in match list; sourceCat is None")
230 sourceList=[match.second
for match
in matches])
235 lsst.afw.math.MEDIAN)
236 scatterOnSky = stats.getValue() * radians
238 self.log.
debug(
"In fitter scatter %.4f", scatterOnSky.asArcseconds())
240 return lsst.pipe.base.Struct(
242 scatterOnSky=scatterOnSky,
247 """Convenience class for appending a shifting an input SkyWcs on sky and
248 appending an affine transform.
250 The class assumes that all frames are sequential and are mapped one to the
255 input_sky_wcs : `lsst.afw.geom.SkyWcs`
256 WCS to decompose and append affine matrix and shift in on sky
268 domains = self.
frameDictframeDict.getAllDomains()
270 for domain
in domains])
287 self.
originorigin = inputSkyWcs.getSkyOrigin()
290 """Apply a shift and affine transform to the WCS internal to this
293 A new SkyWcs with these transforms applied is returns.
297 crval_shift : `numpy.ndarray`, (2,)
298 Shift in radians to apply to the Wcs origin/crvals.
299 aff_matrix : 'numpy.ndarray', (3, 3)
300 Affine matrix to apply to the mapping/transform to add to the
305 outputWcs : `lsst.afw.geom.SkyWcs`
306 Wcs with a final shift and affine transform applied.
314 self.
originorigin.offset(crvalOffset[0] * degrees,
315 crvalOffset[1] * arcseconds),
316 np.array([[1., 0.], [0., 1.]]))
317 iwcToSkyMap = iwcsToSkyWcs.getFrameDict().getMapping(
"PIXELS",
"SKY")
326 outputFrameDict = astshim.FrameDict(
329 if frameIdx == self.
mapFrommapFrom:
330 outputFrameDict.addFrame(
334 elif frameIdx >= self.
mapTomapTo:
337 outputFrameDict.addFrame(
339 self.
frameDictframeDict.getMapping(frameIdx, frameIdx + 1),
342 outputFrameDict.addFrame(
345 iwcsToSkyWcs.getFrameDict().
getFrame(
"SKY"))
347 return SkyWcs(outputFrameDict)
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
Update centroids in a collection of reference objects.
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
Update sky coordinates in a collection of source objects.
Point< double, 2 > Point2D
def setMatchDistance(matches)
afw::math::Statistics makeMatchStatisticsInRadians(afw::geom::SkyWcs const &wcs, std::vector< MatchT > const &matchList, int const flags, afw::math::StatisticsControl const &sctrl=afw::math::StatisticsControl())
Compute statistics of on-sky radial separation for a match list, in radians.