953 Mask SATURATED and SUSPECT pixels and check if any amplifiers
958 badAmpDict : `str` [`bool`]
959 Dictionary of amplifiers, keyed by name, value is True if
960 amplifier is fully masked.
961 ccdExposure : `lsst.afw.image.Exposure`
962 Input exposure to be masked.
963 detector : `lsst.afw.cameraGeom.Detector`
965 defects : `lsst.ip.isr.Defects`
966 List of defects. Used to determine if an entire
968 detectorConfig : `lsst.ip.isr.OverscanDetectorConfig`
969 Per-amplifier configurations.
970 ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional
971 PTC dataset (used if configured to use PTCTURNOFF).
975 badAmpDict : `str`[`bool`]
976 Dictionary of amplifiers, keyed by name.
978 maskedImage = ccdExposure.getMaskedImage()
980 metadata = ccdExposure.metadata
982 if self.config.doSaturation
and self.config.defaultSaturationSource ==
"PTCTURNOFF" and ptc
is None:
983 raise RuntimeError(
"Must provide ptc if using PTCTURNOFF as saturation source.")
984 if self.config.doSuspect
and self.config.defaultSuspectSource ==
"PTCTURNOFF" and ptc
is None:
985 raise RuntimeError(
"Must provide ptc if using PTCTURNOFF as suspect source.")
988 ampName = amp.getName()
990 ampConfig = detectorConfig.getOverscanAmpConfig(amp)
992 if badAmpDict[ampName]:
998 if self.config.doSaturation:
999 if self.config.defaultSaturationSource ==
"PTCTURNOFF":
1000 limits.update({self.config.saturatedMaskName: ptc.ptcTurnoff[amp.getName()]})
1001 elif self.config.defaultSaturationSource ==
"CAMERAMODEL":
1003 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1004 elif self.config.defaultSaturationSource ==
"NONE":
1005 limits.update({self.config.saturatedMaskName: numpy.inf})
1008 if math.isfinite(ampConfig.saturation):
1009 limits.update({self.config.saturatedMaskName: ampConfig.saturation})
1010 metadata[f
"LSST ISR SATURATION LEVEL {ampName}"] = limits[self.config.saturatedMaskName]
1012 if self.config.doSuspect:
1013 if self.config.defaultSuspectSource ==
"PTCTURNOFF":
1014 limits.update({self.config.suspectMaskName: ptc.ptcTurnoff[amp.getName()]})
1015 elif self.config.defaultSuspectSource ==
"CAMERAMODEL":
1017 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1018 elif self.config.defaultSuspectSource ==
"NONE":
1019 limits.update({self.config.suspectMaskName: numpy.inf})
1022 if math.isfinite(ampConfig.suspectLevel):
1023 limits.update({self.config.suspectMaskName: ampConfig.suspectLevel})
1024 metadata[f
"LSST ISR SUSPECT LEVEL {ampName}"] = limits[self.config.suspectMaskName]
1026 for maskName, maskThreshold
in limits.items():
1027 if not math.isnan(maskThreshold):
1028 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1029 toMask = (dataView.image.array >= maskThreshold)
1030 dataView.mask.array[toMask] |= dataView.mask.getPlaneBitMask(maskName)
1034 maskView =
afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1036 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1037 self.config.suspectMaskName])
1038 if numpy.all(maskView.getArray() & maskVal > 0):
1039 self.log.warning(
"Amplifier %s is bad (completely SATURATED or SUSPECT)", ampName)
1040 badAmpDict[ampName] =
True
1041 maskView |= maskView.getPlaneBitMask(
"BAD")
1807 def run(self, ccdExposure, *, dnlLUT=None, bias=None, deferredChargeCalib=None, linearizer=None,
1808 ptc=None, crosstalk=None, defects=None, bfKernel=None, bfGains=None, dark=None,
1809 flat=None, camera=None, **kwargs
1812 detector = ccdExposure.getDetector()
1814 overscanDetectorConfig = self.config.overscanCamera.getOverscanDetectorConfig(detector)
1816 if self.config.doBootstrap
and ptc
is not None:
1817 self.log.warning(
"Task configured with doBootstrap=True. Ignoring provided PTC.")
1820 if not self.config.doBootstrap:
1822 raise RuntimeError(
"A PTC must be supplied if config.doBootstrap is False.")
1825 exposureMetadata = ccdExposure.metadata
1826 doRaise = self.config.doRaiseOnCalibMismatch
1827 keywords = self.config.cameraKeywordsToCompare
1828 if not self.config.doBootstrap:
1831 if self.config.doCorrectGains:
1832 raise RuntimeError(
"doCorrectGains is True but no ptc provided.")
1833 if self.config.doDiffNonLinearCorrection:
1835 raise RuntimeError(
"doDiffNonLinearCorrection is True but no dnlLUT provided.")
1837 if self.config.doLinearize:
1838 if linearizer
is None:
1839 raise RuntimeError(
"doLinearize is True but no linearizer provided.")
1841 if self.config.doBias:
1843 raise RuntimeError(
"doBias is True but no bias provided.")
1846 if self.config.doCrosstalk:
1847 if crosstalk
is None:
1848 raise RuntimeError(
"doCrosstalk is True but no crosstalk provided.")
1850 if self.config.doDeferredCharge:
1851 if deferredChargeCalib
is None:
1852 raise RuntimeError(
"doDeferredCharge is True but no deferredChargeCalib provided.")
1857 deferredChargeCalib,
1861 if self.config.doDefect:
1863 raise RuntimeError(
"doDefect is True but no defects provided.")
1865 if self.config.doDark:
1867 raise RuntimeError(
"doDark is True but no dark frame provided.")
1870 if self.config.doBrighterFatter:
1871 if bfKernel
is None:
1872 raise RuntimeError(
"doBrighterFatter is True not no bfKernel provided.")
1874 if self.config.doFlat:
1876 raise RuntimeError(
"doFlat is True but no flat provided.")
1879 if self.config.doSaturation:
1880 if self.config.defaultSaturationSource
in [
"PTCTURNOFF",]:
1883 "doSaturation is True and defaultSaturationSource is "
1884 f
"{self.config.defaultSaturationSource}, but no ptc provided."
1886 if self.config.doSuspect:
1887 if self.config.defaultSuspectSource
in [
"PTCTURNOFF",]:
1890 "doSuspect is True and defaultSuspectSource is "
1891 f
"{self.config.defaultSuspectSource}, but no ptc provided."
1894 if self.config.doCheckUnprocessableData
and self.config.bssVoltageMinimum > 0.0:
1901 exposureMetadata[
"LSST ISR UNITS"] =
"adu"
1902 exposureMetadata[
"LSST ISR CROSSTALK APPLIED"] =
False
1903 exposureMetadata[
"LSST ISR OVERSCANLEVEL CHECKED"] =
False
1904 exposureMetadata[
"LSST ISR NOISE CHECKED"] =
False
1905 exposureMetadata[
"LSST ISR LINEARIZER APPLIED"] =
False
1906 exposureMetadata[
"LSST ISR CTI APPLIED"] =
False
1907 exposureMetadata[
"LSST ISR BIAS APPLIED"] =
False
1908 exposureMetadata[
"LSST ISR DARK APPLIED"] =
False
1909 exposureMetadata[
"LSST ISR BF APPLIED"] =
False
1910 exposureMetadata[
"LSST ISR FLAT APPLIED"] =
False
1911 exposureMetadata[
"LSST ISR DEFECTS APPLIED"] =
False
1913 if self.config.doBootstrap:
1914 self.log.info(
"Configured using doBootstrap=True; using gain of 1.0 (adu units)")
1916 for amp
in detector:
1917 ptc.gain[amp.getName()] = 1.0
1918 ptc.noise[amp.getName()] = 0.0
1920 exposureMetadata[
"LSST ISR BOOTSTRAP"] = self.config.doBootstrap
1927 for amp
in detector:
1928 if not math.isnan(gain := overscanDetectorConfig.getOverscanAmpConfig(amp).gain):
1929 gains[amp.getName()] = gain
1931 "Overriding gain for amp %s with configured value of %.3f.",
1938 self.log.debug(
"Converting exposure to floating point values.")
1950 if self.config.doDiffNonLinearCorrection:
1961 if overscanDetectorConfig.doAnySerialOverscan:
1964 overscanDetectorConfig,
1970 if self.config.doBootstrap:
1972 for amp, serialOverscan
in zip(detector, serialOverscans):
1973 if serialOverscan
is None:
1974 ptc.noise[amp.getName()] = 0.0
1982 ptc.noise[amp.getName()] = serialOverscan.residualSigma * gains[amp.getName()]
1984 serialOverscans = [
None]*len(detector)
1996 overscanDetectorConfig,
2002 if self.config.doCorrectGains:
2005 self.log.info(
"Apply temperature dependence to the gains.")
2006 gains, readNoise = self.
correctGains(ccdExposure, ptc, gains)
2011 if self.config.doApplyGains:
2012 self.log.info(
"Using gain values to convert from adu to electron units.")
2013 isrFunctions.applyGains(ccdExposure, normalizeGains=
False, ptcGains=gains, isTrimmed=
False)
2015 exposureMetadata[
"LSST ISR UNITS"] =
"electron"
2019 for amp
in detector:
2020 ampName = amp.getName()
2021 if (key := f
"LSST ISR SATURATION LEVEL {ampName}")
in exposureMetadata:
2022 exposureMetadata[key] *= gains[ampName]
2023 if (key := f
"LSST ISR SUSPECT LEVEL {ampName}")
in exposureMetadata:
2024 exposureMetadata[key] *= gains[ampName]
2027 metadata = ccdExposure.metadata
2028 metadata[
"LSST ISR READNOISE UNITS"] =
"electron"
2029 for amp
in detector:
2031 metadata[f
"LSST ISR GAIN {amp.getName()}"] = gains[amp.getName()]
2034 noise = ptc.noise[amp.getName()]
2035 metadata[f
"LSST ISR READNOISE {amp.getName()}"] = noise
2039 if self.config.doCrosstalk:
2040 self.log.info(
"Applying crosstalk corrections to full amplifier region.")
2041 if self.config.doBootstrap
and numpy.any(crosstalk.fitGains != 0):
2042 crosstalkGains =
None
2044 crosstalkGains = gains
2047 crosstalk=crosstalk,
2048 gains=crosstalkGains,
2050 badAmpDict=badAmpDict,
2051 ignoreVariance=
True,
2053 ccdExposure.metadata[
"LSST ISR CROSSTALK APPLIED"] =
True
2056 if numpy.isfinite(self.config.serialOverscanMedianShiftSigmaThreshold):
2058 ccdExposure.metadata[
"LSST ISR OVERSCANLEVEL CHECKED"] =
True
2060 if numpy.isfinite(self.config.ampNoiseThreshold):
2061 badAmpDict = self.
checkAmpNoise(badAmpDict, ccdExposure, ptc)
2062 ccdExposure.metadata[
"LSST ISR NOISE CHECKED"] =
True
2064 if numpy.isfinite(self.config.serialOverscanMedianShiftSigmaThreshold)
or \
2065 numpy.isfinite(self.config.ampNoiseThreshold):
2070 parallelOverscans =
None
2071 if overscanDetectorConfig.doAnyParallelOverscan:
2075 overscanDetectorConfig,
2083 if self.config.doLinearize:
2084 self.log.info(
"Applying linearizer.")
2088 if exposureMetadata[
"LSST ISR UNITS"] ==
"electron":
2089 linearityGains = gains
2091 linearityGains =
None
2092 linearizer.applyLinearity(
2093 image=ccdExposure.image,
2096 gains=linearityGains,
2098 ccdExposure.metadata[
"LSST ISR LINEARIZER APPLIED"] =
True
2103 if self.config.doDeferredCharge:
2104 self.deferredChargeCorrection.run(
2106 deferredChargeCalib,
2109 ccdExposure.metadata[
"LSST ISR CTI APPLIED"] =
True
2113 untrimmedCcdExposure =
None
2114 if self.config.isrStats.doCtiStatistics:
2115 untrimmedCcdExposure = ccdExposure.clone()
2119 if self.config.doAssembleCcd:
2120 self.log.info(
"Assembling CCD from amplifiers.")
2121 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
2123 if self.config.expectWcs
and not ccdExposure.getWcs():
2124 self.log.warning(
"No WCS found in input exposure.")
2127 if self.config.doE2VEdgeBleedMask
and detector.getPhysicalType() ==
"E2V":
2128 isrFunctions.maskE2VEdgeBleed(
2129 exposure=ccdExposure,
2130 e2vEdgeBleedSatMinArea=self.config.e2vEdgeBleedSatMinArea,
2131 e2vEdgeBleedSatMaxArea=self.config.e2vEdgeBleedSatMaxArea,
2132 e2vEdgeBleedYMax=self.config.e2vEdgeBleedYMax,
2133 saturatedMaskName=self.config.saturatedMaskName,
2138 for maskPlane
in self.config.itlDipMaskPlanes:
2139 if maskPlane
not in ccdExposure.mask.getMaskPlaneDict():
2140 self.log.info(
"Adding %s mask plane to image.", maskPlane)
2141 ccdExposure.mask.addMaskPlane(maskPlane)
2143 if self.config.doITLDipMask:
2144 isrFunctions.maskITLDip(
2145 exposure=ccdExposure,
2146 detectorConfig=overscanDetectorConfig,
2148 maskPlaneNames=self.config.itlDipMaskPlanes,
2151 if (self.config.doITLSatSagMask
or self.config.doITLEdgeBleedMask) \
2152 and detector.getPhysicalType() ==
'ITL':
2154 badAmpDict=badAmpDict)
2158 if self.config.doBias:
2159 self.log.info(
"Applying bias correction.")
2162 isrFunctions.biasCorrection(ccdExposure.maskedImage, bias.maskedImage)
2163 ccdExposure.metadata[
"LSST ISR BIAS APPLIED"] =
True
2167 if self.config.doDark:
2168 self.log.info(
"Applying dark subtraction.")
2172 ccdExposure.metadata[
"LSST ISR DARK APPLIED"] =
True
2178 if self.config.doDefect:
2179 self.log.info(
"Applying defect masking.")
2181 ccdExposure.metadata[
"LSST ISR DEFECTS APPLIED"] =
True
2183 self.log.info(
"Adding UNMASKEDNAN mask plane to image.")
2184 ccdExposure.mask.addMaskPlane(
"UNMASKEDNAN")
2185 if self.config.doNanMasking:
2186 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
2189 if self.config.doWidenSaturationTrails:
2190 self.log.info(
"Widening saturation trails.")
2191 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
2195 if self.config.doBrighterFatter:
2196 self.log.info(
"Applying brighter-fatter correction.")
2202 if exposureMetadata[
"LSST ISR UNITS"] ==
"electron":
2203 brighterFatterApplyGain =
False
2205 brighterFatterApplyGain =
True
2207 if brighterFatterApplyGain
and (ptc
is not None)
and (bfGains != gains):
2214 self.log.warning(
"Need to apply gain for brighter-fatter, but the stored"
2215 "gains in the kernel are not the same as the gains stored"
2216 "in the PTC. Using the kernel gains.")
2223 brighterFatterApplyGain,
2227 ccdExposure.metadata[
"LSST ISR BF APPLIED"] =
True
2228 metadata[
"LSST ISR BF ITERS"] = bfCorrIters
2232 if self.config.doVariance:
2239 if self.config.doFlat:
2240 self.log.info(
"Applying flat correction.")
2241 isrFunctions.flatCorrection(
2242 maskedImage=ccdExposure.maskedImage,
2243 flatMaskedImage=flat.maskedImage,
2244 scalingType=self.config.flatScalingType,
2245 userScale=self.config.flatUserScale,
2252 if (validPolygon := flat.info.getValidPolygon())
is not None:
2253 ccdExposure.info.setValidPolygon(validPolygon)
2255 noData = (ccdExposure.mask.array & ccdExposure.mask.getPlaneBitMask(
"NO_DATA")) > 0
2256 ccdExposure.image.array[noData] = 0.0
2257 ccdExposure.variance.array[noData] = 0.0
2259 ccdExposure.metadata[
"LSST ISR FLAT APPLIED"] =
True
2260 ccdExposure.metadata[
"LSST ISR FLAT SOURCE"] = flat.metadata.get(
"FLATSRC",
"UNKNOWN")
2264 if self.config.doSaveInterpPixels:
2265 preInterpExp = ccdExposure.clone()
2267 if self.config.doSetBadRegions:
2268 self.log.info(
'Setting values in large contiguous bad regions.')
2271 if self.config.doInterpolate:
2272 self.log.info(
"Interpolating masked pixels.")
2273 isrFunctions.interpolateFromMask(
2274 maskedImage=ccdExposure.getMaskedImage(),
2275 fwhm=self.config.brighterFatterFwhmForInterpolation,
2276 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2277 maskNameList=list(self.config.maskListToInterpolate),
2278 useLegacyInterp=self.config.useLegacyInterp,
2282 if self.config.doAmpOffset:
2283 if self.config.ampOffset.doApplyAmpOffset:
2284 self.log.info(
"Measuring and applying amp offset corrections.")
2286 self.log.info(
"Measuring amp offset corrections only, without applying them.")
2287 self.ampOffset.run(ccdExposure)
2290 if self.config.doStandardStatistics:
2291 metadata = ccdExposure.metadata
2292 for amp
in detector:
2293 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
2294 ampName = amp.getName()
2295 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
2296 ampExposure.getMaskedImage(),
2297 [self.config.saturatedMaskName]
2299 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
2300 ampExposure.getMaskedImage(),
2303 metadata[f
"LSST ISR MASK SUSPECT {ampName}"] = isrFunctions.countMaskedPixels(
2304 ampExposure.getMaskedImage(),
2308 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
2310 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
2311 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
2312 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
2314 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
2315 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
2316 if overscanDetectorConfig.doAnySerialOverscan
and k1
in metadata
and k2
in metadata:
2317 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
2319 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
2322 outputStatistics =
None
2323 if self.config.doCalculateStatistics:
2324 outputStatistics = self.isrStats.run(ccdExposure,
2325 untrimmedInputExposure=untrimmedCcdExposure,
2326 serialOverscanResults=serialOverscans,
2327 parallelOverscanResults=parallelOverscans,
2328 bias=bias, dark=dark, flat=flat,
2329 ptc=ptc, defects=defects).results
2332 outputBin1Exposure =
None
2333 outputBin2Exposure =
None
2334 if self.config.doBinnedExposures:
2335 self.log.info(
"Creating binned exposures.")
2336 outputBin1Exposure = self.binning.run(
2338 binFactor=self.config.binFactor1,
2340 outputBin2Exposure = self.binning.run(
2342 binFactor=self.config.binFactor2,
2345 return pipeBase.Struct(
2346 exposure=ccdExposure,
2348 outputBin1Exposure=outputBin1Exposure,
2349 outputBin2Exposure=outputBin2Exposure,
2351 preInterpExposure=preInterpExp,
2352 outputExposure=ccdExposure,
2353 outputStatistics=outputStatistics,