def testTaskAPI(self):
        """Test that the Tasks work

        Checks both MeasureCrosstalkTask and the CrosstalkTask.
        """
        # make exposure available to NullIsrTask
        # without NullIsrTask's `self` hiding this test class's `self`
        exposure = self.exposure

        class NullIsrTask(IsrTask):
            def runDataRef(self, dataRef):
                return Struct(exposure=exposure)

        config = MeasureCrosstalkTask.ConfigClass()
        config.isr.retarget(NullIsrTask)
        config.threshold = self.value - 1
        measure = MeasureCrosstalkTask(config=config)
        fakeDataRef = Struct(dataId={'fake': 1})
        coeff, coeffErr, coeffNum = measure.reduce(
            [measure.runDataRef(fakeDataRef)])
        self.checkCoefficients(coeff, coeffErr, coeffNum)

        config = IsrTask.ConfigClass()
        config.crosstalk.minPixelToMask = self.value - 1
        config.crosstalk.crosstalkMaskPlane = self.crosstalkStr
        isr = IsrTask(config=config)
        isr.crosstalk.run(self.exposure)
        self.checkSubtracted(self.exposure)
示例#2
0
    def testGainAndReadnoise(self):

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1.5
        raw.image.set(level)

        amp = detector[0]

        for gain in [-1, 0, 0.1, 1, np.NaN]:
            # Because amplifiers are immutable, we can't change the gain or
            # read noise in-place. Instead, we clone, and update the clone.
            testAmp = Amplifier.Builder()
            testAmp.assign(amp)
            testAmp.setReadNoise(readNoise)
            testAmp.setGain(gain)
            testAmp.finish()

            isrTask.updateVariance(raw, testAmp)
            if gain <= 0:  # behave the same way as amp.setGain
                gain = 1
            if math.isnan(gain):
                gain = 1
            self.assertEqual(raw.variance[0, 0, afwImage.LOCAL],
                             level / gain + readNoise**2)
示例#3
0
    def maskAndInterpDefect(self, ccdExposure, defectBaseList):
        """Mask defects and edges, interpolate over defects in place

        Mask defect pixels using mask plane BAD and interpolate over them.
        Mask the potentially problematic glowing edges as SUSPECT.

        Parameters
        ----------
        ccdExposure : `lsst.afw.image.Exposure`
            exposure to process
        defectBaseList : `list`
            a list of defects to mask and interpolate

        Returns
        -------
        ccdExposure : `lsst.afw.image.Exposure`
            exposure corrected in place
        """
        IsrTask.maskAndInterpDefect(self, ccdExposure, defectBaseList)
        maskedImage = ccdExposure.getMaskedImage()
        goodBBox = maskedImage.getBBox()
        # This makes a bbox numEdgeSuspect pixels smaller than the image on each side
        goodBBox.grow(-self.config.numEdgeSuspect)
        # Mask pixels outside goodBBox as SUSPECT
        SourceDetectionTask.setEdgeBits(
            maskedImage, goodBBox,
            maskedImage.getMask().getPlaneBitMask("SUSPECT"))
def runIsr():
    '''Run the task to do ISR on a ccd'''

    #Create the isr task with modified config
    isrConfig = IsrTask.ConfigClass()
    isrConfig.doBias = False  #We didn't make a zero frame
    isrConfig.doDark = True
    isrConfig.doFlat = True
    isrConfig.doFringe = False  #There is no fringe frame for this example

    isrConfig.assembleCcd.setGain = False
    isrTask = IsrTask(config=isrConfig)

    #Make raw, flat and dark exposures
    DARKVAL = 2.  #e-/sec
    OSCAN = 1000.  #DN
    GRADIENT = .10
    EXPTIME = 15  #seconds
    DARKEXPTIME = 40.  #seconds

    darkExposure = exampleUtils.makeDark(DARKVAL, DARKEXPTIME)
    flatExposure = exampleUtils.makeFlat(GRADIENT)
    rawExposure = exampleUtils.makeRaw(DARKVAL, OSCAN, GRADIENT, EXPTIME)

    output = isrTask.run(rawExposure, dark=darkExposure, flat=flatExposure)
    return output.exposure
    def testGainAndReadnoise(self):
        import lsst.afw.image as afwImage
        from lsst.afw.cameraGeom.testUtils import DetectorWrapper
        from lsst.ip.isr import IsrTask

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1
        raw.image.set(level)

        amp = detector[0]
        amp.setReadNoise(readNoise)

        for gain in [-1, 0, 0.1, 1]:
            amp.setGain(gain)
            isrTask.updateVariance(raw, amp)
            if gain <= 0:
                gain = 1

        self.assertEqual(raw.variance[0, 0, afwImage.LOCAL],
                         level / gain + readNoise**2)
示例#6
0
def runIsr():
    '''Run the task to do ISR on a ccd'''

    #Create the isr task with modified config
    isrConfig = IsrTask.ConfigClass()
    isrConfig.doBias = False #We didn't make a zero frame
    isrConfig.doDark = True
    isrConfig.doFlat = True
    isrConfig.doFringe = False #There is no fringe frame for this example

    isrConfig.assembleCcd.doRenorm = False #We'll take care of gain in the flats
    isrConfig.assembleCcd.setGain = False 
    isrTask = IsrTask(config=isrConfig)

    #Make raw, flat and dark exposures
    DARKVAL = 2. #e-/sec
    OSCAN = 1000. #DN
    GRADIENT = .10
    EXPTIME = 15 #seconds
    DARKEXPTIME = 40. #seconds

    darkExposure = exampleUtils.makeDark(DARKVAL, DARKEXPTIME)
    flatExposure = exampleUtils.makeFlat(GRADIENT)
    rawExposure = exampleUtils.makeRaw(DARKVAL, OSCAN, GRADIENT, EXPTIME)

    output = isrTask.run(rawExposure, dark=darkExposure, flat=flatExposure)
    return output.exposure
    def execute(self, dataRef):
        """!Apply common instrument signature correction algorithms to a raw frame

        @param dataRef: butler data reference
        @return a pipeBase Struct containing:
        - exposure

        similar to IsrTask.runDataRef()
        """
        self.log.info("Performing Super ISR on sensor data ID %s" %
                      (dataRef.dataId, ))

        # IsrTask.runDataRef includes these three steps
        self.log.info("Reading input data using dataRef")
        inputData = self.read_input_data(dataRef)

        self.log.info(
            "Running operations. The run() method should not take anything Butler"
        )
        result = IsrTask.run(IsrTask(self.config), **inputData.getDict())

        self.log.info("Writing output data using dataRef")
        self.write_output_data(dataRef, result)

        return result
示例#8
0
def processExposure(exposure,
                    bias=None,
                    defects=None,
                    repair=False,
                    repo=None):
    from lsst.ip.isr import IsrTask
    import lsst.daf.persistence as dafPersist
    import time

    config = IsrTask.ConfigClass()

    if (bias == None or defects == None) and repo == None:
        raise (AttributeError(
            'Repo keyword not set and is required to find bias/defect frames'))
    else:
        butler = dafPersist.Butler(repo)

    if False:
        config.overscanFitType = "AKIMA_SPLINE"
        config.overscanOrder = 5
    else:
        config.overscanFitType = "POLY"
        config.overscanOrder = 3

    config.doBias = True
    if config.doBias == True and bias == None:
        bias = butler.get(
            'bias',
            visit=exposure.getInfo().getVisitInfo().getExposureId(),
        )

    config.doDark = False
    config.doFringe = False
    config.doFlat = False
    config.doLinearize = False
    # TODO: should be able to run this but there are issues with defects in the stack at the moment
    config.doDefect = True if repair == True else False
    if (config.doDefect == True and bias == None):

        butler = dafPersist.Butler(repo)
        defects = butler.get(
            'defects',
            visit=exposure.getInfo().getVisitInfo().getExposureId(),
        )

    config.doAddDistortionModel = False
    config.doSaturationInterpolation = False
    config.overscanNumLeadingColumnsToSkip = 20

    isrTask = IsrTask(config=config)

    # Run ISR
    start = time.time()
    isr_corrected_exposure = isrTask.run(exposure, bias=bias,
                                         defects=defects).exposure
    end = time.time()
    print('Time to perform image ISR was {0:2f} [s]'.format(end - start))

    return (isr_corrected_exposure)
示例#9
0
    def overscanCorrection(self, exposure, amp):
        """Apply overscan correction in place

        If the input exposure is on the readout backplanes listed in
        config.overscanBiasJumpBKP, cut the amplifier in two vertically
        and correct each piece separately.

        @param[in,out] exposure: exposure to process; must include both
                                 DataSec and BiasSec pixels
        @param[in] amp: amplifier device data
        """
        if not (
            exposure.getMetadata().exists("FPA")
            and exposure.getMetadata().get("FPA") in self.config.overscanBiasJumpBKP
        ):
            IsrTask.overscanCorrection(self, exposure, amp)
            return

        dataBox = amp.getRawDataBBox()
        overscanBox = amp.getRawHorizontalOverscanBBox()

        if amp.getReadoutCorner() in (afwTable.LL, afwTable.LR):
            yLower = self.config.overscanBiasJumpLocation
            yUpper = dataBox.getHeight() - yLower
        else:
            yUpper = self.config.overscanBiasJumpLocation
            yLower = dataBox.getHeight() - yUpper

        lowerDataBBox = afwGeom.Box2I(dataBox.getBegin(), afwGeom.Extent2I(dataBox.getWidth(), yLower))
        upperDataBBox = afwGeom.Box2I(
            dataBox.getBegin() + afwGeom.Extent2I(0, yLower), afwGeom.Extent2I(dataBox.getWidth(), yUpper)
        )
        lowerOverscanBBox = afwGeom.Box2I(overscanBox.getBegin(), afwGeom.Extent2I(overscanBox.getWidth(), yLower))
        upperOverscanBBox = afwGeom.Box2I(
            overscanBox.getBegin() + afwGeom.Extent2I(0, yLower), afwGeom.Extent2I(overscanBox.getWidth(), yUpper)
        )

        maskedImage = exposure.getMaskedImage()
        lowerDataView = maskedImage.Factory(maskedImage, lowerDataBBox)
        upperDataView = maskedImage.Factory(maskedImage, upperDataBBox)

        expImage = exposure.getMaskedImage().getImage()
        lowerOverscanImage = expImage.Factory(expImage, lowerOverscanBBox)
        upperOverscanImage = expImage.Factory(expImage, upperOverscanBBox)

        overscanCorrection(
            ampMaskedImage=lowerDataView,
            overscanImage=lowerOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )
        overscanCorrection(
            ampMaskedImage=upperDataView,
            overscanImage=upperOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )
 def __init__(self, **kwargs):
     IsrTask.__init__(self, **kwargs)
     self.transposeForInterpolation = True  # temporary hack until LSST data is in proper order
     self.statsCtrl = afwMath.StatisticsControl()
     self.statsCtrl.setNumSigmaClip(self.config.sigmaClip)
     self.statsCtrl.setNumIter(self.config.clipIter)
     # Not sure how to do this.
     # self.statsCtrl.setAndMask('BAD')
     self.isr = isr
示例#11
0
 def __init__(self, **kwargs):
     IsrTask.__init__(self, **kwargs)
     self.transposeForInterpolation = True  # temporary hack until LSST data is in proper order
     self.statsCtrl = afwMath.StatisticsControl()
     self.statsCtrl.setNumSigmaClip(self.config.sigmaClip)
     self.statsCtrl.setNumIter(self.config.clipIter)
     # Not sure how to do this.
     # self.statsCtrl.setAndMask('BAD')
     self.isr = isr
 def testRunWithAddDistortionModel(self):
     """Test IsrTask.run with config.doAddDistortionModel true"""
     isrConfig = self.makeMinimalIsrConfig()
     isrConfig.doAddDistortionModel = True
     isrTask = IsrTask(config=isrConfig)
     with self.assertRaises(RuntimeError):
         # the camera argument is required
         isrTask.run(ccdExposure=self.exposure)
     exposure = isrTask.run(ccdExposure=self.exposure, camera=self.camera).exposure
     desiredWcs = self.makeDesiredDistortedWcs()
     self.assertWcsAlmostEqualOverBBox(desiredWcs, exposure.getWcs(), self.bbox)
    def testAddDistortionMethod(self):
        """Call IsrTask.addDistortionModel directly"""
        isrTask = IsrTask()
        isrTask.addDistortionModel(self.exposure, self.camera)
        self.assertFalse(
            wcsAlmostEqualOverBBox(self.wcs, self.exposure.getWcs(),
                                   self.bbox))

        desiredWcs = self.makeDesiredDistortedWcs()
        self.assertWcsAlmostEqualOverBBox(desiredWcs, self.exposure.getWcs(),
                                          self.bbox)
    def testRunWithoutAddDistortionModel(self):
        """Test IsrTask.run with config.doAddDistortionModel false"""
        isrConfig = self.makeMinimalIsrConfig()
        isrTask = IsrTask(config=isrConfig)

        # the camera argument is not needed
        exposure = isrTask.run(ccdExposure=self.exposure).exposure
        self.assertEqual(self.wcs, exposure.getWcs())

        # and the camera argument is ignored if provided
        exposure2 = isrTask.run(ccdExposure=self.exposure, camera=self.camera).exposure
        self.assertEqual(self.wcs, exposure2.getWcs())
 def read_input_data(self, dataRef):
     """Read needed data thru Butler in a pipeBase.Struct based on config"""
     ccdExp = dataRef.get('raw', immediate=True)
     isrData = IsrTask.readIsrData(IsrTask(self.config), dataRef, ccdExp)
     isrDataDict = isrData.getDict()
     return pipeBase.Struct(ccdExposure=ccdExp,
                            bias=isrDataDict['bias'],
                            linearizer=isrDataDict['linearizer'],
                            dark=isrDataDict['dark'],
                            flat=isrDataDict['flat'],
                            defects=isrDataDict['defects'],
                            fringes=isrDataDict['fringes'],
                            bfKernel=isrDataDict['bfKernel'])
示例#16
0
 def testRunWithAddDistortionModel(self):
     """Test IsrTask.run with config.doAddDistortionModel true"""
     isrConfig = self.makeMinimalIsrConfig()
     isrConfig.doAddDistortionModel = True
     isrTask = IsrTask(config=isrConfig)
     with self.assertRaises(RuntimeError):
         # the camera argument is required
         isrTask.run(ccdExposure=self.exposure)
     exposure = isrTask.run(ccdExposure=self.exposure,
                            camera=self.camera).exposure
     desiredWcs = self.makeDesiredDistortedWcs()
     self.assertWcsAlmostEqualOverBBox(desiredWcs, exposure.getWcs(),
                                       self.bbox)
示例#17
0
    def testRunWithoutAddDistortionModel(self):
        """Test IsrTask.run with config.doAddDistortionModel false"""
        isrConfig = self.makeMinimalIsrConfig()
        isrTask = IsrTask(config=isrConfig)

        # the camera argument is not needed
        exposure = isrTask.run(ccdExposure=self.exposure).exposure
        self.assertEqual(self.wcs, exposure.getWcs())

        # and the camera argument is ignored if provided
        exposure2 = isrTask.run(ccdExposure=self.exposure,
                                camera=self.camera).exposure
        self.assertEqual(self.wcs, exposure2.getWcs())
示例#18
0
    def maskAndInterpDefect(self, ccdExposure, defectBaseList):
        """Mask defects and edges, interpolate over defects in place

        Mask defect pixels using mask plane BAD and interpolate over them.
        Mask the potentially problematic glowing edges as SUSPECT.

        @param[in,out] ccdExposure: exposure to process
        @param[in] defectBaseList: a list of defects to mask and interpolate
        """
        IsrTask.maskAndInterpDefect(self, ccdExposure, defectBaseList)
        maskedImage = ccdExposure.getMaskedImage()
        goodBBox = maskedImage.getBBox()
        # This makes a bbox numEdgeSuspect pixels smaller than the image on each side
        goodBBox.grow(-self.config.numEdgeSuspect)
        # Mask pixels outside goodBBox as SUSPECT
        SourceDetectionTask.setEdgeBits(maskedImage, goodBBox, maskedImage.getMask().getPlaneBitMask("SUSPECT"))
示例#19
0
 def makeMinimalIsrConfig(self):
     """Return an IsrConfig with all boolean flags disabled"""
     isrConfig = IsrTask.ConfigClass()
     for name in isrConfig:
         if name.startswith("do"):
             setattr(isrConfig, name, False)
     return isrConfig
示例#20
0
    def testTaskAPI(self):
        """Test that the Tasks work

        Checks both MeasureCrosstalkTask and the CrosstalkTask.
        """
        # make exposure available to NullIsrTask
        # without NullIsrTask's `self` hiding this test class's `self`
        exposure = self.exposure

        class NullIsrTask(IsrTask):
            def runDataRef(self, dataRef):
                return Struct(exposure=exposure)

        coeff = np.array(self.crosstalk).transpose()
        config = IsrTask.ConfigClass()
        config.crosstalk.minPixelToMask = self.value - 1
        config.crosstalk.crosstalkMaskPlane = self.crosstalkStr
        isr = IsrTask(config=config)
        calib = CrosstalkCalib().fromDetector(self.exposure.getDetector(),
                                              coeffVector=coeff)
        isr.crosstalk.run(self.exposure, crosstalk=calib)
        self.checkSubtracted(self.exposure)
示例#21
0
    def run(self, ccdExposure, bias=None, dark=None, flat=None, defects=None, fringes=None):
        """Perform instrument signature removal on an exposure
        
        Steps include:
        - Detect saturation, apply overscan correction, bias, dark and flat
        - Perform CCD assembly
        - Interpolate over defects, saturated pixels and all NaNs
        - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True

        @param[in] ccdExposure  -- lsst.afw.image.exposure of detector data
        @param[in] bias -- exposure of bias frame
        @param[in] dark -- exposure of dark frame
        @param[in] flat -- exposure of flatfield
        @param[in] defects -- list of detects
        @param[in] fringes -- exposure of fringe frame or list of fringe exposure

        @return a pipeBase.Struct with fields:
        - exposure: the exposure after application of ISR
        """
        ccd = ccdExposure.getDetector()
        floatExposure = self.convertIntToFloat(ccdExposure)
        metadata = floatExposure.getMetadata()

        # Detect saturation
        # Saturation values recorded in the fits hader is not reliable, try to estimate it from the pixel vales
        # Find the peak location in the high end part the pixel values' histogram and set the saturation level at
        # safe * (peak location) where safe is a configurable parameter (typically 0.95)
        image = floatExposure.getMaskedImage().getImage()
        imageArray = image.getArray()
        maxValue = np.max(imageArray)
        if maxValue > 60000.0:
            hist, bin_edges = np.histogram(imageArray.ravel(), bins=100, range=(60000.0, maxValue + 1.0))
            saturate = int(self.config.safe * bin_edges[np.argmax(hist)])
        else:
            saturate = metadata.get("SATURATE")
        self.log.info("Saturation set to %d" % saturate)

        for amp in ccd:
            amp.setSaturation(saturate)
            if amp.getName() == "A":
                amp.setGain(metadata.get("GAINA"))
                amp.setReadNoise(metadata.get("RDNOISEA"))
            elif amp.getName() == "B":
                amp.setGain(metadata.get("GAINB"))
                amp.setReadNoise(metadata.get("RDNOISEB"))
            else:
                raise ValueError("Unexpected amplifier name : %s" % (amp.getName()))

        return IsrTask.run(
            self, ccdExposure=ccdExposure, bias=bias, dark=dark, flat=flat, defects=defects, fringes=fringes
        )
示例#22
0
    def test_interChip(self):
        """Test that passing an external exposure as the crosstalk source
        works.
        """
        exposure = self.exposure
        ctSources = [self.ctSource]

        coeff = np.array(self.crosstalk).transpose()
        calib = CrosstalkCalib().fromDetector(exposure.getDetector(),
                                              coeffVector=coeff)
        # Now convert this into zero intra-chip, full inter-chip:
        calib.interChip['detector 2'] = coeff
        calib.coeffs = np.zeros_like(coeff)

        # Process and check as above
        config = IsrTask.ConfigClass()
        config.crosstalk.minPixelToMask = self.value - 1
        config.crosstalk.crosstalkMaskPlane = self.crosstalkStr
        isr = IsrTask(config=config)
        isr.crosstalk.run(exposure,
                          crosstalk=calib,
                          crosstalkSources=ctSources)
        self.checkSubtracted(exposure)
    def testGainAndReadnoise(self):
        import lsst.afw.image as afwImage
        from lsst.afw.cameraGeom.testUtils import DetectorWrapper
        from lsst.ip.isr import IsrTask

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1
        raw.image.set(level)

        amp = detector[0]
        amp.setReadNoise(readNoise)

        for gain in [-1, 0, 0.1, 1]:
            amp.setGain(gain)
            isrTask.updateVariance(raw, amp)
            if gain <= 0:
                gain = 1

        self.assertEqual(raw.variance[0, 0, afwImage.LOCAL], level/gain + readNoise**2)
示例#24
0
    def __init__(self, *, config=None):
        # programatically clone all of the connections from isrTask
        # settin minimum values to zero for everything except the ccdExposure
        super().__init__(config=IsrTask.ConfigClass(
        ))  # need a dummy config, isn't used other than for ctor
        for name, connection in self.allConnections.items():
            if hasattr(connection, 'minimum'):
                args = _getArgs(connection)
                if name != "ccdExposure":  # need one input image always
                    args['minimum'] = 0
                newConnection = type(connection)(**args)
                self.allConnections[name] = newConnection
                setattr(self, name, newConnection)

        exposure = cT.Output(  # called just "exposure" to mimic isrTask's return struct
            name="quickLookExp",
            doc="The quickLook output exposure.",
            storageClass="ExposureF",
            dimensions=("instrument", "exposure", "detector"),
        )
        # set like this to make it explicit that the outputExposure
        # and the exposure are identical. The only reason there are two is for
        # API compatibility.
        self.outputExposure = exposure
示例#25
0
    def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
            fringes=None, bfKernel=None, camera=None, **kwds):
        """Perform instrument signature removal on an exposure

        Steps include:
        - Detect saturation, apply overscan correction, bias, dark and flat
        - Perform CCD assembly
        - Interpolate over defects, saturated pixels and all NaNs
        - Persist the ISR-corrected exposure as "postISRCCD" if
          config.doWrite is True

        Parameters
        ----------
        ccdExposure : `lsst.afw.image.Exposure`
            Detector data.
        bias : `lsst.afw.image.exposure`
            Exposure of bias frame.
        linearizer : `lsst.ip.isr.LinearizeBase` callable
            Linearizing functor; a subclass of lsst.ip.isr.LinearizeBase.
        dark : `lsst.afw.image.exposure`
            Exposure of dark frame.
        flat : `lsst.afw.image.exposure`
            Exposure of flatfield.
        defects : `list`
            list of detects
        fringes : `lsst.afw.image.exposure` or `list` of `lsst.afw.image.exposure`
            exposure of fringe frame or list of fringe exposure
        bfKernel : None
            kernel used for brighter-fatter correction; currently unsupported
        camera : `lsst.afw.cameraGeom.Camera`
            Camera geometry, used by addDistortionModel.
        **kwds : `dict`
            additional kwargs forwarded to IsrTask.run.

        Returns
        -------
        struct : `lsst.pipe.base.Struct` with fields:
            - exposure: the exposure after application of ISR
        """
        if bfKernel is not None:
            raise ValueError("CFHT ISR does not currently support brighter-fatter correction.")

        ccd = ccdExposure.getDetector()
        floatExposure = self.convertIntToFloat(ccdExposure)
        metadata = floatExposure.getMetadata()

        # Detect saturation
        # Saturation values recorded in the fits hader is not reliable, try to
        # estimate it from the pixel values.
        # Find the peak location in the high end part the pixel values'
        # histogram and set the saturation level at safe * (peak location)
        # where safe is a configurable parameter (typically 0.95)
        image = floatExposure.getMaskedImage().getImage()
        imageArray = image.getArray()
        maxValue = np.max(imageArray)
        if maxValue > 60000.0:
            hist, bin_edges = np.histogram(imageArray.ravel(), bins=100, range=(60000.0, maxValue+1.0))
            saturate = int(self.config.safe*bin_edges[np.argmax(hist)])
        else:
            saturate = metadata.getScalar("SATURATE")
        self.log.info("Saturation set to %d" % saturate)

        for amp in ccd:
            amp.setSaturation(saturate)
            if amp.getName() == "A":
                amp.setGain(metadata.getScalar("GAINA"))
                rdnA = metadata.getScalar("RDNOISEA")
                # Check if the noise value is making sense for this amp. If
                # not, replace with value stored in RDNOISE slot. This change
                # is necessary to process some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnA > 60000.0:
                    rdnA = metadata.getScalar("RDNOISE")
                amp.setReadNoise(rdnA)
            elif amp.getName() == "B":
                amp.setGain(metadata.getScalar("GAINB"))
                rdnB = metadata.getScalar("RDNOISEB")
                # Check if the noise value is making sense for this amp.
                # If not, replace with value
                # stored in RDNOISE slot. This change is necessary to process
                # some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnB > 60000.0:
                    rdnB = metadata.getScalar("RDNOISE")
                amp.setReadNoise(rdnB)
            else:
                raise ValueError("Unexpected amplifier name : %s"%(amp.getName()))

        return IsrTask.run(self,
                           ccdExposure=ccdExposure,
                           bias=bias,
                           linearizer=linearizer,
                           dark=dark,
                           flat=flat,
                           defects=defects,
                           fringes=fringes,
                           camera=camera,
                           **kwds
                           )
示例#26
0
 def __init__(self, **kwargs):
     IsrTask.__init__(self, **kwargs)
     self.makeSubtask("snapCombine")
    def testComponents(self):
        """Test that we can run the first-level subtasks of ProcessCcdTasks.

        This tests that we can run these subtasks from the command-line independently (they're all
        CmdLineTasks) as well as directly from Python (without giving them access to a Butler).

        Aside from verifying that no exceptions are raised, we simply tests that most persisted results are
        present and equivalent to both in-memory results.
        """
        outPath = tempfile.mkdtemp(
        ) if OutputName is None else "{}-Components".format(OutputName)
        # We'll use an input butler to get data for the tasks we call from Python, but we won't ever give it
        # to those tasks.
        inputButler = lsst.daf.persistence.Butler(InputDir)
        # Construct task instances we can use directly from Python
        isrTask = IsrTask(config=getObsTestConfig(IsrTask), name="isr2")
        # If we ever enable astrometry and photocal in obs_test, we'll need to pass a refObjLoader to these
        # tasks.  To maintain the spirit of these tests, we'd ideally have a LoadReferenceObjectsTask class
        # that doesn't require a Butler.  If we don't, we should construct a butler-based on outside these
        # task constructors and pass the LoadReferenceObjectsTask instance to the task constructors.
        charImageTask = CharacterizeImageTask(
            config=getObsTestConfig(CharacterizeImageTask), name="charImage2")
        calibrateTask = CalibrateTask(config=getObsTestConfig(CalibrateTask),
                                      name="calibrate2",
                                      icSourceSchema=charImageTask.schema)
        try:
            dataId = dict(visit=1)
            dataIdStrList = [
                "%s=%s" % (key, val) for key, val in dataId.items()
            ]

            isrResult1 = IsrTask.parseAndRun(
                args=[
                    InputDir, "--output", outPath, "--clobber-config",
                    "--doraise", "--id"
                ] + dataIdStrList,
                doReturnResults=True,
            )
            # We'll just use the butler to get the original image and calibration frames; it's not clear
            # extending the test coverage to include that is worth it.
            dataRef = inputButler.dataRef("raw", dataId=dataId)
            rawExposure = dataRef.get("raw", immediate=True)
            camera = dataRef.get("camera")
            isrData = isrTask.readIsrData(dataRef, rawExposure)
            exposureIdInfo = inputButler.get("expIdInfo", dataId=dataId)
            isrResult2 = isrTask.run(
                rawExposure,
                bias=isrData.bias,
                linearizer=isrData.linearizer,
                flat=isrData.flat,
                defects=isrData.defects,
                fringes=isrData.fringes,
                bfKernel=isrData.bfKernel,
                camera=camera,
            )
            self.assertMaskedImagesEqual(
                isrResult1.parsedCmd.butler.get(
                    "postISRCCD", dataId, immediate=True).getMaskedImage(),
                isrResult1.resultList[0].result.exposure.getMaskedImage())
            self.assertMaskedImagesEqual(
                isrResult2.exposure.getMaskedImage(),
                isrResult1.resultList[0].result.exposure.getMaskedImage())

            icResult1 = CharacterizeImageTask.parseAndRun(
                args=[
                    InputDir, "--output", outPath, "--clobber-config",
                    "--doraise", "--id"
                ] + dataIdStrList,
                doReturnResults=True,
            )
            icResult2 = charImageTask.run(isrResult2.exposure,
                                          exposureIdInfo=exposureIdInfo)
            self.assertMaskedImagesEqual(
                icResult1.parsedCmd.butler.get(
                    "icExp", dataId, immediate=True).getMaskedImage(),
                icResult1.resultList[0].result.exposure.getMaskedImage())
            self.assertMaskedImagesEqual(
                icResult2.exposure.getMaskedImage(),
                icResult1.resultList[0].result.exposure.getMaskedImage())
            self.assertCatalogsEqual(
                icResult1.parsedCmd.butler.get("icSrc", dataId,
                                               immediate=True),
                icResult1.resultList[0].result.sourceCat)
            self.assertCatalogsEqual(
                icResult2.sourceCat,
                icResult1.resultList[0].result.sourceCat,
            )
            self.assertBackgroundListsEqual(
                icResult1.parsedCmd.butler.get("icExpBackground",
                                               dataId,
                                               immediate=True),
                icResult1.resultList[0].result.background)
            self.assertBackgroundListsEqual(
                icResult2.background,
                icResult1.resultList[0].result.background)

            calResult1 = CalibrateTask.parseAndRun(
                args=[
                    InputDir, "--output", outPath, "--clobber-config",
                    "--doraise", "--id"
                ] + dataIdStrList,
                doReturnResults=True,
            )
            calResult2 = calibrateTask.run(
                icResult2.exposure,
                background=icResult2.background,
                icSourceCat=icResult2.sourceCat,
                exposureIdInfo=exposureIdInfo,
            )
            self.assertMaskedImagesEqual(
                calResult1.parsedCmd.butler.get(
                    "calexp", dataId, immediate=True).getMaskedImage(),
                calResult1.resultList[0].result.exposure.getMaskedImage())
            self.assertMaskedImagesEqual(
                calResult2.exposure.getMaskedImage(),
                calResult1.resultList[0].result.exposure.getMaskedImage())
            self.assertCatalogsEqual(
                calResult1.parsedCmd.butler.get("src", dataId, immediate=True),
                calResult1.resultList[0].result.sourceCat)
            self.assertCatalogsEqual(calResult2.sourceCat,
                                     calResult1.resultList[0].result.sourceCat,
                                     skipCols=("id", "parent"))
            self.assertBackgroundListsEqual(
                calResult1.parsedCmd.butler.get("calexpBackground",
                                                dataId,
                                                immediate=True),
                calResult1.resultList[0].result.background)
            self.assertBackgroundListsEqual(
                calResult2.background,
                calResult1.resultList[0].result.background)

        finally:
            if OutputName is None:
                shutil.rmtree(outPath)
            else:
                print("testProcessCcd.py's output data saved to %r" %
                      (OutputName, ))
示例#28
0
    def run(self, exposure, crosstalkSources=None):
        """Perform crosstalk correction on a DECam exposure and its corresponding dataRef.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            Exposure to correct.
        dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
            DataRef of exposure to correct which must include a dataId
            with at least one visit and ccdnum.
        crosstalkSources : `defaultdict`
            Must contain image data and corresponding crosstalk coefficients for
            each crosstalk source of the given dataRef victim. This is returned
            by prepCrosstalk.

        Returns
        -------
        `lsst.pipe.base.Struct`
        Struct with components:
        - ``exposure``: The exposure after crosstalk correction has been
                        applied (`lsst.afw.image.Exposure`).
        """
        self.log.info('Applying crosstalk correction')
        assert crosstalkSources is not None, "Sources are required for DECam crosstalk correction; \
                                              you must run CrosstalkTask via IsrTask which will \
                                              call prepCrosstalk to get the sources."

        # Load data from crosstalk 'victim' exposure we want to correct
        det = exposure.getDetector()
        for amp in det:
            ccdnum = det.getId()
            ccdnum_str = '%02d' % ccdnum
            victim = ccdnum_str + amp.getName()
            # If doCrosstalkBeforeAssemble=True, then use getRawBBox().  Otherwise, getBBox().
            dataBBox = amp.getRawBBox()
            # Check to see if victim overscan has been corrected, and if not, correct it first
            if not exposure.getMetadata().exists('OVERSCAN'):
                decamisr = IsrTask()
                decamisr.overscanCorrection(exposure, amp)
                self.log.warn(
                    'Overscan correction did not happen prior to crosstalk correction'
                )
                self.log.info(
                    'Correcting victim %s overscan before crosstalk' % victim)
            image = exposure.getMaskedImage().getImage()
            victim_data = image.Factory(image, dataBBox)
            # Load data from crosstalk 'source' exposures
            for source in crosstalkSources[victim]:
                source_data, source_coeff, source_idx = source
                victim_idx = 0
                if 'A' in victim:
                    victim_idx = 0
                elif 'B' in victim:
                    victim_idx = 1
                else:
                    self.log.fatal(
                        'DECam victim amp name does not contain A or B, cannot proceed'
                    )

                if source_idx != victim_idx:
                    # Occurs with amp A and B mismatch; need to flip horizontally
                    source_data = afwMath.flipImage(source_data,
                                                    flipLR=True,
                                                    flipTB=False)
                # Perform the linear crosstalk correction
                try:
                    source_data *= source_coeff
                    victim_data -= source_data
                except RuntimeError:
                    self.log.fatal(
                        'Crosstalk correction failed for victim %s from source %s'
                        % (victim, source))
        return pipeBase.Struct(exposure=exposure, )
示例#29
0
    def prepCrosstalk(self, dataRef):
        """Retrieve source image data and coefficients to prepare for crosstalk correction.

        This is called by readIsrData. The crosstalkSources land in isrData.

        Parameters
        ----------
        dataRef : `lsst.daf.persistence.butlerSubset.ButlerDataRef`
            DataRef of exposure to correct which must include a dataId with
            at least one visit and a ccdnum corresponding to the detector id.

        Returns
        -------
        crosstalkSources : `defaultdict`
            Contains image data and corresponding crosstalk coefficients for
            each crosstalk source of the given dataRef victim.
        """
        # Retrieve crosstalk sources and coefficients for the whole focal plane
        sources, coeffs = self.config.getSourcesAndCoeffs()

        # Define a butler to prepare for accessing crosstalk 'source' image data
        try:
            butler = dataRef.getButler()
        except RuntimeError:
            self.log.fatal('Cannot get a Butler from the dataRef provided')

        # Retrieve visit and ccdnum from 'victim' so we can look up 'sources'
        victim_exposure = dataRef.get()
        det = victim_exposure.getDetector()
        visit = dataRef.dataId['visit']
        ccdnum = det.getId()
        ccdnum_str = '%02d' % ccdnum
        crosstalkSources = defaultdict(list)
        decamisr = IsrTask()  # needed for source_exposure overscan correction
        for amp in det:
            victim = ccdnum_str + amp.getName()
            for source in sources[victim]:
                source_ccdnum = int(source[:2])
                try:
                    source_exposure = butler.get('raw',
                                                 dataId={
                                                     'visit': visit,
                                                     'ccdnum': source_ccdnum
                                                 })
                except RuntimeError:
                    self.log.warn('Cannot access source %d, SKIPPING IT' %
                                  source_ccdnum)
                else:
                    self.log.info(
                        'Correcting victim %s from crosstalk source %s' %
                        (victim, source))
                    if 'A' in source:
                        amp_idx = 0
                    elif 'B' in source:
                        amp_idx = 1
                    else:
                        self.log.fatal(
                            'DECam source amp name does not contain A or B, cannot proceed'
                        )
                    source_amp = source_exposure.getDetector()[amp_idx]
                    # If doCrosstalkBeforeAssemble=True, then use getRawBBox().  Otherwise, getRawDataBBox().
                    source_dataBBox = source_amp.getRawBBox()
                    # Check to see if source overscan has been corrected, and if not, correct it first
                    if not source_exposure.getMetadata().exists('OVERSCAN'):
                        decamisr.overscanCorrection(source_exposure,
                                                    source_amp)
                        self.log.info(
                            'Correcting source %s overscan before using to correct crosstalk'
                            % source)
                    source_image = source_exposure.getMaskedImage().getImage()

                    source_data = source_image.Factory(source_image,
                                                       source_dataBBox,
                                                       deep=True)
                    crosstalkSources[victim].append(
                        (source_data, coeffs[(victim, source)], amp_idx))
        return crosstalkSources
示例#30
0
    def overscanCorrection(self, exposure, amp):
        """Apply overscan correction in place

        If the input exposure is on the readout backplanes listed in
        config.overscanBiasJumpBKP, cut the amplifier in two vertically
        and correct each piece separately.

        @param[in,out] exposure: exposure to process; must include both
                                 DataSec and BiasSec pixels
        @param[in] amp: amplifier device data
        """
        if not (exposure.getMetadata().exists('FPA')
                and exposure.getMetadata().get('FPA')
                in self.config.overscanBiasJumpBKP):
            IsrTask.overscanCorrection(self, exposure, amp)
            return

        dataBox = amp.getRawDataBBox()
        overscanBox = amp.getRawHorizontalOverscanBBox()

        if amp.getReadoutCorner() in (afwTable.LL, afwTable.LR):
            yLower = self.config.overscanBiasJumpLocation
            yUpper = dataBox.getHeight() - yLower
        else:
            yUpper = self.config.overscanBiasJumpLocation
            yLower = dataBox.getHeight() - yUpper

        lowerDataBBox = afwGeom.Box2I(
            dataBox.getBegin(), afwGeom.Extent2I(dataBox.getWidth(), yLower))
        upperDataBBox = afwGeom.Box2I(
            dataBox.getBegin() + afwGeom.Extent2I(0, yLower),
            afwGeom.Extent2I(dataBox.getWidth(), yUpper))
        lowerOverscanBBox = afwGeom.Box2I(
            overscanBox.getBegin(),
            afwGeom.Extent2I(overscanBox.getWidth(), yLower))
        upperOverscanBBox = afwGeom.Box2I(
            overscanBox.getBegin() + afwGeom.Extent2I(0, yLower),
            afwGeom.Extent2I(overscanBox.getWidth(), yUpper))

        maskedImage = exposure.getMaskedImage()
        lowerDataView = maskedImage.Factory(maskedImage, lowerDataBBox)
        upperDataView = maskedImage.Factory(maskedImage, upperDataBBox)

        expImage = exposure.getMaskedImage().getImage()
        lowerOverscanImage = expImage.Factory(expImage, lowerOverscanBBox)
        upperOverscanImage = expImage.Factory(expImage, upperOverscanBBox)

        overscanCorrection(
            ampMaskedImage=lowerDataView,
            overscanImage=lowerOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )
        overscanCorrection(
            ampMaskedImage=upperDataView,
            overscanImage=upperOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )
示例#31
0
    def overscanCorrection(self, exposure, amp):
        """Apply overscan correction in place

        If the input exposure is on the readout backplanes listed in
        config.overscanBiasJumpBKP, cut the amplifier in two vertically
        and correct each piece separately.

        Parameters
        ----------
        exposure: `lsst.afw.image.Exposure`
            exposure to process; must include both DataSec and BiasSec pixels
        amp: `lsst.afw.table.AmpInfoRecord`
            amplifier device data

        Returns
        -------
        exposure: `lsst.afw.image.Exposure`
            exposure corrected in place with updated metadata
        """
        if not (exposure.getMetadata().exists('FPA')
                and exposure.getMetadata().get('FPA')
                in self.config.overscanBiasJumpBKP):
            IsrTask.overscanCorrection(self, exposure, amp)
            return

        dataBox = amp.getRawDataBBox()
        overscanBox = amp.getRawHorizontalOverscanBBox()

        if amp.getReadoutCorner() in (afwTable.LL, afwTable.LR):
            yLower = self.config.overscanBiasJumpLocation
            yUpper = dataBox.getHeight() - yLower
        else:
            yUpper = self.config.overscanBiasJumpLocation
            yLower = dataBox.getHeight() - yUpper

        lowerDataBBox = afwGeom.Box2I(
            dataBox.getBegin(), afwGeom.Extent2I(dataBox.getWidth(), yLower))
        upperDataBBox = afwGeom.Box2I(
            dataBox.getBegin() + afwGeom.Extent2I(0, yLower),
            afwGeom.Extent2I(dataBox.getWidth(), yUpper))
        lowerOverscanBBox = afwGeom.Box2I(
            overscanBox.getBegin(),
            afwGeom.Extent2I(overscanBox.getWidth(), yLower))
        upperOverscanBBox = afwGeom.Box2I(
            overscanBox.getBegin() + afwGeom.Extent2I(0, yLower),
            afwGeom.Extent2I(overscanBox.getWidth(), yUpper))

        maskedImage = exposure.getMaskedImage()
        lowerDataView = maskedImage.Factory(maskedImage, lowerDataBBox)
        upperDataView = maskedImage.Factory(maskedImage, upperDataBBox)

        expImage = exposure.getMaskedImage().getImage()
        lowerOverscanImage = expImage.Factory(expImage, lowerOverscanBBox)
        upperOverscanImage = expImage.Factory(expImage, upperOverscanBBox)

        overscanCorrection(
            ampMaskedImage=lowerDataView,
            overscanImage=lowerOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )
        overscanCorrection(
            ampMaskedImage=upperDataView,
            overscanImage=upperOverscanImage,
            fitType=self.config.overscanFitType,
            order=self.config.overscanOrder,
            collapseRej=self.config.overscanRej,
        )

        # Note that overscan correction has been done in exposure metadata
        metadata = exposure.getMetadata()
        metadata.set(
            'OVERSCAN', 'Overscan corrected on {0}'.format(
                dt.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")))
        exposure.setMetadata(metadata)
 def __init__(self, **kwargs):
     IsrTask.__init__(self, **kwargs)
     self.makeSubtask("snapCombine")
示例#33
0
    def getExposure(self, expIdOrDataId, extraIsrOptions={}, skipCosmics=False, **kwargs):
        """Get the postIsr and cosmic-repaired image for this dataId.

        Parameters
        ----------
        expIdOrDataId : `dict`
            The dataId
        extraIsrOptions : `dict`, optional
            extraIsrOptions is a dict of extra isr options applied to this
            image only.
        skipCosmics : `bool`, optional  # XXX THIS CURRENTLY DOESN'T WORK!
            Skip doing cosmic ray repair for this image?

        Returns
        -------
        exp : `lsst.afw.image.Exposure`
            The postIsr exposure
        """
        dataId = self._parseExpIdOrDataId(expIdOrDataId, **kwargs)

        try:
            exp = self.butler.get(self._datasetName, dataId=dataId)
            self.log.info("Found a ready-made quickLookExp in the repo. Returning that.")
            return exp
        except LookupError:
            pass

        try:
            raw = self.butler.get('raw', dataId=dataId)
        except LookupError:
            raise RuntimeError(f"Failed to retrieve raw for exp {dataId}") from None

        # default options that are probably good for most engineering time
        isrConfig = IsrTask.ConfigClass()
        isrConfig.doWrite = False  # this task writes separately, no need for this
        isrConfig.doSaturation = True  # saturation very important for roundness measurement in qfm
        isrConfig.doSaturationInterpolation = True
        isrConfig.overscanNumLeadingColumnsToSkip = 5
        isrConfig.overscan.fitType = 'MEDIAN_PER_ROW'

        # apply general overrides
        self._applyConfigOverrides(isrConfig, self.defaultExtraIsrOptions)
        # apply per-image overrides
        self._applyConfigOverrides(isrConfig, extraIsrOptions)

        isrParts = ['camera', 'bias', 'dark', 'flat', 'defects', 'linearizer', 'crosstalk', 'bfKernel',
                    'bfGains', 'ptc']

        isrDict = {}
        # we build a cache of all the isr components which will be used to save
        # the IO time on subsequent calls. This assumes people will not update
        # calibration products while this object lives, but this is a fringe
        # use case, and if they do, all they would need to do would be call
        # .clearCache() and this will rebuild with the new products.
        for component in isrParts:
            if component in self._cache and component != 'flat':
                self.log.info(f"Using {component} from cache...")
                isrDict[component] = self._cache[component]
                continue
            try:
                # TODO: add caching for flats
                item = self.butler.get(component, dataId=dataId)
                self._cache[component] = item
                isrDict[component] = self._cache[component]
            except (RuntimeError, LookupError, OperationalError):
                pass

        quickLookExp = self.quickLookIsrTask.run(raw, **isrDict, isrBaseConfig=isrConfig).outputExposure

        if self.doWrite:
            try:
                self.butler.put(quickLookExp, self._datasetName, dataId)
                self.log.info(f'Put {self._datasetName} for {dataId}')
            except ConflictingDefinitionError:
                # TODO: DM-34302 fix this message so that it's less scary for
                # users. Do this by having daemons know they're daemons.
                self.log.warning('Skipped putting existing exp into collection! (ignore if there was a race)')
                pass

        return quickLookExp
示例#34
0
def run(config,
        inputFiles,
        weightFiles=None,
        varianceFiles=None,
        returnCalibSources=False,
        displayResults=[],
        verbose=False):
    #
    # Create the tasks
    #
    schema = afwTable.SourceTable.makeMinimalSchema()
    algMetadata = dafBase.PropertyList()

    isrTask = IsrTask(config=config.isr)
    charImageTask = CharacterizeImageTask(None, config=config.charImage)
    sourceDetectionTask = SourceDetectionTask(config=config.detection,
                                              schema=schema)
    if config.doDeblend:
        if SourceDeblendTask:
            sourceDeblendTask = SourceDeblendTask(config=config.deblend,
                                                  schema=schema)
        else:
            print >> sys.stderr, "Failed to import lsst.meas.deblender;  setting doDeblend = False"
            config.doDeblend = False
    sourceMeasurementTask = SingleFrameMeasurementTask(
        schema=schema, config=config.measurement, algMetadata=algMetadata)

    keysToCopy = []
    for key in [
            charImageTask.measurePsf.reservedKey,
            charImageTask.measurePsf.usedKey,
    ]:
        keysToCopy.append(
            (schema.addField(charImageTask.schema.find(key).field), key))

    exposureDict = {}
    calibSourcesDict = {}
    sourcesDict = {}

    for inputFile, weightFile, varianceFile in zip(inputFiles, weightFiles,
                                                   varianceFiles):
        #
        # Create the output table
        #
        tab = afwTable.SourceTable.make(schema)
        #
        # read the data
        #
        if verbose:
            print "Reading %s" % inputFile

        exposure = makeExposure(inputFile, weightFile, varianceFile,
                                config.badPixelValue, config.variance)
        #
        if config.interpPlanes:
            import lsst.ip.isr as ipIsr
            defects = ipIsr.getDefectListFromMask(exposure.getMaskedImage(),
                                                  config.interpPlanes,
                                                  growFootprints=0)

            isrTask.run(exposure, defects=defects)
        #
        # process the data
        #
        if config.doCalibrate:
            result = charImageTask.characterize(exposure)
            exposure, calibSources = result.exposure, result.sourceCat
        else:
            calibSources = None
            if not exposure.getPsf():
                charImageTask.installSimplePsf.run(exposure)

        exposureDict[inputFile] = exposure
        calibSourcesDict[
            inputFile] = calibSources if returnCalibSources else None

        result = sourceDetectionTask.run(tab, exposure)
        sources = result.sources
        sourcesDict[inputFile] = sources

        if config.doDeblend:
            sourceDeblendTask.run(exposure, sources)

        sourceMeasurementTask.measure(exposure, sources)

        if verbose:
            print "Detected %d objects" % len(sources)

        propagatePsfFlags(keysToCopy, calibSources, sources)

        if displayResults:  # display results of processing (see also --debug argparse option)
            showApertures = "showApertures".upper() in displayResults
            showPSFs = "showPSFs".upper() in displayResults
            showShapes = "showShapes".upper() in displayResults

            display = afwDisplay.getDisplay(frame=1)

            if algMetadata.exists("base_CircularApertureFlux_radii"):
                radii = algMetadata.get("base_CircularApertureFlux_radii")
            else:
                radii = []

            display.mtv(exposure, title=os.path.split(inputFile)[1])

            with display.Buffering():
                for s in sources:
                    xy = s.getCentroid()
                    display.dot('+',
                                *xy,
                                ctype=afwDisplay.CYAN if
                                s.get("flags_negative") else afwDisplay.GREEN)

                    if showPSFs and (s.get("calib_psfUsed")
                                     or s.get("calib_psfReserved")):
                        display.dot(
                            'o',
                            *xy,
                            size=10,
                            ctype=afwDisplay.GREEN
                            if s.get("calib_psfUsed") else afwDisplay.YELLOW)

                    if showShapes:
                        display.dot(s.getShape(), *xy, ctype=afwDisplay.RED)

                    if showApertures:
                        for radius in radii:
                            display.dot('o',
                                        *xy,
                                        size=radius,
                                        ctype=afwDisplay.YELLOW)

    return exposureDict, calibSourcesDict, sourcesDict
示例#35
0
    def run(self,
            ccdExposure,
            bias=None,
            linearizer=None,
            dark=None,
            flat=None,
            defects=None,
            fringes=None,
            bfKernel=None,
            **kwds):
        """Perform instrument signature removal on an exposure

        Steps include:
        - Detect saturation, apply overscan correction, bias, dark and flat
        - Perform CCD assembly
        - Interpolate over defects, saturated pixels and all NaNs
        - Persist the ISR-corrected exposure as "postISRCCD" if
          config.doWrite is True

        Parameters
        ----------
        ccdExposure : `lsst.afw.image.Exposure`
            Detector data.
        bias : `lsst.afw.image.exposure`
            Exposure of bias frame.
        linearizer : `lsst.ip.isr.LinearizeBase` callable
            Linearizing functor; a subclass of lsst.ip.isr.LinearizeBase.
        dark : `lsst.afw.image.exposure`
            Exposure of dark frame.
        flat : `lsst.afw.image.exposure`
            Exposure of flatfield.
        defects : `list`
            list of detects
        fringes : `lsst.afw.image.Exposure` or list `lsst.afw.image.Exposure`
            exposure of fringe frame or list of fringe exposure
        bfKernel : None
            kernel used for brighter-fatter correction; currently unsupported
        **kwds : `dict`
            additional kwargs forwarded to IsrTask.run.

        Returns
        -------
        struct : `lsst.pipe.base.Struct` with fields:
            - exposure: the exposure after application of ISR
        """
        if bfKernel is not None:
            raise ValueError(
                "CFHT ISR does not currently support brighter-fatter correction."
            )

        ccd = ccdExposure.getDetector()
        floatExposure = self.convertIntToFloat(ccdExposure)
        metadata = floatExposure.getMetadata()

        # Detect saturation
        # Saturation values recorded in the fits hader is not reliable, try to
        # estimate it from the pixel values.
        # Find the peak location in the high end part the pixel values'
        # histogram and set the saturation level at safe * (peak location)
        # where safe is a configurable parameter (typically 0.95)
        image = floatExposure.getMaskedImage().getImage()
        imageArray = image.getArray()
        maxValue = np.max(imageArray)
        if maxValue > 60000.0:
            hist, bin_edges = np.histogram(imageArray.ravel(),
                                           bins=100,
                                           range=(60000.0, maxValue + 1.0))
            saturate = int(self.config.safe * bin_edges[np.argmax(hist)])
        else:
            saturate = metadata.getScalar("SATURATE")
        self.log.info("Saturation set to %d" % saturate)

        tempCcd = ccd.rebuild()
        tempCcd.clear()
        for amp in ccd:
            tempAmp = amp.rebuild()
            tempAmp.setSaturation(saturate)
            if tempAmp.getName() == "A":
                tempAmp.setGain(metadata.getScalar("GAINA"))
                rdnA = metadata.getScalar("RDNOISEA")
                # Check if the noise value is making sense for this amp. If
                # not, replace with value stored in RDNOISE slot. This change
                # is necessary to process some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnA > 60000.0:
                    rdnA = metadata.getScalar("RDNOISE")
                tempAmp.setReadNoise(rdnA)
            elif tempAmp.getName() == "B":
                tempAmp.setGain(metadata.getScalar("GAINB"))
                rdnB = metadata.getScalar("RDNOISEB")
                # Check if the noise value is making sense for this amp.
                # If not, replace with value
                # stored in RDNOISE slot. This change is necessary to process
                # some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnB > 60000.0:
                    rdnB = metadata.getScalar("RDNOISE")
                tempAmp.setReadNoise(rdnB)
            else:
                raise ValueError("Unexpected amplifier name : %s" %
                                 (amp.getName()))
            tempCcd.append(tempAmp)

        ccd = tempCcd.finish()
        ccdExposure.setDetector(ccd)
        return IsrTask.run(self,
                           ccdExposure=ccdExposure,
                           bias=bias,
                           linearizer=linearizer,
                           dark=dark,
                           flat=flat,
                           defects=defects,
                           fringes=fringes,
                           **kwds)
示例#36
0
    def run(self,
            ccdExposure,
            bias=None,
            linearizer=None,
            dark=None,
            flat=None,
            defects=None,
            fringes=None,
            bfKernel=None):
        """Perform instrument signature removal on an exposure

        Steps include:
        - Detect saturation, apply overscan correction, bias, dark and flat
        - Perform CCD assembly
        - Interpolate over defects, saturated pixels and all NaNs
        - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True

        @param[in] ccdExposure  -- lsst.afw.image.exposure of detector data
        @param[in] bias -- exposure of bias frame
        @param[in] linearizer -- linearizing functor; a subclass of lsst.ip.isr.LinearizeBase
        @param[in] dark -- exposure of dark frame
        @param[in] flat -- exposure of flatfield
        @param[in] defects -- list of detects
        @param[in] fringes -- exposure of fringe frame or list of fringe exposure
        @param[in] bfKernel - kernel used for brighter-fatter correction; currently unsupported

        @return a pipeBase.Struct with fields:
        - exposure: the exposure after application of ISR
        """
        if bfKernel is not None:
            raise ValueError(
                "CFHT ISR does not currently support brighter-fatter correction."
            )

        ccd = ccdExposure.getDetector()
        floatExposure = self.convertIntToFloat(ccdExposure)
        metadata = floatExposure.getMetadata()

        # Detect saturation
        # Saturation values recorded in the fits hader is not reliable, try to estimate it from
        # the pixel vales
        # Find the peak location in the high end part the pixel values' histogram and set the saturation
        # level at safe * (peak location) where safe is a configurable parameter (typically 0.95)
        image = floatExposure.getMaskedImage().getImage()
        imageArray = image.getArray()
        maxValue = np.max(imageArray)
        if maxValue > 60000.0:
            hist, bin_edges = np.histogram(imageArray.ravel(),
                                           bins=100,
                                           range=(60000.0, maxValue + 1.0))
            saturate = int(self.config.safe * bin_edges[np.argmax(hist)])
        else:
            saturate = metadata.get("SATURATE")
        self.log.info("Saturation set to %d" % saturate)

        for amp in ccd:
            amp.setSaturation(saturate)
            if amp.getName() == "A":
                amp.setGain(metadata.get("GAINA"))
                rdnA = metadata.get("RDNOISEA")
                # Check if the noise value is making sense for this amp. If not, replace with value
                # stored in RDNOISE slot. This change is necessary to process some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnA > 60000.0:
                    rdnA = metadata.get("RDNOISE")
                amp.setReadNoise(rdnA)
            elif amp.getName() == "B":
                amp.setGain(metadata.get("GAINB"))
                rdnB = metadata.get("RDNOISEB")
                # Check if the noise value is making sense for this amp. If not, replace with value
                # stored in RDNOISE slot. This change is necessary to process some old CFHT images
                # (visit : 7xxxxx) where RDNOISEA/B = 65535
                if rdnB > 60000.0:
                    rdnB = metadata.get("RDNOISE")
                amp.setReadNoise(rdnB)
            else:
                raise ValueError("Unexpected amplifier name : %s" %
                                 (amp.getName()))

        return IsrTask.run(
            self,
            ccdExposure=ccdExposure,
            bias=bias,
            linearizer=linearizer,
            dark=dark,
            flat=flat,
            defects=defects,
            fringes=fringes,
        )
    def testComponents(self):
        """Test that we can run the first-level subtasks of ProcessCcdTasks.

        This tests that we can run these subtasks from the command-line independently (they're all
        CmdLineTasks) as well as directly from Python (without giving them access to a Butler).

        Aside from verifying that no exceptions are raised, we simply tests that most persisted results are
        present and equivalent to both in-memory results.
        """
        outPath = tempfile.mkdtemp() if OutputName is None else "{}-Components".format(OutputName)
        # We'll use an input butler to get data for the tasks we call from Python, but we won't ever give it
        # to those tasks.
        inputButler = lsst.daf.persistence.Butler(InputDir)
        # Construct task instances we can use directly from Python
        isrTask = IsrTask(
            config=getObsTestConfig(IsrTask),
            name="isr2"
        )
        # If we ever enable astrometry and photocal in obs_test, we'll need to pass a refObjLoader to these
        # tasks.  To maintain the spirit of these tests, we'd ideally have a LoadReferenceObjectsTask class
        # that doesn't require a Butler.  If we don't, we should construct a butler-based on outside these
        # task constructors and pass the LoadReferenceObjectsTask instance to the task constructors.
        charImageTask = CharacterizeImageTask(
            config=getObsTestConfig(CharacterizeImageTask),
            name="charImage2"
        )
        calibrateTask = CalibrateTask(
            config=getObsTestConfig(CalibrateTask),
            name="calibrate2",
            icSourceSchema=charImageTask.schema
        )
        try:
            dataId = dict(visit=1)
            dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()]

            isrResult1 = IsrTask.parseAndRun(
                args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
                doReturnResults=True,
            )
            # We'll just use the butler to get the original image and calibration frames; it's not clear
            # extending the test coverage to include that is worth it.
            dataRef = inputButler.dataRef("raw", dataId=dataId)
            rawExposure = dataRef.get("raw", immediate=True)
            camera = dataRef.get("camera")
            isrData = isrTask.readIsrData(dataRef, rawExposure)
            isrResult2 = isrTask.run(
                rawExposure,
                bias=isrData.bias,
                linearizer=isrData.linearizer,
                flat=isrData.flat,
                defects=isrData.defects,
                fringes=isrData.fringes,
                bfKernel=isrData.bfKernel,
                camera=camera,
            )
            self.assertMaskedImagesEqual(
                isrResult1.parsedCmd.butler.get("postISRCCD", dataId, immediate=True).getMaskedImage(),
                isrResult1.resultList[0].result.exposure.getMaskedImage()
            )
            self.assertMaskedImagesEqual(
                isrResult2.exposure.getMaskedImage(),
                isrResult1.resultList[0].result.exposure.getMaskedImage()
            )

            icResult1 = CharacterizeImageTask.parseAndRun(
                args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
                doReturnResults=True,
            )
            icResult2 = charImageTask.run(isrResult2.exposure)
            self.assertMaskedImagesEqual(
                icResult1.parsedCmd.butler.get("icExp", dataId, immediate=True).getMaskedImage(),
                icResult1.resultList[0].result.exposure.getMaskedImage()
            )
            self.assertMaskedImagesEqual(
                icResult2.exposure.getMaskedImage(),
                icResult1.resultList[0].result.exposure.getMaskedImage()
            )
            self.assertCatalogsEqual(
                icResult1.parsedCmd.butler.get("icSrc", dataId, immediate=True),
                icResult1.resultList[0].result.sourceCat
            )
            self.assertCatalogsEqual(
                icResult2.sourceCat,
                icResult1.resultList[0].result.sourceCat,
                skipCols=("id", "parent")  # since we didn't want to pass in an ExposureIdInfo, IDs disagree
            )
            self.assertBackgroundListsEqual(
                icResult1.parsedCmd.butler.get("icExpBackground", dataId, immediate=True),
                icResult1.resultList[0].result.background
            )
            self.assertBackgroundListsEqual(
                icResult2.background,
                icResult1.resultList[0].result.background
            )

            calResult1 = CalibrateTask.parseAndRun(
                args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
                doReturnResults=True,
            )
            calResult2 = calibrateTask.run(
                icResult2.exposure,
                background=icResult2.background,
                icSourceCat=icResult2.sourceCat
            )
            self.assertMaskedImagesEqual(
                calResult1.parsedCmd.butler.get("calexp", dataId, immediate=True).getMaskedImage(),
                calResult1.resultList[0].result.exposure.getMaskedImage()
            )
            self.assertMaskedImagesEqual(
                calResult2.exposure.getMaskedImage(),
                calResult1.resultList[0].result.exposure.getMaskedImage()
            )
            self.assertCatalogsEqual(
                calResult1.parsedCmd.butler.get("src", dataId, immediate=True),
                calResult1.resultList[0].result.sourceCat
            )
            self.assertCatalogsEqual(
                calResult2.sourceCat,
                calResult1.resultList[0].result.sourceCat,
                skipCols=("id", "parent")
            )
            self.assertBackgroundListsEqual(
                calResult1.parsedCmd.butler.get("calexpBackground", dataId, immediate=True),
                calResult1.resultList[0].result.background
            )
            self.assertBackgroundListsEqual(
                calResult2.background,
                calResult1.resultList[0].result.background
            )

        finally:
            if OutputName is None:
                shutil.rmtree(outPath)
            else:
                print("testProcessCcd.py's output data saved to %r" % (OutputName,))
示例#38
0
def run(config, inputFiles, weightFiles=None, varianceFiles=None,
        returnCalibSources=False, displayResults=[], verbose=False):
    #
    # Create the tasks
    #
    schema = afwTable.SourceTable.makeMinimalSchema()
    algMetadata = dafBase.PropertyList()

    isrTask = IsrTask(config=config.isr)
    calibrateTask =         CalibrateTask(config=config.calibrate)
    sourceDetectionTask =   SourceDetectionTask(config=config.detection, schema=schema)
    if config.doDeblend:
        if SourceDeblendTask:
            sourceDeblendTask = SourceDeblendTask(config=config.deblend, schema=schema)
        else:
            print >> sys.stderr, "Failed to import lsst.meas.deblender;  setting doDeblend = False"
            config.doDeblend = False

    sourceMeasurementTask = SingleFrameMeasurementTask(config=config.measurement,
                                                       schema=schema, algMetadata=algMetadata)
    sourceMeasurementTask.config.doApplyApCorr = 'yes'
    #
    # Add fields needed to identify stars while calibrating
    #
    keysToCopy = [(schema.addField(afwTable.Field["Flag"]("calib_detected",
                                                          "Source was detected by calibrate")), None)]
    for key in calibrateTask.getCalibKeys():
        keysToCopy.append((schema.addField(calibrateTask.schema.find(key).field), key))

    exposureDict = {}; calibSourcesDict = {}; sourcesDict = {}

    for inputFile, weightFile, varianceFile in zip(inputFiles, weightFiles, varianceFiles):
        #
        # Create the output table
        #
        tab = afwTable.SourceTable.make(schema)
        #
        # read the data
        #
        if verbose:
            print "Reading %s" % inputFile

        exposure = makeExposure(inputFile, weightFile, varianceFile,
                                config.badPixelValue, config.variance)
        #
        if config.interpPlanes:
            import lsst.ip.isr as ipIsr
            defects = ipIsr.getDefectListFromMask(exposure.getMaskedImage(), config.interpPlanes,
                                                  growFootprints=0)

            isrTask.run(exposure, defects=defects)
        #
        # process the data
        #
        if config.doCalibrate:
            result = calibrateTask.run(exposure)
            exposure, calibSources = result.exposure, result.sources
        else:
            calibSources = None
            if not exposure.getPsf():
                calibrateTask.installInitialPsf(exposure)

        exposureDict[inputFile] = exposure
        calibSourcesDict[inputFile] = calibSources if returnCalibSources else None

        result = sourceDetectionTask.run(tab, exposure)
        sources = result.sources
        sourcesDict[inputFile] = sources

        if config.doDeblend:
            sourceDeblendTask.run(exposure, sources, exposure.getPsf())

        sourceMeasurementTask.measure(exposure, sources)

        if verbose:
            print "Detected %d objects" % len(sources)

        propagateCalibFlags(keysToCopy, calibSources, sources)

        if displayResults:              # display results of processing (see also --debug argparse option)
            showApertures = "showApertures".upper() in displayResults
            showShapes = "showShapes".upper() in displayResults

            display = afwDisplay.getDisplay(frame=1)

            if algMetadata.exists("base_CircularApertureFlux_radii"):
                radii = algMetadata.get("base_CircularApertureFlux_radii")
            else:
                radii = []

            display.mtv(exposure, title=os.path.split(inputFile)[1])

            with display.Buffering():
                for s in sources:
                    xy = s.getCentroid()
                    display.dot('+', *xy,
                                ctype=afwDisplay.CYAN if s.get("flags_negative") else afwDisplay.GREEN)

                    if showShapes:
                        display.dot(s.getShape(), *xy, ctype=afwDisplay.RED)

                    if showApertures:
                        for radius in radii:
                            display.dot('o', *xy, size=radius, ctype=afwDisplay.YELLOW)

    return exposureDict, calibSourcesDict, sourcesDict
示例#39
0
#!/usr/bin/env python
#
# LSST Data Management System
# Copyright 2008, 2009, 2010, 2011, 2012 LSST Corporation.
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program.  If not,
# see <http://www.lsstcorp.org/LegalNotices/>.
#
from lsst.ip.isr import IsrTask

IsrTask.parseAndRun()
示例#40
0
    def run(self,
            ccdExposure,
            *,
            camera=None,
            bias=None,
            dark=None,
            flat=None,
            defects=None,
            linearizer=None,
            crosstalk=None,
            bfKernel=None,
            bfGains=None,
            ptc=None,
            crosstalkSources=None,
            isrBaseConfig=None):
        """Run isr and cosmic ray repair using, doing as much isr as possible.

        Retrieves as many calibration products as are available, and runs isr
        with those settings enabled, but always returns an assembled image at
        a minimum. Then performs cosmic ray repair if configured to.

        Parameters
        ----------
        ccdExposure : `lsst.afw.image.Exposure`
            The raw exposure that is to be run through ISR.  The
            exposure is modified by this method.
        camera : `lsst.afw.cameraGeom.Camera`, optional
            The camera geometry for this exposure. Required if
            one or more of ``ccdExposure``, ``bias``, ``dark``, or
            ``flat`` does not have an associated detector.
        bias : `lsst.afw.image.Exposure`, optional
            Bias calibration frame.
        linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional
            Functor for linearization.
        crosstalk : `lsst.ip.isr.crosstalk.CrosstalkCalib`, optional
            Calibration for crosstalk.
        crosstalkSources : `list`, optional
            List of possible crosstalk sources.
        dark : `lsst.afw.image.Exposure`, optional
            Dark calibration frame.
        flat : `lsst.afw.image.Exposure`, optional
            Flat calibration frame.
        ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional
            Photon transfer curve dataset, with, e.g., gains
            and read noise.
        bfKernel : `numpy.ndarray`, optional
            Brighter-fatter kernel.
        bfGains : `dict` of `float`, optional
            Gains used to override the detector's nominal gains for the
            brighter-fatter correction. A dict keyed by amplifier name for
            the detector in question.
        defects : `lsst.ip.isr.Defects`, optional
            List of defects.
        fringes : `lsst.pipe.base.Struct`, optional
            Struct containing the fringe correction data, with
            elements:
            - ``fringes``: fringe calibration frame (`afw.image.Exposure`)
            - ``seed``: random seed derived from the ccdExposureId for random
                number generator (`uint32`)
        opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional
            A ``TransmissionCurve`` that represents the throughput of the,
            optics, to be evaluated in focal-plane coordinates.
        filterTransmission : `lsst.afw.image.TransmissionCurve`
            A ``TransmissionCurve`` that represents the throughput of the
            filter itself, to be evaluated in focal-plane coordinates.
        sensorTransmission : `lsst.afw.image.TransmissionCurve`
            A ``TransmissionCurve`` that represents the throughput of the
            sensor itself, to be evaluated in post-assembly trimmed detector
            coordinates.
        atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
            A ``TransmissionCurve`` that represents the throughput of the
            atmosphere, assumed to be spatially constant.
        detectorNum : `int`, optional
            The integer number for the detector to process.
        strayLightData : `object`, optional
            Opaque object containing calibration information for stray-light
            correction.  If `None`, no correction will be performed.
        illumMaskedImage : `lsst.afw.image.MaskedImage`, optional
            Illumination correction image.
        isrBaseConfig : `lsst.ip.isr.IsrTaskConfig`, optional
            An isrTask config to act as the base configuration. Options which
            involve applying a calibration product are ignored, but this allows
            for the configuration of e.g. the number of overscan columns.

        Returns
        -------
        result : `lsst.pipe.base.Struct`
            Result struct with component:
            - ``exposure`` : `afw.image.Exposure`
                The ISRed and cosmic-ray-repaired exposure.
        """
        isrConfig = isrBaseConfig if isrBaseConfig else IsrTask.ConfigClass()
        isrConfig.doBias = False
        isrConfig.doDark = False
        isrConfig.doFlat = False
        isrConfig.doFringe = False
        isrConfig.doDefect = False
        isrConfig.doLinearize = False
        isrConfig.doCrosstalk = False
        isrConfig.doBrighterFatter = False
        isrConfig.usePtcGains = False

        if bias:
            isrConfig.doBias = True
            self.log.info("Running with bias correction")

        if dark:
            isrConfig.doDark = True
            self.log.info("Running with dark correction")

        if flat:
            isrConfig.doFlat = True
            self.log.info("Running with flat correction")

        # TODO: deal with fringes here
        if defects:
            isrConfig.doDefect = True
            self.log.info("Running with defect correction")

        if linearizer:
            isrConfig.doLinearize = True
            self.log.info("Running with linearity correction")

        if crosstalk:
            isrConfig.doCrosstalk = True
            self.log.info("Running with crosstalk correction")

        if bfKernel:
            isrConfig.doBrighterFatter = True
            self.log.info("Running with brighter-fatter correction")

        if ptc:
            isrConfig.usePtcGains = True
            self.log.info("Running with ptc correction")

        isrConfig.doWrite = False
        isrTask = IsrTask(config=isrConfig)
        result = isrTask.run(
            ccdExposure,
            camera=camera,
            bias=bias,
            dark=dark,
            flat=flat,
            #  fringes=pipeBase.Struct(fringes=None),
            defects=defects,
            linearizer=linearizer,
            crosstalk=crosstalk,
            bfKernel=bfKernel,
            bfGains=bfGains,
            ptc=ptc,
            crosstalkSources=crosstalkSources,
            isGen3=True,
        )

        postIsr = result.exposure

        if self.config.doRepairCosmics:
            try:  # can fail due to too many CRs detected, and we always want an exposure back
                self.log.info("Repairing cosmics...")
                if postIsr.getPsf() is None:
                    installPsfTask = InstallGaussianPsfTask()
                    installPsfTask.run(postIsr)

                # TODO: try adding a reasonably wide Gaussian as a temp PSF
                # and then just running repairTask on its own without any
                # imChar. It should work, and be faster.
                repairConfig = CharacterizeImageTask.ConfigClass()
                repairConfig.doMeasurePsf = False
                repairConfig.doApCorr = False
                repairConfig.doDeblend = False
                repairConfig.doWrite = False
                repairConfig.repair.cosmicray.nCrPixelMax = 200000
                repairTask = CharacterizeImageTask(config=repairConfig)

                repairTask.repair.run(postIsr)
            except Exception as e:
                self.log.warning(f"During CR repair caught: {e}")

        # exposure is returned for convenience to mimic isrTask's API.
        return pipeBase.Struct(exposure=postIsr, outputExposure=postIsr)