25 "attachTransmissionCurve",
27 "brighterFatterCorrection",
33 "fluxConservingBrighterFatterCorrection",
37 "illuminationCorrection",
38 "interpolateDefectList",
39 "interpolateFromMask",
41 "saturationCorrection",
44 "transposeMaskedImage",
45 "trimToMatchCalibBBox",
47 "widenSaturationTrails",
49 "getExposureReadNoises",
62from lsst.meas.algorithms.detection
import SourceDetectionTask
64from contextlib
import contextmanager
66from .defects
import Defects
70 """Make a double Gaussian PSF.
75 FWHM of double Gaussian smoothing kernel.
79 psf : `lsst.meas.algorithms.DoubleGaussianPsf`
80 The created smoothing kernel.
82 ksize = 4*int(fwhm) + 1
83 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
87 """Make a transposed copy of a masked image.
91 maskedImage : `lsst.afw.image.MaskedImage`
96 transposed : `lsst.afw.image.MaskedImage`
97 The transposed copy of the input image.
99 transposed = maskedImage.Factory(
lsst.geom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
100 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
101 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
102 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
107 maskNameList=None, useLegacyInterp=True):
108 """Interpolate over defects specified in a defect list.
112 maskedImage : `lsst.afw.image.MaskedImage`
114 defectList : `lsst.meas.algorithms.Defects`
115 List of defects to interpolate over.
117 FWHM of double Gaussian smoothing kernel.
118 fallbackValue : scalar, optional
119 Fallback value if an interpolated value cannot be determined.
120 If None, then the clipped mean of the image is used.
121 maskNameList : `list [string]`
122 List of the defects to interpolate over (used for GP interpolator).
123 useLegacyInterp : `bool`
124 Use the legacy interpolation (polynomial interpolation) if True. Use
125 Gaussian Process interpolation if False.
129 The ``fwhm`` parameter is used to create a PSF, but the underlying
130 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
131 not currently make use of this information in legacy Interpolation, but use
132 if for the Gaussian Process as an estimation of the correlation lenght.
135 if fallbackValue
is None:
137 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
138 maskedImage.getMask().addMaskPlane(
'INTRP')
149 kwargs = {
"bin_spacing": 20,
150 "threshold_dynamic_binning": 2000,
151 "threshold_subdivide": 20000}
154 measAlg.interpolateOverDefects(maskedImage, psf, defectList,
155 fallbackValue=fallbackValue,
156 useFallbackValueAtEdge=
True,
158 useLegacyInterp=useLegacyInterp,
159 maskNameList=maskNameList, **kwargs)
164 """Mask pixels based on threshold detection.
168 maskedImage : `lsst.afw.image.MaskedImage`
169 Image to process. Only the mask plane is updated.
172 growFootprints : scalar, optional
173 Number of pixels to grow footprints of detected regions.
174 maskName : str, optional
175 Mask plane name, or list of names to convert
179 defectList : `lsst.meas.algorithms.Defects`
180 Defect list constructed from pixels above the threshold.
183 thresh = afwDetection.Threshold(threshold)
184 fs = afwDetection.FootprintSet(maskedImage, thresh)
186 if growFootprints > 0:
187 fs = afwDetection.FootprintSet(fs, rGrow=growFootprints, isotropic=
False)
188 fpList = fs.getFootprints()
191 mask = maskedImage.getMask()
192 bitmask = mask.getPlaneBitMask(maskName)
193 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
195 return Defects.fromFootprintList(fpList)
198def growMasks(mask, radius=0, maskNameList=['BAD'], maskValue="BAD"):
199 """Grow a mask by an amount and add to the requested plane.
203 mask : `lsst.afw.image.Mask`
204 Mask image to process.
206 Amount to grow the mask.
207 maskNameList : `str` or `list` [`str`]
208 Mask names that should be grown.
210 Mask plane to assign the newly masked pixels to.
213 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
214 fpSet = afwDetection.FootprintSet(mask, thresh)
215 fpSet = afwDetection.FootprintSet(fpSet, rGrow=radius, isotropic=
False)
216 fpSet.setMask(mask, maskValue)
220 maskNameList=['SAT'], fallbackValue=None, useLegacyInterp=True):
221 """Interpolate over defects identified by a particular set of mask planes.
225 maskedImage : `lsst.afw.image.MaskedImage`
228 FWHM of double Gaussian smoothing kernel.
229 growSaturatedFootprints : scalar, optional
230 Number of pixels to grow footprints for saturated pixels.
231 maskNameList : `List` of `str`, optional
233 fallbackValue : scalar, optional
234 Value of last resort for interpolation.
238 The ``fwhm`` parameter is used to create a PSF, but the underlying
239 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
240 not currently make use of this information.
242 mask = maskedImage.getMask()
244 if growSaturatedFootprints > 0
and "SAT" in maskNameList:
248 growMasks(mask, radius=growSaturatedFootprints, maskNameList=[
'SAT'], maskValue=
"SAT")
250 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
251 fpSet = afwDetection.FootprintSet(mask, thresh)
252 defectList = Defects.fromFootprintList(fpSet.getFootprints())
255 maskNameList=maskNameList, useLegacyInterp=useLegacyInterp)
261 fallbackValue=None, useLegacyInterp=True):
262 """Mark saturated pixels and optionally interpolate over them
266 maskedImage : `lsst.afw.image.MaskedImage`
269 Saturation level used as the detection threshold.
271 FWHM of double Gaussian smoothing kernel.
272 growFootprints : scalar, optional
273 Number of pixels to grow footprints of detected regions.
274 interpolate : Bool, optional
275 If True, saturated pixels are interpolated over.
276 maskName : str, optional
278 fallbackValue : scalar, optional
279 Value of last resort for interpolation.
283 The ``fwhm`` parameter is used to create a PSF, but the underlying
284 interpolation code (`lsst.meas.algorithms.interpolateOverDefects`) does
285 not currently make use of this information.
287 defectList = makeThresholdMask(
288 maskedImage=maskedImage,
289 threshold=saturation,
290 growFootprints=growFootprints,
295 maskNameList=[maskName], useLegacyInterp=useLegacyInterp)
301 """Compute number of edge trim pixels to match the calibration data.
303 Use the dimension difference between the raw exposure and the
304 calibration exposure to compute the edge trim pixels. This trim
305 is applied symmetrically, with the same number of pixels masked on
310 rawMaskedImage : `lsst.afw.image.MaskedImage`
312 calibMaskedImage : `lsst.afw.image.MaskedImage`
313 Calibration image to draw new bounding box from.
317 replacementMaskedImage : `lsst.afw.image.MaskedImage`
318 ``rawMaskedImage`` trimmed to the appropriate size.
323 Raised if ``rawMaskedImage`` cannot be symmetrically trimmed to
324 match ``calibMaskedImage``.
326 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
328 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
330 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
332 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
336 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
337 SourceDetectionTask.setEdgeBits(
339 replacementMaskedImage.getBBox(),
340 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
343 replacementMaskedImage = rawMaskedImage
345 return replacementMaskedImage
349 """Apply bias correction in place.
353 maskedImage : `lsst.afw.image.MaskedImage`
354 Image to process. The image is modified by this method.
355 biasMaskedImage : `lsst.afw.image.MaskedImage`
356 Bias image of the same size as ``maskedImage``
357 trimToFit : `Bool`, optional
358 If True, raw data is symmetrically trimmed to match
364 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have
371 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
372 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
373 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
374 maskedImage -= biasMaskedImage
377def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
378 """Apply dark correction in place.
382 maskedImage : `lsst.afw.image.MaskedImage`
383 Image to process. The image is modified by this method.
384 darkMaskedImage : `lsst.afw.image.MaskedImage`
385 Dark image of the same size as ``maskedImage``.
387 Dark exposure time for ``maskedImage``.
389 Dark exposure time for ``darkMaskedImage``.
390 invert : `Bool`, optional
391 If True, re-add the dark to an already corrected image.
392 trimToFit : `Bool`, optional
393 If True, raw data is symmetrically trimmed to match
399 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have
404 The dark correction is applied by calculating:
405 maskedImage -= dark * expScaling / darkScaling
410 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
411 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
412 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
414 scale = expScale / darkScale
416 maskedImage.scaledMinus(scale, darkMaskedImage)
418 maskedImage.scaledPlus(scale, darkMaskedImage)
422 """Set the variance plane based on the image plane.
424 The maskedImage must have units of `adu` (if gain != 1.0) or
425 electron (if gain == 1.0). This routine will always produce a
426 variance plane in the same units as the image.
430 maskedImage : `lsst.afw.image.MaskedImage`
431 Image to process. The variance plane is modified.
433 The amplifier gain in electron/adu.
435 The amplifier read noise in electron/pixel.
437 var = maskedImage.getVariance()
438 var[:] = maskedImage.getImage()
440 var += (readNoise/gain)**2
443def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
444 """Apply flat correction in place.
448 maskedImage : `lsst.afw.image.MaskedImage`
449 Image to process. The image is modified.
450 flatMaskedImage : `lsst.afw.image.MaskedImage`
451 Flat image of the same size as ``maskedImage``
453 Flat scale computation method. Allowed values are 'MEAN',
455 userScale : scalar, optional
456 Scale to use if ``scalingType='USER'``.
457 invert : `Bool`, optional
458 If True, unflatten an already flattened image.
459 trimToFit : `Bool`, optional
460 If True, raw data is symmetrically trimmed to match
466 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have
467 the same size or if ``scalingType`` is not an allowed value.
472 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
473 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
474 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
480 if scalingType
in (
'MEAN',
'MEDIAN'):
483 elif scalingType ==
'USER':
484 flatScale = userScale
486 raise RuntimeError(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
489 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
491 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
495 """Apply illumination correction in place.
499 maskedImage : `lsst.afw.image.MaskedImage`
500 Image to process. The image is modified.
501 illumMaskedImage : `lsst.afw.image.MaskedImage`
502 Illumination correction image of the same size as ``maskedImage``.
504 Scale factor for the illumination correction.
505 trimToFit : `Bool`, optional
506 If True, raw data is symmetrically trimmed to match
512 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have
518 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
519 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
520 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
522 maskedImage.scaledDivides(1.0/illumScale, illumMaskedImage)
526 """Apply brighter fatter correction in place for the image.
530 exposure : `lsst.afw.image.Exposure`
531 Exposure to have brighter-fatter correction applied. Modified
533 kernel : `numpy.ndarray`
534 Brighter-fatter kernel to apply.
536 Number of correction iterations to run.
538 Convergence threshold in terms of the sum of absolute
539 deviations between an iteration and the previous one.
541 If True, then the exposure values are scaled by the gain prior
543 gains : `dict` [`str`, `float`]
544 A dictionary, keyed by amplifier name, of the gains to use.
545 If gains is None, the nominal gains in the amplifier object are used.
550 Final difference between iterations achieved in correction.
552 Number of iterations used to calculate correction.
556 This correction takes a kernel that has been derived from flat
557 field images to redistribute the charge. The gradient of the
558 kernel is the deflection field due to the accumulated charge.
560 Given the original image I(x) and the kernel K(x) we can compute
561 the corrected image Ic(x) using the following equation:
563 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
565 To evaluate the derivative term we expand it as follows:
567 0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y)))
568 + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )
570 Because we use the measured counts instead of the incident counts
571 we apply the correction iteratively to reconstruct the original
572 counts and the correction. We stop iterating when the summed
573 difference between the current corrected image and the one from
574 the previous iteration is below the threshold. We do not require
575 convergence because the number of iterations is too large a
576 computational cost. How we define the threshold still needs to be
577 evaluated, the current default was shown to work reasonably well
578 on a small set of images. For more information on the method see
579 DocuShare Document-19407.
581 The edges as defined by the kernel are not corrected because they
582 have spurious values due to the convolution.
584 image = exposure.getMaskedImage().getImage()
587 with gainContext(exposure, image, applyGain, gains):
589 kLx = numpy.shape(kernel)[0]
590 kLy = numpy.shape(kernel)[1]
591 kernelImage = afwImage.ImageD(kLx, kLy)
592 kernelImage.getArray()[:, :] = kernel
593 tempImage = image.clone()
595 nanIndex = numpy.isnan(tempImage.getArray())
596 tempImage.getArray()[nanIndex] = 0.
598 outImage = afwImage.ImageF(image.getDimensions())
599 corr = numpy.zeros_like(image.getArray())
600 prev_image = numpy.zeros_like(image.getArray())
614 for iteration
in range(maxIter):
617 tmpArray = tempImage.getArray()
618 outArray = outImage.getArray()
620 with numpy.errstate(invalid=
"ignore", over=
"ignore"):
622 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
623 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
624 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
627 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
628 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
629 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
631 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
633 tmpArray[:, :] = image.getArray()[:, :]
634 tmpArray[nanIndex] = 0.
635 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
638 diff = numpy.sum(numpy.abs(prev_image - tmpArray), dtype=numpy.float64)
642 prev_image[:, :] = tmpArray[:, :]
644 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
645 corr[startY + 1:endY - 1, startX + 1:endX - 1]
647 return diff, iteration
651 """Take the input convolved deflection potential and the flux array
652 to compute and apply the flux transfer into the correction array.
657 Deflection potential, being the convolution of the flux F with the
660 The array of flux values which act as the source of the flux transfer.
661 correctionMode: `bool`
662 Defines if applying correction (True) or generating sims (False).
670 if cFunc.shape != fStep.shape:
671 raise RuntimeError(f
'transferFlux: array shapes do not match: {cFunc.shape}, {fStep.shape}')
683 corr = numpy.zeros_like(cFunc)
686 yDim, xDim = cFunc.shape
687 y = numpy.arange(yDim, dtype=int)
688 x = numpy.arange(xDim, dtype=int)
689 xc, yc = numpy.meshgrid(x, y)
695 diff = numpy.diff(cFunc, axis=ax)
698 gx = numpy.zeros_like(cFunc)
699 yDiff, xDiff = diff.shape
700 gx[:yDiff, :xDiff] += diff
705 for i, sel
in enumerate([gx > 0, gx < 0]):
721 flux = factor * fStep[sel]*gx[sel]
724 flux = factor * fStep[yPix, xPix]*gx[sel]
728 corr[yPix, xPix] += flux
735 gains=None, correctionMode=True):
736 """Apply brighter fatter correction in place for the image.
738 This version presents a modified version of the algorithm
739 found in ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``
740 which conserves the image flux, resulting in improved
741 correction of the cores of stars. The convolution has also been
742 modified to mitigate edge effects.
746 exposure : `lsst.afw.image.Exposure`
747 Exposure to have brighter-fatter correction applied. Modified
749 kernel : `numpy.ndarray`
750 Brighter-fatter kernel to apply.
752 Number of correction iterations to run.
754 Convergence threshold in terms of the sum of absolute
755 deviations between an iteration and the previous one.
757 If True, then the exposure values are scaled by the gain prior
759 gains : `dict` [`str`, `float`]
760 A dictionary, keyed by amplifier name, of the gains to use.
761 If gains is None, the nominal gains in the amplifier object are used.
762 correctionMode : `Bool`
763 If True (default) the function applies correction for BFE. If False,
764 the code can instead be used to generate a simulation of BFE (sign
765 change in the direction of the effect)
770 Final difference between iterations achieved in correction.
772 Number of iterations used to calculate correction.
776 Modified version of ``lsst.ip.isr.isrFunctions.brighterFatterCorrection``.
778 This correction takes a kernel that has been derived from flat
779 field images to redistribute the charge. The gradient of the
780 kernel is the deflection field due to the accumulated charge.
782 Given the original image I(x) and the kernel K(x) we can compute
783 the corrected image Ic(x) using the following equation:
785 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
787 Improved algorithm at this step applies the divergence theorem to
788 obtain a pixelised correction.
790 Because we use the measured counts instead of the incident counts
791 we apply the correction iteratively to reconstruct the original
792 counts and the correction. We stop iterating when the summed
793 difference between the current corrected image and the one from
794 the previous iteration is below the threshold. We do not require
795 convergence because the number of iterations is too large a
796 computational cost. How we define the threshold still needs to be
797 evaluated, the current default was shown to work reasonably well
798 on a small set of images.
800 Edges are handled in the convolution by padding. This is still not
801 a physical model for the edge, but avoids discontinuity in the correction.
803 Author of modified version: Lance.Miller@physics.ox.ac.uk
806 image = exposure.getMaskedImage().getImage()
809 with gainContext(exposure, image, applyGain, gains):
812 kLy, kLx = kernel.shape
813 kernelImage = afwImage.ImageD(kLx, kLy)
814 kernelImage.getArray()[:, :] = kernel
815 tempImage = image.clone()
817 nanIndex = numpy.isnan(tempImage.getArray())
818 tempImage.getArray()[nanIndex] = 0.
820 outImage = afwImage.ImageF(image.getDimensions())
821 corr = numpy.zeros_like(image.getArray())
822 prevImage = numpy.zeros_like(image.getArray())
828 kLy = 2 * ((1+kLy)//2)
829 kLx = 2 * ((1+kLx)//2)
836 imYdimension, imXdimension = tempImage.array.shape
837 imean = numpy.mean(tempImage.getArray()[~nanIndex])
840 tempImage.array[nanIndex] = 0.
841 padArray = numpy.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
842 outImage = afwImage.ImageF(numpy.pad(outImage.getArray(), ((0, kLy), (0, kLx))))
844 padImage = afwImage.ImageF(padArray.shape[1], padArray.shape[0])
845 padImage.array[:] = padArray
847 for iteration
in range(maxIter):
852 tmpArray = tempImage.getArray()
853 outArray = outImage.getArray()
856 outArray = outArray[:imYdimension, :imXdimension]
859 corr[...] =
transferFlux(outArray, tmpArray, correctionMode=correctionMode)
862 tmpArray[:, :] = image.getArray()[:, :]
864 tmpArray[nanIndex] = 0.
868 tempImage.array[nanIndex] = 0.
869 padArray = numpy.pad(tempImage.getArray(), ((0, kLy), (0, kLx)))
872 diff = numpy.sum(numpy.abs(prevImage - tmpArray))
876 prevImage[:, :] = tmpArray[:, :]
878 image.getArray()[:] += corr[:]
880 return diff, iteration
884def gainContext(exp, image, apply, gains=None, invert=False, isTrimmed=True):
885 """Context manager that applies and removes gain.
889 exp : `lsst.afw.image.Exposure`
890 Exposure to apply/remove gain.
891 image : `lsst.afw.image.Image`
892 Image to apply/remove gain.
894 If True, apply and remove the amplifier gain.
895 gains : `dict` [`str`, `float`], optional
896 A dictionary, keyed by amplifier name, of the gains to use.
897 If gains is None, the nominal gains in the amplifier object are used.
898 invert : `bool`, optional
899 Invert the gains (e.g. convert electrons to adu temporarily)?
900 isTrimmed : `bool`, optional
901 Is this a trimmed exposure?
905 exp : `lsst.afw.image.Exposure`
906 Exposure with the gain applied.
910 if gains
and apply
is True:
911 ampNames = [amp.getName()
for amp
in exp.getDetector()]
912 for ampName
in ampNames:
913 if ampName
not in gains.keys():
914 raise RuntimeError(f
"Gains provided to gain context, but no entry found for amp {ampName}")
917 ccd = exp.getDetector()
919 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
921 gain = gains[amp.getName()]
933 ccd = exp.getDetector()
935 sim = image.Factory(image, amp.getBBox()
if isTrimmed
else amp.getRawBBox())
937 gain = gains[amp.getName()]
947 sensorTransmission=None, atmosphereTransmission=None):
948 """Attach a TransmissionCurve to an Exposure, given separate curves for
949 different components.
953 exposure : `lsst.afw.image.Exposure`
954 Exposure object to modify by attaching the product of all given
955 ``TransmissionCurves`` in post-assembly trimmed detector coordinates.
956 Must have a valid ``Detector`` attached that matches the detector
957 associated with sensorTransmission.
958 opticsTransmission : `lsst.afw.image.TransmissionCurve`
959 A ``TransmissionCurve`` that represents the throughput of the optics,
960 to be evaluated in focal-plane coordinates.
961 filterTransmission : `lsst.afw.image.TransmissionCurve`
962 A ``TransmissionCurve`` that represents the throughput of the filter
963 itself, to be evaluated in focal-plane coordinates.
964 sensorTransmission : `lsst.afw.image.TransmissionCurve`
965 A ``TransmissionCurve`` that represents the throughput of the sensor
966 itself, to be evaluated in post-assembly trimmed detector coordinates.
967 atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
968 A ``TransmissionCurve`` that represents the throughput of the
969 atmosphere, assumed to be spatially constant.
973 combined : `lsst.afw.image.TransmissionCurve`
974 The TransmissionCurve attached to the exposure.
978 All ``TransmissionCurve`` arguments are optional; if none are provided, the
979 attached ``TransmissionCurve`` will have unit transmission everywhere.
981 combined = afwImage.TransmissionCurve.makeIdentity()
982 if atmosphereTransmission
is not None:
983 combined *= atmosphereTransmission
984 if opticsTransmission
is not None:
985 combined *= opticsTransmission
986 if filterTransmission
is not None:
987 combined *= filterTransmission
988 detector = exposure.getDetector()
989 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
990 toSys=camGeom.PIXELS)
991 combined = combined.transformedBy(fpToPix)
992 if sensorTransmission
is not None:
993 combined *= sensorTransmission
994 exposure.getInfo().setTransmissionCurve(combined)
998def applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True):
999 """Scale an exposure by the amplifier gains.
1003 exposure : `lsst.afw.image.Exposure`
1004 Exposure to process. The image is modified.
1005 normalizeGains : `Bool`, optional
1006 If True, then amplifiers are scaled to force the median of
1007 each amplifier to equal the median of those medians.
1008 ptcGains : `dict`[`str`], optional
1009 Dictionary keyed by amp name containing the PTC gains.
1010 isTrimmed : `bool`, optional
1011 Is the input image trimmed?
1013 ccd = exposure.getDetector()
1014 ccdImage = exposure.getMaskedImage()
1019 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1021 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1023 sim *= ptcGains[amp.getName()]
1025 sim *= amp.getGain()
1028 medians.append(numpy.median(sim.getImage().getArray()))
1031 median = numpy.median(numpy.array(medians))
1032 for index, amp
in enumerate(ccd):
1034 sim = ccdImage.Factory(ccdImage, amp.getBBox())
1036 sim = ccdImage.Factory(ccdImage, amp.getRawBBox())
1037 if medians[index] != 0.0:
1038 sim *= median/medians[index]
1042 """Grow the saturation trails by an amount dependent on the width of the
1047 mask : `lsst.afw.image.Mask`
1048 Mask which will have the saturated areas grown.
1052 for i
in range(1, 6):
1053 extraGrowDict[i] = 0
1054 for i
in range(6, 8):
1055 extraGrowDict[i] = 1
1056 for i
in range(8, 10):
1057 extraGrowDict[i] = 3
1060 if extraGrowMax <= 0:
1063 saturatedBit = mask.getPlaneBitMask(
"SAT")
1065 xmin, ymin = mask.getBBox().getMin()
1066 width = mask.getWidth()
1068 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1069 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1072 for s
in fp.getSpans():
1073 x0, x1 = s.getX0(), s.getX1()
1075 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1078 x0 -= xmin + extraGrow
1079 x1 -= xmin - extraGrow
1086 mask.array[y, x0:x1+1] |= saturatedBit
1090 """Set all BAD areas of the chip to the average of the rest of the exposure
1094 exposure : `lsst.afw.image.Exposure`
1095 Exposure to mask. The exposure mask is modified.
1096 badStatistic : `str`, optional
1097 Statistic to use to generate the replacement value from the
1098 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'.
1102 badPixelCount : scalar
1103 Number of bad pixels masked.
1104 badPixelValue : scalar
1105 Value substituted for bad pixels.
1110 Raised if `badStatistic` is not an allowed value.
1112 if badStatistic ==
"MEDIAN":
1113 statistic = afwMath.MEDIAN
1114 elif badStatistic ==
"MEANCLIP":
1115 statistic = afwMath.MEANCLIP
1117 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1119 mi = exposure.getMaskedImage()
1121 BAD = mask.getPlaneBitMask(
"BAD")
1122 INTRP = mask.getPlaneBitMask(
"INTRP")
1125 sctrl.setAndMask(BAD)
1128 maskArray = mask.getArray()
1129 imageArray = mi.getImage().getArray()
1130 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1131 imageArray[:] = numpy.where(badPixels, value, imageArray)
1133 return badPixels.sum(), value
1137 """Check to see if an exposure is in a filter specified by a list.
1139 The goal of this is to provide a unified filter checking interface
1140 for all filter dependent stages.
1144 exposure : `lsst.afw.image.Exposure`
1145 Exposure to examine.
1146 filterList : `list` [`str`]
1147 List of physical_filter names to check.
1148 log : `logging.Logger`
1149 Logger to handle messages.
1154 True if the exposure's filter is contained in the list.
1156 if len(filterList) == 0:
1158 thisFilter = exposure.getFilter()
1159 if thisFilter
is None:
1160 log.warning(
"No FilterLabel attached to this exposure!")
1164 if thisPhysicalFilter
in filterList:
1166 elif thisFilter.bandLabel
in filterList:
1168 log.warning(
"Physical filter (%s) should be used instead of band %s for filter configurations"
1169 " (%s)", thisPhysicalFilter, thisFilter.bandLabel, filterList)
1176 """Get the physical filter label associated with the given filterLabel.
1178 If ``filterLabel`` is `None` or there is no physicalLabel attribute
1179 associated with the given ``filterLabel``, the returned label will be
1184 filterLabel : `lsst.afw.image.FilterLabel`
1185 The `lsst.afw.image.FilterLabel` object from which to derive the
1186 physical filter label.
1187 log : `logging.Logger`
1188 Logger to handle messages.
1192 physicalFilter : `str`
1193 The value returned by the physicalLabel attribute of ``filterLabel`` if
1194 it exists, otherwise set to \"Unknown\".
1196 if filterLabel
is None:
1197 physicalFilter =
"Unknown"
1198 log.warning(
"filterLabel is None. Setting physicalFilter to \"Unknown\".")
1201 physicalFilter = filterLabel.physicalLabel
1202 except RuntimeError:
1203 log.warning(
"filterLabel has no physicalLabel attribute. Setting physicalFilter to \"Unknown\".")
1204 physicalFilter =
"Unknown"
1205 return physicalFilter
1209 """Count the number of pixels in a given mask plane.
1213 maskedIm : `~lsst.afw.image.MaskedImage`
1214 Masked image to examine.
1216 Name of the mask plane to examine.
1221 Number of pixels in the requested mask plane.
1223 maskBit = maskedIm.mask.getPlaneBitMask(maskPlane)
1224 nPix = numpy.where(numpy.bitwise_and(maskedIm.mask.array, maskBit))[0].flatten().size
1229 """Get the per-amplifier gains used for this exposure.
1233 exposure : `lsst.afw.image.Exposure`
1234 The exposure to find gains for.
1238 gains : `dict` [`str` `float`]
1239 Dictionary of gain values, keyed by amplifier name.
1240 Returns empty dict when detector is None.
1242 det = exposure.getDetector()
1246 metadata = exposure.getMetadata()
1249 ampName = amp.getName()
1251 if (key1 := f
"LSST ISR GAIN {ampName}")
in metadata:
1252 gains[ampName] = metadata[key1]
1253 elif (key2 := f
"LSST GAIN {ampName}")
in metadata:
1254 gains[ampName] = metadata[key2]
1256 gains[ampName] = amp.getGain()
1261 """Get the per-amplifier read noise used for this exposure.
1265 exposure : `lsst.afw.image.Exposure`
1266 The exposure to find read noise for.
1270 readnoises : `dict` [`str` `float`]
1271 Dictionary of read noise values, keyed by amplifier name.
1272 Returns empty dict when detector is None.
1274 det = exposure.getDetector()
1278 metadata = exposure.getMetadata()
1281 ampName = amp.getName()
1283 if (key1 := f
"LSST ISR READNOISE {ampName}")
in metadata:
1284 readnoises[ampName] = metadata[key1]
1285 elif (key2 := f
"LSST READNOISE {ampName}")
in metadata:
1286 readnoises[ampName] = metadata[key2]
1288 readnoises[ampName] = amp.getReadNoise()
1293 """Check if the unused pixels (pre-/over-scan pixels) have
1294 been trimmed from an exposure.
1298 exposure : `lsst.afw.image.Exposure`
1299 The exposure to check.
1304 True if the image is trimmed, else False.
1306 return exposure.getDetector().getBBox() == exposure.getBBox()
1310 """Check if the unused pixels (pre-/over-scan pixels) have
1311 been trimmed from an image
1315 image : `lsst.afw.image.Image`
1317 detector : `lsst.afw.cameraGeom.Detector`
1318 The detector associated with the image.
1323 True if the image is trimmed, else False.
1325 return detector.getBBox() == image.getBBox()
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)
getExposureGains(exposure)
interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None, maskNameList=None, useLegacyInterp=True)
setBadRegions(exposure, badStatistic="MEDIAN")
countMaskedPixels(maskedIm, maskPlane)
attachTransmissionCurve(exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
updateVariance(maskedImage, gain, readNoise)
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")
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)
applyGains(exposure, normalizeGains=False, ptcGains=None, isTrimmed=True)
getPhysicalFilter(filterLabel, log)
saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None, useLegacyInterp=True)
isTrimmedExposure(exposure)
trimToMatchCalibBBox(rawMaskedImage, calibMaskedImage)