Exemplo n.º 1
0
    def testLists(self):
        """Check updating lists of reference objects and sources"""
        # arbitrary but reasonable values that are intentionally different than testCatalogs
        maxPix = 1000
        numPoints = 10
        self.setCatalogs(maxPix=maxPix, numPoints=numPoints)

        # update the catalogs as lists
        afwTable.updateSourceCoords(self.wcs, [s for s in self.sourceCat])
        afwTable.updateRefCentroids(self.wcs, [r for r in self.refCat])

        self.checkCatalogs()
Exemplo n.º 2
0
    def testLists(self):
        """Check updating lists of reference objects and sources"""
        # arbitrary but reasonable values that are intentionally different than
        # testCatalogs
        maxPix = 1000
        numPoints = 10
        self.setCatalogs(maxPix=maxPix, numPoints=numPoints)

        # update the catalogs as lists
        afwTable.updateSourceCoords(self.wcs, [s for s in self.sourceCat])
        afwTable.updateRefCentroids(self.wcs, [r for r in self.refCat])

        self.checkCatalogs()
Exemplo n.º 3
0
    def testCatalogs(self):
        """Check updating catalogs of reference objects and sources"""
        # arbitrary but reasonable values that are intentionally different than testLists
        maxPix = 2000
        numPoints = 9
        self.setCatalogs(maxPix=maxPix, numPoints=numPoints)

        # update the catalogs
        afwTable.updateSourceCoords(self.wcs, self.sourceCat)
        afwTable.updateRefCentroids(self.wcs, self.refCat)

        # check that centroids and coords match
        self.checkCatalogs()
Exemplo n.º 4
0
    def testCatalogs(self):
        """Check updating catalogs of reference objects and sources"""
        # arbitrary but reasonable values that are intentionally different than
        # testLists
        maxPix = 2000
        numPoints = 9
        self.setCatalogs(maxPix=maxPix, numPoints=numPoints)

        # update the catalogs
        afwTable.updateSourceCoords(self.wcs, self.sourceCat)
        afwTable.updateRefCentroids(self.wcs, self.refCat)

        # check that centroids and coords match
        self.checkCatalogs()
Exemplo n.º 5
0
    def testSourceCenter(self):
        """Check that a source at the center is handled as expected"""
        src = self.sourceCat.addNew()
        src.set(self.srcCentroidKey, self.crpix)

        # initial coord should be nan; as a sanity-check
        nanSourceCoord = self.sourceCat[0].get(self.srcCoordKey)
        for val in nanSourceCoord:
            self.assertTrue(math.isnan(val))

        # compute coord should be crval
        afwTable.updateSourceCoords(self.wcs, self.sourceCat)
        srcCoord = self.sourceCat[0].get(self.srcCoordKey)
        self.assertPairsAlmostEqual(srcCoord, self.crval)

        # centroid should not be changed; also make sure that getCentroid words
        self.assertEqual(self.sourceCat[0].getCentroid(), self.crpix)
Exemplo n.º 6
0
    def testSourceCenter(self):
        """Check that a source at the center is handled as expected"""
        src = self.sourceCat.addNew()
        src.set(self.srcCentroidKey, self.crpix)

        # initial coord should be nan; as a sanity-check
        nanSourceCoord = self.sourceCat[0].get(self.srcCoordKey)
        for val in nanSourceCoord:
            self.assertTrue(math.isnan(val))

        # compute coord should be crval
        afwTable.updateSourceCoords(self.wcs, self.sourceCat)
        srcCoord = self.sourceCat[0].get(self.srcCoordKey)
        self.assertPairsAlmostEqual(srcCoord, self.crval)

        # centroid should not be changed; also make sure that getCentroid words
        self.assertEqual(self.sourceCat[0].getCentroid(), self.crpix)
Exemplo n.º 7
0
    def fitWcs(self,
               matches,
               initWcs,
               bbox=None,
               refCat=None,
               sourceCat=None,
               exposure=None):
        """!Fit a TAN-SIP WCS from a list of reference object/source matches

        @param[in,out] matches  a list of lsst::afw::table::ReferenceMatch
            The following fields are read:
            - match.first (reference object) coord
            - match.second (source) centroid
            The following fields are written:
            - match.first (reference object) centroid,
            - match.second (source) centroid
            - match.distance (on sky separation, in radians)
        @param[in] initWcs  initial WCS
        @param[in] bbox  the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
            if None or an empty box then computed from matches
        @param[in,out] refCat  reference object catalog, or None.
            If provided then all centroids are updated with the new WCS,
            otherwise only the centroids for ref objects in matches are updated.
            Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
        @param[in,out] sourceCat  source catalog, or None.
            If provided then coords are updated with the new WCS;
            otherwise only the coords for sources in matches are updated.
            Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
        @param[in] exposure  Ignored; present for consistency with FitSipDistortionTask.

        @return an lsst.pipe.base.Struct with the following fields:
        - wcs  the fit WCS as an lsst.afw.geom.Wcs
        - scatterOnSky  median on-sky separation between reference objects and sources in "matches",
            as an lsst.afw.geom.Angle
        """
        if bbox is None:
            bbox = afwGeom.Box2I()

        import lsstDebug
        debug = lsstDebug.Info(__name__)

        wcs = self.initialWcs(matches, initWcs)
        rejected = np.zeros(len(matches), dtype=bool)
        for rej in range(self.config.numRejIter):
            sipObject = self._fitWcs(
                [mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
            wcs = sipObject.getNewWcs()
            rejected = self.rejectMatches(matches, wcs, rejected)
            if rejected.sum() == len(rejected):
                raise RuntimeError("All matches rejected in iteration %d" %
                                   (rej + 1, ))
            self.log.debug(
                "Iteration {0} of astrometry fitting: rejected {1} outliers, "
                "out of {2} total matches.".format(rej, rejected.sum(),
                                                   len(rejected)))
            if debug.plot:
                print("Plotting fit after rejection iteration %d/%d" %
                      (rej + 1, self.config.numRejIter))
                self.plotFit(matches, wcs, rejected)
        # Final fit after rejection
        sipObject = self._fitWcs(
            [mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
        wcs = sipObject.getNewWcs()
        if debug.plot:
            print("Plotting final fit")
            self.plotFit(matches, wcs, rejected)

        if refCat is not None:
            self.log.debug("Updating centroids in refCat")
            afwTable.updateRefCentroids(wcs, refList=refCat)
        else:
            self.log.warn(
                "Updating reference object centroids in match list; refCat is None"
            )
            afwTable.updateRefCentroids(
                wcs, refList=[match.first for match in matches])

        if sourceCat is not None:
            self.log.debug("Updating coords in sourceCat")
            afwTable.updateSourceCoords(wcs, sourceList=sourceCat)
        else:
            self.log.warn(
                "Updating source coords in match list; sourceCat is None")
            afwTable.updateSourceCoords(
                wcs, sourceList=[match.second for match in matches])

        self.log.debug("Updating distance in match list")
        setMatchDistance(matches)

        scatterOnSky = sipObject.getScatterOnSky()

        if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
            raise pipeBase.TaskError(
                "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec"
                % (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))

        return pipeBase.Struct(
            wcs=wcs,
            scatterOnSky=scatterOnSky,
        )
Exemplo n.º 8
0
    def refitWcs(self, sourceCat, exposure, matches):
        """!A final Wcs solution after matching and removing distortion

        Specifically, fitting the non-linear part, since the linear
        part has been provided by the matching engine.

        @param sourceCat Sources on exposure, an lsst.afw.table.SourceCatalog
        @param exposure Exposure of interest, an lsst.afw.image.ExposureF or D
        @param matches Astrometric matches, as a list of lsst.afw.table.ReferenceMatch

        @return the resolved-Wcs object, or None if config.solver.calculateSip is False.
        """
        sip = None
        if self.config.solver.calculateSip:
            self.log.info("Refitting WCS")
            origMatches = matches
            wcs = exposure.getWcs()

            import lsstDebug
            display = lsstDebug.Info(__name__).display
            frame = lsstDebug.Info(__name__).frame
            pause = lsstDebug.Info(__name__).pause

            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()

            numRejected = 0
            try:
                for i in range(self.config.rejectIter):
                    wcs, scatter = fitWcs(wcs, title="Iteration %d" % i)

                    ref = np.array([wcs.skyToPixel(m.first.getCoord()) for m in matches])
                    src = np.array([m.second.getCentroid() for m in matches])
                    diff = ref - src
                    rms = diff.std()
                    trimmed = []
                    for d, m in zip(diff, matches):
                        if np.all(np.abs(d) < self.config.rejectThresh*rms):
                            trimmed.append(m)
                        else:
                            numRejected += 1
                    if len(matches) == len(trimmed):
                        break
                    matches = trimmed

                # Final fit after rejection iterations
                wcs, scatter = fitWcs(wcs, title="Final astrometry")

            except lsst.pex.exceptions.LengthError as e:
                self.log.warn("Unable to fit SIP: %s", e)

            self.log.info("Astrometric scatter: %f arcsec (%d matches, %d rejected)",
                          scatter.asArcseconds(), len(matches), numRejected)
            exposure.setWcs(wcs)

            # Apply WCS to sources
            updateSourceCoords(wcs, sourceCat)
        else:
            self.log.warn("Not calculating a SIP solution; matches may be suspect")

        if self._display:
            frame = lsstDebug.Info(__name__).frame
            displayAstrometry(exposure=exposure, sourceCat=sourceCat, matches=matches,
                              frame=frame, pause=False)

        return sip
Exemplo n.º 9
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)
Exemplo n.º 10
0
 def testNull(self):
     """Check that an empty list causes no problems for either function"""
     afwTable.updateRefCentroids(self.wcs, [])
     afwTable.updateSourceCoords(self.wcs, [])
Exemplo n.º 11
0
def _loadAndMatchCatalogs(repo,
                          dataIds,
                          matchRadius,
                          doApplyExternalPhotoCalib=False,
                          externalPhotoCalibName=None,
                          doApplyExternalSkyWcs=False,
                          externalSkyWcsName=None,
                          skipTEx=False,
                          skipNonSrd=False):
    """Load data from specific visits and returned a calibrated catalog matched
    with a reference.

    Parameters
    ----------
    repo : `str` or `lsst.daf.persistence.Butler`
        A Butler or a repository URL that can be used to construct one.
    dataIds : list of dict
        List of butler data IDs of Image catalogs to compare to
        reference. The calexp cpixel image is needed for the photometric
        calibration.
    matchRadius :  `lsst.geom.Angle`, optional
        Radius for matching. Default is 1 arcsecond.
    doApplyExternalPhotoCalib : bool, optional
        Apply external photoCalib to calibrate fluxes.
    externalPhotoCalibName : str, optional
        Type of external `PhotoCalib` to apply.  Currently supported are jointcal,
        fgcm, and fgcm_tract.  Must be set if doApplyExternalPhotoCalib is True.
    doApplyExternalSkyWcs : bool, optional
        Apply external wcs to calibrate positions.
    externalSkyWcsName : str, optional
        Type of external `wcs` to apply.  Currently supported is jointcal.
        Must be set if "doApplyExternalWcs" is True.
    skipTEx : `bool`, optional
        Skip TEx calculations (useful for older catalogs that don't have
        PsfShape measurements).
    skipNonSrd : `bool`, optional
        Skip any metrics not defined in the LSST SRD; default False.

    Returns
    -------
    catalog : `lsst.afw.table.SourceCatalog`
        A new calibrated SourceCatalog.
    matches : `lsst.afw.table.GroupView`
        A GroupView of the matched sources.

    Raises
    ------
    RuntimeError:
        Raised if "doApplyExternalPhotoCalib" is True and "externalPhotoCalibName"
        is None, or if "doApplyExternalSkyWcs" is True and "externalSkyWcsName" is
        None.
    """

    if doApplyExternalPhotoCalib and externalPhotoCalibName is None:
        raise RuntimeError(
            "Must set externalPhotoCalibName if doApplyExternalPhotoCalib is True."
        )
    if doApplyExternalSkyWcs and externalSkyWcsName is None:
        raise RuntimeError(
            "Must set externalSkyWcsName if doApplyExternalSkyWcs is True.")

    # Following
    # https://github.com/lsst/afw/blob/tickets/DM-3896/examples/repeatability.ipynb
    if isinstance(repo, dafPersist.Butler):
        butler = repo
    else:
        butler = dafPersist.Butler(repo)
    dataset = 'src'

    # 2016-02-08 MWV:
    # I feel like I could be doing something more efficient with
    # something along the lines of the following:
    #    dataRefs = [dafPersist.ButlerDataRef(butler, vId) for vId in dataIds]

    ccdKeyName = getCcdKeyName(dataIds[0])

    # Hack to support raft and sensor 0,1 IDs as ints for multimatch
    if ccdKeyName == 'sensor':
        ccdKeyName = 'raft_sensor_int'
        for vId in dataIds:
            vId[ccdKeyName] = raftSensorToInt(vId)

    schema = butler.get(dataset + "_schema").schema
    mapper = SchemaMapper(schema)
    mapper.addMinimalSchema(schema)
    mapper.addOutputField(Field[float]('base_PsfFlux_snr', 'PSF flux SNR'))
    mapper.addOutputField(Field[float]('base_PsfFlux_mag', 'PSF magnitude'))
    mapper.addOutputField(Field[float]('base_PsfFlux_magErr',
                                       'PSF magnitude uncertainty'))
    if not skipNonSrd:
        # Needed because addOutputField(... 'slot_ModelFlux_mag') will add a field with that literal name
        aliasMap = schema.getAliasMap()
        # Possibly not needed since base_GaussianFlux is the default, but this ought to be safe
        modelName = aliasMap[
            'slot_ModelFlux'] if 'slot_ModelFlux' in aliasMap.keys(
            ) else 'base_GaussianFlux'
        mapper.addOutputField(Field[float](f'{modelName}_mag',
                                           'Model magnitude'))
        mapper.addOutputField(Field[float](f'{modelName}_magErr',
                                           'Model magnitude uncertainty'))
        mapper.addOutputField(Field[float](f'{modelName}_snr',
                                           'Model flux snr'))
    mapper.addOutputField(Field[float]('e1', 'Source Ellipticity 1'))
    mapper.addOutputField(Field[float]('e2', 'Source Ellipticity 1'))
    mapper.addOutputField(Field[float]('psf_e1', 'PSF Ellipticity 1'))
    mapper.addOutputField(Field[float]('psf_e2', 'PSF Ellipticity 1'))
    newSchema = mapper.getOutputSchema()
    newSchema.setAliasMap(schema.getAliasMap())

    # Create an object that matches multiple catalogs with same schema
    mmatch = MultiMatch(newSchema,
                        dataIdFormat={
                            'visit': np.int32,
                            ccdKeyName: np.int32
                        },
                        radius=matchRadius,
                        RecordClass=SimpleRecord)

    # create the new extented source catalog
    srcVis = SourceCatalog(newSchema)

    for vId in dataIds:
        if not butler.datasetExists('src', vId):
            print(f'Could not find source catalog for {vId}; skipping.')
            continue

        photoCalib = _loadPhotoCalib(butler, vId, doApplyExternalPhotoCalib,
                                     externalPhotoCalibName)
        if photoCalib is None:
            continue

        if doApplyExternalSkyWcs:
            wcs = _loadExternalSkyWcs(butler, vId, externalSkyWcsName)
            if wcs is None:
                continue

        # We don't want to put this above the first _loadPhotoCalib call
        # because we need to use the first `butler.get` in there to quickly
        # catch dataIDs with no usable outputs.
        try:
            # HSC supports these flags, which dramatically improve I/O
            # performance; support for other cameras is DM-6927.
            oldSrc = butler.get('src', vId, flags=SOURCE_IO_NO_FOOTPRINTS)
        except (OperationalError, sqlite3.OperationalError):
            oldSrc = butler.get('src', vId)

        print(len(oldSrc),
              "sources in ccd %s  visit %s" % (vId[ccdKeyName], vId["visit"]))

        # create temporary catalog
        tmpCat = SourceCatalog(SourceCatalog(newSchema).table)
        tmpCat.extend(oldSrc, mapper=mapper)
        tmpCat['base_PsfFlux_snr'][:] = tmpCat['base_PsfFlux_instFlux'] \
            / tmpCat['base_PsfFlux_instFluxErr']

        if doApplyExternalSkyWcs:
            afwTable.updateSourceCoords(wcs, tmpCat)
        photoCalib.instFluxToMagnitude(tmpCat, "base_PsfFlux", "base_PsfFlux")
        if not skipNonSrd:
            tmpCat['slot_ModelFlux_snr'][:] = (
                tmpCat['slot_ModelFlux_instFlux'] /
                tmpCat['slot_ModelFlux_instFluxErr'])
            photoCalib.instFluxToMagnitude(tmpCat, "slot_ModelFlux",
                                           "slot_ModelFlux")

        if not skipTEx:
            _, psf_e1, psf_e2 = ellipticity_from_cat(
                oldSrc, slot_shape='slot_PsfShape')
            _, star_e1, star_e2 = ellipticity_from_cat(oldSrc,
                                                       slot_shape='slot_Shape')
            tmpCat['e1'][:] = star_e1
            tmpCat['e2'][:] = star_e2
            tmpCat['psf_e1'][:] = psf_e1
            tmpCat['psf_e2'][:] = psf_e2

        srcVis.extend(tmpCat, False)
        mmatch.add(catalog=tmpCat, dataId=vId)

    # Complete the match, returning a catalog that includes
    # all matched sources with object IDs that can be used to group them.
    matchCat = mmatch.finish()

    # Create a mapping object that allows the matches to be manipulated
    # as a mapping of object ID to catalog of sources.
    allMatches = GroupView.build(matchCat)

    return srcVis, allMatches
Exemplo n.º 12
0
 def testNull(self):
     """Check that an empty list causes no problems for either function"""
     afwTable.updateRefCentroids(self.wcs, [])
     afwTable.updateSourceCoords(self.wcs, [])
Exemplo n.º 13
0
def match_catalogs(inputs,
                   photoCalibs,
                   astromCalibs,
                   vIds,
                   matchRadius,
                   apply_external_wcs=False,
                   logger=None):
    schema = inputs[0].schema
    mapper = SchemaMapper(schema)
    mapper.addMinimalSchema(schema)
    mapper.addOutputField(Field[float]('base_PsfFlux_snr', 'PSF flux SNR'))
    mapper.addOutputField(Field[float]('base_PsfFlux_mag', 'PSF magnitude'))
    mapper.addOutputField(Field[float]('base_PsfFlux_magErr',
                                       'PSF magnitude uncertainty'))
    # Needed because addOutputField(... 'slot_ModelFlux_mag') will add a field with that literal name
    aliasMap = schema.getAliasMap()
    # Possibly not needed since base_GaussianFlux is the default, but this ought to be safe
    modelName = aliasMap['slot_ModelFlux'] if 'slot_ModelFlux' in aliasMap.keys(
    ) else 'base_GaussianFlux'
    mapper.addOutputField(Field[float](f'{modelName}_mag', 'Model magnitude'))
    mapper.addOutputField(Field[float](f'{modelName}_magErr',
                                       'Model magnitude uncertainty'))
    mapper.addOutputField(Field[float](f'{modelName}_snr', 'Model flux snr'))
    mapper.addOutputField(Field[float]('e1', 'Source Ellipticity 1'))
    mapper.addOutputField(Field[float]('e2', 'Source Ellipticity 1'))
    mapper.addOutputField(Field[float]('psf_e1', 'PSF Ellipticity 1'))
    mapper.addOutputField(Field[float]('psf_e2', 'PSF Ellipticity 1'))
    mapper.addOutputField(Field[np.int32]('filt', 'filter code'))
    newSchema = mapper.getOutputSchema()
    newSchema.setAliasMap(schema.getAliasMap())

    # Create an object that matches multiple catalogs with same schema
    mmatch = MultiMatch(newSchema,
                        dataIdFormat={
                            'visit': np.int32,
                            'detector': np.int32
                        },
                        radius=matchRadius,
                        RecordClass=SimpleRecord)

    # create the new extended source catalog
    srcVis = SourceCatalog(newSchema)

    filter_dict = {
        'u': 1,
        'g': 2,
        'r': 3,
        'i': 4,
        'z': 5,
        'y': 6,
        'HSC-U': 1,
        'HSC-G': 2,
        'HSC-R': 3,
        'HSC-I': 4,
        'HSC-Z': 5,
        'HSC-Y': 6
    }

    # Sort by visit, detector, then filter
    vislist = [v['visit'] for v in vIds]
    ccdlist = [v['detector'] for v in vIds]
    filtlist = [v['band'] for v in vIds]
    tab_vids = Table([vislist, ccdlist, filtlist],
                     names=['vis', 'ccd', 'filt'])
    sortinds = np.argsort(tab_vids, order=('vis', 'ccd', 'filt'))

    for ind in sortinds:
        oldSrc = inputs[ind]
        photoCalib = photoCalibs[ind]
        wcs = astromCalibs[ind]
        vId = vIds[ind]

        if logger:
            logger.debug(
                f"{len(oldSrc)} sources in ccd {vId['detector']}  visit {vId['visit']}"
            )

        # create temporary catalog
        tmpCat = SourceCatalog(SourceCatalog(newSchema).table)
        tmpCat.extend(oldSrc, mapper=mapper)

        filtnum = filter_dict[vId['band']]
        tmpCat['filt'] = np.repeat(filtnum, len(oldSrc))

        tmpCat['base_PsfFlux_snr'][:] = tmpCat['base_PsfFlux_instFlux'] \
            / tmpCat['base_PsfFlux_instFluxErr']

        if apply_external_wcs and wcs is not None:
            updateSourceCoords(wcs, tmpCat)

        photoCalib.instFluxToMagnitude(tmpCat, "base_PsfFlux", "base_PsfFlux")
        tmpCat['slot_ModelFlux_snr'][:] = (
            tmpCat['slot_ModelFlux_instFlux'] /
            tmpCat['slot_ModelFlux_instFluxErr'])
        photoCalib.instFluxToMagnitude(tmpCat, "slot_ModelFlux",
                                       "slot_ModelFlux")

        _, psf_e1, psf_e2 = ellipticity_from_cat(oldSrc,
                                                 slot_shape='slot_PsfShape')
        _, star_e1, star_e2 = ellipticity_from_cat(oldSrc,
                                                   slot_shape='slot_Shape')
        tmpCat['e1'][:] = star_e1
        tmpCat['e2'][:] = star_e2
        tmpCat['psf_e1'][:] = psf_e1
        tmpCat['psf_e2'][:] = psf_e2

        srcVis.extend(tmpCat, False)
        mmatch.add(catalog=tmpCat, dataId=vId)

    # Complete the match, returning a catalog that includes
    # all matched sources with object IDs that can be used to group them.
    matchCat = mmatch.finish()

    # Create a mapping object that allows the matches to be manipulated
    # as a mapping of object ID to catalog of sources.

    # I don't think I can persist a group view, so this may need to be called in a subsequent task
    # allMatches = GroupView.build(matchCat)

    return srcVis, matchCat
    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)
Exemplo n.º 15
0
    def fitWcs(self, matches, initWcs, bbox=None, refCat=None, sourceCat=None, exposure=None):
        """Fit a TAN-SIP WCS from a list of reference object/source matches

        Parameters
        ----------
        matches : `list` of `lsst.afw.table.ReferenceMatch`
            The following fields are read:

            - match.first (reference object) coord
            - match.second (source) centroid

            The following fields are written:

            - match.first (reference object) centroid,
            - match.second (source) centroid
            - match.distance (on sky separation, in radians)

        initWcs : `lsst.afw.geom.SkyWcs`
            initial WCS
        bbox : `lsst.geom.Box2I`
            the region over which the WCS will be valid (an lsst:afw::geom::Box2I);
            if None or an empty box then computed from matches
        refCat : `lsst.afw.table.SimpleCatalog`
            reference object catalog, or None.
            If provided then all centroids are updated with the new WCS,
            otherwise only the centroids for ref objects in matches are updated.
            Required fields are "centroid_x", "centroid_y", "coord_ra", and "coord_dec".
        sourceCat : `lsst.afw.table.SourceCatalog`
            source catalog, or None.
            If provided then coords are updated with the new WCS;
            otherwise only the coords for sources in matches are updated.
            Required fields are "slot_Centroid_x", "slot_Centroid_y", and "coord_ra", and "coord_dec".
        exposure : `lsst.afw.image.Exposure`
            Ignored; present for consistency with FitSipDistortionTask.

        Returns
        -------
        result : `lsst.pipe.base.Struct`
            with the following fields:

            - ``wcs`` :  the fit WCS (`lsst.afw.geom.SkyWcs`)
            - ``scatterOnSky`` :  median on-sky separation between reference
              objects and sources in "matches" (`lsst.afw.geom.Angle`)
        """
        if bbox is None:
            bbox = lsst.geom.Box2I()

        import lsstDebug
        debug = lsstDebug.Info(__name__)

        wcs = self.initialWcs(matches, initWcs)
        rejected = np.zeros(len(matches), dtype=bool)
        for rej in range(self.config.numRejIter):
            sipObject = self._fitWcs([mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
            wcs = sipObject.getNewWcs()
            rejected = self.rejectMatches(matches, wcs, rejected)
            if rejected.sum() == len(rejected):
                raise RuntimeError("All matches rejected in iteration %d" % (rej + 1,))
            self.log.debug(
                "Iteration {0} of astrometry fitting: rejected {1} outliers, "
                "out of {2} total matches.".format(
                    rej, rejected.sum(), len(rejected)
                )
            )
            if debug.plot:
                print("Plotting fit after rejection iteration %d/%d" % (rej + 1, self.config.numRejIter))
                self.plotFit(matches, wcs, rejected)
        # Final fit after rejection
        sipObject = self._fitWcs([mm for i, mm in enumerate(matches) if not rejected[i]], wcs)
        wcs = sipObject.getNewWcs()
        if debug.plot:
            print("Plotting final fit")
            self.plotFit(matches, wcs, rejected)

        if refCat is not None:
            self.log.debug("Updating centroids in refCat")
            afwTable.updateRefCentroids(wcs, refList=refCat)
        else:
            self.log.warn("Updating reference object centroids in match list; refCat is None")
            afwTable.updateRefCentroids(wcs, refList=[match.first for match in matches])

        if sourceCat is not None:
            self.log.debug("Updating coords in sourceCat")
            afwTable.updateSourceCoords(wcs, sourceList=sourceCat)
        else:
            self.log.warn("Updating source coords in match list; sourceCat is None")
            afwTable.updateSourceCoords(wcs, sourceList=[match.second for match in matches])

        self.log.debug("Updating distance in match list")
        setMatchDistance(matches)

        scatterOnSky = sipObject.getScatterOnSky()

        if scatterOnSky.asArcseconds() > self.config.maxScatterArcsec:
            raise pipeBase.TaskError(
                "Fit failed: median scatter on sky = %0.3f arcsec > %0.3f config.maxScatterArcsec" %
                (scatterOnSky.asArcseconds(), self.config.maxScatterArcsec))

        return pipeBase.Struct(
            wcs=wcs,
            scatterOnSky=scatterOnSky,
        )