1 from __future__
import absolute_import, division, print_function
2 from builtins
import range
3 from builtins
import object
28 import lsst.afw.geom
as afwGeom
29 import lsst.afw.image
as afwImage
30 import lsst.meas.algorithms
as measAlg
31 import lsst.pex.config
as pexConfig
32 import lsst.pipe.base
as pipeBase
33 import lsst.afw.math
as afwMath
34 from lsst.daf.persistence
import ButlerDataRef
35 from lsstDebug
import getDebugFrame
36 from lsst.afw.display
import getDisplay
37 from .
import isrFunctions
38 from .assembleCcdTask
import AssembleCcdTask
39 from .fringe
import FringeTask
40 from lsst.afw.geom.polygon
import Polygon
41 from lsst.afw.cameraGeom
import PIXELS, FOCAL_PLANE, NullLinearityType
42 from contextlib
import contextmanager
43 from .isr
import maskNans
47 doBias = pexConfig.Field(
49 doc=
"Apply bias frame correction?",
52 doDark = pexConfig.Field(
54 doc=
"Apply dark frame correction?",
57 doFlat = pexConfig.Field(
59 doc=
"Apply flat field correction?",
62 doFringe = pexConfig.Field(
64 doc=
"Apply fringe correction?",
67 doDefect = pexConfig.Field(
69 doc=
"Apply correction for CCD defects, e.g. hot pixels?",
72 doWrite = pexConfig.Field(
74 doc=
"Persist postISRCCD?",
77 assembleCcd = pexConfig.ConfigurableField(
78 target=AssembleCcdTask,
79 doc=
"CCD assembly task",
81 gain = pexConfig.Field(
83 doc=
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
86 readNoise = pexConfig.Field(
88 doc=
"The read noise to use if no Detector is present in the Exposure",
91 saturation = pexConfig.Field(
93 doc=
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
96 fringeAfterFlat = pexConfig.Field(
98 doc=
"Do fringe subtraction after flat-fielding?",
101 fringe = pexConfig.ConfigurableField(
103 doc=
"Fringe subtraction task",
105 fwhm = pexConfig.Field(
107 doc=
"FWHM of PSF (arcsec)",
110 saturatedMaskName = pexConfig.Field(
112 doc=
"Name of mask plane to use in saturation detection and interpolation",
115 suspectMaskName = pexConfig.Field(
117 doc=
"Name of mask plane to use for suspect pixels",
120 flatScalingType = pexConfig.ChoiceField(
122 doc=
"The method for scaling the flat on the fly.",
125 "USER":
"Scale by flatUserScale",
126 "MEAN":
"Scale by the inverse of the mean",
127 "MEDIAN":
"Scale by the inverse of the median",
130 flatUserScale = pexConfig.Field(
132 doc=
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
135 overscanFitType = pexConfig.ChoiceField(
137 doc=
"The method for fitting the overscan bias level.",
140 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
141 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
142 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
143 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
144 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
145 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
146 "MEAN":
"Correct using the mean of the overscan region",
147 "MEDIAN":
"Correct using the median of the overscan region",
150 overscanOrder = pexConfig.Field(
152 doc=(
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
153 "or number of spline knots if overscan fit type is a spline."),
156 overscanRej = pexConfig.Field(
158 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
161 growSaturationFootprintSize = pexConfig.Field(
163 doc=
"Number of pixels by which to grow the saturation footprints",
166 doSaturationInterpolation = pexConfig.Field(
168 doc=
"Perform interpolation over pixels masked as saturated?",
171 fluxMag0T1 = pexConfig.Field(
173 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure",
176 keysToRemoveFromAssembledCcd = pexConfig.ListField(
178 doc=
"fields to remove from the metadata of the assembled ccd.",
181 doAssembleIsrExposures = pexConfig.Field(
184 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
186 doAssembleCcd = pexConfig.Field(
189 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
191 expectWcs = pexConfig.Field(
194 doc=
"Expect input science images to have a WCS (set False for e.g. spectrographs)"
196 doLinearize = pexConfig.Field(
198 doc=
"Correct for nonlinearity of the detector's response?",
201 doBrighterFatter = pexConfig.Field(
204 doc=
"Apply the brighter fatter correction"
206 brighterFatterKernelFile = pexConfig.Field(
209 doc=
"Kernel file used for the brighter fatter correction"
211 brighterFatterMaxIter = pexConfig.Field(
214 doc=
"Maximum number of iterations for the brighter fatter correction"
216 brighterFatterThreshold = pexConfig.Field(
219 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the "
220 " absolute value of the difference between the current corrected image and the one"
221 " from the previous iteration summed over all the pixels."
223 brighterFatterApplyGain = pexConfig.Field(
226 doc=
"Should the gain be applied when applying the brighter fatter correction?"
228 datasetType = pexConfig.Field(
230 doc=
"Dataset type for input data; users will typically leave this alone, "
231 "but camera-specific ISR tasks will override it",
234 fallbackFilterName = pexConfig.Field(dtype=str,
235 doc=
"Fallback default filter name for calibrations", optional=
True)
249 \brief Apply common instrument signature correction algorithms to a raw frame.
251 \section ip_isr_isr_Contents Contents
253 - \ref ip_isr_isr_Purpose
254 - \ref ip_isr_isr_Initialize
256 - \ref ip_isr_isr_Config
257 - \ref ip_isr_isr_Debug
260 \section ip_isr_isr_Purpose Description
262 The process for correcting imaging data is very similar from camera to camera.
263 This task provides a vanilla implementation of doing these corrections, including
264 the ability to turn certain corrections off if they are not needed.
265 The inputs to the primary method, run, are a raw exposure to be corrected and the
266 calibration data products. The raw input is a single chip sized mosaic of all amps
267 including overscans and other non-science pixels.
268 The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask
269 and takes as input only a daf.persistence.butlerSubset.ButlerDataRef.
270 This task may not meet all needs and it is expected that it will be subclassed for
271 specific applications.
273 \section ip_isr_isr_Initialize Task initialization
275 \copydoc \_\_init\_\_
277 \section ip_isr_isr_IO Inputs/Outputs to the run method
281 \section ip_isr_isr_Config Configuration parameters
283 See \ref IsrTaskConfig
285 \section ip_isr_isr_Debug Debug variables
287 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
288 flag \c --debug, \c -d to import \b debug.py from your \c PYTHONPATH; see <a
289 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
290 Using lsstDebug to control debugging output</a> for more about \b debug.py files.
292 The available variables in IsrTask are:
295 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
298 <DD> display exposure after ISR has been applied
302 For example, put something like
306 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
307 if name == "lsst.ip.isrFunctions.isrTask":
308 di.display = {'postISRCCD':2}
310 lsstDebug.Info = DebugInfo
312 into your debug.py file and run the commandline task with the \c --debug flag.
316 ConfigClass = IsrTaskConfig
320 '''!Constructor for IsrTask
321 \param[in] *args -- a list of positional arguments passed on to the Task constructor
322 \param[in] **kwargs -- a dictionary of keyword arguments passed on to the Task constructor
323 Call the lsst.pipe.base.task.Task.__init__ method
324 Then setup the assembly and fringe correction subtasks
326 pipeBase.Task.__init__(self, *args, **kwargs)
327 self.makeSubtask(
"assembleCcd")
328 self.makeSubtask(
"fringe")
331 """!Retrieve necessary frames for instrument signature removal
332 \param[in] dataRef -- a daf.persistence.butlerSubset.ButlerDataRef
333 of the detector data to be processed
334 \param[in] rawExposure -- a reference raw exposure that will later be
335 corrected with the retrieved calibration data;
336 should not be modified in this method.
337 \return a pipeBase.Struct with fields containing kwargs expected by run()
338 - bias: exposure of bias frame
339 - dark: exposure of dark frame
340 - flat: exposure of flat field
341 - defects: list of detects
342 - fringeStruct: a pipeBase.Struct with field fringes containing
343 exposure of fringe frame or list of fringe exposure
345 ccd = rawExposure.getDetector()
347 biasExposure = self.
getIsrExposure(dataRef,
"bias")
if self.config.doBias
else None
349 linearizer = dataRef.get(
"linearizer", immediate=
True)
if self.
doLinearize(ccd)
else None
350 darkExposure = self.
getIsrExposure(dataRef,
"dark")
if self.config.doDark
else None
351 flatExposure = self.
getIsrExposure(dataRef,
"flat")
if self.config.doFlat
else None
352 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
if self.config.doBrighterFatter
else None
353 defectList = dataRef.get(
"defects")
if self.config.doDefect
else None
355 if self.config.doFringe
and self.fringe.checkFilter(rawExposure):
356 fringeStruct = self.fringe.readFringes(dataRef, assembler=self.assembleCcd
357 if self.config.doAssembleIsrExposures
else None)
359 fringeStruct = pipeBase.Struct(fringes=
None)
362 return pipeBase.Struct(bias=biasExposure,
363 linearizer=linearizer,
367 fringes=fringeStruct,
368 bfKernel=brighterFatterKernel
372 def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
373 fringes=
None, bfKernel=
None):
374 """!Perform instrument signature removal on an exposure
377 - Detect saturation, apply overscan correction, bias, dark and flat
378 - Perform CCD assembly
379 - Interpolate over defects, saturated pixels and all NaNs
381 \param[in] ccdExposure -- lsst.afw.image.exposure of detector data
382 \param[in] bias -- exposure of bias frame
383 \param[in] linearizer -- linearizing functor; a subclass of lsst.ip.isrFunctions.LinearizeBase
384 \param[in] dark -- exposure of dark frame
385 \param[in] flat -- exposure of flatfield
386 \param[in] defects -- list of detects
387 \param[in] fringes -- a pipeBase.Struct with field fringes containing
388 exposure of fringe frame or list of fringe exposure
389 \param[in] bfKernel -- kernel for brighter-fatter correction
391 \return a pipeBase.Struct with field:
396 if isinstance(ccdExposure, ButlerDataRef):
399 ccd = ccdExposure.getDetector()
402 if self.config.doBias
and bias
is None:
403 raise RuntimeError(
"Must supply a bias exposure if config.doBias True")
405 raise RuntimeError(
"Must supply a linearizer if config.doBias True")
406 if self.config.doDark
and dark
is None:
407 raise RuntimeError(
"Must supply a dark exposure if config.doDark True")
408 if self.config.doFlat
and flat
is None:
409 raise RuntimeError(
"Must supply a flat exposure if config.doFlat True")
410 if self.config.doBrighterFatter
and bfKernel
is None:
411 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter True")
413 fringes = pipeBase.Struct(fringes=
None)
414 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
415 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct")
416 if self.config.doDefect
and defects
is None:
417 raise RuntimeError(
"Must supply defects if config.doDefect True")
422 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd"
423 ccd = [
FakeAmp(ccdExposure, self.config)]
427 if ccdExposure.getBBox().contains(amp.getBBox()):
432 if self.config.doAssembleCcd:
433 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
434 if self.config.expectWcs
and not ccdExposure.getWcs():
435 self.log.warn(
"No WCS found in input exposure")
437 if self.config.doBias:
441 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
445 if ccdExposure.getBBox().contains(amp.getBBox()):
446 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
449 if self.config.doBrighterFatter:
451 self.config.brighterFatterMaxIter,
452 self.config.brighterFatterThreshold,
453 self.config.brighterFatterApplyGain,
456 if self.config.doDark:
459 if self.config.doFringe
and not self.config.fringeAfterFlat:
460 self.fringe.run(ccdExposure, **fringes.getDict())
462 if self.config.doFlat:
465 if self.config.doDefect:
468 if self.config.doSaturationInterpolation:
473 if self.config.doFringe
and self.config.fringeAfterFlat:
474 self.fringe.run(ccdExposure, **fringes.getDict())
476 exposureTime = ccdExposure.getInfo().getVisitInfo().getExposureTime()
477 ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1*exposureTime)
479 frame = getDebugFrame(self._display,
"postISRCCD")
481 getDisplay(frame).mtv(ccdExposure)
483 return pipeBase.Struct(
484 exposure=ccdExposure,
489 """!Perform instrument signature removal on a ButlerDataRef of a Sensor
491 - Read in necessary detrending/isr/calibration data
492 - Process raw exposure in run()
493 - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
495 \param[in] sensorRef -- daf.persistence.butlerSubset.ButlerDataRef of the
496 detector data to be processed
497 \return a pipeBase.Struct with fields:
498 - exposure: the exposure after application of ISR
500 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
501 ccdExposure = sensorRef.get(
'raw')
504 result = self.
run(ccdExposure, **isrData.getDict())
506 if self.config.doWrite:
507 sensorRef.put(result.exposure,
"postISRCCD")
512 """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
514 if isinstance(exposure, afwImage.ExposureF):
517 if not hasattr(exposure,
"convertF"):
518 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
520 newexposure = exposure.convertF()
521 maskedImage = newexposure.getMaskedImage()
522 varArray = maskedImage.getVariance().getArray()
524 maskArray = maskedImage.getMask().getArray()
529 """!Apply bias correction in place
531 \param[in,out] exposure exposure to process
532 \param[in] biasExposure bias exposure of same size as exposure
534 isrFunctions.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
537 """!Apply dark correction in place
539 \param[in,out] exposure exposure to process
540 \param[in] darkExposure dark exposure of same size as exposure
542 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
543 if math.isnan(expScale):
544 raise RuntimeError(
"Exposure darktime is NAN")
545 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
546 if math.isnan(darkScale):
547 raise RuntimeError(
"Dark calib darktime is NAN")
548 isrFunctions.darkCorrection(
549 maskedImage=exposure.getMaskedImage(),
550 darkMaskedImage=darkExposure.getMaskedImage(),
556 """!Is linearization wanted for this detector?
558 Checks config.doLinearize and the linearity type of the first amplifier.
560 \param[in] detector detector information (an lsst.afw.cameraGeom.Detector)
562 return self.config.doLinearize
and \
563 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
566 """!Set the variance plane based on the image plane, plus amplifier gain and read noise
568 \param[in,out] ampExposure exposure to process
569 \param[in] amp amplifier detector information
571 if not math.isnan(amp.getGain()):
572 isrFunctions.updateVariance(
573 maskedImage=ampExposure.getMaskedImage(),
575 readNoise=amp.getReadNoise(),
579 """!Apply flat correction in place
581 \param[in,out] exposure exposure to process
582 \param[in] flatExposure flatfield exposure same size as exposure
584 isrFunctions.flatCorrection(
585 maskedImage=exposure.getMaskedImage(),
586 flatMaskedImage=flatExposure.getMaskedImage(),
587 scalingType=self.config.flatScalingType,
588 userScale=self.config.flatUserScale,
592 """!Retrieve a calibration dataset for removing instrument signature
594 \param[in] dataRef data reference for exposure
595 \param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat')
596 \param[in] immediate if True, disable butler proxies to enable error
597 handling within this routine
601 exp = dataRef.get(datasetType, immediate=immediate)
602 except Exception
as exc1:
603 if not self.config.fallbackFilterName:
604 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
606 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
607 except Exception
as exc2:
608 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
609 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
610 self.log.warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
612 if self.config.doAssembleIsrExposures:
613 exp = self.assembleCcd.assembleCcd(exp)
617 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place
619 \param[in,out] exposure exposure to process; only the amp DataSec is processed
620 \param[in] amp amplifier device data
622 if not math.isnan(amp.getSaturation()):
623 maskedImage = exposure.getMaskedImage()
624 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
625 isrFunctions.makeThresholdMask(
626 maskedImage=dataView,
627 threshold=amp.getSaturation(),
629 maskName=self.config.saturatedMaskName,
633 """!Interpolate over saturated pixels, in place
635 \param[in,out] ccdExposure exposure to process
638 - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
639 - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
641 isrFunctions.interpolateFromMask(
642 maskedImage=ccdExposure.getMaskedImage(),
643 fwhm=self.config.fwhm,
644 growFootprints=self.config.growSaturationFootprintSize,
645 maskName=self.config.saturatedMaskName,
649 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place
651 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel().
652 This is intended to indicate pixels that may be affected by unknown systematics;
653 for example if non-linearity corrections above a certain level are unstable
654 then that would be a useful value for suspectLevel. A value of `nan` indicates
655 that no such level exists and no pixels are to be masked as suspicious.
657 \param[in,out] exposure exposure to process; only the amp DataSec is processed
658 \param[in] amp amplifier device data
660 suspectLevel = amp.getSuspectLevel()
661 if math.isnan(suspectLevel):
664 maskedImage = exposure.getMaskedImage()
665 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
666 isrFunctions.makeThresholdMask(
667 maskedImage=dataView,
668 threshold=suspectLevel,
670 maskName=self.config.suspectMaskName,
674 """!Mask defects using mask plane "BAD" and interpolate over them, in place
676 \param[in,out] ccdExposure exposure to process
677 \param[in] defectBaseList a list of defects to mask and interpolate
679 \warning: call this after CCD assembly, since defects may cross amplifier boundaries
681 maskedImage = ccdExposure.getMaskedImage()
683 for d
in defectBaseList:
685 nd = measAlg.Defect(bbox)
686 defectList.append(nd)
687 isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
688 isrFunctions.interpolateDefectList(
689 maskedImage=maskedImage,
690 defectList=defectList,
691 fwhm=self.config.fwhm,
695 """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
697 We mask and interpolate over all NaNs, including those
698 that are masked with other bits (because those may or may
699 not be interpolated over later, and we want to remove all
700 NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
701 is used to preserve the historical name.
703 \param[in,out] exposure exposure to process
705 maskedImage = exposure.getMaskedImage()
708 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
709 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
710 numNans =
maskNans(maskedImage, maskVal)
711 self.metadata.set(
"NUMNANS", numNans)
715 self.log.warn(
"There were %i unmasked NaNs", numNans)
716 nanDefectList = isrFunctions.getDefectListFromMask(
717 maskedImage=maskedImage,
718 maskName=
'UNMASKEDNAN',
721 isrFunctions.interpolateDefectList(
722 maskedImage=exposure.getMaskedImage(),
723 defectList=nanDefectList,
724 fwhm=self.config.fwhm,
728 """!Apply overscan correction, in place
730 \param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels
731 \param[in] amp amplifier device data
733 if not amp.getHasRawInfo():
734 raise RuntimeError(
"This method must be executed on an amp with raw information.")
736 if amp.getRawHorizontalOverscanBBox().isEmpty():
737 self.log.info(
"No Overscan region. Not performing Overscan Correction.")
740 maskedImage = exposure.getMaskedImage()
741 dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
743 expImage = exposure.getMaskedImage().getImage()
744 overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
746 isrFunctions.overscanCorrection(
747 ampMaskedImage=dataView,
748 overscanImage=overscanImage,
749 fitType=self.config.overscanFitType,
750 order=self.config.overscanOrder,
751 collapseRej=self.config.overscanRej,
755 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners
757 \param[in,out] ccdExposure exposure to process
758 \param[in] fpPolygon Polygon in focal plane coordinates
761 ccd = ccdExposure.getDetector()
762 fpCorners = ccd.getCorners(FOCAL_PLANE)
763 ccdPolygon = Polygon(fpCorners)
766 intersect = ccdPolygon.intersectionSingle(fpPolygon)
769 ccdPoints = [ccd.transform(ccd.makeCameraPoint(x, FOCAL_PLANE), PIXELS).getPoint()
for x
in intersect]
770 validPolygon = Polygon(ccdPoints)
771 ccdExposure.getInfo().setValidPolygon(validPolygon)
774 """Apply brighter fatter correction in place for the image
776 This correction takes a kernel that has been derived from flat field images to
777 redistribute the charge. The gradient of the kernel is the deflection
778 field due to the accumulated charge.
780 Given the original image I(x) and the kernel K(x) we can compute the corrected image Ic(x)
781 using the following equation:
783 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
785 To evaluate the derivative term we expand it as follows:
787 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))) )
789 Because we use the measured counts instead of the incident counts we apply the correction
790 iteratively to reconstruct the original counts and the correction. We stop iterating when the
791 summed difference between the current corrected image and the one from the previous iteration
792 is below the threshold. We do not require convergence because the number of iterations is
793 too large a computational cost. How we define the threshold still needs to be evaluated, the
794 current default was shown to work reasonably well on a small set of images. For more information
795 on the method see DocuShare Document-19407.
797 The edges as defined by the kernel are not corrected because they have spurious values
798 due to the convolution.
800 self.log.info(
"Applying brighter fatter correction")
802 image = exposure.getMaskedImage().getImage()
807 kLx = numpy.shape(kernel)[0]
808 kLy = numpy.shape(kernel)[1]
809 kernelImage = afwImage.ImageD(kLx, kLy)
810 kernelImage.getArray()[:, :] = kernel
811 tempImage = image.clone()
813 nanIndex = numpy.isnan(tempImage.getArray())
814 tempImage.getArray()[nanIndex] = 0.
816 outImage = afwImage.ImageF(image.getDimensions())
817 corr = numpy.zeros_like(image.getArray())
818 prev_image = numpy.zeros_like(image.getArray())
819 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
820 fixedKernel = afwMath.FixedKernel(kernelImage)
830 for iteration
in range(maxIter):
832 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
833 tmpArray = tempImage.getArray()
834 outArray = outImage.getArray()
837 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
838 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
839 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
842 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
843 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
844 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
846 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
848 tmpArray[:, :] = image.getArray()[:, :]
849 tmpArray[nanIndex] = 0.
850 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
853 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
857 prev_image[:, :] = tmpArray[:, :]
859 if iteration == maxIter - 1:
860 self.log.warn(
"Brighter fatter correction did not converge, final difference %f" % diff)
862 self.log.info(
"Finished brighter fatter in %d iterations" % (iteration + 1))
863 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
864 corr[startY + 1:endY - 1, startX + 1:endX - 1]
869 """Context manager that applies and removes gain
872 ccd = exp.getDetector()
874 sim = image.Factory(image, amp.getBBox())
881 ccd = exp.getDetector()
883 sim = image.Factory(image, amp.getBBox())
888 """A Detector-like object that supports returning gain and saturation level"""
891 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
893 self.
_gain = config.gain
def doLinearize
Is linearization wanted for this detector?
def getIsrExposure
Retrieve a calibration dataset for removing instrument signature.
def maskAndInterpNan
Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place.
def flatCorrection
Apply flat correction in place.
def run
Perform instrument signature removal on an exposure.
def __init__
Constructor for IsrTask.
Apply common instrument signature correction algorithms to a raw frame.
def runDataRef
Perform instrument signature removal on a ButlerDataRef of a Sensor.
def suspectDetection
Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place.
def updateVariance
Set the variance plane based on the image plane, plus amplifier gain and read noise.
def maskAndInterpDefect
Mask defects using mask plane "BAD" and interpolate over them, in place.
def setValidPolygonIntersect
Set the valid polygon as the intersection of fpPolygon and the ccd corners.
def overscanCorrection
Apply overscan correction, in place.
_RawHorizontalOverscanBBox
def saturationDetection
Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place...
def darkCorrection
Apply dark 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 saturationInterpolation
Interpolate over saturated pixels, in place.
def getRawHorizontalOverscanBBox
def brighterFatterCorrection
def biasCorrection
Apply bias correction in place.
def readIsrData
Retrieve necessary frames for instrument signature removal.