22__all__ = [
"IsrTask",
"IsrTaskConfig"]
31import lsst.pipe.base
as pipeBase
32import lsst.pipe.base.connectionTypes
as cT
34from contextlib
import contextmanager
35from lsstDebug
import getDebugFrame
40from lsst.utils.timer
import timeMethod
42from .
import isrFunctions
44from .
import linearize
45from .defects
import Defects
47from .assembleCcdTask
import AssembleCcdTask
48from .crosstalk
import CrosstalkTask, CrosstalkCalib
49from .fringe
import FringeTask
50from .isr
import maskNans
51from .masking
import MaskingTask
52from .overscan
import OverscanCorrectionTask
53from .straylight
import StrayLightTask
54from .vignette
import VignetteTask
55from .ampOffset
import AmpOffsetTask
56from .deferredCharge
import DeferredChargeTask
57from .isrStatistics
import IsrStatisticsTask
58from lsst.daf.butler
import DimensionGraph
62 """Lookup function to identify crosstalkSource entries.
64 This should return an empty list under most circumstances. Only
65 when inter-chip crosstalk has been identified should this be
72 registry : `lsst.daf.butler.Registry`
73 Butler registry to query.
74 quantumDataId : `lsst.daf.butler.ExpandedDataCoordinate`
75 Data id to transform to identify crosstalkSources. The
76 ``detector`` entry will be stripped.
77 collections : `lsst.daf.butler.CollectionSearch`
78 Collections to search through.
82 results : `list` [`lsst.daf.butler.DatasetRef`]
83 List of datasets that match the query that will be used
as
86 newDataId = quantumDataId.subset(DimensionGraph(registry.dimensions, names=["instrument",
"exposure"]))
87 results = set(registry.queryDatasets(datasetType, collections=collections, dataId=newDataId,
94 return [ref.expanded(registry.expandDataId(ref.dataId, records=newDataId.records))
for ref
in results]
98 dimensions={
"instrument",
"exposure",
"detector"},
100 ccdExposure = cT.Input(
102 doc=
"Input exposure to process.",
103 storageClass=
"Exposure",
104 dimensions=[
"instrument",
"exposure",
"detector"],
106 camera = cT.PrerequisiteInput(
108 storageClass=
"Camera",
109 doc=
"Input camera to construct complete exposures.",
110 dimensions=[
"instrument"],
114 crosstalk = cT.PrerequisiteInput(
116 doc=
"Input crosstalk object",
117 storageClass=
"CrosstalkCalib",
118 dimensions=[
"instrument",
"detector"],
122 crosstalkSources = cT.PrerequisiteInput(
123 name=
"isrOverscanCorrected",
124 doc=
"Overscan corrected input images.",
125 storageClass=
"Exposure",
126 dimensions=[
"instrument",
"exposure",
"detector"],
129 lookupFunction=crosstalkSourceLookup,
132 bias = cT.PrerequisiteInput(
134 doc=
"Input bias calibration.",
135 storageClass=
"ExposureF",
136 dimensions=[
"instrument",
"detector"],
139 dark = cT.PrerequisiteInput(
141 doc=
"Input dark calibration.",
142 storageClass=
"ExposureF",
143 dimensions=[
"instrument",
"detector"],
146 flat = cT.PrerequisiteInput(
148 doc=
"Input flat calibration.",
149 storageClass=
"ExposureF",
150 dimensions=[
"instrument",
"physical_filter",
"detector"],
153 ptc = cT.PrerequisiteInput(
155 doc=
"Input Photon Transfer Curve dataset",
156 storageClass=
"PhotonTransferCurveDataset",
157 dimensions=[
"instrument",
"detector"],
160 fringes = cT.PrerequisiteInput(
162 doc=
"Input fringe calibration.",
163 storageClass=
"ExposureF",
164 dimensions=[
"instrument",
"physical_filter",
"detector"],
168 strayLightData = cT.PrerequisiteInput(
170 doc=
"Input stray light calibration.",
171 storageClass=
"StrayLightData",
172 dimensions=[
"instrument",
"physical_filter",
"detector"],
177 bfKernel = cT.PrerequisiteInput(
179 doc=
"Input brighter-fatter kernel.",
180 storageClass=
"NumpyArray",
181 dimensions=[
"instrument"],
185 newBFKernel = cT.PrerequisiteInput(
186 name=
'brighterFatterKernel',
187 doc=
"Newer complete kernel + gain solutions.",
188 storageClass=
"BrighterFatterKernel",
189 dimensions=[
"instrument",
"detector"],
193 defects = cT.PrerequisiteInput(
195 doc=
"Input defect tables.",
196 storageClass=
"Defects",
197 dimensions=[
"instrument",
"detector"],
200 linearizer = cT.PrerequisiteInput(
202 storageClass=
"Linearizer",
203 doc=
"Linearity correction calibration.",
204 dimensions=[
"instrument",
"detector"],
208 opticsTransmission = cT.PrerequisiteInput(
209 name=
"transmission_optics",
210 storageClass=
"TransmissionCurve",
211 doc=
"Transmission curve due to the optics.",
212 dimensions=[
"instrument"],
215 filterTransmission = cT.PrerequisiteInput(
216 name=
"transmission_filter",
217 storageClass=
"TransmissionCurve",
218 doc=
"Transmission curve due to the filter.",
219 dimensions=[
"instrument",
"physical_filter"],
222 sensorTransmission = cT.PrerequisiteInput(
223 name=
"transmission_sensor",
224 storageClass=
"TransmissionCurve",
225 doc=
"Transmission curve due to the sensor.",
226 dimensions=[
"instrument",
"detector"],
229 atmosphereTransmission = cT.PrerequisiteInput(
230 name=
"transmission_atmosphere",
231 storageClass=
"TransmissionCurve",
232 doc=
"Transmission curve due to the atmosphere.",
233 dimensions=[
"instrument"],
236 illumMaskedImage = cT.PrerequisiteInput(
238 doc=
"Input illumination correction.",
239 storageClass=
"MaskedImageF",
240 dimensions=[
"instrument",
"physical_filter",
"detector"],
243 deferredChargeCalib = cT.PrerequisiteInput(
245 doc=
"Deferred charge/CTI correction dataset.",
246 storageClass=
"IsrCalib",
247 dimensions=[
"instrument",
"detector"],
251 outputExposure = cT.Output(
253 doc=
"Output ISR processed exposure.",
254 storageClass=
"Exposure",
255 dimensions=[
"instrument",
"exposure",
"detector"],
257 preInterpExposure = cT.Output(
258 name=
'preInterpISRCCD',
259 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
260 storageClass=
"ExposureF",
261 dimensions=[
"instrument",
"exposure",
"detector"],
263 outputOssThumbnail = cT.Output(
265 doc=
"Output Overscan-subtracted thumbnail image.",
266 storageClass=
"Thumbnail",
267 dimensions=[
"instrument",
"exposure",
"detector"],
269 outputFlattenedThumbnail = cT.Output(
270 name=
"FlattenedThumb",
271 doc=
"Output flat-corrected thumbnail image.",
272 storageClass=
"Thumbnail",
273 dimensions=[
"instrument",
"exposure",
"detector"],
275 outputStatistics = cT.Output(
276 name=
"isrStatistics",
277 doc=
"Output of additional statistics table.",
278 storageClass=
"StructuredDataDict",
279 dimensions=[
"instrument",
"exposure",
"detector"],
285 if config.doBias
is not True:
286 self.prerequisiteInputs.remove(
"bias")
287 if config.doLinearize
is not True:
288 self.prerequisiteInputs.remove(
"linearizer")
289 if config.doCrosstalk
is not True:
290 self.prerequisiteInputs.remove(
"crosstalkSources")
291 self.prerequisiteInputs.remove(
"crosstalk")
292 if config.doBrighterFatter
is not True:
293 self.prerequisiteInputs.remove(
"bfKernel")
294 self.prerequisiteInputs.remove(
"newBFKernel")
295 if config.doDefect
is not True:
296 self.prerequisiteInputs.remove(
"defects")
297 if config.doDark
is not True:
298 self.prerequisiteInputs.remove(
"dark")
299 if config.doFlat
is not True:
300 self.prerequisiteInputs.remove(
"flat")
301 if config.doFringe
is not True:
302 self.prerequisiteInputs.remove(
"fringes")
303 if config.doStrayLight
is not True:
304 self.prerequisiteInputs.remove(
"strayLightData")
305 if config.usePtcGains
is not True and config.usePtcReadNoise
is not True:
306 self.prerequisiteInputs.remove(
"ptc")
307 if config.doAttachTransmissionCurve
is not True:
308 self.prerequisiteInputs.remove(
"opticsTransmission")
309 self.prerequisiteInputs.remove(
"filterTransmission")
310 self.prerequisiteInputs.remove(
"sensorTransmission")
311 self.prerequisiteInputs.remove(
"atmosphereTransmission")
313 if config.doUseOpticsTransmission
is not True:
314 self.prerequisiteInputs.remove(
"opticsTransmission")
315 if config.doUseFilterTransmission
is not True:
316 self.prerequisiteInputs.remove(
"filterTransmission")
317 if config.doUseSensorTransmission
is not True:
318 self.prerequisiteInputs.remove(
"sensorTransmission")
319 if config.doUseAtmosphereTransmission
is not True:
320 self.prerequisiteInputs.remove(
"atmosphereTransmission")
321 if config.doIlluminationCorrection
is not True:
322 self.prerequisiteInputs.remove(
"illumMaskedImage")
323 if config.doDeferredCharge
is not True:
324 self.prerequisiteInputs.remove(
"deferredChargeCalib")
326 if config.doWrite
is not True:
327 self.outputs.remove(
"outputExposure")
328 self.outputs.remove(
"preInterpExposure")
329 self.outputs.remove(
"outputFlattenedThumbnail")
330 self.outputs.remove(
"outputOssThumbnail")
331 self.outputs.remove(
"outputStatistics")
333 if config.doSaveInterpPixels
is not True:
334 self.outputs.remove(
"preInterpExposure")
335 if config.qa.doThumbnailOss
is not True:
336 self.outputs.remove(
"outputOssThumbnail")
337 if config.qa.doThumbnailFlattened
is not True:
338 self.outputs.remove(
"outputFlattenedThumbnail")
339 if config.doCalculateStatistics
is not True:
340 self.outputs.remove(
"outputStatistics")
344 pipelineConnections=IsrTaskConnections):
345 """Configuration parameters for IsrTask.
347 Items are grouped in the order
in which they are executed by the task.
349 datasetType = pexConfig.Field(
351 doc="Dataset type for input data; users will typically leave this alone, "
352 "but camera-specific ISR tasks will override it",
356 fallbackFilterName = pexConfig.Field(
358 doc=
"Fallback default filter name for calibrations.",
361 useFallbackDate = pexConfig.Field(
363 doc=
"Pass observation date when using fallback filter.",
366 expectWcs = pexConfig.Field(
369 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)."
371 fwhm = pexConfig.Field(
373 doc=
"FWHM of PSF in arcseconds.",
376 qa = pexConfig.ConfigField(
378 doc=
"QA related configuration options.",
380 doHeaderProvenance = pexConfig.Field(
383 doc=
"Write calibration identifiers into output exposure header?",
387 doConvertIntToFloat = pexConfig.Field(
389 doc=
"Convert integer raw images to floating point values?",
394 doSaturation = pexConfig.Field(
396 doc=
"Mask saturated pixels? NB: this is totally independent of the"
397 " interpolation option - this is ONLY setting the bits in the mask."
398 " To have them interpolated make sure doSaturationInterpolation=True",
401 saturatedMaskName = pexConfig.Field(
403 doc=
"Name of mask plane to use in saturation detection and interpolation",
406 saturation = pexConfig.Field(
408 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
409 default=float(
"NaN"),
411 growSaturationFootprintSize = pexConfig.Field(
413 doc=
"Number of pixels by which to grow the saturation footprints",
418 doSuspect = pexConfig.Field(
420 doc=
"Mask suspect pixels?",
423 suspectMaskName = pexConfig.Field(
425 doc=
"Name of mask plane to use for suspect pixels",
428 numEdgeSuspect = pexConfig.Field(
430 doc=
"Number of edge pixels to be flagged as untrustworthy.",
433 edgeMaskLevel = pexConfig.ChoiceField(
435 doc=
"Mask edge pixels in which coordinate frame: DETECTOR or AMP?",
438 'DETECTOR':
'Mask only the edges of the full detector.',
439 'AMP':
'Mask edges of each amplifier.',
444 doSetBadRegions = pexConfig.Field(
446 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
449 badStatistic = pexConfig.ChoiceField(
451 doc=
"How to estimate the average value for BAD regions.",
454 "MEANCLIP":
"Correct using the (clipped) mean of good data",
455 "MEDIAN":
"Correct using the median of the good data",
460 doOverscan = pexConfig.Field(
462 doc=
"Do overscan subtraction?",
465 overscan = pexConfig.ConfigurableField(
466 target=OverscanCorrectionTask,
467 doc=
"Overscan subtraction task for image segments.",
471 doAssembleCcd = pexConfig.Field(
474 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
476 assembleCcd = pexConfig.ConfigurableField(
477 target=AssembleCcdTask,
478 doc=
"CCD assembly task",
482 doAssembleIsrExposures = pexConfig.Field(
485 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
487 doTrimToMatchCalib = pexConfig.Field(
490 doc=
"Trim raw data to match calibration bounding boxes?"
494 doBias = pexConfig.Field(
496 doc=
"Apply bias frame correction?",
499 biasDataProductName = pexConfig.Field(
501 doc=
"Name of the bias data product",
504 doBiasBeforeOverscan = pexConfig.Field(
506 doc=
"Reverse order of overscan and bias correction.",
511 doDeferredCharge = pexConfig.Field(
513 doc=
"Apply deferred charge correction?",
516 deferredChargeCorrection = pexConfig.ConfigurableField(
517 target=DeferredChargeTask,
518 doc=
"Deferred charge correction task.",
522 doVariance = pexConfig.Field(
524 doc=
"Calculate variance?",
527 gain = pexConfig.Field(
529 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
530 default=float(
"NaN"),
532 readNoise = pexConfig.Field(
534 doc=
"The read noise to use if no Detector is present in the Exposure",
537 doEmpiricalReadNoise = pexConfig.Field(
540 doc=
"Calculate empirical read noise instead of value from AmpInfo data?"
542 usePtcReadNoise = pexConfig.Field(
545 doc=
"Use readnoise values from the Photon Transfer Curve?"
547 maskNegativeVariance = pexConfig.Field(
550 doc=
"Mask pixels that claim a negative variance? This likely indicates a failure "
551 "in the measurement of the overscan at an edge due to the data falling off faster "
552 "than the overscan model can account for it."
554 negativeVarianceMaskName = pexConfig.Field(
557 doc=
"Mask plane to use to mark pixels with negative variance, if `maskNegativeVariance` is True.",
560 doLinearize = pexConfig.Field(
562 doc=
"Correct for nonlinearity of the detector's response?",
567 doCrosstalk = pexConfig.Field(
569 doc=
"Apply intra-CCD crosstalk correction?",
572 doCrosstalkBeforeAssemble = pexConfig.Field(
574 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
577 crosstalk = pexConfig.ConfigurableField(
578 target=CrosstalkTask,
579 doc=
"Intra-CCD crosstalk correction",
583 doDefect = pexConfig.Field(
585 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
588 doNanMasking = pexConfig.Field(
590 doc=
"Mask non-finite (NAN, inf) pixels?",
593 doWidenSaturationTrails = pexConfig.Field(
595 doc=
"Widen bleed trails based on their width?",
600 doBrighterFatter = pexConfig.Field(
603 doc=
"Apply the brighter-fatter correction?"
605 brighterFatterLevel = pexConfig.ChoiceField(
608 doc=
"The level at which to correct for brighter-fatter.",
610 "AMP":
"Every amplifier treated separately.",
611 "DETECTOR":
"One kernel per detector",
614 brighterFatterMaxIter = pexConfig.Field(
617 doc=
"Maximum number of iterations for the brighter-fatter correction"
619 brighterFatterThreshold = pexConfig.Field(
622 doc=
"Threshold used to stop iterating the brighter-fatter correction. It is the "
623 "absolute value of the difference between the current corrected image and the one "
624 "from the previous iteration summed over all the pixels."
626 brighterFatterApplyGain = pexConfig.Field(
629 doc=
"Should the gain be applied when applying the brighter-fatter correction?"
631 brighterFatterMaskListToInterpolate = pexConfig.ListField(
633 doc=
"List of mask planes that should be interpolated over when applying the brighter-fatter "
635 default=[
"SAT",
"BAD",
"NO_DATA",
"UNMASKEDNAN"],
637 brighterFatterMaskGrowSize = pexConfig.Field(
640 doc=
"Number of pixels to grow the masks listed in config.brighterFatterMaskListToInterpolate "
641 "when brighter-fatter correction is applied."
645 doDark = pexConfig.Field(
647 doc=
"Apply dark frame correction?",
650 darkDataProductName = pexConfig.Field(
652 doc=
"Name of the dark data product",
657 doStrayLight = pexConfig.Field(
659 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
662 strayLight = pexConfig.ConfigurableField(
663 target=StrayLightTask,
664 doc=
"y-band stray light correction"
668 doFlat = pexConfig.Field(
670 doc=
"Apply flat field correction?",
673 flatDataProductName = pexConfig.Field(
675 doc=
"Name of the flat data product",
678 flatScalingType = pexConfig.ChoiceField(
680 doc=
"The method for scaling the flat on the fly.",
683 "USER":
"Scale by flatUserScale",
684 "MEAN":
"Scale by the inverse of the mean",
685 "MEDIAN":
"Scale by the inverse of the median",
688 flatUserScale = pexConfig.Field(
690 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
693 doTweakFlat = pexConfig.Field(
695 doc=
"Tweak flats to match observed amplifier ratios?",
701 doApplyGains = pexConfig.Field(
703 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
706 usePtcGains = pexConfig.Field(
708 doc=
"Use the gain values from the Photon Transfer Curve?",
711 normalizeGains = pexConfig.Field(
713 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
718 doFringe = pexConfig.Field(
720 doc=
"Apply fringe correction?",
723 fringe = pexConfig.ConfigurableField(
725 doc=
"Fringe subtraction task",
727 fringeAfterFlat = pexConfig.Field(
729 doc=
"Do fringe subtraction after flat-fielding?",
734 doAmpOffset = pexConfig.Field(
735 doc=
"Calculate and apply amp offset corrections?",
739 ampOffset = pexConfig.ConfigurableField(
740 doc=
"Amp offset correction task.",
741 target=AmpOffsetTask,
745 doMeasureBackground = pexConfig.Field(
747 doc=
"Measure the background level on the reduced image?",
752 doCameraSpecificMasking = pexConfig.Field(
754 doc=
"Mask camera-specific bad regions?",
757 masking = pexConfig.ConfigurableField(
763 doInterpolate = pexConfig.Field(
765 doc=
"Interpolate masked pixels?",
768 doSaturationInterpolation = pexConfig.Field(
770 doc=
"Perform interpolation over pixels masked as saturated?"
771 " NB: This is independent of doSaturation; if that is False this plane"
772 " will likely be blank, resulting in a no-op here.",
775 doNanInterpolation = pexConfig.Field(
777 doc=
"Perform interpolation over pixels masked as NaN?"
778 " NB: This is independent of doNanMasking; if that is False this plane"
779 " will likely be blank, resulting in a no-op here.",
782 doNanInterpAfterFlat = pexConfig.Field(
784 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we "
785 "also have to interpolate them before flat-fielding."),
788 maskListToInterpolate = pexConfig.ListField(
790 doc=
"List of mask planes that should be interpolated.",
791 default=[
'SAT',
'BAD'],
793 doSaveInterpPixels = pexConfig.Field(
795 doc=
"Save a copy of the pre-interpolated pixel values?",
800 fluxMag0T1 = pexConfig.DictField(
803 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
804 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
807 defaultFluxMag0T1 = pexConfig.Field(
809 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
810 default=pow(10.0, 0.4*28.0)
814 doVignette = pexConfig.Field(
816 doc=(
"Compute and attach the validPolygon defining the unvignetted region to the exposure "
817 "according to vignetting parameters?"),
820 doMaskVignettePolygon = pexConfig.Field(
822 doc=(
"Add a mask bit for pixels within the vignetted region. Ignored if doVignette "
826 vignetteValue = pexConfig.Field(
828 doc=
"Value to replace image array pixels with in the vignetted region? Ignored if None.",
832 vignette = pexConfig.ConfigurableField(
834 doc=
"Vignetting task.",
838 doAttachTransmissionCurve = pexConfig.Field(
841 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?"
843 doUseOpticsTransmission = pexConfig.Field(
846 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?"
848 doUseFilterTransmission = pexConfig.Field(
851 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?"
853 doUseSensorTransmission = pexConfig.Field(
856 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?"
858 doUseAtmosphereTransmission = pexConfig.Field(
861 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?"
865 doIlluminationCorrection = pexConfig.Field(
868 doc=
"Perform illumination correction?"
870 illuminationCorrectionDataProductName = pexConfig.Field(
872 doc=
"Name of the illumination correction data product.",
875 illumScale = pexConfig.Field(
877 doc=
"Scale factor for the illumination correction.",
880 illumFilters = pexConfig.ListField(
883 doc=
"Only perform illumination correction for these filters."
887 doCalculateStatistics = pexConfig.Field(
889 doc=
"Should additional ISR statistics be calculated?",
892 isrStats = pexConfig.ConfigurableField(
893 target=IsrStatisticsTask,
894 doc=
"Task to calculate additional statistics.",
899 doWrite = pexConfig.Field(
901 doc=
"Persist postISRCCD?",
908 raise ValueError(
"You may not specify both doFlat and doApplyGains")
910 raise ValueError(
"You may not specify both doBiasBeforeOverscan and doTrimToMatchCalib")
920 """Apply common instrument signature correction algorithms to a raw frame.
922 The process for correcting imaging data
is very similar
from
923 camera to camera. This task provides a vanilla implementation of
924 doing these corrections, including the ability to turn certain
925 corrections off
if they are
not needed. The inputs to the primary
926 method, `
run()`, are a raw exposure to be corrected
and the
927 calibration data products. The raw input
is a single chip sized
928 mosaic of all amps including overscans
and other non-science
931 The __init__ method sets up the subtasks
for ISR processing, using
937 Positional arguments passed to the Task constructor.
938 None used at this time.
939 kwargs : `dict`, optional
940 Keyword arguments passed on to the Task constructor.
941 None used at this time.
943 ConfigClass = IsrTaskConfig
948 self.makeSubtask(
"assembleCcd")
949 self.makeSubtask(
"crosstalk")
950 self.makeSubtask(
"strayLight")
951 self.makeSubtask(
"fringe")
952 self.makeSubtask(
"masking")
953 self.makeSubtask(
"overscan")
954 self.makeSubtask(
"vignette")
955 self.makeSubtask(
"ampOffset")
956 self.makeSubtask(
"deferredChargeCorrection")
957 self.makeSubtask(
"isrStats")
960 inputs = butlerQC.get(inputRefs)
963 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
964 except Exception
as e:
965 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
968 detector = inputs[
'ccdExposure'].getDetector()
970 if self.config.doCrosstalk
is True:
973 if 'crosstalk' in inputs
and inputs[
'crosstalk']
is not None:
974 if not isinstance(inputs[
'crosstalk'], CrosstalkCalib):
975 inputs[
'crosstalk'] = CrosstalkCalib.fromTable(inputs[
'crosstalk'])
977 coeffVector = (self.config.crosstalk.crosstalkValues
978 if self.config.crosstalk.useConfigCoefficients
else None)
979 crosstalkCalib =
CrosstalkCalib().fromDetector(detector, coeffVector=coeffVector)
980 inputs[
'crosstalk'] = crosstalkCalib
981 if inputs[
'crosstalk'].interChip
and len(inputs[
'crosstalk'].interChip) > 0:
982 if 'crosstalkSources' not in inputs:
983 self.log.warning(
"No crosstalkSources found for chip with interChip terms!")
986 if 'linearizer' in inputs:
987 if isinstance(inputs[
'linearizer'], dict):
989 linearizer.fromYaml(inputs[
'linearizer'])
990 self.log.warning(
"Dictionary linearizers will be deprecated in DM-28741.")
991 elif isinstance(inputs[
'linearizer'], numpy.ndarray):
995 self.log.warning(
"Bare lookup table linearizers will be deprecated in DM-28741.")
997 linearizer = inputs[
'linearizer']
998 linearizer.log = self.log
999 inputs[
'linearizer'] = linearizer
1002 self.log.warning(
"Constructing linearizer from cameraGeom information.")
1004 if self.config.doDefect
is True:
1005 if "defects" in inputs
and inputs[
'defects']
is not None:
1009 if not isinstance(inputs[
"defects"], Defects):
1010 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
1014 if self.config.doBrighterFatter:
1015 brighterFatterKernel = inputs.pop(
'newBFKernel',
None)
1016 if brighterFatterKernel
is None:
1017 brighterFatterKernel = inputs.get(
'bfKernel',
None)
1019 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
1021 detName = detector.getName()
1022 level = brighterFatterKernel.level
1025 inputs[
'bfGains'] = brighterFatterKernel.gain
1026 if self.config.brighterFatterLevel ==
'DETECTOR':
1027 if level ==
'DETECTOR':
1028 if detName
in brighterFatterKernel.detKernels:
1029 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1031 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
1032 elif level ==
'AMP':
1033 self.log.warning(
"Making DETECTOR level kernel from AMP based brighter "
1035 brighterFatterKernel.makeDetectorKernelFromAmpwiseKernels(detName)
1036 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1037 elif self.config.brighterFatterLevel ==
'AMP':
1038 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
1040 if self.config.doFringe
is True and self.fringe.checkFilter(inputs[
'ccdExposure']):
1041 expId = inputs[
'ccdExposure'].info.id
1042 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
1044 assembler=self.assembleCcd
1045 if self.config.doAssembleIsrExposures
else None)
1047 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
1049 if self.config.doStrayLight
is True and self.strayLight.checkFilter(inputs[
'ccdExposure']):
1050 if 'strayLightData' not in inputs:
1051 inputs[
'strayLightData'] =
None
1053 if self.config.doHeaderProvenance:
1055 exposureMetadata = inputs[
'ccdExposure'].getMetadata()
1056 for inputName
in sorted(inputs.keys()):
1057 reference = getattr(inputRefs, inputName,
None)
1058 if reference
is not None and hasattr(reference,
"run"):
1059 runKey = f
"LSST CALIB RUN {inputName.upper()}"
1060 runValue = reference.run
1061 idKey = f
"LSST CALIB UUID {inputName.upper()}"
1062 idValue =
str(reference.id)
1064 exposureMetadata[runKey] = runValue
1065 exposureMetadata[idKey] = idValue
1067 outputs = self.
run(**inputs)
1068 butlerQC.put(outputs, outputRefs)
1071 def run(self, ccdExposure, *, camera=None, bias=None, linearizer=None,
1072 crosstalk=None, crosstalkSources=None,
1073 dark=None, flat=None, ptc=None, bfKernel=None, bfGains=None, defects=None,
1074 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1075 sensorTransmission=
None, atmosphereTransmission=
None,
1076 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1077 deferredChargeCalib=
None,
1079 """Perform instrument signature removal on an exposure.
1081 Steps included in the ISR processing,
in order performed, are:
1083 - saturation
and suspect pixel masking
1084 - overscan subtraction
1085 - CCD assembly of individual amplifiers
1087 - variance image construction
1088 - linearization of non-linear response
1090 - brighter-fatter correction
1093 - stray light subtraction
1095 - masking of known defects
and camera specific features
1096 - vignette calculation
1097 - appending transmission curve
and distortion model
1102 The raw exposure that
is to be run through ISR. The
1103 exposure
is modified by this method.
1105 The camera geometry
for this exposure. Required
if
1106 one
or more of ``ccdExposure``, ``bias``, ``dark``,
or
1107 ``flat`` does
not have an associated detector.
1109 Bias calibration frame.
1111 Functor
for linearization.
1113 Calibration
for crosstalk.
1114 crosstalkSources : `list`, optional
1115 List of possible crosstalk sources.
1117 Dark calibration frame.
1119 Flat calibration frame.
1121 Photon transfer curve dataset,
with, e.g., gains
1123 bfKernel : `numpy.ndarray`, optional
1124 Brighter-fatter kernel.
1125 bfGains : `dict` of `float`, optional
1126 Gains used to override the detector
's nominal gains for the
1127 brighter-fatter correction. A dict keyed by amplifier name for
1128 the detector
in question.
1131 fringes : `lsst.pipe.base.Struct`, optional
1132 Struct containing the fringe correction data,
with
1138 random seed derived
from the ``ccdExposureId``
for random
1139 number generator (`numpy.uint32`)
1141 A ``TransmissionCurve`` that represents the throughput of the,
1142 optics, to be evaluated
in focal-plane coordinates.
1144 A ``TransmissionCurve`` that represents the throughput of the
1145 filter itself, to be evaluated
in focal-plane coordinates.
1147 A ``TransmissionCurve`` that represents the throughput of the
1148 sensor itself, to be evaluated
in post-assembly trimmed detector
1151 A ``TransmissionCurve`` that represents the throughput of the
1152 atmosphere, assumed to be spatially constant.
1153 detectorNum : `int`, optional
1154 The integer number
for the detector to process.
1155 strayLightData : `object`, optional
1156 Opaque object containing calibration information
for stray-light
1157 correction. If `
None`, no correction will be performed.
1159 Illumination correction image.
1163 result : `lsst.pipe.base.Struct`
1164 Result struct
with component:
1167 The fully ISR corrected exposure.
1172 Thumbnail image of the exposure after overscan subtraction.
1175 Thumbnail image of the exposure after flat-field correction.
1177 ``outputStatistics``
1178 Values of the additional statistics calculated.
1183 Raised
if a configuration option
is set to `
True`, but the
1184 required calibration data has
not been specified.
1188 The current processed exposure can be viewed by setting the
1189 appropriate `lsstDebug` entries
in the ``debug.display``
1190 dictionary. The names of these entries correspond to some of
1191 the `IsrTaskConfig` Boolean options,
with the value denoting the
1192 frame to use. The exposure
is shown inside the matching
1193 option check
and after the processing of that step has
1194 finished. The steps
with debug points are:
1205 In addition, setting the ``postISRCCD`` entry displays the
1206 exposure after all ISR processing has finished.
1209 ccdExposure = self.ensureExposure(ccdExposure, camera, detectorNum)
1214 ccd = ccdExposure.getDetector()
1215 filterLabel = ccdExposure.getFilter()
1216 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
1219 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd."
1220 ccd = [
FakeAmp(ccdExposure, self.config)]
1223 if self.config.doBias
and bias
is None:
1224 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1226 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1227 if self.config.doBrighterFatter
and bfKernel
is None:
1228 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1229 if self.config.doDark
and dark
is None:
1230 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1231 if self.config.doFlat
and flat
is None:
1232 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1233 if self.config.doDefect
and defects
is None:
1234 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1235 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters
1236 and fringes.fringes
is None):
1241 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1242 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters
1243 and illumMaskedImage
is None):
1244 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1245 if (self.config.doDeferredCharge
and deferredChargeCalib
is None):
1246 raise RuntimeError(
"Must supply a deferred charge calibration if config.doDeferredCharge=True.")
1248 if self.config.doHeaderProvenance:
1251 exposureMetadata = ccdExposure.getMetadata()
1252 if self.config.doBias:
1254 if self.config.doBrighterFatter:
1256 if self.config.doCrosstalk:
1257 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1258 if self.config.doDark:
1260 if self.config.doDefect:
1261 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1262 if self.config.doDeferredCharge:
1263 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1264 if self.config.doFlat:
1266 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters):
1267 exposureMetadata[
"LSST CALIB DATE FRINGE"] = self.
extractCalibDate(fringes.fringes)
1268 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters):
1269 exposureMetadata[
"LSST CALIB DATE ILLUMINATION"] = self.
extractCalibDate(illumMaskedImage)
1271 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1272 if self.config.usePtcGains
or self.config.usePtcReadNoise:
1274 if self.config.doStrayLight:
1275 exposureMetadata[
"LSST CALIB DATE STRAYLIGHT"] = self.
extractCalibDate(strayLightData)
1276 if self.config.doAttachTransmissionCurve:
1277 exposureMetadata[
"LSST CALIB DATE OPTICS_TR"] = self.
extractCalibDate(opticsTransmission)
1278 exposureMetadata[
"LSST CALIB DATE FILTER_TR"] = self.
extractCalibDate(filterTransmission)
1279 exposureMetadata[
"LSST CALIB DATE SENSOR_TR"] = self.
extractCalibDate(sensorTransmission)
1280 exposureMetadata[
"LSST CALIB DATE ATMOSP_TR"] = self.
extractCalibDate(atmosphereTransmission)
1283 if self.config.doConvertIntToFloat:
1284 self.log.info(
"Converting exposure to floating point values.")
1287 if self.config.doBias
and self.config.doBiasBeforeOverscan:
1288 self.log.info(
"Applying bias correction.")
1289 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1290 trimToFit=self.config.doTrimToMatchCalib)
1298 if ccdExposure.getBBox().contains(amp.getBBox()):
1303 if self.config.doOverscan
and not badAmp:
1306 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1307 if overscanResults
is not None and \
1308 self.config.qa
is not None and self.config.qa.saveStats
is True:
1310 self.metadata[f
"FIT MEDIAN {amp.getName()}"] = overscanResults.overscanMean
1311 self.metadata[f
"FIT STDEV {amp.getName()}"] = overscanResults.overscanSigma
1312 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1313 amp.getName(), overscanResults.overscanMean,
1314 overscanResults.overscanSigma)
1316 self.metadata[f
"RESIDUAL MEDIAN {amp.getName()}"] = overscanResults.residualMean
1317 self.metadata[f
"RESIDUAL STDEV {amp.getName()}"] = overscanResults.residualSigma
1318 self.log.debug(
" Overscan stats for amplifer %s after correction: %f +/- %f",
1319 amp.getName(), overscanResults.residualMean,
1320 overscanResults.residualSigma)
1322 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1325 self.log.warning(
"Amplifier %s is bad.", amp.getName())
1326 overscanResults =
None
1328 overscans.append(overscanResults
if overscanResults
is not None else None)
1330 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1332 if self.config.doDeferredCharge:
1333 self.log.info(
"Applying deferred charge/CTI correction.")
1334 self.deferredChargeCorrection.
run(ccdExposure, deferredChargeCalib)
1335 self.
debugView(ccdExposure,
"doDeferredCharge")
1337 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1338 self.log.info(
"Applying crosstalk correction.")
1339 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1340 crosstalkSources=crosstalkSources, camera=camera)
1341 self.
debugView(ccdExposure,
"doCrosstalk")
1343 if self.config.doAssembleCcd:
1344 self.log.info(
"Assembling CCD from amplifiers.")
1345 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1347 if self.config.expectWcs
and not ccdExposure.getWcs():
1348 self.log.warning(
"No WCS found in input exposure.")
1349 self.
debugView(ccdExposure,
"doAssembleCcd")
1352 if self.config.qa.doThumbnailOss:
1353 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1355 if self.config.doBias
and not self.config.doBiasBeforeOverscan:
1356 self.log.info(
"Applying bias correction.")
1357 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1358 trimToFit=self.config.doTrimToMatchCalib)
1361 if self.config.doVariance:
1362 for amp, overscanResults
in zip(ccd, overscans):
1363 if ccdExposure.getBBox().contains(amp.getBBox()):
1364 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1365 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1366 if overscanResults
is not None:
1368 overscanImage=overscanResults.overscanImage,
1374 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1375 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1376 afwMath.MEDIAN | afwMath.STDEVCLIP)
1377 self.metadata[f
"ISR VARIANCE {amp.getName()} MEDIAN"] = \
1378 qaStats.getValue(afwMath.MEDIAN)
1379 self.metadata[f
"ISR VARIANCE {amp.getName()} STDEV"] = \
1380 qaStats.getValue(afwMath.STDEVCLIP)
1381 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1382 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1383 qaStats.getValue(afwMath.STDEVCLIP))
1384 if self.config.maskNegativeVariance:
1388 self.log.info(
"Applying linearizer.")
1389 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1390 detector=ccd, log=self.log)
1392 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1393 self.log.info(
"Applying crosstalk correction.")
1394 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1395 crosstalkSources=crosstalkSources, isTrimmed=
True)
1396 self.
debugView(ccdExposure,
"doCrosstalk")
1401 if self.config.doDefect:
1402 self.log.info(
"Masking defects.")
1405 if self.config.numEdgeSuspect > 0:
1406 self.log.info(
"Masking edges as SUSPECT.")
1407 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1408 maskPlane=
"SUSPECT", level=self.config.edgeMaskLevel)
1410 if self.config.doNanMasking:
1411 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1414 if self.config.doWidenSaturationTrails:
1415 self.log.info(
"Widening saturation trails.")
1416 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1418 if self.config.doCameraSpecificMasking:
1419 self.log.info(
"Masking regions for camera specific reasons.")
1420 self.masking.
run(ccdExposure)
1422 if self.config.doBrighterFatter:
1432 interpExp = ccdExposure.clone()
1434 isrFunctions.interpolateFromMask(
1435 maskedImage=interpExp.getMaskedImage(),
1436 fwhm=self.config.fwhm,
1437 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1438 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1440 bfExp = interpExp.clone()
1442 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1443 type(bfKernel), type(bfGains))
1444 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1445 self.config.brighterFatterMaxIter,
1446 self.config.brighterFatterThreshold,
1447 self.config.brighterFatterApplyGain,
1449 if bfResults[1] == self.config.brighterFatterMaxIter:
1450 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1453 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1455 image = ccdExposure.getMaskedImage().getImage()
1456 bfCorr = bfExp.getMaskedImage().getImage()
1457 bfCorr -= interpExp.getMaskedImage().getImage()
1466 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1467 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1470 if self.config.brighterFatterMaskGrowSize > 0:
1471 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1472 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1473 isrFunctions.growMasks(ccdExposure.getMask(),
1474 radius=self.config.brighterFatterMaskGrowSize,
1475 maskNameList=maskPlane,
1476 maskValue=maskPlane)
1478 self.
debugView(ccdExposure,
"doBrighterFatter")
1480 if self.config.doDark:
1481 self.log.info(
"Applying dark correction.")
1485 if self.config.doFringe
and not self.config.fringeAfterFlat:
1486 self.log.info(
"Applying fringe correction before flat.")
1487 self.fringe.
run(ccdExposure, **fringes.getDict())
1490 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1491 self.log.info(
"Checking strayLight correction.")
1492 self.strayLight.
run(ccdExposure, strayLightData)
1493 self.
debugView(ccdExposure,
"doStrayLight")
1495 if self.config.doFlat:
1496 self.log.info(
"Applying flat correction.")
1500 if self.config.doApplyGains:
1501 self.log.info(
"Applying gain correction instead of flat.")
1502 if self.config.usePtcGains:
1503 self.log.info(
"Using gains from the Photon Transfer Curve.")
1504 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains,
1507 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1509 if self.config.doFringe
and self.config.fringeAfterFlat:
1510 self.log.info(
"Applying fringe correction after flat.")
1511 self.fringe.
run(ccdExposure, **fringes.getDict())
1513 if self.config.doVignette:
1514 if self.config.doMaskVignettePolygon:
1515 self.log.info(
"Constructing, attaching, and masking vignette polygon.")
1517 self.log.info(
"Constructing and attaching vignette polygon.")
1519 exposure=ccdExposure, doUpdateMask=self.config.doMaskVignettePolygon,
1520 vignetteValue=self.config.vignetteValue, log=self.log)
1522 if self.config.doAttachTransmissionCurve:
1523 self.log.info(
"Adding transmission curves.")
1524 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1525 filterTransmission=filterTransmission,
1526 sensorTransmission=sensorTransmission,
1527 atmosphereTransmission=atmosphereTransmission)
1529 flattenedThumb =
None
1530 if self.config.qa.doThumbnailFlattened:
1531 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1533 if self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters:
1534 self.log.info(
"Performing illumination correction.")
1535 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1536 illumMaskedImage, illumScale=self.config.illumScale,
1537 trimToFit=self.config.doTrimToMatchCalib)
1540 if self.config.doSaveInterpPixels:
1541 preInterpExp = ccdExposure.clone()
1556 if self.config.doSetBadRegions:
1557 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1558 if badPixelCount > 0:
1559 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1561 if self.config.doInterpolate:
1562 self.log.info(
"Interpolating masked pixels.")
1563 isrFunctions.interpolateFromMask(
1564 maskedImage=ccdExposure.getMaskedImage(),
1565 fwhm=self.config.fwhm,
1566 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1567 maskNameList=list(self.config.maskListToInterpolate)
1573 if self.config.doAmpOffset:
1574 self.log.info(
"Correcting amp offsets.")
1575 self.ampOffset.
run(ccdExposure)
1577 if self.config.doMeasureBackground:
1578 self.log.info(
"Measuring background level.")
1581 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1583 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1584 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1585 afwMath.MEDIAN | afwMath.STDEVCLIP)
1586 self.metadata[f
"ISR BACKGROUND {amp.getName()} MEDIAN"] = qaStats.getValue(afwMath.MEDIAN)
1587 self.metadata[f
"ISR BACKGROUND {amp.getName()} STDEV"] = \
1588 qaStats.getValue(afwMath.STDEVCLIP)
1589 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1590 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1591 qaStats.getValue(afwMath.STDEVCLIP))
1594 outputStatistics =
None
1595 if self.config.doCalculateStatistics:
1596 outputStatistics = self.isrStats.
run(ccdExposure, overscanResults=overscans,
1599 self.
debugView(ccdExposure,
"postISRCCD")
1601 return pipeBase.Struct(
1602 exposure=ccdExposure,
1604 flattenedThumb=flattenedThumb,
1606 preInterpExposure=preInterpExp,
1607 outputExposure=ccdExposure,
1608 outputOssThumbnail=ossThumb,
1609 outputFlattenedThumbnail=flattenedThumb,
1610 outputStatistics=outputStatistics,
1614 """Ensure that the data returned by Butler is a fully constructed exp.
1616 ISR requires exposure-level image data for historical reasons, so
if we
1617 did
not recieve that
from Butler, construct it
from what we have,
1618 modifying the input
in place.
1623 The input data structure obtained
from Butler.
1625 `lsst.afw.image.DecoratedImageU`,
1626 or `lsst.afw.image.ImageF`
1627 camera : `lsst.afw.cameraGeom.camera`, optional
1628 The camera associated
with the image. Used to find the appropriate
1629 detector
if detector
is not already set.
1630 detectorNum : `int`, optional
1631 The detector
in the camera to attach,
if the detector
is not
1637 The re-constructed exposure,
with appropriate detector parameters.
1642 Raised
if the input data cannot be used to construct an exposure.
1644 if isinstance(inputExp, afwImage.DecoratedImageU):
1645 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1646 elif isinstance(inputExp, afwImage.ImageF):
1647 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1648 elif isinstance(inputExp, afwImage.MaskedImageF):
1649 inputExp = afwImage.makeExposure(inputExp)
1650 elif isinstance(inputExp, afwImage.Exposure):
1652 elif inputExp
is None:
1656 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1659 if inputExp.getDetector()
is None:
1660 if camera
is None or detectorNum
is None:
1661 raise RuntimeError(
'Must supply both a camera and detector number when using exposures '
1662 'without a detector set.')
1663 inputExp.setDetector(camera[detectorNum])
1669 """Extract common calibration metadata values that will be written to
1675 Calibration to pull date information
from.
1680 Calibration creation date string to add to header.
1682 if hasattr(calib,
"getMetadata"):
1683 if 'CALIB_CREATION_DATE' in calib.getMetadata():
1684 return " ".join((calib.getMetadata().get(
"CALIB_CREATION_DATE",
"Unknown"),
1685 calib.getMetadata().get(
"CALIB_CREATION_TIME",
"Unknown")))
1687 return " ".join((calib.getMetadata().get(
"CALIB_CREATE_DATE",
"Unknown"),
1688 calib.getMetadata().get(
"CALIB_CREATE_TIME",
"Unknown")))
1690 return "Unknown Unknown"
1693 """Convert exposure image from uint16 to float.
1695 If the exposure does not need to be converted, the input
is
1696 immediately returned. For exposures that are converted to use
1697 floating point pixels, the variance
is set to unity
and the
1703 The raw exposure to be converted.
1708 The input ``exposure``, converted to floating point pixels.
1713 Raised
if the exposure type cannot be converted to float.
1716 if isinstance(exposure, afwImage.ExposureF):
1718 self.log.debug(
"Exposure already of type float.")
1720 if not hasattr(exposure,
"convertF"):
1721 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1723 newexposure = exposure.convertF()
1724 newexposure.variance[:] = 1
1725 newexposure.mask[:] = 0x0
1730 """Identify bad amplifiers, saturated and suspect pixels.
1735 Input exposure to be masked.
1737 Catalog of parameters defining the amplifier on this
1740 List of defects. Used to determine if the entire
1746 If this
is true, the entire amplifier area
is covered by
1747 defects
and unusable.
1750 maskedImage = ccdExposure.getMaskedImage()
1757 if defects
is not None:
1758 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1764 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1766 maskView = dataView.getMask()
1767 maskView |= maskView.getPlaneBitMask(
"BAD")
1775 if self.config.doSaturation
and not badAmp:
1776 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1777 if self.config.doSuspect
and not badAmp:
1778 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1779 if math.isfinite(self.config.saturation):
1780 limits.update({self.config.saturatedMaskName: self.config.saturation})
1782 for maskName, maskThreshold
in limits.items():
1783 if not math.isnan(maskThreshold):
1784 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1785 isrFunctions.makeThresholdMask(
1786 maskedImage=dataView,
1787 threshold=maskThreshold,
1794 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1796 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1797 self.config.suspectMaskName])
1798 if numpy.all(maskView.getArray() & maskVal > 0):
1800 maskView |= maskView.getPlaneBitMask(
"BAD")
1805 """Apply overscan correction in place.
1807 This method does initial pixel rejection of the overscan
1808 region. The overscan can also be optionally segmented to
1809 allow for discontinuous overscan responses to be fit
1810 separately. The actual overscan subtraction
is performed by
1811 the `lsst.ip.isr.overscan.OverscanTask`, which
is called here
1812 after the amplifier
is preprocessed.
1817 Exposure to have overscan correction performed.
1818 amp : `lsst.afw.cameraGeom.Amplifer`
1819 The amplifier to consider
while correcting the overscan.
1823 overscanResults : `lsst.pipe.base.Struct`
1824 Result struct
with components:
1827 Value
or fit subtracted
from the amplifier image data.
1830 Value
or fit subtracted
from the overscan image data.
1833 Image of the overscan region
with the overscan
1834 correction applied. This quantity
is used to estimate
1835 the amplifier read noise empirically.
1840 Median overscan fit value. (`float`)
1842 Clipped standard deviation of the overscan after
1843 correction. (`float`)
1848 Raised
if the ``amp`` does
not contain raw pixel information.
1852 lsst.ip.isr.overscan.OverscanTask
1855 if amp.getRawHorizontalOverscanBBox().isEmpty():
1856 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1860 overscanResults = self.overscan.
run(ccdExposure, amp)
1862 metadata = ccdExposure.getMetadata()
1863 ampNum = amp.getName()
1864 metadata[f
"ISR_OSCAN_LEVEL{ampNum}"] = overscanResults.overscanMean
1865 metadata[f
"ISR_OSCAN_SIGMA{ampNum}"] = overscanResults.overscanSigma
1867 return overscanResults
1870 """Set the variance plane using the gain and read noise
1872 The read noise is calculated
from the ``overscanImage``
if the
1873 ``doEmpiricalReadNoise`` option
is set
in the configuration; otherwise
1874 the value
from the amplifier data
is used.
1879 Exposure to process.
1881 Amplifier detector data.
1883 Image of overscan, required only
for empirical read noise.
1885 PTC dataset containing the gains
and read noise.
1890 Raised
if either ``usePtcGains`` of ``usePtcReadNoise``
1891 are ``
True``, but ptcDataset
is not provided.
1893 Raised
if ```doEmpiricalReadNoise``
is ``
True`` but
1894 ``overscanImage``
is ``
None``.
1898 lsst.ip.isr.isrFunctions.updateVariance
1900 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1901 if self.config.usePtcGains:
1902 if ptcDataset
is None:
1903 raise RuntimeError(
"No ptcDataset provided to use PTC gains.")
1905 gain = ptcDataset.gain[amp.getName()]
1906 self.log.info(
"Using gain from Photon Transfer Curve.")
1908 gain = amp.getGain()
1910 if math.isnan(gain):
1912 self.log.warning(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1915 self.log.warning(
"Gain for amp %s == %g <= 0; setting to %f.",
1916 amp.getName(), gain, patchedGain)
1919 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1920 badPixels = isrFunctions.countMaskedPixels(ampExposure.getMaskedImage(),
1921 [self.config.saturatedMaskName,
1922 self.config.suspectMaskName,
1924 allPixels = ampExposure.getWidth() * ampExposure.getHeight()
1925 if allPixels == badPixels:
1927 self.log.info(
"Skipping empirical read noise for amp %s. No good pixels.",
1930 raise RuntimeError(
"Overscan is none for EmpiricalReadNoise.")
1932 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1933 stats = afwMath.StatisticsControl()
1934 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1935 readNoise = afwMath.makeStatistics(overscanImage.getImage(),
1936 afwMath.STDEVCLIP, stats).getValue()
1937 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
1938 amp.getName(), readNoise)
1939 elif self.config.usePtcReadNoise:
1940 if ptcDataset
is None:
1941 raise RuntimeError(
"No ptcDataset provided to use PTC readnoise.")
1943 readNoise = ptcDataset.noise[amp.getName()]
1944 self.log.info(
"Using read noise from Photon Transfer Curve.")
1946 readNoise = amp.getReadNoise()
1948 metadata = ampExposure.getMetadata()
1949 metadata[f
'LSST GAIN {amp.getName()}'] = gain
1950 metadata[f
'LSST READNOISE {amp.getName()}'] = readNoise
1952 isrFunctions.updateVariance(
1953 maskedImage=ampExposure.getMaskedImage(),
1955 readNoise=readNoise,
1959 """Identify and mask pixels with negative variance values.
1964 Exposure to process.
1968 lsst.ip.isr.isrFunctions.updateVariance
1970 maskPlane = exposure.getMask().getPlaneBitMask(self.config.negativeVarianceMaskName)
1971 bad = numpy.where(exposure.getVariance().getArray() <= 0.0)
1972 exposure.mask.array[bad] |= maskPlane
1975 """Apply dark correction in place.
1980 Exposure to process.
1982 Dark exposure of the same size as ``exposure``.
1983 invert : `Bool`, optional
1984 If
True, re-add the dark to an already corrected image.
1989 Raised
if either ``exposure``
or ``darkExposure`` do
not
1990 have their dark time defined.
1994 lsst.ip.isr.isrFunctions.darkCorrection
1996 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1997 if math.isnan(expScale):
1998 raise RuntimeError(
"Exposure darktime is NAN.")
1999 if darkExposure.getInfo().getVisitInfo()
is not None \
2000 and not math.isnan(darkExposure.getInfo().getVisitInfo().getDarkTime()):
2001 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
2005 self.log.warning(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
2008 isrFunctions.darkCorrection(
2009 maskedImage=exposure.getMaskedImage(),
2010 darkMaskedImage=darkExposure.getMaskedImage(),
2012 darkScale=darkScale,
2014 trimToFit=self.config.doTrimToMatchCalib
2018 """Check if linearization is needed for the detector cameraGeom.
2020 Checks config.doLinearize and the linearity type of the first
2026 Detector to get linearity type
from.
2030 doLinearize : `Bool`
2031 If
True, linearization should be performed.
2033 return self.config.doLinearize
and \
2034 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
2037 """Apply flat correction in place.
2042 Exposure to process.
2044 Flat exposure of the same size as ``exposure``.
2045 invert : `Bool`, optional
2046 If
True, unflatten an already flattened image.
2050 lsst.ip.isr.isrFunctions.flatCorrection
2052 isrFunctions.flatCorrection(
2053 maskedImage=exposure.getMaskedImage(),
2054 flatMaskedImage=flatExposure.getMaskedImage(),
2055 scalingType=self.config.flatScalingType,
2056 userScale=self.config.flatUserScale,
2058 trimToFit=self.config.doTrimToMatchCalib
2062 """Detect and mask saturated pixels in config.saturatedMaskName.
2067 Exposure to process. Only the amplifier DataSec is processed.
2069 Amplifier detector data.
2073 lsst.ip.isr.isrFunctions.makeThresholdMask
2075 if not math.isnan(amp.getSaturation()):
2076 maskedImage = exposure.getMaskedImage()
2077 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2078 isrFunctions.makeThresholdMask(
2079 maskedImage=dataView,
2080 threshold=amp.getSaturation(),
2082 maskName=self.config.saturatedMaskName,
2086 """Interpolate over saturated pixels, in place.
2088 This method should be called after `saturationDetection`, to
2089 ensure that the saturated pixels have been identified in the
2090 SAT mask. It should also be called after `assembleCcd`, since
2091 saturated regions may cross amplifier boundaries.
2096 Exposure to process.
2100 lsst.ip.isr.isrTask.saturationDetection
2101 lsst.ip.isr.isrFunctions.interpolateFromMask
2103 isrFunctions.interpolateFromMask(
2104 maskedImage=exposure.getMaskedImage(),
2105 fwhm=self.config.fwhm,
2106 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2107 maskNameList=list(self.config.saturatedMaskName),
2111 """Detect and mask suspect pixels in config.suspectMaskName.
2116 Exposure to process. Only the amplifier DataSec is processed.
2118 Amplifier detector data.
2122 lsst.ip.isr.isrFunctions.makeThresholdMask
2126 Suspect pixels are pixels whose value
is greater than
2127 amp.getSuspectLevel(). This
is intended to indicate pixels that may be
2128 affected by unknown systematics;
for example
if non-linearity
2129 corrections above a certain level are unstable then that would be a
2130 useful value
for suspectLevel. A value of `nan` indicates that no such
2131 level exists
and no pixels are to be masked
as suspicious.
2133 suspectLevel = amp.getSuspectLevel()
2134 if math.isnan(suspectLevel):
2137 maskedImage = exposure.getMaskedImage()
2138 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2139 isrFunctions.makeThresholdMask(
2140 maskedImage=dataView,
2141 threshold=suspectLevel,
2143 maskName=self.config.suspectMaskName,
2147 """Mask defects using mask plane "BAD", in place.
2152 Exposure to process.
2153 defectBaseList : defect-type
2159 Call this after CCD assembly, since defects may cross amplifier
2162 maskedImage = exposure.getMaskedImage()
2163 if not isinstance(defectBaseList, Defects):
2165 defectList =
Defects(defectBaseList)
2167 defectList = defectBaseList
2168 defectList.maskPixels(maskedImage, maskName=
"BAD")
2170 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR'):
2171 """Mask edge pixels with applicable mask plane.
2176 Exposure to process.
2177 numEdgePixels : `int`, optional
2178 Number of edge pixels to mask.
2179 maskPlane : `str`, optional
2180 Mask plane name to use.
2181 level : `str`, optional
2182 Level at which to mask edges.
2184 maskedImage = exposure.getMaskedImage()
2185 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2187 if numEdgePixels > 0:
2188 if level ==
'DETECTOR':
2189 boxes = [maskedImage.getBBox()]
2190 elif level ==
'AMP':
2191 boxes = [amp.getBBox()
for amp
in exposure.getDetector()]
2196 subImage = maskedImage[box]
2197 box.grow(-numEdgePixels)
2199 SourceDetectionTask.setEdgeBits(
2205 """Mask and interpolate defects using mask plane "BAD", in place.
2210 Exposure to process.
2211 defectBaseList : defects-like
2212 List of defects to mask and interpolate. Can be
2217 lsst.ip.isr.isrTask.maskDefect
2220 self.maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2221 maskPlane="SUSPECT", level=self.config.edgeMaskLevel)
2222 isrFunctions.interpolateFromMask(
2223 maskedImage=exposure.getMaskedImage(),
2224 fwhm=self.config.fwhm,
2225 growSaturatedFootprints=0,
2226 maskNameList=[
"BAD"],
2230 """Mask NaNs using mask plane "UNMASKEDNAN", in place.
2235 Exposure to process.
2239 We mask over all non-finite values (NaN, inf), including those
2240 that are masked with other bits (because those may
or may
not be
2241 interpolated over later,
and we want to remove all NaN/infs).
2242 Despite this behaviour, the
"UNMASKEDNAN" mask plane
is used to
2243 preserve the historical name.
2245 maskedImage = exposure.getMaskedImage()
2248 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2249 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2250 numNans = maskNans(maskedImage, maskVal)
2251 self.metadata[
"NUMNANS"] = numNans
2253 self.log.warning(
"There were %d unmasked NaNs.", numNans)
2256 """"Mask and interpolate NaN/infs using mask plane "UNMASKEDNAN",
2262 Exposure to process.
2266 lsst.ip.isr.isrTask.maskNan
2269 isrFunctions.interpolateFromMask(
2270 maskedImage=exposure.getMaskedImage(),
2271 fwhm=self.config.fwhm,
2272 growSaturatedFootprints=0,
2273 maskNameList=["UNMASKEDNAN"],
2277 """Measure the image background in subgrids, for quality control.
2282 Exposure to process.
2284 Configuration object containing parameters on which background
2285 statistics and subgrids to use.
2287 if IsrQaConfig
is not None:
2288 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2289 IsrQaConfig.flatness.nIter)
2290 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2291 statsControl.setAndMask(maskVal)
2292 maskedImage = exposure.getMaskedImage()
2293 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2294 skyLevel = stats.getValue(afwMath.MEDIAN)
2295 skySigma = stats.getValue(afwMath.STDEVCLIP)
2296 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2297 metadata = exposure.getMetadata()
2298 metadata[
"SKYLEVEL"] = skyLevel
2299 metadata[
"SKYSIGMA"] = skySigma
2302 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2303 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2304 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2305 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2306 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2307 skyLevels = numpy.zeros((nX, nY))
2310 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2312 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2314 xLLC = xc - meshXHalf
2315 yLLC = yc - meshYHalf
2316 xURC = xc + meshXHalf - 1
2317 yURC = yc + meshYHalf - 1
2320 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2322 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2324 good = numpy.where(numpy.isfinite(skyLevels))
2325 skyMedian = numpy.median(skyLevels[good])
2326 flatness = (skyLevels[good] - skyMedian) / skyMedian
2327 flatness_rms = numpy.std(flatness)
2328 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2330 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2331 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2332 nX, nY, flatness_pp, flatness_rms)
2334 metadata[
"FLATNESS_PP"] = float(flatness_pp)
2335 metadata[
"FLATNESS_RMS"] = float(flatness_rms)
2336 metadata[
"FLATNESS_NGRIDS"] =
'%dx%d' % (nX, nY)
2337 metadata[
"FLATNESS_MESHX"] = IsrQaConfig.flatness.meshX
2338 metadata[
"FLATNESS_MESHY"] = IsrQaConfig.flatness.meshY
2341 """Set an approximate magnitude zero point for the exposure.
2346 Exposure to process.
2348 filterLabel = exposure.getFilter()
2349 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
2351 if physicalFilter
in self.config.fluxMag0T1:
2352 fluxMag0 = self.config.fluxMag0T1[physicalFilter]
2354 self.log.warning(
"No rough magnitude zero point defined for filter %s.", physicalFilter)
2355 fluxMag0 = self.config.defaultFluxMag0T1
2357 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2359 self.log.warning(
"Non-positive exposure time; skipping rough zero point.")
2362 self.log.info(
"Setting rough magnitude zero point for filter %s: %f",
2363 physicalFilter, 2.5*math.log10(fluxMag0*expTime))
2364 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2368 """Context manager that applies and removes flats and darks,
2369 if the task
is configured to apply them.
2374 Exposure to process.
2376 Flat exposure the same size
as ``exp``.
2378 Dark exposure the same size
as ``exp``.
2383 The flat
and dark corrected exposure.
2385 if self.config.doDark
and dark
is not None:
2387 if self.config.doFlat:
2392 if self.config.doFlat:
2394 if self.config.doDark
and dark
is not None:
2398 """Utility function to examine ISR exposure at different stages.
2405 State of processing to view.
2407 frame = getDebugFrame(self._display, stepname)
2409 display = getDisplay(frame)
2410 display.scale(
'asinh',
'zscale')
2411 display.mtv(exposure)
2412 prompt =
"Press Enter to continue [c]... "
2414 ans = input(prompt).lower()
2415 if ans
in (
"",
"c",):
2420 """A Detector-like object that supports returning gain and saturation level
2422 This is used when the input exposure does
not have a detector.
2427 Exposure to generate a fake amplifier
for.
2428 config : `lsst.ip.isr.isrTaskConfig`
2429 Configuration to apply to the fake amplifier.
2433 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2435 self.
_gain = config.gain
def getRawHorizontalOverscanBBox(self)
def getSuspectLevel(self)
_RawHorizontalOverscanBBox
def __init__(self, exposure, config)
doSaturationInterpolation
def __init__(self, *config=None)
def flatCorrection(self, exposure, flatExposure, invert=False)
def maskAndInterpolateNan(self, exposure)
def saturationInterpolation(self, exposure)
def maskNan(self, exposure)
def maskAmplifier(self, ccdExposure, amp, defects)
def debugView(self, exposure, stepname)
def extractCalibDate(calib)
def ensureExposure(self, inputExp, camera=None, detectorNum=None)
def maskNegativeVariance(self, exposure)
def saturationDetection(self, exposure, amp)
def maskDefect(self, exposure, defectBaseList)
def __init__(self, **kwargs)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR')
def overscanCorrection(self, ccdExposure, amp)
def run(self, ccdExposure, *camera=None, bias=None, linearizer=None, crosstalk=None, crosstalkSources=None, dark=None, flat=None, ptc=None, bfKernel=None, bfGains=None, defects=None, fringes=pipeBase.Struct(fringes=None), opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, detectorNum=None, strayLightData=None, illumMaskedImage=None, deferredChargeCalib=None)
def measureBackground(self, exposure, IsrQaConfig=None)
def roughZeroPoint(self, exposure)
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def doLinearize(self, detector)
def flatContext(self, exp, flat, dark=None)
def convertIntToFloat(self, exposure)
def suspectDetection(self, exposure, amp)
def updateVariance(self, ampExposure, amp, overscanImage=None, ptcDataset=None)
def darkCorrection(self, exposure, darkExposure, invert=False)
def crosstalkSourceLookup(datasetType, registry, quantumDataId, collections)