32 from contextlib
import contextmanager
33 from lsstDebug
import getDebugFrame
43 from .
import isrFunctions
45 from .
import linearize
47 from .assembleCcdTask
import AssembleCcdTask
48 from .crosstalk
import CrosstalkTask
49 from .fringe
import FringeTask
50 from .isr
import maskNans
51 from .masking
import MaskingTask
52 from .straylight
import StrayLightTask
53 from .vignette
import VignetteTask
56 __all__ = [
"IsrTask",
"IsrTaskConfig",
"RunIsrTask",
"RunIsrConfig"]
60 """Configuration parameters for IsrTask. 62 Items are grouped in the order in which they are executed by the task. 67 isrName = pexConfig.Field(
74 ccdExposure = pipeBase.InputDatasetField(
75 doc=
"Input exposure to process",
78 storageClass=
"Exposure",
79 dimensions=[
"instrument",
"exposure",
"detector"],
81 camera = pipeBase.InputDatasetField(
82 doc=
"Input camera to construct complete exposures.",
85 storageClass=
"Camera",
86 dimensions=[
"instrument",
"calibration_label"],
88 bias = pipeBase.InputDatasetField(
89 doc=
"Input bias calibration.",
92 storageClass=
"ImageF",
93 dimensions=[
"instrument",
"calibration_label",
"detector"],
95 dark = pipeBase.InputDatasetField(
96 doc=
"Input dark calibration.",
99 storageClass=
"ImageF",
100 dimensions=[
"instrument",
"calibration_label",
"detector"],
102 flat = pipeBase.InputDatasetField(
103 doc=
"Input flat calibration.",
106 storageClass=
"MaskedImageF",
107 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
109 bfKernel = pipeBase.InputDatasetField(
110 doc=
"Input brighter-fatter kernel.",
113 storageClass=
"NumpyArray",
114 dimensions=[
"instrument",
"calibration_label"],
116 defects = pipeBase.InputDatasetField(
117 doc=
"Input defect tables.",
120 storageClass=
"DefectsList",
121 dimensions=[
"instrument",
"calibration_label",
"detector"],
123 opticsTransmission = pipeBase.InputDatasetField(
124 doc=
"Transmission curve due to the optics.",
125 name=
"transmission_optics",
127 storageClass=
"TransmissionCurve",
128 dimensions=[
"instrument",
"calibration_label"],
130 filterTransmission = pipeBase.InputDatasetField(
131 doc=
"Transmission curve due to the filter.",
132 name=
"transmission_filter",
134 storageClass=
"TransmissionCurve",
135 dimensions=[
"instrument",
"physical_filter",
"calibration_label"],
137 sensorTransmission = pipeBase.InputDatasetField(
138 doc=
"Transmission curve due to the sensor.",
139 name=
"transmission_sensor",
141 storageClass=
"TransmissionCurve",
142 dimensions=[
"instrument",
"calibration_label",
"detector"],
144 atmosphereTransmission = pipeBase.InputDatasetField(
145 doc=
"Transmission curve due to the atmosphere.",
146 name=
"transmission_atmosphere",
148 storageClass=
"TransmissionCurve",
149 dimensions=[
"instrument"],
151 illumMaskedImage = pipeBase.InputDatasetField(
152 doc=
"Input illumination correction.",
155 storageClass=
"MaskedImageF",
156 dimensions=[
"instrument",
"physical_filter",
"calibration_label",
"detector"],
160 outputExposure = pipeBase.OutputDatasetField(
161 doc=
"Output ISR processed exposure.",
164 storageClass=
"ExposureF",
165 dimensions=[
"instrument",
"visit",
"detector"],
167 outputOssThumbnail = pipeBase.OutputDatasetField(
168 doc=
"Output Overscan-subtracted thumbnail image.",
171 storageClass=
"Thumbnail",
172 dimensions=[
"instrument",
"visit",
"detector"],
174 outputFlattenedThumbnail = pipeBase.OutputDatasetField(
175 doc=
"Output flat-corrected thumbnail image.",
176 name=
"FlattenedThumb",
178 storageClass=
"TextStorage",
179 dimensions=[
"instrument",
"visit",
"detector"],
182 quantum = pipeBase.QuantumConfig(
183 dimensions=[
"visit",
"detector",
"instrument"],
187 datasetType = pexConfig.Field(
189 doc=
"Dataset type for input data; users will typically leave this alone, " 190 "but camera-specific ISR tasks will override it",
194 fallbackFilterName = pexConfig.Field(
196 doc=
"Fallback default filter name for calibrations.",
199 expectWcs = pexConfig.Field(
202 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)." 204 fwhm = pexConfig.Field(
206 doc=
"FWHM of PSF in arcseconds.",
209 qa = pexConfig.ConfigField(
211 doc=
"QA related configuration options.",
215 doConvertIntToFloat = pexConfig.Field(
217 doc=
"Convert integer raw images to floating point values?",
222 doSaturation = pexConfig.Field(
224 doc=
"Mask saturated pixels? NB: this is totally independent of the" 225 " interpolation option - this is ONLY setting the bits in the mask." 226 " To have them interpolated make sure doSaturationInterpolation=True",
229 saturatedMaskName = pexConfig.Field(
231 doc=
"Name of mask plane to use in saturation detection and interpolation",
234 saturation = pexConfig.Field(
236 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
237 default=float(
"NaN"),
239 growSaturationFootprintSize = pexConfig.Field(
241 doc=
"Number of pixels by which to grow the saturation footprints",
246 doSuspect = pexConfig.Field(
248 doc=
"Mask suspect pixels?",
251 suspectMaskName = pexConfig.Field(
253 doc=
"Name of mask plane to use for suspect pixels",
256 numEdgeSuspect = pexConfig.Field(
258 doc=
"Number of edge pixels to be flagged as untrustworthy.",
263 doSetBadRegions = pexConfig.Field(
265 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
268 badStatistic = pexConfig.ChoiceField(
270 doc=
"How to estimate the average value for BAD regions.",
273 "MEANCLIP":
"Correct using the (clipped) mean of good data",
274 "MEDIAN":
"Correct using the median of the good data",
279 doOverscan = pexConfig.Field(
281 doc=
"Do overscan subtraction?",
284 overscanFitType = pexConfig.ChoiceField(
286 doc=
"The method for fitting the overscan bias level.",
289 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
290 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
291 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
292 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
293 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
294 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
295 "MEAN":
"Correct using the mean of the overscan region",
296 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
297 "MEDIAN":
"Correct using the median of the overscan region",
300 overscanOrder = pexConfig.Field(
302 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
303 "or number of spline knots if overscan fit type is a spline."),
306 overscanNumSigmaClip = pexConfig.Field(
308 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
311 overscanIsInt = pexConfig.Field(
313 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN",
316 overscanNumLeadingColumnsToSkip = pexConfig.Field(
318 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
321 overscanNumTrailingColumnsToSkip = pexConfig.Field(
323 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
326 overscanMaxDev = pexConfig.Field(
328 doc=
"Maximum deviation from the median for overscan",
329 default=1000.0, check=
lambda x: x > 0
331 overscanBiasJump = pexConfig.Field(
333 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
336 overscanBiasJumpKeyword = pexConfig.Field(
338 doc=
"Header keyword containing information about devices.",
339 default=
"NO_SUCH_KEY",
341 overscanBiasJumpDevices = pexConfig.ListField(
343 doc=
"List of devices that need piecewise overscan correction.",
346 overscanBiasJumpLocation = pexConfig.Field(
348 doc=
"Location of bias jump along y-axis.",
353 doAssembleCcd = pexConfig.Field(
356 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 358 assembleCcd = pexConfig.ConfigurableField(
359 target=AssembleCcdTask,
360 doc=
"CCD assembly task",
364 doAssembleIsrExposures = pexConfig.Field(
367 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 369 doTrimToMatchCalib = pexConfig.Field(
372 doc=
"Trim raw data to match calibration bounding boxes?" 376 doBias = pexConfig.Field(
378 doc=
"Apply bias frame correction?",
381 biasDataProductName = pexConfig.Field(
383 doc=
"Name of the bias data product",
388 doVariance = pexConfig.Field(
390 doc=
"Calculate variance?",
393 gain = pexConfig.Field(
395 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
396 default=float(
"NaN"),
398 readNoise = pexConfig.Field(
400 doc=
"The read noise to use if no Detector is present in the Exposure",
403 doEmpiricalReadNoise = pexConfig.Field(
406 doc=
"Calculate empirical read noise instead of value from AmpInfo data?" 410 doLinearize = pexConfig.Field(
412 doc=
"Correct for nonlinearity of the detector's response?",
417 doCrosstalk = pexConfig.Field(
419 doc=
"Apply intra-CCD crosstalk correction?",
422 doCrosstalkBeforeAssemble = pexConfig.Field(
424 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
427 crosstalk = pexConfig.ConfigurableField(
428 target=CrosstalkTask,
429 doc=
"Intra-CCD crosstalk correction",
433 doDefect = pexConfig.Field(
435 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
438 doNanMasking = pexConfig.Field(
440 doc=
"Mask NAN pixels?",
443 doWidenSaturationTrails = pexConfig.Field(
445 doc=
"Widen bleed trails based on their width?",
450 doBrighterFatter = pexConfig.Field(
453 doc=
"Apply the brighter fatter correction" 455 brighterFatterLevel = pexConfig.ChoiceField(
458 doc=
"The level at which to correct for brighter-fatter.",
460 "AMP":
"Every amplifier treated separately.",
461 "DETECTOR":
"One kernel per detector",
464 brighterFatterKernelFile = pexConfig.Field(
467 doc=
"Kernel file used for the brighter fatter correction" 469 brighterFatterMaxIter = pexConfig.Field(
472 doc=
"Maximum number of iterations for the brighter fatter correction" 474 brighterFatterThreshold = pexConfig.Field(
477 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 478 " absolute value of the difference between the current corrected image and the one" 479 " from the previous iteration summed over all the pixels." 481 brighterFatterApplyGain = pexConfig.Field(
484 doc=
"Should the gain be applied when applying the brighter fatter correction?" 488 doDark = pexConfig.Field(
490 doc=
"Apply dark frame correction?",
493 darkDataProductName = pexConfig.Field(
495 doc=
"Name of the dark data product",
500 doStrayLight = pexConfig.Field(
502 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
505 strayLight = pexConfig.ConfigurableField(
506 target=StrayLightTask,
507 doc=
"y-band stray light correction" 511 doFlat = pexConfig.Field(
513 doc=
"Apply flat field correction?",
516 flatDataProductName = pexConfig.Field(
518 doc=
"Name of the flat data product",
521 flatScalingType = pexConfig.ChoiceField(
523 doc=
"The method for scaling the flat on the fly.",
526 "USER":
"Scale by flatUserScale",
527 "MEAN":
"Scale by the inverse of the mean",
528 "MEDIAN":
"Scale by the inverse of the median",
531 flatUserScale = pexConfig.Field(
533 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
536 doTweakFlat = pexConfig.Field(
538 doc=
"Tweak flats to match observed amplifier ratios?",
543 doApplyGains = pexConfig.Field(
545 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
548 normalizeGains = pexConfig.Field(
550 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
555 doFringe = pexConfig.Field(
557 doc=
"Apply fringe correction?",
560 fringe = pexConfig.ConfigurableField(
562 doc=
"Fringe subtraction task",
564 fringeAfterFlat = pexConfig.Field(
566 doc=
"Do fringe subtraction after flat-fielding?",
571 doAddDistortionModel = pexConfig.Field(
573 doc=
"Apply a distortion model based on camera geometry to the WCS?",
578 doMeasureBackground = pexConfig.Field(
580 doc=
"Measure the background level on the reduced image?",
585 doCameraSpecificMasking = pexConfig.Field(
587 doc=
"Mask camera-specific bad regions?",
590 masking = pexConfig.ConfigurableField(
597 doInterpolate = pexConfig.Field(
599 doc=
"Interpolate masked pixels?",
602 doSaturationInterpolation = pexConfig.Field(
604 doc=
"Perform interpolation over pixels masked as saturated?" 605 " NB: This is independent of doSaturation; if that is False this plane" 606 " will likely be blank, resulting in a no-op here.",
609 doNanInterpolation = pexConfig.Field(
611 doc=
"Perform interpolation over pixels masked as NaN?" 612 " NB: This is independent of doNanMasking; if that is False this plane" 613 " will likely be blank, resulting in a no-op here.",
616 doNanInterpAfterFlat = pexConfig.Field(
618 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 619 "also have to interpolate them before flat-fielding."),
622 maskListToInterpolate = pexConfig.ListField(
624 doc=
"List of mask planes that should be interpolated.",
625 default=[
'SAT',
'BAD',
'UNMASKEDNAN'],
627 doSaveInterpPixels = pexConfig.Field(
629 doc=
"Save a copy of the pre-interpolated pixel values?",
634 fluxMag0T1 = pexConfig.DictField(
637 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
638 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
641 defaultFluxMag0T1 = pexConfig.Field(
643 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
644 default=pow(10.0, 0.4*28.0)
648 doVignette = pexConfig.Field(
650 doc=
"Apply vignetting parameters?",
653 vignette = pexConfig.ConfigurableField(
655 doc=
"Vignetting task.",
659 doAttachTransmissionCurve = pexConfig.Field(
662 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 664 doUseOpticsTransmission = pexConfig.Field(
667 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 669 doUseFilterTransmission = pexConfig.Field(
672 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 674 doUseSensorTransmission = pexConfig.Field(
677 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 679 doUseAtmosphereTransmission = pexConfig.Field(
682 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 686 doIlluminationCorrection = pexConfig.Field(
689 doc=
"Perform illumination correction?" 691 illuminationCorrectionDataProductName = pexConfig.Field(
693 doc=
"Name of the illumination correction data product.",
696 illumScale = pexConfig.Field(
698 doc=
"Scale factor for the illumination correction.",
701 illumFilters = pexConfig.ListField(
704 doc=
"Only perform illumination correction for these filters." 708 doWrite = pexConfig.Field(
710 doc=
"Persist postISRCCD?",
717 raise ValueError(
"You may not specify both doFlat and doApplyGains")
719 self.config.maskListToInterpolate.append(
"SAT")
721 self.config.maskListToInterpolate.append(
"UNMASKEDNAN")
724 class IsrTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
725 """Apply common instrument signature correction algorithms to a raw frame. 727 The process for correcting imaging data is very similar from 728 camera to camera. This task provides a vanilla implementation of 729 doing these corrections, including the ability to turn certain 730 corrections off if they are not needed. The inputs to the primary 731 method, `run()`, are a raw exposure to be corrected and the 732 calibration data products. The raw input is a single chip sized 733 mosaic of all amps including overscans and other non-science 734 pixels. The method `runDataRef()` identifies and defines the 735 calibration data products, and is intended for use by a 736 `lsst.pipe.base.cmdLineTask.CmdLineTask` and takes as input only a 737 `daf.persistence.butlerSubset.ButlerDataRef`. This task may be 738 subclassed for different camera, although the most camera specific 739 methods have been split into subtasks that can be redirected 742 The __init__ method sets up the subtasks for ISR processing, using 743 the defaults from `lsst.ip.isr`. 748 Positional arguments passed to the Task constructor. None used at this time. 749 kwargs : `dict`, optional 750 Keyword arguments passed on to the Task constructor. None used at this time. 752 ConfigClass = IsrTaskConfig
757 self.makeSubtask(
"assembleCcd")
758 self.makeSubtask(
"crosstalk")
759 self.makeSubtask(
"strayLight")
760 self.makeSubtask(
"fringe")
761 self.makeSubtask(
"masking")
762 self.makeSubtask(
"vignette")
771 if config.doBias
is not True:
772 inputTypeDict.pop(
"bias",
None)
773 if config.doLinearize
is not True:
774 inputTypeDict.pop(
"linearizer",
None)
775 if config.doCrosstalk
is not True:
776 inputTypeDict.pop(
"crosstalkSources",
None)
777 if config.doBrighterFatter
is not True:
778 inputTypeDict.pop(
"bfKernel",
None)
779 if config.doDefect
is not True:
780 inputTypeDict.pop(
"defects",
None)
781 if config.doDark
is not True:
782 inputTypeDict.pop(
"dark",
None)
783 if config.doFlat
is not True:
784 inputTypeDict.pop(
"flat",
None)
785 if config.doAttachTransmissionCurve
is not True:
786 inputTypeDict.pop(
"opticsTransmission",
None)
787 inputTypeDict.pop(
"filterTransmission",
None)
788 inputTypeDict.pop(
"sensorTransmission",
None)
789 inputTypeDict.pop(
"atmosphereTransmission",
None)
790 if config.doUseOpticsTransmission
is not True:
791 inputTypeDict.pop(
"opticsTransmission",
None)
792 if config.doUseFilterTransmission
is not True:
793 inputTypeDict.pop(
"filterTransmission",
None)
794 if config.doUseSensorTransmission
is not True:
795 inputTypeDict.pop(
"sensorTransmission",
None)
796 if config.doUseAtmosphereTransmission
is not True:
797 inputTypeDict.pop(
"atmosphereTransmission",
None)
798 if config.doIlluminationCorrection
is not True:
799 inputTypeDict.pop(
"illumMaskedImage",
None)
807 if config.qa.doThumbnailOss
is not True:
808 outputTypeDict.pop(
"outputOssThumbnail",
None)
809 if config.qa.doThumbnailFlattened
is not True:
810 outputTypeDict.pop(
"outputFlattenedThumbnail",
None)
811 if config.doWrite
is not True:
812 outputTypeDict.pop(
"outputExposure",
None)
814 return outputTypeDict
823 names.remove(
"ccdExposure")
828 inputData[
'detectorNum'] = int(inputDataIds[
'ccdExposure'][
'detector'])
829 except Exception
as e:
830 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
833 inputData[
'isGen3'] =
True 835 if self.config.doLinearize
is True:
836 if 'linearizer' not in inputData.keys():
837 detector = inputData[
'camera'][inputData[
'detectorNum']]
838 linearityName = detector.getAmpInfoCatalog()[0].getLinearityType()
839 inputData[
'linearizer'] = linearize.getLinearityTypeByName(linearityName)()
841 if inputData[
'defects']
is not None:
844 if not isinstance(inputData[
"defects"], Defects):
845 inputData[
"defects"] = Defects.fromTable(inputData[
"defects"])
862 return super().
adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
868 """!Retrieve necessary frames for instrument signature removal. 870 Pre-fetching all required ISR data products limits the IO 871 required by the ISR. Any conflict between the calibration data 872 available and that needed for ISR is also detected prior to 873 doing processing, allowing it to fail quickly. 877 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 878 Butler reference of the detector data to be processed 879 rawExposure : `afw.image.Exposure` 880 The raw exposure that will later be corrected with the 881 retrieved calibration data; should not be modified in this 886 result : `lsst.pipe.base.Struct` 887 Result struct with components (which may be `None`): 888 - ``bias``: bias calibration frame (`afw.image.Exposure`) 889 - ``linearizer``: functor for linearization (`ip.isr.linearize.LinearizeBase`) 890 - ``crosstalkSources``: list of possible crosstalk sources (`list`) 891 - ``dark``: dark calibration frame (`afw.image.Exposure`) 892 - ``flat``: flat calibration frame (`afw.image.Exposure`) 893 - ``bfKernel``: Brighter-Fatter kernel (`numpy.ndarray`) 894 - ``defects``: list of defects (`lsst.meas.algorithms.Defects`) 895 - ``fringes``: `lsst.pipe.base.Struct` with components: 896 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 897 - ``seed``: random seed derived from the ccdExposureId for random 898 number generator (`uint32`). 899 - ``opticsTransmission``: `lsst.afw.image.TransmissionCurve` 900 A ``TransmissionCurve`` that represents the throughput of the optics, 901 to be evaluated in focal-plane coordinates. 902 - ``filterTransmission`` : `lsst.afw.image.TransmissionCurve` 903 A ``TransmissionCurve`` that represents the throughput of the filter 904 itself, to be evaluated in focal-plane coordinates. 905 - ``sensorTransmission`` : `lsst.afw.image.TransmissionCurve` 906 A ``TransmissionCurve`` that represents the throughput of the sensor 907 itself, to be evaluated in post-assembly trimmed detector coordinates. 908 - ``atmosphereTransmission`` : `lsst.afw.image.TransmissionCurve` 909 A ``TransmissionCurve`` that represents the throughput of the 910 atmosphere, assumed to be spatially constant. 911 - ``strayLightData`` : `object` 912 An opaque object containing calibration information for 913 stray-light correction. If `None`, no correction will be 915 - ``illumMaskedImage`` : illumination correction image (`lsst.afw.image.MaskedImage`) 919 NotImplementedError : 920 Raised if a per-amplifier brighter-fatter kernel is requested by the configuration. 922 ccd = rawExposure.getDetector()
923 filterName = afwImage.Filter(rawExposure.getFilter().getId()).getName()
924 rawExposure.mask.addMaskPlane(
"UNMASKEDNAN")
925 biasExposure = (self.
getIsrExposure(dataRef, self.config.biasDataProductName)
926 if self.config.doBias
else None)
928 linearizer = (dataRef.get(
"linearizer", immediate=
True)
930 crosstalkSources = (self.crosstalk.prepCrosstalk(dataRef)
931 if self.config.doCrosstalk
else None)
932 darkExposure = (self.
getIsrExposure(dataRef, self.config.darkDataProductName)
933 if self.config.doDark
else None)
934 flatExposure = (self.
getIsrExposure(dataRef, self.config.flatDataProductName)
935 if self.config.doFlat
else None)
937 brighterFatterKernel =
None 938 if self.config.doBrighterFatter
is True:
942 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
946 brighterFatterKernel = dataRef.get(
"bfKernel")
948 brighterFatterKernel =
None 949 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
952 if self.config.brighterFatterLevel ==
'DETECTOR':
953 brighterFatterKernel = brighterFatterKernel.kernel[ccd.getId()]
956 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
958 defectList = (dataRef.get(
"defects")
959 if self.config.doDefect
else None)
960 fringeStruct = (self.fringe.readFringes(dataRef, assembler=self.assembleCcd
961 if self.config.doAssembleIsrExposures
else None)
962 if self.config.doFringe
and self.fringe.checkFilter(rawExposure)
963 else pipeBase.Struct(fringes=
None))
965 if self.config.doAttachTransmissionCurve:
966 opticsTransmission = (dataRef.get(
"transmission_optics")
967 if self.config.doUseOpticsTransmission
else None)
968 filterTransmission = (dataRef.get(
"transmission_filter")
969 if self.config.doUseFilterTransmission
else None)
970 sensorTransmission = (dataRef.get(
"transmission_sensor")
971 if self.config.doUseSensorTransmission
else None)
972 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
973 if self.config.doUseAtmosphereTransmission
else None)
975 opticsTransmission =
None 976 filterTransmission =
None 977 sensorTransmission =
None 978 atmosphereTransmission =
None 980 if self.config.doStrayLight:
981 strayLightData = self.strayLight.
readIsrData(dataRef, rawExposure)
983 strayLightData =
None 986 self.config.illuminationCorrectionDataProductName).getMaskedImage()
987 if (self.config.doIlluminationCorrection
and 988 filterName
in self.config.illumFilters)
992 return pipeBase.Struct(bias=biasExposure,
993 linearizer=linearizer,
994 crosstalkSources=crosstalkSources,
997 bfKernel=brighterFatterKernel,
999 fringes=fringeStruct,
1000 opticsTransmission=opticsTransmission,
1001 filterTransmission=filterTransmission,
1002 sensorTransmission=sensorTransmission,
1003 atmosphereTransmission=atmosphereTransmission,
1004 strayLightData=strayLightData,
1005 illumMaskedImage=illumMaskedImage
1008 @pipeBase.timeMethod
1009 def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None,
1010 dark=None, flat=None, bfKernel=None, defects=None, fringes=pipeBase.Struct(fringes=
None),
1011 opticsTransmission=
None, filterTransmission=
None,
1012 sensorTransmission=
None, atmosphereTransmission=
None,
1013 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1016 """!Perform instrument signature removal on an exposure. 1018 Steps included in the ISR processing, in order performed, are: 1019 - saturation and suspect pixel masking 1020 - overscan subtraction 1021 - CCD assembly of individual amplifiers 1023 - variance image construction 1024 - linearization of non-linear response 1026 - brighter-fatter correction 1029 - stray light subtraction 1031 - masking of known defects and camera specific features 1032 - vignette calculation 1033 - appending transmission curve and distortion model 1037 ccdExposure : `lsst.afw.image.Exposure` 1038 The raw exposure that is to be run through ISR. The 1039 exposure is modified by this method. 1040 camera : `lsst.afw.cameraGeom.Camera`, optional 1041 The camera geometry for this exposure. Used to select the 1042 distortion model appropriate for this data. 1043 bias : `lsst.afw.image.Exposure`, optional 1044 Bias calibration frame. 1045 linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional 1046 Functor for linearization. 1047 crosstalkSources : `list`, optional 1048 List of possible crosstalk sources. 1049 dark : `lsst.afw.image.Exposure`, optional 1050 Dark calibration frame. 1051 flat : `lsst.afw.image.Exposure`, optional 1052 Flat calibration frame. 1053 bfKernel : `numpy.ndarray`, optional 1054 Brighter-fatter kernel. 1055 defects : `lsst.meas.algorithms.Defects`, optional 1057 fringes : `lsst.pipe.base.Struct`, optional 1058 Struct containing the fringe correction data, with 1060 - ``fringes``: fringe calibration frame (`afw.image.Exposure`) 1061 - ``seed``: random seed derived from the ccdExposureId for random 1062 number generator (`uint32`) 1063 opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional 1064 A ``TransmissionCurve`` that represents the throughput of the optics, 1065 to be evaluated in focal-plane coordinates. 1066 filterTransmission : `lsst.afw.image.TransmissionCurve` 1067 A ``TransmissionCurve`` that represents the throughput of the filter 1068 itself, to be evaluated in focal-plane coordinates. 1069 sensorTransmission : `lsst.afw.image.TransmissionCurve` 1070 A ``TransmissionCurve`` that represents the throughput of the sensor 1071 itself, to be evaluated in post-assembly trimmed detector coordinates. 1072 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 1073 A ``TransmissionCurve`` that represents the throughput of the 1074 atmosphere, assumed to be spatially constant. 1075 detectorNum : `int`, optional 1076 The integer number for the detector to process. 1077 isGen3 : bool, optional 1078 Flag this call to run() as using the Gen3 butler environment. 1079 strayLightData : `object`, optional 1080 Opaque object containing calibration information for stray-light 1081 correction. If `None`, no correction will be performed. 1082 illumMaskedImage : `lsst.afw.image.MaskedImage`, optional 1083 Illumination correction image. 1087 result : `lsst.pipe.base.Struct` 1088 Result struct with component: 1089 - ``exposure`` : `afw.image.Exposure` 1090 The fully ISR corrected exposure. 1091 - ``outputExposure`` : `afw.image.Exposure` 1092 An alias for `exposure` 1093 - ``ossThumb`` : `numpy.ndarray` 1094 Thumbnail image of the exposure after overscan subtraction. 1095 - ``flattenedThumb`` : `numpy.ndarray` 1096 Thumbnail image of the exposure after flat-field correction. 1101 Raised if a configuration option is set to True, but the 1102 required calibration data has not been specified. 1106 The current processed exposure can be viewed by setting the 1107 appropriate lsstDebug entries in the `debug.display` 1108 dictionary. The names of these entries correspond to some of 1109 the IsrTaskConfig Boolean options, with the value denoting the 1110 frame to use. The exposure is shown inside the matching 1111 option check and after the processing of that step has 1112 finished. The steps with debug points are: 1123 In addition, setting the "postISRCCD" entry displays the 1124 exposure after all ISR processing has finished. 1132 self.config.doFringe =
False 1135 if detectorNum
is None:
1136 raise RuntimeError(
"Must supply the detectorNum if running as Gen3.")
1138 ccdExposure = self.
ensureExposure(ccdExposure, camera, detectorNum)
1143 if isinstance(ccdExposure, ButlerDataRef):
1146 ccd = ccdExposure.getDetector()
1147 filterName = afwImage.Filter(ccdExposure.getFilter().getId()).getName()
1150 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd." 1151 ccd = [
FakeAmp(ccdExposure, self.config)]
1154 if self.config.doBias
and bias
is None:
1155 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1157 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1158 if self.config.doBrighterFatter
and bfKernel
is None:
1159 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1160 if self.config.doDark
and dark
is None:
1161 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1162 if self.config.doFlat
and flat
is None:
1163 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1164 if self.config.doDefect
and defects
is None:
1165 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1166 if self.config.doAddDistortionModel
and camera
is None:
1167 raise RuntimeError(
"Must supply camera if config.doAddDistortionModel=True.")
1168 if (self.config.doFringe
and filterName
in self.fringe.config.filters
and 1169 fringes.fringes
is None):
1174 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1175 if (self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters
and 1176 illumMaskedImage
is None):
1177 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1180 if self.config.doConvertIntToFloat:
1181 self.log.info(
"Converting exposure to floating point values.")
1188 if ccdExposure.getBBox().contains(amp.getBBox()):
1192 if self.config.doOverscan
and not badAmp:
1195 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1196 if overscanResults
is not None and \
1197 self.config.qa
is not None and self.config.qa.saveStats
is True:
1198 if isinstance(overscanResults.overscanFit, float):
1199 qaMedian = overscanResults.overscanFit
1200 qaStdev = float(
"NaN")
1202 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1203 afwMath.MEDIAN | afwMath.STDEVCLIP)
1204 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1205 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1207 self.metadata.set(f
"ISR OSCAN {amp.getName()} MEDIAN", qaMedian)
1208 self.metadata.set(f
"ISR OSCAN {amp.getName()} STDEV", qaStdev)
1209 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1210 amp.getName(), qaMedian, qaStdev)
1211 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1214 self.log.warn(
"Amplifier %s is bad.", amp.getName())
1215 overscanResults =
None 1217 overscans.append(overscanResults
if overscanResults
is not None else None)
1219 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1221 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1222 self.log.info(
"Applying crosstalk correction.")
1223 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources)
1224 self.
debugView(ccdExposure,
"doCrosstalk")
1226 if self.config.doAssembleCcd:
1227 self.log.info(
"Assembling CCD from amplifiers.")
1228 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1230 if self.config.expectWcs
and not ccdExposure.getWcs():
1231 self.log.warn(
"No WCS found in input exposure.")
1232 self.
debugView(ccdExposure,
"doAssembleCcd")
1235 if self.config.qa.doThumbnailOss:
1236 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1238 if self.config.doBias:
1239 self.log.info(
"Applying bias correction.")
1240 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1241 trimToFit=self.config.doTrimToMatchCalib)
1244 if self.config.doVariance:
1245 for amp, overscanResults
in zip(ccd, overscans):
1246 if ccdExposure.getBBox().contains(amp.getBBox()):
1247 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1248 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1249 if overscanResults
is not None:
1251 overscanImage=overscanResults.overscanImage)
1255 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1256 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1257 afwMath.MEDIAN | afwMath.STDEVCLIP)
1258 self.metadata.set(f
"ISR VARIANCE {amp.getName()} MEDIAN",
1259 qaStats.getValue(afwMath.MEDIAN))
1260 self.metadata.set(f
"ISR VARIANCE {amp.getName()} STDEV",
1261 qaStats.getValue(afwMath.STDEVCLIP))
1262 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1263 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1264 qaStats.getValue(afwMath.STDEVCLIP))
1267 self.log.info(
"Applying linearizer.")
1268 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
1270 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1271 self.log.info(
"Applying crosstalk correction.")
1272 self.crosstalk.
run(ccdExposure, crosstalkSources=crosstalkSources, isTrimmed=
True)
1273 self.
debugView(ccdExposure,
"doCrosstalk")
1277 if self.config.doDefect:
1278 self.log.info(
"Masking defects.")
1281 if self.config.numEdgeSuspect > 0:
1282 self.log.info(
"Masking edges as SUSPECT.")
1283 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1284 maskPlane=
"SUSPECT")
1286 if self.config.doNanMasking:
1287 self.log.info(
"Masking NAN value pixels.")
1290 if self.config.doWidenSaturationTrails:
1291 self.log.info(
"Widening saturation trails.")
1292 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1294 if self.config.doCameraSpecificMasking:
1295 self.log.info(
"Masking regions for camera specific reasons.")
1296 self.masking.
run(ccdExposure)
1298 if self.config.doBrighterFatter:
1307 interpExp = ccdExposure.clone()
1309 isrFunctions.interpolateFromMask(
1310 maskedImage=interpExp.getMaskedImage(),
1311 fwhm=self.config.fwhm,
1312 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1313 maskNameList=self.config.maskListToInterpolate
1315 bfExp = interpExp.clone()
1317 self.log.info(
"Applying brighter fatter correction.")
1318 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1319 self.config.brighterFatterMaxIter,
1320 self.config.brighterFatterThreshold,
1321 self.config.brighterFatterApplyGain
1323 if bfResults[1] == self.config.brighterFatterMaxIter:
1324 self.log.warn(
"Brighter fatter correction did not converge, final difference %f.",
1327 self.log.info(
"Finished brighter fatter correction in %d iterations.",
1329 image = ccdExposure.getMaskedImage().getImage()
1330 bfCorr = bfExp.getMaskedImage().getImage()
1331 bfCorr -= interpExp.getMaskedImage().getImage()
1340 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1342 self.log.warn(
"Ensuring image edges are masked as SUSPECT to the brighter-fatter kernel size.")
1344 self.
debugView(ccdExposure,
"doBrighterFatter")
1346 if self.config.doDark:
1347 self.log.info(
"Applying dark correction.")
1351 if self.config.doFringe
and not self.config.fringeAfterFlat:
1352 self.log.info(
"Applying fringe correction before flat.")
1353 self.fringe.
run(ccdExposure, **fringes.getDict())
1356 if self.config.doStrayLight:
1357 if strayLightData
is not None:
1358 self.log.info(
"Applying stray light correction.")
1359 self.strayLight.
run(ccdExposure, strayLightData)
1360 self.
debugView(ccdExposure,
"doStrayLight")
1362 self.log.debug(
"Skipping stray light correction: no data found for this image.")
1364 if self.config.doFlat:
1365 self.log.info(
"Applying flat correction.")
1369 if self.config.doApplyGains:
1370 self.log.info(
"Applying gain correction instead of flat.")
1371 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1373 if self.config.doFringe
and self.config.fringeAfterFlat:
1374 self.log.info(
"Applying fringe correction after flat.")
1375 self.fringe.
run(ccdExposure, **fringes.getDict())
1377 if self.config.doVignette:
1378 self.log.info(
"Constructing Vignette polygon.")
1381 if self.config.vignette.doWriteVignettePolygon:
1384 if self.config.doAttachTransmissionCurve:
1385 self.log.info(
"Adding transmission curves.")
1386 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1387 filterTransmission=filterTransmission,
1388 sensorTransmission=sensorTransmission,
1389 atmosphereTransmission=atmosphereTransmission)
1391 if self.config.doAddDistortionModel:
1392 self.log.info(
"Adding a distortion model to the WCS.")
1393 isrFunctions.addDistortionModel(exposure=ccdExposure, camera=camera)
1395 flattenedThumb =
None 1396 if self.config.qa.doThumbnailFlattened:
1397 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1399 if self.config.doIlluminationCorrection
and filterName
in self.config.illumFilters:
1400 self.log.info(
"Performing illumination correction.")
1401 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1402 illumMaskedImage, illumScale=self.config.illumScale,
1403 trimToFit=self.config.doTrimToMatchCalib)
1406 if self.config.doSaveInterpPixels:
1407 preInterpExp = ccdExposure.clone()
1422 if self.config.doSetBadRegions:
1423 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1424 if badPixelCount > 0:
1425 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1427 if self.config.doInterpolate:
1428 self.log.info(
"Interpolating masked pixels.")
1429 isrFunctions.interpolateFromMask(
1430 maskedImage=ccdExposure.getMaskedImage(),
1431 fwhm=self.config.fwhm,
1432 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1433 maskNameList=list(self.config.maskListToInterpolate)
1438 if self.config.doMeasureBackground:
1439 self.log.info(
"Measuring background level.")
1442 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1444 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1445 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1446 afwMath.MEDIAN | afwMath.STDEVCLIP)
1447 self.metadata.set(
"ISR BACKGROUND {} MEDIAN".format(amp.getName()),
1448 qaStats.getValue(afwMath.MEDIAN))
1449 self.metadata.set(
"ISR BACKGROUND {} STDEV".format(amp.getName()),
1450 qaStats.getValue(afwMath.STDEVCLIP))
1451 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1452 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1453 qaStats.getValue(afwMath.STDEVCLIP))
1455 self.
debugView(ccdExposure,
"postISRCCD")
1457 return pipeBase.Struct(
1458 exposure=ccdExposure,
1460 flattenedThumb=flattenedThumb,
1462 preInterpolatedExposure=preInterpExp,
1463 outputExposure=ccdExposure,
1464 outputOssThumbnail=ossThumb,
1465 outputFlattenedThumbnail=flattenedThumb,
1468 @pipeBase.timeMethod
1470 """Perform instrument signature removal on a ButlerDataRef of a Sensor. 1472 This method contains the `CmdLineTask` interface to the ISR 1473 processing. All IO is handled here, freeing the `run()` method 1474 to manage only pixel-level calculations. The steps performed 1476 - Read in necessary detrending/isr/calibration data. 1477 - Process raw exposure in `run()`. 1478 - Persist the ISR-corrected exposure as "postISRCCD" if 1479 config.doWrite=True. 1483 sensorRef : `daf.persistence.butlerSubset.ButlerDataRef` 1484 DataRef of the detector data to be processed 1488 result : `lsst.pipe.base.Struct` 1489 Result struct with component: 1490 - ``exposure`` : `afw.image.Exposure` 1491 The fully ISR corrected exposure. 1496 Raised if a configuration option is set to True, but the 1497 required calibration data does not exist. 1500 self.log.info(
"Performing ISR on sensor %s.", sensorRef.dataId)
1502 ccdExposure = sensorRef.get(self.config.datasetType)
1504 camera = sensorRef.get(
"camera")
1505 if camera
is None and self.config.doAddDistortionModel:
1506 raise RuntimeError(
"config.doAddDistortionModel is True " 1507 "but could not get a camera from the butler.")
1508 isrData = self.
readIsrData(sensorRef, ccdExposure)
1510 result = self.
run(ccdExposure, camera=camera, **isrData.getDict())
1512 if self.config.doWrite:
1513 sensorRef.put(result.exposure,
"postISRCCD")
1514 if result.preInterpolatedExposure
is not None:
1515 sensorRef.put(result.preInterpolatedExposure,
"postISRCCD_uninterpolated")
1516 if result.ossThumb
is not None:
1517 isrQa.writeThumbnail(sensorRef, result.ossThumb,
"ossThumb")
1518 if result.flattenedThumb
is not None:
1519 isrQa.writeThumbnail(sensorRef, result.flattenedThumb,
"flattenedThumb")
1524 """!Retrieve a calibration dataset for removing instrument signature. 1529 dataRef : `daf.persistence.butlerSubset.ButlerDataRef` 1530 DataRef of the detector data to find calibration datasets 1533 Type of dataset to retrieve (e.g. 'bias', 'flat', etc). 1535 If True, disable butler proxies to enable error handling 1536 within this routine. 1540 exposure : `lsst.afw.image.Exposure` 1541 Requested calibration frame. 1546 Raised if no matching calibration frame can be found. 1549 exp = dataRef.get(datasetType, immediate=immediate)
1550 except Exception
as exc1:
1551 if not self.config.fallbackFilterName:
1552 raise RuntimeError(
"Unable to retrieve %s for %s: %s." % (datasetType, dataRef.dataId, exc1))
1554 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
1555 except Exception
as exc2:
1556 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s." %
1557 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
1558 self.log.warn(
"Using fallback calibration from filter %s.", self.config.fallbackFilterName)
1560 if self.config.doAssembleIsrExposures:
1561 exp = self.assembleCcd.assembleCcd(exp)
1565 """Ensure that the data returned by Butler is a fully constructed exposure. 1567 ISR requires exposure-level image data for historical reasons, so if we did 1568 not recieve that from Butler, construct it from what we have, modifying the 1573 inputExp : `lsst.afw.image.Exposure`, `lsst.afw.image.DecoratedImageU`, or 1574 `lsst.afw.image.ImageF` 1575 The input data structure obtained from Butler. 1576 camera : `lsst.afw.cameraGeom.camera` 1577 The camera associated with the image. Used to find the appropriate 1580 The detector this exposure should match. 1584 inputExp : `lsst.afw.image.Exposure` 1585 The re-constructed exposure, with appropriate detector parameters. 1590 Raised if the input data cannot be used to construct an exposure. 1592 if isinstance(inputExp, afwImage.DecoratedImageU):
1593 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1594 elif isinstance(inputExp, afwImage.ImageF):
1595 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1596 elif isinstance(inputExp, afwImage.MaskedImageF):
1597 inputExp = afwImage.makeExposure(inputExp)
1598 elif isinstance(inputExp, afwImage.Exposure):
1600 elif inputExp
is None:
1604 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1607 if inputExp.getDetector()
is None:
1608 inputExp.setDetector(camera[detectorNum])
1613 """Convert exposure image from uint16 to float. 1615 If the exposure does not need to be converted, the input is 1616 immediately returned. For exposures that are converted to use 1617 floating point pixels, the variance is set to unity and the 1622 exposure : `lsst.afw.image.Exposure` 1623 The raw exposure to be converted. 1627 newexposure : `lsst.afw.image.Exposure` 1628 The input ``exposure``, converted to floating point pixels. 1633 Raised if the exposure type cannot be converted to float. 1636 if isinstance(exposure, afwImage.ExposureF):
1638 self.log.debug(
"Exposure already of type float.")
1640 if not hasattr(exposure,
"convertF"):
1641 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1643 newexposure = exposure.convertF()
1644 newexposure.variance[:] = 1
1645 newexposure.mask[:] = 0x0
1650 """Identify bad amplifiers, saturated and suspect pixels. 1654 ccdExposure : `lsst.afw.image.Exposure` 1655 Input exposure to be masked. 1656 amp : `lsst.afw.table.AmpInfoCatalog` 1657 Catalog of parameters defining the amplifier on this 1659 defects : `lsst.meas.algorithms.Defects` 1660 List of defects. Used to determine if the entire 1666 If this is true, the entire amplifier area is covered by 1667 defects and unusable. 1670 maskedImage = ccdExposure.getMaskedImage()
1676 if defects
is not None:
1677 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1682 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1684 maskView = dataView.getMask()
1685 maskView |= maskView.getPlaneBitMask(
"BAD")
1692 if self.config.doSaturation
and not badAmp:
1693 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1694 if self.config.doSuspect
and not badAmp:
1695 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1696 if math.isfinite(self.config.saturation):
1697 limits.update({self.config.saturatedMaskName: self.config.saturation})
1699 for maskName, maskThreshold
in limits.items():
1700 if not math.isnan(maskThreshold):
1701 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1702 isrFunctions.makeThresholdMask(
1703 maskedImage=dataView,
1704 threshold=maskThreshold,
1710 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1712 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1713 self.config.suspectMaskName])
1714 if numpy.all(maskView.getArray() & maskVal > 0):
1716 maskView |= maskView.getPlaneBitMask(
"BAD")
1721 """Apply overscan correction in place. 1723 This method does initial pixel rejection of the overscan 1724 region. The overscan can also be optionally segmented to 1725 allow for discontinuous overscan responses to be fit 1726 separately. The actual overscan subtraction is performed by 1727 the `lsst.ip.isr.isrFunctions.overscanCorrection` function, 1728 which is called here after the amplifier is preprocessed. 1732 ccdExposure : `lsst.afw.image.Exposure` 1733 Exposure to have overscan correction performed. 1734 amp : `lsst.afw.table.AmpInfoCatalog` 1735 The amplifier to consider while correcting the overscan. 1739 overscanResults : `lsst.pipe.base.Struct` 1740 Result struct with components: 1741 - ``imageFit`` : scalar or `lsst.afw.image.Image` 1742 Value or fit subtracted from the amplifier image data. 1743 - ``overscanFit`` : scalar or `lsst.afw.image.Image` 1744 Value or fit subtracted from the overscan image data. 1745 - ``overscanImage`` : `lsst.afw.image.Image` 1746 Image of the overscan region with the overscan 1747 correction applied. This quantity is used to estimate 1748 the amplifier read noise empirically. 1753 Raised if the ``amp`` does not contain raw pixel information. 1757 lsst.ip.isr.isrFunctions.overscanCorrection 1759 if not amp.getHasRawInfo():
1760 raise RuntimeError(
"This method must be executed on an amp with raw information.")
1762 if amp.getRawHorizontalOverscanBBox().isEmpty():
1763 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1766 statControl = afwMath.StatisticsControl()
1767 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1770 dataBBox = amp.getRawDataBBox()
1771 oscanBBox = amp.getRawHorizontalOverscanBBox()
1775 prescanBBox = amp.getRawPrescanBBox()
1776 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1777 dx0 += self.config.overscanNumLeadingColumnsToSkip
1778 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1780 dx0 += self.config.overscanNumTrailingColumnsToSkip
1781 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1787 if ((self.config.overscanBiasJump
and 1788 self.config.overscanBiasJumpLocation)
and 1789 (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
and 1790 ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in 1791 self.config.overscanBiasJumpDevices)):
1792 if amp.getReadoutCorner()
in (afwTable.LL, afwTable.LR):
1793 yLower = self.config.overscanBiasJumpLocation
1794 yUpper = dataBBox.getHeight() - yLower
1796 yUpper = self.config.overscanBiasJumpLocation
1797 yLower = dataBBox.getHeight() - yUpper
1816 oscanBBox.getHeight())))
1819 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1820 ampImage = ccdExposure.maskedImage[imageBBox]
1821 overscanImage = ccdExposure.maskedImage[overscanBBox]
1823 overscanArray = overscanImage.image.array
1824 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1825 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1826 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1828 statControl = afwMath.StatisticsControl()
1829 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1831 overscanResults = isrFunctions.overscanCorrection(ampMaskedImage=ampImage,
1832 overscanImage=overscanImage,
1833 fitType=self.config.overscanFitType,
1834 order=self.config.overscanOrder,
1835 collapseRej=self.config.overscanNumSigmaClip,
1836 statControl=statControl,
1837 overscanIsInt=self.config.overscanIsInt
1841 levelStat = afwMath.MEDIAN
1842 sigmaStat = afwMath.STDEVCLIP
1844 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1845 self.config.qa.flatness.nIter)
1846 metadata = ccdExposure.getMetadata()
1847 ampNum = amp.getName()
1848 if self.config.overscanFitType
in (
"MEDIAN",
"MEAN",
"MEANCLIP"):
1849 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, overscanResults.overscanFit)
1850 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, 0.0)
1852 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1853 metadata.set(
"ISR_OSCAN_LEVEL%s" % ampNum, stats.getValue(levelStat))
1854 metadata.set(
"ISR_OSCAN_SIGMA%s" % ampNum, stats.getValue(sigmaStat))
1856 return overscanResults
1859 """Set the variance plane using the amplifier gain and read noise 1861 The read noise is calculated from the ``overscanImage`` if the 1862 ``doEmpiricalReadNoise`` option is set in the configuration; otherwise 1863 the value from the amplifier data is used. 1867 ampExposure : `lsst.afw.image.Exposure` 1868 Exposure to process. 1869 amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp` 1870 Amplifier detector data. 1871 overscanImage : `lsst.afw.image.MaskedImage`, optional. 1872 Image of overscan, required only for empirical read noise. 1876 lsst.ip.isr.isrFunctions.updateVariance 1878 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1879 gain = amp.getGain()
1881 if math.isnan(gain):
1883 self.log.warn(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1886 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f.",
1887 amp.getName(), gain, patchedGain)
1890 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1891 self.log.info(
"Overscan is none for EmpiricalReadNoise.")
1893 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
1894 stats = afwMath.StatisticsControl()
1895 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
1896 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
1897 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
1898 amp.getName(), readNoise)
1900 readNoise = amp.getReadNoise()
1902 isrFunctions.updateVariance(
1903 maskedImage=ampExposure.getMaskedImage(),
1905 readNoise=readNoise,
1909 """!Apply dark correction in place. 1913 exposure : `lsst.afw.image.Exposure` 1914 Exposure to process. 1915 darkExposure : `lsst.afw.image.Exposure` 1916 Dark exposure of the same size as ``exposure``. 1917 invert : `Bool`, optional 1918 If True, re-add the dark to an already corrected image. 1923 Raised if either ``exposure`` or ``darkExposure`` do not 1924 have their dark time defined. 1928 lsst.ip.isr.isrFunctions.darkCorrection 1930 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
1931 if math.isnan(expScale):
1932 raise RuntimeError(
"Exposure darktime is NAN.")
1933 if darkExposure.getInfo().getVisitInfo()
is not None:
1934 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
1938 self.log.warn(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
1941 if math.isnan(darkScale):
1942 raise RuntimeError(
"Dark calib darktime is NAN.")
1943 isrFunctions.darkCorrection(
1944 maskedImage=exposure.getMaskedImage(),
1945 darkMaskedImage=darkExposure.getMaskedImage(),
1947 darkScale=darkScale,
1949 trimToFit=self.config.doTrimToMatchCalib
1953 """!Check if linearization is needed for the detector cameraGeom. 1955 Checks config.doLinearize and the linearity type of the first 1960 detector : `lsst.afw.cameraGeom.Detector` 1961 Detector to get linearity type from. 1965 doLinearize : `Bool` 1966 If True, linearization should be performed. 1968 return self.config.doLinearize
and \
1969 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
1972 """!Apply flat correction in place. 1976 exposure : `lsst.afw.image.Exposure` 1977 Exposure to process. 1978 flatExposure : `lsst.afw.image.Exposure` 1979 Flat exposure of the same size as ``exposure``. 1980 invert : `Bool`, optional 1981 If True, unflatten an already flattened image. 1985 lsst.ip.isr.isrFunctions.flatCorrection 1987 isrFunctions.flatCorrection(
1988 maskedImage=exposure.getMaskedImage(),
1989 flatMaskedImage=flatExposure.getMaskedImage(),
1990 scalingType=self.config.flatScalingType,
1991 userScale=self.config.flatUserScale,
1993 trimToFit=self.config.doTrimToMatchCalib
1997 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place. 2001 exposure : `lsst.afw.image.Exposure` 2002 Exposure to process. Only the amplifier DataSec is processed. 2003 amp : `lsst.afw.table.AmpInfoCatalog` 2004 Amplifier detector data. 2008 lsst.ip.isr.isrFunctions.makeThresholdMask 2010 if not math.isnan(amp.getSaturation()):
2011 maskedImage = exposure.getMaskedImage()
2012 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2013 isrFunctions.makeThresholdMask(
2014 maskedImage=dataView,
2015 threshold=amp.getSaturation(),
2017 maskName=self.config.saturatedMaskName,
2021 """!Interpolate over saturated pixels, in place. 2023 This method should be called after `saturationDetection`, to 2024 ensure that the saturated pixels have been identified in the 2025 SAT mask. It should also be called after `assembleCcd`, since 2026 saturated regions may cross amplifier boundaries. 2030 exposure : `lsst.afw.image.Exposure` 2031 Exposure to process. 2035 lsst.ip.isr.isrTask.saturationDetection 2036 lsst.ip.isr.isrFunctions.interpolateFromMask 2038 isrFunctions.interpolateFromMask(
2039 maskedImage=exposure.getMaskedImage(),
2040 fwhm=self.config.fwhm,
2041 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2042 maskNameList=list(self.config.saturatedMaskName),
2046 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place. 2050 exposure : `lsst.afw.image.Exposure` 2051 Exposure to process. Only the amplifier DataSec is processed. 2052 amp : `lsst.afw.table.AmpInfoCatalog` 2053 Amplifier detector data. 2057 lsst.ip.isr.isrFunctions.makeThresholdMask 2061 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 2062 This is intended to indicate pixels that may be affected by unknown systematics; 2063 for example if non-linearity corrections above a certain level are unstable 2064 then that would be a useful value for suspectLevel. A value of `nan` indicates 2065 that no such level exists and no pixels are to be masked as suspicious. 2067 suspectLevel = amp.getSuspectLevel()
2068 if math.isnan(suspectLevel):
2071 maskedImage = exposure.getMaskedImage()
2072 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2073 isrFunctions.makeThresholdMask(
2074 maskedImage=dataView,
2075 threshold=suspectLevel,
2077 maskName=self.config.suspectMaskName,
2081 """!Mask defects using mask plane "BAD", in place. 2085 exposure : `lsst.afw.image.Exposure` 2086 Exposure to process. 2087 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2088 `lsst.afw.image.DefectBase`. 2089 List of defects to mask. 2093 Call this after CCD assembly, since defects may cross amplifier boundaries. 2095 maskedImage = exposure.getMaskedImage()
2096 if not isinstance(defectBaseList, Defects):
2098 defectList = Defects(defectBaseList)
2100 defectList = defectBaseList
2101 defectList.maskPixels(maskedImage, maskName=
"BAD")
2103 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT"):
2104 """!Mask edge pixels with applicable mask plane. 2108 exposure : `lsst.afw.image.Exposure` 2109 Exposure to process. 2110 numEdgePixels : `int`, optional 2111 Number of edge pixels to mask. 2112 maskPlane : `str`, optional 2113 Mask plane name to use. 2115 maskedImage = exposure.getMaskedImage()
2116 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2118 if numEdgePixels > 0:
2119 goodBBox = maskedImage.getBBox()
2121 goodBBox.grow(-numEdgePixels)
2123 SourceDetectionTask.setEdgeBits(
2130 """Mask and interpolate defects using mask plane "BAD", in place. 2134 exposure : `lsst.afw.image.Exposure` 2135 Exposure to process. 2136 defectBaseList : `lsst.meas.algorithms.Defects` or `list` of 2137 `lsst.afw.image.DefectBase`. 2138 List of defects to mask and interpolate. 2142 lsst.ip.isr.isrTask.maskDefect() 2145 self.
maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2146 maskPlane=
"SUSPECT")
2147 isrFunctions.interpolateFromMask(
2148 maskedImage=exposure.getMaskedImage(),
2149 fwhm=self.config.fwhm,
2150 growSaturatedFootprints=0,
2151 maskNameList=[
"BAD"],
2155 """Mask NaNs using mask plane "UNMASKEDNAN", in place. 2159 exposure : `lsst.afw.image.Exposure` 2160 Exposure to process. 2164 We mask over all NaNs, including those that are masked with 2165 other bits (because those may or may not be interpolated over 2166 later, and we want to remove all NaNs). Despite this 2167 behaviour, the "UNMASKEDNAN" mask plane is used to preserve 2168 the historical name. 2170 maskedImage = exposure.getMaskedImage()
2173 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2174 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2175 numNans =
maskNans(maskedImage, maskVal)
2176 self.metadata.set(
"NUMNANS", numNans)
2178 self.log.warn(
"There were %d unmasked NaNs.", numNans)
2181 """"Mask and interpolate NaNs using mask plane "UNMASKEDNAN", in place. 2185 exposure : `lsst.afw.image.Exposure` 2186 Exposure to process. 2190 lsst.ip.isr.isrTask.maskNan() 2193 isrFunctions.interpolateFromMask(
2194 maskedImage=exposure.getMaskedImage(),
2195 fwhm=self.config.fwhm,
2196 growSaturatedFootprints=0,
2197 maskNameList=[
"UNMASKEDNAN"],
2201 """Measure the image background in subgrids, for quality control purposes. 2205 exposure : `lsst.afw.image.Exposure` 2206 Exposure to process. 2207 IsrQaConfig : `lsst.ip.isr.isrQa.IsrQaConfig` 2208 Configuration object containing parameters on which background 2209 statistics and subgrids to use. 2211 if IsrQaConfig
is not None:
2212 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2213 IsrQaConfig.flatness.nIter)
2214 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2215 statsControl.setAndMask(maskVal)
2216 maskedImage = exposure.getMaskedImage()
2217 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2218 skyLevel = stats.getValue(afwMath.MEDIAN)
2219 skySigma = stats.getValue(afwMath.STDEVCLIP)
2220 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2221 metadata = exposure.getMetadata()
2222 metadata.set(
'SKYLEVEL', skyLevel)
2223 metadata.set(
'SKYSIGMA', skySigma)
2226 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2227 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2228 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2229 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2230 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2231 skyLevels = numpy.zeros((nX, nY))
2234 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2236 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2238 xLLC = xc - meshXHalf
2239 yLLC = yc - meshYHalf
2240 xURC = xc + meshXHalf - 1
2241 yURC = yc + meshYHalf - 1
2244 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2246 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2248 good = numpy.where(numpy.isfinite(skyLevels))
2249 skyMedian = numpy.median(skyLevels[good])
2250 flatness = (skyLevels[good] - skyMedian) / skyMedian
2251 flatness_rms = numpy.std(flatness)
2252 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2254 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2255 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2256 nX, nY, flatness_pp, flatness_rms)
2258 metadata.set(
'FLATNESS_PP', float(flatness_pp))
2259 metadata.set(
'FLATNESS_RMS', float(flatness_rms))
2260 metadata.set(
'FLATNESS_NGRIDS',
'%dx%d' % (nX, nY))
2261 metadata.set(
'FLATNESS_MESHX', IsrQaConfig.flatness.meshX)
2262 metadata.set(
'FLATNESS_MESHY', IsrQaConfig.flatness.meshY)
2265 """Set an approximate magnitude zero point for the exposure. 2269 exposure : `lsst.afw.image.Exposure` 2270 Exposure to process. 2272 filterName = afwImage.Filter(exposure.getFilter().getId()).getName()
2273 if filterName
in self.config.fluxMag0T1:
2274 fluxMag0 = self.config.fluxMag0T1[filterName]
2276 self.log.warn(
"No rough magnitude zero point set for filter %s.", filterName)
2277 fluxMag0 = self.config.defaultFluxMag0T1
2279 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2281 self.log.warn(
"Non-positive exposure time; skipping rough zero point.")
2284 self.log.info(
"Setting rough magnitude zero point: %f", 2.5*math.log10(fluxMag0*expTime))
2285 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2288 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners. 2292 ccdExposure : `lsst.afw.image.Exposure` 2293 Exposure to process. 2294 fpPolygon : `lsst.afw.geom.Polygon` 2295 Polygon in focal plane coordinates. 2298 ccd = ccdExposure.getDetector()
2299 fpCorners = ccd.getCorners(FOCAL_PLANE)
2300 ccdPolygon = Polygon(fpCorners)
2303 intersect = ccdPolygon.intersectionSingle(fpPolygon)
2306 ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
2307 validPolygon = Polygon(ccdPoints)
2308 ccdExposure.getInfo().setValidPolygon(validPolygon)
2312 """Context manager that applies and removes flats and darks, 2313 if the task is configured to apply them. 2317 exp : `lsst.afw.image.Exposure` 2318 Exposure to process. 2319 flat : `lsst.afw.image.Exposure` 2320 Flat exposure the same size as ``exp``. 2321 dark : `lsst.afw.image.Exposure`, optional 2322 Dark exposure the same size as ``exp``. 2326 exp : `lsst.afw.image.Exposure` 2327 The flat and dark corrected exposure. 2329 if self.config.doDark
and dark
is not None:
2331 if self.config.doFlat:
2336 if self.config.doFlat:
2338 if self.config.doDark
and dark
is not None:
2342 """Utility function to examine ISR exposure at different stages. 2346 exposure : `lsst.afw.image.Exposure` 2349 State of processing to view. 2351 frame = getDebugFrame(self._display, stepname)
2353 display = getDisplay(frame)
2354 display.scale(
'asinh',
'zscale')
2355 display.mtv(exposure)
2356 prompt =
"Press Enter to continue [c]... " 2358 ans = input(prompt).lower()
2359 if ans
in (
"",
"c",):
2364 """A Detector-like object that supports returning gain and saturation level 2366 This is used when the input exposure does not have a detector. 2370 exposure : `lsst.afw.image.Exposure` 2371 Exposure to generate a fake amplifier for. 2372 config : `lsst.ip.isr.isrTaskConfig` 2373 Configuration to apply to the fake amplifier. 2377 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2379 self.
_gain = config.gain
2409 isr = pexConfig.ConfigurableField(target=IsrTask, doc=
"Instrument signature removal")
2413 """Task to wrap the default IsrTask to allow it to be retargeted. 2415 The standard IsrTask can be called directly from a command line 2416 program, but doing so removes the ability of the task to be 2417 retargeted. As most cameras override some set of the IsrTask 2418 methods, this would remove those data-specific methods in the 2419 output post-ISR images. This wrapping class fixes the issue, 2420 allowing identical post-ISR images to be generated by both the 2421 processCcd and isrTask code. 2423 ConfigClass = RunIsrConfig
2424 _DefaultName =
"runIsr" 2428 self.makeSubtask(
"isr")
2434 dataRef : `lsst.daf.persistence.ButlerDataRef` 2435 data reference of the detector data to be processed 2439 result : `pipeBase.Struct` 2440 Result struct with component: 2442 - exposure : `lsst.afw.image.Exposure` 2443 Post-ISR processed exposure. def getInputDatasetTypes(cls, config)
def runDataRef(self, sensorRef)
def measureBackground(self, exposure, IsrQaConfig=None)
def debugView(self, exposure, stepname)
def __init__(self, kwargs)
def ensureExposure(self, inputExp, camera, detectorNum)
def readIsrData(self, dataRef, rawExposure)
Retrieve necessary frames for instrument signature removal.
def adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT")
Mask edge pixels with applicable mask plane.
def runDataRef(self, dataRef)
def run(self, ccdExposure, camera=None, bias=None, linearizer=None, crosstalkSources=None, dark=None, flat=None, bfKernel=None, defects=None, fringes=pipeBase.Struct(fringes=None), opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, detectorNum=None, strayLightData=None, illumMaskedImage=None, isGen3=False)
Perform instrument signature removal on an exposure.
def __init__(self, args, kwargs)
def getPrerequisiteDatasetTypes(cls, config)
def roughZeroPoint(self, exposure)
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def getRawHorizontalOverscanBBox(self)
def maskNan(self, exposure)
def getSuspectLevel(self)
def getOutputDatasetTypes(cls, config)
def maskDefect(self, exposure, defectBaseList)
Mask defects using mask plane "BAD", in place.
def overscanCorrection(self, ccdExposure, amp)
def convertIntToFloat(self, exposure)
def flatCorrection(self, exposure, flatExposure, invert=False)
Apply flat correction in place.
def makeDatasetType(self, dsConfig)
def getIsrExposure(self, dataRef, datasetType, immediate=True)
Retrieve a calibration dataset for removing instrument signature.
_RawHorizontalOverscanBBox
def darkCorrection(self, exposure, darkExposure, invert=False)
Apply dark correction in place.
def doLinearize(self, detector)
Check if linearization is needed for the detector cameraGeom.
def setValidPolygonIntersect(self, ccdExposure, fpPolygon)
Set the valid polygon as the intersection of fpPolygon and the ccd corners.
def maskAmplifier(self, ccdExposure, amp, defects)
def flatContext(self, exp, flat, dark=None)
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Mask NANs in an image.
def updateVariance(self, ampExposure, amp, overscanImage=None)
def maskAndInterpolateNan(self, exposure)
def suspectDetection(self, exposure, amp)
Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place.
def saturationInterpolation(self, exposure)
Interpolate over saturated pixels, in place.
def saturationDetection(self, exposure, amp)
Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place...
doSaturationInterpolation
def __init__(self, exposure, config)