def check(self, expectFactor):
        schema = SourceTable.makeMinimalSchema()
        task = DynamicDetectionTask(config=self.config, schema=schema)
        table = SourceTable.make(schema)

        results = task.run(table, self.exposure, expId=12345)
        self.assertFloatsAlmostEqual(results.factor, expectFactor, rtol=self.rtol)
    def run(self, exposure):

        table = SourceTable.make(self.schema)
        background = None

        detRes = self.detection.run(table=table, exposure=exposure)
        sourceCat = detRes.sources

        if background is None:
            background = BackgroundList()
        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
        )

        #results2 = self.deblend.run(...)
        #results3 = self.measurement.run(...)

        return pipeBase.Struct(
            exposure = exposure,
            background = background,
            sourceCat = sourceCat
        )
Exemple #3
0
    def check(self, expectFactor):
        schema = SourceTable.makeMinimalSchema()
        task = DynamicDetectionTask(config=self.config, schema=schema)
        table = SourceTable.make(schema)

        results = task.run(table, self.exposure, expId=12345)
        self.assertFloatsAlmostEqual(results.factor,
                                     expectFactor,
                                     rtol=self.rtol)
Exemple #4
0
    def run(self, exposure, exposureIdInfo, background=None, allowApCorr=True):
        """!Detect, deblend and perform single-frame measurement on sources and refine the background model

        @param[in,out] exposure  exposure to process. Background must already be subtracted
            to reasonable accuracy, else detection will fail.
            The background model is refined and resubtracted.
            apCorrMap is set if measuring aperture correction.
        @param[in]     exposureIdInfo  ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
        @param[in,out] background  background model to be modified (an lsst.afw.math.BackgroundList),
            or None to create a new background model
        @param[in] allowApCorr  allow measuring and applying aperture correction?

        @return pipe_base Struct containing these fields:
        - exposure: input exposure (as modified in the course of runing)
        - sourceCat: source catalog (an lsst.afw.table.SourceTable)
        - background: background model (input as modified, or a new model if input is None);
            an lsst.afw.math.BackgroundList
        """
        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:
            background.append(detRes.fpSets.background)

        if self.config.doDeblend:
            self.deblend.run(
                exposure=exposure,
                sources=sourceCat,
                psf=exposure.getPsf(),
            )

        self.measure(
            exposure=exposure,
            exposureIdInfo=exposureIdInfo,
            sourceCat=sourceCat,
            allowApCorr=allowApCorr,
        )

        return pipeBase.Struct(
            exposure=exposure,
            sourceCat=sourceCat,
            background=background,
        )
    def run(self, exposure, exposureIdInfo, background=None, allowApCorr=True):
        """!Detect, deblend and perform single-frame measurement on sources and refine the background model

        @param[in,out] exposure  exposure to process. Background must already be subtracted
            to reasonable accuracy, else detection will fail.
            The background model is refined and resubtracted.
            apCorrMap is set if measuring aperture correction.
        @param[in]     exposureIdInfo  ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
        @param[in,out] background  background model to be modified (an lsst.afw.math.BackgroundList),
            or None to create a new background model
        @param[in] allowApCorr  allow measuring and applying aperture correction?

        @return pipe_base Struct containing these fields:
        - exposure: input exposure (as modified in the course of runing)
        - sourceCat: source catalog (an lsst.afw.table.SourceTable)
        - background: background model (input as modified, or a new model if input is None);
            an lsst.afw.math.BackgroundList
        """
        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:
            background.append(detRes.fpSets.background)

        if self.config.doDeblend:
            self.deblend.run(
                exposure = exposure,
                sources = sourceCat,
                psf = exposure.getPsf(),
            )

        self.measure(
            exposure = exposure,
            exposureIdInfo = exposureIdInfo,
            sourceCat = sourceCat,
            allowApCorr = allowApCorr,
        )

        return pipeBase.Struct(
            exposure = exposure,
            sourceCat = sourceCat,
            background = background,
        )
Exemple #6
0
    def run(self, exposure, exposureIdInfo=None):
        
        if not exposure.hasPsf():
            self.installSimplePsf.run(exposure=exposure)

        if exposureIdInfo is None:
            exposureIdInfo = ExposureIdInfo()

        try:
            self.repair.run(exposure=exposure, keepCRs=True)
        except LengthError:
            self.log.info("Skipping cosmic ray detection: Too many CR pixels (max %0.f)" % self.repair.cosmicray.nCrPixelMax)

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

        filtered = maximum_filter(exposure.getImage().array, size=self.config.maximumFilterBoxWidth)
        detected = (filtered == exposure.getImage().getArray()) & (filtered > self.config.thresholdValue)

        detectedImage = afwImage.ImageF(detected.astype(np.float32))
        fps = afwDetect.FootprintSet(detectedImage, afwDetect.Threshold(0.5))
        fp_ctrl = afwDetect.FootprintControl(True, True)
        fps = afwDetect.FootprintSet(fps, self.config.footprintGrowValue, fp_ctrl)

        sources = afwTable.SourceCatalog(table)
        fps.makeSources(sources)

        self.measurement.run(measCat=sources, exposure=exposure, exposureId=exposureIdInfo.expId)
        self.catalogCalculation.run(sources)

        ## Add metadata to source catalog
        md = exposure.getMetadata()
        sources.getMetadata().add("BOTXCAM", md["BOTXCAM"])
        sources.getMetadata().add("BOTYCAM", md["BOTYCAM"])

        self.display("measure", exposure=exposure, sourceCat=sources)

        return pipeBase.Struct(sourceCat=sources) 
Exemple #7
0
    def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo,
                                    background):
        """!Perform one iteration of detect, measure and estimate PSF

        Performs the following operations:
        - if config.doMeasurePsf or not exposure.hasPsf():
            - install a simple PSF model (replacing the existing one, if need be)
        - interpolate over cosmic rays with keepCRs=True
        - estimate background and subtract it from the exposure
        - detect, deblend and measure sources, and subtract a refined background model;
        - if config.doMeasurePsf:
            - measure PSF

        @param[in,out] exposure  exposure to characterize (an lsst.afw.image.ExposureF or similar)
            The following changes are made:
            - update or set psf
            - update detection and cosmic ray mask planes
            - subtract background
        @param[in] exposureIdInfo  ID info for exposure (an lsst.obs_base.ExposureIdInfo)
        @param[in,out] background  initial model of background already subtracted from exposure
            (an lsst.afw.math.BackgroundList).

        @return pipe_base Struct containing these fields, all from the final iteration
        of detect sources, measure sources and estimate PSF:
        - exposure  characterized exposure; image is repaired by interpolating over cosmic rays,
            mask is updated accordingly, and the PSF model is set
        - sourceCat  detected sources (an lsst.afw.table.SourceCatalog)
        - background  model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
        - psfCellSet  spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
        """
        # install a simple PSF model, if needed or wanted
        if not exposure.hasPsf() or (self.config.doMeasurePsf
                                     and self.config.useSimplePsf):
            self.log.warn(
                "Source catalog detected and measured with placeholder or default PSF"
            )
            self.installSimplePsf.run(exposure=exposure)

        # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model)
        self.repair.run(exposure=exposure, keepCRs=True)
        self.display("repair_iter", exposure=exposure)

        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.doDeblend:
            self.deblend.run(exposure=exposure, sources=sourceCat)

        self.measurement.run(measCat=sourceCat,
                             exposure=exposure,
                             exposureId=exposureIdInfo.expId)

        measPsfRes = pipeBase.Struct(cellSet=None)
        if self.config.doMeasurePsf:
            if self.measurePsf.usesMatches:
                matches = self.ref_match.loadAndMatch(
                    exposure=exposure, sourceCat=sourceCat).matches
            else:
                matches = None
            measPsfRes = self.measurePsf.run(exposure=exposure,
                                             sources=sourceCat,
                                             matches=matches,
                                             expId=exposureIdInfo.expId)
        self.display("measure_iter", exposure=exposure, sourceCat=sourceCat)

        return pipeBase.Struct(
            exposure=exposure,
            sourceCat=sourceCat,
            background=background,
            psfCellSet=measPsfRes.cellSet,
        )
Exemple #8
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,
        )
Exemple #9
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 run(self,
            fakeCat,
            exposure,
            wcs=None,
            photoCalib=None,
            exposureIdInfo=None,
            icSourceCat=None,
            sfdSourceCat=None):
        """Add fake sources to a calexp and then run detection, deblending and measurement.

        Parameters
        ----------
        fakeCat : `pandas.core.frame.DataFrame`
                    The catalog of fake sources to add to the exposure
        exposure : `lsst.afw.image.exposure.exposure.ExposureF`
                    The exposure to add the fake sources to
        wcs : `lsst.afw.geom.SkyWcs`
                    WCS to use to add fake sources
        photoCalib : `lsst.afw.image.photoCalib.PhotoCalib`
                    Photometric calibration to be used to calibrate the fake sources
        exposureIdInfo : `lsst.obs.base.ExposureIdInfo`
        icSourceCat : `lsst.afw.table.SourceCatalog`
                    Default : None
                    Catalog to take the information about which sources were used for calibration from.
        sfdSourceCat : `lsst.afw.table.SourceCatalog`
                    Default : None
                    Catalog produced by singleFrameDriver, needed to copy some calibration flags from.

        Returns
        -------
        resultStruct : `lsst.pipe.base.struct.Struct`
            contains : outputExposure : `lsst.afw.image.exposure.exposure.ExposureF`
                       outputCat : `lsst.afw.table.source.source.SourceCatalog`

        Notes
        -----
        Adds pixel coordinates for each source to the fakeCat and removes objects with bulge or disk half
        light radius = 0 (if ``config.cleanCat = True``). These columns are called ``x`` and ``y`` and are in
        pixels.

        Adds the ``Fake`` mask plane to the exposure which is then set by `addFakeSources` to mark where fake
        sources have been added. Uses the information in the ``fakeCat`` to make fake galaxies (using galsim)
        and fake stars, using the PSF models from the PSF information for the calexp. These are then added to
        the calexp and the calexp with fakes included returned.

        The galsim galaxies are made using a double sersic profile, one for the bulge and one for the disk,
        this is then convolved with the PSF at that point.

        If exposureIdInfo is not provided then the SourceCatalog IDs will not be globally unique.
        """

        if wcs is None:
            wcs = exposure.getWcs()

        if photoCalib is None:
            photoCalib = exposure.getPhotoCalib()

        self.insertFakes.run(fakeCat, exposure, wcs, photoCalib)

        # detect, deblend and measure sources
        if exposureIdInfo is None:
            exposureIdInfo = ExposureIdInfo()

        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
        self.deblend.run(exposure=exposure, sources=sourceCat)
        self.measurement.run(measCat=sourceCat,
                             exposure=exposure,
                             exposureId=exposureIdInfo.expId)
        self.applyApCorr.run(catalog=sourceCat,
                             apCorrMap=exposure.getInfo().getApCorrMap())
        self.catalogCalculation.run(sourceCat)
        sourceCat = self.copyCalibrationFields(
            icSourceCat, sourceCat, self.config.calibrationFieldsToCopy)
        sourceCat = self.copyCalibrationFields(sfdSourceCat, sourceCat,
                                               self.config.srcFieldsToCopy)

        resultStruct = pipeBase.Struct(outputExposure=exposure,
                                       outputCat=sourceCat)
        return resultStruct
Exemple #11
0
    def calculateThreshold(self, exposure, seed, sigma=None):
        """Calculate new threshold

        This is the main functional addition to the vanilla
        `SourceDetectionTask`.

        We identify sky objects and perform forced PSF photometry on
        them. Using those PSF flux measurements and estimated errors,
        we set the threshold so that the stdev of the measurements
        matches the median estimated error.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            Exposure on which we're detecting sources.
        seed : `int`
            RNG seed to use for finding sky objects.
        sigma : `float`, optional
            Gaussian sigma of smoothing kernel; if not provided,
            will be deduced from the exposure's PSF.

        Returns
        -------
        result : `lsst.pipe.base.Struct`
            Result struct with components:

            - ``multiplicative``: multiplicative factor to be applied to the
                configured detection threshold (`float`).
            - ``additive``: additive factor to be applied to the background
                level (`float`).
        """
        # Make a catalog of sky objects
        fp = self.skyObjects.run(exposure.maskedImage.mask, seed)
        skyFootprints = FootprintSet(exposure.getBBox())
        skyFootprints.setFootprints(fp)
        table = SourceTable.make(self.skyMeasurement.schema)
        catalog = SourceCatalog(table)
        catalog.reserve(len(skyFootprints.getFootprints()))
        skyFootprints.makeSources(catalog)
        key = catalog.getCentroidKey()
        for source in catalog:
            peaks = source.getFootprint().getPeaks()
            assert len(peaks) == 1
            source.set(key, peaks[0].getF())
            source.updateCoord(exposure.getWcs())

        # Forced photometry on sky objects
        self.skyMeasurement.run(catalog, exposure, catalog, exposure.getWcs())

        # Calculate new threshold
        fluxes = catalog["base_PsfFlux_instFlux"]
        area = catalog["base_PsfFlux_area"]
        bg = catalog["base_LocalBackground_instFlux"]

        good = (~catalog["base_PsfFlux_flag"] & ~catalog["base_LocalBackground_flag"] &
                np.isfinite(fluxes) & np.isfinite(area) & np.isfinite(bg))

        if good.sum() < self.config.minNumSources:
            self.log.warn("Insufficient good flux measurements (%d < %d) for dynamic threshold calculation",
                          good.sum(), self.config.minNumSources)
            return Struct(multiplicative=1.0, additive=0.0)

        bgMedian = np.median((fluxes/area)[good])

        lq, uq = np.percentile((fluxes - bg*area)[good], [25.0, 75.0])
        stdevMeas = 0.741*(uq - lq)
        medianError = np.median(catalog["base_PsfFlux_instFluxErr"][good])
        return Struct(multiplicative=medianError/stdevMeas, additive=bgMedian)
Exemple #12
0
    def run(self,
            exposure=None,
            exposureIdInfo=None,
            background=None,
            doPsf=True,
            doApCorr=True,
            doWrite=True,
            doCalc=True):

        if not exposure.hasPsf():
            self.log.warn("Using SimplePsf for astrometry source detection")
            self.installSimplePsf.run(exposure=exposure)

        #Repair cosmic rays
        #self.repair.run(exposure=exposure, keepCRs=True)

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

        #Table schema needs to be set up prior to detection:
        sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
                                               exposureIdInfo.unusedBits)
        table = SourceTable.make(self.schema, sourceIdFactory)
        table.setMetadata(self.algMetadata)

        #Perform detection
        sourceCat = self.detection.run(table=table,
                                       exposure=exposure,
                                       doSmooth=True).sources

        #Perform measurement
        self.measurement.run(measCat=sourceCat,
                             exposure=exposure,
                             exposureId=exposureIdInfo.expId)

        if self.config.doEarlyAstrometry:
            astromRes = self.astrometry.run(exposure=exposure,
                                            sourceCat=sourceCat)

        measPsfRes = pipeBase.Struct(cellSet=None)
        if doPsf and self.config.doMeasurePsf:
            if self.measurePsf.usesMatches:
                matches = self.ref_match.loadAndMatch(
                    exposure=exposure, sourceCat=sourceCat).matches
            else:
                matches = None
            psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
            for i in range(psfIterations):
                measPsfRes = self.measurePsf.run(exposure=exposure,
                                                 sources=sourceCat,
                                                 matches=matches,
                                                 expId=exposureIdInfo.expId)

        # perform final repair with final PSF
        self.repair.run(exposure=exposure)

        if doApCorr and self.config.doApCorr:
            apCorrMap = self.measureApCorr.run(exposure=exposure,
                                               catalog=sourceCat).apCorrMap
            exposure.getInfo().setApCorrMap(apCorrMap)
            self.applyApCorr.run(catalog=sourceCat,
                                 apCorrMap=exposure.getInfo().getApCorrMap())

        if doCalc:
            self.catalogCalculation.run(sourceCat)

        return pipeBase.Struct(
            exposure=exposure,
            sourceCat=sourceCat,
            background=background,
            psfCellSet=measPsfRes.cellSet,
        )
Exemple #13
0
    def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background):
        """!Perform one iteration of detect, measure and estimate PSF

        Performs the following operations:
        - if config.doMeasurePsf or not exposure.hasPsf():
            - install a simple PSF model (replacing the existing one, if need be)
        - interpolate over cosmic rays with keepCRs=True
        - estimate background and subtract it from the exposure
        - detect, deblend and measure sources, and subtract a refined background model;
        - if config.doMeasurePsf:
            - measure PSF

        @param[in,out] exposure  exposure to characterize (an lsst.afw.image.ExposureF or similar)
            The following changes are made:
            - update or set psf
            - update detection and cosmic ray mask planes
            - subtract background
        @param[in] exposureIdInfo  ID info for exposure (an lsst.obs_base.ExposureIdInfo)
        @param[in,out] background  initial model of background already subtracted from exposure
            (an lsst.afw.math.BackgroundList).

        @return pipe_base Struct containing these fields, all from the final iteration
        of detect sources, measure sources and estimate PSF:
        - exposure  characterized exposure; image is repaired by interpolating over cosmic rays,
            mask is updated accordingly, and the PSF model is set
        - sourceCat  detected sources (an lsst.afw.table.SourceCatalog)
        - background  model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
        - psfCellSet  spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
        """
        # install a simple PSF model, if needed or wanted
        if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf):
            self.log.warn("Source catalog detected and measured with placeholder or default PSF")
            self.installSimplePsf.run(exposure=exposure)

        # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model)
        self.repair.run(exposure=exposure, keepCRs=True)
        self.display("repair_iter", exposure=exposure)

        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.doDeblend:
            self.deblend.run(exposure=exposure, sources=sourceCat)

        self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)

        measPsfRes = pipeBase.Struct(cellSet=None)
        if self.config.doMeasurePsf:
            if self.measurePsf.usesMatches:
                matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
            else:
                matches = None
            measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
                                             expId=exposureIdInfo.expId)
        self.display("measure_iter", exposure=exposure, sourceCat=sourceCat)

        return pipeBase.Struct(
            exposure=exposure,
            sourceCat=sourceCat,
            background=background,
            psfCellSet=measPsfRes.cellSet,
        )
    def calculateThreshold(self, exposure, seed, sigma=None):
        """Calculate new threshold

        This is the main functional addition to the vanilla
        `SourceDetectionTask`.

        We identify sky objects and perform forced PSF photometry on
        them. Using those PSF flux measurements and estimated errors,
        we set the threshold so that the stdev of the measurements
        matches the median estimated error.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            Exposure on which we're detecting sources.
        seed : `int`
            RNG seed to use for finding sky objects.
        sigma : `float`, optional
            Gaussian sigma of smoothing kernel; if not provided,
            will be deduced from the exposure's PSF.

        Returns
        -------
        result : `lsst.pipe.base.Struct`
            Result struct with components:

            - ``multiplicative``: multiplicative factor to be applied to the
                configured detection threshold (`float`).
            - ``additive``: additive factor to be applied to the background
                level (`float`).
        """
        # Make a catalog of sky objects
        fp = self.skyObjects.run(exposure.maskedImage.mask, seed)
        skyFootprints = FootprintSet(exposure.getBBox())
        skyFootprints.setFootprints(fp)
        table = SourceTable.make(self.skyMeasurement.schema)
        catalog = SourceCatalog(table)
        catalog.reserve(len(skyFootprints.getFootprints()))
        skyFootprints.makeSources(catalog)
        key = catalog.getCentroidKey()
        for source in catalog:
            peaks = source.getFootprint().getPeaks()
            assert len(peaks) == 1
            source.set(key, peaks[0].getF())
            source.updateCoord(exposure.getWcs())

        # Forced photometry on sky objects
        self.skyMeasurement.run(catalog, exposure, catalog, exposure.getWcs())

        # Calculate new threshold
        fluxes = catalog["base_PsfFlux_instFlux"]
        area = catalog["base_PsfFlux_area"]
        bg = catalog["base_LocalBackground_instFlux"]

        good = (~catalog["base_PsfFlux_flag"] & ~catalog["base_LocalBackground_flag"] &
                np.isfinite(fluxes) & np.isfinite(area) & np.isfinite(bg))

        if good.sum() < self.config.minNumSources:
            self.log.warn("Insufficient good flux measurements (%d < %d) for dynamic threshold calculation",
                          good.sum(), self.config.minNumSources)
            return Struct(multiplicative=1.0, additive=0.0)

        bgMedian = np.median((fluxes/area)[good])

        lq, uq = np.percentile((fluxes - bg*area)[good], [25.0, 75.0])
        stdevMeas = 0.741*(uq - lq)
        medianError = np.median(catalog["base_PsfFlux_instFluxErr"][good])
        return Struct(multiplicative=medianError/stdevMeas, additive=bgMedian)
Exemple #15
0
    def run(self,
            dataRef,
            exposure=None,
            background=None,
            doUnpersist=True,
            doPsf=True,
            doApCorr=True,
            doWrite=True,
            doCalc=True):

        self.log.info("gotoCharTask Processing %s" % (dataRef.dataId))

        if doUnpersist:
            if exposure is not None or background is not None:
                raise RuntimeError(
                    "doUnpersist true; exposure and background must be None")
            exposure = dataRef.get("postISRCCD", immediate=True)
        elif exposure is None:
            raise RuntimeError("doUnpersist false; exposure must be provided")

        exposureIdInfo = dataRef.get("expIdInfo")

        if not exposure.hasPsf():
            self.log.warn("Using SimplePsf for astrometry source detection")
            self.installSimplePsf.run(exposure=exposure)

        #Repair cosmic rays
        self.repair.run(exposure=exposure, keepCRs=True)

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

        #Table schema needs to be set up prior to detection:
        sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId,
                                               exposureIdInfo.unusedBits)
        table = SourceTable.make(self.schema, sourceIdFactory)
        table.setMetadata(self.algMetadata)

        #Perform detection
        sourceCat = self.detection.run(table=table,
                                       exposure=exposure,
                                       doSmooth=True).sources

        #Perform measurement
        self.measurement.run(measCat=sourceCat,
                             exposure=exposure,
                             exposureId=exposureIdInfo.expId)

        if self.config.doEarlyAstrometry:
            astromRes = self.astrometry.run(exposure=exposure,
                                            sourceCat=sourceCat)

        measPsfRes = pipeBase.Struct(cellSet=None)
        if doPsf and self.config.doMeasurePsf:
            if self.measurePsf.usesMatches:
                matches = self.ref_match.loadAndMatch(
                    exposure=exposure, sourceCat=sourceCat).matches
            else:
                matches = None
            psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
            for i in range(psfIterations):
                measPsfRes = self.measurePsf.run(exposure=exposure,
                                                 sources=sourceCat,
                                                 matches=matches,
                                                 expId=exposureIdInfo.expId)

        # perform final repair with final PSF
        self.repair.run(exposure=exposure)

        if doApCorr and self.config.doApCorr:
            apCorrMap = self.measureApCorr.run(exposure=exposure,
                                               catalog=sourceCat).apCorrMap
            exposure.getInfo().setApCorrMap(apCorrMap)
            self.applyApCorr.run(catalog=sourceCat,
                                 apCorrMap=exposure.getInfo().getApCorrMap())

        if doCalc:
            self.catalogCalculation.run(sourceCat)

        if doWrite and self.config.doWrite:
            dataRef.put(sourceCat, "icSrc")
            if self.config.doWriteExposure:
                dataRef.put(exposure, "icExp")
                dataRef.put(background, "icExpBackground")

        return pipeBase.Struct(
            exposure=exposure,
            sourceCat=sourceCat,
            background=background,
            psfCellSet=measPsfRes.cellSet,
        )