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 doLinearize = pexConfig.Field(
193 doc=
"Correct for nonlinearity of the detector's response?",
196 doBrighterFatter = pexConfig.Field(
199 doc=
"Apply the brighter fatter correction"
201 brighterFatterKernelFile = pexConfig.Field(
204 doc=
"Kernel file used for the brighter fatter correction"
206 brighterFatterMaxIter = pexConfig.Field(
209 doc=
"Maximum number of iterations for the brighter fatter correction"
211 brighterFatterThreshold = pexConfig.Field(
214 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the "
215 " absolute value of the difference between the current corrected image and the one"
216 " from the previous iteration summed over all the pixels."
218 brighterFatterApplyGain = pexConfig.Field(
221 doc=
"Should the gain be applied when applying the brighter fatter correction?"
223 datasetType = pexConfig.Field(
225 doc=
"Dataset type for input data; users will typically leave this alone, "
226 "but camera-specific ISR tasks will override it",
229 fallbackFilterName = pexConfig.Field(dtype=str,
230 doc=
"Fallback default filter name for calibrations", optional=
True)
244 \brief Apply common instrument signature correction algorithms to a raw frame.
246 \section ip_isr_isr_Contents Contents
248 - \ref ip_isr_isr_Purpose
249 - \ref ip_isr_isr_Initialize
251 - \ref ip_isr_isr_Config
252 - \ref ip_isr_isr_Debug
255 \section ip_isr_isr_Purpose Description
257 The process for correcting imaging data is very similar from camera to camera.
258 This task provides a vanilla implementation of doing these corrections, including
259 the ability to turn certain corrections off if they are not needed.
260 The inputs to the primary method, run, are a raw exposure to be corrected and the
261 calibration data products. The raw input is a single chip sized mosaic of all amps
262 including overscans and other non-science pixels.
263 The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask
264 and takes as input only a daf.persistence.butlerSubset.ButlerDataRef.
265 This task may not meet all needs and it is expected that it will be subclassed for
266 specific applications.
268 \section ip_isr_isr_Initialize Task initialization
270 \copydoc \_\_init\_\_
272 \section ip_isr_isr_IO Inputs/Outputs to the run method
276 \section ip_isr_isr_Config Configuration parameters
278 See \ref IsrTaskConfig
280 \section ip_isr_isr_Debug Debug variables
282 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
283 flag \c --debug, \c -d to import \b debug.py from your \c PYTHONPATH; see <a
284 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
285 Using lsstDebug to control debugging output</a> for more about \b debug.py files.
287 The available variables in IsrTask are:
290 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
293 <DD> display exposure after ISR has been applied
297 For example, put something like
301 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
302 if name == "lsst.ip.isrFunctions.isrTask":
303 di.display = {'postISRCCD':2}
305 lsstDebug.Info = DebugInfo
307 into your debug.py file and run the commandline task with the \c --debug flag.
311 ConfigClass = IsrTaskConfig
315 '''!Constructor for IsrTask
316 \param[in] *args -- a list of positional arguments passed on to the Task constructor
317 \param[in] **kwargs -- a dictionary of keyword arguments passed on to the Task constructor
318 Call the lsst.pipe.base.task.Task.__init__ method
319 Then setup the assembly and fringe correction subtasks
321 pipeBase.Task.__init__(self, *args, **kwargs)
322 self.makeSubtask(
"assembleCcd")
323 self.makeSubtask(
"fringe")
326 """!Retrieve necessary frames for instrument signature removal
327 \param[in] dataRef -- a daf.persistence.butlerSubset.ButlerDataRef
328 of the detector data to be processed
329 \param[in] rawExposure -- a reference raw exposure that will later be
330 corrected with the retrieved calibration data;
331 should not be modified in this method.
332 \return a pipeBase.Struct with fields containing kwargs expected by run()
333 - bias: exposure of bias frame
334 - dark: exposure of dark frame
335 - flat: exposure of flat field
336 - defects: list of detects
337 - fringeStruct: a pipeBase.Struct with field fringes containing
338 exposure of fringe frame or list of fringe exposure
340 ccd = rawExposure.getDetector()
342 biasExposure = self.
getIsrExposure(dataRef,
"bias")
if self.config.doBias
else None
344 linearizer = dataRef.get(
"linearizer", immediate=
True)
if self.
doLinearize(ccd)
else None
345 darkExposure = self.
getIsrExposure(dataRef,
"dark")
if self.config.doDark
else None
346 flatExposure = self.
getIsrExposure(dataRef,
"flat")
if self.config.doFlat
else None
347 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
if self.config.doBrighterFatter
else None
348 defectList = dataRef.get(
"defects")
if self.config.doDefect
else None
350 if self.config.doFringe
and self.fringe.checkFilter(rawExposure):
351 fringeStruct = self.fringe.readFringes(dataRef, assembler=self.assembleCcd
352 if self.config.doAssembleIsrExposures
else None)
354 fringeStruct = pipeBase.Struct(fringes=
None)
357 return pipeBase.Struct(bias=biasExposure,
358 linearizer=linearizer,
362 fringes=fringeStruct,
363 bfKernel=brighterFatterKernel
367 def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
368 fringes=
None, bfKernel=
None):
369 """!Perform instrument signature removal on an exposure
372 - Detect saturation, apply overscan correction, bias, dark and flat
373 - Perform CCD assembly
374 - Interpolate over defects, saturated pixels and all NaNs
376 \param[in] ccdExposure -- lsst.afw.image.exposure of detector data
377 \param[in] bias -- exposure of bias frame
378 \param[in] linearizer -- linearizing functor; a subclass of lsst.ip.isrFunctions.LinearizeBase
379 \param[in] dark -- exposure of dark frame
380 \param[in] flat -- exposure of flatfield
381 \param[in] defects -- list of detects
382 \param[in] fringes -- a pipeBase.Struct with field fringes containing
383 exposure of fringe frame or list of fringe exposure
384 \param[in] bfKernel -- kernel for brighter-fatter correction
386 \return a pipeBase.Struct with field:
391 if isinstance(ccdExposure, ButlerDataRef):
394 ccd = ccdExposure.getDetector()
397 if self.config.doBias
and bias
is None:
398 raise RuntimeError(
"Must supply a bias exposure if config.doBias True")
400 raise RuntimeError(
"Must supply a linearizer if config.doBias True")
401 if self.config.doDark
and dark
is None:
402 raise RuntimeError(
"Must supply a dark exposure if config.doDark True")
403 if self.config.doFlat
and flat
is None:
404 raise RuntimeError(
"Must supply a flat exposure if config.doFlat True")
405 if self.config.doBrighterFatter
and bfKernel
is None:
406 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter True")
408 fringes = pipeBase.Struct(fringes=
None)
409 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
410 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct")
411 if self.config.doDefect
and defects
is None:
412 raise RuntimeError(
"Must supply defects if config.doDefect True")
417 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd"
418 ccd = [
FakeAmp(ccdExposure, self.config)]
422 if ccdExposure.getBBox().contains(amp.getBBox()):
427 if self.config.doAssembleCcd:
428 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
430 if self.config.doBias:
434 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
438 if ccdExposure.getBBox().contains(amp.getBBox()):
439 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
442 if self.config.doBrighterFatter:
444 self.config.brighterFatterMaxIter,
445 self.config.brighterFatterThreshold,
446 self.config.brighterFatterApplyGain,
449 if self.config.doDark:
452 if self.config.doFringe
and not self.config.fringeAfterFlat:
453 self.fringe.run(ccdExposure, **fringes.getDict())
455 if self.config.doFlat:
458 if self.config.doDefect:
461 if self.config.doSaturationInterpolation:
466 if self.config.doFringe
and self.config.fringeAfterFlat:
467 self.fringe.run(ccdExposure, **fringes.getDict())
469 exposureTime = ccdExposure.getInfo().getVisitInfo().getExposureTime()
470 ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1*exposureTime)
472 frame = getDebugFrame(self._display,
"postISRCCD")
474 getDisplay(frame).mtv(ccdExposure)
476 return pipeBase.Struct(
477 exposure=ccdExposure,
482 """!Perform instrument signature removal on a ButlerDataRef of a Sensor
484 - Read in necessary detrending/isr/calibration data
485 - Process raw exposure in run()
486 - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
488 \param[in] sensorRef -- daf.persistence.butlerSubset.ButlerDataRef of the
489 detector data to be processed
490 \return a pipeBase.Struct with fields:
491 - exposure: the exposure after application of ISR
493 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
494 ccdExposure = sensorRef.get(
'raw')
497 result = self.
run(ccdExposure, **isrData.getDict())
499 if self.config.doWrite:
500 sensorRef.put(result.exposure,
"postISRCCD")
505 """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
507 if isinstance(exposure, afwImage.ExposureF):
510 if not hasattr(exposure,
"convertF"):
511 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
513 newexposure = exposure.convertF()
514 maskedImage = newexposure.getMaskedImage()
515 varArray = maskedImage.getVariance().getArray()
517 maskArray = maskedImage.getMask().getArray()
522 """!Apply bias correction in place
524 \param[in,out] exposure exposure to process
525 \param[in] biasExposure bias exposure of same size as exposure
527 isrFunctions.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
530 """!Apply dark correction in place
532 \param[in,out] exposure exposure to process
533 \param[in] darkExposure dark exposure of same size as exposure
535 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
536 if math.isnan(expScale):
537 raise RuntimeError(
"Exposure darktime is NAN")
538 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
539 if math.isnan(darkScale):
540 raise RuntimeError(
"Dark calib darktime is NAN")
541 isrFunctions.darkCorrection(
542 maskedImage=exposure.getMaskedImage(),
543 darkMaskedImage=darkExposure.getMaskedImage(),
549 """!Is linearization wanted for this detector?
551 Checks config.doLinearize and the linearity type of the first amplifier.
553 \param[in] detector detector information (an lsst.afw.cameraGeom.Detector)
555 return self.config.doLinearize
and \
556 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
559 """!Set the variance plane based on the image plane, plus amplifier gain and read noise
561 \param[in,out] ampExposure exposure to process
562 \param[in] amp amplifier detector information
564 if not math.isnan(amp.getGain()):
565 isrFunctions.updateVariance(
566 maskedImage=ampExposure.getMaskedImage(),
568 readNoise=amp.getReadNoise(),
572 """!Apply flat correction in place
574 \param[in,out] exposure exposure to process
575 \param[in] flatExposure flatfield exposure same size as exposure
577 isrFunctions.flatCorrection(
578 maskedImage=exposure.getMaskedImage(),
579 flatMaskedImage=flatExposure.getMaskedImage(),
580 scalingType=self.config.flatScalingType,
581 userScale=self.config.flatUserScale,
585 """!Retrieve a calibration dataset for removing instrument signature
587 \param[in] dataRef data reference for exposure
588 \param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat')
589 \param[in] immediate if True, disable butler proxies to enable error
590 handling within this routine
594 exp = dataRef.get(datasetType, immediate=immediate)
595 except Exception
as exc1:
596 if not self.config.fallbackFilterName:
597 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
599 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
600 except Exception
as exc2:
601 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
602 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
603 self.log.warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
605 if self.config.doAssembleIsrExposures:
606 exp = self.assembleCcd.assembleCcd(exp)
610 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place
612 \param[in,out] exposure exposure to process; only the amp DataSec is processed
613 \param[in] amp amplifier device data
615 if not math.isnan(amp.getSaturation()):
616 maskedImage = exposure.getMaskedImage()
617 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
618 isrFunctions.makeThresholdMask(
619 maskedImage=dataView,
620 threshold=amp.getSaturation(),
622 maskName=self.config.saturatedMaskName,
626 """!Interpolate over saturated pixels, in place
628 \param[in,out] ccdExposure exposure to process
631 - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
632 - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
634 isrFunctions.interpolateFromMask(
635 maskedImage=ccdExposure.getMaskedImage(),
636 fwhm=self.config.fwhm,
637 growFootprints=self.config.growSaturationFootprintSize,
638 maskName=self.config.saturatedMaskName,
642 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place
644 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel().
645 This is intended to indicate pixels that may be affected by unknown systematics;
646 for example if non-linearity corrections above a certain level are unstable
647 then that would be a useful value for suspectLevel. A value of `nan` indicates
648 that no such level exists and no pixels are to be masked as suspicious.
650 \param[in,out] exposure exposure to process; only the amp DataSec is processed
651 \param[in] amp amplifier device data
653 suspectLevel = amp.getSuspectLevel()
654 if math.isnan(suspectLevel):
657 maskedImage = exposure.getMaskedImage()
658 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
659 isrFunctions.makeThresholdMask(
660 maskedImage=dataView,
661 threshold=suspectLevel,
663 maskName=self.config.suspectMaskName,
667 """!Mask defects using mask plane "BAD" and interpolate over them, in place
669 \param[in,out] ccdExposure exposure to process
670 \param[in] defectBaseList a list of defects to mask and interpolate
672 \warning: call this after CCD assembly, since defects may cross amplifier boundaries
674 maskedImage = ccdExposure.getMaskedImage()
676 for d
in defectBaseList:
678 nd = measAlg.Defect(bbox)
679 defectList.append(nd)
680 isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
681 isrFunctions.interpolateDefectList(
682 maskedImage=maskedImage,
683 defectList=defectList,
684 fwhm=self.config.fwhm,
688 """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
690 We mask and interpolate over all NaNs, including those
691 that are masked with other bits (because those may or may
692 not be interpolated over later, and we want to remove all
693 NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
694 is used to preserve the historical name.
696 \param[in,out] exposure exposure to process
698 maskedImage = exposure.getMaskedImage()
701 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
702 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
703 numNans =
maskNans(maskedImage, maskVal)
704 self.metadata.set(
"NUMNANS", numNans)
708 self.log.warn(
"There were %i unmasked NaNs", numNans)
709 nanDefectList = isrFunctions.getDefectListFromMask(
710 maskedImage=maskedImage,
711 maskName=
'UNMASKEDNAN',
714 isrFunctions.interpolateDefectList(
715 maskedImage=exposure.getMaskedImage(),
716 defectList=nanDefectList,
717 fwhm=self.config.fwhm,
721 """!Apply overscan correction, in place
723 \param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels
724 \param[in] amp amplifier device data
726 if not amp.getHasRawInfo():
727 raise RuntimeError(
"This method must be executed on an amp with raw information.")
729 if amp.getRawHorizontalOverscanBBox().isEmpty():
730 self.log.info(
"No Overscan region. Not performing Overscan Correction.")
733 maskedImage = exposure.getMaskedImage()
734 dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
736 expImage = exposure.getMaskedImage().getImage()
737 overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
739 isrFunctions.overscanCorrection(
740 ampMaskedImage=dataView,
741 overscanImage=overscanImage,
742 fitType=self.config.overscanFitType,
743 order=self.config.overscanOrder,
744 collapseRej=self.config.overscanRej,
748 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners
750 \param[in,out] ccdExposure exposure to process
751 \param[in] fpPolygon Polygon in focal plane coordinates
754 ccd = ccdExposure.getDetector()
755 fpCorners = ccd.getCorners(FOCAL_PLANE)
756 ccdPolygon = Polygon(fpCorners)
759 intersect = ccdPolygon.intersectionSingle(fpPolygon)
762 ccdPoints = [ccd.transform(ccd.makeCameraPoint(x, FOCAL_PLANE), PIXELS).getPoint()
for x
in intersect]
763 validPolygon = Polygon(ccdPoints)
764 ccdExposure.getInfo().setValidPolygon(validPolygon)
767 """Apply brighter fatter correction in place for the image
769 This correction takes a kernel that has been derived from flat field images to
770 redistribute the charge. The gradient of the kernel is the deflection
771 field due to the accumulated charge.
773 Given the original image I(x) and the kernel K(x) we can compute the corrected image Ic(x)
774 using the following equation:
776 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
778 To evaluate the derivative term we expand it as follows:
780 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))) )
782 Because we use the measured counts instead of the incident counts we apply the correction
783 iteratively to reconstruct the original counts and the correction. We stop iterating when the
784 summed difference between the current corrected image and the one from the previous iteration
785 is below the threshold. We do not require convergence because the number of iterations is
786 too large a computational cost. How we define the threshold still needs to be evaluated, the
787 current default was shown to work reasonably well on a small set of images. For more information
788 on the method see DocuShare Document-19407.
790 The edges as defined by the kernel are not corrected because they have spurious values
791 due to the convolution.
793 self.log.info(
"Applying brighter fatter correction")
795 image = exposure.getMaskedImage().getImage()
800 kLx = numpy.shape(kernel)[0]
801 kLy = numpy.shape(kernel)[1]
802 kernelImage = afwImage.ImageD(kLx, kLy)
803 kernelImage.getArray()[:, :] = kernel
804 tempImage = image.clone()
806 nanIndex = numpy.isnan(tempImage.getArray())
807 tempImage.getArray()[nanIndex] = 0.
809 outImage = afwImage.ImageF(image.getDimensions())
810 corr = numpy.zeros_like(image.getArray())
811 prev_image = numpy.zeros_like(image.getArray())
812 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
813 fixedKernel = afwMath.FixedKernel(kernelImage)
823 for iteration
in range(maxIter):
825 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
826 tmpArray = tempImage.getArray()
827 outArray = outImage.getArray()
830 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
831 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
832 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
835 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
836 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
837 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
839 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
841 tmpArray[:, :] = image.getArray()[:, :]
842 tmpArray[nanIndex] = 0.
843 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
846 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
850 prev_image[:, :] = tmpArray[:, :]
852 if iteration == maxIter - 1:
853 self.log.warn(
"Brighter fatter correction did not converge, final difference %f" % diff)
855 self.log.info(
"Finished brighter fatter in %d iterations" % (iteration + 1))
856 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
857 corr[startY + 1:endY - 1, startX + 1:endX - 1]
862 """Context manager that applies and removes gain
865 ccd = exp.getDetector()
867 sim = image.Factory(image, amp.getBBox())
874 ccd = exp.getDetector()
876 sim = image.Factory(image, amp.getBBox())
881 """A Detector-like object that supports returning gain and saturation level"""
884 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
886 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.