22__all__ = [
"OverscanCorrectionTaskConfig",
"OverscanCorrectionTask",
23 "SerialOverscanCorrectionTaskConfig",
"SerialOverscanCorrectionTask",
24 "ParallelOverscanCorrectionTaskConfig",
"ParallelOverscanCorrectionTask",
28from scipy.signal
import medfilt
36from .isr
import fitOverscanImage, fitOverscanImageMean
37from .isrFunctions
import makeThresholdMask
41 """Overscan correction options.
43 fitType = pexConfig.ChoiceField(
45 doc=
"The method for fitting the overscan bias level.",
48 "POLY":
"Fit ordinary polynomial to the longest axis of the overscan region",
49 "CHEB":
"Fit Chebyshev polynomial to the longest axis of the overscan region",
50 "LEG":
"Fit Legendre polynomial to the longest axis of the overscan region",
51 "NATURAL_SPLINE":
"Fit natural spline to the longest axis of the overscan region",
52 "CUBIC_SPLINE":
"Fit cubic spline to the longest axis of the overscan region",
53 "AKIMA_SPLINE":
"Fit Akima spline to the longest axis of the overscan region",
54 "MEAN":
"Correct using the mean of the overscan region",
55 "MEANCLIP":
"Correct using a clipped mean of the overscan region",
56 "MEDIAN":
"Correct using the median of the overscan region",
57 "MEDIAN_PER_ROW":
"Correct using the median per row of the overscan region",
58 "MEAN_PER_ROW":
"Correct using the mean per row of the overscan region",
61 order = pexConfig.Field(
63 doc=(
"Order of polynomial to fit if overscan fit type is a polynomial, "
64 "or number of spline knots if overscan fit type is a spline."),
67 numSigmaClip = pexConfig.Field(
69 doc=
"Rejection threshold (sigma) for collapsing overscan before fit",
72 maskPlanes = pexConfig.ListField(
74 doc=
"Mask planes to reject when measuring overscan",
75 default=[
'BAD',
'SAT'],
77 overscanIsInt = pexConfig.Field(
79 doc=
"Treat overscan as an integer image for purposes of fitType=MEDIAN"
80 " and fitType=MEDIAN_PER_ROW.",
83 maxDeviation = pexConfig.Field(
85 doc=
"Maximum deviation from median (in ADU) to mask in overscan correction; "
86 "Will be applied to the absolute deviation if doAbsoluteMaxDeviation=True.",
87 default=1000.0, check=
lambda x: x > 0,
89 doAbsoluteMaxDeviation = pexConfig.Field(
91 doc=
"Apply the maxDeviation to the absolute value of the deviation? If "
92 "False, this will be a one-sided cut for positive-only deviations "
93 "(typically for parallel overscan subtraction.",
99 doParallelOverscan = pexConfig.Field(
101 doc=
"Correct using parallel overscan after serial overscan correction?",
104 parallelOverscanMaskThreshold = pexConfig.Field(
106 doc=
"Threshold above which pixels in the parallel overscan are masked as bleeds.",
109 parallelOverscanMaskGrowSize = pexConfig.Field(
111 doc=
"Grow the SAT mask in the parallel overscan region by this many pixels. "
112 "This value was determined from the ITL chip in the LATISS camera.",
115 leadingColumnsToSkip = pexConfig.Field(
117 doc=
"Number of leading columns to skip in serial overscan correction.",
120 trailingColumnsToSkip = pexConfig.Field(
122 doc=
"Number of trailing columns to skip in serial overscan correction.",
125 leadingRowsToSkip = pexConfig.Field(
127 doc=
"Number of leading rows to skip in parallel overscan correction.",
130 trailingRowsToSkip = pexConfig.Field(
132 doc=
"Number of trailing rows to skip in parallel overscan correction.",
138 """Base Correction task for overscan.
140 This class contains a number of utilities that are easier to
141 understand and use when they are not embedded in nested if/else
146 statControl : `lsst.afw.math.StatisticsControl`, optional
147 Statistics control object.
149 ConfigClass = OverscanCorrectionTaskConfigBase
150 _DefaultName =
"overscanBase"
160 self.
statControl.setNumSigmaClip(self.config.numSigmaClip)
161 self.
statControl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.maskPlanes))
163 def run(self, exposure, amp, isTransposed=False):
164 raise NotImplementedError(
"run method is not defined for OverscanCorrectionTaskBase")
163 def run(self, exposure, amp, isTransposed=False):
…
173 maskedRowColumnGrowSize,
174 medianSmoothingKernel,
175 medianSmoothingOutlierThreshold,
176 doAbsoluteMaxDeviation,
179 """Mask overscan rows (~serial) or columns (~parallel).
183 exposure : `lsst.afw.image.Exposure`
184 Exposure containing the data.
185 overscanBBox: `lsst.geom.Box2I`
186 Bounding box for the overscan data.
187 overscanMaskedImage : `lsst.afw.image.MaskedImage`
188 Masked image containing the overscan data.
189 overscanMask : `np.ndarray`
190 Numpy array of the mask bits, anded with appropriate
192 maxDeviation : `float`
193 Maximum deviation from median (overscan units) to mask in overscan
194 correction. For parallel overscan this is a one-sided (positive
196 maskedRowColumnGrowSize : `int`
197 If a column (parallel overscan) or row (serial overscan) is
198 completely masked, then grow the mask by this radius. If the
199 value is <=0 then this will not be checked.
200 medianSmoothingKernel : `int`
201 Kernel (pixels) to smooth rows/columns. If <=0, median smoothing
202 is skipped. Otherwise must be odd.
203 medianSmoothingOutlierThreshold : `float`
204 Outlier threshold after median smoothing (overscan units). This
205 is applied only to positive outliers.
206 doAbsoluteMaxDeviation : `bool`
207 If true, deviation comparisons will use the absolute value;
208 otherwise it will cut positive outliers only.
209 isTransposed : `bool`
210 If true, then the data will be transposed before fitting
215 badRowsOrColumns : `np.ndarray`
216 Array of bad rows (serial) or columns (parallel) that were
217 found, prior to dilation by maskedRowColumnGrowSize.
219 overscanArray = overscanMaskedImage.image.array
221 badRowsOrColumns = np.zeros(0, dtype=np.int64)
223 median = np.ma.median(np.ma.masked_where(overscanMask, overscanArray))
224 if doAbsoluteMaxDeviation:
225 delta = np.abs(overscanArray - median)
227 delta = overscanArray - median
229 bad = np.where((delta > maxDeviation) & (~overscanMask))
231 overscanMaskedImage.mask.array[bad] |= overscanMaskedImage.mask.getPlaneBitMask(
"BAD")
235 nComp = overscanArray.shape[0]
238 nComp = overscanArray.shape[1]
245 overscanMaskTemp = np.zeros_like(overscanMask)
246 overscanMaskTemp[bad] =
True
248 nMaskedArray = np.sum(overscanMaskTemp, axis=axis, dtype=np.int32)
249 badRowsOrColumns, = np.where(nMaskedArray == nComp)
252 if medianSmoothingKernel > 0:
257 rowsCols = np.median(overscanArray, axis=axis)
258 filtered = medfilt(rowsCols, kernel_size=medianSmoothingKernel)
259 delta = rowsCols - filtered
263 high, = np.where(delta[medianSmoothingKernel: -medianSmoothingKernel]
264 >= medianSmoothingOutlierThreshold)
265 high += medianSmoothingKernel
268 badRowsOrColumns = np.unique(np.append(badRowsOrColumns, high))
272 if len(badRowsOrColumns) > 0:
273 dataView = afwImage.MaskedImageF(exposure.maskedImage,
277 pixelsCopy = dataView.image.array[:, badRowsOrColumns].copy()
278 dataView.image.array[:, badRowsOrColumns] = 1e30
280 pixelsCopy = dataView.image.array[badRowsOrColumns, :].copy()
281 dataView.image.array[badRowsOrColumns, :] = 1e30
284 maskedImage=dataView,
286 growFootprints=maskedRowColumnGrowSize,
291 dataView.image.array[:, badRowsOrColumns] = pixelsCopy
293 dataView.image.array[badRowsOrColumns, :] = pixelsCopy
295 return badRowsOrColumns
306 overscanFraction=1.0,
307 imageThreshold=np.inf,
308 maskedRowColumnGrowSize=0,
309 medianSmoothingKernel=0,
310 medianSmoothingOutlierThreshold=np.inf,
312 """Trim the exposure, fit the overscan, subtract the fit, and
313 calculate statistics.
317 exposure : `lsst.afw.image.Exposure`
318 Exposure containing the data.
319 amp : `lsst.afw.cameraGeom.Amplifier`
320 The amplifier that is to be corrected.
321 imageBBox: `lsst.geom.Box2I`
322 Bounding box of the image data that will have the overscan
323 subtracted. If parallel overscan will be performed, that
324 area is added to the image bounding box during serial
326 overscanBBox: `lsst.geom.Box2I`
327 Bounding box for the overscan data.
329 If true, then the data will be transposed before fitting
331 leadingToSkip : `int`, optional
332 Leading rows/columns to skip.
333 trailingToSkip : `int`, optional
334 Leading rows/columns to skip.
335 overscanFraction : `float`, optional
336 If the overscan region median is greater than overscanFraction
337 and the imaging region median is greater than imageThreshold
338 then overscan correction will be skipped.
339 maxLevel : `float`, optional
340 If the overscan region median is greater than overscanFraction
341 and the imaging region median is greater than imageThreshold
342 then overscan correction will be skipped.
343 maskedRowColumnGrowSize : `int`, optional
344 If a column (parallel overscan) or row (serial overscan) is
345 completely masked, then grow the mask by this radius. If the
346 value is <=0 then this will not be checked.
347 medianSmoothingKernel : `int`, optional
348 Kernel (pixels) to use to smooth rows/columns for row/column
349 outlier rejection. Must be odd if positive; if <=0 median
350 smoothing will not be used to find outliers.
351 medianSmoothingOutlierThreshold : `float`, optional
352 Threshold to look for outliers after median smoothing (adu).
356 results : `lsst.pipe.base.Struct`
358 Overscan model broadcast to the full image size.
359 (`lsst.afw.image.Exposure`)
360 ``overscanOverscanModel``
361 Overscan model broadcast to the full overscan image
362 size. (`lsst.afw.image.Exposure`)
364 Overscan image with the overscan fit subtracted.
365 (`lsst.afw.image.Exposure`)
367 Overscan model. (`float` or `np.array`)
369 Mean value of the overscan fit. (`float`)
371 Median value of the overscan fit. (`float`)
373 Standard deviation of the overscan fit. (`float`)
374 ``overscanMeanResidual``
375 Mean value of the overscan region after overscan
376 subtraction. (`float`)
377 ``overscanMedianResidual``
378 Median value of the overscan region after overscan
379 subtraction. (`float`)
380 ``overscanSigmaResidual``
381 Standard deviation of the overscan region after
382 overscan subtraction. (`float`)
384 overscanBox = self.
trimOverscan(exposure, amp, overscanBBox,
387 transpose=isTransposed)
388 overscanImage = exposure[overscanBox].getMaskedImage()
392 if exposure.metadata.get(
"LSST ISR UNITS",
"adu") ==
"electron":
393 gain = exposure.metadata[f
"LSST ISR GAIN {amp.getName()}"]
398 maskVal = overscanImage.mask.getPlaneBitMask(self.config.maskPlanes)
399 overscanMask = ~((overscanImage.mask.array & maskVal) == 0)
402 overscanMedian = np.nanmedian(overscanImage.image.array)
403 imageMedian = np.nanmedian(exposure[imageBBox].image.array)
405 if np.all(overscanMask):
407 "All overscan pixels masked when attempting overscan correction for %s",
411 elif overscanMedian/imageMedian > overscanFraction
and imageMedian > imageThreshold:
413 "The level in the overscan region (%.2f) compared to the image region (%.2f) is "
414 "greater than the maximum fraction (%.2f) for %s",
424 badRowsOrColumns = np.zeros(0, dtype=np.int64)
425 overscanResults = pipeBase.Struct(
437 gain * self.config.maxDeviation,
438 maskedRowColumnGrowSize,
439 medianSmoothingKernel,
440 gain * medianSmoothingOutlierThreshold,
441 self.config.doAbsoluteMaxDeviation,
446 overscanResults = self.
fitOverscan(overscanImage, isTransposed=isTransposed)
449 ampImage = exposure[imageBBox]
451 ampImage.image.array,
452 transpose=isTransposed)
453 ampImage.image.array -= ampOverscanModel
457 overscanImage = exposure[overscanBBox]
460 overscanImage.image.array)
461 self.
debugView(overscanImage, overscanResults.overscanValue, amp, isTransposed=isTransposed)
462 overscanImage.image.array -= overscanOverscanModel
466 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP, self.
statControl)
467 residualMean = stats.getValue(afwMath.MEAN)
468 residualMedian = stats.getValue(afwMath.MEDIAN)
469 residualSigma = stats.getValue(afwMath.STDEVCLIP)
471 return pipeBase.Struct(
472 ampOverscanModel=ampOverscanModel,
473 overscanOverscanModel=overscanOverscanModel,
474 overscanImage=overscanImage,
475 overscanValue=overscanResults.overscanValue,
476 overscanMean=overscanResults.overscanMean,
477 overscanMedian=overscanResults.overscanMedian,
478 overscanSigma=overscanResults.overscanSigma,
479 overscanMeanResidual=residualMean,
480 overscanMedianResidual=residualMedian,
481 overscanSigmaResidual=residualSigma,
482 overscanBadRowsOrColumns=badRowsOrColumns,
486 """Broadcast 0 or 1 dimension fit to appropriate shape.
490 overscanValue : `numpy.ndarray`, (Nrows, ) or scalar
491 Overscan fit to broadcast.
492 imageArray : `numpy.ndarray`, (Nrows, Ncols)
493 Image array that we want to match.
494 transpose : `bool`, optional
495 Switch order to broadcast along the other axis.
499 overscanModel : `numpy.ndarray`, (Nrows, Ncols) or scalar
500 Expanded overscan fit.
505 Raised if no axis has the appropriate dimension.
507 if isinstance(overscanValue, np.ndarray):
508 overscanModel = np.zeros_like(imageArray)
510 if transpose
is False:
511 if imageArray.shape[0] == overscanValue.shape[0]:
512 overscanModel[:, :] = overscanValue[:, np.newaxis]
513 elif imageArray.shape[1] == overscanValue.shape[0]:
514 overscanModel[:, :] = overscanValue[np.newaxis, :]
515 elif imageArray.shape[0] == overscanValue.shape[1]:
516 overscanModel[:, :] = overscanValue[np.newaxis, :]
518 raise RuntimeError(f
"Could not broadcast {overscanValue.shape} to "
519 f
"match {imageArray.shape}")
521 if imageArray.shape[1] == overscanValue.shape[0]:
522 overscanModel[:, :] = overscanValue[np.newaxis, :]
523 elif imageArray.shape[0] == overscanValue.shape[0]:
524 overscanModel[:, :] = overscanValue[:, np.newaxis]
525 elif imageArray.shape[1] == overscanValue.shape[1]:
526 overscanModel[:, :] = overscanValue[:, np.newaxis]
528 raise RuntimeError(f
"Could not broadcast {overscanValue.shape} to "
529 f
"match {imageArray.shape}")
531 overscanModel = overscanValue
535 def trimOverscan(self, exposure, amp, bbox, skipLeading, skipTrailing, transpose=False):
536 """Trim overscan region to remove edges.
540 exposure : `lsst.afw.image.Exposure`
541 Exposure containing data.
542 amp : `lsst.afw.cameraGeom.Amplifier`
543 Amplifier containing geometry information.
544 bbox : `lsst.geom.Box2I`
545 Bounding box of the overscan region.
547 Number of leading (towards data region) rows/columns to skip.
549 Number of trailing (away from data region) rows/columns to skip.
550 transpose : `bool`, optional
551 Operate on the transposed array.
555 overscanArray : `numpy.array`, (N, M)
557 overscanMask : `numpy.array`, (N, M)
560 dx0, dy0, dx1, dy1 = (0, 0, 0, 0)
561 dataBBox = amp.getRawDataBBox()
563 if dataBBox.getBeginY() < bbox.getBeginY():
570 if dataBBox.getBeginX() < bbox.getBeginX():
579 bbox.getHeight() - dy0 + dy1))
535 def trimOverscan(self, exposure, amp, bbox, skipLeading, skipTrailing, transpose=False):
…
583 if self.config.fitType
in (
'MEAN',
'MEANCLIP',
'MEDIAN'):
586 overscanValue = overscanResult.overscanValue
587 overscanMean = overscanValue
588 overscanMedian = overscanValue
590 elif self.config.fitType
in (
'MEDIAN_PER_ROW',
'MEAN_PER_ROW',
'POLY',
'CHEB',
'LEG',
591 'NATURAL_SPLINE',
'CUBIC_SPLINE',
'AKIMA_SPLINE'):
594 overscanValue = overscanResult.overscanValue
597 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP,
599 overscanMean = stats.getValue(afwMath.MEAN)
600 overscanMedian = stats.getValue(afwMath.MEDIAN)
601 overscanSigma = stats.getValue(afwMath.STDEVCLIP)
603 raise ValueError(
'%s : %s an invalid overscan type' %
604 (
"overscanCorrection", self.config.fitType))
606 return pipeBase.Struct(overscanValue=overscanValue,
607 overscanMean=overscanMean,
608 overscanMedian=overscanMedian,
609 overscanSigma=overscanSigma,
613 """Mask the union of high values on all amplifiers in the parallel
616 This operates on the image in-place.
620 exposure : `lsst.afw.image.Exposure`
621 An untrimmed raw exposure.
622 detector : `lsst.afw.cameraGeom.Detector`
623 The detetor to use for amplifier geometry.
628 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
629 amp.getRawParallelOverscanBBox(),
633 maskedImage=dataView,
634 threshold=self.config.parallelOverscanMaskThreshold,
635 growFootprints=self.config.parallelOverscanMaskGrowSize,
638 if parallelMask
is None:
639 parallelMask = dataView.mask.array
641 parallelMask |= dataView.mask.array
643 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
644 amp.getRawParallelOverscanBBox(),
646 dataView.mask.array |= parallelMask
650 """Measure a constant overscan value.
654 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
655 Image data to measure the overscan from.
659 results : `lsst.pipe.base.Struct`
660 Overscan result with entries:
661 - ``overscanValue``: Overscan value to subtract (`float`)
662 - ``isTransposed``: Orientation of the overscan (`bool`)
667 return pipeBase.Struct(overscanValue=overscanValue,
672 """Extract the numpy array from the input image.
676 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
677 Image data to pull array from.
679 calcImage : `numpy.ndarray`
680 Image data array for numpy operating.
682 if hasattr(image,
"getImage"):
683 calcImage = image.getImage().getArray()
684 calcImage = np.ma.masked_where(image.getMask().getArray() & self.
statControl.getAndMask(),
687 calcImage = image.getArray()
691 """Mask outliers in a row of overscan data from a robust sigma
696 imageArray : `numpy.ndarray`
697 Image to filter along numpy axis=1.
701 maskedArray : `numpy.ma.masked_array`
702 Masked image marking outliers.
704 lq, median, uq = np.percentile(np.ma.getdata(imageArray),
705 [25.0, 50.0, 75.0], axis=1)
707 axisStdev = 0.74*(uq - lq)
712 axisStdev = np.where(axisStdev > 2.0 * np.median(axisStdev),
713 np.median(axisStdev), axisStdev)
716 diff = np.abs(imageArray - axisMedians[:, np.newaxis])
717 masked = np.ma.masked_where(diff > self.
statControl.getNumSigmaClip()
718 * axisStdev[:, np.newaxis], imageArray)
723 """Fill masked/NaN pixels in the overscan.
727 overscanVector : `np.array` or `np.ma.masked_array`
728 Overscan vector to fill.
732 overscanVector : `np.ma.masked_array`
737 Each maskSlice is a section of overscan with contiguous masks.
738 Ideally this adds 5 pixels from the left and right of that
739 mask slice, and takes the median of those values to fill the
740 slice. If this isn't possible, the median of all non-masked
741 values is used. The mask is removed for the pixels filled.
743 workingCopy = overscanVector
744 if not isinstance(overscanVector, np.ma.MaskedArray):
745 workingCopy = np.ma.masked_array(overscanVector,
746 mask=~np.isfinite(overscanVector))
748 defaultValue = np.median(workingCopy.data[~workingCopy.mask])
749 for maskSlice
in np.ma.clump_masked(workingCopy):
751 if maskSlice.start > 5:
752 neighborhood.extend(workingCopy[maskSlice.start - 5:maskSlice.start].data)
753 if maskSlice.stop < workingCopy.size - 5:
754 neighborhood.extend(workingCopy[maskSlice.stop:maskSlice.stop+5].data)
755 if len(neighborhood) > 0:
756 workingCopy.data[maskSlice] = np.nanmedian(neighborhood)
757 workingCopy.mask[maskSlice] =
False
759 workingCopy.data[maskSlice] = defaultValue
760 workingCopy.mask[maskSlice] =
False
764 """Collapse overscan array (and mask) to a 1-D vector of values.
768 maskedArray : `numpy.ma.masked_array`
769 Masked array of input overscan data.
770 fillMasked : `bool`, optional
771 If true, fill any pixels that are masked with a median of
776 collapsed : `numpy.ma.masked_array`
777 Single dimensional overscan data, combined with the mean.
780 collapsed = np.mean(maskedArray, axis=1)
781 if collapsed.mask.sum() > 0
and fillMasked:
787 """Wrapper function to match spline fit API to polynomial fit API.
791 indices : `numpy.ndarray`
792 Locations to evaluate the spline.
793 collapsed : `numpy.ndarray`
794 Collapsed overscan values corresponding to the spline
797 Number of bins to use in constructing the spline.
801 interp : `lsst.afw.math.Interpolate`
802 Interpolation object for later evaluation.
804 if not np.ma.is_masked(collapsed):
805 collapsed.mask = np.array(len(collapsed)*[np.ma.nomask])
807 numPerBin, binEdges = np.histogram(indices, bins=numBins,
808 weights=1 - collapsed.mask.astype(int))
809 with np.errstate(invalid=
"ignore"):
810 values = np.histogram(indices, bins=numBins,
811 weights=collapsed.data*~collapsed.mask)[0]/numPerBin
812 binCenters = np.histogram(indices, bins=numBins,
813 weights=indices*~collapsed.mask)[0]/numPerBin
815 if len(binCenters[numPerBin > 0]) < 5:
816 self.log.warning(
"Cannot do spline fitting for overscan: %s valid points.",
817 len(binCenters[numPerBin > 0]))
821 if len(values[numPerBin > 0]) != 0:
822 return float(values[numPerBin > 0][0])
827 values.astype(float)[numPerBin > 0],
833 """Wrapper function to match spline evaluation API to polynomial fit
838 indices : `numpy.ndarray`
839 Locations to evaluate the spline.
840 interp : `lsst.afw.math.interpolate`
841 Interpolation object to use.
845 values : `numpy.ndarray`
846 Evaluated spline values at each index.
849 return interp.interpolate(indices.astype(float))
853 """Create mask if edges are extrapolated.
857 collapsed : `numpy.ma.masked_array`
858 Masked array to check the edges of.
862 maskArray : `numpy.ndarray`
863 Boolean numpy array of pixels to mask.
865 maskArray = np.full_like(collapsed,
False, dtype=bool)
866 if np.ma.is_masked(collapsed):
868 for low
in range(num):
869 if not collapsed.mask[low]:
872 maskArray[:low] =
True
873 for high
in range(1, num):
874 if not collapsed.mask[-high]:
877 maskArray[-high:] =
True
881 """Calculate the 1-d vector overscan from the input overscan image.
885 image : `lsst.afw.image.MaskedImage`
886 Image containing the overscan data.
887 isTransposed : `bool`
888 If true, the image has been transposed.
892 results : `lsst.pipe.base.Struct`
893 Overscan result with entries:
896 Overscan value to subtract (`float`)
898 List of rows that should be masked as ``SUSPECT`` when the
899 overscan solution is applied. (`list` [ `bool` ])
901 Indicates if the overscan data was transposed during
902 calcuation, noting along which axis the overscan should be
909 calcImage = np.transpose(calcImage)
912 if self.config.fitType
in (
'MEDIAN_PER_ROW',
"MEAN_PER_ROW"):
913 if self.config.overscanIsInt:
914 mi = afwImage.MaskedImageI(image.getBBox())
915 masked = masked.astype(int)
920 masked = masked.transpose()
922 mi.image.array[:, :] = masked.data[:, :]
923 if bool(masked.mask.shape):
924 mi.mask.array[:, :] = masked.mask[:, :]
926 if self.config.fitType ==
"MEDIAN_PER_ROW":
937 indices = 2.0*np.arange(num)/float(num) - 1.0
941 'POLY': (poly.polynomial.polyfit, poly.polynomial.polyval),
942 'CHEB': (poly.chebyshev.chebfit, poly.chebyshev.chebval),
943 'LEG': (poly.legendre.legfit, poly.legendre.legval),
947 }[self.config.fitType]
951 coeffs = fitter(indices, collapsed, self.config.order)
953 if isinstance(coeffs, float):
954 self.log.warning(
"Using fallback value %f due to fitter failure. Amplifier will be masked.",
956 overscanVector = np.full_like(indices, coeffs)
957 maskArray = np.full_like(collapsed,
True, dtype=bool)
960 overscanVector = evaler(indices, coeffs)
963 return pipeBase.Struct(overscanValue=np.array(overscanVector),
965 isTransposed=isTransposed)
967 def debugView(self, image, model, amp=None, isTransposed=True):
968 """Debug display for the final overscan solution.
972 image : `lsst.afw.image.Image`
973 Input image the overscan solution was determined from.
974 model : `numpy.ndarray` or `float`
975 Overscan model determined for the image.
976 amp : `lsst.afw.cameraGeom.Amplifier`, optional
977 Amplifier to extract diagnostic information.
978 isTransposed : `bool`, optional
979 Does the data need to be transposed before display?
990 calcImage = np.transpose(calcImage)
995 indices = 2.0 * np.arange(num)/float(num) - 1.0
996 indices = np.arange(num)
998 if np.ma.is_masked(collapsed):
999 collapsedMask = collapsed.mask
1001 collapsedMask = np.array(num*[np.ma.nomask])
1003 import matplotlib.pyplot
as plot
1004 figure = plot.figure(1)
1006 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
1007 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask],
'k+')
1008 if collapsedMask.sum() > 0:
1009 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask],
'b+')
1010 if isinstance(model, np.ndarray):
1013 plotModel = np.zeros_like(indices)
1016 axes.plot(indices, plotModel,
'r-')
1017 plot.xlabel(
"position along overscan region")
1018 plot.ylabel(
"pixel value/fit value")
1020 plot.title(f
"{amp.getName()} DataX: "
1021 f
"[{amp.getRawDataBBox().getBeginX()}:{amp.getRawBBox().getEndX()}]"
1022 f
"OscanX: [{amp.getRawHorizontalOverscanBBox().getBeginX()}:"
1023 f
"{amp.getRawHorizontalOverscanBBox().getEndX()}] {self.config.fitType}")
1025 plot.title(
"No amp supplied.")
1027 prompt =
"Press Enter or c to continue [chp]..."
1029 ans = input(prompt).lower()
1030 if ans
in (
"",
" ",
"c",):
1032 elif ans
in (
"p", ):
1035 elif ans
in (
'x', ):
1038 elif ans
in (
"h", ):
1039 print(
"[h]elp [c]ontinue [p]db e[x]itDebug")
967 def debugView(self, image, model, amp=None, isTransposed=True):
…
1044 """Correction task for serial/parallel overscan.
1046 (Will be deprecated)
1048 This class contains a number of utilities that are easier to
1049 understand and use when they are not embedded in nested if/else
1054 statControl : `lsst.afw.math.StatisticsControl`, optional
1055 Statistics control object.
1057 ConfigClass = OverscanCorrectionTaskConfig
1058 _DefaultName =
"overscan"
1060 def run(self, exposure, amp, isTransposed=False):
1061 """Measure and remove serial/parallel overscan from an amplifier image.
1063 This will be deprecated.
1067 exposure : `lsst.afw.image.Exposure`
1068 Image data that will have the overscan corrections applied.
1069 amp : `lsst.afw.cameraGeom.Amplifier`
1070 Amplifier to use for debugging purposes.
1071 isTransposed : `bool`, optional
1072 Is the image transposed, such that serial and parallel
1073 overscan regions are reversed? Default is False.
1077 overscanResults : `lsst.pipe.base.Struct`
1078 Result struct with components:
1081 Value or fit subtracted from the amplifier image data
1082 (scalar or `lsst.afw.image.Image`).
1084 Value or fit subtracted from the serial overscan image
1085 data (scalar or `lsst.afw.image.Image`).
1087 Image of the serial overscan region with the serial
1088 overscan correction applied
1089 (`lsst.afw.image.Image`). This quantity is used to
1090 estimate the amplifier read noise empirically.
1091 ``parallelOverscanFit``
1092 Value or fit subtracted from the parallel overscan
1093 image data (scalar, `lsst.afw.image.Image`, or None).
1094 ``parallelOverscanImage``
1095 Image of the parallel overscan region with the
1096 parallel overscan correction applied
1097 (`lsst.afw.image.Image` or None).
1099 Mean of the fit serial overscan region.
1100 This and the following values will be tuples of
1101 (serial, parallel) if doParallelOverscan=True.
1103 Median of the fit serial overscan region.
1105 Sigma of the fit serial overscan region.
1107 Mean of the residual of the serial overscan region after
1110 Median of the residual of the serial overscan region after
1113 Mean of the residual of the serial overscan region after
1120 Raised if an invalid overscan type is set.
1123 serialOverscanBBox = amp.getRawSerialOverscanBBox()
1124 imageBBox = amp.getRawDataBBox()
1126 if self.config.doParallelOverscan:
1129 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1130 imageBBox = imageBBox.expandedTo(parallelOverscanBBox)
1134 geom.Point2I(serialOverscanBBox.getMinX(), imageBBox.getEndY()),
1135 geom.Extent2I(imageBBox.getWidth(), serialOverscanBBox.getHeight()),
1140 imageBBox.getMinY()),
1142 imageBBox.getHeight()),
1150 isTransposed=isTransposed,
1151 leadingToSkip=self.config.leadingColumnsToSkip,
1152 trailingToSkip=self.config.trailingColumnsToSkip,
1154 overscanMean = serialResults.overscanMean
1155 overscanMedian = serialResults.overscanMedian
1156 overscanSigma = serialResults.overscanSigma
1157 residualMean = serialResults.overscanMeanResidual
1158 residualMedian = serialResults.overscanMedianResidual
1159 residualSigma = serialResults.overscanSigmaResidual
1162 parallelResults =
None
1163 if self.config.doParallelOverscan:
1166 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1167 imageBBox = amp.getRawDataBBox()
1183 parallelOverscanBBox,
1184 isTransposed=
not isTransposed,
1185 leadingToSkip=self.config.leadingRowsToSkip,
1186 trailingToSkip=self.config.trailingRowsToSkip,
1188 overscanMean = (overscanMean, parallelResults.overscanMean)
1189 overscanMedian = (overscanMedian, parallelResults.overscanMedian)
1190 overscanSigma = (overscanSigma, parallelResults.overscanSigma)
1191 residualMean = (residualMean, parallelResults.overscanMeanResidual)
1192 residualMedian = (residualMedian, parallelResults.overscanMedianResidual)
1193 residualSigma = (residualSigma, parallelResults.overscanSigmaResidual)
1195 parallelOverscanFit = parallelResults.overscanOverscanModel
if parallelResults
else None
1196 parallelOverscanImage = parallelResults.overscanImage
if parallelResults
else None
1198 return pipeBase.Struct(imageFit=serialResults.ampOverscanModel,
1199 overscanFit=serialResults.overscanOverscanModel,
1200 overscanImage=serialResults.overscanImage,
1202 parallelOverscanFit=parallelOverscanFit,
1203 parallelOverscanImage=parallelOverscanImage,
1204 overscanMean=overscanMean,
1205 overscanMedian=overscanMedian,
1206 overscanSigma=overscanSigma,
1207 residualMean=residualMean,
1208 residualMedian=residualMedian,
1209 residualSigma=residualSigma)
1060 def run(self, exposure, amp, isTransposed=False):
…
1213 leadingToSkip = pexConfig.Field(
1215 doc=
"Number of leading values to skip in serial overscan correction.",
1218 trailingToSkip = pexConfig.Field(
1220 doc=
"Number of trailing values to skip in serial overscan correction.",
1226 """Correction task for serial overscan.
1230 statControl : `lsst.afw.math.StatisticsControl`, optional
1231 Statistics control object.
1233 ConfigClass = SerialOverscanCorrectionTaskConfig
1234 _DefaultName =
"serialOverscan"
1236 def run(self, exposure, amp, isTransposed=False):
1237 """Measure and remove serial overscan from an amplifier image.
1241 exposure : `lsst.afw.image.Exposure`
1242 Image data that will have the overscan corrections applied.
1243 amp : `lsst.afw.cameraGeom.Amplifier`
1244 Amplifier to use for debugging purposes.
1245 isTransposed : `bool`, optional
1246 Is the image transposed, such that serial and parallel
1247 overscan regions are reversed? Default is False.
1251 overscanResults : `lsst.pipe.base.Struct`
1252 Result struct with components:
1255 Value or fit subtracted from the amplifier image data
1256 (scalar or `lsst.afw.image.Image`).
1258 Value or fit subtracted from the serial overscan image
1259 data (scalar or `lsst.afw.image.Image`).
1261 Image of the serial overscan region with the serial
1262 overscan correction applied
1263 (`lsst.afw.image.Image`). This quantity is used to
1264 estimate the amplifier read noise empirically.
1266 Mean of the fit serial overscan region.
1268 Median of the fit serial overscan region.
1270 Sigma of the fit serial overscan region.
1272 Mean of the residual of the serial overscan region after
1275 Median of the residual of the serial overscan region after
1278 Mean of the residual of the serial overscan region after
1284 Raised if an invalid overscan type is set.
1286 serialOverscanBBox = amp.getRawSerialOverscanBBox()
1287 imageBBox = amp.getRawDataBBox()
1291 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1292 imageBBox = imageBBox.expandedTo(parallelOverscanBBox)
1296 geom.Point2I(serialOverscanBBox.getMinX(), imageBBox.getEndY()),
1297 geom.Extent2I(imageBBox.getWidth(), serialOverscanBBox.getHeight()),
1302 imageBBox.getMinY()),
1304 imageBBox.getHeight()),
1312 isTransposed=isTransposed,
1313 leadingToSkip=self.config.leadingToSkip,
1314 trailingToSkip=self.config.trailingToSkip,
1316 overscanMean = results.overscanMean
1317 overscanMedian = results.overscanMedian
1318 overscanSigma = results.overscanSigma
1319 residualMean = results.overscanMeanResidual
1320 residualMedian = results.overscanMedianResidual
1321 residualSigma = results.overscanSigmaResidual
1322 badRowsOrColumns = results.overscanBadRowsOrColumns
1324 return pipeBase.Struct(
1325 imageFit=results.ampOverscanModel,
1326 overscanFit=results.overscanOverscanModel,
1327 overscanImage=results.overscanImage,
1328 overscanMean=overscanMean,
1329 overscanMedian=overscanMedian,
1330 overscanSigma=overscanSigma,
1331 residualMean=residualMean,
1332 residualMedian=residualMedian,
1333 residualSigma=residualSigma,
1334 overscanBadRowsOrColumns=badRowsOrColumns,
1236 def run(self, exposure, amp, isTransposed=False):
…
1339 doParallelOverscanSaturation = pexConfig.Field(
1341 doc=
"Mask saturated pixels in parallel overscan region?",
1344 parallelOverscanSaturationLevel = pexConfig.Field(
1346 doc=
"The saturation level (adu) to use if not specified in call to "
1347 "maskParallelOverscanAmp. This should be low enough to capture "
1348 "all possible amplifiers for defect detection.",
1351 parallelOverscanSaturationLevelAdjustmentFactor = pexConfig.Field(
1353 doc=
"The parallel overscan saturation level may be below that of "
1354 "the data region. This factor is applied to the amplifier "
1355 "saturation value when evaluating saturation in the parallel "
1359 parallelOverscanMaskGrowSize = pexConfig.Field(
1361 doc=
"Grow the SAT mask in the parallel overscan region by this many pixels. "
1362 "This value was determined from the ITL chip in the LATISS camera.",
1365 parallelOverscanMaskedColumnGrowSize = pexConfig.Field(
1367 doc=
"When a full column is masked in the parallel overscan (at less "
1368 "than saturation) the mask should be grown by this many pixels. "
1369 "This value is determined from ITL chips in LATISS and LSSTCam.",
1372 leadingToSkip = pexConfig.Field(
1374 doc=
"Number of leading values to skip in parallel overscan correction.",
1377 trailingToSkip = pexConfig.Field(
1379 doc=
"Number of trailing values to skip in parallel overscan correction.",
1382 parallelOverscanFraction = pexConfig.Field(
1384 doc=
"When the parallel overscan region median is greater than parallelOverscanFraction "
1385 "and the imaging region median is greater than parallelOverscanImageThreshold "
1386 "then parallel overscan subtraction will be turned off, as this is usually "
1387 "due to the region being flooded with spillover from a super-saturated flat.",
1390 parallelOverscanImageThreshold = pexConfig.Field(
1392 doc=
"When the parallel overscan region median is greater than parallelOverscanFraction "
1393 "and the imaging region median is greater than parallelOverscanImageThreshold "
1394 "then parallel overscan subtraction will be turned off, as this is usually "
1395 "due to the region being flooded with spillover from a super-saturated flat.",
1398 doMedianSmoothingOutlierRejection = pexConfig.Field(
1400 doc=
"Do column-by-column median smoothing outlier rejection? Columns that are rejected "
1401 "in this way will be grown by parallelOverscanMaskedColumnGrowSize.",
1404 medianSmoothingKernel = pexConfig.Field(
1406 doc=
"Kernel (pixels) to use to smooth the parallel overscan columns. Must be odd.",
1408 check=
lambda x: x // 2 != x / 2,
1410 medianSmoothingOutlierThreshold = pexConfig.Field(
1412 doc=
"Outlier threshold after parallel median smoothing (adu). This is applied only "
1413 "to positive outliers.",
1415 check=
lambda x: x > 0.0,
1426 """Correction task for parallel overscan.
1430 statControl : `lsst.afw.math.StatisticsControl`, optional
1431 Statistics control object.
1433 ConfigClass = ParallelOverscanCorrectionTaskConfig
1434 _DefaultName =
"parallelOverscan"
1436 def run(self, exposure, amp, isTransposed=False):
1437 """Measure and remove parallel overscan from an amplifier image.
1439 This method assumes that serial overscan has already been
1440 removed from the amplifier.
1444 exposure : `lsst.afw.image.Exposure`
1445 Image data that will have the overscan corrections applied.
1446 amp : `lsst.afw.cameraGeom.Amplifier`
1447 Amplifier to use for debugging purposes.
1448 isTransposed : `bool`, optional
1449 Is the image transposed, such that serial and parallel
1450 overscan regions are reversed? Default is False.
1454 overscanResults : `lsst.pipe.base.Struct`
1455 Result struct with components:
1458 Value or fit subtracted from the amplifier image data
1459 (scalar or `lsst.afw.image.Image`).
1461 Value or fit subtracted from the parallel overscan image
1462 data (scalar or `lsst.afw.image.Image`).
1464 Image of the parallel overscan region with the parallel
1465 overscan correction applied
1466 (`lsst.afw.image.Image`). This quantity is used to
1467 estimate the amplifier read noise empirically.
1469 Mean of the fit parallel overscan region.
1471 Median of the fit parallel overscan region.
1473 Sigma of the fit parallel overscan region.
1475 Mean of the residual of the parallel overscan region after
1478 Median of the residual of the parallel overscan region after
1481 Mean of the residual of the parallel overscan region after
1487 Raised if an invalid overscan type is set.
1491 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1492 imageBBox = amp.getRawDataBBox()
1494 medianSmoothingKernel = self.config.medianSmoothingKernel
if \
1495 self.config.doMedianSmoothingOutlierRejection
else 0
1511 parallelOverscanBBox,
1512 isTransposed=
not isTransposed,
1513 leadingToSkip=self.config.leadingToSkip,
1514 trailingToSkip=self.config.trailingToSkip,
1515 overscanFraction=self.config.parallelOverscanFraction,
1516 imageThreshold=self.config.parallelOverscanImageThreshold,
1517 maskedRowColumnGrowSize=self.config.parallelOverscanMaskedColumnGrowSize,
1518 medianSmoothingKernel=medianSmoothingKernel,
1519 medianSmoothingOutlierThreshold=self.config.medianSmoothingOutlierThreshold,
1521 overscanMean = results.overscanMean
1522 overscanMedian = results.overscanMedian
1523 overscanSigma = results.overscanSigma
1524 residualMean = results.overscanMeanResidual
1525 residualMedian = results.overscanMedianResidual
1526 residualSigma = results.overscanSigmaResidual
1527 badRowsOrColumns = results.overscanBadRowsOrColumns
1529 return pipeBase.Struct(
1530 imageFit=results.ampOverscanModel,
1531 overscanFit=results.overscanOverscanModel,
1532 overscanImage=results.overscanImage,
1533 overscanMean=overscanMean,
1534 overscanMedian=overscanMedian,
1535 overscanSigma=overscanSigma,
1536 residualMean=residualMean,
1537 residualMedian=residualMedian,
1538 residualSigma=residualSigma,
1539 badRowsOrColumns=badRowsOrColumns,
1436 def run(self, exposure, amp, isTransposed=False):
…
1543 """Mask parallel overscan, growing saturated pixels.
1545 This operates on the image in-place.
1549 exposure : `lsst.afw.image.Exposure`
1550 An untrimmed raw exposure.
1551 amp : `lsst.afw.cameraGeom.Amplifier`
1552 The amplifier to use for masking.
1553 saturationLevel : `float`, optional
1554 Saturation level to use for masking.
1556 if not self.config.doParallelOverscanSaturation:
1560 if saturationLevel
is None:
1561 saturationLevel = self.config.parallelOverscanSaturationLevel
1563 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
1564 amp.getRawParallelOverscanBBox(),
1568 maskedImage=dataView,
1569 threshold=saturationLevel,
1570 growFootprints=self.config.parallelOverscanMaskGrowSize,
Pass parameters to a Statistics object.
An integer coordinate rectangle.
debugView(self, image, model, amp=None, isTransposed=True)
splineEval(indices, interp)
fitOverscan(self, overscanImage, isTransposed=False)
measureConstantOverscan(self, image)
maskOutliers(self, imageArray)
getImageArray(self, image)
collapseArray(self, maskedArray, fillMasked=True)
trimOverscan(self, exposure, amp, bbox, skipLeading, skipTrailing, transpose=False)
maskParallelOverscan(self, exposure, detector)
fillMaskedPixels(self, overscanVector)
maskExtrapolated(collapsed)
broadcastFitToImage(self, overscanValue, imageArray, transpose=False)
correctOverscan(self, exposure, amp, imageBBox, overscanBBox, isTransposed=True, leadingToSkip=0, trailingToSkip=0, overscanFraction=1.0, imageThreshold=np.inf, maskedRowColumnGrowSize=0, medianSmoothingKernel=0, medianSmoothingOutlierThreshold=np.inf)
splineFit(self, indices, collapsed, numBins)
run(self, exposure, amp, isTransposed=False)
__init__(self, statControl=None, **kwargs)
measureVectorOverscan(self, image, isTransposed=False)
_maskRowsOrColumns(exposure, overscanBBox, overscanMaskedImage, overscanMask, maxDeviation, maskedRowColumnGrowSize, medianSmoothingKernel, medianSmoothingOutlierThreshold, doAbsoluteMaxDeviation, isTransposed)
run(self, exposure, amp, isTransposed=False)
maskParallelOverscanAmp(self, exposure, amp, saturationLevel=None)
run(self, exposure, amp, isTransposed=False)
run(self, exposure, amp, isTransposed=False)
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.
makeThresholdMask(maskedImage, threshold, growFootprints=1, maskName='SAT')
std::vector< double > fitOverscanImageMean(lsst::afw::image::MaskedImage< ImagePixelT > const &overscan, std::vector< std::string > badPixelMask, bool isTransposed)
std::vector< double > fitOverscanImage(lsst::afw::image::MaskedImage< ImagePixelT > const &overscan, std::vector< std::string > badPixelMask, bool isTransposed)