25 "attachTransmissionCurve",
27 "brighterFatterCorrection",
29 "compareCameraKeywords",
34 "fluxConservingBrighterFatterCorrection",
41 "illuminationCorrection",
42 "interpolateDefectList",
43 "interpolateFromMask",
45 "saturationCorrection",
48 "transposeMaskedImage",
49 "trimToMatchCalibBBox",
51 "widenSaturationTrails",
53 "getExposureReadNoises",
61import lsst.afw.image
as afwImage
70from contextlib
import contextmanager
72from .defects
import Defects
76 """Make a double Gaussian PSF.
81 FWHM of double Gaussian smoothing kernel.
85 psf : `lsst.meas.algorithms.DoubleGaussianPsf`
86 The created smoothing kernel.
88 ksize = 4*int(fwhm) + 1
89 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
93 """Make a transposed copy of a masked image.
97 maskedImage : `lsst.afw.image.MaskedImage`
102 transposed : `lsst.afw.image.MaskedImage`
103 The transposed copy of the input image.
105 transposed = maskedImage.Factory(
lsst.geom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
106 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
107 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
108 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
113 maskNameList=None, useLegacyInterp=True):
114 """Interpolate over defects specified in a defect list.
118 maskedImage : `lsst.afw.image.MaskedImage`
120 defectList : `lsst.meas.algorithms.Defects`
121 List of defects to interpolate over.
123 FWHM of double Gaussian smoothing kernel.
124 fallbackValue : scalar, optional
125 Fallback value if an interpolated value cannot be determined.
126 If None, then the clipped mean of the image is used.
127 maskNameList : `list [string]`
128 List of the defects to interpolate over (used for GP interpolator).
129 useLegacyInterp : `bool`
130 Use the legacy interpolation (polynomial interpolation) if True. Use
131 Gaussian Process interpolation if False.
135 The ``fwhm`` parameter is used to create a PSF, but the underlying
136 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
137 not currently make use of this information in legacy Interpolation, but use
138 if for the Gaussian Process as an estimation of the correlation lenght.
141 if fallbackValue
is None:
143 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
144 maskedImage.getMask().addMaskPlane(
'INTRP')
155 kwargs = {
"bin_spacing": 20,
156 "threshold_dynamic_binning": 2000,
157 "threshold_subdivide": 20000}
160 measAlg.interpolateOverDefects(maskedImage, psf, defectList,
161 fallbackValue=fallbackValue,
162 useFallbackValueAtEdge=
True,
164 useLegacyInterp=useLegacyInterp,
165 maskNameList=maskNameList, **kwargs)
170 """Mask pixels based on threshold detection.
174 maskedImage : `lsst.afw.image.MaskedImage`
175 Image to process. Only the mask plane is updated.
178 growFootprints : scalar, optional
179 Number of pixels to grow footprints of detected regions.
180 maskName : str, optional
181 Mask plane name, or list of names to convert
185 defectList : `lsst.meas.algorithms.Defects`
186 Defect list constructed from pixels above the threshold.
189 thresh = afwDetection.Threshold(threshold)
190 fs = afwDetection.FootprintSet(maskedImage, thresh)
192 if growFootprints > 0:
193 fs = afwDetection.FootprintSet(fs, rGrow=growFootprints, isotropic=
False)
194 fpList = fs.getFootprints()
197 mask = maskedImage.getMask()
198 bitmask = mask.getPlaneBitMask(maskName)
199 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
201 return Defects.fromFootprintList(fpList)
204def growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD"):
205 """Grow a mask by an amount and add to the requested plane.
209 mask : `lsst.afw.image.Mask`
210 Mask image to process.
212 Amount to grow the mask.
213 maskNameList : `str` or `list` [`str`]
214 Mask names that should be grown.
216 Mask plane to assign the newly masked pixels to.
219 spans = SpanSet.fromMask(mask, mask.getPlaneBitMask(maskNameList))
222 spans = spans.dilated(radius, Stencil.MANHATTAN)
223 spans = spans.clippedTo(mask.getBBox())
224 spans.setMask(mask, mask.getPlaneBitMask(maskValue))
204def growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD"):
…
228 fpCore, itlEdgeBleedSatMinArea=10000,
229 itlEdgeBleedSatMaxArea=100000,
230 itlEdgeBleedThreshold=5000.,
231 itlEdgeBleedModelConstant=0.02,
232 saturatedMaskName="SAT", log=None):
233 """Mask edge bleeds in ITL detectors.
237 ccdExposure : `lsst.afw.image.Exposure`
238 Exposure to apply masking to.
239 badAmpDict : `dict` [`str`, `bool`]
240 Dictionary of amplifiers, keyed by name, value is True if
241 amplifier is fully masked.
242 fpCore : `lsst.afw.detection._detection.Footprint`
243 Footprint of saturated core.
244 itlEdgeBleedThreshold : `float`, optional
245 Threshold above median sky background for edge bleed detection
247 itlEdgeBleedModelConstant : `float`, optional
248 Constant in the decaying exponential in the edge bleed masking.
249 saturatedMaskName : `str`, optional
250 Mask name for saturation.
251 log : `logging.Logger`, optional
252 Logger to handle messages.
255 log = log
if log
else logging.getLogger(__name__)
258 satLevel = numpy.nanmedian([ccdExposure.metadata[f
"LSST ISR SATURATION LEVEL {amp.getName()}"]
259 for amp
in ccdExposure.getDetector()
if not badAmpDict[amp.getName()]])
263 xCore, yCore = fpCore.getCentroid()
265 yCoreFP = int(yCore) - fpCore.getBBox().getMinY()
269 checkCoreNbRow = fpCore.getSpans().asArray()[yCoreFP, :]
272 indexSwitchFalse = []
273 if checkCoreNbRow[0]:
277 indexSwitchTrue.append(0)
282 for i, value
in enumerate(checkCoreNbRow):
285 indexSwitchTrue.append(i)
290 indexSwitchFalse.append(i)
298 xEdgesCores.append(int((indexSwitchTrue[1] + indexSwitchFalse[0])/2))
299 xEdgesCores.append(fpCore.getSpans().asArray().shape[1])
301 for i
in range(nbCore):
302 subfp = fpCore.getSpans().asArray()[:, xEdgesCores[i]:xEdgesCores[i+1]]
303 xCoreFP = int(xEdgesCores[i] + numpy.argmax(numpy.sum(subfp, axis=0)))
305 xCore = xCoreFP + fpCore.getBBox().getMinX()
308 if subfp.shape[0] <= 200:
309 yCoreFP = int(numpy.argmax(numpy.sum(subfp, axis=1)))
311 yCoreFP = int(numpy.argmax(numpy.sum(subfp[100:-100, :],
313 yCoreFP = 100+yCoreFP
316 widthSat = numpy.sum(subfp[int(yCoreFP), :])
318 subfpArea = numpy.sum(subfp)
319 if subfpArea > itlEdgeBleedSatMinArea
and subfpArea < itlEdgeBleedSatMaxArea:
322 itlEdgeBleedThreshold,
323 itlEdgeBleedModelConstant,
324 saturatedMaskName, log)
328 "Too many (%d) cores in saturated footprint to mask edge bleeds.",
333 xCore, yCore = fpCore.getCentroid()
335 yCoreFP = yCore - fpCore.getBBox().getMinY()
337 widthSat = numpy.sum(fpCore.getSpans().asArray()[int(yCoreFP), :])
339 satLevel, widthSat, itlEdgeBleedThreshold,
340 itlEdgeBleedModelConstant, saturatedMaskName, log)
345 itlEdgeBleedThreshold=5000.,
346 itlEdgeBleedModelConstant=0.03,
347 saturatedMaskName="SAT", log=None):
348 """Apply ITL edge bleed masking model.
352 ccdExposure : `lsst.afw.image.Exposure`
353 Exposure to apply masking to.
355 X coordinate of the saturated core.
357 Minimum saturation level of the detector.
359 Width of the saturated core.
360 itlEdgeBleedThreshold : `float`, optional
361 Threshold above median sky background for edge bleed detection
363 itlEdgeBleedModelConstant : `float`, optional
364 Constant in the decaying exponential in the edge bleed masking.
365 saturatedMaskName : `str`, optional
366 Mask name for saturation.
367 log : `logging.Logger`, optional
368 Logger to handle messages.
370 log = log
if log
else logging.getLogger(__name__)
372 maskedImage = ccdExposure.maskedImage
373 xmax = maskedImage.image.array.shape[1]
374 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
376 for amp
in ccdExposure.getDetector():
382 yBox = amp.getBBox().getCenter()[1]
383 if amp.getBBox().contains(xCore, yBox):
386 ampName = amp.getName()
396 if ampName[:2] ==
'C1':
397 sliceImage = maskedImage.image.array[:200, :]
398 sliceMask = maskedImage.mask.array[:200, :]
399 elif ampName[:2] ==
'C0':
400 sliceImage = numpy.flipud(maskedImage.image.array[-200:, :])
401 sliceMask = numpy.flipud(maskedImage.mask.array[-200:, :])
413 lowerRangeSmall = int(xCore)-5
414 upperRangeSmall = int(xCore)+5
415 if lowerRangeSmall < 0:
417 if upperRangeSmall > xmax:
418 upperRangeSmall = xmax
419 ampImageBG = numpy.median(maskedImage[amp.getBBox()].image.array)
420 edgeMedian = numpy.median(sliceImage[:50, lowerRangeSmall:upperRangeSmall])
421 if edgeMedian > (ampImageBG + itlEdgeBleedThreshold):
423 log.info(
"Found edge bleed around column %d", xCore)
432 subImageXMin = int(xCore)-250
433 subImageXMax = int(xCore)+250
436 elif subImageXMax > xmax:
439 subImage = sliceImage[:100, subImageXMin:subImageXMax]
440 maxWidthEdgeBleed = numpy.max(numpy.sum(subImage > 0.45*satLevel,
445 edgeBleedHalfWidth = \
446 int(((maxWidthEdgeBleed)*numpy.exp(-itlEdgeBleedModelConstant*y)
448 lowerRange = int(xCore)-edgeBleedHalfWidth
449 upperRange = int(xCore)+edgeBleedHalfWidth
455 if upperRange > xmax:
457 sliceMask[y, lowerRange:upperRange] |= saturatedBit
461 """Mask columns presenting saturation sag in saturated footprints in
466 ccdExposure : `lsst.afw.image.Exposure`
467 Exposure to apply masking to.
468 fpCore : `lsst.afw.detection._detection.Footprint`
469 Footprint of saturated core.
470 saturatedMaskName : `str`, optional
471 Mask name for saturation.
476 maskedImage = ccdExposure.maskedImage
477 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
479 cc = numpy.sum(fpCore.getSpans().asArray(), axis=0)
482 columnsToMaskFP = numpy.where(cc > fpCore.getSpans().asArray().shape[0]/5.)
484 columnsToMask = [x + int(fpCore.getBBox().getMinX())
for x
in columnsToMaskFP]
485 maskedImage.mask.array[:, columnsToMask] |= saturatedBit
488def maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None):
489 """Add mask bits according to the ITL dip model.
493 exposure : `lsst.afw.image.Exposure`
494 Exposure to do ITL dip masking.
495 detectorConfig : `lsst.ip.isr.overscanAmpConfig.OverscanDetectorConfig`
496 Configuration for this detector.
497 maskPlaneNames : `list [`str`], optional
498 Name of the ITL Dip mask planes.
499 log : `logging.Logger`, optional
500 If not set, a default logger will be used.
502 if detectorConfig.itlDipBackgroundFraction == 0.0:
507 log = logging.getLogger(__name__)
509 thresh = afwDetection.Threshold(
510 exposure.mask.getPlaneBitMask(
"SAT"),
511 afwDetection.Threshold.BITMASK,
513 fpList = afwDetection.FootprintSet(exposure.mask, thresh).getFootprints()
515 heights = numpy.asarray([fp.getBBox().getHeight()
for fp
in fpList])
517 largeHeights, = numpy.where(heights >= detectorConfig.itlDipMinHeight)
519 if len(largeHeights) == 0:
523 approxBackground = numpy.median(exposure.image.array)
524 maskValue = exposure.mask.getPlaneBitMask(maskPlaneNames)
526 maskBak = exposure.mask.array.copy()
529 for index
in largeHeights:
531 center = fp.getCentroid()
533 nSat = numpy.sum(fp.getSpans().asArray(), axis=0)
534 width = numpy.sum(nSat > detectorConfig.itlDipMinHeight)
536 if width < detectorConfig.itlDipMinWidth:
539 width = numpy.clip(width,
None, detectorConfig.itlDipMaxWidth)
541 dipMax = detectorConfig.itlDipBackgroundFraction * approxBackground * width
544 if dipMax < detectorConfig.itlDipMinBackgroundNoiseFraction * numpy.sqrt(approxBackground):
547 minCol = int(center.getX() - (detectorConfig.itlDipWidthScale * width) / 2.)
548 maxCol = int(center.getX() + (detectorConfig.itlDipWidthScale * width) / 2.)
549 minCol = numpy.clip(minCol, 0,
None)
550 maxCol = numpy.clip(maxCol,
None, exposure.mask.array.shape[1] - 1)
553 "Found ITL dip (width %d; bkg %.2f); masking column %d to %d",
560 exposure.mask.array[:, minCol: maxCol + 1] |= maskValue
562 nMaskedCols += (maxCol - minCol + 1)
564 if nMaskedCols > detectorConfig.itlDipMaxColsPerImage:
566 "Too many (%d) columns would be masked on this image from dip masking; restoring original mask.",
569 exposure.mask.array[:, :] = maskBak
488def maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None):
…
573 maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True):
574 """Interpolate over defects identified by a particular set of mask planes.
578 maskedImage : `lsst.afw.image.MaskedImage`
581 FWHM of double Gaussian smoothing kernel.
582 growSaturatedFootprints : scalar, optional
583 Number of pixels to grow footprints for saturated pixels.
584 maskNameList : `List` of `str`, optional
586 fallbackValue : scalar, optional
587 Value of last resort for interpolation.
591 The ``fwhm`` parameter is used to create a PSF, but the underlying
592 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
593 not currently make use of this information.
595 mask = maskedImage.getMask()
597 if growSaturatedFootprints > 0
and "SAT" in maskNameList:
601 growMasks(mask, radius=growSaturatedFootprints, maskNameList=[
'SAT'], maskValue=
"SAT")
603 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
604 fpSet = afwDetection.FootprintSet(mask, thresh)
605 defectList = Defects.fromFootprintList(fpSet.getFootprints())
608 maskNameList=maskNameList, useLegacyInterp=useLegacyInterp)
614 fallbackValue=None, useLegacyInterp=True):
615 """Mark saturated pixels and optionally interpolate over them
619 maskedImage : `lsst.afw.image.MaskedImage`
622 Saturation level used as the detection threshold.
624 FWHM of double Gaussian smoothing kernel.
625 growFootprints : scalar, optional
626 Number of pixels to grow footprints of detected regions.
627 interpolate : Bool, optional
628 If True, saturated pixels are interpolated over.
629 maskName : str, optional
631 fallbackValue : scalar, optional
632 Value of last resort for interpolation.
636 The ``fwhm`` parameter is used to create a PSF, but the underlying
637 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
638 not currently make use of this information.
641 maskedImage=maskedImage,
642 threshold=saturation,
643 growFootprints=growFootprints,
648 maskNameList=[maskName], useLegacyInterp=useLegacyInterp)
654 """Compute number of edge trim pixels to match the calibration data.
656 Use the dimension difference between the raw exposure and the
657 calibration exposure to compute the edge trim pixels. This trim
658 is applied symmetrically, with the same number of pixels masked on
663 rawMaskedImage : `lsst.afw.image.MaskedImage`
665 calibMaskedImage : `lsst.afw.image.MaskedImage`
666 Calibration image to draw new bounding box from.
670 replacementMaskedImage : `lsst.afw.image.MaskedImage`
671 ``rawMaskedImage`` trimmed to the appropriate size.
676 Raised if ``rawMaskedImage`` cannot be symmetrically trimmed to
677 match ``calibMaskedImage``.
679 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
681 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
683 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
685 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
689 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
690 SourceDetectionTask.setEdgeBits(
692 replacementMaskedImage.getBBox(),
693 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
696 replacementMaskedImage = rawMaskedImage
698 return replacementMaskedImage
702 """Apply bias correction in place.
706 maskedImage : `lsst.afw.image.MaskedImage`
707 Image to process. The image is modified by this method.
708 biasMaskedImage : `lsst.afw.image.MaskedImage`
709 Bias image of the same size as ``maskedImage``
710 trimToFit : `Bool`, optional
711 If True, raw data is symmetrically trimmed to match
717 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have
724 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
725 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
726 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
727 maskedImage -= biasMaskedImage
730def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
731 """Apply dark correction in place.
735 maskedImage : `lsst.afw.image.MaskedImage`
736 Image to process. The image is modified by this method.
737 darkMaskedImage : `lsst.afw.image.MaskedImage`
738 Dark image of the same size as ``maskedImage``.
740 Dark exposure time for ``maskedImage``.
742 Dark exposure time for ``darkMaskedImage``.
743 invert : `Bool`, optional
744 If True, re-add the dark to an already corrected image.
745 trimToFit : `Bool`, optional
746 If True, raw data is symmetrically trimmed to match
752 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have
757 The dark correction is applied by calculating:
758 maskedImage -= dark * expScaling / darkScaling
763 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
764 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
765 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
767 scale = expScale / darkScale
769 maskedImage.scaledMinus(scale, darkMaskedImage)
771 maskedImage.scaledPlus(scale, darkMaskedImage)
730def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
…
775 """Set the variance plane based on the image plane.
777 The maskedImage must have units of `adu` (if gain != 1.0) or
778 electron (if gain == 1.0). This routine will always produce a
779 variance plane in the same units as the image.
783 maskedImage : `lsst.afw.image.MaskedImage`
784 Image to process. The variance plane is modified.
786 The amplifier gain in electron/adu.
788 The amplifier read noise in electron/pixel.
789 replace : `bool`, optional
790 Replace the current variance? If False, the image
791 variance will be added to the current variance plane.
793 var = maskedImage.variance
795 var[:, :] = maskedImage.image
797 var[:, :] += maskedImage.image
799 var += (readNoise/gain)**2
802def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
803 """Apply flat correction in place.
807 maskedImage : `lsst.afw.image.MaskedImage`
808 Image to process. The image is modified.
809 flatMaskedImage : `lsst.afw.image.MaskedImage`
810 Flat image of the same size as ``maskedImage``
812 Flat scale computation method. Allowed values are 'MEAN',
814 userScale : scalar, optional
815 Scale to use if ``scalingType='USER'``.
816 invert : `Bool`, optional
817 If True, unflatten an already flattened image.
818 trimToFit : `Bool`, optional
819 If True, raw data is symmetrically trimmed to match
825 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have
826 the same size or if ``scalingType`` is not an allowed value.
831 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
832 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
833 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
839 if scalingType
in (
'MEAN',
'MEDIAN'):
842 elif scalingType ==
'USER':
843 flatScale = userScale
845 raise RuntimeError(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
848 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
850 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
802def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
…
854 """Apply illumination correction in place.
858 maskedImage : `lsst.afw.image.MaskedImage`
859 Image to process. The image is modified.
860 illumMaskedImage : `lsst.afw.image.MaskedImage`
861 Illumination correction image of the same size as ``maskedImage``.
863 Scale factor for the illumination correction.
864 trimToFit : `Bool`, optional
865 If True, raw data is symmetrically trimmed to match
871 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have
877 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
878 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
879 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
881 maskedImage.scaledDivides(1.0/illumScale, illumMaskedImage)
885 """Apply brighter fatter correction in place for the image.
889 exposure : `lsst.afw.image.Exposure`
890 Exposure to have brighter-fatter correction applied. Modified
892 kernel : `numpy.ndarray`
893 Brighter-fatter kernel to apply.
895 Number of correction iterations to run.
897 Convergence threshold in terms of the sum of absolute
898 deviations between an iteration and the previous one.
900 If True, then the exposure values are scaled by the gain prior
902 gains : `dict` [`str`, `float`]
903 A dictionary, keyed by amplifier name, of the gains to use.
904 If gains is None, the nominal gains in the amplifier object are used.
909 Final difference between iterations achieved in correction.
911 Number of iterations used to calculate correction.
915 This correction takes a kernel that has been derived from flat
916 field images to redistribute the charge. The gradient of the
917 kernel is the deflection field due to the accumulated charge.
919 Given the original image I(x) and the kernel K(x) we can compute
920 the corrected image Ic(x) using the following equation:
922 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
924 To evaluate the derivative term we expand it as follows:
926 0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y)))
927 + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )
929 Because we use the measured counts instead of the incident counts
930 we apply the correction iteratively to reconstruct the original
931 counts and the correction. We stop iterating when the summed
932 difference between the current corrected image and the one from
933 the previous iteration is below the threshold. We do not require
934 convergence because the number of iterations is too large a
935 computational cost. How we define the threshold still needs to be
936 evaluated, the current default was shown to work reasonably well
937 on a small set of images. For more information on the method see
938 DocuShare Document-19407.
940 The edges as defined by the kernel are not corrected because they
941 have spurious values due to the convolution.
943 image = exposure.getMaskedImage().getImage()
946 with gainContext(exposure, image, applyGain, gains):
948 kLx = numpy.shape(kernel)[0]
949 kLy = numpy.shape(kernel)[1]
950 kernelImage = afwImage.ImageD(kLx, kLy)
951 kernelImage.getArray()[:, :] = kernel
952 tempImage = afwImage.ImageD(image, deep=
True)
954 nanIndex = numpy.isnan(tempImage.getArray())
955 tempImage.getArray()[nanIndex] = 0.
957 outImage = afwImage.ImageD(image.getDimensions())
958 corr = numpy.zeros(image.array.shape, dtype=numpy.float64)
959 prev_image = numpy.zeros(image.array.shape, dtype=numpy.float64)
973 for iteration
in range(maxIter):
976 tmpArray = tempImage.getArray()
977 outArray = outImage.getArray()
979 with numpy.errstate(invalid=
"ignore", over=
"ignore"):
981 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
982 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
983 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
986 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
987 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
988 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
990 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
992 tmpArray[:, :] = image.getArray()[:, :]
993 tmpArray[nanIndex] = 0.
994 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
997 diff = numpy.sum(numpy.abs(prev_image - tmpArray), dtype=numpy.float64)
1001 prev_image[:, :] = tmpArray[:, :]
1003 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
1004 corr[startY + 1:endY - 1, startX + 1:endX - 1]
1006 return diff, iteration
1010 """Take the input convolved deflection potential and the flux array
1011 to compute and apply the flux transfer into the correction array.
1015 cFunc: `numpy.array`
1016 Deflection potential, being the convolution of the flux F with the
1018 fStep: `numpy.array`
1019 The array of flux values which act as the source of the flux transfer.
1020 correctionMode: `bool`
1021 Defines if applying correction (True) or generating sims (False).
1026 BFE correction array
1029 if cFunc.shape != fStep.shape:
1030 raise RuntimeError(f
'transferFlux: array shapes do not match: {cFunc.shape}, {fStep.shape}')
1042 corr = numpy.zeros(cFunc.shape, dtype=numpy.float64)
1045 yDim, xDim = cFunc.shape
1046 y = numpy.arange(yDim, dtype=int)
1047 x = numpy.arange(xDim, dtype=int)
1048 xc, yc = numpy.meshgrid(x, y)
1054 diff = numpy.diff(cFunc, axis=ax)
1057 gx = numpy.zeros(cFunc.shape, dtype=numpy.float64)
1058 yDiff, xDiff = diff.shape
1059 gx[:yDiff, :xDiff] += diff
1064 for i, sel
in enumerate([gx > 0, gx < 0]):
1065 xSelPixels = xc[sel]
1066 ySelPixels = yc[sel]
1080 flux = factor * fStep[sel]*gx[sel]
1083 flux = factor * fStep[yPix, xPix]*gx[sel]
1087 corr[yPix, xPix] += flux
1094 gains=None, correctionMode=True):
1095 """Apply brighter fatter correction in place for the image.
1097 This version presents a modified version of the algorithm
1098 found in ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``
1099 which conserves the image flux, resulting in improved
1100 correction of the cores of stars. The convolution has also been
1101 modified to mitigate edge effects.
1105 exposure : `lsst.afw.image.Exposure`
1106 Exposure to have brighter-fatter correction applied. Modified
1108 kernel : `numpy.ndarray`
1109 Brighter-fatter kernel to apply.
1111 Number of correction iterations to run.
1113 Convergence threshold in terms of the sum of absolute
1114 deviations between an iteration and the previous one.
1116 If True, then the exposure values are scaled by the gain prior
1118 gains : `dict` [`str`, `float`]
1119 A dictionary, keyed by amplifier name, of the gains to use.
1120 If gains is None, the nominal gains in the amplifier object are used.
1121 correctionMode : `Bool`
1122 If True (default) the function applies correction for BFE. If False,
1123 the code can instead be used to generate a simulation of BFE (sign
1124 change in the direction of the effect)
1129 Final difference between iterations achieved in correction.
1131 Number of iterations used to calculate correction.
1135 Modified version of ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``.
1137 This correction takes a kernel that has been derived from flat
1138 field images to redistribute the charge. The gradient of the
1139 kernel is the deflection field due to the accumulated charge.
1141 Given the original image I(x) and the kernel K(x) we can compute
1142 the corrected image Ic(x) using the following equation:
1144 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
1146 Improved algorithm at this step applies the divergence theorem to
1147 obtain a pixelised correction.
1149 Because we use the measured counts instead of the incident counts
1150 we apply the correction iteratively to reconstruct the original
1151 counts and the correction. We stop iterating when the summed
1152 difference between the current corrected image and the one from
1153 the previous iteration is below the threshold. We do not require
1154 convergence because the number of iterations is too large a
1155 computational cost. How we define the threshold still needs to be
1156 evaluated, the current default was shown to work reasonably well
1157 on a small set of images.
1159 Edges are handled in the convolution by padding. This is still not
1160 a physical model for the edge, but avoids discontinuity in the correction.
1162 Author of modified version: Lance.Miller@physics.ox.ac.uk
1165 image = exposure.getMaskedImage().getImage()
1168 with gainContext(exposure, image, applyGain, gains):
1171 kLy, kLx = kernel.shape
1172 kernelImage = afwImage.ImageD(kLx, kLy)
1173 kernelImage.getArray()[:, :] = kernel
1174 tempImage = afwImage.ImageD(image, deep=
True)
1176 nanIndex = numpy.isnan(tempImage.getArray())
1177 tempImage.getArray()[nanIndex] = 0.
1179 outImage = afwImage.ImageD(image.getDimensions())
1180 corr = numpy.zeros(image.array.shape, dtype=numpy.float64)
1181 prevImage = numpy.zeros(image.array.shape, dtype=numpy.float64)
1187 kLy = 2 * ((1+kLy)//2)
1188 kLx = 2 * ((1+kLx)//2)
1195 imYdimension, imXdimension = tempImage.array.shape
1196 imean = numpy.mean(tempImage.getArray()[~nanIndex], dtype=numpy.float64)
1199 tempImage.array[nanIndex] = 0.0
1200 padArray = numpy.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
1201 outImage = afwImage.ImageD(numpy.pad(outImage.getArray(), ((0, kLy), (0, kLx))))
1203 padImage = afwImage.ImageD(padArray.shape[1], padArray.shape[0])
1204 padImage.array[:] = padArray
1206 for iteration
in range(maxIter):
1211 tmpArray = tempImage.getArray()
1212 outArray = outImage.getArray()
1215 outArray = outArray[:imYdimension, :imXdimension]
1218 corr[...] =
transferFlux(outArray, tmpArray, correctionMode=correctionMode)
1221 tmpArray[:, :] = image.getArray()[:, :]
1223 tmpArray[nanIndex] = 0.
1227 tempImage.array[nanIndex] = 0.
1228 padArray = numpy.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
1231 diff = numpy.sum(numpy.abs(prevImage - tmpArray), dtype=numpy.float64)
1233 if diff < threshold:
1235 prevImage[:, :] = tmpArray[:, :]
1237 image.getArray()[:] += corr[:]
1239 return diff, iteration
1243def gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True):
1244 """Context manager that applies and removes gain.
1248 exp : `lsst.afw.image.Exposure`
1249 Exposure to apply/remove gain.
1250 image : `lsst.afw.image.Image`
1251 Image to apply/remove gain.
1253 If True, apply and remove the amplifier gain.
1254 gains : `dict` [`str`, `float`], optional
1255 A dictionary, keyed by amplifier name, of the gains to use.
1256 If gains is None, the nominal gains in the amplifier object are used.
1257 invert : `bool`, optional
1258 Invert the gains (e.g. convert electrons to adu temporarily)?
1259 isTrimmed : `bool`, optional
1260 Is this a trimmed exposure?
1264 exp : `lsst.afw.image.Exposure`
1265 Exposure with the gain applied.
1269 if gains
and apply
is True:
1270 ampNames = [amp.getName()
for amp
in exp.getDetector()]
1271 for ampName
in ampNames:
1272 if ampName
not in gains.keys():
1273 raise RuntimeError(f
"Gains provided to gain context, but no entry found for amp {ampName}")
1276 ccd = exp.getDetector()
1278 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
1280 gain = gains[amp.getName()]
1282 gain = amp.getGain()
1292 ccd = exp.getDetector()
1294 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
1296 gain = gains[amp.getName()]
1298 gain = amp.getGain()
1243def gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True):
…
1306 sensorTransmission=None, atmosphereTransmission=None):
1307 """Attach a TransmissionCurve to an Exposure, given separate curves for
1308 different components.
1312 exposure : `lsst.afw.image.Exposure`
1313 Exposure object to modify by attaching the product of all given
1314 ``TransmissionCurves`` in post-assembly trimmed detector coordinates.
1315 Must have a valid ``Detector`` attached that matches the detector
1316 associated with sensorTransmission.
1317 opticsTransmission : `lsst.afw.image.TransmissionCurve`
1318 A ``TransmissionCurve`` that represents the throughput of the optics,
1319 to be evaluated in focal-plane coordinates.
1320 filterTransmission : `lsst.afw.image.TransmissionCurve`
1321 A ``TransmissionCurve`` that represents the throughput of the filter
1322 itself, to be evaluated in focal-plane coordinates.
1323 sensorTransmission : `lsst.afw.image.TransmissionCurve`
1324 A ``TransmissionCurve`` that represents the throughput of the sensor
1325 itself, to be evaluated in post-assembly trimmed detector coordinates.
1326 atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
1327 A ``TransmissionCurve`` that represents the throughput of the
1328 atmosphere, assumed to be spatially constant.
1332 combined : `lsst.afw.image.TransmissionCurve`
1333 The TransmissionCurve attached to the exposure.
1337 All ``TransmissionCurve`` arguments are optional; if none are provided, the
1338 attached ``TransmissionCurve`` will have unit transmission everywhere.
1340 combined = afwImage.TransmissionCurve.makeIdentity()
1341 if atmosphereTransmission
is not None:
1342 combined *= atmosphereTransmission
1343 if opticsTransmission
is not None:
1344 combined *= opticsTransmission
1345 if filterTransmission
is not None:
1346 combined *= filterTransmission
1347 detector = exposure.getDetector()
1348 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
1349 toSys=camGeom.PIXELS)
1350 combined = combined.transformedBy(fpToPix)
1351 if sensorTransmission
is not None:
1352 combined *= sensorTransmission
1353 exposure.getInfo().setTransmissionCurve(combined)
1357def applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True):
1358 """Scale an exposure by the amplifier gains.
1362 exposure : `lsst.afw.image.Exposure`
1363 Exposure to process. The image is modified.
1364 normalizeGains : `Bool`, optional
1365 If True, then amplifiers are scaled to force the median of
1366 each amplifier to equal the median of those medians.
1367 ptcGains : `dict`[`str`], optional
1368 Dictionary keyed by amp name containing the PTC gains.
1369 isTrimmed : `bool`, optional
1370 Is the input image trimmed?
1372 ccd = exposure.getDetector()
1373 ccdImage = exposure.getMaskedImage()
1378 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1380 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1382 sim *= ptcGains[amp.getName()]
1384 sim *= amp.getGain()
1387 medians.append(numpy.median(sim.getImage().getArray()))
1390 median = numpy.median(numpy.array(medians))
1391 for index, amp
in enumerate(ccd):
1393 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1395 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1396 if medians[index] != 0.0:
1397 sim *= median/medians[index]
1357def applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True):
…
1401 """Grow the saturation trails by an amount dependent on the width of the
1406 mask : `lsst.afw.image.Mask`
1407 Mask which will have the saturated areas grown.
1411 for i
in range(1, 6):
1412 extraGrowDict[i] = 0
1413 for i
in range(6, 8):
1414 extraGrowDict[i] = 1
1415 for i
in range(8, 10):
1416 extraGrowDict[i] = 3
1419 if extraGrowMax <= 0:
1422 saturatedBit = mask.getPlaneBitMask(
"SAT")
1424 xmin, ymin = mask.getBBox().getMin()
1425 width = mask.getWidth()
1427 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1428 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1431 for s
in fp.getSpans():
1432 x0, x1 = s.getX0(), s.getX1()
1434 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1437 x0 -= xmin + extraGrow
1438 x1 -= xmin - extraGrow
1445 mask.array[y, x0:x1+1] |= saturatedBit
1449 """Set all BAD areas of the chip to the average of the rest of the exposure
1453 exposure : `lsst.afw.image.Exposure`
1454 Exposure to mask. The exposure mask is modified.
1455 badStatistic : `str`, optional
1456 Statistic to use to generate the replacement value from the
1457 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'.
1461 badPixelCount : scalar
1462 Number of bad pixels masked.
1463 badPixelValue : scalar
1464 Value substituted for bad pixels.
1469 Raised if `badStatistic` is not an allowed value.
1471 if badStatistic ==
"MEDIAN":
1472 statistic = afwMath.MEDIAN
1473 elif badStatistic ==
"MEANCLIP":
1474 statistic = afwMath.MEANCLIP
1476 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1478 mi = exposure.getMaskedImage()
1480 BAD = mask.getPlaneBitMask(
"BAD")
1481 INTRP = mask.getPlaneBitMask(
"INTRP")
1484 sctrl.setAndMask(BAD)
1487 maskArray = mask.getArray()
1488 imageArray = mi.getImage().getArray()
1489 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1490 imageArray[:] = numpy.where(badPixels, value, imageArray)
1492 return badPixels.sum(), value
1496 """Check to see if an exposure is in a filter specified by a list.
1498 The goal of this is to provide a unified filter checking interface
1499 for all filter dependent stages.
1503 exposure : `lsst.afw.image.Exposure`
1504 Exposure to examine.
1505 filterList : `list` [`str`]
1506 List of physical_filter names to check.
1507 log : `logging.Logger`
1508 Logger to handle messages.
1513 True if the exposure's filter is contained in the list.
1515 if len(filterList) == 0:
1517 thisFilter = exposure.getFilter()
1518 if thisFilter
is None:
1519 log.warning(
"No FilterLabel attached to this exposure!")
1523 if thisPhysicalFilter
in filterList:
1525 elif thisFilter.bandLabel
in filterList:
1527 log.warning(
"Physical filter (%s) should be used instead of band %s for filter configurations"
1528 " (%s)", thisPhysicalFilter, thisFilter.bandLabel, filterList)
1535 """Get the physical filter label associated with the given filterLabel.
1537 If ``filterLabel`` is `None` or there is no physicalLabel attribute
1538 associated with the given ``filterLabel``, the returned label will be
1543 filterLabel : `lsst.afw.image.FilterLabel`
1544 The `lsst.afw.image.FilterLabel` object from which to derive the
1545 physical filter label.
1546 log : `logging.Logger`
1547 Logger to handle messages.
1551 physicalFilter : `str`
1552 The value returned by the physicalLabel attribute of ``filterLabel`` if
1553 it exists, otherwise set to \"Unknown\".
1555 if filterLabel
is None:
1556 physicalFilter =
"Unknown"
1557 log.warning(
"filterLabel is None. Setting physicalFilter to \"Unknown\".")
1560 physicalFilter = filterLabel.physicalLabel
1561 except RuntimeError:
1562 log.warning(
"filterLabel has no physicalLabel attribute. Setting physicalFilter to \"Unknown\".")
1563 physicalFilter =
"Unknown"
1564 return physicalFilter
1568 """Count the number of pixels in a given mask plane.
1572 maskedIm : `~lsst.afw.image.MaskedImage`
1573 Masked image to examine.
1575 Name of the mask plane to examine.
1580 Number of pixels in the requested mask plane.
1582 maskBit = maskedIm.mask.getPlaneBitMask(maskPlane)
1583 nPix = numpy.where(numpy.bitwise_and(maskedIm.mask.array, maskBit))[0].flatten().size
1588 """Get the per-amplifier gains used for this exposure.
1592 exposure : `lsst.afw.image.Exposure`
1593 The exposure to find gains for.
1597 gains : `dict` [`str` `float`]
1598 Dictionary of gain values, keyed by amplifier name.
1599 Returns empty dict when detector is None.
1601 det = exposure.getDetector()
1605 metadata = exposure.getMetadata()
1608 ampName = amp.getName()
1610 if (key1 := f
"LSST ISR GAIN {ampName}")
in metadata:
1611 gains[ampName] = metadata[key1]
1612 elif (key2 := f
"LSST GAIN {ampName}")
in metadata:
1613 gains[ampName] = metadata[key2]
1615 gains[ampName] = amp.getGain()
1620 """Get the per-amplifier read noise used for this exposure.
1624 exposure : `lsst.afw.image.Exposure`
1625 The exposure to find read noise for.
1629 readnoises : `dict` [`str` `float`]
1630 Dictionary of read noise values, keyed by amplifier name.
1631 Returns empty dict when detector is None.
1633 det = exposure.getDetector()
1637 metadata = exposure.getMetadata()
1640 ampName = amp.getName()
1642 if (key1 := f
"LSST ISR READNOISE {ampName}")
in metadata:
1643 readnoises[ampName] = metadata[key1]
1644 elif (key2 := f
"LSST READNOISE {ampName}")
in metadata:
1645 readnoises[ampName] = metadata[key2]
1647 readnoises[ampName] = amp.getReadNoise()
1652 """Check if the unused pixels (pre-/over-scan pixels) have
1653 been trimmed from an exposure.
1657 exposure : `lsst.afw.image.Exposure`
1658 The exposure to check.
1663 True if the image is trimmed, else False.
1665 return exposure.getDetector().getBBox() == exposure.getBBox()
1669 """Check if the unused pixels (pre-/over-scan pixels) have
1670 been trimmed from an image
1674 image : `lsst.afw.image.Image`
1676 detector : `lsst.afw.cameraGeom.Detector`
1677 The detector associated with the image.
1682 True if the image is trimmed, else False.
1684 return detector.getBBox() == image.getBBox()
1688 doRaiseOnCalibMismatch,
1689 cameraKeywordsToCompare,
1695 """Compare header keywords to confirm camera states match.
1699 doRaiseOnCalibMismatch : `bool`
1700 Raise on calibration mismatch? Otherwise, log a warning.
1701 cameraKeywordsToCompare : `list` [`str`]
1702 List of camera keywords to compare.
1703 exposureMetadata : `lsst.daf.base.PropertyList`
1704 Header for the exposure being processed.
1705 calib : `lsst.afw.image.Exposure` or `lsst.ip.isr.IsrCalib`
1706 Calibration to be applied.
1708 Calib type for log message.
1709 log : `logging.Logger`, optional
1710 Logger to handle messages.
1713 calibMetadata = calib.metadata
1714 except AttributeError:
1717 log = log
if log
else logging.getLogger(__name__)
1719 missingKeywords = []
1720 for keyword
in cameraKeywordsToCompare:
1721 exposureValue = exposureMetadata.get(keyword,
None)
1722 if exposureValue
is None:
1723 log.debug(
"Sequencer keyword %s not found in exposure metadata.", keyword)
1726 calibValue = calibMetadata.get(keyword,
None)
1729 if calibValue
is None:
1730 missingKeywords.append(keyword)
1733 if exposureValue != calibValue:
1734 if doRaiseOnCalibMismatch:
1736 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1744 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1750 exposureMetadata[f
"ISR {calibName.upper()} SEQUENCER MISMATCH"] =
True
1754 "Calibration %s missing keywords %s, which were not checked.",
1756 ",".join(missingKeywords),
Parameters to control convolution.
A kernel created from an Image.
Pass parameters to a Statistics object.
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
void convolve(OutImageT &convolvedImage, InImageT const &inImage, KernelT const &kernel, ConvolutionControl const &convolutionControl=ConvolutionControl())
Convolve an Image or MaskedImage with a Kernel, setting pixels of an existing output image.
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True)
compareCameraKeywords(doRaiseOnCalibMismatch, cameraKeywordsToCompare, exposureMetadata, calib, calibName, log=None)
getExposureGains(exposure)
interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None, maskNameList=None, useLegacyInterp=True)
setBadRegions(exposure, badStatistic="MEDIAN")
countMaskedPixels(maskedIm, maskPlane)
maskITLEdgeBleed(ccdExposure, badAmpDict, fpCore, itlEdgeBleedSatMinArea=10000, itlEdgeBleedSatMaxArea=100000, itlEdgeBleedThreshold=5000., itlEdgeBleedModelConstant=0.02, saturatedMaskName="SAT", log=None)
attachTransmissionCurve(exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False)
illuminationCorrection(maskedImage, illumMaskedImage, illumScale, trimToFit=True)
growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD")
maskITLSatSag(ccdExposure, fpCore, saturatedMaskName="SAT")
widenSaturationTrails(mask)
transferFlux(cFunc, fStep, correctionMode=True)
biasCorrection(maskedImage, biasMaskedImage, trimToFit=False)
checkFilter(exposure, filterList, log)
darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False)
fluxConservingBrighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain, gains=None, correctionMode=True)
brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain, gains=None)
makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
getExposureReadNoises(exposure)
transposeMaskedImage(maskedImage)
interpolateFromMask(maskedImage, fwhm, growSaturatedFootprints=1, maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True)
isTrimmedImage(image, detector)
maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None)
updateVariance(maskedImage, gain, readNoise, replace=True)
applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True)
getPhysicalFilter(filterLabel, log)
_applyMaskITLEdgeBleed(ccdExposure, xCore, satLevel, widthSat, itlEdgeBleedThreshold=5000., itlEdgeBleedModelConstant=0.03, saturatedMaskName="SAT", log=None)
saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None, useLegacyInterp=True)
isTrimmedExposure(exposure)
trimToMatchCalibBBox(rawMaskedImage, calibMaskedImage)