140 self.
statControl = afwMath.StatisticsControl(self.config.nSigmaClip, self.config.nIter,
141 afwImage.Mask.getPlaneBitMask(self.config.badMask))
142 self.
statType = afwMath.stringToStatisticsProperty(self.config.stat)
144 def run(self, inputExp, ptc=None, overscanResults=None, **kwargs):
145 """Task to run arbitrary statistics.
147 The statistics should be measured by individual methods, and
148 add to the dictionary in the return struct.
152 inputExp : `lsst.afw.image.Exposure`
153 The exposure to measure.
154 ptc : `lsst.ip.isr.PtcDataset`, optional
155 A PTC object containing gains to use.
156 overscanResults : `list` [`lsst.pipe.base.Struct`], optional
157 List of overscan results. Expected fields are:
160 Value or fit subtracted from the amplifier image data
161 (scalar or `lsst.afw.image.Image`).
163 Value or fit subtracted from the overscan image data
164 (scalar or `lsst.afw.image.Image`).
166 Image of the overscan region with the overscan
167 correction applied (`lsst.afw.image.Image`). This
168 quantity is used to estimate the amplifier read noise
173 resultStruct : `lsst.pipe.base.Struct`
174 Contains the measured statistics as a dict stored in a
175 field named ``results``.
180 Raised if the amplifier gains could not be found.
183 detector = inputExp.getDetector()
186 elif detector
is not None:
187 gains = {amp.getName(): amp.getGain()
for amp
in detector.getAmplifiers()}
189 raise RuntimeError(
"No source of gains provided.")
192 if self.config.doCtiStatistics:
193 ctiResults = self.
measureCti(inputExp, overscanResults, gains)
195 bandingResults =
None
196 if self.config.doBandingStatistics:
199 projectionResults =
None
200 if self.config.doProjectionStatistics:
203 return pipeBase.Struct(
204 results={
'CTI': ctiResults,
205 'BANDING': bandingResults,
206 'PROJECTION': projectionResults,
211 """Task to measure CTI statistics.
215 inputExp : `lsst.afw.image.Exposure`
217 overscans : `list` [`lsst.pipe.base.Struct`]
218 List of overscan results. Expected fields are:
221 Value or fit subtracted from the amplifier image data
222 (scalar or `lsst.afw.image.Image`).
224 Value or fit subtracted from the overscan image data
225 (scalar or `lsst.afw.image.Image`).
227 Image of the overscan region with the overscan
228 correction applied (`lsst.afw.image.Image`). This
229 quantity is used to estimate the amplifier read noise
231 gains : `dict` [`str` `float`]
232 Dictionary of per-amplifier gains, indexed by amplifier name.
236 outputStats : `dict` [`str`, [`dict` [`str`,`float]]
237 Dictionary of measurements, keyed by amplifier name and
242 detector = inputExp.getDetector()
243 image = inputExp.image
246 assert len(overscans) == len(detector.getAmplifiers())
248 for ampIter, amp
in enumerate(detector.getAmplifiers()):
250 gain = gains[amp.getName()]
251 readoutCorner = amp.getReadoutCorner()
253 dataRegion = image[amp.getBBox()]
254 ampStats[
'IMAGE_MEAN'] = afwMath.makeStatistics(dataRegion, self.
statType,
258 pixelA = afwMath.makeStatistics(dataRegion.array[:, 0],
261 pixelZ = afwMath.makeStatistics(dataRegion.array[:, -1],
267 if readoutCorner
in (ReadoutCorner.LR, ReadoutCorner.UR):
268 ampStats[
'FIRST_MEAN'] = pixelZ
269 ampStats[
'LAST_MEAN'] = pixelA
271 ampStats[
'FIRST_MEAN'] = pixelA
272 ampStats[
'LAST_MEAN'] = pixelZ
275 if overscans[ampIter]
is None:
278 self.log.warn(
"No overscan information available for ISR statistics for amp %s.",
280 nCols = amp.getSerialOverscanBBox().getWidth()
281 ampStats[
'OVERSCAN_COLUMNS'] = np.full((nCols, ), np.nan)
282 ampStats[
'OVERSCAN_VALUES'] = np.full((nCols, ), np.nan)
284 overscanImage = overscans[ampIter].overscanImage
287 for column
in range(0, overscanImage.getWidth()):
288 osMean = afwMath.makeStatistics(overscanImage.image.array[:, column],
290 columns.append(column)
291 if self.config.doApplyGainsForCtiStatistics:
292 values.append(gain * osMean)
294 values.append(osMean)
298 if readoutCorner
in (ReadoutCorner.LR, ReadoutCorner.UR):
299 ampStats[
'OVERSCAN_COLUMNS'] = list(reversed(columns))
300 ampStats[
'OVERSCAN_VALUES'] = list(reversed(values))
302 ampStats[
'OVERSCAN_COLUMNS'] = columns
303 ampStats[
'OVERSCAN_VALUES'] = values
305 outputStats[amp.getName()] = ampStats
330 """Task to measure banding statistics.
334 inputExp : `lsst.afw.image.Exposure`
336 overscans : `list` [`lsst.pipe.base.Struct`]
337 List of overscan results. Expected fields are:
340 Value or fit subtracted from the amplifier image data
341 (scalar or `lsst.afw.image.Image`).
343 Value or fit subtracted from the overscan image data
344 (scalar or `lsst.afw.image.Image`).
346 Image of the overscan region with the overscan
347 correction applied (`lsst.afw.image.Image`). This
348 quantity is used to estimate the amplifier read noise
353 outputStats : `dict` [`str`, [`dict` [`str`,`float]]
354 Dictionary of measurements, keyed by amplifier name and
359 detector = inputExp.getDetector()
360 kernel = self.
makeKernel(self.config.bandingKernelSize)
362 outputStats[
'AMP_BANDING'] = []
363 for amp, overscanData
in zip(detector.getAmplifiers(), overscans):
364 overscanFit = np.array(overscanData.overscanFit)
365 overscanArray = overscanData.overscanImage.image.array
366 rawOverscan = np.mean(overscanArray + overscanFit, axis=1)
368 smoothedOverscan = np.convolve(rawOverscan, kernel, mode=
'valid')
370 low, high = np.quantile(smoothedOverscan, [self.config.bandingFractionLow,
371 self.config.bandingFractionHigh])
372 outputStats[
'AMP_BANDING'].append(float(high - low))
374 if self.config.bandingUseHalfDetector:
375 fullLength = len(outputStats[
'AMP_BANDING'])
376 outputStats[
'DET_BANDING'] = float(np.nanmedian(outputStats[
'AMP_BANDING'][0:fullLength//2]))
378 outputStats[
'DET_BANDING'] = float(np.nanmedian(outputStats[
'AMP_BANDING']))
383 """Task to measure metrics from image slicing.
387 inputExp : `lsst.afw.image.Exposure`
389 overscans : `list` [`lsst.pipe.base.Struct`]
390 List of overscan results. Expected fields are:
393 Value or fit subtracted from the amplifier image data
394 (scalar or `lsst.afw.image.Image`).
396 Value or fit subtracted from the overscan image data
397 (scalar or `lsst.afw.image.Image`).
399 Image of the overscan region with the overscan
400 correction applied (`lsst.afw.image.Image`). This
401 quantity is used to estimate the amplifier read noise
406 outputStats : `dict` [`str`, [`dict` [`str`,`float]]
407 Dictionary of measurements, keyed by amplifier name and
412 detector = inputExp.getDetector()
413 kernel = self.
makeKernel(self.config.projectionKernelSize)
415 outputStats[
'AMP_VPROJECTION'] = {}
416 outputStats[
'AMP_HPROJECTION'] = {}
417 convolveMode =
'valid'
418 if self.config.doProjectionFft:
419 outputStats[
'AMP_VFFT_REAL'] = {}
420 outputStats[
'AMP_VFFT_IMAG'] = {}
421 outputStats[
'AMP_HFFT_REAL'] = {}
422 outputStats[
'AMP_HFFT_IMAG'] = {}
423 convolveMode =
'same'
425 for amp
in detector.getAmplifiers():
426 ampArray = inputExp.image[amp.getBBox()].array
428 horizontalProjection = np.mean(ampArray, axis=0)
429 verticalProjection = np.mean(ampArray, axis=1)
431 horizontalProjection = np.convolve(horizontalProjection, kernel, mode=convolveMode)
432 verticalProjection = np.convolve(verticalProjection, kernel, mode=convolveMode)
434 outputStats[
'AMP_HPROJECTION'][amp.getName()] = horizontalProjection.tolist()
435 outputStats[
'AMP_VPROJECTION'][amp.getName()] = verticalProjection.tolist()
437 if self.config.doProjectionFft:
438 horizontalWindow = np.ones_like(horizontalProjection)
439 verticalWindow = np.ones_like(verticalProjection)
440 if self.config.projectionFftWindow ==
"NONE":
442 elif self.config.projectionFftWindow ==
"HAMMING":
443 horizontalWindow = hamming(len(horizontalProjection))
444 verticalWindow = hamming(len(verticalProjection))
445 elif self.config.projectionFftWindow ==
"HANN":
446 horizontalWindow = hann(len(horizontalProjection))
447 verticalWindow = hann(len(verticalProjection))
448 elif self.config.projectionFftWindow ==
"GAUSSIAN":
449 horizontalWindow = gaussian(len(horizontalProjection))
450 verticalWindow = gaussian(len(verticalProjection))
452 raise RuntimeError(f
"Invalid window function: {self.config.projectionFftWindow}")
454 horizontalFFT = np.fft.rfft(np.multiply(horizontalProjection, horizontalWindow))
455 verticalFFT = np.fft.rfft(np.multiply(verticalProjection, verticalWindow))
456 outputStats[
'AMP_HFFT_REAL'][amp.getName()] = np.real(horizontalFFT).tolist()
457 outputStats[
'AMP_HFFT_IMAG'][amp.getName()] = np.imag(horizontalFFT).tolist()
458 outputStats[
'AMP_VFFT_REAL'][amp.getName()] = np.real(verticalFFT).tolist()
459 outputStats[
'AMP_VFFT_IMAG'][amp.getName()] = np.imag(verticalFFT).tolist()