28 import lsst.pex.config
as pexConfig
32 from contextlib
import contextmanager
33 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
57 __all__ = [
"IsrTask",
"IsrTaskConfig",
"RunIsrTask",
"RunIsrConfig"]
61 dimensions={
"instrument",
"visit",
"detector"},
63 ccdExposure = cT.PrerequisiteInput(
65 doc=
"Input exposure to process.",
66 storageClass=
"Exposure",
67 dimensions=[
"instrument",
"visit",
"detector"],
69 camera = cT.PrerequisiteInput(
71 storageClass=
"Camera",
72 doc=
"Input camera to construct complete exposures.",
73 dimensions=[
"instrument",
"calibration_label"],
75 bias = cT.PrerequisiteInput(
77 doc=
"Input bias calibration.",
78 storageClass=
"ImageF",
79 dimensions=[
"instrument",
"calibration_label",
"detector"],
81 dark = cT.PrerequisiteInput(
83 doc=
"Input dark calibration.",
84 storageClass=
"ImageF",
85 dimensions=[
"instrument",
"calibration_label",
"detector"],
87 flat = cT.PrerequisiteInput(
89 doc=
"Input flat calibration.",
90 storageClass=
"MaskedImageF",
91 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
93 bfKernel = cT.PrerequisiteInput(
95 doc=
"Input brighter-fatter kernel.",
96 storageClass=
"NumpyArray",
97 dimensions=[
"instrument",
"calibration_label"],
99 defects = cT.PrerequisiteInput(
101 doc=
"Input defect tables.",
102 storageClass=
"DefectsList",
103 dimensions=[
"instrument",
"calibration_label",
"detector"],
105 opticsTransmission = cT.PrerequisiteInput(
106 name=
"transmission_optics",
107 storageClass=
"TransmissionCurve",
108 doc=
"Transmission curve due to the optics.",
109 dimensions=[
"instrument",
"calibration_label"],
111 filterTransmission = cT.PrerequisiteInput(
112 name=
"transmission_filter",
113 storageClass=
"TransmissionCurve",
114 doc=
"Transmission curve due to the filter.",
115 dimensions=[
"instrument",
"physical_filter",
"calibration_label"],
117 sensorTransmission = cT.PrerequisiteInput(
118 name=
"transmission_sensor",
119 storageClass=
"TransmissionCurve",
120 doc=
"Transmission curve due to the sensor.",
121 dimensions=[
"instrument",
"calibration_label",
"detector"],
123 atmosphereTransmission = cT.PrerequisiteInput(
124 name=
"transmission_atmosphere",
125 storageClass=
"TransmissionCurve",
126 doc=
"Transmission curve due to the atmosphere.",
127 dimensions=[
"instrument"],
129 illumMaskedImage = cT.PrerequisiteInput(
131 doc=
"Input illumination correction.",
132 storageClass=
"MaskedImageF",
133 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
136 outputExposure = cT.Output(
138 doc=
"Output ISR processed exposure.",
139 storageClass=
"ExposureF",
140 dimensions=[
"instrument",
"visit",
"detector"],
142 preInterpExposure = cT.Output(
143 name=
'preInterpISRCCD',
144 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
145 storageClass=
"ExposureF",
146 dimensions=[
"instrument",
"visit",
"detector"],
148 outputOssThumbnail = cT.Output(
150 doc=
"Output Overscan-subtracted thumbnail image.",
151 storageClass=
"Thumbnail",
152 dimensions=[
"instrument",
"visit",
"detector"],
154 outputFlattenedThumbnail = cT.Output(
155 name=
"FlattenedThumb",
156 doc=
"Output flat-corrected thumbnail image.",
157 storageClass=
"Thumbnail",
158 dimensions=[
"instrument",
"visit",
"detector"],
164 if config.doBias
is not True:
165 self.prerequisiteInputs.discard(
"bias")
166 if config.doLinearize
is not True:
167 self.prerequisiteInputs.discard(
"linearizer")
168 if config.doCrosstalk
is not True:
169 self.prerequisiteInputs.discard(
"crosstalkSources")
170 if config.doBrighterFatter
is not True:
171 self.prerequisiteInputs.discard(
"bfKernel")
172 if config.doDefect
is not True:
173 self.prerequisiteInputs.discard(
"defects")
174 if config.doDark
is not True:
175 self.prerequisiteInputs.discard(
"dark")
176 if config.doFlat
is not True:
177 self.prerequisiteInputs.discard(
"flat")
178 if config.doAttachTransmissionCurve
is not True:
179 self.prerequisiteInputs.discard(
"opticsTransmission")
180 self.prerequisiteInputs.discard(
"filterTransmission")
181 self.prerequisiteInputs.discard(
"sensorTransmission")
182 self.prerequisiteInputs.discard(
"atmosphereTransmission")
183 if config.doUseOpticsTransmission
is not True:
184 self.prerequisiteInputs.discard(
"opticsTransmission")
185 if config.doUseFilterTransmission
is not True:
186 self.prerequisiteInputs.discard(
"filterTransmission")
187 if config.doUseSensorTransmission
is not True:
188 self.prerequisiteInputs.discard(
"sensorTransmission")
189 if config.doUseAtmosphereTransmission
is not True:
190 self.prerequisiteInputs.discard(
"atmosphereTransmission")
191 if config.doIlluminationCorrection
is not True:
192 self.prerequisiteInputs.discard(
"illumMaskedImage")
194 if config.doWrite
is not True:
195 self.outputs.discard(
"outputExposure")
196 self.outputs.discard(
"preInterpExposure")
197 self.outputs.discard(
"outputFlattenedThumbnail")
198 self.outputs.discard(
"outputOssThumbnail")
199 if config.doSaveInterpPixels
is not True:
200 self.outputs.discard(
"preInterpExposure")
201 if config.qa.doThumbnailOss
is not True:
202 self.outputs.discard(
"outputOssThumbnail")
203 if config.qa.doThumbnailFlattened
is not True:
204 self.outputs.discard(
"outputFlattenedThumbnail")
208 pipelineConnections=IsrTaskConnections):
209 """Configuration parameters for IsrTask. 211 Items are grouped in the order in which they are executed by the task. 213 datasetType = pexConfig.Field(
215 doc=
"Dataset type for input data; users will typically leave this alone, " 216 "but camera-specific ISR tasks will override it",
220 fallbackFilterName = pexConfig.Field(
222 doc=
"Fallback default filter name for calibrations.",
225 expectWcs = pexConfig.Field(
228 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 230 fwhm = pexConfig.Field(
232 doc=
"FWHM of PSF in arcseconds.",
235 qa = pexConfig.ConfigField(
237 doc=
"QA related configuration options.",
241 doConvertIntToFloat = pexConfig.Field(
243 doc=
"Convert integer raw images to floating point values?",
248 doSaturation = pexConfig.Field(
250 doc=
"Mask saturated pixels? NB: this is totally independent of the" 251 " interpolation option - this is ONLY setting the bits in the mask." 252 " To have them interpolated make sure doSaturationInterpolation=True",
255 saturatedMaskName = pexConfig.Field(
257 doc=
"Name of mask plane to use in saturation detection and interpolation",
260 saturation = pexConfig.Field(
262 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
263 default=float(
"NaN"),
265 growSaturationFootprintSize = pexConfig.Field(
267 doc=
"Number of pixels by which to grow the saturation footprints",
272 doSuspect = pexConfig.Field(
274 doc=
"Mask suspect pixels?",
277 suspectMaskName = pexConfig.Field(
279 doc=
"Name of mask plane to use for suspect pixels",
282 numEdgeSuspect = pexConfig.Field(
284 doc=
"Number of edge pixels to be flagged as untrustworthy.",
289 doSetBadRegions = pexConfig.Field(
291 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
294 badStatistic = pexConfig.ChoiceField(
296 doc=
"How to estimate the average value for BAD regions.",
299 "MEANCLIP":
"Correct using the (clipped) mean of good data",
300 "MEDIAN":
"Correct using the median of the good data",
305 doOverscan = pexConfig.Field(
307 doc=
"Do overscan subtraction?",
310 overscanFitType = pexConfig.ChoiceField(
312 doc=
"The method for fitting the overscan bias level.",
315 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
316 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
317 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
318 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
319 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
320 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
321 "MEAN":
"Correct using the mean of the overscan region",
322 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
323 "MEDIAN":
"Correct using the median of the overscan region",
326 overscanOrder = pexConfig.Field(
328 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
329 "or number of spline knots if overscan fit type is a spline."),
332 overscanNumSigmaClip = pexConfig.Field(
334 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
337 overscanIsInt = pexConfig.Field(
339 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN",
342 overscanNumLeadingColumnsToSkip = pexConfig.Field(
344 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
347 overscanNumTrailingColumnsToSkip = pexConfig.Field(
349 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
352 overscanMaxDev = pexConfig.Field(
354 doc=
"Maximum deviation from the median for overscan",
355 default=1000.0, check=
lambda x: x > 0
357 overscanBiasJump = pexConfig.Field(
359 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
362 overscanBiasJumpKeyword = pexConfig.Field(
364 doc=
"Header keyword containing information about devices.",
365 default=
"NO_SUCH_KEY",
367 overscanBiasJumpDevices = pexConfig.ListField(
369 doc=
"List of devices that need piecewise overscan correction.",
372 overscanBiasJumpLocation = pexConfig.Field(
374 doc=
"Location of bias jump along y-axis.",
379 doAssembleCcd = pexConfig.Field(
382 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 384 assembleCcd = pexConfig.ConfigurableField(
385 target=AssembleCcdTask,
386 doc=
"CCD assembly task",
390 doAssembleIsrExposures = pexConfig.Field(
393 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 395 doTrimToMatchCalib = pexConfig.Field(
398 doc=
"Trim raw data to match calibration bounding boxes?" 402 doBias = pexConfig.Field(
404 doc=
"Apply bias frame correction?",
407 biasDataProductName = pexConfig.Field(
409 doc=
"Name of the bias data product",
414 doVariance = pexConfig.Field(
416 doc=
"Calculate variance?",
419 gain = pexConfig.Field(
421 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
422 default=float(
"NaN"),
424 readNoise = pexConfig.Field(
426 doc=
"The read noise to use if no Detector is present in the Exposure",
429 doEmpiricalReadNoise = pexConfig.Field(
432 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 436 doLinearize = pexConfig.Field(
438 doc=
"Correct for nonlinearity of the detector's response?",
443 doCrosstalk = pexConfig.Field(
445 doc=
"Apply intra-CCD crosstalk correction?",
448 doCrosstalkBeforeAssemble = pexConfig.Field(
450 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
453 crosstalk = pexConfig.ConfigurableField(
454 target=CrosstalkTask,
455 doc=
"Intra-CCD crosstalk correction",
459 doDefect = pexConfig.Field(
461 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
464 doNanMasking = pexConfig.Field(
466 doc=
"Mask NAN pixels?",
469 doWidenSaturationTrails = pexConfig.Field(
471 doc=
"Widen bleed trails based on their width?",
476 doBrighterFatter = pexConfig.Field(
479 doc=
"Apply the brighter fatter correction" 481 brighterFatterLevel = pexConfig.ChoiceField(
484 doc=
"The level at which to correct for brighter-fatter.",
486 "AMP":
"Every amplifier treated separately.",
487 "DETECTOR":
"One kernel per detector",
490 brighterFatterKernelFile = pexConfig.Field(
493 doc=
"Kernel file used for the brighter fatter correction" 495 brighterFatterMaxIter = pexConfig.Field(
498 doc=
"Maximum number of iterations for the brighter fatter correction" 500 brighterFatterThreshold = pexConfig.Field(
503 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 504 " absolute value of the difference between the current corrected image and the one" 505 " from the previous iteration summed over all the pixels." 507 brighterFatterApplyGain = pexConfig.Field(
510 doc=
"Should the gain be applied when applying the brighter fatter correction?" 514 doDark = pexConfig.Field(
516 doc=
"Apply dark frame correction?",
519 darkDataProductName = pexConfig.Field(
521 doc=
"Name of the dark data product",
526 doStrayLight = pexConfig.Field(
528 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
531 strayLight = pexConfig.ConfigurableField(
532 target=StrayLightTask,
533 doc=
"y-band stray light correction" 537 doFlat = pexConfig.Field(
539 doc=
"Apply flat field correction?",
542 flatDataProductName = pexConfig.Field(
544 doc=
"Name of the flat data product",
547 flatScalingType = pexConfig.ChoiceField(
549 doc=
"The method for scaling the flat on the fly.",
552 "USER":
"Scale by flatUserScale",
553 "MEAN":
"Scale by the inverse of the mean",
554 "MEDIAN":
"Scale by the inverse of the median",
557 flatUserScale = pexConfig.Field(
559 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
562 doTweakFlat = pexConfig.Field(
564 doc=
"Tweak flats to match observed amplifier ratios?",
569 doApplyGains = pexConfig.Field(
571 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
574 normalizeGains = pexConfig.Field(
576 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
581 doFringe = pexConfig.Field(
583 doc=
"Apply fringe correction?",
586 fringe = pexConfig.ConfigurableField(
588 doc=
"Fringe subtraction task",
590 fringeAfterFlat = pexConfig.Field(
592 doc=
"Do fringe subtraction after flat-fielding?",
597 doAddDistortionModel = pexConfig.Field(
599 doc=
"Apply a distortion model based on camera geometry to the WCS?",
601 deprecated=(
"Camera geometry is incorporated when reading the raw files." 602 " This option no longer is used, and will be removed after v19.")
606 doMeasureBackground = pexConfig.Field(
608 doc=
"Measure the background level on the reduced image?",
613 doCameraSpecificMasking = pexConfig.Field(
615 doc=
"Mask camera-specific bad regions?",
618 masking = pexConfig.ConfigurableField(
625 doInterpolate = pexConfig.Field(
627 doc=
"Interpolate masked pixels?",
630 doSaturationInterpolation = pexConfig.Field(
632 doc=
"Perform interpolation over pixels masked as saturated?" 633 " NB: This is independent of doSaturation; if that is False this plane" 634 " will likely be blank, resulting in a no-op here.",
637 doNanInterpolation = pexConfig.Field(
639 doc=
"Perform interpolation over pixels masked as NaN?" 640 " NB: This is independent of doNanMasking; if that is False this plane" 641 " will likely be blank, resulting in a no-op here.",
644 doNanInterpAfterFlat = pexConfig.Field(
646 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 647 "also have to interpolate them before flat-fielding."),
650 maskListToInterpolate = pexConfig.ListField(
652 doc=
"List of mask planes that should be interpolated.",
653 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
655 doSaveInterpPixels = pexConfig.Field(
657 doc=
"Save a copy of the pre-interpolated pixel values?",
662 fluxMag0T1 = pexConfig.DictField(
665 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
666 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
669 defaultFluxMag0T1 = pexConfig.Field(
671 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
672 default=pow(10.0, 0.4*28.0)
676 doVignette = pexConfig.Field(
678 doc=
"Apply vignetting parameters?",
681 vignette = pexConfig.ConfigurableField(
683 doc=
"Vignetting task.",
687 doAttachTransmissionCurve = pexConfig.Field(
690 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 692 doUseOpticsTransmission = pexConfig.Field(
695 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 697 doUseFilterTransmission = pexConfig.Field(
700 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 702 doUseSensorTransmission = pexConfig.Field(
705 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 707 doUseAtmosphereTransmission = pexConfig.Field(
710 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 714 doIlluminationCorrection = pexConfig.Field(
717 doc=
"Perform illumination correction?" 719 illuminationCorrectionDataProductName = pexConfig.Field(
721 doc=
"Name of the illumination correction data product.",
724 illumScale = pexConfig.Field(
726 doc=
"Scale factor for the illumination correction.",
729 illumFilters = pexConfig.ListField(
732 doc=
"Only perform illumination correction for these filters." 736 doWrite = pexConfig.Field(
738 doc=
"Persist postISRCCD?",
745 raise ValueError(
"You may not specify both doFlat and doApplyGains")
747 self.config.maskListToInterpolate.append(
"SAT")
749 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
752 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
753 """Apply common instrument signature correction algorithms to a raw frame. 755 The process for correcting imaging data is very similar from 756 camera to camera. This task provides a vanilla implementation of 757 doing these corrections, including the ability to turn certain 758 corrections off if they are not needed. The inputs to the primary 759 method, `run()`, are a raw exposure to be corrected and the 760 calibration data products. The raw input is a single chip sized 761 mosaic of all amps including overscans and other non-science 762 pixels. The method `runDataRef()` identifies and defines the 763 calibration data products, and is intended for use by a 764 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 765 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 766 subclassed for different camera, although the most camera specific 767 methods have been split into subtasks that can be redirected 770 The __init__ method sets up the subtasks for ISR processing, using 771 the defaults from `lsst.ip.isr`. 776 Positional arguments passed to the Task constructor. None used at this time. 777 kwargs : `dict`, optional 778 Keyword arguments passed on to the Task constructor. None used at this time. 780 ConfigClass = IsrTaskConfig
785 self.makeSubtask(
"assembleCcd")
786 self.makeSubtask(
"crosstalk")
787 self.makeSubtask(
"strayLight")
788 self.makeSubtask(
"fringe")
789 self.makeSubtask(
"masking")
790 self.makeSubtask(
"vignette")
793 inputs = butlerQC.get(inputRefs)
796 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
797 except Exception
as e:
798 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
801 inputs[
'isGen3'] =
True 803 if self.config.doLinearize
is True:
804 if 'linearizer' not in inputs.keys():
805 detector = inputs[
'ccdExposure'].getDetector()
806 linearityName = detector.getAmplifiers()[0].getLinearityType()
807 inputs[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
809 if inputs[
'defects']
is not None:
812 if not isinstance(inputs[
"defects"], Defects):
813 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
830 outputs = self.
run(**inputs)
831 butlerQC.put(outputs, outputRefs)
834 """!Retrieve necessary frames for instrument signature removal. 836 Pre-fetching all required ISR data products limits the IO 837 required by the ISR. Any conflict between the calibration data 838 available and that needed for ISR is also detected prior to 839 doing processing, allowing it to fail quickly. 843 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 844 Butler reference of the detector data to be processed 845 rawExposure : `afw.image.Exposure` 846 The raw exposure that will later be corrected with the 847 retrieved calibration data; should not be modified in this 852 result : `lsst.pipe.base.Struct` 853 Result struct with components (which may be `None`): 854 - ``bias``: bias calibration frame (`afw.image.Exposure`) 855 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 856 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 857 - ``dark``: dark calibration frame (`afw.image.Exposure`) 858 - ``flat``: flat calibration frame (`afw.image.Exposure`) 859 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 860 - ``defects``: list of defects (`lsst.meas.algorithms.Defects`) 861 - ``fringes``: `lsst.pipe.base.Struct` with components: 862 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 863 - ``seed``: random seed derived from the ccdExposureId for random 864 number generator (`uint32`). 865 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 866 A ``TransmissionCurve`` that represents the throughput of the optics, 867 to be evaluated in focal-plane coordinates. 868 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 869 A ``TransmissionCurve`` that represents the throughput of the filter 870 itself, to be evaluated in focal-plane coordinates. 871 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 872 A ``TransmissionCurve`` that represents the throughput of the sensor 873 itself, to be evaluated in post-assembly trimmed detector coordinates. 874 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 875 A ``TransmissionCurve`` that represents the throughput of the 876 atmosphere, assumed to be spatially constant. 877 - ``strayLightData`` : `object` 878 An opaque object containing calibration information for 879 stray-light correction. If `None`, no correction will be 881 - ``illumMaskedImage`` : illumination correction image (`lsst.afw.image.MaskedImage`) 885 NotImplementedError : 886 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 888 ccd = rawExposure.getDetector()
889 filterName = afwImage.Filter(rawExposure.getFilter().getId()).getName()
890 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
891 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
892 if self.config.doBias
else None)
894 linearizer = (dataRef.get(
"linearizer", immediate=
True)
896 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
897 if self.config.doCrosstalk
else None)
898 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
899 if self.config.doDark
else None)
900 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName)
901 if self.config.doFlat
else None)
903 brighterFatterKernel =
None 904 if self.config.doBrighterFatter
is True:
908 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
912 brighterFatterKernel = dataRef.get(
"bfKernel")
914 brighterFatterKernel =
None 915 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
918 if self.config.brighterFatterLevel ==
'DETECTOR':
919 brighterFatterKernel = brighterFatterKernel.kernel[ccd.getId()]
922 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
924 defectList = (dataRef.get(
"defects")
925 if self.config.doDefect
else None)
926 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
927 if self.config.doAssembleIsrExposures
else None)
928 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
929 else pipeBase.Struct(fringes=
None))
931 if self.config.doAttachTransmissionCurve:
932 opticsTransmission = (dataRef.get(
"transmission_optics")
933 if self.config.doUseOpticsTransmission
else None)
934 filterTransmission = (dataRef.get(
"transmission_filter")
935 if self.config.doUseFilterTransmission
else None)
936 sensorTransmission = (dataRef.get(
"transmission_sensor")
937 if self.config.doUseSensorTransmission
else None)
938 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
939 if self.config.doUseAtmosphereTransmission
else None)
941 opticsTransmission =
None 942 filterTransmission =
None 943 sensorTransmission =
None 944 atmosphereTransmission =
None 946 if self.config.doStrayLight:
947 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
949 strayLightData =
None 952 self.config.illuminationCorrectionDataProductName).getMaskedImage()
953 if (self.config.doIlluminationCorrection
and 954 filterName
in self.config.illumFilters)
958 return pipeBase.Struct(bias=biasExposure,
959 linearizer=linearizer,
960 crosstalkSources=crosstalkSources,
963 bfKernel=brighterFatterKernel,
965 fringes=fringeStruct,
966 opticsTransmission=opticsTransmission,
967 filterTransmission=filterTransmission,
968 sensorTransmission=sensorTransmission,
969 atmosphereTransmission=atmosphereTransmission,
970 strayLightData=strayLightData,
971 illumMaskedImage=illumMaskedImage
975 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
976 dark=None, flat=None, bfKernel=None, defects=None, fringes=pipeBase.Struct(fringes=
None),
977 opticsTransmission=
None, filterTransmission=
None,
978 sensorTransmission=
None, atmosphereTransmission=
None,
979 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
982 """!Perform instrument signature removal on an exposure. 984 Steps included in the ISR processing, in order performed, are: 985 - saturation and suspect pixel masking 986 - overscan subtraction 987 - CCD assembly of individual amplifiers 989 - variance image construction 990 - linearization of non-linear response 992 - brighter-fatter correction 995 - stray light subtraction 997 - masking of known defects and camera specific features 998 - vignette calculation 999 - appending transmission curve and distortion model 1003 ccdExposure : `lsst.afw.image.Exposure` 1004 The raw exposure that is to be run through ISR. The 1005 exposure is modified by this method. 1006 camera : `lsst.afw.cameraGeom.Camera`, optional 1007 The camera geometry for this exposure. Used to select the 1008 distortion model appropriate for this data. 1009 bias : `lsst.afw.image.Exposure`, optional 1010 Bias calibration frame. 1011 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1012 Functor for linearization. 1013 crosstalkSources : `list`, optional 1014 List of possible crosstalk sources. 1015 dark : `lsst.afw.image.Exposure`, optional 1016 Dark calibration frame. 1017 flat : `lsst.afw.image.Exposure`, optional 1018 Flat calibration frame. 1019 bfKernel : `numpy.ndarray`, optional 1020 Brighter-fatter kernel. 1021 defects : `lsst.meas.algorithms.Defects`, optional 1023 fringes : `lsst.pipe.base.Struct`, optional 1024 Struct containing the fringe correction data, with 1026 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1027 - ``seed``: random seed derived from the ccdExposureId for random 1028 number generator (`uint32`) 1029 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1030 A ``TransmissionCurve`` that represents the throughput of the optics, 1031 to be evaluated in focal-plane coordinates. 1032 filterTransmission : `lsst.afw.image.TransmissionCurve` 1033 A ``TransmissionCurve`` that represents the throughput of the filter 1034 itself, to be evaluated in focal-plane coordinates. 1035 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1036 A ``TransmissionCurve`` that represents the throughput of the sensor 1037 itself, to be evaluated in post-assembly trimmed detector coordinates. 1038 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1039 A ``TransmissionCurve`` that represents the throughput of the 1040 atmosphere, assumed to be spatially constant. 1041 detectorNum : `int`, optional 1042 The integer number for the detector to process. 1043 isGen3 : bool, optional 1044 Flag this call to run() as using the Gen3 butler environment. 1045 strayLightData : `object`, optional 1046 Opaque object containing calibration information for stray-light 1047 correction. If `None`, no correction will be performed. 1048 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional 1049 Illumination correction image. 1053 result : `lsst.pipe.base.Struct` 1054 Result struct with component: 1055 - ``exposure`` : `afw.image.Exposure` 1056 The fully ISR corrected exposure. 1057 - ``outputExposure`` : `afw.image.Exposure` 1058 An alias for `exposure` 1059 - ``ossThumb`` : `numpy.ndarray` 1060 Thumbnail image of the exposure after overscan subtraction. 1061 - ``flattenedThumb`` : `numpy.ndarray` 1062 Thumbnail image of the exposure after flat-field correction. 1067 Raised if a configuration option is set to True, but the 1068 required calibration data has not been specified. 1072 The current processed exposure can be viewed by setting the 1073 appropriate lsstDebug entries in the `debug.display` 1074 dictionary. The names of these entries correspond to some of 1075 the IsrTaskConfig Boolean options, with the value denoting the 1076 frame to use. The exposure is shown inside the matching 1077 option check and after the processing of that step has 1078 finished. The steps with debug points are: 1089 In addition, setting the "postISRCCD" entry displays the 1090 exposure after all ISR processing has finished. 1098 self.config.doFringe =
False 1101 if detectorNum
is None:
1102 raise RuntimeError(
"Must supply the detectorNum if running as Gen3.")
1104 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1109 if isinstance(ccdExposure, ButlerDataRef):
1112 ccd = ccdExposure.getDetector()
1113 filterName = afwImage.Filter(ccdExposure.getFilter().getId()).getName()
1116 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd." 1117 ccd = [
FakeAmp(ccdExposure, self.config)]
1120 if self.config.doBias
and bias
is None:
1121 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1123 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1124 if self.config.doBrighterFatter
and bfKernel
is None:
1125 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1126 if self.config.doDark
and dark
is None:
1127 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1128 if self.config.doFlat
and flat
is None:
1129 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1130 if self.config.doDefect
and defects
is None:
1131 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1132 if (self.config.doFringe
and filterName
in self.fringe.config.filters
and 1133 fringes.fringes
is None):
1138 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1139 if (self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters
and 1140 illumMaskedImage
is None):
1141 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1144 if self.config.doConvertIntToFloat:
1145 self.log.info(
"Converting exposure to floating point values.")
1152 if ccdExposure.getBBox().contains(amp.getBBox()):
1156 if self.config.doOverscan
and not badAmp:
1159 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1160 if overscanResults
is not None and \
1161 self.config.qa
is not None and self.config.qa.saveStats
is True:
1162 if isinstance(overscanResults.overscanFit, float):
1163 qaMedian = overscanResults.overscanFit
1164 qaStdev = float(
"NaN")
1166 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1167 afwMath.MEDIAN | afwMath.STDEVCLIP)
1168 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1169 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1171 self.metadata.set(f
"ISR OSCAN {amp.getName()} MEDIAN", qaMedian)
1172 self.metadata.set(f
"ISR OSCAN {amp.getName()} STDEV", qaStdev)
1173 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1174 amp.getName(), qaMedian, qaStdev)
1175 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1178 self.log.warn(
"Amplifier %s is bad.", amp.getName())
1179 overscanResults =
None 1181 overscans.append(overscanResults
if overscanResults
is not None else None)
1183 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1185 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1186 self.log.info(
"Applying crosstalk correction.")
1187 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1188 self.
debugView(ccdExposure,
"doCrosstalk")
1190 if self.config.doAssembleCcd:
1191 self.log.info(
"Assembling CCD from amplifiers.")
1192 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1194 if self.config.expectWcs
and not ccdExposure.getWcs():
1195 self.log.warn(
"No WCS found in input exposure.")
1196 self.
debugView(ccdExposure,
"doAssembleCcd")
1199 if self.config.qa.doThumbnailOss:
1200 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1202 if self.config.doBias:
1203 self.log.info(
"Applying bias correction.")
1204 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1205 trimToFit=self.config.doTrimToMatchCalib)
1208 if self.config.doVariance:
1209 for amp, overscanResults
in zip(ccd, overscans):
1210 if ccdExposure.getBBox().contains(amp.getBBox()):
1211 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1212 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1213 if overscanResults
is not None:
1215 overscanImage=overscanResults.overscanImage)
1219 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1220 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1221 afwMath.MEDIAN | afwMath.STDEVCLIP)
1222 self.metadata.set(f
"ISR VARIANCE {amp.getName()} MEDIAN",
1223 qaStats.getValue(afwMath.MEDIAN))
1224 self.metadata.set(f
"ISR VARIANCE {amp.getName()} STDEV",
1225 qaStats.getValue(afwMath.STDEVCLIP))
1226 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1227 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1228 qaStats.getValue(afwMath.STDEVCLIP))
1231 self.log.info(
"Applying linearizer.")
1232 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1234 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1235 self.log.info(
"Applying crosstalk correction.")
1236 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1237 self.
debugView(ccdExposure,
"doCrosstalk")
1241 if self.config.doDefect:
1242 self.log.info(
"Masking defects.")
1245 if self.config.numEdgeSuspect > 0:
1246 self.log.info(
"Masking edges as SUSPECT.")
1247 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1248 maskPlane=
"SUSPECT")
1250 if self.config.doNanMasking:
1251 self.log.info(
"Masking NAN value pixels.")
1254 if self.config.doWidenSaturationTrails:
1255 self.log.info(
"Widening saturation trails.")
1256 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1258 if self.config.doCameraSpecificMasking:
1259 self.log.info(
"Masking regions for camera specific reasons.")
1260 self.masking.
run(ccdExposure)
1262 if self.config.doBrighterFatter:
1271 interpExp = ccdExposure.clone()
1273 isrFunctions.interpolateFromMask(
1274 maskedImage=interpExp.getMaskedImage(),
1275 fwhm=self.config.fwhm,
1276 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1277 maskNameList=self.config.maskListToInterpolate
1279 bfExp = interpExp.clone()
1281 self.log.info(
"Applying brighter fatter correction.")
1282 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1283 self.config.brighterFatterMaxIter,
1284 self.config.brighterFatterThreshold,
1285 self.config.brighterFatterApplyGain
1287 if bfResults[1] == self.config.brighterFatterMaxIter:
1288 self.log.warn(
"Brighter fatter correction did not converge, final difference %f.",
1291 self.log.info(
"Finished brighter fatter correction in %d iterations.",
1293 image = ccdExposure.getMaskedImage().getImage()
1294 bfCorr = bfExp.getMaskedImage().getImage()
1295 bfCorr -= interpExp.getMaskedImage().getImage()
1304 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1306 self.log.warn(
"Ensuring image edges are masked as SUSPECT to the brighter-fatter kernel size.")
1308 self.
debugView(ccdExposure,
"doBrighterFatter")
1310 if self.config.doDark:
1311 self.log.info(
"Applying dark correction.")
1315 if self.config.doFringe
and not self.config.fringeAfterFlat:
1316 self.log.info(
"Applying fringe correction before flat.")
1317 self.fringe.
run(ccdExposure, **fringes.getDict())
1320 if self.config.doStrayLight:
1321 if strayLightData
is not None:
1322 self.log.info(
"Applying stray light correction.")
1323 self.strayLight.
run(ccdExposure, strayLightData)
1324 self.
debugView(ccdExposure,
"doStrayLight")
1326 self.log.debug(
"Skipping stray light correction: no data found for this image.")
1328 if self.config.doFlat:
1329 self.log.info(
"Applying flat correction.")
1333 if self.config.doApplyGains:
1334 self.log.info(
"Applying gain correction instead of flat.")
1335 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1337 if self.config.doFringe
and self.config.fringeAfterFlat:
1338 self.log.info(
"Applying fringe correction after flat.")
1339 self.fringe.
run(ccdExposure, **fringes.getDict())
1341 if self.config.doVignette:
1342 self.log.info(
"Constructing Vignette polygon.")
1345 if self.config.vignette.doWriteVignettePolygon:
1348 if self.config.doAttachTransmissionCurve:
1349 self.log.info(
"Adding transmission curves.")
1350 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1351 filterTransmission=filterTransmission,
1352 sensorTransmission=sensorTransmission,
1353 atmosphereTransmission=atmosphereTransmission)
1355 flattenedThumb =
None 1356 if self.config.qa.doThumbnailFlattened:
1357 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1359 if self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters:
1360 self.log.info(
"Performing illumination correction.")
1361 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1362 illumMaskedImage, illumScale=self.config.illumScale,
1363 trimToFit=self.config.doTrimToMatchCalib)
1366 if self.config.doSaveInterpPixels:
1367 preInterpExp = ccdExposure.clone()
1382 if self.config.doSetBadRegions:
1383 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1384 if badPixelCount > 0:
1385 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1387 if self.config.doInterpolate:
1388 self.log.info(
"Interpolating masked pixels.")
1389 isrFunctions.interpolateFromMask(
1390 maskedImage=ccdExposure.getMaskedImage(),
1391 fwhm=self.config.fwhm,
1392 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1393 maskNameList=list(self.config.maskListToInterpolate)
1398 if self.config.doMeasureBackground:
1399 self.log.info(
"Measuring background level.")
1402 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1404 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1405 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1406 afwMath.MEDIAN | afwMath.STDEVCLIP)
1407 self.metadata.set(
"ISR BACKGROUND {} MEDIAN".format(amp.getName()),
1408 qaStats.getValue(afwMath.MEDIAN))
1409 self.metadata.set(
"ISR BACKGROUND {} STDEV".format(amp.getName()),
1410 qaStats.getValue(afwMath.STDEVCLIP))
1411 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1412 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1413 qaStats.getValue(afwMath.STDEVCLIP))
1415 self.
debugView(ccdExposure,
"postISRCCD")
1417 return pipeBase.Struct(
1418 exposure=ccdExposure,
1420 flattenedThumb=flattenedThumb,
1422 preInterpolatedExposure=preInterpExp,
1423 outputExposure=ccdExposure,
1424 outputOssThumbnail=ossThumb,
1425 outputFlattenedThumbnail=flattenedThumb,
1428 @pipeBase.timeMethod
1430 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1432 This method contains the `CmdLineTask` interface to the ISR 1433 processing. All IO is handled here, freeing the `run()` method 1434 to manage only pixel-level calculations. The steps performed 1436 - Read in necessary detrending/isr/calibration data. 1437 - Process raw exposure in `run()`. 1438 - Persist the ISR-corrected exposure as "postISRCCD" if 1439 config.doWrite=True. 1443 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1444 DataRef of the detector data to be processed 1448 result : `lsst.pipe.base.Struct` 1449 Result struct with component: 1450 - ``exposure`` : `afw.image.Exposure` 1451 The fully ISR corrected exposure. 1456 Raised if a configuration option is set to True, but the 1457 required calibration data does not exist. 1460 self.log.info(
"Performing ISR on sensor %s.", sensorRef.dataId)
1462 ccdExposure = sensorRef.get(self.config.datasetType)
1464 camera = sensorRef.get(
"camera")
1465 isrData = self.
readIsrData(sensorRef, ccdExposure)
1467 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1469 if self.config.doWrite:
1470 sensorRef.put(result.exposure,
"postISRCCD")
1471 if result.preInterpolatedExposure
is not None:
1472 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1473 if result.ossThumb
is not None:
1474 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1475 if result.flattenedThumb
is not None:
1476 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1481 """!Retrieve a calibration dataset for removing instrument signature. 1486 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1487 DataRef of the detector data to find calibration datasets 1490 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1492 If True, disable butler proxies to enable error handling 1493 within this routine. 1497 exposure : `lsst.afw.image.Exposure` 1498 Requested calibration frame. 1503 Raised if no matching calibration frame can be found. 1506 exp = dataRef.get(datasetType, immediate=immediate)
1507 except Exception
as exc1:
1508 if not self.config.fallbackFilterName:
1509 raise RuntimeError(
"Unable to retrieve %s for %s: %s." % (datasetType, dataRef.dataId, exc1))
1511 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1512 except Exception
as exc2:
1513 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s." %
1514 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1515 self.log.warn(
"Using fallback calibration from filter %s.", self.config.fallbackFilterName)
1517 if self.config.doAssembleIsrExposures:
1518 exp = self.assembleCcd.assembleCcd(exp)
1522 """Ensure that the data returned by Butler is a fully constructed exposure. 1524 ISR requires exposure-level image data for historical reasons, so if we did 1525 not recieve that from Butler, construct it from what we have, modifying the 1530 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1531 `lsst.afw.image.ImageF` 1532 The input data structure obtained from Butler. 1533 camera : `lsst.afw.cameraGeom.camera` 1534 The camera associated with the image. Used to find the appropriate 1537 The detector this exposure should match. 1541 inputExp : `lsst.afw.image.Exposure` 1542 The re-constructed exposure, with appropriate detector parameters. 1547 Raised if the input data cannot be used to construct an exposure. 1549 if isinstance(inputExp, afwImage.DecoratedImageU):
1550 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1551 elif isinstance(inputExp, afwImage.ImageF):
1552 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1553 elif isinstance(inputExp, afwImage.MaskedImageF):
1554 inputExp = afwImage.makeExposure(inputExp)
1555 elif isinstance(inputExp, afwImage.Exposure):
1557 elif inputExp
is None:
1561 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1564 if inputExp.getDetector()
is None:
1565 inputExp.setDetector(camera[detectorNum])
1570 """Convert exposure image from uint16 to float. 1572 If the exposure does not need to be converted, the input is 1573 immediately returned. For exposures that are converted to use 1574 floating point pixels, the variance is set to unity and the 1579 exposure : `lsst.afw.image.Exposure` 1580 The raw exposure to be converted. 1584 newexposure : `lsst.afw.image.Exposure` 1585 The input ``exposure``, converted to floating point pixels. 1590 Raised if the exposure type cannot be converted to float. 1593 if isinstance(exposure, afwImage.ExposureF):
1595 self.log.debug(
"Exposure already of type float.")
1597 if not hasattr(exposure,
"convertF"):
1598 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1600 newexposure = exposure.convertF()
1601 newexposure.variance[:] = 1
1602 newexposure.mask[:] = 0x0
1607 """Identify bad amplifiers, saturated and suspect pixels. 1611 ccdExposure : `lsst.afw.image.Exposure` 1612 Input exposure to be masked. 1613 amp : `lsst.afw.table.AmpInfoCatalog` 1614 Catalog of parameters defining the amplifier on this 1616 defects : `lsst.meas.algorithms.Defects` 1617 List of defects. Used to determine if the entire 1623 If this is true, the entire amplifier area is covered by 1624 defects and unusable. 1627 maskedImage = ccdExposure.getMaskedImage()
1633 if defects
is not None:
1634 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1639 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1641 maskView = dataView.getMask()
1642 maskView |= maskView.getPlaneBitMask(
"BAD")
1649 if self.config.doSaturation
and not badAmp:
1650 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1651 if self.config.doSuspect
and not badAmp:
1652 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1653 if math.isfinite(self.config.saturation):
1654 limits.update({self.config.saturatedMaskName: self.config.saturation})
1656 for maskName, maskThreshold
in limits.items():
1657 if not math.isnan(maskThreshold):
1658 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1659 isrFunctions.makeThresholdMask(
1660 maskedImage=dataView,
1661 threshold=maskThreshold,
1667 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1669 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1670 self.config.suspectMaskName])
1671 if numpy.all(maskView.getArray() & maskVal > 0):
1673 maskView |= maskView.getPlaneBitMask(
"BAD")
1678 """Apply overscan correction in place. 1680 This method does initial pixel rejection of the overscan 1681 region. The overscan can also be optionally segmented to 1682 allow for discontinuous overscan responses to be fit 1683 separately. The actual overscan subtraction is performed by 1684 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1685 which is called here after the amplifier is preprocessed. 1689 ccdExposure : `lsst.afw.image.Exposure` 1690 Exposure to have overscan correction performed. 1691 amp : `lsst.afw.table.AmpInfoCatalog` 1692 The amplifier to consider while correcting the overscan. 1696 overscanResults : `lsst.pipe.base.Struct` 1697 Result struct with components: 1698 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1699 Value or fit subtracted from the amplifier image data. 1700 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1701 Value or fit subtracted from the overscan image data. 1702 - ``overscanImage`` : `lsst.afw.image.Image` 1703 Image of the overscan region with the overscan 1704 correction applied. This quantity is used to estimate 1705 the amplifier read noise empirically. 1710 Raised if the ``amp`` does not contain raw pixel information. 1714 lsst.ip.isr.isrFunctions.overscanCorrection 1716 if not amp.getHasRawInfo():
1717 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1719 if amp.getRawHorizontalOverscanBBox().isEmpty():
1720 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1723 statControl = afwMath.StatisticsControl()
1724 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1727 dataBBox = amp.getRawDataBBox()
1728 oscanBBox = amp.getRawHorizontalOverscanBBox()
1732 prescanBBox = amp.getRawPrescanBBox()
1733 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1734 dx0 += self.config.overscanNumLeadingColumnsToSkip
1735 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1737 dx0 += self.config.overscanNumTrailingColumnsToSkip
1738 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1744 if ((self.config.overscanBiasJump
and 1745 self.config.overscanBiasJumpLocation)
and 1746 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1747 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1748 self.config.overscanBiasJumpDevices)):
1749 if amp.getReadoutCorner()
in (ReadoutCorner.LL, ReadoutCorner.LR):
1750 yLower = self.config.overscanBiasJumpLocation
1751 yUpper = dataBBox.getHeight() - yLower
1753 yUpper = self.config.overscanBiasJumpLocation
1754 yLower = dataBBox.getHeight() - yUpper
1773 oscanBBox.getHeight())))
1776 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1777 ampImage = ccdExposure.maskedImage[imageBBox]
1778 overscanImage = ccdExposure.maskedImage[overscanBBox]
1780 overscanArray = overscanImage.image.array
1781 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1782 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1783 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1785 statControl = afwMath.StatisticsControl()
1786 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1788 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1789 overscanImage=overscanImage,
1790 fitType=self.config.overscanFitType,
1791 order=self.config.overscanOrder,
1792 collapseRej=self.config.overscanNumSigmaClip,
1793 statControl=statControl,
1794 overscanIsInt=self.config.overscanIsInt
1798 levelStat = afwMath.MEDIAN
1799 sigmaStat = afwMath.STDEVCLIP
1801 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1802 self.config.qa.flatness.nIter)
1803 metadata = ccdExposure.getMetadata()
1804 ampNum = amp.getName()
1805 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1806 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1807 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1809 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1810 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1811 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1813 return overscanResults
1816 """Set the variance plane using the amplifier gain and read noise 1818 The read noise is calculated from the ``overscanImage`` if the 1819 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1820 the value from the amplifier data is used. 1824 ampExposure : `lsst.afw.image.Exposure` 1825 Exposure to process. 1826 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1827 Amplifier detector data. 1828 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1829 Image of overscan, required only for empirical read noise. 1833 lsst.ip.isr.isrFunctions.updateVariance 1835 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1836 gain = amp.getGain()
1838 if math.isnan(gain):
1840 self.log.warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1843 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f.",
1844 amp.getName(), gain, patchedGain)
1847 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1848 self.log.info(
"Overscan is none for EmpiricalReadNoise.")
1850 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1851 stats = afwMath.StatisticsControl()
1852 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1853 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
1854 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
1855 amp.getName(), readNoise)
1857 readNoise = amp.getReadNoise()
1859 isrFunctions.updateVariance(
1860 maskedImage=ampExposure.getMaskedImage(),
1862 readNoise=readNoise,
1866 """!Apply dark correction in place. 1870 exposure : `lsst.afw.image.Exposure` 1871 Exposure to process. 1872 darkExposure : `lsst.afw.image.Exposure` 1873 Dark exposure of the same size as ``exposure``. 1874 invert : `Bool`, optional 1875 If True, re-add the dark to an already corrected image. 1880 Raised if either ``exposure`` or ``darkExposure`` do not 1881 have their dark time defined. 1885 lsst.ip.isr.isrFunctions.darkCorrection 1887 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1888 if math.isnan(expScale):
1889 raise RuntimeError(
"Exposure darktime is NAN.")
1890 if darkExposure.getInfo().getVisitInfo()
is not None:
1891 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1895 self.log.warn(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
1898 if math.isnan(darkScale):
1899 raise RuntimeError(
"Dark calib darktime is NAN.")
1900 isrFunctions.darkCorrection(
1901 maskedImage=exposure.getMaskedImage(),
1902 darkMaskedImage=darkExposure.getMaskedImage(),
1904 darkScale=darkScale,
1906 trimToFit=self.config.doTrimToMatchCalib
1910 """!Check if linearization is needed for the detector cameraGeom. 1912 Checks config.doLinearize and the linearity type of the first 1917 detector : `lsst.afw.cameraGeom.Detector` 1918 Detector to get linearity type from. 1922 doLinearize : `Bool` 1923 If True, linearization should be performed. 1925 return self.config.doLinearize
and \
1926 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
1929 """!Apply flat correction in place. 1933 exposure : `lsst.afw.image.Exposure` 1934 Exposure to process. 1935 flatExposure : `lsst.afw.image.Exposure` 1936 Flat exposure of the same size as ``exposure``. 1937 invert : `Bool`, optional 1938 If True, unflatten an already flattened image. 1942 lsst.ip.isr.isrFunctions.flatCorrection 1944 isrFunctions.flatCorrection(
1945 maskedImage=exposure.getMaskedImage(),
1946 flatMaskedImage=flatExposure.getMaskedImage(),
1947 scalingType=self.config.flatScalingType,
1948 userScale=self.config.flatUserScale,
1950 trimToFit=self.config.doTrimToMatchCalib
1954 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 1958 exposure : `lsst.afw.image.Exposure` 1959 Exposure to process. Only the amplifier DataSec is processed. 1960 amp : `lsst.afw.table.AmpInfoCatalog` 1961 Amplifier detector data. 1965 lsst.ip.isr.isrFunctions.makeThresholdMask 1967 if not math.isnan(amp.getSaturation()):
1968 maskedImage = exposure.getMaskedImage()
1969 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1970 isrFunctions.makeThresholdMask(
1971 maskedImage=dataView,
1972 threshold=amp.getSaturation(),
1974 maskName=self.config.saturatedMaskName,
1978 """!Interpolate over saturated pixels, in place. 1980 This method should be called after `saturationDetection`, to 1981 ensure that the saturated pixels have been identified in the 1982 SAT mask. It should also be called after `assembleCcd`, since 1983 saturated regions may cross amplifier boundaries. 1987 exposure : `lsst.afw.image.Exposure` 1988 Exposure to process. 1992 lsst.ip.isr.isrTask.saturationDetection 1993 lsst.ip.isr.isrFunctions.interpolateFromMask 1995 isrFunctions.interpolateFromMask(
1996 maskedImage=exposure.getMaskedImage(),
1997 fwhm=self.config.fwhm,
1998 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1999 maskNameList=list(self.config.saturatedMaskName),
2003 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 2007 exposure : `lsst.afw.image.Exposure` 2008 Exposure to process. Only the amplifier DataSec is processed. 2009 amp : `lsst.afw.table.AmpInfoCatalog` 2010 Amplifier detector data. 2014 lsst.ip.isr.isrFunctions.makeThresholdMask 2018 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 2019 This is intended to indicate pixels that may be affected by unknown systematics; 2020 for example if non-linearity corrections above a certain level are unstable 2021 then that would be a useful value for suspectLevel. A value of `nan` indicates 2022 that no such level exists and no pixels are to be masked as suspicious. 2024 suspectLevel = amp.getSuspectLevel()
2025 if math.isnan(suspectLevel):
2028 maskedImage = exposure.getMaskedImage()
2029 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2030 isrFunctions.makeThresholdMask(
2031 maskedImage=dataView,
2032 threshold=suspectLevel,
2034 maskName=self.config.suspectMaskName,
2038 """!Mask defects using mask plane "BAD", in place. 2042 exposure : `lsst.afw.image.Exposure` 2043 Exposure to process. 2044 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2045 `lsst.afw.image.DefectBase`. 2046 List of defects to mask. 2050 Call this after CCD assembly, since defects may cross amplifier boundaries. 2052 maskedImage = exposure.getMaskedImage()
2053 if not isinstance(defectBaseList, Defects):
2055 defectList = Defects(defectBaseList)
2057 defectList = defectBaseList
2058 defectList.maskPixels(maskedImage, maskName=
"BAD")
2060 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT"):
2061 """!Mask edge pixels with applicable mask plane. 2065 exposure : `lsst.afw.image.Exposure` 2066 Exposure to process. 2067 numEdgePixels : `int`, optional 2068 Number of edge pixels to mask. 2069 maskPlane : `str`, optional 2070 Mask plane name to use. 2072 maskedImage = exposure.getMaskedImage()
2073 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2075 if numEdgePixels > 0:
2076 goodBBox = maskedImage.getBBox()
2078 goodBBox.grow(-numEdgePixels)
2080 SourceDetectionTask.setEdgeBits(
2087 """Mask and interpolate defects using mask plane "BAD", in place. 2091 exposure : `lsst.afw.image.Exposure` 2092 Exposure to process. 2093 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2094 `lsst.afw.image.DefectBase`. 2095 List of defects to mask and interpolate. 2099 lsst.ip.isr.isrTask.maskDefect() 2102 self.
maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2103 maskPlane=
"SUSPECT")
2104 isrFunctions.interpolateFromMask(
2105 maskedImage=exposure.getMaskedImage(),
2106 fwhm=self.config.fwhm,
2107 growSaturatedFootprints=0,
2108 maskNameList=[
"BAD"],
2112 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2116 exposure : `lsst.afw.image.Exposure` 2117 Exposure to process. 2121 We mask over all NaNs, including those that are masked with 2122 other bits (because those may or may not be interpolated over 2123 later, and we want to remove all NaNs). Despite this 2124 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2125 the historical name. 2127 maskedImage = exposure.getMaskedImage()
2130 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2131 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2132 numNans =
maskNans(maskedImage, maskVal)
2133 self.metadata.set(
"NUMNANS", numNans)
2135 self.log.warn(
"There were %d unmasked NaNs.", numNans)
2138 """"Mask and interpolate NaNs using mask plane "UNMASKEDNAN", in place. 2142 exposure : `lsst.afw.image.Exposure` 2143 Exposure to process. 2147 lsst.ip.isr.isrTask.maskNan() 2150 isrFunctions.interpolateFromMask(
2151 maskedImage=exposure.getMaskedImage(),
2152 fwhm=self.config.fwhm,
2153 growSaturatedFootprints=0,
2154 maskNameList=[
"UNMASKEDNAN"],
2158 """Measure the image background in subgrids, for quality control purposes. 2162 exposure : `lsst.afw.image.Exposure` 2163 Exposure to process. 2164 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2165 Configuration object containing parameters on which background 2166 statistics and subgrids to use. 2168 if IsrQaConfig
is not None:
2169 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2170 IsrQaConfig.flatness.nIter)
2171 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2172 statsControl.setAndMask(maskVal)
2173 maskedImage = exposure.getMaskedImage()
2174 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2175 skyLevel = stats.getValue(afwMath.MEDIAN)
2176 skySigma = stats.getValue(afwMath.STDEVCLIP)
2177 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2178 metadata = exposure.getMetadata()
2179 metadata.set(
'SKYLEVEL', skyLevel)
2180 metadata.set(
'SKYSIGMA', skySigma)
2183 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2184 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2185 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2186 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2187 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2188 skyLevels = numpy.zeros((nX, nY))
2191 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2193 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2195 xLLC = xc - meshXHalf
2196 yLLC = yc - meshYHalf
2197 xURC = xc + meshXHalf - 1
2198 yURC = yc + meshYHalf - 1
2201 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2203 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2205 good = numpy.where(numpy.isfinite(skyLevels))
2206 skyMedian = numpy.median(skyLevels[good])
2207 flatness = (skyLevels[good] - skyMedian) / skyMedian
2208 flatness_rms = numpy.std(flatness)
2209 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2211 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2212 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2213 nX, nY, flatness_pp, flatness_rms)
2215 metadata.set(
'FLATNESS_PP', float(flatness_pp))
2216 metadata.set(
'FLATNESS_RMS', float(flatness_rms))
2217 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2218 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2219 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2222 """Set an approximate magnitude zero point for the exposure. 2226 exposure : `lsst.afw.image.Exposure` 2227 Exposure to process. 2229 filterName = afwImage.Filter(exposure.getFilter().getId()).getName()
2230 if filterName
in self.config.fluxMag0T1:
2231 fluxMag0 = self.config.fluxMag0T1[filterName]
2233 self.log.warn(
"No rough magnitude zero point set for filter %s.", filterName)
2234 fluxMag0 = self.config.defaultFluxMag0T1
2236 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2238 self.log.warn(
"Non-positive exposure time; skipping rough zero point.")
2241 self.log.info(
"Setting rough magnitude zero point: %f", 2.5*math.log10(fluxMag0*expTime))
2242 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2245 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2249 ccdExposure : `lsst.afw.image.Exposure` 2250 Exposure to process. 2251 fpPolygon : `lsst.afw.geom.Polygon` 2252 Polygon in focal plane coordinates. 2255 ccd = ccdExposure.getDetector()
2256 fpCorners = ccd.getCorners(FOCAL_PLANE)
2257 ccdPolygon = Polygon(fpCorners)
2260 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2263 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2264 validPolygon = Polygon(ccdPoints)
2265 ccdExposure.getInfo().setValidPolygon(validPolygon)
2269 """Context manager that applies and removes flats and darks, 2270 if the task is configured to apply them. 2274 exp : `lsst.afw.image.Exposure` 2275 Exposure to process. 2276 flat : `lsst.afw.image.Exposure` 2277 Flat exposure the same size as ``exp``. 2278 dark : `lsst.afw.image.Exposure`, optional 2279 Dark exposure the same size as ``exp``. 2283 exp : `lsst.afw.image.Exposure` 2284 The flat and dark corrected exposure. 2286 if self.config.doDark
and dark
is not None:
2288 if self.config.doFlat:
2293 if self.config.doFlat:
2295 if self.config.doDark
and dark
is not None:
2299 """Utility function to examine ISR exposure at different stages. 2303 exposure : `lsst.afw.image.Exposure` 2306 State of processing to view. 2308 frame = getDebugFrame(self._display, stepname)
2310 display = getDisplay(frame)
2311 display.scale(
'asinh',
'zscale')
2312 display.mtv(exposure)
2313 prompt =
"Press Enter to continue [c]... " 2315 ans = input(prompt).lower()
2316 if ans
in (
"",
"c",):
2321 """A Detector-like object that supports returning gain and saturation level 2323 This is used when the input exposure does not have a detector. 2327 exposure : `lsst.afw.image.Exposure` 2328 Exposure to generate a fake amplifier for. 2329 config : `lsst.ip.isr.isrTaskConfig` 2330 Configuration to apply to the fake amplifier. 2334 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2336 self.
_gain = config.gain
2366 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2370 """Task to wrap the default IsrTask to allow it to be retargeted. 2372 The standard IsrTask can be called directly from a command line 2373 program, but doing so removes the ability of the task to be 2374 retargeted. As most cameras override some set of the IsrTask 2375 methods, this would remove those data-specific methods in the 2376 output post-ISR images. This wrapping class fixes the issue, 2377 allowing identical post-ISR images to be generated by both the 2378 processCcd and isrTask code. 2380 ConfigClass = RunIsrConfig
2381 _DefaultName =
"runIsr" 2385 self.makeSubtask(
"isr")
2391 dataRef : `lsst.daf.persistence.ButlerDataRef` 2392 data reference of the detector data to be processed 2396 result : `pipeBase.Struct` 2397 Result struct with component: 2399 - exposure : `lsst.afw.image.Exposure` 2400 Post-ISR processed exposure.
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 maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT")
Mask edge pixels with applicable mask plane.
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def runDataRef(self, dataRef)
def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None, dark=None, flat=None, bfKernel=None, defects=None, fringes=pipeBase.Struct(fringes=None), opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, detectorNum=None, strayLightData=None, illumMaskedImage=None, isGen3=False)
Perform instrument signature removal on an exposure.
def __init__(self, args, kwargs)
def roughZeroPoint(self, exposure)
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def getRawHorizontalOverscanBBox(self)
def maskNan(self, exposure)
def getSuspectLevel(self)
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 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 __init__(self, config=None)
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 maskAndInterpolateNan(self, exposure)
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)