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 fluxMag0T1 = pexConfig.Field(
168 doc=
"The approximate flux of a zero-magnitude object in a one-second exposure",
171 keysToRemoveFromAssembledCcd = pexConfig.ListField(
173 doc=
"fields to remove from the metadata of the assembled ccd.",
176 doAssembleIsrExposures = pexConfig.Field(
179 doc=
"Assemble amp-level calibration exposures into ccd-level exposure?"
181 doAssembleCcd = pexConfig.Field(
184 doc=
"Assemble amp-level exposures into a ccd-level exposure?"
186 doLinearize = pexConfig.Field(
188 doc=
"Correct for nonlinearity of the detector's response?",
191 doBrighterFatter = pexConfig.Field(
194 doc=
"Apply the brighter fatter correction"
196 brighterFatterKernelFile = pexConfig.Field(
199 doc=
"Kernel file used for the brighter fatter correction"
201 brighterFatterMaxIter = pexConfig.Field(
204 doc=
"Maximum number of iterations for the brighter fatter correction"
206 brighterFatterThreshold = pexConfig.Field(
209 doc=
"Threshold used to stop iterating the brighter fatter correction. It is the "
210 " absolute value of the difference between the current corrected image and the one"
211 " from the previous iteration summed over all the pixels."
213 brighterFatterApplyGain = pexConfig.Field(
216 doc=
"Should the gain be applied when applying the brighter fatter correction?"
218 datasetType = pexConfig.Field(
220 doc=
"Dataset type for input data; users will typically leave this alone, "
221 "but camera-specific ISR tasks will override it",
224 fallbackFilterName = pexConfig.Field(dtype=str,
225 doc=
"Fallback default filter name for calibrations", optional=
True)
239 \brief Apply common instrument signature correction algorithms to a raw frame.
241 \section ip_isr_isr_Contents Contents
243 - \ref ip_isr_isr_Purpose
244 - \ref ip_isr_isr_Initialize
246 - \ref ip_isr_isr_Config
247 - \ref ip_isr_isr_Debug
250 \section ip_isr_isr_Purpose Description
252 The process for correcting imaging data is very similar from camera to camera.
253 This task provides a vanilla implementation of doing these corrections, including
254 the ability to turn certain corrections off if they are not needed.
255 The inputs to the primary method, run, are a raw exposure to be corrected and the
256 calibration data products. The raw input is a single chip sized mosaic of all amps
257 including overscans and other non-science pixels.
258 The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask
259 and takes as input only a daf.persistence.butlerSubset.ButlerDataRef.
260 This task may not meet all needs and it is expected that it will be subclassed for
261 specific applications.
263 \section ip_isr_isr_Initialize Task initialization
265 \copydoc \_\_init\_\_
267 \section ip_isr_isr_IO Inputs/Outputs to the run method
271 \section ip_isr_isr_Config Configuration parameters
273 See \ref IsrTaskConfig
275 \section ip_isr_isr_Debug Debug variables
277 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
278 flag \c --debug, \c -d to import \b debug.py from your \c PYTHONPATH; see <a
279 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
280 Using lsstDebug to control debugging output</a> for more about \b debug.py files.
282 The available variables in IsrTask are:
285 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
288 <DD> display exposure after ISR has been applied
292 For example, put something like
296 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
297 if name == "lsst.ip.isrFunctions.isrTask":
298 di.display = {'postISRCCD':2}
300 lsstDebug.Info = DebugInfo
302 into your debug.py file and run the commandline task with the \c --debug flag.
306 ConfigClass = IsrTaskConfig
310 '''!Constructor for IsrTask
311 \param[in] *args -- a list of positional arguments passed on to the Task constructor
312 \param[in] **kwargs -- a dictionary of keyword arguments passed on to the Task constructor
313 Call the lsst.pipe.base.task.Task.__init__ method
314 Then setup the assembly and fringe correction subtasks
316 pipeBase.Task.__init__(self, *args, **kwargs)
317 self.makeSubtask(
"assembleCcd")
318 self.makeSubtask(
"fringe")
321 """!Retrieve necessary frames for instrument signature removal
322 \param[in] dataRef -- a daf.persistence.butlerSubset.ButlerDataRef
323 of the detector data to be processed
324 \param[in] rawExposure -- a reference raw exposure that will later be
325 corrected with the retrieved calibration data;
326 should not be modified in this method.
327 \return a pipeBase.Struct with fields containing kwargs expected by run()
328 - bias: exposure of bias frame
329 - dark: exposure of dark frame
330 - flat: exposure of flat field
331 - defects: list of detects
332 - fringeStruct: a pipeBase.Struct with field fringes containing
333 exposure of fringe frame or list of fringe exposure
335 ccd = rawExposure.getDetector()
337 biasExposure = self.
getIsrExposure(dataRef,
"bias")
if self.config.doBias
else None
339 linearizer = dataRef.get(
"linearizer", immediate=
True)
if self.
doLinearize(ccd)
else None
340 darkExposure = self.
getIsrExposure(dataRef,
"dark")
if self.config.doDark
else None
341 flatExposure = self.
getIsrExposure(dataRef,
"flat")
if self.config.doFlat
else None
342 brighterFatterKernel = dataRef.get(
"brighterFatterKernel")
if self.config.doBrighterFatter
else None
343 defectList = dataRef.get(
"defects")
if self.config.doDefect
else None
345 if self.config.doFringe
and self.fringe.checkFilter(rawExposure):
346 fringeStruct = self.fringe.readFringes(dataRef, assembler=self.assembleCcd
347 if self.config.doAssembleIsrExposures
else None)
349 fringeStruct = pipeBase.Struct(fringes=
None)
352 return pipeBase.Struct(bias=biasExposure,
353 linearizer=linearizer,
357 fringes=fringeStruct,
358 bfKernel=brighterFatterKernel
362 def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
363 fringes=
None, bfKernel=
None):
364 """!Perform instrument signature removal on an exposure
367 - Detect saturation, apply overscan correction, bias, dark and flat
368 - Perform CCD assembly
369 - Interpolate over defects, saturated pixels and all NaNs
371 \param[in] ccdExposure -- lsst.afw.image.exposure of detector data
372 \param[in] bias -- exposure of bias frame
373 \param[in] linearizer -- linearizing functor; a subclass of lsst.ip.isrFunctions.LinearizeBase
374 \param[in] dark -- exposure of dark frame
375 \param[in] flat -- exposure of flatfield
376 \param[in] defects -- list of detects
377 \param[in] fringes -- a pipeBase.Struct with field fringes containing
378 exposure of fringe frame or list of fringe exposure
379 \param[in] bfKernel -- kernel for brighter-fatter correction
381 \return a pipeBase.Struct with field:
386 if isinstance(ccdExposure, ButlerDataRef):
389 ccd = ccdExposure.getDetector()
392 if self.config.doBias
and bias
is None:
393 raise RuntimeError(
"Must supply a bias exposure if config.doBias True")
395 raise RuntimeError(
"Must supply a linearizer if config.doBias True")
396 if self.config.doDark
and dark
is None:
397 raise RuntimeError(
"Must supply a dark exposure if config.doDark True")
398 if self.config.doFlat
and flat
is None:
399 raise RuntimeError(
"Must supply a flat exposure if config.doFlat True")
400 if self.config.doBrighterFatter
and bfKernel
is None:
401 raise RuntimeError(
"Must supply a kernel if config.doBrighterFatter True")
403 fringes = pipeBase.Struct(fringes=
None)
404 if self.config.doFringe
and not isinstance(fringes, pipeBase.Struct):
405 raise RuntimeError(
"Must supply fringe exposure as a pipeBase.Struct")
406 if self.config.doDefect
and defects
is None:
407 raise RuntimeError(
"Must supply defects if config.doDefect True")
412 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd"
413 ccd = [
FakeAmp(ccdExposure, self.config)]
417 if ccdExposure.getBBox().contains(amp.getBBox()):
422 if self.config.doAssembleCcd:
423 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
425 if self.config.doBias:
429 linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
433 if ccdExposure.getBBox().contains(amp.getBBox()):
434 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
437 if self.config.doBrighterFatter:
439 self.config.brighterFatterMaxIter,
440 self.config.brighterFatterThreshold,
441 self.config.brighterFatterApplyGain,
444 if self.config.doDark:
447 if self.config.doFringe
and not self.config.fringeAfterFlat:
448 self.fringe.run(ccdExposure, **fringes.getDict())
450 if self.config.doFlat:
453 if self.config.doDefect:
460 if self.config.doFringe
and self.config.fringeAfterFlat:
461 self.fringe.run(ccdExposure, **fringes.getDict())
463 exposureTime = ccdExposure.getInfo().getVisitInfo().getExposureTime()
464 ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1*exposureTime)
466 frame = getDebugFrame(self._display,
"postISRCCD")
468 getDisplay(frame).mtv(ccdExposure)
470 return pipeBase.Struct(
471 exposure=ccdExposure,
476 """!Perform instrument signature removal on a ButlerDataRef of a Sensor
478 - Read in necessary detrending/isr/calibration data
479 - Process raw exposure in run()
480 - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
482 \param[in] sensorRef -- daf.persistence.butlerSubset.ButlerDataRef of the
483 detector data to be processed
484 \return a pipeBase.Struct with fields:
485 - exposure: the exposure after application of ISR
487 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
488 ccdExposure = sensorRef.get(
'raw')
491 result = self.
run(ccdExposure, **isrData.getDict())
493 if self.config.doWrite:
494 sensorRef.put(result.exposure,
"postISRCCD")
499 """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
501 if isinstance(exposure, afwImage.ExposureF):
504 if not hasattr(exposure,
"convertF"):
505 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
507 newexposure = exposure.convertF()
508 maskedImage = newexposure.getMaskedImage()
509 varArray = maskedImage.getVariance().getArray()
511 maskArray = maskedImage.getMask().getArray()
516 """!Apply bias correction in place
518 \param[in,out] exposure exposure to process
519 \param[in] biasExposure bias exposure of same size as exposure
521 isrFunctions.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
524 """!Apply dark correction in place
526 \param[in,out] exposure exposure to process
527 \param[in] darkExposure dark exposure of same size as exposure
529 expScale = exposure.getInfo().getVisitInfo().getDarkTime()
530 if math.isnan(expScale):
531 raise RuntimeError(
"Exposure darktime is NAN")
532 darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
533 if math.isnan(darkScale):
534 raise RuntimeError(
"Dark calib darktime is NAN")
535 isrFunctions.darkCorrection(
536 maskedImage=exposure.getMaskedImage(),
537 darkMaskedImage=darkExposure.getMaskedImage(),
543 """!Is linearization wanted for this detector?
545 Checks config.doLinearize and the linearity type of the first amplifier.
547 \param[in] detector detector information (an lsst.afw.cameraGeom.Detector)
549 return self.config.doLinearize
and \
550 detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
553 """!Set the variance plane based on the image plane, plus amplifier gain and read noise
555 \param[in,out] ampExposure exposure to process
556 \param[in] amp amplifier detector information
558 if not math.isnan(amp.getGain()):
559 isrFunctions.updateVariance(
560 maskedImage=ampExposure.getMaskedImage(),
562 readNoise=amp.getReadNoise(),
566 """!Apply flat correction in place
568 \param[in,out] exposure exposure to process
569 \param[in] flatExposure flatfield exposure same size as exposure
571 isrFunctions.flatCorrection(
572 maskedImage=exposure.getMaskedImage(),
573 flatMaskedImage=flatExposure.getMaskedImage(),
574 scalingType=self.config.flatScalingType,
575 userScale=self.config.flatUserScale,
579 """!Retrieve a calibration dataset for removing instrument signature
581 \param[in] dataRef data reference for exposure
582 \param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat')
583 \param[in] immediate if True, disable butler proxies to enable error
584 handling within this routine
588 exp = dataRef.get(datasetType, immediate=immediate)
589 except Exception
as exc1:
590 if not self.config.fallbackFilterName:
591 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
593 exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
594 except Exception
as exc2:
595 raise RuntimeError(
"Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
596 (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
597 self.log.warn(
"Using fallback calibration from filter %s" % self.config.fallbackFilterName)
599 if self.config.doAssembleIsrExposures:
600 exp = self.assembleCcd.assembleCcd(exp)
604 """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place
606 \param[in,out] exposure exposure to process; only the amp DataSec is processed
607 \param[in] amp amplifier device data
609 if not math.isnan(amp.getSaturation()):
610 maskedImage = exposure.getMaskedImage()
611 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
612 isrFunctions.makeThresholdMask(
613 maskedImage=dataView,
614 threshold=amp.getSaturation(),
616 maskName=self.config.saturatedMaskName,
620 """!Interpolate over saturated pixels, in place
622 \param[in,out] ccdExposure exposure to process
625 - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
626 - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
628 isrFunctions.interpolateFromMask(
629 maskedImage=ccdExposure.getMaskedImage(),
630 fwhm=self.config.fwhm,
631 growFootprints=self.config.growSaturationFootprintSize,
632 maskName=self.config.saturatedMaskName,
636 """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place
638 Suspect pixels are pixels whose value is greater than amp.getSuspectLevel().
639 This is intended to indicate pixels that may be affected by unknown systematics;
640 for example if non-linearity corrections above a certain level are unstable
641 then that would be a useful value for suspectLevel. A value of `nan` indicates
642 that no such level exists and no pixels are to be masked as suspicious.
644 \param[in,out] exposure exposure to process; only the amp DataSec is processed
645 \param[in] amp amplifier device data
647 suspectLevel = amp.getSuspectLevel()
648 if math.isnan(suspectLevel):
651 maskedImage = exposure.getMaskedImage()
652 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
653 isrFunctions.makeThresholdMask(
654 maskedImage=dataView,
655 threshold=suspectLevel,
657 maskName=self.config.suspectMaskName,
661 """!Mask defects using mask plane "BAD" and interpolate over them, in place
663 \param[in,out] ccdExposure exposure to process
664 \param[in] defectBaseList a list of defects to mask and interpolate
666 \warning: call this after CCD assembly, since defects may cross amplifier boundaries
668 maskedImage = ccdExposure.getMaskedImage()
670 for d
in defectBaseList:
672 nd = measAlg.Defect(bbox)
673 defectList.append(nd)
674 isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
675 isrFunctions.interpolateDefectList(
676 maskedImage=maskedImage,
677 defectList=defectList,
678 fwhm=self.config.fwhm,
682 """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
684 We mask and interpolate over all NaNs, including those
685 that are masked with other bits (because those may or may
686 not be interpolated over later, and we want to remove all
687 NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
688 is used to preserve the historical name.
690 \param[in,out] exposure exposure to process
692 maskedImage = exposure.getMaskedImage()
695 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
696 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
697 numNans =
maskNans(maskedImage, maskVal)
698 self.metadata.set(
"NUMNANS", numNans)
702 self.log.warn(
"There were %i unmasked NaNs", numNans)
703 nanDefectList = isrFunctions.getDefectListFromMask(
704 maskedImage=maskedImage,
705 maskName=
'UNMASKEDNAN',
708 isrFunctions.interpolateDefectList(
709 maskedImage=exposure.getMaskedImage(),
710 defectList=nanDefectList,
711 fwhm=self.config.fwhm,
715 """!Apply overscan correction, in place
717 \param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels
718 \param[in] amp amplifier device data
720 if not amp.getHasRawInfo():
721 raise RuntimeError(
"This method must be executed on an amp with raw information.")
723 if amp.getRawHorizontalOverscanBBox().isEmpty():
724 self.log.info(
"No Overscan region. Not performing Overscan Correction.")
727 maskedImage = exposure.getMaskedImage()
728 dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
730 expImage = exposure.getMaskedImage().getImage()
731 overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
733 isrFunctions.overscanCorrection(
734 ampMaskedImage=dataView,
735 overscanImage=overscanImage,
736 fitType=self.config.overscanFitType,
737 order=self.config.overscanOrder,
738 collapseRej=self.config.overscanRej,
742 """!Set the valid polygon as the intersection of fpPolygon and the ccd corners
744 \param[in,out] ccdExposure exposure to process
745 \param[in] fpPolygon Polygon in focal plane coordinates
748 ccd = ccdExposure.getDetector()
749 fpCorners = ccd.getCorners(FOCAL_PLANE)
750 ccdPolygon = Polygon(fpCorners)
753 intersect = ccdPolygon.intersectionSingle(fpPolygon)
756 ccdPoints = [ccd.transform(ccd.makeCameraPoint(x, FOCAL_PLANE), PIXELS).getPoint()
for x
in intersect]
757 validPolygon = Polygon(ccdPoints)
758 ccdExposure.getInfo().setValidPolygon(validPolygon)
761 """Apply brighter fatter correction in place for the image
763 This correction takes a kernel that has been derived from flat field images to
764 redistribute the charge. The gradient of the kernel is the deflection
765 field due to the accumulated charge.
767 Given the original image I(x) and the kernel K(x) we can compute the corrected image Ic(x)
768 using the following equation:
770 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
772 To evaluate the derivative term we expand it as follows:
774 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))) )
776 Because we use the measured counts instead of the incident counts we apply the correction
777 iteratively to reconstruct the original counts and the correction. We stop iterating when the
778 summed difference between the current corrected image and the one from the previous iteration
779 is below the threshold. We do not require convergence because the number of iterations is
780 too large a computational cost. How we define the threshold still needs to be evaluated, the
781 current default was shown to work reasonably well on a small set of images. For more information
782 on the method see DocuShare Document-19407.
784 The edges as defined by the kernel are not corrected because they have spurious values
785 due to the convolution.
787 self.log.info(
"Applying brighter fatter correction")
789 image = exposure.getMaskedImage().getImage()
794 kLx = numpy.shape(kernel)[0]
795 kLy = numpy.shape(kernel)[1]
796 kernelImage = afwImage.ImageD(kLx, kLy)
797 kernelImage.getArray()[:, :] = kernel
798 tempImage = image.clone()
800 nanIndex = numpy.isnan(tempImage.getArray())
801 tempImage.getArray()[nanIndex] = 0.
803 outImage = afwImage.ImageF(image.getDimensions())
804 corr = numpy.zeros_like(image.getArray())
805 prev_image = numpy.zeros_like(image.getArray())
806 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
807 fixedKernel = afwMath.FixedKernel(kernelImage)
817 for iteration
in range(maxIter):
819 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
820 tmpArray = tempImage.getArray()
821 outArray = outImage.getArray()
824 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
825 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
826 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
829 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
830 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
831 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
833 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
835 tmpArray[:, :] = image.getArray()[:, :]
836 tmpArray[nanIndex] = 0.
837 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
840 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
844 prev_image[:, :] = tmpArray[:, :]
846 if iteration == maxIter - 1:
847 self.log.warn(
"Brighter fatter correction did not converge, final difference %f" % diff)
849 self.log.info(
"Finished brighter fatter in %d iterations" % (iteration + 1))
850 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
851 corr[startY + 1:endY - 1, startX + 1:endX - 1]
856 """Context manager that applies and removes gain
859 ccd = exp.getDetector()
861 sim = image.Factory(image, amp.getBBox())
868 ccd = exp.getDetector()
870 sim = image.Factory(image, amp.getBBox())
875 """A Detector-like object that supports returning gain and saturation level"""
878 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
880 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.