LSST Applications  21.0.0-131-g8cabc107+528f53ee53,22.0.0+00495a2688,22.0.0+0ef2527977,22.0.0+11a2aa21cd,22.0.0+269b7e55e3,22.0.0+2c6b6677a3,22.0.0+64c1bc5aa5,22.0.0+7b3a3f865e,22.0.0+e1b6d2281c,22.0.0+ff3c34362c,22.0.1-1-g1b65d06+c95cbdf3df,22.0.1-1-g7058be7+1cf78af69b,22.0.1-1-g7dab645+2a65e40b06,22.0.1-1-g8760c09+64c1bc5aa5,22.0.1-1-g949febb+64c1bc5aa5,22.0.1-1-ga324b9c+269b7e55e3,22.0.1-1-gf9d8b05+ff3c34362c,22.0.1-10-g781e53d+9b51d1cd24,22.0.1-10-gba590ab+b9624b875d,22.0.1-13-g76f9b8d+2c6b6677a3,22.0.1-14-g22236948+57af756299,22.0.1-18-g3db9cf4b+9b7092c56c,22.0.1-18-gb17765a+2264247a6b,22.0.1-2-g8ef0a89+2c6b6677a3,22.0.1-2-gcb770ba+c99495d3c6,22.0.1-24-g2e899d296+4206820b0d,22.0.1-3-g7aa11f2+2c6b6677a3,22.0.1-3-g8c1d971+f253ffa91f,22.0.1-3-g997b569+ff3b2f8649,22.0.1-4-g1930a60+6871d0c7f6,22.0.1-4-g5b7b756+6b209d634c,22.0.1-6-ga02864e+6871d0c7f6,22.0.1-7-g3402376+a1a2182ac4,22.0.1-7-g65f59fa+54b92689ce,master-gcc5351303a+e1b6d2281c,w.2021.32
LSST Data Management Base Package
Public Member Functions | Static Public Attributes | List of all members
lsst.cp.pipe.defects.MeasureDefectsTask Class Reference
Inheritance diagram for lsst.cp.pipe.defects.MeasureDefectsTask:

Public Member Functions

def run (self, inputExp, camera)
 
def findHotAndColdPixels (self, exp, nSigma)
 
def maskBlocksIfIntermitentBadPixelsInColumn (self, defects)
 
def debugView (self, stepname, ampImage, defects, detector)
 
def debugHistogram (self, stepname, ampImage, nSigmaUsed, exp)
 

Static Public Attributes

 ConfigClass = MeasureDefectsTaskConfig
 

Detailed Description

Measure the defects from one exposure.

Definition at line 126 of file defects.py.

Member Function Documentation

◆ debugHistogram()

def lsst.cp.pipe.defects.MeasureDefectsTask.debugHistogram (   self,
  stepname,
  ampImage,
  nSigmaUsed,
  exp 
)
Make a histogram of the distribution of pixel values for each amp.

The main image data histogram is plotted in blue. Edge pixels,
if masked, are in red. Note that masked edge pixels do not contribute
to the underflow and overflow numbers.

Note that this currently only supports the 16-amp LSST detectors.

Parameters
----------
dataRef : `lsst.daf.persistence.ButlerDataRef`
    dataRef for the detector.
exp : `lsst.afw.image.exposure.Exposure`
    The exposure in which the defects were found.
visit : `int`
    The visit number.
nSigmaUsed : `float`
    The number of sigma used for detection

Definition at line 439 of file defects.py.

439  def debugHistogram(self, stepname, ampImage, nSigmaUsed, exp):
440  """
441  Make a histogram of the distribution of pixel values for each amp.
442 
443  The main image data histogram is plotted in blue. Edge pixels,
444  if masked, are in red. Note that masked edge pixels do not contribute
445  to the underflow and overflow numbers.
446 
447  Note that this currently only supports the 16-amp LSST detectors.
448 
449  Parameters
450  ----------
451  dataRef : `lsst.daf.persistence.ButlerDataRef`
452  dataRef for the detector.
453  exp : `lsst.afw.image.exposure.Exposure`
454  The exposure in which the defects were found.
455  visit : `int`
456  The visit number.
457  nSigmaUsed : `float`
458  The number of sigma used for detection
459  """
460  frame = getDebugFrame(self._display, stepname)
461  if frame:
462  import matplotlib.pyplot as plt
463 
464  detector = exp.getDetector()
465  nX = np.floor(np.sqrt(len(detector)))
466  nY = len(detector) // nX
467  fig, ax = plt.subplots(nrows=nY, ncols=nX, sharex='col', sharey='row', figsize=(13, 10))
468 
469  expTime = exp.getInfo().getVisitInfo().getExposureTime()
470 
471  for (amp, a) in zip(reversed(detector), ax.flatten()):
472  mi = exp.maskedImage[amp.getBBox()]
473 
474  # normalize by expTime as we plot in ADU/s and don't always work with master calibs
475  mi.image.array /= expTime
476  stats = afwMath.makeStatistics(mi, afwMath.MEANCLIP | afwMath.STDEVCLIP)
477  mean, sigma = stats.getValue(afwMath.MEANCLIP), stats.getValue(afwMath.STDEVCLIP)
478  # Get array of pixels
479  EDGEBIT = exp.maskedImage.mask.getPlaneBitMask("EDGE")
480  imgData = mi.image.array[(mi.mask.array & EDGEBIT) == 0].flatten()
481  edgeData = mi.image.array[(mi.mask.array & EDGEBIT) != 0].flatten()
482 
483  thrUpper = mean + nSigmaUsed*sigma
484  thrLower = mean - nSigmaUsed*sigma
485 
486  nRight = len(imgData[imgData > thrUpper])
487  nLeft = len(imgData[imgData < thrLower])
488 
489  nsig = nSigmaUsed + 1.2 # add something small so the edge of the plot is out from level used
490  leftEdge = mean - nsig * nSigmaUsed*sigma
491  rightEdge = mean + nsig * nSigmaUsed*sigma
492  nbins = np.linspace(leftEdge, rightEdge, 1000)
493  ey, bin_borders, patches = a.hist(edgeData, histtype='step', bins=nbins,
494  lw=1, edgecolor='red')
495  y, bin_borders, patches = a.hist(imgData, histtype='step', bins=nbins,
496  lw=3, edgecolor='blue')
497 
498  # Report number of entries in over-and -underflow bins, i.e. off the edges of the histogram
499  nOverflow = len(imgData[imgData > rightEdge])
500  nUnderflow = len(imgData[imgData < leftEdge])
501 
502  # Put v-lines and textboxes in
503  a.axvline(thrUpper, c='k')
504  a.axvline(thrLower, c='k')
505  msg = f"{amp.getName()}\nmean:{mean: .2f}\n$\\sigma$:{sigma: .2f}"
506  a.text(0.65, 0.6, msg, transform=a.transAxes, fontsize=11)
507  msg = f"nLeft:{nLeft}\nnRight:{nRight}\nnOverflow:{nOverflow}\nnUnderflow:{nUnderflow}"
508  a.text(0.03, 0.6, msg, transform=a.transAxes, fontsize=11.5)
509 
510  # set axis limits and scales
511  a.set_ylim([1., 1.7*np.max(y)])
512  lPlot, rPlot = a.get_xlim()
513  a.set_xlim(np.array([lPlot, rPlot]))
514  a.set_yscale('log')
515  a.set_xlabel("ADU/s")
516  return
517 
518 
Statistics makeStatistics(lsst::afw::image::Image< Pixel > const &img, lsst::afw::image::Mask< image::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl=StatisticsControl())
Handle a watered-down front-end to the constructor (no variance)
Definition: Statistics.h:360
def getDebugFrame(debugDisplay, name)
Definition: lsstDebug.py:95

◆ debugView()

def lsst.cp.pipe.defects.MeasureDefectsTask.debugView (   self,
  stepname,
  ampImage,
  defects,
  detector 
)
Plot the defects found by the task.

Parameters
----------
exp : `lsst.afw.image.exposure.Exposure`
    The exposure in which the defects were found.
visit : `int`
    The visit number.
defects : `lsst.ip.isr.Defect`
    The defects to plot.
imageType : `str`
    The type of image, either 'dark' or 'flat'.

Definition at line 399 of file defects.py.

399  def debugView(self, stepname, ampImage, defects, detector):
400  # def _plotDefects(self, exp, visit, defects, imageType): # pragma: no cover
401  """Plot the defects found by the task.
402 
403  Parameters
404  ----------
405  exp : `lsst.afw.image.exposure.Exposure`
406  The exposure in which the defects were found.
407  visit : `int`
408  The visit number.
409  defects : `lsst.ip.isr.Defect`
410  The defects to plot.
411  imageType : `str`
412  The type of image, either 'dark' or 'flat'.
413  """
414  frame = getDebugFrame(self._display, stepname)
415  if frame:
416  disp = afwDisplay.Display(frame=frame)
417  disp.scale('asinh', 'zscale')
418  disp.setMaskTransparency(80)
419  disp.setMaskPlaneColor("BAD", afwDisplay.RED)
420 
421  maskedIm = ampImage.clone()
422  defects.maskPixels(maskedIm, "BAD")
423 
424  mpDict = maskedIm.mask.getMaskPlaneDict()
425  for plane in mpDict.keys():
426  if plane in ['BAD']:
427  continue
428  disp.setMaskPlaneColor(plane, afwDisplay.IGNORE)
429 
430  disp.setImageColormap('gray')
431  disp.mtv(maskedIm)
432  cameraGeom.utils.overlayCcdBoxes(detector, isTrimmed=True, display=disp)
433  prompt = "Press Enter to continue [c]... "
434  while True:
435  ans = input(prompt).lower()
436  if ans in ('', 'c', ):
437  break
438 

◆ findHotAndColdPixels()

def lsst.cp.pipe.defects.MeasureDefectsTask.findHotAndColdPixels (   self,
  exp,
  nSigma 
)
Find hot and cold pixels in an image.

Using config-defined thresholds on a per-amp basis, mask
pixels that are nSigma above threshold in dark frames (hot
pixels), or nSigma away from the clipped mean in flats (hot &
cold pixels).

Parameters
----------
exp : `lsst.afw.image.exposure.Exposure`
    The exposure in which to find defects.
nSigma : `list [ `float` ]
    Detection threshold to use.  Positive for DETECTED pixels,
    negative for DETECTED_NEGATIVE pixels.

Returns
-------
defects : `lsst.ip.isr.Defect`
    The defects found in the image.

Definition at line 190 of file defects.py.

190  def findHotAndColdPixels(self, exp, nSigma):
191  """Find hot and cold pixels in an image.
192 
193  Using config-defined thresholds on a per-amp basis, mask
194  pixels that are nSigma above threshold in dark frames (hot
195  pixels), or nSigma away from the clipped mean in flats (hot &
196  cold pixels).
197 
198  Parameters
199  ----------
200  exp : `lsst.afw.image.exposure.Exposure`
201  The exposure in which to find defects.
202  nSigma : `list [ `float` ]
203  Detection threshold to use. Positive for DETECTED pixels,
204  negative for DETECTED_NEGATIVE pixels.
205 
206  Returns
207  -------
208  defects : `lsst.ip.isr.Defect`
209  The defects found in the image.
210 
211  """
212 
213  self._setEdgeBits(exp)
214  maskedIm = exp.maskedImage
215 
216  # the detection polarity for afwDetection, True for positive,
217  # False for negative, and therefore True for darks as they only have
218  # bright pixels, and both for flats, as they have bright and dark pix
219  footprintList = []
220 
221  for amp in exp.getDetector():
222  ampImg = maskedIm[amp.getBBox()].clone()
223 
224  # crop ampImage depending on where the amp lies in the image
225  if self.config.nPixBorderLeftRight:
226  if ampImg.getX0() == 0:
227  ampImg = ampImg[self.config.nPixBorderLeftRight:, :, afwImage.LOCAL]
228  else:
229  ampImg = ampImg[:-self.config.nPixBorderLeftRight, :, afwImage.LOCAL]
230  if self.config.nPixBorderUpDown:
231  if ampImg.getY0() == 0:
232  ampImg = ampImg[:, self.config.nPixBorderUpDown:, afwImage.LOCAL]
233  else:
234  ampImg = ampImg[:, :-self.config.nPixBorderUpDown, afwImage.LOCAL]
235 
236  if self._getNumGoodPixels(ampImg) == 0: # amp contains no usable pixels
237  continue
238 
239  # Remove a background estimate
240  ampImg -= afwMath.makeStatistics(ampImg, afwMath.MEANCLIP, ).getValue()
241 
242  mergedSet = None
243  for sigma in nSigma:
244  nSig = np.abs(sigma)
245  self.debugHistogram('ampFlux', ampImg, nSig, exp)
246  polarity = {-1: False, 1: True}[np.sign(sigma)]
247 
248  threshold = afwDetection.createThreshold(nSig, 'stdev', polarity=polarity)
249 
250  footprintSet = afwDetection.FootprintSet(ampImg, threshold)
251  footprintSet.setMask(maskedIm.mask, ("DETECTED" if polarity else "DETECTED_NEGATIVE"))
252 
253  if mergedSet is None:
254  mergedSet = footprintSet
255  else:
256  mergedSet.merge(footprintSet)
257 
258  footprintList += mergedSet.getFootprints()
259 
260  self.debugView('defectMap', ampImg,
261  Defects.fromFootprintList(mergedSet.getFootprints()), exp.getDetector())
262 
263  defects = Defects.fromFootprintList(footprintList)
264  defects = self.maskBlocksIfIntermitentBadPixelsInColumn(defects)
265 
266  return defects
267 

◆ maskBlocksIfIntermitentBadPixelsInColumn()

def lsst.cp.pipe.defects.MeasureDefectsTask.maskBlocksIfIntermitentBadPixelsInColumn (   self,
  defects 
)
Mask blocks in a column if there are on-and-off bad pixels

If there's a column with on and off bad pixels, mask all the
pixels in between, except if there is a large enough gap of
consecutive good pixels between two bad pixels in the column.

Parameters
---------
defects: `lsst.ip.isr.Defect`
    The defects found in the image so far

Returns
------
defects: `lsst.ip.isr.Defect`
    If the number of bad pixels in a column is not larger or
    equal than self.config.badPixelColumnThreshold, the iput
    list is returned. Otherwise, the defects list returned
    will include boxes that mask blocks of on-and-of pixels.

Definition at line 298 of file defects.py.

298  def maskBlocksIfIntermitentBadPixelsInColumn(self, defects):
299  """Mask blocks in a column if there are on-and-off bad pixels
300 
301  If there's a column with on and off bad pixels, mask all the
302  pixels in between, except if there is a large enough gap of
303  consecutive good pixels between two bad pixels in the column.
304 
305  Parameters
306  ---------
307  defects: `lsst.ip.isr.Defect`
308  The defects found in the image so far
309 
310  Returns
311  ------
312  defects: `lsst.ip.isr.Defect`
313  If the number of bad pixels in a column is not larger or
314  equal than self.config.badPixelColumnThreshold, the iput
315  list is returned. Otherwise, the defects list returned
316  will include boxes that mask blocks of on-and-of pixels.
317 
318  """
319  # Get the (x, y) values of each bad pixel in amp.
320  coordinates = []
321  for defect in defects:
322  bbox = defect.getBBox()
323  x0, y0 = bbox.getMinX(), bbox.getMinY()
324  deltaX0, deltaY0 = bbox.getDimensions()
325  for j in np.arange(y0, y0+deltaY0):
326  for i in np.arange(x0, x0 + deltaX0):
327  coordinates.append((i, j))
328 
329  x, y = [], []
330  for coordinatePair in coordinates:
331  x.append(coordinatePair[0])
332  y.append(coordinatePair[1])
333 
334  x = np.array(x)
335  y = np.array(y)
336  # Find the defects with same "x" (vertical) coordinate (column).
337  unique, counts = np.unique(x, return_counts=True)
338  multipleX = []
339  for (a, b) in zip(unique, counts):
340  if b >= self.config.badOnAndOffPixelColumnThreshold:
341  multipleX.append(a)
342  if len(multipleX) != 0:
343  defects = self._markBlocksInBadColumn(x, y, multipleX, defects)
344 
345  return defects
346 

◆ run()

def lsst.cp.pipe.defects.MeasureDefectsTask.run (   self,
  inputExp,
  camera 
)
Measure one exposure for defects.

Parameters
----------
inputExp : `lsst.afw.image.Exposure`
     Exposure to examine.
camera : `lsst.afw.cameraGeom.Camera`
     Camera to use for metadata.

Returns
-------
results : `lsst.pipe.base.Struct`
     Results struct containing:
     - ``outputDefects` : `lsst.ip.isr.Defects`
         The defects measured from this exposure.

Definition at line 132 of file defects.py.

132  def run(self, inputExp, camera):
133  """Measure one exposure for defects.
134 
135  Parameters
136  ----------
137  inputExp : `lsst.afw.image.Exposure`
138  Exposure to examine.
139  camera : `lsst.afw.cameraGeom.Camera`
140  Camera to use for metadata.
141 
142  Returns
143  -------
144  results : `lsst.pipe.base.Struct`
145  Results struct containing:
146  - ``outputDefects` : `lsst.ip.isr.Defects`
147  The defects measured from this exposure.
148  """
149  detector = inputExp.getDetector()
150 
151  filterName = inputExp.getFilterLabel().physicalLabel
152  datasetType = inputExp.getMetadata().get('IMGTYPE', 'UNKNOWN')
153 
154  if datasetType.lower() == 'dark':
155  nSigmaList = [self.config.nSigmaBright]
156  else:
157  nSigmaList = [self.config.nSigmaBright, self.config.nSigmaDark]
158  defects = self.findHotAndColdPixels(inputExp, nSigmaList)
159 
160  msg = "Found %s defects containing %s pixels in %s"
161  self.log.info(msg, len(defects), self._nPixFromDefects(defects), datasetType)
162 
163  defects.updateMetadata(camera=camera, detector=detector, filterName=filterName,
164  setCalibId=True, setDate=True,
165  cpDefectGenImageType=datasetType)
166 
167  return pipeBase.Struct(
168  outputDefects=defects,
169  )
170 
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

Member Data Documentation

◆ ConfigClass

lsst.cp.pipe.defects.MeasureDefectsTask.ConfigClass = MeasureDefectsTaskConfig
static

Definition at line 129 of file defects.py.


The documentation for this class was generated from the following file: