24 from deprecated.sphinx
import deprecated
38 from contextlib
import contextmanager
42 """Make a double Gaussian PSF. 47 FWHM of double Gaussian smoothing kernel. 51 psf : `lsst.meas.algorithms.DoubleGaussianPsf` 52 The created smoothing kernel. 54 ksize = 4*int(fwhm) + 1
55 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
59 """Make a transposed copy of a masked image. 63 maskedImage : `lsst.afw.image.MaskedImage` 68 transposed : `lsst.afw.image.MaskedImage` 69 The transposed copy of the input image. 71 transposed = maskedImage.Factory(
lsst.geom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
72 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
73 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
74 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
79 """Interpolate over defects specified in a defect list. 83 maskedImage : `lsst.afw.image.MaskedImage` 85 defectList : `lsst.meas.algorithms.Defects` 86 List of defects to interpolate over. 88 FWHM of double Gaussian smoothing kernel. 89 fallbackValue : scalar, optional 90 Fallback value if an interpolated value cannot be determined. 91 If None, then the clipped mean of the image is used. 94 if fallbackValue
is None:
95 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
96 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
97 maskedImage.getMask().addMaskPlane(
'INTRP')
98 measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue,
True)
102 @deprecated(reason=
"Replaced by Defects.fromFootPrintList() (will be removed after v18)",
103 category=FutureWarning)
105 """Compute a defect list from a footprint list, optionally growing the footprints. 109 fpList : `list` of `lsst.afw.detection.Footprint` 110 Footprint list to process. 114 defectList : `lsst.meas.algorithms.Defects` 117 return measAlg.Defects.fromFootprintList(fpList)
119 @deprecated(reason=
"Replaced by Defects.transpose() (will be removed after v18)",
120 category=FutureWarning)
122 """Make a transposed copy of a defect list. 126 defectList : `lsst.meas.algorithms.Defects` 127 Input list of defects. 131 retDefectList : `lsst.meas.algorithms.Defects` 132 Transposed list of defects. 134 if isinstance(defectList, measAlg.Defects):
135 return defectList.transpose()
136 return measAlg.Defects(defectList).transpose()
139 @deprecated(reason=
"Replaced by Defects.maskPixels() (will be removed after v18)",
140 category=FutureWarning)
142 """Set mask plane based on a defect list. 146 maskedImage : `lsst.afw.image.MaskedImage` 147 Image to process. Only the mask plane is updated. 148 defectList : `lsst.meas.algorithms.Defects` 150 maskName : str, optional 151 Mask plane name to use. 153 return lsst.meas.algorithms.Defects(defectList).maskPixels(maskedImage, maskName=maskName)
156 @deprecated(reason=
"Replaced by Defects.fromMask() (will be removed after v18)",
157 category=FutureWarning)
159 """Compute a defect list from a specified mask plane. 163 maskedImage : `lsst.afw.image.MaskedImage` 165 maskName : `str` or `list` 166 Mask plane name, or list of names to convert. 170 defectList : `lsst.meas.algorithms.Defects` 171 Defect list constructed from masked pixels. 173 return measAlg.Defects.fromMask(maskedImage, maskName)
177 """Mask pixels based on threshold detection. 181 maskedImage : `lsst.afw.image.MaskedImage` 182 Image to process. Only the mask plane is updated. 185 growFootprints : scalar, optional 186 Number of pixels to grow footprints of detected regions. 187 maskName : str, optional 188 Mask plane name, or list of names to convert 192 defectList : `lsst.meas.algorithms.Defects` 193 Defect list constructed from pixels above the threshold. 196 thresh = afwDetection.Threshold(threshold)
197 fs = afwDetection.FootprintSet(maskedImage, thresh)
199 if growFootprints > 0:
200 fs = afwDetection.FootprintSet(fs, rGrow=growFootprints, isotropic=
False)
201 fpList = fs.getFootprints()
204 mask = maskedImage.getMask()
205 bitmask = mask.getPlaneBitMask(maskName)
206 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
208 return measAlg.Defects.fromFootprintList(fpList)
212 """Interpolate over defects identified by a particular mask plane. 216 maskedImage : `lsst.afw.image.MaskedImage` 219 FWHM of double Gaussian smoothing kernel. 220 growFootprints : scalar, optional 221 Number of pixels to grow footprints of detected regions. 222 maskName : str, optional 224 fallbackValue : scalar, optional 225 Value of last resort for interpolation. 227 mask = maskedImage.getMask()
228 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
229 fpSet = afwDetection.FootprintSet(mask, thresh)
230 if growFootprints > 0:
231 fpSet = afwDetection.FootprintSet(fpSet, rGrow=growFootprints, isotropic=
False)
234 fpSet.setMask(mask, maskName)
235 defectList = measAlg.Defects.fromFootprintList(fpSet.getFootprints())
241 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
243 """Mark saturated pixels and optionally interpolate over them 247 maskedImage : `lsst.afw.image.MaskedImage` 250 Saturation level used as the detection threshold. 252 FWHM of double Gaussian smoothing kernel. 253 growFootprints : scalar, optional 254 Number of pixels to grow footprints of detected regions. 255 interpolate : Bool, optional 256 If True, saturated pixels are interpolated over. 257 maskName : str, optional 259 fallbackValue : scalar, optional 260 Value of last resort for interpolation. 263 maskedImage=maskedImage,
264 threshold=saturation,
265 growFootprints=growFootprints,
275 """Compute number of edge trim pixels to match the calibration data. 277 Use the dimension difference between the raw exposure and the 278 calibration exposure to compute the edge trim pixels. This trim 279 is applied symmetrically, with the same number of pixels masked on 284 rawMaskedImage : `lsst.afw.image.MaskedImage` 286 calibMaskedImage : `lsst.afw.image.MaskedImage` 287 Calibration image to draw new bounding box from. 291 replacementMaskedImage : `lsst.afw.image.MaskedImage` 292 ``rawMaskedImage`` trimmed to the appropriate size 296 Rasied if ``rawMaskedImage`` cannot be symmetrically trimmed to 297 match ``calibMaskedImage``. 299 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
301 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
303 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
305 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
309 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
310 SourceDetectionTask.setEdgeBits(
312 replacementMaskedImage.getBBox(),
313 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
316 replacementMaskedImage = rawMaskedImage
318 return replacementMaskedImage
322 """Apply bias correction in place. 326 maskedImage : `lsst.afw.image.MaskedImage` 327 Image to process. The image is modified by this method. 328 biasMaskedImage : `lsst.afw.image.MaskedImage` 329 Bias image of the same size as ``maskedImage`` 330 trimToFit : `Bool`, optional 331 If True, raw data is symmetrically trimmed to match 337 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have 344 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
345 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
346 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
347 maskedImage -= biasMaskedImage
350 def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
351 """Apply dark correction in place. 355 maskedImage : `lsst.afw.image.MaskedImage` 356 Image to process. The image is modified by this method. 357 darkMaskedImage : `lsst.afw.image.MaskedImage` 358 Dark image of the same size as ``maskedImage``. 360 Dark exposure time for ``maskedImage``. 362 Dark exposure time for ``darkMaskedImage``. 363 invert : `Bool`, optional 364 If True, re-add the dark to an already corrected image. 365 trimToFit : `Bool`, optional 366 If True, raw data is symmetrically trimmed to match 372 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have 377 The dark correction is applied by calculating: 378 maskedImage -= dark * expScaling / darkScaling 383 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
384 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
385 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
387 scale = expScale / darkScale
389 maskedImage.scaledMinus(scale, darkMaskedImage)
391 maskedImage.scaledPlus(scale, darkMaskedImage)
395 """Set the variance plane based on the image plane. 399 maskedImage : `lsst.afw.image.MaskedImage` 400 Image to process. The variance plane is modified. 402 The amplifier gain in electrons/ADU. 404 The amplifier read nmoise in ADU/pixel. 406 var = maskedImage.getVariance()
407 var[:] = maskedImage.getImage()
412 def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
413 """Apply flat correction in place. 417 maskedImage : `lsst.afw.image.MaskedImage` 418 Image to process. The image is modified. 419 flatMaskedImage : `lsst.afw.image.MaskedImage` 420 Flat image of the same size as ``maskedImage`` 422 Flat scale computation method. Allowed values are 'MEAN', 424 userScale : scalar, optional 425 Scale to use if ``scalingType``='USER'. 426 invert : `Bool`, optional 427 If True, unflatten an already flattened image. 428 trimToFit : `Bool`, optional 429 If True, raw data is symmetrically trimmed to match 435 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have 436 the same size or if ``scalingType`` is not an allowed value. 441 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
442 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
443 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
448 if scalingType
in (
'MEAN',
'MEDIAN'):
449 scalingType = afwMath.stringToStatisticsProperty(scalingType)
450 flatScale = afwMath.makeStatistics(flatMaskedImage.image, scalingType).getValue()
451 elif scalingType ==
'USER':
452 flatScale = userScale
454 raise RuntimeError(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
457 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
459 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
463 """Apply illumination correction in place. 467 maskedImage : `lsst.afw.image.MaskedImage` 468 Image to process. The image is modified. 469 illumMaskedImage : `lsst.afw.image.MaskedImage` 470 Illumination correction image of the same size as ``maskedImage``. 472 Scale factor for the illumination correction. 477 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have 480 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
481 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
482 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
484 maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
487 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0,
488 statControl=None, overscanIsInt=True):
489 """Apply overscan correction in place. 493 ampMaskedImage : `lsst.afw.image.MaskedImage` 494 Image of amplifier to correct; modified. 495 overscanImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 496 Image of overscan; modified. 498 Type of fit for overscan correction. May be one of: 500 - ``MEAN``: use mean of overscan. 501 - ``MEANCLIP``: use clipped mean of overscan. 502 - ``MEDIAN``: use median of overscan. 503 - ``POLY``: fit with ordinary polynomial. 504 - ``CHEB``: fit with Chebyshev polynomial. 505 - ``LEG``: fit with Legendre polynomial. 506 - ``NATURAL_SPLINE``: fit with natural spline. 507 - ``CUBIC_SPLINE``: fit with cubic spline. 508 - ``AKIMA_SPLINE``: fit with Akima spline. 511 Polynomial order or number of spline knots; ignored unless 512 ``fitType`` indicates a polynomial or spline. 513 statControl : `lsst.afw.math.StatisticsControl` 514 Statistics control object. In particular, we pay attention to numSigmaClip 515 overscanIsInt : `bool` 516 Treat the overscan region as consisting of integers, even if it's been 517 converted to float. E.g. handle ties properly. 521 result : `lsst.pipe.base.Struct` 522 Result struct with components: 524 - ``imageFit``: Value(s) removed from image (scalar or 525 `lsst.afw.image.Image`) 526 - ``overscanFit``: Value(s) removed from overscan (scalar or 527 `lsst.afw.image.Image`) 528 - ``overscanImage``: Overscan corrected overscan region 529 (`lsst.afw.image.Image`) 533 Raised if ``fitType`` is not an allowed value. 537 The ``ampMaskedImage`` and ``overscanImage`` are modified, with the fit 538 subtracted. Note that the ``overscanImage`` should not be a subimage of 539 the ``ampMaskedImage``, to avoid being subtracted twice. 541 Debug plots are available for the SPLINE fitTypes by setting the 542 `debug.display` for `name` == "lsst.ip.isr.isrFunctions". These 543 plots show the scatter plot of the overscan data (collapsed along 544 the perpendicular dimension) as a function of position on the CCD 545 (normalized between +/-1). 547 ampImage = ampMaskedImage.getImage()
548 if statControl
is None:
549 statControl = afwMath.StatisticsControl()
551 numSigmaClip = statControl.getNumSigmaClip()
553 if fitType
in (
'MEAN',
'MEANCLIP'):
554 fitType = afwMath.stringToStatisticsProperty(fitType)
555 offImage = afwMath.makeStatistics(overscanImage, fitType, statControl).getValue()
556 overscanFit = offImage
557 elif fitType
in (
'MEDIAN',):
560 if hasattr(overscanImage,
"image"):
561 imageI = overscanImage.image.convertI()
562 overscanImageI = afwImage.MaskedImageI(imageI, overscanImage.mask, overscanImage.variance)
564 overscanImageI = overscanImage.convertI()
566 overscanImageI = overscanImage
568 fitType = afwMath.stringToStatisticsProperty(fitType)
569 offImage = afwMath.makeStatistics(overscanImageI, fitType, statControl).getValue()
570 overscanFit = offImage
574 elif fitType
in (
'POLY',
'CHEB',
'LEG',
'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
575 if hasattr(overscanImage,
"getImage"):
576 biasArray = overscanImage.getImage().getArray()
577 biasArray = numpy.ma.masked_where(overscanImage.getMask().getArray() & statControl.getAndMask(),
580 biasArray = overscanImage.getArray()
582 shortInd = numpy.argmin(biasArray.shape)
585 biasArray = numpy.transpose(biasArray)
588 percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
589 medianBiasArr = percentiles[1]
590 stdevBiasArr = 0.74*(percentiles[2] - percentiles[0])
591 diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
592 biasMaskedArr = numpy.ma.masked_where(diff > numSigmaClip*stdevBiasArr[:, numpy.newaxis], biasArray)
593 collapsed = numpy.mean(biasMaskedArr, axis=1)
594 if collapsed.mask.sum() > 0:
595 collapsed.data[collapsed.mask] = numpy.mean(biasArray.data[collapsed.mask], axis=1)
596 del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
599 collapsed = numpy.transpose(collapsed)
602 indices = 2.0*numpy.arange(num)/float(num) - 1.0
604 if fitType
in (
'POLY',
'CHEB',
'LEG'):
606 poly = numpy.polynomial
607 fitter, evaler = {
"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
608 "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
609 "LEG": (poly.legendre.legfit, poly.legendre.legval),
612 coeffs = fitter(indices, collapsed, order)
613 fitBiasArr = evaler(indices, coeffs)
614 elif 'SPLINE' in fitType:
623 collapsedMask = collapsed.mask
625 if collapsedMask == numpy.ma.nomask:
626 collapsedMask = numpy.array(len(collapsed)*[numpy.ma.nomask])
630 numPerBin, binEdges = numpy.histogram(indices, bins=numBins,
631 weights=1-collapsedMask.astype(int))
634 with numpy.errstate(invalid=
"ignore"):
635 values = numpy.histogram(indices, bins=numBins,
636 weights=collapsed.data*~collapsedMask)[0]/numPerBin
637 binCenters = numpy.histogram(indices, bins=numBins,
638 weights=indices*~collapsedMask)[0]/numPerBin
639 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
640 values.astype(float)[numPerBin > 0],
641 afwMath.stringToInterpStyle(fitType))
642 fitBiasArr = numpy.array([interp.interpolate(i)
for i
in indices])
646 import matplotlib.pyplot
as plot
647 figure = plot.figure(1)
649 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
650 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
651 if collapsedMask.sum() > 0:
652 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
653 axes.plot(indices, fitBiasArr,
'r-')
654 plot.xlabel(
"centered/scaled position along overscan region")
655 plot.ylabel(
"pixel value/fit value")
657 prompt =
"Press Enter or c to continue [chp]... " 659 ans = input(prompt).lower()
660 if ans
in (
"",
"c",):
666 print(
"h[elp] c[ontinue] p[db]")
669 offImage = ampImage.Factory(ampImage.getDimensions())
670 offArray = offImage.getArray()
671 overscanFit = afwImage.ImageF(overscanImage.getDimensions())
672 overscanArray = overscanFit.getArray()
674 offArray[:, :] = fitBiasArr[:, numpy.newaxis]
675 overscanArray[:, :] = fitBiasArr[:, numpy.newaxis]
677 offArray[:, :] = fitBiasArr[numpy.newaxis, :]
678 overscanArray[:, :] = fitBiasArr[numpy.newaxis, :]
686 mask = ampMaskedImage.getMask()
687 maskArray = mask.getArray()
if shortInd == 1
else mask.getArray().transpose()
688 suspect = mask.getPlaneBitMask(
"SUSPECT")
690 if collapsed.mask == numpy.ma.nomask:
694 for low
in range(num):
695 if not collapsed.mask[low]:
698 maskArray[:low, :] |= suspect
699 for high
in range(1, num):
700 if not collapsed.mask[-high]:
703 maskArray[-high:, :] |= suspect
706 raise pexExcept.Exception(
'%s : %s an invalid overscan type' % (
"overscanCorrection", fitType))
708 overscanImage -= overscanFit
709 return Struct(imageFit=offImage, overscanFit=overscanFit, overscanImage=overscanImage)
713 """Apply brighter fatter correction in place for the image. 717 exposure : `lsst.afw.image.Exposure` 718 Exposure to have brighter-fatter correction applied. Modified 720 kernel : `numpy.ndarray` 721 Brighter-fatter kernel to apply. 723 Number of correction iterations to run. 725 Convergence threshold in terms of the sum of absolute 726 deviations between an iteration and the previous one. 728 If True, then the exposure values are scaled by the gain prior 733 This correction takes a kernel that has been derived from flat 734 field images to redistribute the charge. The gradient of the 735 kernel is the deflection field due to the accumulated charge. 737 Given the original image I(x) and the kernel K(x) we can compute 738 the corrected image Ic(x) using the following equation: 740 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y)))) 742 To evaluate the derivative term we expand it as follows: 744 0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y))) + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) ) 746 Because we use the measured counts instead of the incident counts 747 we apply the correction iteratively to reconstruct the original 748 counts and the correction. We stop iterating when the summed 749 difference between the current corrected image and the one from 750 the previous iteration is below the threshold. We do not require 751 convergence because the number of iterations is too large a 752 computational cost. How we define the threshold still needs to be 753 evaluated, the current default was shown to work reasonably well 754 on a small set of images. For more information on the method see 755 DocuShare Document-19407. 757 The edges as defined by the kernel are not corrected because they 758 have spurious values due to the convolution. 760 image = exposure.getMaskedImage().getImage()
765 kLx = numpy.shape(kernel)[0]
766 kLy = numpy.shape(kernel)[1]
767 kernelImage = afwImage.ImageD(kLx, kLy)
768 kernelImage.getArray()[:, :] = kernel
769 tempImage = image.clone()
771 nanIndex = numpy.isnan(tempImage.getArray())
772 tempImage.getArray()[nanIndex] = 0.
774 outImage = afwImage.ImageF(image.getDimensions())
775 corr = numpy.zeros_like(image.getArray())
776 prev_image = numpy.zeros_like(image.getArray())
777 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
778 fixedKernel = afwMath.FixedKernel(kernelImage)
788 for iteration
in range(maxIter):
790 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
791 tmpArray = tempImage.getArray()
792 outArray = outImage.getArray()
794 with numpy.errstate(invalid=
"ignore", over=
"ignore"):
796 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
797 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
798 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
801 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
802 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
803 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
805 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
807 tmpArray[:, :] = image.getArray()[:, :]
808 tmpArray[nanIndex] = 0.
809 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
812 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
816 prev_image[:, :] = tmpArray[:, :]
823 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
824 corr[startY + 1:endY - 1, startX + 1:endX - 1]
829 """Context manager that applies and removes gain. 833 exp : `lsst.afw.image.Exposure` 834 Exposure to apply/remove gain. 835 image : `lsst.afw.image.Image` 836 Image to apply/remove gain. 838 If True, apply and remove the amplifier gain. 842 exp : `lsst.afw.image.Exposure` 843 Exposure with the gain applied. 846 ccd = exp.getDetector()
848 sim = image.Factory(image, amp.getBBox())
855 ccd = exp.getDetector()
857 sim = image.Factory(image, amp.getBBox())
862 sensorTransmission=None, atmosphereTransmission=None):
863 """Attach a TransmissionCurve to an Exposure, given separate curves for 864 different components. 868 exposure : `lsst.afw.image.Exposure` 869 Exposure object to modify by attaching the product of all given 870 ``TransmissionCurves`` in post-assembly trimmed detector coordinates. 871 Must have a valid ``Detector`` attached that matches the detector 872 associated with sensorTransmission. 873 opticsTransmission : `lsst.afw.image.TransmissionCurve` 874 A ``TransmissionCurve`` that represents the throughput of the optics, 875 to be evaluated in focal-plane coordinates. 876 filterTransmission : `lsst.afw.image.TransmissionCurve` 877 A ``TransmissionCurve`` that represents the throughput of the filter 878 itself, to be evaluated in focal-plane coordinates. 879 sensorTransmission : `lsst.afw.image.TransmissionCurve` 880 A ``TransmissionCurve`` that represents the throughput of the sensor 881 itself, to be evaluated in post-assembly trimmed detector coordinates. 882 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 883 A ``TransmissionCurve`` that represents the throughput of the 884 atmosphere, assumed to be spatially constant. 888 combined : `lsst.afw.image.TransmissionCurve` 889 The TransmissionCurve attached to the exposure. 893 All ``TransmissionCurve`` arguments are optional; if none are provided, the 894 attached ``TransmissionCurve`` will have unit transmission everywhere. 896 combined = afwImage.TransmissionCurve.makeIdentity()
897 if atmosphereTransmission
is not None:
898 combined *= atmosphereTransmission
899 if opticsTransmission
is not None:
900 combined *= opticsTransmission
901 if filterTransmission
is not None:
902 combined *= filterTransmission
903 detector = exposure.getDetector()
904 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
905 toSys=camGeom.PIXELS)
906 combined = combined.transformedBy(fpToPix)
907 if sensorTransmission
is not None:
908 combined *= sensorTransmission
909 exposure.getInfo().setTransmissionCurve(combined)
914 """!Update the WCS in exposure with a distortion model based on camera 919 exposure : `lsst.afw.image.Exposure` 920 Exposure to process. Must contain a Detector and WCS. The 921 exposure is modified. 922 camera : `lsst.afw.cameraGeom.Camera` 928 Raised if ``exposure`` is lacking a Detector or WCS, or if 932 Add a model for optical distortion based on geometry found in ``camera`` 933 and the ``exposure``'s detector. The raw input exposure is assumed 934 have a TAN WCS that has no compensation for optical distortion. 935 Two other possibilities are: 936 - The raw input exposure already has a model for optical distortion, 937 as is the case for raw DECam data. 938 In that case you should set config.doAddDistortionModel False. 939 - The raw input exposure has a model for distortion, but it has known 940 deficiencies severe enough to be worth fixing (e.g. because they 941 cause problems for fitting a better WCS). In that case you should 942 override this method with a version suitable for your raw data. 945 wcs = exposure.getWcs()
947 raise RuntimeError(
"exposure has no WCS")
949 raise RuntimeError(
"camera is None")
950 detector = exposure.getDetector()
952 raise RuntimeError(
"exposure has no Detector")
953 pixelToFocalPlane = detector.getTransform(camGeom.PIXELS, camGeom.FOCAL_PLANE)
954 focalPlaneToFieldAngle = camera.getTransformMap().getTransform(camGeom.FOCAL_PLANE,
956 distortedWcs = makeDistortedTanWcs(wcs, pixelToFocalPlane, focalPlaneToFieldAngle)
957 exposure.setWcs(distortedWcs)
961 """Scale an exposure by the amplifier gains. 965 exposure : `lsst.afw.image.Exposure` 966 Exposure to process. The image is modified. 967 normalizeGains : `Bool`, optional 968 If True, then amplifiers are scaled to force the median of 969 each amplifier to equal the median of those medians. 971 ccd = exposure.getDetector()
972 ccdImage = exposure.getMaskedImage()
976 sim = ccdImage.Factory(ccdImage, amp.getBBox())
980 medians.append(numpy.median(sim.getImage().getArray()))
983 median = numpy.median(numpy.array(medians))
984 for index, amp
in enumerate(ccd):
985 sim = ccdImage.Factory(ccdImage, amp.getBBox())
986 if medians[index] != 0.0:
987 sim *= median/medians[index]
991 """Grow the saturation trails by an amount dependent on the width of the trail. 995 mask : `lsst.afw.image.Mask` 996 Mask which will have the saturated areas grown. 1000 for i
in range(1, 6):
1001 extraGrowDict[i] = 0
1002 for i
in range(6, 8):
1003 extraGrowDict[i] = 1
1004 for i
in range(8, 10):
1005 extraGrowDict[i] = 3
1008 if extraGrowMax <= 0:
1011 saturatedBit = mask.getPlaneBitMask(
"SAT")
1013 xmin, ymin = mask.getBBox().getMin()
1014 width = mask.getWidth()
1016 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1017 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1020 for s
in fp.getSpans():
1021 x0, x1 = s.getX0(), s.getX1()
1023 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1026 x0 -= xmin + extraGrow
1027 x1 -= xmin - extraGrow
1034 mask.array[y, x0:x1+1] |= saturatedBit
1038 """Set all BAD areas of the chip to the average of the rest of the exposure 1042 exposure : `lsst.afw.image.Exposure` 1043 Exposure to mask. The exposure mask is modified. 1044 badStatistic : `str`, optional 1045 Statistic to use to generate the replacement value from the 1046 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'. 1050 badPixelCount : scalar 1051 Number of bad pixels masked. 1052 badPixelValue : scalar 1053 Value substituted for bad pixels. 1058 Raised if `badStatistic` is not an allowed value. 1060 if badStatistic ==
"MEDIAN":
1061 statistic = afwMath.MEDIAN
1062 elif badStatistic ==
"MEANCLIP":
1063 statistic = afwMath.MEANCLIP
1065 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1067 mi = exposure.getMaskedImage()
1069 BAD = mask.getPlaneBitMask(
"BAD")
1070 INTRP = mask.getPlaneBitMask(
"INTRP")
1072 sctrl = afwMath.StatisticsControl()
1073 sctrl.setAndMask(BAD)
1074 value = afwMath.makeStatistics(mi, statistic, sctrl).getValue()
1076 maskArray = mask.getArray()
1077 imageArray = mi.getImage().getArray()
1078 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1079 imageArray[:] = numpy.where(badPixels, value, imageArray)
1081 return badPixels.sum(), value
def illuminationCorrection(maskedImage, illumMaskedImage, illumScale)
def addDistortionModel(exposure, camera)
Update the WCS in exposure with a distortion model based on camera geometry.
def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None)
def setBadRegions(exposure, badStatistic="MEDIAN")
def brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain)
def transposeDefectList(defectList)
def getDefectListFromMask(maskedImage, maskName)
def interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None)
def defectListFromFootprintList(fpList)
def transposeMaskedImage(maskedImage)
def interpolateFromMask(maskedImage, fwhm, growFootprints=1, maskName='SAT', fallbackValue=None)
def trimToMatchCalibBBox(rawMaskedImage, calibMaskedImage)
def attachTransmissionCurve(exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False)
def makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
def widenSaturationTrails(mask)
def applyGains(exposure, normalizeGains=False)
def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False)
def biasCorrection(maskedImage, biasMaskedImage, trimToFit=False)
def gainContext(exp, image, apply)
def updateVariance(maskedImage, gain, readNoise)
def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0, statControl=None, overscanIsInt=True)
def maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')