22__all__ = [
"IsrTask",
"IsrTaskConfig"]
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 (currently unused).",
376 qa = pexConfig.ConfigField(
378 doc=
"QA related configuration options.",
380 doHeaderProvenance = pexConfig.Field(
383 doc=
"Write calibration identifiers into output exposure header?",
387 doRaiseOnCalibMismatch = pexConfig.Field(
390 doc=
"Should IsrTask halt if exposure and calibration header values do not match?",
392 cameraKeywordsToCompare = pexConfig.ListField(
394 doc=
"List of header keywords to compare between exposure and calibrations.",
399 doConvertIntToFloat = pexConfig.Field(
401 doc=
"Convert integer raw images to floating point values?",
406 doSaturation = pexConfig.Field(
408 doc=
"Mask saturated pixels? NB: this is totally independent of the"
409 " interpolation option - this is ONLY setting the bits in the mask."
410 " To have them interpolated make sure doSaturationInterpolation=True",
413 saturatedMaskName = pexConfig.Field(
415 doc=
"Name of mask plane to use in saturation detection and interpolation",
418 saturation = pexConfig.Field(
420 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
421 default=float(
"NaN"),
423 growSaturationFootprintSize = pexConfig.Field(
425 doc=
"Number of pixels by which to grow the saturation footprints",
430 doSuspect = pexConfig.Field(
432 doc=
"Mask suspect pixels?",
435 suspectMaskName = pexConfig.Field(
437 doc=
"Name of mask plane to use for suspect pixels",
440 numEdgeSuspect = pexConfig.Field(
442 doc=
"Number of edge pixels to be flagged as untrustworthy.",
445 edgeMaskLevel = pexConfig.ChoiceField(
447 doc=
"Mask edge pixels in which coordinate frame: DETECTOR or AMP?",
450 'DETECTOR':
'Mask only the edges of the full detector.',
451 'AMP':
'Mask edges of each amplifier.',
456 doSetBadRegions = pexConfig.Field(
458 doc=
"Should we set the level of all BAD patches of the chip to the chip's average value?",
461 badStatistic = pexConfig.ChoiceField(
463 doc=
"How to estimate the average value for BAD regions.",
466 "MEANCLIP":
"Correct using the (clipped) mean of good data",
467 "MEDIAN":
"Correct using the median of the good data",
472 doOverscan = pexConfig.Field(
474 doc=
"Do overscan subtraction?",
477 overscan = pexConfig.ConfigurableField(
478 target=OverscanCorrectionTask,
479 doc=
"Overscan subtraction task for image segments.",
483 doAssembleCcd = pexConfig.Field(
486 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
488 assembleCcd = pexConfig.ConfigurableField(
489 target=AssembleCcdTask,
490 doc=
"CCD assembly task",
494 doAssembleIsrExposures = pexConfig.Field(
497 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
499 doTrimToMatchCalib = pexConfig.Field(
502 doc=
"Trim raw data to match calibration bounding boxes?"
506 doBias = pexConfig.Field(
508 doc=
"Apply bias frame correction?",
511 biasDataProductName = pexConfig.Field(
513 doc=
"Name of the bias data product",
516 doBiasBeforeOverscan = pexConfig.Field(
518 doc=
"Reverse order of overscan and bias correction.",
523 doDeferredCharge = pexConfig.Field(
525 doc=
"Apply deferred charge correction?",
528 deferredChargeCorrection = pexConfig.ConfigurableField(
529 target=DeferredChargeTask,
530 doc=
"Deferred charge correction task.",
534 doVariance = pexConfig.Field(
536 doc=
"Calculate variance?",
539 gain = pexConfig.Field(
541 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
542 default=float(
"NaN"),
544 readNoise = pexConfig.Field(
546 doc=
"The read noise to use if no Detector is present in the Exposure",
549 doEmpiricalReadNoise = pexConfig.Field(
552 doc=
"Calculate empirical read noise instead of value from AmpInfo data?"
554 usePtcReadNoise = pexConfig.Field(
557 doc=
"Use readnoise values from the Photon Transfer Curve?"
559 maskNegativeVariance = pexConfig.Field(
562 doc=
"Mask pixels that claim a negative variance? This likely indicates a failure "
563 "in the measurement of the overscan at an edge due to the data falling off faster "
564 "than the overscan model can account for it."
566 negativeVarianceMaskName = pexConfig.Field(
569 doc=
"Mask plane to use to mark pixels with negative variance, if `maskNegativeVariance` is True.",
572 doLinearize = pexConfig.Field(
574 doc=
"Correct for nonlinearity of the detector's response?",
579 doCrosstalk = pexConfig.Field(
581 doc=
"Apply intra-CCD crosstalk correction?",
584 doCrosstalkBeforeAssemble = pexConfig.Field(
586 doc=
"Apply crosstalk correction before CCD assembly, and before trimming?",
589 crosstalk = pexConfig.ConfigurableField(
590 target=CrosstalkTask,
591 doc=
"Intra-CCD crosstalk correction",
595 doDefect = pexConfig.Field(
597 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
600 doNanMasking = pexConfig.Field(
602 doc=
"Mask non-finite (NAN, inf) pixels?",
605 doWidenSaturationTrails = pexConfig.Field(
607 doc=
"Widen bleed trails based on their width?",
612 doBrighterFatter = pexConfig.Field(
615 doc=
"Apply the brighter-fatter correction?"
617 doFluxConservingBrighterFatterCorrection = pexConfig.Field(
620 doc=
"Apply the flux-conserving BFE correction by Miller et al.?"
622 brighterFatterLevel = pexConfig.ChoiceField(
625 doc=
"The level at which to correct for brighter-fatter.",
627 "AMP":
"Every amplifier treated separately.",
628 "DETECTOR":
"One kernel per detector",
631 brighterFatterMaxIter = pexConfig.Field(
634 doc=
"Maximum number of iterations for the brighter-fatter correction"
636 brighterFatterThreshold = pexConfig.Field(
639 doc=
"Threshold used to stop iterating the brighter-fatter correction. It is the "
640 "absolute value of the difference between the current corrected image and the one "
641 "from the previous iteration summed over all the pixels."
643 brighterFatterApplyGain = pexConfig.Field(
646 doc=
"Should the gain be applied when applying the brighter-fatter correction?"
648 brighterFatterMaskListToInterpolate = pexConfig.ListField(
650 doc=
"List of mask planes that should be interpolated over when applying the brighter-fatter "
652 default=[
"SAT",
"BAD",
"NO_DATA",
"UNMASKEDNAN"],
654 brighterFatterMaskGrowSize = pexConfig.Field(
657 doc=
"Number of pixels to grow the masks listed in config.brighterFatterMaskListToInterpolate "
658 "when brighter-fatter correction is applied."
662 doDark = pexConfig.Field(
664 doc=
"Apply dark frame correction?",
667 darkDataProductName = pexConfig.Field(
669 doc=
"Name of the dark data product",
674 doStrayLight = pexConfig.Field(
676 doc=
"Subtract stray light in the y-band (due to encoder LEDs)?",
679 strayLight = pexConfig.ConfigurableField(
680 target=StrayLightTask,
681 doc=
"y-band stray light correction"
685 doFlat = pexConfig.Field(
687 doc=
"Apply flat field correction?",
690 flatDataProductName = pexConfig.Field(
692 doc=
"Name of the flat data product",
695 flatScalingType = pexConfig.ChoiceField(
697 doc=
"The method for scaling the flat on the fly.",
700 "USER":
"Scale by flatUserScale",
701 "MEAN":
"Scale by the inverse of the mean",
702 "MEDIAN":
"Scale by the inverse of the median",
705 flatUserScale = pexConfig.Field(
707 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
710 doTweakFlat = pexConfig.Field(
712 doc=
"Tweak flats to match observed amplifier ratios?",
718 doApplyGains = pexConfig.Field(
720 doc=
"Correct the amplifiers for their gains instead of applying flat correction",
723 usePtcGains = pexConfig.Field(
725 doc=
"Use the gain values from the Photon Transfer Curve?",
728 normalizeGains = pexConfig.Field(
730 doc=
"Normalize all the amplifiers in each CCD to have the same median value.",
735 doFringe = pexConfig.Field(
737 doc=
"Apply fringe correction?",
740 fringe = pexConfig.ConfigurableField(
742 doc=
"Fringe subtraction task",
744 fringeAfterFlat = pexConfig.Field(
746 doc=
"Do fringe subtraction after flat-fielding?",
751 doAmpOffset = pexConfig.Field(
752 doc=
"Calculate and apply amp offset corrections?",
756 ampOffset = pexConfig.ConfigurableField(
757 doc=
"Amp offset correction task.",
758 target=AmpOffsetTask,
762 doMeasureBackground = pexConfig.Field(
764 doc=
"Measure the background level on the reduced image?",
769 doCameraSpecificMasking = pexConfig.Field(
771 doc=
"Mask camera-specific bad regions?",
774 masking = pexConfig.ConfigurableField(
780 doInterpolate = pexConfig.Field(
782 doc=
"Interpolate masked pixels?",
785 doSaturationInterpolation = pexConfig.Field(
787 doc=
"Perform interpolation over pixels masked as saturated?"
788 " NB: This is independent of doSaturation; if that is False this plane"
789 " will likely be blank, resulting in a no-op here.",
792 doNanInterpolation = pexConfig.Field(
794 doc=
"Perform interpolation over pixels masked as NaN?"
795 " NB: This is independent of doNanMasking; if that is False this plane"
796 " will likely be blank, resulting in a no-op here.",
799 doNanInterpAfterFlat = pexConfig.Field(
801 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we "
802 "also have to interpolate them before flat-fielding."),
805 maskListToInterpolate = pexConfig.ListField(
807 doc=
"List of mask planes that should be interpolated.",
808 default=[
'SAT',
'BAD'],
810 doSaveInterpPixels = pexConfig.Field(
812 doc=
"Save a copy of the pre-interpolated pixel values?",
817 fluxMag0T1 = pexConfig.DictField(
820 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure, per filter.",
821 default=dict((f, pow(10.0, 0.4*m))
for f, m
in ((
"Unknown", 28.0),
824 defaultFluxMag0T1 = pexConfig.Field(
826 doc=
"Default value for fluxMag0T1 (for an unrecognized filter).",
827 default=pow(10.0, 0.4*28.0)
831 doVignette = pexConfig.Field(
833 doc=(
"Compute and attach the validPolygon defining the unvignetted region to the exposure "
834 "according to vignetting parameters?"),
837 doMaskVignettePolygon = pexConfig.Field(
839 doc=(
"Add a mask bit for pixels within the vignetted region. Ignored if doVignette "
843 vignetteValue = pexConfig.Field(
845 doc=
"Value to replace image array pixels with in the vignetted region? Ignored if None.",
849 vignette = pexConfig.ConfigurableField(
851 doc=
"Vignetting task.",
855 doAttachTransmissionCurve = pexConfig.Field(
858 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?"
860 doUseOpticsTransmission = pexConfig.Field(
863 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?"
865 doUseFilterTransmission = pexConfig.Field(
868 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?"
870 doUseSensorTransmission = pexConfig.Field(
873 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?"
875 doUseAtmosphereTransmission = pexConfig.Field(
878 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?"
882 doIlluminationCorrection = pexConfig.Field(
885 doc=
"Perform illumination correction?"
887 illuminationCorrectionDataProductName = pexConfig.Field(
889 doc=
"Name of the illumination correction data product.",
892 illumScale = pexConfig.Field(
894 doc=
"Scale factor for the illumination correction.",
897 illumFilters = pexConfig.ListField(
900 doc=
"Only perform illumination correction for these filters."
904 doStandardStatistics = pexConfig.Field(
906 doc=
"Should standard image quality statistics be calculated?",
910 doCalculateStatistics = pexConfig.Field(
912 doc=
"Should additional ISR statistics be calculated?",
915 isrStats = pexConfig.ConfigurableField(
916 target=IsrStatisticsTask,
917 doc=
"Task to calculate additional statistics.",
922 doWrite = pexConfig.Field(
924 doc=
"Persist postISRCCD?",
931 raise ValueError(
"You may not specify both doFlat and doApplyGains")
933 raise ValueError(
"You may not specify both doBiasBeforeOverscan and doTrimToMatchCalib")
943 """Apply common instrument signature correction algorithms to a raw frame.
945 The process for correcting imaging data
is very similar
from
946 camera to camera. This task provides a vanilla implementation of
947 doing these corrections, including the ability to turn certain
948 corrections off
if they are
not needed. The inputs to the primary
949 method, `run()`, are a raw exposure to be corrected
and the
950 calibration data products. The raw input
is a single chip sized
951 mosaic of all amps including overscans
and other non-science
954 The __init__ method sets up the subtasks
for ISR processing, using
960 Positional arguments passed to the Task constructor.
961 None used at this time.
962 kwargs : `dict`, optional
963 Keyword arguments passed on to the Task constructor.
964 None used at this time.
966 ConfigClass = IsrTaskConfig
969 def __init__(self, **kwargs):
970 super().__init__(**kwargs)
971 self.makeSubtask(
"assembleCcd")
972 self.makeSubtask(
"crosstalk")
973 self.makeSubtask(
"strayLight")
974 self.makeSubtask(
"fringe")
975 self.makeSubtask(
"masking")
976 self.makeSubtask(
"overscan")
977 self.makeSubtask(
"vignette")
978 self.makeSubtask(
"ampOffset")
979 self.makeSubtask(
"deferredChargeCorrection")
980 self.makeSubtask(
"isrStats")
983 inputs = butlerQC.get(inputRefs)
986 inputs[
'detectorNum'] = inputRefs.ccdExposure.dataId[
'detector']
987 except Exception
as e:
988 raise ValueError(
"Failure to find valid detectorNum value for Dataset %s: %s." %
991 detector = inputs[
'ccdExposure'].getDetector()
993 if self.config.doCrosstalk
is True:
996 if 'crosstalk' in inputs
and inputs[
'crosstalk']
is not None:
997 if not isinstance(inputs[
'crosstalk'], CrosstalkCalib):
998 inputs[
'crosstalk'] = CrosstalkCalib.fromTable(inputs[
'crosstalk'])
1000 coeffVector = (self.config.crosstalk.crosstalkValues
1001 if self.config.crosstalk.useConfigCoefficients
else None)
1002 crosstalkCalib =
CrosstalkCalib().fromDetector(detector, coeffVector=coeffVector)
1003 inputs[
'crosstalk'] = crosstalkCalib
1004 if inputs[
'crosstalk'].interChip
and len(inputs[
'crosstalk'].interChip) > 0:
1005 if 'crosstalkSources' not in inputs:
1006 self.log.warning(
"No crosstalkSources found for chip with interChip terms!")
1009 if 'linearizer' in inputs:
1010 if isinstance(inputs[
'linearizer'], dict):
1012 linearizer.fromYaml(inputs[
'linearizer'])
1013 self.log.warning(
"Dictionary linearizers will be deprecated in DM-28741.")
1014 elif isinstance(inputs[
'linearizer'], numpy.ndarray):
1018 self.log.warning(
"Bare lookup table linearizers will be deprecated in DM-28741.")
1020 linearizer = inputs[
'linearizer']
1021 linearizer.log = self.log
1022 inputs[
'linearizer'] = linearizer
1025 self.log.warning(
"Constructing linearizer from cameraGeom information.")
1027 if self.config.doDefect
is True:
1028 if "defects" in inputs
and inputs[
'defects']
is not None:
1032 if not isinstance(inputs[
"defects"], Defects):
1033 inputs[
"defects"] = Defects.fromTable(inputs[
"defects"])
1037 if self.config.doBrighterFatter:
1038 brighterFatterKernel = inputs.pop(
'newBFKernel',
None)
1039 if brighterFatterKernel
is None:
1043 brighterFatterKernel = inputs.get(
'bfKernel',
None)
1045 if brighterFatterKernel
is not None and not isinstance(brighterFatterKernel, numpy.ndarray):
1052 detName = detector.getName()
1053 level = brighterFatterKernel.level
1056 inputs[
'bfGains'] = brighterFatterKernel.gain
1057 if self.config.brighterFatterLevel ==
'DETECTOR':
1059 if level ==
'DETECTOR':
1060 if detName
in brighterFatterKernel.detKernels:
1061 kernel = brighterFatterKernel.detKernels[detName]
1063 raise RuntimeError(
"Failed to extract kernel from new-style BF kernel.")
1064 elif level ==
'AMP':
1065 self.log.warning(
"Making DETECTOR level kernel from AMP based brighter "
1067 brighterFatterKernel.makeDetectorKernelFromAmpwiseKernels(detName)
1068 kernel = brighterFatterKernel.detKernels[detName]
1070 raise RuntimeError(
"Could not identify brighter-fatter kernel!")
1074 inputs[
'bfKernel'] = numpy.transpose(kernel)
1075 elif self.config.brighterFatterLevel ==
'AMP':
1076 raise NotImplementedError(
"Per-amplifier brighter-fatter correction not implemented")
1078 if self.config.doFringe
is True and self.fringe.checkFilter(inputs[
'ccdExposure']):
1079 expId = inputs[
'ccdExposure'].info.id
1080 inputs[
'fringes'] = self.fringe.loadFringes(inputs[
'fringes'],
1082 assembler=self.assembleCcd
1083 if self.config.doAssembleIsrExposures
else None)
1085 inputs[
'fringes'] = pipeBase.Struct(fringes=
None)
1087 if self.config.doStrayLight
is True and self.strayLight.checkFilter(inputs[
'ccdExposure']):
1088 if 'strayLightData' not in inputs:
1089 inputs[
'strayLightData'] =
None
1091 if self.config.doHeaderProvenance:
1093 exposureMetadata = inputs[
'ccdExposure'].getMetadata()
1094 for inputName
in sorted(inputs.keys()):
1095 reference = getattr(inputRefs, inputName,
None)
1096 if reference
is not None and hasattr(reference,
"run"):
1097 runKey = f
"LSST CALIB RUN {inputName.upper()}"
1098 runValue = reference.run
1099 idKey = f
"LSST CALIB UUID {inputName.upper()}"
1100 idValue = str(reference.id)
1102 exposureMetadata[runKey] = runValue
1103 exposureMetadata[idKey] = idValue
1105 outputs = self.
run(**inputs)
1106 butlerQC.put(outputs, outputRefs)
1109 def run(self, ccdExposure, *, camera=None, bias=None, linearizer=None,
1110 crosstalk=None, crosstalkSources=None,
1111 dark=None, flat=None, ptc=None, bfKernel=None, bfGains=None, defects=None,
1112 fringes=pipeBase.Struct(fringes=
None), opticsTransmission=
None, filterTransmission=
None,
1113 sensorTransmission=
None, atmosphereTransmission=
None,
1114 detectorNum=
None, strayLightData=
None, illumMaskedImage=
None,
1115 deferredChargeCalib=
None,
1117 """Perform instrument signature removal on an exposure.
1119 Steps included in the ISR processing,
in order performed, are:
1121 - saturation
and suspect pixel masking
1122 - overscan subtraction
1123 - CCD assembly of individual amplifiers
1125 - variance image construction
1126 - linearization of non-linear response
1128 - brighter-fatter correction
1131 - stray light subtraction
1133 - masking of known defects
and camera specific features
1134 - vignette calculation
1135 - appending transmission curve
and distortion model
1140 The raw exposure that
is to be run through ISR. The
1141 exposure
is modified by this method.
1143 The camera geometry
for this exposure. Required
if
1144 one
or more of ``ccdExposure``, ``bias``, ``dark``,
or
1145 ``flat`` does
not have an associated detector.
1147 Bias calibration frame.
1149 Functor
for linearization.
1151 Calibration
for crosstalk.
1152 crosstalkSources : `list`, optional
1153 List of possible crosstalk sources.
1155 Dark calibration frame.
1157 Flat calibration frame.
1159 Photon transfer curve dataset,
with, e.g., gains
1161 bfKernel : `numpy.ndarray`, optional
1162 Brighter-fatter kernel.
1163 bfGains : `dict` of `float`, optional
1164 Gains used to override the detector
's nominal gains for the
1165 brighter-fatter correction. A dict keyed by amplifier name for
1166 the detector
in question.
1169 fringes : `lsst.pipe.base.Struct`, optional
1170 Struct containing the fringe correction data,
with
1176 random seed derived
from the ``ccdExposureId``
for random
1177 number generator (`numpy.uint32`)
1179 A ``TransmissionCurve`` that represents the throughput of the,
1180 optics, to be evaluated
in focal-plane coordinates.
1182 A ``TransmissionCurve`` that represents the throughput of the
1183 filter itself, to be evaluated
in focal-plane coordinates.
1185 A ``TransmissionCurve`` that represents the throughput of the
1186 sensor itself, to be evaluated
in post-assembly trimmed detector
1189 A ``TransmissionCurve`` that represents the throughput of the
1190 atmosphere, assumed to be spatially constant.
1191 detectorNum : `int`, optional
1192 The integer number
for the detector to process.
1193 strayLightData : `object`, optional
1194 Opaque object containing calibration information
for stray-light
1195 correction. If `
None`, no correction will be performed.
1197 Illumination correction image.
1201 result : `lsst.pipe.base.Struct`
1202 Result struct
with component:
1205 The fully ISR corrected exposure.
1210 Thumbnail image of the exposure after overscan subtraction.
1213 Thumbnail image of the exposure after flat-field correction.
1215 ``outputStatistics``
1216 Values of the additional statistics calculated.
1221 Raised
if a configuration option
is set to `
True`, but the
1222 required calibration data has
not been specified.
1226 The current processed exposure can be viewed by setting the
1227 appropriate `lsstDebug` entries
in the ``debug.display``
1228 dictionary. The names of these entries correspond to some of
1229 the `IsrTaskConfig` Boolean options,
with the value denoting the
1230 frame to use. The exposure
is shown inside the matching
1231 option check
and after the processing of that step has
1232 finished. The steps
with debug points are:
1243 In addition, setting the ``postISRCCD`` entry displays the
1244 exposure after all ISR processing has finished.
1247 ccdExposure = self.ensureExposure(ccdExposure, camera, detectorNum)
1252 ccd = ccdExposure.getDetector()
1253 filterLabel = ccdExposure.getFilter()
1254 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
1257 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd."
1258 ccd = [
FakeAmp(ccdExposure, self.config)]
1261 if self.config.doBias
and bias
is None:
1262 raise RuntimeError(
"Must supply a bias exposure if config.doBias=True.")
1264 raise RuntimeError(
"Must supply a linearizer if config.doLinearize=True for this detector.")
1265 if self.config.doBrighterFatter
and bfKernel
is None:
1266 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter=True.")
1267 if self.config.doDark
and dark
is None:
1268 raise RuntimeError(
"Must supply a dark exposure if config.doDark=True.")
1269 if self.config.doFlat
and flat
is None:
1270 raise RuntimeError(
"Must supply a flat exposure if config.doFlat=True.")
1271 if self.config.doDefect
and defects
is None:
1272 raise RuntimeError(
"Must supply defects if config.doDefect=True.")
1273 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters
1274 and fringes.fringes
is None):
1279 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct.")
1280 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters
1281 and illumMaskedImage
is None):
1282 raise RuntimeError(
"Must supply an illumcor if config.doIlluminationCorrection=True.")
1283 if (self.config.doDeferredCharge
and deferredChargeCalib
is None):
1284 raise RuntimeError(
"Must supply a deferred charge calibration if config.doDeferredCharge=True.")
1286 if self.config.doHeaderProvenance:
1289 exposureMetadata = ccdExposure.getMetadata()
1290 if self.config.doBias:
1293 if self.config.doBrighterFatter:
1296 if self.config.doCrosstalk:
1297 exposureMetadata[
"LSST CALIB DATE CROSSTALK"] = self.
extractCalibDate(crosstalk)
1299 if self.config.doDark:
1302 if self.config.doDefect:
1303 exposureMetadata[
"LSST CALIB DATE DEFECTS"] = self.
extractCalibDate(defects)
1305 if self.config.doDeferredCharge:
1306 exposureMetadata[
"LSST CALIB DATE CTI"] = self.
extractCalibDate(deferredChargeCalib)
1308 if self.config.doFlat:
1311 if (self.config.doFringe
and physicalFilter
in self.fringe.config.filters):
1312 exposureMetadata[
"LSST CALIB DATE FRINGE"] = self.
extractCalibDate(fringes.fringes)
1314 if (self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters):
1315 exposureMetadata[
"LSST CALIB DATE ILLUMINATION"] = self.
extractCalibDate(illumMaskedImage)
1318 exposureMetadata[
"LSST CALIB DATE LINEARIZER"] = self.
extractCalibDate(linearizer)
1320 if self.config.usePtcGains
or self.config.usePtcReadNoise:
1323 if self.config.doStrayLight:
1324 exposureMetadata[
"LSST CALIB DATE STRAYLIGHT"] = self.
extractCalibDate(strayLightData)
1326 if self.config.doAttachTransmissionCurve:
1327 exposureMetadata[
"LSST CALIB DATE OPTICS_TR"] = self.
extractCalibDate(opticsTransmission)
1328 exposureMetadata[
"LSST CALIB DATE FILTER_TR"] = self.
extractCalibDate(filterTransmission)
1329 exposureMetadata[
"LSST CALIB DATE SENSOR_TR"] = self.
extractCalibDate(sensorTransmission)
1330 exposureMetadata[
"LSST CALIB DATE ATMOSP_TR"] = self.
extractCalibDate(atmosphereTransmission)
1333 if self.config.doConvertIntToFloat:
1334 self.log.info(
"Converting exposure to floating point values.")
1337 if self.config.doBias
and self.config.doBiasBeforeOverscan:
1338 self.log.info(
"Applying bias correction.")
1339 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1340 trimToFit=self.config.doTrimToMatchCalib)
1346 if self.config.doOverscan
and self.config.overscan.doParallelOverscan:
1348 self.overscan.maskParallelOverscan(ccdExposure, ccd)
1353 if ccdExposure.getBBox().contains(amp.getBBox()):
1358 if self.config.doOverscan
and not badAmp:
1361 self.log.debug(
"Corrected overscan for amplifier %s.", amp.getName())
1362 if overscanResults
is not None and \
1363 self.config.qa
is not None and self.config.qa.saveStats
is True:
1364 if isinstance(overscanResults.overscanMean, float):
1366 mean = overscanResults.overscanMean
1367 sigma = overscanResults.overscanSigma
1368 residMean = overscanResults.residualMean
1369 residSigma = overscanResults.residualSigma
1373 mean = overscanResults.overscanMean[0]
1374 sigma = overscanResults.overscanSigma[0]
1375 residMean = overscanResults.residualMean[0]
1376 residSigma = overscanResults.residualSigma[0]
1378 self.metadata[f
"FIT MEDIAN {amp.getName()}"] = mean
1379 self.metadata[f
"FIT STDEV {amp.getName()}"] = sigma
1380 self.log.debug(
" Overscan stats for amplifer %s: %f +/- %f",
1381 amp.getName(), mean, sigma)
1383 self.metadata[f
"RESIDUAL MEDIAN {amp.getName()}"] = residMean
1384 self.metadata[f
"RESIDUAL STDEV {amp.getName()}"] = residSigma
1385 self.log.debug(
" Overscan stats for amplifer %s after correction: %f +/- %f",
1386 amp.getName(), residMean, residSigma)
1388 ccdExposure.getMetadata().
set(
'OVERSCAN',
"Overscan corrected")
1391 self.log.warning(
"Amplifier %s is bad.", amp.getName())
1392 overscanResults =
None
1394 overscans.append(overscanResults
if overscanResults
is not None else None)
1396 self.log.info(
"Skipped OSCAN for %s.", amp.getName())
1398 if self.config.doDeferredCharge:
1399 self.log.info(
"Applying deferred charge/CTI correction.")
1400 self.deferredChargeCorrection.run(ccdExposure, deferredChargeCalib)
1401 self.
debugView(ccdExposure,
"doDeferredCharge")
1403 if self.config.doCrosstalk
and self.config.doCrosstalkBeforeAssemble:
1404 self.log.info(
"Applying crosstalk correction.")
1405 self.crosstalk.run(ccdExposure, crosstalk=crosstalk,
1406 crosstalkSources=crosstalkSources, camera=camera)
1407 self.
debugView(ccdExposure,
"doCrosstalk")
1409 if self.config.doAssembleCcd:
1410 self.log.info(
"Assembling CCD from amplifiers.")
1411 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
1413 if self.config.expectWcs
and not ccdExposure.getWcs():
1414 self.log.warning(
"No WCS found in input exposure.")
1415 self.
debugView(ccdExposure,
"doAssembleCcd")
1418 if self.config.qa.doThumbnailOss:
1419 ossThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1421 if self.config.doBias
and not self.config.doBiasBeforeOverscan:
1422 self.log.info(
"Applying bias correction.")
1423 isrFunctions.biasCorrection(ccdExposure.getMaskedImage(), bias.getMaskedImage(),
1424 trimToFit=self.config.doTrimToMatchCalib)
1427 if self.config.doVariance:
1428 for amp, overscanResults
in zip(ccd, overscans):
1429 if ccdExposure.getBBox().contains(amp.getBBox()):
1430 self.log.debug(
"Constructing variance map for amplifer %s.", amp.getName())
1431 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1432 if overscanResults
is not None:
1434 overscanImage=overscanResults.overscanImage,
1440 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1442 afwMath.MEDIAN | afwMath.STDEVCLIP)
1443 self.metadata[f
"ISR VARIANCE {amp.getName()} MEDIAN"] = \
1444 qaStats.getValue(afwMath.MEDIAN)
1445 self.metadata[f
"ISR VARIANCE {amp.getName()} STDEV"] = \
1446 qaStats.getValue(afwMath.STDEVCLIP)
1447 self.log.debug(
" Variance stats for amplifer %s: %f +/- %f.",
1448 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1449 qaStats.getValue(afwMath.STDEVCLIP))
1450 if self.config.maskNegativeVariance:
1454 self.log.info(
"Applying linearizer.")
1455 linearizer.applyLinearity(image=ccdExposure.getMaskedImage().getImage(),
1456 detector=ccd, log=self.log)
1458 if self.config.doCrosstalk
and not self.config.doCrosstalkBeforeAssemble:
1459 self.log.info(
"Applying crosstalk correction.")
1460 self.crosstalk.run(ccdExposure, crosstalk=crosstalk,
1461 crosstalkSources=crosstalkSources, isTrimmed=
True)
1462 self.
debugView(ccdExposure,
"doCrosstalk")
1467 if self.config.doDefect:
1468 self.log.info(
"Masking defects.")
1471 if self.config.numEdgeSuspect > 0:
1472 self.log.info(
"Masking edges as SUSPECT.")
1473 self.
maskEdges(ccdExposure, numEdgePixels=self.config.numEdgeSuspect,
1474 maskPlane=
"SUSPECT", level=self.config.edgeMaskLevel)
1476 if self.config.doNanMasking:
1477 self.log.info(
"Masking non-finite (NAN, inf) value pixels.")
1480 if self.config.doWidenSaturationTrails:
1481 self.log.info(
"Widening saturation trails.")
1482 isrFunctions.widenSaturationTrails(ccdExposure.getMaskedImage().getMask())
1484 if self.config.doCameraSpecificMasking:
1485 self.log.info(
"Masking regions for camera specific reasons.")
1486 self.masking.run(ccdExposure)
1488 if self.config.doBrighterFatter:
1498 interpExp = ccdExposure.clone()
1500 isrFunctions.interpolateFromMask(
1501 maskedImage=interpExp.getMaskedImage(),
1502 fwhm=self.config.fwhm,
1503 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1504 maskNameList=
list(self.config.brighterFatterMaskListToInterpolate)
1506 bfExp = interpExp.clone()
1508 self.log.info(
"Applying brighter-fatter correction using kernel type %s / gains %s.",
1510 if self.config.doFluxConservingBrighterFatterCorrection:
1511 bfResults = isrFunctions.fluxConservingBrighterFatterCorrection(
1514 self.config.brighterFatterMaxIter,
1515 self.config.brighterFatterThreshold,
1516 self.config.brighterFatterApplyGain,
1520 bfResults = isrFunctions.brighterFatterCorrection(
1523 self.config.brighterFatterMaxIter,
1524 self.config.brighterFatterThreshold,
1525 self.config.brighterFatterApplyGain,
1528 if bfResults[1] == self.config.brighterFatterMaxIter:
1529 self.log.warning(
"Brighter-fatter correction did not converge, final difference %f.",
1532 self.log.info(
"Finished brighter-fatter correction in %d iterations.",
1534 image = ccdExposure.getMaskedImage().getImage()
1535 bfCorr = bfExp.getMaskedImage().getImage()
1536 bfCorr -= interpExp.getMaskedImage().getImage()
1545 self.log.info(
"Ensuring image edges are masked as EDGE to the brighter-fatter kernel size.")
1546 self.
maskEdges(ccdExposure, numEdgePixels=numpy.max(bfKernel.shape) // 2,
1549 if self.config.brighterFatterMaskGrowSize > 0:
1550 self.log.info(
"Growing masks to account for brighter-fatter kernel convolution.")
1551 for maskPlane
in self.config.brighterFatterMaskListToInterpolate:
1552 isrFunctions.growMasks(ccdExposure.getMask(),
1553 radius=self.config.brighterFatterMaskGrowSize,
1554 maskNameList=maskPlane,
1555 maskValue=maskPlane)
1557 self.
debugView(ccdExposure,
"doBrighterFatter")
1559 if self.config.doDark:
1560 self.log.info(
"Applying dark correction.")
1564 if self.config.doFringe
and not self.config.fringeAfterFlat:
1565 self.log.info(
"Applying fringe correction before flat.")
1566 self.fringe.run(ccdExposure, **fringes.getDict())
1569 if self.config.doStrayLight
and self.strayLight.check(ccdExposure):
1570 self.log.info(
"Checking strayLight correction.")
1571 self.strayLight.run(ccdExposure, strayLightData)
1572 self.
debugView(ccdExposure,
"doStrayLight")
1574 if self.config.doFlat:
1575 self.log.info(
"Applying flat correction.")
1579 if self.config.doApplyGains:
1580 self.log.info(
"Applying gain correction instead of flat.")
1581 if self.config.usePtcGains:
1582 self.log.info(
"Using gains from the Photon Transfer Curve.")
1583 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains,
1586 isrFunctions.applyGains(ccdExposure, self.config.normalizeGains)
1588 if self.config.doFringe
and self.config.fringeAfterFlat:
1589 self.log.info(
"Applying fringe correction after flat.")
1590 self.fringe.run(ccdExposure, **fringes.getDict())
1592 if self.config.doVignette:
1593 if self.config.doMaskVignettePolygon:
1594 self.log.info(
"Constructing, attaching, and masking vignette polygon.")
1596 self.log.info(
"Constructing and attaching vignette polygon.")
1598 exposure=ccdExposure, doUpdateMask=self.config.doMaskVignettePolygon,
1599 vignetteValue=self.config.vignetteValue, log=self.log)
1601 if self.config.doAttachTransmissionCurve:
1602 self.log.info(
"Adding transmission curves.")
1603 isrFunctions.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
1604 filterTransmission=filterTransmission,
1605 sensorTransmission=sensorTransmission,
1606 atmosphereTransmission=atmosphereTransmission)
1608 flattenedThumb =
None
1609 if self.config.qa.doThumbnailFlattened:
1610 flattenedThumb = isrQa.makeThumbnail(ccdExposure, isrQaConfig=self.config.qa)
1612 if self.config.doIlluminationCorrection
and physicalFilter
in self.config.illumFilters:
1613 self.log.info(
"Performing illumination correction.")
1614 isrFunctions.illuminationCorrection(ccdExposure.getMaskedImage(),
1615 illumMaskedImage, illumScale=self.config.illumScale,
1616 trimToFit=self.config.doTrimToMatchCalib)
1619 if self.config.doSaveInterpPixels:
1620 preInterpExp = ccdExposure.clone()
1635 if self.config.doSetBadRegions:
1636 badPixelCount, badPixelValue = isrFunctions.setBadRegions(ccdExposure)
1637 if badPixelCount > 0:
1638 self.log.info(
"Set %d BAD pixels to %f.", badPixelCount, badPixelValue)
1640 if self.config.doInterpolate:
1641 self.log.info(
"Interpolating masked pixels.")
1642 isrFunctions.interpolateFromMask(
1643 maskedImage=ccdExposure.getMaskedImage(),
1644 fwhm=self.config.fwhm,
1645 growSaturatedFootprints=self.config.growSaturationFootprintSize,
1646 maskNameList=
list(self.config.maskListToInterpolate)
1652 if self.config.doAmpOffset:
1653 self.log.info(
"Correcting amp offsets.")
1654 self.ampOffset.run(ccdExposure)
1656 if self.config.doMeasureBackground:
1657 self.log.info(
"Measuring background level.")
1660 if self.config.qa
is not None and self.config.qa.saveStats
is True:
1662 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1664 afwMath.MEDIAN | afwMath.STDEVCLIP)
1665 self.metadata[f
"ISR BACKGROUND {amp.getName()} MEDIAN"] = qaStats.getValue(afwMath.MEDIAN)
1666 self.metadata[f
"ISR BACKGROUND {amp.getName()} STDEV"] = \
1667 qaStats.getValue(afwMath.STDEVCLIP)
1668 self.log.debug(
" Background stats for amplifer %s: %f +/- %f",
1669 amp.getName(), qaStats.getValue(afwMath.MEDIAN),
1670 qaStats.getValue(afwMath.STDEVCLIP))
1673 if self.config.doStandardStatistics:
1674 metadata = ccdExposure.getMetadata()
1676 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
1677 ampName = amp.getName()
1678 metadata[f
"LSST ISR MASK SAT {ampName}"] = isrFunctions.countMaskedPixels(
1679 ampExposure.getMaskedImage(),
1680 [self.config.saturatedMaskName]
1682 metadata[f
"LSST ISR MASK BAD {ampName}"] = isrFunctions.countMaskedPixels(
1683 ampExposure.getMaskedImage(),
1687 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP)
1689 metadata[f
"LSST ISR FINAL MEAN {ampName}"] = qaStats.getValue(afwMath.MEAN)
1690 metadata[f
"LSST ISR FINAL MEDIAN {ampName}"] = qaStats.getValue(afwMath.MEDIAN)
1691 metadata[f
"LSST ISR FINAL STDEV {ampName}"] = qaStats.getValue(afwMath.STDEVCLIP)
1693 k1 = f
"LSST ISR FINAL MEDIAN {ampName}"
1694 k2 = f
"LSST ISR OVERSCAN SERIAL MEDIAN {ampName}"
1695 if self.config.doOverscan
and k1
in metadata
and k2
in metadata:
1696 metadata[f
"LSST ISR LEVEL {ampName}"] = metadata[k1] - metadata[k2]
1698 metadata[f
"LSST ISR LEVEL {ampName}"] = numpy.nan
1701 outputStatistics =
None
1702 if self.config.doCalculateStatistics:
1703 outputStatistics = self.isrStats.run(ccdExposure, overscanResults=overscans,
1706 self.
debugView(ccdExposure,
"postISRCCD")
1708 return pipeBase.Struct(
1709 exposure=ccdExposure,
1711 flattenedThumb=flattenedThumb,
1713 preInterpExposure=preInterpExp,
1714 outputExposure=ccdExposure,
1715 outputOssThumbnail=ossThumb,
1716 outputFlattenedThumbnail=flattenedThumb,
1717 outputStatistics=outputStatistics,
1721 """Ensure that the data returned by Butler is a fully constructed exp.
1723 ISR requires exposure-level image data for historical reasons, so
if we
1724 did
not recieve that
from Butler, construct it
from what we have,
1725 modifying the input
in place.
1730 The input data structure obtained
from Butler.
1732 `lsst.afw.image.DecoratedImageU`,
1733 or `lsst.afw.image.ImageF`
1734 camera : `lsst.afw.cameraGeom.camera`, optional
1735 The camera associated
with the image. Used to find the appropriate
1736 detector
if detector
is not already set.
1737 detectorNum : `int`, optional
1738 The detector
in the camera to attach,
if the detector
is not
1744 The re-constructed exposure,
with appropriate detector parameters.
1749 Raised
if the input data cannot be used to construct an exposure.
1751 if isinstance(inputExp, afwImage.DecoratedImageU):
1753 elif isinstance(inputExp, afwImage.ImageF):
1755 elif isinstance(inputExp, afwImage.MaskedImageF):
1759 elif inputExp
is None:
1763 raise TypeError(
"Input Exposure is not known type in isrTask.ensureExposure: %s." %
1766 if inputExp.getDetector()
is None:
1767 if camera
is None or detectorNum
is None:
1768 raise RuntimeError(
'Must supply both a camera and detector number when using exposures '
1769 'without a detector set.')
1770 inputExp.setDetector(camera[detectorNum])
1776 """Extract common calibration metadata values that will be written to
1782 Calibration to pull date information
from.
1787 Calibration creation date string to add to header.
1789 if hasattr(calib,
"getMetadata"):
1790 if 'CALIB_CREATION_DATE' in calib.getMetadata():
1791 return " ".join((calib.getMetadata().get(
"CALIB_CREATION_DATE",
"Unknown"),
1792 calib.getMetadata().get(
"CALIB_CREATION_TIME",
"Unknown")))
1794 return " ".join((calib.getMetadata().get(
"CALIB_CREATE_DATE",
"Unknown"),
1795 calib.getMetadata().get(
"CALIB_CREATE_TIME",
"Unknown")))
1797 return "Unknown Unknown"
1800 """Compare header keywords to confirm camera states match.
1805 Header for the exposure being processed.
1807 Calibration to be applied.
1809 Calib type
for log message.
1812 calibMetadata = calib.getMetadata()
1813 except AttributeError:
1815 for keyword
in self.config.cameraKeywordsToCompare:
1816 if keyword
in exposureMetadata
and keyword
in calibMetadata:
1817 if exposureMetadata[keyword] != calibMetadata[keyword]:
1818 if self.config.doRaiseOnCalibMismatch:
1819 raise RuntimeError(
"Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1821 exposureMetadata[keyword], calibMetadata[keyword])
1823 self.log.warning(
"Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1825 exposureMetadata[keyword], calibMetadata[keyword])
1827 self.log.debug(
"Sequencer keyword %s not found.", keyword)
1830 """Convert exposure image from uint16 to float.
1832 If the exposure does not need to be converted, the input
is
1833 immediately returned. For exposures that are converted to use
1834 floating point pixels, the variance
is set to unity
and the
1840 The raw exposure to be converted.
1845 The input ``exposure``, converted to floating point pixels.
1850 Raised
if the exposure type cannot be converted to float.
1853 if isinstance(exposure, afwImage.ExposureF):
1855 self.log.debug(
"Exposure already of type float.")
1857 if not hasattr(exposure,
"convertF"):
1858 raise RuntimeError(
"Unable to convert exposure (%s) to float." %
type(exposure))
1860 newexposure = exposure.convertF()
1861 newexposure.variance[:] = 1
1862 newexposure.mask[:] = 0x0
1867 """Identify bad amplifiers, saturated and suspect pixels.
1872 Input exposure to be masked.
1874 Catalog of parameters defining the amplifier on this
1877 List of defects. Used to determine if the entire
1883 If this
is true, the entire amplifier area
is covered by
1884 defects
and unusable.
1887 maskedImage = ccdExposure.getMaskedImage()
1894 if defects
is not None:
1895 badAmp = bool(sum([v.getBBox().contains(amp.getBBox())
for v
in defects]))
1901 dataView = afwImage.MaskedImageF(maskedImage, amp.getRawBBox(),
1903 maskView = dataView.getMask()
1904 maskView |= maskView.getPlaneBitMask(
"BAD")
1912 if self.config.doSaturation
and not badAmp:
1913 limits.update({self.config.saturatedMaskName: amp.getSaturation()})
1914 if self.config.doSuspect
and not badAmp:
1915 limits.update({self.config.suspectMaskName: amp.getSuspectLevel()})
1916 if math.isfinite(self.config.saturation):
1917 limits.update({self.config.saturatedMaskName: self.config.saturation})
1919 for maskName, maskThreshold
in limits.items():
1920 if not math.isnan(maskThreshold):
1921 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
1922 isrFunctions.makeThresholdMask(
1923 maskedImage=dataView,
1924 threshold=maskThreshold,
1931 maskView =
afwImage.Mask(maskedImage.getMask(), amp.getRawDataBBox(),
1933 maskVal = maskView.getPlaneBitMask([self.config.saturatedMaskName,
1934 self.config.suspectMaskName])
1935 if numpy.all(maskView.getArray() & maskVal > 0):
1937 maskView |= maskView.getPlaneBitMask(
"BAD")
1942 """Apply overscan correction in place.
1944 This method does initial pixel rejection of the overscan
1945 region. The overscan can also be optionally segmented to
1946 allow for discontinuous overscan responses to be fit
1947 separately. The actual overscan subtraction
is performed by
1948 the `lsst.ip.isr.overscan.OverscanTask`, which
is called here
1949 after the amplifier
is preprocessed.
1954 Exposure to have overscan correction performed.
1955 amp : `lsst.afw.cameraGeom.Amplifer`
1956 The amplifier to consider
while correcting the overscan.
1960 overscanResults : `lsst.pipe.base.Struct`
1961 Result struct
with components:
1964 Value
or fit subtracted
from the amplifier image data.
1967 Value
or fit subtracted
from the overscan image data.
1970 Image of the overscan region
with the overscan
1971 correction applied. This quantity
is used to estimate
1972 the amplifier read noise empirically.
1977 Median overscan fit value. (`float`)
1979 Clipped standard deviation of the overscan after
1980 correction. (`float`)
1985 Raised
if the ``amp`` does
not contain raw pixel information.
1989 lsst.ip.isr.overscan.OverscanTask
1991 if amp.getRawHorizontalOverscanBBox().isEmpty():
1992 self.log.info(
"ISR_OSCAN: No overscan region. Not performing overscan correction.")
1996 overscanResults = self.overscan.run(ccdExposure, amp)
1998 metadata = ccdExposure.getMetadata()
1999 ampName = amp.getName()
2001 keyBase =
"LSST ISR OVERSCAN"
2003 if isinstance(overscanResults.overscanMean, float):
2005 metadata[f
"{keyBase} SERIAL MEAN {ampName}"] = overscanResults.overscanMean
2006 metadata[f
"{keyBase} SERIAL MEDIAN {ampName}"] = overscanResults.overscanMedian
2007 metadata[f
"{keyBase} SERIAL STDEV {ampName}"] = overscanResults.overscanSigma
2009 metadata[f
"{keyBase} RESIDUAL SERIAL MEAN {ampName}"] = overscanResults.residualMean
2010 metadata[f
"{keyBase} RESIDUAL SERIAL MEDIAN {ampName}"] = overscanResults.residualMedian
2011 metadata[f
"{keyBase} RESIDUAL SERIAL STDEV {ampName}"] = overscanResults.residualSigma
2012 elif isinstance(overscanResults.overscanMean, tuple):
2014 metadata[f
"{keyBase} SERIAL MEAN {ampName}"] = overscanResults.overscanMean[0]
2015 metadata[f
"{keyBase} SERIAL MEDIAN {ampName}"] = overscanResults.overscanMedian[0]
2016 metadata[f
"{keyBase} SERIAL STDEV {ampName}"] = overscanResults.overscanSigma[0]
2018 metadata[f
"{keyBase} PARALLEL MEAN {ampName}"] = overscanResults.overscanMean[1]
2019 metadata[f
"{keyBase} PARALLEL MEDIAN {ampName}"] = overscanResults.overscanMedian[1]
2020 metadata[f
"{keyBase} PARALLEL STDEV {ampName}"] = overscanResults.overscanSigma[1]
2022 metadata[f
"{keyBase} RESIDUAL SERIAL MEAN {ampName}"] = overscanResults.residualMean[0]
2023 metadata[f
"{keyBase} RESIDUAL SERIAL MEDIAN {ampName}"] = overscanResults.residualMedian[0]
2024 metadata[f
"{keyBase} RESIDUAL SERIAL STDEV {ampName}"] = overscanResults.residualSigma[0]
2026 metadata[f
"{keyBase} RESIDUAL PARALLEL MEAN {ampName}"] = overscanResults.residualMean[1]
2027 metadata[f
"{keyBase} RESIDUAL PARALLEL MEDIAN {ampName}"] = overscanResults.residualMedian[1]
2028 metadata[f
"{keyBase} RESIDUAL PARALLEL STDEV {ampName}"] = overscanResults.residualSigma[1]
2030 self.log.warning(
"Unexpected type for overscan values; none added to header.")
2032 return overscanResults
2035 """Set the variance plane using the gain and read noise
2037 The read noise is calculated
from the ``overscanImage``
if the
2038 ``doEmpiricalReadNoise`` option
is set
in the configuration; otherwise
2039 the value
from the amplifier data
is used.
2044 Exposure to process.
2046 Amplifier detector data.
2048 Image of overscan, required only
for empirical read noise.
2050 PTC dataset containing the gains
and read noise.
2055 Raised
if either ``usePtcGains`` of ``usePtcReadNoise``
2056 are ``
True``, but ptcDataset
is not provided.
2058 Raised
if ```doEmpiricalReadNoise``
is ``
True`` but
2059 ``overscanImage``
is ``
None``.
2063 lsst.ip.isr.isrFunctions.updateVariance
2065 maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
2066 if self.config.usePtcGains:
2067 if ptcDataset
is None:
2068 raise RuntimeError(
"No ptcDataset provided to use PTC gains.")
2070 gain = ptcDataset.gain[amp.getName()]
2071 self.log.info(
"Using gain from Photon Transfer Curve.")
2073 gain = amp.getGain()
2075 if math.isnan(gain):
2077 self.log.warning(
"Gain set to NAN! Updating to 1.0 to generate Poisson variance.")
2080 self.log.warning(
"Gain for amp %s == %g <= 0; setting to %f.",
2081 amp.getName(), gain, patchedGain)
2084 if self.config.doEmpiricalReadNoise
and overscanImage
is None:
2085 badPixels = isrFunctions.countMaskedPixels(ampExposure.getMaskedImage(),
2086 [self.config.saturatedMaskName,
2087 self.config.suspectMaskName,
2089 allPixels = ampExposure.getWidth() * ampExposure.getHeight()
2090 if allPixels == badPixels:
2092 self.log.info(
"Skipping empirical read noise for amp %s. No good pixels.",
2095 raise RuntimeError(
"Overscan is none for EmpiricalReadNoise.")
2097 if self.config.doEmpiricalReadNoise
and overscanImage
is not None:
2099 stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
2101 afwMath.STDEVCLIP, stats).getValue()
2102 self.log.info(
"Calculated empirical read noise for amp %s: %f.",
2103 amp.getName(), readNoise)
2104 elif self.config.usePtcReadNoise:
2105 if ptcDataset
is None:
2106 raise RuntimeError(
"No ptcDataset provided to use PTC readnoise.")
2108 readNoise = ptcDataset.noise[amp.getName()]
2109 self.log.info(
"Using read noise from Photon Transfer Curve.")
2111 readNoise = amp.getReadNoise()
2113 metadata = ampExposure.getMetadata()
2114 metadata[f
'LSST GAIN {amp.getName()}'] = gain
2115 metadata[f
'LSST READNOISE {amp.getName()}'] = readNoise
2117 isrFunctions.updateVariance(
2118 maskedImage=ampExposure.getMaskedImage(),
2120 readNoise=readNoise,
2124 """Identify and mask pixels with negative variance values.
2129 Exposure to process.
2133 lsst.ip.isr.isrFunctions.updateVariance
2135 maskPlane = exposure.getMask().getPlaneBitMask(self.config.negativeVarianceMaskName)
2136 bad = numpy.where(exposure.getVariance().getArray() <= 0.0)
2137 exposure.mask.array[bad] |= maskPlane
2140 """Apply dark correction in place.
2145 Exposure to process.
2147 Dark exposure of the same size as ``exposure``.
2148 invert : `Bool`, optional
2149 If
True, re-add the dark to an already corrected image.
2154 Raised
if either ``exposure``
or ``darkExposure`` do
not
2155 have their dark time defined.
2159 lsst.ip.isr.isrFunctions.darkCorrection
2161 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
2162 if math.isnan(expScale):
2163 raise RuntimeError(
"Exposure darktime is NAN.")
2164 if darkExposure.getInfo().getVisitInfo()
is not None \
2165 and not math.isnan(darkExposure.getInfo().getVisitInfo().getDarkTime()):
2166 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
2170 self.log.warning(
"darkExposure.getInfo().getVisitInfo() does not exist. Using darkScale = 1.0.")
2173 isrFunctions.darkCorrection(
2174 maskedImage=exposure.getMaskedImage(),
2175 darkMaskedImage=darkExposure.getMaskedImage(),
2177 darkScale=darkScale,
2179 trimToFit=self.config.doTrimToMatchCalib
2183 """Check if linearization is needed for the detector cameraGeom.
2185 Checks config.doLinearize and the linearity type of the first
2191 Detector to get linearity type
from.
2195 doLinearize : `Bool`
2196 If
True, linearization should be performed.
2198 return self.config.doLinearize
and \
2199 detector.getAmplifiers()[0].getLinearityType() != NullLinearityType
2202 """Apply flat correction in place.
2207 Exposure to process.
2209 Flat exposure of the same size as ``exposure``.
2210 invert : `Bool`, optional
2211 If
True, unflatten an already flattened image.
2215 lsst.ip.isr.isrFunctions.flatCorrection
2217 isrFunctions.flatCorrection(
2218 maskedImage=exposure.getMaskedImage(),
2219 flatMaskedImage=flatExposure.getMaskedImage(),
2220 scalingType=self.config.flatScalingType,
2221 userScale=self.config.flatUserScale,
2223 trimToFit=self.config.doTrimToMatchCalib
2227 """Detect and mask saturated pixels in config.saturatedMaskName.
2232 Exposure to process. Only the amplifier DataSec is processed.
2234 Amplifier detector data.
2238 lsst.ip.isr.isrFunctions.makeThresholdMask
2240 if not math.isnan(amp.getSaturation()):
2241 maskedImage = exposure.getMaskedImage()
2242 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2243 isrFunctions.makeThresholdMask(
2244 maskedImage=dataView,
2245 threshold=amp.getSaturation(),
2247 maskName=self.config.saturatedMaskName,
2251 """Interpolate over saturated pixels, in place.
2253 This method should be called after `saturationDetection`, to
2254 ensure that the saturated pixels have been identified in the
2255 SAT mask. It should also be called after `assembleCcd`, since
2256 saturated regions may cross amplifier boundaries.
2261 Exposure to process.
2265 lsst.ip.isr.isrTask.saturationDetection
2266 lsst.ip.isr.isrFunctions.interpolateFromMask
2268 isrFunctions.interpolateFromMask(
2269 maskedImage=exposure.getMaskedImage(),
2270 fwhm=self.config.fwhm,
2271 growSaturatedFootprints=self.config.growSaturationFootprintSize,
2272 maskNameList=list(self.config.saturatedMaskName),
2276 """Detect and mask suspect pixels in config.suspectMaskName.
2281 Exposure to process. Only the amplifier DataSec is processed.
2283 Amplifier detector data.
2287 lsst.ip.isr.isrFunctions.makeThresholdMask
2291 Suspect pixels are pixels whose value
is greater than
2292 amp.getSuspectLevel(). This
is intended to indicate pixels that may be
2293 affected by unknown systematics;
for example
if non-linearity
2294 corrections above a certain level are unstable then that would be a
2295 useful value
for suspectLevel. A value of `nan` indicates that no such
2296 level exists
and no pixels are to be masked
as suspicious.
2298 suspectLevel = amp.getSuspectLevel()
2299 if math.isnan(suspectLevel):
2302 maskedImage = exposure.getMaskedImage()
2303 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
2304 isrFunctions.makeThresholdMask(
2305 maskedImage=dataView,
2306 threshold=suspectLevel,
2308 maskName=self.config.suspectMaskName,
2312 """Mask defects using mask plane "BAD", in place.
2317 Exposure to process.
2318 defectBaseList : defect-type
2324 Call this after CCD assembly, since defects may cross amplifier
2327 maskedImage = exposure.getMaskedImage()
2328 if not isinstance(defectBaseList, Defects):
2330 defectList =
Defects(defectBaseList)
2332 defectList = defectBaseList
2333 defectList.maskPixels(maskedImage, maskName=
"BAD")
2335 def maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR'):
2336 """Mask edge pixels with applicable mask plane.
2341 Exposure to process.
2342 numEdgePixels : `int`, optional
2343 Number of edge pixels to mask.
2344 maskPlane : `str`, optional
2345 Mask plane name to use.
2346 level : `str`, optional
2347 Level at which to mask edges.
2349 maskedImage = exposure.getMaskedImage()
2350 maskBitMask = maskedImage.getMask().getPlaneBitMask(maskPlane)
2352 if numEdgePixels > 0:
2353 if level ==
'DETECTOR':
2354 boxes = [maskedImage.getBBox()]
2355 elif level ==
'AMP':
2356 boxes = [amp.getBBox()
for amp
in exposure.getDetector()]
2361 subImage = maskedImage[box]
2362 box.grow(-numEdgePixels)
2364 SourceDetectionTask.setEdgeBits(
2370 """Mask and interpolate defects using mask plane "BAD", in place.
2375 Exposure to process.
2376 defectBaseList : defects-like
2377 List of defects to mask and interpolate. Can be
2382 lsst.ip.isr.isrTask.maskDefect
2385 self.maskEdges(exposure, numEdgePixels=self.config.numEdgeSuspect,
2386 maskPlane="SUSPECT", level=self.config.edgeMaskLevel)
2387 isrFunctions.interpolateFromMask(
2388 maskedImage=exposure.getMaskedImage(),
2389 fwhm=self.config.fwhm,
2390 growSaturatedFootprints=0,
2391 maskNameList=[
"BAD"],
2395 """Mask NaNs using mask plane "UNMASKEDNAN", in place.
2400 Exposure to process.
2404 We mask over all non-finite values (NaN, inf), including those
2405 that are masked with other bits (because those may
or may
not be
2406 interpolated over later,
and we want to remove all NaN/infs).
2407 Despite this behaviour, the
"UNMASKEDNAN" mask plane
is used to
2408 preserve the historical name.
2410 maskedImage = exposure.getMaskedImage()
2413 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
2414 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
2415 numNans = maskNans(maskedImage, maskVal)
2416 self.metadata[
"NUMNANS"] = numNans
2418 self.log.warning(
"There were %d unmasked NaNs.", numNans)
2421 """"Mask and interpolate NaN/infs using mask plane "UNMASKEDNAN",
2427 Exposure to process.
2431 lsst.ip.isr.isrTask.maskNan
2434 isrFunctions.interpolateFromMask(
2435 maskedImage=exposure.getMaskedImage(),
2436 fwhm=self.config.fwhm,
2437 growSaturatedFootprints=0,
2438 maskNameList=["UNMASKEDNAN"],
2442 """Measure the image background in subgrids, for quality control.
2447 Exposure to process.
2449 Configuration object containing parameters on which background
2450 statistics and subgrids to use.
2452 if IsrQaConfig
is not None:
2454 IsrQaConfig.flatness.nIter)
2455 maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask([
"BAD",
"SAT",
"DETECTED"])
2456 statsControl.setAndMask(maskVal)
2457 maskedImage = exposure.getMaskedImage()
2459 skyLevel = stats.getValue(afwMath.MEDIAN)
2460 skySigma = stats.getValue(afwMath.STDEVCLIP)
2461 self.log.info(
"Flattened sky level: %f +/- %f.", skyLevel, skySigma)
2462 metadata = exposure.getMetadata()
2463 metadata[
"SKYLEVEL"] = skyLevel
2464 metadata[
"SKYSIGMA"] = skySigma
2467 stat = afwMath.MEANCLIP
if IsrQaConfig.flatness.doClip
else afwMath.MEAN
2468 meshXHalf = int(IsrQaConfig.flatness.meshX/2.)
2469 meshYHalf = int(IsrQaConfig.flatness.meshY/2.)
2470 nX = int((exposure.getWidth() + meshXHalf) / IsrQaConfig.flatness.meshX)
2471 nY = int((exposure.getHeight() + meshYHalf) / IsrQaConfig.flatness.meshY)
2472 skyLevels = numpy.zeros((nX, nY))
2475 yc = meshYHalf + j * IsrQaConfig.flatness.meshY
2477 xc = meshXHalf + i * IsrQaConfig.flatness.meshX
2479 xLLC = xc - meshXHalf
2480 yLLC = yc - meshYHalf
2481 xURC = xc + meshXHalf - 1
2482 yURC = yc + meshYHalf - 1
2485 miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL)
2489 good = numpy.where(numpy.isfinite(skyLevels))
2490 skyMedian = numpy.median(skyLevels[good])
2491 flatness = (skyLevels[good] - skyMedian) / skyMedian
2492 flatness_rms = numpy.std(flatness)
2493 flatness_pp = flatness.max() - flatness.min()
if len(flatness) > 0
else numpy.nan
2495 self.log.info(
"Measuring sky levels in %dx%d grids: %f.", nX, nY, skyMedian)
2496 self.log.info(
"Sky flatness in %dx%d grids - pp: %f rms: %f.",
2497 nX, nY, flatness_pp, flatness_rms)
2499 metadata[
"FLATNESS_PP"] = float(flatness_pp)
2500 metadata[
"FLATNESS_RMS"] = float(flatness_rms)
2501 metadata[
"FLATNESS_NGRIDS"] =
'%dx%d' % (nX, nY)
2502 metadata[
"FLATNESS_MESHX"] = IsrQaConfig.flatness.meshX
2503 metadata[
"FLATNESS_MESHY"] = IsrQaConfig.flatness.meshY
2506 """Set an approximate magnitude zero point for the exposure.
2511 Exposure to process.
2513 filterLabel = exposure.getFilter()
2514 physicalFilter = isrFunctions.getPhysicalFilter(filterLabel, self.log)
2516 if physicalFilter
in self.config.fluxMag0T1:
2517 fluxMag0 = self.config.fluxMag0T1[physicalFilter]
2519 self.log.warning(
"No rough magnitude zero point defined for filter %s.", physicalFilter)
2520 fluxMag0 = self.config.defaultFluxMag0T1
2522 expTime = exposure.getInfo().getVisitInfo().getExposureTime()
2524 self.log.warning(
"Non-positive exposure time; skipping rough zero point.")
2527 self.log.info(
"Setting rough magnitude zero point for filter %s: %f",
2528 physicalFilter, 2.5*math.log10(fluxMag0*expTime))
2533 """Context manager that applies and removes flats and darks,
2534 if the task
is configured to apply them.
2539 Exposure to process.
2541 Flat exposure the same size
as ``exp``.
2543 Dark exposure the same size
as ``exp``.
2548 The flat
and dark corrected exposure.
2550 if self.config.doDark
and dark
is not None:
2552 if self.config.doFlat:
2557 if self.config.doFlat:
2559 if self.config.doDark
and dark
is not None:
2563 """Utility function to examine ISR exposure at different stages.
2570 State of processing to view.
2572 frame = getDebugFrame(self._display, stepname)
2574 display = getDisplay(frame)
2575 display.scale(
'asinh',
'zscale')
2576 display.mtv(exposure)
2577 prompt =
"Press Enter to continue [c]... "
2579 ans = input(prompt).lower()
2580 if ans
in (
"",
"c",):
2585 """A Detector-like object that supports returning gain and saturation level
2587 This is used when the input exposure does
not have a detector.
2592 Exposure to generate a fake amplifier
for.
2593 config : `lsst.ip.isr.isrTaskConfig`
2594 Configuration to apply to the fake amplifier.
2598 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
table::Key< std::string > object
Geometry and electronic information about raw amplifier images.
An immutable representation of a camera.
A representation of a detector in a mosaic camera.
Encapsulate information about a bad portion of a detector.
A class to contain the data, WCS, and other information needed to describe an image of the sky.
A class to represent a 2-dimensional array of pixels.
Represent a 2-dimensional array of bitmask pixels.
A class to manipulate images, masks, and variance as a single object.
A spatially-varying transmission curve as a function of wavelength.
Pass parameters to a Statistics object.
Class for storing generic metadata.
An integer coordinate rectangle.
getRawHorizontalOverscanBBox(self)
_RawHorizontalOverscanBBox
__init__(self, exposure, config)
doSaturationInterpolation
__init__(self, *config=None)
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)
maskEdges(self, exposure, numEdgePixels=0, maskPlane="SUSPECT", level='DETECTOR')
maskAndInterpolateDefects(self, exposure, defectBaseList)
roughZeroPoint(self, exposure)
runQuantum(self, butlerQC, inputRefs, outputRefs)
ensureExposure(self, inputExp, camera=None, detectorNum=None)
maskDefect(self, exposure, defectBaseList)
convertIntToFloat(self, exposure)
updateVariance(self, ampExposure, amp, overscanImage=None, ptcDataset=None)
maskAndInterpolateNan(self, exposure)
flatCorrection(self, exposure, flatExposure, invert=False)
debugView(self, exposure, stepname)
measureBackground(self, exposure, IsrQaConfig=None)
compareCameraKeywords(self, exposureMetadata, calib, calibName)
maskAmplifier(self, ccdExposure, amp, defects)
saturationInterpolation(self, exposure)
saturationDetection(self, exposure, amp)
doLinearize(self, detector)
darkCorrection(self, exposure, darkExposure, invert=False)
suspectDetection(self, exposure, amp)
overscanCorrection(self, ccdExposure, amp)
flatContext(self, exp, flat, dark=None)
maskNegativeVariance(self, exposure)
daf::base::PropertyList * list
daf::base::PropertySet * set
std::shared_ptr< PhotoCalib > makePhotoCalibFromCalibZeroPoint(double instFluxMag0, double instFluxMag0Err)
Construct a PhotoCalib from the deprecated Calib-style instFluxMag0/instFluxMag0Err values.
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename std::shared_ptr< Image< ImagePixelT > > image, typename std::shared_ptr< Mask< MaskPixelT > > mask=Mask< MaskPixelT >(), typename std::shared_ptr< Image< VariancePixelT > > variance=Image< VariancePixelT >())
A function to return a MaskedImage of the correct type (cf.
std::shared_ptr< Exposure< ImagePixelT, MaskPixelT, VariancePixelT > > makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, std::shared_ptr< geom::SkyWcs const > wcs=std::shared_ptr< geom::SkyWcs const >())
A function to return an Exposure of the correct type (cf.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
crosstalkSourceLookup(datasetType, registry, quantumDataId, collections)