LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
isrTask.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 lsst.afw.image as afwImage
23 import lsst.meas.algorithms as measAlg
24 import lsst.pex.config as pexConfig
25 import lsst.pipe.base as pipeBase
26 from . import isr
27 from .isrLib import maskNans
28 from .assembleCcdTask import AssembleCcdTask
29 from .fringe import FringeTask
30 
31 class IsrTaskConfig(pexConfig.Config):
32  doBias = pexConfig.Field(
33  dtype = bool,
34  doc = "Apply bias frame correction?",
35  default = True,
36  )
37  doDark = pexConfig.Field(
38  dtype = bool,
39  doc = "Apply dark frame correction?",
40  default = True,
41  )
42  doFlat = pexConfig.Field(
43  dtype = bool,
44  doc = "Apply flat field correction?",
45  default = True,
46  )
47  doFringe = pexConfig.Field(
48  dtype = bool,
49  doc = "Apply fringe correction?",
50  default = True,
51  )
52  doWrite = pexConfig.Field(
53  dtype = bool,
54  doc = "Persist postISRCCD?",
55  default = True,
56  )
57  assembleCcd = pexConfig.ConfigurableField(
58  target = AssembleCcdTask,
59  doc = "CCD assembly task",
60  )
61  fringeAfterFlat = pexConfig.Field(
62  dtype = bool,
63  doc = "Do fringe subtraction after flat-fielding?",
64  default = True,
65  )
66  fringe = pexConfig.ConfigurableField(
67  target = FringeTask,
68  doc = "Fringe subtraction task",
69  )
70  fwhm = pexConfig.Field(
71  dtype = float,
72  doc = "FWHM of PSF (arcsec)",
73  default = 1.0,
74  )
75  saturatedMaskName = pexConfig.Field(
76  dtype = str,
77  doc = "Name of mask plane to use in saturation detection and interpolation",
78  default = "SAT",
79  )
80  flatScalingType = pexConfig.ChoiceField(
81  dtype = str,
82  doc = "The method for scaling the flat on the fly.",
83  default = 'USER',
84  allowed = {
85  "USER": "Scale by flatUserScale",
86  "MEAN": "Scale by the inverse of the mean",
87  "MEDIAN": "Scale by the inverse of the median",
88  },
89  )
90  flatUserScale = pexConfig.Field(
91  dtype = float,
92  doc = "If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
93  default = 1.0,
94  )
95  overscanFitType = pexConfig.ChoiceField(
96  dtype = str,
97  doc = "The method for fitting the overscan bias level.",
98  default = 'MEDIAN',
99  allowed = {
100  "POLY": "Fit ordinary polynomial to the longest axis of the overscan region",
101  "CHEB": "Fit Chebyshev polynomial to the longest axis of the overscan region",
102  "LEG": "Fit Legendre polynomial to the longest axis of the overscan region",
103  "NATURAL_SPLINE": "Fit natural spline to the longest axis of the overscan region",
104  "CUBIC_SPLINE": "Fit cubic spline to the longest axis of the overscan region",
105  "AKIMA_SPLINE": "Fit Akima spline to the longest axis of the overscan region",
106  "MEAN": "Correct using the mean of the overscan region",
107  "MEDIAN": "Correct using the median of the overscan region",
108  },
109  )
110  overscanOrder = pexConfig.Field(
111  dtype = int,
112  doc = ("Order of polynomial or to fit if overscan fit type is a polynomial, " +
113  "or number of spline knots if overscan fit type is a spline."),
114  default = 1,
115  )
116  overscanRej = pexConfig.Field(
117  dtype = float,
118  doc = "Rejection threshold (sigma) for collapsing overscan before fit",
119  default = 3.0,
120  )
121  growSaturationFootprintSize = pexConfig.Field(
122  dtype = int,
123  doc = "Number of pixels by which to grow the saturation footprints",
124  default = 1,
125  )
126  fluxMag0T1 = pexConfig.Field(
127  dtype = float,
128  doc = "The approximate flux of a zero-magnitude object in a one-second exposure",
129  default = 1e10,
130  )
131  setGainAssembledCcd = pexConfig.Field(
132  dtype = bool,
133  doc = "update exposure metadata in the assembled ccd to reflect the effective gain of the assembled chip",
134  default = True,
135  )
136  keysToRemoveFromAssembledCcd = pexConfig.ListField(
137  dtype = str,
138  doc = "fields to remove from the metadata of the assembled ccd.",
139  default = [],
140  )
141  doAssembleDetrends = pexConfig.Field(
142  dtype = bool,
143  default = False,
144  doc = "Assemble detrend/calibration frames?"
145  )
146 
147 ## \addtogroup LSST_task_documentation
148 ## \{
149 ## \page IsrTask
150 ## \ref IsrTask_ "IsrTask"
151 ## \copybrief IsrTask
152 ## \}
153 
154 class IsrTask(pipeBase.CmdLineTask):
155  """!
156  \anchor IsrTask_
157 
158  \brief Apply common instrument signature correction algorithms to a raw frame.
159 
160  \section ip_isr_isr_Contents Contents
161 
162  - \ref ip_isr_isr_Purpose
163  - \ref ip_isr_isr_Initialize
164  - \ref ip_isr_isr_IO
165  - \ref ip_isr_isr_Config
166  - \ref ip_isr_isr_Debug
167  - \ref ip_isr_isr_Example
168 
169  \section ip_isr_isr_Purpose Description
170 
171  The process for correcting imaging data is very similar from camera to camera.
172  This task provides a vanilla implementation of doing these corrections, including
173  the ability to turn certain corrections off if they are not needed. The input
174  is a daf.persistence.butlerSubset.ButlerDataRef. The data reference can return
175  the raw input and all the calibration products. The raw input is a single chip
176  sized mosaic of all amps including overscans and other non-science pixels. This
177  task may not meet all needs and it is expected that it will be subclassed for specific
178  applications.
179 
180  \section ip_isr_isr_Initialize Task initialization
181 
182  \copydoc \_\_init\_\_
183 
184  \section ip_isr_isr_IO Inputs/Outputs to the assembleCcd method
185 
186  \copydoc run
187 
188  \section ip_isr_isr_Config Configuration parameters
189 
190  See \ref IsrTaskConfig
191 
192  \section ip_isr_isr_Debug Debug variables
193 
194  The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
195  flag \c -d to import \b debug.py from your \c PYTHONPATH; see <a
196  href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
197  Using lsstDebug to control debugging output</a> for more about \b debug.py files.
198 
199  The available variables in AssembleCcdTask are:
200  <DL>
201  <DT> \c display
202  <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
203  <DL>
204  <DT> postISRCCD
205  <DD> display exposure after ISR has been applied
206  </DL>
207  </DL>
208 
209  \section ip_isr_isr_Example A complete example of using IsrTask
210 
211  This code is in runIsrTask.py in the examples directory, and can be run as \em e.g.
212  \code
213  python examples/runIsrTask.py
214  \endcode
215 <HR>
216  \dontinclude runIsrTask.py
217  Import the task. There are other imports. Read the source file for more info.
218  \skipline IsrTask
219 
220  \dontinclude exampleUtils.py
221  Create the input data reference with the help of some utilities in examples/exampleUtils.py. This
222  is a mock data reference that has all the right methods to run through ISR. We will only
223  do overscan, dark and flat correction, so it only needs to know how to get those products (and an
224  empty list of defects).
225  \skip FakeDataRef
226  \until writeFits
227  The above numbers can be changed to modify the gradient in the flat, for example.
228 
229  \dontinclude exampleUtils.py
230  The data are constructed by hand so that all effects will be corrected for essentially perfectly
231  \skip makeRaw
232  \until return flatExposure
233 
234 
235  \dontinclude runIsrTask.py
236  Construct the task and set some config parameters. Specifically, we don't want to
237  do zero or fringe correction. We also don't want the assembler messing with the gain.
238  \skip runIsr
239  \until config=isrConfig
240 
241  Now make the fake data reference and run it through ISR.
242  \skip sensorRef
243  \until isrTask.run
244 
245  <HR>
246  To investigate the \ref ip_isr_isr_Debug, put something like
247  \code{.py}
248  import lsstDebug
249  def DebugInfo(name):
250  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
251  if name == "lsst.ip.isr.isrTask":
252  di.display = {'postISRCCD':2}
253  return di
254 
255  lsstDebug.Info = DebugInfo
256  \endcode
257  into your debug.py file and run runAssembleTask.py with the \c --debug flag.
258 
259 
260  Conversion notes:
261  Display code should be updated once we settle on a standard way of controlling what is displayed.
262  """
263  ConfigClass = IsrTaskConfig
264  _DefaultName = "isr"
265 
266  def __init__(self, *args, **kwargs):
267  '''!Constructor for IsrTask
268  \param[in] *args -- a list of positional arguments passed on to the Task constructor
269  \param[in] **kwargs -- a dictionary of keyword arguments passed on to the Task constructor
270  Call the lsst.pipe.base.task.Task.__init__ method
271  Then setup the assembly and fringe correction subtasks
272  '''
273  pipeBase.Task.__init__(self, *args, **kwargs)
274  self.makeSubtask("assembleCcd")
275  self.makeSubtask("fringe")
276 
277  @pipeBase.timeMethod
278  def run(self, sensorRef):
279  """!Perform instrument signature removal on an exposure
280 
281  Steps include:
282  - Detect saturation, apply overscan correction, bias, dark and flat
283  - Perform CCD assembly
284  - Interpolate over defects, saturated pixels and all NaNs
285  - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
286 
287  \param[in] sensorRef -- daf.persistence.butlerSubset.ButlerDataRef of the detector data to be processed
288  \return a pipeBase.Struct with fields:
289  - exposure: the exposure after application of ISR
290  """
291  self.log.log(self.log.INFO, "Performing ISR on sensor %s" % (sensorRef.dataId))
292  ccdExposure = sensorRef.get('raw')
293  ccd = ccdExposure.getDetector()
294 
295  ccdExposure = self.convertIntToFloat(ccdExposure)
296  for amp in ccd:
297  self.saturationDetection(ccdExposure, amp)
298 
299  self.overscanCorrection(ccdExposure, amp)
300 
301  ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
302 
303  ccd = ccdExposure.getDetector()
304 
305  if self.config.doBias:
306  self.biasCorrection(ccdExposure, sensorRef)
307 
308  if self.config.doDark:
309  self.darkCorrection(ccdExposure, sensorRef)
310 
311  for amp in ccd:
312  ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
313 
314  self.updateVariance(ampExposure, amp)
315 
316  if self.config.doFringe and not self.config.fringeAfterFlat:
317  self.fringe.run(ccdExposure, sensorRef,
318  assembler=self.assembleCcd if self.config.doAssembleDetrends else None)
319 
320  if self.config.doFlat:
321  self.flatCorrection(ccdExposure, sensorRef)
322 
323  defects = sensorRef.get('defects')
324  self.maskAndInterpDefect(ccdExposure, defects)
325 
326  self.saturationInterpolation(ccdExposure)
327 
328  self.maskAndInterpNan(ccdExposure)
329 
330  if self.config.doFringe and self.config.fringeAfterFlat:
331  self.fringe.run(ccdExposure, sensorRef,
332  assembler=self.assembleCcd if self.config.doAssembleDetrends else None)
333 
334  ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1 * ccdExposure.getCalib().getExptime())
335 
336  if self.config.doWrite:
337  sensorRef.put(ccdExposure, "postISRCCD")
338 
339  self.display("postISRCCD", ccdExposure)
340 
341  return pipeBase.Struct(
342  exposure = ccdExposure,
343  )
344 
345  def convertIntToFloat(self, exposure):
346  """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
347  """
348  if isinstance(exposure, afwImage.ExposureF):
349  # Nothing to be done
350  return exposure
351  if not hasattr(exposure, "convertF"):
352  raise RuntimeError("Unable to convert exposure (%s) to float" % type(exposure))
353 
354  newexposure = exposure.convertF()
355  maskedImage = newexposure.getMaskedImage()
356  varArray = maskedImage.getVariance().getArray()
357  varArray[:,:] = 1
358  maskArray = maskedImage.getMask().getArray()
359  maskArray[:,:] = 0
360  return newexposure
361 
362  def biasCorrection(self, exposure, dataRef):
363  """!Apply bias correction in place
364 
365  \param[in,out] exposure exposure to process
366  \param[in] dataRef data reference at same level as exposure
367  """
368  bias = self.getDetrend(dataRef, "bias")
369  isr.biasCorrection(exposure.getMaskedImage(), bias.getMaskedImage())
370 
371  def darkCorrection(self, exposure, dataRef):
372  """!Apply dark correction in place
373 
374  \param[in,out] exposure exposure to process
375  \param[in] dataRef data reference at same level as exposure
376  """
377  darkExposure = self.getDetrend(dataRef, "dark")
378  darkCalib = darkExposure.getCalib()
379  isr.darkCorrection(
380  maskedImage = exposure.getMaskedImage(),
381  darkMaskedImage = darkExposure.getMaskedImage(),
382  expScale = exposure.getCalib().getExptime(),
383  darkScale = darkCalib.getExptime(),
384  )
385 
386  def updateVariance(self, ampExposure, amp):
387  """!Set the variance plane based on the image plane, plus amplifier gain and read noise
388 
389  \param[in,out] ampExposure exposure to process
390  \param[in] amp amplifier detector information
391  """
392  isr.updateVariance(
393  maskedImage = ampExposure.getMaskedImage(),
394  gain = amp.getGain(),
395  readNoise = amp.getReadNoise(),
396  )
397 
398  def flatCorrection(self, exposure, dataRef):
399  """!Apply flat correction in place
400 
401  \param[in,out] exposure exposure to process
402  \param[in] dataRef data reference at same level as exposure
403  """
404  flatfield = self.getDetrend(dataRef, "flat")
405  isr.flatCorrection(
406  maskedImage = exposure.getMaskedImage(),
407  flatMaskedImage = flatfield.getMaskedImage(),
408  scalingType = self.config.flatScalingType,
409  userScale = self.config.flatUserScale,
410  )
411 
412  def getDetrend(self, dataRef, detrend, immediate=True):
413  """!Get a detrend exposure
414 
415  \param[in] dataRef data reference for exposure
416  \param[in] detrend detrend/calibration to read
417  \param[in] immediate if True, disable butler proxies to enable error
418  handling within this routine
419  \return Detrend exposure
420  """
421  try:
422  exp = dataRef.get(detrend, immediate=immediate)
423  except Exception, e:
424  raise RuntimeError("Unable to retrieve %s for %s: %s" % (detrend, dataRef.dataId, e))
425  if self.config.doAssembleDetrends:
426  exp = self.assembleCcd.assembleCcd(exp)
427  return exp
428 
429  def saturationDetection(self, exposure, amp):
430  """!Detect saturated pixels and mask them using mask plane "SAT", in place
431 
432  \param[in,out] exposure exposure to process; only the amp DataSec is processed
433  \param[in] amp amplifier device data
434  """
435  maskedImage = exposure.getMaskedImage()
436  dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
437  isr.makeThresholdMask(
438  maskedImage = dataView,
439  threshold = amp.getSaturation(),
440  growFootprints = 0,
441  maskName = self.config.saturatedMaskName,
442  )
443 
444  def saturationInterpolation(self, ccdExposure):
445  """!Interpolate over saturated pixels, in place
446 
447  \param[in,out] ccdExposure exposure to process
448 
449  \warning:
450  - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
451  - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
452  """
453  isr.interpolateFromMask(
454  maskedImage = ccdExposure.getMaskedImage(),
455  fwhm = self.config.fwhm,
456  growFootprints = self.config.growSaturationFootprintSize,
457  maskName = self.config.saturatedMaskName,
458  )
459 
460  def maskAndInterpDefect(self, ccdExposure, defectBaseList):
461  """!Mask defects using mask plane "BAD" and interpolate over them, in place
462 
463  \param[in,out] ccdExposure exposure to process
464  \param[in] defectBaseList a list of defects to mask and interpolate
465 
466  \warning: call this after CCD assembly, since defects may cross amplifier boundaries
467  """
468  maskedImage = ccdExposure.getMaskedImage()
469  defectList = measAlg.DefectListT()
470  for d in defectBaseList:
471  bbox = d.getBBox()
472  nd = measAlg.Defect(bbox)
473  defectList.append(nd)
474  isr.maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')
475  isr.interpolateDefectList(
476  maskedImage = maskedImage,
477  defectList = defectList,
478  fwhm = self.config.fwhm,
479  )
480 
481  def maskAndInterpNan(self, exposure):
482  """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
483 
484  We mask and interpolate over all NaNs, including those
485  that are masked with other bits (because those may or may
486  not be interpolated over later, and we want to remove all
487  NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
488  is used to preserve the historical name.
489 
490  \param[in,out] exposure exposure to process
491  """
492  maskedImage = exposure.getMaskedImage()
493 
494  # Find and mask NaNs
495  maskedImage.getMask().addMaskPlane("UNMASKEDNAN")
496  maskVal = maskedImage.getMask().getPlaneBitMask("UNMASKEDNAN")
497  numNans = maskNans(maskedImage, maskVal)
498  self.metadata.set("NUMNANS", numNans)
499 
500  # Interpolate over these previously-unmasked NaNs
501  if numNans > 0:
502  self.log.log(self.log.WARN, "There were %i unmasked NaNs" % (numNans,))
503  nanDefectList = isr.getDefectListFromMask(
504  maskedImage = maskedImage,
505  maskName = 'UNMASKEDNAN',
506  growFootprints = 0,
507  )
508  isr.interpolateDefectList(
509  maskedImage = exposure.getMaskedImage(),
510  defectList = nanDefectList,
511  fwhm = self.config.fwhm,
512  )
513 
514  def overscanCorrection(self, exposure, amp):
515  """!Apply overscan correction, in place
516 
517  \param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels
518  \param[in] amp amplifier device data
519  """
520  if not amp.getHasRawInfo():
521  raise RuntimeError("This method must be executed on an amp with raw information.")
522  maskedImage = exposure.getMaskedImage()
523  dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
524 
525  expImage = exposure.getMaskedImage().getImage()
526  overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
527 
528  isr.overscanCorrection(
529  ampMaskedImage = dataView,
530  overscanImage = overscanImage,
531  fitType = self.config.overscanFitType,
532  order = self.config.overscanOrder,
533  collapseRej = self.config.overscanRej,
534  )
def maskAndInterpNan
Mask NaNs using mask plane &quot;UNMASKEDNAN&quot; and interpolate over them, in place.
Definition: isrTask.py:481
def flatCorrection
Apply flat correction in place.
Definition: isrTask.py:398
def run
Perform instrument signature removal on an exposure.
Definition: isrTask.py:278
def __init__
Constructor for IsrTask.
Definition: isrTask.py:266
Apply common instrument signature correction algorithms to a raw frame.
Definition: isrTask.py:154
def updateVariance
Set the variance plane based on the image plane, plus amplifier gain and read noise.
Definition: isrTask.py:386
def maskAndInterpDefect
Mask defects using mask plane &quot;BAD&quot; and interpolate over them, in place.
Definition: isrTask.py:460
def getDetrend
Get a detrend exposure.
Definition: isrTask.py:412
def overscanCorrection
Apply overscan correction, in place.
Definition: isrTask.py:514
def saturationDetection
Detect saturated pixels and mask them using mask plane &quot;SAT&quot;, in place.
Definition: isrTask.py:429
def darkCorrection
Apply dark correction in place.
Definition: isrTask.py:371
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow)
Definition: Isr.cc:61
Encapsulate information about a bad portion of a detector.
Definition: Interp.h:70
def saturationInterpolation
Interpolate over saturated pixels, in place.
Definition: isrTask.py:444
def biasCorrection
Apply bias correction in place.
Definition: isrTask.py:362