33 from contextlib
import contextmanager
34 from lsstDebug
import getDebugFrame
44 from .
import isrFunctions
46 from .
import linearize
48 from .assembleCcdTask
import AssembleCcdTask
49 from .crosstalk
import CrosstalkTask
50 from .fringe
import FringeTask
51 from .isr
import maskNans
52 from .masking
import MaskingTask
53 from .straylight
import StrayLightTask
54 from .vignette
import VignetteTask
56 __all__ = [
"IsrTask",
"RunIsrTask"]
60 """Configuration parameters for IsrTask. 62 Items are grouped in the order in which they are executed by the task. 67 isrName = pexConfig.Field(
74 ccdExposure = pipeBase.InputDatasetField(
75 doc=
"Input exposure to process",
78 storageClass=
"ExposureU",
79 dimensions=[
"Instrument",
"Exposure",
"Detector"],
81 camera = pipeBase.InputDatasetField(
82 doc=
"Input camera to construct complete exposures.",
85 storageClass=
"TablePersistableCamera",
86 dimensions=[
"Instrument",
"CalibrationLabel"],
88 bias = pipeBase.InputDatasetField(
89 doc=
"Input bias calibration.",
92 storageClass=
"ImageF",
93 dimensions=[
"Instrument",
"CalibrationLabel",
"Detector"],
95 dark = pipeBase.InputDatasetField(
96 doc=
"Input dark calibration.",
99 storageClass=
"ImageF",
100 dimensions=[
"Instrument",
"CalibrationLabel",
"Detector"],
102 flat = pipeBase.InputDatasetField(
103 doc=
"Input flat calibration.",
106 storageClass=
"MaskedImageF",
107 dimensions=[
"Instrument",
"PhysicalFilter",
"CalibrationLabel",
"Detector"],
109 bfKernel = pipeBase.InputDatasetField(
110 doc=
"Input brighter-fatter kernel.",
113 storageClass=
"NumpyArray",
114 dimensions=[
"Instrument",
"CalibrationLabel"],
116 defects = pipeBase.InputDatasetField(
117 doc=
"Input defect tables.",
120 storageClass=
"Catalog",
121 dimensions=[
"Instrument",
"CalibrationLabel",
"Detector"],
123 opticsTransmission = pipeBase.InputDatasetField(
124 doc=
"Transmission curve due to the optics.",
125 name=
"transmission_optics",
127 storageClass=
"TablePersistableTransmissionCurve",
128 dimensions=[
"Instrument",
"CalibrationLabel"],
130 filterTransmission = pipeBase.InputDatasetField(
131 doc=
"Transmission curve due to the filter.",
132 name=
"transmission_filter",
134 storageClass=
"TablePersistableTransmissionCurve",
135 dimensions=[
"Instrument",
"PhysicalFilter",
"CalibrationLabel"],
137 sensorTransmission = pipeBase.InputDatasetField(
138 doc=
"Transmission curve due to the sensor.",
139 name=
"transmission_sensor",
141 storageClass=
"TablePersistableTransmissionCurve",
142 dimensions=[
"Instrument",
"CalibrationLabel",
"Detector"],
144 atmosphereTransmission = pipeBase.InputDatasetField(
145 doc=
"Transmission curve due to the atmosphere.",
146 name=
"transmission_atmosphere",
148 storageClass=
"TablePersistableTransmissionCurve",
149 dimensions=[
"Instrument"],
153 outputExposure = pipeBase.OutputDatasetField(
154 doc=
"Output ISR processed exposure.",
157 storageClass=
"ExposureF",
158 dimensions=[
"Instrument",
"Visit",
"Detector"],
160 outputOssThumbnail = pipeBase.OutputDatasetField(
161 doc=
"Output Overscan-subtracted thumbnail image.",
164 storageClass=
"Thumbnail",
165 dimensions=[
"Instrument",
"Visit",
"Detector"],
167 outputFlattenedThumbnail = pipeBase.OutputDatasetField(
168 doc=
"Output flat-corrected thumbnail image.",
169 name=
"FlattenedThumb",
171 storageClass=
"TextStorage",
172 dimensions=[
"Instrument",
"Visit",
"Detector"],
175 quantum = pipeBase.QuantumConfig(
176 dimensions=[
"Visit",
"Detector",
"Instrument"],
180 datasetType = pexConfig.Field(
182 doc=
"Dataset type for input data; users will typically leave this alone, " 183 "but camera-specific ISR tasks will override it",
187 fallbackFilterName = pexConfig.Field(
189 doc=
"Fallback default filter name for calibrations.",
192 expectWcs = pexConfig.Field(
195 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 197 fwhm = pexConfig.Field(
199 doc=
"FWHM of PSF in arcseconds.",
202 qa = pexConfig.ConfigField(
204 doc=
"QA related configuration options.",
208 doConvertIntToFloat = pexConfig.Field(
210 doc=
"Convert integer raw images to floating point values?",
215 doSaturation = pexConfig.Field(
217 doc=
"Mask saturated pixels? NB: this is totally independent of the" 218 " interpolation option - this is ONLY setting the bits in the mask." 219 " To have them interpolated make sure doSaturationInterpolation=True",
222 saturatedMaskName = pexConfig.Field(
224 doc=
"Name of mask plane to use in saturation detection and interpolation",
227 saturation = pexConfig.Field(
229 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
230 default=float(
"NaN"),
232 growSaturationFootprintSize = pexConfig.Field(
234 doc=
"Number of pixels by which to grow the saturation footprints",
239 doSuspect = pexConfig.Field(
241 doc=
"Mask suspect pixels?",
244 suspectMaskName = pexConfig.Field(
246 doc=
"Name of mask plane to use for suspect pixels",
249 numEdgeSuspect = pexConfig.Field(
251 doc=
"Number of edge pixels to be flagged as untrustworthy.",
256 doSetBadRegions = pexConfig.Field(
258 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
261 badStatistic = pexConfig.ChoiceField(
263 doc=
"How to estimate the average value for BAD regions.",
266 "MEANCLIP":
"Correct using the (clipped) mean of good data",
267 "MEDIAN":
"Correct using the median of the good data",
272 doOverscan = pexConfig.Field(
274 doc=
"Do overscan subtraction?",
277 overscanFitType = pexConfig.ChoiceField(
279 doc=
"The method for fitting the overscan bias level.",
282 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
283 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
284 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
285 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
286 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
287 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
288 "MEAN":
"Correct using the mean of the overscan region",
289 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
290 "MEDIAN":
"Correct using the median of the overscan region",
293 overscanOrder = pexConfig.Field(
295 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
296 "or number of spline knots if overscan fit type is a spline."),
299 overscanNumSigmaClip = pexConfig.Field(
301 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
304 overscanIsInt = pexConfig.Field(
306 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN",
309 overscanNumLeadingColumnsToSkip = pexConfig.Field(
311 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
314 overscanNumTrailingColumnsToSkip = pexConfig.Field(
316 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
319 overscanMaxDev = pexConfig.Field(
321 doc=
"Maximum deviation from the median for overscan",
322 default=1000.0, check=
lambda x: x > 0
324 overscanBiasJump = pexConfig.Field(
326 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
329 overscanBiasJumpKeyword = pexConfig.Field(
331 doc=
"Header keyword containing information about devices.",
332 default=
"NO_SUCH_KEY",
334 overscanBiasJumpDevices = pexConfig.ListField(
336 doc=
"List of devices that need piecewise overscan correction.",
339 overscanBiasJumpLocation = pexConfig.Field(
341 doc=
"Location of bias jump along y-axis.",
346 doAssembleCcd = pexConfig.Field(
349 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 351 assembleCcd = pexConfig.ConfigurableField(
352 target=AssembleCcdTask,
353 doc=
"CCD assembly task",
357 doAssembleIsrExposures = pexConfig.Field(
360 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 362 doTrimToMatchCalib = pexConfig.Field(
365 doc=
"Trim raw data to match calibration bounding boxes?" 369 doBias = pexConfig.Field(
371 doc=
"Apply bias frame correction?",
374 biasDataProductName = pexConfig.Field(
376 doc=
"Name of the bias data product",
381 doVariance = pexConfig.Field(
383 doc=
"Calculate variance?",
386 gain = pexConfig.Field(
388 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
389 default=float(
"NaN"),
391 readNoise = pexConfig.Field(
393 doc=
"The read noise to use if no Detector is present in the Exposure",
396 doEmpiricalReadNoise = pexConfig.Field(
399 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 403 doLinearize = pexConfig.Field(
405 doc=
"Correct for nonlinearity of the detector's response?",
410 doCrosstalk = pexConfig.Field(
412 doc=
"Apply intra-CCD crosstalk correction?",
415 doCrosstalkBeforeAssemble = pexConfig.Field(
417 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
420 crosstalk = pexConfig.ConfigurableField(
421 target=CrosstalkTask,
422 doc=
"Intra-CCD crosstalk correction",
426 doDefect = pexConfig.Field(
428 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
431 numEdgeSuspect = pexConfig.Field(
433 doc=
"Number of edge pixels to be flagged as untrustworthy.",
436 doNanMasking = pexConfig.Field(
438 doc=
"Mask NAN pixels?",
441 doWidenSaturationTrails = pexConfig.Field(
443 doc=
"Widen bleed trails based on their width?",
448 doBrighterFatter = pexConfig.Field(
451 doc=
"Apply the brighter fatter correction" 453 brighterFatterLevel = pexConfig.ChoiceField(
456 doc=
"The level at which to correct for brighter-fatter.",
458 "AMP":
"Every amplifier treated separately.",
459 "DETECTOR":
"One kernel per detector",
462 brighterFatterKernelFile = pexConfig.Field(
465 doc=
"Kernel file used for the brighter fatter correction" 467 brighterFatterMaxIter = pexConfig.Field(
470 doc=
"Maximum number of iterations for the brighter fatter correction" 472 brighterFatterThreshold = pexConfig.Field(
475 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 476 " absolute value of the difference between the current corrected image and the one" 477 " from the previous iteration summed over all the pixels." 479 brighterFatterApplyGain = pexConfig.Field(
482 doc=
"Should the gain be applied when applying the brighter fatter correction?" 486 doDark = pexConfig.Field(
488 doc=
"Apply dark frame correction?",
491 darkDataProductName = pexConfig.Field(
493 doc=
"Name of the dark data product",
498 doStrayLight = pexConfig.Field(
500 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
503 strayLight = pexConfig.ConfigurableField(
504 target=StrayLightTask,
505 doc=
"y-band stray light correction" 509 doFlat = pexConfig.Field(
511 doc=
"Apply flat field correction?",
514 flatDataProductName = pexConfig.Field(
516 doc=
"Name of the flat data product",
519 flatScalingType = pexConfig.ChoiceField(
521 doc=
"The method for scaling the flat on the fly.",
524 "USER":
"Scale by flatUserScale",
525 "MEAN":
"Scale by the inverse of the mean",
526 "MEDIAN":
"Scale by the inverse of the median",
529 flatUserScale = pexConfig.Field(
531 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
534 doTweakFlat = pexConfig.Field(
536 doc=
"Tweak flats to match observed amplifier ratios?",
541 doApplyGains = pexConfig.Field(
543 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
546 normalizeGains = pexConfig.Field(
548 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
553 doFringe = pexConfig.Field(
555 doc=
"Apply fringe correction?",
558 fringe = pexConfig.ConfigurableField(
560 doc=
"Fringe subtraction task",
562 fringeAfterFlat = pexConfig.Field(
564 doc=
"Do fringe subtraction after flat-fielding?",
569 doAddDistortionModel = pexConfig.Field(
571 doc=
"Apply a distortion model based on camera geometry to the WCS?",
576 doMeasureBackground = pexConfig.Field(
578 doc=
"Measure the background level on the reduced image?",
583 doCameraSpecificMasking = pexConfig.Field(
585 doc=
"Mask camera-specific bad regions?",
588 masking = pexConfig.ConfigurableField(
595 doInterpolate = pexConfig.Field(
597 doc=
"Interpolate masked pixels?",
600 doSaturationInterpolation = pexConfig.Field(
602 doc=
"Perform interpolation over pixels masked as saturated?" 603 " NB: This is independent of doSaturation; if that is False this plane" 604 " will likely be blank, resulting in a no-op here.",
607 doNanInterpolation = pexConfig.Field(
609 doc=
"Perform interpolation over pixels masked as NaN?" 610 " NB: This is independent of doNanMasking; if that is False this plane" 611 " will likely be blank, resulting in a no-op here.",
614 doNanInterpAfterFlat = pexConfig.Field(
616 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 617 "also have to interpolate them before flat-fielding."),
620 maskListToInterpolate = pexConfig.ListField(
622 doc=
"List of mask planes that should be interpolated.",
623 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
625 doSaveInterpPixels = pexConfig.Field(
627 doc=
"Save a copy of the pre-interpolated pixel values?",
632 fluxMag0T1 = pexConfig.DictField(
635 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
636 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
639 defaultFluxMag0T1 = pexConfig.Field(
641 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
642 default=pow(10.0, 0.4*28.0)
646 doVignette = pexConfig.Field(
648 doc=
"Apply vignetting parameters?",
651 vignette = pexConfig.ConfigurableField(
653 doc=
"Vignetting task.",
657 doAttachTransmissionCurve = pexConfig.Field(
660 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 662 doUseOpticsTransmission = pexConfig.Field(
665 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 667 doUseFilterTransmission = pexConfig.Field(
670 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 672 doUseSensorTransmission = pexConfig.Field(
675 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 677 doUseAtmosphereTransmission = pexConfig.Field(
680 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 684 doWrite = pexConfig.Field(
686 doc=
"Persist postISRCCD?",
693 raise ValueError(
"You may not specify both doFlat and doApplyGains")
695 self.config.maskListToInterpolate.append(
"SAT")
697 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
700 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
701 r"""Apply common instrument signature correction algorithms to a raw frame. 703 The process for correcting imaging data is very similar from 704 camera to camera. This task provides a vanilla implementation of 705 doing these corrections, including the ability to turn certain 706 corrections off if they are not needed. The inputs to the primary 707 method, `run()`, are a raw exposure to be corrected and the 708 calibration data products. The raw input is a single chip sized 709 mosaic of all amps including overscans and other non-science 710 pixels. The method `runDataRef()` identifies and defines the 711 calibration data products, and is intended for use by a 712 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 713 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 714 subclassed for different camera, although the most camera specific 715 methods have been split into subtasks that can be redirected 718 The __init__ method sets up the subtasks for ISR processing, using 719 the defaults from `lsst.ip.isr`. 724 Positional arguments passed to the Task constructor. None used at this time. 725 kwargs : `dict`, optional 726 Keyword arguments passed on to the Task constructor. None used at this time. 728 ConfigClass = IsrTaskConfig
733 self.makeSubtask(
"assembleCcd")
734 self.makeSubtask(
"crosstalk")
735 self.makeSubtask(
"strayLight")
736 self.makeSubtask(
"fringe")
737 self.makeSubtask(
"masking")
738 self.makeSubtask(
"vignette")
747 if config.doBias
is not True:
748 inputTypeDict.pop(
"bias",
None)
749 if config.doLinearize
is not True:
750 inputTypeDict.pop(
"linearizer",
None)
751 if config.doCrosstalk
is not True:
752 inputTypeDict.pop(
"crosstalkSources",
None)
753 if config.doBrighterFatter
is not True:
754 inputTypeDict.pop(
"bfKernel",
None)
755 if config.doDefect
is not True:
756 inputTypeDict.pop(
"defects",
None)
757 if config.doDark
is not True:
758 inputTypeDict.pop(
"dark",
None)
759 if config.doFlat
is not True:
760 inputTypeDict.pop(
"flat",
None)
761 if config.doAttachTransmissionCurve
is not True:
762 inputTypeDict.pop(
"opticsTransmission",
None)
763 inputTypeDict.pop(
"filterTransmission",
None)
764 inputTypeDict.pop(
"sensorTransmission",
None)
765 inputTypeDict.pop(
"atmosphereTransmission",
None)
766 if config.doUseOpticsTransmission
is not True:
767 inputTypeDict.pop(
"opticsTransmission",
None)
768 if config.doUseFilterTransmission
is not True:
769 inputTypeDict.pop(
"filterTransmission",
None)
770 if config.doUseSensorTransmission
is not True:
771 inputTypeDict.pop(
"sensorTransmission",
None)
772 if config.doUseAtmosphereTransmission
is not True:
773 inputTypeDict.pop(
"atmosphereTransmission",
None)
781 if config.qa.doThumbnailOss
is not True:
782 outputTypeDict.pop(
"outputOssThumbnail",
None)
783 if config.qa.doThumbnailFlattened
is not True:
784 outputTypeDict.pop(
"outputFlattenedThumbnail",
None)
785 if config.doWrite
is not True:
786 outputTypeDict.pop(
"outputExposure",
None)
788 return outputTypeDict
797 names.remove(
"ccdExposure")
806 return frozenset([
"CalibrationLabel"])
810 inputData[
'detectorNum'] = int(inputDataIds[
'ccdExposure'][
'detector'])
811 except Exception
as e:
812 raise ValueError(f
"Failure to find valid detectorNum value for Dataset {inputDataIds}: {e}")
814 inputData[
'isGen3'] =
True 816 if self.config.doLinearize
is True:
817 if 'linearizer' not in inputData.keys():
818 detector = inputData[
'camera'][inputData[
'detectorNum']]
819 linearityName = detector.getAmpInfoCatalog()[0].getLinearityType()
820 inputData[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
822 if inputData[
'defects']
is not None:
827 for r
in inputData[
'defects']:
828 bbox = afwGeom.BoxI(afwGeom.PointI(r.get(
"x0"), r.get(
"y0")),
829 afwGeom.ExtentI(r.get(
"width"), r.get(
"height")))
830 defectList.append(
Defect(bbox))
832 inputData[
'defects'] = defectList
849 return super().
adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
855 """!Retrieve necessary frames for instrument signature removal. 857 Pre-fetching all required ISR data products limits the IO 858 required by the ISR. Any conflict between the calibration data 859 available and that needed for ISR is also detected prior to 860 doing processing, allowing it to fail quickly. 864 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 865 Butler reference of the detector data to be processed 866 rawExposure : `afw.image.Exposure` 867 The raw exposure that will later be corrected with the 868 retrieved calibration data; should not be modified in this 873 result : `lsst.pipe.base.Struct` 874 Result struct with components (which may be `None`): 875 - ``bias``: bias calibration frame (`afw.image.Exposure`) 876 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 877 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 878 - ``dark``: dark calibration frame (`afw.image.Exposure`) 879 - ``flat``: flat calibration frame (`afw.image.Exposure`) 880 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 881 - ``defects``: list of defects (`list`) 882 - ``fringes``: `lsst.pipe.base.Struct` with components: 883 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 884 - ``seed``: random seed derived from the ccdExposureId for random 885 number generator (`uint32`) 886 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 887 A ``TransmissionCurve`` that represents the throughput of the optics, 888 to be evaluated in focal-plane coordinates. 889 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 890 A ``TransmissionCurve`` that represents the throughput of the filter 891 itself, to be evaluated in focal-plane coordinates. 892 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 893 A ``TransmissionCurve`` that represents the throughput of the sensor 894 itself, to be evaluated in post-assembly trimmed detector coordinates. 895 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 896 A ``TransmissionCurve`` that represents the throughput of the 897 atmosphere, assumed to be spatially constant. 898 - ``strayLightData`` : `object` 899 An opaque object containing calibration information for 900 stray-light correction. If `None`, no correction will be 905 NotImplementedError : 906 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 908 ccd = rawExposure.getDetector()
909 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
910 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
911 if self.config.doBias
else None)
913 linearizer = (dataRef.get(
"linearizer", immediate=
True)
915 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
916 if self.config.doCrosstalk
else None)
917 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
918 if self.config.doDark
else None)
919 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName)
920 if self.config.doFlat
else None)
922 brighterFatterKernel =
None 923 if self.config.doBrighterFatter
is True:
927 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
931 brighterFatterKernel = dataRef.get(
"bfKernel")
933 brighterFatterKernel =
None 934 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
937 if self.config.brighterFatterLevel ==
'DETECTOR':
938 brighterFatterKernel = brighterFatterKernel.kernel[ccd.getId()]
941 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
943 defectList = (dataRef.get(
"defects")
944 if self.config.doDefect
else None)
945 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
946 if self.config.doAssembleIsrExposures
else None)
947 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
948 else pipeBase.Struct(fringes=
None))
950 if self.config.doAttachTransmissionCurve:
951 opticsTransmission = (dataRef.get(
"transmission_optics")
952 if self.config.doUseOpticsTransmission
else None)
953 filterTransmission = (dataRef.get(
"transmission_filter")
954 if self.config.doUseFilterTransmission
else None)
955 sensorTransmission = (dataRef.get(
"transmission_sensor")
956 if self.config.doUseSensorTransmission
else None)
957 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
958 if self.config.doUseAtmosphereTransmission
else None)
960 opticsTransmission =
None 961 filterTransmission =
None 962 sensorTransmission =
None 963 atmosphereTransmission =
None 965 if self.config.doStrayLight:
966 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
968 strayLightData =
None 971 return pipeBase.Struct(bias=biasExposure,
972 linearizer=linearizer,
973 crosstalkSources=crosstalkSources,
976 bfKernel=brighterFatterKernel,
978 fringes=fringeStruct,
979 opticsTransmission=opticsTransmission,
980 filterTransmission=filterTransmission,
981 sensorTransmission=sensorTransmission,
982 atmosphereTransmission=atmosphereTransmission,
983 strayLightData=strayLightData
987 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
988 dark=None, flat=None, bfKernel=None, defects=None, fringes=None,
989 opticsTransmission=None, filterTransmission=None,
990 sensorTransmission=None, atmosphereTransmission=None,
991 detectorNum=None, strayLightData=None, isGen3=False,
993 """!Perform instrument signature removal on an exposure. 995 Steps included in the ISR processing, in order performed, are: 996 - saturation and suspect pixel masking 997 - overscan subtraction 998 - CCD assembly of individual amplifiers 1000 - variance image construction 1001 - linearization of non-linear response 1003 - brighter-fatter correction 1006 - stray light subtraction 1008 - masking of known defects and camera specific features 1009 - vignette calculation 1010 - appending transmission curve and distortion model 1014 ccdExposure : `lsst.afw.image.Exposure` 1015 The raw exposure that is to be run through ISR. The 1016 exposure is modified by this method. 1017 camera : `lsst.afw.cameraGeom.Camera`, optional 1018 The camera geometry for this exposure. Used to select the 1019 distortion model appropriate for this data. 1020 bias : `lsst.afw.image.Exposure`, optional 1021 Bias calibration frame. 1022 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1023 Functor for linearization. 1024 crosstalkSources : `list`, optional 1025 List of possible crosstalk sources. 1026 dark : `lsst.afw.image.Exposure`, optional 1027 Dark calibration frame. 1028 flat : `lsst.afw.image.Exposure`, optional 1029 Flat calibration frame. 1030 bfKernel : `numpy.ndarray`, optional 1031 Brighter-fatter kernel. 1032 defects : `list`, optional 1034 fringes : `lsst.pipe.base.Struct`, optional 1035 Struct containing the fringe correction data, with 1037 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1038 - ``seed``: random seed derived from the ccdExposureId for random 1039 number generator (`uint32`) 1040 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1041 A ``TransmissionCurve`` that represents the throughput of the optics, 1042 to be evaluated in focal-plane coordinates. 1043 filterTransmission : `lsst.afw.image.TransmissionCurve` 1044 A ``TransmissionCurve`` that represents the throughput of the filter 1045 itself, to be evaluated in focal-plane coordinates. 1046 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1047 A ``TransmissionCurve`` that represents the throughput of the sensor 1048 itself, to be evaluated in post-assembly trimmed detector coordinates. 1049 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1050 A ``TransmissionCurve`` that represents the throughput of the 1051 atmosphere, assumed to be spatially constant. 1052 detectorNum : `int`, optional 1053 The integer number for the detector to process. 1054 isGen3 : bool, optional 1055 Flag this call to run() as using the Gen3 butler environment. 1056 strayLightData : `object`, optional 1057 Opaque object containing calibration information for stray-light 1058 correction. If `None`, no correction will be performed. 1062 result : `lsst.pipe.base.Struct` 1063 Result struct with component: 1064 - ``exposure`` : `afw.image.Exposure` 1065 The fully ISR corrected exposure. 1066 - ``outputExposure`` : `afw.image.Exposure` 1067 An alias for `exposure` 1068 - ``ossThumb`` : `numpy.ndarray` 1069 Thumbnail image of the exposure after overscan subtraction. 1070 - ``flattenedThumb`` : `numpy.ndarray` 1071 Thumbnail image of the exposure after flat-field correction. 1076 Raised if a configuration option is set to True, but the 1077 required calibration data has not been specified. 1081 The current processed exposure can be viewed by setting the 1082 appropriate lsstDebug entries in the `debug.display` 1083 dictionary. The names of these entries correspond to some of 1084 the IsrTaskConfig Boolean options, with the value denoting the 1085 frame to use. The exposure is shown inside the matching 1086 option check and after the processing of that step has 1087 finished. The steps with debug points are: 1098 In addition, setting the "postISRCCD" entry displays the 1099 exposure after all ISR processing has finished. 1107 self.config.doFringe =
False 1110 if detectorNum
is None:
1111 raise RuntimeError(
"Must supply the detectorNum if running as Gen3")
1113 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1118 if isinstance(ccdExposure, ButlerDataRef):
1121 ccd = ccdExposure.getDetector()
1124 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd" 1125 ccd = [
FakeAmp(ccdExposure, self.config)]
1128 if self.config.doBias
and bias
is None:
1129 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1131 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1132 if self.config.doBrighterFatter
and bfKernel
is None:
1133 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1134 if self.config.doDark
and dark
is None:
1135 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1137 fringes = pipeBase.Struct(fringes=
None)
1138 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
1139 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1140 if self.config.doFlat
and flat
is None:
1141 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1142 if self.config.doDefect
and defects
is None:
1143 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1144 if self.config.doAddDistortionModel
and camera
is None:
1145 raise RuntimeError(
"Must supply camera if config.doAddDistortionModel=True.")
1148 if self.config.doConvertIntToFloat:
1149 self.log.info(
"Converting exposure to floating point values")
1156 if ccdExposure.getBBox().contains(amp.getBBox()):
1160 if self.config.doOverscan
and not badAmp:
1163 self.log.debug(
"Corrected overscan for amplifier %s" % (amp.getName()))
1164 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1165 if isinstance(overscanResults.overscanFit, float):
1166 qaMedian = overscanResults.overscanFit
1167 qaStdev = float(
"NaN")
1169 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1170 afwMath.MEDIAN | afwMath.STDEVCLIP)
1171 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1172 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1174 self.metadata.set(
"ISR OSCAN {} MEDIAN".format(amp.getName()), qaMedian)
1175 self.metadata.set(
"ISR OSCAN {} STDEV".format(amp.getName()), qaStdev)
1176 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f" %
1177 (amp.getName(), qaMedian, qaStdev))
1178 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1180 self.log.warn(
"Amplifier %s is bad." % (amp.getName()))
1181 overscanResults =
None 1183 overscans.append(overscanResults
if overscanResults
is not None else None)
1185 self.log.info(
"Skipped OSCAN")
1187 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1188 self.log.info(
"Applying crosstalk correction.")
1189 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1190 self.
debugView(ccdExposure,
"doCrosstalk")
1192 if self.config.doAssembleCcd:
1193 self.log.info(
"Assembling CCD from amplifiers")
1194 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1196 if self.config.expectWcs
and not ccdExposure.getWcs():
1197 self.log.warn(
"No WCS found in input exposure")
1198 self.
debugView(ccdExposure,
"doAssembleCcd")
1201 if self.config.qa.doThumbnailOss:
1202 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1204 if self.config.doBias:
1205 self.log.info(
"Applying bias correction.")
1206 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1207 trimToFit=self.config.doTrimToMatchCalib)
1210 if self.config.doVariance:
1211 for amp, overscanResults
in zip(ccd, overscans):
1212 if ccdExposure.getBBox().contains(amp.getBBox()):
1213 self.log.debug(
"Constructing variance map for amplifer %s" % (amp.getName()))
1214 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1215 if overscanResults
is not None:
1217 overscanImage=overscanResults.overscanImage)
1221 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1222 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1223 afwMath.MEDIAN | afwMath.STDEVCLIP)
1224 self.metadata.set(
"ISR VARIANCE {} MEDIAN".format(amp.getName()),
1225 qaStats.getValue(afwMath.MEDIAN))
1226 self.metadata.set(
"ISR VARIANCE {} STDEV".format(amp.getName()),
1227 qaStats.getValue(afwMath.STDEVCLIP))
1228 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f" %
1229 (amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1230 qaStats.getValue(afwMath.STDEVCLIP)))
1233 self.log.info(
"Applying linearizer.")
1234 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1236 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1237 self.log.info(
"Applying crosstalk correction.")
1238 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1239 self.
debugView(ccdExposure,
"doCrosstalk")
1243 if self.config.doDefect:
1244 self.log.info(
"Masking defects.")
1247 if self.config.doNanMasking:
1248 self.log.info(
"Masking NAN value pixels.")
1251 if self.config.doWidenSaturationTrails:
1252 self.log.info(
"Widening saturation trails.")
1253 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1255 if self.config.doCameraSpecificMasking:
1256 self.log.info(
"Masking regions for camera specific reasons.")
1257 self.masking.
run(ccdExposure)
1259 if self.config.doBrighterFatter:
1268 interpExp = ccdExposure.clone()
1270 isrFunctions.interpolateFromMask(
1271 maskedImage=interpExp.getMaskedImage(),
1272 fwhm=self.config.fwhm,
1273 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1274 maskNameList=self.config.maskListToInterpolate
1276 bfExp = interpExp.clone()
1278 self.log.info(
"Applying brighter fatter correction.")
1279 isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1280 self.config.brighterFatterMaxIter,
1281 self.config.brighterFatterThreshold,
1282 self.config.brighterFatterApplyGain,
1284 image = ccdExposure.getMaskedImage().getImage()
1285 bfCorr = bfExp.getMaskedImage().getImage()
1286 bfCorr -= interpExp.getMaskedImage().getImage()
1289 self.
debugView(ccdExposure,
"doBrighterFatter")
1291 if self.config.doDark:
1292 self.log.info(
"Applying dark correction.")
1296 if self.config.doFringe
and not self.config.fringeAfterFlat:
1297 self.log.info(
"Applying fringe correction before flat.")
1298 self.fringe.
run(ccdExposure, **fringes.getDict())
1301 if self.config.doStrayLight:
1302 if strayLightData
is not None:
1303 self.log.info(
"Applying stray light correction.")
1304 self.strayLight.
run(ccdExposure, strayLightData)
1305 self.
debugView(ccdExposure,
"doStrayLight")
1307 self.log.debug(
"Skipping stray light correction: no data found for this image.")
1309 if self.config.doFlat:
1310 self.log.info(
"Applying flat correction.")
1314 if self.config.doApplyGains:
1315 self.log.info(
"Applying gain correction instead of flat.")
1316 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1318 if self.config.doFringe
and self.config.fringeAfterFlat:
1319 self.log.info(
"Applying fringe correction after flat.")
1320 self.fringe.
run(ccdExposure, **fringes.getDict())
1322 if self.config.doVignette:
1323 self.log.info(
"Constructing Vignette polygon.")
1326 if self.config.vignette.doWriteVignettePolygon:
1329 if self.config.doAttachTransmissionCurve:
1330 self.log.info(
"Adding transmission curves.")
1331 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1332 filterTransmission=filterTransmission,
1333 sensorTransmission=sensorTransmission,
1334 atmosphereTransmission=atmosphereTransmission)
1336 if self.config.doAddDistortionModel:
1337 self.log.info(
"Adding a distortion model to the WCS.")
1338 isrFunctions.addDistortionModel(exposure=ccdExposure, camera=camera)
1340 flattenedThumb =
None 1341 if self.config.qa.doThumbnailFlattened:
1342 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1345 if self.config.doSaveInterpPixels:
1346 preInterpExp = ccdExposure.clone()
1361 if self.config.doSetBadRegions:
1362 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1363 if badPixelCount > 0:
1364 self.log.info(
"Set %d BAD pixels to %f." % (badPixelCount, badPixelValue))
1366 if self.config.doInterpolate:
1367 self.log.info(
"Interpolating masked pixels.")
1368 isrFunctions.interpolateFromMask(
1369 maskedImage=ccdExposure.getMaskedImage(),
1370 fwhm=self.config.fwhm,
1371 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1372 maskNameList=list(self.config.maskListToInterpolate)
1377 if self.config.doMeasureBackground:
1378 self.log.info(
"Measuring background level:")
1381 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1383 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1384 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1385 afwMath.MEDIAN | afwMath.STDEVCLIP)
1386 self.metadata.set(
"ISR BACKGROUND {} MEDIAN".format(amp.getName()),
1387 qaStats.getValue(afwMath.MEDIAN))
1388 self.metadata.set(
"ISR BACKGROUND {} STDEV".format(amp.getName()),
1389 qaStats.getValue(afwMath.STDEVCLIP))
1390 self.log.debug(
" Background stats for amplifer %s: %f +/- %f" %
1391 (amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1392 qaStats.getValue(afwMath.STDEVCLIP)))
1394 self.
debugView(ccdExposure,
"postISRCCD")
1396 return pipeBase.Struct(
1397 exposure=ccdExposure,
1399 flattenedThumb=flattenedThumb,
1401 preInterpolatedExposure=preInterpExp,
1402 outputExposure=ccdExposure,
1403 outputOssThumbnail=ossThumb,
1404 outputFlattenedThumbnail=flattenedThumb,
1407 @pipeBase.timeMethod
1409 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1411 This method contains the `CmdLineTask` interface to the ISR 1412 processing. All IO is handled here, freeing the `run()` method 1413 to manage only pixel-level calculations. The steps performed 1415 - Read in necessary detrending/isr/calibration data. 1416 - Process raw exposure in `run()`. 1417 - Persist the ISR-corrected exposure as "postISRCCD" if 1418 config.doWrite=True. 1422 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1423 DataRef of the detector data to be processed 1427 result : `lsst.pipe.base.Struct` 1428 Result struct with component: 1429 - ``exposure`` : `afw.image.Exposure` 1430 The fully ISR corrected exposure. 1435 Raised if a configuration option is set to True, but the 1436 required calibration data does not exist. 1439 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
1441 ccdExposure = sensorRef.get(self.config.datasetType)
1443 camera = sensorRef.get(
"camera")
1444 if camera
is None and self.config.doAddDistortionModel:
1445 raise RuntimeError(
"config.doAddDistortionModel is True " 1446 "but could not get a camera from the butler")
1447 isrData = self.
readIsrData(sensorRef, ccdExposure)
1449 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1451 if self.config.doWrite:
1452 sensorRef.put(result.exposure,
"postISRCCD")
1453 if result.preInterpolatedExposure
is not None:
1454 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1455 if result.ossThumb
is not None:
1456 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1457 if result.flattenedThumb
is not None:
1458 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1463 """!Retrieve a calibration dataset for removing instrument signature. 1468 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1469 DataRef of the detector data to find calibration datasets 1472 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1474 If True, disable butler proxies to enable error handling 1475 within this routine. 1479 exposure : `lsst.afw.image.Exposure` 1480 Requested calibration frame. 1485 Raised if no matching calibration frame can be found. 1488 exp = dataRef.get(datasetType, immediate=immediate)
1489 except Exception
as exc1:
1490 if not self.config.fallbackFilterName:
1491 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
1493 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1494 except Exception
as exc2:
1495 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
1496 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1497 self.log.warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
1499 if self.config.doAssembleIsrExposures:
1500 exp = self.assembleCcd.assembleCcd(exp)
1504 """Ensure that the data returned by Butler is a fully constructed exposure. 1506 ISR requires exposure-level image data for historical reasons, so if we did 1507 not recieve that from Butler, construct it from what we have, modifying the 1512 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1513 `lsst.afw.image.ImageF` 1514 The input data structure obtained from Butler. 1515 camera : `lsst.afw.cameraGeom.camera` 1516 The camera associated with the image. Used to find the appropriate 1519 The detector this exposure should match. 1523 inputExp : `lsst.afw.image.Exposure` 1524 The re-constructed exposure, with appropriate detector parameters. 1529 Raised if the input data cannot be used to construct an exposure. 1531 if isinstance(inputExp, afwImage.DecoratedImageU):
1532 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1533 elif isinstance(inputExp, afwImage.ImageF):
1534 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1535 elif isinstance(inputExp, afwImage.MaskedImageF):
1536 inputExp = afwImage.makeExposure(inputExp)
1537 elif isinstance(inputExp, afwImage.Exposure):
1540 raise TypeError(f
"Input Exposure is not known type in isrTask.ensureExposure: {type(inputExp)}")
1542 if inputExp.getDetector()
is None:
1543 inputExp.setDetector(camera[detectorNum])
1548 """Convert exposure image from uint16 to float. 1550 If the exposure does not need to be converted, the input is 1551 immediately returned. For exposures that are converted to use 1552 floating point pixels, the variance is set to unity and the 1557 exposure : `lsst.afw.image.Exposure` 1558 The raw exposure to be converted. 1562 newexposure : `lsst.afw.image.Exposure` 1563 The input ``exposure``, converted to floating point pixels. 1568 Raised if the exposure type cannot be converted to float. 1571 if isinstance(exposure, afwImage.ExposureF):
1574 if not hasattr(exposure,
"convertF"):
1575 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
1577 newexposure = exposure.convertF()
1578 newexposure.variance[:] = 1
1579 newexposure.mask[:] = 0x0
1584 """Identify bad amplifiers, saturated and suspect pixels. 1588 ccdExposure : `lsst.afw.image.Exposure` 1589 Input exposure to be masked. 1590 amp : `lsst.afw.table.AmpInfoCatalog` 1591 Catalog of parameters defining the amplifier on this 1594 List of defects. Used to determine if the entire 1600 If this is true, the entire amplifier area is covered by 1601 defects and unusable. 1604 maskedImage = ccdExposure.getMaskedImage()
1610 if defects
is not None:
1611 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1616 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1618 maskView = dataView.getMask()
1619 maskView |= maskView.getPlaneBitMask(
"BAD")
1626 if self.config.doSaturation
and not badAmp:
1627 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1628 if self.config.doSuspect
and not badAmp:
1629 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1631 for maskName, maskThreshold
in limits.items():
1632 if not math.isnan(maskThreshold):
1633 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1634 isrFunctions.makeThresholdMask(
1635 maskedImage=dataView,
1636 threshold=maskThreshold,
1642 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1644 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1645 self.config.suspectMaskName])
1646 if numpy.all(maskView.getArray() & maskVal > 0):
1648 maskView |= maskView.getPlaneBitMask(
"BAD")
1653 """Apply overscan correction in place. 1655 This method does initial pixel rejection of the overscan 1656 region. The overscan can also be optionally segmented to 1657 allow for discontinuous overscan responses to be fit 1658 separately. The actual overscan subtraction is performed by 1659 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1660 which is called here after the amplifier is preprocessed. 1664 ccdExposure : `lsst.afw.image.Exposure` 1665 Exposure to have overscan correction performed. 1666 amp : `lsst.afw.table.AmpInfoCatalog` 1667 The amplifier to consider while correcting the overscan. 1671 overscanResults : `lsst.pipe.base.Struct` 1672 Result struct with components: 1673 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1674 Value or fit subtracted from the amplifier image data. 1675 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1676 Value or fit subtracted from the overscan image data. 1677 - ``overscanImage`` : `lsst.afw.image.Image` 1678 Image of the overscan region with the overscan 1679 correction applied. This quantity is used to estimate 1680 the amplifier read noise empirically. 1685 Raised if the ``amp`` does not contain raw pixel information. 1689 lsst.ip.isr.isrFunctions.overscanCorrection 1691 if not amp.getHasRawInfo():
1692 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1694 if amp.getRawHorizontalOverscanBBox().isEmpty():
1695 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1698 statControl = afwMath.StatisticsControl()
1699 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1702 dataBBox = amp.getRawDataBBox()
1703 oscanBBox = amp.getRawHorizontalOverscanBBox()
1707 prescanBBox = amp.getRawPrescanBBox()
1708 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1709 dx0 += self.config.overscanNumLeadingColumnsToSkip
1710 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1712 dx0 += self.config.overscanNumTrailingColumnsToSkip
1713 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1719 if ((self.config.overscanBiasJump
and 1720 self.config.overscanBiasJumpLocation)
and 1721 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1722 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1723 self.config.overscanBiasJumpDevices)):
1724 if amp.getReadoutCorner()
in (afwTable.LL, afwTable.LR):
1725 yLower = self.config.overscanBiasJumpLocation
1726 yUpper = dataBBox.getHeight() - yLower
1728 yUpper = self.config.overscanBiasJumpLocation
1729 yLower = dataBBox.getHeight() - yUpper
1731 imageBBoxes.append(afwGeom.Box2I(dataBBox.getBegin(),
1732 afwGeom.Extent2I(dataBBox.getWidth(), yLower)))
1733 overscanBBoxes.append(afwGeom.Box2I(oscanBBox.getBegin() +
1734 afwGeom.Extent2I(dx0, 0),
1735 afwGeom.Extent2I(oscanBBox.getWidth() - dx0 + dx1,
1738 imageBBoxes.append(afwGeom.Box2I(dataBBox.getBegin() + afwGeom.Extent2I(0, yLower),
1739 afwGeom.Extent2I(dataBBox.getWidth(), yUpper)))
1740 overscanBBoxes.append(afwGeom.Box2I(oscanBBox.getBegin() + afwGeom.Extent2I(dx0, yLower),
1741 afwGeom.Extent2I(oscanBBox.getWidth() - dx0 + dx1,
1744 imageBBoxes.append(afwGeom.Box2I(dataBBox.getBegin(),
1745 afwGeom.Extent2I(dataBBox.getWidth(), dataBBox.getHeight())))
1746 overscanBBoxes.append(afwGeom.Box2I(oscanBBox.getBegin() + afwGeom.Extent2I(dx0, 0),
1747 afwGeom.Extent2I(oscanBBox.getWidth() - dx0 + dx1,
1748 oscanBBox.getHeight())))
1751 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1752 ampImage = ccdExposure.maskedImage[imageBBox]
1753 overscanImage = ccdExposure.maskedImage[overscanBBox]
1755 overscanArray = overscanImage.image.array
1756 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1757 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1758 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1760 statControl = afwMath.StatisticsControl()
1761 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1763 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1764 overscanImage=overscanImage,
1765 fitType=self.config.overscanFitType,
1766 order=self.config.overscanOrder,
1767 collapseRej=self.config.overscanNumSigmaClip,
1768 statControl=statControl,
1769 overscanIsInt=self.config.overscanIsInt
1773 levelStat = afwMath.MEDIAN
1774 sigmaStat = afwMath.STDEVCLIP
1776 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1777 self.config.qa.flatness.nIter)
1778 metadata = ccdExposure.getMetadata()
1779 ampNum = amp.getName()
1780 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1781 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1782 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1784 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1785 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1786 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1788 return overscanResults
1791 """Set the variance plane using the amplifier gain and read noise 1793 The read noise is calculated from the ``overscanImage`` if the 1794 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1795 the value from the amplifier data is used. 1799 ampExposure : `lsst.afw.image.Exposure` 1800 Exposure to process. 1801 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1802 Amplifier detector data. 1803 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1804 Image of overscan, required only for empirical read noise. 1808 lsst.ip.isr.isrFunctions.updateVariance 1810 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1811 gain = amp.getGain()
1813 if math.isnan(gain):
1815 self.log.warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1818 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f" %
1819 (amp.getName(), gain, patchedGain))
1822 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1823 self.log.info(
"Overscan is none for EmpiricalReadNoise")
1825 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1826 stats = afwMath.StatisticsControl()
1827 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1828 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
1829 self.log.info(
"Calculated empirical read noise for amp %s: %f", amp.getName(), readNoise)
1831 readNoise = amp.getReadNoise()
1833 isrFunctions.updateVariance(
1834 maskedImage=ampExposure.getMaskedImage(),
1836 readNoise=readNoise,
1840 """!Apply dark correction in place. 1844 exposure : `lsst.afw.image.Exposure` 1845 Exposure to process. 1846 darkExposure : `lsst.afw.image.Exposure` 1847 Dark exposure of the same size as ``exposure``. 1848 invert : `Bool`, optional 1849 If True, re-add the dark to an already corrected image. 1854 Raised if either ``exposure`` or ``darkExposure`` do not 1855 have their dark time defined. 1859 lsst.ip.isr.isrFunctions.darkCorrection 1861 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1862 if math.isnan(expScale):
1863 raise RuntimeError(
"Exposure darktime is NAN")
1864 if darkExposure.getInfo().getVisitInfo()
is not None:
1865 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1871 if math.isnan(darkScale):
1872 raise RuntimeError(
"Dark calib darktime is NAN")
1873 isrFunctions.darkCorrection(
1874 maskedImage=exposure.getMaskedImage(),
1875 darkMaskedImage=darkExposure.getMaskedImage(),
1877 darkScale=darkScale,
1879 trimToFit=self.config.doTrimToMatchCalib
1883 """!Check if linearization is needed for the detector cameraGeom. 1885 Checks config.doLinearize and the linearity type of the first 1890 detector : `lsst.afw.cameraGeom.Detector` 1891 Detector to get linearity type from. 1895 doLinearize : `Bool` 1896 If True, linearization should be performed. 1898 return self.config.doLinearize
and \
1899 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
1902 """!Apply flat correction in place. 1906 exposure : `lsst.afw.image.Exposure` 1907 Exposure to process. 1908 flatExposure : `lsst.afw.image.Exposure` 1909 Flat exposure of the same size as ``exposure``. 1910 invert : `Bool`, optional 1911 If True, unflatten an already flattened image. 1915 lsst.ip.isr.isrFunctions.flatCorrection 1917 isrFunctions.flatCorrection(
1918 maskedImage=exposure.getMaskedImage(),
1919 flatMaskedImage=flatExposure.getMaskedImage(),
1920 scalingType=self.config.flatScalingType,
1921 userScale=self.config.flatUserScale,
1923 trimToFit=self.config.doTrimToMatchCalib
1927 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 1931 exposure : `lsst.afw.image.Exposure` 1932 Exposure to process. Only the amplifier DataSec is processed. 1933 amp : `lsst.afw.table.AmpInfoCatalog` 1934 Amplifier detector data. 1938 lsst.ip.isr.isrFunctions.makeThresholdMask 1940 if not math.isnan(amp.getSaturation()):
1941 maskedImage = exposure.getMaskedImage()
1942 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1943 isrFunctions.makeThresholdMask(
1944 maskedImage=dataView,
1945 threshold=amp.getSaturation(),
1947 maskName=self.config.saturatedMaskName,
1951 """!Interpolate over saturated pixels, in place. 1953 This method should be called after `saturationDetection`, to 1954 ensure that the saturated pixels have been identified in the 1955 SAT mask. It should also be called after `assembleCcd`, since 1956 saturated regions may cross amplifier boundaries. 1960 exposure : `lsst.afw.image.Exposure` 1961 Exposure to process. 1965 lsst.ip.isr.isrTask.saturationDetection 1966 lsst.ip.isr.isrFunctions.interpolateFromMask 1968 isrFunctions.interpolateFromMask(
1969 maskedImage=exposure.getMaskedImage(),
1970 fwhm=self.config.fwhm,
1971 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1972 maskNameList=list(self.config.saturatedMaskName),
1976 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 1980 exposure : `lsst.afw.image.Exposure` 1981 Exposure to process. Only the amplifier DataSec is processed. 1982 amp : `lsst.afw.table.AmpInfoCatalog` 1983 Amplifier detector data. 1987 lsst.ip.isr.isrFunctions.makeThresholdMask 1991 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 1992 This is intended to indicate pixels that may be affected by unknown systematics; 1993 for example if non-linearity corrections above a certain level are unstable 1994 then that would be a useful value for suspectLevel. A value of `nan` indicates 1995 that no such level exists and no pixels are to be masked as suspicious. 1997 suspectLevel = amp.getSuspectLevel()
1998 if math.isnan(suspectLevel):
2001 maskedImage = exposure.getMaskedImage()
2002 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2003 isrFunctions.makeThresholdMask(
2004 maskedImage=dataView,
2005 threshold=suspectLevel,
2007 maskName=self.config.suspectMaskName,
2011 """!Mask defects using mask plane "BAD", in place. 2015 exposure : `lsst.afw.image.Exposure` 2016 Exposure to process. 2017 defectBaseList : `List` 2018 List of defects to mask and interpolate. 2022 Call this after CCD assembly, since defects may cross amplifier boundaries. 2024 maskedImage = exposure.getMaskedImage()
2026 for d
in defectBaseList:
2028 nd = measAlg.Defect(bbox)
2029 defectList.append(nd)
2030 isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
2032 if self.config.numEdgeSuspect > 0:
2033 goodBBox = maskedImage.getBBox()
2035 goodBBox.grow(-self.config.numEdgeSuspect)
2037 SourceDetectionTask.setEdgeBits(
2040 maskedImage.getMask().getPlaneBitMask(
"SUSPECT")
2044 """Mask and interpolate defects using mask plane "BAD", in place. 2048 exposure : `lsst.afw.image.Exposure` 2049 Exposure to process. 2050 defectBaseList : `List` of `Defects` 2053 self.maskDefects(exposure, defectBaseList)
2054 isrFunctions.interpolateFromMask(
2055 maskedImage=exposure.getMaskedImage(),
2056 fwhm=self.config.fwhm,
2057 growSaturatedFootprints=0,
2058 maskNameList=[
"BAD"],
2062 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2066 exposure : `lsst.afw.image.Exposure` 2067 Exposure to process. 2071 We mask over all NaNs, including those that are masked with 2072 other bits (because those may or may not be interpolated over 2073 later, and we want to remove all NaNs). Despite this 2074 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2075 the historical name. 2077 maskedImage = exposure.getMaskedImage()
2080 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2081 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2082 numNans =
maskNans(maskedImage, maskVal)
2083 self.metadata.set(
"NUMNANS", numNans)
2085 self.log.warn(
"There were %i unmasked NaNs", numNans)
2088 """Measure the image background in subgrids, for quality control purposes. 2092 exposure : `lsst.afw.image.Exposure` 2093 Exposure to process. 2094 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2095 Configuration object containing parameters on which background 2096 statistics and subgrids to use. 2098 if IsrQaConfig
is not None:
2099 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2100 IsrQaConfig.flatness.nIter)
2101 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2102 statsControl.setAndMask(maskVal)
2103 maskedImage = exposure.getMaskedImage()
2104 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2105 skyLevel = stats.getValue(afwMath.MEDIAN)
2106 skySigma = stats.getValue(afwMath.STDEVCLIP)
2107 self.log.info(
"Flattened sky level: %f +/- %f" % (skyLevel, skySigma))
2108 metadata = exposure.getMetadata()
2109 metadata.set(
'SKYLEVEL', skyLevel)
2110 metadata.set(
'SKYSIGMA', skySigma)
2113 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2114 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2115 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2116 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2117 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2118 skyLevels = numpy.zeros((nX, nY))
2121 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2123 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2125 xLLC = xc - meshXHalf
2126 yLLC = yc - meshYHalf
2127 xURC = xc + meshXHalf - 1
2128 yURC = yc + meshYHalf - 1
2130 bbox = afwGeom.Box2I(afwGeom.Point2I(xLLC, yLLC), afwGeom.Point2I(xURC, yURC))
2131 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2133 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2135 good = numpy.where(numpy.isfinite(skyLevels))
2136 skyMedian = numpy.median(skyLevels[good])
2137 flatness = (skyLevels[good] - skyMedian) / skyMedian
2138 flatness_rms = numpy.std(flatness)
2139 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2141 self.log.info(
"Measuring sky levels in %dx%d grids: %f" % (nX, nY, skyMedian))
2142 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f" %
2143 (nX, nY, flatness_pp, flatness_rms))
2145 metadata.set(
'FLATNESS_PP', float(flatness_pp))
2146 metadata.set(
'FLATNESS_RMS', float(flatness_rms))
2147 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2148 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2149 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2152 """Set an approximate magnitude zero point for the exposure. 2156 exposure : `lsst.afw.image.Exposure` 2157 Exposure to process. 2159 filterName = afwImage.Filter(exposure.getFilter().getId()).getName()
2160 if filterName
in self.config.fluxMag0T1:
2161 fluxMag0 = self.config.fluxMag0T1[filterName]
2163 self.log.warn(
"No rough magnitude zero point set for filter %s" % filterName)
2164 fluxMag0 = self.config.defaultFluxMag0T1
2166 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2168 self.log.warn(
"Non-positive exposure time; skipping rough zero point")
2171 self.log.info(
"Setting rough magnitude zero point: %f" % (2.5*math.log10(fluxMag0*expTime),))
2172 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2175 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2179 ccdExposure : `lsst.afw.image.Exposure` 2180 Exposure to process. 2181 fpPolygon : `lsst.afw.geom.Polygon` 2182 Polygon in focal plane coordinates. 2185 ccd = ccdExposure.getDetector()
2186 fpCorners = ccd.getCorners(FOCAL_PLANE)
2187 ccdPolygon = Polygon(fpCorners)
2190 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2193 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2194 validPolygon = Polygon(ccdPoints)
2195 ccdExposure.getInfo().setValidPolygon(validPolygon)
2199 """Context manager that applies and removes flats and darks, 2200 if the task is configured to apply them. 2204 exp : `lsst.afw.image.Exposure` 2205 Exposure to process. 2206 flat : `lsst.afw.image.Exposure` 2207 Flat exposure the same size as ``exp``. 2208 dark : `lsst.afw.image.Exposure`, optional 2209 Dark exposure the same size as ``exp``. 2213 exp : `lsst.afw.image.Exposure` 2214 The flat and dark corrected exposure. 2216 if self.config.doDark
and dark
is not None:
2218 if self.config.doFlat:
2223 if self.config.doFlat:
2225 if self.config.doDark
and dark
is not None:
2229 """Utility function to examine ISR exposure at different stages. 2233 exposure : `lsst.afw.image.Exposure` 2236 State of processing to view. 2238 frame = getDebugFrame(self._display, stepname)
2240 display = getDisplay(frame)
2241 display.scale(
'asinh',
'zscale')
2242 display.mtv(exposure)
2243 prompt =
"Press Enter to continue [c]... " 2245 ans = input(prompt).lower()
2246 if ans
in (
"",
"c",):
2251 """A Detector-like object that supports returning gain and saturation level 2253 This is used when the input exposure does not have a detector. 2257 exposure : `lsst.afw.image.Exposure` 2258 Exposure to generate a fake amplifier for. 2259 config : `lsst.ip.isr.isrTaskConfig` 2260 Configuration to apply to the fake amplifier. 2264 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2266 self.
_gain = config.gain
2296 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2300 """Task to wrap the default IsrTask to allow it to be retargeted. 2302 The standard IsrTask can be called directly from a command line 2303 program, but doing so removes the ability of the task to be 2304 retargeted. As most cameras override some set of the IsrTask 2305 methods, this would remove those data-specific methods in the 2306 output post-ISR images. This wrapping class fixes the issue, 2307 allowing identical post-ISR images to be generated by both the 2308 processCcd and isrTask code. 2310 ConfigClass = RunIsrConfig
2311 _DefaultName =
"runIsr" 2315 self.makeSubtask(
"isr")
2321 dataRef : `lsst.daf.persistence.ButlerDataRef` 2322 data reference of the detector data to be processed 2326 result : `pipeBase.Struct` 2327 Result struct with component: 2329 - exposure : `lsst.afw.image.Exposure` 2330 Post-ISR processed exposure. def getInputDatasetTypes(cls, config)
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)
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)
def roughZeroPoint(self, exposure)
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def getRawHorizontalOverscanBBox(self)
def maskNan(self, exposure)
def getSuspectLevel(self)
def getOutputDatasetTypes(cls, config)
def maskDefect(self, exposure, defectBaseList)
Mask defects using mask plane "BAD", in place.
def overscanCorrection(self, ccdExposure, amp)
def convertIntToFloat(self, exposure)
def flatCorrection(self, exposure, flatExposure, invert=False)
Apply flat correction in place.
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.
def updateVariance(self, ampExposure, amp, overscanImage=None)
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)