22 __all__ = [
"FitAffineWcsTask", 
"FitAffineWcsConfig", 
"TransformedSkyWcsMaker"]
 
   27 from scipy.optimize 
import least_squares
 
   31 from lsst.geom import Point2D, degrees, arcseconds, radians
 
   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)