38 """Make a double Gaussian PSF 40 @param[in] fwhm FWHM of double Gaussian smoothing kernel 41 @return measAlg.DoubleGaussianPsf 43 ksize = 4*int(fwhm) + 1
44 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
48 """Make a transposed copy of a masked image 50 @param[in] maskedImage afw.image.MaskedImage to process 51 @return transposed masked image 53 transposed = maskedImage.Factory(afwGeom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
54 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
55 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
56 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
61 """Interpolate over defects specified in a defect list 63 @param[in,out] maskedImage masked image to process 64 @param[in] defectList defect list 65 @param[in] fwhm FWHM of double Gaussian smoothing kernel 66 @param[in] fallbackValue fallback value if an interpolated value cannot be determined; 67 if None then use clipped mean image value 70 if fallbackValue
is None:
71 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
72 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
73 maskedImage.getMask.addMaskPlane(
'INTRP')
74 measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue,
True)
78 """Compute a defect list from a footprint list, optionally growing the footprints 80 @param[in] fpList footprint list 84 for bbox
in afwDetection.footprintToBBoxList(fp):
85 defect = measAlg.Defect(bbox)
86 defectList.append(defect)
91 """Make a transposed copy of a defect list 93 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 94 @return a defect list with transposed defects 97 for defect
in defectList:
98 bbox = defect.getBBox()
99 nbbox = afwGeom.Box2I(afwGeom.Point2I(bbox.getMinY(), bbox.getMinX()),
100 afwGeom.Extent2I(bbox.getDimensions()[1], bbox.getDimensions()[0]))
101 retDefectList.append(measAlg.Defect(nbbox))
106 """Set mask plane based on a defect list 108 @param[in,out] maskedImage afw.image.MaskedImage to process; mask plane is updated 109 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 110 @param[in] maskName mask plane name 113 mask = maskedImage.getMask()
114 bitmask = mask.getPlaneBitMask(maskName)
115 for defect
in defectList:
116 bbox = defect.getBBox()
117 afwGeom.SpanSet(bbox).clippedTo(mask.getBBox()).setMask(mask, bitmask)
121 """Compute a defect list from a specified mask plane 123 @param[in] maskedImage masked image to process 124 @param[in] maskName mask plane name, or list of names 126 mask = maskedImage.getMask()
127 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
128 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
133 """Mask pixels based on threshold detection 135 @param[in,out] maskedImage afw.image.MaskedImage to process; the mask is altered 136 @param[in] threshold detection threshold 137 @param[in] growFootprints amount by which to grow footprints of detected regions 138 @param[in] maskName mask plane name 139 @return a list of defects (meas.algrithms.Defect) of regions set in the mask. 142 thresh = afwDetection.Threshold(threshold)
143 fs = afwDetection.FootprintSet(maskedImage, thresh)
145 if growFootprints > 0:
146 fs = afwDetection.FootprintSet(fs, growFootprints)
148 fpList = fs.getFootprints()
150 mask = maskedImage.getMask()
151 bitmask = mask.getPlaneBitMask(maskName)
152 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
158 """Interpolate over defects identified by a particular mask plane 160 @param[in,out] maskedImage afw.image.MaskedImage to process 161 @param[in] fwhm FWHM of double Gaussian smoothing kernel 162 @param[in] growFootprints amount by which to grow footprints of detected regions 163 @param[in] maskName mask plane name 164 @param[in] fallbackValue value of last resort for interpolation 166 mask = maskedImage.getMask()
167 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
168 fpSet = afwDetection.FootprintSet(mask, thresh)
169 if growFootprints > 0:
170 fpSet = afwDetection.FootprintSet(fpSet, rGrow=growFootprints, isotropic=
False)
173 fpSet.setMask(mask, maskName)
178 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
180 """Mark saturated pixels and optionally interpolate over them 182 @param[in,out] maskedImage afw.image.MaskedImage to process 183 @param[in] saturation saturation level (used as a detection threshold) 184 @param[in] fwhm FWHM of double Gaussian smoothing kernel 185 @param[in] growFootprints amount by which to grow footprints of detected regions 186 @param[in] interpolate interpolate over saturated pixels? 187 @param[in] maskName mask plane name 188 @param[in] fallbackValue value of last resort for interpolation 191 maskedImage=maskedImage,
192 threshold=saturation,
193 growFootprints=growFootprints,
201 """Apply bias correction in place 203 @param[in,out] maskedImage masked image to correct 204 @param[in] biasMaskedImage bias, as a masked image 206 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
207 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
208 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
209 maskedImage -= biasMaskedImage
212 def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False):
213 """Apply dark correction in place 215 maskedImage -= dark * expScaling / darkScaling 217 @param[in,out] maskedImage afw.image.MaskedImage to correct 218 @param[in] darkMaskedImage dark afw.image.MaskedImage 219 @param[in] expScale exposure scale 220 @param[in] darkScale dark scale 221 @param[in] invert if True, remove the dark from an already-corrected image 223 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
224 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
225 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
227 scale = expScale / darkScale
229 maskedImage.scaledMinus(scale, darkMaskedImage)
231 maskedImage.scaledPlus(scale, darkMaskedImage)
235 """Set the variance plane based on the image plane 237 @param[in,out] maskedImage afw.image.MaskedImage; image plane is read and variance plane is written 238 @param[in] gain amplifier gain (e-/ADU) 239 @param[in] readNoise amplifier read noise (ADU/pixel) 241 var = maskedImage.getVariance()
242 var[:] = maskedImage.getImage()
247 def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False):
248 """Apply flat correction in place 250 @param[in,out] maskedImage afw.image.MaskedImage to correct 251 @param[in] flatMaskedImage flat field afw.image.MaskedImage 252 @param[in] scalingType how to compute flat scale; one of 'MEAN', 'MEDIAN' or 'USER' 253 @param[in] userScale scale to use if scalingType is 'USER', else ignored 254 @param[in] invert if True, unflatten an already-flattened image instead. 256 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
257 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
258 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
263 if scalingType
in (
'MEAN',
'MEDIAN'):
264 scalingType = afwMath.stringToStatisticsProperty(scalingType)
265 flatScale = afwMath.makeStatistics(flatMaskedImage.image, scalingType).getValue()
266 elif scalingType ==
'USER':
267 flatScale = userScale
269 raise pexExcept.Exception(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
272 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
274 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
278 """Apply illumination correction in place 280 @param[in,out] maskedImage afw.image.MaskedImage to correct 281 @param[in] illumMaskedImage illumination correction masked image 282 @param[in] illumScale scale value for illumination correction 284 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
285 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
286 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
288 maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
291 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, statControl=None,
293 """Apply overscan correction in-place 295 The ``ampMaskedImage`` and ``overscanImage`` are modified, with the fit 296 subtracted. Note that the ``overscanImage`` should not be a subimage of 297 the ``ampMaskedImage``, to avoid being subtracted twice. 301 ampMaskedImage : `lsst.afw.image.MaskedImage` 302 Image of amplifier to correct; modified. 303 overscanImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 304 Image of overscan; modified. 306 Type of fit for overscan correction. May be one of: 308 - ``MEAN``: use mean of overscan. 309 - ``MEANCLIP``: use clipped mean of overscan. 310 - ``MEDIAN``: use median of overscan. 311 - ``POLY``: fit with ordinary polynomial. 312 - ``CHEB``: fit with Chebyshev polynomial. 313 - ``LEG``: fit with Legendre polynomial. 314 - ``NATURAL_SPLINE``: fit with natural spline. 315 - ``CUBIC_SPLINE``: fit with cubic spline. 316 - ``AKIMA_SPLINE``: fit with Akima spline. 319 Polynomial order or number of spline knots; ignored unless 320 ``fitType`` indicates a polynomial or spline. 321 statControl : `lsst.afw.math.StatisticsControl` 322 Statistics control object. In particular, we pay attention to numSigmaClip 323 overscanIsInt : `bool` 324 Treat the overscan region as consisting of integers, even if it's been 325 converted to float. E.g. handle ties properly 329 result : `lsst.pipe.base.Struct` 330 Result struct with components: 332 - ``imageFit``: Value(s) removed from image (scalar or 333 `lsst.afw.image.Image`) 334 - ``overscanFit``: Value(s) removed from overscan (scalar or 335 `lsst.afw.image.Image`) 337 ampImage = ampMaskedImage.getImage()
338 if statControl
is None:
339 statControl = afwMath.StatisticsControl()
341 numSigmaClip = statControl.getNumSigmaClip()
343 if fitType
in (
'MEAN',
'MEANCLIP'):
344 fitType = afwMath.stringToStatisticsProperty(fitType)
345 offImage = afwMath.makeStatistics(overscanImage, fitType, statControl).getValue()
346 overscanFit = offImage
347 elif fitType
in (
'MEDIAN',):
350 if hasattr(overscanImage,
"image"):
351 imageI = overscanImage.image.convertI()
352 overscanImageI = afwImage.MaskedImageI(imageI, overscanImage.mask, overscanImage.variance)
354 overscanImageI = overscanImage.convertI()
356 overscanImageI = overscanImage
358 fitType = afwMath.stringToStatisticsProperty(fitType)
359 offImage = afwMath.makeStatistics(overscanImageI, fitType, statControl).getValue()
360 overscanFit = offImage
364 elif fitType
in (
'POLY',
'CHEB',
'LEG',
'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
365 if hasattr(overscanImage,
"getImage"):
366 biasArray = overscanImage.getImage().getArray()
367 biasArray = numpy.ma.masked_where(overscanImage.getMask().getArray() & statControl.getAndMask(),
370 biasArray = overscanImage.getArray()
372 shortInd = numpy.argmin(biasArray.shape)
375 biasArray = numpy.transpose(biasArray)
378 percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
379 medianBiasArr = percentiles[1]
380 stdevBiasArr = 0.74*(percentiles[2] - percentiles[0])
381 diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
382 biasMaskedArr = numpy.ma.masked_where(diff > numSigmaClip*stdevBiasArr[:, numpy.newaxis], biasArray)
383 collapsed = numpy.mean(biasMaskedArr, axis=1)
384 if collapsed.mask.sum() > 0:
385 collapsed.data[collapsed.mask] = numpy.mean(biasArray.data[collapsed.mask], axis=1)
386 del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
389 collapsed = numpy.transpose(collapsed)
392 indices = 2.0*numpy.arange(num)/float(num) - 1.0
394 if fitType
in (
'POLY',
'CHEB',
'LEG'):
396 poly = numpy.polynomial
397 fitter, evaler = {
"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
398 "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
399 "LEG": (poly.legendre.legfit, poly.legendre.legval),
402 coeffs = fitter(indices, collapsed, order)
403 fitBiasArr = evaler(indices, coeffs)
404 elif 'SPLINE' in fitType:
413 collapsedMask = collapsed.mask
415 if collapsedMask == numpy.ma.nomask:
416 collapsedMask = numpy.array(len(collapsed)*[numpy.ma.nomask])
420 numPerBin, binEdges = numpy.histogram(indices, bins=numBins,
421 weights=1-collapsedMask.astype(int))
424 with numpy.errstate(invalid=
"ignore"):
425 values = numpy.histogram(indices, bins=numBins,
426 weights=collapsed.data*~collapsedMask)[0]/numPerBin
427 binCenters = numpy.histogram(indices, bins=numBins,
428 weights=indices*~collapsedMask)[0]/numPerBin
429 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
430 values.astype(float)[numPerBin > 0],
431 afwMath.stringToInterpStyle(fitType))
432 fitBiasArr = numpy.array([interp.interpolate(i)
for i
in indices])
436 import matplotlib.pyplot
as plot
437 figure = plot.figure(1)
439 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
440 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
441 if collapsedMask.sum() > 0:
442 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
443 axes.plot(indices, fitBiasArr,
'r-')
445 prompt =
"Press Enter or c to continue [chp]... " 447 ans = input(prompt).lower()
448 if ans
in (
"",
"c",):
454 print(
"h[elp] c[ontinue] p[db]")
457 offImage = ampImage.Factory(ampImage.getDimensions())
458 offArray = offImage.getArray()
459 overscanFit = afwImage.ImageF(overscanImage.getDimensions())
460 overscanArray = overscanFit.getArray()
462 offArray[:, :] = fitBiasArr[:, numpy.newaxis]
463 overscanArray[:, :] = fitBiasArr[:, numpy.newaxis]
465 offArray[:, :] = fitBiasArr[numpy.newaxis, :]
466 overscanArray[:, :] = fitBiasArr[numpy.newaxis, :]
474 mask = ampMaskedImage.getMask()
475 maskArray = mask.getArray()
if shortInd == 1
else mask.getArray().transpose()
476 suspect = mask.getPlaneBitMask(
"SUSPECT")
478 if collapsed.mask == numpy.ma.nomask:
482 for low
in range(num):
483 if not collapsed.mask[low]:
486 maskArray[:low, :] |= suspect
487 for high
in range(1, num):
488 if not collapsed.mask[-high]:
491 maskArray[-high:, :] |= suspect
494 raise pexExcept.Exception(
'%s : %s an invalid overscan type' % (
"overscanCorrection", fitType))
496 overscanImage -= overscanFit
497 return Struct(imageFit=offImage, overscanFit=overscanFit)
501 sensorTransmission=None, atmosphereTransmission=None):
502 """Attach a TransmissionCurve to an Exposure, given separate curves for 503 different components. 507 exposure : `lsst.afw.image.Exposure` 508 Exposure object to modify by attaching the product of all given 509 ``TransmissionCurves`` in post-assembly trimmed detector coordinates. 510 Must have a valid ``Detector`` attached that matches the detector 511 associated with sensorTransmission. 512 opticsTransmission : `lsst.afw.image.TransmissionCurve` 513 A ``TransmissionCurve`` that represents the throughput of the optics, 514 to be evaluated in focal-plane coordinates. 515 filterTransmission : `lsst.afw.image.TransmissionCurve` 516 A ``TransmissionCurve`` that represents the throughput of the filter 517 itself, to be evaluated in focal-plane coordinates. 518 sensorTransmission : `lsst.afw.image.TransmissionCurve` 519 A ``TransmissionCurve`` that represents the throughput of the sensor 520 itself, to be evaluated in post-assembly trimmed detector coordinates. 521 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 522 A ``TransmissionCurve`` that represents the throughput of the 523 atmosphere, assumed to be spatially constant. 525 All ``TransmissionCurve`` arguments are optional; if none are provided, the 526 attached ``TransmissionCurve`` will have unit transmission everywhere. 530 combined : ``lsst.afw.image.TransmissionCurve`` 531 The TransmissionCurve attached to the exposure. 533 combined = afwImage.TransmissionCurve.makeIdentity()
534 if atmosphereTransmission
is not None:
535 combined *= atmosphereTransmission
536 if opticsTransmission
is not None:
537 combined *= opticsTransmission
538 if filterTransmission
is not None:
539 combined *= filterTransmission
540 detector = exposure.getDetector()
541 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
542 toSys=camGeom.PIXELS)
543 combined = combined.transformedBy(fpToPix)
544 if sensorTransmission
is not None:
545 combined *= sensorTransmission
546 exposure.getInfo().setTransmissionCurve(combined)
def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False)
def illuminationCorrection(maskedImage, illumMaskedImage, illumScale)
def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None)
def transposeDefectList(defectList)
def getDefectListFromMask(maskedImage, maskName)
def interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None)
def defectListFromFootprintList(fpList)
def transposeMaskedImage(maskedImage)
def biasCorrection(maskedImage, biasMaskedImage)
def interpolateFromMask(maskedImage, fwhm, growFootprints=1, maskName='SAT', fallbackValue=None)
def attachTransmissionCurve(exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, statControl=None, overscanIsInt=True)
def makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False)
def updateVariance(maskedImage, gain, readNoise)
def maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')