LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
isr.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 
24 import numpy
25 
26 import lsst.afw.geom as afwGeom
27 import lsst.afw.image as afwImage
28 import lsst.afw.detection as afwDetection
29 import lsst.afw.math as afwMath
30 import lsst.meas.algorithms as measAlg
31 import lsst.pex.exceptions as pexExcept
32 
33 def createPsf(fwhm):
34  """Make a double Gaussian PSF
35 
36  @param[in] fwhm FWHM of double Gaussian smoothing kernel
37  @return measAlg.DoubleGaussianPsf
38  """
39  ksize = 4*int(fwhm) + 1
40  return measAlg.DoubleGaussianPsf(ksize, ksize, fwhm/(2*math.sqrt(2*math.log(2))))
41 
42 def calcEffectiveGain(maskedImage):
43  """Calculate effective gain
44 
45  @param[in] maskedImage afw.image.MaskedImage to process
46  @return (median gain, mean gain) in e-/ADU
47  """
48  im = afwImage.ImageF(maskedImage.getImage(), True)
49  var = maskedImage.getVariance()
50  im /= var
51  medgain = afwMath.makeStatistics(im, afwMath.MEDIAN).getValue()
52  meangain = afwMath.makeStatistics(im, afwMath.MEANCLIP).getValue()
53  return medgain, meangain
54 
55 def transposeMaskedImage(maskedImage):
56  """Make a transposed copy of a masked image
57 
58  @param[in] maskedImage afw.image.MaskedImage to process
59  @return transposed masked image
60  """
61  imarr = maskedImage.getImage().getArray().T.__copy__()
62  vararr = maskedImage.getVariance().getArray().T.__copy__()
63  maskarr = maskedImage.getMask().getArray().T.__copy__()
64  return afwImage.makeMaskedImageFromArrays(imarr, maskarr, vararr)
65 
66 def interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=None):
67  """Interpolate over defects specified in a defect list
68 
69  @param[in,out] maskedImage masked image to process
70  @param[in] defectList defect list
71  @param[in] fwhm FWHM of double Gaussian smoothing kernel
72  @param[in] fallbackValue fallback value if an interpolated value cannot be determined;
73  if None then use clipped mean image value
74  """
75  psf = createPsf(fwhm)
76  if fallbackValue is None:
77  fallbackValue = afwMath.makeStatistics(maskedImage.getImage(), afwMath.MEANCLIP).getValue()
78  if 'INTRP' not in maskedImage.getMask().getMaskPlaneDict().keys():
79  maskedImage.getMask.addMaskPlane('INTRP')
80  measAlg.interpolateOverDefects(maskedImage, psf, defectList, fallbackValue)
81 
82 def defectListFromFootprintList(fpList, growFootprints=1):
83  """Compute a defect list from a footprint list, optionally growing the footprints
84 
85  @param[in] fpList footprint list
86  @param[in] growFootprints amount by which to grow footprints of detected regions
87  @return meas.algorithms.DefectListT
88  """
89  defectList = measAlg.DefectListT()
90  for fp in fpList:
91  if growFootprints > 0:
92  # if "True", growing requires a convolution
93  # if "False", its faster
94  fpGrow = afwDetection.growFootprint(fp, growFootprints, False)
95  else:
96  fpGrow = fp
97  for bbox in afwDetection.footprintToBBoxList(fpGrow):
98  defect = measAlg.Defect(bbox)
99  defectList.push_back(defect)
100  return defectList
101 
102 def transposeDefectList(defectList):
103  """Make a transposed copy of a defect list
104 
105  @param[in] defectList defect list
106  @return meas.algorithms.DefectListT with transposed defects
107  """
108  retDefectList = measAlg.DefectListT()
109  for defect in defectList:
110  bbox = defect.getBBox()
111  nbbox = afwGeom.Box2I(afwGeom.Point2I(bbox.getMinY(), bbox.getMinX()),
112  afwGeom.Extent2I(bbox.getDimensions()[1], bbox.getDimensions()[0]))
113  retDefectList.push_back(measAlg.Defect(nbbox))
114  return retDefectList
115 
116 def maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD'):
117  """Set mask plane based on a defect list
118 
119  @param[in,out] maskedImage afw.image.MaskedImage to process; mask plane is updated
120  @param[in] defectList meas.algorithms.DefectListT
121  @param[in] maskName mask plane name
122  """
123  # mask bad pixels
124  mask = maskedImage.getMask()
125  bitmask = mask.getPlaneBitMask(maskName)
126  for defect in defectList:
127  bbox = defect.getBBox()
129 
130 def getDefectListFromMask(maskedImage, maskName, growFootprints=1):
131  """Compute a defect list from a specified mask plane
132 
133  @param[in] maskedImage masked image to process
134  @param[in] maskName mask plane name
135  @param[in] growFootprints amount by which to grow footprints of detected regions
136  @return meas.algrithms.DefectListT of regions in mask
137  """
138  mask = maskedImage.getMask()
139  workmask = afwImage.MaskU(mask, True)
140  workmask &= mask.getPlaneBitMask(maskName)
141  thresh = afwDetection.Threshold(0.5)
142  maskimg = afwImage.ImageU(workmask.getBBox())
143  maskimg <<= workmask
144  ds = afwDetection.FootprintSet(maskimg, thresh)
145  fpList = ds.getFootprints()
146  return defectListFromFootprintList(fpList, growFootprints)
147 
148 def makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName = 'SAT'):
149  """Mask pixels based on threshold detection
150 
151  @param[in,out] maskedImage afw.image.MaskedImage to process; the mask is altered
152  @param[in] threshold detection threshold
153  @param[in] growFootprints amount by which to grow footprints of detected regions
154  @param[in] maskName mask plane name
155  @return meas.algorihtms.DefectListT of regions set in the mask.
156  """
157  # find saturated regions
158  thresh = afwDetection.Threshold(threshold)
159  fs = afwDetection.FootprintSet(maskedImage, thresh)
160 
161  if growFootprints > 0:
162  fs = afwDetection.FootprintSet(fs, growFootprints)
163 
164  fpList = fs.getFootprints()
165  # set mask
166  mask = maskedImage.getMask()
167  bitmask = mask.getPlaneBitMask(maskName)
168  afwDetection.setMaskFromFootprintList(mask, fpList, bitmask)
169 
170  return defectListFromFootprintList(fpList, growFootprints=0)
171 
172 def interpolateFromMask(maskedImage, fwhm, growFootprints=1, maskName='SAT', fallbackValue=None):
173  """Interpolate over defects identified by a particular mask plane
174 
175  @param[in,out] maskedImage afw.image.MaskedImage to process
176  @param[in] fwhm FWHM of double Gaussian smoothing kernel
177  @param[in] growFootprints amount by which to grow footprints of detected regions
178  @param[in] maskName mask plane name
179  @param[in] fallbackValue value of last resort for interpolation
180  """
181  defectList = getDefectListFromMask(maskedImage, maskName, growFootprints)
182  interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=fallbackValue)
183 
184 def saturationCorrection(maskedImage, saturation, fwhm, growFootprints=1, interpolate=True, maskName='SAT',
185  fallbackValue=None):
186  """Mark saturated pixels and optionally interpolate over them
187 
188  @param[in,out] maskedImage afw.image.MaskedImage to process
189  @param[in] saturation saturation level (used as a detection threshold)
190  @param[in] fwhm FWHM of double Gaussian smoothing kernel
191  @param[in] growFootprints amount by which to grow footprints of detected regions
192  @param[in] interpolate interpolate over saturated pixels?
193  @param[in] maskName mask plane name
194  @param[in] fallbackValue value of last resort for interpolation
195  """
196  defectList = makeThresholdMask(
197  maskedImage = maskedImage,
198  threshold = saturation,
199  growFootprints = growFootprints,
200  maskName = maskName,
201  )
202  if interpolate:
203  interpolateDefectList(maskedImage, defectList, fwhm, fallbackValue=fallbackValue)
204 
205 def biasCorrection(maskedImage, biasMaskedImage):
206  """Apply bias correction in place
207 
208  @param[in,out] maskedImage masked image to correct
209  @param[in] biasMaskedImage bias, as a masked image
210  """
211  maskedImage -= biasMaskedImage
212 
213 def darkCorrection(maskedImage, darkMaskedImage, expScale, darkScale):
214  """Apply dark correction in place
215 
216  maskedImage -= dark * expScaling / darkScaling
217 
218  @param[in,out] maskedImage afw.image.MaskedImage to correct
219  @param[in] darkMaskedImage dark afw.image.MaskedImage
220  @param[in] expScale exposure scale
221  @param[in] darkScale dark scale
222  """
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)))
226 
227  scale = expScale / darkScale
228  maskedImage.scaledMinus(scale, darkMaskedImage)
229 
230 def updateVariance(maskedImage, gain, readNoise):
231  """Set the variance plane based on the image plane
232 
233  @param[in,out] maskedImage afw.image.MaskedImage; image plane is read and variance plane is written
234  @param[in] gain amplifier gain (e-/ADU)
235  @param[in] readNoise amplifier read noise (ADU/pixel)
236  """
237  var = maskedImage.getVariance()
238  var <<= maskedImage.getImage()
239  var /= gain
240  var += readNoise**2
241 
242 def flatCorrection(maskedImage, flatMaskedImage, scalingType, userScale=1.0):
243  """Apply flat correction in place
244 
245  @param[in,out] maskedImage afw.image.MaskedImage to correct
246  @param[in] flatMaskedImage flat field afw.image.MaskedImage
247  @param[in] scalingType how to compute flat scale; one of 'MEAN', 'MEDIAN' or 'USER'
248  @param[in] userScale scale to use if scalingType is 'USER', else ignored
249  """
250  if maskedImage.getBBox(afwImage.LOCAL) != flatMaskedImage.getBBox(afwImage.LOCAL):
251  raise RuntimeError("maskedImage bbox %s != flatMaskedImage bbox %s" % \
252  (maskedImage.getBBox(afwImage.LOCAL), flatMaskedImage.getBBox(afwImage.LOCAL)))
253 
254  # Figure out scale from the data
255  # Ideally the flats are normalized by the calibration product pipelin, but this allows some flexibility
256  # in the case that the flat is created by some other mechanism.
257  if scalingType == 'MEAN':
258  flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(), afwMath.MEAN).getValue(afwMath.MEAN)
259  elif scalingType == 'MEDIAN':
260  flatScale = afwMath.makeStatistics(flatMaskedImage.getImage(), afwMath.MEDIAN).getValue(afwMath.MEDIAN)
261  elif scalingType == 'USER':
262  flatScale = userScale
263  else:
264  raise pexExcept.Exception, '%s : %s not implemented' % ("flatCorrection", scalingType)
265 
266  maskedImage.scaledDivides(1.0/flatScale, flatMaskedImage)
267 
268 def illuminationCorrection(maskedImage, illumMaskedImage, illumScale):
269  """Apply illumination correction in place
270 
271  @param[in,out] maskedImage afw.image.MaskedImage to correct
272  @param[in] illumMaskedImage illumination correction masked image
273  @param[in] illumScale scale value for illumination correction
274  """
275  if maskedImage.getBBox(afwImage.LOCAL) != illumMaskedImage.getBBox(afwImage.LOCAL):
276  raise RuntimeError("maskedImage bbox %s != illumMaskedImage bbox %s" % \
277  (maskedImage.getBBox(afwImage.LOCAL), illumMaskedImage.getBBox(afwImage.LOCAL)))
278 
279  maskedImage.scaledDivides(1./illumScale, illumMaskedImage)
280 
281 def overscanCorrection(ampMaskedImage, overscanImage, fitType='MEDIAN', order=1, collapseRej=3.0):
282  """Apply overscan correction in place
283 
284  @param[in,out] ampMaskedImage masked image to correct
285  @param[in] overscanImage overscan data as an afw.image.IMage
286  @param[in] fitType type of fit for overscan correction; one of:
287  - 'MEAN'
288  - 'MEDIAN'
289  - 'POLY' (ordinary polynomial)
290  - 'CHEB' (Chebyshev polynomial)
291  - 'LEG' (Legendre polynomial)
292  - 'NATURAL_SPLINE', 'CUBIC_SPLINE', 'AKIMA_SPLINE' (splines)
293  @param[in] order polynomial order or spline knots (ignored unless fitType
294  indicates a polynomial or spline)
295  @param[in] collapseRej Rejection threshold (sigma) for collapsing dimension of overscan
296  """
297  ampImage = ampMaskedImage.getImage()
298  if fitType == 'MEAN':
299  offImage = afwMath.makeStatistics(overscanImage, afwMath.MEAN).getValue(afwMath.MEAN)
300  elif fitType == 'MEDIAN':
301  offImage = afwMath.makeStatistics(overscanImage, afwMath.MEDIAN).getValue(afwMath.MEDIAN)
302  elif fitType in ('POLY', 'CHEB', 'LEG', 'NATURAL_SPLINE', 'CUBIC_SPLINE', 'AKIMA_SPLINE'):
303  biasArray = overscanImage.getArray()
304  # Fit along the long axis, so collapse along each short row and fit the resulting array
305  shortInd = numpy.argmin(biasArray.shape)
306  if shortInd == 0:
307  # Convert to some 'standard' representation to make things easier
308  biasArray = numpy.transpose(biasArray)
309 
310  # Do a single round of clipping to weed out CR hits and signal leaking into the overscan
311  percentiles = numpy.percentile(biasArray, [25.0, 50.0, 75.0], axis=1)
312  medianBiasArr = percentiles[1]
313  stdevBiasArr = 0.74*(percentiles[2] - percentiles[0]) # robust stdev
314  diff = numpy.abs(biasArray - medianBiasArr[:,numpy.newaxis])
315  biasMaskedArr = numpy.ma.masked_where(diff > collapseRej*stdevBiasArr[:,numpy.newaxis], biasArray)
316  collapsed = numpy.mean(biasMaskedArr, axis=1)
317  del biasArray, percentiles, stdevBiasArr, diff, biasMaskedArr
318 
319  if shortInd == 0:
320  collapsed = numpy.transpose(collapsed)
321 
322  num = len(collapsed)
323  indices = 2.0*numpy.arange(num)/float(num) - 1.0
324 
325  if fitType in ('POLY', 'CHEB', 'LEG'):
326  # A numpy polynomial
327  poly = numpy.polynomial
328  fitter, evaler = {"POLY": (poly.polynomial.polyfit, poly.polynomial.polyval),
329  "CHEB": (poly.chebyshev.chebfit, poly.chebyshev.chebval),
330  "LEG": (poly.legendre.legfit, poly.legendre.legval),
331  }[fitType]
332 
333  coeffs = fitter(indices, collapsed, order)
334  fitBiasArr = evaler(indices, coeffs)
335  elif 'SPLINE' in fitType:
336  # An afw interpolation
337  numBins = order
338  numPerBin, binEdges = numpy.histogram(indices, bins=numBins, weights=numpy.ones_like(collapsed))
339  # Binning is just a histogram, with weights equal to the values.
340  # Use a similar trick to get the bin centers (this deals with different numbers per bin).
341  values = numpy.histogram(indices, bins=numBins, weights=collapsed)[0]/numPerBin
342  binCenters = numpy.histogram(indices, bins=numBins, weights=indices)[0]/numPerBin
343 
344  interp = afwMath.makeInterpolate(tuple(binCenters.astype(float)),
345  tuple(values.astype(float)),
347  fitBiasArr = numpy.array([interp.interpolate(i) for i in indices])
348 
349  import lsstDebug
350  if lsstDebug.Info(__name__).display:
351  import matplotlib.pyplot as plot
352  figure = plot.figure(1)
353  figure.clear()
354  axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
355  axes.plot(indices, collapsed, 'k+')
356  axes.plot(indices, fitBiasArr, 'r-')
357  figure.show()
358  prompt = "Press Enter or c to continue [chp]... "
359  while True:
360  ans = raw_input(prompt).lower()
361  if ans in ("", "c",):
362  break
363  if ans in ("p",):
364  import pdb; pdb.set_trace()
365  elif ans in ("h", ):
366  print "h[elp] c[ontinue] p[db]"
367  figure.close()
368 
369  offImage = ampImage.Factory(ampImage.getDimensions())
370  offArray = offImage.getArray()
371  if shortInd == 1:
372  offArray[:,:] = fitBiasArr[:,numpy.newaxis]
373  else:
374  offArray[:,:] = fitBiasArr[numpy.newaxis,:]
375  else:
376  raise pexExcept.Exception, '%s : %s an invalid overscan type' % \
377  ("overscanCorrection", fitType)
378  ampImage -= offImage
379 
Interpolate::Style stringToInterpStyle(std::string const &style)
Conversion function to switch a string to an Interpolate::Style.
Definition: Interpolate.cc:264
def updateVariance
Definition: isr.py:230
boost::shared_ptr< Footprint > growFootprint(Footprint const &foot, int nGrow, bool left, bool right, bool up, bool down)
Grow a Footprint in at least one of the cardinal directions, returning a new Footprint.
def flatCorrection
Definition: isr.py:242
boost::shared_ptr< Interpolate > makeInterpolate(std::vector< double > const &x, std::vector< double > const &y, Interpolate::Style const style=Interpolate::AKIMA_SPLINE)
Definition: Interpolate.cc:355
A Threshold is used to pass a threshold value to detection algorithms.
Definition: Threshold.h:44
def transposeDefectList
Definition: isr.py:102
void interpolateOverDefects(MaskedImageT &mimage, lsst::afw::detection::Psf const &, std::vector< Defect::Ptr > &_badList, double fallbackValue)
Process a set of known bad pixels in an image.
Definition: Interp.cc:1895
An integer coordinate rectangle.
Definition: Box.h:53
def maskPixelsFromDefectList
Definition: isr.py:116
def saturationCorrection
Definition: isr.py:185
Represent a Psf as a circularly symmetrical double Gaussian.
def interpolateDefectList
Definition: isr.py:66
def illuminationCorrection
Definition: isr.py:268
def calcEffectiveGain
Definition: isr.py:42
def createPsf
Definition: isr.py:33
def makeThresholdMask
Definition: isr.py:148
A set of pixels in an Image.
Definition: Footprint.h:73
def getDefectListFromMask
Definition: isr.py:130
def transposeMaskedImage
Definition: isr.py:55
def overscanCorrection
Definition: isr.py:281
std::vector< lsst::afw::geom::Box2I > footprintToBBoxList(Footprint const &foot)
A set of Footprints, associated with a MaskedImage.
Definition: FootprintSet.h:53
def makeMaskedImageFromArrays
Definition: __init__.py:45
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels that are in the Footprint.
def interpolateFromMask
Definition: isr.py:172
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1023
MaskT setMaskFromFootprintList(lsst::afw::image::Mask< MaskT > *mask, boost::shared_ptr< std::vector< boost::shared_ptr< Footprint >> const > const &footprints, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels which are in the set of Footprints.
def darkCorrection
Definition: isr.py:213
Encapsulate information about a bad portion of a detector.
Definition: Interp.h:70
def biasCorrection
Definition: isr.py:205
def defectListFromFootprintList
Definition: isr.py:82