22__all__ = [
"IsrTask",
"IsrTaskConfig"]
31import lsst.pipe.base
as pipeBase
32import lsst.pipe.base.connectionTypes
as cT
34from contextlib
import contextmanager
35from lsstDebug
import getDebugFrame
40from lsst.utils.timer
import timeMethod
42from .
import isrFunctions
44from .
import linearize
45from .defects
import Defects
47from .assembleCcdTask
import AssembleCcdTask
48from .crosstalk
import CrosstalkTask, CrosstalkCalib
49from .fringe
import FringeTask
50from .isr
import maskNans
51from .masking
import MaskingTask
52from .overscan
import OverscanCorrectionTask
53from .straylight
import StrayLightTask
54from .vignette
import VignetteTask
55from .ampOffset
import AmpOffsetTask
56from .deferredCharge
import DeferredChargeTask
57from .isrStatistics
import IsrStatisticsTask
58from lsst.daf.butler
import DimensionGraph
62 """Lookup function to identify crosstalkSource entries.
64 This should return an empty list under most circumstances. Only
65 when inter-chip crosstalk has been identified should this be
72 registry : `lsst.daf.butler.Registry`
73 Butler registry to query.
74 quantumDataId : `lsst.daf.butler.ExpandedDataCoordinate`
75 Data id to transform to identify crosstalkSources. The
76 ``detector`` entry will be stripped.
77 collections : `lsst.daf.butler.CollectionSearch`
78 Collections to search through.
82 results : `list` [`lsst.daf.butler.DatasetRef`]
83 List of datasets that match the query that will be used
as
86 newDataId = quantumDataId.subset(DimensionGraph(registry.dimensions, names=["instrument",
"exposure"]))
87 results = set(registry.queryDatasets(datasetType, collections=collections, dataId=newDataId,
94 return [ref.expanded(registry.expandDataId(ref.dataId, records=newDataId.records))
for ref
in results]
98 dimensions={
"instrument",
"exposure",
"detector"},
100 ccdExposure = cT.Input(
102 doc=
"Input exposure to process.",
103 storageClass=
"Exposure",
104 dimensions=[
"instrument",
"exposure",
"detector"],
106 camera = cT.PrerequisiteInput(
108 storageClass=
"Camera",
109 doc=
"Input camera to construct complete exposures.",
110 dimensions=[
"instrument"],
114 crosstalk = cT.PrerequisiteInput(
116 doc=
"Input crosstalk object",
117 storageClass=
"CrosstalkCalib",
118 dimensions=[
"instrument",
"detector"],
122 crosstalkSources = cT.PrerequisiteInput(
123 name=
"isrOverscanCorrected",
124 doc=
"Overscan corrected input images.",
125 storageClass=
"Exposure",
126 dimensions=[
"instrument",
"exposure",
"detector"],
129 lookupFunction=crosstalkSourceLookup,
132 bias = cT.PrerequisiteInput(
134 doc=
"Input bias calibration.",
135 storageClass=
"ExposureF",
136 dimensions=[
"instrument",
"detector"],
139 dark = cT.PrerequisiteInput(
141 doc=
"Input dark calibration.",
142 storageClass=
"ExposureF",
143 dimensions=[
"instrument",
"detector"],
146 flat = cT.PrerequisiteInput(
148 doc=
"Input flat calibration.",
149 storageClass=
"ExposureF",
150 dimensions=[
"instrument",
"physical_filter",
"detector"],
153 ptc = cT.PrerequisiteInput(
155 doc=
"Input Photon Transfer Curve dataset",
156 storageClass=
"PhotonTransferCurveDataset",
157 dimensions=[
"instrument",
"detector"],
160 fringes = cT.PrerequisiteInput(
162 doc=
"Input fringe calibration.",
163 storageClass=
"ExposureF",
164 dimensions=[
"instrument",
"physical_filter",
"detector"],
168 strayLightData = cT.PrerequisiteInput(
170 doc=
"Input stray light calibration.",
171 storageClass=
"StrayLightData",
172 dimensions=[
"instrument",
"physical_filter",
"detector"],
177 bfKernel = cT.PrerequisiteInput(
179 doc=
"Input brighter-fatter kernel.",
180 storageClass=
"NumpyArray",
181 dimensions=[
"instrument"],
185 newBFKernel = cT.PrerequisiteInput(
186 name=
'brighterFatterKernel',
187 doc=
"Newer complete kernel + gain solutions.",
188 storageClass=
"BrighterFatterKernel",
189 dimensions=[
"instrument",
"detector"],
193 defects = cT.PrerequisiteInput(
195 doc=
"Input defect tables.",
196 storageClass=
"Defects",
197 dimensions=[
"instrument",
"detector"],
200 linearizer = cT.PrerequisiteInput(
202 storageClass=
"Linearizer",
203 doc=
"Linearity correction calibration.",
204 dimensions=[
"instrument",
"detector"],
208 opticsTransmission = cT.PrerequisiteInput(
209 name=
"transmission_optics",
210 storageClass=
"TransmissionCurve",
211 doc=
"Transmission curve due to the optics.",
212 dimensions=[
"instrument"],
215 filterTransmission = cT.PrerequisiteInput(
216 name=
"transmission_filter",
217 storageClass=
"TransmissionCurve",
218 doc=
"Transmission curve due to the filter.",
219 dimensions=[
"instrument",
"physical_filter"],
222 sensorTransmission = cT.PrerequisiteInput(
223 name=
"transmission_sensor",
224 storageClass=
"TransmissionCurve",
225 doc=
"Transmission curve due to the sensor.",
226 dimensions=[
"instrument",
"detector"],
229 atmosphereTransmission = cT.PrerequisiteInput(
230 name=
"transmission_atmosphere",
231 storageClass=
"TransmissionCurve",
232 doc=
"Transmission curve due to the atmosphere.",
233 dimensions=[
"instrument"],
236 illumMaskedImage = cT.PrerequisiteInput(
238 doc=
"Input illumination correction.",
239 storageClass=
"MaskedImageF",
240 dimensions=[
"instrument",
"physical_filter",
"detector"],
243 deferredChargeCalib = cT.PrerequisiteInput(
245 doc=
"Deferred charge/CTI correction dataset.",
246 storageClass=
"IsrCalib",
247 dimensions=[
"instrument",
"detector"],
251 outputExposure = cT.Output(
253 doc=
"Output ISR processed exposure.",
254 storageClass=
"Exposure",
255 dimensions=[
"instrument",
"exposure",
"detector"],
257 preInterpExposure = cT.Output(
258 name=
'preInterpISRCCD',
259 doc=
"Output ISR processed exposure, with pixels left uninterpolated.",
260 storageClass=
"ExposureF",
261 dimensions=[
"instrument",
"exposure",
"detector"],
263 outputOssThumbnail = cT.Output(
265 doc=
"Output Overscan-subtracted thumbnail image.",
266 storageClass=
"Thumbnail",
267 dimensions=[
"instrument",
"exposure",
"detector"],
269 outputFlattenedThumbnail = cT.Output(
270 name=
"FlattenedThumb",
271 doc=
"Output flat-corrected thumbnail image.",
272 storageClass=
"Thumbnail",
273 dimensions=[
"instrument",
"exposure",
"detector"],
275 outputStatistics = cT.Output(
276 name=
"isrStatistics",
277 doc=
"Output of additional statistics table.",
278 storageClass=
"StructuredDataDict",
279 dimensions=[
"instrument",
"exposure",
"detector"],
285 if config.doBias
is not True:
286 self.prerequisiteInputs.remove(
"bias")
287 if config.doLinearize
is not True:
288 self.prerequisiteInputs.remove(
"linearizer")
289 if config.doCrosstalk
is not True:
290 self.prerequisiteInputs.remove(
"crosstalkSources")
291 self.prerequisiteInputs.remove(
"crosstalk")
292 if config.doBrighterFatter
is not True:
293 self.prerequisiteInputs.remove(
"bfKernel")
294 self.prerequisiteInputs.remove(
"newBFKernel")
295 if config.doDefect
is not True:
296 self.prerequisiteInputs.remove(
"defects")
297 if config.doDark
is not True:
298 self.prerequisiteInputs.remove(
"dark")
299 if config.doFlat
is not True:
300 self.prerequisiteInputs.remove(
"flat")
301 if config.doFringe
is not True:
302 self.prerequisiteInputs.remove(
"fringes")
303 if config.doStrayLight
is not True:
304 self.prerequisiteInputs.remove(
"strayLightData")
305 if config.usePtcGains
is not True and config.usePtcReadNoise
is not True:
306 self.prerequisiteInputs.remove(
"ptc")
307 if config.doAttachTransmissionCurve
is not True:
308 self.prerequisiteInputs.remove(
"opticsTransmission")
309 self.prerequisiteInputs.remove(
"filterTransmission")
310 self.prerequisiteInputs.remove(
"sensorTransmission")
311 self.prerequisiteInputs.remove(
"atmosphereTransmission")
313 if config.doUseOpticsTransmission
is not True:
314 self.prerequisiteInputs.remove(
"opticsTransmission")
315 if config.doUseFilterTransmission
is not True:
316 self.prerequisiteInputs.remove(
"filterTransmission")
317 if config.doUseSensorTransmission
is not True:
318 self.prerequisiteInputs.remove(
"sensorTransmission")
319 if config.doUseAtmosphereTransmission
is not True:
320 self.prerequisiteInputs.remove(
"atmosphereTransmission")
321 if config.doIlluminationCorrection
is not True:
322 self.prerequisiteInputs.remove(
"illumMaskedImage")
323 if config.doDeferredCharge
is not True:
324 self.prerequisiteInputs.remove(
"deferredChargeCalib")
326 if config.doWrite
is not True:
327 self.outputs.remove(
"outputExposure")
328 self.outputs.remove(
"preInterpExposure")
329 self.outputs.remove(
"outputFlattenedThumbnail")
330 self.outputs.remove(
"outputOssThumbnail")
331 self.outputs.remove(
"outputStatistics")
333 if config.doSaveInterpPixels
is not True:
334 self.outputs.remove(
"preInterpExposure")
335 if config.qa.doThumbnailOss
is not True:
336 self.outputs.remove(
"outputOssThumbnail")
337 if config.qa.doThumbnailFlattened
is not True:
338 self.outputs.remove(
"outputFlattenedThumbnail")
339 if config.doCalculateStatistics
is not True:
340 self.outputs.remove(
"outputStatistics")
344 pipelineConnections=IsrTaskConnections):
345 """Configuration parameters for IsrTask.
347 Items are grouped in the order
in which they are executed by the task.
349 datasetType = pexConfig.Field(
351 doc="Dataset type for input data; users will typically leave this alone, "
352 "but camera-specific ISR tasks will override it",
356 fallbackFilterName = pexConfig.Field(
358 doc=
"Fallback default filter name for calibrations.",
361 useFallbackDate = pexConfig.Field(
363 doc=
"Pass observation date when using fallback filter.",
366 expectWcs = pexConfig.Field(
369 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)."
371 fwhm = pexConfig.Field(
373 doc=
"FWHM of PSF in arcseconds.",
376 qa = pexConfig.ConfigField(
378 doc=
"QA related configuration options.",
380 doHeaderProvenance = pexConfig.Field(
383 doc=
"Write calibration identifiers into output exposure header?",
387 doConvertIntToFloat = pexConfig.Field(
389 doc=
"Convert integer raw images to floating point values?",
394 doSaturation = pexConfig.Field(
396 doc=
"Mask saturated pixels? NB: this is totally independent of the"
397 " interpolation option - this is ONLY setting the bits in the mask."
398 " To have them interpolated make sure doSaturationInterpolation=True",
401 saturatedMaskName = pexConfig.Field(
403 doc=
"Name of mask plane to use in saturation detection and interpolation",
406 saturation = pexConfig.Field(
408 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
409 default=float(
"NaN"),
411 growSaturationFootprintSize = pexConfig.Field(
413 doc=
"Number of pixels by which to grow the saturation footprints",
418 doSuspect = pexConfig.Field(
420 doc=
"Mask suspect pixels?",
423 suspectMaskName = pexConfig.Field(
425 doc=
"Name of mask plane to use for suspect pixels",
428 numEdgeSuspect = pexConfig.Field(
430 doc=
"Number of edge pixels to be flagged as untrustworthy.",
433 edgeMaskLevel = pexConfig.ChoiceField(
435 doc=
"Mask edge pixels in which coordinate frame: DETECTOR or AMP?",
438 'DETECTOR':
'Mask only the edges of the full detector.',
439 'AMP':
'Mask edges of each amplifier.',
444 doSetBadRegions = pexConfig.Field(
446 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
449 badStatistic = pexConfig.ChoiceField(
451 doc=
"How to estimate the average value for BAD regions.",
454 "MEANCLIP":
"Correct using the (clipped) mean of good data",
455 "MEDIAN":
"Correct using the median of the good data",
460 doOverscan = pexConfig.Field(
462 doc=
"Do overscan subtraction?",
465 overscan = pexConfig.ConfigurableField(
466 target=OverscanCorrectionTask,
467 doc=
"Overscan subtraction task for image segments.",
471 doAssembleCcd = pexConfig.Field(
474 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
476 assembleCcd = pexConfig.ConfigurableField(
477 target=AssembleCcdTask,
478 doc=
"CCD assembly task",
482 doAssembleIsrExposures = pexConfig.Field(
485 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
487 doTrimToMatchCalib = pexConfig.Field(
490 doc=
"Trim raw data to match calibration bounding boxes?"
494 doBias = pexConfig.Field(
496 doc=
"Apply bias frame correction?",
499 biasDataProductName = pexConfig.Field(
501 doc=
"Name of the bias data product",
504 doBiasBeforeOverscan = pexConfig.Field(
506 doc=
"Reverse order of overscan and bias correction.",
511 doDeferredCharge = pexConfig.Field(
513 doc=
"Apply deferred charge correction?",
516 deferredChargeCorrection = pexConfig.ConfigurableField(
517 target=DeferredChargeTask,
518 doc=
"Deferred charge correction task.",
522 doVariance = pexConfig.Field(
524 doc=
"Calculate variance?",
527 gain = pexConfig.Field(
529 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
530 default=float(
"NaN"),
532 readNoise = pexConfig.Field(
534 doc=
"The read noise to use if no Detector is present in the Exposure",
537 doEmpiricalReadNoise = pexConfig.Field(
540 doc=
"Calculate empirical read noise instead of value from AmpInfo data?"
542 usePtcReadNoise = pexConfig.Field(
545 doc=
"Use readnoise values from the Photon Transfer Curve?"
547 maskNegativeVariance = pexConfig.Field(
550 doc=
"Mask pixels that claim a negative variance? This likely indicates a failure "
551 "in the measurement of the overscan at an edge due to the data falling off faster "
552 "than the overscan model can account for it."
554 negativeVarianceMaskName = pexConfig.Field(
557 doc=
"Mask plane to use to mark pixels with negative variance, if `maskNegativeVariance` is True.",
560 doLinearize = pexConfig.Field(
562 doc=
"Correct for nonlinearity of the detector's response?",
567 doCrosstalk = pexConfig.Field(
569 doc=
"Apply intra-CCD crosstalk correction?",
572 doCrosstalkBeforeAssemble = pexConfig.Field(
574 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
577 crosstalk = pexConfig.ConfigurableField(
578 target=CrosstalkTask,
579 doc=
"Intra-CCD crosstalk correction",
583 doDefect = pexConfig.Field(
585 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
588 doNanMasking = pexConfig.Field(
590 doc=
"Mask non-finite (NAN, inf) pixels?",
593 doWidenSaturationTrails = pexConfig.Field(
595 doc=
"Widen bleed trails based on their width?",
600 doBrighterFatter = pexConfig.Field(
603 doc=
"Apply the brighter-fatter correction?"
605 brighterFatterLevel = pexConfig.ChoiceField(
608 doc=
"The level at which to correct for brighter-fatter.",
610 "AMP":
"Every amplifier treated separately.",
611 "DETECTOR":
"One kernel per detector",
614 brighterFatterMaxIter = pexConfig.Field(
617 doc=
"Maximum number of iterations for the brighter-fatter correction"
619 brighterFatterThreshold = pexConfig.Field(
622 doc=
"Threshold used to stop iterating the brighter-fatter correction. It is the "
623 "absolute value of the difference between the current corrected image and the one "
624 "from the previous iteration summed over all the pixels."
626 brighterFatterApplyGain = pexConfig.Field(
629 doc=
"Should the gain be applied when applying the brighter-fatter correction?"
631 brighterFatterMaskListToInterpolate = pexConfig.ListField(
633 doc=
"List of mask planes that should be interpolated over when applying the brighter-fatter "
635 default=[
"SAT",
"BAD",
"NO_DATA",
"UNMASKEDNAN"],
637 brighterFatterMaskGrowSize = pexConfig.Field(
640 doc=
"Number of pixels to grow the masks listed in config.brighterFatterMaskListToInterpolate "
641 "when brighter-fatter correction is applied."
645 doDark = pexConfig.Field(
647 doc=
"Apply dark frame correction?",
650 darkDataProductName = pexConfig.Field(
652 doc=
"Name of the dark data product",
657 doStrayLight = pexConfig.Field(
659 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
662 strayLight = pexConfig.ConfigurableField(
663 target=StrayLightTask,
664 doc=
"y-band stray light correction"
668 doFlat = pexConfig.Field(
670 doc=
"Apply flat field correction?",
673 flatDataProductName = pexConfig.Field(
675 doc=
"Name of the flat data product",
678 flatScalingType = pexConfig.ChoiceField(
680 doc=
"The method for scaling the flat on the fly.",
683 "USER":
"Scale by flatUserScale",
684 "MEAN":
"Scale by the inverse of the mean",
685 "MEDIAN":
"Scale by the inverse of the median",
688 flatUserScale = pexConfig.Field(
690 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
693 doTweakFlat = pexConfig.Field(
695 doc=
"Tweak flats to match observed amplifier ratios?",
701 doApplyGains = pexConfig.Field(
703 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
706 usePtcGains = pexConfig.Field(
708 doc=
"Use the gain values from the Photon Transfer Curve?",
711 normalizeGains = pexConfig.Field(
713 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
718 doFringe = pexConfig.Field(
720 doc=
"Apply fringe correction?",
723 fringe = pexConfig.ConfigurableField(
725 doc=
"Fringe subtraction task",
727 fringeAfterFlat = pexConfig.Field(
729 doc=
"Do fringe subtraction after flat-fielding?",
734 doAmpOffset = pexConfig.Field(
735 doc=
"Calculate and apply amp offset corrections?",
739 ampOffset = pexConfig.ConfigurableField(
740 doc=
"Amp offset correction task.",
741 target=AmpOffsetTask,
745 doMeasureBackground = pexConfig.Field(
747 doc=
"Measure the background level on the reduced image?",
752 doCameraSpecificMasking = pexConfig.Field(
754 doc=
"Mask camera-specific bad regions?",
757 masking = pexConfig.ConfigurableField(
763 doInterpolate = pexConfig.Field(
765 doc=
"Interpolate masked pixels?",
768 doSaturationInterpolation = pexConfig.Field(
770 doc=
"Perform interpolation over pixels masked as saturated?"
771 " NB: This is independent of doSaturation; if that is False this plane"
772 " will likely be blank, resulting in a no-op here.",
775 doNanInterpolation = pexConfig.Field(
777 doc=
"Perform interpolation over pixels masked as NaN?"
778 " NB: This is independent of doNanMasking; if that is False this plane"
779 " will likely be blank, resulting in a no-op here.",
782 doNanInterpAfterFlat = pexConfig.Field(
784 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we "
785 "also have to interpolate them before flat-fielding."),
788 maskListToInterpolate = pexConfig.ListField(
790 doc=
"List of mask planes that should be interpolated.",
791 default=[
'SAT',
'BAD'],
793 doSaveInterpPixels = pexConfig.Field(
795 doc=
"Save a copy of the pre-interpolated pixel values?",
800 fluxMag0T1 = pexConfig.DictField(
803 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
804 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
807 defaultFluxMag0T1 = pexConfig.Field(
809 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
810 default=pow(10.0, 0.4*28.0)
814 doVignette = pexConfig.Field(
816 doc=(
"Compute and attach the validPolygon defining the unvignetted region to the exposure "
817 "according to vignetting parameters?"),
820 doMaskVignettePolygon = pexConfig.Field(
822 doc=(
"Add a mask bit for pixels within the vignetted region. Ignored if doVignette "
826 vignetteValue = pexConfig.Field(
828 doc=
"Value to replace image array pixels with in the vignetted region? Ignored if None.",
832 vignette = pexConfig.ConfigurableField(
834 doc=
"Vignetting task.",
838 doAttachTransmissionCurve = pexConfig.Field(
841 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?"
843 doUseOpticsTransmission = pexConfig.Field(
846 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?"
848 doUseFilterTransmission = pexConfig.Field(
851 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?"
853 doUseSensorTransmission = pexConfig.Field(
856 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?"
858 doUseAtmosphereTransmission = pexConfig.Field(
861 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?"
865 doIlluminationCorrection = pexConfig.Field(
868 doc=
"Perform illumination correction?"
870 illuminationCorrectionDataProductName = pexConfig.Field(
872 doc=
"Name of the illumination correction data product.",
875 illumScale = pexConfig.Field(
877 doc=
"Scale factor for the illumination correction.",
880 illumFilters = pexConfig.ListField(
883 doc=
"Only perform illumination correction for these filters."
887 doStandardStatistics = pexConfig.Field(
889 doc=
"Should standard image quality statistics be calculated?",
893 doCalculateStatistics = pexConfig.Field(
895 doc=
"Should additional ISR statistics be calculated?",
898 isrStats = pexConfig.ConfigurableField(
899 target=IsrStatisticsTask,
900 doc=
"Task to calculate additional statistics.",
905 doWrite = pexConfig.Field(
907 doc=
"Persist postISRCCD?",
914 raise ValueError(
"You may not specify both doFlat and doApplyGains")
916 raise ValueError(
"You may not specify both doBiasBeforeOverscan and doTrimToMatchCalib")
926 """Apply common instrument signature correction algorithms to a raw frame.
928 The process for correcting imaging data
is very similar
from
929 camera to camera. This task provides a vanilla implementation of
930 doing these corrections, including the ability to turn certain
931 corrections off
if they are
not needed. The inputs to the primary
932 method, `
run()`, are a raw exposure to be corrected
and the
933 calibration data products. The raw input
is a single chip sized
934 mosaic of all amps including overscans
and other non-science
937 The __init__ method sets up the subtasks
for ISR processing, using
943 Positional arguments passed to the Task constructor.
944 None used at this time.
945 kwargs : `dict`, optional
946 Keyword arguments passed on to the Task constructor.
947 None used at this time.
949 ConfigClass = IsrTaskConfig
954 self.makeSubtask(
"assembleCcd")
955 self.makeSubtask(
"crosstalk")
956 self.makeSubtask(
"strayLight")
957 self.makeSubtask(
"fringe")
958 self.makeSubtask(
"masking")
959 self.makeSubtask(
"overscan")
960 self.makeSubtask(
"vignette")
961 self.makeSubtask(
"ampOffset")
962 self.makeSubtask(
"deferredChargeCorrection")
963 self.makeSubtask(
"isrStats")
966 inputs = butlerQC.get(inputRefs)
969 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
970 except Exception
as e:
971 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
974 detector = inputs[
'ccdExposure'].getDetector()
976 if self.config.doCrosstalk
is True:
979 if 'crosstalk' in inputs
and inputs[
'crosstalk']
is not None:
980 if not isinstance(inputs[
'crosstalk'], CrosstalkCalib):
981 inputs[
'crosstalk'] = CrosstalkCalib.fromTable(inputs[
'crosstalk'])
983 coeffVector = (self.config.crosstalk.crosstalkValues
984 if self.config.crosstalk.useConfigCoefficients
else None)
985 crosstalkCalib =
CrosstalkCalib().fromDetector(detector, coeffVector=coeffVector)
986 inputs[
'crosstalk'] = crosstalkCalib
987 if inputs[
'crosstalk'].interChip
and len(inputs[
'crosstalk'].interChip) > 0:
988 if 'crosstalkSources' not in inputs:
989 self.log.warning(
"No crosstalkSources found for chip with interChip terms!")
992 if 'linearizer' in inputs:
993 if isinstance(inputs[
'linearizer'], dict):
995 linearizer.fromYaml(inputs[
'linearizer'])
996 self.log.warning(
"Dictionary linearizers will be deprecated in DM-28741.")
997 elif isinstance(inputs[
'linearizer'], numpy.ndarray):
1001 self.log.warning(
"Bare lookup table linearizers will be deprecated in DM-28741.")
1003 linearizer = inputs[
'linearizer']
1004 linearizer.log = self.log
1005 inputs[
'linearizer'] = linearizer
1008 self.log.warning(
"Constructing linearizer from cameraGeom information.")
1010 if self.config.doDefect
is True:
1011 if "defects" in inputs
and inputs[
'defects']
is not None:
1015 if not isinstance(inputs[
"defects"], Defects):
1016 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
1020 if self.config.doBrighterFatter:
1021 brighterFatterKernel = inputs.pop(
'newBFKernel',
None)
1022 if brighterFatterKernel
is None:
1023 brighterFatterKernel = inputs.get(
'bfKernel',
None)
1025 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
1027 detName = detector.getName()
1028 level = brighterFatterKernel.level
1031 inputs[
'bfGains'] = brighterFatterKernel.gain
1032 if self.config.brighterFatterLevel ==
'DETECTOR':
1033 if level ==
'DETECTOR':
1034 if detName
in brighterFatterKernel.detKernels:
1035 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1037 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
1038 elif level ==
'AMP':
1039 self.log.warning(
"Making DETECTOR level kernel from AMP based brighter "
1041 brighterFatterKernel.makeDetectorKernelFromAmpwiseKernels(detName)
1042 inputs[
'bfKernel'] = brighterFatterKernel.detKernels[detName]
1043 elif self.config.brighterFatterLevel ==
'AMP':
1044 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
1046 if self.config.doFringe
is True and self.fringe.checkFilter(inputs[
'ccdExposure']):
1047 expId = inputs[
'ccdExposure'].info.id
1048 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
1050 assembler=self.assembleCcd
1051 if self.config.doAssembleIsrExposures
else None)
1053 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
1055 if self.config.doStrayLight
is True and self.strayLight.checkFilter(inputs[
'ccdExposure']):
1056 if 'strayLightData' not in inputs:
1057 inputs[
'strayLightData'] =
None
1059 if self.config.doHeaderProvenance:
1061 exposureMetadata = inputs[
'ccdExposure'].getMetadata()
1062 for inputName
in sorted(inputs.keys()):
1063 reference = getattr(inputRefs, inputName,
None)
1064 if reference
is not None and hasattr(reference,
"run"):
1065 runKey = f
"LSST CALIB RUN {inputName.upper()}"
1066 runValue = reference.run
1067 idKey = f
"LSST CALIB UUID {inputName.upper()}"
1068 idValue =
str(reference.id)
1070 exposureMetadata[runKey] = runValue
1071 exposureMetadata[idKey] = idValue
1073 outputs = self.
run(**inputs)
1074 butlerQC.put(outputs, outputRefs)
1077 def run(self, ccdExposure, *, camera=None, bias=None, linearizer=None,
1078 crosstalk=None, crosstalkSources=None,
1079 dark=None, flat=None, ptc=None, bfKernel=None, bfGains=None, defects=None,
1080 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1081 sensorTransmission=
None, atmosphereTransmission=
None,
1082 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1083 deferredChargeCalib=
None,
1085 """Perform instrument signature removal on an exposure.
1087 Steps included in the ISR processing,
in order performed, are:
1089 - saturation
and suspect pixel masking
1090 - overscan subtraction
1091 - CCD assembly of individual amplifiers
1093 - variance image construction
1094 - linearization of non-linear response
1096 - brighter-fatter correction
1099 - stray light subtraction
1101 - masking of known defects
and camera specific features
1102 - vignette calculation
1103 - appending transmission curve
and distortion model
1108 The raw exposure that
is to be run through ISR. The
1109 exposure
is modified by this method.
1111 The camera geometry
for this exposure. Required
if
1112 one
or more of ``ccdExposure``, ``bias``, ``dark``,
or
1113 ``flat`` does
not have an associated detector.
1115 Bias calibration frame.
1117 Functor
for linearization.
1119 Calibration
for crosstalk.
1120 crosstalkSources : `list`, optional
1121 List of possible crosstalk sources.
1123 Dark calibration frame.
1125 Flat calibration frame.
1127 Photon transfer curve dataset,
with, e.g., gains
1129 bfKernel : `numpy.ndarray`, optional
1130 Brighter-fatter kernel.
1131 bfGains : `dict` of `float`, optional
1132 Gains used to override the detector
's nominal gains for the
1133 brighter-fatter correction. A dict keyed by amplifier name for
1134 the detector
in question.
1137 fringes : `lsst.pipe.base.Struct`, optional
1138 Struct containing the fringe correction data,
with
1144 random seed derived
from the ``ccdExposureId``
for random
1145 number generator (`numpy.uint32`)
1147 A ``TransmissionCurve`` that represents the throughput of the,
1148 optics, to be evaluated
in focal-plane coordinates.
1150 A ``TransmissionCurve`` that represents the throughput of the
1151 filter itself, to be evaluated
in focal-plane coordinates.
1153 A ``TransmissionCurve`` that represents the throughput of the
1154 sensor itself, to be evaluated
in post-assembly trimmed detector
1157 A ``TransmissionCurve`` that represents the throughput of the
1158 atmosphere, assumed to be spatially constant.
1159 detectorNum : `int`, optional
1160 The integer number
for the detector to process.
1161 strayLightData : `object`, optional
1162 Opaque object containing calibration information
for stray-light
1163 correction. If `
None`, no correction will be performed.
1165 Illumination correction image.
1169 result : `lsst.pipe.base.Struct`
1170 Result struct
with component:
1173 The fully ISR corrected exposure.
1178 Thumbnail image of the exposure after overscan subtraction.
1181 Thumbnail image of the exposure after flat-field correction.
1183 ``outputStatistics``
1184 Values of the additional statistics calculated.
1189 Raised
if a configuration option
is set to `
True`, but the
1190 required calibration data has
not been specified.
1194 The current processed exposure can be viewed by setting the
1195 appropriate `lsstDebug` entries
in the ``debug.display``
1196 dictionary. The names of these entries correspond to some of
1197 the `IsrTaskConfig` Boolean options,
with the value denoting the
1198 frame to use. The exposure
is shown inside the matching
1199 option check
and after the processing of that step has
1200 finished. The steps
with debug points are:
1211 In addition, setting the ``postISRCCD`` entry displays the
1212 exposure after all ISR processing has finished.
1215 ccdExposure = self.ensureExposure(ccdExposure, camera, detectorNum)
1220 ccd = ccdExposure.getDetector()
1221 filterLabel = ccdExposure.getFilter()
1222 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
1225 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd."
1226 ccd = [
FakeAmp(ccdExposure, self.config)]
1229 if self.config.doBias
and bias
is None:
1230 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1232 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1233 if self.config.doBrighterFatter
and bfKernel
is None:
1234 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1235 if self.config.doDark
and dark
is None:
1236 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1237 if self.config.doFlat
and flat
is None:
1238 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1239 if self.config.doDefect
and defects
is None:
1240 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1241 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters
1242 and fringes.fringes
is None):
1247 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1248 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters
1249 and illumMaskedImage
is None):
1250 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1251 if (self.config.doDeferredCharge
and deferredChargeCalib
is None):
1252 raise RuntimeError(
"Must supply a deferred charge calibration if config.doDeferredCharge=True.")
1254 if self.config.doHeaderProvenance:
1257 exposureMetadata = ccdExposure.getMetadata()
1258 if self.config.doBias:
1260 if self.config.doBrighterFatter:
1262 if self.config.doCrosstalk:
1263 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1264 if self.config.doDark:
1266 if self.config.doDefect:
1267 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1268 if self.config.doDeferredCharge:
1269 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1270 if self.config.doFlat:
1272 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters):
1273 exposureMetadata[
"LSST CALIB DATE FRINGE"] = self.
extractCalibDate(fringes.fringes)
1274 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters):
1275 exposureMetadata[
"LSST CALIB DATE ILLUMINATION"] = self.
extractCalibDate(illumMaskedImage)
1277 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1278 if self.config.usePtcGains
or self.config.usePtcReadNoise:
1280 if self.config.doStrayLight:
1281 exposureMetadata[
"LSST CALIB DATE STRAYLIGHT"] = self.
extractCalibDate(strayLightData)
1282 if self.config.doAttachTransmissionCurve:
1283 exposureMetadata[
"LSST CALIB DATE OPTICS_TR"] = self.
extractCalibDate(opticsTransmission)
1284 exposureMetadata[
"LSST CALIB DATE FILTER_TR"] = self.
extractCalibDate(filterTransmission)
1285 exposureMetadata[
"LSST CALIB DATE SENSOR_TR"] = self.
extractCalibDate(sensorTransmission)
1286 exposureMetadata[
"LSST CALIB DATE ATMOSP_TR"] = self.
extractCalibDate(atmosphereTransmission)
1289 if self.config.doConvertIntToFloat:
1290 self.log.info(
"Converting exposure to floating point values.")
1293 if self.config.doBias
and self.config.doBiasBeforeOverscan:
1294 self.log.info(
"Applying bias correction.")
1295 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1296 trimToFit=self.config.doTrimToMatchCalib)
1302 if self.config.doOverscan
and self.config.overscan.doParallelOverscan:
1304 self.overscan.maskParallelOverscan(ccdExposure, ccd)
1309 if ccdExposure.getBBox().contains(amp.getBBox()):
1314 if self.config.doOverscan
and not badAmp:
1317 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1318 if overscanResults
is not None and \
1319 self.config.qa
is not None and self.config.qa.saveStats
is True:
1321 self.metadata[f
"FIT MEDIAN {amp.getName()}"] = overscanResults.overscanMean
1322 self.metadata[f
"FIT STDEV {amp.getName()}"] = overscanResults.overscanSigma
1323 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1324 amp.getName(), overscanResults.overscanMean,
1325 overscanResults.overscanSigma)
1327 self.metadata[f
"RESIDUAL MEDIAN {amp.getName()}"] = overscanResults.residualMean
1328 self.metadata[f
"RESIDUAL STDEV {amp.getName()}"] = overscanResults.residualSigma
1329 self.log.debug(
" Overscan stats for amplifer %s after correction: %f +/- %f",
1330 amp.getName(), overscanResults.residualMean,
1331 overscanResults.residualSigma)
1333 ccdExposure.getMetadata().set(
'OVERSCAN',
"Overscan corrected")
1336 self.log.warning(
"Amplifier %s is bad.", amp.getName())
1337 overscanResults =
None
1339 overscans.append(overscanResults
if overscanResults
is not None else None)
1341 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1343 if self.config.doDeferredCharge:
1344 self.log.info(
"Applying deferred charge/CTI correction.")
1345 self.deferredChargeCorrection.
run(ccdExposure, deferredChargeCalib)
1346 self.
debugView(ccdExposure,
"doDeferredCharge")
1348 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1349 self.log.info(
"Applying crosstalk correction.")
1350 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1351 crosstalkSources=crosstalkSources, camera=camera)
1352 self.
debugView(ccdExposure,
"doCrosstalk")
1354 if self.config.doAssembleCcd:
1355 self.log.info(
"Assembling CCD from amplifiers.")
1356 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1358 if self.config.expectWcs
and not ccdExposure.getWcs():
1359 self.log.warning(
"No WCS found in input exposure.")
1360 self.
debugView(ccdExposure,
"doAssembleCcd")
1363 if self.config.qa.doThumbnailOss:
1364 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1366 if self.config.doBias
and not self.config.doBiasBeforeOverscan:
1367 self.log.info(
"Applying bias correction.")
1368 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1369 trimToFit=self.config.doTrimToMatchCalib)
1372 if self.config.doVariance:
1373 for amp, overscanResults
in zip(ccd, overscans):
1374 if ccdExposure.getBBox().contains(amp.getBBox()):
1375 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1376 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1377 if overscanResults
is not None:
1379 overscanImage=overscanResults.overscanImage,
1385 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1386 qaStats = afwMath.makeStatistics(ampExposure.getVariance(),
1387 afwMath.MEDIAN | afwMath.STDEVCLIP)
1388 self.metadata[f
"ISR VARIANCE {amp.getName()} MEDIAN"] = \
1389 qaStats.getValue(afwMath.MEDIAN)
1390 self.metadata[f
"ISR VARIANCE {amp.getName()} STDEV"] = \
1391 qaStats.getValue(afwMath.STDEVCLIP)
1392 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1393 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1394 qaStats.getValue(afwMath.STDEVCLIP))
1395 if self.config.maskNegativeVariance:
1399 self.log.info(
"Applying linearizer.")
1400 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1401 detector=ccd, log=self.log)
1403 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1404 self.log.info(
"Applying crosstalk correction.")
1405 self.crosstalk.
run(ccdExposure, crosstalk=crosstalk,
1406 crosstalkSources=crosstalkSources, isTrimmed=
True)
1407 self.
debugView(ccdExposure,
"doCrosstalk")
1412 if self.config.doDefect:
1413 self.log.info(
"Masking defects.")
1416 if self.config.numEdgeSuspect > 0:
1417 self.log.info(
"Masking edges as SUSPECT.")
1418 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1419 maskPlane=
"SUSPECT", level=self.config.edgeMaskLevel)
1421 if self.config.doNanMasking:
1422 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1425 if self.config.doWidenSaturationTrails:
1426 self.log.info(
"Widening saturation trails.")
1427 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1429 if self.config.doCameraSpecificMasking:
1430 self.log.info(
"Masking regions for camera specific reasons.")
1431 self.masking.
run(ccdExposure)
1433 if self.config.doBrighterFatter:
1443 interpExp = ccdExposure.clone()
1445 isrFunctions.interpolateFromMask(
1446 maskedImage=interpExp.getMaskedImage(),
1447 fwhm=self.config.fwhm,
1448 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1449 maskNameList=list(self.config.brighterFatterMaskListToInterpolate)
1451 bfExp = interpExp.clone()
1453 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1454 type(bfKernel), type(bfGains))
1455 bfResults = isrFunctions.brighterFatterCorrection(bfExp, bfKernel,
1456 self.config.brighterFatterMaxIter,
1457 self.config.brighterFatterThreshold,
1458 self.config.brighterFatterApplyGain,
1460 if bfResults[1] == self.config.brighterFatterMaxIter:
1461 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1464 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1466 image = ccdExposure.getMaskedImage().getImage()
1467 bfCorr = bfExp.getMaskedImage().getImage()
1468 bfCorr -= interpExp.getMaskedImage().getImage()
1477 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1478 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1481 if self.config.brighterFatterMaskGrowSize > 0:
1482 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1483 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1484 isrFunctions.growMasks(ccdExposure.getMask(),
1485 radius=self.config.brighterFatterMaskGrowSize,
1486 maskNameList=maskPlane,
1487 maskValue=maskPlane)
1489 self.
debugView(ccdExposure,
"doBrighterFatter")
1491 if self.config.doDark:
1492 self.log.info(
"Applying dark correction.")
1496 if self.config.doFringe
and not self.config.fringeAfterFlat:
1497 self.log.info(
"Applying fringe correction before flat.")
1498 self.fringe.
run(ccdExposure, **fringes.getDict())
1501 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1502 self.log.info(
"Checking strayLight correction.")
1503 self.strayLight.
run(ccdExposure, strayLightData)
1504 self.
debugView(ccdExposure,
"doStrayLight")
1506 if self.config.doFlat:
1507 self.log.info(
"Applying flat correction.")
1511 if self.config.doApplyGains:
1512 self.log.info(
"Applying gain correction instead of flat.")
1513 if self.config.usePtcGains:
1514 self.log.info(
"Using gains from the Photon Transfer Curve.")
1515 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains,
1518 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1520 if self.config.doFringe
and self.config.fringeAfterFlat:
1521 self.log.info(
"Applying fringe correction after flat.")
1522 self.fringe.
run(ccdExposure, **fringes.getDict())
1524 if self.config.doVignette:
1525 if self.config.doMaskVignettePolygon:
1526 self.log.info(
"Constructing, attaching, and masking vignette polygon.")
1528 self.log.info(
"Constructing and attaching vignette polygon.")
1530 exposure=ccdExposure, doUpdateMask=self.config.doMaskVignettePolygon,
1531 vignetteValue=self.config.vignetteValue, log=self.log)
1533 if self.config.doAttachTransmissionCurve:
1534 self.log.info(
"Adding transmission curves.")
1535 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1536 filterTransmission=filterTransmission,
1537 sensorTransmission=sensorTransmission,
1538 atmosphereTransmission=atmosphereTransmission)
1540 flattenedThumb =
None
1541 if self.config.qa.doThumbnailFlattened:
1542 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1544 if self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters:
1545 self.log.info(
"Performing illumination correction.")
1546 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1547 illumMaskedImage, illumScale=self.config.illumScale,
1548 trimToFit=self.config.doTrimToMatchCalib)
1551 if self.config.doSaveInterpPixels:
1552 preInterpExp = ccdExposure.clone()
1567 if self.config.doSetBadRegions:
1568 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1569 if badPixelCount > 0:
1570 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1572 if self.config.doInterpolate:
1573 self.log.info(
"Interpolating masked pixels.")
1574 isrFunctions.interpolateFromMask(
1575 maskedImage=ccdExposure.getMaskedImage(),
1576 fwhm=self.config.fwhm,
1577 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1578 maskNameList=list(self.config.maskListToInterpolate)
1584 if self.config.doAmpOffset:
1585 self.log.info(
"Correcting amp offsets.")
1586 self.ampOffset.
run(ccdExposure)
1588 if self.config.doMeasureBackground:
1589 self.log.info(
"Measuring background level.")
1592 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1594 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1595 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1596 afwMath.MEDIAN | afwMath.STDEVCLIP)
1597 self.metadata[f
"ISR BACKGROUND {amp.getName()} MEDIAN"] = qaStats.getValue(afwMath.MEDIAN)
1598 self.metadata[f
"ISR BACKGROUND {amp.getName()} STDEV"] = \
1599 qaStats.getValue(afwMath.STDEVCLIP)
1600 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1601 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1602 qaStats.getValue(afwMath.STDEVCLIP))
1605 if self.config.doStandardStatistics:
1606 metadata = ccdExposure.getMetadata()
1608 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1609 ampName = amp.getName()
1610 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1611 ampExposure.getMaskedImage(),
1612 [self.config.saturatedMaskName]
1614 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1615 ampExposure.getMaskedImage(),
1618 qaStats = afwMath.makeStatistics(ampExposure.getImage(),
1619 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1621 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1622 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1623 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1625 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1626 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1627 if self.config.doOverscan
and k1
in metadata
and k2
in metadata:
1628 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1630 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1633 outputStatistics =
None
1634 if self.config.doCalculateStatistics:
1635 outputStatistics = self.isrStats.
run(ccdExposure, overscanResults=overscans,
1638 self.
debugView(ccdExposure,
"postISRCCD")
1640 return pipeBase.Struct(
1641 exposure=ccdExposure,
1643 flattenedThumb=flattenedThumb,
1645 preInterpExposure=preInterpExp,
1646 outputExposure=ccdExposure,
1647 outputOssThumbnail=ossThumb,
1648 outputFlattenedThumbnail=flattenedThumb,
1649 outputStatistics=outputStatistics,
1653 """Ensure that the data returned by Butler is a fully constructed exp.
1655 ISR requires exposure-level image data for historical reasons, so
if we
1656 did
not recieve that
from Butler, construct it
from what we have,
1657 modifying the input
in place.
1662 The input data structure obtained
from Butler.
1664 `lsst.afw.image.DecoratedImageU`,
1665 or `lsst.afw.image.ImageF`
1666 camera : `lsst.afw.cameraGeom.camera`, optional
1667 The camera associated
with the image. Used to find the appropriate
1668 detector
if detector
is not already set.
1669 detectorNum : `int`, optional
1670 The detector
in the camera to attach,
if the detector
is not
1676 The re-constructed exposure,
with appropriate detector parameters.
1681 Raised
if the input data cannot be used to construct an exposure.
1683 if isinstance(inputExp, afwImage.DecoratedImageU):
1684 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1685 elif isinstance(inputExp, afwImage.ImageF):
1686 inputExp = afwImage.makeExposure(afwImage.makeMaskedImage(inputExp))
1687 elif isinstance(inputExp, afwImage.MaskedImageF):
1688 inputExp = afwImage.makeExposure(inputExp)
1689 elif isinstance(inputExp, afwImage.Exposure):
1691 elif inputExp
is None:
1695 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1698 if inputExp.getDetector()
is None:
1699 if camera
is None or detectorNum
is None:
1700 raise RuntimeError(
'Must supply both a camera and detector number when using exposures '
1701 'without a detector set.')
1702 inputExp.setDetector(camera[detectorNum])
1708 """Extract common calibration metadata values that will be written to
1714 Calibration to pull date information
from.
1719 Calibration creation date string to add to header.
1721 if hasattr(calib,
"getMetadata"):
1722 if 'CALIB_CREATION_DATE' in calib.getMetadata():
1723 return " ".join((calib.getMetadata().get(
"CALIB_CREATION_DATE",
"Unknown"),
1724 calib.getMetadata().get(
"CALIB_CREATION_TIME",
"Unknown")))
1726 return " ".join((calib.getMetadata().get(
"CALIB_CREATE_DATE",
"Unknown"),
1727 calib.getMetadata().get(
"CALIB_CREATE_TIME",
"Unknown")))
1729 return "Unknown Unknown"
1732 """Convert exposure image from uint16 to float.
1734 If the exposure does not need to be converted, the input
is
1735 immediately returned. For exposures that are converted to use
1736 floating point pixels, the variance
is set to unity
and the
1742 The raw exposure to be converted.
1747 The input ``exposure``, converted to floating point pixels.
1752 Raised
if the exposure type cannot be converted to float.
1755 if isinstance(exposure, afwImage.ExposureF):
1757 self.log.debug(
"Exposure already of type float.")
1759 if not hasattr(exposure,
"convertF"):
1760 raise RuntimeError(
"Unable to convert exposure (%s) to float." % type(exposure))
1762 newexposure = exposure.convertF()
1763 newexposure.variance[:] = 1
1764 newexposure.mask[:] = 0x0
1769 """Identify bad amplifiers, saturated and suspect pixels.
1774 Input exposure to be masked.
1776 Catalog of parameters defining the amplifier on this
1779 List of defects. Used to determine if the entire
1785 If this
is true, the entire amplifier area
is covered by
1786 defects
and unusable.
1789 maskedImage = ccdExposure.getMaskedImage()
1796 if defects
is not None:
1797 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1803 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1805 maskView = dataView.getMask()
1806 maskView |= maskView.getPlaneBitMask(
"BAD")
1814 if self.config.doSaturation
and not badAmp:
1815 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1816 if self.config.doSuspect
and not badAmp:
1817 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1818 if math.isfinite(self.config.saturation):
1819 limits.update({self.config.saturatedMaskName: self.config.saturation})
1821 for maskName, maskThreshold
in limits.items():
1822 if not math.isnan(maskThreshold):
1823 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1824 isrFunctions.makeThresholdMask(
1825 maskedImage=dataView,
1826 threshold=maskThreshold,
1833 maskView = afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1835 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1836 self.config.suspectMaskName])
1837 if numpy.all(maskView.getArray() & maskVal > 0):
1839 maskView |= maskView.getPlaneBitMask(
"BAD")
1844 """Apply overscan correction in place.
1846 This method does initial pixel rejection of the overscan
1847 region. The overscan can also be optionally segmented to
1848 allow for discontinuous overscan responses to be fit
1849 separately. The actual overscan subtraction
is performed by
1850 the `lsst.ip.isr.overscan.OverscanTask`, which
is called here
1851 after the amplifier
is preprocessed.
1856 Exposure to have overscan correction performed.
1857 amp : `lsst.afw.cameraGeom.Amplifer`
1858 The amplifier to consider
while correcting the overscan.
1862 overscanResults : `lsst.pipe.base.Struct`
1863 Result struct
with components:
1866 Value
or fit subtracted
from the amplifier image data.
1869 Value
or fit subtracted
from the overscan image data.
1872 Image of the overscan region
with the overscan
1873 correction applied. This quantity
is used to estimate
1874 the amplifier read noise empirically.
1879 Median overscan fit value. (`float`)
1881 Clipped standard deviation of the overscan after
1882 correction. (`float`)
1887 Raised
if the ``amp`` does
not contain raw pixel information.
1891 lsst.ip.isr.overscan.OverscanTask
1893 if amp.getRawHorizontalOverscanBBox().isEmpty():
1894 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1898 overscanResults = self.overscan.
run(ccdExposure, amp)
1900 metadata = ccdExposure.getMetadata()
1901 ampName = amp.getName()
1903 keyBase =
"LSST ISR OVERSCAN"
1905 if isinstance(overscanResults.overscanMean, float):
1907 metadata[f
"{keyBase} SERIAL MEAN {ampName}"] = overscanResults.overscanMean
1908 metadata[f
"{keyBase} SERIAL MEDIAN {ampName}"] = overscanResults.overscanMedian
1909 metadata[f
"{keyBase} SERIAL STDEV {ampName}"] = overscanResults.overscanSigma
1911 metadata[f
"{keyBase} RESIDUAL SERIAL MEAN {ampName}"] = overscanResults.residualMean
1912 metadata[f
"{keyBase} RESIDUAL SERIAL MEDIAN {ampName}"] = overscanResults.residualMedian
1913 metadata[f
"{keyBase} RESIDUAL SERIAL STDEV {ampName}"] = overscanResults.residualSigma
1914 elif isinstance(overscanResults.overscanMean, tuple):
1916 metadata[f
"{keyBase} SERIAL MEAN {ampName}"] = overscanResults.overscanMean[0]
1917 metadata[f
"{keyBase} SERIAL MEDIAN {ampName}"] = overscanResults.overscanMedian[0]
1918 metadata[f
"{keyBase} SERIAL STDEV {ampName}"] = overscanResults.overscanSigma[0]
1920 metadata[f
"{keyBase} PARALLEL MEAN {ampName}"] = overscanResults.overscanMean[1]
1921 metadata[f
"{keyBase} PARALLEL MEDIAN {ampName}"] = overscanResults.overscanMedian[1]
1922 metadata[f
"{keyBase} PARALLEL STDEV {ampName}"] = overscanResults.overscanSigma[1]
1924 metadata[f
"{keyBase} RESIDUAL SERIAL MEAN {ampName}"] = overscanResults.residualMean[0]
1925 metadata[f
"{keyBase} RESIDUAL SERIAL MEDIAN {ampName}"] = overscanResults.residualMedian[0]
1926 metadata[f
"{keyBase} RESIDUAL SERIAL STDEV {ampName}"] = overscanResults.residualSigma[0]
1928 metadata[f
"{keyBase} RESIDUAL PARALLEL MEAN {ampName}"] = overscanResults.residualMean[1]
1929 metadata[f
"{keyBase} RESIDUAL PARALLEL MEDIAN {ampName}"] = overscanResults.residualMedian[1]
1930 metadata[f
"{keyBase} RESIDUAL PARALLEL STDEV {ampName}"] = overscanResults.residualSigma[1]
1932 self.log.warning(
"Unexpected type for overscan values; none added to header.")
1934 return overscanResults
1937 """Set the variance plane using the gain and read noise
1939 The read noise is calculated
from the ``overscanImage``
if the
1940 ``doEmpiricalReadNoise`` option
is set
in the configuration; otherwise
1941 the value
from the amplifier data
is used.
1946 Exposure to process.
1948 Amplifier detector data.
1950 Image of overscan, required only
for empirical read noise.
1952 PTC dataset containing the gains
and read noise.
1957 Raised
if either ``usePtcGains`` of ``usePtcReadNoise``
1958 are ``
True``, but ptcDataset
is not provided.
1960 Raised
if ```doEmpiricalReadNoise``
is ``
True`` but
1961 ``overscanImage``
is ``
None``.
1965 lsst.ip.isr.isrFunctions.updateVariance
1967 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
1968 if self.config.usePtcGains:
1969 if ptcDataset
is None:
1970 raise RuntimeError(
"No ptcDataset provided to use PTC gains.")
1972 gain = ptcDataset.gain[amp.getName()]
1973 self.log.info(
"Using gain from Photon Transfer Curve.")
1975 gain = amp.getGain()
1977 if math.isnan(gain):
1979 self.log.warning(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
1982 self.log.warning(
"Gain for amp %s == %g <= 0; setting to %f.",
1983 amp.getName(), gain, patchedGain)
1986 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
1987 badPixels = isrFunctions.countMaskedPixels(ampExposure.getMaskedImage(),
1988 [self.config.saturatedMaskName,
1989 self.config.suspectMaskName,
1991 allPixels = ampExposure.getWidth() * ampExposure.getHeight()
1992 if allPixels == badPixels:
1994 self.log.info(
"Skipping empirical read noise for amp %s. No good pixels.",
1997 raise RuntimeError(
"Overscan is none for EmpiricalReadNoise.")
1999 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
2000 stats = afwMath.StatisticsControl()
2001 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
2002 readNoise = afwMath.makeStatistics(overscanImage.getImage(),
2003 afwMath.STDEVCLIP, stats).getValue()
2004 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
2005 amp.getName(), readNoise)
2006 elif self.config.usePtcReadNoise:
2007 if ptcDataset
is None:
2008 raise RuntimeError(
"No ptcDataset provided to use PTC readnoise.")
2010 readNoise = ptcDataset.noise[amp.getName()]
2011 self.log.info(
"Using read noise from Photon Transfer Curve.")
2013 readNoise = amp.getReadNoise()
2015 metadata = ampExposure.getMetadata()
2016 metadata[f
'LSST GAIN {amp.getName()}'] = gain
2017 metadata[f
'LSST READNOISE {amp.getName()}'] = readNoise
2019 isrFunctions.updateVariance(
2020 maskedImage=ampExposure.getMaskedImage(),
2022 readNoise=readNoise,
2026 """Identify and mask pixels with negative variance values.
2031 Exposure to process.
2035 lsst.ip.isr.isrFunctions.updateVariance
2037 maskPlane = exposure.getMask().getPlaneBitMask(self.config.negativeVarianceMaskName)
2038 bad = numpy.where(exposure.getVariance().getArray() <= 0.0)
2039 exposure.mask.array[bad] |= maskPlane
2042 """Apply dark correction in place.
2047 Exposure to process.
2049 Dark exposure of the same size as ``exposure``.
2050 invert : `Bool`, optional
2051 If
True, re-add the dark to an already corrected image.
2056 Raised
if either ``exposure``
or ``darkExposure`` do
not
2057 have their dark time defined.
2061 lsst.ip.isr.isrFunctions.darkCorrection
2063 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
2064 if math.isnan(expScale):
2065 raise RuntimeError(
"Exposure darktime is NAN.")
2066 if darkExposure.getInfo().getVisitInfo()
is not None \
2067 and not math.isnan(darkExposure.getInfo().getVisitInfo().getDarkTime()):
2068 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
2072 self.log.warning(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
2075 isrFunctions.darkCorrection(
2076 maskedImage=exposure.getMaskedImage(),
2077 darkMaskedImage=darkExposure.getMaskedImage(),
2079 darkScale=darkScale,
2081 trimToFit=self.config.doTrimToMatchCalib
2085 """Check if linearization is needed for the detector cameraGeom.
2087 Checks config.doLinearize and the linearity type of the first
2093 Detector to get linearity type
from.
2097 doLinearize : `Bool`
2098 If
True, linearization should be performed.
2100 return self.config.doLinearize
and \
2101 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
2104 """Apply flat correction in place.
2109 Exposure to process.
2111 Flat exposure of the same size as ``exposure``.
2112 invert : `Bool`, optional
2113 If
True, unflatten an already flattened image.
2117 lsst.ip.isr.isrFunctions.flatCorrection
2119 isrFunctions.flatCorrection(
2120 maskedImage=exposure.getMaskedImage(),
2121 flatMaskedImage=flatExposure.getMaskedImage(),
2122 scalingType=self.config.flatScalingType,
2123 userScale=self.config.flatUserScale,
2125 trimToFit=self.config.doTrimToMatchCalib
2129 """Detect and mask saturated pixels in config.saturatedMaskName.
2134 Exposure to process. Only the amplifier DataSec is processed.
2136 Amplifier detector data.
2140 lsst.ip.isr.isrFunctions.makeThresholdMask
2142 if not math.isnan(amp.getSaturation()):
2143 maskedImage = exposure.getMaskedImage()
2144 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2145 isrFunctions.makeThresholdMask(
2146 maskedImage=dataView,
2147 threshold=amp.getSaturation(),
2149 maskName=self.config.saturatedMaskName,
2153 """Interpolate over saturated pixels, in place.
2155 This method should be called after `saturationDetection`, to
2156 ensure that the saturated pixels have been identified in the
2157 SAT mask. It should also be called after `assembleCcd`, since
2158 saturated regions may cross amplifier boundaries.
2163 Exposure to process.
2167 lsst.ip.isr.isrTask.saturationDetection
2168 lsst.ip.isr.isrFunctions.interpolateFromMask
2170 isrFunctions.interpolateFromMask(
2171 maskedImage=exposure.getMaskedImage(),
2172 fwhm=self.config.fwhm,
2173 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2174 maskNameList=list(self.config.saturatedMaskName),
2178 """Detect and mask suspect pixels in config.suspectMaskName.
2183 Exposure to process. Only the amplifier DataSec is processed.
2185 Amplifier detector data.
2189 lsst.ip.isr.isrFunctions.makeThresholdMask
2193 Suspect pixels are pixels whose value
is greater than
2194 amp.getSuspectLevel(). This
is intended to indicate pixels that may be
2195 affected by unknown systematics;
for example
if non-linearity
2196 corrections above a certain level are unstable then that would be a
2197 useful value
for suspectLevel. A value of `nan` indicates that no such
2198 level exists
and no pixels are to be masked
as suspicious.
2200 suspectLevel = amp.getSuspectLevel()
2201 if math.isnan(suspectLevel):
2204 maskedImage = exposure.getMaskedImage()
2205 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2206 isrFunctions.makeThresholdMask(
2207 maskedImage=dataView,
2208 threshold=suspectLevel,
2210 maskName=self.config.suspectMaskName,
2214 """Mask defects using mask plane "BAD", in place.
2219 Exposure to process.
2220 defectBaseList : defect-type
2226 Call this after CCD assembly, since defects may cross amplifier
2229 maskedImage = exposure.getMaskedImage()
2230 if not isinstance(defectBaseList, Defects):
2232 defectList =
Defects(defectBaseList)
2234 defectList = defectBaseList
2235 defectList.maskPixels(maskedImage, maskName=
"BAD")
2237 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR'):
2238 """Mask edge pixels with applicable mask plane.
2243 Exposure to process.
2244 numEdgePixels : `int`, optional
2245 Number of edge pixels to mask.
2246 maskPlane : `str`, optional
2247 Mask plane name to use.
2248 level : `str`, optional
2249 Level at which to mask edges.
2251 maskedImage = exposure.getMaskedImage()
2252 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2254 if numEdgePixels > 0:
2255 if level ==
'DETECTOR':
2256 boxes = [maskedImage.getBBox()]
2257 elif level ==
'AMP':
2258 boxes = [amp.getBBox()
for amp
in exposure.getDetector()]
2263 subImage = maskedImage[box]
2264 box.grow(-numEdgePixels)
2266 SourceDetectionTask.setEdgeBits(
2272 """Mask and interpolate defects using mask plane "BAD", in place.
2277 Exposure to process.
2278 defectBaseList : defects-like
2279 List of defects to mask and interpolate. Can be
2284 lsst.ip.isr.isrTask.maskDefect
2287 self.maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2288 maskPlane="SUSPECT", level=self.config.edgeMaskLevel)
2289 isrFunctions.interpolateFromMask(
2290 maskedImage=exposure.getMaskedImage(),
2291 fwhm=self.config.fwhm,
2292 growSaturatedFootprints=0,
2293 maskNameList=[
"BAD"],
2297 """Mask NaNs using mask plane "UNMASKEDNAN", in place.
2302 Exposure to process.
2306 We mask over all non-finite values (NaN, inf), including those
2307 that are masked with other bits (because those may
or may
not be
2308 interpolated over later,
and we want to remove all NaN/infs).
2309 Despite this behaviour, the
"UNMASKEDNAN" mask plane
is used to
2310 preserve the historical name.
2312 maskedImage = exposure.getMaskedImage()
2315 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2316 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2317 numNans = maskNans(maskedImage, maskVal)
2318 self.metadata[
"NUMNANS"] = numNans
2320 self.log.warning(
"There were %d unmasked NaNs.", numNans)
2323 """"Mask and interpolate NaN/infs using mask plane "UNMASKEDNAN",
2329 Exposure to process.
2333 lsst.ip.isr.isrTask.maskNan
2336 isrFunctions.interpolateFromMask(
2337 maskedImage=exposure.getMaskedImage(),
2338 fwhm=self.config.fwhm,
2339 growSaturatedFootprints=0,
2340 maskNameList=["UNMASKEDNAN"],
2344 """Measure the image background in subgrids, for quality control.
2349 Exposure to process.
2351 Configuration object containing parameters on which background
2352 statistics and subgrids to use.
2354 if IsrQaConfig
is not None:
2355 statsControl = afwMath.StatisticsControl(IsrQaConfig.flatness.clipSigma,
2356 IsrQaConfig.flatness.nIter)
2357 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2358 statsControl.setAndMask(maskVal)
2359 maskedImage = exposure.getMaskedImage()
2360 stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl)
2361 skyLevel = stats.getValue(afwMath.MEDIAN)
2362 skySigma = stats.getValue(afwMath.STDEVCLIP)
2363 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2364 metadata = exposure.getMetadata()
2365 metadata[
"SKYLEVEL"] = skyLevel
2366 metadata[
"SKYSIGMA"] = skySigma
2369 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2370 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2371 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2372 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2373 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2374 skyLevels = numpy.zeros((nX, nY))
2377 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2379 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2381 xLLC = xc - meshXHalf
2382 yLLC = yc - meshYHalf
2383 xURC = xc + meshXHalf - 1
2384 yURC = yc + meshYHalf - 1
2387 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2389 skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue()
2391 good = numpy.where(numpy.isfinite(skyLevels))
2392 skyMedian = numpy.median(skyLevels[good])
2393 flatness = (skyLevels[good] - skyMedian) / skyMedian
2394 flatness_rms = numpy.std(flatness)
2395 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2397 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2398 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2399 nX, nY, flatness_pp, flatness_rms)
2401 metadata[
"FLATNESS_PP"] = float(flatness_pp)
2402 metadata[
"FLATNESS_RMS"] = float(flatness_rms)
2403 metadata[
"FLATNESS_NGRIDS"] =
'%dx%d' % (nX, nY)
2404 metadata[
"FLATNESS_MESHX"] = IsrQaConfig.flatness.meshX
2405 metadata[
"FLATNESS_MESHY"] = IsrQaConfig.flatness.meshY
2408 """Set an approximate magnitude zero point for the exposure.
2413 Exposure to process.
2415 filterLabel = exposure.getFilter()
2416 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
2418 if physicalFilter
in self.config.fluxMag0T1:
2419 fluxMag0 = self.config.fluxMag0T1[physicalFilter]
2421 self.log.warning(
"No rough magnitude zero point defined for filter %s.", physicalFilter)
2422 fluxMag0 = self.config.defaultFluxMag0T1
2424 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2426 self.log.warning(
"Non-positive exposure time; skipping rough zero point.")
2429 self.log.info(
"Setting rough magnitude zero point for filter %s: %f",
2430 physicalFilter, 2.5*math.log10(fluxMag0*expTime))
2431 exposure.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(fluxMag0*expTime, 0.0))
2435 """Context manager that applies and removes flats and darks,
2436 if the task
is configured to apply them.
2441 Exposure to process.
2443 Flat exposure the same size
as ``exp``.
2445 Dark exposure the same size
as ``exp``.
2450 The flat
and dark corrected exposure.
2452 if self.config.doDark
and dark
is not None:
2454 if self.config.doFlat:
2459 if self.config.doFlat:
2461 if self.config.doDark
and dark
is not None:
2465 """Utility function to examine ISR exposure at different stages.
2472 State of processing to view.
2474 frame = getDebugFrame(self._display, stepname)
2476 display = getDisplay(frame)
2477 display.scale(
'asinh',
'zscale')
2478 display.mtv(exposure)
2479 prompt =
"Press Enter to continue [c]... "
2481 ans = input(prompt).lower()
2482 if ans
in (
"",
"c",):
2487 """A Detector-like object that supports returning gain and saturation level
2489 This is used when the input exposure does
not have a detector.
2494 Exposure to generate a fake amplifier
for.
2495 config : `lsst.ip.isr.isrTaskConfig`
2496 Configuration to apply to the fake amplifier.
2500 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
2502 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 extractCalibDate(calib)
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, deferredChargeCalib=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 crosstalkSourceLookup(datasetType, registry, quantumDataId, collections)