Exemple #1
0
    def setUp(self):
        self.crval = afwCoord.IcrsCoord(afwGeom.PointD(44., 45.))
        self.crpix = afwGeom.Point2D(15000, 4000)

        arcsecPerPixel = 1 / 3600.0
        CD11 = arcsecPerPixel
        CD12 = 0
        CD21 = 0
        CD22 = arcsecPerPixel

        self.wcs = afwImage.makeWcs(self.crval, self.crpix, CD11, CD12, CD21,
                                    CD22)

        refSchema = afwTable.SimpleTable.makeMinimalSchema()
        self.refCentroidKey = afwTable.Point2DKey.addFields(
            refSchema, "centroid", "centroid", "pixels")
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.refCat = afwTable.SimpleCatalog(refSchema)

        # an alias is required to make src.getCentroid() work;
        # simply defining a field named "slot_Centroid" doesn't suffice
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        self.srcCentroidKey = afwTable.Point2DKey.addFields(
            srcSchema, "base_SdssCentroid", "centroid", "pixels")
        srcAliases = srcSchema.getAliasMap()
        srcAliases.set("slot_Centroid", "base_SdssCentroid")
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.sourceCat = afwTable.SourceCatalog(srcSchema)
    def setUp(self):
        self.crval = afwGeom.SpherePoint(44.0, 45.0, afwGeom.degrees)
        self.crpix = afwGeom.Point2D(15000, 4000)

        arcsecPerPixel = 1 / 3600.0
        cdMatrix = afwGeom.makeCdMatrix(arcsecPerPixel * afwGeom.arcseconds)
        self.wcs = afwGeom.makeSkyWcs(crval=self.crval,
                                      crpix=self.crpix,
                                      cdMatrix=cdMatrix)

        refSchema = afwTable.SimpleTable.makeMinimalSchema()
        self.refCentroidKey = afwTable.Point2DKey.addFields(
            refSchema, "centroid", "centroid", "pixels")
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.refHasCentroidKey = refSchema.addField("hasCentroid", type="Flag")
        self.refCat = afwTable.SimpleCatalog(refSchema)

        # an alias is required to make src.getCentroid() work;
        # simply defining a field named "slot_Centroid" doesn't suffice
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        self.srcCentroidKey = afwTable.Point2DKey.addFields(
            srcSchema, "base_SdssCentroid", "centroid", "pixels")
        srcAliases = srcSchema.getAliasMap()
        srcAliases.set("slot_Centroid", "base_SdssCentroid")
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.sourceCat = afwTable.SourceCatalog(srcSchema)
    def doTest(self, pixelsToTanPixels, order=3):
        """Test using pixelsToTanPixels to distort the source positions
        """
        distortedWcs = afwGeom.makeModifiedWcs(pixelTransform=pixelsToTanPixels, wcs=self.tanWcs,
                                               modifyActualPixels=False)
        self.exposure.setWcs(distortedWcs)
        sourceCat = self.makeSourceCat(distortedWcs)
        config = AstrometryTask.ConfigClass()
        config.wcsFitter.order = order
        config.wcsFitter.numRejIter = 0
        solver = AstrometryTask(config=config, refObjLoader=self.refObjLoader)
        results = solver.run(
            sourceCat=sourceCat,
            exposure=self.exposure,
        )
        fitWcs = self.exposure.getWcs()
        self.assertRaises(Exception, self.assertWcsAlmostEqualOverBBox, fitWcs, distortedWcs)
        self.assertWcsAlmostEqualOverBBox(distortedWcs, fitWcs, self.bbox,
                                          maxDiffSky=0.01*afwGeom.arcseconds, maxDiffPix=0.02)

        srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"])
        refCoordKey = afwTable.CoordKey(results.refCat.schema["coord"])
        refCentroidKey = afwTable.Point2DKey(results.refCat.schema["centroid"])
        maxAngSep = afwGeom.Angle(0)
        maxPixSep = 0
        for refObj, src, d in results.matches:
            refCoord = refObj.get(refCoordKey)
            refPixPos = refObj.get(refCentroidKey)
            srcCoord = src.get(srcCoordKey)
            srcPixPos = src.getCentroid()

            angSep = refCoord.separation(srcCoord)
            maxAngSep = max(maxAngSep, angSep)

            pixSep = math.hypot(*(srcPixPos-refPixPos))
            maxPixSep = max(maxPixSep, pixSep)
        print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(),))
        print("max pixel separation = %0.3f" % (maxPixSep,))
        self.assertLess(maxAngSep.asArcseconds(), 0.0026)
        self.assertLess(maxPixSep, 0.015)

        # try again, but without fitting the WCS
        config.forceKnownWcs = True
        solverNoFit = AstrometryTask(config=config, refObjLoader=self.refObjLoader)
        self.exposure.setWcs(distortedWcs)
        resultsNoFit = solverNoFit.run(
            sourceCat=sourceCat,
            exposure=self.exposure,
        )
        self.assertIsNone(resultsNoFit.scatterOnSky)

        # fitting should result in matches that are at least as good
        # (strictly speaking fitting might result in a larger match list with
        # some outliers, but in practice this test passes)
        meanFitDist = np.mean([match.distance for match in results.matches])
        meanNoFitDist = np.mean([match.distance for match in resultsNoFit.matches])
        self.assertLessEqual(meanFitDist, meanNoFitDist)
    def doTest(self, pixelsToTanPixels):
        """Test using pixelsToTanPixels to distort the source positions
        """
        distortedWcs = afwGeom.makeModifiedWcs(pixelTransform=pixelsToTanPixels, wcs=self.tanWcs,
                                               modifyActualPixels=False)
        self.exposure.setWcs(distortedWcs)
        sourceCat = self.makeSourceCat(distortedWcs)
        print("number of stars =", len(sourceCat))
        config = ANetAstrometryTask.ConfigClass()
        solver = ANetAstrometryTask(config=config, refObjLoader=self.refObjLoader,
                                    schema=self.makeSourceSchema())
        results = solver.run(
            sourceCat=sourceCat,
            exposure=self.exposure,
        )
        fitWcs = self.exposure.getWcs()
        self.assertRaises(Exception, self.assertWcsAlmostEqualOverBBox, fitWcs, distortedWcs)
        self.assertWcsAlmostEqualOverBBox(distortedWcs, fitWcs, self.bbox,
                                          maxDiffSky=0.01*lsst.geom.arcseconds, maxDiffPix=0.02)

        srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"])
        refCoordKey = afwTable.CoordKey(results.refCat.schema["coord"])
        maxAngSep = 0*lsst.geom.radians
        maxPixSep = 0
        for refObj, src, d in results.matches:
            refCoord = refObj.get(refCoordKey)
            refPixPos = fitWcs.skyToPixel(refCoord)
            srcCoord = src.get(srcCoordKey)
            srcPixPos = src.getCentroid()

            angSep = refCoord.separation(srcCoord)
            maxAngSep = max(maxAngSep, angSep)

            pixSep = math.hypot(*(srcPixPos-refPixPos))
            maxPixSep = max(maxPixSep, pixSep)
        print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(),))
        print("max pixel separation = %0.3f" % (maxPixSep,))
        self.assertLess(maxAngSep.asArcseconds(), 0.005)
        self.assertLess(maxPixSep, 0.03)

        # try again, but without fitting the WCS
        config.forceKnownWcs = True
        solverNoFit = ANetAstrometryTask(config=config, refObjLoader=self.refObjLoader,
                                         schema=self.makeSourceSchema())
        self.exposure.setWcs(distortedWcs)
        noFitResults = solverNoFit.run(
            sourceCat=sourceCat,
            exposure=self.exposure,
        )
        self.assertGreater(len(noFitResults.refCat), 300)
Exemple #5
0
    def setUp(self):
        # make a nominal match list where the distances are 0; test can then modify
        # source centroid, reference coord or distance field for each match, as desired
        self.wcs = afwGeom.makeSkyWcs(crpix=lsst.geom.Point2D(1500, 1500),
                                      crval=lsst.geom.SpherePoint(215.5, 53.0, lsst.geom.degrees),
                                      cdMatrix=afwGeom.makeCdMatrix(scale=5.1e-5*lsst.geom.degrees))
        self.bboxD = lsst.geom.Box2D(lsst.geom.Point2D(10, 100), lsst.geom.Extent2D(1000, 1500))
        self.numMatches = 25

        sourceSchema = afwTable.SourceTable.makeMinimalSchema()
        # add centroid (and many other unwanted fields) to sourceSchema
        SingleFrameMeasurementTask(schema=sourceSchema)
        self.sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"])
        self.sourceCat = afwTable.SourceCatalog(sourceSchema)

        refSchema = afwTable.SourceTable.makeMinimalSchema()
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.refCat = afwTable.SourceCatalog(refSchema)

        self.matchList = []

        np.random.seed(5)
        pixPointList = [lsst.geom.Point2D(pos) for pos in
                        np.random.random_sample([self.numMatches, 2])*self.bboxD.getDimensions() +
                        self.bboxD.getMin()]
        for pixPoint in pixPointList:
            src = self.sourceCat.addNew()
            src.set(self.sourceCentroidKey, pixPoint)
            ref = self.refCat.addNew()
            ref.set(self.refCoordKey, self.wcs.pixelToSky(pixPoint))

            match = afwTable.ReferenceMatch(ref, src, 0)
            self.matchList.append(match)
    def loadData(self, rangePix=3000, numPoints=25):
        """Load catalogs and make the match list

        This is a separate function so data can be reloaded if fitting more than once
        (each time a WCS is fit it may update the source catalog, reference catalog and match list)
        """
        refSchema = LoadReferenceObjectsTask.makeMinimalSchema(
            filterNameList=["r"], addIsPhotometric=True, addCentroid=True)
        self.refCat = afwTable.SimpleCatalog(refSchema)
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        SingleFrameMeasurementTask(schema=srcSchema)
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"])
        self.srcCentroidKey_xErr = srcSchema["slot_Centroid_xErr"].asKey()
        self.srcCentroidKey_yErr = srcSchema["slot_Centroid_yErr"].asKey()
        self.sourceCat = afwTable.SourceCatalog(srcSchema)

        self.matches = []

        for i in np.linspace(0., rangePix, numPoints):
            for j in np.linspace(0., rangePix, numPoints):
                src = self.sourceCat.addNew()
                refObj = self.refCat.addNew()

                src.set(self.srcCentroidKey, lsst.geom.Point2D(i, j))
                src.set(self.srcCentroidKey_xErr, 0.1)
                src.set(self.srcCentroidKey_yErr, 0.1)

                c = self.tanWcs.pixelToSky(i, j)
                refObj.setCoord(c)

                self.matches.append(self.MatchClass(refObj, src, 0.0))
Exemple #7
0
    def setUp(self):
        crval = IcrsCoord(afwGeom.PointD(44., 45.))
        crpix = afwGeom.PointD(0, 0)

        arcsecPerPixel = 1 / 3600.0
        CD11 = arcsecPerPixel
        CD12 = 0
        CD21 = 0
        CD22 = arcsecPerPixel

        self.tanWcs = makeWcs(crval, crpix, CD11, CD12, CD21, CD22)

        S = 300
        N = 5

        if self.MatchClass == afwTable.ReferenceMatch:
            refSchema = LoadReferenceObjectsTask.makeMinimalSchema(
                filterNameList=["r"], addFluxSigma=True, addIsPhotometric=True)
            self.refCat = afwTable.SimpleCatalog(refSchema)
        elif self.MatchClass == afwTable.SourceMatch:
            refSchema = afwTable.SourceTable.makeMinimalSchema()
            self.refCat = afwTable.SourceCatalog(refSchema)
        else:
            raise RuntimeError("Unsupported MatchClass=%r" %
                               (self.MatchClass, ))
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        SingleFrameMeasurementTask(schema=srcSchema)
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"])
        self.sourceCat = afwTable.SourceCatalog(srcSchema)
        self.origSourceCat = afwTable.SourceCatalog(
            srcSchema)  # undistorted copy
        self.matches = []

        for i in np.linspace(0., S, N):
            for j in np.linspace(0., S, N):
                src = self.sourceCat.addNew()
                refObj = self.refCat.addNew()

                src.set(self.srcCentroidKey, afwGeom.Point2D(i, j))

                c = self.tanWcs.pixelToSky(afwGeom.Point2D(i, j))
                refObj.setCoord(c)

                self.matches.append(self.MatchClass(refObj, src, 0.0))
def setMatchDistance(matches):
    """Set the distance field of the matches in a match list to the distance in radians on the sky

    @warning the coord field of the source in each match must be correct

    @param[in,out] matches  a list of matches, an instance of lsst.afw.table.ReferenceMatch
        reads the coord field of the source and reference object of each match
        writes the distance field of each match
    """
    if len(matches) < 1:
        return

    sourceCoordKey = afwTable.CoordKey(matches[0].first.schema["coord"])
    refObjCoordKey = afwTable.CoordKey(matches[0].second.schema["coord"])
    for match in matches:
        sourceCoord = match.first.get(sourceCoordKey)
        refObjCoord = match.second.get(refObjCoordKey)
        match.distance = refObjCoord.separation(sourceCoord).asRadians()
Exemple #9
0
    def setUp(self):
        crval = afwGeom.SpherePoint(44, 45, afwGeom.degrees)
        crpix = afwGeom.PointD(0, 0)

        scale = 1 * afwGeom.arcseconds
        self.tanWcs = afwGeom.makeSkyWcs(
            crpix=crpix,
            crval=crval,
            cdMatrix=afwGeom.makeCdMatrix(scale=scale))

        S = 300
        N = 5

        if self.MatchClass == afwTable.ReferenceMatch:
            refSchema = LoadReferenceObjectsTask.makeMinimalSchema(
                filterNameList=["r"], addFluxSigma=True, addIsPhotometric=True)
            self.refCat = afwTable.SimpleCatalog(refSchema)
        elif self.MatchClass == afwTable.SourceMatch:
            refSchema = afwTable.SourceTable.makeMinimalSchema()
            self.refCat = afwTable.SourceCatalog(refSchema)
        else:
            raise RuntimeError("Unsupported MatchClass=%r" %
                               (self.MatchClass, ))
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        SingleFrameMeasurementTask(schema=srcSchema)
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"])
        self.sourceCat = afwTable.SourceCatalog(srcSchema)
        self.origSourceCat = afwTable.SourceCatalog(
            srcSchema)  # undistorted copy
        self.matches = []

        for i in np.linspace(0., S, N):
            for j in np.linspace(0., S, N):
                src = self.sourceCat.addNew()
                refObj = self.refCat.addNew()

                src.set(self.srcCentroidKey, afwGeom.Point2D(i, j))

                c = self.tanWcs.pixelToSky(afwGeom.Point2D(i, j))
                refObj.setCoord(c)

                self.matches.append(self.MatchClass(refObj, src, 0.0))
    def testInterface(self):
        obsTestDir = lsst.utils.getPackageDir('obs_test')
        inputDir = os.path.join(obsTestDir, "data", "input")

        # Configure a ProcessCcd task such that it will return a minimal
        # number of measurements plus our test plugin.
        cfg = ProcessCcdConfig()
        cfg.calibrate.measurement.plugins.names = ["base_SdssCentroid", "base_SkyCoord", PLUGIN_NAME]
        cfg.calibrate.measurement.slots.shape = None
        cfg.calibrate.measurement.slots.psfFlux = None
        cfg.calibrate.measurement.slots.apFlux = None
        cfg.calibrate.measurement.slots.gaussianFlux = None
        cfg.calibrate.measurement.slots.modelFlux = None
        cfg.calibrate.measurement.slots.calibFlux = None
        # no reference catalog, so...
        cfg.calibrate.doAstrometry = False
        cfg.calibrate.doPhotoCal = False
        # disable aperture correction because we aren't measuring aperture flux
        cfg.calibrate.doApCorr = False
        # Extendedness requires modelFlux, disabled above.
        cfg.calibrate.catalogCalculation.plugins.names.discard("base_ClassificationExtendedness")

        # Process the test data with ProcessCcd then perform a transform.
        with tempDirectory() as tempDir:
            measResult = ProcessCcdTask.parseAndRun(args=[inputDir, "--output", tempDir, "--id", "visit=1"],
                                                    config=cfg, doReturnResults=True)
            trArgs = [tempDir, "--output", tempDir, "--id", "visit=1",
                      "-c", "inputConfigType=processCcd_config"]
            trResult = SrcTransformTask.parseAndRun(args=trArgs, doReturnResults=True)

            # It should be possible to reprocess the data through a new transform task with exactly
            # the same configuration without throwing. This check is useful since we are
            # constructing the task on the fly, which could conceivably cause problems with
            # configuration/metadata persistence.
            trResult = SrcTransformTask.parseAndRun(args=trArgs, doReturnResults=True)

        measSrcs = measResult.resultList[0].result.calibRes.sourceCat
        trSrcs = trResult.resultList[0].result

        # The length of the measured and transformed catalogs should be the same.
        self.assertEqual(len(measSrcs), len(trSrcs))

        # Each source should have been measured & transformed appropriately.
        for measSrc, trSrc in zip(measSrcs, trSrcs):
            # The TrivialMeasurement should be transformed as defined above.
            self.assertEqual(trSrc[PLUGIN_NAME], measSrc[PLUGIN_NAME])
            self.assertEqual(trSrc[PLUGIN_NAME + "_transform"], -1.0 * measSrc[PLUGIN_NAME])

            # The SdssCentroid should be transformed to celestial coordinates.
            # Checking that the full transformation has been done correctly is
            # out of scope for this test case; we just ensure that there's
            # plausible position in the transformed record.
            trCoord = afwTable.CoordKey(trSrcs.schema["base_SdssCentroid"]).get(trSrc)
            self.assertAlmostEqual(measSrc.getCoord().getLongitude(), trCoord.getLongitude())
            self.assertAlmostEqual(measSrc.getCoord().getLatitude(), trCoord.getLatitude())
    def setUp(self):
        # make a nominal match list where the distances are 0; test can then modify
        # source centroid, reference coord or distance field for each match, as desired
        ctrPix = afwGeom.Point2I(1500, 1500)
        metadata = PropertySet()
        metadata.set("RADECSYS", "FK5")
        metadata.set("EQUINOX", 2000.0)
        metadata.set("CTYPE1", "RA---TAN")
        metadata.set("CTYPE2", "DEC--TAN")
        metadata.set("CUNIT1", "deg")
        metadata.set("CUNIT2", "deg")
        metadata.set("CRVAL1", 215.5)
        metadata.set("CRVAL2", 53.0)
        metadata.set("CRPIX1", ctrPix[0] + 1)
        metadata.set("CRPIX2", ctrPix[1] + 1)
        metadata.set("CD1_1", 5.1e-05)
        metadata.set("CD1_2", 0.0)
        metadata.set("CD2_2", -5.1e-05)
        metadata.set("CD2_1", 0.0)
        self.wcs = afwImage.makeWcs(metadata)
        self.bboxD = afwGeom.Box2D(afwGeom.Point2D(10, 100),
                                   afwGeom.Extent2D(1000, 1500))
        self.numMatches = 25

        sourceSchema = afwTable.SourceTable.makeMinimalSchema()
        # add centroid (and many other unwanted fields) to sourceSchema
        SingleFrameMeasurementTask(schema=sourceSchema)
        self.sourceCentroidKey = afwTable.Point2DKey(
            sourceSchema["slot_Centroid"])
        self.sourceCat = afwTable.SourceCatalog(sourceSchema)

        refSchema = afwTable.SourceTable.makeMinimalSchema()
        self.refCoordKey = afwTable.CoordKey(refSchema["coord"])
        self.refCat = afwTable.SourceCatalog(refSchema)

        self.matchList = []

        np.random.seed(5)
        pixPointList = [
            afwGeom.Point2D(pos)
            for pos in np.random.random_sample([self.numMatches, 2]) *
            self.bboxD.getDimensions() + self.bboxD.getMin()
        ]
        for pixPoint in pixPointList:
            src = self.sourceCat.addNew()
            src.set(self.sourceCentroidKey, pixPoint)
            ref = self.refCat.addNew()
            ref.set(self.refCoordKey, self.wcs.pixelToSky(pixPoint))

            match = afwTable.ReferenceMatch(ref, src, 0)
            self.matchList.append(match)
    def checkResults(self, fitRes, catsUpdated):
        """Check results

        @param[in] fitRes  a object with two fields:
            - wcs  fit TAN-SIP WCS, an lsst.afw.geom.SkyWcs
            - scatterOnSky  median on-sky scatter, an lsst.afw.geom.Angle
        @param[in] catsUpdated  if True then coord field of self.sourceCat and centroid fields of self.refCat
            have been updated
        """
        self.assertLess(fitRes.scatterOnSky.asArcseconds(), 0.001)
        tanSipWcs = fitRes.wcs
        maxAngSep = 0 * lsst.geom.radians
        maxPixSep = 0
        refCoordKey = afwTable.CoordKey(self.refCat.schema["coord"])
        if catsUpdated:
            refCentroidKey = afwTable.Point2DKey(
                self.refCat.schema["centroid"])
        maxDistErr = 0 * lsst.geom.radians
        for refObj, src, distRad in self.matches:
            srcPixPos = src.get(self.srcCentroidKey)
            refCoord = refObj.get(refCoordKey)
            if catsUpdated:
                refPixPos = refObj.get(refCentroidKey)
                srcCoord = src.get(self.srcCoordKey)
            else:
                refPixPos = tanSipWcs.skyToPixel(refCoord)
                srcCoord = tanSipWcs.pixelToSky(srcPixPos)

            angSep = refCoord.separation(srcCoord)
            dist = distRad * lsst.geom.radians
            distErr = abs(dist - angSep)
            maxDistErr = max(maxDistErr, distErr)
            maxAngSep = max(maxAngSep, angSep)

            pixSep = math.hypot(*(srcPixPos - refPixPos))
            maxPixSep = max(maxPixSep, pixSep)

        print("max angular separation = %0.4f arcsec" %
              (maxAngSep.asArcseconds(), ))
        print("max pixel separation = %0.3f" % (maxPixSep, ))
        self.assertLess(maxAngSep.asArcseconds(), 0.001)
        self.assertLess(maxPixSep, 0.005)
        if catsUpdated:
            allowedDistErr = 1e-7
        else:
            allowedDistErr = 0.001
        self.assertLess(
            maxDistErr.asArcseconds(), allowedDistErr,
            "Computed distance in match list is off by %s arcsec" %
            (maxDistErr.asArcseconds(), ))
Exemple #13
0
def setMatchDistance(matches):
    """Set the distance field of the matches in a match list to the distance in
    radians on the sky.

    Parameters
    ----------
    matches : `list` of `lsst.afw.table.ReferenceMatch`
        a list of matches, reads the coord field of the source and reference
        object of each match writes the distance field of each match

    Notes
    -----
    .. warning::
       the coord field of the source in each match must be correct
    """
    if len(matches) < 1:
        return

    sourceCoordKey = afwTable.CoordKey(matches[0].first.schema["coord"])
    refObjCoordKey = afwTable.CoordKey(matches[0].second.schema["coord"])
    for match in matches:
        sourceCoord = match.first.get(sourceCoordKey)
        refObjCoord = match.second.get(refObjCoordKey)
        match.distance = refObjCoord.separation(sourceCoord).asRadians()
    def loadData(self, rangePix=3000, numPoints=25):
        """Load catalogs and make the match list

        This is a separate function so data can be reloaded if fitting more than once
        (each time a WCS is fit it may update the source catalog, reference catalog and match list)
        """
        if self.MatchClass == afwTable.ReferenceMatch:
            refSchema = LoadReferenceObjectsTask.makeMinimalSchema(
                filterNameList=["r"], addIsPhotometric=True, addCentroid=True)
            self.refCat = afwTable.SimpleCatalog(refSchema)
        elif self.MatchClass == afwTable.SourceMatch:
            refSchema = afwTable.SourceTable.makeMinimalSchema()
            self.refCat = afwTable.SourceCatalog(refSchema)
        else:
            raise RuntimeError("Unsupported MatchClass=%r" %
                               (self.MatchClass, ))
        srcSchema = afwTable.SourceTable.makeMinimalSchema()
        SingleFrameMeasurementTask(schema=srcSchema)
        self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
        self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"])
        self.srcCentroidKey_xErr = srcSchema["slot_Centroid_xErr"].asKey()
        self.srcCentroidKey_yErr = srcSchema["slot_Centroid_yErr"].asKey()
        self.sourceCat = afwTable.SourceCatalog(srcSchema)

        self.matches = []

        for i in np.linspace(0., rangePix, numPoints):
            for j in np.linspace(0., rangePix, numPoints):
                src = self.sourceCat.addNew()
                refObj = self.refCat.addNew()

                src.set(self.srcCentroidKey, lsst.geom.Point2D(i, j))
                src.set(self.srcCentroidKey_xErr, 0.1)
                src.set(self.srcCentroidKey_yErr, 0.1)

                c = self.tanWcs.pixelToSky(i, j)
                refObj.setCoord(c)

                if False:
                    print(
                        "x,y = (%.1f, %.1f) pixels -- RA,Dec = (%.3f, %.3f) deg"
                        % (i, j, c.toFk5().getRa().asDegrees(),
                           c.toFk5().getDec().asDegrees()))

                self.matches.append(self.MatchClass(refObj, src, 0.0))
    def checkResults(self, fitRes, catsUpdated):
        """Check results.
        """
        self.assertLess(fitRes.scatterOnSky.asArcseconds(), 0.001)
        affineWcs = fitRes.wcs
        maxAngSep = 0 * lsst.geom.radians
        maxPixSep = 0
        refCoordKey = afwTable.CoordKey(self.refCat.schema["coord"])
        if catsUpdated:
            refCentroidKey = afwTable.Point2DKey(
                self.refCat.schema["centroid"])
        maxDistErr = 0 * lsst.geom.radians
        for refObj, src, distRad in self.matches:
            srcPixPos = src.get(self.srcCentroidKey)
            refCoord = refObj.get(refCoordKey)
            if catsUpdated:
                refPixPos = refObj.get(refCentroidKey)
                srcCoord = src.get(self.srcCoordKey)
            else:
                refPixPos = affineWcs.skyToPixel(refCoord)
                srcCoord = affineWcs.pixelToSky(srcPixPos)

            angSep = refCoord.separation(srcCoord)
            dist = distRad * lsst.geom.radians
            distErr = abs(dist - angSep)
            maxDistErr = max(maxDistErr, distErr)
            maxAngSep = max(maxAngSep, angSep)

            pixSep = math.hypot(*(srcPixPos - refPixPos))
            maxPixSep = max(maxPixSep, pixSep)

        print("max angular separation = %0.4f arcsec" %
              (maxAngSep.asArcseconds(), ))
        print("max pixel separation = %0.3f" % (maxPixSep, ))
        self.assertLess(maxAngSep.asArcseconds(), 0.001)
        self.assertLess(maxPixSep, 0.005)
        if catsUpdated:
            allowedDistErr = 1e-7
        else:
            allowedDistErr = 0.001
        self.assertLess(
            maxDistErr.asArcseconds(), allowedDistErr,
            "Computed distance in match list is off by %s arcsec" %
            (maxDistErr.asArcseconds(), ))
Exemple #16
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
Exemple #17
0
    def singleTestInstance(self, filename, distortFunc, doPlot=False):
        sourceCat = self.loadSourceCatalog(self.filename)
        refCat = self.computePosRefCatalog(sourceCat)
        distortedCat = distort.distortList(sourceCat, distortFunc)

        if doPlot:
            import matplotlib.pyplot as plt

            undistorted = [
                self.wcs.skyToPixel(
                    self.distortedWcs.pixelToSky(ss.getCentroid()))
                for ss in distortedCat
            ]
            refs = [self.wcs.skyToPixel(ss.getCoord()) for ss in refCat]

            def plot(catalog, symbol):
                plt.plot([ss.getX() for ss in catalog],
                         [ss.getY() for ss in catalog], symbol)

            plot(distortedCat, 'b+')  # Distorted positions: blue +
            plot(undistorted, 'g+')  # Undistorted positions: green +
            plot(refs, 'rx')  # Reference catalog: red x
            # The green + should overlap with the red x, because that's how
            # MatchPessimisticB does it.

            plt.show()

        sourceCat = distortedCat

        matchRes = self.MatchPessimisticB.matchObjectsToSources(
            refCat=refCat,
            sourceCat=sourceCat,
            wcs=self.distortedWcs,
            refFluxField="r_flux",
        )
        matches = matchRes.matches
        if doPlot:
            measAstrom.plotAstrometry(matches=matches,
                                      refCat=refCat,
                                      sourceCat=sourceCat)
        self.assertEqual(len(matches), 183)

        refCoordKey = afwTable.CoordKey(refCat.schema["coord"])
        srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"])
        refCentroidKey = afwTable.Point2DKey(refCat.getSchema()["centroid"])
        maxDistErr = afwGeom.Angle(0)

        for refObj, source, distRad in matches:
            sourceCoord = source.get(srcCoordKey)
            refCoord = refObj.get(refCoordKey)
            predDist = sourceCoord.angularSeparation(refCoord)
            distErr = abs(predDist - distRad * afwGeom.radians)
            maxDistErr = max(distErr, maxDistErr)

            if refObj.getId() != source.getId():
                refCentroid = refObj.get(refCentroidKey)
                sourceCentroid = source.getCentroid()
                radius = math.hypot(*(refCentroid - sourceCentroid))
                self.fail(
                    "ID mismatch: %s at %s != %s at %s; error = %0.1f pix" %
                    (refObj.getId(), refCentroid, source.getId(),
                     sourceCentroid, radius))

        self.assertLess(maxDistErr.asArcseconds(), 1e-7)
Exemple #18
0
    def singleTestInstance(self, filename, distortFunc, doPlot=False):
        sourceCat = self.loadSourceCatalog(self.filename)
        refCat = self.computePosRefCatalog(sourceCat)

        # Apply source selector to sourceCat, using the astrometry config defaults
        tempConfig = measAstrom.AstrometryTask.ConfigClass()
        tempConfig.matcher.retarget(measAstrom.MatchOptimisticBTask)
        tempConfig.sourceSelector["matcher"].excludePixelFlags = False
        tempSolver = measAstrom.AstrometryTask(config=tempConfig,
                                               refObjLoader=None)
        sourceSelection = tempSolver.sourceSelector.run(sourceCat)

        distortedCat = distort.distortList(sourceSelection.sourceCat,
                                           distortFunc)

        if doPlot:
            import matplotlib.pyplot as plt
            undistorted = [
                self.wcs.skyToPixel(
                    self.distortedWcs.pixelToSky(ss.getCentroid()))
                for ss in distortedCat
            ]
            refs = [self.wcs.skyToPixel(ss.getCoord()) for ss in refCat]

            def plot(catalog, symbol):
                plt.plot([ss.getX() for ss in catalog],
                         [ss.getY() for ss in catalog], symbol)

            # plot(sourceCat, 'k+') # Original positions: black +
            plot(distortedCat, 'b+')  # Distorted positions: blue +
            plot(undistorted, 'g+')  # Undistorted positions: green +
            plot(refs, 'rx')  # Reference catalog: red x
            # The green + should overlap with the red x, because that's how matchOptimisticB does it.
            # The black + happens to overlap with those also, but that's beside the point.
            plt.show()

        sourceCat = distortedCat

        matchRes = self.matchOptimisticB.matchObjectsToSources(
            refCat=refCat,
            sourceCat=sourceCat,
            wcs=self.distortedWcs,
            sourceFluxField='slot_ApFlux_instFlux',
            refFluxField="r_flux",
        )
        matches = matchRes.matches
        if doPlot:
            measAstrom.plotAstrometry(matches=matches,
                                      refCat=refCat,
                                      sourceCat=sourceCat)
        self.assertEqual(len(matches), 183)

        refCoordKey = afwTable.CoordKey(refCat.schema["coord"])
        srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"])
        refCentroidKey = afwTable.Point2DKey(refCat.getSchema()["centroid"])
        maxDistErr = 0 * lsst.geom.radians
        for refObj, source, distRad in matches:
            sourceCoord = source.get(srcCoordKey)
            refCoord = refObj.get(refCoordKey)
            predDist = sourceCoord.separation(refCoord)
            distErr = abs(predDist - distRad * lsst.geom.radians)
            maxDistErr = max(distErr, maxDistErr)

            if refObj.getId() != source.getId():
                refCentroid = refObj.get(refCentroidKey)
                sourceCentroid = source.getCentroid()
                radius = math.hypot(*(refCentroid - sourceCentroid))
                self.fail(
                    "ID mismatch: %s at %s != %s at %s; error = %0.1f pix" %
                    (refObj.getId(), refCentroid, source.getId(),
                     sourceCentroid, radius))

        self.assertLess(maxDistErr.asArcseconds(), 1e-7)
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,
                   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