22 __all__ = [
"FitAffineWcsTask",
"FitAffineWcsConfig",
"TransformedSkyWcsMaker"]
27 from scipy.optimize
import least_squares
29 from lsst.afw.geom import makeSkyWcs, degrees, arcseconds, radians, SkyWcs
32 import lsst.pex.config
as pexConfig
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())
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 271 for domain
in domains])
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.
origin.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(
331 outputFrameDict.addFrame(
335 elif frameIdx >= self.
mapTo:
338 outputFrameDict.addFrame(
340 self.
frameDict.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)
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
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.
def setMatchDistance(matches)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.