22__all__ = [
"FitAffineWcsTask",
"FitAffineWcsConfig",
"TransformedSkyWcsMaker"]
27from scipy.optimize
import least_squares
31from lsst.geom import Point2D, degrees, arcseconds, radians
35from .makeMatchStatistics
import makeMatchStatisticsInRadians
36from .setMatchDistance
import setMatchDistance
39def _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.
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.
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)
136 Ignored; present
for consistency
with FitSipDistortionTask.
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".
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".
150 Ignored; present
for consistency
with FitSipDistortionTask.
154 result : `lsst.pipe.base.Struct`
155 with the following fields:
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
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
307 Wcs
with a final shift
and affine transform applied.
313 iwcsToSkyWcs = makeSkyWcs(
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")
322 newMapping = self.
lastMapBeforeSkylastMapBeforeSky.then(astshim.MatrixMap(affMatrix))
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),
341 self.
frameDictframeDict.getFrame(frameIdx + 1))
343 outputFrameDict.addFrame(
346 iwcsToSkyWcs.getFrameDict().getFrame(
"SKY"))
348 return SkyWcs(outputFrameDict)
def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
void updateRefCentroids(geom::SkyWcs const &wcs, ReferenceCollection &refList)
void updateSourceCoords(geom::SkyWcs const &wcs, SourceCollection &sourceList)
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.