582 Mask SATURATED and SUSPECT pixels and check if any amplifiers
587 badAmpDict : `str` [`bool`]
588 Dictionary of amplifiers, keyed by name, value is True if
589 amplifier is fully masked.
590 ccdExposure : `lsst.afw.image.Exposure`
591 Input exposure to be masked.
592 detector : `lsst.afw.cameraGeom.Detector`
594 defects : `lsst.ip.isr.Defects`
595 List of defects. Used to determine if an entire
600 badAmpDict : `str`[`bool`]
601 Dictionary of amplifiers, keyed by name.
603 maskedImage = ccdExposure.getMaskedImage()
606 ampName = amp.getName()
608 if badAmpDict[ampName]:
614 if self.config.doSaturation:
616 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
618 if math.isfinite(self.config.saturation):
619 limits.update({self.config.saturatedMaskName: self.config.saturation})
620 if self.config.doSuspect:
621 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
623 for maskName, maskThreshold
in limits.items():
624 if not math.isnan(maskThreshold):
625 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
626 isrFunctions.makeThresholdMask(
627 maskedImage=dataView,
628 threshold=maskThreshold,
635 maskView =
afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
637 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
638 self.config.suspectMaskName])
639 if numpy.all(maskView.getArray() & maskVal > 0):
640 self.log.warning(
"Amplifier %s is bad (completely SATURATED or SUSPECT)", ampName)
641 badAmpDict[ampName] =
True
642 maskView |= maskView.getPlaneBitMask(
"BAD")
647 """Apply serial overscan correction in place to all amps.
649 The actual overscan subtraction is performed by the
650 `lsst.ip.isr.overscan.OverscanTask`, which is called here.
655 Must be `SERIAL` or `PARALLEL`.
656 detectorConfig : `lsst.ip.isr.OverscanDetectorConfig`
657 Per-amplifier configurations.
658 detector : `lsst.afw.cameraGeom.Detector`
661 Dictionary of amp name to whether it is a bad amp.
662 ccdExposure : `lsst.afw.image.Exposure`
663 Exposure to have overscan correction performed.
667 overscans : `list` [`lsst.pipe.base.Struct` or None]
668 Each result struct has components:
671 Value or fit subtracted from the amplifier image data.
672 (scalar or `lsst.afw.image.Image`)
674 Value or fit subtracted from the overscan image data.
675 (scalar or `lsst.afw.image.Image`)
677 Image of the overscan region with the overscan
678 correction applied. This quantity is used to estimate
679 the amplifier read noise empirically.
680 (`lsst.afw.image.Image`)
682 Mean overscan fit value. (`float`)
684 Median overscan fit value. (`float`)
686 Clipped standard deviation of the overscan fit. (`float`)
688 Mean of the overscan after fit subtraction. (`float`)
690 Median of the overscan after fit subtraction. (`float`)
692 Clipped standard deviation of the overscan after fit
693 subtraction. (`float`)
697 lsst.ip.isr.overscan.OverscanTask
699 if mode
not in [
"SERIAL",
"PARALLEL"]:
700 raise ValueError(
"Mode must be SERIAL or PARALLEL")
705 for i, amp
in enumerate(detector):
706 ampName = amp.getName()
708 ampConfig = detectorConfig.getOverscanAmpConfig(ampName)
710 if mode ==
"SERIAL" and not ampConfig.doSerialOverscan:
712 "ISR_OSCAN: Amplifier %s/%s configured to skip serial overscan.",
717 elif mode ==
"PARALLEL" and not ampConfig.doParallelOverscan:
719 "ISR_OSCAN: Amplifier %s configured to skip parallel overscan.",
724 elif badAmpDict[ampName]
or not ccdExposure.getBBox().contains(amp.getBBox()):
731 if amp.getRawHorizontalOverscanBBox().isEmpty():
733 "ISR_OSCAN: No overscan region for amp %s. Not performing overscan correction.",
742 results = serialOverscan.run(ccdExposure, amp)
745 config=ampConfig.parallelOverscanConfig,
747 results = parallelOverscan.run(ccdExposure, amp)
749 metadata = ccdExposure.getMetadata()
750 keyBase =
"LSST ISR OVERSCAN"
751 metadata[f
"{keyBase} {mode} MEAN {ampName}"] = results.overscanMean
752 metadata[f
"{keyBase} {mode} MEDIAN {ampName}"] = results.overscanMedian
753 metadata[f
"{keyBase} {mode} STDEV {ampName}"] = results.overscanSigma
755 metadata[f
"{keyBase} RESIDUAL {mode} MEAN {ampName}"] = results.residualMean
756 metadata[f
"{keyBase} RESIDUAL {mode} MEDIAN {ampName}"] = results.residualMedian
757 metadata[f
"{keyBase} RESIDUAL {mode} STDEV {ampName}"] = results.residualSigma
759 overscans[i] = results
762 ccdExposure.getMetadata().
set(
"OVERSCAN",
"Overscan corrected")
1033 interpExp = ccdExposure.clone()
1035 isrFunctions.interpolateFromMask(
1036 maskedImage=interpExp.getMaskedImage(),
1037 fwhm=self.config.brighterFatterFwhmForInterpolation,
1038 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1039 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1041 bfExp = interpExp.clone()
1042 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1043 type(bfKernel), type(bfGains))
1044 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1045 self.config.brighterFatterMaxIter,
1046 self.config.brighterFatterThreshold,
1047 self.config.brighterFatterApplyGain,
1049 if bfResults[1] == self.config.brighterFatterMaxIter:
1050 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1053 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1056 image = ccdExposure.getMaskedImage().getImage()
1057 bfCorr = bfExp.getMaskedImage().getImage()
1058 bfCorr -= interpExp.getMaskedImage().getImage()
1067 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1068 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1071 if self.config.brighterFatterMaskGrowSize > 0:
1072 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1073 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1074 isrFunctions.growMasks(ccdExposure.getMask(),
1075 radius=self.config.brighterFatterMaskGrowSize,
1076 maskNameList=maskPlane,
1077 maskValue=maskPlane)
1213 def run(self, *, ccdExposure, dnlLUT=None, bias=None, deferredChargeCalib=None, linearizer=None,
1214 ptc=None, crosstalk=None, defects=None, bfKernel=None, bfGains=None, dark=None,
1215 flat=None, camera=None, **kwargs
1218 detector = ccdExposure.getDetector()
1220 overscanDetectorConfig = self.config.overscanCamera.getOverscanDetectorConfig(detector)
1224 if self.config.doHeaderProvenance:
1227 exposureMetadata = ccdExposure.getMetadata()
1228 exposureMetadata[
"LSST CALIB OVERSCAN HASH"] = overscanDetectorConfig.md5
1230 if self.config.doDiffNonLinearCorrection:
1232 if self.config.doBias:
1234 if self.config.doDeferredCharge:
1235 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1237 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1238 if self.config.doCrosstalk
or overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1239 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1240 if self.config.doDefect:
1241 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1242 if self.config.doBrighterFatter:
1244 if self.config.doDark:
1246 if self.config.doFlat:
1252 if self.config.doDiffNonLinearCorrection:
1255 if overscanDetectorConfig.doAnySerialOverscan:
1259 overscanDetectorConfig,
1265 serialOverscans = [
None]*len(detector)
1267 if overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1273 crosstalk=crosstalk,
1275 parallelOverscanRegion=
True,
1276 detectorConfig=overscanDetectorConfig,
1284 if overscanDetectorConfig.doAnyParallelOverscan:
1289 overscanDetectorConfig,
1295 if self.config.doAssembleCcd:
1297 self.log.info(
"Assembling CCD from amplifiers.")
1298 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1300 if self.config.expectWcs
and not ccdExposure.getWcs():
1301 self.log.warning(
"No WCS found in input exposure.")
1303 if self.config.doLinearize:
1305 self.log.info(
"Applying linearizer.")
1307 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1308 detector=detector, log=self.log)
1310 if self.config.doCrosstalk:
1312 self.log.info(
"Applying crosstalk correction.")
1313 self.crosstalk.run(ccdExposure, crosstalk=crosstalk)
1315 if self.config.doBias:
1317 self.log.info(
"Applying bias correction.")
1318 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage())
1320 if self.config.doGainsCorrection:
1322 self.log.info(
"Apply temperature dependence to the gains.")
1325 if self.config.doApplyGains:
1328 self.log.info(
"Apply PTC gains (temperature corrected or not) to the image.")
1329 isrFunctions.applyGains(ccdExposure, normalizeGains=
False, ptcGains=gains)
1331 if self.config.doDeferredCharge:
1333 self.log.info(
"Applying deferred charge/CTI correction.")
1334 self.deferredChargeCorrection.run(ccdExposure, deferredChargeCalib)
1336 if self.config.doVariance:
1342 if self.config.doDefect:
1344 self.log.info(
"Applying defects masking.")
1347 if self.config.doNanMasking:
1348 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1351 if self.config.doWidenSaturationTrails:
1352 self.log.info(
"Widening saturation trails.")
1353 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1356 if self.config.doSaveInterpPixels:
1357 preInterpExp = ccdExposure.clone()
1359 if self.config.doSetBadRegions:
1360 self.log.info(
'Counting pixels in BAD regions.')
1363 if self.config.doInterpolate:
1364 self.log.info(
"Interpolating masked pixels.")
1365 isrFunctions.interpolateFromMask(
1366 maskedImage=ccdExposure.getMaskedImage(),
1367 fwhm=self.config.brighterFatterFwhmForInterpolation,
1368 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1369 maskNameList=list(self.config.maskListToInterpolate)
1372 if self.config.doDark:
1374 self.log.info(
"Applying dark subtraction.")
1377 if self.config.doBrighterFatter:
1379 self.log.info(
"Applying Bright-Fatter kernels.")
1383 if self.config.doFlat:
1385 self.log.info(
"Applying flat correction.")
1391 if self.config.doStandardStatistics:
1392 metadata = ccdExposure.getMetadata()
1393 for amp
in detector:
1394 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1395 ampName = amp.getName()
1396 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1397 ampExposure.getMaskedImage(),
1398 [self.config.saturatedMaskName]
1400 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1401 ampExposure.getMaskedImage(),
1405 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1407 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1408 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1409 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1411 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1412 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1413 if overscanDetectorConfig.doAnySerialOverscan
and k1
in metadata
and k2
in metadata:
1414 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1416 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1419 outputStatistics =
None
1420 if self.config.doCalculateStatistics:
1421 outputStatistics = self.isrStats.run(ccdExposure, overscanResults=serialOverscans,
1425 outputBin1Exposure =
None
1426 outputBin2Exposure =
None
1427 if self.config.doBinnedExposures:
1428 outputBin1Exposure, outputBin2Exposure = self.
makeBinnedImages(ccdExposure)
1430 return pipeBase.Struct(
1431 exposure=ccdExposure,
1433 outputBin1Exposure=outputBin1Exposure,
1434 outputBin2Exposure=outputBin2Exposure,
1436 preInterpExposure=preInterpExp,
1437 outputExposure=ccdExposure,
1438 outputStatistics=outputStatistics,