22 __all__ = [
"FitAffineWcsTask",
"FitAffineWcsConfig",
"TransformedSkyWcsMaker"]
27 from scipy.optimize
import least_squares
31 from lsst.geom import Point2D, degrees, arcseconds, radians
35 from .makeMatchStatistics
import makeMatchStatisticsInRadians
36 from .setMatchDistance
import setMatchDistance
39 def _chiFunc(x, refPoints, srcPixels, wcsMaker):
40 """Function to minimize to fit the shift and rotation in the WCS.
45 Current fit values to test. Float values in array are:
47 - ``bearingTo``: Direction to move the wcs coord in.
48 - ``separation``: Distance along sphere to move wcs coord in.
49 - ``affine0,0``: [0, 0] value of the 2x2 affine transform matrix.
50 - ``affine0,1``: [0, 1] value of the 2x2 affine transform matrix.
51 - ``affine1,0``: [1, 0] value of the 2x2 affine transform matrix.
52 - ``affine1,1``: [1, 1] value of the 2x2 affine transform matrix.
53 refPoints : `list` of `lsst.afw.geom.SpherePoint`
54 Reference object on Sky locations.
55 srcPixels : `list` of `lsst.geom.Point2D`
56 Source object positions on the pixels.
57 wcsMaker : `TransformedSkyWcsMaker`
58 Container class for producing the updated Wcs.
62 outputSeparations : `list` of `float`
63 Separation between predicted source location and reference location in
66 wcs = wcsMaker.makeWcs(x[:2], x[2:].reshape((2, 2)))
68 outputSeparations = []
71 for ref, src
in zip(refPoints, srcPixels):
72 skySep = ref.getTangentPlaneOffset(wcs.pixelToSky(src))
73 outputSeparations.append(skySep[0].asArcseconds())
74 outputSeparations.append(skySep[1].asArcseconds())
75 xySep = src - wcs.skyToPixel(ref)
78 outputSeparations.append(
79 xySep[0] * wcs.getPixelScale(src).asArcseconds())
80 outputSeparations.append(
81 xySep[1] * wcs.getPixelScale(src).asArcseconds())
83 return outputSeparations
90 """Config for FitTanSipWcsTask."""
95 """Fit a TAN-SIP WCS given a list of reference object/source matches.
97 This WCS fitter should be used on top of a cameraGeom distortion model as
98 the model assumes that only a shift the WCS center position and a small
99 affine transform are required.
101 ConfigClass = FitAffineWcsConfig
102 _DefaultName =
"fitAffineWcs"
112 """Fit a simple Affine transform with a shift to the matches and update
115 This method assumes that the distortion model of the telescope is
116 applied correctly and is accurate with only a slight rotation,
117 rotation, and "squish" required to fit to the reference locations.
121 matches : `list` of `lsst.afw.table.ReferenceMatch`
122 The following fields are read:
124 - match.first (reference object) coord
125 - match.second (source) centroid
127 The following fields are written:
129 - match.first (reference object) centroid,
130 - match.second (source) centroid
131 - match.distance (on sky separation, in radians)
133 initWcs : `lsst.afw.geom.SkyWcs`
135 bbox : `lsst.geom.Box2I`
136 Ignored; present for consistency with FitSipDistortionTask.
137 refCat : `lsst.afw.table.SimpleCatalog`
138 reference object catalog, or None.
139 If provided then all centroids are updated with the new WCS,
140 otherwise only the centroids for ref objects in matches are
141 updated. Required fields are "centroid_x", "centroid_y",
142 "coord_ra", and "coord_dec".
143 sourceCat : `lsst.afw.table.SourceCatalog`
144 source catalog, or None.
145 If provided then coords are updated with the new WCS;
146 otherwise only the coords for sources in matches are updated.
147 Required fields are "slot_Centroid_x", "slot_Centroid_y", and
148 "coord_ra", and "coord_dec".
149 exposure : `lsst.afw.image.Exposure`
150 Ignored; present for consistency with FitSipDistortionTask.
154 result : `lsst.pipe.base.Struct`
155 with the following fields:
157 - ``wcs`` : the fit WCS (`lsst.afw.geom.SkyWcs`)
158 - ``scatterOnSky`` : median on-sky separation between reference
159 objects and sources in "matches" (`lsst.afw.geom.Angle`)
171 for match
in matches:
172 refCoord = match.first.getCoord()
173 refPoints.append(refCoord)
174 srcCentroid = match.second.getCentroid()
175 srcPixels.append(srcCentroid)
176 srcCoord = initWcs.pixelToSky(srcCentroid)
177 deltaLong, deltaLat = srcCoord.getTangentPlaneOffset(refCoord)
178 offsetLong += deltaLong.asArcseconds()
179 offsetLat += deltaLat.asArcseconds()
180 offsetLong /= len(srcPixels)
181 offsetLat /= len(srcPixels)
182 offsetDist = np.sqrt(offsetLong ** 2 + offsetLat ** 2)
184 offsetDir = np.degrees(np.arccos(offsetLong / offsetDist))
187 offsetDir *= np.sign(offsetLat)
188 self.log.
debug(
"Initial shift guess: Direction: %.3f, Dist %.3f..." %
189 (offsetDir, offsetDist))
197 x0=[offsetDir, offsetDist, 1., 1e-8, 1e-8, 1.],
198 args=(refPoints, srcPixels, wcsMaker),
200 bounds=[[-360, -np.inf, -np.inf, -np.inf, -np.inf, -np.inf],
201 [360, np.inf, np.inf, np.inf, np.inf, np.inf]],
205 self.log.
debug(
"Best fit: Direction: %.3f, Dist: %.3f, "
206 "Affine matrix: [[%.6f, %.6f], [%.6f, %.6f]]..." %
208 fit.x[2], fit.x[3], fit.x[4], fit.x[5]))
210 wcs = wcsMaker.makeWcs(fit.x[:2], fit.x[2:].reshape((2, 2)))
213 if refCat
is not None:
214 self.log.
debug(
"Updating centroids in refCat")
217 self.log.
warn(
"Updating reference object centroids in match list; "
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.
warn(
"Updating source coords in match list; sourceCat is "
231 sourceList=[match.second
for match
in matches])
236 lsst.afw.math.MEDIAN)
237 scatterOnSky = stats.getValue() * radians
239 self.log.
debug(
"In fitter scatter %.4f" % scatterOnSky.asArcseconds())
241 return lsst.pipe.base.Struct(
243 scatterOnSky=scatterOnSky,
248 """Convenience class for appending a shifting an input SkyWcs on sky and
249 appending an affine transform.
251 The class assumes that all frames are sequential and are mapped one to the
256 input_sky_wcs : `lsst.afw.geom.SkyWcs`
257 WCS to decompose and append affine matrix and shift in on sky
269 domains = self.
frameDictframeDict.getAllDomains()
271 for domain
in domains])
288 self.
originorigin = inputSkyWcs.getSkyOrigin()
291 """Apply a shift and affine transform to the WCS internal to this
294 A new SkyWcs with these transforms applied is returns.
298 crval_shift : `numpy.ndarray`, (2,)
299 Shift in radians to apply to the Wcs origin/crvals.
300 aff_matrix : 'numpy.ndarray', (3, 3)
301 Affine matrix to apply to the mapping/transform to add to the
306 outputWcs : `lsst.afw.geom.SkyWcs`
307 Wcs with a final shift and affine transform applied.
315 self.
originorigin.offset(crvalOffset[0] * degrees,
316 crvalOffset[1] * arcseconds),
317 np.array([[1., 0.], [0., 1.]]))
318 iwcToSkyMap = iwcsToSkyWcs.getFrameDict().getMapping(
"PIXELS",
"SKY")
327 outputFrameDict = astshim.FrameDict(
330 if frameIdx == self.
mapFrommapFrom:
331 outputFrameDict.addFrame(
335 elif frameIdx >= self.
mapTomapTo:
338 outputFrameDict.addFrame(
340 self.
frameDictframeDict.getMapping(frameIdx, frameIdx + 1),
343 outputFrameDict.addFrame(
346 iwcsToSkyWcs.getFrameDict().
getFrame(
"SKY"))
348 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.