29import lsst.pipe.base
as pipeBase
30import lsst.pipe.base.connectionTypes
as cT
32from contextlib
import contextmanager
33from lsstDebug
import getDebugFrame
38from lsst.utils.timer
import timeMethod
40from .
import isrFunctions
42from .
import linearize
43from .defects
import Defects
45from .assembleCcdTask
import AssembleCcdTask
46from .crosstalk
import CrosstalkTask, CrosstalkCalib
47from .fringe
import FringeTask
48from .isr
import maskNans
49from .masking
import MaskingTask
50from .overscan
import OverscanCorrectionTask
51from .straylight
import StrayLightTask
52from .vignette
import VignetteTask
53from .ampOffset
import AmpOffsetTask
54from .deferredCharge
import DeferredChargeTask
55from .isrStatistics
import IsrStatisticsTask
56from lsst.daf.butler
import DimensionGraph
59__all__ = [
"IsrTask",
"IsrTaskConfig"]
63 """Lookup function to identify crosstalkSource entries.
65 This should return an empty list under most circumstances. Only
66 when inter-chip crosstalk has been identified should this be
73 registry : `lsst.daf.butler.Registry`
74 Butler registry to query.
75 quantumDataId : `lsst.daf.butler.ExpandedDataCoordinate`
76 Data id to transform to identify crosstalkSources. The
77 ``detector`` entry will be stripped.
78 collections : `lsst.daf.butler.CollectionSearch`
79 Collections to search through.
83 results : `list` [`lsst.daf.butler.DatasetRef`]
84 List of datasets that match the query that will be used
as
87 newDataId = quantumDataId.subset(DimensionGraph(registry.dimensions, names=["instrument",
"exposure"]))
88 results = set(registry.queryDatasets(datasetType, collections=collections, dataId=newDataId,
95 return [ref.expanded(registry.expandDataId(ref.dataId, records=newDataId.records))
for ref
in results]
99 dimensions={
"instrument",
"exposure",
"detector"},
100 defaultTemplates={}):
101 ccdExposure = cT.Input(
103 doc=
"Input exposure to process.",
104 storageClass=
"Exposure",
105 dimensions=[
"instrument",
"exposure",
"detector"],
107 camera = cT.PrerequisiteInput(
109 storageClass=
"Camera",
110 doc=
"Input camera to construct complete exposures.",
111 dimensions=[
"instrument"],
115 crosstalk = cT.PrerequisiteInput(
117 doc=
"Input crosstalk object",
118 storageClass=
"CrosstalkCalib",
119 dimensions=[
"instrument",
"detector"],
123 crosstalkSources = cT.PrerequisiteInput(
124 name=
"isrOverscanCorrected",
125 doc=
"Overscan corrected input images.",
126 storageClass=
"Exposure",
127 dimensions=[
"instrument",
"exposure",
"detector"],
130 lookupFunction=crosstalkSourceLookup,
133 bias = cT.PrerequisiteInput(
135 doc=
"Input bias calibration.",
136 storageClass=
"ExposureF",
137 dimensions=[
"instrument",
"detector"],
140 dark = cT.PrerequisiteInput(
142 doc=
"Input dark calibration.",
143 storageClass=
"ExposureF",
144 dimensions=[
"instrument",
"detector"],
147 flat = cT.PrerequisiteInput(
149 doc=
"Input flat calibration.",
150 storageClass=
"ExposureF",
151 dimensions=[
"instrument",
"physical_filter",
"detector"],
154 ptc = cT.PrerequisiteInput(
156 doc=
"Input Photon Transfer Curve dataset",
157 storageClass=
"PhotonTransferCurveDataset",
158 dimensions=[
"instrument",
"detector"],
161 fringes = cT.PrerequisiteInput(
163 doc=
"Input fringe calibration.",
164 storageClass=
"ExposureF",
165 dimensions=[
"instrument",
"physical_filter",
"detector"],
169 strayLightData = cT.PrerequisiteInput(
171 doc=
"Input stray light calibration.",
172 storageClass=
"StrayLightData",
173 dimensions=[
"instrument",
"physical_filter",
"detector"],
178 bfKernel = cT.PrerequisiteInput(
180 doc=
"Input brighter-fatter kernel.",
181 storageClass=
"NumpyArray",
182 dimensions=[
"instrument"],
186 newBFKernel = cT.PrerequisiteInput(
187 name=
'brighterFatterKernel',
188 doc=
"Newer complete kernel + gain solutions.",
189 storageClass=
"BrighterFatterKernel",
190 dimensions=[
"instrument",
"detector"],
194 defects = cT.PrerequisiteInput(
196 doc=
"Input defect tables.",
197 storageClass=
"Defects",
198 dimensions=[
"instrument",
"detector"],
201 linearizer = cT.PrerequisiteInput(
203 storageClass=
"Linearizer",
204 doc=
"Linearity correction calibration.",
205 dimensions=[
"instrument",
"detector"],
209 opticsTransmission = cT.PrerequisiteInput(
210 name=
"transmission_optics",
211 storageClass=
"TransmissionCurve",
212 doc=
"Transmission curve due to the optics.",
213 dimensions=[
"instrument"],
216 filterTransmission = cT.PrerequisiteInput(
217 name=
"transmission_filter",
218 storageClass=
"TransmissionCurve",
219 doc=
"Transmission curve due to the filter.",
220 dimensions=[
"instrument",
"physical_filter"],
223 sensorTransmission = cT.PrerequisiteInput(
224 name=
"transmission_sensor",
225 storageClass=
"TransmissionCurve",
226 doc=
"Transmission curve due to the sensor.",
227 dimensions=[
"instrument",
"detector"],
230 atmosphereTransmission = cT.PrerequisiteInput(
231 name=
"transmission_atmosphere",
232 storageClass=
"TransmissionCurve",
233 doc=
"Transmission curve due to the atmosphere.",
234 dimensions=[
"instrument"],
237 illumMaskedImage = cT.PrerequisiteInput(
239 doc=
"Input illumination correction.",
240 storageClass=
"MaskedImageF",
241 dimensions=[
"instrument",
"physical_filter",
"detector"],
244 deferredChargeCalib = cT.PrerequisiteInput(
245 name=
"deferredCharge",
246 doc=
"Deferred charge/CTI correction dataset.",
247 storageClass=
"IsrCalib",
248 dimensions=[
"instrument",
"detector"],
252 outputExposure = cT.Output(
254 doc=
"Output ISR processed exposure.",
255 storageClass=
"Exposure",
256 dimensions=[
"instrument",
"exposure",
"detector"],
258 preInterpExposure = cT.Output(
259 name=
'preInterpISRCCD',
260 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
261 storageClass=
"ExposureF",
262 dimensions=[
"instrument",
"exposure",
"detector"],
264 outputOssThumbnail = cT.Output(
266 doc=
"Output Overscan-subtracted thumbnail image.",
267 storageClass=
"Thumbnail",
268 dimensions=[
"instrument",
"exposure",
"detector"],
270 outputFlattenedThumbnail = cT.Output(
271 name=
"FlattenedThumb",
272 doc=
"Output flat-corrected thumbnail image.",
273 storageClass=
"Thumbnail",
274 dimensions=[
"instrument",
"exposure",
"detector"],
276 outputStatistics = cT.Output(
277 name=
"isrStatistics",
278 doc=
"Output of additional statistics table.",
279 storageClass=
"StructuredDataDict",
280 dimensions=[
"instrument",
"exposure",
"detector"],
286 if config.doBias
is not True:
287 self.prerequisiteInputs.remove(
"bias")
288 if config.doLinearize
is not True:
289 self.prerequisiteInputs.remove(
"linearizer")
290 if config.doCrosstalk
is not True:
291 self.prerequisiteInputs.remove(
"crosstalkSources")
292 self.prerequisiteInputs.remove(
"crosstalk")
293 if config.doBrighterFatter
is not True:
294 self.prerequisiteInputs.remove(
"bfKernel")
295 self.prerequisiteInputs.remove(
"newBFKernel")
296 if config.doDefect
is not True:
297 self.prerequisiteInputs.remove(
"defects")
298 if config.doDark
is not True:
299 self.prerequisiteInputs.remove(
"dark")
300 if config.doFlat
is not True:
301 self.prerequisiteInputs.remove(
"flat")
302 if config.doFringe
is not True:
303 self.prerequisiteInputs.remove(
"fringes")
304 if config.doStrayLight
is not True:
305 self.prerequisiteInputs.remove(
"strayLightData")
306 if config.usePtcGains
is not True and config.usePtcReadNoise
is not True:
307 self.prerequisiteInputs.remove(
"ptc")
308 if config.doAttachTransmissionCurve
is not True:
309 self.prerequisiteInputs.remove(
"opticsTransmission")
310 self.prerequisiteInputs.remove(
"filterTransmission")
311 self.prerequisiteInputs.remove(
"sensorTransmission")
312 self.prerequisiteInputs.remove(
"atmosphereTransmission")
314 if config.doUseOpticsTransmission
is not True:
315 self.prerequisiteInputs.remove(
"opticsTransmission")
316 if config.doUseFilterTransmission
is not True:
317 self.prerequisiteInputs.remove(
"filterTransmission")
318 if config.doUseSensorTransmission
is not True:
319 self.prerequisiteInputs.remove(
"sensorTransmission")
320 if config.doUseAtmosphereTransmission
is not True:
321 self.prerequisiteInputs.remove(
"atmosphereTransmission")
322 if config.doIlluminationCorrection
is not True:
323 self.prerequisiteInputs.remove(
"illumMaskedImage")
324 if config.doDeferredCharge
is not True:
325 self.prerequisiteInputs.remove(
"deferredChargeCalib")
327 if config.doWrite
is not True:
328 self.outputs.remove(
"outputExposure")
329 self.outputs.remove(
"preInterpExposure")
330 self.outputs.remove(
"outputFlattenedThumbnail")
331 self.outputs.remove(
"outputOssThumbnail")
332 self.outputs.remove(
"outputStatistics")
334 if config.doSaveInterpPixels
is not True:
335 self.outputs.remove(
"preInterpExposure")
336 if config.qa.doThumbnailOss
is not True:
337 self.outputs.remove(
"outputOssThumbnail")
338 if config.qa.doThumbnailFlattened
is not True:
339 self.outputs.remove(
"outputFlattenedThumbnail")
340 if config.doCalculateStatistics
is not True:
341 self.outputs.remove(
"outputStatistics")
345 pipelineConnections=IsrTaskConnections):
346 """Configuration parameters for IsrTask.
348 Items are grouped in the order
in which they are executed by the task.
350 datasetType = pexConfig.Field(
352 doc="Dataset type for input data; users will typically leave this alone, "
353 "but camera-specific ISR tasks will override it",
357 fallbackFilterName = pexConfig.Field(
359 doc=
"Fallback default filter name for calibrations.",
362 useFallbackDate = pexConfig.Field(
364 doc=
"Pass observation date when using fallback filter.",
367 expectWcs = pexConfig.Field(
370 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)."
372 fwhm = pexConfig.Field(
374 doc=
"FWHM of PSF in arcseconds.",
377 qa = pexConfig.ConfigField(
379 doc=
"QA related configuration options.",
383 doConvertIntToFloat = pexConfig.Field(
385 doc=
"Convert integer raw images to floating point values?",
390 doSaturation = pexConfig.Field(
392 doc=
"Mask saturated pixels? NB: this is totally independent of the"
393 " interpolation option - this is ONLY setting the bits in the mask."
394 " To have them interpolated make sure doSaturationInterpolation=True",
397 saturatedMaskName = pexConfig.Field(
399 doc=
"Name of mask plane to use in saturation detection and interpolation",
402 saturation = pexConfig.Field(
404 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
405 default=float(
"NaN"),
407 growSaturationFootprintSize = pexConfig.Field(
409 doc=
"Number of pixels by which to grow the saturation footprints",
414 doSuspect = pexConfig.Field(
416 doc=
"Mask suspect pixels?",
419 suspectMaskName = pexConfig.Field(
421 doc=
"Name of mask plane to use for suspect pixels",
424 numEdgeSuspect = pexConfig.Field(
426 doc=
"Number of edge pixels to be flagged as untrustworthy.",
429 edgeMaskLevel = pexConfig.ChoiceField(
431 doc=
"Mask edge pixels in which coordinate frame: DETECTOR or AMP?",
434 'DETECTOR':
'Mask only the edges of the full detector.',
435 'AMP':
'Mask edges of each amplifier.',
440 doSetBadRegions = pexConfig.Field(
442 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
445 badStatistic = pexConfig.ChoiceField(
447 doc=
"How to estimate the average value for BAD regions.",
450 "MEANCLIP":
"Correct using the (clipped) mean of good data",
451 "MEDIAN":
"Correct using the median of the good data",
456 doOverscan = pexConfig.Field(
458 doc=
"Do overscan subtraction?",
461 overscan = pexConfig.ConfigurableField(
462 target=OverscanCorrectionTask,
463 doc=
"Overscan subtraction task for image segments.",
465 overscanFitType = pexConfig.ChoiceField(
467 doc=
"The method for fitting the overscan bias level.",
470 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
471 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
472 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
473 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
474 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
475 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
476 "MEAN":
"Correct using the mean of the overscan region",
477 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
478 "MEDIAN":
"Correct using the median of the overscan region",
479 "MEDIAN_PER_ROW":
"Correct using the median per row of the overscan region",
481 deprecated=(
"Please configure overscan via the OverscanCorrectionConfig interface."
482 " This option will no longer be used, and will be removed after v20.")
484 overscanOrder = pexConfig.Field(
486 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, "
487 "or number of spline knots if overscan fit type is a spline."),
489 deprecated=(
"Please configure overscan via the OverscanCorrectionConfig interface."
490 " This option will no longer be used, and will be removed after v20.")
492 overscanNumSigmaClip = pexConfig.Field(
494 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
496 deprecated=(
"Please configure overscan via the OverscanCorrectionConfig interface."
497 " This option will no longer be used, and will be removed after v20.")
499 overscanIsInt = pexConfig.Field(
501 doc=
"Treat overscan as an integer image for purposes of overscan.FitType=MEDIAN"
502 " and overscan.FitType=MEDIAN_PER_ROW.",
504 deprecated=(
"Please configure overscan via the OverscanCorrectionConfig interface."
505 " This option will no longer be used, and will be removed after v20.")
509 overscanNumLeadingColumnsToSkip = pexConfig.Field(
511 doc=
"Number of columns to skip in overscan, i.e. those closest to amplifier",
514 overscanNumTrailingColumnsToSkip = pexConfig.Field(
516 doc=
"Number of columns to skip in overscan, i.e. those farthest from amplifier",
519 overscanMaxDev = pexConfig.Field(
521 doc=
"Maximum deviation from the median for overscan",
522 default=1000.0, check=
lambda x: x > 0
524 overscanBiasJump = pexConfig.Field(
526 doc=
"Fit the overscan in a piecewise-fashion to correct for bias jumps?",
529 overscanBiasJumpKeyword = pexConfig.Field(
531 doc=
"Header keyword containing information about devices.",
532 default=
"NO_SUCH_KEY",
534 overscanBiasJumpDevices = pexConfig.ListField(
536 doc=
"List of devices that need piecewise overscan correction.",
539 overscanBiasJumpLocation = pexConfig.Field(
541 doc=
"Location of bias jump along y-axis.",
546 doAssembleCcd = pexConfig.Field(
549 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
551 assembleCcd = pexConfig.ConfigurableField(
552 target=AssembleCcdTask,
553 doc=
"CCD assembly task",
557 doAssembleIsrExposures = pexConfig.Field(
560 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
562 doTrimToMatchCalib = pexConfig.Field(
565 doc=
"Trim raw data to match calibration bounding boxes?"
569 doBias = pexConfig.Field(
571 doc=
"Apply bias frame correction?",
574 biasDataProductName = pexConfig.Field(
576 doc=
"Name of the bias data product",
579 doBiasBeforeOverscan = pexConfig.Field(
581 doc=
"Reverse order of overscan and bias correction.",
586 doDeferredCharge = pexConfig.Field(
588 doc=
"Apply deferred charge correction?",
591 deferredChargeCorrection = pexConfig.ConfigurableField(
592 target=DeferredChargeTask,
593 doc=
"Deferred charge correction task.",
597 doVariance = pexConfig.Field(
599 doc=
"Calculate variance?",
602 gain = pexConfig.Field(
604 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
605 default=float(
"NaN"),
607 readNoise = pexConfig.Field(
609 doc=
"The read noise to use if no Detector is present in the Exposure",
612 doEmpiricalReadNoise = pexConfig.Field(
615 doc=
"Calculate empirical read noise instead of value from AmpInfo data?"
617 usePtcReadNoise = pexConfig.Field(
620 doc=
"Use readnoise values from the Photon Transfer Curve?"
622 maskNegativeVariance = pexConfig.Field(
625 doc=
"Mask pixels that claim a negative variance? This likely indicates a failure "
626 "in the measurement of the overscan at an edge due to the data falling off faster "
627 "than the overscan model can account for it."
629 negativeVarianceMaskName = pexConfig.Field(
632 doc=
"Mask plane to use to mark pixels with negative variance, if `maskNegativeVariance` is True.",
635 doLinearize = pexConfig.Field(
637 doc=
"Correct for nonlinearity of the detector's response?",
642 doCrosstalk = pexConfig.Field(
644 doc=
"Apply intra-CCD crosstalk correction?",
647 doCrosstalkBeforeAssemble = pexConfig.Field(
649 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
652 crosstalk = pexConfig.ConfigurableField(
653 target=CrosstalkTask,
654 doc=
"Intra-CCD crosstalk correction",
658 doDefect = pexConfig.Field(
660 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
663 doNanMasking = pexConfig.Field(
665 doc=
"Mask non-finite (NAN, inf) pixels?",
668 doWidenSaturationTrails = pexConfig.Field(
670 doc=
"Widen bleed trails based on their width?",
675 doBrighterFatter = pexConfig.Field(
678 doc=
"Apply the brighter-fatter correction?"
680 brighterFatterLevel = pexConfig.ChoiceField(
683 doc=
"The level at which to correct for brighter-fatter.",
685 "AMP":
"Every amplifier treated separately.",
686 "DETECTOR":
"One kernel per detector",
689 brighterFatterMaxIter = pexConfig.Field(
692 doc=
"Maximum number of iterations for the brighter-fatter correction"
694 brighterFatterThreshold = pexConfig.Field(
697 doc=
"Threshold used to stop iterating the brighter-fatter correction. It is the "
698 "absolute value of the difference between the current corrected image and the one "
699 "from the previous iteration summed over all the pixels."
701 brighterFatterApplyGain = pexConfig.Field(
704 doc=
"Should the gain be applied when applying the brighter-fatter correction?"
706 brighterFatterMaskListToInterpolate = pexConfig.ListField(
708 doc=
"List of mask planes that should be interpolated over when applying the brighter-fatter "
710 default=[
"SAT",
"BAD",
"NO_DATA",
"UNMASKEDNAN"],
712 brighterFatterMaskGrowSize = pexConfig.Field(
715 doc=
"Number of pixels to grow the masks listed in config.brighterFatterMaskListToInterpolate "
716 "when brighter-fatter correction is applied."
720 doDark = pexConfig.Field(
722 doc=
"Apply dark frame correction?",
725 darkDataProductName = pexConfig.Field(
727 doc=
"Name of the dark data product",
732 doStrayLight = pexConfig.Field(
734 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
737 strayLight = pexConfig.ConfigurableField(
738 target=StrayLightTask,
739 doc=
"y-band stray light correction"
743 doFlat = pexConfig.Field(
745 doc=
"Apply flat field correction?",
748 flatDataProductName = pexConfig.Field(
750 doc=
"Name of the flat data product",
753 flatScalingType = pexConfig.ChoiceField(
755 doc=
"The method for scaling the flat on the fly.",
758 "USER":
"Scale by flatUserScale",
759 "MEAN":
"Scale by the inverse of the mean",
760 "MEDIAN":
"Scale by the inverse of the median",
763 flatUserScale = pexConfig.Field(
765 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
768 doTweakFlat = pexConfig.Field(
770 doc=
"Tweak flats to match observed amplifier ratios?",
776 doApplyGains = pexConfig.Field(
778 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
781 usePtcGains = pexConfig.Field(
783 doc=
"Use the gain values from the Photon Transfer Curve?",
786 normalizeGains = pexConfig.Field(
788 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
793 doFringe = pexConfig.Field(
795 doc=
"Apply fringe correction?",
798 fringe = pexConfig.ConfigurableField(
800 doc=
"Fringe subtraction task",
802 fringeAfterFlat = pexConfig.Field(
804 doc=
"Do fringe subtraction after flat-fielding?",
809 doAmpOffset = pexConfig.Field(
810 doc=
"Calculate and apply amp offset corrections?",
814 ampOffset = pexConfig.ConfigurableField(
815 doc=
"Amp offset correction task.",
816 target=AmpOffsetTask,
820 doMeasureBackground = pexConfig.Field(
822 doc=
"Measure the background level on the reduced image?",
827 doCameraSpecificMasking = pexConfig.Field(
829 doc=
"Mask camera-specific bad regions?",
832 masking = pexConfig.ConfigurableField(
838 doInterpolate = pexConfig.Field(
840 doc=
"Interpolate masked pixels?",
843 doSaturationInterpolation = pexConfig.Field(
845 doc=
"Perform interpolation over pixels masked as saturated?"
846 " NB: This is independent of doSaturation; if that is False this plane"
847 " will likely be blank, resulting in a no-op here.",
850 doNanInterpolation = pexConfig.Field(
852 doc=
"Perform interpolation over pixels masked as NaN?"
853 " NB: This is independent of doNanMasking; if that is False this plane"
854 " will likely be blank, resulting in a no-op here.",
857 doNanInterpAfterFlat = pexConfig.Field(
859 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we "
860 "also have to interpolate them before flat-fielding."),
863 maskListToInterpolate = pexConfig.ListField(
865 doc=
"List of mask planes that should be interpolated.",
866 default=[
'SAT',
'BAD'],
868 doSaveInterpPixels = pexConfig.Field(
870 doc=
"Save a copy of the pre-interpolated pixel values?",
875 fluxMag0T1 = pexConfig.DictField(
878 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
879 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
882 defaultFluxMag0T1 = pexConfig.Field(
884 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
885 default=pow(10.0, 0.4*28.0)
889 doVignette = pexConfig.Field(
891 doc=(
"Compute and attach the validPolygon defining the unvignetted region to the exposure "
892 "according to vignetting parameters?"),
895 doMaskVignettePolygon = pexConfig.Field(
897 doc=(
"Add a mask bit for pixels within the vignetted region. Ignored if doVignette "
901 vignetteValue = pexConfig.Field(
903 doc=
"Value to replace image array pixels with in the vignetted region? Ignored if None.",
907 vignette = pexConfig.ConfigurableField(
909 doc=
"Vignetting task.",
913 doAttachTransmissionCurve = pexConfig.Field(
916 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?"
918 doUseOpticsTransmission = pexConfig.Field(
921 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?"
923 doUseFilterTransmission = pexConfig.Field(
926 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?"
928 doUseSensorTransmission = pexConfig.Field(
931 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?"
933 doUseAtmosphereTransmission = pexConfig.Field(
936 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?"
940 doIlluminationCorrection = pexConfig.Field(
943 doc=
"Perform illumination correction?"
945 illuminationCorrectionDataProductName = pexConfig.Field(
947 doc=
"Name of the illumination correction data product.",
950 illumScale = pexConfig.Field(
952 doc=
"Scale factor for the illumination correction.",
955 illumFilters = pexConfig.ListField(
958 doc=
"Only perform illumination correction for these filters."
962 doCalculateStatistics = pexConfig.Field(
964 doc=
"Should additional ISR statistics be calculated?",
967 isrStats = pexConfig.ConfigurableField(
968 target=IsrStatisticsTask,
969 doc=
"Task to calculate additional statistics.",
974 doWrite = pexConfig.Field(
976 doc=
"Persist postISRCCD?",
983 raise ValueError(
"You may not specify both doFlat and doApplyGains")
985 raise ValueError(
"You may not specify both doBiasBeforeOverscan and doTrimToMatchCalib")
995 """Apply common instrument signature correction algorithms to a raw frame.
997 The process for correcting imaging data
is very similar
from
998 camera to camera. This task provides a vanilla implementation of
999 doing these corrections, including the ability to turn certain
1000 corrections off
if they are
not needed. The inputs to the primary
1001 method, `
run()`, are a raw exposure to be corrected
and the
1002 calibration data products. The raw input
is a single chip sized
1003 mosaic of all amps including overscans
and other non-science
1006 The __init__ method sets up the subtasks
for ISR processing, using
1012 Positional arguments passed to the Task constructor.
1013 None used at this time.
1014 kwargs : `dict`, optional
1015 Keyword arguments passed on to the Task constructor.
1016 None used at this time.
1018 ConfigClass = IsrTaskConfig
1019 _DefaultName = "isr"
1023 self.makeSubtask(
"assembleCcd")
1024 self.makeSubtask(
"crosstalk")
1025 self.makeSubtask(
"strayLight")
1026 self.makeSubtask(
"fringe")
1027 self.makeSubtask(
"masking")
1028 self.makeSubtask(
"overscan")
1029 self.makeSubtask(
"vignette")
1030 self.makeSubtask(
"ampOffset")
1031 self.makeSubtask(
"deferredChargeCorrection")
1032 self.makeSubtask(
"isrStats")
1035 inputs = butlerQC.get(inputRefs)
1038 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
1039 except Exception
as e:
1040 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
1043 detector = inputs[
'ccdExposure'].getDetector()
1045 if self.config.doCrosstalk
is True:
1048 if 'crosstalk' in inputs
and inputs[
'crosstalk']
is not None:
1049 if not isinstance(inputs[
'crosstalk'], CrosstalkCalib):
1050 inputs[
'crosstalk'] = CrosstalkCalib.fromTable(inputs[
'crosstalk'])
1052 coeffVector = (self.config.crosstalk.crosstalkValues
1053 if self.config.crosstalk.useConfigCoefficients
else None)
1054 crosstalkCalib =
CrosstalkCalib().fromDetector(detector, coeffVector=coeffVector)
1055 inputs[
'crosstalk'] = crosstalkCalib
1056 if inputs[
'crosstalk'].interChip
and len(inputs[
'crosstalk'].interChip) > 0:
1057 if 'crosstalkSources' not in inputs:
1058 self.log.warning(
"No crosstalkSources found for chip with interChip terms!")
1061 if 'linearizer' in inputs:
1062 if isinstance(inputs[
'linearizer'], dict):
1064 linearizer.fromYaml(inputs[
'linearizer'])
1065 self.log.warning(
"Dictionary linearizers will be deprecated in DM-28741.")
1066 elif isinstance(inputs[
'linearizer'], numpy.ndarray):
1070 self.log.warning(
"Bare lookup table linearizers will be deprecated in DM-28741.")
1072 linearizer = inputs[
'linearizer']
1073 linearizer.log = self.log
1074 inputs[
'linearizer'] = linearizer
1077 self.log.warning(
"Constructing linearizer from cameraGeom information.")
1079 if self.config.doDefect
is True:
1080 if "defects" in inputs
and inputs[
'defects']
is not None:
1084 if not isinstance(inputs[
"defects"], Defects):
1085 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
1089 if self.config.doBrighterFatter:
1090 brighterFatterKernel = inputs.pop(
'newBFKernel',
None)
1091 if brighterFatterKernel
is None:
1092 brighterFatterKernel = inputs.get(
'bfKernel',
None)
1094 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
1096 detName = detector.getName()
1097 level = brighterFatterKernel.level
1100 inputs[
'bfGains'] = brighterFatterKernel.gain
1101 if self.config.brighterFatterLevel ==
'DETECTOR':
1102 if level ==
'DETECTOR':
1103 if detName
in brighterFatterKernel.detKernels:
1104 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1106 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
1107 elif level ==
'AMP':
1108 self.log.warning(
"Making DETECTOR level kernel from AMP based brighter "
1110 brighterFatterKernel.makeDetectorKernelFromAmpwiseKernels(detName)
1111 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1112 elif self.config.brighterFatterLevel ==
'AMP':
1113 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
1115 if self.config.doFringe
is True and self.fringe.
checkFilter(inputs[
'ccdExposure']):
1116 expId = inputs[
'ccdExposure'].info.id
1117 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
1119 assembler=self.assembleCcd
1120 if self.config.doAssembleIsrExposures
else None)
1122 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
1124 if self.config.doStrayLight
is True and self.strayLight.
checkFilter(inputs[
'ccdExposure']):
1125 if 'strayLightData' not in inputs:
1126 inputs[
'strayLightData'] =
None
1128 outputs = self.
run(**inputs)
1129 butlerQC.put(outputs, outputRefs)
1132 def run(self, ccdExposure, *, camera=None, bias=None, linearizer=None,
1133 crosstalk=None, crosstalkSources=None,
1134 dark=None, flat=None, ptc=None, bfKernel=None, bfGains=None, defects=None,
1135 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1136 sensorTransmission=
None, atmosphereTransmission=
None,
1137 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1138 deferredCharge=
None,
1140 """Perform instrument signature removal on an exposure.
1142 Steps included in the ISR processing,
in order performed, are:
1143 - saturation
and suspect pixel masking
1144 - overscan subtraction
1145 - CCD assembly of individual amplifiers
1147 - variance image construction
1148 - linearization of non-linear response
1150 - brighter-fatter correction
1153 - stray light subtraction
1155 - masking of known defects
and camera specific features
1156 - vignette calculation
1157 - appending transmission curve
and distortion model
1162 The raw exposure that
is to be run through ISR. The
1163 exposure
is modified by this method.
1165 The camera geometry
for this exposure. Required
if
1166 one
or more of ``ccdExposure``, ``bias``, ``dark``,
or
1167 ``flat`` does
not have an associated detector.
1169 Bias calibration frame.
1171 Functor
for linearization.
1173 Calibration
for crosstalk.
1174 crosstalkSources : `list`, optional
1175 List of possible crosstalk sources.
1177 Dark calibration frame.
1179 Flat calibration frame.
1181 Photon transfer curve dataset,
with, e.g., gains
1183 bfKernel : `numpy.ndarray`, optional
1184 Brighter-fatter kernel.
1185 bfGains : `dict` of `float`, optional
1186 Gains used to override the detector
's nominal gains for the
1187 brighter-fatter correction. A dict keyed by amplifier name for
1188 the detector
in question.
1191 fringes : `lsst.pipe.base.Struct`, optional
1192 Struct containing the fringe correction data,
with
1195 - ``seed``: random seed derived
from the ccdExposureId
for random
1196 number generator (`uint32`)
1198 A ``TransmissionCurve`` that represents the throughput of the,
1199 optics, to be evaluated
in focal-plane coordinates.
1201 A ``TransmissionCurve`` that represents the throughput of the
1202 filter itself, to be evaluated
in focal-plane coordinates.
1204 A ``TransmissionCurve`` that represents the throughput of the
1205 sensor itself, to be evaluated
in post-assembly trimmed detector
1208 A ``TransmissionCurve`` that represents the throughput of the
1209 atmosphere, assumed to be spatially constant.
1210 detectorNum : `int`, optional
1211 The integer number
for the detector to process.
1212 strayLightData : `object`, optional
1213 Opaque object containing calibration information
for stray-light
1214 correction. If `
None`, no correction will be performed.
1216 Illumination correction image.
1220 result : `lsst.pipe.base.Struct`
1221 Result struct
with component:
1223 The fully ISR corrected exposure.
1225 An alias
for `exposure`
1226 - ``ossThumb`` : `numpy.ndarray`
1227 Thumbnail image of the exposure after overscan subtraction.
1228 - ``flattenedThumb`` : `numpy.ndarray`
1229 Thumbnail image of the exposure after flat-field correction.
1230 - ``outputStatistics`` : ``
1231 Values of the additional statistics calculated.
1236 Raised
if a configuration option
is set to
True, but the
1237 required calibration data has
not been specified.
1241 The current processed exposure can be viewed by setting the
1242 appropriate lsstDebug entries
in the `debug.display`
1243 dictionary. The names of these entries correspond to some of
1244 the IsrTaskConfig Boolean options,
with the value denoting the
1245 frame to use. The exposure
is shown inside the matching
1246 option check
and after the processing of that step has
1247 finished. The steps
with debug points are:
1258 In addition, setting the
"postISRCCD" entry displays the
1259 exposure after all ISR processing has finished.
1263 ccdExposure = self.ensureExposure(ccdExposure, camera, detectorNum)
1268 ccd = ccdExposure.getDetector()
1269 filterLabel = ccdExposure.getFilter()
1270 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
1273 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd."
1274 ccd = [
FakeAmp(ccdExposure, self.config)]
1277 if self.config.doBias
and bias
is None:
1278 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1280 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1281 if self.config.doBrighterFatter
and bfKernel
is None:
1282 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1283 if self.config.doDark
and dark
is None:
1284 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1285 if self.config.doFlat
and flat
is None:
1286 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1287 if self.config.doDefect
and defects
is None:
1288 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1289 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters
1290 and fringes.fringes
is None):
1295 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1296 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters
1297 and illumMaskedImage
is None):
1298 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1299 if (self.config.doDeferredCharge
and deferredCharge
is None):
1300 raise RuntimeError(
"Must supply a deferred charge calibration if config.doDeferredCharge=True.")
1303 if self.config.doConvertIntToFloat:
1304 self.log.info(
"Converting exposure to floating point values.")
1307 if self.config.doBias
and self.config.doBiasBeforeOverscan:
1308 self.log.info(
"Applying bias correction.")
1309 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1310 trimToFit=self.config.doTrimToMatchCalib)
1318 if ccdExposure.getBBox().contains(amp.getBBox()):
1323 if self.config.doOverscan
and not badAmp:
1326 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1327 if overscanResults
is not None and \
1328 self.config.qa
is not None and self.config.qa.saveStats
is True:
1329 if isinstance(overscanResults.overscanFit, float):
1330 qaMedian = overscanResults.overscanFit
1331 qaStdev = float(
"NaN")
1333 qaStats = afwMath.makeStatistics(overscanResults.overscanFit,
1334 afwMath.MEDIAN | afwMath.STDEVCLIP)
1335 qaMedian = qaStats.getValue(afwMath.MEDIAN)
1336 qaStdev = qaStats.getValue(afwMath.STDEVCLIP)
1338 self.metadata[f
"FIT MEDIAN {amp.getName()}"] = qaMedian
1339 self.metadata[f
"FIT STDEV {amp.getName()}"] = qaStdev
1340 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1341 amp.getName(), qaMedian, qaStdev)
1344 qaStatsAfter = afwMath.makeStatistics(overscanResults.overscanImage,
1345 afwMath.MEDIAN | afwMath.STDEVCLIP)
1346 qaMedianAfter = qaStatsAfter.getValue(afwMath.MEDIAN)
1347 qaStdevAfter = qaStatsAfter.getValue(afwMath.STDEVCLIP)
1349 self.metadata[f
"RESIDUAL MEDIAN {amp.getName()}"] = qaMedianAfter
1350 self.metadata[f
"RESIDUAL STDEV {amp.getName()}"] = qaStdevAfter
1351 self.log.debug(
" Overscan stats for amplifer %s after correction: %f +/- %f",
1352 amp.getName(), qaMedianAfter, qaStdevAfter)
1354 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1357 self.log.warning(
"Amplifier %s is bad.", amp.getName())
1358 overscanResults =
None
1360 overscans.append(overscanResults
if overscanResults
is not None else None)
1362 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1364 if self.config.doDeferredCharge:
1365 self.log.info(
"Applying deferred charge/CTI correction.")
1366 self.deferredChargeCorrection.
run(ccdExposure, deferredCharge)
1367 self.
debugView(ccdExposure,
"doDeferredCharge")
1369 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1370 self.log.info(
"Applying crosstalk correction.")
1371 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1372 crosstalkSources=crosstalkSources, camera=camera)
1373 self.
debugView(ccdExposure,
"doCrosstalk")
1375 if self.config.doAssembleCcd:
1376 self.log.info(
"Assembling CCD from amplifiers.")
1377 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1379 if self.config.expectWcs
and not ccdExposure.getWcs():
1380 self.log.warning(
"No WCS found in input exposure.")
1381 self.
debugView(ccdExposure,
"doAssembleCcd")
1384 if self.config.qa.doThumbnailOss:
1385 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1387 if self.config.doBias
and not self.config.doBiasBeforeOverscan:
1388 self.log.info(
"Applying bias correction.")
1389 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1390 trimToFit=self.config.doTrimToMatchCalib)
1393 if self.config.doVariance:
1394 for amp, overscanResults
in zip(ccd, overscans):
1395 if ccdExposure.getBBox().contains(amp.getBBox()):
1396 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1397 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1398 if overscanResults
is not None:
1400 overscanImage=overscanResults.overscanImage,
1406 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1407 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1408 afwMath.MEDIAN | afwMath.STDEVCLIP)
1409 self.metadata[f
"ISR VARIANCE {amp.getName()} MEDIAN"] = \
1410 qaStats.getValue(afwMath.MEDIAN)
1411 self.metadata[f
"ISR VARIANCE {amp.getName()} STDEV"] = \
1412 qaStats.getValue(afwMath.STDEVCLIP)
1413 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1414 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1415 qaStats.getValue(afwMath.STDEVCLIP))
1416 if self.config.maskNegativeVariance:
1420 self.log.info(
"Applying linearizer.")
1421 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1422 detector=ccd, log=self.log)
1424 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1425 self.log.info(
"Applying crosstalk correction.")
1426 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1427 crosstalkSources=crosstalkSources, isTrimmed=
True)
1428 self.
debugView(ccdExposure,
"doCrosstalk")
1433 if self.config.doDefect:
1434 self.log.info(
"Masking defects.")
1437 if self.config.numEdgeSuspect > 0:
1438 self.log.info(
"Masking edges as SUSPECT.")
1439 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1440 maskPlane=
"SUSPECT", level=self.config.edgeMaskLevel)
1442 if self.config.doNanMasking:
1443 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1446 if self.config.doWidenSaturationTrails:
1447 self.log.info(
"Widening saturation trails.")
1448 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1450 if self.config.doCameraSpecificMasking:
1451 self.log.info(
"Masking regions for camera specific reasons.")
1452 self.masking.
run(ccdExposure)
1454 if self.config.doBrighterFatter:
1464 interpExp = ccdExposure.clone()
1466 isrFunctions.interpolateFromMask(
1467 maskedImage=interpExp.getMaskedImage(),
1468 fwhm=self.config.fwhm,
1469 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1470 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1472 bfExp = interpExp.clone()
1474 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1475 type(bfKernel), type(bfGains))
1476 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1477 self.config.brighterFatterMaxIter,
1478 self.config.brighterFatterThreshold,
1479 self.config.brighterFatterApplyGain,
1481 if bfResults[1] == self.config.brighterFatterMaxIter:
1482 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1485 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1487 image = ccdExposure.getMaskedImage().getImage()
1488 bfCorr = bfExp.getMaskedImage().getImage()
1489 bfCorr -= interpExp.getMaskedImage().getImage()
1498 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1499 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1502 if self.config.brighterFatterMaskGrowSize > 0:
1503 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1504 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1505 isrFunctions.growMasks(ccdExposure.getMask(),
1506 radius=self.config.brighterFatterMaskGrowSize,
1507 maskNameList=maskPlane,
1508 maskValue=maskPlane)
1510 self.
debugView(ccdExposure,
"doBrighterFatter")
1512 if self.config.doDark:
1513 self.log.info(
"Applying dark correction.")
1517 if self.config.doFringe
and not self.config.fringeAfterFlat:
1518 self.log.info(
"Applying fringe correction before flat.")
1519 self.fringe.
run(ccdExposure, **fringes.getDict())
1522 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1523 self.log.info(
"Checking strayLight correction.")
1524 self.strayLight.
run(ccdExposure, strayLightData)
1525 self.
debugView(ccdExposure,
"doStrayLight")
1527 if self.config.doFlat:
1528 self.log.info(
"Applying flat correction.")
1532 if self.config.doApplyGains:
1533 self.log.info(
"Applying gain correction instead of flat.")
1534 if self.config.usePtcGains:
1535 self.log.info(
"Using gains from the Photon Transfer Curve.")
1536 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains,
1539 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1541 if self.config.doFringe
and self.config.fringeAfterFlat:
1542 self.log.info(
"Applying fringe correction after flat.")
1543 self.fringe.
run(ccdExposure, **fringes.getDict())
1545 if self.config.doVignette:
1546 if self.config.doMaskVignettePolygon:
1547 self.log.info(
"Constructing, attaching, and masking vignette polygon.")
1549 self.log.info(
"Constructing and attaching vignette polygon.")
1551 exposure=ccdExposure, doUpdateMask=self.config.doMaskVignettePolygon,
1552 vignetteValue=self.config.vignetteValue, log=self.log)
1554 if self.config.doAttachTransmissionCurve:
1555 self.log.info(
"Adding transmission curves.")
1556 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1557 filterTransmission=filterTransmission,
1558 sensorTransmission=sensorTransmission,
1559 atmosphereTransmission=atmosphereTransmission)
1561 flattenedThumb =
None
1562 if self.config.qa.doThumbnailFlattened:
1563 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1565 if self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters:
1566 self.log.info(
"Performing illumination correction.")
1567 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1568 illumMaskedImage, illumScale=self.config.illumScale,
1569 trimToFit=self.config.doTrimToMatchCalib)
1572 if self.config.doSaveInterpPixels:
1573 preInterpExp = ccdExposure.clone()
1588 if self.config.doSetBadRegions:
1589 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1590 if badPixelCount > 0:
1591 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1593 if self.config.doInterpolate:
1594 self.log.info(
"Interpolating masked pixels.")
1595 isrFunctions.interpolateFromMask(
1596 maskedImage=ccdExposure.getMaskedImage(),
1597 fwhm=self.config.fwhm,
1598 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1599 maskNameList=list(self.config.maskListToInterpolate)
1605 if self.config.doAmpOffset:
1606 self.log.info(
"Correcting amp offsets.")
1607 self.ampOffset.
run(ccdExposure)
1609 if self.config.doMeasureBackground:
1610 self.log.info(
"Measuring background level.")
1613 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1615 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1616 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1617 afwMath.MEDIAN | afwMath.STDEVCLIP)
1618 self.metadata[f
"ISR BACKGROUND {amp.getName()} MEDIAN"] = qaStats.getValue(afwMath.MEDIAN)
1619 self.metadata[f
"ISR BACKGROUND {amp.getName()} STDEV"] = \
1620 qaStats.getValue(afwMath.STDEVCLIP)
1621 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1622 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1623 qaStats.getValue(afwMath.STDEVCLIP))
1626 outputStatistics =
None
1627 if self.config.doCalculateStatistics:
1628 outputStatistics = self.isrStats.
run(ccdExposure, overscanResults=overscans,
1631 self.
debugView(ccdExposure,
"postISRCCD")
1633 return pipeBase.Struct(
1634 exposure=ccdExposure,
1636 flattenedThumb=flattenedThumb,
1638 preInterpExposure=preInterpExp,
1639 outputExposure=ccdExposure,
1640 outputOssThumbnail=ossThumb,
1641 outputFlattenedThumbnail=flattenedThumb,
1642 outputStatistics=outputStatistics,
1646 """Ensure that the data returned by Butler is a fully constructed exp.
1648 ISR requires exposure-level image data for historical reasons, so
if we
1649 did
not recieve that
from Butler, construct it
from what we have,
1650 modifying the input
in place.
1655 or `lsst.afw.image.ImageF`
1656 The input data structure obtained
from Butler.
1657 camera : `lsst.afw.cameraGeom.camera`, optional
1658 The camera associated
with the image. Used to find the appropriate
1659 detector
if detector
is not already set.
1660 detectorNum : `int`, optional
1661 The detector
in the camera to attach,
if the detector
is not
1667 The re-constructed exposure,
with appropriate detector parameters.
1672 Raised
if the input data cannot be used to construct an exposure.
1674 if isinstance(inputExp, afwImage.DecoratedImageU):
1675 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1676 elif isinstance(inputExp, afwImage.ImageF):
1677 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1678 elif isinstance(inputExp, afwImage.MaskedImageF):
1679 inputExp = afwImage.makeExposure(inputExp)
1680 elif isinstance(inputExp, afwImage.Exposure):
1682 elif inputExp
is None:
1686 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1689 if inputExp.getDetector()
is None:
1690 if camera
is None or detectorNum
is None:
1691 raise RuntimeError(
'Must supply both a camera and detector number when using exposures '
1692 'without a detector set.')
1693 inputExp.setDetector(camera[detectorNum])
1698 """Convert exposure image from uint16 to float.
1700 If the exposure does not need to be converted, the input
is
1701 immediately returned. For exposures that are converted to use
1702 floating point pixels, the variance
is set to unity
and the
1708 The raw exposure to be converted.
1713 The input ``exposure``, converted to floating point pixels.
1718 Raised
if the exposure type cannot be converted to float.
1721 if isinstance(exposure, afwImage.ExposureF):
1723 self.log.debug(
"Exposure already of type float.")
1725 if not hasattr(exposure,
"convertF"):
1726 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1728 newexposure = exposure.convertF()
1729 newexposure.variance[:] = 1
1730 newexposure.mask[:] = 0x0
1735 """Identify bad amplifiers, saturated and suspect pixels.
1740 Input exposure to be masked.
1742 Catalog of parameters defining the amplifier on this
1745 List of defects. Used to determine if the entire
1751 If this
is true, the entire amplifier area
is covered by
1752 defects
and unusable.
1755 maskedImage = ccdExposure.getMaskedImage()
1762 if defects
is not None:
1763 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1769 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1771 maskView = dataView.getMask()
1772 maskView |= maskView.getPlaneBitMask(
"BAD")
1780 if self.config.doSaturation
and not badAmp:
1781 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1782 if self.config.doSuspect
and not badAmp:
1783 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1784 if math.isfinite(self.config.saturation):
1785 limits.update({self.config.saturatedMaskName: self.config.saturation})
1787 for maskName, maskThreshold
in limits.items():
1788 if not math.isnan(maskThreshold):
1789 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1790 isrFunctions.makeThresholdMask(
1791 maskedImage=dataView,
1792 threshold=maskThreshold,
1799 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1801 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1802 self.config.suspectMaskName])
1803 if numpy.all(maskView.getArray() & maskVal > 0):
1805 maskView |= maskView.getPlaneBitMask(
"BAD")
1810 """Apply overscan correction in place.
1812 This method does initial pixel rejection of the overscan
1813 region. The overscan can also be optionally segmented to
1814 allow for discontinuous overscan responses to be fit
1815 separately. The actual overscan subtraction
is performed by
1816 the `lsst.ip.isr.isrFunctions.overscanCorrection` function,
1817 which
is called here after the amplifier
is preprocessed.
1822 Exposure to have overscan correction performed.
1823 amp : `lsst.afw.cameraGeom.Amplifer`
1824 The amplifier to consider
while correcting the overscan.
1828 overscanResults : `lsst.pipe.base.Struct`
1829 Result struct
with components:
1831 Value
or fit subtracted
from the amplifier image data.
1833 Value
or fit subtracted
from the overscan image data.
1835 Image of the overscan region
with the overscan
1836 correction applied. This quantity
is used to estimate
1837 the amplifier read noise empirically.
1842 Raised
if the ``amp`` does
not contain raw pixel information.
1846 lsst.ip.isr.isrFunctions.overscanCorrection
1848 if amp.getRawHorizontalOverscanBBox().isEmpty():
1849 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1852 statControl = afwMath.StatisticsControl()
1853 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1856 dataBBox = amp.getRawDataBBox()
1857 oscanBBox = amp.getRawHorizontalOverscanBBox()
1861 prescanBBox = amp.getRawPrescanBBox()
1862 if (oscanBBox.getBeginX() > prescanBBox.getBeginX()):
1863 dx0 += self.config.overscanNumLeadingColumnsToSkip
1864 dx1 -= self.config.overscanNumTrailingColumnsToSkip
1866 dx0 += self.config.overscanNumTrailingColumnsToSkip
1867 dx1 -= self.config.overscanNumLeadingColumnsToSkip
1874 if ((self.config.overscanBiasJump
1875 and self.config.overscanBiasJumpLocation)
1876 and (ccdExposure.getMetadata().exists(self.config.overscanBiasJumpKeyword)
1877 and ccdExposure.getMetadata().getScalar(self.config.overscanBiasJumpKeyword)
in
1878 self.config.overscanBiasJumpDevices)):
1879 if amp.getReadoutCorner()
in (ReadoutCorner.LL, ReadoutCorner.LR):
1880 yLower = self.config.overscanBiasJumpLocation
1881 yUpper = dataBBox.getHeight() - yLower
1883 yUpper = self.config.overscanBiasJumpLocation
1884 yLower = dataBBox.getHeight() - yUpper
1902 oscanBBox.getHeight())))
1906 for imageBBox, overscanBBox
in zip(imageBBoxes, overscanBBoxes):
1907 ampImage = ccdExposure.maskedImage[imageBBox]
1908 overscanImage = ccdExposure.maskedImage[overscanBBox]
1910 overscanArray = overscanImage.image.array
1911 median = numpy.ma.median(numpy.ma.masked_where(overscanImage.mask.array, overscanArray))
1912 bad = numpy.where(numpy.abs(overscanArray - median) > self.config.overscanMaxDev)
1913 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask(
"SAT")
1915 statControl = afwMath.StatisticsControl()
1916 statControl.setAndMask(ccdExposure.mask.getPlaneBitMask(
"SAT"))
1918 overscanResults = self.overscan.
run(ampImage.getImage(), overscanImage, amp)
1921 if dx0 != 0
or dx1 != 0:
1922 fullOverscan = ccdExposure.maskedImage[oscanBBox]
1923 overscanVector = overscanResults.overscanFit.array[:, 0]
1924 overscanModel = afwImage.ImageF(fullOverscan.getDimensions())
1925 overscanModel.array[:, :] = 0.0
1926 overscanModel.array[:, 0:dx0] = overscanVector[:, numpy.newaxis]
1927 overscanModel.array[:, dx1:] = overscanVector[:, numpy.newaxis]
1928 fullOverscanImage = fullOverscan.getImage()
1929 fullOverscanImage -= overscanModel
1930 overscanResults = pipeBase.Struct(imageFit=overscanResults.imageFit,
1931 overscanFit=overscanModel,
1932 overscanImage=fullOverscan,
1933 edgeMask=overscanResults.edgeMask)
1936 levelStat = afwMath.MEDIAN
1937 sigmaStat = afwMath.STDEVCLIP
1939 sctrl = afwMath.StatisticsControl(self.config.qa.flatness.clipSigma,
1940 self.config.qa.flatness.nIter)
1941 metadata = ccdExposure.getMetadata()
1942 ampNum = amp.getName()
1944 if isinstance(overscanResults.overscanFit, float):
1945 metadata[f
"ISR_OSCAN_LEVEL{ampNum}"] = overscanResults.overscanFit
1946 metadata[f
"ISR_OSCAN_SIGMA{ampNum}"] = 0.0
1948 stats = afwMath.makeStatistics(overscanResults.overscanFit, levelStat | sigmaStat, sctrl)
1949 metadata[f
"ISR_OSCAN_LEVEL{ampNum}"] = stats.getValue(levelStat)
1950 metadata[f
"ISR_OSCAN_SIGMA%{ampNum}"] = stats.getValue(sigmaStat)
1952 return overscanResults
1955 """Set the variance plane using the gain and read noise
1957 The read noise is calculated
from the ``overscanImage``
if the
1958 ``doEmpiricalReadNoise`` option
is set
in the configuration; otherwise
1959 the value
from the amplifier data
is used.
1964 Exposure to process.
1965 amp : `lsst.afw.table.AmpInfoRecord`
or `FakeAmp`
1966 Amplifier detector data.
1968 Image of overscan, required only
for empirical read noise.
1970 PTC dataset containing the gains
and read noise.
1976 Raised
if either ``usePtcGains`` of ``usePtcReadNoise``
1977 are ``
True``, but ptcDataset
is not provided.
1979 Raised
if ```doEmpiricalReadNoise``
is ``
True`` but
1980 ``overscanImage``
is ``
None``.
1984 lsst.ip.isr.isrFunctions.updateVariance
1986 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1987 if self.config.usePtcGains:
1988 if ptcDataset
is None:
1989 raise RuntimeError(
"No ptcDataset provided to use PTC gains.")
1991 gain = ptcDataset.gain[amp.getName()]
1992 self.log.info(
"Using gain from Photon Transfer Curve.")
1994 gain = amp.getGain()
1996 if math.isnan(gain):
1998 self.log.warning(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
2001 self.log.warning(
"Gain for amp %s == %g <= 0; setting to %f.",
2002 amp.getName(), gain, patchedGain)
2005 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
2006 raise RuntimeError(
"Overscan is none for EmpiricalReadNoise.")
2008 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
2009 stats = afwMath.StatisticsControl()
2010 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
2011 readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
2012 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
2013 amp.getName(), readNoise)
2014 elif self.config.usePtcReadNoise:
2015 if ptcDataset
is None:
2016 raise RuntimeError(
"No ptcDataset provided to use PTC readnoise.")
2018 readNoise = ptcDataset.noise[amp.getName()]
2019 self.log.info(
"Using read noise from Photon Transfer Curve.")
2021 readNoise = amp.getReadNoise()
2023 isrFunctions.updateVariance(
2024 maskedImage=ampExposure.getMaskedImage(),
2026 readNoise=readNoise,
2030 """Identify and mask pixels with negative variance values.
2035 Exposure to process.
2039 lsst.ip.isr.isrFunctions.updateVariance
2041 maskPlane = exposure.getMask().getPlaneBitMask(self.config.negativeVarianceMaskName)
2042 bad = numpy.where(exposure.getVariance().getArray() <= 0.0)
2043 exposure.mask.array[bad] |= maskPlane
2046 """Apply dark correction in place.
2051 Exposure to process.
2053 Dark exposure of the same size as ``exposure``.
2054 invert : `Bool`, optional
2055 If
True, re-add the dark to an already corrected image.
2060 Raised
if either ``exposure``
or ``darkExposure`` do
not
2061 have their dark time defined.
2065 lsst.ip.isr.isrFunctions.darkCorrection
2067 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
2068 if math.isnan(expScale):
2069 raise RuntimeError(
"Exposure darktime is NAN.")
2070 if darkExposure.getInfo().getVisitInfo()
is not None \
2071 and not math.isnan(darkExposure.getInfo().getVisitInfo().getDarkTime()):
2072 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
2076 self.log.warning(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
2079 isrFunctions.darkCorrection(
2080 maskedImage=exposure.getMaskedImage(),
2081 darkMaskedImage=darkExposure.getMaskedImage(),
2083 darkScale=darkScale,
2085 trimToFit=self.config.doTrimToMatchCalib
2089 """Check if linearization is needed for the detector cameraGeom.
2091 Checks config.doLinearize and the linearity type of the first
2097 Detector to get linearity type
from.
2101 doLinearize : `Bool`
2102 If
True, linearization should be performed.
2104 return self.config.doLinearize
and \
2105 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
2108 """Apply flat correction in place.
2113 Exposure to process.
2115 Flat exposure of the same size as ``exposure``.
2116 invert : `Bool`, optional
2117 If
True, unflatten an already flattened image.
2121 lsst.ip.isr.isrFunctions.flatCorrection
2123 isrFunctions.flatCorrection(
2124 maskedImage=exposure.getMaskedImage(),
2125 flatMaskedImage=flatExposure.getMaskedImage(),
2126 scalingType=self.config.flatScalingType,
2127 userScale=self.config.flatUserScale,
2129 trimToFit=self.config.doTrimToMatchCalib
2133 """Detect and mask saturated pixels in config.saturatedMaskName.
2138 Exposure to process. Only the amplifier DataSec is processed.
2140 Amplifier detector data.
2144 lsst.ip.isr.isrFunctions.makeThresholdMask
2146 if not math.isnan(amp.getSaturation()):
2147 maskedImage = exposure.getMaskedImage()
2148 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2149 isrFunctions.makeThresholdMask(
2150 maskedImage=dataView,
2151 threshold=amp.getSaturation(),
2153 maskName=self.config.saturatedMaskName,
2157 """Interpolate over saturated pixels, in place.
2159 This method should be called after `saturationDetection`, to
2160 ensure that the saturated pixels have been identified in the
2161 SAT mask. It should also be called after `assembleCcd`, since
2162 saturated regions may cross amplifier boundaries.
2167 Exposure to process.
2171 lsst.ip.isr.isrTask.saturationDetection
2172 lsst.ip.isr.isrFunctions.interpolateFromMask
2174 isrFunctions.interpolateFromMask(
2175 maskedImage=exposure.getMaskedImage(),
2176 fwhm=self.config.fwhm,
2177 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2178 maskNameList=list(self.config.saturatedMaskName),
2182 """Detect and mask suspect pixels in config.suspectMaskName.
2187 Exposure to process. Only the amplifier DataSec is processed.
2189 Amplifier detector data.
2193 lsst.ip.isr.isrFunctions.makeThresholdMask
2197 Suspect pixels are pixels whose value
is greater than
2198 amp.getSuspectLevel(). This
is intended to indicate pixels that may be
2199 affected by unknown systematics;
for example
if non-linearity
2200 corrections above a certain level are unstable then that would be a
2201 useful value
for suspectLevel. A value of `nan` indicates that no such
2202 level exists
and no pixels are to be masked
as suspicious.
2204 suspectLevel = amp.getSuspectLevel()
2205 if math.isnan(suspectLevel):
2208 maskedImage = exposure.getMaskedImage()
2209 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2210 isrFunctions.makeThresholdMask(
2211 maskedImage=dataView,
2212 threshold=suspectLevel,
2214 maskName=self.config.suspectMaskName,
2218 """Mask defects using mask plane "BAD", in place.
2223 Exposure to process.
2226 List of defects to mask.
2230 Call this after CCD assembly, since defects may cross amplifier
2233 maskedImage = exposure.getMaskedImage()
2234 if not isinstance(defectBaseList, Defects):
2236 defectList =
Defects(defectBaseList)
2238 defectList = defectBaseList
2239 defectList.maskPixels(maskedImage, maskName=
"BAD")
2241 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR'):
2242 """Mask edge pixels with applicable mask plane.
2247 Exposure to process.
2248 numEdgePixels : `int`, optional
2249 Number of edge pixels to mask.
2250 maskPlane : `str`, optional
2251 Mask plane name to use.
2252 level : `str`, optional
2253 Level at which to mask edges.
2255 maskedImage = exposure.getMaskedImage()
2256 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2258 if numEdgePixels > 0:
2259 if level ==
'DETECTOR':
2260 boxes = [maskedImage.getBBox()]
2261 elif level ==
'AMP':
2262 boxes = [amp.getBBox()
for amp
in exposure.getDetector()]
2267 subImage = maskedImage[box]
2268 box.grow(-numEdgePixels)
2270 SourceDetectionTask.setEdgeBits(
2276 """Mask and interpolate defects using mask plane "BAD", in place.
2281 Exposure to process.
2284 List of defects to mask
and interpolate.
2288 lsst.ip.isr.isrTask.maskDefect
2291 self.maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2292 maskPlane="SUSPECT", level=self.config.edgeMaskLevel)
2293 isrFunctions.interpolateFromMask(
2294 maskedImage=exposure.getMaskedImage(),
2295 fwhm=self.config.fwhm,
2296 growSaturatedFootprints=0,
2297 maskNameList=[
"BAD"],
2301 """Mask NaNs using mask plane "UNMASKEDNAN", in place.
2306 Exposure to process.
2310 We mask over all non-finite values (NaN, inf), including those
2311 that are masked with other bits (because those may
or may
not be
2312 interpolated over later,
and we want to remove all NaN/infs).
2313 Despite this behaviour, the
"UNMASKEDNAN" mask plane
is used to
2314 preserve the historical name.
2316 maskedImage = exposure.getMaskedImage()
2319 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2320 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2321 numNans =
maskNans(maskedImage, maskVal)
2322 self.metadata[
"NUMNANS"] = numNans
2324 self.log.warning(
"There were %d unmasked NaNs.", numNans)
2327 """"Mask and interpolate NaN/infs using mask plane "UNMASKEDNAN",
2333 Exposure to process.
2337 lsst.ip.isr.isrTask.maskNan
2340 isrFunctions.interpolateFromMask(
2341 maskedImage=exposure.getMaskedImage(),
2342 fwhm=self.config.fwhm,
2343 growSaturatedFootprints=0,
2344 maskNameList=["UNMASKEDNAN"],
2348 """Measure the image background in subgrids, for quality control.
2353 Exposure to process.
2355 Configuration object containing parameters on which background
2356 statistics and subgrids to use.
2358 if IsrQaConfig
is not None:
2359 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2360 IsrQaConfig.flatness.nIter)
2361 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2362 statsControl.setAndMask(maskVal)
2363 maskedImage = exposure.getMaskedImage()
2364 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2365 skyLevel = stats.getValue(afwMath.MEDIAN)
2366 skySigma = stats.getValue(afwMath.STDEVCLIP)
2367 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2368 metadata = exposure.getMetadata()
2369 metadata[
"SKYLEVEL"] = skyLevel
2370 metadata[
"SKYSIGMA"] = skySigma
2373 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2374 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2375 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2376 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2377 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2378 skyLevels = numpy.zeros((nX, nY))
2381 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2383 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2385 xLLC = xc - meshXHalf
2386 yLLC = yc - meshYHalf
2387 xURC = xc + meshXHalf - 1
2388 yURC = yc + meshYHalf - 1
2391 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2393 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2395 good = numpy.where(numpy.isfinite(skyLevels))
2396 skyMedian = numpy.median(skyLevels[good])
2397 flatness = (skyLevels[good] - skyMedian) / skyMedian
2398 flatness_rms = numpy.std(flatness)
2399 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2401 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2402 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2403 nX, nY, flatness_pp, flatness_rms)
2405 metadata[
"FLATNESS_PP"] = float(flatness_pp)
2406 metadata[
"FLATNESS_RMS"] = float(flatness_rms)
2407 metadata[
"FLATNESS_NGRIDS"] =
'%dx%d' % (nX, nY)
2408 metadata[
"FLATNESS_MESHX"] = IsrQaConfig.flatness.meshX
2409 metadata[
"FLATNESS_MESHY"] = IsrQaConfig.flatness.meshY
2412 """Set an approximate magnitude zero point for the exposure.
2417 Exposure to process.
2419 filterLabel = exposure.getFilter()
2420 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
2422 if physicalFilter
in self.config.fluxMag0T1:
2423 fluxMag0 = self.config.fluxMag0T1[physicalFilter]
2425 self.log.warning(
"No rough magnitude zero point defined for filter %s.", physicalFilter)
2426 fluxMag0 = self.config.defaultFluxMag0T1
2428 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2430 self.log.warning(
"Non-positive exposure time; skipping rough zero point.")
2433 self.log.info(
"Setting rough magnitude zero point for filter %s: %f",
2434 physicalFilter, 2.5*math.log10(fluxMag0*expTime))
2435 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2439 """Context manager that applies and removes flats and darks,
2440 if the task
is configured to apply them.
2445 Exposure to process.
2447 Flat exposure the same size
as ``exp``.
2449 Dark exposure the same size
as ``exp``.
2454 The flat
and dark corrected exposure.
2456 if self.config.doDark
and dark
is not None:
2458 if self.config.doFlat:
2463 if self.config.doFlat:
2465 if self.config.doDark
and dark
is not None:
2469 """Utility function to examine ISR exposure at different stages.
2476 State of processing to view.
2478 frame = getDebugFrame(self._display, stepname)
2480 display = getDisplay(frame)
2481 display.scale(
'asinh',
'zscale')
2482 display.mtv(exposure)
2483 prompt =
"Press Enter to continue [c]... "
2485 ans = input(prompt).lower()
2486 if ans
in (
"",
"c",):
2491 """A Detector-like object that supports returning gain and saturation level
2493 This is used when the input exposure does
not have a detector.
2498 Exposure to generate a fake amplifier
for.
2499 config : `lsst.ip.isr.isrTaskConfig`
2500 Configuration to apply to the fake amplifier.
2504 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2506 self.
_gain = config.gain
def getRawHorizontalOverscanBBox(self)
def getSuspectLevel(self)
_RawHorizontalOverscanBBox
def __init__(self, exposure, config)
doSaturationInterpolation
def __init__(self, *config=None)
def flatCorrection(self, exposure, flatExposure, invert=False)
def maskAndInterpolateNan(self, exposure)
def saturationInterpolation(self, exposure)
def maskNan(self, exposure)
def maskAmplifier(self, ccdExposure, amp, defects)
def debugView(self, exposure, stepname)
def ensureExposure(self, inputExp, camera=None, detectorNum=None)
def maskNegativeVariance(self, exposure)
def saturationDetection(self, exposure, amp)
def maskDefect(self, exposure, defectBaseList)
def __init__(self, **kwargs)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR')
def overscanCorrection(self, ccdExposure, amp)
def run(self, ccdExposure, *camera=None, bias=None, linearizer=None, crosstalk=None, crosstalkSources=None, dark=None, flat=None, ptc=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, deferredCharge=None)
def measureBackground(self, exposure, IsrQaConfig=None)
def roughZeroPoint(self, exposure)
def maskAndInterpolateDefects(self, exposure, defectBaseList)
def doLinearize(self, detector)
def flatContext(self, exp, flat, dark=None)
def convertIntToFloat(self, exposure)
def suspectDetection(self, exposure, amp)
def updateVariance(self, ampExposure, amp, overscanImage=None, ptcDataset=None)
def darkCorrection(self, exposure, darkExposure, invert=False)
def checkFilter(exposure, filterList, log)
def crosstalkSourceLookup(datasetType, registry, quantumDataId, collections)
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Mask NANs in an image.