1 from __future__
import division, print_function, absolute_import
2 from builtins
import input
3 from builtins
import range
41 """Make a double Gaussian PSF 43 @param[in] fwhm FWHM of double Gaussian smoothing kernel 44 @return measAlg.DoubleGaussianPsf 46 ksize = 4*int(fwhm) + 1
47 return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
50 """Make a transposed copy of a masked image 52 @param[in] maskedImage afw.image.MaskedImage to process 53 @return transposed masked image 55 transposed = maskedImage.Factory(afwGeom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
56 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
57 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
58 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
63 """Interpolate over defects specified in a defect list 65 @param[in,out] maskedImage masked image to process 66 @param[in] defectList defect list 67 @param[in] fwhm FWHM of double Gaussian smoothing kernel 68 @param[in] fallbackValue fallback value if an interpolated value cannot be determined; 69 if None then use clipped mean image value 72 if fallbackValue
is None:
73 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
74 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
75 maskedImage.getMask.addMaskPlane(
'INTRP')
76 measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue,
True)
80 """Compute a defect list from a footprint list, optionally growing the footprints 82 @param[in] fpList footprint list 86 for bbox
in afwDetection.footprintToBBoxList(fp):
87 defect = measAlg.Defect(bbox)
88 defectList.append(defect)
93 """Make a transposed copy of a defect list 95 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 96 @return a defect list with transposed defects 99 for defect
in defectList:
100 bbox = defect.getBBox()
101 nbbox = afwGeom.Box2I(afwGeom.Point2I(bbox.getMinY(), bbox.getMinX()),
102 afwGeom.Extent2I(bbox.getDimensions()[1], bbox.getDimensions()[0]))
103 retDefectList.append(measAlg.Defect(nbbox))
108 """Set mask plane based on a defect list 110 @param[in,out] maskedImage afw.image.MaskedImage to process; mask plane is updated 111 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 112 @param[in] maskName mask plane name 115 mask = maskedImage.getMask()
116 bitmask = mask.getPlaneBitMask(maskName)
117 for defect
in defectList:
118 bbox = defect.getBBox()
119 afwGeom.SpanSet(bbox).clippedTo(mask.getBBox()).setMask(mask, bitmask)
123 """Compute a defect list from a specified mask plane 125 @param[in] maskedImage masked image to process 126 @param[in] maskName mask plane name, or list of names 128 mask = maskedImage.getMask()
129 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
130 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
135 """Mask pixels based on threshold detection 137 @param[in,out] maskedImage afw.image.MaskedImage to process; the mask is altered 138 @param[in] threshold detection threshold 139 @param[in] growFootprints amount by which to grow footprints of detected regions 140 @param[in] maskName mask plane name 141 @return a list of defects (meas.algrithms.Defect) of regions set in the mask. 144 thresh = afwDetection.Threshold(threshold)
145 fs = afwDetection.FootprintSet(maskedImage, thresh)
147 if growFootprints > 0:
148 fs = afwDetection.FootprintSet(fs, growFootprints)
150 fpList = fs.getFootprints()
152 mask = maskedImage.getMask()
153 bitmask = mask.getPlaneBitMask(maskName)
154 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
160 """Interpolate over defects identified by a particular mask plane 162 @param[in,out] maskedImage afw.image.MaskedImage to process 163 @param[in] fwhm FWHM of double Gaussian smoothing kernel 164 @param[in] growFootprints amount by which to grow footprints of detected regions 165 @param[in] maskName mask plane name 166 @param[in] fallbackValue value of last resort for interpolation 168 mask = maskedImage.getMask()
169 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
170 fpSet = afwDetection.FootprintSet(mask, thresh)
171 if growFootprints > 0:
172 fpSet = afwDetection.FootprintSet(fpSet, rGrow=growFootprints, isotropic=
False)
175 fpSet.setMask(mask, maskName)
180 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
182 """Mark saturated pixels and optionally interpolate over them 184 @param[in,out] maskedImage afw.image.MaskedImage to process 185 @param[in] saturation saturation level (used as a detection threshold) 186 @param[in] fwhm FWHM of double Gaussian smoothing kernel 187 @param[in] growFootprints amount by which to grow footprints of detected regions 188 @param[in] interpolate interpolate over saturated pixels? 189 @param[in] maskName mask plane name 190 @param[in] fallbackValue value of last resort for interpolation 193 maskedImage=maskedImage,
194 threshold=saturation,
195 growFootprints=growFootprints,
203 """Apply bias correction in place 205 @param[in,out] maskedImage masked image to correct 206 @param[in] biasMaskedImage bias, as a masked image 208 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
209 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
210 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
211 maskedImage -= biasMaskedImage
214 def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale, invert=False):
215 """Apply dark correction in place 217 maskedImage -= dark * expScaling / darkScaling 219 @param[in,out] maskedImage afw.image.MaskedImage to correct 220 @param[in] darkMaskedImage dark afw.image.MaskedImage 221 @param[in] expScale exposure scale 222 @param[in] darkScale dark scale 223 @param[in] invert if True, remove the dark from an already-corrected image 225 if maskedImage.getBBox(afwImage.LOCAL) != darkMaskedImage.getBBox(afwImage.LOCAL):
226 raise RuntimeError(
"maskedImage bbox %s != darkMaskedImage bbox %s" %
227 (maskedImage.getBBox(afwImage.LOCAL), darkMaskedImage.getBBox(afwImage.LOCAL)))
229 scale = expScale / darkScale
231 maskedImage.scaledMinus(scale, darkMaskedImage)
233 maskedImage.scaledPlus(scale, darkMaskedImage)
237 """Set the variance plane based on the image plane 239 @param[in,out] maskedImage afw.image.MaskedImage; image plane is read and variance plane is written 240 @param[in] gain amplifier gain (e-/ADU) 241 @param[in] readNoise amplifier read noise (ADU/pixel) 243 var = maskedImage.getVariance()
244 var[:] = maskedImage.getImage()
249 def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False):
250 """Apply flat correction in place 252 @param[in,out] maskedImage afw.image.MaskedImage to correct 253 @param[in] flatMaskedImage flat field afw.image.MaskedImage 254 @param[in] scalingType how to compute flat scale; one of 'MEAN', 'MEDIAN' or 'USER' 255 @param[in] userScale scale to use if scalingType is 'USER', else ignored 256 @param[in] invert if True, unflatten an already-flattened image instead. 258 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
259 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
260 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
265 if scalingType ==
'MEAN':
266 flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(), afwMath.MEAN).getValue(afwMath.MEAN)
267 elif scalingType ==
'MEDIAN':
268 flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(),
269 afwMath.MEDIAN).getValue(afwMath.MEDIAN)
270 elif scalingType ==
'USER':
271 flatScale = userScale
273 raise pexExcept.Exception(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
276 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
278 maskedImage.scaledMultiplies(1.0/flatScale, flatMaskedImage)
282 """Apply illumination correction in place 284 @param[in,out] maskedImage afw.image.MaskedImage to correct 285 @param[in] illumMaskedImage illumination correction masked image 286 @param[in] illumScale scale value for illumination correction 288 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
289 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
290 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
292 maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
295 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0,
297 """Apply overscan correction in-place 299 The ``ampMaskedImage`` and ``overscanImage`` are modified, with the fit 300 subtracted. Note that the ``overscanImage`` should not be a subimage of 301 the ``ampMaskedImage``, to avoid being subtracted twice. 305 ampMaskedImage : `lsst.afw.image.MaskedImage` 306 Image of amplifier to correct; modified. 307 overscanImage : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage` 308 Image of overscan; modified. 310 Type of fit for overscan correction. May be one of: 312 - ``MEAN``: use mean of overscan. 313 - ``MEDIAN``: use median of overscan. 314 - ``POLY``: fit with ordinary polynomial. 315 - ``CHEB``: fit with Chebyshev polynomial. 316 - ``LEG``: fit with Legendre polynomial. 317 - ``NATURAL_SPLINE``: fit with natural spline. 318 - ``CUBIC_SPLINE``: fit with cubic spline. 319 - ``AKIMA_SPLINE``: fit with Akima spline. 322 Polynomial order or number of spline knots; ignored unless 323 ``fitType`` indicates a polynomial or spline. 324 collapseRej : `float` 325 Rejection threshold (sigma) for collapsing dimension of overscan. 326 statControl : `lsst.afw.math.StatisticsControl` 327 Statistics control object. 331 result : `lsst.pipe.base.Struct` 332 Result struct with components: 334 - ``imageFit``: Value(s) removed from image (scalar or 335 `lsst.afw.image.Image`) 336 - ``overscanFit``: Value(s) removed from overscan (scalar or 337 `lsst.afw.image.Image`) 339 ampImage = ampMaskedImage.getImage()
340 if statControl
is None:
341 statControl = afwMath.StatisticsControl()
342 if fitType ==
'MEAN':
343 offImage = afwMath.makeStatistics(overscanImage, afwMath.MEAN, statControl).getValue(afwMath.MEAN)
344 overscanFit = offImage
345 elif fitType ==
'MEDIAN':
346 offImage = afwMath.makeStatistics(overscanImage, afwMath.MEDIAN, statControl).getValue(afwMath.MEDIAN)
347 overscanFit = offImage
348 elif fitType
in (
'POLY',
'CHEB',
'LEG',
'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
349 if hasattr(overscanImage,
"getImage"):
350 biasArray = overscanImage.getImage().getArray()
351 biasArray = numpy.ma.masked_where(overscanImage.getMask().getArray() & statControl.getAndMask(),
354 biasArray = overscanImage.getArray()
356 shortInd = numpy.argmin(biasArray.shape)
359 biasArray = numpy.transpose(biasArray)
362 percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
363 medianBiasArr = percentiles[1]
364 stdevBiasArr = 0.74*(percentiles[2] - percentiles[0])
365 diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
366 biasMaskedArr = numpy.ma.masked_where(diff > collapseRej*stdevBiasArr[:, numpy.newaxis], biasArray)
367 collapsed = numpy.mean(biasMaskedArr, axis=1)
368 if collapsed.mask.sum() > 0:
369 collapsed.data[collapsed.mask] = numpy.mean(biasArray.data[collapsed.mask], axis=1)
370 del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
373 collapsed = numpy.transpose(collapsed)
376 indices = 2.0*numpy.arange(num)/float(num) - 1.0
378 if fitType
in (
'POLY',
'CHEB',
'LEG'):
380 poly = numpy.polynomial
381 fitter, evaler = {
"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
382 "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
383 "LEG": (poly.legendre.legfit, poly.legendre.legval),
386 coeffs = fitter(indices, collapsed, order)
387 fitBiasArr = evaler(indices, coeffs)
388 elif 'SPLINE' in fitType:
397 collapsedMask = collapsed.mask
399 if collapsedMask == numpy.ma.nomask:
400 collapsedMask = numpy.array(len(collapsed)*[numpy.ma.nomask])
404 numPerBin, binEdges = numpy.histogram(indices, bins=numBins,
405 weights=1-collapsedMask.astype(int))
408 with numpy.errstate(invalid=
"ignore"):
409 values = numpy.histogram(indices, bins=numBins,
410 weights=collapsed.data*~collapsedMask)[0]/numPerBin
411 binCenters = numpy.histogram(indices, bins=numBins,
412 weights=indices*~collapsedMask)[0]/numPerBin
413 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
414 values.astype(float)[numPerBin > 0],
415 afwMath.stringToInterpStyle(fitType))
416 fitBiasArr = numpy.array([interp.interpolate(i)
for i
in indices])
420 import matplotlib.pyplot
as plot
421 figure = plot.figure(1)
423 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
424 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
425 if collapsedMask.sum() > 0:
426 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
427 axes.plot(indices, fitBiasArr,
'r-')
429 prompt =
"Press Enter or c to continue [chp]... " 431 ans = input(prompt).lower()
432 if ans
in (
"",
"c",):
438 print(
"h[elp] c[ontinue] p[db]")
441 offImage = ampImage.Factory(ampImage.getDimensions())
442 offArray = offImage.getArray()
443 overscanFit = afwImage.ImageF(overscanImage.getDimensions())
444 overscanArray = overscanFit.getArray()
446 offArray[:, :] = fitBiasArr[:, numpy.newaxis]
447 overscanArray[:, :] = fitBiasArr[:, numpy.newaxis]
449 offArray[:, :] = fitBiasArr[numpy.newaxis, :]
450 overscanArray[:, :] = fitBiasArr[numpy.newaxis, :]
458 mask = ampMaskedImage.getMask()
459 maskArray = mask.getArray()
if shortInd == 1
else mask.getArray().transpose()
460 suspect = mask.getPlaneBitMask(
"SUSPECT")
462 if collapsed.mask == numpy.ma.nomask:
466 for low
in range(num):
467 if not collapsed.mask[low]:
470 maskArray[:low, :] |= suspect
471 for high
in range(1, num):
472 if not collapsed.mask[-high]:
475 maskArray[-high:, :] |= suspect
478 raise pexExcept.Exception(
'%s : %s an invalid overscan type' % \
479 (
"overscanCorrection", fitType))
481 overscanImage -= overscanFit
482 return Struct(imageFit=offImage, overscanFit=overscanFit)
486 sensorTransmission=None, atmosphereTransmission=None):
487 """Attach a TransmissionCurve to an Exposure, given separate curves for 488 different components. 492 exposure : `lsst.afw.image.Exposure` 493 Exposure object to modify by attaching the product of all given 494 ``TransmissionCurves`` in post-assembly trimmed detector coordinates. 495 Must have a valid ``Detector`` attached that matches the detector 496 associated with sensorTransmission. 497 opticsTransmission : `lsst.afw.image.TransmissionCurve` 498 A ``TransmissionCurve`` that represents the throughput of the optics, 499 to be evaluated in focal-plane coordinates. 500 filterTransmission : `lsst.afw.image.TransmissionCurve` 501 A ``TransmissionCurve`` that represents the throughput of the filter 502 itself, to be evaluated in focal-plane coordinates. 503 sensorTransmission : `lsst.afw.image.TransmissionCurve` 504 A ``TransmissionCurve`` that represents the throughput of the sensor 505 itself, to be evaluated in post-assembly trimmed detector coordinates. 506 atmosphereTransmission : `lsst.afw.image.TransmissionCurve` 507 A ``TransmissionCurve`` that represents the throughput of the 508 atmosphere, assumed to be spatially constant. 510 All ``TransmissionCurve`` arguments are optional; if none are provided, the 511 attached ``TransmissionCurve`` will have unit transmission everywhere. 515 combined : ``lsst.afw.image.TransmissionCurve`` 516 The TransmissionCurve attached to the exposure. 518 combined = afwImage.TransmissionCurve.makeIdentity()
519 if atmosphereTransmission
is not None:
520 combined *= atmosphereTransmission
521 if opticsTransmission
is not None:
522 combined *= opticsTransmission
523 if filterTransmission
is not None:
524 combined *= filterTransmission
525 detector = exposure.getDetector()
526 fpToPix = detector.getTransform(fromSys=camGeom.FOCAL_PLANE,
527 toSys=camGeom.PIXELS)
528 combined = combined.transformedBy(fpToPix)
529 if sensorTransmission
is not None:
530 combined *= sensorTransmission
531 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 makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0, statControl=None)
def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0, invert=False)
def updateVariance(maskedImage, gain, readNoise)
def maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')