def run(display=False):
    """Subtract background, mask cosmic rays, then detect and measure
    """
    # Create the tasks; if you use the default config then you don't have to construct the config,
    # as shown in constructing backgroundTask, but constructing the config makes it easier to modify
    repairConfig = RepairTask.ConfigClass()
    repairTask = RepairTask(config=repairConfig)

    backgroundTask = SubtractBackgroundTask()

    damConfig = DetectAndMeasureTask.ConfigClass()
    damConfig.detection.thresholdValue = 5.0
    damConfig.detection.includeThresholdMultiplier = 1.0
    damConfig.measurement.doApplyApCorr = "yes"
    detectAndMeasureTask = DetectAndMeasureTask(config=damConfig)

    # load the data
    # Exposure ID and the number of bits required for exposure IDs are usually obtained from a data repo,
    # but here we pick reasonable values (there are 64 bits to share between exposure IDs and source IDs).
    exposure = loadData()
    exposureIdInfo = ExposureIdInfo(expId=1, expBits=5)

    # repair cosmic rays
    repairTask.run(exposure=exposure)

    # subtract an initial estimate of background level
    backgroundTask.run(exposure=exposure)

    # detect and measure
    damRes = detectAndMeasureTask.run(exposure=exposure, exposureIdInfo=exposureIdInfo)
    if display:
        displayAstrometry(frame=2, exposure=damRes.exposure, sourceCat=damRes.sourceCat, pause=False)
Пример #2
0
    def distort(self, sourceCat, exposure):
        """!Calculate distorted source positions

        CCD images are often affected by optical distortion that makes
        the astrometric solution higher order than linear.  Unfortunately,
        most (all?) matching algorithms require that the distortion be
        small or zero, and so it must be removed.  We do this by calculating
        (un-)distorted positions, based on a known optical distortion model
        in the Ccd.

        The distortion correction moves sources, so we return the distorted bounding box.

        \param[in]     exposure Exposure to process
        \param[in,out] sourceCat  SourceCatalog; getX() and getY() will be used as inputs,
                                with distorted points in "centroid.distorted" field.
        \return bounding box of distorted exposure
        """
        detector = exposure.getDetector()
        pixToTanXYTransform = None
        if detector is None:
            self.log.warn(
                "No detector associated with exposure; assuming null distortion"
            )
        else:
            tanSys = detector.makeCameraSys(TAN_PIXELS)
            pixToTanXYTransform = detector.getTransformMap().get(tanSys)

        if pixToTanXYTransform is None:
            self.log.info("Null distortion correction")
            for s in sourceCat:
                s.set(self.centroidKey, s.getCentroid())
                s.set(self.centroidErrKey, s.getCentroidErr())
                s.set(self.centroidFlagKey, s.getCentroidFlag())
            return exposure.getBBox()

        # Distort source positions
        self.log.info("Applying distortion correction")
        for s in sourceCat:
            centroid = pixToTanXYTransform.forwardTransform(s.getCentroid())
            s.set(self.centroidKey, centroid)
            s.set(self.centroidErrKey, s.getCentroidErr())
            s.set(self.centroidFlagKey, s.getCentroidFlag())

        # Get distorted image size so that astrometry_net does not clip.
        bboxD = afwGeom.Box2D()
        for corner in detector.getCorners(TAN_PIXELS):
            bboxD.include(corner)

        if lsstDebug.Info(__name__).display:
            frame = lsstDebug.Info(__name__).frame
            pause = lsstDebug.Info(__name__).pause
            displayAstrometry(sourceCat=sourceCat,
                              distortedCentroidKey=self.centroidKey,
                              exposure=exposure,
                              frame=frame,
                              pause=pause)

        return afwGeom.Box2I(bboxD)
Пример #3
0
    def display(self, itemName, exposure, sourceCat=None):
        """Display exposure and sources on next frame, if display of itemName has been requested

        @param[in] itemName  name of item in debugInfo
        @param[in] exposure  exposure to display
        @param[in] sourceCat  source catalog to display
        """
        val = getDebugFrame(self._display, itemName)
        if not val:
            return

        displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self._frame, pause=False)
        self._frame += 1
Пример #4
0
    def loadAndMatch(self, exposure, sourceCat, bbox=None):
        """!Load reference objects overlapping an exposure and match to sources detected on that exposure

        @param[in] exposure  exposure whose WCS is to be fit
        @param[in] sourceCat  catalog of sourceCat detected on the exposure (an lsst.afw.table.SourceCatalog)
        @param[in] bbox  bounding box go use for finding reference objects; if None, use exposure's bbox

        @return an lsst.pipe.base.Struct with these fields:
        - refCat  reference object catalog of objects that overlap the exposure (with some margin)
            (an lsst::afw::table::SimpleCatalog)
        - matches  astrometric matches, a list of lsst.afw.table.ReferenceMatch
        - matchMeta  metadata about the field (an lsst.daf.base.PropertyList)

        @note ignores config.forceKnownWcs
        """
        with self.distortionContext(sourceCat=sourceCat,
                                    exposure=exposure) as bbox:
            if not self.solver:
                self.makeSubtask("solver")

            astrom = self.solver.useKnownWcs(
                sourceCat=sourceCat,
                exposure=exposure,
                bbox=bbox,
                calculateSip=False,
            )

            if astrom is None or astrom.getWcs() is None:
                raise RuntimeError("Unable to solve astrometry")

            matches = astrom.getMatches()
            matchMeta = astrom.getMatchMetadata()
            if matches is None or len(matches) == 0:
                raise RuntimeError("No astrometric matches")
            self.log.info("%d astrometric matches" % (len(matches)))

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

            return pipeBase.Struct(
                refCat=astrom.refCat,
                matches=matches,
                matchMeta=matchMeta,
            )
    def _astrometry(self, sourceCat, exposure, bbox=None):
        """!Solve astrometry to produce WCS

        \param[in] sourceCat Sources on exposure, an lsst.afw.table.SourceCatalog
        \param[in,out] exposure Exposure to process, an lsst.afw.image.ExposureF or D; wcs is updated
        \param[in] bbox Bounding box, or None to use exposure
        \return a pipe.base.Struct with fields:
        - refCat  reference object catalog of objects that overlap the exposure (with some margin)
            (an lsst::afw::table::SimpleCatalog)
        - matches  astrometric matches, a list of lsst.afw.table.ReferenceMatch
        - matchMeta  metadata about the field (an lsst.daf.base.PropertyList)
        """
        self.log.info("Solving astrometry")
        if bbox is None:
            bbox = exposure.getBBox()

        if not self.solver:
            self.makeSubtask("solver")

        astrom = self.solver.determineWcs(sourceCat=sourceCat,
                                          exposure=exposure,
                                          bbox=bbox)

        if astrom is None or astrom.getWcs() is None:
            raise RuntimeError("Unable to solve astrometry")

        matches = astrom.getMatches()
        matchMeta = astrom.getMatchMetadata()
        if matches is None or len(matches) == 0:
            raise RuntimeError("No astrometric matches")
        self.log.info("%d astrometric matches" % (len(matches)))

        # Note that this is the Wcs for the provided positions, which may be distorted
        exposure.setWcs(astrom.getWcs())

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

        return pipeBase.Struct(
            refCat=astrom.refCat,
            matches=matches,
            matchMeta=matchMeta,
        )
Пример #6
0
    def display(self, itemName, exposure, sourceCat=None):
        """Display exposure and sources on next frame, if display of itemName has been requested

        @param[in] itemName  name of item in debugInfo
        @param[in] exposure  exposure to display
        @param[in] sourceCat  source catalog to display
        """
        val = getDebugFrame(self._display, itemName)
        if not val:
            return

        displayAstrometry(exposure=exposure,
                          sourceCat=sourceCat,
                          frame=self._frame,
                          pause=False)
        self._frame += 1
Пример #7
0
def run(display=False):
    """Subtract background, mask cosmic rays, then detect and measure
    """
    # Create the tasks; note that background estimation is performed by a function,
    # not a task, though it has a config
    repairConfig = RepairTask.ConfigClass()
    repairTask = RepairTask(config=repairConfig)

    backgroundConfig = estimateBackground.ConfigClass()

    damConfig = DetectAndMeasureTask.ConfigClass()
    damConfig.detection.thresholdValue = 5.0
    damConfig.detection.includeThresholdMultiplier = 1.0
    damConfig.measurement.doApplyApCorr = "yes"
    detectAndMeasureTask = DetectAndMeasureTask(config=damConfig)

    # load the data
    # Exposure ID and the number of bits required for exposure IDs are usually obtained from a data repo,
    # but here we pick reasonable values (there are 64 bits to share between exposure IDs and source IDs).
    exposure = loadData()
    exposureIdInfo = ExposureIdInfo(expId=1, expBits=5)

    # repair cosmic rays
    repairTask.run(exposure=exposure)

    # subtract an initial estimate of background level
    estBg, exposure = estimateBackground(
        exposure=exposure,
        backgroundConfig=backgroundConfig,
        subtract=True,
    )

    # detect and measure
    damRes = detectAndMeasureTask.run(exposure=exposure,
                                      exposureIdInfo=exposureIdInfo)
    if display:
        displayAstrometry(frame=2,
                          exposure=damRes.exposure,
                          sourceCat=damRes.sourceCat,
                          pause=False)
def run(display=False):
    """Subtract background, mask cosmic rays, then detect and measure
    """
    # Create the tasks; note that background estimation is performed by a function,
    # not a task, though it has a config
    repairConfig = RepairTask.ConfigClass()
    repairTask = RepairTask(config=repairConfig)

    backgroundConfig = estimateBackground.ConfigClass()

    damConfig = DetectAndMeasureTask.ConfigClass()
    damConfig.detection.thresholdValue = 5.0
    damConfig.detection.includeThresholdMultiplier = 1.0
    damConfig.measurement.doApplyApCorr = "yes"
    detectAndMeasureTask = DetectAndMeasureTask(config=damConfig)

    # load the data
    # Exposure ID and the number of bits required for exposure IDs are usually obtained from a data repo,
    # but here we pick reasonable values (there are 64 bits to share between exposure IDs and source IDs).
    exposure = loadData()
    exposureIdInfo = ExposureIdInfo(expId=1, expBits=5)

    # repair cosmic rays
    repairTask.run(exposure=exposure)

    # subtract an initial estimate of background level
    estBg, exposure = estimateBackground(
        exposure = exposure,
        backgroundConfig = backgroundConfig,
        subtract = True,
    )

    # detect and measure
    damRes = detectAndMeasureTask.run(exposure=exposure, exposureIdInfo=exposureIdInfo)
    if display:
        displayAstrometry(frame=2, exposure=damRes.exposure, sourceCat=damRes.sourceCat, pause=False)
Пример #9
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
Пример #10
0
    def run(self, exposure, exposureIdInfo=None, background=None,
            icSourceCat=None):
        """!Calibrate an exposure (science image or coadd)

        @param[in,out] exposure  exposure to calibrate (an
            lsst.afw.image.ExposureF or similar);
            in:
            - MaskedImage
            - Psf
            out:
            - MaskedImage has background subtracted
            - Wcs is replaced
            - PhotoCalib is replaced
        @param[in] exposureIdInfo  ID info for exposure (an
            lsst.obs.base.ExposureIdInfo) If not provided, returned
            SourceCatalog IDs will not be globally unique.
        @param[in,out] background  background model already subtracted from
            exposure (an lsst.afw.math.BackgroundList). May be None if no
            background has been subtracted, though that is unusual for
            calibration. A refined background model is output.
        @param[in] icSourceCat  A SourceCatalog from CharacterizeImageTask
            from which we can copy some fields.

        @return pipe_base Struct containing these fields:
        - exposure  calibrate science exposure with refined WCS and PhotoCalib
        - background  model of background subtracted from exposure (an
          lsst.afw.math.BackgroundList)
        - sourceCat  catalog of measured sources
        - astromMatches  list of source/refObj matches from the astrometry
          solver
        """
        # detect, deblend and measure sources
        if exposureIdInfo is None:
            exposureIdInfo = ExposureIdInfo()

        if background is None:
            background = BackgroundList()
        sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
                                               exposureIdInfo.unusedBits)
        table = SourceTable.make(self.schema, sourceIdFactory)
        table.setMetadata(self.algMetadata)

        detRes = self.detection.run(table=table, exposure=exposure,
                                    doSmooth=True)
        sourceCat = detRes.sources
        if detRes.fpSets.background:
            for bg in detRes.fpSets.background:
                background.append(bg)
        if self.config.doSkySources:
            skySourceFootprints = self.skySources.run(mask=exposure.mask, seed=exposureIdInfo.expId)
            if skySourceFootprints:
                for foot in skySourceFootprints:
                    s = sourceCat.addNew()
                    s.setFootprint(foot)
                    s.set(self.skySourceKey, True)
        if self.config.doDeblend:
            self.deblend.run(exposure=exposure, sources=sourceCat)
        self.measurement.run(
            measCat=sourceCat,
            exposure=exposure,
            exposureId=exposureIdInfo.expId
        )
        if self.config.doApCorr:
            self.applyApCorr.run(
                catalog=sourceCat,
                apCorrMap=exposure.getInfo().getApCorrMap()
            )
        self.catalogCalculation.run(sourceCat)

        self.setPrimaryFlags.run(sourceCat, includeDeblend=self.config.doDeblend)

        if icSourceCat is not None and \
           len(self.config.icSourceFieldsToCopy) > 0:
            self.copyIcSourceFields(icSourceCat=icSourceCat,
                                    sourceCat=sourceCat)

        # TODO DM-11568: this contiguous check-and-copy could go away if we
        # reserve enough space during SourceDetection and/or SourceDeblend.
        # NOTE: sourceSelectors require contiguous catalogs, so ensure
        # contiguity now, so views are preserved from here on.
        if not sourceCat.isContiguous():
            sourceCat = sourceCat.copy(deep=True)

        # perform astrometry calibration:
        # fit an improved WCS and update the exposure's WCS in place
        astromMatches = None
        matchMeta = None
        if self.config.doAstrometry:
            try:
                astromRes = self.astrometry.run(
                    exposure=exposure,
                    sourceCat=sourceCat,
                )
                astromMatches = astromRes.matches
                matchMeta = astromRes.matchMeta
            except Exception as e:
                if self.config.requireAstrometry:
                    raise
                self.log.warn("Unable to perform astrometric calibration "
                              "(%s): attempting to proceed" % e)

        # compute photometric calibration
        if self.config.doPhotoCal:
            try:
                photoRes = self.photoCal.run(exposure, sourceCat=sourceCat, expId=exposureIdInfo.expId)
                exposure.setPhotoCalib(photoRes.photoCalib)
                # TODO: reword this to phrase it in terms of the calibration factor?
                self.log.info("Photometric zero-point: %f" %
                              photoRes.photoCalib.instFluxToMagnitude(1.0))
                self.setMetadata(exposure=exposure, photoRes=photoRes)
            except Exception as e:
                if self.config.requirePhotoCal:
                    raise
                self.log.warn("Unable to perform photometric calibration "
                              "(%s): attempting to proceed" % e)
                self.setMetadata(exposure=exposure, photoRes=None)

        if self.config.doInsertFakes:
            self.insertFakes.run(exposure, background=background)

            table = SourceTable.make(self.schema, sourceIdFactory)
            table.setMetadata(self.algMetadata)

            detRes = self.detection.run(table=table, exposure=exposure,
                                        doSmooth=True)
            sourceCat = detRes.sources
            if detRes.fpSets.background:
                for bg in detRes.fpSets.background:
                    background.append(bg)
            if self.config.doDeblend:
                self.deblend.run(exposure=exposure, sources=sourceCat)
            self.measurement.run(
                measCat=sourceCat,
                exposure=exposure,
                exposureId=exposureIdInfo.expId
            )
            if self.config.doApCorr:
                self.applyApCorr.run(
                    catalog=sourceCat,
                    apCorrMap=exposure.getInfo().getApCorrMap()
                )
            self.catalogCalculation.run(sourceCat)

            if icSourceCat is not None and len(self.config.icSourceFieldsToCopy) > 0:
                self.copyIcSourceFields(icSourceCat=icSourceCat,
                                        sourceCat=sourceCat)

        frame = getDebugFrame(self._display, "calibrate")
        if frame:
            displayAstrometry(
                sourceCat=sourceCat,
                exposure=exposure,
                matches=astromMatches,
                frame=frame,
                pause=False,
            )

        return pipeBase.Struct(
            exposure=exposure,
            background=background,
            sourceCat=sourceCat,
            astromMatches=astromMatches,
            matchMeta=matchMeta,
            # These are duplicate entries with different names for use with
            # gen3 middleware
            outputExposure=exposure,
            outputCat=sourceCat,
            outputBackground=background,
        )
Пример #11
0
    def calibrate(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None):
        """!Calibrate an exposure (science image or coadd)

        @param[in,out] exposure  exposure to calibrate (an lsst.afw.image.ExposureF or similar);
            in:
            - MaskedImage
            - Psf
            out:
            - MaskedImage has background subtracted
            - Wcs is replaced
            - Calib zero-point is set
        @param[in] exposureIdInfo  ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
            If not provided, returned SourceCatalog IDs will not be globally unique.
        @param[in,out] background  background model already subtracted from exposure
            (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
            though that is unusual for calibration.
            A refined background model is output.
        @param[in] icSourceCat  A SourceCatalog from CharacterizeImageTask from which we can copy
            some fields.

        @return pipe_base Struct containing these fields:
        - exposure  calibrate science exposure with refined WCS and Calib
        - background  model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
        - sourceCat  catalog of measured sources
        - astromMatches  list of source/refObj matches from the astrometry solver
        """
        # detect, deblend and measure sources
        if exposureIdInfo is None:
            exposureIdInfo = ExposureIdInfo()

        procRes = self.detectAndMeasure.run(
            exposure = exposure,
            exposureIdInfo = exposureIdInfo,
            background = background,
        )
        background = procRes.background
        sourceCat = procRes.sourceCat

        if icSourceCat is not None and len(self.config.icSourceFieldsToCopy) > 0:
            self.copyIcSourceFields(icSourceCat=icSourceCat, sourceCat=sourceCat)

        # perform astrometry calibration:
        # fit an improved WCS and update the exposure's WCS in place
        astromMatches = None
        if self.config.doAstrometry:
            try:
                astromRes = self.astrometry.run(
                    exposure = exposure,
                    sourceCat = sourceCat,
                )
                astromMatches = astromRes.matches
            except Exception as e:
                if self.config.requireAstrometry:
                    raise
                self.log.warn("Unable to perform astrometric calibration (%s): attempting to proceed" % e)

        # compute photometric calibration
        if self.config.doPhotoCal:
            try:
                if astromMatches is None:
                    astromRes = self.astrometry.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
                photoRes = self.photoCal.run(exposure, astromMatches)
                exposure.getCalib().setFluxMag0(photoRes.calib.getFluxMag0())
                self.log.info("Photometric zero-point: %f" % photoRes.calib.getMagnitude(1.0))
                self.setMetadata(exposure=exposure, photoRes=photoRes)
            except Exception as e:
                if self.config.requirePhotoCal:
                    raise
                self.log.warn("Unable to perform photometric calibration (%s): attempting to proceed" % e)
                self.setMetadata(exposure=exposure, photoRes=None)

        frame = getDebugFrame(self._display, "calibrate")
        if frame:
            displayAstrometry(
                sourceCat = sourceCat,
                exposure = exposure,
                matches = astromMatches,
                frame = frame,
                pause = False,
            )

        return pipeBase.Struct(
            exposure = exposure,
            background = background,
            sourceCat = sourceCat,
            astromMatches = astromMatches,
        )
Пример #12
0
    def calibrate(self,
                  exposure,
                  exposureIdInfo=None,
                  background=None,
                  icSourceCat=None):
        """!Calibrate an exposure (science image or coadd)

        @param[in,out] exposure  exposure to calibrate (an
            lsst.afw.image.ExposureF or similar);
            in:
            - MaskedImage
            - Psf
            out:
            - MaskedImage has background subtracted
            - Wcs is replaced
            - Calib zero-point is set
        @param[in] exposureIdInfo  ID info for exposure (an
            lsst.obs.base.ExposureIdInfo) If not provided, returned
            SourceCatalog IDs will not be globally unique.
        @param[in,out] background  background model already subtracted from
            exposure (an lsst.afw.math.BackgroundList). May be None if no
            background has been subtracted, though that is unusual for
            calibration. A refined background model is output.
        @param[in] icSourceCat  A SourceCatalog from CharacterizeImageTask
            from which we can copy some fields.

        @return pipe_base Struct containing these fields:
        - exposure  calibrate science exposure with refined WCS and Calib
        - background  model of background subtracted from exposure (an
          lsst.afw.math.BackgroundList)
        - sourceCat  catalog of measured sources
        - astromMatches  list of source/refObj matches from the astrometry
          solver
        """
        # detect, deblend and measure sources
        if exposureIdInfo is None:
            exposureIdInfo = ExposureIdInfo()

        if background is None:
            background = BackgroundList()
        sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
                                               exposureIdInfo.unusedBits)
        table = SourceTable.make(self.schema, sourceIdFactory)
        table.setMetadata(self.algMetadata)

        if self.config.doInsertFakes:
            self.insertFakes.run(exposure, background=background)

        detRes = self.detection.run(table=table,
                                    exposure=exposure,
                                    doSmooth=True)
        sourceCat = detRes.sources
        if detRes.fpSets.background:
            background.append(detRes.fpSets.background)
        if self.config.doDeblend:
            self.deblend.run(exposure=exposure, sources=sourceCat)
        self.measurement.run(measCat=sourceCat,
                             exposure=exposure,
                             exposureId=exposureIdInfo.expId)
        if self.config.doApCorr:
            self.applyApCorr.run(catalog=sourceCat,
                                 apCorrMap=exposure.getInfo().getApCorrMap())
        self.catalogCalculation.run(sourceCat)

        if icSourceCat is not None and \
           len(self.config.icSourceFieldsToCopy) > 0:
            self.copyIcSourceFields(icSourceCat=icSourceCat,
                                    sourceCat=sourceCat)

        # perform astrometry calibration:
        # fit an improved WCS and update the exposure's WCS in place
        astromMatches = None
        if self.config.doAstrometry:
            try:
                astromRes = self.astrometry.run(
                    exposure=exposure,
                    sourceCat=sourceCat,
                )
                astromMatches = astromRes.matches
            except Exception as e:
                if self.config.requireAstrometry:
                    raise
                self.log.warn("Unable to perform astrometric calibration "
                              "(%s): attempting to proceed" % e)

        # compute photometric calibration
        if self.config.doPhotoCal:
            try:
                photoRes = self.photoCal.run(exposure, sourceCat=sourceCat)
                exposure.getCalib().setFluxMag0(photoRes.calib.getFluxMag0())
                self.log.info("Photometric zero-point: %f" %
                              photoRes.calib.getMagnitude(1.0))
                self.setMetadata(exposure=exposure, photoRes=photoRes)
            except Exception as e:
                if self.config.requirePhotoCal:
                    raise
                self.log.warn("Unable to perform photometric calibration "
                              "(%s): attempting to proceed" % e)
                self.setMetadata(exposure=exposure, photoRes=None)

        frame = getDebugFrame(self._display, "calibrate")
        if frame:
            displayAstrometry(
                sourceCat=sourceCat,
                exposure=exposure,
                matches=astromMatches,
                frame=frame,
                pause=False,
            )

        return pipeBase.Struct(
            exposure=exposure,
            background=background,
            sourceCat=sourceCat,
            astromMatches=astromMatches,
        )
    def _solve(self,
               sourceCat,
               wcs,
               bbox,
               pixelScale,
               radecCenter,
               searchRadius,
               parity,
               filterName=None):
        """
        @param[in] parity  True for flipped parity, False for normal parity, None to leave parity unchanged
        """
        solver = self.refObjLoader._getSolver()

        imageSize = bbox.getDimensions()
        x0, y0 = bbox.getMin()

        # select sources with valid x, y, flux
        xybb = afwGeom.Box2D()
        goodsources = afwTable.SourceCatalog(sourceCat.table)
        badkeys = [
            goodsources.getSchema().find(name).key
            for name in self.config.badFlags
        ]

        for s in sourceCat:
            if np.isfinite(s.getX()) and np.isfinite(s.getY()) and np.isfinite(s.getPsfFlux()) \
                    and self._isGoodSource(s, badkeys):
                goodsources.append(s)
                xybb.include(afwGeom.Point2D(s.getX() - x0, s.getY() - y0))
        self.log.info("Number of selected sources for astrometry : %d" %
                      (len(goodsources)))
        if len(goodsources) < len(sourceCat):
            self.log.debug(
                'Keeping %i of %i sources with finite X,Y positions and PSF flux',
                len(goodsources), len(sourceCat))
        self.log.debug(
            'Feeding sources in range x=[%.1f, %.1f], y=[%.1f, %.1f] ' +
            '(after subtracting x0,y0 = %.1f,%.1f) to Astrometry.net',
            xybb.getMinX(), xybb.getMaxX(), xybb.getMinY(), xybb.getMaxY(), x0,
            y0)
        # setStars sorts them by PSF flux.
        solver.setStars(goodsources, x0, y0)
        solver.setMaxStars(self.config.maxStars)
        solver.setImageSize(*imageSize)
        solver.setMatchThreshold(self.config.matchThreshold)
        raDecRadius = None
        if radecCenter is not None:
            raDecRadius = (radecCenter.getLongitude().asDegrees(),
                           radecCenter.getLatitude().asDegrees(),
                           searchRadius.asDegrees())
            solver.setRaDecRadius(*raDecRadius)
            self.log.debug(
                'Searching for match around RA,Dec = (%g, %g) with radius %g deg'
                % raDecRadius)

        if pixelScale is not None:
            dscale = self.config.pixelScaleUncertainty
            scale = pixelScale.asArcseconds()
            lo = scale / dscale
            hi = scale * dscale
            solver.setPixelScaleRange(lo, hi)
            self.log.debug(
                'Searching for matches with pixel scale = %g +- %g %% -> range [%g, %g] arcsec/pix',
                scale, 100. * (dscale - 1.), lo, hi)

        if parity is not None:
            solver.setParity(parity)
            self.log.debug('Searching for match with parity = %s', str(parity))

        # Find and load index files within RA,Dec range and scale range.
        if radecCenter is not None:
            multiInds = self.refObjLoader._getMIndexesWithinRange(
                radecCenter, searchRadius)
        else:
            multiInds = self.refObjLoader.multiInds
        qlo, qhi = solver.getQuadSizeRangeArcsec()

        toload_multiInds = set()
        toload_inds = []
        for mi in multiInds:
            for i in range(len(mi)):
                ind = mi[i]
                if not ind.overlapsScaleRange(qlo, qhi):
                    continue
                toload_multiInds.add(mi)
                toload_inds.append(ind)

        import lsstDebug
        if lsstDebug.Info(__name__).display:
            # Use separate context for display, since astrometry.net can segfault if we don't...
            with LoadMultiIndexes(toload_multiInds):
                displayAstrometry(refCat=self.refObjLoader.loadPixelBox(
                    bbox, wcs, filterName).refCat,
                                  frame=lsstDebug.Info(__name__).frame,
                                  pause=lsstDebug.Info(__name__).pause)

        with LoadMultiIndexes(toload_multiInds):
            solver.addIndices(toload_inds)
            self.memusage('Index files loaded: ')

            cpulimit = self.config.maxCpuTime
            solver.run(cpulimit)

            self.memusage('Solving finished: ')

        self.memusage('Index files unloaded: ')

        if solver.didSolve():
            self.log.debug('Solved!')
            wcs = solver.getWcs()

            if x0 != 0 or y0 != 0:
                wcs = wcs.copyAtShiftedPixelOrigin(afwGeom.Extent2D(x0, y0))

        else:
            self.log.warn(
                'Did not get an astrometric solution from Astrometry.net')
            wcs = None
            # Gather debugging info...

            # -are there any reference stars in the proposed search area?
            # log the number found and discard the results
            if radecCenter is not None:
                self.refObjLoader.loadSkyCircle(radecCenter, searchRadius,
                                                filterName)

        qa = solver.getSolveStats()
        self.log.debug('qa: %s', qa.toString())
        return wcs, qa
Пример #14
0
    def calibrate(self,
                  exposure,
                  exposureIdInfo,
                  background=None,
                  icSourceCat=None):
        """!Calibrate an exposure (science image or coadd)

        @param[in,out] exposure  exposure to calibrate (an lsst.afw.image.ExposureF or similar);
            in:
            - MaskedImage
            - Psf
            out:
            - MaskedImage has background subtracted
            - Wcs is replaced
            - Calib zero-point is set
        @param[in,out] background  background model already subtracted from exposure
            (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
            though that is unusual for calibration.
            A refined background model is output.

        @return pipe_base Struct containing these fields:
        - exposure  calibrate science exposure with refined WCS and Calib
        - background  model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
        - sourceCat  catalog of measured sources
        - astromMatches  list of source/refObj matches from the astrometry solver
        """
        # detect, deblend and measure sources
        procRes = self.detectAndMeasure.run(
            exposure=exposure,
            exposureIdInfo=exposureIdInfo,
            background=background,
        )
        background = procRes.background
        sourceCat = procRes.sourceCat

        if icSourceCat is not None and len(
                self.config.icSourceFieldsToCopy) > 0:
            self.copyIcSourceFields(icSourceCat=icSourceCat,
                                    sourceCat=sourceCat)

        # perform astrometry calibration:
        # fit an improved WCS and update the exposure's WCS in place
        astromMatches = None
        if self.config.doAstrometry:
            try:
                astromRes = self.astrometry.run(
                    exposure=exposure,
                    sourceCat=sourceCat,
                )
                astromMatches = astromRes.matches
            except Exception as e:
                if self.config.requireAstrometry:
                    raise
                self.log.warn(
                    "Unable to perform astrometric calibration (%s): attempting to proceed"
                    % e)

        # compute photometric calibration
        if self.config.doPhotoCal:
            try:
                if astromMatches is None:
                    astromRes = self.astrometry.loadAndMatch(
                        exposure=exposure, sourceCat=sourceCat)
                photoRes = self.photoCal.run(exposure, astromMatches)
                exposure.getCalib().setFluxMag0(photoRes.calib.getFluxMag0())
                self.log.info("Photometric zero-point: %f" %
                              photoRes.calib.getMagnitude(1.0))
                self.setMetadata(exposure=exposure, photoRes=photoRes)
            except Exception as e:
                if self.config.requirePhotoCal:
                    raise
                self.log.warn(
                    "Unable to perform photometric calibration (%s): attempting to proceed"
                    % e)
                self.setMetadata(exposure=exposure, photoRes=None)

        frame = getDebugFrame(self._display, "calibrate")
        if frame:
            displayAstrometry(
                sourceCat=sourceCat,
                exposure=exposure,
                astromMatches=astromMatches,
                frame=frame,
                pause=False,
            )

        return pipeBase.Struct(
            exposure=exposure,
            background=background,
            sourceCat=sourceCat,
            astromMatches=astromMatches,
        )