Пример #1
0
    def cosmicRay(self, exposure, keepCRs=None):
        """Mask cosmic rays

        @param[in,out] exposure Exposure to process
        @param keepCRs  Don't interpolate over the CR pixels (defer to pex_config if None)
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayCR = lsstDebug.Info(__name__).displayCR

        assert exposure, "No exposure provided"
        psf = exposure.getPsf()
        assert psf, "No psf provided"

        # Blow away old mask
        try:
            mask = exposure.getMaskedImage().getMask()
            crBit = mask.getMaskPlane("CR")
            mask.clearMaskPlane(crBit)
        except Exception:
            pass

        exposure0 = exposure            # initial value of exposure
        binSize = self.config.cosmicray.background.binSize
        nx, ny = exposure.getWidth()/binSize, exposure.getHeight()/binSize
        if nx*ny <= 1:
            bg = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.MEDIAN).getValue()
            bkgd = None
        else:
            exposure = exposure.Factory(exposure, True)
            bkgd, exposure = measAlg.estimateBackground(exposure, self.config.cosmicray.background,
                                                      subtract=True)
            bg = 0.0

        if keepCRs is None:
            keepCRs = self.config.cosmicray.keepCRs
        try:
            crs = measAlg.findCosmicRays(exposure.getMaskedImage(),
                                         psf, bg, pexConfig.makePolicy(self.config.cosmicray), keepCRs)
            if bkgd:
                # Add back background image
                img = exposure.getMaskedImage().getImage()
                img += bkgd.getImageF()
                del img
                # Replace original image with CR subtracted image
                mimg = exposure0.getMaskedImage()
                mimg <<= exposure.getMaskedImage()
                del mimg
        except Exception, e:
            if display:
                import lsst.afw.display.ds9 as ds9
                ds9.mtv(exposure0, title="Failed CR")
            raise
Пример #2
0
    def process(self, dataRef):
        """Process focus CCD in preparation for focus measurement

        @param dataRef: Data reference for CCD
        @return Struct(sources: source measurements,
                       ccdId: CCD number,
                       filterName: name of filter,
                       dims: exposure dimensions
                       )
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display

        exp = self.isr.runDataRef(dataRef).exposure

        if display:
            import lsst.afw.display.ds9 as ds9
            ds9.mtv(exp, title="Post-ISR", frame=1)

        self.installPsf.run(exposure=exp)
        bg, exp = measAlg.estimateBackground(exp, self.config.background, subtract=True)

        if display:
            ds9.mtv(exp, title="Post-background", frame=2)

        dmResults = self.detectAndMeasure.run(exp, dataRef.get("expIdInfo"))
        sources = dmResults.sourceCat

        self.starSelector.run(exp, sources, isStarField="hscPipeline_focus_candidate")

        if display:
            ds9.mtv(exp, title="Post-measurement", frame=3)
            with ds9.Buffering():
                for s in sources:
                    ds9.dot("o", s.getX(), s.getY(), frame=3,
                            ctype=ds9.GREEN if s.get("calib.psf.candidate") else ds9.RED)
            import pdb;pdb.set_trace() # pause to allow inspection

        filterName = exp.getFilter().getName()

        if self.config.doWrite:
            dataRef.put(sources, "src")
            dataRef.put(exp, "visitim")

        return Struct(sources=sources, ccdId=dataRef.dataId["ccd"], filterName=filterName,
                      dims=exp.getDimensions())
Пример #3
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)
Пример #5
0
    def characterize(self, exposure, exposureIdInfo, background=None):
        """!Characterize a science image

        Peforms the following operations:
        - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
            - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
        - interpolate over cosmic rays
        - perform final measurement

        @param[in,out] exposure  exposure to characterize (an lsst.afw.image.ExposureF or similar).
            The following changes are made:
            - update or set psf
            - set apCorrMap
            - update detection and cosmic ray mask planes
            - subtract background and interpolate over cosmic rays
        @param[in] exposureIdInfo  ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
        @param[in] background  model of background model already subtracted from exposure
            (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
            which is typical for image characterization.

        @return pipe_base Struct containing these fields, all from the final iteration
        of detectMeasureAndEstimatePsf:
        - 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)
        """
        self._frame = self._initialFrame # reset debug display frame

        if not self.config.doMeasurePsf and not exposure.hasPsf():
            raise RuntimeError("exposure has no PSF model and config.doMeasurePsf false")

        if background is None:
            background = BackgroundList()

        # make a deep copy of the mask
        originalMask = exposure.getMaskedImage().getMask().clone()

        # subtract an initial estimate of background level
        estBg = estimateBackground(
            exposure = exposure,
            backgroundConfig = self.config.background,
            subtract = False, # this makes a deep copy, which we don't want
        )[0]
        image = exposure.getMaskedImage().getImage()
        image -= estBg.getImageF()
        background.append(estBg)

        psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
        for i in range(psfIterations):
            if i > 1:
                # restore original mask so that detections and cosmic rays
                # are only marked by the final iteration
                exposure.getMaskedImage().getMask()[:] = originalMask

            dmeRes = self.detectMeasureAndEstimatePsf(
                exposure = exposure,
                exposureIdInfo = exposureIdInfo,
                background = background,
            )

            psf = dmeRes.exposure.getPsf()
            psfSigma = psf.computeShape().getDeterminantRadius()
            psfDimensions = psf.computeImage().getDimensions()
            medBackground = np.median(dmeRes.background.getImage().getArray())
            self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" % \
                (i + 1, psfSigma, psfDimensions, medBackground))

        self.display("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)

        # perform final repair with final PSF
        self.repair.run(exposure=dmeRes.exposure)
        self.display("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)

        # perform final measurement with final PSF, including measuring and applying aperture correction,
        # if wanted
        self.detectAndMeasure.measure(
            exposure = dmeRes.exposure,
            exposureIdInfo = exposureIdInfo,
            sourceCat = dmeRes.sourceCat,
            allowApCorr = True,  # the default; listed for emphasis
        )
        self.display("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)

        return dmeRes
Пример #6
0
    def run(self, exposure, defects=None, idFactory=None):
        """!Run the calibration task on an exposure

        \param[in,out]  exposure   Exposure to calibrate; measured PSF will be installed there as well
        \param[in]      defects    List of defects on exposure
        \param[in]      idFactory  afw.table.IdFactory to use for source catalog.
        \return a pipeBase.Struct with fields:
        - exposure: Repaired exposure
        - backgrounds: A list of background models applied in the calibration phase
        - psf: Point spread function
        - sources: Sources used in calibration
        - matches: A list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
        - matchMeta: Metadata about the field (an lsst.daf.base.PropertyList)
        - photocal: Output of photocal subtask

        It is moderately important to provide a decent initial guess for the seeing if you want to
        deal with cosmic rays.  If there's a PSF in the exposure it'll be used; failing that the
        CalibrateConfig.initialPsf is consulted (although the pixel scale will be taken from the
        WCS if available).

        If the exposure contains an lsst.afw.image.Calib object with the exposure time set, MAGZERO
        will be set in the task metadata.
        """
        assert exposure is not None, "No exposure provided"

        if not exposure.hasPsf():
            self.installInitialPsf(exposure)
        if idFactory is None:
            idFactory = afwTable.IdFactory.makeSimple()
        backgrounds = afwMath.BackgroundList()
        keepCRs = True                  # At least until we know the PSF
        self.repair.run(exposure, defects=defects, keepCRs=keepCRs)
        self.display('repair', exposure=exposure)

        if self.config.doBackground:
            with self.timer("background"):
                bg, exposure = measAlg.estimateBackground(exposure, self.config.background, subtract=True)
                backgrounds.append(bg)
            self.display('background', exposure=exposure)

        # Make both tables from the same detRet, since detection can only be run once
        table1 = afwTable.SourceTable.make(self.schema1, idFactory)
        table1.setMetadata(self.algMetadata)
        detRet = self.detection.makeSourceCatalog(table1, exposure)
        sources1 = detRet.sources
        if detRet.fpSets.background:
            backgrounds.append(detRet.fpSets.background)

        # do the initial measurement.  This is normally done for star selection, but do it 
        # even if the psf is not going to be calculated for consistency
        self.initialMeasurement.run(exposure, sources1, allowApCorr=False)

        if self.config.doPsf:
            matches = None
            if self.config.doAstrometry:
                # If doAstrometry is False, we force the Star Selector to either make them itself
                # or hope it doesn't need them.
                origWcs = exposure.getWcs()
                try:
                    astromRet = self.astrometry.run(exposure, sources1)
                    matches = astromRet.matches
                except RuntimeError as e:
                    if self.config.requireAstrometry:
                        raise
                    self.log.warn("Unable to perform astrometry (%s): attempting to proceed" % e)
                finally:
                    # Restore original Wcs: we're going to repeat the astrometry later, and if it succeeded
                    # this time, running it again with the same basic setup means it should succeed again.
                    exposure.setWcs(origWcs)
            psfRet = self.measurePsf.run(exposure, sources1, matches=matches)
            psf = psfRet.psf
        elif exposure.hasPsf():
            psf = exposure.getPsf()
        else:
            psf = None

        # Wash, rinse, repeat with proper PSF

        if self.config.doPsf:
            self.repair.run(exposure, defects=defects, keepCRs=None)
            self.display('PSF_repair', exposure=exposure)

        if self.config.doBackground:
            # Background estimation ignores (by default) pixels with the
            # DETECTED bit set, so now we re-estimate the background,
            # ignoring sources.  (see BackgroundConfig.ignoredPixelMask)
            with self.timer("background"):
                # Subtract background
                bg, exposure = measAlg.estimateBackground(
                    exposure, self.config.background, subtract=True,
                    statsKeys=('BGMEAN2', 'BGVAR2'))
                self.log.info("Fit and subtracted background")
                backgrounds.append(bg)

            self.display('PSF_background', exposure=exposure)

        # make a second table with which to do the second measurement
        # the schemaMapper will copy the footprints and ids, which is all we need.
        table2 = afwTable.SourceTable.make(self.schema, idFactory)
        table2.setMetadata(self.algMetadata)
        sources = afwTable.SourceCatalog(table2)
        # transfer to a second table -- note that the slots do not have to be reset here
        # as long as measurement.run follows immediately
        sources.extend(sources1, self.schemaMapper)

        if self.config.doMeasureApCorr:
            # Run measurement through all flux measurements (all have the same execution order),
            # then apply aperture corrections, then run the rest of the measurements
            self.measurement.run(exposure, sources, endOrder=BasePlugin.APCORR_ORDER)
            apCorrMap = self.measureApCorr.run(bbox=exposure.getBBox(), catalog=sources).apCorrMap
            exposure.getInfo().setApCorrMap(apCorrMap)
            self.measurement.run(exposure, sources, beginOrder=BasePlugin.APCORR_ORDER)
        else:
            self.measurement.run(exposure, sources)

        matches, matchMeta = None, None
        if self.config.doAstrometry:
            try:
                astromRet = self.astrometry.run(exposure, sources)
                matches = astromRet.matches
                matchMeta = astromRet.matchMeta
            except RuntimeError as e:
                if self.config.requireAstrometry:
                    raise
                self.log.warn("Unable to perform astrometry (%s): attempting to proceed" % e)

        if self.config.doPhotoCal:
            try:
                if not matches:
                    raise RuntimeError("No matches available")
                photocalRet = self.photocal.run(exposure, matches)
            except Exception, e:
                if self.config.requirePhotoCal:
                    raise
                self.log.warn("Failed to determine photometric zero-point: %s" % e)
                photocalRet = None
                self.metadata.set('MAGZERO', float("NaN"))

            if photocalRet:
                self.log.info("Photometric zero-point: %f" % photocalRet.calib.getMagnitude(1.0))
                exposure.getCalib().setFluxMag0(photocalRet.calib.getFluxMag0())
                metadata = exposure.getMetadata()
                # convert to (mag/sec/adu) for metadata
                try:
                    magZero = photocalRet.zp - 2.5 * math.log10(exposure.getCalib().getExptime() )
                    metadata.set('MAGZERO', magZero)
                except:
                    self.log.warn("Could not set normalized MAGZERO in header: no exposure time")
                metadata.set('MAGZERO_RMS', photocalRet.sigma)
                metadata.set('MAGZERO_NOBJ', photocalRet.ngood)
                metadata.set('COLORTERM1', 0.0)
                metadata.set('COLORTERM2', 0.0)
                metadata.set('COLORTERM3', 0.0)
Пример #7
0
def doit(args):
    dataId = args
    print "# Running", dataId
 
    # I need to create a separate instance in each thread
    mapper = Mapper(root = "/lsst7/stripe82/dr7/runs/", calibRoot = None, outputRoot = None)
    butler = dafPersist.ButlerFactory(mapper = mapper).create()

    # Grab science pixels
    im     = butler.get(datasetType="fpC", dataId = dataId).convertF()

    # Remove the 128 pixel duplicate overlap between fields
    # See python/lsst/obs/sdss/processCcdSdss.py for guidance
    bbox    = im.getBBox()
    begin   = bbox.getBegin()
    extent  = bbox.getDimensions()
    extent -= afwGeom.Extent2I(0, 128)
    tbbox   = afwGeom.BoxI(begin, extent)
    im      = afwImage.ImageF(im, tbbox, True)

    # Remove 1000 count pedestal
    im    -= 1000.0 

    # Create image variance from gain
    calib, gain = butler.get(datasetType="tsField", dataId = dataId)
    var    = afwImage.ImageF(im, True)
    var   /= gain

    # Note I need to do a bit extra for the mask; I actually need to call
    # convertfpM with allPlanes = True to get all the SDSS info
    #
    # mask   = butler.get(datasetType="fpM", dataId = dataId)
    fpMFile = butler.mapper.map_fpM(dataId = dataId).getLocations()[0]
    mask    = convertfpM(fpMFile, True)
    # Remove the 128 pixel duplicate overlap...
    mask    = afwImage.MaskU(mask, tbbox, True)

    # We need this for the background estimation
    exp = afwImage.ExposureF(afwImage.MaskedImageF(im, mask, var))

    # Subtract off background, and scale by stdev
    # This will turn the image into "sigma"
    if False:
        ctrl      = afwMath.StatisticsControl(5.0, 5)
        bitPlanes = mask.getMaskPlaneDict().values()
        bitMask   = 2**bitPlanes[0]
        for plane in bitPlanes[1:]:
            bitMask |= 2**bitPlanes[plane]
        ctrl.setAndMask(bitMask)
        stat    = afwMath.makeStatistics(im, mask, afwMath.STDEVCLIP | afwMath.MEDIAN | afwMath.NPOINT, ctrl)
        stdev   = stat.getValue(afwMath.STDEVCLIP)
        bg      = stat.getValue(afwMath.MEDIAN)
    elif False:
        # It should be that afwMath.NPOINT = len(idx[0])
        # Not the case, exactly, so go with what you know
        idx     = np.where(mask.getArray() == 0)
        gdata   = im.getArray()[idx] 
        bg      = np.median(gdata)
        stdev   = 0.741 * (np.percentile(gdata, 75) - np.percentile(gdata, 25))
    else:
        # Do full-on background subtraction
        bgctrl = measAlg.BackgroundConfig(binSize=512, statisticsProperty="MEANCLIP", ignoredPixelMask=mask.getMaskPlaneDict().keys())
        bg, bgsubexp = measAlg.estimateBackground(exp, bgctrl, subtract=True)
        im = bgsubexp.getMaskedImage().getImage()
        sctrl = afwMath.StatisticsControl()
        sctrl.setAndMask(reduce(lambda x, y, mask=mask: x | mask.getPlaneBitMask(y), bgctrl.ignoredPixelMask, 0x0))
        stdev = afwMath.makeStatistics(im, mask, afwMath.STDEVCLIP, sctrl).getValue(afwMath.STDEVCLIP)
        im /= stdev

    # Decision point: do I send the convolution a MaskedImage, in which
    # case the mask is also spread, or just an Image, and not spread
    # the mask...  
    # 
    # I think for now I will not spread the mask so that it represents the
    # condition of the underlying pixels, not the Psf-filtered ones

    psf    = butler.get(datasetType="psField", dataId = dataId)
    wcs    = butler.get(datasetType="asTrans", dataId = dataId)

    # Image convolved with the Psf, i.e. maximum point source likelihood image
    cim    = afwImage.ImageF(im, True)
    afwMath.convolve(cim, im, psf.getKernel(), True)
    # The pixels that are "good" in the image, i.e. ignore borders
    cBBox  = psf.getKernel().shrinkBBox(cim.getBBox())
    cim    = afwImage.ImageF(cim, cBBox)
    mask   = afwImage.MaskU(mask, cBBox)
 
    # Create an ra,decl map for the good pixels
    raIm   = afwImage.ImageF(cim.getDimensions())
    decIm  = afwImage.ImageF(cim.getDimensions())
    nx, ny = cim.getDimensions()
    # But note that the Wcs expects their coordinates in the non-shrunk image
    x0     = cBBox.getBeginX()
    y0     = cBBox.getBeginY()
    x1     = cBBox.getEndX()
    y1     = cBBox.getEndY()
    for y in range(ny):
        for x in range(nx):
            ra, decl = getRaDecl(wcs, x+x0, y+y0)
            raIm.set(x, y, ra)
            decIm.set(x, y, decl) 

    run = dataId["run"]
    camcol = dataId["camcol"]
    field = dataId["field"]
    filterName = dataId["filter"]
    if doWriteSql:
        # Make the table inputs
        xll, yll = getRaDecl(wcs, 0 +x0, 0+ y0)
        xlr, ylr = getRaDecl(wcs, x1+x0, 0+ y0)
        xur, yur = getRaDecl(wcs, x1+x0, y1+y0)
        xul, yul = getRaDecl(wcs, 0 +x0, y1+y0)
        tc       = calib.getMidTime()
        t0       = dafBase.DateTime(tc.nsecs() - int(0.5 * calib.getExptime() * 1e9), dafBase.DateTime.TAI)
        t1       = dafBase.DateTime(tc.nsecs() + int(0.5 * calib.getExptime() * 1e9), dafBase.DateTime.TAI)

        # Magic for the day; 2**63 because BIGINT is signed
        fieldId  = int(md5.new(" ".join(map(str, [run, filterName, camcol, field]))).hexdigest(), 16) % 2**63

        pfile = "pixel-%06d-%s%s-%04d.csv" % (run, filterName, camcol, field)
        ffile = "field-%06d-%s%s-%04d.pgsql" % (run, filterName, camcol, field)
        pbuff = open(pfile, "w")
        fbuff = open(ffile, "w")
        fbuff.write("INSERT INTO fields (fieldId, run, camcol, field, filter, bbox, tmid, trange) VALUES\n")
        fbuff.write("  (%d, %d, %d, %d, '%s', ST_GeomFromText('POLYGON((\n" % (fieldId, run, camcol, field, filterName))
        fbuff.write("        %.9f %.9f, %.9f %.9f,\n" % (xll, yll, xlr, ylr))
        fbuff.write("        %.9f %.9f, %.9f %.9f,\n" % (xur, yur, xul, yul))
        fbuff.write("        %.9f %.9f))',3786),\n" % (xll, yll))
        fbuff.write("         '%s',\n" % (re.sub("T", " ", tc.toString())))
        fbuff.write("         '[%s, %s]');\n" % (re.sub("T", " ", t0.toString()), re.sub("T", " ", t1.toString())))

        for y in range(ny):
            for x in range(nx):
                # Note the different orders of raIm,decIm and cim,mask
                pbuff.write("%d, %.9f, %.9f, %f, %d\n" % (fieldId, raIm.get(x,y), decIm.get(x,y), cim.get(x,y), mask.get(x,y)))
        pbuff.close()
        fbuff.close()

    if False:
        cim.writeFits("image-%06d-%s%s-%04d.fits" % (run, filterName, camcol, field))
        mask.writeFits("mask-%06d-%s%s-%04d.fits" % (run, filterName, camcol, field))
        raIm.writeFits("ra-%06d-%s%s-%04d.fits" % (run, filterName, camcol, field))
        decIm.writeFits("dec-%06d-%s%s-%04d.fits" % (run, filterName, camcol, field))
    def characterize(self, exposure, exposureIdInfo, background=None):
        """!Characterize a science image

        Peforms the following operations:
        - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
            - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
        - interpolate over cosmic rays
        - perform final measurement

        @param[in,out] exposure  exposure to characterize (an lsst.afw.image.ExposureF or similar).
            The following changes are made:
            - update or set psf
            - set apCorrMap
            - update detection and cosmic ray mask planes
            - subtract background and interpolate over cosmic rays
        @param[in] exposureIdInfo  ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
        @param[in] background  model of background model already subtracted from exposure
            (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
            which is typical for image characterization.

        @return pipe_base Struct containing these fields, all from the final iteration
        of detectMeasureAndEstimatePsf:
        - 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)
        """
        self._frame = self._initialFrame  # reset debug display frame

        if not self.config.doMeasurePsf and not exposure.hasPsf():
            raise RuntimeError(
                "exposure has no PSF model and config.doMeasurePsf false")

        if background is None:
            background = BackgroundList()

        # make a deep copy of the mask
        originalMask = exposure.getMaskedImage().getMask().clone()

        # subtract an initial estimate of background level
        estBg = estimateBackground(
            exposure=exposure,
            backgroundConfig=self.config.background,
            subtract=False,  # this makes a deep copy, which we don't want
        )[0]
        image = exposure.getMaskedImage().getImage()
        image -= estBg.getImageF()
        background.append(estBg)

        psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
        for i in range(psfIterations):
            if i > 1:
                # restore original mask so that detections and cosmic rays
                # are only marked by the final iteration
                exposure.getMaskedImage().getMask()[:] = originalMask

            dmeRes = self.detectMeasureAndEstimatePsf(
                exposure=exposure,
                exposureIdInfo=exposureIdInfo,
                background=background,
            )

            psf = dmeRes.exposure.getPsf()
            psfSigma = psf.computeShape().getDeterminantRadius()
            psfDimensions = psf.computeImage().getDimensions()
            medBackground = np.median(dmeRes.background.getImage().getArray())
            self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" % \
                (i + 1, psfSigma, psfDimensions, medBackground))

        self.display("psf",
                     exposure=dmeRes.exposure,
                     sourceCat=dmeRes.sourceCat)

        # perform final repair with final PSF
        self.repair.run(exposure=dmeRes.exposure)
        self.display("repair",
                     exposure=dmeRes.exposure,
                     sourceCat=dmeRes.sourceCat)

        # perform final measurement with final PSF, including measuring and applying aperture correction,
        # if wanted
        self.detectAndMeasure.measure(
            exposure=dmeRes.exposure,
            exposureIdInfo=exposureIdInfo,
            sourceCat=dmeRes.sourceCat,
            allowApCorr=True,  # the default; listed for emphasis
        )
        self.display("measure",
                     exposure=dmeRes.exposure,
                     sourceCat=dmeRes.sourceCat)

        return dmeRes
Пример #9
0
    def run(self, exposure, defects=None, idFactory=None, expId=0):
        """Calibrate an exposure: measure PSF, subtract background, measure astrometry and photometry

        @param[in,out]  exposure   Exposure to calibrate; measured Psf, Wcs, ApCorr, Calib, etc. will
                                   be installed there as well
        @param[in]      defects    List of defects on exposure
        @param[in]      idFactory  afw.table.IdFactory to use for source catalog.
        @param[in]      expId      Exposure id used for random number generation.
        @return a pipeBase.Struct with fields:
        - backgrounds: A list of background models applied in the calibration phase
        - psf: Point spread function
        - sources: Sources used in calibration
        - matches: Astrometric matches
        - matchMeta: Metadata for astrometric matches
        - apCorrMap: Map of aperture corrections
        - photocal: Output of photocal subtask
        """
        assert exposure is not None, "No exposure provided"

        if not exposure.hasPsf():
            self.installInitialPsf(exposure)
        if idFactory is None:
            idFactory = afwTable.IdFactory.makeSimple()
        backgrounds = afwMath.BackgroundList()
        keepCRs = True                  # At least until we know the PSF
        self.repair.run(exposure, defects=defects, keepCRs=keepCRs)
        self.display('repair', exposure=exposure)
        if self.config.doBackground:
            with self.timer("background"):
                bg, exposure = measAlg.estimateBackground(exposure, self.config.background, subtract=True)
                backgrounds.append(bg)

            self.display('background', exposure=exposure)
        table = afwTable.SourceTable.make(self.schema, idFactory)
        table.setMetadata(self.algMetadata)
        detRet = self.detection.makeSourceCatalog(table, exposure)
        sources = detRet.sources
        if detRet.fpSets.background:
            backgrounds.append(detRet.fpSets.background)

        if self.config.doPsf:
            self.initialMeasurement.measure(exposure, sources)

            matches = None
            if self.config.doAstrometry:
                # If doAstrometry is False, we force the Star Selector to either make them itself
                # or hope it doesn't need them.
                origWcs = exposure.getWcs()
                try:
                    astromRet = self.astrometry.run(exposure, sources)
                    matches = astromRet.matches
                except RuntimeError as e:
                    if self.config.requireAstrometry:
                        raise
                    self.log.warn("Unable to perform astrometry (%s): attempting to proceed" % e)
                finally:
                    # Restore original Wcs: we're going to repeat the astrometry later, and if it succeeded
                    # this time, running it again with the same basic setup means it should succeed again.
                    exposure.setWcs(origWcs)

            # This is an initial, throw-away run of photocal, since we need a valid Calib to run CModel,
            # and we need to run CModel to compute aperture corrections from it.
            if self.config.doPhotoCal:
                try:
                    if not matches:
                        raise RuntimeError("No matches available")
                    photocalRet = self.photocal.run(exposure, matches,
                                                    prefix=self.config.initialMeasurement.prefix,
                                                    doSelectUnresolved=False)# don't trust s/g without good PSF
                    self.log.info("Initial photometric zero-point: %f" % photocalRet.calib.getMagnitude(1.0))
                    exposure.getCalib().setFluxMag0(photocalRet.calib.getFluxMag0())
                except Exception, e:
                    self.log.warn("Failed to determine initial photometric zero-point: %s" % e)

            psfRet = self.measurePsf.run(exposure, sources, expId=expId, matches=matches)
            psf = psfRet.psf
Пример #10
0
            psf = None

        # Wash, rinse, repeat with proper PSF

        if self.config.doPsf:
            self.repair.run(exposure, defects=defects, keepCRs=None)
            self.display('repair', exposure=exposure)

        if self.config.doBackground:
            # Background estimation ignores (by default) pixels with the
            # DETECTED bit set, so now we re-estimate the background,
            # ignoring sources.  (see BackgroundConfig.ignoredPixelMask)
            with self.timer("background"):
                # Subtract background
                bg, exposure = measAlg.estimateBackground(
                    exposure, self.config.background, subtract=True,
                    statsKeys=('BGMEAN2', 'BGVAR2'))
                self.log.info("Fit and subtracted background")
                backgrounds.append(bg)

            self.display('background', exposure=exposure)

        if self.config.doMeasureApCorr:
            # Because we want to both compute the aperture corrections and apply them - and we do the latter
            # as a source measurement plugin ("CorrectFluxes"), we have to sandwich the aperture correction
            # measurement in between two source measurement passes, using the priority range arguments added
            # just for this purpose.
            apCorrApplyPriority = self.config.measurement.algorithms["correctfluxes"].priority
            self.measurement.run(exposure, sources, endPriority=apCorrApplyPriority)
            if self.config.doCurveOfGrowth:
                curveOfGrowth = self.applyCurveOfGrowth(sources)
Пример #11
0
    def run(self, exposure, defects=None, idFactory=None):
        """Calibrate an exposure: measure PSF, subtract background, measure astrometry and photometry

        @param[in,out]  exposure   Exposure to calibrate; measured PSF will be installed there as well
        @param[in]      defects    List of defects on exposure
        @param[in]      idFactory  afw.table.IdFactory to use for source catalog.
        @return a pipeBase.Struct with fields:
        - backgrounds: A list of background models applied in the calibration phase
        - psf: Point spread function
        - apCorr: Aperture correction
        - sources: Sources used in calibration
        - matches: Astrometric matches
        - matchMeta: Metadata for astrometric matches
        - photocal: Output of photocal subtask
        """
        assert exposure is not None, "No exposure provided"

        self.installInitialPsf(exposure)
        if idFactory is None:
            idFactory = afwTable.IdFactory.makeSimple()
        backgrounds = []
        keepCRs = True                  # At least until we know the PSF
        self.repair.run(exposure, defects=defects, keepCRs=keepCRs)
        self.display('repair', exposure=exposure)
        if self.config.doBackground:
            with self.timer("background"):
                bg, exposure = measAlg.estimateBackground(exposure, self.config.background, subtract=True)
                backgrounds.append(bg)

            self.display('background', exposure=exposure)
        table = afwTable.SourceTable.make(self.schema, idFactory)
        table.setMetadata(self.algMetadata)
        detRet = self.detection.makeSourceCatalog(table, exposure)
        sources = detRet.sources
        if detRet.fpSets.background:
            backgrounds.append(detRet.fpSets.background)

        if self.config.doPsf:
            self.initialMeasurement.measure(exposure, sources)

            if self.config.doAstrometry:
                astromRet = self.astrometry.run(exposure, sources)
                matches = astromRet.matches
            else:
                # If doAstrometry is False, we force the Star Selector to either make them itself
                # or hope it doesn't need them.
                matches = None
            psfRet = self.measurePsf.run(exposure, sources, matches=matches)
            cellSet = psfRet.cellSet
            psf = psfRet.psf
        else:
            psf, cellSet = None, None

        # Wash, rinse, repeat with proper PSF

        if self.config.doPsf:
            self.repair.run(exposure, defects=defects, keepCRs=None)
            self.display('repair', exposure=exposure)

        if self.config.doBackground:
            # Background estimation ignores (by default) pixels with the
            # DETECTED bit set, so now we re-estimate the background,
            # ignoring sources.  (see BackgroundConfig.ignoredPixelMask)
            with self.timer("background"):
                # Subtract background
                bg, exposure = measAlg.estimateBackground(
                    exposure, self.config.background, subtract=True,
                    statsKeys=('BGMEAN2', 'BGVAR2'))
                self.log.info("Fit and subtracted background")
                backgrounds.append(bg)

            self.display('background', exposure=exposure)

        if self.config.doComputeApCorr or self.config.doAstrometry or self.config.doPhotoCal:
            self.measurement.measure(exposure, sources)   # don't use run, because we don't have apCorr yet

        if self.config.doComputeApCorr:
            assert(self.config.doPsf)
            apCorr = self.computeApCorr(exposure, cellSet)
        else:
            apCorr = None

        if self.measurement.config.doApplyApCorr:
            assert(apCorr is not None)
            self.measurement.applyApCorr(sources, apCorr)

        if self.config.doAstrometry:
            astromRet = self.astrometry.run(exposure, sources)
            matches = astromRet.matches
            matchMeta = astromRet.matchMeta
        else:
            matches, matchMeta = None, None

        if self.config.doPhotoCal:
            assert(matches is not None)
            try:
                photocalRet = self.photocal.run(exposure, matches)
            except Exception, e:
                self.log.warn("Failed to determine photometric zero-point: %s" % e)
                photocalRet = None
                
            if photocalRet:
                self.log.info("Photometric zero-point: %f" % photocalRet.calib.getMagnitude(1.0))
                exposure.getCalib().setFluxMag0(photocalRet.calib.getFluxMag0())
                metadata = exposure.getMetadata()
                # convert to (mag/sec/adu) for metadata
                try:
                    magZero = photocalRet.zp - 2.5 * math.log10(exposure.getCalib().getExptime() )
                    metadata.set('MAGZERO', magZero)
                except:
                    self.log.warn("Could not set normalized MAGZERO in header: no exposure time")
                metadata.set('MAGZERO_RMS', photocalRet.sigma)
                metadata.set('MAGZERO_NOBJ', photocalRet.ngood)
                metadata.set('COLORTERM1', 0.0)
                metadata.set('COLORTERM2', 0.0)
                metadata.set('COLORTERM3', 0.0)    
Пример #12
0
    def cosmicRay(self, exposure, keepCRs=None):
        """Mask cosmic rays

        \param[in,out] exposure Exposure to process
        \param[in]     keepCRs  Don't interpolate over the CR pixels (defer to pex_config if None)
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayCR = lsstDebug.Info(__name__).displayCR

        assert exposure, "No exposure provided"
        psf = exposure.getPsf()
        assert psf, "No psf provided"

        # Blow away old mask
        try:
            mask = exposure.getMaskedImage().getMask()
            crBit = mask.getMaskPlane("CR")
            mask.clearMaskPlane(crBit)
        except Exception:
            pass

        exposure0 = exposure            # initial value of exposure
        binSize = self.config.cosmicray.background.binSize
        nx, ny = exposure.getWidth()/binSize, exposure.getHeight()/binSize
        # Treat constant background as a special case to avoid the extra complexity in calling
        # measAlg.estimateBackground().
        if nx*ny <= 1:
            medianBg = afwMath.makeStatistics(exposure.getMaskedImage(), afwMath.MEDIAN).getValue()
            modelBg = None
        else:
            exposure = exposure.Factory(exposure, True)
            modelBg, exposure = measAlg.estimateBackground(exposure, self.config.cosmicray.background,
                                                           subtract=True)
            medianBg = 0.0

        if keepCRs is None:
            keepCRs = self.config.cosmicray.keepCRs
        try:
            crs = measAlg.findCosmicRays(exposure.getMaskedImage(), psf, medianBg,
                                         pexConfig.makePolicy(self.config.cosmicray), keepCRs)
            if modelBg:
                # Add back background image
                img = exposure.getMaskedImage()
                img += modelBg.getImageF()
                del img
                # Replace original image with CR subtracted image
                exposure0.setMaskedImage(exposure.getMaskedImage())

        except Exception:
            if display:
                import lsst.afw.display.ds9 as ds9
                ds9.mtv(exposure0, title="Failed CR")
            raise

        num = 0
        if crs is not None:
            mask = exposure0.getMaskedImage().getMask()
            crBit = mask.getPlaneBitMask("CR")
            afwDet.setMaskFromFootprintList(mask, crs, crBit)
            num = len(crs)

            if display and displayCR:
                import lsst.afw.display.ds9 as ds9
                import lsst.afw.display.utils as displayUtils

                ds9.incrDefaultFrame()
                ds9.mtv(exposure0, title="Post-CR")

                with ds9.Buffering():
                    for cr in crs:
                        displayUtils.drawBBox(cr.getBBox(), borderWidth=0.55)

        self.log.info("Identified %s cosmic rays." % (num,))
Пример #13
0
        def createLikeImage(self, data):
            print "# Converting", data
            
            # Convert from Pandas to Dictionary
            dataId = {}
            for key in self.ukeys:
                dataId[key] = data[key]
         
            # I need to create a separate butler instance in case of multi threading
            mapper = Mapper(root = "/lsst7/stripe82/dr7/runs/", calibRoot = None, outputRoot = None)
            butler = dafPersist.ButlerFactory(mapper = mapper).create()

            # Grab science pixels
            im     = butler.get(datasetType="fpC", dataId = dataId).convertF()

            # Remove the 128 pixel duplicate overlap between fields
            # See python/lsst/obs/sdss/processCcdSdss.py for guidance
            bbox    = im.getBBox()
            begin   = bbox.getBegin()
            extent  = bbox.getDimensions()
            extent -= afwGeom.Extent2I(0, 128)
            tbbox   = afwGeom.BoxI(begin, extent)
            im      = afwImage.ImageF(im, tbbox, True)
            nx0, ny0 = extent

            # Remove 1000 count pedestal
            im    -= 1000.0 

            # Create image variance from gain
            calib, gain = butler.get(datasetType="tsField", dataId = dataId)
            var    = afwImage.ImageF(im, True)
            var   /= gain

            # Note I need to do a bit extra for the mask; I actually need to call
            # convertfpM with allPlanes = True to get all the SDSS info
            fpMFile = butler.mapper.map_fpM(dataId = dataId).getLocations()[0]
            mask    = convertfpM(fpMFile, True)

            # Remove the 128 pixel duplicate overlap...
            mask    = afwImage.MaskU(mask, tbbox, True)

            # Convert to Exposure for the background estimation
            exp = afwImage.ExposureF(afwImage.MaskedImageF(im, mask, var))

            # Subtract off background, and scale by stdev.  
            # This will turn the image into a "sigma" image
            bgctrl = measAlg.BackgroundConfig(binSize=512, statisticsProperty="MEANCLIP", ignoredPixelMask=mask.getMaskPlaneDict().keys())
            bg, bgsubexp = measAlg.estimateBackground(exp, bgctrl, subtract=True)
            im = bgsubexp.getMaskedImage().getImage()
            sctrl = afwMath.StatisticsControl()
            sctrl.setAndMask(reduce(lambda x, y, mask=mask: x | mask.getPlaneBitMask(y), bgctrl.ignoredPixelMask, 0x0))
            stdev = afwMath.makeStatistics(im, mask, afwMath.STDEVCLIP, sctrl).getValue(afwMath.STDEVCLIP)
            im /= stdev

            # Grab the Psf for filtering, and the Wcs for inclusion in final Exposure
            psf    = butler.get(datasetType="psField", dataId = dataId)
            wcs    = butler.get(datasetType="asTrans", dataId = dataId)

            # Decision point: do I send the convolution a MaskedImage, in which
            # case the mask is also spread, or just an Image, and not spread
            # the mask...  
            # 
            # I think for now I will not spread the mask so that it represents the
            # condition of the underlying pixels, not the Psf-filtered ones
            #
            # Create sigma image convolved with the Psf, i.e. maximum point source likelihood image
            cim    = afwImage.ImageF(im, True)
            afwMath.convolve(cim, im, psf.getKernel(), True)

            # Note that the convolution will create border regions of "bad" pixels.
            # If we tried to trim these, we would have to tweak the Wcs CRPIX etc.  So just leave as-is.
            cexp   = afwImage.ExposureF(afwImage.MaskedImageF(cim, mask, var))
            cexp.setWcs(wcs)
            return cexp
Пример #14
0
    def run(self, exposure, defects=None, idFactory=None):
        """!Run the calibration task on an exposure

        \param[in,out]  exposure   Exposure to calibrate; measured PSF will be installed there as well
        \param[in]      defects    List of defects on exposure
        \param[in]      idFactory  afw.table.IdFactory to use for source catalog.
        \return a pipeBase.Struct with fields:
        - exposure: Repaired exposure
        - backgrounds: A list of background models applied in the calibration phase
        - psf: Point spread function
        - sources: Sources used in calibration
        - matches: Astrometric matches
        - matchMeta: Metadata for astrometric matches
        - photocal: Output of photocal subtask

        It is moderately important to provide a decent initial guess for the seeing if you want to
        deal with cosmic rays.  If there's a PSF in the exposure it'll be used; failing that the
        CalibrateConfig.initialPsf is consulted (although the pixel scale will be taken from the
        WCS if available).

        If the exposure contains an lsst.afw.image.Calib object with the exposure time set, MAGZERO
        will be set in the task metadata.
        """
        assert exposure is not None, "No exposure provided"

        if not exposure.hasPsf():
            self.installInitialPsf(exposure)
        if idFactory is None:
            idFactory = afwTable.IdFactory.makeSimple()
        backgrounds = afwMath.BackgroundList()
        keepCRs = True                  # At least until we know the PSF
        self.repair.run(exposure, defects=defects, keepCRs=keepCRs)
        self.display('repair', exposure=exposure)
        if self.config.doBackground:
            with self.timer("background"):
                bg, exposure = measAlg.estimateBackground(exposure, self.config.background, subtract=True)
                backgrounds.append(bg)
            self.display('background', exposure=exposure)

        # Make both tables from the same detRet, since detRet can only be run once
        table1 = afwTable.SourceTable.make(self.schema1, idFactory)
        table1.setMetadata(self.algMetadata)
        detRet = self.detection.makeSourceCatalog(table1, exposure)
        sources1 = detRet.sources


        if detRet.fpSets.background:
            backgrounds.append(detRet.fpSets.background)

        if self.config.doPsf:
            self.initialMeasurement.measure(exposure, sources1)

 # ### Do not compute astrometry before PSF determination. Astrometry will be computed afterwards
 # ###
 #           if self.config.doAstrometry:
 #               astromRet = self.astrometry.run(exposure, sources1)
 #               matches = astromRet.matches
 #           else:
                # If doAstrometry is False, we force the Star Selector to either make them itself
                # or hope it doesn't need them.
 #               matches = None
            matches = None
            psfRet = self.measurePsf.run(exposure, sources1, matches=matches)
            cellSet = psfRet.cellSet
            psf = psfRet.psf
        elif exposure.hasPsf():
            psf = exposure.getPsf()
            cellSet = None
        else:
            psf, cellSet = None, None

        # Wash, rinse, repeat with proper PSF

        if self.config.doPsf:
            self.repair.run(exposure, defects=defects, keepCRs=None)
            self.display('PSF_repair', exposure=exposure)

        if self.config.doBackground:
            # Background estimation ignores (by default) pixels with the
            # DETECTED bit set, so now we re-estimate the background,
            # ignoring sources.  (see BackgroundConfig.ignoredPixelMask)
            with self.timer("background"):
                # Subtract background
                bg, exposure = measAlg.estimateBackground(
                    exposure, self.config.background, subtract=True,
                    statsKeys=('BGMEAN2', 'BGVAR2'))
                self.log.info("Fit and subtracted background")
                backgrounds.append(bg)

            self.display('PSF_background', exposure=exposure)

        if self.config.doAstrometry or self.config.doPhotoCal:
            # make a second table with which to do the second measurement
            # the schemaMapper will copy the footprints and ids, which is all we need.
            table2 = afwTable.SourceTable.make(self.schema, idFactory)
            table2.setMetadata(self.algMetadata)
            sources = afwTable.SourceCatalog(table2)
            # transfer to a second table
            sources.extend(sources1, self.schemaMapper)
            self.measurement.run(exposure, sources)
        else:
            sources = sources1

        if self.config.doAstrometry:
            astromRet = self.astrometry.run(exposure, sources)
            matches = astromRet.matches
            matchMeta = astromRet.matchMeta
        else:
            matches, matchMeta = None, None

        if self.config.doPhotoCal:
            assert(matches is not None)
            try:
                photocalRet = self.photocal.run(exposure, matches)
            except Exception, e:
                raise
                self.log.warn("Failed to determine photometric zero-point: %s" % e)
                photocalRet = None
                self.metadata.set('MAGZERO', float("NaN"))

            if photocalRet:
                self.log.info("Photometric zero-point: %f" % photocalRet.calib.getMagnitude(1.0))
                exposure.getCalib().setFluxMag0(photocalRet.calib.getFluxMag0())
                metadata = exposure.getMetadata()
                # convert to (mag/sec/adu) for metadata
                try:
                    magZero = photocalRet.zp - 2.5 * math.log10(exposure.getCalib().getExptime() )
                    metadata.set('MAGZERO', magZero)
                except:
                    self.log.warn("Could not set normalized MAGZERO in header: no exposure time")
                metadata.set('MAGZERO_RMS', photocalRet.sigma)
                metadata.set('MAGZERO_NOBJ', photocalRet.ngood)
                metadata.set('COLORTERM1', 0.0)
                metadata.set('COLORTERM2', 0.0)
                metadata.set('COLORTERM3', 0.0)
Пример #15
0
def convert(dataId):
    print "# Converting", dataId
 
    # I need to create a separate instance in each thread
    mapper = Mapper(root = "/lsst7/stripe82/dr7/runs/", calibRoot = None, outputRoot = None)
    butler = dafPersist.ButlerFactory(mapper = mapper).create()

    # Grab science pixels
    im     = butler.get(datasetType="fpC", dataId = dataId).convertF()

    # Remove the 128 pixel duplicate overlap between fields
    # See python/lsst/obs/sdss/processCcdSdss.py for guidance
    bbox    = im.getBBox()
    begin   = bbox.getBegin()
    extent  = bbox.getDimensions()
    extent -= afwGeom.Extent2I(0, 128)
    tbbox   = afwGeom.BoxI(begin, extent)
    im      = afwImage.ImageF(im, tbbox, True)
    nx0, ny0 = extent

    # Remove 1000 count pedestal
    im    -= 1000.0 

    # Create image variance from gain
    calib, gain = butler.get(datasetType="tsField", dataId = dataId)
    var    = afwImage.ImageF(im, True)
    var   /= gain

    # Note I need to do a bit extra for the mask; I actually need to call
    # convertfpM with allPlanes = True to get all the SDSS info
    #
    # mask   = butler.get(datasetType="fpM", dataId = dataId)
    fpMFile = butler.mapper.map_fpM(dataId = dataId).getLocations()[0]
    mask    = convertfpM(fpMFile, True)
    # Remove the 128 pixel duplicate overlap...
    mask    = afwImage.MaskU(mask, tbbox, True)

    # We need this for the background estimation
    exp = afwImage.ExposureF(afwImage.MaskedImageF(im, mask, var))

    # Subtract off background, and scale by stdev
    # This will turn the image into "sigma"
    bgctrl = measAlg.BackgroundConfig(binSize=512, statisticsProperty="MEANCLIP", ignoredPixelMask=mask.getMaskPlaneDict().keys())
    bg, bgsubexp = measAlg.estimateBackground(exp, bgctrl, subtract=True)
    im = bgsubexp.getMaskedImage().getImage()
    sctrl = afwMath.StatisticsControl()
    sctrl.setAndMask(reduce(lambda x, y, mask=mask: x | mask.getPlaneBitMask(y), bgctrl.ignoredPixelMask, 0x0))
    stdev = afwMath.makeStatistics(im, mask, afwMath.STDEVCLIP, sctrl).getValue(afwMath.STDEVCLIP)
    im /= stdev

    # Additional info
    psf    = butler.get(datasetType="psField", dataId = dataId)
    wcs    = butler.get(datasetType="asTrans", dataId = dataId)

    # Decision point: do I send the convolution a MaskedImage, in which
    # case the mask is also spread, or just an Image, and not spread
    # the mask...  
    # 
    # I think for now I will not spread the mask so that it represents the
    # condition of the underlying pixels, not the Psf-filtered ones

    # Image convolved with the Psf, i.e. maximum point source likelihood image
    cim    = afwImage.ImageF(im, True)
    afwMath.convolve(cim, im, psf.getKernel(), True)

    # NOTE: DO WE SHRINK THE IMAGES HERE?  IF SO, WE NEED TO TWEAK WCS
    # For now, I will not do so
    # The pixels that are "good" in the image, i.e. ignore borders
    #cBBox  = psf.getKernel().shrinkBBox(cim.getBBox())
    #cim    = afwImage.ImageF(cim, cBBox)
    #mask   = afwImage.MaskU(mask, cBBox)

    cexp   = afwImage.ExposureF(afwImage.MaskedImageF(cim, mask, var))
    cexp.setWcs(wcs)
    return cexp