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 fringes = cT.PrerequisiteInput(
95 doc=
"Input fringe calibration.",
96 storageClass=
"ExposureF",
97 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
99 strayLightData = cT.PrerequisiteInput(
101 doc=
"Input stray light calibration.",
102 storageClass=
"StrayLightData",
103 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
105 bfKernel = cT.PrerequisiteInput(
107 doc=
"Input brighter-fatter kernel.",
108 storageClass=
"NumpyArray",
109 dimensions=[
"instrument",
"calibration_label"],
111 newBFKernel = cT.PrerequisiteInput(
112 name=
'brighterFatterKernel',
113 doc=
"Newer complete kernel + gain solutions.",
114 storageClass=
"BrighterFatterKernel",
115 dimensions=[
"instrument",
"calibration_label",
"detector"],
117 defects = cT.PrerequisiteInput(
119 doc=
"Input defect tables.",
120 storageClass=
"DefectsList",
121 dimensions=[
"instrument",
"calibration_label",
"detector"],
123 opticsTransmission = cT.PrerequisiteInput(
124 name=
"transmission_optics",
125 storageClass=
"TransmissionCurve",
126 doc=
"Transmission curve due to the optics.",
127 dimensions=[
"instrument",
"calibration_label"],
129 filterTransmission = cT.PrerequisiteInput(
130 name=
"transmission_filter",
131 storageClass=
"TransmissionCurve",
132 doc=
"Transmission curve due to the filter.",
133 dimensions=[
"instrument",
"physical_filter",
"calibration_label"],
135 sensorTransmission = cT.PrerequisiteInput(
136 name=
"transmission_sensor",
137 storageClass=
"TransmissionCurve",
138 doc=
"Transmission curve due to the sensor.",
139 dimensions=[
"instrument",
"calibration_label",
"detector"],
141 atmosphereTransmission = cT.PrerequisiteInput(
142 name=
"transmission_atmosphere",
143 storageClass=
"TransmissionCurve",
144 doc=
"Transmission curve due to the atmosphere.",
145 dimensions=[
"instrument"],
147 illumMaskedImage = cT.PrerequisiteInput(
149 doc=
"Input illumination correction.",
150 storageClass=
"MaskedImageF",
151 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
154 outputExposure = cT.Output(
156 doc=
"Output ISR processed exposure.",
157 storageClass=
"ExposureF",
158 dimensions=[
"instrument",
"visit",
"detector"],
160 preInterpExposure = cT.Output(
161 name=
'preInterpISRCCD',
162 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
163 storageClass=
"ExposureF",
164 dimensions=[
"instrument",
"visit",
"detector"],
166 outputOssThumbnail = cT.Output(
168 doc=
"Output Overscan-subtracted thumbnail image.",
169 storageClass=
"Thumbnail",
170 dimensions=[
"instrument",
"visit",
"detector"],
172 outputFlattenedThumbnail = cT.Output(
173 name=
"FlattenedThumb",
174 doc=
"Output flat-corrected thumbnail image.",
175 storageClass=
"Thumbnail",
176 dimensions=[
"instrument",
"visit",
"detector"],
182 if config.doBias
is not True:
183 self.prerequisiteInputs.discard(
"bias")
184 if config.doLinearize
is not True:
185 self.prerequisiteInputs.discard(
"linearizer")
186 if config.doCrosstalk
is not True:
187 self.prerequisiteInputs.discard(
"crosstalkSources")
188 if config.doBrighterFatter
is not True:
189 self.prerequisiteInputs.discard(
"bfKernel")
190 self.prerequisiteInputs.discard(
"newBFKernel")
191 if config.doDefect
is not True:
192 self.prerequisiteInputs.discard(
"defects")
193 if config.doDark
is not True:
194 self.prerequisiteInputs.discard(
"dark")
195 if config.doFlat
is not True:
196 self.prerequisiteInputs.discard(
"flat")
197 if config.doAttachTransmissionCurve
is not True:
198 self.prerequisiteInputs.discard(
"opticsTransmission")
199 self.prerequisiteInputs.discard(
"filterTransmission")
200 self.prerequisiteInputs.discard(
"sensorTransmission")
201 self.prerequisiteInputs.discard(
"atmosphereTransmission")
202 if config.doUseOpticsTransmission
is not True:
203 self.prerequisiteInputs.discard(
"opticsTransmission")
204 if config.doUseFilterTransmission
is not True:
205 self.prerequisiteInputs.discard(
"filterTransmission")
206 if config.doUseSensorTransmission
is not True:
207 self.prerequisiteInputs.discard(
"sensorTransmission")
208 if config.doUseAtmosphereTransmission
is not True:
209 self.prerequisiteInputs.discard(
"atmosphereTransmission")
210 if config.doIlluminationCorrection
is not True:
211 self.prerequisiteInputs.discard(
"illumMaskedImage")
213 if config.doWrite
is not True:
214 self.outputs.discard(
"outputExposure")
215 self.outputs.discard(
"preInterpExposure")
216 self.outputs.discard(
"outputFlattenedThumbnail")
217 self.outputs.discard(
"outputOssThumbnail")
218 if config.doSaveInterpPixels
is not True:
219 self.outputs.discard(
"preInterpExposure")
220 if config.qa.doThumbnailOss
is not True:
221 self.outputs.discard(
"outputOssThumbnail")
222 if config.qa.doThumbnailFlattened
is not True:
223 self.outputs.discard(
"outputFlattenedThumbnail")
227 pipelineConnections=IsrTaskConnections):
228 """Configuration parameters for IsrTask. 230 Items are grouped in the order in which they are executed by the task. 232 datasetType = pexConfig.Field(
234 doc=
"Dataset type for input data; users will typically leave this alone, " 235 "but camera-specific ISR tasks will override it",
239 fallbackFilterName = pexConfig.Field(
241 doc=
"Fallback default filter name for calibrations.",
244 useFallbackDate = pexConfig.Field(
246 doc=
"Pass observation date when using fallback filter.",
249 expectWcs = pexConfig.Field(
252 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 254 fwhm = pexConfig.Field(
256 doc=
"FWHM of PSF in arcseconds.",
259 qa = pexConfig.ConfigField(
261 doc=
"QA related configuration options.",
265 doConvertIntToFloat = pexConfig.Field(
267 doc=
"Convert integer raw images to floating point values?",
272 doSaturation = pexConfig.Field(
274 doc=
"Mask saturated pixels? NB: this is totally independent of the" 275 " interpolation option - this is ONLY setting the bits in the mask." 276 " To have them interpolated make sure doSaturationInterpolation=True",
279 saturatedMaskName = pexConfig.Field(
281 doc=
"Name of mask plane to use in saturation detection and interpolation",
284 saturation = pexConfig.Field(
286 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
287 default=float(
"NaN"),
289 growSaturationFootprintSize = pexConfig.Field(
291 doc=
"Number of pixels by which to grow the saturation footprints",
296 doSuspect = pexConfig.Field(
298 doc=
"Mask suspect pixels?",
301 suspectMaskName = pexConfig.Field(
303 doc=
"Name of mask plane to use for suspect pixels",
306 numEdgeSuspect = pexConfig.Field(
308 doc=
"Number of edge pixels to be flagged as untrustworthy.",
313 doSetBadRegions = pexConfig.Field(
315 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
318 badStatistic = pexConfig.ChoiceField(
320 doc=
"How to estimate the average value for BAD regions.",
323 "MEANCLIP":
"Correct using the (clipped) mean of good data",
324 "MEDIAN":
"Correct using the median of the good data",
329 doOverscan = pexConfig.Field(
331 doc=
"Do overscan subtraction?",
334 overscanFitType = pexConfig.ChoiceField(
336 doc=
"The method for fitting the overscan bias level.",
339 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
340 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
341 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
342 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
343 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
344 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
345 "MEAN":
"Correct using the mean of the overscan region",
346 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
347 "MEDIAN":
"Correct using the median of the overscan region",
348 "MEDIAN_PER_ROW":
"Correct using the median per row of the overscan region",
351 overscanOrder = pexConfig.Field(
353 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
354 "or number of spline knots if overscan fit type is a spline."),
357 overscanNumSigmaClip = pexConfig.Field(
359 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
362 overscanIsInt = pexConfig.Field(
364 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN" +
365 " and overscan.FitType=MEDIAN_PER_ROW.",
368 overscanNumLeadingColumnsToSkip = pexConfig.Field(
370 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
373 overscanNumTrailingColumnsToSkip = pexConfig.Field(
375 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
378 overscanMaxDev = pexConfig.Field(
380 doc=
"Maximum deviation from the median for overscan",
381 default=1000.0, check=
lambda x: x > 0
383 overscanBiasJump = pexConfig.Field(
385 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
388 overscanBiasJumpKeyword = pexConfig.Field(
390 doc=
"Header keyword containing information about devices.",
391 default=
"NO_SUCH_KEY",
393 overscanBiasJumpDevices = pexConfig.ListField(
395 doc=
"List of devices that need piecewise overscan correction.",
398 overscanBiasJumpLocation = pexConfig.Field(
400 doc=
"Location of bias jump along y-axis.",
405 doAssembleCcd = pexConfig.Field(
408 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 410 assembleCcd = pexConfig.ConfigurableField(
411 target=AssembleCcdTask,
412 doc=
"CCD assembly task",
416 doAssembleIsrExposures = pexConfig.Field(
419 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 421 doTrimToMatchCalib = pexConfig.Field(
424 doc=
"Trim raw data to match calibration bounding boxes?" 428 doBias = pexConfig.Field(
430 doc=
"Apply bias frame correction?",
433 biasDataProductName = pexConfig.Field(
435 doc=
"Name of the bias data product",
440 doVariance = pexConfig.Field(
442 doc=
"Calculate variance?",
445 gain = pexConfig.Field(
447 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
448 default=float(
"NaN"),
450 readNoise = pexConfig.Field(
452 doc=
"The read noise to use if no Detector is present in the Exposure",
455 doEmpiricalReadNoise = pexConfig.Field(
458 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 462 doLinearize = pexConfig.Field(
464 doc=
"Correct for nonlinearity of the detector's response?",
469 doCrosstalk = pexConfig.Field(
471 doc=
"Apply intra-CCD crosstalk correction?",
474 doCrosstalkBeforeAssemble = pexConfig.Field(
476 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
479 crosstalk = pexConfig.ConfigurableField(
480 target=CrosstalkTask,
481 doc=
"Intra-CCD crosstalk correction",
485 doDefect = pexConfig.Field(
487 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
490 doNanMasking = pexConfig.Field(
492 doc=
"Mask NAN pixels?",
495 doWidenSaturationTrails = pexConfig.Field(
497 doc=
"Widen bleed trails based on their width?",
502 doBrighterFatter = pexConfig.Field(
505 doc=
"Apply the brighter fatter correction" 507 brighterFatterLevel = pexConfig.ChoiceField(
510 doc=
"The level at which to correct for brighter-fatter.",
512 "AMP":
"Every amplifier treated separately.",
513 "DETECTOR":
"One kernel per detector",
516 brighterFatterMaxIter = pexConfig.Field(
519 doc=
"Maximum number of iterations for the brighter fatter correction" 521 brighterFatterThreshold = pexConfig.Field(
524 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 525 " absolute value of the difference between the current corrected image and the one" 526 " from the previous iteration summed over all the pixels." 528 brighterFatterApplyGain = pexConfig.Field(
531 doc=
"Should the gain be applied when applying the brighter fatter correction?" 533 brighterFatterMaskGrowSize = pexConfig.Field(
536 doc=
"Number of pixels to grow the masks listed in config.maskListToInterpolate " 537 " when brighter-fatter correction is applied." 541 doDark = pexConfig.Field(
543 doc=
"Apply dark frame correction?",
546 darkDataProductName = pexConfig.Field(
548 doc=
"Name of the dark data product",
553 doStrayLight = pexConfig.Field(
555 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
558 strayLight = pexConfig.ConfigurableField(
559 target=StrayLightTask,
560 doc=
"y-band stray light correction" 564 doFlat = pexConfig.Field(
566 doc=
"Apply flat field correction?",
569 flatDataProductName = pexConfig.Field(
571 doc=
"Name of the flat data product",
574 flatScalingType = pexConfig.ChoiceField(
576 doc=
"The method for scaling the flat on the fly.",
579 "USER":
"Scale by flatUserScale",
580 "MEAN":
"Scale by the inverse of the mean",
581 "MEDIAN":
"Scale by the inverse of the median",
584 flatUserScale = pexConfig.Field(
586 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
589 doTweakFlat = pexConfig.Field(
591 doc=
"Tweak flats to match observed amplifier ratios?",
596 doApplyGains = pexConfig.Field(
598 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
601 normalizeGains = pexConfig.Field(
603 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
608 doFringe = pexConfig.Field(
610 doc=
"Apply fringe correction?",
613 fringe = pexConfig.ConfigurableField(
615 doc=
"Fringe subtraction task",
617 fringeAfterFlat = pexConfig.Field(
619 doc=
"Do fringe subtraction after flat-fielding?",
624 doAddDistortionModel = pexConfig.Field(
626 doc=
"Apply a distortion model based on camera geometry to the WCS?",
628 deprecated=(
"Camera geometry is incorporated when reading the raw files." 629 " This option no longer is used, and will be removed after v19.")
633 doMeasureBackground = pexConfig.Field(
635 doc=
"Measure the background level on the reduced image?",
640 doCameraSpecificMasking = pexConfig.Field(
642 doc=
"Mask camera-specific bad regions?",
645 masking = pexConfig.ConfigurableField(
652 doInterpolate = pexConfig.Field(
654 doc=
"Interpolate masked pixels?",
657 doSaturationInterpolation = pexConfig.Field(
659 doc=
"Perform interpolation over pixels masked as saturated?" 660 " NB: This is independent of doSaturation; if that is False this plane" 661 " will likely be blank, resulting in a no-op here.",
664 doNanInterpolation = pexConfig.Field(
666 doc=
"Perform interpolation over pixels masked as NaN?" 667 " NB: This is independent of doNanMasking; if that is False this plane" 668 " will likely be blank, resulting in a no-op here.",
671 doNanInterpAfterFlat = pexConfig.Field(
673 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 674 "also have to interpolate them before flat-fielding."),
677 maskListToInterpolate = pexConfig.ListField(
679 doc=
"List of mask planes that should be interpolated.",
680 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
682 doSaveInterpPixels = pexConfig.Field(
684 doc=
"Save a copy of the pre-interpolated pixel values?",
689 fluxMag0T1 = pexConfig.DictField(
692 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
693 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
696 defaultFluxMag0T1 = pexConfig.Field(
698 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
699 default=pow(10.0, 0.4*28.0)
703 doVignette = pexConfig.Field(
705 doc=
"Apply vignetting parameters?",
708 vignette = pexConfig.ConfigurableField(
710 doc=
"Vignetting task.",
714 doAttachTransmissionCurve = pexConfig.Field(
717 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 719 doUseOpticsTransmission = pexConfig.Field(
722 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 724 doUseFilterTransmission = pexConfig.Field(
727 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 729 doUseSensorTransmission = pexConfig.Field(
732 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 734 doUseAtmosphereTransmission = pexConfig.Field(
737 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 741 doIlluminationCorrection = pexConfig.Field(
744 doc=
"Perform illumination correction?" 746 illuminationCorrectionDataProductName = pexConfig.Field(
748 doc=
"Name of the illumination correction data product.",
751 illumScale = pexConfig.Field(
753 doc=
"Scale factor for the illumination correction.",
756 illumFilters = pexConfig.ListField(
759 doc=
"Only perform illumination correction for these filters." 763 doWrite = pexConfig.Field(
765 doc=
"Persist postISRCCD?",
772 raise ValueError(
"You may not specify both doFlat and doApplyGains")
774 self.config.maskListToInterpolate.append(
"SAT")
776 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
779 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
780 """Apply common instrument signature correction algorithms to a raw frame. 782 The process for correcting imaging data is very similar from 783 camera to camera. This task provides a vanilla implementation of 784 doing these corrections, including the ability to turn certain 785 corrections off if they are not needed. The inputs to the primary 786 method, `run()`, are a raw exposure to be corrected and the 787 calibration data products. The raw input is a single chip sized 788 mosaic of all amps including overscans and other non-science 789 pixels. The method `runDataRef()` identifies and defines the 790 calibration data products, and is intended for use by a 791 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 792 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 793 subclassed for different camera, although the most camera specific 794 methods have been split into subtasks that can be redirected 797 The __init__ method sets up the subtasks for ISR processing, using 798 the defaults from `lsst.ip.isr`. 803 Positional arguments passed to the Task constructor. None used at this time. 804 kwargs : `dict`, optional 805 Keyword arguments passed on to the Task constructor. None used at this time. 807 ConfigClass = IsrTaskConfig
812 self.makeSubtask(
"assembleCcd")
813 self.makeSubtask(
"crosstalk")
814 self.makeSubtask(
"strayLight")
815 self.makeSubtask(
"fringe")
816 self.makeSubtask(
"masking")
817 self.makeSubtask(
"vignette")
820 inputs = butlerQC.get(inputRefs)
823 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
824 except Exception
as e:
825 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
828 inputs[
'isGen3'] =
True 830 detector = inputs[
'ccdExposure'].getDetector()
832 if 'linearizer' not in inputs:
833 linearityName = detector.getAmplifiers()[0].getLinearityType()
834 inputs[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
836 if self.config.doDefect
is True:
837 if "defects" in inputs
and inputs[
'defects']
is not None:
840 if not isinstance(inputs[
"defects"], Defects):
841 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
845 if self.config.doBrighterFatter:
846 brighterFatterKernel = inputs.pop(
'newBFKernel',
None)
847 if brighterFatterKernel
is None:
848 brighterFatterKernel = inputs.get(
'bfKernel',
None)
850 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
851 detId = detector.getId()
852 inputs[
'bfGains'] = brighterFatterKernel.gain
855 if self.config.brighterFatterLevel ==
'DETECTOR':
856 if brighterFatterKernel.detectorKernel:
857 inputs[
'bfKernel'] = brighterFatterKernel.detectorKernel[detId]
858 elif brighterFatterKernel.detectorKernelFromAmpKernels:
859 inputs[
'bfKernel'] = brighterFatterKernel.detectorKernelFromAmpKernels[detId]
861 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
864 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
872 if self.config.doFringe
is True and self.fringe.checkFilter(inputs[
'ccdExposure']):
873 expId = inputs[
'ccdExposure'].getInfo().getVisitInfo().getExposureId()
874 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
876 assembler=self.assembleCcd
877 if self.config.doAssembleIsrExposures
else None)
879 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
881 if self.config.doStrayLight
is True and self.strayLight.checkFilter(inputs[
'ccdExposure']):
882 if 'strayLightData' not in inputs:
883 inputs[
'strayLightData'] =
None 885 outputs = self.
run(**inputs)
886 butlerQC.put(outputs, outputRefs)
889 """!Retrieve necessary frames for instrument signature removal. 891 Pre-fetching all required ISR data products limits the IO 892 required by the ISR. Any conflict between the calibration data 893 available and that needed for ISR is also detected prior to 894 doing processing, allowing it to fail quickly. 898 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 899 Butler reference of the detector data to be processed 900 rawExposure : `afw.image.Exposure` 901 The raw exposure that will later be corrected with the 902 retrieved calibration data; should not be modified in this 907 result : `lsst.pipe.base.Struct` 908 Result struct with components (which may be `None`): 909 - ``bias``: bias calibration frame (`afw.image.Exposure`) 910 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 911 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 912 - ``dark``: dark calibration frame (`afw.image.Exposure`) 913 - ``flat``: flat calibration frame (`afw.image.Exposure`) 914 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 915 - ``defects``: list of defects (`lsst.meas.algorithms.Defects`) 916 - ``fringes``: `lsst.pipe.base.Struct` with components: 917 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 918 - ``seed``: random seed derived from the ccdExposureId for random 919 number generator (`uint32`). 920 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 921 A ``TransmissionCurve`` that represents the throughput of the optics, 922 to be evaluated in focal-plane coordinates. 923 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 924 A ``TransmissionCurve`` that represents the throughput of the filter 925 itself, to be evaluated in focal-plane coordinates. 926 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 927 A ``TransmissionCurve`` that represents the throughput of the sensor 928 itself, to be evaluated in post-assembly trimmed detector coordinates. 929 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 930 A ``TransmissionCurve`` that represents the throughput of the 931 atmosphere, assumed to be spatially constant. 932 - ``strayLightData`` : `object` 933 An opaque object containing calibration information for 934 stray-light correction. If `None`, no correction will be 936 - ``illumMaskedImage`` : illumination correction image (`lsst.afw.image.MaskedImage`) 940 NotImplementedError : 941 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 944 dateObs = rawExposure.getInfo().getVisitInfo().getDate()
945 dateObs = dateObs.toPython().isoformat()
947 self.log.warn(
"Unable to identify dateObs for rawExposure.")
950 ccd = rawExposure.getDetector()
951 filterName = afwImage.Filter(rawExposure.getFilter().getId()).getName()
952 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
953 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
954 if self.config.doBias
else None)
956 linearizer = (dataRef.get(
"linearizer", immediate=
True)
958 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
959 if self.config.doCrosstalk
else None)
960 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
961 if self.config.doDark
else None)
962 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName,
964 if self.config.doFlat
else None)
966 brighterFatterKernel =
None 967 brighterFatterGains =
None 968 if self.config.doBrighterFatter
is True:
973 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
974 brighterFatterGains = brighterFatterKernel.gain
975 self.log.info(
"New style bright-fatter kernel (brighterFatterKernel) loaded")
978 brighterFatterKernel = dataRef.get(
"bfKernel")
979 self.log.info(
"Old style bright-fatter kernel (np.array) loaded")
981 brighterFatterKernel =
None 982 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
985 if self.config.brighterFatterLevel ==
'DETECTOR':
986 if brighterFatterKernel.detectorKernel:
987 brighterFatterKernel = brighterFatterKernel.detectorKernel[ccd.getId()]
988 elif brighterFatterKernel.detectorKernelFromAmpKernels:
989 brighterFatterKernel = brighterFatterKernel.detectorKernelFromAmpKernels[ccd.getId()]
991 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
994 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
996 defectList = (dataRef.get(
"defects")
997 if self.config.doDefect
else None)
998 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
999 if self.config.doAssembleIsrExposures
else None)
1000 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
1001 else pipeBase.Struct(fringes=
None))
1003 if self.config.doAttachTransmissionCurve:
1004 opticsTransmission = (dataRef.get(
"transmission_optics")
1005 if self.config.doUseOpticsTransmission
else None)
1006 filterTransmission = (dataRef.get(
"transmission_filter")
1007 if self.config.doUseFilterTransmission
else None)
1008 sensorTransmission = (dataRef.get(
"transmission_sensor")
1009 if self.config.doUseSensorTransmission
else None)
1010 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
1011 if self.config.doUseAtmosphereTransmission
else None)
1013 opticsTransmission =
None 1014 filterTransmission =
None 1015 sensorTransmission =
None 1016 atmosphereTransmission =
None 1018 if self.config.doStrayLight:
1019 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
1021 strayLightData =
None 1024 self.config.illuminationCorrectionDataProductName).getMaskedImage()
1025 if (self.config.doIlluminationCorrection
and 1026 filterName
in self.config.illumFilters)
1030 return pipeBase.Struct(bias=biasExposure,
1031 linearizer=linearizer,
1032 crosstalkSources=crosstalkSources,
1035 bfKernel=brighterFatterKernel,
1036 bfGains=brighterFatterGains,
1038 fringes=fringeStruct,
1039 opticsTransmission=opticsTransmission,
1040 filterTransmission=filterTransmission,
1041 sensorTransmission=sensorTransmission,
1042 atmosphereTransmission=atmosphereTransmission,
1043 strayLightData=strayLightData,
1044 illumMaskedImage=illumMaskedImage
1047 @pipeBase.timeMethod
1048 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
1049 dark=None, flat=None, bfKernel=None, bfGains=None, defects=None,
1050 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1051 sensorTransmission=
None, atmosphereTransmission=
None,
1052 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1055 """!Perform instrument signature removal on an exposure. 1057 Steps included in the ISR processing, in order performed, are: 1058 - saturation and suspect pixel masking 1059 - overscan subtraction 1060 - CCD assembly of individual amplifiers 1062 - variance image construction 1063 - linearization of non-linear response 1065 - brighter-fatter correction 1068 - stray light subtraction 1070 - masking of known defects and camera specific features 1071 - vignette calculation 1072 - appending transmission curve and distortion model 1076 ccdExposure : `lsst.afw.image.Exposure` 1077 The raw exposure that is to be run through ISR. The 1078 exposure is modified by this method. 1079 camera : `lsst.afw.cameraGeom.Camera`, optional 1080 The camera geometry for this exposure. Used to select the 1081 distortion model appropriate for this data. 1082 bias : `lsst.afw.image.Exposure`, optional 1083 Bias calibration frame. 1084 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1085 Functor for linearization. 1086 crosstalkSources : `list`, optional 1087 List of possible crosstalk sources. 1088 dark : `lsst.afw.image.Exposure`, optional 1089 Dark calibration frame. 1090 flat : `lsst.afw.image.Exposure`, optional 1091 Flat calibration frame. 1092 bfKernel : `numpy.ndarray`, optional 1093 Brighter-fatter kernel. 1094 bfGains : `dict` of `float`, optional 1095 Gains used to override the detector's nominal gains for the 1096 brighter-fatter correction. A dict keyed by amplifier name for 1097 the detector in question. 1098 defects : `lsst.meas.algorithms.Defects`, optional 1100 fringes : `lsst.pipe.base.Struct`, optional 1101 Struct containing the fringe correction data, with 1103 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1104 - ``seed``: random seed derived from the ccdExposureId for random 1105 number generator (`uint32`) 1106 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1107 A ``TransmissionCurve`` that represents the throughput of the optics, 1108 to be evaluated in focal-plane coordinates. 1109 filterTransmission : `lsst.afw.image.TransmissionCurve` 1110 A ``TransmissionCurve`` that represents the throughput of the filter 1111 itself, to be evaluated in focal-plane coordinates. 1112 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1113 A ``TransmissionCurve`` that represents the throughput of the sensor 1114 itself, to be evaluated in post-assembly trimmed detector coordinates. 1115 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1116 A ``TransmissionCurve`` that represents the throughput of the 1117 atmosphere, assumed to be spatially constant. 1118 detectorNum : `int`, optional 1119 The integer number for the detector to process. 1120 isGen3 : bool, optional 1121 Flag this call to run() as using the Gen3 butler environment. 1122 strayLightData : `object`, optional 1123 Opaque object containing calibration information for stray-light 1124 correction. If `None`, no correction will be performed. 1125 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional 1126 Illumination correction image. 1130 result : `lsst.pipe.base.Struct` 1131 Result struct with component: 1132 - ``exposure`` : `afw.image.Exposure` 1133 The fully ISR corrected exposure. 1134 - ``outputExposure`` : `afw.image.Exposure` 1135 An alias for `exposure` 1136 - ``ossThumb`` : `numpy.ndarray` 1137 Thumbnail image of the exposure after overscan subtraction. 1138 - ``flattenedThumb`` : `numpy.ndarray` 1139 Thumbnail image of the exposure after flat-field correction. 1144 Raised if a configuration option is set to True, but the 1145 required calibration data has not been specified. 1149 The current processed exposure can be viewed by setting the 1150 appropriate lsstDebug entries in the `debug.display` 1151 dictionary. The names of these entries correspond to some of 1152 the IsrTaskConfig Boolean options, with the value denoting the 1153 frame to use. The exposure is shown inside the matching 1154 option check and after the processing of that step has 1155 finished. The steps with debug points are: 1166 In addition, setting the "postISRCCD" entry displays the 1167 exposure after all ISR processing has finished. 1175 if detectorNum
is None:
1176 raise RuntimeError(
"Must supply the detectorNum if running as Gen3.")
1178 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1183 if isinstance(ccdExposure, ButlerDataRef):
1186 ccd = ccdExposure.getDetector()
1187 filterName = afwImage.Filter(ccdExposure.getFilter().getId()).getName()
1190 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd." 1191 ccd = [
FakeAmp(ccdExposure, self.config)]
1194 if self.config.doBias
and bias
is None:
1195 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1197 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1198 if self.config.doBrighterFatter
and bfKernel
is None:
1199 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1200 if self.config.doDark
and dark
is None:
1201 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1202 if self.config.doFlat
and flat
is None:
1203 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1204 if self.config.doDefect
and defects
is None:
1205 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1206 if (self.config.doFringe
and filterName
in self.fringe.config.filters
and 1207 fringes.fringes
is None):
1212 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1213 if (self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters
and 1214 illumMaskedImage
is None):
1215 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1218 if self.config.doConvertIntToFloat:
1219 self.log.info(
"Converting exposure to floating point values.")
1226 if ccdExposure.getBBox().contains(amp.getBBox()):
1230 if self.config.doOverscan
and not badAmp:
1233 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1234 if overscanResults
is not None and \
1235 self.config.qa
is not None and self.config.qa.saveStats
is True:
1236 if isinstance(overscanResults.overscanFit, float):
1237 qaMedian = overscanResults.overscanFit
1238 qaStdev = float(
"NaN")
1240 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1241 afwMath.MEDIAN | afwMath.STDEVCLIP)
1242 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1243 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1245 self.metadata.set(f
"ISR OSCAN {amp.getName()} MEDIAN", qaMedian)
1246 self.metadata.set(f
"ISR OSCAN {amp.getName()} STDEV", qaStdev)
1247 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1248 amp.getName(), qaMedian, qaStdev)
1249 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1252 self.log.warn(
"Amplifier %s is bad.", amp.getName())
1253 overscanResults =
None 1255 overscans.append(overscanResults
if overscanResults
is not None else None)
1257 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1259 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1260 self.log.info(
"Applying crosstalk correction.")
1261 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1262 self.
debugView(ccdExposure,
"doCrosstalk")
1264 if self.config.doAssembleCcd:
1265 self.log.info(
"Assembling CCD from amplifiers.")
1266 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1268 if self.config.expectWcs
and not ccdExposure.getWcs():
1269 self.log.warn(
"No WCS found in input exposure.")
1270 self.
debugView(ccdExposure,
"doAssembleCcd")
1273 if self.config.qa.doThumbnailOss:
1274 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1276 if self.config.doBias:
1277 self.log.info(
"Applying bias correction.")
1278 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1279 trimToFit=self.config.doTrimToMatchCalib)
1282 if self.config.doVariance:
1283 for amp, overscanResults
in zip(ccd, overscans):
1284 if ccdExposure.getBBox().contains(amp.getBBox()):
1285 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1286 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1287 if overscanResults
is not None:
1289 overscanImage=overscanResults.overscanImage)
1293 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1294 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1295 afwMath.MEDIAN | afwMath.STDEVCLIP)
1296 self.metadata.set(f
"ISR VARIANCE {amp.getName()} MEDIAN",
1297 qaStats.getValue(afwMath.MEDIAN))
1298 self.metadata.set(f
"ISR VARIANCE {amp.getName()} STDEV",
1299 qaStats.getValue(afwMath.STDEVCLIP))
1300 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1301 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1302 qaStats.getValue(afwMath.STDEVCLIP))
1305 self.log.info(
"Applying linearizer.")
1306 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1308 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1309 self.log.info(
"Applying crosstalk correction.")
1310 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1311 self.
debugView(ccdExposure,
"doCrosstalk")
1315 if self.config.doDefect:
1316 self.log.info(
"Masking defects.")
1319 if self.config.numEdgeSuspect > 0:
1320 self.log.info(
"Masking edges as SUSPECT.")
1321 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1322 maskPlane=
"SUSPECT")
1324 if self.config.doNanMasking:
1325 self.log.info(
"Masking NAN value pixels.")
1328 if self.config.doWidenSaturationTrails:
1329 self.log.info(
"Widening saturation trails.")
1330 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1332 if self.config.doCameraSpecificMasking:
1333 self.log.info(
"Masking regions for camera specific reasons.")
1334 self.masking.
run(ccdExposure)
1336 if self.config.doBrighterFatter:
1345 interpExp = ccdExposure.clone()
1347 isrFunctions.interpolateFromMask(
1348 maskedImage=interpExp.getMaskedImage(),
1349 fwhm=self.config.fwhm,
1350 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1351 maskNameList=self.config.maskListToInterpolate
1353 bfExp = interpExp.clone()
1355 self.log.info(
"Applying brighter fatter correction using kernel type %s / gains %s.",
1356 type(bfKernel), type(bfGains))
1357 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1358 self.config.brighterFatterMaxIter,
1359 self.config.brighterFatterThreshold,
1360 self.config.brighterFatterApplyGain,
1362 if bfResults[1] == self.config.brighterFatterMaxIter:
1363 self.log.warn(
"Brighter fatter correction did not converge, final difference %f.",
1366 self.log.info(
"Finished brighter fatter correction in %d iterations.",
1368 image = ccdExposure.getMaskedImage().getImage()
1369 bfCorr = bfExp.getMaskedImage().getImage()
1370 bfCorr -= interpExp.getMaskedImage().getImage()
1379 self.log.info(
"Ensuring image edges are masked as SUSPECT to the brighter-fatter kernel size.")
1380 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1383 if self.config.brighterFatterMaskGrowSize > 0:
1384 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1385 for maskPlane
in self.config.maskListToInterpolate:
1386 isrFunctions.growMasks(ccdExposure.getMask(),
1387 radius=self.config.brighterFatterMaskGrowSize,
1388 maskNameList=maskPlane,
1389 maskValue=maskPlane)
1391 self.
debugView(ccdExposure,
"doBrighterFatter")
1393 if self.config.doDark:
1394 self.log.info(
"Applying dark correction.")
1398 if self.config.doFringe
and not self.config.fringeAfterFlat:
1399 self.log.info(
"Applying fringe correction before flat.")
1400 self.fringe.
run(ccdExposure, **fringes.getDict())
1403 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1404 self.log.info(
"Checking strayLight correction.")
1405 self.strayLight.
run(ccdExposure, strayLightData)
1406 self.
debugView(ccdExposure,
"doStrayLight")
1408 if self.config.doFlat:
1409 self.log.info(
"Applying flat correction.")
1413 if self.config.doApplyGains:
1414 self.log.info(
"Applying gain correction instead of flat.")
1415 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1417 if self.config.doFringe
and self.config.fringeAfterFlat:
1418 self.log.info(
"Applying fringe correction after flat.")
1419 self.fringe.
run(ccdExposure, **fringes.getDict())
1421 if self.config.doVignette:
1422 self.log.info(
"Constructing Vignette polygon.")
1425 if self.config.vignette.doWriteVignettePolygon:
1428 if self.config.doAttachTransmissionCurve:
1429 self.log.info(
"Adding transmission curves.")
1430 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1431 filterTransmission=filterTransmission,
1432 sensorTransmission=sensorTransmission,
1433 atmosphereTransmission=atmosphereTransmission)
1435 flattenedThumb =
None 1436 if self.config.qa.doThumbnailFlattened:
1437 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1439 if self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters:
1440 self.log.info(
"Performing illumination correction.")
1441 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1442 illumMaskedImage, illumScale=self.config.illumScale,
1443 trimToFit=self.config.doTrimToMatchCalib)
1446 if self.config.doSaveInterpPixels:
1447 preInterpExp = ccdExposure.clone()
1462 if self.config.doSetBadRegions:
1463 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1464 if badPixelCount > 0:
1465 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1467 if self.config.doInterpolate:
1468 self.log.info(
"Interpolating masked pixels.")
1469 isrFunctions.interpolateFromMask(
1470 maskedImage=ccdExposure.getMaskedImage(),
1471 fwhm=self.config.fwhm,
1472 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1473 maskNameList=list(self.config.maskListToInterpolate)
1478 if self.config.doMeasureBackground:
1479 self.log.info(
"Measuring background level.")
1482 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1484 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1485 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1486 afwMath.MEDIAN | afwMath.STDEVCLIP)
1487 self.metadata.set(
"ISR BACKGROUND {} MEDIAN".format(amp.getName()),
1488 qaStats.getValue(afwMath.MEDIAN))
1489 self.metadata.set(
"ISR BACKGROUND {} STDEV".format(amp.getName()),
1490 qaStats.getValue(afwMath.STDEVCLIP))
1491 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1492 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1493 qaStats.getValue(afwMath.STDEVCLIP))
1495 self.
debugView(ccdExposure,
"postISRCCD")
1497 return pipeBase.Struct(
1498 exposure=ccdExposure,
1500 flattenedThumb=flattenedThumb,
1502 preInterpolatedExposure=preInterpExp,
1503 outputExposure=ccdExposure,
1504 outputOssThumbnail=ossThumb,
1505 outputFlattenedThumbnail=flattenedThumb,
1508 @pipeBase.timeMethod
1510 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1512 This method contains the `CmdLineTask` interface to the ISR 1513 processing. All IO is handled here, freeing the `run()` method 1514 to manage only pixel-level calculations. The steps performed 1516 - Read in necessary detrending/isr/calibration data. 1517 - Process raw exposure in `run()`. 1518 - Persist the ISR-corrected exposure as "postISRCCD" if 1519 config.doWrite=True. 1523 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1524 DataRef of the detector data to be processed 1528 result : `lsst.pipe.base.Struct` 1529 Result struct with component: 1530 - ``exposure`` : `afw.image.Exposure` 1531 The fully ISR corrected exposure. 1536 Raised if a configuration option is set to True, but the 1537 required calibration data does not exist. 1540 self.log.info(
"Performing ISR on sensor %s.", sensorRef.dataId)
1542 ccdExposure = sensorRef.get(self.config.datasetType)
1544 camera = sensorRef.get(
"camera")
1545 isrData = self.
readIsrData(sensorRef, ccdExposure)
1547 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1549 if self.config.doWrite:
1550 sensorRef.put(result.exposure,
"postISRCCD")
1551 if result.preInterpolatedExposure
is not None:
1552 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1553 if result.ossThumb
is not None:
1554 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1555 if result.flattenedThumb
is not None:
1556 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1561 """!Retrieve a calibration dataset for removing instrument signature. 1566 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1567 DataRef of the detector data to find calibration datasets 1570 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1571 dateObs : `str`, optional 1572 Date of the observation. Used to correct butler failures 1573 when using fallback filters. 1575 If True, disable butler proxies to enable error handling 1576 within this routine. 1580 exposure : `lsst.afw.image.Exposure` 1581 Requested calibration frame. 1586 Raised if no matching calibration frame can be found. 1589 exp = dataRef.get(datasetType, immediate=immediate)
1590 except Exception
as exc1:
1591 if not self.config.fallbackFilterName:
1592 raise RuntimeError(
"Unable to retrieve %s for %s: %s." % (datasetType, dataRef.dataId, exc1))
1594 if self.config.useFallbackDate
and dateObs:
1595 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName,
1596 dateObs=dateObs, immediate=immediate)
1598 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1599 except Exception
as exc2:
1600 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s." %
1601 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1602 self.log.warn(
"Using fallback calibration from filter %s.", self.config.fallbackFilterName)
1604 if self.config.doAssembleIsrExposures:
1605 exp = self.assembleCcd.assembleCcd(exp)
1609 """Ensure that the data returned by Butler is a fully constructed exposure. 1611 ISR requires exposure-level image data for historical reasons, so if we did 1612 not recieve that from Butler, construct it from what we have, modifying the 1617 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1618 `lsst.afw.image.ImageF` 1619 The input data structure obtained from Butler. 1620 camera : `lsst.afw.cameraGeom.camera` 1621 The camera associated with the image. Used to find the appropriate 1624 The detector this exposure should match. 1628 inputExp : `lsst.afw.image.Exposure` 1629 The re-constructed exposure, with appropriate detector parameters. 1634 Raised if the input data cannot be used to construct an exposure. 1636 if isinstance(inputExp, afwImage.DecoratedImageU):
1637 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1638 elif isinstance(inputExp, afwImage.ImageF):
1639 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1640 elif isinstance(inputExp, afwImage.MaskedImageF):
1641 inputExp = afwImage.makeExposure(inputExp)
1642 elif isinstance(inputExp, afwImage.Exposure):
1644 elif inputExp
is None:
1648 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1651 if inputExp.getDetector()
is None:
1652 inputExp.setDetector(camera[detectorNum])
1657 """Convert exposure image from uint16 to float. 1659 If the exposure does not need to be converted, the input is 1660 immediately returned. For exposures that are converted to use 1661 floating point pixels, the variance is set to unity and the 1666 exposure : `lsst.afw.image.Exposure` 1667 The raw exposure to be converted. 1671 newexposure : `lsst.afw.image.Exposure` 1672 The input ``exposure``, converted to floating point pixels. 1677 Raised if the exposure type cannot be converted to float. 1680 if isinstance(exposure, afwImage.ExposureF):
1682 self.log.debug(
"Exposure already of type float.")
1684 if not hasattr(exposure,
"convertF"):
1685 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1687 newexposure = exposure.convertF()
1688 newexposure.variance[:] = 1
1689 newexposure.mask[:] = 0x0
1694 """Identify bad amplifiers, saturated and suspect pixels. 1698 ccdExposure : `lsst.afw.image.Exposure` 1699 Input exposure to be masked. 1700 amp : `lsst.afw.table.AmpInfoCatalog` 1701 Catalog of parameters defining the amplifier on this 1703 defects : `lsst.meas.algorithms.Defects` 1704 List of defects. Used to determine if the entire 1710 If this is true, the entire amplifier area is covered by 1711 defects and unusable. 1714 maskedImage = ccdExposure.getMaskedImage()
1720 if defects
is not None:
1721 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1726 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1728 maskView = dataView.getMask()
1729 maskView |= maskView.getPlaneBitMask(
"BAD")
1736 if self.config.doSaturation
and not badAmp:
1737 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1738 if self.config.doSuspect
and not badAmp:
1739 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1740 if math.isfinite(self.config.saturation):
1741 limits.update({self.config.saturatedMaskName: self.config.saturation})
1743 for maskName, maskThreshold
in limits.items():
1744 if not math.isnan(maskThreshold):
1745 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1746 isrFunctions.makeThresholdMask(
1747 maskedImage=dataView,
1748 threshold=maskThreshold,
1754 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1756 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1757 self.config.suspectMaskName])
1758 if numpy.all(maskView.getArray() & maskVal > 0):
1760 maskView |= maskView.getPlaneBitMask(
"BAD")
1765 """Apply overscan correction in place. 1767 This method does initial pixel rejection of the overscan 1768 region. The overscan can also be optionally segmented to 1769 allow for discontinuous overscan responses to be fit 1770 separately. The actual overscan subtraction is performed by 1771 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1772 which is called here after the amplifier is preprocessed. 1776 ccdExposure : `lsst.afw.image.Exposure` 1777 Exposure to have overscan correction performed. 1778 amp : `lsst.afw.table.AmpInfoCatalog` 1779 The amplifier to consider while correcting the overscan. 1783 overscanResults : `lsst.pipe.base.Struct` 1784 Result struct with components: 1785 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1786 Value or fit subtracted from the amplifier image data. 1787 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1788 Value or fit subtracted from the overscan image data. 1789 - ``overscanImage`` : `lsst.afw.image.Image` 1790 Image of the overscan region with the overscan 1791 correction applied. This quantity is used to estimate 1792 the amplifier read noise empirically. 1797 Raised if the ``amp`` does not contain raw pixel information. 1801 lsst.ip.isr.isrFunctions.overscanCorrection 1803 if not amp.getHasRawInfo():
1804 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1806 if amp.getRawHorizontalOverscanBBox().isEmpty():
1807 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1810 statControl = afwMath.StatisticsControl()
1811 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1814 dataBBox = amp.getRawDataBBox()
1815 oscanBBox = amp.getRawHorizontalOverscanBBox()
1819 prescanBBox = amp.getRawPrescanBBox()
1820 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1821 dx0 += self.config.overscanNumLeadingColumnsToSkip
1822 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1824 dx0 += self.config.overscanNumTrailingColumnsToSkip
1825 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1831 if ((self.config.overscanBiasJump
and 1832 self.config.overscanBiasJumpLocation)
and 1833 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1834 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1835 self.config.overscanBiasJumpDevices)):
1836 if amp.getReadoutCorner()
in (ReadoutCorner.LL, ReadoutCorner.LR):
1837 yLower = self.config.overscanBiasJumpLocation
1838 yUpper = dataBBox.getHeight() - yLower
1840 yUpper = self.config.overscanBiasJumpLocation
1841 yLower = dataBBox.getHeight() - yUpper
1860 oscanBBox.getHeight())))
1863 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1864 ampImage = ccdExposure.maskedImage[imageBBox]
1865 overscanImage = ccdExposure.maskedImage[overscanBBox]
1867 overscanArray = overscanImage.image.array
1868 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1869 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1870 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1872 statControl = afwMath.StatisticsControl()
1873 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1875 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1876 overscanImage=overscanImage,
1877 fitType=self.config.overscanFitType,
1878 order=self.config.overscanOrder,
1879 collapseRej=self.config.overscanNumSigmaClip,
1880 statControl=statControl,
1881 overscanIsInt=self.config.overscanIsInt
1885 levelStat = afwMath.MEDIAN
1886 sigmaStat = afwMath.STDEVCLIP
1888 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1889 self.config.qa.flatness.nIter)
1890 metadata = ccdExposure.getMetadata()
1891 ampNum = amp.getName()
1892 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1893 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1894 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1896 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1897 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1898 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1900 return overscanResults
1903 """Set the variance plane using the amplifier gain and read noise 1905 The read noise is calculated from the ``overscanImage`` if the 1906 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1907 the value from the amplifier data is used. 1911 ampExposure : `lsst.afw.image.Exposure` 1912 Exposure to process. 1913 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1914 Amplifier detector data. 1915 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1916 Image of overscan, required only for empirical read noise. 1920 lsst.ip.isr.isrFunctions.updateVariance 1922 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1923 gain = amp.getGain()
1925 if math.isnan(gain):
1927 self.log.warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1930 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f.",
1931 amp.getName(), gain, patchedGain)
1934 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1935 self.log.info(
"Overscan is none for EmpiricalReadNoise.")
1937 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1938 stats = afwMath.StatisticsControl()
1939 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1940 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
1941 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
1942 amp.getName(), readNoise)
1944 readNoise = amp.getReadNoise()
1946 isrFunctions.updateVariance(
1947 maskedImage=ampExposure.getMaskedImage(),
1949 readNoise=readNoise,
1953 """!Apply dark correction in place. 1957 exposure : `lsst.afw.image.Exposure` 1958 Exposure to process. 1959 darkExposure : `lsst.afw.image.Exposure` 1960 Dark exposure of the same size as ``exposure``. 1961 invert : `Bool`, optional 1962 If True, re-add the dark to an already corrected image. 1967 Raised if either ``exposure`` or ``darkExposure`` do not 1968 have their dark time defined. 1972 lsst.ip.isr.isrFunctions.darkCorrection 1974 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1975 if math.isnan(expScale):
1976 raise RuntimeError(
"Exposure darktime is NAN.")
1977 if darkExposure.getInfo().getVisitInfo()
is not None:
1978 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1982 self.log.warn(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
1985 if math.isnan(darkScale):
1986 raise RuntimeError(
"Dark calib darktime is NAN.")
1987 isrFunctions.darkCorrection(
1988 maskedImage=exposure.getMaskedImage(),
1989 darkMaskedImage=darkExposure.getMaskedImage(),
1991 darkScale=darkScale,
1993 trimToFit=self.config.doTrimToMatchCalib
1997 """!Check if linearization is needed for the detector cameraGeom. 1999 Checks config.doLinearize and the linearity type of the first 2004 detector : `lsst.afw.cameraGeom.Detector` 2005 Detector to get linearity type from. 2009 doLinearize : `Bool` 2010 If True, linearization should be performed. 2012 return self.config.doLinearize
and \
2013 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
2016 """!Apply flat correction in place. 2020 exposure : `lsst.afw.image.Exposure` 2021 Exposure to process. 2022 flatExposure : `lsst.afw.image.Exposure` 2023 Flat exposure of the same size as ``exposure``. 2024 invert : `Bool`, optional 2025 If True, unflatten an already flattened image. 2029 lsst.ip.isr.isrFunctions.flatCorrection 2031 isrFunctions.flatCorrection(
2032 maskedImage=exposure.getMaskedImage(),
2033 flatMaskedImage=flatExposure.getMaskedImage(),
2034 scalingType=self.config.flatScalingType,
2035 userScale=self.config.flatUserScale,
2037 trimToFit=self.config.doTrimToMatchCalib
2041 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 2045 exposure : `lsst.afw.image.Exposure` 2046 Exposure to process. Only the amplifier DataSec is processed. 2047 amp : `lsst.afw.table.AmpInfoCatalog` 2048 Amplifier detector data. 2052 lsst.ip.isr.isrFunctions.makeThresholdMask 2054 if not math.isnan(amp.getSaturation()):
2055 maskedImage = exposure.getMaskedImage()
2056 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2057 isrFunctions.makeThresholdMask(
2058 maskedImage=dataView,
2059 threshold=amp.getSaturation(),
2061 maskName=self.config.saturatedMaskName,
2065 """!Interpolate over saturated pixels, in place. 2067 This method should be called after `saturationDetection`, to 2068 ensure that the saturated pixels have been identified in the 2069 SAT mask. It should also be called after `assembleCcd`, since 2070 saturated regions may cross amplifier boundaries. 2074 exposure : `lsst.afw.image.Exposure` 2075 Exposure to process. 2079 lsst.ip.isr.isrTask.saturationDetection 2080 lsst.ip.isr.isrFunctions.interpolateFromMask 2082 isrFunctions.interpolateFromMask(
2083 maskedImage=exposure.getMaskedImage(),
2084 fwhm=self.config.fwhm,
2085 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2086 maskNameList=list(self.config.saturatedMaskName),
2090 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 2094 exposure : `lsst.afw.image.Exposure` 2095 Exposure to process. Only the amplifier DataSec is processed. 2096 amp : `lsst.afw.table.AmpInfoCatalog` 2097 Amplifier detector data. 2101 lsst.ip.isr.isrFunctions.makeThresholdMask 2105 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 2106 This is intended to indicate pixels that may be affected by unknown systematics; 2107 for example if non-linearity corrections above a certain level are unstable 2108 then that would be a useful value for suspectLevel. A value of `nan` indicates 2109 that no such level exists and no pixels are to be masked as suspicious. 2111 suspectLevel = amp.getSuspectLevel()
2112 if math.isnan(suspectLevel):
2115 maskedImage = exposure.getMaskedImage()
2116 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2117 isrFunctions.makeThresholdMask(
2118 maskedImage=dataView,
2119 threshold=suspectLevel,
2121 maskName=self.config.suspectMaskName,
2125 """!Mask defects using mask plane "BAD", in place. 2129 exposure : `lsst.afw.image.Exposure` 2130 Exposure to process. 2131 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2132 `lsst.afw.image.DefectBase`. 2133 List of defects to mask. 2137 Call this after CCD assembly, since defects may cross amplifier boundaries. 2139 maskedImage = exposure.getMaskedImage()
2140 if not isinstance(defectBaseList, Defects):
2142 defectList = Defects(defectBaseList)
2144 defectList = defectBaseList
2145 defectList.maskPixels(maskedImage, maskName=
"BAD")
2147 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT"):
2148 """!Mask edge pixels with applicable mask plane. 2152 exposure : `lsst.afw.image.Exposure` 2153 Exposure to process. 2154 numEdgePixels : `int`, optional 2155 Number of edge pixels to mask. 2156 maskPlane : `str`, optional 2157 Mask plane name to use. 2159 maskedImage = exposure.getMaskedImage()
2160 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2162 if numEdgePixels > 0:
2163 goodBBox = maskedImage.getBBox()
2165 goodBBox.grow(-numEdgePixels)
2167 SourceDetectionTask.setEdgeBits(
2174 """Mask and interpolate defects using mask plane "BAD", in place. 2178 exposure : `lsst.afw.image.Exposure` 2179 Exposure to process. 2180 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2181 `lsst.afw.image.DefectBase`. 2182 List of defects to mask and interpolate. 2186 lsst.ip.isr.isrTask.maskDefect() 2189 self.
maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2190 maskPlane=
"SUSPECT")
2191 isrFunctions.interpolateFromMask(
2192 maskedImage=exposure.getMaskedImage(),
2193 fwhm=self.config.fwhm,
2194 growSaturatedFootprints=0,
2195 maskNameList=[
"BAD"],
2199 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2203 exposure : `lsst.afw.image.Exposure` 2204 Exposure to process. 2208 We mask over all NaNs, including those that are masked with 2209 other bits (because those may or may not be interpolated over 2210 later, and we want to remove all NaNs). Despite this 2211 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2212 the historical name. 2214 maskedImage = exposure.getMaskedImage()
2217 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2218 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2219 numNans =
maskNans(maskedImage, maskVal)
2220 self.metadata.set(
"NUMNANS", numNans)
2222 self.log.warn(
"There were %d unmasked NaNs.", numNans)
2225 """"Mask and interpolate NaNs using mask plane "UNMASKEDNAN", in place. 2229 exposure : `lsst.afw.image.Exposure` 2230 Exposure to process. 2234 lsst.ip.isr.isrTask.maskNan() 2237 isrFunctions.interpolateFromMask(
2238 maskedImage=exposure.getMaskedImage(),
2239 fwhm=self.config.fwhm,
2240 growSaturatedFootprints=0,
2241 maskNameList=[
"UNMASKEDNAN"],
2245 """Measure the image background in subgrids, for quality control purposes. 2249 exposure : `lsst.afw.image.Exposure` 2250 Exposure to process. 2251 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2252 Configuration object containing parameters on which background 2253 statistics and subgrids to use. 2255 if IsrQaConfig
is not None:
2256 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2257 IsrQaConfig.flatness.nIter)
2258 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2259 statsControl.setAndMask(maskVal)
2260 maskedImage = exposure.getMaskedImage()
2261 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2262 skyLevel = stats.getValue(afwMath.MEDIAN)
2263 skySigma = stats.getValue(afwMath.STDEVCLIP)
2264 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2265 metadata = exposure.getMetadata()
2266 metadata.set(
'SKYLEVEL', skyLevel)
2267 metadata.set(
'SKYSIGMA', skySigma)
2270 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2271 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2272 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2273 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2274 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2275 skyLevels = numpy.zeros((nX, nY))
2278 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2280 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2282 xLLC = xc - meshXHalf
2283 yLLC = yc - meshYHalf
2284 xURC = xc + meshXHalf - 1
2285 yURC = yc + meshYHalf - 1
2288 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2290 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2292 good = numpy.where(numpy.isfinite(skyLevels))
2293 skyMedian = numpy.median(skyLevels[good])
2294 flatness = (skyLevels[good] - skyMedian) / skyMedian
2295 flatness_rms = numpy.std(flatness)
2296 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2298 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2299 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2300 nX, nY, flatness_pp, flatness_rms)
2302 metadata.set(
'FLATNESS_PP', float(flatness_pp))
2303 metadata.set(
'FLATNESS_RMS', float(flatness_rms))
2304 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2305 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2306 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2309 """Set an approximate magnitude zero point for the exposure. 2313 exposure : `lsst.afw.image.Exposure` 2314 Exposure to process. 2316 filterName = afwImage.Filter(exposure.getFilter().getId()).getName()
2317 if filterName
in self.config.fluxMag0T1:
2318 fluxMag0 = self.config.fluxMag0T1[filterName]
2320 self.log.warn(
"No rough magnitude zero point set for filter %s.", filterName)
2321 fluxMag0 = self.config.defaultFluxMag0T1
2323 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2325 self.log.warn(
"Non-positive exposure time; skipping rough zero point.")
2328 self.log.info(
"Setting rough magnitude zero point: %f", 2.5*math.log10(fluxMag0*expTime))
2329 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2332 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2336 ccdExposure : `lsst.afw.image.Exposure` 2337 Exposure to process. 2338 fpPolygon : `lsst.afw.geom.Polygon` 2339 Polygon in focal plane coordinates. 2342 ccd = ccdExposure.getDetector()
2343 fpCorners = ccd.getCorners(FOCAL_PLANE)
2344 ccdPolygon = Polygon(fpCorners)
2347 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2350 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2351 validPolygon = Polygon(ccdPoints)
2352 ccdExposure.getInfo().setValidPolygon(validPolygon)
2356 """Context manager that applies and removes flats and darks, 2357 if the task is configured to apply them. 2361 exp : `lsst.afw.image.Exposure` 2362 Exposure to process. 2363 flat : `lsst.afw.image.Exposure` 2364 Flat exposure the same size as ``exp``. 2365 dark : `lsst.afw.image.Exposure`, optional 2366 Dark exposure the same size as ``exp``. 2370 exp : `lsst.afw.image.Exposure` 2371 The flat and dark corrected exposure. 2373 if self.config.doDark
and dark
is not None:
2375 if self.config.doFlat:
2380 if self.config.doFlat:
2382 if self.config.doDark
and dark
is not None:
2386 """Utility function to examine ISR exposure at different stages. 2390 exposure : `lsst.afw.image.Exposure` 2393 State of processing to view. 2395 frame = getDebugFrame(self._display, stepname)
2397 display = getDisplay(frame)
2398 display.scale(
'asinh',
'zscale')
2399 display.mtv(exposure)
2400 prompt =
"Press Enter to continue [c]... " 2402 ans = input(prompt).lower()
2403 if ans
in (
"",
"c",):
2408 """A Detector-like object that supports returning gain and saturation level 2410 This is used when the input exposure does not have a detector. 2414 exposure : `lsst.afw.image.Exposure` 2415 Exposure to generate a fake amplifier for. 2416 config : `lsst.ip.isr.isrTaskConfig` 2417 Configuration to apply to the fake amplifier. 2421 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2423 self.
_gain = config.gain
2453 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2457 """Task to wrap the default IsrTask to allow it to be retargeted. 2459 The standard IsrTask can be called directly from a command line 2460 program, but doing so removes the ability of the task to be 2461 retargeted. As most cameras override some set of the IsrTask 2462 methods, this would remove those data-specific methods in the 2463 output post-ISR images. This wrapping class fixes the issue, 2464 allowing identical post-ISR images to be generated by both the 2465 processCcd and isrTask code. 2467 ConfigClass = RunIsrConfig
2468 _DefaultName =
"runIsr" 2472 self.makeSubtask(
"isr")
2478 dataRef : `lsst.daf.persistence.ButlerDataRef` 2479 data reference of the detector data to be processed 2483 result : `pipeBase.Struct` 2484 Result struct with component: 2486 - exposure : `lsst.afw.image.Exposure` 2487 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 __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 run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None, dark=None, flat=None, bfKernel=None, bfGains=None, defects=None, fringes=pipeBase.Struct(fringes=None), opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, detectorNum=None, strayLightData=None, illumMaskedImage=None, isGen3=False)
Perform instrument signature removal on an exposure.
_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)
def getIsrExposure(self, dataRef, datasetType, dateObs=None, immediate=True)
Retrieve a calibration dataset for removing instrument signature.
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)