Beispiel #1
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 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, )
    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
Beispiel #4
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,
        )
Beispiel #5
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)