29 from .isrLib
import maskNans
30 from .assembleCcdTask
import AssembleCcdTask
31 from .fringe
import FringeTask
34 doBias = pexConfig.Field(
36 doc =
"Apply bias frame correction?",
39 doDark = pexConfig.Field(
41 doc =
"Apply dark frame correction?",
44 doFlat = pexConfig.Field(
46 doc =
"Apply flat field correction?",
49 doFringe = pexConfig.Field(
51 doc =
"Apply fringe correction?",
54 doWrite = pexConfig.Field(
56 doc =
"Persist postISRCCD?",
59 assembleCcd = pexConfig.ConfigurableField(
60 target = AssembleCcdTask,
61 doc =
"CCD assembly task",
63 gain = pexConfig.Field(
65 doc =
"The gain to use if no Detector is present in the Exposure (ignored if NaN)",
66 default = float(
"NaN"),
68 readNoise = pexConfig.Field(
70 doc =
"The read noise to use if no Detector is present in the Exposure",
73 saturation = pexConfig.Field(
75 doc =
"The saturation level to use if no Detector is present in the Exposure (ignored if NaN)",
76 default = float(
"NaN"),
78 fringeAfterFlat = pexConfig.Field(
80 doc =
"Do fringe subtraction after flat-fielding?",
83 fringe = pexConfig.ConfigurableField(
85 doc =
"Fringe subtraction task",
87 fwhm = pexConfig.Field(
89 doc =
"FWHM of PSF (arcsec)",
92 saturatedMaskName = pexConfig.Field(
94 doc =
"Name of mask plane to use in saturation detection and interpolation",
97 flatScalingType = pexConfig.ChoiceField(
99 doc =
"The method for scaling the flat on the fly.",
102 "USER":
"Scale by flatUserScale",
103 "MEAN":
"Scale by the inverse of the mean",
104 "MEDIAN":
"Scale by the inverse of the median",
107 flatUserScale = pexConfig.Field(
109 doc =
"If flatScalingType is 'USER' then scale flat by this amount; ignored otherwise",
112 overscanFitType = pexConfig.ChoiceField(
114 doc =
"The method for fitting the overscan bias level.",
117 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
118 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
119 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
120 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
121 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
122 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
123 "MEAN":
"Correct using the mean of the overscan region",
124 "MEDIAN":
"Correct using the median of the overscan region",
127 overscanOrder = pexConfig.Field(
129 doc = (
"Order of polynomial or to fit if overscan fit type is a polynomial, " +
130 "or number of spline knots if overscan fit type is a spline."),
133 overscanRej = pexConfig.Field(
135 doc =
"Rejection threshold (sigma) for collapsing overscan before fit",
138 growSaturationFootprintSize = pexConfig.Field(
140 doc =
"Number of pixels by which to grow the saturation footprints",
143 fluxMag0T1 = pexConfig.Field(
145 doc =
"The approximate flux of a zero-magnitude object in a one-second exposure",
148 setGainAssembledCcd = pexConfig.Field(
150 doc =
"update exposure metadata in the assembled ccd to reflect the effective gain of the assembled chip",
153 keysToRemoveFromAssembledCcd = pexConfig.ListField(
155 doc =
"fields to remove from the metadata of the assembled ccd.",
158 doAssembleIsrExposures = pexConfig.Field(
161 doc =
"Assemble amp-level calibration exposures into ccd-level exposure?"
163 doAssembleCcd = pexConfig.Field(
166 doc =
"Assemble amp-level exposures into a ccd-level exposure?"
180 \brief Apply common instrument signature correction algorithms to a raw frame.
182 \section ip_isr_isr_Contents Contents
184 - \ref ip_isr_isr_Purpose
185 - \ref ip_isr_isr_Initialize
187 - \ref ip_isr_isr_Config
188 - \ref ip_isr_isr_Debug
189 - \ref ip_isr_isr_Example
191 \section ip_isr_isr_Purpose Description
193 The process for correcting imaging data is very similar from camera to camera.
194 This task provides a vanilla implementation of doing these corrections, including
195 the ability to turn certain corrections off if they are not needed.
196 The inputs to the primary method, run, are a raw exposure to be corrected and the
197 calibration data products. The raw input is a single chip sized mosaic of all amps
198 including overscans and other non-science pixels.
199 The method runDataRef() is intended for use by a lsst.pipe.base.cmdLineTask.CmdLineTask
200 and takes as input only a daf.persistence.butlerSubset.ButlerDataRef.
201 This task may not meet all needs and it is expected that it will be subclassed for
202 specific applications.
204 \section ip_isr_isr_Initialize Task initialization
206 \copydoc \_\_init\_\_
208 \section ip_isr_isr_IO Inputs/Outputs to the run method
212 \section ip_isr_isr_Config Configuration parameters
214 See \ref IsrTaskConfig
216 \section ip_isr_isr_Debug Debug variables
218 The \link lsst.pipe.base.cmdLineTask.CmdLineTask command line task\endlink interface supports a
219 flag \c --debug, \c -d to import \b debug.py from your \c PYTHONPATH; see <a
220 href="http://lsst-web.ncsa.illinois.edu/~buildbot/doxygen/x_masterDoxyDoc/base_debug.html">
221 Using lsstDebug to control debugging output</a> for more about \b debug.py files.
223 The available variables in IsrTask are:
226 <DD> A dictionary containing debug point names as keys with frame number as value. Valid keys are:
229 <DD> display exposure after ISR has been applied
233 For example, put something like
237 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
238 if name == "lsst.ip.isr.isrTask":
239 di.display = {'postISRCCD':2}
241 lsstDebug.Info = DebugInfo
243 into your debug.py file and run the commandline task with the \c --debug flag.
245 \section ip_isr_isr_Example A complete example of using IsrTask
247 This code is in runIsrTask.py in the examples directory and can be run as \em e.g.
249 python examples/runIsrTask.py
251 # optional arguments:
252 # --debug, -d Load debug.py?
253 # --ds9 Display the result?
254 # --write Write the result?
258 Stepping through the example:
260 \dontinclude runIsrTask.py
261 Import the task. There are other imports. Read the source file for more info.
263 \skipline exampleUtils
265 Create the raw input data with the help of some utilities in \link exampleUtils.py \endlink
266 also in the examples directory.
267 \dontinclude runIsrTask.py
268 We will only do overscan, dark and flat correction.
269 The data are constructed by hand so that all effects will be corrected for essentially perfectly.
272 The above numbers can be changed to modify the gradient in the flat, for example.
273 For the parameters in this particular example,
274 the image after ISR will be a constant 5000 counts
275 (with some variation in floating point represenation).
277 \note Alternatively, images can be read from disk, either using the Butler or manually:
279 import lsst.afw.image as afwImage
280 darkExposure = afwImage.ExposureF("/path/to/dark.fits")
281 flatExposure = afwImage.ExposureF("/path/to/flat.fits")
282 rawExposure = afwImage.ExposureF("/path/to/raw.fits")
284 In order to perform overscanCorrection IsrTask.run() requires Exposures which have
285 a \link lsst.afw.cameraGeom.Detector \endlink.
286 Detector objects describe details such as data dimensions, number of amps,
287 orientation and overscan dimensions.
288 If requesting images from the Butler, Exposures will automatically have detector information.
289 If running IsrTask on arbitrary images from a camera without an obs_ package,
290 a lsst.afw.cameraGeom.Detector can be generated using lsst.afw.cameraGeom.fitsUtils.DetectorBuilder
293 rawExposure.setDetector(myDetectorObject)
295 See \link lsst.afw.cameraGeom.fitsUtils.DetectorBuilder \endlink for more details.
297 \note The updateVariance and saturationDetection steps are not run for Exposures
298 without a \link lsst.afw.cameraGeom.Detector \endlink, unless \ref IsrTaskConfig.gain,
299 \ref IsrTaskConfig.readNoise,
300 and/or \ref IsrTaskConfig.saturation
301 are set in the config, in which case they are applied to the entire image rather than per amp.
303 \dontinclude runIsrTask.py
304 Construct the task and set some config parameters. Specifically, we don't want to
305 do zero or fringe correction. We also don't want the assembler messing with the gain.
307 \until config=isrConfig
309 Finally, run the exposures through ISR.
310 \skipline isrTask.run
314 ConfigClass = IsrTaskConfig
318 '''!Constructor for IsrTask
319 \param[in] *args -- a list of positional arguments passed on to the Task constructor
320 \param[in] **kwargs -- a dictionary of keyword arguments passed on to the Task constructor
321 Call the lsst.pipe.base.task.Task.__init__ method
322 Then setup the assembly and fringe correction subtasks
324 pipeBase.Task.__init__(self, *args, **kwargs)
325 self.makeSubtask(
"assembleCcd")
326 self.makeSubtask(
"fringe")
330 """!Retrieve necessary frames for instrument signature removal
331 \param[in] dataRef -- a daf.persistence.butlerSubset.ButlerDataRef
332 of the detector data to be processed
333 \return a pipeBase.Struct with fields containing kwargs expected by run()
334 - bias: exposure of bias frame
335 - dark: exposure of dark frame
336 - flat: exposure of flat field
337 - defects: list of detects
338 - fringes: exposure of fringe frame or list of fringe exposure
340 biasExposure = self.
getIsrExposure(dataRef,
"bias")
if self.config.doBias
else None
341 darkExposure = self.
getIsrExposure(dataRef,
"dark")
if self.config.doDark
else None
342 flatExposure = self.
getIsrExposure(dataRef,
"flat")
if self.config.doFlat
else None
344 defectList = dataRef.get(
"defects")
346 if self.config.doFringe:
347 fringes = self.fringe.readFringes(dataRef, assembler=self.assembleCcd \
348 if self.config.doAssembleIsrExposures
else None)
353 return pipeBase.Struct(bias = biasExposure,
356 defects = defectList,
361 def run(self, ccdExposure, bias=None, dark=None, flat=None, defects=None, fringes=None):
362 """!Perform instrument signature removal on an exposure
365 - Detect saturation, apply overscan correction, bias, dark and flat
366 - Perform CCD assembly
367 - Interpolate over defects, saturated pixels and all NaNs
369 \param[in] ccdExposure -- lsst.afw.image.exposure of detector data
370 \param[in] bias -- exposure of bias frame
371 \param[in] dark -- exposure of dark frame
372 \param[in] flat -- exposure of flatfield
373 \param[in] defects -- list of detects
374 \param[in] fringes -- exposure of fringe frame or list of fringe exposure
376 \return a pipeBase.Struct with field:
381 if self.config.doBias
and bias
is None:
382 raise RuntimeError(
"Must supply a bias exposure if config.doBias True")
383 if self.config.doDark
and dark
is None:
384 raise RuntimeError(
"Must supply a dark exposure if config.doDark True")
385 if self.config.doFlat
and flat
is None:
386 raise RuntimeError(
"Must supply a flat exposure if config.doFlat True")
387 if self.config.doFringe
and fringes
is None:
388 raise RuntimeError(
"Must supply fringe list or exposure if config.doFringe True")
390 defects = []
if defects
is None else defects
392 ccd = ccdExposure.getDetector()
396 assert not self.config.doAssembleCcd,
"You need a Detector to run assembleCcd"
397 ccd = [
FakeAmp(ccdExposure, self.config)]
401 if ccdExposure.getBBox().contains(amp.getBBox()):
405 if self.config.doAssembleCcd:
406 ccdExposure = self.assembleCcd.assembleCcd(ccdExposure)
408 if self.config.doBias:
411 if self.config.doDark:
416 if ccdExposure.getBBox().contains(amp.getBBox()):
417 ampExposure = ccdExposure.Factory(ccdExposure, amp.getBBox())
420 if self.config.doFringe
and not self.config.fringeAfterFlat:
421 self.fringe.removeFringe(ccdExposure, fringes)
423 if self.config.doFlat:
432 if self.config.doFringe
and self.config.fringeAfterFlat:
433 self.fringe.removeFringe(ccdExposure, fringes)
435 ccdExposure.getCalib().setFluxMag0(self.config.fluxMag0T1 * ccdExposure.getCalib().getExptime())
437 self.display(
"postISRCCD", ccdExposure)
439 return pipeBase.Struct(
440 exposure = ccdExposure,
446 """!Perform instrument signature removal on a ButlerDataRef of a Sensor
448 - Read in necessary detrending/isr/calibration data
449 - Process raw exposure in run()
450 - Persist the ISR-corrected exposure as "postISRCCD" if config.doWrite is True
452 \param[in] sensorRef -- daf.persistence.butlerSubset.ButlerDataRef of the
453 detector data to be processed
454 \return a pipeBase.Struct with fields:
455 - exposure: the exposure after application of ISR
457 self.log.info(
"Performing ISR on sensor %s" % (sensorRef.dataId))
458 ccdExposure = sensorRef.get(
'raw')
461 result = self.
run(ccdExposure, **isrData.getDict())
463 if self.config.doWrite:
464 sensorRef.put(result.exposure,
"postISRCCD")
469 """Convert an exposure from uint16 to float, set variance plane to 1 and mask plane to 0
471 if isinstance(exposure, afwImage.ExposureF):
474 if not hasattr(exposure,
"convertF"):
475 raise RuntimeError(
"Unable to convert exposure (%s) to float" % type(exposure))
477 newexposure = exposure.convertF()
478 maskedImage = newexposure.getMaskedImage()
479 varArray = maskedImage.getVariance().getArray()
481 maskArray = maskedImage.getMask().getArray()
486 """!Apply bias correction in place
488 \param[in,out] exposure exposure to process
489 \param[in] biasExposure bias exposure of same size as exposure
491 isr.biasCorrection(exposure.getMaskedImage(), biasExposure.getMaskedImage())
494 """!Apply dark correction in place
496 \param[in,out] exposure exposure to process
497 \param[in] darkExposure dark exposure of same size as exposure
499 darkCalib = darkExposure.getCalib()
501 maskedImage = exposure.getMaskedImage(),
502 darkMaskedImage = darkExposure.getMaskedImage(),
503 expScale = exposure.getCalib().getExptime(),
504 darkScale = darkCalib.getExptime(),
508 """!Set the variance plane based on the image plane, plus amplifier gain and read noise
510 \param[in,out] ampExposure exposure to process
511 \param[in] amp amplifier detector information
513 if not math.isnan(amp.getGain()):
515 maskedImage = ampExposure.getMaskedImage(),
516 gain = amp.getGain(),
517 readNoise = amp.getReadNoise(),
521 """!Apply flat correction in place
523 \param[in,out] exposure exposure to process
524 \param[in] flatExposure flatfield exposure same size as exposure
527 maskedImage = exposure.getMaskedImage(),
528 flatMaskedImage = flatExposure.getMaskedImage(),
529 scalingType = self.config.flatScalingType,
530 userScale = self.config.flatUserScale,
534 """!Retrieve a calibration dataset for removing instrument signature
536 \param[in] dataRef data reference for exposure
537 \param[in] datasetType type of dataset to retrieve (e.g. 'bias', 'flat')
538 \param[in] immediate if True, disable butler proxies to enable error
539 handling within this routine
543 exp = dataRef.get(datasetType, immediate=immediate)
545 raise RuntimeError(
"Unable to retrieve %s for %s: %s" % (datasetType, dataRef.dataId, e))
546 if self.config.doAssembleIsrExposures:
547 exp = self.assembleCcd.assembleCcd(exp)
551 """!Detect saturated pixels and mask them using mask plane "SAT", in place
553 \param[in,out] exposure exposure to process; only the amp DataSec is processed
554 \param[in] amp amplifier device data
556 if not math.isnan(amp.getSaturation()):
557 maskedImage = exposure.getMaskedImage()
558 dataView = maskedImage.Factory(maskedImage, amp.getRawBBox())
559 isr.makeThresholdMask(
560 maskedImage = dataView,
561 threshold = amp.getSaturation(),
563 maskName = self.config.saturatedMaskName,
567 """!Interpolate over saturated pixels, in place
569 \param[in,out] ccdExposure exposure to process
572 - Call saturationDetection first, so that saturated pixels have been identified in the "SAT" mask.
573 - Call this after CCD assembly, since saturated regions may cross amplifier boundaries
575 isr.interpolateFromMask(
576 maskedImage = ccdExposure.getMaskedImage(),
577 fwhm = self.config.fwhm,
578 growFootprints = self.config.growSaturationFootprintSize,
579 maskName = self.config.saturatedMaskName,
583 """!Mask defects using mask plane "BAD" and interpolate over them, in place
585 \param[in,out] ccdExposure exposure to process
586 \param[in] defectBaseList a list of defects to mask and interpolate
588 \warning: call this after CCD assembly, since defects may cross amplifier boundaries
590 maskedImage = ccdExposure.getMaskedImage()
591 defectList = measAlg.DefectListT()
592 for d
in defectBaseList:
595 defectList.append(nd)
596 isr.maskPixelsFromDefectList(maskedImage, defectList, maskName=
'BAD')
597 isr.interpolateDefectList(
598 maskedImage = maskedImage,
599 defectList = defectList,
600 fwhm = self.config.fwhm,
604 """!Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place
606 We mask and interpolate over all NaNs, including those
607 that are masked with other bits (because those may or may
608 not be interpolated over later, and we want to remove all
609 NaNs). Despite this behaviour, the "UNMASKEDNAN" mask plane
610 is used to preserve the historical name.
612 \param[in,out] exposure exposure to process
614 maskedImage = exposure.getMaskedImage()
617 maskedImage.getMask().addMaskPlane(
"UNMASKEDNAN")
618 maskVal = maskedImage.getMask().getPlaneBitMask(
"UNMASKEDNAN")
619 numNans =
maskNans(maskedImage, maskVal)
620 self.metadata.set(
"NUMNANS", numNans)
624 self.log.log(self.log.WARN,
"There were %i unmasked NaNs" % (numNans,))
625 nanDefectList = isr.getDefectListFromMask(
626 maskedImage = maskedImage,
627 maskName =
'UNMASKEDNAN',
630 isr.interpolateDefectList(
631 maskedImage = exposure.getMaskedImage(),
632 defectList = nanDefectList,
633 fwhm = self.config.fwhm,
637 """!Apply overscan correction, in place
639 \param[in,out] exposure exposure to process; must include both DataSec and BiasSec pixels
640 \param[in] amp amplifier device data
642 if not amp.getHasRawInfo():
643 raise RuntimeError(
"This method must be executed on an amp with raw information.")
645 if amp.getRawHorizontalOverscanBBox().isEmpty():
646 self.log.info(
"No Overscan region. Not performing Overscan Correction.")
649 maskedImage = exposure.getMaskedImage()
650 dataView = maskedImage.Factory(maskedImage, amp.getRawDataBBox())
652 expImage = exposure.getMaskedImage().getImage()
653 overscanImage = expImage.Factory(expImage, amp.getRawHorizontalOverscanBBox())
655 isr.overscanCorrection(
656 ampMaskedImage = dataView,
657 overscanImage = overscanImage,
658 fitType = self.config.overscanFitType,
659 order = self.config.overscanOrder,
660 collapseRej = self.config.overscanRej,
664 """A Detector-like object that supports returning gain and saturation level"""
666 self.
_bbox = exposure.getBBox(afwImage.LOCAL)
def getIsrExposure
Retrieve a calibration dataset for removing instrument signature.
def maskAndInterpNan
Mask NaNs using mask plane "UNMASKEDNAN" and interpolate over them, in place.
def flatCorrection
Apply flat correction in place.
def run
Perform instrument signature removal on an exposure.
def __init__
Constructor for IsrTask.
Apply common instrument signature correction algorithms to a raw frame.
def runDataRef
Perform instrument signature removal on a ButlerDataRef of a Sensor.
An integer coordinate rectangle.
def updateVariance
Set the variance plane based on the image plane, plus amplifier gain and read noise.
def maskAndInterpDefect
Mask defects using mask plane "BAD" and interpolate over them, in place.
def overscanCorrection
Apply overscan correction, in place.
_RawHorizontalOverscanBBox
def saturationDetection
Detect saturated pixels and mask them using mask plane "SAT", in place.
def darkCorrection
Apply dark correction in place.
size_t maskNans(afw::image::MaskedImage< PixelT > const &mi, afw::image::MaskPixel maskVal, afw::image::MaskPixel allow=0)
Encapsulate information about a bad portion of a detector.
def saturationInterpolation
Interpolate over saturated pixels, in place.
def getRawHorizontalOverscanBBox
def biasCorrection
Apply bias correction in place.
def readIsrData
Retrieve necessary frames for instrument signature removal.