LSST Applications g180d380827+6621f76652,g2079a07aa2+86d27d4dc4,g2305ad1205+f5a9e323a1,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3ddfee87b4+9a10e1fe7b,g48712c4677+c9a099281a,g487adcacf7+f2e03ea30b,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+aead732c78,g64a986408d+eddffb812c,g858d7b2824+eddffb812c,g864b0138d7+aa38e45daa,g974c55ee3d+f37bf00e57,g99cad8db69+119519a52d,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+eddffb812c,gba4ed39666+c2a2e4ac27,gbb8dafda3b+27317ec8e9,gbd998247f1+585e252eca,gc120e1dc64+5817c176a8,gc28159a63d+c6a8a0fb72,gc3e9b769f7+6707aea8b4,gcf0d15dbbd+9a10e1fe7b,gdaeeff99f8+f9a426f77a,ge6526c86ff+6a2e01d432,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,gff1a9f87cc+eddffb812c,v27.0.0.rc1
LSST Data Management Base Package
Loading...
Searching...
No Matches
overscan.py
Go to the documentation of this file.
1# This file is part of ip_isr.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21
22__all__ = ["OverscanCorrectionTaskConfig", "OverscanCorrectionTask",
23 "SerialOverscanCorrectionTaskConfig", "SerialOverscanCorrectionTask",
24 "ParallelOverscanCorrectionTaskConfig", "ParallelOverscanCorrectionTask",
25 ]
26
27import numpy as np
28import lsst.afw.math as afwMath
29import lsst.afw.image as afwImage
30import lsst.geom as geom
31import lsst.pipe.base as pipeBase
32import lsst.pex.config as pexConfig
33
34from .isr import fitOverscanImage
35from .isrFunctions import makeThresholdMask
36
37
38class OverscanCorrectionTaskConfigBase(pexConfig.Config):
39 """Overscan correction options.
40 """
41 fitType = pexConfig.ChoiceField(
42 dtype=str,
43 doc="The method for fitting the overscan bias level.",
44 default='MEDIAN',
45 allowed={
46 "POLY": "Fit ordinary polynomial to the longest axis of the overscan region",
47 "CHEB": "Fit Chebyshev polynomial to the longest axis of the overscan region",
48 "LEG": "Fit Legendre polynomial to the longest axis of the overscan region",
49 "NATURAL_SPLINE": "Fit natural spline to the longest axis of the overscan region",
50 "CUBIC_SPLINE": "Fit cubic spline to the longest axis of the overscan region",
51 "AKIMA_SPLINE": "Fit Akima spline to the longest axis of the overscan region",
52 "MEAN": "Correct using the mean of the overscan region",
53 "MEANCLIP": "Correct using a clipped mean of the overscan region",
54 "MEDIAN": "Correct using the median of the overscan region",
55 "MEDIAN_PER_ROW": "Correct using the median per row of the overscan region",
56 },
57 )
58 order = pexConfig.Field(
59 dtype=int,
60 doc=("Order of polynomial to fit if overscan fit type is a polynomial, "
61 "or number of spline knots if overscan fit type is a spline."),
62 default=1,
63 )
64 numSigmaClip = pexConfig.Field(
65 dtype=float,
66 doc="Rejection threshold (sigma) for collapsing overscan before fit",
67 default=3.0,
68 )
69 maskPlanes = pexConfig.ListField(
70 dtype=str,
71 doc="Mask planes to reject when measuring overscan",
72 default=['BAD', 'SAT'],
73 )
74 overscanIsInt = pexConfig.Field(
75 dtype=bool,
76 doc="Treat overscan as an integer image for purposes of fitType=MEDIAN"
77 " and fitType=MEDIAN_PER_ROW.",
78 default=True,
79 )
80 maxDeviation = pexConfig.Field(
81 dtype=float,
82 doc="Maximum deviation from median (in ADU) to mask in overscan correction.",
83 default=1000.0, check=lambda x: x > 0,
84 )
85
86
88 doParallelOverscan = pexConfig.Field(
89 dtype=bool,
90 doc="Correct using parallel overscan after serial overscan correction?",
91 default=False,
92 )
93 parallelOverscanMaskThreshold = pexConfig.Field(
94 dtype=int,
95 doc="Threshold above which pixels in the parallel overscan are masked as bleeds.",
96 default=100000,
97 )
98 parallelOverscanMaskGrowSize = pexConfig.Field(
99 dtype=int,
100 doc="Masks created from saturated bleeds should be grown by this many "
101 "pixels during construction of the parallel overscan mask. "
102 "This value determined from the ITL chip in the LATISS camera",
103 default=7,
104 )
105 leadingColumnsToSkip = pexConfig.Field(
106 dtype=int,
107 doc="Number of leading columns to skip in serial overscan correction.",
108 default=0,
109 )
110 trailingColumnsToSkip = pexConfig.Field(
111 dtype=int,
112 doc="Number of trailing columns to skip in serial overscan correction.",
113 default=0,
114 )
115 leadingRowsToSkip = pexConfig.Field(
116 dtype=int,
117 doc="Number of leading rows to skip in parallel overscan correction.",
118 default=0,
119 )
120 trailingRowsToSkip = pexConfig.Field(
121 dtype=int,
122 doc="Number of trailing rows to skip in parallel overscan correction.",
123 default=0,
124 )
125
126
127class OverscanCorrectionTaskBase(pipeBase.Task):
128 """Base Correction task for overscan.
129
130 This class contains a number of utilities that are easier to
131 understand and use when they are not embedded in nested if/else
132 loops.
133
134 Parameters
135 ----------
136 statControl : `lsst.afw.math.StatisticsControl`, optional
137 Statistics control object.
138 """
139 ConfigClass = OverscanCorrectionTaskConfigBase
140 _DefaultName = "overscanBase"
141
142 def __init__(self, statControl=None, **kwargs):
143 super().__init__(**kwargs)
144 self.allowDebug = True
145
146 if statControl:
147 self.statControl = statControl
148 else:
150 self.statControl.setNumSigmaClip(self.config.numSigmaClip)
151 self.statControl.setAndMask(afwImage.Mask.getPlaneBitMask(self.config.maskPlanes))
152
153 def run(self, exposure, amp, isTransposed=False):
154 raise NotImplementedError("run method is not defined for OverscanCorrectionTaskBase")
155
156 def correctOverscan(self, exposure, amp, imageBBox, overscanBBox,
157 isTransposed=True, leadingToSkip=0, trailingToSkip=0):
158 """Trim the exposure, fit the overscan, subtract the fit, and
159 calculate statistics.
160
161 Parameters
162 ----------
163 exposure : `lsst.afw.image.Exposure`
164 Exposure containing the data.
165 amp : `lsst.afw.cameraGeom.Amplifier`
166 The amplifier that is to be corrected.
167 imageBBox: `lsst.geom.Box2I`
168 Bounding box of the image data that will have the overscan
169 subtracted. If parallel overscan will be performed, that
170 area is added to the image bounding box during serial
171 overscan correction.
172 overscanBBox: `lsst.geom.Box2I`
173 Bounding box for the overscan data.
174 isTransposed: `bool`
175 If true, then the data will be transposed before fitting
176 the overscan.
177 leadingToSkip : `int`, optional
178 Leading rows/columns to skip.
179 trailingToSkip : `int`, optional
180 Leading rows/columns to skip.
181
182 Returns
183 -------
184 results : `lsst.pipe.base.Struct`
185 ``ampOverscanModel``
186 Overscan model broadcast to the full image size.
187 (`lsst.afw.image.Exposure`)
188 ``overscanOverscanModel``
189 Overscan model broadcast to the full overscan image
190 size. (`lsst.afw.image.Exposure`)
191 ``overscanImage``
192 Overscan image with the overscan fit subtracted.
193 (`lsst.afw.image.Exposure`)
194 ``overscanValue``
195 Overscan model. (`float` or `np.array`)
196 ``overscanMean``
197 Mean value of the overscan fit. (`float`)
198 ``overscanMedian``
199 Median value of the overscan fit. (`float`)
200 ``overscanSigma``
201 Standard deviation of the overscan fit. (`float`)
202 ``overscanMeanResidual``
203 Mean value of the overscan region after overscan
204 subtraction. (`float`)
205 ``overscanMedianResidual``
206 Median value of the overscan region after overscan
207 subtraction. (`float`)
208 ``overscanSigmaResidual``
209 Standard deviation of the overscan region after
210 overscan subtraction. (`float`)
211 """
212 overscanBox = self.trimOverscan(exposure, amp, overscanBBox,
213 leadingToSkip,
214 trailingToSkip,
215 transpose=isTransposed)
216 overscanImage = exposure[overscanBox].getMaskedImage()
217 overscanArray = overscanImage.image.array
218
219 # Mask pixels.
220 maskVal = overscanImage.mask.getPlaneBitMask(self.config.maskPlanes)
221 overscanMask = ~((overscanImage.mask.array & maskVal) == 0)
222
223 median = np.ma.median(np.ma.masked_where(overscanMask, overscanArray))
224 bad = np.where(np.abs(overscanArray - median) > self.config.maxDeviation)
225 overscanImage.mask.array[bad] = overscanImage.mask.getPlaneBitMask("SAT")
226
227 # Do overscan fit.
228 # CZW: Handle transposed correctly.
229 overscanResults = self.fitOverscan(overscanImage, isTransposed=isTransposed)
230
231 # Correct image region (and possibly parallel-overscan region).
232 ampImage = exposure[imageBBox]
233 ampOverscanModel = self.broadcastFitToImage(overscanResults.overscanValue,
234 ampImage.image.array,
235 transpose=isTransposed)
236 ampImage.image.array -= ampOverscanModel
237
238 # Correct overscan region (and possibly doubly-overscaned
239 # region).
240 overscanImage = exposure[overscanBBox]
241 # CZW: Transposed?
242 overscanOverscanModel = self.broadcastFitToImage(overscanResults.overscanValue,
243 overscanImage.image.array)
244 self.debugView(overscanImage, overscanResults.overscanValue, amp, isTransposed=isTransposed)
245 overscanImage.image.array -= overscanOverscanModel
246
247 # Find residual fit statistics.
248 stats = afwMath.makeStatistics(overscanImage.getMaskedImage(),
249 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP, self.statControl)
250 residualMean = stats.getValue(afwMath.MEAN)
251 residualMedian = stats.getValue(afwMath.MEDIAN)
252 residualSigma = stats.getValue(afwMath.STDEVCLIP)
253
254 return pipeBase.Struct(ampOverscanModel=ampOverscanModel,
255 overscanOverscanModel=overscanOverscanModel,
256 overscanImage=overscanImage,
257 overscanValue=overscanResults.overscanValue,
258
259 overscanMean=overscanResults.overscanMean,
260 overscanMedian=overscanResults.overscanMedian,
261 overscanSigma=overscanResults.overscanSigma,
262 overscanMeanResidual=residualMean,
263 overscanMedianResidual=residualMedian,
264 overscanSigmaResidual=residualSigma
265 )
266
267 def broadcastFitToImage(self, overscanValue, imageArray, transpose=False):
268 """Broadcast 0 or 1 dimension fit to appropriate shape.
269
270 Parameters
271 ----------
272 overscanValue : `numpy.ndarray`, (Nrows, ) or scalar
273 Overscan fit to broadcast.
274 imageArray : `numpy.ndarray`, (Nrows, Ncols)
275 Image array that we want to match.
276 transpose : `bool`, optional
277 Switch order to broadcast along the other axis.
278
279 Returns
280 -------
281 overscanModel : `numpy.ndarray`, (Nrows, Ncols) or scalar
282 Expanded overscan fit.
283
284 Raises
285 ------
286 RuntimeError
287 Raised if no axis has the appropriate dimension.
288 """
289 if isinstance(overscanValue, np.ndarray):
290 overscanModel = np.zeros_like(imageArray)
291
292 if transpose is False:
293 if imageArray.shape[0] == overscanValue.shape[0]:
294 overscanModel[:, :] = overscanValue[:, np.newaxis]
295 elif imageArray.shape[1] == overscanValue.shape[0]:
296 overscanModel[:, :] = overscanValue[np.newaxis, :]
297 elif imageArray.shape[0] == overscanValue.shape[1]:
298 overscanModel[:, :] = overscanValue[np.newaxis, :]
299 else:
300 raise RuntimeError(f"Could not broadcast {overscanValue.shape} to "
301 f"match {imageArray.shape}")
302 else:
303 if imageArray.shape[1] == overscanValue.shape[0]:
304 overscanModel[:, :] = overscanValue[np.newaxis, :]
305 elif imageArray.shape[0] == overscanValue.shape[0]:
306 overscanModel[:, :] = overscanValue[:, np.newaxis]
307 elif imageArray.shape[1] == overscanValue.shape[1]:
308 overscanModel[:, :] = overscanValue[:, np.newaxis]
309 else:
310 raise RuntimeError(f"Could not broadcast {overscanValue.shape} to "
311 f"match {imageArray.shape}")
312 else:
313 overscanModel = overscanValue
314
315 return overscanModel
316
317 def trimOverscan(self, exposure, amp, bbox, skipLeading, skipTrailing, transpose=False):
318 """Trim overscan region to remove edges.
319
320 Parameters
321 ----------
322 exposure : `lsst.afw.image.Exposure`
323 Exposure containing data.
324 amp : `lsst.afw.cameraGeom.Amplifier`
325 Amplifier containing geometry information.
326 bbox : `lsst.geom.Box2I`
327 Bounding box of the overscan region.
328 skipLeading : `int`
329 Number of leading (towards data region) rows/columns to skip.
330 skipTrailing : `int`
331 Number of trailing (away from data region) rows/columns to skip.
332 transpose : `bool`, optional
333 Operate on the transposed array.
334
335 Returns
336 -------
337 overscanArray : `numpy.array`, (N, M)
338 Data array to fit.
339 overscanMask : `numpy.array`, (N, M)
340 Data mask.
341 """
342 dx0, dy0, dx1, dy1 = (0, 0, 0, 0)
343 dataBBox = amp.getRawDataBBox()
344 if transpose:
345 if dataBBox.getBeginY() < bbox.getBeginY():
346 dy0 += skipLeading
347 dy1 -= skipTrailing
348 else:
349 dy0 += skipTrailing
350 dy1 -= skipLeading
351 else:
352 if dataBBox.getBeginX() < bbox.getBeginX():
353 dx0 += skipLeading
354 dx1 -= skipTrailing
355 else:
356 dx0 += skipTrailing
357 dx1 -= skipLeading
358
359 overscanBBox = geom.Box2I(bbox.getBegin() + geom.Extent2I(dx0, dy0),
360 geom.Extent2I(bbox.getWidth() - dx0 + dx1,
361 bbox.getHeight() - dy0 + dy1))
362 return overscanBBox
363
364 def fitOverscan(self, overscanImage, isTransposed=False):
365 if self.config.fitType in ('MEAN', 'MEANCLIP', 'MEDIAN'):
366 # Transposition has no effect here.
367 overscanResult = self.measureConstantOverscan(overscanImage)
368 overscanValue = overscanResult.overscanValue
369 overscanMean = overscanValue
370 overscanMedian = overscanValue
371 overscanSigma = 0.0
372 elif self.config.fitType in ('MEDIAN_PER_ROW', 'POLY', 'CHEB', 'LEG',
373 'NATURAL_SPLINE', 'CUBIC_SPLINE', 'AKIMA_SPLINE'):
374 # Force transposes as needed
375 overscanResult = self.measureVectorOverscan(overscanImage, isTransposed)
376 overscanValue = overscanResult.overscanValue
377
378 stats = afwMath.makeStatistics(overscanResult.overscanValue,
379 afwMath.MEAN | afwMath.MEDIAN | afwMath.STDEVCLIP,
380 self.statControl)
381 overscanMean = stats.getValue(afwMath.MEAN)
382 overscanMedian = stats.getValue(afwMath.MEDIAN)
383 overscanSigma = stats.getValue(afwMath.STDEVCLIP)
384 else:
385 raise ValueError('%s : %s an invalid overscan type' %
386 ("overscanCorrection", self.config.fitType))
387
388 return pipeBase.Struct(overscanValue=overscanValue,
389 overscanMean=overscanMean,
390 overscanMedian=overscanMedian,
391 overscanSigma=overscanSigma,
392 )
393
394 @staticmethod
395 def integerConvert(image):
396 """Return an integer version of the input image.
397
398 Parameters
399 ----------
400 image : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
401 Image to convert to integers.
402
403 Returns
404 -------
405 outI : `numpy.ndarray`, `lsst.afw.image.Image` or `MaskedImage`
406 The integer converted image.
407
408 Raises
409 ------
410 RuntimeError
411 Raised if the input image could not be converted.
412 """
413 if hasattr(image, "image"):
414 # Is a maskedImage:
415 imageI = image.image.convertI()
416 outI = afwImage.MaskedImageI(imageI, image.mask, image.variance)
417 elif hasattr(image, "convertI"):
418 # Is an Image:
419 outI = image.convertI()
420 elif hasattr(image, "astype"):
421 # Is a numpy array:
422 outI = image.astype(int)
423 else:
424 raise RuntimeError("Could not convert this to integers: %s %s %s",
425 image, type(image), dir(image))
426 return outI
427
428 def maskParallelOverscan(self, exposure, detector):
429 """Mask the union of high values on all amplifiers in the parallel
430 overscan.
431
432 This operates on the image in-place.
433
434 Parameters
435 ----------
436 exposure : `lsst.afw.image.Exposure`
437 An untrimmed raw exposure.
438 detector : `lsst.afw.cameraGeom.Detector`
439 The detetor to use for amplifier geometry.
440 """
441 parallelMask = None
442
443 for amp in detector:
444 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
445 amp.getRawParallelOverscanBBox(),
446 afwImage.PARENT)
447 makeThresholdMask(
448 maskedImage=dataView,
449 threshold=self.config.parallelOverscanMaskThreshold,
450 growFootprints=self.config.parallelOverscanMaskGrowSize,
451 maskName="BAD"
452 )
453 if parallelMask is None:
454 parallelMask = dataView.mask.array
455 else:
456 parallelMask |= dataView.mask.array
457 for amp in detector:
458 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
459 amp.getRawParallelOverscanBBox(),
460 afwImage.PARENT)
461 dataView.mask.array |= parallelMask
462
463 # Constant methods
464 def measureConstantOverscan(self, image):
465 """Measure a constant overscan value.
466
467 Parameters
468 ----------
469 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
470 Image data to measure the overscan from.
471
472 Returns
473 -------
474 results : `lsst.pipe.base.Struct`
475 Overscan result with entries:
476 - ``overscanValue``: Overscan value to subtract (`float`)
477 - ``isTransposed``: Orientation of the overscan (`bool`)
478 """
479 fitType = afwMath.stringToStatisticsProperty(self.config.fitType)
480 overscanValue = afwMath.makeStatistics(image, fitType, self.statControl).getValue()
481
482 return pipeBase.Struct(overscanValue=overscanValue,
483 isTransposed=False)
484
485 # Vector correction utilities
486 def getImageArray(self, image):
487 """Extract the numpy array from the input image.
488
489 Parameters
490 ----------
491 image : `lsst.afw.image.Image` or `lsst.afw.image.MaskedImage`
492 Image data to pull array from.
493
494 calcImage : `numpy.ndarray`
495 Image data array for numpy operating.
496 """
497 if hasattr(image, "getImage"):
498 calcImage = image.getImage().getArray()
499 calcImage = np.ma.masked_where(image.getMask().getArray() & self.statControl.getAndMask(),
500 calcImage)
501 else:
502 calcImage = image.getArray()
503 return calcImage
504
505 def maskOutliers(self, imageArray):
506 """Mask outliers in a row of overscan data from a robust sigma
507 clipping procedure.
508
509 Parameters
510 ----------
511 imageArray : `numpy.ndarray`
512 Image to filter along numpy axis=1.
513
514 Returns
515 -------
516 maskedArray : `numpy.ma.masked_array`
517 Masked image marking outliers.
518 """
519 lq, median, uq = np.percentile(np.ma.getdata(imageArray),
520 [25.0, 50.0, 75.0], axis=1)
521 axisMedians = median
522 axisStdev = 0.74*(uq - lq) # robust stdev
523
524 # Replace pixels that have excessively large stdev values
525 # with the median of stdev values. A large stdev likely
526 # indicates a bleed is spilling into the overscan.
527 axisStdev = np.where(axisStdev > 2.0 * np.median(axisStdev),
528 np.median(axisStdev), axisStdev)
529
530 # Mask pixels that are N-sigma away from their array medians.
531 diff = np.abs(imageArray - axisMedians[:, np.newaxis])
532 masked = np.ma.masked_where(diff > self.statControl.getNumSigmaClip()
533 * axisStdev[:, np.newaxis], imageArray)
534
535 return masked
536
537 def fillMaskedPixels(self, overscanVector):
538 """Fill masked/NaN pixels in the overscan.
539
540 Parameters
541 ----------
542 overscanVector : `np.array` or `np.ma.masked_array`
543 Overscan vector to fill.
544
545 Returns
546 -------
547 overscanVector : `np.ma.masked_array`
548 Filled vector.
549
550 Notes
551 -----
552 Each maskSlice is a section of overscan with contiguous masks.
553 Ideally this adds 5 pixels from the left and right of that
554 mask slice, and takes the median of those values to fill the
555 slice. If this isn't possible, the median of all non-masked
556 values is used. The mask is removed for the pixels filled.
557 """
558 workingCopy = overscanVector
559 if not isinstance(overscanVector, np.ma.MaskedArray):
560 workingCopy = np.ma.masked_array(overscanVector,
561 mask=~np.isfinite(overscanVector))
562
563 defaultValue = np.median(workingCopy.data[~workingCopy.mask])
564 for maskSlice in np.ma.clump_masked(workingCopy):
565 neighborhood = []
566 if maskSlice.start > 5:
567 neighborhood.extend(workingCopy[maskSlice.start - 5:maskSlice.start].data)
568 if maskSlice.stop < workingCopy.size - 5:
569 neighborhood.extend(workingCopy[maskSlice.stop:maskSlice.stop+5].data)
570 if len(neighborhood) > 0:
571 workingCopy.data[maskSlice] = np.nanmedian(neighborhood)
572 workingCopy.mask[maskSlice] = False
573 else:
574 workingCopy.data[maskSlice] = defaultValue
575 workingCopy.mask[maskSlice] = False
576 return workingCopy
577
578 def collapseArray(self, maskedArray, fillMasked=True):
579 """Collapse overscan array (and mask) to a 1-D vector of values.
580
581 Parameters
582 ----------
583 maskedArray : `numpy.ma.masked_array`
584 Masked array of input overscan data.
585 fillMasked : `bool`, optional
586 If true, fill any pixels that are masked with a median of
587 neighbors.
588
589 Returns
590 -------
591 collapsed : `numpy.ma.masked_array`
592 Single dimensional overscan data, combined with the mean.
593
594 """
595 collapsed = np.mean(maskedArray, axis=1)
596 if collapsed.mask.sum() > 0 and fillMasked:
597 collapsed = self.fillMaskedPixels(collapsed)
598
599 return collapsed
600
601 def collapseArrayMedian(self, maskedArray):
602 """Collapse overscan array (and mask) to a 1-D vector of using the
603 correct integer median of row-values.
604
605 Parameters
606 ----------
607 maskedArray : `numpy.ma.masked_array`
608 Masked array of input overscan data.
609
610 Returns
611 -------
612 collapsed : `numpy.ma.masked_array`
613 Single dimensional overscan data, combined with the afwMath median.
614 """
615 integerMI = self.integerConvert(maskedArray)
616
617 collapsed = []
618 fitType = afwMath.stringToStatisticsProperty('MEDIAN')
619 for row in integerMI:
620 newRow = row.compressed()
621 if len(newRow) > 0:
622 rowMedian = afwMath.makeStatistics(newRow, fitType, self.statControl).getValue()
623 else:
624 rowMedian = np.nan
625 collapsed.append(rowMedian)
626
627 return np.array(collapsed)
628
629 def splineFit(self, indices, collapsed, numBins):
630 """Wrapper function to match spline fit API to polynomial fit API.
631
632 Parameters
633 ----------
634 indices : `numpy.ndarray`
635 Locations to evaluate the spline.
636 collapsed : `numpy.ndarray`
637 Collapsed overscan values corresponding to the spline
638 evaluation points.
639 numBins : `int`
640 Number of bins to use in constructing the spline.
641
642 Returns
643 -------
644 interp : `lsst.afw.math.Interpolate`
645 Interpolation object for later evaluation.
646 """
647 if not np.ma.is_masked(collapsed):
648 collapsed.mask = np.array(len(collapsed)*[np.ma.nomask])
649
650 numPerBin, binEdges = np.histogram(indices, bins=numBins,
651 weights=1 - collapsed.mask.astype(int))
652 with np.errstate(invalid="ignore"):
653 values = np.histogram(indices, bins=numBins,
654 weights=collapsed.data*~collapsed.mask)[0]/numPerBin
655 binCenters = np.histogram(indices, bins=numBins,
656 weights=indices*~collapsed.mask)[0]/numPerBin
657
658 if len(binCenters[numPerBin > 0]) < 5:
659 self.log.warning("Cannot do spline fitting for overscan: %s valid points.",
660 len(binCenters[numPerBin > 0]))
661 # Return a scalar value if we have one, otherwise
662 # return zero. This amplifier is hopefully already
663 # masked.
664 if len(values[numPerBin > 0]) != 0:
665 return float(values[numPerBin > 0][0])
666 else:
667 return 0.0
668
669 interp = afwMath.makeInterpolate(binCenters.astype(float)[numPerBin > 0],
670 values.astype(float)[numPerBin > 0],
671 afwMath.stringToInterpStyle(self.config.fitType))
672 return interp
673
674 @staticmethod
675 def splineEval(indices, interp):
676 """Wrapper function to match spline evaluation API to polynomial fit
677 API.
678
679 Parameters
680 ----------
681 indices : `numpy.ndarray`
682 Locations to evaluate the spline.
683 interp : `lsst.afw.math.interpolate`
684 Interpolation object to use.
685
686 Returns
687 -------
688 values : `numpy.ndarray`
689 Evaluated spline values at each index.
690 """
691
692 return interp.interpolate(indices.astype(float))
693
694 @staticmethod
695 def maskExtrapolated(collapsed):
696 """Create mask if edges are extrapolated.
697
698 Parameters
699 ----------
700 collapsed : `numpy.ma.masked_array`
701 Masked array to check the edges of.
702
703 Returns
704 -------
705 maskArray : `numpy.ndarray`
706 Boolean numpy array of pixels to mask.
707 """
708 maskArray = np.full_like(collapsed, False, dtype=bool)
709 if np.ma.is_masked(collapsed):
710 num = len(collapsed)
711 for low in range(num):
712 if not collapsed.mask[low]:
713 break
714 if low > 0:
715 maskArray[:low] = True
716 for high in range(1, num):
717 if not collapsed.mask[-high]:
718 break
719 if high > 1:
720 maskArray[-high:] = True
721 return maskArray
722
723 def measureVectorOverscan(self, image, isTransposed=False):
724 """Calculate the 1-d vector overscan from the input overscan image.
725
726 Parameters
727 ----------
728 image : `lsst.afw.image.MaskedImage`
729 Image containing the overscan data.
730 isTransposed : `bool`
731 If true, the image has been transposed.
732
733 Returns
734 -------
735 results : `lsst.pipe.base.Struct`
736 Overscan result with entries:
737
738 ``overscanValue``
739 Overscan value to subtract (`float`)
740 ``maskArray``
741 List of rows that should be masked as ``SUSPECT`` when the
742 overscan solution is applied. (`list` [ `bool` ])
743 ``isTransposed``
744 Indicates if the overscan data was transposed during
745 calcuation, noting along which axis the overscan should be
746 subtracted. (`bool`)
747 """
748 calcImage = self.getImageArray(image)
749
750 # operate on numpy-arrays from here
751 if isTransposed:
752 calcImage = np.transpose(calcImage)
753 masked = self.maskOutliers(calcImage)
754
755 if self.config.fitType == 'MEDIAN_PER_ROW':
756 mi = afwImage.MaskedImageI(image.getBBox())
757 masked = masked.astype(int)
758 if isTransposed:
759 masked = masked.transpose()
760
761 mi.image.array[:, :] = masked.data[:, :]
762 if bool(masked.mask.shape):
763 mi.mask.array[:, :] = masked.mask[:, :]
764
765 overscanVector = fitOverscanImage(mi, self.config.maskPlanes, isTransposed)
766 overscanVector = self.fillMaskedPixels(overscanVector)
767 maskArray = self.maskExtrapolated(overscanVector)
768 else:
769 collapsed = self.collapseArray(masked)
770
771 num = len(collapsed)
772 indices = 2.0*np.arange(num)/float(num) - 1.0
773
774 poly = np.polynomial
775 fitter, evaler = {
776 'POLY': (poly.polynomial.polyfit, poly.polynomial.polyval),
777 'CHEB': (poly.chebyshev.chebfit, poly.chebyshev.chebval),
778 'LEG': (poly.legendre.legfit, poly.legendre.legval),
779 'NATURAL_SPLINE': (self.splineFitsplineFit, self.splineEvalsplineEval),
780 'CUBIC_SPLINE': (self.splineFitsplineFit, self.splineEvalsplineEval),
781 'AKIMA_SPLINE': (self.splineFitsplineFit, self.splineEvalsplineEval)
782 }[self.config.fitType]
783
784 # These are the polynomial coefficients, or an
785 # interpolation object.
786 coeffs = fitter(indices, collapsed, self.config.order)
787
788 if isinstance(coeffs, float):
789 self.log.warning("Using fallback value %f due to fitter failure. Amplifier will be masked.",
790 coeffs)
791 overscanVector = np.full_like(indices, coeffs)
792 maskArray = np.full_like(collapsed, True, dtype=bool)
793 else:
794 # Otherwise we can just use things as normal.
795 overscanVector = evaler(indices, coeffs)
796 maskArray = self.maskExtrapolated(collapsed)
797
798 return pipeBase.Struct(overscanValue=np.array(overscanVector),
799 maskArray=maskArray,
800 isTransposed=isTransposed)
801
802 def debugView(self, image, model, amp=None, isTransposed=True):
803 """Debug display for the final overscan solution.
804
805 Parameters
806 ----------
807 image : `lsst.afw.image.Image`
808 Input image the overscan solution was determined from.
809 model : `numpy.ndarray` or `float`
810 Overscan model determined for the image.
811 amp : `lsst.afw.cameraGeom.Amplifier`, optional
812 Amplifier to extract diagnostic information.
813 isTransposed : `bool`, optional
814 Does the data need to be transposed before display?
815 """
816 import lsstDebug
817 if not lsstDebug.Info(__name__).display:
818 return
819 if not self.allowDebug:
820 return
821
822 calcImage = self.getImageArray(image)
823 # CZW: Check that this is ok
824 if isTransposed:
825 calcImage = np.transpose(calcImage)
826 masked = self.maskOutliers(calcImage)
827 collapsed = self.collapseArray(masked, fillMasked=False)
828
829 num = len(collapsed)
830 indices = 2.0 * np.arange(num)/float(num) - 1.0
831 indices = np.arange(num)
832
833 if np.ma.is_masked(collapsed):
834 collapsedMask = collapsed.mask
835 else:
836 collapsedMask = np.array(num*[np.ma.nomask])
837
838 import matplotlib.pyplot as plot
839 figure = plot.figure(1)
840 figure.clear()
841 axes = figure.add_axes((0.1, 0.1, 0.8, 0.8))
842 axes.plot(indices[~collapsedMask], collapsed[~collapsedMask], 'k+')
843 if collapsedMask.sum() > 0:
844 axes.plot(indices[collapsedMask], collapsed.data[collapsedMask], 'b+')
845 if isinstance(model, np.ndarray):
846 plotModel = model
847 else:
848 plotModel = np.zeros_like(indices)
849 plotModel += model
850
851 axes.plot(indices, plotModel, 'r-')
852 plot.xlabel("position along overscan region")
853 plot.ylabel("pixel value/fit value")
854 if amp:
855 plot.title(f"{amp.getName()} DataX: "
856 f"[{amp.getRawDataBBox().getBeginX()}:{amp.getRawBBox().getEndX()}]"
857 f"OscanX: [{amp.getRawHorizontalOverscanBBox().getBeginX()}:"
858 f"{amp.getRawHorizontalOverscanBBox().getEndX()}] {self.config.fitType}")
859 else:
860 plot.title("No amp supplied.")
861 figure.show()
862 prompt = "Press Enter or c to continue [chp]..."
863 while True:
864 ans = input(prompt).lower()
865 if ans in ("", " ", "c",):
866 break
867 elif ans in ("p", ):
868 import pdb
869 pdb.set_trace()
870 elif ans in ('x', ):
871 self.allowDebug = False
872 break
873 elif ans in ("h", ):
874 print("[h]elp [c]ontinue [p]db e[x]itDebug")
875 plot.close()
876
877
879 """Correction task for serial/parallel overscan.
880
881 (Will be deprecated)
882
883 This class contains a number of utilities that are easier to
884 understand and use when they are not embedded in nested if/else
885 loops.
886
887 Parameters
888 ----------
889 statControl : `lsst.afw.math.StatisticsControl`, optional
890 Statistics control object.
891 """
892 ConfigClass = OverscanCorrectionTaskConfig
893 _DefaultName = "overscan"
894
895 def run(self, exposure, amp, isTransposed=False):
896 """Measure and remove serial/parallel overscan from an amplifier image.
897
898 This will be deprecated.
899
900 Parameters
901 ----------
902 exposure : `lsst.afw.image.Exposure`
903 Image data that will have the overscan corrections applied.
904 amp : `lsst.afw.cameraGeom.Amplifier`
905 Amplifier to use for debugging purposes.
906 isTransposed : `bool`, optional
907 Is the image transposed, such that serial and parallel
908 overscan regions are reversed? Default is False.
909
910 Returns
911 -------
912 overscanResults : `lsst.pipe.base.Struct`
913 Result struct with components:
914
915 ``imageFit``
916 Value or fit subtracted from the amplifier image data
917 (scalar or `lsst.afw.image.Image`).
918 ``overscanFit``
919 Value or fit subtracted from the serial overscan image
920 data (scalar or `lsst.afw.image.Image`).
921 ``overscanImage``
922 Image of the serial overscan region with the serial
923 overscan correction applied
924 (`lsst.afw.image.Image`). This quantity is used to
925 estimate the amplifier read noise empirically.
926 ``parallelOverscanFit``
927 Value or fit subtracted from the parallel overscan
928 image data (scalar, `lsst.afw.image.Image`, or None).
929 ``parallelOverscanImage``
930 Image of the parallel overscan region with the
931 parallel overscan correction applied
932 (`lsst.afw.image.Image` or None).
933 ``overscanMean``
934 Mean of the fit serial overscan region.
935 This and the following values will be tuples of
936 (serial, parallel) if doParallelOverscan=True.
937 ``overscanMedian``
938 Median of the fit serial overscan region.
939 ``overscanSigma``
940 Sigma of the fit serial overscan region.
941 ``residualMean``
942 Mean of the residual of the serial overscan region after
943 correction.
944 ``residualMedian``
945 Median of the residual of the serial overscan region after
946 correction.
947 ``residualSigma``
948 Mean of the residual of the serial overscan region after
949 correction.
950
951
952 Raises
953 ------
954 RuntimeError
955 Raised if an invalid overscan type is set.
956 """
957 # Do Serial overscan first.
958 serialOverscanBBox = amp.getRawSerialOverscanBBox()
959 imageBBox = amp.getRawDataBBox()
960
961 if self.config.doParallelOverscan:
962 # We need to extend the serial overscan BBox to the full
963 # size of the detector.
964 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
965 imageBBox = imageBBox.expandedTo(parallelOverscanBBox)
966
967 if isTransposed:
968 serialOverscanBBox = geom.Box2I(
969 geom.Point2I(serialOverscanBBox.getMinX(), imageBBox.getEndY()),
970 geom.Extent2I(imageBBox.getWidth(), serialOverscanBBox.getHeight()),
971 )
972 else:
973 serialOverscanBBox = geom.Box2I(
974 geom.Point2I(serialOverscanBBox.getMinX(),
975 imageBBox.getMinY()),
976 geom.Extent2I(serialOverscanBBox.getWidth(),
977 imageBBox.getHeight()),
978 )
979
980 serialResults = self.correctOverscan(
981 exposure,
982 amp,
983 imageBBox,
984 serialOverscanBBox,
985 isTransposed=isTransposed,
986 leadingToSkip=self.config.leadingColumnsToSkip,
987 trailingToSkip=self.config.trailingColumnsToSkip,
988 )
989 overscanMean = serialResults.overscanMean
990 overscanMedian = serialResults.overscanMedian
991 overscanSigma = serialResults.overscanSigma
992 residualMean = serialResults.overscanMeanResidual
993 residualMedian = serialResults.overscanMedianResidual
994 residualSigma = serialResults.overscanSigmaResidual
995
996 # Do Parallel Overscan
997 parallelResults = None
998 if self.config.doParallelOverscan:
999 # This does not need any extensions, as we'll only
1000 # subtract it from the data region.
1001 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1002 imageBBox = amp.getRawDataBBox()
1003
1004 # The serial overscan correction has removed some signal
1005 # from the parallel overscan region, but that is largely a
1006 # constant offset. The collapseArray method now attempts
1007 # to fill fully masked columns with the median of
1008 # neighboring values, with a fallback to the median of the
1009 # correction in all other columns. Filling with neighbor
1010 # values ensures that large variations in the parallel
1011 # overscan do not create new outlier points. The
1012 # MEDIAN_PER_ROW method does this filling as a separate
1013 # operation, using the same method.
1014 parallelResults = self.correctOverscan(
1015 exposure,
1016 amp,
1017 imageBBox,
1018 parallelOverscanBBox,
1019 isTransposed=not isTransposed,
1020 leadingToSkip=self.config.leadingRowsToSkip,
1021 trailingToSkip=self.config.trailingRowsToSkip,
1022 )
1023 overscanMean = (overscanMean, parallelResults.overscanMean)
1024 overscanMedian = (overscanMedian, parallelResults.overscanMedian)
1025 overscanSigma = (overscanSigma, parallelResults.overscanSigma)
1026 residualMean = (residualMean, parallelResults.overscanMeanResidual)
1027 residualMedian = (residualMedian, parallelResults.overscanMedianResidual)
1028 residualSigma = (residualSigma, parallelResults.overscanSigmaResidual)
1029
1030 parallelOverscanFit = parallelResults.overscanOverscanModel if parallelResults else None
1031 parallelOverscanImage = parallelResults.overscanImage if parallelResults else None
1032
1033 return pipeBase.Struct(imageFit=serialResults.ampOverscanModel,
1034 overscanFit=serialResults.overscanOverscanModel,
1035 overscanImage=serialResults.overscanImage,
1036
1037 parallelOverscanFit=parallelOverscanFit,
1038 parallelOverscanImage=parallelOverscanImage,
1039 overscanMean=overscanMean,
1040 overscanMedian=overscanMedian,
1041 overscanSigma=overscanSigma,
1042 residualMean=residualMean,
1043 residualMedian=residualMedian,
1044 residualSigma=residualSigma)
1045
1046
1048 leadingToSkip = pexConfig.Field(
1049 dtype=int,
1050 doc="Number of leading values to skip in serial overscan correction.",
1051 default=0,
1052 )
1053 trailingToSkip = pexConfig.Field(
1054 dtype=int,
1055 doc="Number of trailing values to skip in serial overscan correction.",
1056 default=0,
1057 )
1058
1059
1061 """Correction task for serial overscan.
1062
1063 Parameters
1064 ----------
1065 statControl : `lsst.afw.math.StatisticsControl`, optional
1066 Statistics control object.
1067 """
1068 ConfigClass = SerialOverscanCorrectionTaskConfig
1069 _DefaultName = "serialOverscan"
1070
1071 def run(self, exposure, amp, isTransposed=False):
1072 """Measure and remove serial overscan from an amplifier image.
1073
1074 Parameters
1075 ----------
1076 exposure : `lsst.afw.image.Exposure`
1077 Image data that will have the overscan corrections applied.
1078 amp : `lsst.afw.cameraGeom.Amplifier`
1079 Amplifier to use for debugging purposes.
1080 isTransposed : `bool`, optional
1081 Is the image transposed, such that serial and parallel
1082 overscan regions are reversed? Default is False.
1083
1084 Returns
1085 -------
1086 overscanResults : `lsst.pipe.base.Struct`
1087 Result struct with components:
1088
1089 ``imageFit``
1090 Value or fit subtracted from the amplifier image data
1091 (scalar or `lsst.afw.image.Image`).
1092 ``overscanFit``
1093 Value or fit subtracted from the serial overscan image
1094 data (scalar or `lsst.afw.image.Image`).
1095 ``overscanImage``
1096 Image of the serial overscan region with the serial
1097 overscan correction applied
1098 (`lsst.afw.image.Image`). This quantity is used to
1099 estimate the amplifier read noise empirically.
1100 ``overscanMean``
1101 Mean of the fit serial overscan region.
1102 ``overscanMedian``
1103 Median of the fit serial overscan region.
1104 ``overscanSigma``
1105 Sigma of the fit serial overscan region.
1106 ``residualMean``
1107 Mean of the residual of the serial overscan region after
1108 correction.
1109 ``residualMedian``
1110 Median of the residual of the serial overscan region after
1111 correction.
1112 ``residualSigma``
1113 Mean of the residual of the serial overscan region after
1114 correction.
1115
1116 Raises
1117 ------
1118 RuntimeError
1119 Raised if an invalid overscan type is set.
1120 """
1121 serialOverscanBBox = amp.getRawSerialOverscanBBox()
1122 imageBBox = amp.getRawDataBBox()
1123
1124 # We always want to extend the serial overscan bounding box to
1125 # the full size of the detector.
1126 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1127 imageBBox = imageBBox.expandedTo(parallelOverscanBBox)
1128
1129 if isTransposed:
1130 serialOverscanBBox = geom.Box2I(
1131 geom.Point2I(serialOverscanBBox.getMinX(), imageBBox.getEndY()),
1132 geom.Extent2I(imageBBox.getWidth(), serialOverscanBBox.getHeight()),
1133 )
1134 else:
1135 serialOverscanBBox = geom.Box2I(
1136 geom.Point2I(serialOverscanBBox.getMinX(),
1137 imageBBox.getMinY()),
1138 geom.Extent2I(serialOverscanBBox.getWidth(),
1139 imageBBox.getHeight()),
1140 )
1141
1142 results = self.correctOverscan(
1143 exposure,
1144 amp,
1145 imageBBox,
1146 serialOverscanBBox,
1147 isTransposed=isTransposed,
1148 leadingToSkip=self.config.leadingToSkip,
1149 trailingToSkip=self.config.trailingToSkip,
1150 )
1151 overscanMean = results.overscanMean
1152 overscanMedian = results.overscanMedian
1153 overscanSigma = results.overscanSigma
1154 residualMean = results.overscanMeanResidual
1155 residualMedian = results.overscanMedianResidual
1156 residualSigma = results.overscanSigmaResidual
1157
1158 return pipeBase.Struct(
1159 imageFit=results.ampOverscanModel,
1160 overscanFit=results.overscanOverscanModel,
1161 overscanImage=results.overscanImage,
1162 overscanMean=overscanMean,
1163 overscanMedian=overscanMedian,
1164 overscanSigma=overscanSigma,
1165 residualMean=residualMean,
1166 residualMedian=residualMedian,
1167 residualSigma=residualSigma,
1168 )
1169
1170
1172 doParallelOverscanSaturation = pexConfig.Field(
1173 dtype=bool,
1174 doc="Mask saturated pixels in parallel overscan region?",
1175 default=True,
1176 )
1177 parallelOverscanSaturationLevel = pexConfig.Field(
1178 dtype=float,
1179 doc="The saturation level to use if not specified in call to "
1180 "maskParallelOverscan.",
1181 default=100000.,
1182 )
1183 parallelOverscanMaskGrowSize = pexConfig.Field(
1184 dtype=int,
1185 doc="Masks created from saturated bleeds should be grown by this many "
1186 "pixels during construction of the parallel overscan mask. "
1187 "This value determined from the ITL chip in the LATISS camera",
1188 default=7,
1189 )
1190 leadingToSkip = pexConfig.Field(
1191 dtype=int,
1192 doc="Number of leading values to skip in parallel overscan correction.",
1193 default=0,
1194 )
1195 trailingToSkip = pexConfig.Field(
1196 dtype=int,
1197 doc="Number of trailing values to skip in parallel overscan correction.",
1198 default=0,
1199 )
1200
1201
1203 """Correction task for parallel overscan.
1204
1205 Parameters
1206 ----------
1207 statControl : `lsst.afw.math.StatisticsControl`, optional
1208 Statistics control object.
1209 """
1210 ConfigClass = ParallelOverscanCorrectionTaskConfig
1211 _DefaultName = "parallelOverscan"
1212
1213 def run(self, exposure, amp, isTransposed=False):
1214 """Measure and remove parallel overscan from an amplifier image.
1215
1216 This method assumes that serial overscan has already been
1217 removed from the amplifier.
1218
1219 Parameters
1220 ----------
1221 exposure : `lsst.afw.image.Exposure`
1222 Image data that will have the overscan corrections applied.
1223 amp : `lsst.afw.cameraGeom.Amplifier`
1224 Amplifier to use for debugging purposes.
1225 isTransposed : `bool`, optional
1226 Is the image transposed, such that serial and parallel
1227 overscan regions are reversed? Default is False.
1228
1229 Returns
1230 -------
1231 overscanResults : `lsst.pipe.base.Struct`
1232 Result struct with components:
1233
1234 ``imageFit``
1235 Value or fit subtracted from the amplifier image data
1236 (scalar or `lsst.afw.image.Image`).
1237 ``overscanFit``
1238 Value or fit subtracted from the parallel overscan image
1239 data (scalar or `lsst.afw.image.Image`).
1240 ``overscanImage``
1241 Image of the parallel overscan region with the parallel
1242 overscan correction applied
1243 (`lsst.afw.image.Image`). This quantity is used to
1244 estimate the amplifier read noise empirically.
1245 ``overscanMean``
1246 Mean of the fit parallel overscan region.
1247 ``overscanMedian``
1248 Median of the fit parallel overscan region.
1249 ``overscanSigma``
1250 Sigma of the fit parallel overscan region.
1251 ``residualMean``
1252 Mean of the residual of the parallel overscan region after
1253 correction.
1254 ``residualMedian``
1255 Median of the residual of the parallel overscan region after
1256 correction.
1257 ``residualSigma``
1258 Mean of the residual of the parallel overscan region after
1259 correction.
1260
1261 Raises
1262 ------
1263 RuntimeError
1264 Raised if an invalid overscan type is set.
1265 """
1266 # This does not need any extending, as we only subtract
1267 # from the data region.
1268 parallelOverscanBBox = amp.getRawParallelOverscanBBox()
1269 imageBBox = amp.getRawDataBBox()
1270
1271 # The serial overscan correction has removed some signal
1272 # from the parallel overscan region, but that is largely a
1273 # constant offset. The collapseArray method now attempts
1274 # to fill fully masked columns with the median of
1275 # neighboring values, with a fallback to the median of the
1276 # correction in all other columns. Filling with neighbor
1277 # values ensures that large variations in the parallel
1278 # overscan do not create new outlier points. The
1279 # MEDIAN_PER_ROW method does this filling as a separate
1280 # operation, using the same method.
1281 results = self.correctOverscan(
1282 exposure,
1283 amp,
1284 imageBBox,
1285 parallelOverscanBBox,
1286 isTransposed=not isTransposed,
1287 leadingToSkip=self.config.leadingToSkip,
1288 trailingToSkip=self.config.trailingToSkip,
1289 )
1290 overscanMean = results.overscanMean
1291 overscanMedian = results.overscanMedian
1292 overscanSigma = results.overscanSigma
1293 residualMean = results.overscanMeanResidual
1294 residualMedian = results.overscanMedianResidual
1295 residualSigma = results.overscanSigmaResidual
1296
1297 return pipeBase.Struct(
1298 imageFit=results.ampOverscanModel,
1299 overscanFit=results.overscanOverscanModel,
1300 overscanImage=results.overscanImage,
1301 overscanMean=overscanMean,
1302 overscanMedian=overscanMedian,
1303 overscanSigma=overscanSigma,
1304 residualMean=residualMean,
1305 residualMedian=residualMedian,
1306 residualSigma=residualSigma,
1307 )
1308
1309 def maskParallelOverscan(self, exposure, detector, saturationLevel=None):
1310 """Mask parallel overscan, growing saturated pixels.
1311
1312 This operates on the image in-place.
1313
1314 Parameters
1315 ----------
1316 exposure : `lsst.afw.image.Exposure`
1317 An untrimmed raw exposure.
1318 detector : `lsst.afw.cameraGeom.Detector`
1319 The detetor to use for amplifier geometry.
1320 saturationLevel : `float`, optional
1321 Saturation level to use for masking.
1322 """
1323 if not self.config.doParallelOverscanSaturation:
1324 # This is a no-op.
1325 return
1326
1327 if saturationLevel is None:
1328 saturationLevel = self.config.parallelOverscanSaturationLevel
1329
1330 for amp in detector:
1331 dataView = afwImage.MaskedImageF(exposure.getMaskedImage(),
1332 amp.getRawParallelOverscanBBox(),
1333 afwImage.PARENT)
1334 makeThresholdMask(
1335 maskedImage=dataView,
1336 threshold=saturationLevel,
1337 growFootprints=self.config.parallelOverscanMaskGrowSize,
1338 maskName="BAD"
1339 )
Pass parameters to a Statistics object.
Definition Statistics.h:83
An integer coordinate rectangle.
Definition Box.h:55
debugView(self, image, model, amp=None, isTransposed=True)
Definition overscan.py:802
fitOverscan(self, overscanImage, isTransposed=False)
Definition overscan.py:364
correctOverscan(self, exposure, amp, imageBBox, overscanBBox, isTransposed=True, leadingToSkip=0, trailingToSkip=0)
Definition overscan.py:157
collapseArray(self, maskedArray, fillMasked=True)
Definition overscan.py:578
trimOverscan(self, exposure, amp, bbox, skipLeading, skipTrailing, transpose=False)
Definition overscan.py:317
maskParallelOverscan(self, exposure, detector)
Definition overscan.py:428
broadcastFitToImage(self, overscanValue, imageArray, transpose=False)
Definition overscan.py:267
splineFit(self, indices, collapsed, numBins)
Definition overscan.py:629
run(self, exposure, amp, isTransposed=False)
Definition overscan.py:153
__init__(self, statControl=None, **kwargs)
Definition overscan.py:142
measureVectorOverscan(self, image, isTransposed=False)
Definition overscan.py:723
run(self, exposure, amp, isTransposed=False)
Definition overscan.py:895
run(self, exposure, amp, isTransposed=False)
Definition overscan.py:1213
maskParallelOverscan(self, exposure, detector, saturationLevel=None)
Definition overscan.py:1309
run(self, exposure, amp, isTransposed=False)
Definition overscan.py:1071
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)
Definition Statistics.h:361
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.