28 __all__ = [
"OverscanCorrectionTaskConfig",
"OverscanCorrectionTask"]
32 """Overscan correction options.
34 fitType = pexConfig.ChoiceField(
36 doc=
"The method for fitting the overscan bias level.",
39 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
40 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
41 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
42 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
43 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
44 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
45 "MEAN":
"Correct using the mean of the overscan region",
46 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
47 "MEDIAN":
"Correct using the median of the overscan region",
48 "MEDIAN_PER_ROW":
"Correct using the median per row of the overscan region",
51 order = pexConfig.Field(
53 doc=(
"Order of polynomial to fit if overscan fit type is a polynomial, "
54 "or number of spline knots if overscan fit type is a spline."),
57 numSigmaClip = pexConfig.Field(
59 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
62 maskPlanes = pexConfig.ListField(
64 doc=
"Mask planes to reject when measuring overscan",
67 overscanIsInt = pexConfig.Field(
69 doc=
"Treat overscan as an integer image for purposes of fitType=MEDIAN"
70 " and fitType=MEDIAN_PER_ROW.",
76 """Correction task for overscan.
78 This class contains a number of utilities that are easier to
79 understand and use when they are not embedded in nested if/else
84 statControl : `lsst.afw.math.StatisticsControl`, optional
85 Statistics control object.
87 ConfigClass = OverscanCorrectionTaskConfig
88 _DefaultName =
"overscan"
90 def __init__(self, statControl=None, **kwargs):
98 self.
statControlstatControl.setNumSigmaClip(self.config.numSigmaClip)
99 self.
statControlstatControl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.maskPlanes))
101 def run(self, ampImage, overscanImage, amp=None):
102 """Measure and remove an overscan from an amplifier image.
106 ampImage : `lsst.afw.image.Image`
107 Image data that will have the overscan removed.
108 overscanImage : `lsst.afw.image.Image`
109 Overscan data that the overscan is measured from.
110 amp : `lsst.afw.cameraGeom.Amplifier`, optional
111 Amplifier to use for debugging purposes.
115 overscanResults : `lsst.pipe.base.Struct`
116 Result struct with components:
119 Value or fit subtracted from the amplifier image data
120 (scalar or `lsst.afw.image.Image`).
122 Value or fit subtracted from the overscan image data
123 (scalar or `lsst.afw.image.Image`).
125 Image of the overscan region with the overscan
126 correction applied (`lsst.afw.image.Image`). This
127 quantity is used to estimate the amplifier read noise
133 Raised if an invalid overscan type is set.
136 if self.config.fitType
in (
'MEAN',
'MEANCLIP',
'MEDIAN'):
138 overscanValue = overscanResult.overscanValue
139 offImage = overscanValue
140 overscanModel = overscanValue
142 elif self.config.fitType
in (
'MEDIAN_PER_ROW',
'POLY',
'CHEB',
'LEG',
143 'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
145 overscanValue = overscanResult.overscanValue
146 maskArray = overscanResult.maskArray
147 isTransposed = overscanResult.isTransposed
149 offImage = afwImage.ImageF(ampImage.getDimensions())
150 offArray = offImage.getArray()
151 overscanModel = afwImage.ImageF(overscanImage.getDimensions())
152 overscanArray = overscanModel.getArray()
154 if hasattr(ampImage,
'getMask'):
160 offArray[:, :] = overscanValue[np.newaxis, :]
161 overscanArray[:, :] = overscanValue[np.newaxis, :]
163 maskSuspect.getArray()[:, maskArray] |= ampImage.getMask().getPlaneBitMask(
"SUSPECT")
165 offArray[:, :] = overscanValue[:, np.newaxis]
166 overscanArray[:, :] = overscanValue[:, np.newaxis]
168 maskSuspect.getArray()[maskArray, :] |= ampImage.getMask().getPlaneBitMask(
"SUSPECT")
170 raise RuntimeError(
'%s : %s an invalid overscan type' %
171 (
"overscanCorrection", self.config.fitType))
173 self.
debugViewdebugView(overscanImage, overscanValue, amp)
177 ampImage.getMask().getArray()[:, :] |= maskSuspect.getArray()[:, :]
178 overscanImage -= overscanModel
179 return pipeBase.Struct(imageFit=offImage,
180 overscanFit=overscanModel,
181 overscanImage=overscanImage,
182 edgeMask=maskSuspect)
186 """Return an integer version of the input image.
190 image : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
191 Image to convert to integers.
195 outI : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
196 The integer converted image.
201 Raised if the input image could not be converted.
203 if hasattr(image,
"image"):
205 imageI = image.image.convertI()
206 outI = afwImage.MaskedImageI(imageI, image.mask, image.variance)
207 elif hasattr(image,
"convertI"):
209 outI = image.convertI()
210 elif hasattr(image,
"astype"):
212 outI = image.astype(int)
214 raise RuntimeError(
"Could not convert this to integers: %s %s %s",
215 image,
type(image), dir(image))
220 """Measure a constant overscan value.
224 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
225 Image data to measure the overscan from.
229 results : `lsst.pipe.base.Struct`
230 Overscan result with entries:
231 - ``overscanValue``: Overscan value to subtract (`float`)
232 - ``maskArray``: Placeholder for a mask array (`list`)
233 - ``isTransposed``: Orientation of the overscan (`bool`)
235 if self.config.fitType ==
'MEDIAN':
243 return pipeBase.Struct(overscanValue=overscanValue,
249 """Extract the numpy array from the input image.
253 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
254 Image data to pull array from.
256 calcImage : `numpy.ndarray`
257 Image data array for numpy operating.
259 if hasattr(image,
"getImage"):
260 calcImage = image.getImage().getArray()
261 calcImage = np.ma.masked_where(image.getMask().getArray() & self.
statControlstatControl.getAndMask(),
264 calcImage = image.getArray()
269 """Transpose input numpy array if necessary.
273 imageArray : `numpy.ndarray`
274 Image data to transpose.
278 imageArray : `numpy.ndarray`
279 Transposed image data.
280 isTransposed : `bool`
281 Indicates whether the input data was transposed.
283 if np.argmin(imageArray.shape) == 0:
284 return np.transpose(imageArray),
True
286 return imageArray,
False
289 """Mask outliers in a row of overscan data from a robust sigma
294 imageArray : `numpy.ndarray`
295 Image to filter along numpy axis=1.
299 maskedArray : `numpy.ma.masked_array`
300 Masked image marking outliers.
302 lq, median, uq = np.percentile(imageArray, [25.0, 50.0, 75.0], axis=1)
304 axisStdev = 0.74*(uq - lq)
306 diff = np.abs(imageArray - axisMedians[:, np.newaxis])
307 return np.ma.masked_where(diff > self.
statControlstatControl.getNumSigmaClip()
308 * axisStdev[:, np.newaxis], imageArray)
312 """Collapse overscan array (and mask) to a 1-D vector of values.
316 maskedArray : `numpy.ma.masked_array`
317 Masked array of input overscan data.
321 collapsed : `numpy.ma.masked_array`
322 Single dimensional overscan data, combined with the mean.
324 collapsed = np.mean(maskedArray, axis=1)
325 if collapsed.mask.sum() > 0:
326 collapsed.data[collapsed.mask] = np.mean(maskedArray.data[collapsed.mask], axis=1)
330 """Collapse overscan array (and mask) to a 1-D vector of using the
331 correct integer median of row-values.
335 maskedArray : `numpy.ma.masked_array`
336 Masked array of input overscan data.
340 collapsed : `numpy.ma.masked_array`
341 Single dimensional overscan data, combined with the afwMath median.
347 for row
in integerMI:
348 newRow = row.compressed()
353 collapsed.append(rowMedian)
355 return np.array(collapsed)
358 """Wrapper function to match spline fit API to polynomial fit API.
362 indices : `numpy.ndarray`
363 Locations to evaluate the spline.
364 collapsed : `numpy.ndarray`
365 Collapsed overscan values corresponding to the spline
368 Number of bins to use in constructing the spline.
372 interp : `lsst.afw.math.Interpolate`
373 Interpolation object for later evaluation.
375 if not np.ma.is_masked(collapsed):
376 collapsed.mask = np.array(len(collapsed)*[np.ma.nomask])
378 numPerBin, binEdges = np.histogram(indices, bins=numBins,
379 weights=1 - collapsed.mask.astype(int))
380 with np.errstate(invalid=
"ignore"):
381 values = np.histogram(indices, bins=numBins,
382 weights=collapsed.data*~collapsed.mask)[0]/numPerBin
383 binCenters = np.histogram(indices, bins=numBins,
384 weights=indices*~collapsed.mask)[0]/numPerBin
386 values.astype(float)[numPerBin > 0],
392 """Wrapper function to match spline evaluation API to polynomial fit API.
396 indices : `numpy.ndarray`
397 Locations to evaluate the spline.
398 interp : `lsst.afw.math.interpolate`
399 Interpolation object to use.
403 values : `numpy.ndarray`
404 Evaluated spline values at each index.
407 return interp.interpolate(indices.astype(float))
411 """Create mask if edges are extrapolated.
415 collapsed : `numpy.ma.masked_array`
416 Masked array to check the edges of.
420 maskArray : `numpy.ndarray`
421 Boolean numpy array of pixels to mask.
423 maskArray = np.full_like(collapsed,
False, dtype=bool)
424 if np.ma.is_masked(collapsed):
426 for low
in range(num):
427 if not collapsed.mask[low]:
430 maskArray[:low] =
True
431 for high
in range(1, num):
432 if not collapsed.mask[-high]:
435 maskArray[-high:] =
True
439 """Calculate the 1-d vector overscan from the input overscan image.
443 image : `lsst.afw.image.MaskedImage`
444 Image containing the overscan data.
448 results : `lsst.pipe.base.Struct`
449 Overscan result with entries:
450 - ``overscanValue``: Overscan value to subtract (`float`)
451 - ``maskArray`` : `list` [ `bool` ]
452 List of rows that should be masked as ``SUSPECT`` when the
453 overscan solution is applied.
454 - ``isTransposed`` : `bool`
455 Indicates if the overscan data was transposed during
456 calcuation, noting along which axis the overscan should be
462 calcImage, isTransposed = self.
transposetranspose(calcImage)
465 if self.config.fitType ==
'MEDIAN_PER_ROW':
472 indices = 2.0*np.arange(num)/float(num) - 1.0
476 'POLY': (poly.polynomial.polyfit, poly.polynomial.polyval),
477 'CHEB': (poly.chebyshev.chebfit, poly.chebyshev.chebval),
478 'LEG': (poly.legendre.legfit, poly.legendre.legval),
482 }[self.config.fitType]
484 coeffs =
fitter(indices, collapsed, self.config.order)
485 overscanVector = evaler(indices, coeffs)
487 return pipeBase.Struct(overscanValue=np.array(overscanVector),
489 isTransposed=isTransposed)
492 """Debug display for the final overscan solution.
496 image : `lsst.afw.image.Image`
497 Input image the overscan solution was determined from.
498 model : `numpy.ndarray` or `float`
499 Overscan model determined for the image.
500 amp : `lsst.afw.cameraGeom.Amplifier`, optional
501 Amplifier to extract diagnostic information.
510 calcImage, isTransposed = self.
transposetranspose(calcImage)
515 indices = 2.0 * np.arange(num)/float(num) - 1.0
517 if np.ma.is_masked(collapsed):
518 collapsedMask = collapsed.mask
520 collapsedMask = np.array(num*[np.ma.nomask])
522 import matplotlib.pyplot
as plot
523 figure = plot.figure(1)
525 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
526 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
527 if collapsedMask.sum() > 0:
528 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
529 if isinstance(model, np.ndarray):
532 plotModel = np.zeros_like(indices)
534 axes.plot(indices, plotModel,
'r-')
535 plot.xlabel(
"centered/scaled position along overscan region")
536 plot.ylabel(
"pixel value/fit value")
538 plot.title(f
"{amp.getName()} DataX: "
539 f
"[{amp.getRawDataBBox().getBeginX()}:{amp.getRawBBox().getEndX()}]"
540 f
"OscanX: [{amp.getRawHorizontalOverscanBBox().getBeginX()}:"
541 f
"{amp.getRawHorizontalOverscanBBox().getEndX()}] {self.config.fitType}")
543 plot.title(
"No amp supplied.")
545 prompt =
"Press Enter or c to continue [chp]..."
547 ans = input(prompt).lower()
548 if ans
in (
"",
" ",
"c",):
557 print(
"[h]elp [c]ontinue [p]db e[x]itDebug")
Represent a 2-dimensional array of bitmask pixels.
Pass parameters to a Statistics object.
def collapseArrayMedian(self, maskedArray)
def maskExtrapolated(collapsed)
def measureVectorOverscan(self, image)
def __init__(self, statControl=None, **kwargs)
def collapseArray(maskedArray)
def transpose(imageArray)
def maskOutliers(self, imageArray)
def debugView(self, image, model, amp=None)
def run(self, ampImage, overscanImage, amp=None)
def getImageArray(self, image)
def integerConvert(image)
def splineFit(self, indices, collapsed, numBins)
def splineEval(indices, interp)
def measureConstantOverscan(self, image)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
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)
Property stringToStatisticsProperty(std::string const property)
Conversion function to switch a string to a Property (see Statistics.h)
Interpolate::Style stringToInterpStyle(std::string const &style)
Conversion function to switch a string to an Interpolate::Style.
std::shared_ptr< Interpolate > makeInterpolate(std::vector< double > const &x, std::vector< double > const &y, Interpolate::Style const style=Interpolate::AKIMA_SPLINE)
A factory function to make Interpolate objects.