555 Mask SATURATED and SUSPECT pixels and check if any amplifiers
560 badAmpDict : `str` [`bool`]
561 Dictionary of amplifiers, keyed by name, value is True if
562 amplifier is fully masked.
563 ccdExposure : `lsst.afw.image.Exposure`
564 Input exposure to be masked.
565 detector : `lsst.afw.cameraGeom.Detector`
567 defects : `lsst.ip.isr.Defects`
568 List of defects. Used to determine if an entire
573 badAmpDict : `str`[`bool`]
574 Dictionary of amplifiers, keyed by name.
576 maskedImage = ccdExposure.getMaskedImage()
579 ampName = amp.getName()
581 if badAmpDict[ampName]:
587 if self.config.doSaturation:
588 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
589 if self.config.doSuspect:
590 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
591 if math.isfinite(self.config.saturation):
592 limits.update({self.config.saturatedMaskName: self.config.saturation})
594 for maskName, maskThreshold
in limits.items():
595 if not math.isnan(maskThreshold):
596 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
597 isrFunctions.makeThresholdMask(
598 maskedImage=dataView,
599 threshold=maskThreshold,
606 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
608 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
609 self.config.suspectMaskName])
610 if numpy.all(maskView.getArray() & maskVal > 0):
611 self.log.warning(
"Amplifier %s is bad (completely SATURATED or SUSPECT)", ampName)
612 badAmpDict[ampName] =
True
613 maskView |= maskView.getPlaneBitMask(
"BAD")
618 """Apply serial overscan correction in place to all amps.
620 The actual overscan subtraction is performed by the
621 `lsst.ip.isr.overscan.OverscanTask`, which is called here.
626 Must be `SERIAL` or `PARALLEL`.
627 detectorConfig : `lsst.ip.isr.OverscanDetectorConfig`
628 Per-amplifier configurations.
629 detector : `lsst.afw.cameraGeom.Detector`
632 Dictionary of amp name to whether it is a bad amp.
633 ccdExposure : `lsst.afw.image.Exposure`
634 Exposure to have overscan correction performed.
638 overscans : `list` [`lsst.pipe.base.Struct` or None]
639 Each result struct has components:
642 Value or fit subtracted from the amplifier image data.
643 (scalar or `lsst.afw.image.Image`)
645 Value or fit subtracted from the overscan image data.
646 (scalar or `lsst.afw.image.Image`)
648 Image of the overscan region with the overscan
649 correction applied. This quantity is used to estimate
650 the amplifier read noise empirically.
651 (`lsst.afw.image.Image`)
653 Mean overscan fit value. (`float`)
655 Median overscan fit value. (`float`)
657 Clipped standard deviation of the overscan fit. (`float`)
659 Mean of the overscan after fit subtraction. (`float`)
661 Median of the overscan after fit subtraction. (`float`)
663 Clipped standard deviation of the overscan after fit
664 subtraction. (`float`)
668 lsst.ip.isr.overscan.OverscanTask
670 if mode
not in [
"SERIAL",
"PARALLEL"]:
671 raise ValueError(
"Mode must be SERIAL or PARALLEL")
676 for i, amp
in enumerate(detector):
677 ampName = amp.getName()
679 ampConfig = detectorConfig.getOverscanAmpConfig(ampName)
681 if mode ==
"SERIAL" and not ampConfig.doSerialOverscan:
683 "ISR_OSCAN: Amplifier %s/%s configured to skip serial overscan.",
688 elif mode ==
"PARALLEL" and not ampConfig.doParallelOverscan:
690 "ISR_OSCAN: Amplifier %s configured to skip parallel overscan.",
695 elif badAmpDict[ampName]
or not ccdExposure.getBBox().contains(amp.getBBox()):
702 if amp.getRawHorizontalOverscanBBox().isEmpty():
704 "ISR_OSCAN: No overscan region for amp %s. Not performing overscan correction.",
713 results = serialOverscan.run(ccdExposure, amp)
716 config=ampConfig.parallelOverscanConfig,
718 results = parallelOverscan.run(ccdExposure, amp)
720 metadata = ccdExposure.getMetadata()
721 keyBase =
"LSST ISR OVERSCAN"
722 metadata[f
"{keyBase} {mode} MEAN {ampName}"] = results.overscanMean
723 metadata[f
"{keyBase} {mode} MEDIAN {ampName}"] = results.overscanMedian
724 metadata[f
"{keyBase} {mode} STDEV {ampName}"] = results.overscanSigma
726 metadata[f
"{keyBase} RESIDUAL {mode} MEAN {ampName}"] = results.residualMean
727 metadata[f
"{keyBase} RESIDUAL {mode} MEDIAN {ampName}"] = results.residualMedian
728 metadata[f
"{keyBase} RESIDUAL {mode} STDEV {ampName}"] = results.residualSigma
730 overscans[i] = results
733 ccdExposure.getMetadata().set(
"OVERSCAN",
"Overscan corrected")
1004 interpExp = ccdExposure.clone()
1006 isrFunctions.interpolateFromMask(
1007 maskedImage=interpExp.getMaskedImage(),
1008 fwhm=self.config.brighterFatterFwhmForInterpolation,
1009 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1010 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1012 bfExp = interpExp.clone()
1013 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1014 type(bfKernel), type(bfGains))
1015 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1016 self.config.brighterFatterMaxIter,
1017 self.config.brighterFatterThreshold,
1018 self.config.brighterFatterApplyGain,
1020 if bfResults[1] == self.config.brighterFatterMaxIter:
1021 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1024 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1027 image = ccdExposure.getMaskedImage().getImage()
1028 bfCorr = bfExp.getMaskedImage().getImage()
1029 bfCorr -= interpExp.getMaskedImage().getImage()
1038 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1039 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1042 if self.config.brighterFatterMaskGrowSize > 0:
1043 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1044 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1045 isrFunctions.growMasks(ccdExposure.getMask(),
1046 radius=self.config.brighterFatterMaskGrowSize,
1047 maskNameList=maskPlane,
1048 maskValue=maskPlane)
1160 def run(self, *, ccdExposure, dnlLUT=None, bias=None, deferredChargeCalib=None, linearizer=None,
1161 ptc=None, crosstalk=None, defects=None, bfKernel=None, bfGains=None, dark=None,
1162 flat=None, camera=None, **kwargs
1165 detector = ccdExposure.getDetector()
1167 overscanDetectorConfig = self.config.overscanCamera.getOverscanDetectorConfig(detector)
1169 if self.config.doHeaderProvenance:
1172 exposureMetadata = ccdExposure.getMetadata()
1173 exposureMetadata[
"LSST CALIB OVERSCAN HASH"] = overscanDetectorConfig.md5
1175 if self.config.doDiffNonLinearCorrection:
1177 if self.config.doBias:
1179 if self.config.doDeferredCharge:
1180 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1182 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1183 if self.config.doCrosstalk
or overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1184 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1185 if self.config.doDefect:
1186 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1187 if self.config.doBrighterFatter:
1189 if self.config.doDark:
1195 if self.config.doDiffNonLinearCorrection:
1198 if overscanDetectorConfig.doAnySerialOverscan:
1202 overscanDetectorConfig,
1208 serialOverscans = [
None]*len(detector)
1210 if overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1216 crosstalk=crosstalk,
1218 parallelOverscanRegion=
True,
1219 detectorConfig=overscanDetectorConfig,
1227 if overscanDetectorConfig.doAnyParallelOverscan:
1232 overscanDetectorConfig,
1238 if self.config.doAssembleCcd:
1240 self.log.info(
"Assembling CCD from amplifiers.")
1241 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1243 if self.config.expectWcs
and not ccdExposure.getWcs():
1244 self.log.warning(
"No WCS found in input exposure.")
1246 if self.config.doLinearize:
1248 self.log.info(
"Applying linearizer.")
1250 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1251 detector=detector, log=self.log)
1253 if self.config.doCrosstalk:
1255 self.log.info(
"Applying crosstalk correction.")
1256 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk)
1258 if self.config.doBias:
1260 self.log.info(
"Applying bias correction.")
1261 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage())
1263 if self.config.doGainNormalize:
1269 if self.config.doDeferredCharge:
1271 self.log.info(
"Applying deferred charge/CTI correction.")
1272 self.deferredChargeCorrection.
run(ccdExposure, deferredChargeCalib)
1274 if self.config.doVariance:
1280 if self.config.doDefect:
1282 self.log.info(
"Applying defects masking.")
1285 if self.config.doNanMasking:
1286 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1289 if self.config.doWidenSaturationTrails:
1290 self.log.info(
"Widening saturation trails.")
1291 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1294 if self.config.doSaveInterpPixels:
1295 preInterpExp = ccdExposure.clone()
1297 if self.config.doSetBadRegions:
1298 self.log.info(
'Counting pixels in BAD regions.')
1301 if self.config.doInterpolate:
1302 self.log.info(
"Interpolating masked pixels.")
1303 isrFunctions.interpolateFromMask(
1304 maskedImage=ccdExposure.getMaskedImage(),
1305 fwhm=self.config.brighterFatterFwhmForInterpolation,
1306 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1307 maskNameList=list(self.config.maskListToInterpolate)
1310 if self.config.doDark:
1312 self.log.info(
"Applying dark subtraction.")
1315 if self.config.doBrighterFatter:
1317 self.log.info(
"Applying Bright-Fatter kernels.")
1322 if self.config.doStandardStatistics:
1323 metadata = ccdExposure.getMetadata()
1324 for amp
in detector:
1325 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1326 ampName = amp.getName()
1327 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1328 ampExposure.getMaskedImage(),
1329 [self.config.saturatedMaskName]
1331 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1332 ampExposure.getMaskedImage(),
1335 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1336 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1338 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1339 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1340 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1342 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1343 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1344 if overscanDetectorConfig.doAnySerialOverscan
and k1
in metadata
and k2
in metadata:
1345 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1347 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1350 outputStatistics =
None
1351 if self.config.doCalculateStatistics:
1352 outputStatistics = self.isrStats.
run(ccdExposure, overscanResults=serialOverscans,
1356 outputBin1Exposure =
None
1357 outputBin2Exposure =
None
1358 if self.config.doBinnedExposures:
1359 outputBin1Exposure, outputBin2Exposure = self.
makeBinnedImages(ccdExposure)
1361 return pipeBase.Struct(
1362 exposure=ccdExposure,
1364 outputBin1Exposure=outputBin1Exposure,
1365 outputBin2Exposure=outputBin2Exposure,
1367 preInterpExposure=preInterpExp,
1368 outputExposure=ccdExposure,
1369 outputStatistics=outputStatistics,