25 "attachTransmissionCurve",
28 "compareCameraKeywords",
40 "illuminationCorrection",
41 "interpolateDefectList",
42 "interpolateFromMask",
44 "saturationCorrection",
46 "transposeMaskedImage",
47 "trimToMatchCalibBBox",
49 "widenSaturationTrails",
51 "getExposureReadNoises",
59import lsst.afw.image
as afwImage
68from contextlib
import contextmanager
70from .defects
import Defects
74 """Make a double Gaussian PSF.
79 FWHM of double Gaussian smoothing kernel.
83 psf : `lsst.meas.algorithms.DoubleGaussianPsf`
84 The created smoothing kernel.
86 ksize = 4*int(fwhm) + 1
87 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
91 """Make a transposed copy of a masked image.
95 maskedImage : `lsst.afw.image.MaskedImage`
100 transposed : `lsst.afw.image.MaskedImage`
101 The transposed copy of the input image.
103 transposed = maskedImage.Factory(
lsst.geom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
104 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
105 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
106 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
111 maskNameList=None, useLegacyInterp=True):
112 """Interpolate over defects specified in a defect list.
116 maskedImage : `lsst.afw.image.MaskedImage`
118 defectList : `lsst.meas.algorithms.Defects`
119 List of defects to interpolate over.
121 FWHM of double Gaussian smoothing kernel.
122 fallbackValue : scalar, optional
123 Fallback value if an interpolated value cannot be determined.
124 If None, then the clipped mean of the image is used.
125 maskNameList : `list [string]`
126 List of the defects to interpolate over (used for GP interpolator).
127 useLegacyInterp : `bool`
128 Use the legacy interpolation (polynomial interpolation) if True. Use
129 Gaussian Process interpolation if False.
133 The ``fwhm`` parameter is used to create a PSF, but the underlying
134 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
135 not currently make use of this information in legacy Interpolation, but use
136 if for the Gaussian Process as an estimation of the correlation lenght.
139 if fallbackValue
is None:
141 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
142 maskedImage.getMask().addMaskPlane(
'INTRP')
153 kwargs = {
"bin_spacing": 20,
154 "threshold_dynamic_binning": 2000,
155 "threshold_subdivide": 20000}
158 measAlg.interpolateOverDefects(maskedImage, psf, defectList,
159 fallbackValue=fallbackValue,
160 useFallbackValueAtEdge=
True,
162 useLegacyInterp=useLegacyInterp,
163 maskNameList=maskNameList, **kwargs)
168 """Mask pixels based on threshold detection.
172 maskedImage : `lsst.afw.image.MaskedImage`
173 Image to process. Only the mask plane is updated.
176 growFootprints : scalar, optional
177 Number of pixels to grow footprints of detected regions.
178 maskName : str, optional
179 Mask plane name, or list of names to convert
183 defectList : `lsst.meas.algorithms.Defects`
184 Defect list constructed from pixels above the threshold.
187 thresh = afwDetection.Threshold(threshold)
188 fs = afwDetection.FootprintSet(maskedImage, thresh)
190 if growFootprints > 0:
191 fs = afwDetection.FootprintSet(fs, rGrow=growFootprints, isotropic=
False)
192 fpList = fs.getFootprints()
195 mask = maskedImage.getMask()
196 bitmask = mask.getPlaneBitMask(maskName)
197 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
199 return Defects.fromFootprintList(fpList)
202def growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD"):
203 """Grow a mask by an amount and add to the requested plane.
207 mask : `lsst.afw.image.Mask`
208 Mask image to process.
210 Amount to grow the mask.
211 maskNameList : `str` or `list` [`str`]
212 Mask names that should be grown.
214 Mask plane to assign the newly masked pixels to.
217 spans = SpanSet.fromMask(mask, mask.getPlaneBitMask(maskNameList))
220 spans = spans.dilated(radius, Stencil.MANHATTAN)
221 spans = spans.clippedTo(mask.getBBox())
222 spans.setMask(mask, mask.getPlaneBitMask(maskValue))
226 e2vEdgeBleedSatMaxArea=100000,
227 e2vEdgeBleedYMax=350,
228 saturatedMaskName="SAT", log=None):
229 """Mask edge bleeds in E2V detectors.
233 exposure : `lsst.afw.image.Exposure`
234 Exposure to apply masking to.
235 e2vEdgeBleedSatMinArea : `int`, optional
236 Minimum limit of saturated cores footprint area.
237 e2vEdgeBleedSatMaxArea : `int`, optional
238 Maximum limit of saturated cores footprint area.
239 e2vEdgeBleedYMax: `float`, optional
240 Height of edge bleed masking.
241 saturatedMaskName : `str`, optional
242 Mask name for saturation.
243 log : `logging.Logger`, optional
244 Logger to handle messages.
247 log = log
if log
else logging.getLogger(__name__)
249 maskedImage = exposure.maskedImage
250 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
252 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
254 fpList = afwDetection.FootprintSet(exposure.mask, thresh).getFootprints()
256 satAreas = numpy.asarray([fp.getArea()
for fp
in fpList])
257 largeAreas, = numpy.where((satAreas >= e2vEdgeBleedSatMinArea)
258 & (satAreas < e2vEdgeBleedSatMaxArea))
259 for largeAreasIndex
in largeAreas:
260 fpCore = fpList[largeAreasIndex]
261 xCore, yCore = fpCore.getCentroid()
265 for amp
in exposure.getDetector():
266 if amp.getBBox().contains(xCore, yCore):
267 ampName = amp.getName()
268 if ampName[:2] ==
'C0':
271 if fpCore.getBBox().getMinY() == 0:
279 log.info(
"Found E2V edge bleed in amp %s, column %d.", ampName, xCore)
280 maskedImage.mask[amp.getBBox()].array[:e2vEdgeBleedYMax, :] |= saturatedBit
284 fpCore, itlEdgeBleedSatMinArea=10000,
285 itlEdgeBleedSatMaxArea=100000,
286 itlEdgeBleedThreshold=5000.,
287 itlEdgeBleedModelConstant=0.02,
288 saturatedMaskName="SAT", log=None):
289 """Mask edge bleeds in ITL detectors.
293 ccdExposure : `lsst.afw.image.Exposure`
294 Exposure to apply masking to.
295 badAmpDict : `dict` [`str`, `bool`]
296 Dictionary of amplifiers, keyed by name, value is True if
297 amplifier is fully masked.
298 fpCore : `lsst.afw.detection._detection.Footprint`
299 Footprint of saturated core.
300 itlEdgeBleedThreshold : `float`, optional
301 Threshold above median sky background for edge bleed detection
303 itlEdgeBleedModelConstant : `float`, optional
304 Constant in the decaying exponential in the edge bleed masking.
305 saturatedMaskName : `str`, optional
306 Mask name for saturation.
307 log : `logging.Logger`, optional
308 Logger to handle messages.
311 log = log
if log
else logging.getLogger(__name__)
314 satLevel = numpy.nanmedian([ccdExposure.metadata[f
"LSST ISR SATURATION LEVEL {amp.getName()}"]
315 for amp
in ccdExposure.getDetector()
if not badAmpDict[amp.getName()]])
319 xCore, yCore = fpCore.getCentroid()
321 yCoreFP = int(yCore) - fpCore.getBBox().getMinY()
325 checkCoreNbRow = fpCore.getSpans().asArray()[yCoreFP, :]
328 indexSwitchFalse = []
329 if checkCoreNbRow[0]:
333 indexSwitchTrue.append(0)
338 for i, value
in enumerate(checkCoreNbRow):
341 indexSwitchTrue.append(i)
346 indexSwitchFalse.append(i)
354 xEdgesCores.append(int((indexSwitchTrue[1] + indexSwitchFalse[0])/2))
355 xEdgesCores.append(fpCore.getSpans().asArray().shape[1])
357 for i
in range(nbCore):
358 subfp = fpCore.getSpans().asArray()[:, xEdgesCores[i]:xEdgesCores[i+1]]
359 xCoreFP = int(xEdgesCores[i] + numpy.argmax(numpy.sum(subfp, axis=0)))
361 xCore = xCoreFP + fpCore.getBBox().getMinX()
364 if subfp.shape[0] <= 200:
365 yCoreFP = int(numpy.argmax(numpy.sum(subfp, axis=1)))
367 yCoreFP = int(numpy.argmax(numpy.sum(subfp[100:-100, :],
369 yCoreFP = 100+yCoreFP
372 widthSat = numpy.sum(subfp[int(yCoreFP), :])
374 subfpArea = numpy.sum(subfp)
375 if subfpArea > itlEdgeBleedSatMinArea
and subfpArea < itlEdgeBleedSatMaxArea:
378 itlEdgeBleedThreshold,
379 itlEdgeBleedModelConstant,
380 saturatedMaskName, log)
384 "Too many (%d) cores in saturated footprint to mask edge bleeds.",
389 xCore, yCore = fpCore.getCentroid()
391 yCoreFP = yCore - fpCore.getBBox().getMinY()
393 widthSat = numpy.sum(fpCore.getSpans().asArray()[int(yCoreFP), :])
395 satLevel, widthSat, itlEdgeBleedThreshold,
396 itlEdgeBleedModelConstant, saturatedMaskName, log)
401 itlEdgeBleedThreshold=5000.,
402 itlEdgeBleedModelConstant=0.03,
403 saturatedMaskName="SAT", log=None):
404 """Apply ITL edge bleed masking model.
408 ccdExposure : `lsst.afw.image.Exposure`
409 Exposure to apply masking to.
411 X coordinate of the saturated core.
413 Minimum saturation level of the detector.
415 Width of the saturated core.
416 itlEdgeBleedThreshold : `float`, optional
417 Threshold above median sky background for edge bleed detection
419 itlEdgeBleedModelConstant : `float`, optional
420 Constant in the decaying exponential in the edge bleed masking.
421 saturatedMaskName : `str`, optional
422 Mask name for saturation.
423 log : `logging.Logger`, optional
424 Logger to handle messages.
426 log = log
if log
else logging.getLogger(__name__)
428 maskedImage = ccdExposure.maskedImage
429 xmax = maskedImage.image.array.shape[1]
430 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
432 for amp
in ccdExposure.getDetector():
438 yBox = amp.getBBox().getCenter()[1]
439 if amp.getBBox().contains(xCore, yBox):
442 ampName = amp.getName()
452 if ampName[:2] ==
'C1':
453 sliceImage = maskedImage.image.array[:200, :]
454 sliceMask = maskedImage.mask.array[:200, :]
455 elif ampName[:2] ==
'C0':
456 sliceImage = numpy.flipud(maskedImage.image.array[-200:, :])
457 sliceMask = numpy.flipud(maskedImage.mask.array[-200:, :])
469 lowerRangeSmall = int(xCore)-5
470 upperRangeSmall = int(xCore)+5
471 if lowerRangeSmall < 0:
473 if upperRangeSmall > xmax:
474 upperRangeSmall = xmax
475 ampImageBG = numpy.median(maskedImage[amp.getBBox()].image.array)
476 edgeMedian = numpy.median(sliceImage[:50, lowerRangeSmall:upperRangeSmall])
477 if edgeMedian > (ampImageBG + itlEdgeBleedThreshold):
479 log.info(
"Found ITL edge bleed in amp %s, column %d.", ampName, xCore)
488 subImageXMin = int(xCore)-250
489 subImageXMax = int(xCore)+250
492 elif subImageXMax > xmax:
495 subImage = sliceImage[:100, subImageXMin:subImageXMax]
496 maxWidthEdgeBleed = numpy.max(numpy.sum(subImage > 0.45*satLevel,
501 edgeBleedHalfWidth = \
502 int(((maxWidthEdgeBleed)*numpy.exp(-itlEdgeBleedModelConstant*y)
504 lowerRange = int(xCore)-edgeBleedHalfWidth
505 upperRange = int(xCore)+edgeBleedHalfWidth
511 if upperRange > xmax:
513 sliceMask[y, lowerRange:upperRange] |= saturatedBit
517 """Mask columns presenting saturation sag in saturated footprints in
522 ccdExposure : `lsst.afw.image.Exposure`
523 Exposure to apply masking to.
524 fpCore : `lsst.afw.detection._detection.Footprint`
525 Footprint of saturated core.
526 saturatedMaskName : `str`, optional
527 Mask name for saturation.
532 maskedImage = ccdExposure.maskedImage
533 saturatedBit = maskedImage.mask.getPlaneBitMask(saturatedMaskName)
535 cc = numpy.sum(fpCore.getSpans().asArray(), axis=0)
538 columnsToMaskFP = numpy.where(cc > fpCore.getSpans().asArray().shape[0]/5.)
540 columnsToMask = [x + int(fpCore.getBBox().getMinX())
for x
in columnsToMaskFP]
541 maskedImage.mask.array[:, columnsToMask] |= saturatedBit
544def maskITLDip(exposure, detectorConfig, maskPlaneNames=["SUSPECT", "ITL_DIP"], log=None):
545 """Add mask bits according to the ITL dip model.
549 exposure : `lsst.afw.image.Exposure`
550 Exposure to do ITL dip masking.
551 detectorConfig : `lsst.ip.isr.overscanAmpConfig.OverscanDetectorConfig`
552 Configuration for this detector.
553 maskPlaneNames : `list [`str`], optional
554 Name of the ITL Dip mask planes.
555 log : `logging.Logger`, optional
556 If not set, a default logger will be used.
558 if detectorConfig.itlDipBackgroundFraction == 0.0:
563 log = logging.getLogger(__name__)
565 thresh = afwDetection.Threshold(
566 exposure.mask.getPlaneBitMask(
"SAT"),
567 afwDetection.Threshold.BITMASK,
569 fpList = afwDetection.FootprintSet(exposure.mask, thresh).getFootprints()
571 heights = numpy.asarray([fp.getBBox().getHeight()
for fp
in fpList])
573 largeHeights, = numpy.where(heights >= detectorConfig.itlDipMinHeight)
575 if len(largeHeights) == 0:
579 approxBackground = numpy.median(exposure.image.array)
580 maskValue = exposure.mask.getPlaneBitMask(maskPlaneNames)
582 maskBak = exposure.mask.array.copy()
585 for index
in largeHeights:
587 center = fp.getCentroid()
589 nSat = numpy.sum(fp.getSpans().asArray(), axis=0)
590 width = numpy.sum(nSat > detectorConfig.itlDipMinHeight)
592 if width < detectorConfig.itlDipMinWidth:
595 width = numpy.clip(width,
None, detectorConfig.itlDipMaxWidth)
597 dipMax = detectorConfig.itlDipBackgroundFraction * approxBackground * width
600 if dipMax < detectorConfig.itlDipMinBackgroundNoiseFraction * numpy.sqrt(approxBackground):
603 minCol = int(center.getX() - (detectorConfig.itlDipWidthScale * width) / 2.)
604 maxCol = int(center.getX() + (detectorConfig.itlDipWidthScale * width) / 2.)
605 minCol = numpy.clip(minCol, 0,
None)
606 maxCol = numpy.clip(maxCol,
None, exposure.mask.array.shape[1] - 1)
609 "Found ITL dip (width %d; bkg %.2f); masking column %d to %d.",
616 exposure.mask.array[:, minCol: maxCol + 1] |= maskValue
618 nMaskedCols += (maxCol - minCol + 1)
620 if nMaskedCols > detectorConfig.itlDipMaxColsPerImage:
622 "Too many (%d) columns would be masked on this image from dip masking; restoring original mask.",
625 exposure.mask.array[:, :] = maskBak
629 maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True):
630 """Interpolate over defects identified by a particular set of mask planes.
634 maskedImage : `lsst.afw.image.MaskedImage`
637 FWHM of double Gaussian smoothing kernel.
638 growSaturatedFootprints : scalar, optional
639 Number of pixels to grow footprints for saturated pixels.
640 maskNameList : `List` of `str`, optional
642 fallbackValue : scalar, optional
643 Value of last resort for interpolation.
647 The ``fwhm`` parameter is used to create a PSF, but the underlying
648 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
649 not currently make use of this information.
651 mask = maskedImage.getMask()
653 if growSaturatedFootprints > 0
and "SAT" in maskNameList:
657 growMasks(mask, radius=growSaturatedFootprints, maskNameList=[
'SAT'], maskValue=
"SAT")
659 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
660 fpSet = afwDetection.FootprintSet(mask, thresh)
661 defectList = Defects.fromFootprintList(fpSet.getFootprints())
664 maskNameList=maskNameList, useLegacyInterp=useLegacyInterp)
670 fallbackValue=None, useLegacyInterp=True):
671 """Mark saturated pixels and optionally interpolate over them
675 maskedImage : `lsst.afw.image.MaskedImage`
678 Saturation level used as the detection threshold.
680 FWHM of double Gaussian smoothing kernel.
681 growFootprints : scalar, optional
682 Number of pixels to grow footprints of detected regions.
683 interpolate : Bool, optional
684 If True, saturated pixels are interpolated over.
685 maskName : str, optional
687 fallbackValue : scalar, optional
688 Value of last resort for interpolation.
692 The ``fwhm`` parameter is used to create a PSF, but the underlying
693 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
694 not currently make use of this information.
697 maskedImage=maskedImage,
698 threshold=saturation,
699 growFootprints=growFootprints,
704 maskNameList=[maskName], useLegacyInterp=useLegacyInterp)
710 """Compute number of edge trim pixels to match the calibration data.
712 Use the dimension difference between the raw exposure and the
713 calibration exposure to compute the edge trim pixels. This trim
714 is applied symmetrically, with the same number of pixels masked on
719 rawMaskedImage : `lsst.afw.image.MaskedImage`
721 calibMaskedImage : `lsst.afw.image.MaskedImage`
722 Calibration image to draw new bounding box from.
726 replacementMaskedImage : `lsst.afw.image.MaskedImage`
727 ``rawMaskedImage`` trimmed to the appropriate size.
732 Raised if ``rawMaskedImage`` cannot be symmetrically trimmed to
733 match ``calibMaskedImage``.
735 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
737 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
739 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
741 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
745 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
746 SourceDetectionTask.setEdgeBits(
748 replacementMaskedImage.getBBox(),
749 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
752 replacementMaskedImage = rawMaskedImage
754 return replacementMaskedImage
758 """Apply bias correction in place.
762 maskedImage : `lsst.afw.image.MaskedImage`
763 Image to process. The image is modified by this method.
764 biasMaskedImage : `lsst.afw.image.MaskedImage`
765 Bias image of the same size as ``maskedImage``
766 trimToFit : `Bool`, optional
767 If True, raw data is symmetrically trimmed to match
773 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have
780 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
781 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
782 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
783 maskedImage -= biasMaskedImage
786def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
787 """Apply dark correction in place.
791 maskedImage : `lsst.afw.image.MaskedImage`
792 Image to process. The image is modified by this method.
793 darkMaskedImage : `lsst.afw.image.MaskedImage`
794 Dark image of the same size as ``maskedImage``.
796 Dark exposure time for ``maskedImage``.
798 Dark exposure time for ``darkMaskedImage``.
799 invert : `Bool`, optional
800 If True, re-add the dark to an already corrected image.
801 trimToFit : `Bool`, optional
802 If True, raw data is symmetrically trimmed to match
808 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have
813 The dark correction is applied by calculating:
814 maskedImage -= dark * expScaling / darkScaling
819 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
820 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
821 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
823 scale = expScale / darkScale
825 maskedImage.scaledMinus(scale, darkMaskedImage)
827 maskedImage.scaledPlus(scale, darkMaskedImage)
831 """Set the variance plane based on the image plane.
833 The maskedImage must have units of `adu` (if gain != 1.0) or
834 electron (if gain == 1.0). This routine will always produce a
835 variance plane in the same units as the image.
839 maskedImage : `lsst.afw.image.MaskedImage`
840 Image to process. The variance plane is modified.
842 The amplifier gain in electron/adu.
844 The amplifier read noise in electron/pixel.
845 replace : `bool`, optional
846 Replace the current variance? If False, the image
847 variance will be added to the current variance plane.
849 var = maskedImage.variance
851 var[:, :] = maskedImage.image
853 var[:, :] += maskedImage.image
855 var += (readNoise/gain)**2
858def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
859 """Apply flat correction in place.
863 maskedImage : `lsst.afw.image.MaskedImage`
864 Image to process. The image is modified.
865 flatMaskedImage : `lsst.afw.image.MaskedImage`
866 Flat image of the same size as ``maskedImage``
868 Flat scale computation method. Allowed values are 'MEAN',
870 userScale : scalar, optional
871 Scale to use if ``scalingType='USER'``.
872 invert : `Bool`, optional
873 If True, unflatten an already flattened image.
874 trimToFit : `Bool`, optional
875 If True, raw data is symmetrically trimmed to match
881 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have
882 the same size or if ``scalingType`` is not an allowed value.
887 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
888 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
889 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
895 if scalingType
in (
'MEAN',
'MEDIAN'):
898 elif scalingType ==
'USER':
899 flatScale = userScale
901 raise RuntimeError(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
904 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
906 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
910 """Apply illumination correction in place.
914 maskedImage : `lsst.afw.image.MaskedImage`
915 Image to process. The image is modified.
916 illumMaskedImage : `lsst.afw.image.MaskedImage`
917 Illumination correction image of the same size as ``maskedImage``.
919 Scale factor for the illumination correction.
920 trimToFit : `Bool`, optional
921 If True, raw data is symmetrically trimmed to match
927 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have
933 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
934 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
935 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
937 maskedImage.scaledDivides(1.0/illumScale, illumMaskedImage)
941def gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True):
942 """Context manager that applies and removes gain.
946 exp : `lsst.afw.image.Exposure`
947 Exposure to apply/remove gain.
948 image : `lsst.afw.image.Image`
949 Image to apply/remove gain.
951 If True, apply and remove the amplifier gain.
952 gains : `dict` [`str`, `float`], optional
953 A dictionary, keyed by amplifier name, of the gains to use.
954 If gains is None, the nominal gains in the amplifier object are used.
955 invert : `bool`, optional
956 Invert the gains (e.g. convert electrons to adu temporarily)?
957 isTrimmed : `bool`, optional
958 Is this a trimmed exposure?
962 exp : `lsst.afw.image.Exposure`
963 Exposure with the gain applied.
967 if gains
and apply
is True:
968 ampNames = [amp.getName()
for amp
in exp.getDetector()]
969 for ampName
in ampNames:
970 if ampName
not in gains.keys():
971 raise RuntimeError(f
"Gains provided to gain context, but no entry found for amp {ampName}")
974 ccd = exp.getDetector()
976 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
978 gain = gains[amp.getName()]
990 ccd = exp.getDetector()
992 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
994 gain = gains[amp.getName()]
1004 sensorTransmission=None, atmosphereTransmission=None):
1005 """Attach a TransmissionCurve to an Exposure, given separate curves for
1006 different components.
1010 exposure : `lsst.afw.image.Exposure`
1011 Exposure object to modify by attaching the product of all given
1012 ``TransmissionCurves`` in post-assembly trimmed detector coordinates.
1013 Must have a valid ``Detector`` attached that matches the detector
1014 associated with sensorTransmission.
1015 opticsTransmission : `lsst.afw.image.TransmissionCurve`
1016 A ``TransmissionCurve`` that represents the throughput of the optics,
1017 to be evaluated in focal-plane coordinates.
1018 filterTransmission : `lsst.afw.image.TransmissionCurve`
1019 A ``TransmissionCurve`` that represents the throughput of the filter
1020 itself, to be evaluated in focal-plane coordinates.
1021 sensorTransmission : `lsst.afw.image.TransmissionCurve`
1022 A ``TransmissionCurve`` that represents the throughput of the sensor
1023 itself, to be evaluated in post-assembly trimmed detector coordinates.
1024 atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
1025 A ``TransmissionCurve`` that represents the throughput of the
1026 atmosphere, assumed to be spatially constant.
1030 combined : `lsst.afw.image.TransmissionCurve`
1031 The TransmissionCurve attached to the exposure.
1035 All ``TransmissionCurve`` arguments are optional; if none are provided, the
1036 attached ``TransmissionCurve`` will have unit transmission everywhere.
1038 combined = afwImage.TransmissionCurve.makeIdentity()
1039 if atmosphereTransmission
is not None:
1040 combined *= atmosphereTransmission
1041 if opticsTransmission
is not None:
1042 combined *= opticsTransmission
1043 if filterTransmission
is not None:
1044 combined *= filterTransmission
1045 detector = exposure.getDetector()
1046 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
1047 toSys=camGeom.PIXELS)
1048 combined = combined.transformedBy(fpToPix)
1049 if sensorTransmission
is not None:
1050 combined *= sensorTransmission
1051 exposure.getInfo().setTransmissionCurve(combined)
1055def applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True):
1056 """Scale an exposure by the amplifier gains.
1060 exposure : `lsst.afw.image.Exposure`
1061 Exposure to process. The image is modified.
1062 normalizeGains : `Bool`, optional
1063 If True, then amplifiers are scaled to force the median of
1064 each amplifier to equal the median of those medians.
1065 ptcGains : `dict`[`str`], optional
1066 Dictionary keyed by amp name containing the PTC gains.
1067 isTrimmed : `bool`, optional
1068 Is the input image trimmed?
1070 ccd = exposure.getDetector()
1071 ccdImage = exposure.getMaskedImage()
1076 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1078 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1080 sim *= ptcGains[amp.getName()]
1082 sim *= amp.getGain()
1085 medians.append(numpy.median(sim.getImage().getArray()))
1088 median = numpy.median(numpy.array(medians))
1089 for index, amp
in enumerate(ccd):
1091 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1093 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1094 if medians[index] != 0.0:
1095 sim *= median/medians[index]
1099 """Grow the saturation trails by an amount dependent on the width of the
1104 mask : `lsst.afw.image.Mask`
1105 Mask which will have the saturated areas grown.
1109 for i
in range(1, 6):
1110 extraGrowDict[i] = 0
1111 for i
in range(6, 8):
1112 extraGrowDict[i] = 1
1113 for i
in range(8, 10):
1114 extraGrowDict[i] = 3
1117 if extraGrowMax <= 0:
1120 saturatedBit = mask.getPlaneBitMask(
"SAT")
1122 xmin, ymin = mask.getBBox().getMin()
1123 width = mask.getWidth()
1125 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1126 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1129 for s
in fp.getSpans():
1130 x0, x1 = s.getX0(), s.getX1()
1132 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1135 x0 -= xmin + extraGrow
1136 x1 -= xmin - extraGrow
1143 mask.array[y, x0:x1+1] |= saturatedBit
1147 """Set all BAD areas of the chip to the average of the rest of the exposure
1151 exposure : `lsst.afw.image.Exposure`
1152 Exposure to mask. The exposure mask is modified.
1153 badStatistic : `str`, optional
1154 Statistic to use to generate the replacement value from the
1155 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'.
1159 badPixelCount : scalar
1160 Number of bad pixels masked.
1161 badPixelValue : scalar
1162 Value substituted for bad pixels.
1167 Raised if `badStatistic` is not an allowed value.
1169 if badStatistic ==
"MEDIAN":
1170 statistic = afwMath.MEDIAN
1171 elif badStatistic ==
"MEANCLIP":
1172 statistic = afwMath.MEANCLIP
1174 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1176 mi = exposure.getMaskedImage()
1178 BAD = mask.getPlaneBitMask(
"BAD")
1179 INTRP = mask.getPlaneBitMask(
"INTRP")
1182 sctrl.setAndMask(BAD)
1185 maskArray = mask.getArray()
1186 imageArray = mi.getImage().getArray()
1187 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1188 imageArray[:] = numpy.where(badPixels, value, imageArray)
1190 return badPixels.sum(), value
1194 """Check to see if an exposure is in a filter specified by a list.
1196 The goal of this is to provide a unified filter checking interface
1197 for all filter dependent stages.
1201 exposure : `lsst.afw.image.Exposure`
1202 Exposure to examine.
1203 filterList : `list` [`str`]
1204 List of physical_filter names to check.
1205 log : `logging.Logger`
1206 Logger to handle messages.
1211 True if the exposure's filter is contained in the list.
1213 if len(filterList) == 0:
1215 thisFilter = exposure.getFilter()
1216 if thisFilter
is None:
1217 log.warning(
"No FilterLabel attached to this exposure!")
1221 if thisPhysicalFilter
in filterList:
1223 elif thisFilter.bandLabel
in filterList:
1225 log.warning(
"Physical filter (%s) should be used instead of band %s for filter configurations"
1226 " (%s)", thisPhysicalFilter, thisFilter.bandLabel, filterList)
1233 """Get the physical filter label associated with the given filterLabel.
1235 If ``filterLabel`` is `None` or there is no physicalLabel attribute
1236 associated with the given ``filterLabel``, the returned label will be
1241 filterLabel : `lsst.afw.image.FilterLabel`
1242 The `lsst.afw.image.FilterLabel` object from which to derive the
1243 physical filter label.
1244 log : `logging.Logger`
1245 Logger to handle messages.
1249 physicalFilter : `str`
1250 The value returned by the physicalLabel attribute of ``filterLabel`` if
1251 it exists, otherwise set to \"Unknown\".
1253 if filterLabel
is None:
1254 physicalFilter =
"Unknown"
1255 log.warning(
"filterLabel is None. Setting physicalFilter to \"Unknown\".")
1258 physicalFilter = filterLabel.physicalLabel
1259 except RuntimeError:
1260 log.warning(
"filterLabel has no physicalLabel attribute. Setting physicalFilter to \"Unknown\".")
1261 physicalFilter =
"Unknown"
1262 return physicalFilter
1266 """Count the number of pixels in a given mask plane.
1270 maskedIm : `~lsst.afw.image.MaskedImage`
1271 Masked image to examine.
1273 Name of the mask plane to examine.
1278 Number of pixels in the requested mask plane.
1280 maskBit = maskedIm.mask.getPlaneBitMask(maskPlane)
1281 nPix = numpy.where(numpy.bitwise_and(maskedIm.mask.array, maskBit))[0].flatten().size
1286 """Get the per-amplifier gains used for this exposure.
1290 exposure : `lsst.afw.image.Exposure`
1291 The exposure to find gains for.
1295 gains : `dict` [`str` `float`]
1296 Dictionary of gain values, keyed by amplifier name.
1297 Returns empty dict when detector is None.
1299 det = exposure.getDetector()
1303 metadata = exposure.getMetadata()
1306 ampName = amp.getName()
1308 if (key1 := f
"LSST ISR GAIN {ampName}")
in metadata:
1309 gains[ampName] = metadata[key1]
1310 elif (key2 := f
"LSST GAIN {ampName}")
in metadata:
1311 gains[ampName] = metadata[key2]
1313 gains[ampName] = amp.getGain()
1318 """Get the per-amplifier read noise used for this exposure.
1322 exposure : `lsst.afw.image.Exposure`
1323 The exposure to find read noise for.
1327 readnoises : `dict` [`str` `float`]
1328 Dictionary of read noise values, keyed by amplifier name.
1329 Returns empty dict when detector is None.
1331 det = exposure.getDetector()
1335 metadata = exposure.getMetadata()
1338 ampName = amp.getName()
1340 if (key1 := f
"LSST ISR READNOISE {ampName}")
in metadata:
1341 readnoises[ampName] = metadata[key1]
1342 elif (key2 := f
"LSST READNOISE {ampName}")
in metadata:
1343 readnoises[ampName] = metadata[key2]
1345 readnoises[ampName] = amp.getReadNoise()
1350 """Check if the unused pixels (pre-/over-scan pixels) have
1351 been trimmed from an exposure.
1355 exposure : `lsst.afw.image.Exposure`
1356 The exposure to check.
1361 True if the image is trimmed, else False.
1363 return exposure.getDetector().getBBox() == exposure.getBBox()
1367 """Check if the unused pixels (pre-/over-scan pixels) have
1368 been trimmed from an image
1372 image : `lsst.afw.image.Image`
1374 detector : `lsst.afw.cameraGeom.Detector`
1375 The detector associated with the image.
1380 True if the image is trimmed, else False.
1382 return detector.getBBox() == image.getBBox()
1386 doRaiseOnCalibMismatch,
1387 cameraKeywordsToCompare,
1393 """Compare header keywords to confirm camera states match.
1397 doRaiseOnCalibMismatch : `bool`
1398 Raise on calibration mismatch? Otherwise, log a warning.
1399 cameraKeywordsToCompare : `list` [`str`]
1400 List of camera keywords to compare.
1401 exposureMetadata : `lsst.daf.base.PropertyList`
1402 Header for the exposure being processed.
1403 calib : `lsst.afw.image.Exposure` or `lsst.ip.isr.IsrCalib`
1404 Calibration to be applied.
1406 Calib type for log message.
1407 log : `logging.Logger`, optional
1408 Logger to handle messages.
1411 calibMetadata = calib.metadata
1412 except AttributeError:
1415 log = log
if log
else logging.getLogger(__name__)
1417 missingKeywords = []
1418 for keyword
in cameraKeywordsToCompare:
1419 exposureValue = exposureMetadata.get(keyword,
None)
1420 if exposureValue
is None:
1421 log.debug(
"Sequencer keyword %s not found in exposure metadata.", keyword)
1424 calibValue = calibMetadata.get(keyword,
None)
1427 if calibValue
is None:
1428 missingKeywords.append(keyword)
1431 if exposureValue != calibValue:
1432 if doRaiseOnCalibMismatch:
1434 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1442 "Sequencer mismatch for %s [%s]: exposure: %s calib: %s",
1448 exposureMetadata[f
"ISR {calibName.upper()} SEQUENCER MISMATCH"] =
True
1452 "Calibration %s missing keywords %s, which were not checked.",
1454 ",".join(missingKeywords),
1459 """ Copy array over 4 quadrants prior to convolution.
1463 inputarray : `numpy.array`
1464 Input array to symmetrize.
1468 aSym : `numpy.array`
1471 targetShape = list(inputArray.shape)
1472 r1, r2 = inputArray.shape[-1], inputArray.shape[-2]
1473 targetShape[-1] = 2*r1-1
1474 targetShape[-2] = 2*r2-1
1475 aSym = numpy.ndarray(tuple(targetShape))
1476 aSym[..., r2-1:, r1-1:] = inputArray
1477 aSym[..., r2-1:, r1-1::-1] = inputArray
1478 aSym[..., r2-1::-1, r1-1::-1] = inputArray
1479 aSym[..., r2-1::-1, r1-1:] = inputArray
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)
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)
biasCorrection(maskedImage, biasMaskedImage, trimToFit=False)
checkFilter(exposure, filterList, log)
darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False)
maskE2VEdgeBleed(exposure, e2vEdgeBleedSatMinArea=10000, e2vEdgeBleedSatMaxArea=100000, e2vEdgeBleedYMax=350, saturatedMaskName="SAT", log=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)