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 defects = cT.PrerequisiteInput(
113 doc=
"Input defect tables.",
114 storageClass=
"DefectsList",
115 dimensions=[
"instrument",
"calibration_label",
"detector"],
117 opticsTransmission = cT.PrerequisiteInput(
118 name=
"transmission_optics",
119 storageClass=
"TransmissionCurve",
120 doc=
"Transmission curve due to the optics.",
121 dimensions=[
"instrument",
"calibration_label"],
123 filterTransmission = cT.PrerequisiteInput(
124 name=
"transmission_filter",
125 storageClass=
"TransmissionCurve",
126 doc=
"Transmission curve due to the filter.",
127 dimensions=[
"instrument",
"physical_filter",
"calibration_label"],
129 sensorTransmission = cT.PrerequisiteInput(
130 name=
"transmission_sensor",
131 storageClass=
"TransmissionCurve",
132 doc=
"Transmission curve due to the sensor.",
133 dimensions=[
"instrument",
"calibration_label",
"detector"],
135 atmosphereTransmission = cT.PrerequisiteInput(
136 name=
"transmission_atmosphere",
137 storageClass=
"TransmissionCurve",
138 doc=
"Transmission curve due to the atmosphere.",
139 dimensions=[
"instrument"],
141 illumMaskedImage = cT.PrerequisiteInput(
143 doc=
"Input illumination correction.",
144 storageClass=
"MaskedImageF",
145 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
148 outputExposure = cT.Output(
150 doc=
"Output ISR processed exposure.",
151 storageClass=
"ExposureF",
152 dimensions=[
"instrument",
"visit",
"detector"],
154 preInterpExposure = cT.Output(
155 name=
'preInterpISRCCD',
156 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
157 storageClass=
"ExposureF",
158 dimensions=[
"instrument",
"visit",
"detector"],
160 outputOssThumbnail = cT.Output(
162 doc=
"Output Overscan-subtracted thumbnail image.",
163 storageClass=
"Thumbnail",
164 dimensions=[
"instrument",
"visit",
"detector"],
166 outputFlattenedThumbnail = cT.Output(
167 name=
"FlattenedThumb",
168 doc=
"Output flat-corrected thumbnail image.",
169 storageClass=
"Thumbnail",
170 dimensions=[
"instrument",
"visit",
"detector"],
176 if config.doBias
is not True:
177 self.prerequisiteInputs.discard(
"bias")
178 if config.doLinearize
is not True:
179 self.prerequisiteInputs.discard(
"linearizer")
180 if config.doCrosstalk
is not True:
181 self.prerequisiteInputs.discard(
"crosstalkSources")
182 if config.doBrighterFatter
is not True:
183 self.prerequisiteInputs.discard(
"bfKernel")
184 if config.doDefect
is not True:
185 self.prerequisiteInputs.discard(
"defects")
186 if config.doDark
is not True:
187 self.prerequisiteInputs.discard(
"dark")
188 if config.doFlat
is not True:
189 self.prerequisiteInputs.discard(
"flat")
190 if config.doAttachTransmissionCurve
is not True:
191 self.prerequisiteInputs.discard(
"opticsTransmission")
192 self.prerequisiteInputs.discard(
"filterTransmission")
193 self.prerequisiteInputs.discard(
"sensorTransmission")
194 self.prerequisiteInputs.discard(
"atmosphereTransmission")
195 if config.doUseOpticsTransmission
is not True:
196 self.prerequisiteInputs.discard(
"opticsTransmission")
197 if config.doUseFilterTransmission
is not True:
198 self.prerequisiteInputs.discard(
"filterTransmission")
199 if config.doUseSensorTransmission
is not True:
200 self.prerequisiteInputs.discard(
"sensorTransmission")
201 if config.doUseAtmosphereTransmission
is not True:
202 self.prerequisiteInputs.discard(
"atmosphereTransmission")
203 if config.doIlluminationCorrection
is not True:
204 self.prerequisiteInputs.discard(
"illumMaskedImage")
206 if config.doWrite
is not True:
207 self.outputs.discard(
"outputExposure")
208 self.outputs.discard(
"preInterpExposure")
209 self.outputs.discard(
"outputFlattenedThumbnail")
210 self.outputs.discard(
"outputOssThumbnail")
211 if config.doSaveInterpPixels
is not True:
212 self.outputs.discard(
"preInterpExposure")
213 if config.qa.doThumbnailOss
is not True:
214 self.outputs.discard(
"outputOssThumbnail")
215 if config.qa.doThumbnailFlattened
is not True:
216 self.outputs.discard(
"outputFlattenedThumbnail")
220 pipelineConnections=IsrTaskConnections):
221 """Configuration parameters for IsrTask. 223 Items are grouped in the order in which they are executed by the task. 225 datasetType = pexConfig.Field(
227 doc=
"Dataset type for input data; users will typically leave this alone, " 228 "but camera-specific ISR tasks will override it",
232 fallbackFilterName = pexConfig.Field(
234 doc=
"Fallback default filter name for calibrations.",
237 useFallbackDate = pexConfig.Field(
239 doc=
"Pass observation date when using fallback filter.",
242 expectWcs = pexConfig.Field(
245 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 247 fwhm = pexConfig.Field(
249 doc=
"FWHM of PSF in arcseconds.",
252 qa = pexConfig.ConfigField(
254 doc=
"QA related configuration options.",
258 doConvertIntToFloat = pexConfig.Field(
260 doc=
"Convert integer raw images to floating point values?",
265 doSaturation = pexConfig.Field(
267 doc=
"Mask saturated pixels? NB: this is totally independent of the" 268 " interpolation option - this is ONLY setting the bits in the mask." 269 " To have them interpolated make sure doSaturationInterpolation=True",
272 saturatedMaskName = pexConfig.Field(
274 doc=
"Name of mask plane to use in saturation detection and interpolation",
277 saturation = pexConfig.Field(
279 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
280 default=float(
"NaN"),
282 growSaturationFootprintSize = pexConfig.Field(
284 doc=
"Number of pixels by which to grow the saturation footprints",
289 doSuspect = pexConfig.Field(
291 doc=
"Mask suspect pixels?",
294 suspectMaskName = pexConfig.Field(
296 doc=
"Name of mask plane to use for suspect pixels",
299 numEdgeSuspect = pexConfig.Field(
301 doc=
"Number of edge pixels to be flagged as untrustworthy.",
306 doSetBadRegions = pexConfig.Field(
308 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
311 badStatistic = pexConfig.ChoiceField(
313 doc=
"How to estimate the average value for BAD regions.",
316 "MEANCLIP":
"Correct using the (clipped) mean of good data",
317 "MEDIAN":
"Correct using the median of the good data",
322 doOverscan = pexConfig.Field(
324 doc=
"Do overscan subtraction?",
327 overscanFitType = pexConfig.ChoiceField(
329 doc=
"The method for fitting the overscan bias level.",
332 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
333 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
334 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
335 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
336 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
337 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
338 "MEAN":
"Correct using the mean of the overscan region",
339 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
340 "MEDIAN":
"Correct using the median of the overscan region",
343 overscanOrder = pexConfig.Field(
345 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
346 "or number of spline knots if overscan fit type is a spline."),
349 overscanNumSigmaClip = pexConfig.Field(
351 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
354 overscanIsInt = pexConfig.Field(
356 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN",
359 overscanNumLeadingColumnsToSkip = pexConfig.Field(
361 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
364 overscanNumTrailingColumnsToSkip = pexConfig.Field(
366 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
369 overscanMaxDev = pexConfig.Field(
371 doc=
"Maximum deviation from the median for overscan",
372 default=1000.0, check=
lambda x: x > 0
374 overscanBiasJump = pexConfig.Field(
376 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
379 overscanBiasJumpKeyword = pexConfig.Field(
381 doc=
"Header keyword containing information about devices.",
382 default=
"NO_SUCH_KEY",
384 overscanBiasJumpDevices = pexConfig.ListField(
386 doc=
"List of devices that need piecewise overscan correction.",
389 overscanBiasJumpLocation = pexConfig.Field(
391 doc=
"Location of bias jump along y-axis.",
396 doAssembleCcd = pexConfig.Field(
399 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 401 assembleCcd = pexConfig.ConfigurableField(
402 target=AssembleCcdTask,
403 doc=
"CCD assembly task",
407 doAssembleIsrExposures = pexConfig.Field(
410 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 412 doTrimToMatchCalib = pexConfig.Field(
415 doc=
"Trim raw data to match calibration bounding boxes?" 419 doBias = pexConfig.Field(
421 doc=
"Apply bias frame correction?",
424 biasDataProductName = pexConfig.Field(
426 doc=
"Name of the bias data product",
431 doVariance = pexConfig.Field(
433 doc=
"Calculate variance?",
436 gain = pexConfig.Field(
438 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
439 default=float(
"NaN"),
441 readNoise = pexConfig.Field(
443 doc=
"The read noise to use if no Detector is present in the Exposure",
446 doEmpiricalReadNoise = pexConfig.Field(
449 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 453 doLinearize = pexConfig.Field(
455 doc=
"Correct for nonlinearity of the detector's response?",
460 doCrosstalk = pexConfig.Field(
462 doc=
"Apply intra-CCD crosstalk correction?",
465 doCrosstalkBeforeAssemble = pexConfig.Field(
467 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
470 crosstalk = pexConfig.ConfigurableField(
471 target=CrosstalkTask,
472 doc=
"Intra-CCD crosstalk correction",
476 doDefect = pexConfig.Field(
478 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
481 doNanMasking = pexConfig.Field(
483 doc=
"Mask NAN pixels?",
486 doWidenSaturationTrails = pexConfig.Field(
488 doc=
"Widen bleed trails based on their width?",
493 doBrighterFatter = pexConfig.Field(
496 doc=
"Apply the brighter fatter correction" 498 brighterFatterLevel = pexConfig.ChoiceField(
501 doc=
"The level at which to correct for brighter-fatter.",
503 "AMP":
"Every amplifier treated separately.",
504 "DETECTOR":
"One kernel per detector",
507 brighterFatterMaxIter = pexConfig.Field(
510 doc=
"Maximum number of iterations for the brighter fatter correction" 512 brighterFatterThreshold = pexConfig.Field(
515 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 516 " absolute value of the difference between the current corrected image and the one" 517 " from the previous iteration summed over all the pixels." 519 brighterFatterApplyGain = pexConfig.Field(
522 doc=
"Should the gain be applied when applying the brighter fatter correction?" 526 doDark = pexConfig.Field(
528 doc=
"Apply dark frame correction?",
531 darkDataProductName = pexConfig.Field(
533 doc=
"Name of the dark data product",
538 doStrayLight = pexConfig.Field(
540 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
543 strayLight = pexConfig.ConfigurableField(
544 target=StrayLightTask,
545 doc=
"y-band stray light correction" 549 doFlat = pexConfig.Field(
551 doc=
"Apply flat field correction?",
554 flatDataProductName = pexConfig.Field(
556 doc=
"Name of the flat data product",
559 flatScalingType = pexConfig.ChoiceField(
561 doc=
"The method for scaling the flat on the fly.",
564 "USER":
"Scale by flatUserScale",
565 "MEAN":
"Scale by the inverse of the mean",
566 "MEDIAN":
"Scale by the inverse of the median",
569 flatUserScale = pexConfig.Field(
571 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
574 doTweakFlat = pexConfig.Field(
576 doc=
"Tweak flats to match observed amplifier ratios?",
581 doApplyGains = pexConfig.Field(
583 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
586 normalizeGains = pexConfig.Field(
588 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
593 doFringe = pexConfig.Field(
595 doc=
"Apply fringe correction?",
598 fringe = pexConfig.ConfigurableField(
600 doc=
"Fringe subtraction task",
602 fringeAfterFlat = pexConfig.Field(
604 doc=
"Do fringe subtraction after flat-fielding?",
609 doAddDistortionModel = pexConfig.Field(
611 doc=
"Apply a distortion model based on camera geometry to the WCS?",
613 deprecated=(
"Camera geometry is incorporated when reading the raw files." 614 " This option no longer is used, and will be removed after v19.")
618 doMeasureBackground = pexConfig.Field(
620 doc=
"Measure the background level on the reduced image?",
625 doCameraSpecificMasking = pexConfig.Field(
627 doc=
"Mask camera-specific bad regions?",
630 masking = pexConfig.ConfigurableField(
637 doInterpolate = pexConfig.Field(
639 doc=
"Interpolate masked pixels?",
642 doSaturationInterpolation = pexConfig.Field(
644 doc=
"Perform interpolation over pixels masked as saturated?" 645 " NB: This is independent of doSaturation; if that is False this plane" 646 " will likely be blank, resulting in a no-op here.",
649 doNanInterpolation = pexConfig.Field(
651 doc=
"Perform interpolation over pixels masked as NaN?" 652 " NB: This is independent of doNanMasking; if that is False this plane" 653 " will likely be blank, resulting in a no-op here.",
656 doNanInterpAfterFlat = pexConfig.Field(
658 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 659 "also have to interpolate them before flat-fielding."),
662 maskListToInterpolate = pexConfig.ListField(
664 doc=
"List of mask planes that should be interpolated.",
665 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
667 doSaveInterpPixels = pexConfig.Field(
669 doc=
"Save a copy of the pre-interpolated pixel values?",
674 fluxMag0T1 = pexConfig.DictField(
677 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
678 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
681 defaultFluxMag0T1 = pexConfig.Field(
683 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
684 default=pow(10.0, 0.4*28.0)
688 doVignette = pexConfig.Field(
690 doc=
"Apply vignetting parameters?",
693 vignette = pexConfig.ConfigurableField(
695 doc=
"Vignetting task.",
699 doAttachTransmissionCurve = pexConfig.Field(
702 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 704 doUseOpticsTransmission = pexConfig.Field(
707 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 709 doUseFilterTransmission = pexConfig.Field(
712 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 714 doUseSensorTransmission = pexConfig.Field(
717 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 719 doUseAtmosphereTransmission = pexConfig.Field(
722 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 726 doIlluminationCorrection = pexConfig.Field(
729 doc=
"Perform illumination correction?" 731 illuminationCorrectionDataProductName = pexConfig.Field(
733 doc=
"Name of the illumination correction data product.",
736 illumScale = pexConfig.Field(
738 doc=
"Scale factor for the illumination correction.",
741 illumFilters = pexConfig.ListField(
744 doc=
"Only perform illumination correction for these filters." 748 doWrite = pexConfig.Field(
750 doc=
"Persist postISRCCD?",
757 raise ValueError(
"You may not specify both doFlat and doApplyGains")
759 self.config.maskListToInterpolate.append(
"SAT")
761 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
764 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
765 """Apply common instrument signature correction algorithms to a raw frame. 767 The process for correcting imaging data is very similar from 768 camera to camera. This task provides a vanilla implementation of 769 doing these corrections, including the ability to turn certain 770 corrections off if they are not needed. The inputs to the primary 771 method, `run()`, are a raw exposure to be corrected and the 772 calibration data products. The raw input is a single chip sized 773 mosaic of all amps including overscans and other non-science 774 pixels. The method `runDataRef()` identifies and defines the 775 calibration data products, and is intended for use by a 776 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 777 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 778 subclassed for different camera, although the most camera specific 779 methods have been split into subtasks that can be redirected 782 The __init__ method sets up the subtasks for ISR processing, using 783 the defaults from `lsst.ip.isr`. 788 Positional arguments passed to the Task constructor. None used at this time. 789 kwargs : `dict`, optional 790 Keyword arguments passed on to the Task constructor. None used at this time. 792 ConfigClass = IsrTaskConfig
797 self.makeSubtask(
"assembleCcd")
798 self.makeSubtask(
"crosstalk")
799 self.makeSubtask(
"strayLight")
800 self.makeSubtask(
"fringe")
801 self.makeSubtask(
"masking")
802 self.makeSubtask(
"vignette")
805 inputs = butlerQC.get(inputRefs)
808 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
809 except Exception
as e:
810 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
813 inputs[
'isGen3'] =
True 815 detector = inputs[
'ccdExposure'].getDetector()
817 if 'linearizer' not in inputs:
818 linearityName = detector.getAmplifiers()[0].getLinearityType()
819 inputs[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
821 if self.config.doDefect
is True:
822 if "defects" in inputs
and inputs[
'defects']
is not None:
825 if not isinstance(inputs[
"defects"], Defects):
826 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
838 if self.config.doFringe
is True and self.fringe.checkFilter(inputs[
'ccdExposure']):
839 expId = inputs[
'ccdExposure'].getInfo().getVisitInfo().getExposureId()
840 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
842 assembler=self.assembleCcd
843 if self.config.doAssembleIsrExposures
else None)
845 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
847 if self.config.doStrayLight
is True and self.strayLight.checkFilter(inputs[
'ccdExposure']):
848 if 'strayLightData' not in inputs:
849 inputs[
'strayLightData'] =
None 851 outputs = self.
run(**inputs)
852 butlerQC.put(outputs, outputRefs)
855 """!Retrieve necessary frames for instrument signature removal. 857 Pre-fetching all required ISR data products limits the IO 858 required by the ISR. Any conflict between the calibration data 859 available and that needed for ISR is also detected prior to 860 doing processing, allowing it to fail quickly. 864 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 865 Butler reference of the detector data to be processed 866 rawExposure : `afw.image.Exposure` 867 The raw exposure that will later be corrected with the 868 retrieved calibration data; should not be modified in this 873 result : `lsst.pipe.base.Struct` 874 Result struct with components (which may be `None`): 875 - ``bias``: bias calibration frame (`afw.image.Exposure`) 876 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 877 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 878 - ``dark``: dark calibration frame (`afw.image.Exposure`) 879 - ``flat``: flat calibration frame (`afw.image.Exposure`) 880 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 881 - ``defects``: list of defects (`lsst.meas.algorithms.Defects`) 882 - ``fringes``: `lsst.pipe.base.Struct` with components: 883 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 884 - ``seed``: random seed derived from the ccdExposureId for random 885 number generator (`uint32`). 886 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 887 A ``TransmissionCurve`` that represents the throughput of the optics, 888 to be evaluated in focal-plane coordinates. 889 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 890 A ``TransmissionCurve`` that represents the throughput of the filter 891 itself, to be evaluated in focal-plane coordinates. 892 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 893 A ``TransmissionCurve`` that represents the throughput of the sensor 894 itself, to be evaluated in post-assembly trimmed detector coordinates. 895 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 896 A ``TransmissionCurve`` that represents the throughput of the 897 atmosphere, assumed to be spatially constant. 898 - ``strayLightData`` : `object` 899 An opaque object containing calibration information for 900 stray-light correction. If `None`, no correction will be 902 - ``illumMaskedImage`` : illumination correction image (`lsst.afw.image.MaskedImage`) 906 NotImplementedError : 907 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 910 dateObs = rawExposure.getInfo().getVisitInfo().getDate()
911 dateObs = dateObs.toPython().isoformat()
913 self.log.warn(
"Unable to identify dateObs for rawExposure.")
916 ccd = rawExposure.getDetector()
917 filterName = afwImage.Filter(rawExposure.getFilter().getId()).getName()
918 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
919 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
920 if self.config.doBias
else None)
922 linearizer = (dataRef.get(
"linearizer", immediate=
True)
924 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
925 if self.config.doCrosstalk
else None)
926 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
927 if self.config.doDark
else None)
928 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName,
930 if self.config.doFlat
else None)
932 brighterFatterKernel =
None 933 brighterFatterGains =
None 934 if self.config.doBrighterFatter
is True:
939 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
940 brighterFatterGains = brighterFatterKernel.gain
941 self.log.info(
"New style bright-fatter kernel (brighterFatterKernel) loaded")
944 brighterFatterKernel = dataRef.get(
"bfKernel")
945 self.log.info(
"Old style bright-fatter kernel (np.array) loaded")
947 brighterFatterKernel =
None 948 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
951 if self.config.brighterFatterLevel ==
'DETECTOR':
952 if brighterFatterKernel.detectorKernel:
953 brighterFatterKernel = brighterFatterKernel.detectorKernel[ccd.getId()]
954 elif brighterFatterKernel.detectorKernelFromAmpKernels:
955 brighterFatterKernel = brighterFatterKernel.detectorKernelFromAmpKernels[ccd.getId()]
957 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
960 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
962 defectList = (dataRef.get(
"defects")
963 if self.config.doDefect
else None)
964 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
965 if self.config.doAssembleIsrExposures
else None)
966 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
967 else pipeBase.Struct(fringes=
None))
969 if self.config.doAttachTransmissionCurve:
970 opticsTransmission = (dataRef.get(
"transmission_optics")
971 if self.config.doUseOpticsTransmission
else None)
972 filterTransmission = (dataRef.get(
"transmission_filter")
973 if self.config.doUseFilterTransmission
else None)
974 sensorTransmission = (dataRef.get(
"transmission_sensor")
975 if self.config.doUseSensorTransmission
else None)
976 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
977 if self.config.doUseAtmosphereTransmission
else None)
979 opticsTransmission =
None 980 filterTransmission =
None 981 sensorTransmission =
None 982 atmosphereTransmission =
None 984 if self.config.doStrayLight:
985 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
987 strayLightData =
None 990 self.config.illuminationCorrectionDataProductName).getMaskedImage()
991 if (self.config.doIlluminationCorrection
and 992 filterName
in self.config.illumFilters)
996 return pipeBase.Struct(bias=biasExposure,
997 linearizer=linearizer,
998 crosstalkSources=crosstalkSources,
1001 bfKernel=brighterFatterKernel,
1002 bfGains=brighterFatterGains,
1004 fringes=fringeStruct,
1005 opticsTransmission=opticsTransmission,
1006 filterTransmission=filterTransmission,
1007 sensorTransmission=sensorTransmission,
1008 atmosphereTransmission=atmosphereTransmission,
1009 strayLightData=strayLightData,
1010 illumMaskedImage=illumMaskedImage
1013 @pipeBase.timeMethod
1014 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
1015 dark=None, flat=None, bfKernel=None, bfGains=None, defects=None,
1016 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1017 sensorTransmission=
None, atmosphereTransmission=
None,
1018 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1021 """!Perform instrument signature removal on an exposure. 1023 Steps included in the ISR processing, in order performed, are: 1024 - saturation and suspect pixel masking 1025 - overscan subtraction 1026 - CCD assembly of individual amplifiers 1028 - variance image construction 1029 - linearization of non-linear response 1031 - brighter-fatter correction 1034 - stray light subtraction 1036 - masking of known defects and camera specific features 1037 - vignette calculation 1038 - appending transmission curve and distortion model 1042 ccdExposure : `lsst.afw.image.Exposure` 1043 The raw exposure that is to be run through ISR. The 1044 exposure is modified by this method. 1045 camera : `lsst.afw.cameraGeom.Camera`, optional 1046 The camera geometry for this exposure. Used to select the 1047 distortion model appropriate for this data. 1048 bias : `lsst.afw.image.Exposure`, optional 1049 Bias calibration frame. 1050 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1051 Functor for linearization. 1052 crosstalkSources : `list`, optional 1053 List of possible crosstalk sources. 1054 dark : `lsst.afw.image.Exposure`, optional 1055 Dark calibration frame. 1056 flat : `lsst.afw.image.Exposure`, optional 1057 Flat calibration frame. 1058 bfKernel : `numpy.ndarray`, optional 1059 Brighter-fatter kernel. 1060 bfGains : `dict` of `float`, optional 1061 Gains used to override the detector's nominal gains for the 1062 brighter-fatter correction. A dict keyed by amplifier name for 1063 the detector in question. 1064 defects : `lsst.meas.algorithms.Defects`, optional 1066 fringes : `lsst.pipe.base.Struct`, optional 1067 Struct containing the fringe correction data, with 1069 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1070 - ``seed``: random seed derived from the ccdExposureId for random 1071 number generator (`uint32`) 1072 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1073 A ``TransmissionCurve`` that represents the throughput of the optics, 1074 to be evaluated in focal-plane coordinates. 1075 filterTransmission : `lsst.afw.image.TransmissionCurve` 1076 A ``TransmissionCurve`` that represents the throughput of the filter 1077 itself, to be evaluated in focal-plane coordinates. 1078 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1079 A ``TransmissionCurve`` that represents the throughput of the sensor 1080 itself, to be evaluated in post-assembly trimmed detector coordinates. 1081 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1082 A ``TransmissionCurve`` that represents the throughput of the 1083 atmosphere, assumed to be spatially constant. 1084 detectorNum : `int`, optional 1085 The integer number for the detector to process. 1086 isGen3 : bool, optional 1087 Flag this call to run() as using the Gen3 butler environment. 1088 strayLightData : `object`, optional 1089 Opaque object containing calibration information for stray-light 1090 correction. If `None`, no correction will be performed. 1091 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional 1092 Illumination correction image. 1096 result : `lsst.pipe.base.Struct` 1097 Result struct with component: 1098 - ``exposure`` : `afw.image.Exposure` 1099 The fully ISR corrected exposure. 1100 - ``outputExposure`` : `afw.image.Exposure` 1101 An alias for `exposure` 1102 - ``ossThumb`` : `numpy.ndarray` 1103 Thumbnail image of the exposure after overscan subtraction. 1104 - ``flattenedThumb`` : `numpy.ndarray` 1105 Thumbnail image of the exposure after flat-field correction. 1110 Raised if a configuration option is set to True, but the 1111 required calibration data has not been specified. 1115 The current processed exposure can be viewed by setting the 1116 appropriate lsstDebug entries in the `debug.display` 1117 dictionary. The names of these entries correspond to some of 1118 the IsrTaskConfig Boolean options, with the value denoting the 1119 frame to use. The exposure is shown inside the matching 1120 option check and after the processing of that step has 1121 finished. The steps with debug points are: 1132 In addition, setting the "postISRCCD" entry displays the 1133 exposure after all ISR processing has finished. 1141 if detectorNum
is None:
1142 raise RuntimeError(
"Must supply the detectorNum if running as Gen3.")
1144 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1149 if isinstance(ccdExposure, ButlerDataRef):
1152 ccd = ccdExposure.getDetector()
1153 filterName = afwImage.Filter(ccdExposure.getFilter().getId()).getName()
1156 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd." 1157 ccd = [
FakeAmp(ccdExposure, self.config)]
1160 if self.config.doBias
and bias
is None:
1161 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1163 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1164 if self.config.doBrighterFatter
and bfKernel
is None:
1165 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1166 if self.config.doDark
and dark
is None:
1167 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1168 if self.config.doFlat
and flat
is None:
1169 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1170 if self.config.doDefect
and defects
is None:
1171 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1172 if (self.config.doFringe
and filterName
in self.fringe.config.filters
and 1173 fringes.fringes
is None):
1178 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1179 if (self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters
and 1180 illumMaskedImage
is None):
1181 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1184 if self.config.doConvertIntToFloat:
1185 self.log.info(
"Converting exposure to floating point values.")
1192 if ccdExposure.getBBox().contains(amp.getBBox()):
1196 if self.config.doOverscan
and not badAmp:
1199 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1200 if overscanResults
is not None and \
1201 self.config.qa
is not None and self.config.qa.saveStats
is True:
1202 if isinstance(overscanResults.overscanFit, float):
1203 qaMedian = overscanResults.overscanFit
1204 qaStdev = float(
"NaN")
1206 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1207 afwMath.MEDIAN | afwMath.STDEVCLIP)
1208 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1209 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1211 self.metadata.set(f
"ISR OSCAN {amp.getName()} MEDIAN", qaMedian)
1212 self.metadata.set(f
"ISR OSCAN {amp.getName()} STDEV", qaStdev)
1213 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1214 amp.getName(), qaMedian, qaStdev)
1215 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1218 self.log.warn(
"Amplifier %s is bad.", amp.getName())
1219 overscanResults =
None 1221 overscans.append(overscanResults
if overscanResults
is not None else None)
1223 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1225 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1226 self.log.info(
"Applying crosstalk correction.")
1227 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1228 self.
debugView(ccdExposure,
"doCrosstalk")
1230 if self.config.doAssembleCcd:
1231 self.log.info(
"Assembling CCD from amplifiers.")
1232 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1234 if self.config.expectWcs
and not ccdExposure.getWcs():
1235 self.log.warn(
"No WCS found in input exposure.")
1236 self.
debugView(ccdExposure,
"doAssembleCcd")
1239 if self.config.qa.doThumbnailOss:
1240 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1242 if self.config.doBias:
1243 self.log.info(
"Applying bias correction.")
1244 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1245 trimToFit=self.config.doTrimToMatchCalib)
1248 if self.config.doVariance:
1249 for amp, overscanResults
in zip(ccd, overscans):
1250 if ccdExposure.getBBox().contains(amp.getBBox()):
1251 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1252 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1253 if overscanResults
is not None:
1255 overscanImage=overscanResults.overscanImage)
1259 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1260 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1261 afwMath.MEDIAN | afwMath.STDEVCLIP)
1262 self.metadata.set(f
"ISR VARIANCE {amp.getName()} MEDIAN",
1263 qaStats.getValue(afwMath.MEDIAN))
1264 self.metadata.set(f
"ISR VARIANCE {amp.getName()} STDEV",
1265 qaStats.getValue(afwMath.STDEVCLIP))
1266 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1267 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1268 qaStats.getValue(afwMath.STDEVCLIP))
1271 self.log.info(
"Applying linearizer.")
1272 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1274 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1275 self.log.info(
"Applying crosstalk correction.")
1276 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1277 self.
debugView(ccdExposure,
"doCrosstalk")
1281 if self.config.doDefect:
1282 self.log.info(
"Masking defects.")
1285 if self.config.numEdgeSuspect > 0:
1286 self.log.info(
"Masking edges as SUSPECT.")
1287 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1288 maskPlane=
"SUSPECT")
1290 if self.config.doNanMasking:
1291 self.log.info(
"Masking NAN value pixels.")
1294 if self.config.doWidenSaturationTrails:
1295 self.log.info(
"Widening saturation trails.")
1296 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1298 if self.config.doCameraSpecificMasking:
1299 self.log.info(
"Masking regions for camera specific reasons.")
1300 self.masking.
run(ccdExposure)
1302 if self.config.doBrighterFatter:
1311 interpExp = ccdExposure.clone()
1313 isrFunctions.interpolateFromMask(
1314 maskedImage=interpExp.getMaskedImage(),
1315 fwhm=self.config.fwhm,
1316 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1317 maskNameList=self.config.maskListToInterpolate
1319 bfExp = interpExp.clone()
1321 self.log.info(
"Applying brighter fatter correction.")
1322 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1323 self.config.brighterFatterMaxIter,
1324 self.config.brighterFatterThreshold,
1325 self.config.brighterFatterApplyGain,
1327 if bfResults[1] == self.config.brighterFatterMaxIter:
1328 self.log.warn(
"Brighter fatter correction did not converge, final difference %f.",
1331 self.log.info(
"Finished brighter fatter correction in %d iterations.",
1333 image = ccdExposure.getMaskedImage().getImage()
1334 bfCorr = bfExp.getMaskedImage().getImage()
1335 bfCorr -= interpExp.getMaskedImage().getImage()
1344 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1346 self.log.warn(
"Ensuring image edges are masked as SUSPECT to the brighter-fatter kernel size.")
1348 self.
debugView(ccdExposure,
"doBrighterFatter")
1350 if self.config.doDark:
1351 self.log.info(
"Applying dark correction.")
1355 if self.config.doFringe
and not self.config.fringeAfterFlat:
1356 self.log.info(
"Applying fringe correction before flat.")
1357 self.fringe.
run(ccdExposure, **fringes.getDict())
1360 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1361 self.log.info(
"Checking strayLight correction.")
1362 self.strayLight.
run(ccdExposure, strayLightData)
1363 self.
debugView(ccdExposure,
"doStrayLight")
1365 if self.config.doFlat:
1366 self.log.info(
"Applying flat correction.")
1370 if self.config.doApplyGains:
1371 self.log.info(
"Applying gain correction instead of flat.")
1372 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1374 if self.config.doFringe
and self.config.fringeAfterFlat:
1375 self.log.info(
"Applying fringe correction after flat.")
1376 self.fringe.
run(ccdExposure, **fringes.getDict())
1378 if self.config.doVignette:
1379 self.log.info(
"Constructing Vignette polygon.")
1382 if self.config.vignette.doWriteVignettePolygon:
1385 if self.config.doAttachTransmissionCurve:
1386 self.log.info(
"Adding transmission curves.")
1387 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1388 filterTransmission=filterTransmission,
1389 sensorTransmission=sensorTransmission,
1390 atmosphereTransmission=atmosphereTransmission)
1392 flattenedThumb =
None 1393 if self.config.qa.doThumbnailFlattened:
1394 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1396 if self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters:
1397 self.log.info(
"Performing illumination correction.")
1398 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1399 illumMaskedImage, illumScale=self.config.illumScale,
1400 trimToFit=self.config.doTrimToMatchCalib)
1403 if self.config.doSaveInterpPixels:
1404 preInterpExp = ccdExposure.clone()
1419 if self.config.doSetBadRegions:
1420 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1421 if badPixelCount > 0:
1422 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1424 if self.config.doInterpolate:
1425 self.log.info(
"Interpolating masked pixels.")
1426 isrFunctions.interpolateFromMask(
1427 maskedImage=ccdExposure.getMaskedImage(),
1428 fwhm=self.config.fwhm,
1429 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1430 maskNameList=list(self.config.maskListToInterpolate)
1435 if self.config.doMeasureBackground:
1436 self.log.info(
"Measuring background level.")
1439 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1441 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1442 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1443 afwMath.MEDIAN | afwMath.STDEVCLIP)
1444 self.metadata.set(
"ISR BACKGROUND {} MEDIAN".format(amp.getName()),
1445 qaStats.getValue(afwMath.MEDIAN))
1446 self.metadata.set(
"ISR BACKGROUND {} STDEV".format(amp.getName()),
1447 qaStats.getValue(afwMath.STDEVCLIP))
1448 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1449 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1450 qaStats.getValue(afwMath.STDEVCLIP))
1452 self.
debugView(ccdExposure,
"postISRCCD")
1454 return pipeBase.Struct(
1455 exposure=ccdExposure,
1457 flattenedThumb=flattenedThumb,
1459 preInterpolatedExposure=preInterpExp,
1460 outputExposure=ccdExposure,
1461 outputOssThumbnail=ossThumb,
1462 outputFlattenedThumbnail=flattenedThumb,
1465 @pipeBase.timeMethod
1467 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1469 This method contains the `CmdLineTask` interface to the ISR 1470 processing. All IO is handled here, freeing the `run()` method 1471 to manage only pixel-level calculations. The steps performed 1473 - Read in necessary detrending/isr/calibration data. 1474 - Process raw exposure in `run()`. 1475 - Persist the ISR-corrected exposure as "postISRCCD" if 1476 config.doWrite=True. 1480 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1481 DataRef of the detector data to be processed 1485 result : `lsst.pipe.base.Struct` 1486 Result struct with component: 1487 - ``exposure`` : `afw.image.Exposure` 1488 The fully ISR corrected exposure. 1493 Raised if a configuration option is set to True, but the 1494 required calibration data does not exist. 1497 self.log.info(
"Performing ISR on sensor %s.", sensorRef.dataId)
1499 ccdExposure = sensorRef.get(self.config.datasetType)
1501 camera = sensorRef.get(
"camera")
1502 isrData = self.
readIsrData(sensorRef, ccdExposure)
1504 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1506 if self.config.doWrite:
1507 sensorRef.put(result.exposure,
"postISRCCD")
1508 if result.preInterpolatedExposure
is not None:
1509 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1510 if result.ossThumb
is not None:
1511 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1512 if result.flattenedThumb
is not None:
1513 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1518 """!Retrieve a calibration dataset for removing instrument signature. 1523 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1524 DataRef of the detector data to find calibration datasets 1527 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1528 dateObs : `str`, optional 1529 Date of the observation. Used to correct butler failures 1530 when using fallback filters. 1532 If True, disable butler proxies to enable error handling 1533 within this routine. 1537 exposure : `lsst.afw.image.Exposure` 1538 Requested calibration frame. 1543 Raised if no matching calibration frame can be found. 1546 exp = dataRef.get(datasetType, immediate=immediate)
1547 except Exception
as exc1:
1548 if not self.config.fallbackFilterName:
1549 raise RuntimeError(
"Unable to retrieve %s for %s: %s." % (datasetType, dataRef.dataId, exc1))
1551 if self.config.useFallbackDate
and dateObs:
1552 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName,
1553 dateObs=dateObs, immediate=immediate)
1555 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1556 except Exception
as exc2:
1557 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s." %
1558 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1559 self.log.warn(
"Using fallback calibration from filter %s.", self.config.fallbackFilterName)
1561 if self.config.doAssembleIsrExposures:
1562 exp = self.assembleCcd.assembleCcd(exp)
1566 """Ensure that the data returned by Butler is a fully constructed exposure. 1568 ISR requires exposure-level image data for historical reasons, so if we did 1569 not recieve that from Butler, construct it from what we have, modifying the 1574 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1575 `lsst.afw.image.ImageF` 1576 The input data structure obtained from Butler. 1577 camera : `lsst.afw.cameraGeom.camera` 1578 The camera associated with the image. Used to find the appropriate 1581 The detector this exposure should match. 1585 inputExp : `lsst.afw.image.Exposure` 1586 The re-constructed exposure, with appropriate detector parameters. 1591 Raised if the input data cannot be used to construct an exposure. 1593 if isinstance(inputExp, afwImage.DecoratedImageU):
1594 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1595 elif isinstance(inputExp, afwImage.ImageF):
1596 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1597 elif isinstance(inputExp, afwImage.MaskedImageF):
1598 inputExp = afwImage.makeExposure(inputExp)
1599 elif isinstance(inputExp, afwImage.Exposure):
1601 elif inputExp
is None:
1605 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1608 if inputExp.getDetector()
is None:
1609 inputExp.setDetector(camera[detectorNum])
1614 """Convert exposure image from uint16 to float. 1616 If the exposure does not need to be converted, the input is 1617 immediately returned. For exposures that are converted to use 1618 floating point pixels, the variance is set to unity and the 1623 exposure : `lsst.afw.image.Exposure` 1624 The raw exposure to be converted. 1628 newexposure : `lsst.afw.image.Exposure` 1629 The input ``exposure``, converted to floating point pixels. 1634 Raised if the exposure type cannot be converted to float. 1637 if isinstance(exposure, afwImage.ExposureF):
1639 self.log.debug(
"Exposure already of type float.")
1641 if not hasattr(exposure,
"convertF"):
1642 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1644 newexposure = exposure.convertF()
1645 newexposure.variance[:] = 1
1646 newexposure.mask[:] = 0x0
1651 """Identify bad amplifiers, saturated and suspect pixels. 1655 ccdExposure : `lsst.afw.image.Exposure` 1656 Input exposure to be masked. 1657 amp : `lsst.afw.table.AmpInfoCatalog` 1658 Catalog of parameters defining the amplifier on this 1660 defects : `lsst.meas.algorithms.Defects` 1661 List of defects. Used to determine if the entire 1667 If this is true, the entire amplifier area is covered by 1668 defects and unusable. 1671 maskedImage = ccdExposure.getMaskedImage()
1677 if defects
is not None:
1678 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1683 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1685 maskView = dataView.getMask()
1686 maskView |= maskView.getPlaneBitMask(
"BAD")
1693 if self.config.doSaturation
and not badAmp:
1694 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1695 if self.config.doSuspect
and not badAmp:
1696 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1697 if math.isfinite(self.config.saturation):
1698 limits.update({self.config.saturatedMaskName: self.config.saturation})
1700 for maskName, maskThreshold
in limits.items():
1701 if not math.isnan(maskThreshold):
1702 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1703 isrFunctions.makeThresholdMask(
1704 maskedImage=dataView,
1705 threshold=maskThreshold,
1711 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1713 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1714 self.config.suspectMaskName])
1715 if numpy.all(maskView.getArray() & maskVal > 0):
1717 maskView |= maskView.getPlaneBitMask(
"BAD")
1722 """Apply overscan correction in place. 1724 This method does initial pixel rejection of the overscan 1725 region. The overscan can also be optionally segmented to 1726 allow for discontinuous overscan responses to be fit 1727 separately. The actual overscan subtraction is performed by 1728 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1729 which is called here after the amplifier is preprocessed. 1733 ccdExposure : `lsst.afw.image.Exposure` 1734 Exposure to have overscan correction performed. 1735 amp : `lsst.afw.table.AmpInfoCatalog` 1736 The amplifier to consider while correcting the overscan. 1740 overscanResults : `lsst.pipe.base.Struct` 1741 Result struct with components: 1742 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1743 Value or fit subtracted from the amplifier image data. 1744 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1745 Value or fit subtracted from the overscan image data. 1746 - ``overscanImage`` : `lsst.afw.image.Image` 1747 Image of the overscan region with the overscan 1748 correction applied. This quantity is used to estimate 1749 the amplifier read noise empirically. 1754 Raised if the ``amp`` does not contain raw pixel information. 1758 lsst.ip.isr.isrFunctions.overscanCorrection 1760 if not amp.getHasRawInfo():
1761 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1763 if amp.getRawHorizontalOverscanBBox().isEmpty():
1764 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1767 statControl = afwMath.StatisticsControl()
1768 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1771 dataBBox = amp.getRawDataBBox()
1772 oscanBBox = amp.getRawHorizontalOverscanBBox()
1776 prescanBBox = amp.getRawPrescanBBox()
1777 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1778 dx0 += self.config.overscanNumLeadingColumnsToSkip
1779 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1781 dx0 += self.config.overscanNumTrailingColumnsToSkip
1782 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1788 if ((self.config.overscanBiasJump
and 1789 self.config.overscanBiasJumpLocation)
and 1790 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1791 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1792 self.config.overscanBiasJumpDevices)):
1793 if amp.getReadoutCorner()
in (ReadoutCorner.LL, ReadoutCorner.LR):
1794 yLower = self.config.overscanBiasJumpLocation
1795 yUpper = dataBBox.getHeight() - yLower
1797 yUpper = self.config.overscanBiasJumpLocation
1798 yLower = dataBBox.getHeight() - yUpper
1817 oscanBBox.getHeight())))
1820 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1821 ampImage = ccdExposure.maskedImage[imageBBox]
1822 overscanImage = ccdExposure.maskedImage[overscanBBox]
1824 overscanArray = overscanImage.image.array
1825 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1826 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1827 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1829 statControl = afwMath.StatisticsControl()
1830 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1832 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1833 overscanImage=overscanImage,
1834 fitType=self.config.overscanFitType,
1835 order=self.config.overscanOrder,
1836 collapseRej=self.config.overscanNumSigmaClip,
1837 statControl=statControl,
1838 overscanIsInt=self.config.overscanIsInt
1842 levelStat = afwMath.MEDIAN
1843 sigmaStat = afwMath.STDEVCLIP
1845 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1846 self.config.qa.flatness.nIter)
1847 metadata = ccdExposure.getMetadata()
1848 ampNum = amp.getName()
1849 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1850 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1851 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1853 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1854 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1855 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1857 return overscanResults
1860 """Set the variance plane using the amplifier gain and read noise 1862 The read noise is calculated from the ``overscanImage`` if the 1863 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1864 the value from the amplifier data is used. 1868 ampExposure : `lsst.afw.image.Exposure` 1869 Exposure to process. 1870 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1871 Amplifier detector data. 1872 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1873 Image of overscan, required only for empirical read noise. 1877 lsst.ip.isr.isrFunctions.updateVariance 1879 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1880 gain = amp.getGain()
1882 if math.isnan(gain):
1884 self.log.warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1887 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f.",
1888 amp.getName(), gain, patchedGain)
1891 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1892 self.log.info(
"Overscan is none for EmpiricalReadNoise.")
1894 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1895 stats = afwMath.StatisticsControl()
1896 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1897 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
1898 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
1899 amp.getName(), readNoise)
1901 readNoise = amp.getReadNoise()
1903 isrFunctions.updateVariance(
1904 maskedImage=ampExposure.getMaskedImage(),
1906 readNoise=readNoise,
1910 """!Apply dark correction in place. 1914 exposure : `lsst.afw.image.Exposure` 1915 Exposure to process. 1916 darkExposure : `lsst.afw.image.Exposure` 1917 Dark exposure of the same size as ``exposure``. 1918 invert : `Bool`, optional 1919 If True, re-add the dark to an already corrected image. 1924 Raised if either ``exposure`` or ``darkExposure`` do not 1925 have their dark time defined. 1929 lsst.ip.isr.isrFunctions.darkCorrection 1931 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1932 if math.isnan(expScale):
1933 raise RuntimeError(
"Exposure darktime is NAN.")
1934 if darkExposure.getInfo().getVisitInfo()
is not None:
1935 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1939 self.log.warn(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
1942 if math.isnan(darkScale):
1943 raise RuntimeError(
"Dark calib darktime is NAN.")
1944 isrFunctions.darkCorrection(
1945 maskedImage=exposure.getMaskedImage(),
1946 darkMaskedImage=darkExposure.getMaskedImage(),
1948 darkScale=darkScale,
1950 trimToFit=self.config.doTrimToMatchCalib
1954 """!Check if linearization is needed for the detector cameraGeom. 1956 Checks config.doLinearize and the linearity type of the first 1961 detector : `lsst.afw.cameraGeom.Detector` 1962 Detector to get linearity type from. 1966 doLinearize : `Bool` 1967 If True, linearization should be performed. 1969 return self.config.doLinearize
and \
1970 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
1973 """!Apply flat correction in place. 1977 exposure : `lsst.afw.image.Exposure` 1978 Exposure to process. 1979 flatExposure : `lsst.afw.image.Exposure` 1980 Flat exposure of the same size as ``exposure``. 1981 invert : `Bool`, optional 1982 If True, unflatten an already flattened image. 1986 lsst.ip.isr.isrFunctions.flatCorrection 1988 isrFunctions.flatCorrection(
1989 maskedImage=exposure.getMaskedImage(),
1990 flatMaskedImage=flatExposure.getMaskedImage(),
1991 scalingType=self.config.flatScalingType,
1992 userScale=self.config.flatUserScale,
1994 trimToFit=self.config.doTrimToMatchCalib
1998 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 2002 exposure : `lsst.afw.image.Exposure` 2003 Exposure to process. Only the amplifier DataSec is processed. 2004 amp : `lsst.afw.table.AmpInfoCatalog` 2005 Amplifier detector data. 2009 lsst.ip.isr.isrFunctions.makeThresholdMask 2011 if not math.isnan(amp.getSaturation()):
2012 maskedImage = exposure.getMaskedImage()
2013 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2014 isrFunctions.makeThresholdMask(
2015 maskedImage=dataView,
2016 threshold=amp.getSaturation(),
2018 maskName=self.config.saturatedMaskName,
2022 """!Interpolate over saturated pixels, in place. 2024 This method should be called after `saturationDetection`, to 2025 ensure that the saturated pixels have been identified in the 2026 SAT mask. It should also be called after `assembleCcd`, since 2027 saturated regions may cross amplifier boundaries. 2031 exposure : `lsst.afw.image.Exposure` 2032 Exposure to process. 2036 lsst.ip.isr.isrTask.saturationDetection 2037 lsst.ip.isr.isrFunctions.interpolateFromMask 2039 isrFunctions.interpolateFromMask(
2040 maskedImage=exposure.getMaskedImage(),
2041 fwhm=self.config.fwhm,
2042 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2043 maskNameList=list(self.config.saturatedMaskName),
2047 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 2051 exposure : `lsst.afw.image.Exposure` 2052 Exposure to process. Only the amplifier DataSec is processed. 2053 amp : `lsst.afw.table.AmpInfoCatalog` 2054 Amplifier detector data. 2058 lsst.ip.isr.isrFunctions.makeThresholdMask 2062 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 2063 This is intended to indicate pixels that may be affected by unknown systematics; 2064 for example if non-linearity corrections above a certain level are unstable 2065 then that would be a useful value for suspectLevel. A value of `nan` indicates 2066 that no such level exists and no pixels are to be masked as suspicious. 2068 suspectLevel = amp.getSuspectLevel()
2069 if math.isnan(suspectLevel):
2072 maskedImage = exposure.getMaskedImage()
2073 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2074 isrFunctions.makeThresholdMask(
2075 maskedImage=dataView,
2076 threshold=suspectLevel,
2078 maskName=self.config.suspectMaskName,
2082 """!Mask defects using mask plane "BAD", in place. 2086 exposure : `lsst.afw.image.Exposure` 2087 Exposure to process. 2088 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2089 `lsst.afw.image.DefectBase`. 2090 List of defects to mask. 2094 Call this after CCD assembly, since defects may cross amplifier boundaries. 2096 maskedImage = exposure.getMaskedImage()
2097 if not isinstance(defectBaseList, Defects):
2099 defectList = Defects(defectBaseList)
2101 defectList = defectBaseList
2102 defectList.maskPixels(maskedImage, maskName=
"BAD")
2104 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT"):
2105 """!Mask edge pixels with applicable mask plane. 2109 exposure : `lsst.afw.image.Exposure` 2110 Exposure to process. 2111 numEdgePixels : `int`, optional 2112 Number of edge pixels to mask. 2113 maskPlane : `str`, optional 2114 Mask plane name to use. 2116 maskedImage = exposure.getMaskedImage()
2117 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2119 if numEdgePixels > 0:
2120 goodBBox = maskedImage.getBBox()
2122 goodBBox.grow(-numEdgePixels)
2124 SourceDetectionTask.setEdgeBits(
2131 """Mask and interpolate defects using mask plane "BAD", in place. 2135 exposure : `lsst.afw.image.Exposure` 2136 Exposure to process. 2137 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2138 `lsst.afw.image.DefectBase`. 2139 List of defects to mask and interpolate. 2143 lsst.ip.isr.isrTask.maskDefect() 2146 self.
maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2147 maskPlane=
"SUSPECT")
2148 isrFunctions.interpolateFromMask(
2149 maskedImage=exposure.getMaskedImage(),
2150 fwhm=self.config.fwhm,
2151 growSaturatedFootprints=0,
2152 maskNameList=[
"BAD"],
2156 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2160 exposure : `lsst.afw.image.Exposure` 2161 Exposure to process. 2165 We mask over all NaNs, including those that are masked with 2166 other bits (because those may or may not be interpolated over 2167 later, and we want to remove all NaNs). Despite this 2168 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2169 the historical name. 2171 maskedImage = exposure.getMaskedImage()
2174 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2175 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2176 numNans =
maskNans(maskedImage, maskVal)
2177 self.metadata.set(
"NUMNANS", numNans)
2179 self.log.warn(
"There were %d unmasked NaNs.", numNans)
2182 """"Mask and interpolate NaNs using mask plane "UNMASKEDNAN", in place. 2186 exposure : `lsst.afw.image.Exposure` 2187 Exposure to process. 2191 lsst.ip.isr.isrTask.maskNan() 2194 isrFunctions.interpolateFromMask(
2195 maskedImage=exposure.getMaskedImage(),
2196 fwhm=self.config.fwhm,
2197 growSaturatedFootprints=0,
2198 maskNameList=[
"UNMASKEDNAN"],
2202 """Measure the image background in subgrids, for quality control purposes. 2206 exposure : `lsst.afw.image.Exposure` 2207 Exposure to process. 2208 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2209 Configuration object containing parameters on which background 2210 statistics and subgrids to use. 2212 if IsrQaConfig
is not None:
2213 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2214 IsrQaConfig.flatness.nIter)
2215 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2216 statsControl.setAndMask(maskVal)
2217 maskedImage = exposure.getMaskedImage()
2218 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2219 skyLevel = stats.getValue(afwMath.MEDIAN)
2220 skySigma = stats.getValue(afwMath.STDEVCLIP)
2221 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2222 metadata = exposure.getMetadata()
2223 metadata.set(
'SKYLEVEL', skyLevel)
2224 metadata.set(
'SKYSIGMA', skySigma)
2227 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2228 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2229 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2230 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2231 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2232 skyLevels = numpy.zeros((nX, nY))
2235 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2237 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2239 xLLC = xc - meshXHalf
2240 yLLC = yc - meshYHalf
2241 xURC = xc + meshXHalf - 1
2242 yURC = yc + meshYHalf - 1
2245 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2247 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2249 good = numpy.where(numpy.isfinite(skyLevels))
2250 skyMedian = numpy.median(skyLevels[good])
2251 flatness = (skyLevels[good] - skyMedian) / skyMedian
2252 flatness_rms = numpy.std(flatness)
2253 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2255 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2256 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2257 nX, nY, flatness_pp, flatness_rms)
2259 metadata.set(
'FLATNESS_PP', float(flatness_pp))
2260 metadata.set(
'FLATNESS_RMS', float(flatness_rms))
2261 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2262 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2263 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2266 """Set an approximate magnitude zero point for the exposure. 2270 exposure : `lsst.afw.image.Exposure` 2271 Exposure to process. 2273 filterName = afwImage.Filter(exposure.getFilter().getId()).getName()
2274 if filterName
in self.config.fluxMag0T1:
2275 fluxMag0 = self.config.fluxMag0T1[filterName]
2277 self.log.warn(
"No rough magnitude zero point set for filter %s.", filterName)
2278 fluxMag0 = self.config.defaultFluxMag0T1
2280 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2282 self.log.warn(
"Non-positive exposure time; skipping rough zero point.")
2285 self.log.info(
"Setting rough magnitude zero point: %f", 2.5*math.log10(fluxMag0*expTime))
2286 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2289 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2293 ccdExposure : `lsst.afw.image.Exposure` 2294 Exposure to process. 2295 fpPolygon : `lsst.afw.geom.Polygon` 2296 Polygon in focal plane coordinates. 2299 ccd = ccdExposure.getDetector()
2300 fpCorners = ccd.getCorners(FOCAL_PLANE)
2301 ccdPolygon = Polygon(fpCorners)
2304 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2307 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2308 validPolygon = Polygon(ccdPoints)
2309 ccdExposure.getInfo().setValidPolygon(validPolygon)
2313 """Context manager that applies and removes flats and darks, 2314 if the task is configured to apply them. 2318 exp : `lsst.afw.image.Exposure` 2319 Exposure to process. 2320 flat : `lsst.afw.image.Exposure` 2321 Flat exposure the same size as ``exp``. 2322 dark : `lsst.afw.image.Exposure`, optional 2323 Dark exposure the same size as ``exp``. 2327 exp : `lsst.afw.image.Exposure` 2328 The flat and dark corrected exposure. 2330 if self.config.doDark
and dark
is not None:
2332 if self.config.doFlat:
2337 if self.config.doFlat:
2339 if self.config.doDark
and dark
is not None:
2343 """Utility function to examine ISR exposure at different stages. 2347 exposure : `lsst.afw.image.Exposure` 2350 State of processing to view. 2352 frame = getDebugFrame(self._display, stepname)
2354 display = getDisplay(frame)
2355 display.scale(
'asinh',
'zscale')
2356 display.mtv(exposure)
2357 prompt =
"Press Enter to continue [c]... " 2359 ans = input(prompt).lower()
2360 if ans
in (
"",
"c",):
2365 """A Detector-like object that supports returning gain and saturation level 2367 This is used when the input exposure does not have a detector. 2371 exposure : `lsst.afw.image.Exposure` 2372 Exposure to generate a fake amplifier for. 2373 config : `lsst.ip.isr.isrTaskConfig` 2374 Configuration to apply to the fake amplifier. 2378 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2380 self.
_gain = config.gain
2410 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2414 """Task to wrap the default IsrTask to allow it to be retargeted. 2416 The standard IsrTask can be called directly from a command line 2417 program, but doing so removes the ability of the task to be 2418 retargeted. As most cameras override some set of the IsrTask 2419 methods, this would remove those data-specific methods in the 2420 output post-ISR images. This wrapping class fixes the issue, 2421 allowing identical post-ISR images to be generated by both the 2422 processCcd and isrTask code. 2424 ConfigClass = RunIsrConfig
2425 _DefaultName =
"runIsr" 2429 self.makeSubtask(
"isr")
2435 dataRef : `lsst.daf.persistence.ButlerDataRef` 2436 data reference of the detector data to be processed 2440 result : `pipeBase.Struct` 2441 Result struct with component: 2443 - exposure : `lsst.afw.image.Exposure` 2444 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)