32 from contextlib
import contextmanager
33 from lsstDebug
import getDebugFrame
43 from .
import isrFunctions
45 from .
import linearize
47 from .assembleCcdTask
import AssembleCcdTask
48 from .crosstalk
import CrosstalkTask
49 from .fringe
import FringeTask
50 from .isr
import maskNans
51 from .masking
import MaskingTask
52 from .straylight
import StrayLightTask
53 from .vignette
import VignetteTask
55 __all__ = [
"IsrTask",
"IsrTaskConfig",
"RunIsrTask",
"RunIsrConfig"]
59 """Configuration parameters for IsrTask. 61 Items are grouped in the order in which they are executed by the task. 66 isrName = pexConfig.Field(
73 ccdExposure = pipeBase.InputDatasetField(
74 doc=
"Input exposure to process",
77 storageClass=
"Exposure",
78 dimensions=[
"instrument",
"exposure",
"detector"],
80 camera = pipeBase.InputDatasetField(
81 doc=
"Input camera to construct complete exposures.",
84 storageClass=
"TablePersistableCamera",
85 dimensions=[
"instrument",
"calibration_label"],
87 bias = pipeBase.InputDatasetField(
88 doc=
"Input bias calibration.",
91 storageClass=
"ImageF",
92 dimensions=[
"instrument",
"calibration_label",
"detector"],
94 dark = pipeBase.InputDatasetField(
95 doc=
"Input dark calibration.",
98 storageClass=
"ImageF",
99 dimensions=[
"instrument",
"calibration_label",
"detector"],
101 flat = pipeBase.InputDatasetField(
102 doc=
"Input flat calibration.",
105 storageClass=
"MaskedImageF",
106 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
108 bfKernel = pipeBase.InputDatasetField(
109 doc=
"Input brighter-fatter kernel.",
112 storageClass=
"NumpyArray",
113 dimensions=[
"instrument",
"calibration_label"],
115 defects = pipeBase.InputDatasetField(
116 doc=
"Input defect tables.",
119 storageClass=
"DefectsList",
120 dimensions=[
"instrument",
"calibration_label",
"detector"],
122 opticsTransmission = pipeBase.InputDatasetField(
123 doc=
"Transmission curve due to the optics.",
124 name=
"transmission_optics",
126 storageClass=
"TablePersistableTransmissionCurve",
127 dimensions=[
"instrument",
"calibration_label"],
129 filterTransmission = pipeBase.InputDatasetField(
130 doc=
"Transmission curve due to the filter.",
131 name=
"transmission_filter",
133 storageClass=
"TablePersistableTransmissionCurve",
134 dimensions=[
"instrument",
"physical_filter",
"calibration_label"],
136 sensorTransmission = pipeBase.InputDatasetField(
137 doc=
"Transmission curve due to the sensor.",
138 name=
"transmission_sensor",
140 storageClass=
"TablePersistableTransmissionCurve",
141 dimensions=[
"instrument",
"calibration_label",
"detector"],
143 atmosphereTransmission = pipeBase.InputDatasetField(
144 doc=
"Transmission curve due to the atmosphere.",
145 name=
"transmission_atmosphere",
147 storageClass=
"TablePersistableTransmissionCurve",
148 dimensions=[
"instrument"],
152 outputExposure = pipeBase.OutputDatasetField(
153 doc=
"Output ISR processed exposure.",
156 storageClass=
"ExposureF",
157 dimensions=[
"instrument",
"visit",
"detector"],
159 outputOssThumbnail = pipeBase.OutputDatasetField(
160 doc=
"Output Overscan-subtracted thumbnail image.",
163 storageClass=
"Thumbnail",
164 dimensions=[
"instrument",
"visit",
"detector"],
166 outputFlattenedThumbnail = pipeBase.OutputDatasetField(
167 doc=
"Output flat-corrected thumbnail image.",
168 name=
"FlattenedThumb",
170 storageClass=
"TextStorage",
171 dimensions=[
"instrument",
"visit",
"detector"],
174 quantum = pipeBase.QuantumConfig(
175 dimensions=[
"visit",
"detector",
"instrument"],
179 datasetType = pexConfig.Field(
181 doc=
"Dataset type for input data; users will typically leave this alone, " 182 "but camera-specific ISR tasks will override it",
186 fallbackFilterName = pexConfig.Field(
188 doc=
"Fallback default filter name for calibrations.",
191 expectWcs = pexConfig.Field(
194 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 196 fwhm = pexConfig.Field(
198 doc=
"FWHM of PSF in arcseconds.",
201 qa = pexConfig.ConfigField(
203 doc=
"QA related configuration options.",
207 doConvertIntToFloat = pexConfig.Field(
209 doc=
"Convert integer raw images to floating point values?",
214 doSaturation = pexConfig.Field(
216 doc=
"Mask saturated pixels? NB: this is totally independent of the" 217 " interpolation option - this is ONLY setting the bits in the mask." 218 " To have them interpolated make sure doSaturationInterpolation=True",
221 saturatedMaskName = pexConfig.Field(
223 doc=
"Name of mask plane to use in saturation detection and interpolation",
226 saturation = pexConfig.Field(
228 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
229 default=
float(
"NaN"),
231 growSaturationFootprintSize = pexConfig.Field(
233 doc=
"Number of pixels by which to grow the saturation footprints",
238 doSuspect = pexConfig.Field(
240 doc=
"Mask suspect pixels?",
243 suspectMaskName = pexConfig.Field(
245 doc=
"Name of mask plane to use for suspect pixels",
248 numEdgeSuspect = pexConfig.Field(
250 doc=
"Number of edge pixels to be flagged as untrustworthy.",
255 doSetBadRegions = pexConfig.Field(
257 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
260 badStatistic = pexConfig.ChoiceField(
262 doc=
"How to estimate the average value for BAD regions.",
265 "MEANCLIP":
"Correct using the (clipped) mean of good data",
266 "MEDIAN":
"Correct using the median of the good data",
271 doOverscan = pexConfig.Field(
273 doc=
"Do overscan subtraction?",
276 overscanFitType = pexConfig.ChoiceField(
278 doc=
"The method for fitting the overscan bias level.",
281 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
282 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
283 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
284 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
285 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
286 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
287 "MEAN":
"Correct using the mean of the overscan region",
288 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
289 "MEDIAN":
"Correct using the median of the overscan region",
292 overscanOrder = pexConfig.Field(
294 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
295 "or number of spline knots if overscan fit type is a spline."),
298 overscanNumSigmaClip = pexConfig.Field(
300 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
303 overscanIsInt = pexConfig.Field(
305 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN",
308 overscanNumLeadingColumnsToSkip = pexConfig.Field(
310 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
313 overscanNumTrailingColumnsToSkip = pexConfig.Field(
315 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
318 overscanMaxDev = pexConfig.Field(
320 doc=
"Maximum deviation from the median for overscan",
321 default=1000.0, check=
lambda x: x > 0
323 overscanBiasJump = pexConfig.Field(
325 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
328 overscanBiasJumpKeyword = pexConfig.Field(
330 doc=
"Header keyword containing information about devices.",
331 default=
"NO_SUCH_KEY",
333 overscanBiasJumpDevices = pexConfig.ListField(
335 doc=
"List of devices that need piecewise overscan correction.",
338 overscanBiasJumpLocation = pexConfig.Field(
340 doc=
"Location of bias jump along y-axis.",
345 doAssembleCcd = pexConfig.Field(
348 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 350 assembleCcd = pexConfig.ConfigurableField(
351 target=AssembleCcdTask,
352 doc=
"CCD assembly task",
356 doAssembleIsrExposures = pexConfig.Field(
359 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 361 doTrimToMatchCalib = pexConfig.Field(
364 doc=
"Trim raw data to match calibration bounding boxes?" 368 doBias = pexConfig.Field(
370 doc=
"Apply bias frame correction?",
373 biasDataProductName = pexConfig.Field(
375 doc=
"Name of the bias data product",
380 doVariance = pexConfig.Field(
382 doc=
"Calculate variance?",
385 gain = pexConfig.Field(
387 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
388 default=
float(
"NaN"),
390 readNoise = pexConfig.Field(
392 doc=
"The read noise to use if no Detector is present in the Exposure",
395 doEmpiricalReadNoise = pexConfig.Field(
398 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 402 doLinearize = pexConfig.Field(
404 doc=
"Correct for nonlinearity of the detector's response?",
409 doCrosstalk = pexConfig.Field(
411 doc=
"Apply intra-CCD crosstalk correction?",
414 doCrosstalkBeforeAssemble = pexConfig.Field(
416 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
419 crosstalk = pexConfig.ConfigurableField(
420 target=CrosstalkTask,
421 doc=
"Intra-CCD crosstalk correction",
425 doDefect = pexConfig.Field(
427 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
430 numEdgeSuspect = pexConfig.Field(
432 doc=
"Number of edge pixels to be flagged as untrustworthy.",
435 doNanMasking = pexConfig.Field(
437 doc=
"Mask NAN pixels?",
440 doWidenSaturationTrails = pexConfig.Field(
442 doc=
"Widen bleed trails based on their width?",
447 doBrighterFatter = pexConfig.Field(
450 doc=
"Apply the brighter fatter correction" 452 brighterFatterLevel = pexConfig.ChoiceField(
455 doc=
"The level at which to correct for brighter-fatter.",
457 "AMP":
"Every amplifier treated separately.",
458 "DETECTOR":
"One kernel per detector",
461 brighterFatterKernelFile = pexConfig.Field(
464 doc=
"Kernel file used for the brighter fatter correction" 466 brighterFatterMaxIter = pexConfig.Field(
469 doc=
"Maximum number of iterations for the brighter fatter correction" 471 brighterFatterThreshold = pexConfig.Field(
474 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 475 " absolute value of the difference between the current corrected image and the one" 476 " from the previous iteration summed over all the pixels." 478 brighterFatterApplyGain = pexConfig.Field(
481 doc=
"Should the gain be applied when applying the brighter fatter correction?" 485 doDark = pexConfig.Field(
487 doc=
"Apply dark frame correction?",
490 darkDataProductName = pexConfig.Field(
492 doc=
"Name of the dark data product",
497 doStrayLight = pexConfig.Field(
499 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
502 strayLight = pexConfig.ConfigurableField(
503 target=StrayLightTask,
504 doc=
"y-band stray light correction" 508 doFlat = pexConfig.Field(
510 doc=
"Apply flat field correction?",
513 flatDataProductName = pexConfig.Field(
515 doc=
"Name of the flat data product",
518 flatScalingType = pexConfig.ChoiceField(
520 doc=
"The method for scaling the flat on the fly.",
523 "USER":
"Scale by flatUserScale",
524 "MEAN":
"Scale by the inverse of the mean",
525 "MEDIAN":
"Scale by the inverse of the median",
528 flatUserScale = pexConfig.Field(
530 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
533 doTweakFlat = pexConfig.Field(
535 doc=
"Tweak flats to match observed amplifier ratios?",
540 doApplyGains = pexConfig.Field(
542 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
545 normalizeGains = pexConfig.Field(
547 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
552 doFringe = pexConfig.Field(
554 doc=
"Apply fringe correction?",
557 fringe = pexConfig.ConfigurableField(
559 doc=
"Fringe subtraction task",
561 fringeAfterFlat = pexConfig.Field(
563 doc=
"Do fringe subtraction after flat-fielding?",
568 doAddDistortionModel = pexConfig.Field(
570 doc=
"Apply a distortion model based on camera geometry to the WCS?",
575 doMeasureBackground = pexConfig.Field(
577 doc=
"Measure the background level on the reduced image?",
582 doCameraSpecificMasking = pexConfig.Field(
584 doc=
"Mask camera-specific bad regions?",
587 masking = pexConfig.ConfigurableField(
594 doInterpolate = pexConfig.Field(
596 doc=
"Interpolate masked pixels?",
599 doSaturationInterpolation = pexConfig.Field(
601 doc=
"Perform interpolation over pixels masked as saturated?" 602 " NB: This is independent of doSaturation; if that is False this plane" 603 " will likely be blank, resulting in a no-op here.",
606 doNanInterpolation = pexConfig.Field(
608 doc=
"Perform interpolation over pixels masked as NaN?" 609 " NB: This is independent of doNanMasking; if that is False this plane" 610 " will likely be blank, resulting in a no-op here.",
613 doNanInterpAfterFlat = pexConfig.Field(
615 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 616 "also have to interpolate them before flat-fielding."),
619 maskListToInterpolate = pexConfig.ListField(
621 doc=
"List of mask planes that should be interpolated.",
622 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
624 doSaveInterpPixels = pexConfig.Field(
626 doc=
"Save a copy of the pre-interpolated pixel values?",
631 fluxMag0T1 = pexConfig.DictField(
634 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
635 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
638 defaultFluxMag0T1 = pexConfig.Field(
640 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
641 default=pow(10.0, 0.4*28.0)
645 doVignette = pexConfig.Field(
647 doc=
"Apply vignetting parameters?",
650 vignette = pexConfig.ConfigurableField(
652 doc=
"Vignetting task.",
656 doAttachTransmissionCurve = pexConfig.Field(
659 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 661 doUseOpticsTransmission = pexConfig.Field(
664 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 666 doUseFilterTransmission = pexConfig.Field(
669 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 671 doUseSensorTransmission = pexConfig.Field(
674 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 676 doUseAtmosphereTransmission = pexConfig.Field(
679 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 683 doWrite = pexConfig.Field(
685 doc=
"Persist postISRCCD?",
692 raise ValueError(
"You may not specify both doFlat and doApplyGains")
694 self.config.maskListToInterpolate.append(
"SAT")
696 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
699 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
700 """Apply common instrument signature correction algorithms to a raw frame. 702 The process for correcting imaging data is very similar from 703 camera to camera. This task provides a vanilla implementation of 704 doing these corrections, including the ability to turn certain 705 corrections off if they are not needed. The inputs to the primary 706 method, `run()`, are a raw exposure to be corrected and the 707 calibration data products. The raw input is a single chip sized 708 mosaic of all amps including overscans and other non-science 709 pixels. The method `runDataRef()` identifies and defines the 710 calibration data products, and is intended for use by a 711 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 712 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 713 subclassed for different camera, although the most camera specific 714 methods have been split into subtasks that can be redirected 717 The __init__ method sets up the subtasks for ISR processing, using 718 the defaults from `lsst.ip.isr`. 723 Positional arguments passed to the Task constructor. None used at this time. 724 kwargs : `dict`, optional 725 Keyword arguments passed on to the Task constructor. None used at this time. 727 ConfigClass = IsrTaskConfig
732 self.makeSubtask(
"assembleCcd")
733 self.makeSubtask(
"crosstalk")
734 self.makeSubtask(
"strayLight")
735 self.makeSubtask(
"fringe")
736 self.makeSubtask(
"masking")
737 self.makeSubtask(
"vignette")
746 if config.doBias
is not True:
747 inputTypeDict.pop(
"bias",
None)
748 if config.doLinearize
is not True:
749 inputTypeDict.pop(
"linearizer",
None)
750 if config.doCrosstalk
is not True:
751 inputTypeDict.pop(
"crosstalkSources",
None)
752 if config.doBrighterFatter
is not True:
753 inputTypeDict.pop(
"bfKernel",
None)
754 if config.doDefect
is not True:
755 inputTypeDict.pop(
"defects",
None)
756 if config.doDark
is not True:
757 inputTypeDict.pop(
"dark",
None)
758 if config.doFlat
is not True:
759 inputTypeDict.pop(
"flat",
None)
760 if config.doAttachTransmissionCurve
is not True:
761 inputTypeDict.pop(
"opticsTransmission",
None)
762 inputTypeDict.pop(
"filterTransmission",
None)
763 inputTypeDict.pop(
"sensorTransmission",
None)
764 inputTypeDict.pop(
"atmosphereTransmission",
None)
765 if config.doUseOpticsTransmission
is not True:
766 inputTypeDict.pop(
"opticsTransmission",
None)
767 if config.doUseFilterTransmission
is not True:
768 inputTypeDict.pop(
"filterTransmission",
None)
769 if config.doUseSensorTransmission
is not True:
770 inputTypeDict.pop(
"sensorTransmission",
None)
771 if config.doUseAtmosphereTransmission
is not True:
772 inputTypeDict.pop(
"atmosphereTransmission",
None)
780 if config.qa.doThumbnailOss
is not True:
781 outputTypeDict.pop(
"outputOssThumbnail",
None)
782 if config.qa.doThumbnailFlattened
is not True:
783 outputTypeDict.pop(
"outputFlattenedThumbnail",
None)
784 if config.doWrite
is not True:
785 outputTypeDict.pop(
"outputExposure",
None)
787 return outputTypeDict
796 names.remove(
"ccdExposure")
805 return frozenset([
"calibration_label"])
809 inputData[
'detectorNum'] =
int(inputDataIds[
'ccdExposure'][
'detector'])
810 except Exception
as e:
811 raise ValueError(f
"Failure to find valid detectorNum value for Dataset {inputDataIds}: {e}")
813 inputData[
'isGen3'] =
True 815 if self.config.doLinearize
is True:
816 if 'linearizer' not in inputData.keys():
817 detector = inputData[
'camera'][inputData[
'detectorNum']]
818 linearityName = detector.getAmpInfoCatalog()[0].getLinearityType()
819 inputData[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
821 if inputData[
'defects']
is not None:
824 if not isinstance(inputData[
"defects"], Defects):
825 inputData[
"defects"] = Defects.fromTable(inputData[
"defects"])
842 return super().
adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
848 """!Retrieve necessary frames for instrument signature removal. 850 Pre-fetching all required ISR data products limits the IO 851 required by the ISR. Any conflict between the calibration data 852 available and that needed for ISR is also detected prior to 853 doing processing, allowing it to fail quickly. 857 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 858 Butler reference of the detector data to be processed 859 rawExposure : `afw.image.Exposure` 860 The raw exposure that will later be corrected with the 861 retrieved calibration data; should not be modified in this 866 result : `lsst.pipe.base.Struct` 867 Result struct with components (which may be `None`): 868 - ``bias``: bias calibration frame (`afw.image.Exposure`) 869 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 870 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 871 - ``dark``: dark calibration frame (`afw.image.Exposure`) 872 - ``flat``: flat calibration frame (`afw.image.Exposure`) 873 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 874 - ``defects``: list of defects (`lsst.meas.algorithms.Defects`) 875 - ``fringes``: `lsst.pipe.base.Struct` with components: 876 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 877 - ``seed``: random seed derived from the ccdExposureId for random 878 number generator (`uint32`) 879 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 880 A ``TransmissionCurve`` that represents the throughput of the optics, 881 to be evaluated in focal-plane coordinates. 882 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 883 A ``TransmissionCurve`` that represents the throughput of the filter 884 itself, to be evaluated in focal-plane coordinates. 885 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 886 A ``TransmissionCurve`` that represents the throughput of the sensor 887 itself, to be evaluated in post-assembly trimmed detector coordinates. 888 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 889 A ``TransmissionCurve`` that represents the throughput of the 890 atmosphere, assumed to be spatially constant. 891 - ``strayLightData`` : `object` 892 An opaque object containing calibration information for 893 stray-light correction. If `None`, no correction will be 898 NotImplementedError : 899 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 901 ccd = rawExposure.getDetector()
902 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
903 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
904 if self.config.doBias
else None)
906 linearizer = (dataRef.get(
"linearizer", immediate=
True)
908 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
909 if self.config.doCrosstalk
else None)
910 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
911 if self.config.doDark
else None)
912 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName)
913 if self.config.doFlat
else None)
915 brighterFatterKernel =
None 916 if self.config.doBrighterFatter
is True:
920 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
924 brighterFatterKernel = dataRef.get(
"bfKernel")
926 brighterFatterKernel =
None 927 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
930 if self.config.brighterFatterLevel ==
'DETECTOR':
931 brighterFatterKernel = brighterFatterKernel.kernel[ccd.getId()]
934 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
936 defectList = (dataRef.get(
"defects")
937 if self.config.doDefect
else None)
938 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
939 if self.config.doAssembleIsrExposures
else None)
940 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
941 else pipeBase.Struct(fringes=
None))
943 if self.config.doAttachTransmissionCurve:
944 opticsTransmission = (dataRef.get(
"transmission_optics")
945 if self.config.doUseOpticsTransmission
else None)
946 filterTransmission = (dataRef.get(
"transmission_filter")
947 if self.config.doUseFilterTransmission
else None)
948 sensorTransmission = (dataRef.get(
"transmission_sensor")
949 if self.config.doUseSensorTransmission
else None)
950 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
951 if self.config.doUseAtmosphereTransmission
else None)
953 opticsTransmission =
None 954 filterTransmission =
None 955 sensorTransmission =
None 956 atmosphereTransmission =
None 958 if self.config.doStrayLight:
959 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
961 strayLightData =
None 964 return pipeBase.Struct(bias=biasExposure,
965 linearizer=linearizer,
966 crosstalkSources=crosstalkSources,
969 bfKernel=brighterFatterKernel,
971 fringes=fringeStruct,
972 opticsTransmission=opticsTransmission,
973 filterTransmission=filterTransmission,
974 sensorTransmission=sensorTransmission,
975 atmosphereTransmission=atmosphereTransmission,
976 strayLightData=strayLightData
980 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
981 dark=None, flat=None, bfKernel=None, defects=None, fringes=None,
982 opticsTransmission=None, filterTransmission=None,
983 sensorTransmission=None, atmosphereTransmission=None,
984 detectorNum=None, strayLightData=None, isGen3=False,
986 """!Perform instrument signature removal on an exposure. 988 Steps included in the ISR processing, in order performed, are: 989 - saturation and suspect pixel masking 990 - overscan subtraction 991 - CCD assembly of individual amplifiers 993 - variance image construction 994 - linearization of non-linear response 996 - brighter-fatter correction 999 - stray light subtraction 1001 - masking of known defects and camera specific features 1002 - vignette calculation 1003 - appending transmission curve and distortion model 1007 ccdExposure : `lsst.afw.image.Exposure` 1008 The raw exposure that is to be run through ISR. The 1009 exposure is modified by this method. 1010 camera : `lsst.afw.cameraGeom.Camera`, optional 1011 The camera geometry for this exposure. Used to select the 1012 distortion model appropriate for this data. 1013 bias : `lsst.afw.image.Exposure`, optional 1014 Bias calibration frame. 1015 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1016 Functor for linearization. 1017 crosstalkSources : `list`, optional 1018 List of possible crosstalk sources. 1019 dark : `lsst.afw.image.Exposure`, optional 1020 Dark calibration frame. 1021 flat : `lsst.afw.image.Exposure`, optional 1022 Flat calibration frame. 1023 bfKernel : `numpy.ndarray`, optional 1024 Brighter-fatter kernel. 1025 defects : `lsst.meas.algorithms.Defects`, optional 1027 fringes : `lsst.pipe.base.Struct`, optional 1028 Struct containing the fringe correction data, with 1030 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1031 - ``seed``: random seed derived from the ccdExposureId for random 1032 number generator (`uint32`) 1033 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1034 A ``TransmissionCurve`` that represents the throughput of the optics, 1035 to be evaluated in focal-plane coordinates. 1036 filterTransmission : `lsst.afw.image.TransmissionCurve` 1037 A ``TransmissionCurve`` that represents the throughput of the filter 1038 itself, to be evaluated in focal-plane coordinates. 1039 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1040 A ``TransmissionCurve`` that represents the throughput of the sensor 1041 itself, to be evaluated in post-assembly trimmed detector coordinates. 1042 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1043 A ``TransmissionCurve`` that represents the throughput of the 1044 atmosphere, assumed to be spatially constant. 1045 detectorNum : `int`, optional 1046 The integer number for the detector to process. 1047 isGen3 : bool, optional 1048 Flag this call to run() as using the Gen3 butler environment. 1049 strayLightData : `object`, optional 1050 Opaque object containing calibration information for stray-light 1051 correction. If `None`, no correction will be performed. 1055 result : `lsst.pipe.base.Struct` 1056 Result struct with component: 1057 - ``exposure`` : `afw.image.Exposure` 1058 The fully ISR corrected exposure. 1059 - ``outputExposure`` : `afw.image.Exposure` 1060 An alias for `exposure` 1061 - ``ossThumb`` : `numpy.ndarray` 1062 Thumbnail image of the exposure after overscan subtraction. 1063 - ``flattenedThumb`` : `numpy.ndarray` 1064 Thumbnail image of the exposure after flat-field correction. 1069 Raised if a configuration option is set to True, but the 1070 required calibration data has not been specified. 1074 The current processed exposure can be viewed by setting the 1075 appropriate lsstDebug entries in the `debug.display` 1076 dictionary. The names of these entries correspond to some of 1077 the IsrTaskConfig Boolean options, with the value denoting the 1078 frame to use. The exposure is shown inside the matching 1079 option check and after the processing of that step has 1080 finished. The steps with debug points are: 1091 In addition, setting the "postISRCCD" entry displays the 1092 exposure after all ISR processing has finished. 1100 self.config.doFringe =
False 1103 if detectorNum
is None:
1104 raise RuntimeError(
"Must supply the detectorNum if running as Gen3")
1106 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1111 if isinstance(ccdExposure, ButlerDataRef):
1114 ccd = ccdExposure.getDetector()
1117 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd" 1118 ccd = [
FakeAmp(ccdExposure, self.config)]
1121 if self.config.doBias
and bias
is None:
1122 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1124 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1125 if self.config.doBrighterFatter
and bfKernel
is None:
1126 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1127 if self.config.doDark
and dark
is None:
1128 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1130 fringes = pipeBase.Struct(fringes=
None)
1131 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
1132 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1133 if self.config.doFlat
and flat
is None:
1134 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1135 if self.config.doDefect
and defects
is None:
1136 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1137 if self.config.doAddDistortionModel
and camera
is None:
1138 raise RuntimeError(
"Must supply camera if config.doAddDistortionModel=True.")
1141 if self.config.doConvertIntToFloat:
1142 self.log.
info(
"Converting exposure to floating point values")
1149 if ccdExposure.getBBox().
contains(amp.getBBox()):
1153 if self.config.doOverscan
and not badAmp:
1156 self.log.
debug(
"Corrected overscan for amplifier %s" % (amp.getName()))
1157 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1158 if isinstance(overscanResults.overscanFit, float):
1159 qaMedian = overscanResults.overscanFit
1160 qaStdev =
float(
"NaN")
1163 afwMath.MEDIAN | afwMath.STDEVCLIP)
1164 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1165 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1167 self.metadata.
set(
"ISR OSCAN {} MEDIAN".
format(amp.getName()), qaMedian)
1168 self.metadata.
set(
"ISR OSCAN {} STDEV".
format(amp.getName()), qaStdev)
1169 self.log.
debug(
" Overscan stats for amplifer %s: %f +/- %f" %
1170 (amp.getName(), qaMedian, qaStdev))
1171 ccdExposure.getMetadata().
set(
'OVERSCAN',
"Overscan corrected")
1174 self.log.
warn(
"Amplifier %s is bad." % (amp.getName()))
1175 overscanResults =
None 1177 overscans.append(overscanResults
if overscanResults
is not None else None)
1179 self.log.
info(
"Skipped OSCAN")
1181 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1182 self.log.
info(
"Applying crosstalk correction.")
1183 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1184 self.
debugView(ccdExposure,
"doCrosstalk")
1186 if self.config.doAssembleCcd:
1187 self.log.
info(
"Assembling CCD from amplifiers")
1188 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1190 if self.config.expectWcs
and not ccdExposure.getWcs():
1191 self.log.
warn(
"No WCS found in input exposure")
1192 self.
debugView(ccdExposure,
"doAssembleCcd")
1195 if self.config.qa.doThumbnailOss:
1196 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1198 if self.config.doBias:
1199 self.log.
info(
"Applying bias correction.")
1200 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1201 trimToFit=self.config.doTrimToMatchCalib)
1204 if self.config.doVariance:
1205 for amp, overscanResults
in zip(ccd, overscans):
1206 if ccdExposure.getBBox().
contains(amp.getBBox()):
1207 self.log.
debug(
"Constructing variance map for amplifer %s" % (amp.getName()))
1208 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1209 if overscanResults
is not None:
1211 overscanImage=overscanResults.overscanImage)
1215 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1217 afwMath.MEDIAN | afwMath.STDEVCLIP)
1218 self.metadata.
set(
"ISR VARIANCE {} MEDIAN".
format(amp.getName()),
1219 qaStats.getValue(afwMath.MEDIAN))
1220 self.metadata.
set(
"ISR VARIANCE {} STDEV".
format(amp.getName()),
1221 qaStats.getValue(afwMath.STDEVCLIP))
1222 self.log.
debug(
" Variance stats for amplifer %s: %f +/- %f" %
1223 (amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1224 qaStats.getValue(afwMath.STDEVCLIP)))
1227 self.log.
info(
"Applying linearizer.")
1228 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1230 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1231 self.log.
info(
"Applying crosstalk correction.")
1232 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1233 self.
debugView(ccdExposure,
"doCrosstalk")
1237 if self.config.doDefect:
1238 self.log.
info(
"Masking defects.")
1241 if self.config.doNanMasking:
1242 self.log.
info(
"Masking NAN value pixels.")
1245 if self.config.doWidenSaturationTrails:
1246 self.log.
info(
"Widening saturation trails.")
1247 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1249 if self.config.doCameraSpecificMasking:
1250 self.log.
info(
"Masking regions for camera specific reasons.")
1251 self.masking.
run(ccdExposure)
1253 if self.config.doBrighterFatter:
1262 interpExp = ccdExposure.clone()
1264 isrFunctions.interpolateFromMask(
1265 maskedImage=ccdExposure.getMaskedImage(),
1266 fwhm=self.config.fwhm,
1267 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1268 maskNameList=self.config.maskListToInterpolate
1270 bfExp = interpExp.clone()
1272 self.log.
info(
"Applying brighter fatter correction.")
1273 isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1274 self.config.brighterFatterMaxIter,
1275 self.config.brighterFatterThreshold,
1276 self.config.brighterFatterApplyGain,
1278 image = ccdExposure.getMaskedImage().getImage()
1279 bfCorr = bfExp.getMaskedImage().getImage()
1280 bfCorr -= interpExp.getMaskedImage().getImage()
1283 self.
debugView(ccdExposure,
"doBrighterFatter")
1285 if self.config.doDark:
1286 self.log.
info(
"Applying dark correction.")
1290 if self.config.doFringe
and not self.config.fringeAfterFlat:
1291 self.log.
info(
"Applying fringe correction before flat.")
1292 self.fringe.
run(ccdExposure, **fringes.getDict())
1295 if self.config.doStrayLight:
1296 if strayLightData
is not None:
1297 self.log.
info(
"Applying stray light correction.")
1298 self.strayLight.
run(ccdExposure, strayLightData)
1299 self.
debugView(ccdExposure,
"doStrayLight")
1301 self.log.
debug(
"Skipping stray light correction: no data found for this image.")
1303 if self.config.doFlat:
1304 self.log.
info(
"Applying flat correction.")
1308 if self.config.doApplyGains:
1309 self.log.
info(
"Applying gain correction instead of flat.")
1310 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1312 if self.config.doFringe
and self.config.fringeAfterFlat:
1313 self.log.
info(
"Applying fringe correction after flat.")
1314 self.fringe.
run(ccdExposure, **fringes.getDict())
1316 if self.config.doVignette:
1317 self.log.
info(
"Constructing Vignette polygon.")
1320 if self.config.vignette.doWriteVignettePolygon:
1323 if self.config.doAttachTransmissionCurve:
1324 self.log.
info(
"Adding transmission curves.")
1325 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1326 filterTransmission=filterTransmission,
1327 sensorTransmission=sensorTransmission,
1328 atmosphereTransmission=atmosphereTransmission)
1330 if self.config.doAddDistortionModel:
1331 self.log.
info(
"Adding a distortion model to the WCS.")
1332 isrFunctions.addDistortionModel(exposure=ccdExposure, camera=camera)
1334 flattenedThumb =
None 1335 if self.config.qa.doThumbnailFlattened:
1336 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1339 if self.config.doSaveInterpPixels:
1340 preInterpExp = ccdExposure.clone()
1342 if self.config.doInterpolate:
1343 self.log.
info(
"Interpolating masked pixels.")
1344 isrFunctions.interpolateFromMask(
1345 maskedImage=ccdExposure.getMaskedImage(),
1346 fwhm=self.config.fwhm,
1347 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1348 maskNameList=
list(self.config.maskListToInterpolate)
1351 if self.config.doSetBadRegions:
1353 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1354 if badPixelCount > 0:
1355 self.log.
info(
"Set %d BAD pixels to %f." % (badPixelCount, badPixelValue))
1359 if self.config.doMeasureBackground:
1360 self.log.
info(
"Measuring background level:")
1363 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1365 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1367 afwMath.MEDIAN | afwMath.STDEVCLIP)
1368 self.metadata.
set(
"ISR BACKGROUND {} MEDIAN".
format(amp.getName()),
1369 qaStats.getValue(afwMath.MEDIAN))
1370 self.metadata.
set(
"ISR BACKGROUND {} STDEV".
format(amp.getName()),
1371 qaStats.getValue(afwMath.STDEVCLIP))
1372 self.log.
debug(
" Background stats for amplifer %s: %f +/- %f" %
1373 (amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1374 qaStats.getValue(afwMath.STDEVCLIP)))
1376 self.
debugView(ccdExposure,
"postISRCCD")
1378 return pipeBase.Struct(
1379 exposure=ccdExposure,
1381 flattenedThumb=flattenedThumb,
1383 preInterpolatedExposure=preInterpExp,
1384 outputExposure=ccdExposure,
1385 outputOssThumbnail=ossThumb,
1386 outputFlattenedThumbnail=flattenedThumb,
1389 @pipeBase.timeMethod
1391 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1393 This method contains the `CmdLineTask` interface to the ISR 1394 processing. All IO is handled here, freeing the `run()` method 1395 to manage only pixel-level calculations. The steps performed 1397 - Read in necessary detrending/isr/calibration data. 1398 - Process raw exposure in `run()`. 1399 - Persist the ISR-corrected exposure as "postISRCCD" if 1400 config.doWrite=True. 1404 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1405 DataRef of the detector data to be processed 1409 result : `lsst.pipe.base.Struct` 1410 Result struct with component: 1411 - ``exposure`` : `afw.image.Exposure` 1412 The fully ISR corrected exposure. 1417 Raised if a configuration option is set to True, but the 1418 required calibration data does not exist. 1421 self.log.
info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
1423 ccdExposure = sensorRef.get(self.config.datasetType)
1425 camera = sensorRef.get(
"camera")
1426 if camera
is None and self.config.doAddDistortionModel:
1427 raise RuntimeError(
"config.doAddDistortionModel is True " 1428 "but could not get a camera from the butler")
1429 isrData = self.
readIsrData(sensorRef, ccdExposure)
1431 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1433 if self.config.doWrite:
1434 sensorRef.put(result.exposure,
"postISRCCD")
1435 if result.preInterpolatedExposure
is not None:
1436 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1437 if result.ossThumb
is not None:
1438 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1439 if result.flattenedThumb
is not None:
1440 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1445 """!Retrieve a calibration dataset for removing instrument signature. 1450 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1451 DataRef of the detector data to find calibration datasets 1454 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1456 If True, disable butler proxies to enable error handling 1457 within this routine. 1461 exposure : `lsst.afw.image.Exposure` 1462 Requested calibration frame. 1467 Raised if no matching calibration frame can be found. 1470 exp = dataRef.get(datasetType, immediate=immediate)
1471 except Exception
as exc1:
1472 if not self.config.fallbackFilterName:
1473 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
1475 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1476 except Exception
as exc2:
1477 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
1478 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1479 self.log.
warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
1481 if self.config.doAssembleIsrExposures:
1482 exp = self.assembleCcd.assembleCcd(exp)
1486 """Ensure that the data returned by Butler is a fully constructed exposure. 1488 ISR requires exposure-level image data for historical reasons, so if we did 1489 not recieve that from Butler, construct it from what we have, modifying the 1494 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1495 `lsst.afw.image.ImageF` 1496 The input data structure obtained from Butler. 1497 camera : `lsst.afw.cameraGeom.camera` 1498 The camera associated with the image. Used to find the appropriate 1501 The detector this exposure should match. 1505 inputExp : `lsst.afw.image.Exposure` 1506 The re-constructed exposure, with appropriate detector parameters. 1511 Raised if the input data cannot be used to construct an exposure. 1513 if isinstance(inputExp, afwImage.DecoratedImageU):
1515 elif isinstance(inputExp, afwImage.ImageF):
1517 elif isinstance(inputExp, afwImage.MaskedImageF):
1521 elif inputExp
is None:
1525 raise TypeError(f
"Input Exposure is not known type in isrTask.ensureExposure: {type(inputExp)}")
1527 if inputExp.getDetector()
is None:
1528 inputExp.setDetector(camera[detectorNum])
1533 """Convert exposure image from uint16 to float. 1535 If the exposure does not need to be converted, the input is 1536 immediately returned. For exposures that are converted to use 1537 floating point pixels, the variance is set to unity and the 1542 exposure : `lsst.afw.image.Exposure` 1543 The raw exposure to be converted. 1547 newexposure : `lsst.afw.image.Exposure` 1548 The input ``exposure``, converted to floating point pixels. 1553 Raised if the exposure type cannot be converted to float. 1556 if isinstance(exposure, afwImage.ExposureF):
1559 if not hasattr(exposure,
"convertF"):
1560 raise RuntimeError(
"Unable to convert exposure (%s) to float" %
type(exposure))
1562 newexposure = exposure.convertF()
1563 newexposure.variance[:] = 1
1564 newexposure.mask[:] = 0x0
1569 """Identify bad amplifiers, saturated and suspect pixels. 1573 ccdExposure : `lsst.afw.image.Exposure` 1574 Input exposure to be masked. 1575 amp : `lsst.afw.table.AmpInfoCatalog` 1576 Catalog of parameters defining the amplifier on this 1578 defects : `lsst.meas.algorithms.Defects` 1579 List of defects. Used to determine if the entire 1585 If this is true, the entire amplifier area is covered by 1586 defects and unusable. 1589 maskedImage = ccdExposure.getMaskedImage()
1595 if defects
is not None:
1596 badAmp = bool(sum([v.getBBox().
contains(amp.getBBox())
for v
in defects]))
1601 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1603 maskView = dataView.getMask()
1604 maskView |= maskView.getPlaneBitMask(
"BAD")
1611 if self.config.doSaturation
and not badAmp:
1612 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1613 if self.config.doSuspect
and not badAmp:
1614 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1615 if math.isfinite(self.config.saturation):
1616 limits.update({self.config.saturatedMaskName: self.config.saturation})
1618 for maskName, maskThreshold
in limits.items():
1619 if not math.isnan(maskThreshold):
1620 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1621 isrFunctions.makeThresholdMask(
1622 maskedImage=dataView,
1623 threshold=maskThreshold,
1629 maskView =
afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1631 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1632 self.config.suspectMaskName])
1633 if numpy.all(maskView.getArray() & maskVal > 0):
1639 """Apply overscan correction in place. 1641 This method does initial pixel rejection of the overscan 1642 region. The overscan can also be optionally segmented to 1643 allow for discontinuous overscan responses to be fit 1644 separately. The actual overscan subtraction is performed by 1645 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1646 which is called here after the amplifier is preprocessed. 1650 ccdExposure : `lsst.afw.image.Exposure` 1651 Exposure to have overscan correction performed. 1652 amp : `lsst.afw.table.AmpInfoCatalog` 1653 The amplifier to consider while correcting the overscan. 1657 overscanResults : `lsst.pipe.base.Struct` 1658 Result struct with components: 1659 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1660 Value or fit subtracted from the amplifier image data. 1661 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1662 Value or fit subtracted from the overscan image data. 1663 - ``overscanImage`` : `lsst.afw.image.Image` 1664 Image of the overscan region with the overscan 1665 correction applied. This quantity is used to estimate 1666 the amplifier read noise empirically. 1671 Raised if the ``amp`` does not contain raw pixel information. 1675 lsst.ip.isr.isrFunctions.overscanCorrection 1677 if not amp.getHasRawInfo():
1678 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1680 if amp.getRawHorizontalOverscanBBox().isEmpty():
1681 self.log.
info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1685 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1688 dataBBox = amp.getRawDataBBox()
1689 oscanBBox = amp.getRawHorizontalOverscanBBox()
1693 prescanBBox = amp.getRawPrescanBBox()
1694 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1695 dx0 += self.config.overscanNumLeadingColumnsToSkip
1696 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1698 dx0 += self.config.overscanNumTrailingColumnsToSkip
1699 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1705 if ((self.config.overscanBiasJump
and 1706 self.config.overscanBiasJumpLocation)
and 1707 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1708 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1709 self.config.overscanBiasJumpDevices)):
1710 if amp.getReadoutCorner()
in (afwTable.LL, afwTable.LR):
1711 yLower = self.config.overscanBiasJumpLocation
1712 yUpper = dataBBox.getHeight() - yLower
1714 yUpper = self.config.overscanBiasJumpLocation
1715 yLower = dataBBox.getHeight() - yUpper
1734 oscanBBox.getHeight())))
1737 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1738 ampImage = ccdExposure.maskedImage[imageBBox]
1739 overscanImage = ccdExposure.maskedImage[overscanBBox]
1741 overscanArray = overscanImage.image.array
1742 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1743 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1744 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1747 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1749 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1750 overscanImage=overscanImage,
1751 fitType=self.config.overscanFitType,
1752 order=self.config.overscanOrder,
1753 collapseRej=self.config.overscanNumSigmaClip,
1754 statControl=statControl,
1755 overscanIsInt=self.config.overscanIsInt
1759 levelStat = afwMath.MEDIAN
1760 sigmaStat = afwMath.STDEVCLIP
1763 self.config.qa.flatness.nIter)
1764 metadata = ccdExposure.getMetadata()
1765 ampNum = amp.getName()
1766 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1767 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1768 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1771 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1772 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1774 return overscanResults
1777 """Set the variance plane using the amplifier gain and read noise 1779 The read noise is calculated from the ``overscanImage`` if the 1780 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1781 the value from the amplifier data is used. 1785 ampExposure : `lsst.afw.image.Exposure` 1786 Exposure to process. 1787 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1788 Amplifier detector data. 1789 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1790 Image of overscan, required only for empirical read noise. 1794 lsst.ip.isr.isrFunctions.updateVariance 1796 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1797 gain = amp.getGain()
1799 if math.isnan(gain):
1801 self.log.
warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1804 self.log.
warn(
"Gain for amp %s == %g <= 0; setting to %f" %
1805 (amp.getName(), gain, patchedGain))
1808 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1809 self.log.
info(
"Overscan is none for EmpiricalReadNoise")
1811 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1813 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1815 self.log.
info(
"Calculated empirical read noise for amp %s: %f", amp.getName(), readNoise)
1817 readNoise = amp.getReadNoise()
1819 isrFunctions.updateVariance(
1820 maskedImage=ampExposure.getMaskedImage(),
1822 readNoise=readNoise,
1826 """!Apply dark correction in place. 1830 exposure : `lsst.afw.image.Exposure` 1831 Exposure to process. 1832 darkExposure : `lsst.afw.image.Exposure` 1833 Dark exposure of the same size as ``exposure``. 1834 invert : `Bool`, optional 1835 If True, re-add the dark to an already corrected image. 1840 Raised if either ``exposure`` or ``darkExposure`` do not 1841 have their dark time defined. 1845 lsst.ip.isr.isrFunctions.darkCorrection 1847 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1848 if math.isnan(expScale):
1849 raise RuntimeError(
"Exposure darktime is NAN")
1850 if darkExposure.getInfo().getVisitInfo()
is not None:
1851 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1857 if math.isnan(darkScale):
1858 raise RuntimeError(
"Dark calib darktime is NAN")
1859 isrFunctions.darkCorrection(
1860 maskedImage=exposure.getMaskedImage(),
1861 darkMaskedImage=darkExposure.getMaskedImage(),
1863 darkScale=darkScale,
1865 trimToFit=self.config.doTrimToMatchCalib
1869 """!Check if linearization is needed for the detector cameraGeom. 1871 Checks config.doLinearize and the linearity type of the first 1876 detector : `lsst.afw.cameraGeom.Detector` 1877 Detector to get linearity type from. 1881 doLinearize : `Bool` 1882 If True, linearization should be performed. 1884 return self.config.doLinearize
and \
1885 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
1888 """!Apply flat correction in place. 1892 exposure : `lsst.afw.image.Exposure` 1893 Exposure to process. 1894 flatExposure : `lsst.afw.image.Exposure` 1895 Flat exposure of the same size as ``exposure``. 1896 invert : `Bool`, optional 1897 If True, unflatten an already flattened image. 1901 lsst.ip.isr.isrFunctions.flatCorrection 1903 isrFunctions.flatCorrection(
1904 maskedImage=exposure.getMaskedImage(),
1905 flatMaskedImage=flatExposure.getMaskedImage(),
1906 scalingType=self.config.flatScalingType,
1907 userScale=self.config.flatUserScale,
1909 trimToFit=self.config.doTrimToMatchCalib
1913 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 1917 exposure : `lsst.afw.image.Exposure` 1918 Exposure to process. Only the amplifier DataSec is processed. 1919 amp : `lsst.afw.table.AmpInfoCatalog` 1920 Amplifier detector data. 1924 lsst.ip.isr.isrFunctions.makeThresholdMask 1926 if not math.isnan(amp.getSaturation()):
1927 maskedImage = exposure.getMaskedImage()
1928 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1929 isrFunctions.makeThresholdMask(
1930 maskedImage=dataView,
1931 threshold=amp.getSaturation(),
1933 maskName=self.config.saturatedMaskName,
1937 """!Interpolate over saturated pixels, in place. 1939 This method should be called after `saturationDetection`, to 1940 ensure that the saturated pixels have been identified in the 1941 SAT mask. It should also be called after `assembleCcd`, since 1942 saturated regions may cross amplifier boundaries. 1946 exposure : `lsst.afw.image.Exposure` 1947 Exposure to process. 1951 lsst.ip.isr.isrTask.saturationDetection 1952 lsst.ip.isr.isrFunctions.interpolateFromMask 1954 isrFunctions.interpolateFromMask(
1955 maskedImage=exposure.getMaskedImage(),
1956 fwhm=self.config.fwhm,
1957 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1958 maskNameList=
list(self.config.saturatedMaskName),
1962 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 1966 exposure : `lsst.afw.image.Exposure` 1967 Exposure to process. Only the amplifier DataSec is processed. 1968 amp : `lsst.afw.table.AmpInfoCatalog` 1969 Amplifier detector data. 1973 lsst.ip.isr.isrFunctions.makeThresholdMask 1977 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 1978 This is intended to indicate pixels that may be affected by unknown systematics; 1979 for example if non-linearity corrections above a certain level are unstable 1980 then that would be a useful value for suspectLevel. A value of `nan` indicates 1981 that no such level exists and no pixels are to be masked as suspicious. 1983 suspectLevel = amp.getSuspectLevel()
1984 if math.isnan(suspectLevel):
1987 maskedImage = exposure.getMaskedImage()
1988 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1989 isrFunctions.makeThresholdMask(
1990 maskedImage=dataView,
1991 threshold=suspectLevel,
1993 maskName=self.config.suspectMaskName,
1997 """!Mask defects using mask plane "BAD", in place. 2001 exposure : `lsst.afw.image.Exposure` 2002 Exposure to process. 2003 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2004 `lsst.afw.image.DefectBase`. 2005 List of defects to mask and interpolate. 2009 Call this after CCD assembly, since defects may cross amplifier boundaries. 2011 maskedImage = exposure.getMaskedImage()
2012 if not isinstance(defectBaseList, Defects):
2014 defectList =
Defects(defectBaseList)
2016 defectList = defectBaseList
2017 defectList.maskPixels(maskedImage, maskName=
"BAD")
2019 if self.config.numEdgeSuspect > 0:
2020 goodBBox = maskedImage.getBBox()
2022 goodBBox.grow(-self.config.numEdgeSuspect)
2024 SourceDetectionTask.setEdgeBits(
2027 maskedImage.getMask().getPlaneBitMask(
"SUSPECT")
2031 """Mask and interpolate defects using mask plane "BAD", in place. 2035 exposure : `lsst.afw.image.Exposure` 2036 Exposure to process. 2037 defectBaseList : `List` of `Defects` 2040 self.maskDefects(exposure, defectBaseList)
2041 isrFunctions.interpolateFromMask(
2042 maskedImage=exposure.getMaskedImage(),
2043 fwhm=self.config.fwhm,
2044 growSaturatedFootprints=0,
2045 maskNameList=[
"BAD"],
2049 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2053 exposure : `lsst.afw.image.Exposure` 2054 Exposure to process. 2058 We mask over all NaNs, including those that are masked with 2059 other bits (because those may or may not be interpolated over 2060 later, and we want to remove all NaNs). Despite this 2061 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2062 the historical name. 2064 maskedImage = exposure.getMaskedImage()
2067 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2068 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2069 numNans =
maskNans(maskedImage, maskVal)
2070 self.metadata.
set(
"NUMNANS", numNans)
2072 self.log.
warn(f
"There were {numNans} unmasked NaNs")
2075 """"Mask and interpolate NaNs using mask plane "UNMASKEDNAN", in place. 2079 exposure : `lsst.afw.image.Exposure` 2080 Exposure to process. 2084 lsst.ip.isr.isrTask.maskNan() 2087 isrFunctions.interpolateFromMask(
2088 maskedImage=exposure.getMaskedImage(),
2089 fwhm=self.config.fwhm,
2090 growSaturatedFootprints=0,
2091 maskNameList=[
"UNMASKEDNAN"],
2095 """Measure the image background in subgrids, for quality control purposes. 2099 exposure : `lsst.afw.image.Exposure` 2100 Exposure to process. 2101 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2102 Configuration object containing parameters on which background 2103 statistics and subgrids to use. 2105 if IsrQaConfig
is not None:
2107 IsrQaConfig.flatness.nIter)
2108 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2109 statsControl.setAndMask(maskVal)
2110 maskedImage = exposure.getMaskedImage()
2112 skyLevel = stats.getValue(afwMath.MEDIAN)
2113 skySigma = stats.getValue(afwMath.STDEVCLIP)
2114 self.log.
info(
"Flattened sky level: %f +/- %f" % (skyLevel, skySigma))
2115 metadata = exposure.getMetadata()
2116 metadata.set(
'SKYLEVEL', skyLevel)
2117 metadata.set(
'SKYSIGMA', skySigma)
2120 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2121 meshXHalf =
int(IsrQaConfig.flatness.meshX/2.)
2122 meshYHalf =
int(IsrQaConfig.flatness.meshY/2.)
2123 nX =
int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2124 nY =
int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2125 skyLevels = numpy.zeros((nX, nY))
2128 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2130 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2132 xLLC = xc - meshXHalf
2133 yLLC = yc - meshYHalf
2134 xURC = xc + meshXHalf - 1
2135 yURC = yc + meshYHalf - 1
2138 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2142 good = numpy.where(numpy.isfinite(skyLevels))
2143 skyMedian = numpy.median(skyLevels[good])
2144 flatness = (skyLevels[good] - skyMedian) / skyMedian
2145 flatness_rms = numpy.std(flatness)
2146 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2148 self.log.
info(
"Measuring sky levels in %dx%d grids: %f" % (nX, nY, skyMedian))
2149 self.log.
info(
"Sky flatness in %dx%d grids - pp: %f rms: %f" %
2150 (nX, nY, flatness_pp, flatness_rms))
2152 metadata.set(
'FLATNESS_PP',
float(flatness_pp))
2153 metadata.set(
'FLATNESS_RMS',
float(flatness_rms))
2154 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2155 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2156 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2159 """Set an approximate magnitude zero point for the exposure. 2163 exposure : `lsst.afw.image.Exposure` 2164 Exposure to process. 2167 if filterName
in self.config.fluxMag0T1:
2168 fluxMag0 = self.config.fluxMag0T1[filterName]
2170 self.log.
warn(
"No rough magnitude zero point set for filter %s" % filterName)
2171 fluxMag0 = self.config.defaultFluxMag0T1
2173 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2175 self.log.
warn(
"Non-positive exposure time; skipping rough zero point")
2178 self.log.
info(
"Setting rough magnitude zero point: %f" % (2.5*math.log10(fluxMag0*expTime),))
2182 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2186 ccdExposure : `lsst.afw.image.Exposure` 2187 Exposure to process. 2188 fpPolygon : `lsst.afw.geom.Polygon` 2189 Polygon in focal plane coordinates. 2192 ccd = ccdExposure.getDetector()
2193 fpCorners = ccd.getCorners(FOCAL_PLANE)
2194 ccdPolygon =
Polygon(fpCorners)
2197 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2200 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2201 validPolygon =
Polygon(ccdPoints)
2202 ccdExposure.getInfo().setValidPolygon(validPolygon)
2206 """Context manager that applies and removes flats and darks, 2207 if the task is configured to apply them. 2211 exp : `lsst.afw.image.Exposure` 2212 Exposure to process. 2213 flat : `lsst.afw.image.Exposure` 2214 Flat exposure the same size as ``exp``. 2215 dark : `lsst.afw.image.Exposure`, optional 2216 Dark exposure the same size as ``exp``. 2220 exp : `lsst.afw.image.Exposure` 2221 The flat and dark corrected exposure. 2223 if self.config.doDark
and dark
is not None:
2225 if self.config.doFlat:
2230 if self.config.doFlat:
2232 if self.config.doDark
and dark
is not None:
2236 """Utility function to examine ISR exposure at different stages. 2240 exposure : `lsst.afw.image.Exposure` 2243 State of processing to view. 2248 display.scale(
'asinh',
'zscale')
2249 display.mtv(exposure)
2253 """A Detector-like object that supports returning gain and saturation level 2255 This is used when the input exposure does not have a detector. 2259 exposure : `lsst.afw.image.Exposure` 2260 Exposure to generate a fake amplifier for. 2261 config : `lsst.ip.isr.isrTaskConfig` 2262 Configuration to apply to the fake amplifier. 2266 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2268 self.
_gain = config.gain
2298 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2302 """Task to wrap the default IsrTask to allow it to be retargeted. 2304 The standard IsrTask can be called directly from a command line 2305 program, but doing so removes the ability of the task to be 2306 retargeted. As most cameras override some set of the IsrTask 2307 methods, this would remove those data-specific methods in the 2308 output post-ISR images. This wrapping class fixes the issue, 2309 allowing identical post-ISR images to be generated by both the 2310 processCcd and isrTask code. 2312 ConfigClass = RunIsrConfig
2313 _DefaultName =
"runIsr" 2317 self.makeSubtask(
"isr")
2323 dataRef : `lsst.daf.persistence.ButlerDataRef` 2324 data reference of the detector data to be processed 2328 result : `pipeBase.Struct` 2329 Result struct with component: 2331 - exposure : `lsst.afw.image.Exposure` 2332 Post-ISR processed exposure.
def getInputDatasetTypes(cls, config)
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values...
bool contains(VertexIterator const begin, VertexIterator const end, UnitVector3d const &v)
def runDataRef(self, sensorRef)
def measureBackground(self, exposure, IsrQaConfig=None)
def debugView(self, exposure, stepname)
def __init__(self, kwargs)
def ensureExposure(self, inputExp, camera, detectorNum)
A class to contain the data, WCS, and other information needed to describe an image of the sky...
def readIsrData(self, dataRef, rawExposure)
Retrieve necessary frames for instrument signature removal.
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
def runDataRef(self, dataRef)
def __init__(self, args, kwargs)
def getPrerequisiteDatasetTypes(cls, config)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
daf::base::PropertySet * set
def roughZeroPoint(self, exposure)
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT >> image, typename std::shared_ptr< Mask< MaskPixelT >> mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT >> variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def getRawHorizontalOverscanBBox(self)
Statistics makeStatistics(lsst::afw::math::MaskedVector< EntryT > const &mv, std::vector< WeightPixel > const &vweights, int const flags, StatisticsControl const &sctrl=StatisticsControl())
The makeStatistics() overload to handle lsst::afw::math::MaskedVector<>
def maskNan(self, exposure)
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
def getSuspectLevel(self)
Pass parameters to a Statistics object.
def getOutputDatasetTypes(cls, config)
Represent a 2-dimensional array of bitmask pixels.
def maskDefect(self, exposure, defectBaseList)
Mask defects using mask plane "BAD", in place.
def overscanCorrection(self, ccdExposure, amp)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def convertIntToFloat(self, exposure)
Holds an integer identifier for an LSST filter.
def flatCorrection(self, exposure, flatExposure, invert=False)
Apply flat correction in place.
def getDebugFrame(debugDisplay, name)
def makeDatasetType(self, dsConfig)
def getIsrExposure(self, dataRef, datasetType, immediate=True)
Retrieve a calibration dataset for removing instrument signature.
_RawHorizontalOverscanBBox
def darkCorrection(self, exposure, darkExposure, invert=False)
Apply dark correction in place.
def doLinearize(self, detector)
Check if linearization is needed for the detector cameraGeom.
def setValidPolygonIntersect(self, ccdExposure, fpPolygon)
Set the valid polygon as the intersection of fpPolygon and the ccd corners.
def maskAmplifier(self, ccdExposure, amp, defects)
def getPerDatasetTypeDimensions(cls, config)
def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None, dark=None, flat=None, bfKernel=None, defects=None, fringes=None, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, detectorNum=None, strayLightData=None, isGen3=False)
Perform instrument signature removal on an exposure.
def flatContext(self, exp, flat, dark=None)
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Mask NANs in an image.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
def updateVariance(self, ampExposure, amp, overscanImage=None)
def maskAndInterpolateNan(self, exposure)
An integer coordinate rectangle.
daf::base::PropertyList * list
def suspectDetection(self, exposure, amp)
Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place.
def saturationInterpolation(self, exposure)
Interpolate over saturated pixels, in place.
def saturationDetection(self, exposure, amp)
Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place...
doSaturationInterpolation
def __init__(self, exposure, config)