1 from __future__
import absolute_import, division, print_function
2 from builtins
import range
3 from builtins
import object
35 from lsstDebug
import getDebugFrame
37 from .
import isrFunctions
38 from .assembleCcdTask
import AssembleCcdTask
39 from .fringe
import FringeTask
42 from contextlib
import contextmanager
43 from .isr
import maskNans
44 from .crosstalk
import CrosstalkTask
48 doBias = pexConfig.Field(
50 doc=
"Apply bias frame correction?",
53 doDark = pexConfig.Field(
55 doc=
"Apply dark frame correction?",
58 doFlat = pexConfig.Field(
60 doc=
"Apply flat field correction?",
63 doFringe = pexConfig.Field(
65 doc=
"Apply fringe correction?",
68 doDefect = pexConfig.Field(
70 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
73 doWrite = pexConfig.Field(
75 doc=
"Persist postISRCCD?",
78 assembleCcd = pexConfig.ConfigurableField(
79 target=AssembleCcdTask,
80 doc=
"CCD assembly task",
82 gain = pexConfig.Field(
84 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
87 readNoise = pexConfig.Field(
89 doc=
"The read noise to use if no Detector is present in the Exposure",
92 saturation = pexConfig.Field(
94 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
97 fringeAfterFlat = pexConfig.Field(
99 doc=
"Do fringe subtraction after flat-fielding?",
102 fringe = pexConfig.ConfigurableField(
104 doc=
"Fringe subtraction task",
106 fwhm = pexConfig.Field(
108 doc=
"FWHM of PSF (arcsec)",
111 saturatedMaskName = pexConfig.Field(
113 doc=
"Name of mask plane to use in saturation detection and interpolation",
116 suspectMaskName = pexConfig.Field(
118 doc=
"Name of mask plane to use for suspect pixels",
121 flatScalingType = pexConfig.ChoiceField(
123 doc=
"The method for scaling the flat on the fly.",
126 "USER":
"Scale by flatUserScale",
127 "MEAN":
"Scale by the inverse of the mean",
128 "MEDIAN":
"Scale by the inverse of the median",
131 flatUserScale = pexConfig.Field(
133 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
136 overscanFitType = pexConfig.ChoiceField(
138 doc=
"The method for fitting the overscan bias level.",
141 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
142 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
143 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
144 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
145 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
146 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
147 "MEAN":
"Correct using the mean of the overscan region",
148 "MEDIAN":
"Correct using the median of the overscan region",
151 overscanOrder = pexConfig.Field(
153 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
154 "or number of spline knots if overscan fit type is a spline."),
157 overscanRej = pexConfig.Field(
159 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
162 growSaturationFootprintSize = pexConfig.Field(
164 doc=
"Number of pixels by which to grow the saturation footprints",
167 doSaturationInterpolation = pexConfig.Field(
169 doc=
"Perform interpolation over pixels masked as saturated?",
172 doNanInterpAfterFlat = pexConfig.Field(
174 doc=(
"If True, ensure we interpolate NaNs after flat-fielding, even if we " 175 "also have to interpolate them before flat-fielding."),
178 fluxMag0T1 = pexConfig.Field(
180 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure",
183 keysToRemoveFromAssembledCcd = pexConfig.ListField(
185 doc=
"fields to remove from the metadata of the assembled ccd.",
188 doAssembleIsrExposures = pexConfig.Field(
191 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?" 193 doAssembleCcd = pexConfig.Field(
196 doc=
"Assemble amp-level exposures into a ccd-level exposure?" 198 expectWcs = pexConfig.Field(
201 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)" 203 doLinearize = pexConfig.Field(
205 doc=
"Correct for nonlinearity of the detector's response?",
208 doCrosstalk = pexConfig.Field(
210 doc=
"Apply intra-CCD crosstalk correction?",
213 crosstalk = pexConfig.ConfigurableField(
214 target=CrosstalkTask,
215 doc=
"Intra-CCD crosstalk correction",
217 doBrighterFatter = pexConfig.Field(
220 doc=
"Apply the brighter fatter correction" 222 brighterFatterKernelFile = pexConfig.Field(
225 doc=
"Kernel file used for the brighter fatter correction" 227 brighterFatterMaxIter = pexConfig.Field(
230 doc=
"Maximum number of iterations for the brighter fatter correction" 232 brighterFatterThreshold = pexConfig.Field(
235 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the " 236 " absolute value of the difference between the current corrected image and the one" 237 " from the previous iteration summed over all the pixels." 239 brighterFatterApplyGain = pexConfig.Field(
242 doc=
"Should the gain be applied when applying the brighter fatter correction?" 244 datasetType = pexConfig.Field(
246 doc=
"Dataset type for input data; users will typically leave this alone, " 247 "but camera-specific ISR tasks will override it",
250 fallbackFilterName = pexConfig.Field(dtype=str,
251 doc=
"Fallback default filter name for calibrations", optional=
True)
252 doAttachTransmissionCurve = pexConfig.Field(
255 doc=
"Construct and attach a wavelength-dependent throughput curve for this CCD image?" 257 doUseOpticsTransmission = pexConfig.Field(
260 doc=
"Load and use transmission_optics (if doAttachTransmissionCurve is True)?" 262 doUseFilterTransmission = pexConfig.Field(
265 doc=
"Load and use transmission_filter (if doAttachTransmissionCurve is True)?" 267 doUseSensorTransmission = pexConfig.Field(
270 doc=
"Load and use transmission_sensor (if doAttachTransmissionCurve is True)?" 272 doUseAtmosphereTransmission = pexConfig.Field(
275 doc=
"Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?" 291 \brief Apply common instrument signature correction algorithms to a raw frame. 293 \section ip_isr_isr_Contents Contents 295 - \ref ip_isr_isr_Purpose 296 - \ref ip_isr_isr_Initialize 298 - \ref ip_isr_isr_Config 299 - \ref ip_isr_isr_Debug 302 \section ip_isr_isr_Purpose Description 304 The process for correcting imaging data is very similar from camera to camera. 305 This task provides a vanilla implementation of doing these corrections, including 306 the ability to turn certain corrections off if they are not needed. 307 The inputs to the primary method, run, are a raw exposure to be corrected and the 308 calibration data products. The raw input is a single chip sized mosaic of all amps 309 including overscans and other non-science pixels. 310 The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask 311 and takes as input only a daf.persistence.butlerSubset.ButlerDataRef. 312 This task may not meet all needs and it is expected that it will be subclassed for 313 specific applications. 315 \section ip_isr_isr_Initialize Task initialization 317 \copydoc \_\_init\_\_ 319 \section ip_isr_isr_IO Inputs/Outputs to the run method 323 \section ip_isr_isr_Config Configuration parameters 325 See \ref IsrTaskConfig 327 \section ip_isr_isr_Debug Debug variables 329 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a 330 flag \c --debug, \c -d to import \b debug.py from your \c PYTHONPATH; see <a 331 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html"> 332 Using lsstDebug to control debugging output</a> for more about \b debug.py files. 334 The available variables in IsrTask are: 337 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are: 340 <DD> display exposure after ISR has been applied 344 For example, put something like 348 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 349 if name == "lsst.ip.isrFunctions.isrTask": 350 di.display = {'postISRCCD':2} 352 lsstDebug.Info = DebugInfo 354 into your debug.py file and run the commandline task with the \c --debug flag. 358 ConfigClass = IsrTaskConfig
362 '''!Constructor for IsrTask 363 @param[in] *args a list of positional arguments passed on to the Task constructor 364 @param[in] **kwargs a dictionary of keyword arguments passed on to the Task constructor 365 Call the lsst.pipe.base.task.Task.__init__ method 366 Then setup the assembly and fringe correction subtasks 368 pipeBase.Task.__init__(self, *args, **kwargs)
369 self.makeSubtask(
"assembleCcd")
370 self.makeSubtask(
"fringe")
371 self.makeSubtask(
"crosstalk")
374 """!Retrieve necessary frames for instrument signature removal 375 @param[in] dataRef a daf.persistence.butlerSubset.ButlerDataRef 376 of the detector data to be processed 377 @param[in] rawExposure a reference raw exposure that will later be 378 corrected with the retrieved calibration data; 379 should not be modified in this method. 380 \return a pipeBase.Struct with fields containing kwargs expected by run() 381 - bias: exposure of bias frame 382 - dark: exposure of dark frame 383 - flat: exposure of flat field 384 - defects: list of detects 385 - fringeStruct: a pipeBase.Struct with field fringes containing 386 exposure of fringe frame or list of fringe exposure 388 ccd = rawExposure.getDetector()
390 biasExposure = self.
getIsrExposure(dataRef,
"bias")
if self.config.doBias
else None 392 linearizer = dataRef.get(
"linearizer", immediate=
True)
if self.
doLinearize(ccd)
else None 393 darkExposure = self.
getIsrExposure(dataRef,
"dark")
if self.config.doDark
else None 394 flatExposure = self.
getIsrExposure(dataRef,
"flat")
if self.config.doFlat
else None 395 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
if self.config.doBrighterFatter
else None 396 defectList = dataRef.get(
"defects")
if self.config.doDefect
else None 398 if self.config.doFringe
and self.fringe.checkFilter(rawExposure):
399 fringeStruct = self.fringe.readFringes(dataRef, assembler=self.assembleCcd
400 if self.config.doAssembleIsrExposures
else None)
402 fringeStruct = pipeBase.Struct(fringes=
None)
404 if self.config.doAttachTransmissionCurve:
405 opticsTransmission = (dataRef.get(
"transmission_optics")
406 if self.config.doUseOpticsTransmission
else None)
407 filterTransmission = (dataRef.get(
"transmission_filter")
408 if self.config.doUseFilterTransmission
else None)
409 sensorTransmission = (dataRef.get(
"transmission_sensor")
410 if self.config.doUseSensorTransmission
else None)
411 atmosphereTransmission = (dataRef.get(
"transmission_atmosphere")
412 if self.config.doUseAtmosphereTransmission
else None)
414 opticsTransmission =
None 415 filterTransmission =
None 416 sensorTransmission =
None 417 atmosphereTransmission =
None 420 return pipeBase.Struct(bias=biasExposure,
421 linearizer=linearizer,
425 fringes=fringeStruct,
426 bfKernel=brighterFatterKernel,
427 opticsTransmission=opticsTransmission,
428 filterTransmission=filterTransmission,
429 sensorTransmission=sensorTransmission,
430 atmosphereTransmission=atmosphereTransmission,
434 def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
435 fringes=None, bfKernel=None,
436 opticsTransmission=None, filterTransmission=None,
437 sensorTransmission=None, atmosphereTransmission=None):
438 """!Perform instrument signature removal on an exposure 441 - Detect saturation, apply overscan correction, bias, dark and flat 442 - Perform CCD assembly 443 - Interpolate over defects, saturated pixels and all NaNs 445 @param[in] ccdExposure lsst.afw.image.exposure of detector data 446 @param[in] bias exposure of bias frame 447 @param[in] linearizer linearizing functor; a subclass of lsst.ip.isrFunctions.LinearizeBase 448 @param[in] dark exposure of dark frame 449 @param[in] flat exposure of flatfield 450 @param[in] defects list of detects 451 @param[in] fringes a pipeBase.Struct with field fringes containing 452 exposure of fringe frame or list of fringe exposure 453 @param[in] bfKernel kernel for brighter-fatter correction 454 @param[in] opticsTransmission a TransmissionCurve for the optics 455 @param[in] filterTransmission a TransmissionCurve for the filter 456 @param[in] sensorTransmission a TransmissionCurve for the sensor 457 @param[in] atmosphereTransmission a TransmissionCurve for the atmosphere 459 @return a pipeBase.Struct with field: 463 if isinstance(ccdExposure, ButlerDataRef):
466 ccd = ccdExposure.getDetector()
469 if self.config.doBias
and bias
is None:
470 raise RuntimeError(
"Must supply a bias exposure if config.doBias True")
472 raise RuntimeError(
"Must supply a linearizer if config.doBias True")
473 if self.config.doDark
and dark
is None:
474 raise RuntimeError(
"Must supply a dark exposure if config.doDark True")
475 if self.config.doFlat
and flat
is None:
476 raise RuntimeError(
"Must supply a flat exposure if config.doFlat True")
477 if self.config.doBrighterFatter
and bfKernel
is None:
478 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter True")
480 fringes = pipeBase.Struct(fringes=
None)
481 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
482 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct")
483 if self.config.doDefect
and defects
is None:
484 raise RuntimeError(
"Must supply defects if config.doDefect True")
489 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd" 490 ccd = [
FakeAmp(ccdExposure, self.config)]
494 if ccdExposure.getBBox().contains(amp.getBBox()):
499 if self.config.doAssembleCcd:
500 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
501 if self.config.expectWcs
and not ccdExposure.getWcs():
502 self.log.warn(
"No WCS found in input exposure")
504 if self.config.doBias:
508 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
510 if self.config.doCrosstalk:
511 self.crosstalk.
run(ccdExposure)
515 if ccdExposure.getBBox().contains(amp.getBBox()):
516 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
519 interpolationDone =
False 521 if self.config.doBrighterFatter:
528 if self.config.doDefect:
530 if self.config.doSaturationInterpolation:
533 interpolationDone =
True 536 self.config.brighterFatterMaxIter,
537 self.config.brighterFatterThreshold,
538 self.config.brighterFatterApplyGain,
541 if self.config.doDark:
544 if self.config.doFringe
and not self.config.fringeAfterFlat:
545 self.fringe.
run(ccdExposure, **fringes.getDict())
547 if self.config.doFlat:
550 if not interpolationDone:
551 if self.config.doDefect:
553 if self.config.doSaturationInterpolation:
555 if not interpolationDone
or self.config.doNanInterpAfterFlat:
558 if self.config.doFringe
and self.config.fringeAfterFlat:
559 self.fringe.
run(ccdExposure, **fringes.getDict())
561 exposureTime = ccdExposure.getInfo().getVisitInfo().getExposureTime()
562 ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1*exposureTime)
564 if self.config.doAttachTransmissionCurve:
566 filterTransmission=filterTransmission,
567 sensorTransmission=sensorTransmission,
568 atmosphereTransmission=atmosphereTransmission)
570 frame = getDebugFrame(self._display,
"postISRCCD")
572 getDisplay(frame).mtv(ccdExposure)
574 return pipeBase.Struct(
575 exposure=ccdExposure,
580 """!Perform instrument signature removal on a ButlerDataRef of a Sensor 582 - Read in necessary detrending/isr/calibration data 583 - Process raw exposure in run() 584 - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True 586 @param[in] sensorRef daf.persistence.butlerSubset.ButlerDataRef of the 587 detector data to be processed 588 @return a pipeBase.Struct with fields: 589 - exposure: the exposure after application of ISR 591 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
592 ccdExposure = sensorRef.get(
'raw')
595 result = self.
run(ccdExposure, **isrData.getDict())
597 if self.config.doWrite:
598 sensorRef.put(result.exposure,
"postISRCCD")
603 """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0 605 if isinstance(exposure, afwImage.ExposureF):
608 if not hasattr(exposure,
"convertF"):
609 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
611 newexposure = exposure.convertF()
612 maskedImage = newexposure.getMaskedImage()
613 varArray = maskedImage.getVariance().getArray()
615 maskArray = maskedImage.getMask().getArray()
620 """!Apply bias correction in place 622 @param[in,out] exposure exposure to process 623 @param[in] biasExposure bias exposure of same size as exposure 625 isrFunctions.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
628 """!Apply dark correction in place 630 @param[in,out] exposure exposure to process 631 @param[in] darkExposure dark exposure of same size as exposure 632 @param[in] invert if True, remove the dark from an already-corrected image 634 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
635 if math.isnan(expScale):
636 raise RuntimeError(
"Exposure darktime is NAN")
637 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
638 if math.isnan(darkScale):
639 raise RuntimeError(
"Dark calib darktime is NAN")
640 isrFunctions.darkCorrection(
641 maskedImage=exposure.getMaskedImage(),
642 darkMaskedImage=darkExposure.getMaskedImage(),
649 """!Is linearization wanted for this detector? 651 Checks config.doLinearize and the linearity type of the first amplifier. 653 @param[in] detector detector information (an lsst.afw.cameraGeom.Detector) 655 return self.config.doLinearize
and \
656 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
659 """!Set the variance plane based on the image plane, plus amplifier gain and read noise 661 @param[in,out] ampExposure exposure to process 662 @param[in] amp amplifier detector information 665 if not math.isnan(gain):
668 self.log.warn(
"Gain for amp %s == %g <= 0; setting to %f" % (amp.getName(), gain, patchedGain))
671 isrFunctions.updateVariance(
672 maskedImage=ampExposure.getMaskedImage(),
674 readNoise=amp.getReadNoise(),
678 """!Apply flat correction in place 680 @param[in,out] exposure exposure to process 681 @param[in] flatExposure flatfield exposure same size as exposure 682 @param[in] invert if True, unflatten an already-flattened image instead. 684 isrFunctions.flatCorrection(
685 maskedImage=exposure.getMaskedImage(),
686 flatMaskedImage=flatExposure.getMaskedImage(),
687 scalingType=self.config.flatScalingType,
688 userScale=self.config.flatUserScale,
693 """!Retrieve a calibration dataset for removing instrument signature 695 @param[in] dataRef data reference for exposure 696 @param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat') 697 @param[in] immediate if True, disable butler proxies to enable error 698 handling within this routine 702 exp = dataRef.get(datasetType, immediate=immediate)
703 except Exception
as exc1:
704 if not self.config.fallbackFilterName:
705 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
707 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
708 except Exception
as exc2:
709 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
710 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
711 self.log.warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
713 if self.config.doAssembleIsrExposures:
714 exp = self.assembleCcd.assembleCcd(exp)
718 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place 720 @param[in,out] exposure exposure to process; only the amp DataSec is processed 721 @param[in] amp amplifier device data 723 if not math.isnan(amp.getSaturation()):
724 maskedImage = exposure.getMaskedImage()
725 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
726 isrFunctions.makeThresholdMask(
727 maskedImage=dataView,
728 threshold=amp.getSaturation(),
730 maskName=self.config.saturatedMaskName,
734 """!Interpolate over saturated pixels, in place 736 @param[in,out] ccdExposure exposure to process 739 - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask. 740 - Call this after CCD assembly, since saturated regions may cross amplifier boundaries 742 isrFunctions.interpolateFromMask(
743 maskedImage=ccdExposure.getMaskedImage(),
744 fwhm=self.config.fwhm,
745 growFootprints=self.config.growSaturationFootprintSize,
746 maskName=self.config.saturatedMaskName,
750 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place 752 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel(). 753 This is intended to indicate pixels that may be affected by unknown systematics; 754 for example if non-linearity corrections above a certain level are unstable 755 then that would be a useful value for suspectLevel. A value of `nan` indicates 756 that no such level exists and no pixels are to be masked as suspicious. 758 @param[in,out] exposure exposure to process; only the amp DataSec is processed 759 @param[in] amp amplifier device data 761 suspectLevel = amp.getSuspectLevel()
762 if math.isnan(suspectLevel):
765 maskedImage = exposure.getMaskedImage()
766 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
767 isrFunctions.makeThresholdMask(
768 maskedImage=dataView,
769 threshold=suspectLevel,
771 maskName=self.config.suspectMaskName,
775 """!Mask defects using mask plane "BAD" and interpolate over them, in place 777 @param[in,out] ccdExposure exposure to process 778 @param[in] defectBaseList a list of defects to mask and interpolate 780 \warning: call this after CCD assembly, since defects may cross amplifier boundaries 782 maskedImage = ccdExposure.getMaskedImage()
784 for d
in defectBaseList:
786 nd = measAlg.Defect(bbox)
787 defectList.append(nd)
788 isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
789 isrFunctions.interpolateDefectList(
790 maskedImage=maskedImage,
791 defectList=defectList,
792 fwhm=self.config.fwhm,
796 """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place 798 We mask and interpolate over all NaNs, including those 799 that are masked with other bits (because those may or may 800 not be interpolated over later, and we want to remove all 801 NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane 802 is used to preserve the historical name. 804 @param[in,out] exposure exposure to process 806 maskedImage = exposure.getMaskedImage()
809 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
810 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
811 numNans =
maskNans(maskedImage, maskVal)
812 self.metadata.set(
"NUMNANS", numNans)
816 self.log.warn(
"There were %i unmasked NaNs", numNans)
817 nanDefectList = isrFunctions.getDefectListFromMask(
818 maskedImage=maskedImage,
819 maskName=
'UNMASKEDNAN',
821 isrFunctions.interpolateDefectList(
822 maskedImage=exposure.getMaskedImage(),
823 defectList=nanDefectList,
824 fwhm=self.config.fwhm,
828 """!Apply overscan correction, in place 830 @param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels 831 @param[in] amp amplifier device data 833 if not amp.getHasRawInfo():
834 raise RuntimeError(
"This method must be executed on an amp with raw information.")
836 if amp.getRawHorizontalOverscanBBox().isEmpty():
837 self.log.info(
"No Overscan region. Not performing Overscan Correction.")
840 maskedImage = exposure.getMaskedImage()
841 dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
843 expImage = exposure.getMaskedImage().getImage()
844 overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
846 isrFunctions.overscanCorrection(
847 ampMaskedImage=dataView,
848 overscanImage=overscanImage,
849 fitType=self.config.overscanFitType,
850 order=self.config.overscanOrder,
851 collapseRej=self.config.overscanRej,
855 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners 857 @param[in,out] ccdExposure exposure to process 858 @param[in] fpPolygon Polygon in focal plane coordinates 861 ccd = ccdExposure.getDetector()
862 fpCorners = ccd.getCorners(FOCAL_PLANE)
863 ccdPolygon =
Polygon(fpCorners)
866 intersect = ccdPolygon.intersectionSingle(fpPolygon)
869 ccdPoints = [ccd.transform(ccd.makeCameraPoint(x, FOCAL_PLANE), PIXELS).getPoint()
for x
in intersect]
870 validPolygon =
Polygon(ccdPoints)
871 ccdExposure.getInfo().setValidPolygon(validPolygon)
874 """Apply brighter fatter correction in place for the image 876 This correction takes a kernel that has been derived from flat field images to 877 redistribute the charge. The gradient of the kernel is the deflection 878 field due to the accumulated charge. 880 Given the original image I(x) and the kernel K(x) we can compute the corrected image Ic(x) 881 using the following equation: 883 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y)))) 885 To evaluate the derivative term we expand it as follows: 887 0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y))) + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) ) 889 Because we use the measured counts instead of the incident counts we apply the correction 890 iteratively to reconstruct the original counts and the correction. We stop iterating when the 891 summed difference between the current corrected image and the one from the previous iteration 892 is below the threshold. We do not require convergence because the number of iterations is 893 too large a computational cost. How we define the threshold still needs to be evaluated, the 894 current default was shown to work reasonably well on a small set of images. For more information 895 on the method see DocuShare Document-19407. 897 The edges as defined by the kernel are not corrected because they have spurious values 898 due to the convolution. 900 self.log.info(
"Applying brighter fatter correction")
902 image = exposure.getMaskedImage().getImage()
907 kLx = numpy.shape(kernel)[0]
908 kLy = numpy.shape(kernel)[1]
909 kernelImage = afwImage.ImageD(kLx, kLy)
910 kernelImage.getArray()[:, :] = kernel
911 tempImage = image.clone()
913 nanIndex = numpy.isnan(tempImage.getArray())
914 tempImage.getArray()[nanIndex] = 0.
916 outImage = afwImage.ImageF(image.getDimensions())
917 corr = numpy.zeros_like(image.getArray())
918 prev_image = numpy.zeros_like(image.getArray())
919 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
920 fixedKernel = afwMath.FixedKernel(kernelImage)
930 for iteration
in range(maxIter):
932 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
933 tmpArray = tempImage.getArray()
934 outArray = outImage.getArray()
936 with numpy.errstate(invalid=
"ignore", over=
"ignore"):
938 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
939 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
940 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
943 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
944 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
945 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
947 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
949 tmpArray[:, :] = image.getArray()[:, :]
950 tmpArray[nanIndex] = 0.
951 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
954 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
958 prev_image[:, :] = tmpArray[:, :]
960 if iteration == maxIter - 1:
961 self.log.warn(
"Brighter fatter correction did not converge, final difference %f" % diff)
963 self.log.info(
"Finished brighter fatter in %d iterations" % (iteration + 1))
964 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
965 corr[startY + 1:endY - 1, startX + 1:endX - 1]
968 sensorTransmission=None, atmosphereTransmission=None):
969 """Attach a TransmissionCurve to an Exposure, given separate curves for 970 different components. 974 exposure : `lsst.afw.image.Exposure` 975 Exposure object to modify by attaching the product of all given 976 ``TransmissionCurves`` in post-assembly trimmed detector 977 coordinates. Must have a valid ``Detector`` attached that matches 978 the detector associated with sensorTransmission. 979 opticsTransmission : `lsst.afw.image.TransmissionCurve` 980 A ``TransmissionCurve`` that represents the throughput of the 981 optics, to be evaluated in focal-plane coordinates. 982 filterTransmission : `lsst.afw.image.TransmissionCurve` 983 A ``TransmissionCurve`` that represents the throughput of the 984 filter itself, to be evaluated in focal-plane coordinates. 985 sensorTransmission : `lsst.afw.image.TransmissionCurve` 986 A ``TransmissionCurve`` that represents the throughput of the 987 sensor itself, to be evaluated in post-assembly trimmed detector 989 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 990 A ``TransmissionCurve`` that represents the throughput of the 991 atmosphere, assumed to be spatially constant. 993 All ``TransmissionCurve`` arguments are optional; if none are provided, 994 the attached ``TransmissionCurve`` will have unit transmission 999 combined : ``lsst.afw.image.TransmissionCurve`` 1000 The TransmissionCurve attached to the exposure. 1002 return isrFunctions.attachTransmissionCurve(exposure, opticsTransmission=opticsTransmission,
1003 filterTransmission=filterTransmission,
1004 sensorTransmission=sensorTransmission,
1005 atmosphereTransmission=atmosphereTransmission)
1009 """Context manager that applies and removes gain 1012 ccd = exp.getDetector()
1014 sim = image.Factory(image, amp.getBBox())
1015 sim *= amp.getGain()
1021 ccd = exp.getDetector()
1023 sim = image.Factory(image, amp.getBBox())
1024 sim /= amp.getGain()
1029 """Context manager that applies and removes flats and darks, 1030 if the task is configured to apply them. 1032 if self.config.doDark
and dark
is not None:
1034 if self.config.doFlat:
1039 if self.config.doFlat:
1041 if self.config.doDark
and dark
is not None:
1046 """A Detector-like object that supports returning gain and saturation level""" 1049 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
1051 self.
_gain = config.gain
def brighterFatterCorrection(self, exposure, kernel, maxIter, threshold, applyGain)
def runDataRef(self, sensorRef)
Perform instrument signature removal on a ButlerDataRef of a Sensor.
def gainContext(self, exp, image, apply)
def __init__(self, args, kwargs)
Constructor for IsrTask.
def readIsrData(self, dataRef, rawExposure)
Retrieve necessary frames for instrument signature removal.
def attachTransmissionCurve(self, exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
def maskAndInterpNan(self, exposure)
Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place.
def saturationInterpolation(self, ccdExposure)
Interpolate over saturated pixels, in place.
Apply common instrument signature correction algorithms to a raw frame.
def getRawHorizontalOverscanBBox(self)
def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None, fringes=None, bfKernel=None, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
Perform instrument signature removal on an exposure.
def getSuspectLevel(self)
def convertIntToFloat(self, exposure)
def flatCorrection(self, exposure, flatExposure, invert=False)
Apply flat correction in place.
def getIsrExposure(self, dataRef, datasetType, immediate=True)
Retrieve a calibration dataset for removing instrument signature.
_RawHorizontalOverscanBBox
def darkCorrection(self, exposure, darkExposure, invert=False)
Apply dark correction in place.
def doLinearize(self, detector)
Is linearization wanted for this detector?
def setValidPolygonIntersect(self, ccdExposure, fpPolygon)
Set the valid polygon as the intersection of fpPolygon and the ccd corners.
def biasCorrection(self, exposure, biasExposure)
Apply bias correction in place.
def flatContext(self, exp, flat, dark=None)
def updateVariance(self, ampExposure, amp)
Set the variance plane based on the image plane, plus amplifier gain and read noise.
def overscanCorrection(self, exposure, amp)
Apply overscan correction, in place.
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Mask NANs in an image.
def suspectDetection(self, exposure, amp)
Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place.
def maskAndInterpDefect(self, ccdExposure, defectBaseList)
Mask defects using mask plane "BAD" and interpolate over them, in place.
def saturationDetection(self, exposure, amp)
Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place...
def __init__(self, exposure, config)