22__all__ = [
"FitAffineWcsTask",
"FitAffineWcsConfig",
"TransformedSkyWcsMaker"]
27from scipy.optimize
import least_squares
31from lsst.geom import Point2D, degrees, arcseconds, radians
34from lsst.utils.timer
import timeMethod
36from .makeMatchStatistics
import makeMatchStatisticsInRadians
37from .setMatchDistance
import setMatchDistance
40def _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.
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.
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)
137 Ignored; present
for consistency
with FitSipDistortionTask.
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".
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".
151 Ignored; present
for consistency
with FitSipDistortionTask.
155 result : `lsst.pipe.base.Struct`
156 with the following fields:
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])
231 setMatchDistance(matches)
233 stats = makeMatchStatisticsInRadians(wcs,
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
256 WCS to decompose
and append affine matrix
and shift
in on sky
270 for domain
in domains])
289 def makeWcs(self, crvalOffset, affMatrix):
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
306 Wcs
with a final shift
and affine transform applied.
312 iwcsToSkyWcs = makeSkyWcs(
314 self.
origin.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(
330 outputFrameDict.addFrame(
334 elif frameIdx >= self.
mapTo:
337 outputFrameDict.addFrame(
339 self.
frameDict.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...
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
An integer coordinate rectangle.
def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None)
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.
Lightweight representation of a geometric match between two records.