547 def run(self, exposure, ctiCalib, gains=None):
548 """Correct deferred charge/CTI issues.
552 exposure : `lsst.afw.image.Exposure`
553 Exposure to correct the deferred charge on.
554 ctiCalib : `lsst.ip.isr.DeferredChargeCalib`
555 Calibration object containing the charge transfer
557 gains : `dict` [`str`, `float`]
558 A dictionary, keyed by amplifier name, of the gains to
559 use. If gains is None, the nominal gains in the amplifier
564 exposure : `lsst.afw.image.Exposure`
565 The corrected exposure.
567 image = exposure.getMaskedImage().image
568 detector = exposure.getDetector()
575 if "USEGAINS" in ctiCalib.getMetadata().keys():
576 useGains = ctiCalib.getMetadata()[
"USEGAINS"]
577 self.log.info(f
"useGains = {useGains} from calibration metadata.")
579 useGains = self.config.useGains
580 self.log.info(f
"USEGAINS not found in calibration metadata. Using {useGains} from config.")
584 gains = {amp.getName(): amp.getGain()
for amp
in detector.getAmplifiers()}
586 with gainContext(exposure, image, useGains, gains):
587 for amp
in detector.getAmplifiers():
588 ampName = amp.getName()
590 ampImage = image[amp.getRawBBox()]
591 if self.config.zeroUnusedPixels:
594 ampImage[amp.getRawParallelOverscanBBox()].array[:, :] = 0.0
595 ampImage[amp.getRawSerialPrescanBBox()].array[:, :] = 0.0
600 ampData = self.
flipData(ampImage.array, amp)
602 if ctiCalib.driftScale[ampName] > 0.0:
604 ctiCalib.driftScale[ampName],
605 ctiCalib.decayTime[ampName],
606 self.config.nPixelOffsetCorrection)
608 correctedAmpData = ampData.copy()
611 ctiCalib.serialTraps[ampName],
612 ctiCalib.globalCti[ampName],
613 self.config.nPixelTrapCorrection)
616 correctedAmpData = self.
flipData(correctedAmpData, amp)
617 image[amp.getRawBBox()].array[:, :] = correctedAmpData[:, :]
623 """Flip data array such that readout corner is at lower-left.
627 ampData : `numpy.ndarray`, (nx, ny)
629 amp : `lsst.afw.cameraGeom.Amplifier`
630 Amplifier to get readout corner information.
634 ampData : `numpy.ndarray`, (nx, ny)
637 X_FLIP = {ReadoutCorner.LL:
False,
638 ReadoutCorner.LR:
True,
639 ReadoutCorner.UL:
False,
640 ReadoutCorner.UR:
True}
641 Y_FLIP = {ReadoutCorner.LL:
False,
642 ReadoutCorner.LR:
False,
643 ReadoutCorner.UL:
True,
644 ReadoutCorner.UR:
True}
646 if X_FLIP[amp.getReadoutCorner()]:
647 ampData = np.fliplr(ampData)
648 if Y_FLIP[amp.getReadoutCorner()]:
649 ampData = np.flipud(ampData)
655 r"""Remove CTI effects from local offsets.
657 This implements equation 10 of Snyder+21. For an image with
658 CTI, s'(m, n), the correction factor is equal to the maximum
663 {A_L s'(m, n - j) exp(-j t / \tau_L)}_j=0^jmax
667 inputArr : `numpy.ndarray`, (nx, ny)
668 Input image data to correct.
669 drift_scale : `float`
670 Drift scale (Snyder+21 A_L value) to use in correction.
672 Decay time (Snyder+21 \tau_L) of the correction.
673 num_previous_pixels : `int`, optional
674 Number of previous pixels to use for correction. As the
675 CTI has an exponential decay, this essentially truncates
676 the correction where that decay scales the input charge to
681 outputArr : `numpy.ndarray`, (nx, ny)
682 Corrected image data.
684 r = np.exp(-1/decay_time)
685 Ny, Nx = inputArr.shape
688 offset = np.zeros((num_previous_pixels, Ny, Nx))
689 offset[0, :, :] = drift_scale*np.maximum(0, inputArr)
692 for n
in range(1, num_previous_pixels):
693 offset[n, :, n:] = drift_scale*np.maximum(0, inputArr[:, :-n])*(r**n)
695 Linv = np.amax(offset, axis=0)
696 outputArr = inputArr - Linv
702 r"""Apply localized trapping inverse operator to pixel signals.
704 This implements equation 13 of Snyder+21. For an image with
705 CTI, s'(m, n), the correction factor is equal to the maximum
710 {A_L s'(m, n - j) exp(-j t / \tau_L)}_j=0^jmax
714 inputArr : `numpy.ndarray`, (nx, ny)
715 Input image data to correct.
716 trap : `lsst.ip.isr.SerialTrap`
717 Serial trap describing the capture and release of charge.
719 Mean charge transfer inefficiency, b from Snyder+21.
720 num_previous_pixels : `int`, optional
721 Number of previous pixels to use for correction.
725 outputArr : `numpy.ndarray`, (nx, ny)
726 Corrected image data.
729 Ny, Nx = inputArr.shape
731 r = np.exp(-1/trap.emission_time)
734 trap_occupancy = np.zeros((num_previous_pixels, Ny, Nx))
735 for n
in range(num_previous_pixels):
736 trap_occupancy[n, :, n+1:] = trap.capture(np.maximum(0, inputArr))[:, :-(n+1)]*(r**n)
737 trap_occupancy = np.amax(trap_occupancy, axis=0)
740 C = trap.capture(np.maximum(0, inputArr)) - trap_occupancy*r
744 R = np.zeros(inputArr.shape)
745 R[:, 1:] = trap_occupancy[:, 1:]*(1-r)
748 outputArr = inputArr - a*T