lsst.ip.isr  15.0-4-gf5d1e39+1
isrTask.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 from builtins import range
3 from builtins import object
4 #
5 # LSST Data Management System
6 # Copyright 2008-2016 AURA/LSST.
7 #
8 # This product includes software developed by the
9 # LSST Project (http://www.lsst.org/).
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the LSST License Statement and
22 # the GNU General Public License along with this program. If not,
23 # see <http://www.lsstcorp.org/LegalNotices/>.
24 #
25 import math
26 import numpy
27 
28 import lsst.afw.geom as afwGeom
29 import lsst.afw.image as afwImage
30 import lsst.meas.algorithms as measAlg
31 import lsst.pex.config as pexConfig
32 import lsst.pipe.base as pipeBase
33 import lsst.afw.math as afwMath
34 from lsst.daf.persistence import ButlerDataRef
35 from lsstDebug import getDebugFrame
36 from lsst.afw.display import getDisplay
37 from . import isrFunctions
38 from .assembleCcdTask import AssembleCcdTask
39 from .fringe import FringeTask
40 from lsst.afw.geom import Polygon
41 from lsst.afw.geom.wcsUtils import makeDistortedTanWcs
42 from lsst.afw.cameraGeom import PIXELS, FOCAL_PLANE, FIELD_ANGLE, NullLinearityType
43 from contextlib import contextmanager
44 from .isr import maskNans
45 from .crosstalk import CrosstalkTask
46 
47 
48 class IsrTaskConfig(pexConfig.Config):
49  doBias = pexConfig.Field(
50  dtype=bool,
51  doc="Apply bias frame correction?",
52  default=True,
53  )
54  doDark = pexConfig.Field(
55  dtype=bool,
56  doc="Apply dark frame correction?",
57  default=True,
58  )
59  doFlat = pexConfig.Field(
60  dtype=bool,
61  doc="Apply flat field correction?",
62  default=True,
63  )
64  doFringe = pexConfig.Field(
65  dtype=bool,
66  doc="Apply fringe correction?",
67  default=True,
68  )
69  doDefect = pexConfig.Field(
70  dtype=bool,
71  doc="Apply correction for CCD defects, e.g. hot pixels?",
72  default=True,
73  )
74  doAddDistortionModel = pexConfig.Field(
75  dtype=bool,
76  doc="Apply a distortion model based on camera geometry to the WCS?",
77  default=True,
78  )
79  doWrite = pexConfig.Field(
80  dtype=bool,
81  doc="Persist postISRCCD?",
82  default=True,
83  )
84  biasDataProductName = pexConfig.Field(
85  dtype=str,
86  doc="Name of the bias data product",
87  default="bias",
88  )
89  darkDataProductName = pexConfig.Field(
90  dtype=str,
91  doc="Name of the dark data product",
92  default="dark",
93  )
94  flatDataProductName = pexConfig.Field(
95  dtype=str,
96  doc="Name of the flat data product",
97  default="flat",
98  )
99  assembleCcd = pexConfig.ConfigurableField(
100  target=AssembleCcdTask,
101  doc="CCD assembly task",
102  )
103  gain = pexConfig.Field(
104  dtype=float,
105  doc="The gain to use if no Detector is present in the Exposure (ignored if NaN)",
106  default=float("NaN"),
107  )
108  readNoise = pexConfig.Field(
109  dtype=float,
110  doc="The read noise to use if no Detector is present in the Exposure",
111  default=0.0,
112  )
113  saturation = pexConfig.Field(
114  dtype=float,
115  doc="The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
116  default=float("NaN"),
117  )
118  fringeAfterFlat = pexConfig.Field(
119  dtype=bool,
120  doc="Do fringe subtraction after flat-fielding?",
121  default=True,
122  )
123  fringe = pexConfig.ConfigurableField(
124  target=FringeTask,
125  doc="Fringe subtraction task",
126  )
127  fwhm = pexConfig.Field(
128  dtype=float,
129  doc="FWHM of PSF (arcsec)",
130  default=1.0,
131  )
132  saturatedMaskName = pexConfig.Field(
133  dtype=str,
134  doc="Name of mask plane to use in saturation detection and interpolation",
135  default="SAT",
136  )
137  suspectMaskName = pexConfig.Field(
138  dtype=str,
139  doc="Name of mask plane to use for suspect pixels",
140  default="SUSPECT",
141  )
142  flatScalingType = pexConfig.ChoiceField(
143  dtype=str,
144  doc="The method for scaling the flat on the fly.",
145  default='USER',
146  allowed={
147  "USER": "Scale by flatUserScale",
148  "MEAN": "Scale by the inverse of the mean",
149  "MEDIAN": "Scale by the inverse of the median",
150  },
151  )
152  flatUserScale = pexConfig.Field(
153  dtype=float,
154  doc="If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
155  default=1.0,
156  )
157  overscanFitType = pexConfig.ChoiceField(
158  dtype=str,
159  doc="The method for fitting the overscan bias level.",
160  default='MEDIAN',
161  allowed={
162  "POLY": "Fit ordinary polynomial to the longest axis of the overscan region",
163  "CHEB": "Fit Chebyshev polynomial to the longest axis of the overscan region",
164  "LEG": "Fit Legendre polynomial to the longest axis of the overscan region",
165  "NATURAL_SPLINE": "Fit natural spline to the longest axis of the overscan region",
166  "CUBIC_SPLINE": "Fit cubic spline to the longest axis of the overscan region",
167  "AKIMA_SPLINE": "Fit Akima spline to the longest axis of the overscan region",
168  "MEAN": "Correct using the mean of the overscan region",
169  "MEDIAN": "Correct using the median of the overscan region",
170  },
171  )
172  overscanOrder = pexConfig.Field(
173  dtype=int,
174  doc=("Order of polynomial or to fit if overscan fit type is a polynomial, " +
175  "or number of spline knots if overscan fit type is a spline."),
176  default=1,
177  )
178  overscanRej = pexConfig.Field(
179  dtype=float,
180  doc="Rejection threshold (sigma) for collapsing overscan before fit",
181  default=3.0,
182  )
183  growSaturationFootprintSize = pexConfig.Field(
184  dtype=int,
185  doc="Number of pixels by which to grow the saturation footprints",
186  default=1,
187  )
188  doSaturationInterpolation = pexConfig.Field(
189  dtype=bool,
190  doc="Perform interpolation over pixels masked as saturated?",
191  default=True,
192  )
193  doNanInterpAfterFlat = pexConfig.Field(
194  dtype=bool,
195  doc=("If True, ensure we interpolate NaNs after flat-fielding, even if we "
196  "also have to interpolate them before flat-fielding."),
197  default=False,
198  )
199  fluxMag0T1 = pexConfig.Field(
200  dtype=float,
201  doc="The approximate flux of a zero-magnitude object in a one-second exposure",
202  default=1e10,
203  )
204  keysToRemoveFromAssembledCcd = pexConfig.ListField(
205  dtype=str,
206  doc="fields to remove from the metadata of the assembled ccd.",
207  default=[],
208  )
209  doAssembleIsrExposures = pexConfig.Field(
210  dtype=bool,
211  default=False,
212  doc="Assemble amp-level calibration exposures into ccd-level exposure?"
213  )
214  doAssembleCcd = pexConfig.Field(
215  dtype=bool,
216  default=True,
217  doc="Assemble amp-level exposures into a ccd-level exposure?"
218  )
219  expectWcs = pexConfig.Field(
220  dtype=bool,
221  default=True,
222  doc="Expect input science images to have a WCS (set False for e.g. spectrographs)"
223  )
224  doLinearize = pexConfig.Field(
225  dtype=bool,
226  doc="Correct for nonlinearity of the detector's response?",
227  default=True,
228  )
229  doCrosstalk = pexConfig.Field(
230  dtype=bool,
231  doc="Apply intra-CCD crosstalk correction?",
232  default=False,
233  )
234  crosstalk = pexConfig.ConfigurableField(
235  target=CrosstalkTask,
236  doc="Intra-CCD crosstalk correction",
237  )
238  doBrighterFatter = pexConfig.Field(
239  dtype=bool,
240  default=False,
241  doc="Apply the brighter fatter correction"
242  )
243  brighterFatterKernelFile = pexConfig.Field(
244  dtype=str,
245  default='',
246  doc="Kernel file used for the brighter fatter correction"
247  )
248  brighterFatterMaxIter = pexConfig.Field(
249  dtype=int,
250  default=10,
251  doc="Maximum number of iterations for the brighter fatter correction"
252  )
253  brighterFatterThreshold = pexConfig.Field(
254  dtype=float,
255  default=1000,
256  doc="Threshold used to stop iterating the brighter fatter correction. It is the "
257  " absolute value of the difference between the current corrected image and the one"
258  " from the previous iteration summed over all the pixels."
259  )
260  brighterFatterApplyGain = pexConfig.Field(
261  dtype=bool,
262  default=True,
263  doc="Should the gain be applied when applying the brighter fatter correction?"
264  )
265  datasetType = pexConfig.Field(
266  dtype=str,
267  doc="Dataset type for input data; users will typically leave this alone, "
268  "but camera-specific ISR tasks will override it",
269  default="raw",
270  )
271  fallbackFilterName = pexConfig.Field(dtype=str,
272  doc="Fallback default filter name for calibrations", optional=True)
273  doAttachTransmissionCurve = pexConfig.Field(
274  dtype=bool,
275  default=False,
276  doc="Construct and attach a wavelength-dependent throughput curve for this CCD image?"
277  )
278  doUseOpticsTransmission = pexConfig.Field(
279  dtype=bool,
280  default=True,
281  doc="Load and use transmission_optics (if doAttachTransmissionCurve is True)?"
282  )
283  doUseFilterTransmission = pexConfig.Field(
284  dtype=bool,
285  default=True,
286  doc="Load and use transmission_filter (if doAttachTransmissionCurve is True)?"
287  )
288  doUseSensorTransmission = pexConfig.Field(
289  dtype=bool,
290  default=True,
291  doc="Load and use transmission_sensor (if doAttachTransmissionCurve is True)?"
292  )
293  doUseAtmosphereTransmission = pexConfig.Field(
294  dtype=bool,
295  default=True,
296  doc="Load and use transmission_atmosphere (if doAttachTransmissionCurve is True)?"
297  )
298  doEmpiricalReadNoise = pexConfig.Field(
299  dtype=bool,
300  default=False,
301  doc="Calculate empirical read noise instead of value from AmpInfo data?"
302  )
303 
304 
310 
311 
312 class IsrTask(pipeBase.CmdLineTask):
313  """!
314  @anchor IsrTask_
315 
316  @brief Apply common instrument signature correction algorithms to a raw frame.
317 
318  @section ip_isr_isr_Contents Contents
319 
320  - @ref ip_isr_isr_Purpose
321  - @ref ip_isr_isr_Initialize
322  - @ref ip_isr_isr_IO
323  - @ref ip_isr_isr_Config
324  - @ref ip_isr_isr_Debug
325 
326 
327  @section ip_isr_isr_Purpose Description
328 
329  The process for correcting imaging data is very similar from camera to camera.
330  This task provides a vanilla implementation of doing these corrections, including
331  the ability to turn certain corrections off if they are not needed.
332  The inputs to the primary method, run, are a raw exposure to be corrected and the
333  calibration data products. The raw input is a single chip sized mosaic of all amps
334  including overscans and other non-science pixels.
335  The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask
336  and takes as input only a daf.persistence.butlerSubset.ButlerDataRef.
337  This task may not meet all needs and it is expected that it will be subclassed for
338  specific applications.
339 
340  @section ip_isr_isr_Initialize Task initialization
341 
342  @copydoc \_\_init\_\_
343 
344  @section ip_isr_isr_IO Inputs/Outputs to the run method
345 
346  @copydoc run
347 
348  @section ip_isr_isr_Config Configuration parameters
349 
350  See @ref IsrTaskConfig
351 
352  @section ip_isr_isr_Debug Debug variables
353 
354  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a
355  flag @c --debug, @c -d to import @b debug.py from your @c PYTHONPATH; see <a
356  href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
357  Using lsstDebug to control debugging output</a> for more about @b debug.py files.
358 
359  The available variables in IsrTask are:
360  <DL>
361  <DT> @c display
362  <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
363  <DL>
364  <DT> postISRCCD
365  <DD> display exposure after ISR has been applied
366  </DL>
367  </DL>
368 
369  For example, put something like
370  @code{.py}
371  import lsstDebug
372  def DebugInfo(name):
373  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
374  if name == "lsst.ip.isrFunctions.isrTask":
375  di.display = {'postISRCCD':2}
376  return di
377  lsstDebug.Info = DebugInfo
378  @endcode
379  into your debug.py file and run the commandline task with the @c --debug flag.
380 
381  <HR>
382  """
383  ConfigClass = IsrTaskConfig
384  _DefaultName = "isr"
385 
386  def __init__(self, *args, **kwargs):
387  '''!Constructor for IsrTask
388  @param[in] *args a list of positional arguments passed on to the Task constructor
389  @param[in] **kwargs a dictionary of keyword arguments passed on to the Task constructor
390  Call the lsst.pipe.base.task.Task.__init__ method
391  Then setup the assembly and fringe correction subtasks
392  '''
393  pipeBase.Task.__init__(self, *args, **kwargs)
394  self.makeSubtask("assembleCcd")
395  self.makeSubtask("fringe")
396  self.makeSubtask("crosstalk")
397 
398  def readIsrData(self, dataRef, rawExposure):
399  """!Retrieve necessary frames for instrument signature removal
400  @param[in] dataRef a daf.persistence.butlerSubset.ButlerDataRef
401  of the detector data to be processed
402  @param[in] rawExposure a reference raw exposure that will later be
403  corrected with the retrieved calibration data;
404  should not be modified in this method.
405  @return a pipeBase.Struct with fields containing kwargs expected by run()
406  - bias: exposure of bias frame
407  - dark: exposure of dark frame
408  - flat: exposure of flat field
409  - defects: list of detects
410  - fringeStruct: a pipeBase.Struct with field fringes containing
411  exposure of fringe frame or list of fringe exposure
412  """
413  ccd = rawExposure.getDetector()
414 
415  biasExposure = self.getIsrExposure(dataRef, self.config.biasDataProductName) \
416  if self.config.doBias else None
417  # immediate=True required for functors and linearizers are functors; see ticket DM-6515
418  linearizer = dataRef.get("linearizer", immediate=True) if self.doLinearize(ccd) else None
419  darkExposure = self.getIsrExposure(dataRef, self.config.darkDataProductName) \
420  if self.config.doDark else None
421  flatExposure = self.getIsrExposure(dataRef, self.config.flatDataProductName) \
422  if self.config.doFlat else None
423  brighterFatterKernel = dataRef.get("brighterFatterKernel") if self.config.doBrighterFatter else None
424  defectList = dataRef.get("defects") if self.config.doDefect else None
425 
426  if self.config.doCrosstalk:
427  crosstalkSources = self.crosstalk.prepCrosstalk(dataRef)
428  else:
429  crosstalkSources = None
430 
431  if self.config.doFringe and self.fringe.checkFilter(rawExposure):
432  fringeStruct = self.fringe.readFringes(dataRef, assembler=self.assembleCcd
433  if self.config.doAssembleIsrExposures else None)
434  else:
435  fringeStruct = pipeBase.Struct(fringes=None)
436 
437  if self.config.doAttachTransmissionCurve:
438  opticsTransmission = (dataRef.get("transmission_optics")
439  if self.config.doUseOpticsTransmission else None)
440  filterTransmission = (dataRef.get("transmission_filter")
441  if self.config.doUseFilterTransmission else None)
442  sensorTransmission = (dataRef.get("transmission_sensor")
443  if self.config.doUseSensorTransmission else None)
444  atmosphereTransmission = (dataRef.get("transmission_atmosphere")
445  if self.config.doUseAtmosphereTransmission else None)
446  else:
447  opticsTransmission = None
448  filterTransmission = None
449  sensorTransmission = None
450  atmosphereTransmission = None
451 
452  # Struct should include only kwargs to run()
453  return pipeBase.Struct(bias=biasExposure,
454  linearizer=linearizer,
455  dark=darkExposure,
456  flat=flatExposure,
457  defects=defectList,
458  fringes=fringeStruct,
459  bfKernel=brighterFatterKernel,
460  opticsTransmission=opticsTransmission,
461  filterTransmission=filterTransmission,
462  sensorTransmission=sensorTransmission,
463  atmosphereTransmission=atmosphereTransmission,
464  crosstalkSources=crosstalkSources,
465  )
466 
467  @pipeBase.timeMethod
468  def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None,
469  fringes=None, bfKernel=None, camera=None,
470  opticsTransmission=None, filterTransmission=None,
471  sensorTransmission=None, atmosphereTransmission=None,
472  crosstalkSources=None):
473  """!Perform instrument signature removal on an exposure
474 
475  Steps include:
476  - Detect saturation, apply overscan correction, bias, dark and flat
477  - Perform CCD assembly
478  - Interpolate over defects, saturated pixels and all NaNs
479 
480  @param[in] ccdExposure lsst.afw.image.exposure of detector data
481  @param[in] bias exposure of bias frame
482  @param[in] linearizer linearizing functor; a subclass of lsst.ip.isrFunctions.LinearizeBase
483  @param[in] dark exposure of dark frame
484  @param[in] flat exposure of flatfield
485  @param[in] defects list of detects
486  @param[in] fringes a pipeBase.Struct with field fringes containing
487  exposure of fringe frame or list of fringe exposure
488  @param[in] bfKernel kernel for brighter-fatter correction
489  @param[in] camera camera geometry, an lsst.afw.cameraGeom.Camera;
490  used by addDistortionModel
491  @param[in] opticsTransmission a TransmissionCurve for the optics
492  @param[in] filterTransmission a TransmissionCurve for the filter
493  @param[in] sensorTransmission a TransmissionCurve for the sensor
494  @param[in] atmosphereTransmission a TransmissionCurve for the atmosphere
495  @param[in] crosstalkSources a defaultdict used for DECam inter-CCD crosstalk
496 
497  @return a pipeBase.Struct with field:
498  - exposure
499  """
500  # parseAndRun expects to be able to call run() with a dataRef; see DM-6640
501  if isinstance(ccdExposure, ButlerDataRef):
502  return self.runDataRef(ccdExposure)
503 
504  ccd = ccdExposure.getDetector()
505 
506  # Validate Input
507  if self.config.doBias and bias is None:
508  raise RuntimeError("Must supply a bias exposure if config.doBias True")
509  if self.doLinearize(ccd) and linearizer is None:
510  raise RuntimeError("Must supply a linearizer if config.doBias True")
511  if self.config.doDark and dark is None:
512  raise RuntimeError("Must supply a dark exposure if config.doDark True")
513  if self.config.doFlat and flat is None:
514  raise RuntimeError("Must supply a flat exposure if config.doFlat True")
515  if self.config.doBrighterFatter and bfKernel is None:
516  raise RuntimeError("Must supply a kernel if config.doBrighterFatter True")
517  if fringes is None:
518  fringes = pipeBase.Struct(fringes=None)
519  if self.config.doFringe and not isinstance(fringes, pipeBase.Struct):
520  raise RuntimeError("Must supply fringe exposure as a pipeBase.Struct")
521  if self.config.doDefect and defects is None:
522  raise RuntimeError("Must supply defects if config.doDefect True")
523  if self.config.doAddDistortionModel and camera is None:
524  raise RuntimeError("Must supply camera if config.doAddDistortionModel True")
525 
526  ccdExposure = self.convertIntToFloat(ccdExposure)
527 
528  if not ccd:
529  assert not self.config.doAssembleCcd, "You need a Detector to run assembleCcd"
530  ccd = [FakeAmp(ccdExposure, self.config)]
531 
532  overscans = []
533  for amp in ccd:
534  # if ccdExposure is one amp, check for coverage to prevent performing ops multiple times
535  if ccdExposure.getBBox().contains(amp.getBBox()):
536  self.saturationDetection(ccdExposure, amp)
537  self.suspectDetection(ccdExposure, amp)
538  overscanResults = self.overscanCorrection(ccdExposure, amp)
539  overscans.append(overscanResults.overscanImage if overscanResults is not None else None)
540  else:
541  overscans.append(None)
542 
543  if self.config.doCrosstalk:
544  self.crosstalk.run(ccdExposure, crosstalkSources)
545 
546  if self.config.doAssembleCcd:
547  ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
548  if self.config.expectWcs and not ccdExposure.getWcs():
549  self.log.warn("No WCS found in input exposure")
550 
551  if self.config.doBias:
552  self.biasCorrection(ccdExposure, bias)
553 
554  if self.doLinearize(ccd):
555  linearizer(image=ccdExposure.getMaskedImage().getImage(), detector=ccd, log=self.log)
556 
557  assert len(ccd) == len(overscans)
558  for amp, overscanImage in zip(ccd, overscans):
559  # if ccdExposure is one amp, check for coverage to prevent performing ops multiple times
560  if ccdExposure.getBBox().contains(amp.getBBox()):
561  ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
562  self.updateVariance(ampExposure, amp, overscanImage)
563 
564  interpolationDone = False
565 
566  if self.config.doBrighterFatter:
567 
568  # We need to apply flats and darks before we can interpolate, and we
569  # need to interpolate before we do B-F, but we do B-F without the
570  # flats and darks applied so we can work in units of electrons or holes.
571  # This context manager applies and then removes the darks and flats.
572  with self.flatContext(ccdExposure, flat, dark):
573  if self.config.doDefect:
574  self.maskAndInterpDefect(ccdExposure, defects)
575  if self.config.doSaturationInterpolation:
576  self.saturationInterpolation(ccdExposure)
577  self.maskAndInterpNan(ccdExposure)
578  interpolationDone = True
579 
580  self.brighterFatterCorrection(ccdExposure, bfKernel,
581  self.config.brighterFatterMaxIter,
582  self.config.brighterFatterThreshold,
583  self.config.brighterFatterApplyGain,
584  )
585 
586  if self.config.doDark:
587  self.darkCorrection(ccdExposure, dark)
588 
589  if self.config.doFringe and not self.config.fringeAfterFlat:
590  self.fringe.run(ccdExposure, **fringes.getDict())
591 
592  if self.config.doFlat:
593  self.flatCorrection(ccdExposure, flat)
594 
595  if not interpolationDone:
596  if self.config.doDefect:
597  self.maskAndInterpDefect(ccdExposure, defects)
598  if self.config.doSaturationInterpolation:
599  self.saturationInterpolation(ccdExposure)
600  if not interpolationDone or self.config.doNanInterpAfterFlat:
601  self.maskAndInterpNan(ccdExposure)
602 
603  if self.config.doFringe and self.config.fringeAfterFlat:
604  self.fringe.run(ccdExposure, **fringes.getDict())
605 
606  exposureTime = ccdExposure.getInfo().getVisitInfo().getExposureTime()
607  ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1*exposureTime)
608 
609  if self.config.doAddDistortionModel:
610  self.addDistortionModel(exposure=ccdExposure, camera=camera)
611 
612  if self.config.doAttachTransmissionCurve:
613  self.attachTransmissionCurve(ccdExposure, opticsTransmission=opticsTransmission,
614  filterTransmission=filterTransmission,
615  sensorTransmission=sensorTransmission,
616  atmosphereTransmission=atmosphereTransmission)
617 
618  frame = getDebugFrame(self._display, "postISRCCD")
619  if frame:
620  getDisplay(frame).mtv(ccdExposure)
621 
622  return pipeBase.Struct(
623  exposure=ccdExposure,
624  )
625 
626  @pipeBase.timeMethod
627  def runDataRef(self, sensorRef):
628  """Perform instrument signature removal on a ButlerDataRef of a Sensor
629 
630  - Read in necessary detrending/isr/calibration data
631  - Process raw exposure in run()
632  - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
633 
634  Parameters
635  ----------
636  sensorRef : `daf.persistence.butlerSubset.ButlerDataRef`
637  DataRef of the detector data to be processed
638 
639  Returns
640  -------
641  result : `pipeBase.Struct`
642  Struct contains field "exposure," which is the exposure after application of ISR
643  """
644  self.log.info("Performing ISR on sensor %s" % (sensorRef.dataId))
645  ccdExposure = sensorRef.get('raw')
646  camera = sensorRef.get("camera")
647  if camera is None and self.config.doAddDistortionModel:
648  raise RuntimeError("config.doAddDistortionModel is True "
649  "but could not get a camera from the butler")
650  isrData = self.readIsrData(sensorRef, ccdExposure)
651 
652  result = self.run(ccdExposure, camera=camera, **isrData.getDict())
653 
654  if self.config.doWrite:
655  sensorRef.put(result.exposure, "postISRCCD")
656 
657  return result
658 
659  def convertIntToFloat(self, exposure):
660  """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
661  """
662  if isinstance(exposure, afwImage.ExposureF):
663  # Nothing to be done
664  return exposure
665  if not hasattr(exposure, "convertF"):
666  raise RuntimeError("Unable to convert exposure (%s) to float" % type(exposure))
667 
668  newexposure = exposure.convertF()
669  maskedImage = newexposure.getMaskedImage()
670  varArray = maskedImage.getVariance().getArray()
671  varArray[:, :] = 1
672  maskArray = maskedImage.getMask().getArray()
673  maskArray[:, :] = 0
674  return newexposure
675 
676  def biasCorrection(self, exposure, biasExposure):
677  """!Apply bias correction in place
678 
679  @param[in,out] exposure exposure to process
680  @param[in] biasExposure bias exposure of same size as exposure
681  """
682  isrFunctions.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
683 
684  def darkCorrection(self, exposure, darkExposure, invert=False):
685  """!Apply dark correction in place
686 
687  @param[in,out] exposure exposure to process
688  @param[in] darkExposure dark exposure of same size as exposure
689  @param[in] invert if True, remove the dark from an already-corrected image
690  """
691  expScale = exposure.getInfo().getVisitInfo().getDarkTime()
692  if math.isnan(expScale):
693  raise RuntimeError("Exposure darktime is NAN")
694  darkScale = darkExposure.getInfo().getVisitInfo().getDarkTime()
695  if math.isnan(darkScale):
696  raise RuntimeError("Dark calib darktime is NAN")
697  isrFunctions.darkCorrection(
698  maskedImage=exposure.getMaskedImage(),
699  darkMaskedImage=darkExposure.getMaskedImage(),
700  expScale=expScale,
701  darkScale=darkScale,
702  invert=invert
703  )
704 
705  def doLinearize(self, detector):
706  """!Is linearization wanted for this detector?
707 
708  Checks config.doLinearize and the linearity type of the first amplifier.
709 
710  @param[in] detector detector information (an lsst.afw.cameraGeom.Detector)
711  """
712  return self.config.doLinearize and \
713  detector.getAmpInfoCatalog()[0].getLinearityType() != NullLinearityType
714 
715  def updateVariance(self, ampExposure, amp, overscanImage=None):
716  """Set the variance plane using the amplifier gain and read noise
717 
718  The read noise is calculated from the ``overscanImage`` if the
719  ``doEmpiricalReadNoise`` option is set in the configuration; otherwise
720  the value from the amplifier data is used.
721 
722  Parameters
723  ----------
724  ampExposure : `lsst.afw.image.Exposure`
725  Exposure to process.
726  amp : `lsst.afw.table.AmpInfoRecord` or `FakeAmp`
727  Amplifier detector data.
728  overscanImage : `lsst.afw.image.MaskedImage`, optional.
729  Image of overscan, required only for empirical read noise.
730  """
731  maskPlanes = [self.config.saturatedMaskName, self.config.suspectMaskName]
732  gain = amp.getGain()
733  if not math.isnan(gain):
734  if gain <= 0:
735  patchedGain = 1.0
736  self.log.warn("Gain for amp %s == %g <= 0; setting to %f" %
737  (amp.getName(), gain, patchedGain))
738  gain = patchedGain
739 
740  if self.config.doEmpiricalReadNoise and overscanImage is not None:
741  stats = afwMath.StatisticsControl()
742  stats.setAndMask(overscanImage.mask.getPlaneBitMask(maskPlanes))
743  readNoise = afwMath.makeStatistics(overscanImage, afwMath.STDEVCLIP, stats).getValue()
744  self.log.info("Calculated empirical read noise for amp %s: %f", amp.getName(), readNoise)
745  else:
746  readNoise = amp.getReadNoise()
747 
748  isrFunctions.updateVariance(
749  maskedImage=ampExposure.getMaskedImage(),
750  gain=gain,
751  readNoise=readNoise,
752  )
753 
754  def flatCorrection(self, exposure, flatExposure, invert=False):
755  """!Apply flat correction in place
756 
757  @param[in,out] exposure exposure to process
758  @param[in] flatExposure flatfield exposure same size as exposure
759  @param[in] invert if True, unflatten an already-flattened image instead.
760  """
761  isrFunctions.flatCorrection(
762  maskedImage=exposure.getMaskedImage(),
763  flatMaskedImage=flatExposure.getMaskedImage(),
764  scalingType=self.config.flatScalingType,
765  userScale=self.config.flatUserScale,
766  invert=invert
767  )
768 
769  def getIsrExposure(self, dataRef, datasetType, immediate=True):
770  """!Retrieve a calibration dataset for removing instrument signature
771 
772  @param[in] dataRef data reference for exposure
773  @param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat')
774  @param[in] immediate if True, disable butler proxies to enable error
775  handling within this routine
776  @return exposure
777  """
778  try:
779  exp = dataRef.get(datasetType, immediate=immediate)
780  except Exception as exc1:
781  if not self.config.fallbackFilterName:
782  raise RuntimeError("Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, exc1))
783  try:
784  exp = dataRef.get(datasetType, filter=self.config.fallbackFilterName, immediate=immediate)
785  except Exception as exc2:
786  raise RuntimeError("Unable to retrieve %s for %s, even with fallback filter %s: %s AND %s" %
787  (datasetType, dataRef.dataId, self.config.fallbackFilterName, exc1, exc2))
788  self.log.warn("Using fallback calibration from filter %s" % self.config.fallbackFilterName)
789 
790  if self.config.doAssembleIsrExposures:
791  exp = self.assembleCcd.assembleCcd(exp)
792  return exp
793 
794  def saturationDetection(self, exposure, amp):
795  """!Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place
796 
797  @param[in,out] exposure exposure to process; only the amp DataSec is processed
798  @param[in] amp amplifier device data
799  """
800  if not math.isnan(amp.getSaturation()):
801  maskedImage = exposure.getMaskedImage()
802  dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
803  isrFunctions.makeThresholdMask(
804  maskedImage=dataView,
805  threshold=amp.getSaturation(),
806  growFootprints=0,
807  maskName=self.config.saturatedMaskName,
808  )
809 
810  def saturationInterpolation(self, ccdExposure):
811  """!Interpolate over saturated pixels, in place
812 
813  @param[in,out] ccdExposure exposure to process
814 
815  @warning:
816  - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
817  - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
818  """
819  isrFunctions.interpolateFromMask(
820  maskedImage=ccdExposure.getMaskedImage(),
821  fwhm=self.config.fwhm,
822  growFootprints=self.config.growSaturationFootprintSize,
823  maskName=self.config.saturatedMaskName,
824  )
825 
826  def suspectDetection(self, exposure, amp):
827  """!Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place
828 
829  Suspect pixels are pixels whose value is greater than amp.getSuspectLevel().
830  This is intended to indicate pixels that may be affected by unknown systematics;
831  for example if non-linearity corrections above a certain level are unstable
832  then that would be a useful value for suspectLevel. A value of `nan` indicates
833  that no such level exists and no pixels are to be masked as suspicious.
834 
835  @param[in,out] exposure exposure to process; only the amp DataSec is processed
836  @param[in] amp amplifier device data
837  """
838  suspectLevel = amp.getSuspectLevel()
839  if math.isnan(suspectLevel):
840  return
841 
842  maskedImage = exposure.getMaskedImage()
843  dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
844  isrFunctions.makeThresholdMask(
845  maskedImage=dataView,
846  threshold=suspectLevel,
847  growFootprints=0,
848  maskName=self.config.suspectMaskName,
849  )
850 
851  def maskAndInterpDefect(self, ccdExposure, defectBaseList):
852  """!Mask defects using mask plane "BAD" and interpolate over them, in place
853 
854  @param[in,out] ccdExposure exposure to process
855  @param[in] defectBaseList a list of defects to mask and interpolate
856 
857  @warning: call this after CCD assembly, since defects may cross amplifier boundaries
858  """
859  maskedImage = ccdExposure.getMaskedImage()
860  defectList = []
861  for d in defectBaseList:
862  bbox = d.getBBox()
863  nd = measAlg.Defect(bbox)
864  defectList.append(nd)
865  isrFunctions.maskPixelsFromDefectList(maskedImage, defectList, maskName='BAD')
866  isrFunctions.interpolateDefectList(
867  maskedImage=maskedImage,
868  defectList=defectList,
869  fwhm=self.config.fwhm,
870  )
871 
872  def maskAndInterpNan(self, exposure):
873  """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
874 
875  We mask and interpolate over all NaNs, including those
876  that are masked with other bits (because those may or may
877  not be interpolated over later, and we want to remove all
878  NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
879  is used to preserve the historical name.
880 
881  @param[in,out] exposure exposure to process
882  """
883  maskedImage = exposure.getMaskedImage()
884 
885  # Find and mask NaNs
886  maskedImage.getMask().addMaskPlane("UNMASKEDNAN")
887  maskVal = maskedImage.getMask().getPlaneBitMask("UNMASKEDNAN")
888  numNans = maskNans(maskedImage, maskVal)
889  self.metadata.set("NUMNANS", numNans)
890 
891  # Interpolate over these previously-unmasked NaNs
892  if numNans > 0:
893  self.log.warn("There were %i unmasked NaNs", numNans)
894  nanDefectList = isrFunctions.getDefectListFromMask(
895  maskedImage=maskedImage,
896  maskName='UNMASKEDNAN',
897  )
898  isrFunctions.interpolateDefectList(
899  maskedImage=exposure.getMaskedImage(),
900  defectList=nanDefectList,
901  fwhm=self.config.fwhm,
902  )
903 
904  def overscanCorrection(self, exposure, amp):
905  """Apply overscan correction, in-place
906 
907  Parameters
908  ----------
909  exposure : `lsst.afw.image.Exposure`
910  Exposure to process; must include both data and bias regions.
911  amp : `lsst.afw.table.AmpInfoRecord`
912  Amplifier device data.
913 
914  Results
915  -------
916  result : `lsst.pipe.base.Struct` or `NoneType`
917  `None` if there is no overscan; otherwise, this is a
918  result struct with components:
919 
920  - ``imageFit``: Value(s) removed from image (scalar or
921  `lsst.afw.image.Image`).
922  - ``overscanFit``: Value(s) removed from overscan (scalar or
923  `lsst.afw.image.Image`).
924  - ``overscanImage``: Image of the overscan, post-subtraction
925  (`lsst.afw.image.Image`).
926  """
927  if not amp.getHasRawInfo():
928  raise RuntimeError("This method must be executed on an amp with raw information.")
929 
930  if amp.getRawHorizontalOverscanBBox().isEmpty():
931  self.log.info("No Overscan region. Not performing Overscan Correction.")
932  return None
933 
934  maskedImage = exposure.getMaskedImage()
935  dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
936  overscanImage = maskedImage.Factory(maskedImage, amp.getRawHorizontalOverscanBBox())
937 
938  results = isrFunctions.overscanCorrection(
939  ampMaskedImage=dataView,
940  overscanImage=overscanImage,
941  fitType=self.config.overscanFitType,
942  order=self.config.overscanOrder,
943  collapseRej=self.config.overscanRej,
944  )
945  results.overscanImage = overscanImage
946  return results
947 
948  def addDistortionModel(self, exposure, camera):
949  """!Update the WCS in exposure with a distortion model based on camera geometry
950 
951  Add a model for optical distortion based on geometry found in `camera`
952  and the `exposure`'s detector. The raw input exposure is assumed
953  have a TAN WCS that has no compensation for optical distortion.
954  Two other possibilities are:
955  - The raw input exposure already has a model for optical distortion,
956  as is the case for raw DECam data.
957  In that case you should set config.doAddDistortionModel False.
958  - The raw input exposure has a model for distortion, but it has known
959  deficiencies severe enough to be worth fixing (e.g. because they
960  cause problems for fitting a better WCS). In that case you should
961  override this method with a version suitable for your raw data.
962 
963  @param[in,out] exposure exposure to process; must include a Detector and a WCS;
964  the WCS of the exposure is modified in place
965  @param[in] camera camera geometry; an lsst.afw.cameraGeom.Camera
966  """
967  self.log.info("Adding a distortion model to the WCS")
968  wcs = exposure.getWcs()
969  if wcs is None:
970  raise RuntimeError("exposure has no WCS")
971  if camera is None:
972  raise RuntimeError("camera is None")
973  detector = exposure.getDetector()
974  if detector is None:
975  raise RuntimeError("exposure has no Detector")
976  pixelToFocalPlane = detector.getTransform(PIXELS, FOCAL_PLANE)
977  focalPlaneToFieldAngle = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
978  distortedWcs = makeDistortedTanWcs(wcs, pixelToFocalPlane, focalPlaneToFieldAngle)
979  exposure.setWcs(distortedWcs)
980 
981  def setValidPolygonIntersect(self, ccdExposure, fpPolygon):
982  """!Set the valid polygon as the intersection of fpPolygon and the ccd corners
983 
984  @param[in,out] ccdExposure exposure to process
985  @param[in] fpPolygon Polygon in focal plane coordinates
986  """
987  # Get ccd corners in focal plane coordinates
988  ccd = ccdExposure.getDetector()
989  fpCorners = ccd.getCorners(FOCAL_PLANE)
990  ccdPolygon = Polygon(fpCorners)
991 
992  # Get intersection of ccd corners with fpPolygon
993  intersect = ccdPolygon.intersectionSingle(fpPolygon)
994 
995  # Transform back to pixel positions and build new polygon
996  ccdPoints = ccd.transform(intersect, FOCAL_PLANE, PIXELS)
997  validPolygon = Polygon(ccdPoints)
998  ccdExposure.getInfo().setValidPolygon(validPolygon)
999 
1000  def brighterFatterCorrection(self, exposure, kernel, maxIter, threshold, applyGain):
1001  """Apply brighter fatter correction in place for the image
1002 
1003  This correction takes a kernel that has been derived from flat field images to
1004  redistribute the charge. The gradient of the kernel is the deflection
1005  field due to the accumulated charge.
1006 
1007  Given the original image I(x) and the kernel K(x) we can compute the corrected image Ic(x)
1008  using the following equation:
1009 
1010  Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))
1011 
1012  To evaluate the derivative term we expand it as follows:
1013 
1014  0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y))) + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )
1015 
1016  Because we use the measured counts instead of the incident counts we apply the correction
1017  iteratively to reconstruct the original counts and the correction. We stop iterating when the
1018  summed difference between the current corrected image and the one from the previous iteration
1019  is below the threshold. We do not require convergence because the number of iterations is
1020  too large a computational cost. How we define the threshold still needs to be evaluated, the
1021  current default was shown to work reasonably well on a small set of images. For more information
1022  on the method see DocuShare Document-19407.
1023 
1024  The edges as defined by the kernel are not corrected because they have spurious values
1025  due to the convolution.
1026  """
1027  self.log.info("Applying brighter fatter correction")
1028 
1029  image = exposure.getMaskedImage().getImage()
1030 
1031  # The image needs to be units of electrons/holes
1032  with self.gainContext(exposure, image, applyGain):
1033 
1034  kLx = numpy.shape(kernel)[0]
1035  kLy = numpy.shape(kernel)[1]
1036  kernelImage = afwImage.ImageD(kLx, kLy)
1037  kernelImage.getArray()[:, :] = kernel
1038  tempImage = image.clone()
1039 
1040  nanIndex = numpy.isnan(tempImage.getArray())
1041  tempImage.getArray()[nanIndex] = 0.
1042 
1043  outImage = afwImage.ImageF(image.getDimensions())
1044  corr = numpy.zeros_like(image.getArray())
1045  prev_image = numpy.zeros_like(image.getArray())
1046  convCntrl = afwMath.ConvolutionControl(False, True, 1)
1047  fixedKernel = afwMath.FixedKernel(kernelImage)
1048 
1049  # Define boundary by convolution region. The region that the correction will be
1050  # calculated for is one fewer in each dimension because of the second derivative terms.
1051  # NOTE: these need to use integer math, as we're using start:end as numpy index ranges.
1052  startX = kLx//2
1053  endX = -kLx//2
1054  startY = kLy//2
1055  endY = -kLy//2
1056 
1057  for iteration in range(maxIter):
1058 
1059  afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
1060  tmpArray = tempImage.getArray()
1061  outArray = outImage.getArray()
1062 
1063  with numpy.errstate(invalid="ignore", over="ignore"):
1064  # First derivative term
1065  gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
1066  gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
1067  first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])[1:-1, 1:-1]
1068 
1069  # Second derivative term
1070  diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY, startX + 1:endX - 1]
1071  diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1, startX:endX]
1072  second = tmpArray[startY + 1:endY - 1, startX + 1:endX - 1]*(diffOut20 + diffOut21)
1073 
1074  corr[startY + 1:endY - 1, startX + 1:endX - 1] = 0.5*(first + second)
1075 
1076  tmpArray[:, :] = image.getArray()[:, :]
1077  tmpArray[nanIndex] = 0.
1078  tmpArray[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
1079 
1080  if iteration > 0:
1081  diff = numpy.sum(numpy.abs(prev_image - tmpArray))
1082 
1083  if diff < threshold:
1084  break
1085  prev_image[:, :] = tmpArray[:, :]
1086 
1087  if iteration == maxIter - 1:
1088  self.log.warn("Brighter fatter correction did not converge, final difference %f" % diff)
1089 
1090  self.log.info("Finished brighter fatter in %d iterations" % (iteration + 1))
1091  image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
1092  corr[startY + 1:endY - 1, startX + 1:endX - 1]
1093 
1094  def attachTransmissionCurve(self, exposure, opticsTransmission=None, filterTransmission=None,
1095  sensorTransmission=None, atmosphereTransmission=None):
1096  """Attach a TransmissionCurve to an Exposure, given separate curves for
1097  different components.
1098 
1099  Parameters
1100  ----------
1101  exposure : `lsst.afw.image.Exposure`
1102  Exposure object to modify by attaching the product of all given
1103  ``TransmissionCurves`` in post-assembly trimmed detector
1104  coordinates. Must have a valid ``Detector`` attached that matches
1105  the detector associated with sensorTransmission.
1106  opticsTransmission : `lsst.afw.image.TransmissionCurve`
1107  A ``TransmissionCurve`` that represents the throughput of the
1108  optics, to be evaluated in focal-plane coordinates.
1109  filterTransmission : `lsst.afw.image.TransmissionCurve`
1110  A ``TransmissionCurve`` that represents the throughput of the
1111  filter itself, to be evaluated in focal-plane coordinates.
1112  sensorTransmission : `lsst.afw.image.TransmissionCurve`
1113  A ``TransmissionCurve`` that represents the throughput of the
1114  sensor itself, to be evaluated in post-assembly trimmed detector
1115  coordinates.
1116  atmosphereTransmission : `lsst.afw.image.TransmissionCurve`
1117  A ``TransmissionCurve`` that represents the throughput of the
1118  atmosphere, assumed to be spatially constant.
1119 
1120  All ``TransmissionCurve`` arguments are optional; if none are provided,
1121  the attached ``TransmissionCurve`` will have unit transmission
1122  everywhere.
1123 
1124  Returns
1125  -------
1126  combined : ``lsst.afw.image.TransmissionCurve``
1127  The TransmissionCurve attached to the exposure.
1128  """
1129  return isrFunctions.attachTransmissionCurve(exposure, opticsTransmission=opticsTransmission,
1130  filterTransmission=filterTransmission,
1131  sensorTransmission=sensorTransmission,
1132  atmosphereTransmission=atmosphereTransmission)
1133 
1134  @contextmanager
1135  def gainContext(self, exp, image, apply):
1136  """Context manager that applies and removes gain
1137  """
1138  if apply:
1139  ccd = exp.getDetector()
1140  for amp in ccd:
1141  sim = image.Factory(image, amp.getBBox())
1142  sim *= amp.getGain()
1143 
1144  try:
1145  yield exp
1146  finally:
1147  if apply:
1148  ccd = exp.getDetector()
1149  for amp in ccd:
1150  sim = image.Factory(image, amp.getBBox())
1151  sim /= amp.getGain()
1152 
1153  @contextmanager
1154  def flatContext(self, exp, flat, dark=None):
1155  """Context manager that applies and removes flats and darks,
1156  if the task is configured to apply them.
1157  """
1158  if self.config.doDark and dark is not None:
1159  self.darkCorrection(exp, dark)
1160  if self.config.doFlat:
1161  self.flatCorrection(exp, flat)
1162  try:
1163  yield exp
1164  finally:
1165  if self.config.doFlat:
1166  self.flatCorrection(exp, flat, invert=True)
1167  if self.config.doDark and dark is not None:
1168  self.darkCorrection(exp, dark, invert=True)
1169 
1170 
1171 class FakeAmp(object):
1172  """A Detector-like object that supports returning gain and saturation level"""
1173 
1174  def __init__(self, exposure, config):
1175  self._bbox = exposure.getBBox(afwImage.LOCAL)
1176  self._RawHorizontalOverscanBBox = afwGeom.Box2I()
1177  self._gain = config.gain
1178  self._readNoise = config.readNoise
1179  self._saturation = config.saturation
1180 
1181  def getBBox(self):
1182  return self._bbox
1183 
1184  def getRawBBox(self):
1185  return self._bbox
1186 
1187  def getHasRawInfo(self):
1188  return True # but see getRawHorizontalOverscanBBox()
1189 
1191  return self._RawHorizontalOverscanBBox
1192 
1193  def getGain(self):
1194  return self._gain
1195 
1196  def getReadNoise(self):
1197  return self._readNoise
1198 
1199  def getSaturation(self):
1200  return self._saturation
1201 
1202  def getSuspectLevel(self):
1203  return float("NaN")
def brighterFatterCorrection(self, exposure, kernel, maxIter, threshold, applyGain)
Definition: isrTask.py:1000
def runDataRef(self, sensorRef)
Definition: isrTask.py:627
def run(self, ccdExposure, bias=None, linearizer=None, dark=None, flat=None, defects=None, fringes=None, bfKernel=None, camera=None, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None, crosstalkSources=None)
Perform instrument signature removal on an exposure.
Definition: isrTask.py:472
def gainContext(self, exp, image, apply)
Definition: isrTask.py:1135
def __init__(self, args, kwargs)
Constructor for IsrTask.
Definition: isrTask.py:386
def readIsrData(self, dataRef, rawExposure)
Retrieve necessary frames for instrument signature removal.
Definition: isrTask.py:398
def attachTransmissionCurve(self, exposure, opticsTransmission=None, filterTransmission=None, sensorTransmission=None, atmosphereTransmission=None)
Definition: isrTask.py:1095
def maskAndInterpNan(self, exposure)
Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place.
Definition: isrTask.py:872
def saturationInterpolation(self, ccdExposure)
Interpolate over saturated pixels, in place.
Definition: isrTask.py:810
Apply common instrument signature correction algorithms to a raw frame.
Definition: isrTask.py:312
def getRawHorizontalOverscanBBox(self)
Definition: isrTask.py:1190
def convertIntToFloat(self, exposure)
Definition: isrTask.py:659
def flatCorrection(self, exposure, flatExposure, invert=False)
Apply flat correction in place.
Definition: isrTask.py:754
def getIsrExposure(self, dataRef, datasetType, immediate=True)
Retrieve a calibration dataset for removing instrument signature.
Definition: isrTask.py:769
def darkCorrection(self, exposure, darkExposure, invert=False)
Apply dark correction in place.
Definition: isrTask.py:684
def doLinearize(self, detector)
Is linearization wanted for this detector?
Definition: isrTask.py:705
def addDistortionModel(self, exposure, camera)
Update the WCS in exposure with a distortion model based on camera geometry.
Definition: isrTask.py:948
def setValidPolygonIntersect(self, ccdExposure, fpPolygon)
Set the valid polygon as the intersection of fpPolygon and the ccd corners.
Definition: isrTask.py:981
def biasCorrection(self, exposure, biasExposure)
Apply bias correction in place.
Definition: isrTask.py:676
def flatContext(self, exp, flat, dark=None)
Definition: isrTask.py:1154
def overscanCorrection(self, exposure, amp)
Definition: isrTask.py:904
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Mask NANs in an image.
Definition: Isr.cc:34
def updateVariance(self, ampExposure, amp, overscanImage=None)
Definition: isrTask.py:715
def suspectDetection(self, exposure, amp)
Detect suspect pixels and mask them using mask plane config.suspectMaskName, in place.
Definition: isrTask.py:826
def maskAndInterpDefect(self, ccdExposure, defectBaseList)
Mask defects using mask plane "BAD" and interpolate over them, in place.
Definition: isrTask.py:851
def saturationDetection(self, exposure, amp)
Detect saturated pixels and mask them using mask plane config.saturatedMaskName, in place...
Definition: isrTask.py:794
def __init__(self, exposure, config)
Definition: isrTask.py:1174