599 Mask SATURATED and SUSPECT pixels and check if any amplifiers
604 badAmpDict : `str` [`bool`]
605 Dictionary of amplifiers, keyed by name, value is True if
606 amplifier is fully masked.
607 ccdExposure : `lsst.afw.image.Exposure`
608 Input exposure to be masked.
609 detector : `lsst.afw.cameraGeom.Detector`
611 defects : `lsst.ip.isr.Defects`
612 List of defects. Used to determine if an entire
617 badAmpDict : `str`[`bool`]
618 Dictionary of amplifiers, keyed by name.
620 maskedImage = ccdExposure.getMaskedImage()
623 ampName = amp.getName()
625 if badAmpDict[ampName]:
631 if self.config.doSaturation:
633 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
635 if math.isfinite(self.config.saturation):
636 limits.update({self.config.saturatedMaskName: self.config.saturation})
637 if self.config.doSuspect:
638 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
640 for maskName, maskThreshold
in limits.items():
641 if not math.isnan(maskThreshold):
642 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
643 isrFunctions.makeThresholdMask(
644 maskedImage=dataView,
645 threshold=maskThreshold,
652 maskView =
afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
654 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
655 self.config.suspectMaskName])
656 if numpy.all(maskView.getArray() & maskVal > 0):
657 self.log.warning(
"Amplifier %s is bad (completely SATURATED or SUSPECT)", ampName)
658 badAmpDict[ampName] =
True
659 maskView |= maskView.getPlaneBitMask(
"BAD")
664 """Apply serial overscan correction in place to all amps.
666 The actual overscan subtraction is performed by the
667 `lsst.ip.isr.overscan.OverscanTask`, which is called here.
672 Must be `SERIAL` or `PARALLEL`.
673 detectorConfig : `lsst.ip.isr.OverscanDetectorConfig`
674 Per-amplifier configurations.
675 detector : `lsst.afw.cameraGeom.Detector`
678 Dictionary of amp name to whether it is a bad amp.
679 ccdExposure : `lsst.afw.image.Exposure`
680 Exposure to have overscan correction performed.
684 overscans : `list` [`lsst.pipe.base.Struct` or None]
685 Each result struct has components:
688 Value or fit subtracted from the amplifier image data.
689 (scalar or `lsst.afw.image.Image`)
691 Value or fit subtracted from the overscan image data.
692 (scalar or `lsst.afw.image.Image`)
694 Image of the overscan region with the overscan
695 correction applied. This quantity is used to estimate
696 the amplifier read noise empirically.
697 (`lsst.afw.image.Image`)
699 Mean overscan fit value. (`float`)
701 Median overscan fit value. (`float`)
703 Clipped standard deviation of the overscan fit. (`float`)
705 Mean of the overscan after fit subtraction. (`float`)
707 Median of the overscan after fit subtraction. (`float`)
709 Clipped standard deviation of the overscan after fit
710 subtraction. (`float`)
714 lsst.ip.isr.overscan.OverscanTask
716 if mode
not in [
"SERIAL",
"PARALLEL"]:
717 raise ValueError(
"Mode must be SERIAL or PARALLEL")
722 for i, amp
in enumerate(detector):
723 ampName = amp.getName()
725 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
727 if mode ==
"SERIAL" and not ampConfig.doSerialOverscan:
729 "ISR_OSCAN: Amplifier %s/%s configured to skip serial overscan.",
734 elif mode ==
"PARALLEL" and not ampConfig.doParallelOverscan:
736 "ISR_OSCAN: Amplifier %s configured to skip parallel overscan.",
741 elif badAmpDict[ampName]
or not ccdExposure.getBBox().contains(amp.getBBox()):
748 if amp.getRawHorizontalOverscanBBox().isEmpty():
750 "ISR_OSCAN: No overscan region for amp %s. Not performing overscan correction.",
759 results = serialOverscan.run(ccdExposure, amp)
762 config=ampConfig.parallelOverscanConfig,
764 results = parallelOverscan.run(ccdExposure, amp)
766 metadata = ccdExposure.getMetadata()
767 keyBase =
"LSST ISR OVERSCAN"
768 metadata[f
"{keyBase} {mode} MEAN {ampName}"] = results.overscanMean
769 metadata[f
"{keyBase} {mode} MEDIAN {ampName}"] = results.overscanMedian
770 metadata[f
"{keyBase} {mode} STDEV {ampName}"] = results.overscanSigma
772 metadata[f
"{keyBase} RESIDUAL {mode} MEAN {ampName}"] = results.residualMean
773 metadata[f
"{keyBase} RESIDUAL {mode} MEDIAN {ampName}"] = results.residualMedian
774 metadata[f
"{keyBase} RESIDUAL {mode} STDEV {ampName}"] = results.residualSigma
776 overscans.append(results)
779 ccdExposure.getMetadata().
set(
"OVERSCAN",
"Overscan corrected")
1050 interpExp = ccdExposure.clone()
1052 isrFunctions.interpolateFromMask(
1053 maskedImage=interpExp.getMaskedImage(),
1054 fwhm=self.config.brighterFatterFwhmForInterpolation,
1055 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1056 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1058 bfExp = interpExp.clone()
1059 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1060 type(bfKernel), type(bfGains))
1061 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1062 self.config.brighterFatterMaxIter,
1063 self.config.brighterFatterThreshold,
1064 self.config.brighterFatterApplyGain,
1066 if bfResults[1] == self.config.brighterFatterMaxIter:
1067 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1070 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1073 image = ccdExposure.getMaskedImage().getImage()
1074 bfCorr = bfExp.getMaskedImage().getImage()
1075 bfCorr -= interpExp.getMaskedImage().getImage()
1084 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1085 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1088 if self.config.brighterFatterMaskGrowSize > 0:
1089 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1090 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1091 isrFunctions.growMasks(ccdExposure.getMask(),
1092 radius=self.config.brighterFatterMaskGrowSize,
1093 maskNameList=maskPlane,
1094 maskValue=maskPlane)
1230 def run(self, ccdExposure, *, dnlLUT=None, bias=None, deferredChargeCalib=None, linearizer=None,
1231 ptc=None, crosstalk=None, defects=None, bfKernel=None, bfGains=None, dark=None,
1232 flat=None, camera=None, **kwargs
1235 detector = ccdExposure.getDetector()
1237 overscanDetectorConfig = self.config.overscanCamera.getOverscanDetectorConfig(detector)
1241 if self.config.doHeaderProvenance:
1244 exposureMetadata = ccdExposure.getMetadata()
1245 exposureMetadata[
"LSST CALIB OVERSCAN HASH"] = overscanDetectorConfig.md5
1247 if self.config.doDiffNonLinearCorrection:
1249 if self.config.doBias:
1251 if self.config.doDeferredCharge:
1252 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1254 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1255 if self.config.doCrosstalk
or overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1256 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1257 if self.config.doDefect:
1258 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1259 if self.config.doBrighterFatter:
1261 if self.config.doDark:
1263 if self.config.doFlat:
1269 if self.config.doDiffNonLinearCorrection:
1272 if overscanDetectorConfig.doAnySerialOverscan:
1276 overscanDetectorConfig,
1282 serialOverscans = [
None]*len(detector)
1284 if overscanDetectorConfig.doAnyParallelOverscanCrosstalk:
1290 crosstalk=crosstalk,
1292 parallelOverscanRegion=
True,
1293 detectorConfig=overscanDetectorConfig,
1301 if overscanDetectorConfig.doAnyParallelOverscan:
1306 overscanDetectorConfig,
1312 if self.config.doAssembleCcd:
1314 self.log.info(
"Assembling CCD from amplifiers.")
1315 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1317 if self.config.expectWcs
and not ccdExposure.getWcs():
1318 self.log.warning(
"No WCS found in input exposure.")
1320 if self.config.doLinearize:
1322 self.log.info(
"Applying linearizer.")
1324 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1325 detector=detector, log=self.log)
1327 if self.config.doCrosstalk:
1329 self.log.info(
"Applying crosstalk correction.")
1330 self.crosstalk.run(ccdExposure, crosstalk=crosstalk, isTrimmed=
True)
1332 if self.config.doBias:
1334 self.log.info(
"Applying bias correction.")
1335 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage())
1337 if self.config.doGainsCorrection:
1339 self.log.info(
"Apply temperature dependence to the gains.")
1342 if self.config.doApplyGains:
1345 self.log.info(
"Apply PTC gains (temperature corrected or not) to the image.")
1346 isrFunctions.applyGains(ccdExposure, normalizeGains=
False, ptcGains=gains)
1348 if self.config.doDeferredCharge:
1350 self.log.info(
"Applying deferred charge/CTI correction.")
1351 self.deferredChargeCorrection.run(ccdExposure, deferredChargeCalib)
1353 if self.config.doVariance:
1359 if self.config.doDefect:
1361 self.log.info(
"Applying defects masking.")
1364 if self.config.doNanMasking:
1365 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1368 if self.config.doWidenSaturationTrails:
1369 self.log.info(
"Widening saturation trails.")
1370 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
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.")
1392 if self.config.doSaveInterpPixels:
1393 preInterpExp = ccdExposure.clone()
1395 if self.config.doSetBadRegions:
1396 self.log.info(
'Counting pixels in BAD regions.')
1399 if self.config.doInterpolate:
1400 self.log.info(
"Interpolating masked pixels.")
1401 isrFunctions.interpolateFromMask(
1402 maskedImage=ccdExposure.getMaskedImage(),
1403 fwhm=self.config.brighterFatterFwhmForInterpolation,
1404 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1405 maskNameList=list(self.config.maskListToInterpolate)
1409 if self.config.doStandardStatistics:
1410 metadata = ccdExposure.getMetadata()
1411 for amp
in detector:
1412 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1413 ampName = amp.getName()
1414 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1415 ampExposure.getMaskedImage(),
1416 [self.config.saturatedMaskName]
1418 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1419 ampExposure.getMaskedImage(),
1423 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1425 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1426 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1427 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1429 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1430 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1431 if overscanDetectorConfig.doAnySerialOverscan
and k1
in metadata
and k2
in metadata:
1432 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1434 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1437 outputStatistics =
None
1438 if self.config.doCalculateStatistics:
1439 outputStatistics = self.isrStats.run(ccdExposure, overscanResults=serialOverscans,
1440 bias=bias, dark=dark, flat=flat, ptc=ptc,
1441 defects=defects).results
1444 outputBin1Exposure =
None
1445 outputBin2Exposure =
None
1446 if self.config.doBinnedExposures:
1447 outputBin1Exposure, outputBin2Exposure = self.
makeBinnedImages(ccdExposure)
1449 return pipeBase.Struct(
1450 exposure=ccdExposure,
1452 outputBin1Exposure=outputBin1Exposure,
1453 outputBin2Exposure=outputBin2Exposure,
1455 preInterpExposure=preInterpExp,
1456 outputExposure=ccdExposure,
1457 outputStatistics=outputStatistics,