def doTest(self, name, func, order=3, numIter=4, specifyBBox=False, doPlot=False):
        """Apply func(x, y) to each source in self.sourceCat, then fit and check the resulting WCS
        """
        bbox = afwGeom.Box2I()
        for refObj, src, d in self.matches:
            origPos = src.get(self.srcCentroidKey)
            x, y = func(*origPos)
            distortedPos = afwGeom.Point2D(*func(*origPos))
            src.set(self.srcCentroidKey, distortedPos)
            bbox.include(afwGeom.Point2I(afwGeom.Point2I(distortedPos)))

        tanSipWcs = self.tanWcs
        for i in range(numIter):
            if specifyBBox:
                sipObject = makeCreateWcsWithSip(self.matches, tanSipWcs, order, bbox)
            else:
                sipObject = makeCreateWcsWithSip(self.matches, tanSipWcs, order)
            tanSipWcs = sipObject.getNewWcs()
        setMatchDistance(self.matches)
        fitRes = lsst.pipe.base.Struct(
            wcs = tanSipWcs,
            scatterOnSky = sipObject.getScatterOnSky(),
        )

        if doPlot:
            self.plotWcs(tanSipWcs, name=name)

        self.checkResults(fitRes, catsUpdated=False)

        if self.MatchClass == afwTable.ReferenceMatch:
            # reset source coord and reference centroid based on initial WCS
            FitTanSipWcsTask.updateRefCentroids(wcs=self.tanWcs, refList = self.refCat)
            FitTanSipWcsTask.updateSourceCoords(wcs=self.tanWcs, sourceList = self.sourceCat)

            fitterConfig = FitTanSipWcsTask.ConfigClass()
            fitterConfig.order = order
            fitterConfig.numIter = numIter
            fitter = FitTanSipWcsTask(config=fitterConfig)
            self.loadData()
            if specifyBBox:
                fitRes = fitter.fitWcs(
                    matches = self.matches,
                    initWcs = self.tanWcs,
                    bbox = bbox,
                    refCat = self.refCat,
                    sourceCat = self.sourceCat,
                )
            else:
                fitRes = fitter.fitWcs(
                    matches = self.matches,
                    initWcs = self.tanWcs,
                    bbox = bbox,
                    refCat = self.refCat,
                    sourceCat = self.sourceCat,
                )

            self.checkResults(fitRes, catsUpdated=True)
Example #2
0
    def refitWcs(self, exposure, sources, matches):
        """We have better matches after solving astrometry, so re-solve the WCS

        @param exposure Exposure of interest
        @param sources Sources on image (no distortion applied)
        @param matches Astrometric matches

        @return the resolved-Wcs object, or None if config.solver.calculateSip is False.
        """
        wcs = exposure.getWcs()

        sip = None

        self.log.info("Refitting WCS")
        # Re-fit the WCS using the current matches
        if self.config.solver.calculateSip:
            try:
                sip = makeCreateWcsWithSip(matches, exposure.getWcs(), self.config.solver.sipOrder)
            except Exception, e:
                self.log.warn("Fitting SIP failed: %s" % e)
                sip = None

            if sip:
                wcs = sip.getNewWcs()
                self.log.info("Astrometric scatter: %f arcsec (%s non-linear terms)" %
                              (sip.getScatterOnSky().asArcseconds(), "with" if wcs.hasDistortion() else "without"))
                exposure.setWcs(wcs)
            
                # Apply WCS to sources
                for index, source in enumerate(sources):
                    sky = wcs.pixelToSky(source.getX(), source.getY())
                    source.setCoord(sky)
    def _calculateSipTerms(self, origWcs, refCat, sourceCat, matches, bbox):
        """!Iteratively calculate SIP distortions and regenerate matches based on improved WCS.

        @param[in] origWcs  original WCS object, probably (but not necessarily) a TAN WCS;
           this is used to set the baseline when determining whether a SIP
           solution is any better; it will be returned if no better SIP solution
           can be found.
        @param[in] refCat  reference source catalog
        @param[in] sourceCat  sources in the image to be solved
        @param[in] matches  list of supposedly matched sources, using the "origWcs".
        @param[in] bbox  bounding box of image, which is used when finding reverse SIP coefficients.
        """
        sipOrder = self.config.sipOrder
        wcs = origWcs

        lastMatchSize = len(matches)
        lastMatchStats = self._computeMatchStatsOnSky(wcs=wcs, matchList=matches)
        for i in range(self.config.maxIter):
            # fit SIP terms
            try:
                sipObject = astromSip.makeCreateWcsWithSip(matches, wcs, sipOrder, bbox)
                proposedWcs = sipObject.getNewWcs()
                self.plotSolution(matches, proposedWcs, bbox.getDimensions())
            except pexExceptions.Exception as e:
                self.log.warn('Failed to calculate distortion terms. Error: ', str(e))
                break

            # update the source catalog
            for source in sourceCat:
                skyPos = proposedWcs.pixelToSky(source.getCentroid())
                source.setCoord(skyPos)

            # use new WCS to get new matchlist.
            proposedMatchlist = self._getMatchList(sourceCat, refCat, proposedWcs)
            proposedMatchSize = len(proposedMatchlist)
            proposedMatchStats = self._computeMatchStatsOnSky(wcs=proposedWcs, matchList=proposedMatchlist)

            self.log.debug(
                "SIP iteration %i: %i objects match, previous = %i;" %
                (i, proposedMatchSize, lastMatchSize) +
                " clipped mean scatter = %s arcsec, previous = %s; " %
                (proposedMatchStats.distMean.asArcseconds(), lastMatchStats.distMean.asArcseconds()) +
                " max match dist = %s arcsec, previous = %s" %
                (proposedMatchStats.maxMatchDist.asArcseconds(),
                 lastMatchStats.maxMatchDist.asArcseconds())
            )

            if lastMatchStats.maxMatchDist <= proposedMatchStats.maxMatchDist:
                self.log.debug(
                    "Fit WCS: use iter %s because max match distance no better in next iter: " % (i-1,) +
                    " %g < %g arcsec" % (lastMatchStats.maxMatchDist.asArcseconds(),
                                         proposedMatchStats.maxMatchDist.asArcseconds()))
                break

            wcs = proposedWcs
            matches = proposedMatchlist
            lastMatchSize = proposedMatchSize
            lastMatchStats = proposedMatchStats

        return wcs, matches
    def singleTestInstance(self, filename, distortFunc):
        cat = self.loadCatalogue(self.filename)
        img = distort.distortList(cat, distortFunc)
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                             afwGeom.Extent2I(1000, 1000))
        res = self.astrom.determineWcs2(img, bbox=bbox)
        imgWcs = res.getWcs()

        # Create a wcs with sip
        cat = cat.cast(SimpleCatalog, False)
        matchList = self.matchSrcAndCatalogue(cat, img, imgWcs)
        print("*** num matches =", len(matchList))
        return
        sipObject = sip.makeCreateWcsWithSip(matchList, imgWcs, 3)

        imgWcs = sipObject.getNewWcs()

        print('number of matches:', len(matchList), sipObject.getNPoints())
        scatter = sipObject.getScatterOnSky().asArcseconds()
        print("Scatter in arcsec is %g" % (scatter))

        self.assertLess(scatter, self.tolArcsec,
                        "Scatter exceeds tolerance in arcsec")

        if False:
            scatter = sipObject.getScatterInPixels()
            self.assertLess(
                scatter, self.tolPixel,
                "Scatter exceeds tolerance in pixels: %g" % (scatter, ))
Example #5
0
    def fitWcs(self, matches, inputWcs, inputBBox):
        """Fit Wcs to matches

        The fitting includes iterative sigma-clipping.

        @param matches: List of matches (first is target, second is input)
        @param inputWcs: Original input Wcs
        @param inputBBox: Bounding box of input image
        @return Wcs
        """
        copyMatches = type(matches)(matches)
        refCoordKey = copyMatches[0].first.getTable().getCoordKey()
        inCentroidKey = copyMatches[0].second.getTable().getCentroidSlot(
        ).getMeasKey()
        for i in range(self.config.sipIter):
            sipFit = makeCreateWcsWithSip(copyMatches, inputWcs,
                                          self.config.sipOrder, inputBBox)
            self.log.debug("Registration WCS RMS iteration %d: %f pixels", i,
                           sipFit.getScatterInPixels())
            wcs = sipFit.getNewWcs()
            dr = [
                m.first.get(refCoordKey).separation(
                    wcs.pixelToSky(
                        m.second.get(inCentroidKey))).asArcseconds()
                for m in copyMatches
            ]
            dr = numpy.array(dr)
            rms = math.sqrt((dr * dr).mean())  # RMS from zero
            rms = max(rms, 1.0e-9)  # Don't believe any RMS smaller than this
            self.log.debug("Registration iteration %d: rms=%f", i, rms)
            good = numpy.where(dr < self.config.sipRej * rms)[0]
            numBad = len(copyMatches) - len(good)
            self.log.debug("Registration iteration %d: rejected %d", i, numBad)
            if numBad == 0:
                break
            copyMatches = type(matches)(copyMatches[i] for i in good)

        sipFit = makeCreateWcsWithSip(copyMatches, inputWcs,
                                      self.config.sipOrder, inputBBox)
        self.log.info(
            "Registration WCS: final WCS RMS=%f pixels from %d matches",
            sipFit.getScatterInPixels(), len(copyMatches))
        self.metadata["SIP_RMS"] = sipFit.getScatterInPixels()
        self.metadata["SIP_GOOD"] = len(copyMatches)
        self.metadata["SIP_REJECTED"] = len(matches) - len(copyMatches)
        wcs = sipFit.getNewWcs()
        return wcs
Example #6
0
 def fitWcs(initialWcs, title=None):
     """Do the WCS fitting and display of the results"""
     sip = makeCreateWcsWithSip(matches, initialWcs, self.config.solver.sipOrder)
     resultWcs = sip.getNewWcs()
     if display:
         showAstrometry(exposure, resultWcs, origMatches, matches, frame=frame,
                        title=title, pause=pause)
     return resultWcs, sip.getScatterOnSky()
Example #7
0
 def fitWcs(initialWcs, title=None):
     """!Do the WCS fitting and display of the results"""
     sip = makeCreateWcsWithSip(matches, initialWcs, self.config.solver.sipOrder)
     resultWcs = sip.getNewWcs()
     if display:
         showAstrometry(exposure, resultWcs, origMatches, matches, frame=frame,
                        title=title, pause=pause)
     return resultWcs, sip.getScatterOnSky()
    def fitWcs(self, matches, inputWcs, inputBBox):
        """Fit Wcs to matches

        The fitting includes iterative sigma-clipping.

        @param matches: List of matches (first is target, second is input)
        @param inputWcs: Original input Wcs
        @param inputBBox: Bounding box of input image
        @return Wcs
        """
        copyMatches = type(matches)(matches)
        refCoordKey = copyMatches[0].first.getTable().getCoordKey()
        inCentroidKey = copyMatches[0].second.getTable().getCentroidKey()
        for i in range(self.config.sipIter):
            sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
            self.log.debug("Registration WCS RMS iteration %d: %f pixels",
                           i, sipFit.getScatterInPixels())
            wcs = sipFit.getNewWcs()
            dr = [m.first.get(refCoordKey).separation(
                wcs.pixelToSky(m.second.get(inCentroidKey))).asArcseconds() for
                m in copyMatches]
            dr = numpy.array(dr)
            rms = math.sqrt((dr*dr).mean())  # RMS from zero
            rms = max(rms, 1.0e-9)  # Don't believe any RMS smaller than this
            self.log.debug("Registration iteration %d: rms=%f", i, rms)
            good = numpy.where(dr < self.config.sipRej*rms)[0]
            numBad = len(copyMatches) - len(good)
            self.log.debug("Registration iteration %d: rejected %d", i, numBad)
            if numBad == 0:
                break
            copyMatches = type(matches)(copyMatches[i] for i in good)

        sipFit = makeCreateWcsWithSip(copyMatches, inputWcs, self.config.sipOrder, inputBBox)
        self.log.info("Registration WCS: final WCS RMS=%f pixels from %d matches" %
                      (sipFit.getScatterInPixels(), len(copyMatches)))
        self.metadata.set("SIP_RMS", sipFit.getScatterInPixels())
        self.metadata.set("SIP_GOOD", len(copyMatches))
        self.metadata.set("SIP_REJECTED", len(matches) - len(copyMatches))
        wcs = sipFit.getNewWcs()
        return wcs
Example #9
0
    def singleTestInstance(self, filename, distortFunc):
        cat = self.loadCatalogue(self.filename)
        img = distort.distortList(cat, distortFunc)
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0),
                             afwGeom.Extent2I(1000, 1000))
        res = self.astrom.determineWcs2(img, bbox=bbox)
        imgWcs = res.getWcs()

        def printWcs(wcs):
            print("WCS metadata:")
            md = wcs.getFitsMetadata()
            for name in md.names():
                print("%s: %r" % (name, md.get(name)))

        printWcs(imgWcs)

        # Create a wcs with sip
        cat = cat.cast(SimpleCatalog, False)
        matchList = self.matchSrcAndCatalogue(cat, img, imgWcs)
        print("*** num matches =", len(matchList))
        return
        sipObject = sip.makeCreateWcsWithSip(matchList, imgWcs, 3)

        # print 'Put in TAN Wcs:'
        # print imgWcs.getFitsMetadata().toString()
        imgWcs = sipObject.getNewWcs()
        # print 'Got SIP Wcs:'
        # print imgWcs.getFitsMetadata().toString()

        # Write out the SIP header
        # afwImage.fits_write_imageF('createWcsWithSip.sip', afwImage.ImageF(0,0),
        # imgWcs.getFitsMetadata())

        print('number of matches:', len(matchList), sipObject.getNPoints())
        scatter = sipObject.getScatterOnSky().asArcseconds()
        print("Scatter in arcsec is %g" % (scatter))

        self.assertLess(scatter, self.tolArcsec,
                        "Scatter exceeds tolerance in arcsec")

        if False:
            scatter = sipObject.getScatterInPixels()
            self.assertLess(
                scatter, self.tolPixel,
                "Scatter exceeds tolerance in pixels: %g" % (scatter, ))
    def singleTestInstance(self, filename, distortFunc):
        cat = self.loadCatalogue(self.filename)
        img = distort.distortList(cat, distortFunc)
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000))
        res = self.astrom.determineWcs2(img, bbox=bbox)
        imgWcs = res.getWcs()

        def printWcs(wcs):
            print "WCS metadata:"
            md = wcs.getFitsMetadata()
            for name in md.names():
                print "%s: %r" % (name, md.get(name))

        printWcs(imgWcs)

        #Create a wcs with sip
        cat = cat.cast(afwTable.SimpleCatalog, False)
        matchList = self.matchSrcAndCatalogue(cat, img, imgWcs)
        print "*** num matches =", len(matchList)
        return
        sipObject = sip.makeCreateWcsWithSip(matchList, imgWcs, 3)

        #print 'Put in TAN Wcs:'
        #print imgWcs.getFitsMetadata().toString()
        imgWcs = sipObject.getNewWcs()
        #print 'Got SIP Wcs:'
        #print imgWcs.getFitsMetadata().toString()

        # Write out the SIP header
        #afwImage.fits_write_imageF('createWcsWithSip.sip', afwImage.ImageF(0,0),
        #imgWcs.getFitsMetadata())

        print 'number of matches:', len(matchList), sipObject.getNPoints()
        scatter = sipObject.getScatterOnSky().asArcseconds()
        print "Scatter in arcsec is %g" % (scatter)

        self.assertLess(scatter, self.tolArcsec, "Scatter exceeds tolerance in arcsec")

        if False:
            scatter = sipObject.getScatterInPixels()
            self.assertLess(scatter, self.tolPixel, "Scatter exceeds tolerance in pixels: %g" %(scatter))
    def getSipWcsFromCorrespondences(self, origWcs, refCat, sourceCat, bbox):
        """Produce a SIP solution given a list of known correspondences.

        Unlike _calculateSipTerms, this does not iterate the solution;
        it assumes you have given it a good sets of corresponding stars.

        NOTE that "refCat" and "sourceCat" are assumed to be the same length;
        entries "refCat[i]" and "sourceCat[i]" are assumed to be correspondences.

        @param[in] origWcs  the WCS to linearize in order to get the TAN part of the TAN-SIP WCS.
        @param[in] refCat  reference source catalog
        @param[in] sourceCat  source catalog
        @param[in] bbox  bounding box of image
        """
        sipOrder = self.config.sipOrder
        matches = []
        for ci, si in zip(refCat, sourceCat):
            matches.append(afwTable.ReferenceMatch(ci, si, 0.))

        sipObject = astromSip.makeCreateWcsWithSip(matches, origWcs, sipOrder, bbox)
        return sipObject.getNewWcs()
def approximateWcs(wcs, bbox, order=3, nx=20, ny=20, iterations=3, \
                   skyTolerance=0.001*afwGeom.arcseconds, pixelTolerance=0.02, useTanWcs=False):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] bbox  the region over which the WCS will be fit
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @param[in] useTanWcs  send a TAN version of wcs to the fitter? It is documented to require that,
        but I don't think the fitter actually cares
    @return the fit TAN-SIP WCS
    """
    if useTanWcs:
        crCoord = wcs.getSkyOrigin()
        crPix = wcs.getPixelOrigin()
        cdMat = wcs.getCDMatrix()
        tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0,0], cdMat[0,1], cdMat[1,0], cdMat[1,1])
    else:
        tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    matchList = afwTable.ReferenceMatchVector()

    bboxd = afwGeom.Box2D(bbox)
    for x in numpy.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in numpy.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)
            skyCoord = wcs.pixelToSky(pixelPos)

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations) :
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    mockTest = _MockTestCase()
    assertWcsNearlyEqualOverBBox(mockTest, wcs, fitWcs, bbox, maxDiffSky=skyTolerance,
                                 maxDiffPix=pixelTolerance)

    return fitWcs
def approximateWcs(wcs,
                   camera_wrapper=None,
                   detector_name=None,
                   obs_metadata=None,
                   order=3,
                   nx=20,
                   ny=20,
                   iterations=3,
                   skyTolerance=0.001 * afwGeom.arcseconds,
                   pixelTolerance=0.02):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] camera_wrapper is an instantiation of GalSimCameraWrapper
    @param[in] detector_name is the name of the detector
    @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @return the fit TAN-SIP WCS
    """
    tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema)  # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    # 20 March 2017
    # the 'try' block is how it works in swig;
    # the 'except' block is how it works in pybind11
    try:
        matchList = afwTable.ReferenceMatchVector()
    except AttributeError:
        matchList = []

    bbox = camera_wrapper.getBBox(detector_name)
    bboxd = afwGeom.Box2D(bbox)

    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)

            ra, dec = camera_wrapper.raDecFromPixelCoords(
                np.array([x]),
                np.array([y]),
                detector_name,
                obs_metadata=obs_metadata,
                epoch=2000.0,
                includeDistortion=True)

            skyCoord = afwGeom.SpherePoint(ra[0], dec[0], LsstGeom.degrees)

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations):
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    return fitWcs
Example #14
0
def createWcs(x, y, mapper, order=4, cOffset=1.0):
    # Here cOffset reflects the differences between FITS coords (LLC =
    # 1,1) and LSST coords (LLC = 0,0).  That is, when creating a Wcs
    # from scratch, we need to evaluate our WCS at coordinate 0,0 to
    # create CRVAL, but set CRPIX to 1,1.

    ra_rad, dec_rad = mapper.xyToRaDec(x, y)

    # Minimial table for sky coordinates
    catTable = afwTable.SimpleTable.make(afwTable.SimpleTable.makeMinimalSchema())

    # Minimial table + centroids for focal plane coordintes
    srcSchema = afwTable.SourceTable.makeMinimalSchema()
    centroidKey = afwTable.Point2DKey.addFields(srcSchema, "centroid", "centroid", "pixel")

    srcTable = afwTable.SourceTable.make(srcSchema)
    srcTable.defineCentroid("centroid")

    matches = []
    for i in range(len(x)):
        src = srcTable.makeRecord()
        src.set(centroidKey.getX(), x[i])
        src.set(centroidKey.getY(), y[i])

        cat = catTable.makeRecord()
        cat.set(catTable.getCoordKey().getRa(), afwGeom.Angle(ra_rad[i], afwGeom.radians))
        cat.set(catTable.getCoordKey().getDec(), afwGeom.Angle(dec_rad[i], afwGeom.radians))

        mat = afwTable.ReferenceMatch(cat, src, 0.0)
        matches.append(mat)

    # Need to make linear Wcs around which to expand solution

    # CRPIX1  = Column Pixel Coordinate of Ref. Pixel
    # CRPIX2  = Row Pixel Coordinate of Ref. Pixel
    crpix = afwGeom.Point2D(x[0] + cOffset, y[0] + cOffset)

    # CRVAL1  = RA at Reference Pixel
    # CRVAL2  = DEC at Reference Pixel
    crval = afwGeom.SpherePoint(ra_rad[0], dec_rad[0], afwGeom.radians)

    # CD1_1   = RA  degrees per column pixel
    # CD1_2   = RA  degrees per row pixel
    # CD2_1   = DEC degrees per column pixel
    # CD2_2   = DEC degrees per row pixel
    LLl = mapper.xyToRaDec(0., 0.)
    ULl = mapper.xyToRaDec(0., 1.)
    LRl = mapper.xyToRaDec(1., 0.)

    LLc = afwGeom.SpherePoint(LLl[0], LLl[1], afwGeom.radians)
    ULc = afwGeom.SpherePoint(ULl[0], ULl[1], afwGeom.radians)
    LRc = afwGeom.SpherePoint(LRl[0], LRl[1], afwGeom.radians)

    cdN_1 = LLc.getTangentPlaneOffset(LRc)
    cdN_2 = LLc.getTangentPlaneOffset(ULc)
    cd1_1, cd2_1 = cdN_1[0].asDegrees(), cdN_1[1].asDegrees()
    cd1_2, cd2_2 = cdN_2[0].asDegrees(), cdN_2[1].asDegrees()

    cdMatrix = np.array([cd1_1, cd2_1, cd1_2, cd2_2], dtype=float)
    cdMatrix.shape = (2, 2)

    linearWcs = makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix)
    wcs = sip.makeCreateWcsWithSip(matches, linearWcs, order).getNewWcs()

    return wcs
def approximateWcs(wcs, bbox, camera=None, detector=None, obs_metadata=None,
                   order=3, nx=20, ny=20, iterations=3,
                   skyTolerance=0.001*afwGeom.arcseconds, pixelTolerance=0.02,
                   useTanWcs=False):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] bbox  the region over which the WCS will be fit
    @param[in] camera is an instantiation of afw.cameraGeom.camera
    @param[in] detector is a detector from camera
    @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @param[in] useTanWcs  send a TAN version of wcs to the fitter? It is documented to require that,
        but I don't think the fitter actually cares
    @return the fit TAN-SIP WCS
    """
    if useTanWcs:
        crCoord = wcs.getSkyOrigin()
        crPix = wcs.getPixelOrigin()
        cdMat = wcs.getCDMatrix()
        tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0,0], cdMat[0,1], cdMat[1,0], cdMat[1,1])
    else:
        tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    matchList = afwTable.ReferenceMatchVector()

    bboxd = afwGeom.Box2D(bbox)
    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)

            ra, dec = raDecFromPixelCoords(np.array([x]), np.array([y]),
                                           [detector.getName()],
                                           camera=camera,
                                           obs_metadata=obs_metadata,
                                           epoch=2000.0,
                                           includeDistortion=True)

            skyCoord = afwCoord.Coord(afwGeom.Point2D(ra[0], dec[0]))

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations) :
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    return fitWcs
def approximateWcs(wcs,
                   bbox,
                   camera=None,
                   detector=None,
                   obs_metadata=None,
                   order=3,
                   nx=20,
                   ny=20,
                   iterations=3,
                   skyTolerance=0.001 * afwGeom.arcseconds,
                   pixelTolerance=0.02,
                   useTanWcs=False):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] bbox  the region over which the WCS will be fit
    @param[in] camera is an instantiation of afw.cameraGeom.camera
    @param[in] detector is a detector from camera
    @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @param[in] useTanWcs  send a TAN version of wcs to the fitter? It is documented to require that,
        but I don't think the fitter actually cares
    @return the fit TAN-SIP WCS
    """
    if useTanWcs:
        crCoord = wcs.getSkyOrigin()
        crPix = wcs.getPixelOrigin()
        cdMat = wcs.getCDMatrix()
        tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0, 0], cdMat[0, 1],
                                  cdMat[1, 0], cdMat[1, 1])
    else:
        tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema)  # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    # 20 March 2017
    # the 'try' block is how it works in swig;
    # the 'except' block is how it works in pybind11
    try:
        matchList = afwTable.ReferenceMatchVector()
    except AttributeError:
        matchList = []

    bboxd = afwGeom.Box2D(bbox)
    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)

            ra, dec = raDecFromPixelCoords(np.array([x]),
                                           np.array([y]), [detector.getName()],
                                           camera=camera,
                                           obs_metadata=obs_metadata,
                                           epoch=2000.0,
                                           includeDistortion=True)

            skyCoord = afwCoord.Coord(afwGeom.Point2D(ra[0], dec[0]))

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations):
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    return fitWcs
def approximateWcs(wcs, bbox, order=3, nx=20, ny=20, iterations=3,
                   skyTolerance=0.001*lsst.geom.arcseconds, pixelTolerance=0.02, useTanWcs=False):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points
    within a bounding box.

    Parameters
    ----------
    wcs : `lsst.afw.geom.SkyWcs`
        wcs to approximate
    bbox : `lsst.geom.Box2I`
        the region over which the WCS will be fit
    order : `int`
        order of SIP fit
    nx : `int`
        number of grid points along x
    ny : `int`
        number of grid points along y
    iterations : `int`
        number of times to iterate over fitting
    skyTolerance : `lsst.geom.Angle`
        maximum allowed difference in world coordinates between
        input wcs and approximate wcs (default is 0.001 arcsec)
    pixelTolerance : `float`
        maximum allowed difference in pixel coordinates between
        input wcs and approximate wcs (default is 0.02 pixels)
    useTanWcs : `bool`
        send a TAN version of wcs to the fitter? It is documented to require that,
        but I don't think the fitter actually cares

    Returns
    -------
    fitWcs : `lsst.afw.geom.SkyWcs`
        the fit TAN-SIP WCS
    """
    if useTanWcs:
        crpix = wcs.getPixelOrigin()
        crval = wcs.getSkyOrigin()
        cdMatrix = wcs.getCdMatrix(crpix)
        tanWcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix)
    else:
        tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema)  # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    matchList = []

    bboxd = lsst.geom.Box2D(bbox)
    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = lsst.geom.Point2D(x, y)
            skyCoord = wcs.pixelToSky(pixelPos)

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations):
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    mockTest = _MockTestCase()
    assertWcsAlmostEqualOverBBox(mockTest, wcs, fitWcs, bbox, maxDiffSky=skyTolerance,
                                 maxDiffPix=pixelTolerance)

    return fitWcs
Example #18
0
def approximateWcs(wcs,
                   bbox,
                   order=3,
                   nx=20,
                   ny=20,
                   iterations=3,
                   skyTolerance=0.001 * afwGeom.arcseconds,
                   pixelTolerance=0.02,
                   useTanWcs=False):
    """Approximate an existing WCS as a TAN-SIP WCS

    The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box.

    @param[in] wcs  wcs to approximate
    @param[in] bbox  the region over which the WCS will be fit
    @param[in] order  order of SIP fit
    @param[in] nx  number of grid points along x
    @param[in] ny  number of grid points along y
    @param[in] iterations number of times to iterate over fitting
    @param[in] skyTolerance maximum allowed difference in world coordinates between
               input wcs and approximate wcs (default is 0.001 arcsec)
    @param[in] pixelTolerance maximum allowed difference in pixel coordinates between
               input wcs and approximate wcs (default is 0.02 pixels)
    @param[in] useTanWcs  send a TAN version of wcs to the fitter? It is documented to require that,
        but I don't think the fitter actually cares
    @return the fit TAN-SIP WCS
    """
    if useTanWcs:
        crCoord = wcs.getSkyOrigin()
        crPix = wcs.getPixelOrigin()
        cdMat = wcs.getCDMatrix()
        tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0, 0], cdMat[0, 1],
                                  cdMat[1, 0], cdMat[1, 1])
    else:
        tanWcs = wcs

    # create a matchList consisting of a grid of points covering the bbox
    refSchema = afwTable.SimpleTable.makeMinimalSchema()
    refCoordKey = afwTable.CoordKey(refSchema["coord"])
    refCat = afwTable.SimpleCatalog(refSchema)

    sourceSchema = afwTable.SourceTable.makeMinimalSchema()
    SingleFrameMeasurementTask(schema=sourceSchema)  # expand the schema
    sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])

    sourceCat = afwTable.SourceCatalog(sourceSchema)

    matchList = []

    bboxd = afwGeom.Box2D(bbox)
    for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx):
        for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny):
            pixelPos = afwGeom.Point2D(x, y)
            skyCoord = wcs.pixelToSky(pixelPos)

            refObj = refCat.addNew()
            refObj.set(refCoordKey, skyCoord)

            source = sourceCat.addNew()
            source.set(sourceCentroidKey, pixelPos)

            matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0))

    # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge
    for indx in range(iterations):
        sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox)
        tanWcs = sipObject.getNewWcs()
    fitWcs = sipObject.getNewWcs()

    mockTest = _MockTestCase()
    assertWcsAlmostEqualOverBBox(mockTest,
                                 wcs,
                                 fitWcs,
                                 bbox,
                                 maxDiffSky=skyTolerance,
                                 maxDiffPix=pixelTolerance)

    return fitWcs
Example #19
0
def createWcs(x, y, mapper, order=4, cOffset=1.0):
    # Here cOffset reflects the differences between FITS coords (LLC =
    # 1,1) and LSST coords (LLC = 0,0).  That is, when creating a Wcs
    # from scratch, we need to evaluate our WCS at coordinate 0,0 to
    # create CRVAL, but set CRPIX to 1,1.

    ra_rad, dec_rad = mapper.xyToRaDec(x, y)

    # Minimial table for sky coordinates
    catTable = afwTable.SimpleTable.make(
        afwTable.SimpleTable.makeMinimalSchema())

    # Minimial table + centroids for focal plane coordintes
    srcSchema = afwTable.SourceTable.makeMinimalSchema()
    centroidKey = afwTable.Point2DKey.addFields(srcSchema, "centroid",
                                                "centroid", "pixel")

    srcTable = afwTable.SourceTable.make(srcSchema)
    srcTable.defineCentroid("centroid")

    matches = []
    for i in range(len(x)):
        src = srcTable.makeRecord()
        src.set(centroidKey.getX(), x[i])
        src.set(centroidKey.getY(), y[i])

        cat = catTable.makeRecord()
        cat.set(catTable.getCoordKey().getRa(),
                afwGeom.Angle(ra_rad[i], afwGeom.radians))
        cat.set(catTable.getCoordKey().getDec(),
                afwGeom.Angle(dec_rad[i], afwGeom.radians))

        mat = afwTable.ReferenceMatch(cat, src, 0.0)
        matches.append(mat)

    # Need to make linear Wcs around which to expand solution

    # CRPIX1  = Column Pixel Coordinate of Ref. Pixel
    # CRPIX2  = Row Pixel Coordinate of Ref. Pixel
    crpix = afwGeom.Point2D(x[0] + cOffset, y[0] + cOffset)

    # CRVAL1  = RA at Reference Pixel
    # CRVAL2  = DEC at Reference Pixel
    crval = afwCoord.Coord(afwGeom.Point2D(ra_rad[0], dec_rad[0]),
                           afwGeom.radians)

    # CD1_1   = RA  degrees per column pixel
    # CD1_2   = RA  degrees per row pixel
    # CD2_1   = DEC degrees per column pixel
    # CD2_2   = DEC degrees per row pixel
    LLl = mapper.xyToRaDec(0., 0.)
    ULl = mapper.xyToRaDec(0., 1.)
    LRl = mapper.xyToRaDec(1., 0.)

    LLc = afwCoord.Coord(afwGeom.Point2D(LLl[0], LLl[1]), afwGeom.radians)
    ULc = afwCoord.Coord(afwGeom.Point2D(ULl[0], ULl[1]), afwGeom.radians)
    LRc = afwCoord.Coord(afwGeom.Point2D(LRl[0], LRl[1]), afwGeom.radians)

    cdN_1 = LLc.getTangentPlaneOffset(LRc)
    cdN_2 = LLc.getTangentPlaneOffset(ULc)
    cd1_1, cd2_1 = cdN_1[0].asDegrees(), cdN_1[1].asDegrees()
    cd1_2, cd2_2 = cdN_2[0].asDegrees(), cdN_2[1].asDegrees()

    linearWcs = afwImage.makeWcs(crval, crpix, cd1_1, cd2_1, cd1_2, cd2_2)
    wcs = sip.makeCreateWcsWithSip(matches, linearWcs, order).getNewWcs()

    return wcs
Example #20
0
    def doTest(self,
               name,
               func,
               order=3,
               numIter=4,
               specifyBBox=False,
               doPlot=False,
               doPrint=False):
        """Apply func(x, y) to each source in self.sourceCat, then fit and check the resulting WCS
        """
        bbox = lsst.geom.Box2I()
        for refObj, src, d in self.matches:
            origPos = src.get(self.srcCentroidKey)
            x, y = func(*origPos)
            distortedPos = lsst.geom.Point2D(*func(*origPos))
            src.set(self.srcCentroidKey, distortedPos)
            bbox.include(lsst.geom.Point2I(lsst.geom.Point2I(distortedPos)))

        tanSipWcs = self.tanWcs
        for i in range(numIter):
            if specifyBBox:
                sipObject = makeCreateWcsWithSip(self.matches, tanSipWcs,
                                                 order, bbox)
            else:
                sipObject = makeCreateWcsWithSip(self.matches, tanSipWcs,
                                                 order)
            tanSipWcs = sipObject.getNewWcs()
        setMatchDistance(self.matches)
        fitRes = lsst.pipe.base.Struct(
            wcs=tanSipWcs,
            scatterOnSky=sipObject.getScatterOnSky(),
        )

        if doPrint:
            print("TAN-SIP metadata fit over bbox=", bbox)
            metadata = makeTanSipMetadata(
                crpix=tanSipWcs.getPixelOrigin(),
                crval=tanSipWcs.getSkyOrigin(),
                cdMatrix=tanSipWcs.getCdMatrix(),
                sipA=sipObject.getSipA(),
                sipB=sipObject.getSipB(),
                sipAp=sipObject.getSipAp(),
                sipBp=sipObject.getSipBp(),
            )
            print(metadata.toString())

        if doPlot:
            self.plotWcs(tanSipWcs, name=name)

        self.checkResults(fitRes, catsUpdated=False)

        if self.MatchClass == afwTable.ReferenceMatch:
            # reset source coord and reference centroid based on initial WCS
            afwTable.updateRefCentroids(wcs=self.tanWcs, refList=self.refCat)
            afwTable.updateSourceCoords(wcs=self.tanWcs,
                                        sourceList=self.sourceCat)

            fitterConfig = FitTanSipWcsTask.ConfigClass()
            fitterConfig.order = order
            fitterConfig.numIter = numIter
            fitter = FitTanSipWcsTask(config=fitterConfig)
            self.loadData()
            if specifyBBox:
                fitRes = fitter.fitWcs(
                    matches=self.matches,
                    initWcs=self.tanWcs,
                    bbox=bbox,
                    refCat=self.refCat,
                    sourceCat=self.sourceCat,
                )
            else:
                fitRes = fitter.fitWcs(
                    matches=self.matches,
                    initWcs=self.tanWcs,
                    bbox=bbox,
                    refCat=self.refCat,
                    sourceCat=self.sourceCat,
                )

            self.checkResults(fitRes, catsUpdated=True)