1 from __future__
import division, print_function, absolute_import
2 from builtins
import input
3 from builtins
import range
29 import lsst.afw.geom
as afwGeom
30 import lsst.afw.image
as afwImage
31 import lsst.afw.detection
as afwDetection
32 import lsst.afw.math
as afwMath
33 import lsst.meas.algorithms
as measAlg
34 import lsst.pex.exceptions
as pexExcept
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))))
47 """Make a transposed copy of a masked image 49 @param[in] maskedImage afw.image.MaskedImage to process 50 @return transposed masked image 52 transposed = maskedImage.Factory(afwGeom.Extent2I(maskedImage.getHeight(), maskedImage.getWidth()))
53 transposed.getImage().getArray()[:] = maskedImage.getImage().getArray().T
54 transposed.getMask().getArray()[:] = maskedImage.getMask().getArray().T
55 transposed.getVariance().getArray()[:] = maskedImage.getVariance().getArray().T
60 """Interpolate over defects specified in a defect list 62 @param[in,out] maskedImage masked image to process 63 @param[in] defectList defect list 64 @param[in] fwhm FWHM of double Gaussian smoothing kernel 65 @param[in] fallbackValue fallback value if an interpolated value cannot be determined; 66 if None then use clipped mean image value 69 if fallbackValue
is None:
70 fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
71 if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict():
72 maskedImage.getMask.addMaskPlane(
'INTRP')
73 measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue,
True)
77 """Compute a defect list from a footprint list, optionally growing the footprints 79 @param[in] fpList footprint list 80 @param[in] growFootprints amount by which to grow footprints of detected regions 81 @return a list of defects (meas.algorithms.Defect) 85 if growFootprints > 0:
88 tempSpans = fp.spans.dilated(growFootprints,
89 afwGeom.Stencil.MANHATTAN)
90 fpGrow = afwDetection.Footprint(tempSpans, fp.getRegion())
93 for bbox
in afwDetection.footprintToBBoxList(fpGrow):
94 defect = measAlg.Defect(bbox)
95 defectList.append(defect)
100 """Make a transposed copy of a defect list 102 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 103 @return a defect list with transposed defects 106 for defect
in defectList:
107 bbox = defect.getBBox()
108 nbbox = afwGeom.Box2I(afwGeom.Point2I(bbox.getMinY(), bbox.getMinX()),
109 afwGeom.Extent2I(bbox.getDimensions()[1], bbox.getDimensions()[0]))
110 retDefectList.append(measAlg.Defect(nbbox))
115 """Set mask plane based on a defect list 117 @param[in,out] maskedImage afw.image.MaskedImage to process; mask plane is updated 118 @param[in] defectList a list of defects (afw.meas.algorithms.Defect) 119 @param[in] maskName mask plane name 122 mask = maskedImage.getMask()
123 bitmask = mask.getPlaneBitMask(maskName)
124 for defect
in defectList:
125 bbox = defect.getBBox()
126 afwGeom.SpanSet(bbox).clippedTo(mask.getBBox()).setMask(mask, bitmask)
130 """Compute a defect list from a specified mask plane 132 @param[in] maskedImage masked image to process 133 @param[in] maskName mask plane name, or list of names 134 @param[in] growFootprints amount by which to grow footprints of detected regions 135 @return a list of defects (each an meas.algrithms.Defect) of regions in mask 137 mask = maskedImage.getMask()
138 thresh = afwDetection.Threshold(mask.getPlaneBitMask(maskName), afwDetection.Threshold.BITMASK)
139 fpList = afwDetection.FootprintSet(mask, thresh).getFootprints()
144 """Mask pixels based on threshold detection 146 @param[in,out] maskedImage afw.image.MaskedImage to process; the mask is altered 147 @param[in] threshold detection threshold 148 @param[in] growFootprints amount by which to grow footprints of detected regions 149 @param[in] maskName mask plane name 150 @return a list of defects (meas.algrithms.Defect) of regions set in the mask. 153 thresh = afwDetection.Threshold(threshold)
154 fs = afwDetection.FootprintSet(maskedImage, thresh)
156 if growFootprints > 0:
157 fs = afwDetection.FootprintSet(fs, growFootprints)
159 fpList = fs.getFootprints()
161 mask = maskedImage.getMask()
162 bitmask = mask.getPlaneBitMask(maskName)
163 afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
169 """Interpolate over defects identified by a particular mask plane 171 @param[in,out] maskedImage afw.image.MaskedImage to process 172 @param[in] fwhm FWHM of double Gaussian smoothing kernel 173 @param[in] growFootprints amount by which to grow footprints of detected regions 174 @param[in] maskName mask plane name 175 @param[in] fallbackValue value of last resort for interpolation 181 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
183 """Mark saturated pixels and optionally interpolate over them 185 @param[in,out] maskedImage afw.image.MaskedImage to process 186 @param[in] saturation saturation level (used as a detection threshold) 187 @param[in] fwhm FWHM of double Gaussian smoothing kernel 188 @param[in] growFootprints amount by which to grow footprints of detected regions 189 @param[in] interpolate interpolate over saturated pixels? 190 @param[in] maskName mask plane name 191 @param[in] fallbackValue value of last resort for interpolation 194 maskedImage=maskedImage,
195 threshold=saturation,
196 growFootprints=growFootprints,
204 """Apply bias correction in place 206 @param[in,out] maskedImage masked image to correct 207 @param[in] biasMaskedImage bias, as a masked image 209 if maskedImage.getBBox(afwImage.LOCAL) != biasMaskedImage.getBBox(afwImage.LOCAL):
210 raise RuntimeError(
"maskedImage bbox %s != biasMaskedImage bbox %s" %
211 (maskedImage.getBBox(afwImage.LOCAL), biasMaskedImage.getBBox(afwImage.LOCAL)))
212 maskedImage -= biasMaskedImage
216 """Apply dark correction in place 218 maskedImage -= dark * expScaling / darkScaling 220 @param[in,out] maskedImage afw.image.MaskedImage to correct 221 @param[in] darkMaskedImage dark afw.image.MaskedImage 222 @param[in] expScale exposure scale 223 @param[in] darkScale dark scale 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
230 maskedImage.scaledMinus(scale, darkMaskedImage)
234 """Set the variance plane based on the image plane 236 @param[in,out] maskedImage afw.image.MaskedImage; image plane is read and variance plane is written 237 @param[in] gain amplifier gain (e-/ADU) 238 @param[in] readNoise amplifier read noise (ADU/pixel) 240 var = maskedImage.getVariance()
241 var[:] = maskedImage.getImage()
247 """Apply flat correction in place 249 @param[in,out] maskedImage afw.image.MaskedImage to correct 250 @param[in] flatMaskedImage flat field afw.image.MaskedImage 251 @param[in] scalingType how to compute flat scale; one of 'MEAN', 'MEDIAN' or 'USER' 252 @param[in] userScale scale to use if scalingType is 'USER', else ignored 254 if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
255 raise RuntimeError(
"maskedImage bbox %s != flatMaskedImage bbox %s" %
256 (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
261 if scalingType ==
'MEAN':
262 flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(), afwMath.MEAN).getValue(afwMath.MEAN)
263 elif scalingType ==
'MEDIAN':
264 flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(),
265 afwMath.MEDIAN).getValue(afwMath.MEDIAN)
266 elif scalingType ==
'USER':
267 flatScale = userScale
269 raise pexExcept.Exception(
'%s : %s not implemented' % (
"flatCorrection", scalingType))
271 maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
275 """Apply illumination correction in place 277 @param[in,out] maskedImage afw.image.MaskedImage to correct 278 @param[in] illumMaskedImage illumination correction masked image 279 @param[in] illumScale scale value for illumination correction 281 if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
282 raise RuntimeError(
"maskedImage bbox %s != illumMaskedImage bbox %s" %
283 (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
285 maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
288 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0,
290 """Apply overscan correction in place 292 @param[in,out] ampMaskedImage masked image to correct 293 @param[in] overscanImage overscan data as an afw.image.Image or afw.image.MaskedImage. 294 If a masked image is passed in the mask plane will be used 295 to constrain the fit of the bias level. 296 @param[in] fitType type of fit for overscan correction; one of: 299 - 'POLY' (ordinary polynomial) 300 - 'CHEB' (Chebyshev polynomial) 301 - 'LEG' (Legendre polynomial) 302 - 'NATURAL_SPLINE', 'CUBIC_SPLINE', 'AKIMA_SPLINE' (splines) 303 @param[in] order polynomial order or spline knots (ignored unless fitType 304 indicates a polynomial or spline) 305 @param[in] collapseRej Rejection threshold (sigma) for collapsing dimension of overscan 306 @param[in] statControl Statistics control object 308 ampImage = ampMaskedImage.getImage()
309 if statControl
is None:
310 statControl = afwMath.StatisticsControl()
311 if fitType ==
'MEAN':
312 offImage = afwMath.makeStatistics(overscanImage, afwMath.MEAN, statControl).getValue(afwMath.MEAN)
313 elif fitType ==
'MEDIAN':
314 offImage = afwMath.makeStatistics(overscanImage, afwMath.MEDIAN, statControl).getValue(afwMath.MEDIAN)
315 elif fitType
in (
'POLY',
'CHEB',
'LEG',
'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
316 if hasattr(overscanImage,
"getImage"):
317 biasArray = overscanImage.getImage().getArray()
318 biasArray = numpy.ma.masked_where(overscanImage.getMask().getArray() & statControl.getAndMask(),
321 biasArray = overscanImage.getArray()
323 shortInd = numpy.argmin(biasArray.shape)
326 biasArray = numpy.transpose(biasArray)
329 percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
330 medianBiasArr = percentiles[1]
331 stdevBiasArr = 0.74*(percentiles[2] - percentiles[0])
332 diff = numpy.abs(biasArray - medianBiasArr[:, numpy.newaxis])
333 biasMaskedArr = numpy.ma.masked_where(diff > collapseRej*stdevBiasArr[:, numpy.newaxis], biasArray)
334 collapsed = numpy.mean(biasMaskedArr, axis=1)
335 if collapsed.mask.sum() > 0:
336 collapsed.data[collapsed.mask] = numpy.mean(biasArray.data[collapsed.mask], axis=1)
337 del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
340 collapsed = numpy.transpose(collapsed)
343 indices = 2.0*numpy.arange(num)/float(num) - 1.0
345 if fitType
in (
'POLY',
'CHEB',
'LEG'):
347 poly = numpy.polynomial
348 fitter, evaler = {
"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
349 "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
350 "LEG": (poly.legendre.legfit, poly.legendre.legval),
353 coeffs = fitter(indices, collapsed, order)
354 fitBiasArr = evaler(indices, coeffs)
355 elif 'SPLINE' in fitType:
364 collapsedMask = collapsed.mask
366 if collapsedMask == numpy.ma.nomask:
367 collapsedMask = numpy.array(len(collapsed)*[numpy.ma.nomask])
371 numPerBin, binEdges = numpy.histogram(indices, bins=numBins,
372 weights=1-collapsedMask.astype(int))
375 values = numpy.histogram(indices, bins=numBins,
376 weights=collapsed.data*~collapsedMask)[0]/numPerBin
377 binCenters = numpy.histogram(indices, bins=numBins,
378 weights=indices*~collapsedMask)[0]/numPerBin
379 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
380 values.astype(float)[numPerBin > 0],
381 afwMath.stringToInterpStyle(fitType))
382 fitBiasArr = numpy.array([interp.interpolate(i)
for i
in indices])
385 if lsstDebug.Info(__name__).display:
386 import matplotlib.pyplot
as plot
387 figure = plot.figure(1)
389 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
390 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
391 if collapsedMask.sum() > 0:
392 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
393 axes.plot(indices, fitBiasArr,
'r-')
395 prompt =
"Press Enter or c to continue [chp]... " 397 ans = input(prompt).lower()
398 if ans
in (
"",
"c",):
404 print(
"h[elp] c[ontinue] p[db]")
407 offImage = ampImage.Factory(ampImage.getDimensions())
408 offArray = offImage.getArray()
410 offArray[:, :] = fitBiasArr[:, numpy.newaxis]
412 offArray[:, :] = fitBiasArr[numpy.newaxis, :]
420 mask = ampMaskedImage.getMask()
421 maskArray = mask.getArray()
if shortInd == 1
else mask.getArray().transpose()
422 suspect = mask.getPlaneBitMask(
"SUSPECT")
424 if collapsed.mask == numpy.ma.nomask:
428 for low
in range(num):
429 if not collapsed.mask[low]:
432 maskArray[:low, :] |= suspect
433 for high
in range(1, num):
434 if not collapsed.mask[-high]:
437 maskArray[-high:, :] |= suspect
440 raise pexExcept.Exception(
'%s : %s an invalid overscan type' % \
441 (
"overscanCorrection", fitType))
def illuminationCorrection(maskedImage, illumMaskedImage, illumScale)
def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT', fallbackValue=None)
def transposeDefectList(defectList)
def interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None)
def transposeMaskedImage(maskedImage)
def defectListFromFootprintList(fpList, growFootprints=1)
def biasCorrection(maskedImage, biasMaskedImage)
def interpolateFromMask(maskedImage, fwhm, growFootprints=1, maskName='SAT', fallbackValue=None)
def getDefectListFromMask(maskedImage, maskName, growFootprints=1)
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)
def updateVariance(maskedImage, gain, readNoise)
def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale)
def maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')