25 "attachTransmissionCurve",
28 "compareCameraKeywords",
41 "illuminationCorrection",
42 "interpolateDefectList",
43 "interpolateFromMask",
45 "saturationCorrection",
47 "transposeMaskedImage",
48 "trimToMatchCalibBBox",
50 "widenSaturationTrails",
52 "getExposureReadNoises",
69from contextlib
import contextmanager
71from .defects
import Defects
75 """Make a double Gaussian PSF.
80 FWHM of double Gaussian smoothing kernel.
84 psf : `lsst.meas.algorithms.DoubleGaussianPsf`
85 The created smoothing kernel.
87 ksize = 4*int(fwhm) + 1
88 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
92 """Make a transposed copy of a masked image.
96 maskedImage : `lsst.afw.image.MaskedImage`
101 transposed : `lsst.afw.image.MaskedImage`
102 The transposed copy of the input image.
104 transposed = maskedImage.Factory(
lsst.geom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
105 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
106 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
107 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
112 maskNameList=None, useLegacyInterp=True):
113 """Interpolate over defects specified in a defect list.
117 maskedImage : `lsst.afw.image.MaskedImage`
119 defectList : `lsst.meas.algorithms.Defects`
120 List of defects to interpolate over.
122 FWHM of double Gaussian smoothing kernel.
123 fallbackValue : scalar, optional
124 Fallback value if an interpolated value cannot be determined.
125 If None, then the clipped mean of the image is used.
126 maskNameList : `list [string]`
127 List of the defects to interpolate over (used for GP interpolator).
128 useLegacyInterp : `bool`
129 Use the legacy interpolation (polynomial interpolation) if True. Use
130 Gaussian Process interpolation if False.
134 The ``fwhm`` parameter is used to create a PSF, but the underlying
135 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
136 not currently make use of this information in legacy Interpolation, but use
137 if for the Gaussian Process as an estimation of the correlation lenght.
140 if fallbackValue
is None:
141 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
142 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
143 maskedImage.getMask().addMaskPlane(
'INTRP')
154 kwargs = {
"bin_spacing": 20,
155 "threshold_dynamic_binning": 2000,
156 "threshold_subdivide": 20000}
159 measAlg.interpolateOverDefects(maskedImage, psf, defectList,
160 fallbackValue=fallbackValue,
161 useFallbackValueAtEdge=
True,
163 useLegacyInterp=useLegacyInterp,
164 maskNameList=maskNameList, **kwargs)
169 """Mask pixels based on threshold detection.
173 maskedImage : `lsst.afw.image.MaskedImage`
174 Image to process. Only the mask plane is updated.
177 growFootprints : scalar, optional
178 Number of pixels to grow footprints of detected regions.
179 maskName : str, optional
180 Mask plane name, or list of names to convert
184 defectList : `lsst.meas.algorithms.Defects`
185 Defect list constructed from pixels above the threshold.
188 thresh = afwDetection.Threshold(threshold)
189 fs = afwDetection.FootprintSet(maskedImage, thresh)
191 if growFootprints > 0:
192 fs = afwDetection.FootprintSet(fs, rGrow=growFootprints, isotropic=
False)
193 fpList = fs.getFootprints()
196 mask = maskedImage.getMask()
197 bitmask = mask.getPlaneBitMask(maskName)
198 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
200 return Defects.fromFootprintList(fpList)
203def growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD"):
204 """Grow a mask by an amount and add to the requested plane.
208 mask : `lsst.afw.image.Mask`
209 Mask image to process.
211 Amount to grow the mask.
212 maskNameList : `str` or `list` [`str`]
213 Mask names that should be grown.
215 Mask plane to assign the newly masked pixels to.
218 spans = SpanSet.fromMask(mask, mask.getPlaneBitMask(maskNameList))
221 spans = spans.dilated(radius, Stencil.MANHATTAN)
222 spans = spans.clippedTo(mask.getBBox())
223 spans.setMask(mask, mask.getPlaneBitMask(maskValue))
227 e2vEdgeBleedSatMaxArea=100000,
228 e2vEdgeBleedYMax=350,
229 saturatedMaskName="SAT", log=None):
230 """Mask edge bleeds in E2V detectors.
234 exposure : `lsst.afw.image.Exposure`
235 Exposure to apply masking to.
236 e2vEdgeBleedSatMinArea : `int`, optional
237 Minimum limit of saturated cores footprint area.
238 e2vEdgeBleedSatMaxArea : `int`, optional
239 Maximum limit of saturated cores footprint area.
240 e2vEdgeBleedYMax: `float`, optional
241 Height of edge bleed masking.
242 saturatedMaskName : `str`, optional
243 Mask name for saturation.
244 log : `logging.Logger`, optional
245 Logger to handle messages.
248 log = log
if log
else logging.getLogger(__name__)
250 maskedImage = exposure.maskedImage
251 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
253 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
255 fpList = afwDetection.FootprintSet(exposure.mask, thresh).getFootprints()
257 satAreas = numpy.asarray([fp.getArea()
for fp
in fpList])
258 largeAreas, = numpy.where((satAreas >= e2vEdgeBleedSatMinArea)
259 & (satAreas < e2vEdgeBleedSatMaxArea))
260 for largeAreasIndex
in largeAreas:
261 fpCore = fpList[largeAreasIndex]
262 xCore, yCore = fpCore.getCentroid()
266 for amp
in exposure.getDetector():
267 if amp.getBBox().contains(xCore, yCore):
268 ampName = amp.getName()
269 if ampName[:2] ==
'C0':
272 if fpCore.getBBox().getMinY() == 0:
280 log.info(
"Found E2V edge bleed in amp %s, column %d.", ampName, xCore)
281 maskedImage.mask[amp.getBBox()].array[:e2vEdgeBleedYMax, :] |= saturatedBit
285 nRowsCheck=20, nRowsCheckShort=5, minUsablePixelsPerRow=30,
286 minLowFraction=0.03, minLowFractionExtent=0.01, minBlockedRows=5,
287 marginFraction=0.125, saturatedMaskName="SAT", log=None):
288 """Mask DECam-style edge bleeds with low rows next to the read register.
290 For each amplifier that contains a large saturated footprint reaching
291 within ``approachRows`` of its read edge, this function identifies a dip
292 using the per-row fraction of usable pixels more than ``nSigma`` below
293 the clipped sky, measures how far the dip extends inward, and marks
294 those rows (plus a margin) with the saturation mask plane.
296 Rows next to the read edge that are unusable are the signature of a
297 drained serial register, which the overscan correction flags as bad;
298 ``minBlockedRows`` rows are also flagged, despite not having a
303 exposure : `lsst.afw.image.Exposure`
304 Assembled exposure to mask.
305 satMinArea : `int`, optional
306 Minimum area (pixels) of a saturated footprint to be considered.
307 approachRows : `int`, optional
308 A saturated footprint must come within this many rows of the read edge
309 to trigger the edge bleed check.
310 nSigma : `float`, optional
311 A pixel is "low" if it is more than this many sigma below sky.
312 nRowsCheck : `int`, optional
313 Number of rows from the read edge to check for an edge bleed dip.
314 nRowsCheckShort : `int`, optional
315 A second, shorter window from the read edge to check for a short
317 minUsablePixelsPerRow : `int`, optional
318 Minimum number of pixels not saturated, bad or missing to search for
319 dips. Rows with fewer pixels may be "blocked" due to a drained
321 minLowFraction : `float`, optional
322 Mean fraction of usable pixels that are low, over either ``nRowsCheck``
323 or ``nRowsCheckShort``, to confirm a dip.
324 minLowFractionExtent : `float`, optional
325 Fraction of usable pixels that are low that each row must exceed
326 to count toward the bleed height; the scan stops after five consecutive
327 rows that do not. Should not exceed ``minLowFraction``.
328 minBlockedRows : `int`, optional
329 Minimum number of blocked rows beyond the normal border at the read
330 edge to be masked if found.
331 marginFraction : `float`, optional
332 Extra rows masked beyond the measured height, as a fraction of
333 that height (plus one row).
334 saturatedMaskName : `str`, optional
335 Mask plane name for saturation.
336 log : `logging.Logger`, optional
337 Logger to handle messages.
342 Raised if the exposure has no detector with amplifiers.
344 log = log
if log
else logging.getLogger(__name__)
346 detector = exposure.getDetector()
347 if detector
is None or len(detector) == 0:
348 raise RuntimeError(
"Cannot mask DECam edge bleeds: exposure has no detector with amplifiers.")
350 maskedImage = exposure.maskedImage
351 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
352 borderBits = maskedImage.mask.getPlaneBitMask([
"BAD",
"NO_DATA"])
353 unusableBits = borderBits | saturatedBit
354 excludeStatsBits = unusableBits | maskedImage.mask.getPlaneBitMask(
"SUSPECT")
357 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
358 fpList = afwDetection.FootprintSet(maskedImage.mask, thresh).getFootprints()
361 if fp.getArea() >= satMinArea:
362 xCore, yCore = fp.getCentroid()
363 candidates.append((int(xCore), int(yCore), fp.getBBox()))
365 log.debug(
"No saturated footprints large enough for the DECam edge bleed check.")
368 statsCtrl = afwMath.StatisticsControl()
369 statsCtrl.setAndMask(excludeStatsBits)
375 ampBBox = amp.getBBox()
376 if not exposure.getBBox().contains(ampBBox):
378 readTop = amp.getReadoutCorner()
in (camGeom.ReadoutCorner.UL, camGeom.ReadoutCorner.UR)
382 reachesReadEdge = any(ampBBox.contains(x, y)
383 and fpBBox.getMaxY() >= ampBBox.getMaxY() - approachRows
384 for x, y, fpBBox
in candidates)
386 reachesReadEdge = any(ampBBox.contains(x, y)
387 and fpBBox.getMinY() <= ampBBox.getMinY() + approachRows
388 for x, y, fpBBox
in candidates)
389 if not reachesReadEdge:
392 ampImage = maskedImage[ampBBox]
393 stats = afwMath.makeStatistics(ampImage, afwMath.MEANCLIP | afwMath.STDEVCLIP | afwMath.NPOINT,
395 if stats.getValue(afwMath.NPOINT) < 1000:
396 log.debug(
"Skipping DECam edge bleed check in amp %s: too few unmasked pixels.",
399 sky = stats.getValue(afwMath.MEANCLIP)
400 sigma = stats.getValue(afwMath.STDEVCLIP)
402 usable = (ampImage.mask.array & unusableBits) == 0
403 low = (ampImage.image.array < sky - nSigma*sigma) & usable
405 nLow = low.sum(axis=1)
406 nUsable = usable.sum(axis=1)
407 nBorderUsable = ((ampImage.mask.array & borderBits) == 0).sum(axis=1)
410 nUsable = nUsable[::-1]
412 nBorderUsable = nBorderUsable[::-1]
414 enoughUsable = numpy.nonzero(nUsable >= minUsablePixelsPerRow)[0]
415 if len(enoughUsable) == 0:
416 log.debug(
"Skipping DECam edge bleed check in amp %s: no rows with usable pixels.",
419 start = int(enoughUsable[0])
420 borderRows = numpy.nonzero(nBorderUsable >= minUsablePixelsPerRow)[0]
421 border = int(borderRows[0])
if len(borderRows)
else 0
422 blocked = start - border
424 fraction = numpy.zeros(len(nLow), dtype=float)
425 good = nUsable >= minUsablePixelsPerRow
426 fraction[good] = nLow[good]/nUsable[good]
428 dipFound = fraction[start:start + nRowsCheck].mean() > minLowFraction
429 if nRowsCheckShort > 0:
430 dipFound |= fraction[start:start + nRowsCheckShort].mean() > minLowFraction
431 if not dipFound
and blocked < minBlockedRows:
432 log.debug(
"Saturated footprint reaches read edge of amp %s but no dip found.",
436 height = start
if blocked >= minBlockedRows
else 0
438 for i
in range(start, len(fraction)):
439 if fraction[i] > minLowFractionExtent:
444 if nBelow >= nRowsStop:
446 height += int(height*marginFraction) + 1
447 height = min(height, ampBBox.getHeight())
450 maskedImage.mask[ampBBox].array[-height:, :] |= saturatedBit
452 maskedImage.mask[ampBBox].array[:height, :] |= saturatedBit
453 log.info(
"Found DECam edge bleed in amp %s at the %s read edge (%d blocked rows); masked %d rows.",
454 amp.getName(),
"top" if readTop
else "bottom", max(blocked, 0), height)
458 fpCore, itlEdgeBleedSatMinArea=10000,
459 itlEdgeBleedSatMaxArea=100000,
460 itlEdgeBleedThreshold=5000.,
461 itlEdgeBleedModelConstant=0.02,
462 saturatedMaskName="SAT", log=None):
463 """Mask edge bleeds in ITL detectors.
467 ccdExposure : `lsst.afw.image.Exposure`
468 Exposure to apply masking to.
469 badAmpDict : `dict` [`str`, `bool`]
470 Dictionary of amplifiers, keyed by name, value is True if
471 amplifier is fully masked.
472 fpCore : `lsst.afw.detection._detection.Footprint`
473 Footprint of saturated core.
474 itlEdgeBleedThreshold : `float`, optional
475 Threshold above median sky background for edge bleed detection
477 itlEdgeBleedModelConstant : `float`, optional
478 Constant in the decaying exponential in the edge bleed masking.
479 saturatedMaskName : `str`, optional
480 Mask name for saturation.
481 log : `logging.Logger`, optional
482 Logger to handle messages.
485 log = log
if log
else logging.getLogger(__name__)
488 satLevel = numpy.nanmedian([ccdExposure.metadata[f
"LSST ISR SATURATION LEVEL {amp.getName()}"]
489 for amp
in ccdExposure.getDetector()
if not badAmpDict[amp.getName()]])
493 xCore, yCore = fpCore.getCentroid()
495 yCoreFP = int(yCore) - fpCore.getBBox().getMinY()
499 checkCoreNbRow = fpCore.getSpans().asArray()[yCoreFP, :]
502 indexSwitchFalse = []
503 if checkCoreNbRow[0]:
507 indexSwitchTrue.append(0)
512 for i, value
in enumerate(checkCoreNbRow):
515 indexSwitchTrue.append(i)
520 indexSwitchFalse.append(i)
528 xEdgesCores.append(int((indexSwitchTrue[1] + indexSwitchFalse[0])/2))
529 xEdgesCores.append(fpCore.getSpans().asArray().shape[1])
531 for i
in range(nbCore):
532 subfp = fpCore.getSpans().asArray()[:, xEdgesCores[i]:xEdgesCores[i+1]]
533 xCoreFP = int(xEdgesCores[i] + numpy.argmax(numpy.sum(subfp, axis=0)))
535 xCore = xCoreFP + fpCore.getBBox().getMinX()
538 if subfp.shape[0] <= 200:
539 yCoreFP = int(numpy.argmax(numpy.sum(subfp, axis=1)))
541 yCoreFP = int(numpy.argmax(numpy.sum(subfp[100:-100, :],
543 yCoreFP = 100+yCoreFP
546 widthSat = numpy.sum(subfp[int(yCoreFP), :])
548 subfpArea = numpy.sum(subfp)
549 if subfpArea > itlEdgeBleedSatMinArea
and subfpArea < itlEdgeBleedSatMaxArea:
552 itlEdgeBleedThreshold,
553 itlEdgeBleedModelConstant,
554 saturatedMaskName, log)
558 "Too many (%d) cores in saturated footprint to mask edge bleeds.",
563 xCore, yCore = fpCore.getCentroid()
565 yCoreFP = yCore - fpCore.getBBox().getMinY()
567 widthSat = numpy.sum(fpCore.getSpans().asArray()[int(yCoreFP), :])
569 satLevel, widthSat, itlEdgeBleedThreshold,
570 itlEdgeBleedModelConstant, saturatedMaskName, log)
575 itlEdgeBleedThreshold=5000.,
576 itlEdgeBleedModelConstant=0.03,
577 saturatedMaskName="SAT", log=None):
578 """Apply ITL edge bleed masking model.
582 ccdExposure : `lsst.afw.image.Exposure`
583 Exposure to apply masking to.
585 X coordinate of the saturated core.
587 Minimum saturation level of the detector.
589 Width of the saturated core.
590 itlEdgeBleedThreshold : `float`, optional
591 Threshold above median sky background for edge bleed detection
593 itlEdgeBleedModelConstant : `float`, optional
594 Constant in the decaying exponential in the edge bleed masking.
595 saturatedMaskName : `str`, optional
596 Mask name for saturation.
597 log : `logging.Logger`, optional
598 Logger to handle messages.
600 log = log
if log
else logging.getLogger(__name__)
602 maskedImage = ccdExposure.maskedImage
603 xmax = maskedImage.image.array.shape[1]
604 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
606 for amp
in ccdExposure.getDetector():
612 yBox = amp.getBBox().getCenter()[1]
613 if amp.getBBox().contains(xCore, yBox):
616 ampName = amp.getName()
626 if ampName[:2] ==
'C1':
627 sliceImage = maskedImage.image.array[:200, :]
628 sliceMask = maskedImage.mask.array[:200, :]
629 elif ampName[:2] ==
'C0':
630 sliceImage = numpy.flipud(maskedImage.image.array[-200:, :])
631 sliceMask = numpy.flipud(maskedImage.mask.array[-200:, :])
643 lowerRangeSmall = int(xCore)-5
644 upperRangeSmall = int(xCore)+5
645 if lowerRangeSmall < 0:
647 if upperRangeSmall > xmax:
648 upperRangeSmall = xmax
649 ampImageBG = numpy.median(maskedImage[amp.getBBox()].image.array)
650 edgeMedian = numpy.median(sliceImage[:50, lowerRangeSmall:upperRangeSmall])
651 if edgeMedian > (ampImageBG + itlEdgeBleedThreshold):
653 log.info(
"Found ITL edge bleed in amp %s, column %d.", ampName, xCore)
662 subImageXMin = int(xCore)-250
663 subImageXMax = int(xCore)+250
666 elif subImageXMax > xmax:
669 subImage = sliceImage[:100, subImageXMin:subImageXMax]
670 maxWidthEdgeBleed = numpy.max(numpy.sum(subImage > 0.45*satLevel,
675 edgeBleedHalfWidth = \
676 int(((maxWidthEdgeBleed)*numpy.exp(-itlEdgeBleedModelConstant*y)
678 lowerRange = int(xCore)-edgeBleedHalfWidth
679 upperRange = int(xCore)+edgeBleedHalfWidth
685 if upperRange > xmax:
687 sliceMask[y, lowerRange:upperRange] |= saturatedBit
691 """Mask columns presenting saturation sag in saturated footprints in
696 ccdExposure : `lsst.afw.image.Exposure`
697 Exposure to apply masking to.
698 fpCore : `lsst.afw.detection._detection.Footprint`
699 Footprint of saturated core.
700 saturatedMaskName : `str`, optional
701 Mask name for saturation.
706 maskedImage = ccdExposure.maskedImage
707 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
709 cc = numpy.sum(fpCore.getSpans().asArray(), axis=0)
712 columnsToMaskFP = numpy.where(cc > fpCore.getSpans().asArray().shape[0]/5.)
714 columnsToMask = [x + int(fpCore.getBBox().getMinX())
for x
in columnsToMaskFP]
715 maskedImage.mask.array[:, columnsToMask] |= saturatedBit
718def maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None):
719 """Add mask bits according to the ITL dip model.
723 exposure : `lsst.afw.image.Exposure`
724 Exposure to do ITL dip masking.
725 detectorConfig : `lsst.ip.isr.overscanAmpConfig.OverscanDetectorConfig`
726 Configuration for this detector.
727 maskPlaneNames : `list [`str`], optional
728 Name of the ITL Dip mask planes.
729 log : `logging.Logger`, optional
730 If not set, a default logger will be used.
732 if detectorConfig.itlDipBackgroundFraction == 0.0:
737 log = logging.getLogger(__name__)
739 thresh = afwDetection.Threshold(
740 exposure.mask.getPlaneBitMask(
"SAT"),
741 afwDetection.Threshold.BITMASK,
743 fpList = afwDetection.FootprintSet(exposure.mask, thresh).getFootprints()
745 heights = numpy.asarray([fp.getBBox().getHeight()
for fp
in fpList])
747 largeHeights, = numpy.where(heights >= detectorConfig.itlDipMinHeight)
749 if len(largeHeights) == 0:
753 approxBackground = numpy.median(exposure.image.array)
754 maskValue = exposure.mask.getPlaneBitMask(maskPlaneNames)
756 maskBak = exposure.mask.array.copy()
759 for index
in largeHeights:
761 center = fp.getCentroid()
763 nSat = numpy.sum(fp.getSpans().asArray(), axis=0)
764 width = numpy.sum(nSat > detectorConfig.itlDipMinHeight)
766 if width < detectorConfig.itlDipMinWidth:
769 width = numpy.clip(width,
None, detectorConfig.itlDipMaxWidth)
771 dipMax = detectorConfig.itlDipBackgroundFraction * approxBackground * width
774 if dipMax < detectorConfig.itlDipMinBackgroundNoiseFraction * numpy.sqrt(approxBackground):
777 minCol = int(center.getX() - (detectorConfig.itlDipWidthScale * width) / 2.)
778 maxCol = int(center.getX() + (detectorConfig.itlDipWidthScale * width) / 2.)
779 minCol = numpy.clip(minCol, 0,
None)
780 maxCol = numpy.clip(maxCol,
None, exposure.mask.array.shape[1] - 1)
783 "Found ITL dip (width %d; bkg %.2f); masking column %d to %d.",
790 exposure.mask.array[:, minCol: maxCol + 1] |= maskValue
792 nMaskedCols += (maxCol - minCol + 1)
794 if nMaskedCols > detectorConfig.itlDipMaxColsPerImage:
796 "Too many (%d) columns would be masked on this image from dip masking; restoring original mask.",
799 exposure.mask.array[:, :] = maskBak
803 maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True):
804 """Interpolate over defects identified by a particular set of mask planes.
808 maskedImage : `lsst.afw.image.MaskedImage`
811 FWHM of double Gaussian smoothing kernel.
812 growSaturatedFootprints : scalar, optional
813 Number of pixels to grow footprints for saturated pixels.
814 maskNameList : `List` of `str`, optional
816 fallbackValue : scalar, optional
817 Value of last resort for interpolation.
821 The ``fwhm`` parameter is used to create a PSF, but the underlying
822 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
823 not currently make use of this information.
825 mask = maskedImage.getMask()
827 if growSaturatedFootprints > 0
and "SAT" in maskNameList:
831 growMasks(mask, radius=growSaturatedFootprints, maskNameList=[
'SAT'], maskValue=
"SAT")
833 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
834 fpSet = afwDetection.FootprintSet(mask, thresh)
835 defectList = Defects.fromFootprintList(fpSet.getFootprints())
838 maskNameList=maskNameList, useLegacyInterp=useLegacyInterp)
844 fallbackValue=None, useLegacyInterp=True):
845 """Mark saturated pixels and optionally interpolate over them
849 maskedImage : `lsst.afw.image.MaskedImage`
852 Saturation level used as the detection threshold.
854 FWHM of double Gaussian smoothing kernel.
855 growFootprints : scalar, optional
856 Number of pixels to grow footprints of detected regions.
857 interpolate : Bool, optional
858 If True, saturated pixels are interpolated over.
859 maskName : str, optional
861 fallbackValue : scalar, optional
862 Value of last resort for interpolation.
866 The ``fwhm`` parameter is used to create a PSF, but the underlying
867 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
868 not currently make use of this information.
871 maskedImage=maskedImage,
872 threshold=saturation,
873 growFootprints=growFootprints,
878 maskNameList=[maskName], useLegacyInterp=useLegacyInterp)
884 """Compute number of edge trim pixels to match the calibration data.
886 Use the dimension difference between the raw exposure and the
887 calibration exposure to compute the edge trim pixels. This trim
888 is applied symmetrically, with the same number of pixels masked on
893 rawMaskedImage : `lsst.afw.image.MaskedImage`
895 calibMaskedImage : `lsst.afw.image.MaskedImage`
896 Calibration image to draw new bounding box from.
900 replacementMaskedImage : `lsst.afw.image.MaskedImage`
901 ``rawMaskedImage`` trimmed to the appropriate size.
906 Raised if ``rawMaskedImage`` cannot be symmetrically trimmed to
907 match ``calibMaskedImage``.
909 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
911 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
913 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
915 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
919 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
920 SourceDetectionTask.setEdgeBits(
922 replacementMaskedImage.getBBox(),
923 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
926 replacementMaskedImage = rawMaskedImage
928 return replacementMaskedImage
932 """Apply bias correction in place.
936 maskedImage : `lsst.afw.image.MaskedImage`
937 Image to process. The image is modified by this method.
938 biasMaskedImage : `lsst.afw.image.MaskedImage`
939 Bias image of the same size as ``maskedImage``
940 trimToFit : `Bool`, optional
941 If True, raw data is symmetrically trimmed to match
947 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have
954 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
955 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
956 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
957 maskedImage -= biasMaskedImage
960def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
961 """Apply dark correction in place.
965 maskedImage : `lsst.afw.image.MaskedImage`
966 Image to process. The image is modified by this method.
967 darkMaskedImage : `lsst.afw.image.MaskedImage`
968 Dark image of the same size as ``maskedImage``.
970 Dark exposure time for ``maskedImage``.
972 Dark exposure time for ``darkMaskedImage``.
973 invert : `Bool`, optional
974 If True, re-add the dark to an already corrected image.
975 trimToFit : `Bool`, optional
976 If True, raw data is symmetrically trimmed to match
982 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have
987 The dark correction is applied by calculating:
988 maskedImage -= dark * expScaling / darkScaling
993 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
994 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
995 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
997 scale = expScale / darkScale
999 maskedImage.scaledMinus(scale, darkMaskedImage)
1001 maskedImage.scaledPlus(scale, darkMaskedImage)
1005 """Set the variance plane based on the image plane.
1007 The maskedImage must have units of `adu` (if gain != 1.0) or
1008 electron (if gain == 1.0). This routine will always produce a
1009 variance plane in the same units as the image.
1013 maskedImage : `lsst.afw.image.MaskedImage`
1014 Image to process. The variance plane is modified.
1016 The amplifier gain in electron/adu.
1018 The amplifier read noise in electron/pixel.
1019 replace : `bool`, optional
1020 Replace the current variance? If False, the image
1021 variance will be added to the current variance plane.
1023 var = maskedImage.variance
1025 var[:, :] = maskedImage.image
1027 var[:, :] += maskedImage.image
1029 var += (readNoise/gain)**2
1032def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
1033 """Apply flat correction in place.
1037 maskedImage : `lsst.afw.image.MaskedImage`
1038 Image to process. The image is modified.
1039 flatMaskedImage : `lsst.afw.image.MaskedImage`
1040 Flat image of the same size as ``maskedImage``
1042 Flat scale computation method. Allowed values are 'MEAN',
1043 'MEDIAN', or 'USER'.
1044 userScale : scalar, optional
1045 Scale to use if ``scalingType='USER'``.
1046 invert : `Bool`, optional
1047 If True, unflatten an already flattened image.
1048 trimToFit : `Bool`, optional
1049 If True, raw data is symmetrically trimmed to match
1055 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have
1056 the same size or if ``scalingType`` is not an allowed value.
1061 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
1062 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
1063 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
1069 if scalingType
in (
'MEAN',
'MEDIAN'):
1070 scalingType = afwMath.stringToStatisticsProperty(scalingType)
1071 flatScale = afwMath.makeStatistics(flatMaskedImage.image, scalingType).getValue()
1072 elif scalingType ==
'USER':
1073 flatScale = userScale
1075 raise RuntimeError(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
1078 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
1080 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
1084 """Apply illumination correction in place.
1088 maskedImage : `lsst.afw.image.MaskedImage`
1089 Image to process. The image is modified.
1090 illumMaskedImage : `lsst.afw.image.MaskedImage`
1091 Illumination correction image of the same size as ``maskedImage``.
1093 Scale factor for the illumination correction.
1094 trimToFit : `Bool`, optional
1095 If True, raw data is symmetrically trimmed to match
1101 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have
1107 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
1108 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
1109 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
1111 maskedImage.scaledDivides(1.0/illumScale, illumMaskedImage)
1115def gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True):
1116 """Context manager that applies and removes gain.
1120 exp : `lsst.afw.image.Exposure`
1121 Exposure to apply/remove gain.
1122 image : `lsst.afw.image.Image`
1123 Image to apply/remove gain.
1125 If True, apply and remove the amplifier gain.
1126 gains : `dict` [`str`, `float`], optional
1127 A dictionary, keyed by amplifier name, of the gains to use.
1128 If gains is None, the nominal gains in the amplifier object are used.
1129 invert : `bool`, optional
1130 Invert the gains (e.g. convert electrons to adu temporarily)?
1131 isTrimmed : `bool`, optional
1132 Is this a trimmed exposure?
1136 exp : `lsst.afw.image.Exposure`
1137 Exposure with the gain applied.
1141 if gains
and apply
is True:
1142 ampNames = [amp.getName()
for amp
in exp.getDetector()]
1143 for ampName
in ampNames:
1144 if ampName
not in gains.keys():
1145 raise RuntimeError(f
"Gains provided to gain context, but no entry found for amp {ampName}")
1148 ccd = exp.getDetector()
1150 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
1152 gain = gains[amp.getName()]
1154 gain = amp.getGain()
1164 ccd = exp.getDetector()
1166 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
1168 gain = gains[amp.getName()]
1170 gain = amp.getGain()
1178 sensorTransmission=None, atmosphereTransmission=None):
1179 """Attach a TransmissionCurve to an Exposure, given separate curves for
1180 different components.
1184 exposure : `lsst.afw.image.Exposure`
1185 Exposure object to modify by attaching the product of all given
1186 ``TransmissionCurves`` in post-assembly trimmed detector coordinates.
1187 Must have a valid ``Detector`` attached that matches the detector
1188 associated with sensorTransmission.
1189 opticsTransmission : `lsst.afw.image.TransmissionCurve`
1190 A ``TransmissionCurve`` that represents the throughput of the optics,
1191 to be evaluated in focal-plane coordinates.
1192 filterTransmission : `lsst.afw.image.TransmissionCurve`
1193 A ``TransmissionCurve`` that represents the throughput of the filter
1194 itself, to be evaluated in focal-plane coordinates.
1195 sensorTransmission : `lsst.afw.image.TransmissionCurve`
1196 A ``TransmissionCurve`` that represents the throughput of the sensor
1197 itself, to be evaluated in post-assembly trimmed detector coordinates.
1198 atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
1199 A ``TransmissionCurve`` that represents the throughput of the
1200 atmosphere, assumed to be spatially constant.
1204 combined : `lsst.afw.image.TransmissionCurve`
1205 The TransmissionCurve attached to the exposure.
1209 All ``TransmissionCurve`` arguments are optional; if none are provided, the
1210 attached ``TransmissionCurve`` will have unit transmission everywhere.
1212 combined = afwImage.TransmissionCurve.makeIdentity()
1213 if atmosphereTransmission
is not None:
1214 combined *= atmosphereTransmission
1215 if opticsTransmission
is not None:
1216 combined *= opticsTransmission
1217 if filterTransmission
is not None:
1218 combined *= filterTransmission
1219 detector = exposure.getDetector()
1220 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
1221 toSys=camGeom.PIXELS)
1222 combined = combined.transformedBy(fpToPix)
1223 if sensorTransmission
is not None:
1224 combined *= sensorTransmission
1225 exposure.getInfo().setTransmissionCurve(combined)
1229def applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True):
1230 """Scale an exposure by the amplifier gains.
1234 exposure : `lsst.afw.image.Exposure`
1235 Exposure to process. The image is modified.
1236 normalizeGains : `Bool`, optional
1237 If True, then amplifiers are scaled to force the median of
1238 each amplifier to equal the median of those medians.
1239 ptcGains : `dict`[`str`], optional
1240 Dictionary keyed by amp name containing the PTC gains.
1241 isTrimmed : `bool`, optional
1242 Is the input image trimmed?
1244 ccd = exposure.getDetector()
1245 ccdImage = exposure.getMaskedImage()
1250 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1252 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1254 sim *= ptcGains[amp.getName()]
1256 sim *= amp.getGain()
1259 medians.append(numpy.median(sim.getImage().getArray()))
1262 median = numpy.median(numpy.array(medians))
1263 for index, amp
in enumerate(ccd):
1265 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1267 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1268 if medians[index] != 0.0:
1269 sim *= median/medians[index]
1273 """Grow the saturation trails by an amount dependent on the width of the
1278 mask : `lsst.afw.image.Mask`
1279 Mask which will have the saturated areas grown.
1283 for i
in range(1, 6):
1284 extraGrowDict[i] = 0
1285 for i
in range(6, 8):
1286 extraGrowDict[i] = 1
1287 for i
in range(8, 10):
1288 extraGrowDict[i] = 3
1291 if extraGrowMax <= 0:
1294 saturatedBit = mask.getPlaneBitMask(
"SAT")
1296 xmin, ymin = mask.getBBox().getMin()
1297 width = mask.getWidth()
1299 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1300 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1303 for s
in fp.getSpans():
1304 x0, x1 = s.getX0(), s.getX1()
1306 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1309 x0 -= xmin + extraGrow
1310 x1 -= xmin - extraGrow
1317 mask.array[y, x0:x1+1] |= saturatedBit
1321 """Set all BAD areas of the chip to the average of the rest of the exposure
1325 exposure : `lsst.afw.image.Exposure`
1326 Exposure to mask. The exposure mask is modified.
1327 badStatistic : `str`, optional
1328 Statistic to use to generate the replacement value from the
1329 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'.
1333 badPixelCount : scalar
1334 Number of bad pixels masked.
1335 badPixelValue : scalar
1336 Value substituted for bad pixels.
1341 Raised if `badStatistic` is not an allowed value.
1343 if badStatistic ==
"MEDIAN":
1344 statistic = afwMath.MEDIAN
1345 elif badStatistic ==
"MEANCLIP":
1346 statistic = afwMath.MEANCLIP
1348 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1350 mi = exposure.getMaskedImage()
1352 BAD = mask.getPlaneBitMask(
"BAD")
1353 INTRP = mask.getPlaneBitMask(
"INTRP")
1355 sctrl = afwMath.StatisticsControl()
1356 sctrl.setAndMask(BAD)
1357 value = afwMath.makeStatistics(mi, statistic, sctrl).getValue()
1359 maskArray = mask.getArray()
1360 imageArray = mi.getImage().getArray()
1361 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1362 imageArray[:] = numpy.where(badPixels, value, imageArray)
1364 return badPixels.sum(), value
1368 """Check to see if an exposure is in a filter specified by a list.
1370 The goal of this is to provide a unified filter checking interface
1371 for all filter dependent stages.
1375 exposure : `lsst.afw.image.Exposure`
1376 Exposure to examine.
1377 filterList : `list` [`str`]
1378 List of physical_filter names to check.
1379 log : `logging.Logger`
1380 Logger to handle messages.
1385 True if the exposure's filter is contained in the list.
1387 if len(filterList) == 0:
1389 thisFilter = exposure.getFilter()
1390 if thisFilter
is None:
1391 log.warning(
"No FilterLabel attached to this exposure!")
1395 if thisPhysicalFilter
in filterList:
1397 elif thisFilter.bandLabel
in filterList:
1399 log.warning(
"Physical filter (%s) should be used instead of band %s for filter configurations"
1400 " (%s)", thisPhysicalFilter, thisFilter.bandLabel, filterList)
1407 """Get the physical filter label associated with the given filterLabel.
1409 If ``filterLabel`` is `None` or there is no physicalLabel attribute
1410 associated with the given ``filterLabel``, the returned label will be
1415 filterLabel : `lsst.afw.image.FilterLabel`
1416 The `lsst.afw.image.FilterLabel` object from which to derive the
1417 physical filter label.
1418 log : `logging.Logger`
1419 Logger to handle messages.
1423 physicalFilter : `str`
1424 The value returned by the physicalLabel attribute of ``filterLabel`` if
1425 it exists, otherwise set to \"Unknown\".
1427 if filterLabel
is None:
1428 physicalFilter =
"Unknown"
1429 log.warning(
"filterLabel is None. Setting physicalFilter to \"Unknown\".")
1432 physicalFilter = filterLabel.physicalLabel
1433 except RuntimeError:
1434 log.warning(
"filterLabel has no physicalLabel attribute. Setting physicalFilter to \"Unknown\".")
1435 physicalFilter =
"Unknown"
1436 return physicalFilter
1440 """Count the number of pixels in a given mask plane.
1444 maskedIm : `~lsst.afw.image.MaskedImage`
1445 Masked image to examine.
1447 Name of the mask plane to examine.
1452 Number of pixels in the requested mask plane.
1454 maskBit = maskedIm.mask.getPlaneBitMask(maskPlane)
1455 nPix = numpy.where(numpy.bitwise_and(maskedIm.mask.array, maskBit))[0].flatten().size
1460 """Get the per-amplifier gains used for this exposure.
1464 exposure : `lsst.afw.image.Exposure`
1465 The exposure to find gains for.
1469 gains : `dict` [`str` `float`]
1470 Dictionary of gain values, keyed by amplifier name.
1471 Returns empty dict when detector is None.
1473 det = exposure.getDetector()
1477 metadata = exposure.getMetadata()
1480 ampName = amp.getName()
1482 if (key1 := f
"LSST ISR GAIN {ampName}")
in metadata:
1483 gains[ampName] = metadata[key1]
1484 elif (key2 := f
"LSST GAIN {ampName}")
in metadata:
1485 gains[ampName] = metadata[key2]
1487 gains[ampName] = amp.getGain()
1492 """Get the per-amplifier read noise used for this exposure.
1496 exposure : `lsst.afw.image.Exposure`
1497 The exposure to find read noise for.
1501 readnoises : `dict` [`str` `float`]
1502 Dictionary of read noise values, keyed by amplifier name.
1503 Returns empty dict when detector is None.
1505 det = exposure.getDetector()
1509 metadata = exposure.getMetadata()
1512 ampName = amp.getName()
1514 if (key1 := f
"LSST ISR READNOISE {ampName}")
in metadata:
1515 readnoises[ampName] = metadata[key1]
1516 elif (key2 := f
"LSST READNOISE {ampName}")
in metadata:
1517 readnoises[ampName] = metadata[key2]
1519 readnoises[ampName] = amp.getReadNoise()
1524 """Check if the unused pixels (pre-/over-scan pixels) have
1525 been trimmed from an exposure.
1529 exposure : `lsst.afw.image.Exposure`
1530 The exposure to check.
1535 True if the image is trimmed, else False.
1537 return exposure.getDetector().getBBox() == exposure.getBBox()
1541 """Check if the unused pixels (pre-/over-scan pixels) have
1542 been trimmed from an image
1546 image : `lsst.afw.image.Image`
1548 detector : `lsst.afw.cameraGeom.Detector`
1549 The detector associated with the image.
1554 True if the image is trimmed, else False.
1556 return detector.getBBox() == image.getBBox()
1560 doRaiseOnCalibMismatch,
1561 cameraKeywordsToCompare,
1567 """Compare header keywords to confirm camera states match.
1571 doRaiseOnCalibMismatch : `bool`
1572 Raise on calibration mismatch? Otherwise, log a warning.
1573 cameraKeywordsToCompare : `list` [`str`]
1574 List of camera keywords to compare.
1575 exposureMetadata : `lsst.daf.base.PropertyList`
1576 Header for the exposure being processed.
1577 calib : `lsst.afw.image.Exposure` or `lsst.ip.isr.IsrCalib`
1578 Calibration to be applied.
1580 Calib type for log message.
1581 log : `logging.Logger`, optional
1582 Logger to handle messages.
1585 calibMetadata = calib.metadata
1586 except AttributeError:
1589 log = log
if log
else logging.getLogger(__name__)
1591 missingKeywords = []
1592 for keyword
in cameraKeywordsToCompare:
1593 exposureValue = exposureMetadata.get(keyword,
None)
1594 if exposureValue
is None:
1595 log.debug(
"Sequencer keyword %s not found in exposure metadata.", keyword)
1598 calibValue = calibMetadata.get(keyword,
None)
1601 if calibValue
is None:
1602 missingKeywords.append(keyword)
1605 if exposureValue != calibValue:
1606 if doRaiseOnCalibMismatch:
1608 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1616 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1622 exposureMetadata[f
"ISR {calibName.upper()} SEQUENCER MISMATCH"] =
True
1626 "Calibration %s missing keywords %s, which were not checked.",
1628 ",".join(missingKeywords),
1633 """ Copy array over 4 quadrants prior to convolution.
1637 inputarray : `numpy.array`
1638 Input array to symmetrize.
1642 aSym : `numpy.array`
1645 targetShape = list(inputArray.shape)
1646 r1, r2 = inputArray.shape[-1], inputArray.shape[-2]
1647 targetShape[-1] = 2*r1-1
1648 targetShape[-2] = 2*r2-1
1649 aSym = numpy.ndarray(tuple(targetShape))
1650 aSym[..., r2-1:, r1-1:] = inputArray
1651 aSym[..., r2-1:, r1-1::-1] = inputArray
1652 aSym[..., r2-1::-1, r1-1::-1] = inputArray
1653 aSym[..., r2-1::-1, r1-1:] = inputArray
gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True)
makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None)
_applyMaskITLEdgeBleed(ccdExposure, xCore, satLevel, widthSat, itlEdgeBleedThreshold=5000., itlEdgeBleedModelConstant=0.03, saturatedMaskName="SAT", log=None)
illuminationCorrection(maskedImage, illumMaskedImage, illumScale, trimToFit=True)
setBadRegions(exposure, badStatistic="MEDIAN")
isTrimmedImage(image, detector)
getExposureReadNoises(exposure)
maskE2VEdgeBleed(exposure, e2vEdgeBleedSatMinArea=10000, e2vEdgeBleedSatMaxArea=100000, e2vEdgeBleedYMax=350, saturatedMaskName="SAT", log=None)
flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False)
applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True)
trimToMatchCalibBBox(rawMaskedImage, calibMaskedImage)
updateVariance(maskedImage, gain, readNoise, replace=True)
interpolateFromMask(maskedImage, fwhm, growSaturatedFootprints=1, maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True)
biasCorrection(maskedImage, biasMaskedImage, trimToFit=False)
darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False)
maskITLSatSag(ccdExposure, fpCore, saturatedMaskName="SAT")
isTrimmedExposure(exposure)
growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD")
checkFilter(exposure, filterList, log)
transposeMaskedImage(maskedImage)
maskDECamEdgeBleed(exposure, satMinArea=10000, approachRows=20, nSigma=5.0, nRowsCheck=20, nRowsCheckShort=5, minUsablePixelsPerRow=30, minLowFraction=0.03, minLowFractionExtent=0.01, minBlockedRows=5, marginFraction=0.125, saturatedMaskName="SAT", log=None)
compareCameraKeywords(doRaiseOnCalibMismatch, cameraKeywordsToCompare, exposureMetadata, calib, calibName, log=None)
interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None, maskNameList=None, useLegacyInterp=True)
countMaskedPixels(maskedIm, maskPlane)
getExposureGains(exposure)
getPhysicalFilter(filterLabel, log)
saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None, useLegacyInterp=True)
widenSaturationTrails(mask)
attachTransmissionCurve(exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
maskITLEdgeBleed(ccdExposure, badAmpDict, fpCore, itlEdgeBleedSatMinArea=10000, itlEdgeBleedSatMaxArea=100000, itlEdgeBleedThreshold=5000., itlEdgeBleedModelConstant=0.02, saturatedMaskName="SAT", log=None)