37 from contextlib
import contextmanager
41 """Make a double Gaussian PSF. 46 FWHM of double Gaussian smoothing kernel. 50 psf : `lsst.meas.algorithms.DoubleGaussianPsf` 51 The created smoothing kernel. 53 ksize = 4*int(fwhm) + 1
54 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
58 """Make a transposed copy of a masked image. 62 maskedImage : `lsst.afw.image.MaskedImage` 67 transposed : `lsst.afw.image.MaskedImage` 68 The transposed copy of the input image. 70 transposed = maskedImage.Factory(afwGeom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
71 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
72 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
73 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
78 """Interpolate over defects specified in a defect list. 82 maskedImage : `lsst.afw.image.MaskedImage` 85 List of defects to interpolate over. 87 FWHM of double Gaussian smoothing kernel. 88 fallbackValue : scalar, optional 89 Fallback value if an interpolated value cannot be determined. 90 If None, then the clipped mean of the image is used. 93 if fallbackValue
is None:
94 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
95 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
96 maskedImage.getMask.addMaskPlane(
'INTRP')
97 measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue,
True)
101 """Compute a defect list from a footprint list, optionally growing the footprints. 105 fpList : `list` of `lsst.afw.detection.Footprint` 106 Footprint list to process. 110 defectList : `list` of `lsst.afw.meas.algorithms.Defect` 115 for bbox
in afwDetection.footprintToBBoxList(fp):
116 defect = measAlg.Defect(bbox)
117 defectList.append(defect)
122 """Make a transposed copy of a defect list. 126 defectList : `list` of `lsst.afw.meas.algorithms.Defect` 127 Input list of defects. 131 retDefectList : `list` of `lsst.afw.meas.algorithms.Defect` 132 Transposed list of defects. 135 for defect
in defectList:
136 bbox = defect.getBBox()
137 nbbox = afwGeom.Box2I(afwGeom.Point2I(bbox.getMinY(), bbox.getMinX()),
138 afwGeom.Extent2I(bbox.getDimensions()[1], bbox.getDimensions()[0]))
139 retDefectList.append(measAlg.Defect(nbbox))
144 """Set mask plane based on a defect list. 148 maskedImage : `lsst.afw.image.MaskedImage` 149 Image to process. Only the mask plane is updated. 150 defectList : `list` of `lsst.afw.meas.algorithms.Defect` 152 maskName : str, optional 153 Mask plane name to use. 156 mask = maskedImage.getMask()
157 bitmask = mask.getPlaneBitMask(maskName)
158 for defect
in defectList:
159 bbox = defect.getBBox()
160 afwGeom.SpanSet(bbox).clippedTo(mask.getBBox()).setMask(mask, bitmask)
164 """Compute a defect list from a specified mask plane. 168 maskedImage : `lsst.afw.image.MaskedImage` 170 maskName : str or `list` 171 Mask plane name, or list of names to convert. 175 defectList : `list` of `lsst.afw.meas.algorithms.Defect` 176 Defect list constructed from masked pixels. 178 mask = maskedImage.getMask()
179 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
180 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
185 """Mask pixels based on threshold detection. 189 maskedImage : `lsst.afw.image.MaskedImage` 190 Image to process. Only the mask plane is updated. 193 growFootprints : scalar, optional 194 Number of pixels to grow footprints of detected regions. 195 maskName : str, optional 196 Mask plane name, or list of names to convert 200 defectList : `list` of `lsst.afw.meas.algorithms.Defect` 201 Defect list constructed from pixels above the threshold. 204 thresh = afwDetection.Threshold(threshold)
205 fs = afwDetection.FootprintSet(maskedImage, thresh)
207 if growFootprints > 0:
208 fs = afwDetection.FootprintSet(fs, growFootprints)
209 fpList = fs.getFootprints()
212 mask = maskedImage.getMask()
213 bitmask = mask.getPlaneBitMask(maskName)
214 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
220 maskNameList=['SAT'], fallbackValue=None):
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. 236 mask = maskedImage.getMask()
238 if growSaturatedFootprints > 0
and "SAT" in maskNameList:
239 thresh = afwDetection.Threshold(mask.getPlaneBitMask(
"SAT"), afwDetection.Threshold.BITMASK)
240 fpSet = afwDetection.FootprintSet(mask, thresh)
243 fpSet = afwDetection.FootprintSet(fpSet, rGrow=growSaturatedFootprints, isotropic=
False)
244 fpSet.setMask(mask,
"SAT")
246 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskNameList), afwDetection.Threshold.BITMASK)
247 fpSet = afwDetection.FootprintSet(mask, thresh)
254 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
256 """Mark saturated pixels and optionally interpolate over them 260 maskedImage : `lsst.afw.image.MaskedImage` 263 Saturation level used as the detection threshold. 265 FWHM of double Gaussian smoothing kernel. 266 growFootprints : scalar, optional 267 Number of pixels to grow footprints of detected regions. 268 interpolate : Bool, optional 269 If True, saturated pixels are interpolated over. 270 maskName : str, optional 272 fallbackValue : scalar, optional 273 Value of last resort for interpolation. 276 maskedImage=maskedImage,
277 threshold=saturation,
278 growFootprints=growFootprints,
286 """Compute number of edge trim pixels to match the calibration data. 288 Use the dimension difference between the raw exposure and the 289 calibration exposure to compute the edge trim pixels. This trim 290 is applied symmetrically, with the same number of pixels masked on 295 rawMaskedImage : `lsst.afw.image.MaskedImage` 297 calibMaskedImage : `lsst.afw.image.MaskedImage` 298 Calibration image to draw new bounding box from. 302 replacementMaskedImage : `lsst.afw.image.MaskedImage` 303 ``rawMaskedImage`` trimmed to the appropriate size 307 Rasied if ``rawMaskedImage`` cannot be symmetrically trimmed to 308 match ``calibMaskedImage``. 310 nx, ny = rawMaskedImage.getBBox().getDimensions() - calibMaskedImage.getBBox().getDimensions()
312 raise RuntimeError(
"Raw and calib maskedImages are trimmed differently in X and Y.")
314 raise RuntimeError(
"Calibration maskedImage is trimmed unevenly in X.")
316 raise RuntimeError(
"Calibration maskedImage is larger than raw data.")
320 replacementMaskedImage = rawMaskedImage[nEdge:-nEdge, nEdge:-nEdge, afwImage.LOCAL]
321 SourceDetectionTask.setEdgeBits(
323 replacementMaskedImage.getBBox(),
324 rawMaskedImage.getMask().getPlaneBitMask(
"EDGE")
327 replacementMaskedImage = rawMaskedImage
329 return replacementMaskedImage
333 """Apply bias correction in place. 337 maskedImage : `lsst.afw.image.MaskedImage` 338 Image to process. The image is modified by this method. 339 biasMaskedImage : `lsst.afw.image.MaskedImage` 340 Bias image of the same size as ``maskedImage`` 341 trimToFit : `Bool`, optional 342 If True, raw data is symmetrically trimmed to match 348 Raised if ``maskedImage`` and ``biasMaskedImage`` do not have 355 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
356 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
357 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
358 maskedImage -= biasMaskedImage
361 def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False, trimToFit=False):
362 """Apply dark correction in place. 366 maskedImage : `lsst.afw.image.MaskedImage` 367 Image to process. The image is modified by this method. 368 darkMaskedImage : `lsst.afw.image.MaskedImage` 369 Dark image of the same size as ``maskedImage``. 371 Dark exposure time for ``maskedImage``. 373 Dark exposure time for ``darkMaskedImage``. 374 invert : `Bool`, optional 375 If True, re-add the dark to an already corrected image. 376 trimToFit : `Bool`, optional 377 If True, raw data is symmetrically trimmed to match 383 Raised if ``maskedImage`` and ``darkMaskedImage`` do not have 388 The dark correction is applied by calculating: 389 maskedImage -= dark * expScaling / darkScaling 394 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
395 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
396 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
398 scale = expScale / darkScale
400 maskedImage.scaledMinus(scale, darkMaskedImage)
402 maskedImage.scaledPlus(scale, darkMaskedImage)
406 """Set the variance plane based on the image plane. 410 maskedImage : `lsst.afw.image.MaskedImage` 411 Image to process. The variance plane is modified. 413 The amplifier gain in electrons/ADU. 415 The amplifier read nmoise in ADU/pixel. 417 var = maskedImage.getVariance()
418 var[:] = maskedImage.getImage()
423 def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False, trimToFit=False):
424 """Apply flat correction in place. 428 maskedImage : `lsst.afw.image.MaskedImage` 429 Image to process. The image is modified. 430 flatMaskedImage : `lsst.afw.image.MaskedImage` 431 Flat image of the same size as ``maskedImage`` 433 Flat scale computation method. Allowed values are 'MEAN', 435 userScale : scalar, optional 436 Scale to use if ``scalingType``='USER'. 437 invert : `Bool`, optional 438 If True, unflatten an already flattened image. 439 trimToFit : `Bool`, optional 440 If True, raw data is symmetrically trimmed to match 446 Raised if ``maskedImage`` and ``flatMaskedImage`` do not have 449 Raised if ``scalingType`` is not an allowed value. 454 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
455 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
456 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
461 if scalingType
in (
'MEAN',
'MEDIAN'):
462 scalingType = afwMath.stringToStatisticsProperty(scalingType)
463 flatScale = afwMath.makeStatistics(flatMaskedImage.image, scalingType).getValue()
464 elif scalingType ==
'USER':
465 flatScale = userScale
467 raise pexExcept.Exception(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
470 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
472 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
476 """Apply illumination correction in place. 480 maskedImage : `lsst.afw.image.MaskedImage` 481 Image to process. The image is modified. 482 illumMaskedImage : `lsst.afw.image.MaskedImage` 483 Illumination correction image of the same size as ``maskedImage``. 485 Scale factor for the illumination correction. 490 Raised if ``maskedImage`` and ``illumMaskedImage`` do not have 493 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
494 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
495 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
497 maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
500 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0,
501 statControl=None, overscanIsInt=True):
502 """Apply overscan correction in place. 506 ampMaskedImage : `lsst.afw.image.MaskedImage` 507 Image of amplifier to correct; modified. 508 overscanImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 509 Image of overscan; modified. 511 Type of fit for overscan correction. May be one of: 513 - ``MEAN``: use mean of overscan. 514 - ``MEANCLIP``: use clipped mean of overscan. 515 - ``MEDIAN``: use median of overscan. 516 - ``POLY``: fit with ordinary polynomial. 517 - ``CHEB``: fit with Chebyshev polynomial. 518 - ``LEG``: fit with Legendre polynomial. 519 - ``NATURAL_SPLINE``: fit with natural spline. 520 - ``CUBIC_SPLINE``: fit with cubic spline. 521 - ``AKIMA_SPLINE``: fit with Akima spline. 524 Polynomial order or number of spline knots; ignored unless 525 ``fitType`` indicates a polynomial or spline. 526 statControl : `lsst.afw.math.StatisticsControl` 527 Statistics control object. In particular, we pay attention to numSigmaClip 528 overscanIsInt : `bool` 529 Treat the overscan region as consisting of integers, even if it's been 530 converted to float. E.g. handle ties properly. 534 result : `lsst.pipe.base.Struct` 535 Result struct with components: 537 - ``imageFit``: Value(s) removed from image (scalar or 538 `lsst.afw.image.Image`) 539 - ``overscanFit``: Value(s) removed from overscan (scalar or 540 `lsst.afw.image.Image`) 541 - ``overscanImage``: Overscan corrected overscan region 542 (`lsst.afw.image.Image`) 546 Raised if ``fitType`` is not an allowed value. 550 The ``ampMaskedImage`` and ``overscanImage`` are modified, with the fit 551 subtracted. Note that the ``overscanImage`` should not be a subimage of 552 the ``ampMaskedImage``, to avoid being subtracted twice. 554 Debug plots are available for the SPLINE fitTypes by setting the 555 `debug.display` for `name` == "lsst.ip.isr.isrFunctions". These 556 plots show the scatter plot of the overscan data (collapsed along 557 the perpendicular dimension) as a function of position on the CCD 558 (normalized between +/-1). 560 ampImage = ampMaskedImage.getImage()
561 if statControl
is None:
562 statControl = afwMath.StatisticsControl()
564 numSigmaClip = statControl.getNumSigmaClip()
566 if fitType
in (
'MEAN',
'MEANCLIP'):
567 fitType = afwMath.stringToStatisticsProperty(fitType)
568 offImage = afwMath.makeStatistics(overscanImage, fitType, statControl).getValue()
569 overscanFit = offImage
570 elif fitType
in (
'MEDIAN',):
573 if hasattr(overscanImage,
"image"):
574 imageI = overscanImage.image.convertI()
575 overscanImageI = afwImage.MaskedImageI(imageI, overscanImage.mask, overscanImage.variance)
577 overscanImageI = overscanImage.convertI()
579 overscanImageI = overscanImage
581 fitType = afwMath.stringToStatisticsProperty(fitType)
582 offImage = afwMath.makeStatistics(overscanImageI, fitType, statControl).getValue()
583 overscanFit = offImage
587 elif fitType
in (
'POLY',
'CHEB',
'LEG',
'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
588 if hasattr(overscanImage,
"getImage"):
589 biasArray = overscanImage.getImage().getArray()
590 biasArray = numpy.ma.masked_where(overscanImage.getMask().getArray() & statControl.getAndMask(),
593 biasArray = overscanImage.getArray()
595 shortInd = numpy.argmin(biasArray.shape)
598 biasArray = numpy.transpose(biasArray)
601 percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
602 medianBiasArr = percentiles[1]
603 stdevBiasArr = 0.74*(percentiles[2] - percentiles[0])
604 diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
605 biasMaskedArr = numpy.ma.masked_where(diff > numSigmaClip*stdevBiasArr[:, numpy.newaxis], biasArray)
606 collapsed = numpy.mean(biasMaskedArr, axis=1)
607 if collapsed.mask.sum() > 0:
608 collapsed.data[collapsed.mask] = numpy.mean(biasArray.data[collapsed.mask], axis=1)
609 del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
612 collapsed = numpy.transpose(collapsed)
615 indices = 2.0*numpy.arange(num)/float(num) - 1.0
617 if fitType
in (
'POLY',
'CHEB',
'LEG'):
619 poly = numpy.polynomial
620 fitter, evaler = {
"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
621 "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
622 "LEG": (poly.legendre.legfit, poly.legendre.legval),
625 coeffs = fitter(indices, collapsed, order)
626 fitBiasArr = evaler(indices, coeffs)
627 elif 'SPLINE' in fitType:
636 collapsedMask = collapsed.mask
638 if collapsedMask == numpy.ma.nomask:
639 collapsedMask = numpy.array(len(collapsed)*[numpy.ma.nomask])
643 numPerBin, binEdges = numpy.histogram(indices, bins=numBins,
644 weights=1-collapsedMask.astype(int))
647 with numpy.errstate(invalid=
"ignore"):
648 values = numpy.histogram(indices, bins=numBins,
649 weights=collapsed.data*~collapsedMask)[0]/numPerBin
650 binCenters = numpy.histogram(indices, bins=numBins,
651 weights=indices*~collapsedMask)[0]/numPerBin
652 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
653 values.astype(float)[numPerBin > 0],
654 afwMath.stringToInterpStyle(fitType))
655 fitBiasArr = numpy.array([interp.interpolate(i)
for i
in indices])
659 import matplotlib.pyplot
as plot
660 figure = plot.figure(1)
662 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
663 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
664 if collapsedMask.sum() > 0:
665 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
666 axes.plot(indices, fitBiasArr,
'r-')
667 plot.xlabel(
"centered/scaled position along overscan region")
668 plot.ylabel(
"pixel value/fit value")
670 prompt =
"Press Enter or c to continue [chp]... " 672 ans = input(prompt).lower()
673 if ans
in (
"",
"c",):
679 print(
"h[elp] c[ontinue] p[db]")
682 offImage = ampImage.Factory(ampImage.getDimensions())
683 offArray = offImage.getArray()
684 overscanFit = afwImage.ImageF(overscanImage.getDimensions())
685 overscanArray = overscanFit.getArray()
687 offArray[:, :] = fitBiasArr[:, numpy.newaxis]
688 overscanArray[:, :] = fitBiasArr[:, numpy.newaxis]
690 offArray[:, :] = fitBiasArr[numpy.newaxis, :]
691 overscanArray[:, :] = fitBiasArr[numpy.newaxis, :]
699 mask = ampMaskedImage.getMask()
700 maskArray = mask.getArray()
if shortInd == 1
else mask.getArray().transpose()
701 suspect = mask.getPlaneBitMask(
"SUSPECT")
703 if collapsed.mask == numpy.ma.nomask:
707 for low
in range(num):
708 if not collapsed.mask[low]:
711 maskArray[:low, :] |= suspect
712 for high
in range(1, num):
713 if not collapsed.mask[-high]:
716 maskArray[-high:, :] |= suspect
719 raise pexExcept.Exception(
'%s : %s an invalid overscan type' % (
"overscanCorrection", fitType))
721 overscanImage -= overscanFit
722 return Struct(imageFit=offImage, overscanFit=overscanFit, overscanImage=overscanImage)
726 """Apply brighter fatter correction in place for the image. 730 exposure : `lsst.afw.image.Exposure` 731 Exposure to have brighter-fatter correction applied. Modified 733 kernel : `numpy.ndarray` 734 Brighter-fatter kernel to apply. 736 Number of correction iterations to run. 738 Convergence threshold in terms of the sum of absolute 739 deviations between an iteration and the previous one. 741 If True, then the exposure values are scaled by the gain prior 746 This correction takes a kernel that has been derived from flat 747 field images to redistribute the charge. The gradient of the 748 kernel is the deflection field due to the accumulated charge. 750 Given the original image I(x) and the kernel K(x) we can compute 751 the corrected image Ic(x) using the following equation: 753 Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y)))) 755 To evaluate the derivative term we expand it as follows: 757 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))) ) 759 Because we use the measured counts instead of the incident counts 760 we apply the correction iteratively to reconstruct the original 761 counts and the correction. We stop iterating when the summed 762 difference between the current corrected image and the one from 763 the previous iteration is below the threshold. We do not require 764 convergence because the number of iterations is too large a 765 computational cost. How we define the threshold still needs to be 766 evaluated, the current default was shown to work reasonably well 767 on a small set of images. For more information on the method see 768 DocuShare Document-19407. 770 The edges as defined by the kernel are not corrected because they 771 have spurious values due to the convolution. 773 image = exposure.getMaskedImage().getImage()
778 kLx = numpy.shape(kernel)[0]
779 kLy = numpy.shape(kernel)[1]
780 kernelImage = afwImage.ImageD(kLx, kLy)
781 kernelImage.getArray()[:, :] = kernel
782 tempImage = image.clone()
784 nanIndex = numpy.isnan(tempImage.getArray())
785 tempImage.getArray()[nanIndex] = 0.
787 outImage = afwImage.ImageF(image.getDimensions())
788 corr = numpy.zeros_like(image.getArray())
789 prev_image = numpy.zeros_like(image.getArray())
790 convCntrl = afwMath.ConvolutionControl(
False,
True, 1)
791 fixedKernel = afwMath.FixedKernel(kernelImage)
801 for iteration
in range(maxIter):
803 afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
804 tmpArray = tempImage.getArray()
805 outArray = outImage.getArray()
807 with numpy.errstate(invalid=
"ignore", over=
"ignore"):
809 gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
810 gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
811 first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
814 diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
815 diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
816 second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
818 corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
820 tmpArray[:, :] = image.getArray()[:, :]
821 tmpArray[nanIndex] = 0.
822 tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
825 diff = numpy.sum(numpy.abs(prev_image - tmpArray))
829 prev_image[:, :] = tmpArray[:, :]
836 image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
837 corr[startY + 1:endY - 1, startX + 1:endX - 1]
842 """Context manager that applies and removes gain. 846 exp : `lsst.afw.image.Exposure` 847 Exposure to apply/remove gain. 848 image : `lsst.afw.image.Image` 849 Image to apply/remove gain. 851 If True, apply and remove the amplifier gain. 855 exp : `lsst.afw.image.Exposure` 856 Exposure with the gain applied. 859 ccd = exp.getDetector()
861 sim = image.Factory(image, amp.getBBox())
868 ccd = exp.getDetector()
870 sim = image.Factory(image, amp.getBBox())
875 sensorTransmission=None, atmosphereTransmission=None):
876 """Attach a TransmissionCurve to an Exposure, given separate curves for 877 different components. 881 exposure : `lsst.afw.image.Exposure` 882 Exposure object to modify by attaching the product of all given 883 ``TransmissionCurves`` in post-assembly trimmed detector coordinates. 884 Must have a valid ``Detector`` attached that matches the detector 885 associated with sensorTransmission. 886 opticsTransmission : `lsst.afw.image.TransmissionCurve` 887 A ``TransmissionCurve`` that represents the throughput of the optics, 888 to be evaluated in focal-plane coordinates. 889 filterTransmission : `lsst.afw.image.TransmissionCurve` 890 A ``TransmissionCurve`` that represents the throughput of the filter 891 itself, to be evaluated in focal-plane coordinates. 892 sensorTransmission : `lsst.afw.image.TransmissionCurve` 893 A ``TransmissionCurve`` that represents the throughput of the sensor 894 itself, to be evaluated in post-assembly trimmed detector coordinates. 895 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 896 A ``TransmissionCurve`` that represents the throughput of the 897 atmosphere, assumed to be spatially constant. 901 combined : `lsst.afw.image.TransmissionCurve` 902 The TransmissionCurve attached to the exposure. 906 All ``TransmissionCurve`` arguments are optional; if none are provided, the 907 attached ``TransmissionCurve`` will have unit transmission everywhere. 909 combined = afwImage.TransmissionCurve.makeIdentity()
910 if atmosphereTransmission
is not None:
911 combined *= atmosphereTransmission
912 if opticsTransmission
is not None:
913 combined *= opticsTransmission
914 if filterTransmission
is not None:
915 combined *= filterTransmission
916 detector = exposure.getDetector()
917 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
918 toSys=camGeom.PIXELS)
919 combined = combined.transformedBy(fpToPix)
920 if sensorTransmission
is not None:
921 combined *= sensorTransmission
922 exposure.getInfo().setTransmissionCurve(combined)
927 """!Update the WCS in exposure with a distortion model based on camera 932 exposure : `lsst.afw.image.Exposure` 933 Exposure to process. Must contain a Detector and WCS. The 934 exposure is modified. 935 camera : `lsst.afw.cameraGeom.Camera` 941 Raised if ``exposure`` is lacking a Detector or WCS, or if 945 Add a model for optical distortion based on geometry found in ``camera`` 946 and the ``exposure``'s detector. The raw input exposure is assumed 947 have a TAN WCS that has no compensation for optical distortion. 948 Two other possibilities are: 949 - The raw input exposure already has a model for optical distortion, 950 as is the case for raw DECam data. 951 In that case you should set config.doAddDistortionModel False. 952 - The raw input exposure has a model for distortion, but it has known 953 deficiencies severe enough to be worth fixing (e.g. because they 954 cause problems for fitting a better WCS). In that case you should 955 override this method with a version suitable for your raw data. 958 wcs = exposure.getWcs()
960 raise RuntimeError(
"exposure has no WCS")
962 raise RuntimeError(
"camera is None")
963 detector = exposure.getDetector()
965 raise RuntimeError(
"exposure has no Detector")
966 pixelToFocalPlane = detector.getTransform(camGeom.PIXELS, camGeom.FOCAL_PLANE)
967 focalPlaneToFieldAngle = camera.getTransformMap().getTransform(camGeom.FOCAL_PLANE,
969 distortedWcs = makeDistortedTanWcs(wcs, pixelToFocalPlane, focalPlaneToFieldAngle)
970 exposure.setWcs(distortedWcs)
974 """Scale an exposure by the amplifier gains. 978 exposure : `lsst.afw.image.Exposure` 979 Exposure to process. The image is modified. 980 normalizeGains : `Bool`, optional 981 If True, then amplifiers are scaled to force the median of 982 each amplifier to equal the median of those medians. 984 ccd = exposure.getDetector()
985 ccdImage = exposure.getMaskedImage()
989 sim = ccdImage.Factory(ccdImage, amp.getBBox())
993 medians.append(numpy.median(sim.getImage().getArray()))
996 median = numpy.median(numpy.array(medians))
997 for index, amp
in enumerate(ccd):
998 sim = ccdImage.Factory(ccdImage, amp.getDataSec())
999 sim *= median/medians[index]
1003 """Grow the saturation trails by an amount dependent on the width of the trail. 1007 mask : `lsst.afw.image.Mask` 1008 Mask which will have the saturated areas grown. 1012 for i
in range(1, 6):
1013 extraGrowDict[i] = 0
1014 for i
in range(6, 8):
1015 extraGrowDict[i] = 1
1016 for i
in range(8, 10):
1017 extraGrowDict[i] = 3
1020 if extraGrowMax <= 0:
1023 saturatedBit = mask.getPlaneBitMask(
"SAT")
1025 xmin, ymin = mask.getBBox().getMin()
1026 width = mask.getWidth()
1028 thresh = afwDetection.Threshold(saturatedBit, afwDetection.Threshold.BITMASK)
1029 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
1032 for s
in fp.getSpans():
1033 x0, x1 = s.getX0(), s.getX1()
1035 extraGrow = extraGrowDict.get(x1 - x0 + 1, extraGrowMax)
1038 x0 -= xmin + extraGrow
1039 x1 -= xmin - extraGrow
1046 mask.array[y, x0:x1+1] |= saturatedBit
1050 """Set all BAD areas of the chip to the average of the rest of the exposure 1054 exposure : `lsst.afw.image.Exposure` 1055 Exposure to mask. The exposure mask is modified. 1056 badStatistic : `str`, optional 1057 Statistic to use to generate the replacement value from the 1058 image data. Allowed values are 'MEDIAN' or 'MEANCLIP'. 1062 badPixelCount : scalar 1063 Number of bad pixels masked. 1064 badPixelValue : scalar 1065 Value substituted for bad pixels. 1070 Raised if `badStatistic` is not an allowed value. 1072 if badStatistic ==
"MEDIAN":
1073 statistic = afwMath.MEDIAN
1074 elif badStatistic ==
"MEANCLIP":
1075 statistic = afwMath.MEANCLIP
1077 raise RuntimeError(
"Impossible method %s of bad region correction" % badStatistic)
1079 mi = exposure.getMaskedImage()
1081 BAD = mask.getPlaneBitMask(
"BAD")
1082 INTRP = mask.getPlaneBitMask(
"INTRP")
1084 sctrl = afwMath.StatisticsControl()
1085 sctrl.setAndMask(BAD)
1086 value = afwMath.makeStatistics(mi, statistic, sctrl).getValue()
1088 maskArray = mask.getArray()
1089 imageArray = mi.getImage().getArray()
1090 badPixels = numpy.logical_and((maskArray & BAD) > 0, (maskArray & INTRP) == 0)
1091 imageArray[:] = numpy.where(badPixels, value, imageArray)
1093 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 interpolateFromMask(maskedImage, fwhm, growSaturatedFootprints=1, maskNameList=['SAT'], fallbackValue=None)
def interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None)
def defectListFromFootprintList(fpList)
def transposeMaskedImage(maskedImage)
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')