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:
589 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
591 if math.isfinite(self.config.saturation):
592 limits.update({self.config.saturatedMaskName: self.config.saturation})
593 if self.config.doSuspect:
594 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
596 for maskName, maskThreshold
in limits.items():
597 if not math.isnan(maskThreshold):
598 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
599 isrFunctions.makeThresholdMask(
600 maskedImage=dataView,
601 threshold=maskThreshold,
608 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
610 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
611 self.config.suspectMaskName])
612 if numpy.all(maskView.getArray() & maskVal > 0):
613 self.log.warning(
"Amplifier %s is bad (completely SATURATED or SUSPECT)", ampName)
614 badAmpDict[ampName] =
True
615 maskView |= maskView.getPlaneBitMask(
"BAD")
620 """Apply serial overscan correction in place to all amps.
622 The actual overscan subtraction is performed by the
623 `lsst.ip.isr.overscan.OverscanTask`, which is called here.
628 Must be `SERIAL` or `PARALLEL`.
629 detectorConfig : `lsst.ip.isr.OverscanDetectorConfig`
630 Per-amplifier configurations.
631 detector : `lsst.afw.cameraGeom.Detector`
634 Dictionary of amp name to whether it is a bad amp.
635 ccdExposure : `lsst.afw.image.Exposure`
636 Exposure to have overscan correction performed.
640 overscans : `list` [`lsst.pipe.base.Struct` or None]
641 Each result struct has components:
644 Value or fit subtracted from the amplifier image data.
645 (scalar or `lsst.afw.image.Image`)
647 Value or fit subtracted from the overscan image data.
648 (scalar or `lsst.afw.image.Image`)
650 Image of the overscan region with the overscan
651 correction applied. This quantity is used to estimate
652 the amplifier read noise empirically.
653 (`lsst.afw.image.Image`)
655 Mean overscan fit value. (`float`)
657 Median overscan fit value. (`float`)
659 Clipped standard deviation of the overscan fit. (`float`)
661 Mean of the overscan after fit subtraction. (`float`)
663 Median of the overscan after fit subtraction. (`float`)
665 Clipped standard deviation of the overscan after fit
666 subtraction. (`float`)
670 lsst.ip.isr.overscan.OverscanTask
672 if mode
not in [
"SERIAL",
"PARALLEL"]:
673 raise ValueError(
"Mode must be SERIAL or PARALLEL")
678 for i, amp
in enumerate(detector):
679 ampName = amp.getName()
681 ampConfig = detectorConfig.getOverscanAmpConfig(ampName)
683 if mode ==
"SERIAL" and not ampConfig.doSerialOverscan:
685 "ISR_OSCAN: Amplifier %s/%s configured to skip serial overscan.",
690 elif mode ==
"PARALLEL" and not ampConfig.doParallelOverscan:
692 "ISR_OSCAN: Amplifier %s configured to skip parallel overscan.",
697 elif badAmpDict[ampName]
or not ccdExposure.getBBox().contains(amp.getBBox()):
704 if amp.getRawHorizontalOverscanBBox().isEmpty():
706 "ISR_OSCAN: No overscan region for amp %s. Not performing overscan correction.",
715 results = serialOverscan.run(ccdExposure, amp)
718 config=ampConfig.parallelOverscanConfig,
720 results = parallelOverscan.run(ccdExposure, amp)
722 metadata = ccdExposure.getMetadata()
723 keyBase =
"LSST ISR OVERSCAN"
724 metadata[f
"{keyBase} {mode} MEAN {ampName}"] = results.overscanMean
725 metadata[f
"{keyBase} {mode} MEDIAN {ampName}"] = results.overscanMedian
726 metadata[f
"{keyBase} {mode} STDEV {ampName}"] = results.overscanSigma
728 metadata[f
"{keyBase} RESIDUAL {mode} MEAN {ampName}"] = results.residualMean
729 metadata[f
"{keyBase} RESIDUAL {mode} MEDIAN {ampName}"] = results.residualMedian
730 metadata[f
"{keyBase} RESIDUAL {mode} STDEV {ampName}"] = results.residualSigma
732 overscans[i] = results
735 ccdExposure.getMetadata().set(
"OVERSCAN",
"Overscan corrected")
1006 interpExp = ccdExposure.clone()
1008 isrFunctions.interpolateFromMask(
1009 maskedImage=interpExp.getMaskedImage(),
1010 fwhm=self.config.brighterFatterFwhmForInterpolation,
1011 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1012 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1014 bfExp = interpExp.clone()
1015 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1016 type(bfKernel), type(bfGains))
1017 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1018 self.config.brighterFatterMaxIter,
1019 self.config.brighterFatterThreshold,
1020 self.config.brighterFatterApplyGain,
1022 if bfResults[1] == self.config.brighterFatterMaxIter:
1023 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1026 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1029 image = ccdExposure.getMaskedImage().getImage()
1030 bfCorr = bfExp.getMaskedImage().getImage()
1031 bfCorr -= interpExp.getMaskedImage().getImage()
1040 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1041 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1044 if self.config.brighterFatterMaskGrowSize > 0:
1045 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1046 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1047 isrFunctions.growMasks(ccdExposure.getMask(),
1048 radius=self.config.brighterFatterMaskGrowSize,
1049 maskNameList=maskPlane,
1050 maskValue=maskPlane)
1162 def run(self, *, ccdExposure, dnlLUT=None, bias=None, deferredChargeCalib=None, linearizer=None,
1163 ptc=None, crosstalk=None, defects=None, bfKernel=None, bfGains=None, dark=None,
1164 flat=None, camera=None, **kwargs
1167 detector = ccdExposure.getDetector()
1169 overscanDetectorConfig = self.config.overscanCamera.getOverscanDetectorConfig(detector)
1171 if self.config.doHeaderProvenance:
1174 exposureMetadata = ccdExposure.getMetadata()
1175 exposureMetadata[
"LSST CALIB OVERSCAN HASH"] = overscanDetectorConfig.md5
1177 if self.config.doDiffNonLinearCorrection:
1179 if self.config.doBias:
1181 if self.config.doDeferredCharge:
1182 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1184 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1185 if self.config.doCrosstalk
or overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1186 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1187 if self.config.doDefect:
1188 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1189 if self.config.doBrighterFatter:
1191 if self.config.doDark:
1197 if self.config.doDiffNonLinearCorrection:
1200 if overscanDetectorConfig.doAnySerialOverscan:
1204 overscanDetectorConfig,
1210 serialOverscans = [
None]*len(detector)
1212 if overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1218 crosstalk=crosstalk,
1220 parallelOverscanRegion=
True,
1221 detectorConfig=overscanDetectorConfig,
1229 if overscanDetectorConfig.doAnyParallelOverscan:
1234 overscanDetectorConfig,
1240 if self.config.doAssembleCcd:
1242 self.log.info(
"Assembling CCD from amplifiers.")
1243 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1245 if self.config.expectWcs
and not ccdExposure.getWcs():
1246 self.log.warning(
"No WCS found in input exposure.")
1248 if self.config.doLinearize:
1250 self.log.info(
"Applying linearizer.")
1252 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1253 detector=detector, log=self.log)
1255 if self.config.doCrosstalk:
1257 self.log.info(
"Applying crosstalk correction.")
1258 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk)
1260 if self.config.doBias:
1262 self.log.info(
"Applying bias correction.")
1263 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage())
1265 if self.config.doGainNormalize:
1271 if self.config.doDeferredCharge:
1273 self.log.info(
"Applying deferred charge/CTI correction.")
1274 self.deferredChargeCorrection.
run(ccdExposure, deferredChargeCalib)
1276 if self.config.doVariance:
1282 if self.config.doDefect:
1284 self.log.info(
"Applying defects masking.")
1287 if self.config.doNanMasking:
1288 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1291 if self.config.doWidenSaturationTrails:
1292 self.log.info(
"Widening saturation trails.")
1293 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1296 if self.config.doSaveInterpPixels:
1297 preInterpExp = ccdExposure.clone()
1299 if self.config.doSetBadRegions:
1300 self.log.info(
'Counting pixels in BAD regions.')
1303 if self.config.doInterpolate:
1304 self.log.info(
"Interpolating masked pixels.")
1305 isrFunctions.interpolateFromMask(
1306 maskedImage=ccdExposure.getMaskedImage(),
1307 fwhm=self.config.brighterFatterFwhmForInterpolation,
1308 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1309 maskNameList=list(self.config.maskListToInterpolate)
1312 if self.config.doDark:
1314 self.log.info(
"Applying dark subtraction.")
1317 if self.config.doBrighterFatter:
1319 self.log.info(
"Applying Bright-Fatter kernels.")
1324 if self.config.doStandardStatistics:
1325 metadata = ccdExposure.getMetadata()
1326 for amp
in detector:
1327 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1328 ampName = amp.getName()
1329 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1330 ampExposure.getMaskedImage(),
1331 [self.config.saturatedMaskName]
1333 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1334 ampExposure.getMaskedImage(),
1337 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1338 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1340 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1341 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1342 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1344 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1345 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1346 if overscanDetectorConfig.doAnySerialOverscan
and k1
in metadata
and k2
in metadata:
1347 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1349 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1352 outputStatistics =
None
1353 if self.config.doCalculateStatistics:
1354 outputStatistics = self.isrStats.
run(ccdExposure, overscanResults=serialOverscans,
1358 outputBin1Exposure =
None
1359 outputBin2Exposure =
None
1360 if self.config.doBinnedExposures:
1361 outputBin1Exposure, outputBin2Exposure = self.
makeBinnedImages(ccdExposure)
1363 return pipeBase.Struct(
1364 exposure=ccdExposure,
1366 outputBin1Exposure=outputBin1Exposure,
1367 outputBin2Exposure=outputBin2Exposure,
1369 preInterpExposure=preInterpExp,
1370 outputExposure=ccdExposure,
1371 outputStatistics=outputStatistics,