LSST Applications g070148d5b3+33e5256705,g0d53e28543+25c8b88941,g0da5cf3356+2dd1178308,g1081da9e2a+62d12e78cb,g17e5ecfddb+7e422d6136,g1c76d35bf8+ede3a706f7,g295839609d+225697d880,g2e2c1a68ba+cc1f6f037e,g2ffcdf413f+853cd4dcde,g38293774b4+62d12e78cb,g3b44f30a73+d953f1ac34,g48ccf36440+885b902d19,g4b2f1765b6+7dedbde6d2,g5320a0a9f6+0c5d6105b6,g56b687f8c9+ede3a706f7,g5c4744a4d9+ef6ac23297,g5ffd174ac0+0c5d6105b6,g6075d09f38+66af417445,g667d525e37+2ced63db88,g670421136f+2ced63db88,g71f27ac40c+2ced63db88,g774830318a+463cbe8d1f,g7876bc68e5+1d137996f1,g7985c39107+62d12e78cb,g7fdac2220c+0fd8241c05,g96f01af41f+368e6903a7,g9ca82378b8+2ced63db88,g9d27549199+ef6ac23297,gabe93b2c52+e3573e3735,gb065e2a02a+3dfbe639da,gbc3249ced9+0c5d6105b6,gbec6a3398f+0c5d6105b6,gc9534b9d65+35b9f25267,gd01420fc67+0c5d6105b6,geee7ff78d7+a14128c129,gf63283c776+ede3a706f7,gfed783d017+0c5d6105b6,w.2022.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
isrStatistics.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__ = ["IsrStatisticsTaskConfig", "IsrStatisticsTask"]
23
24import numpy as np
25import lsst.afw.math as afwMath
26import lsst.afw.image as afwImage
27import lsst.pipe.base as pipeBase
28import lsst.pex.config as pexConfig
29
30from lsst.afw.cameraGeom import ReadoutCorner
31
32
33class IsrStatisticsTaskConfig(pexConfig.Config):
34 """Image statistics options.
35 """
36 doCtiStatistics = pexConfig.Field(
37 dtype=bool,
38 doc="Measure CTI statistics from image and overscans?",
39 default=False,
40 )
41 stat = pexConfig.Field(
42 dtype=str,
43 default='MEANCLIP',
44 doc="Statistic name to use to measure regions.",
45 )
46 nSigmaClip = pexConfig.Field(
47 dtype=float,
48 default=3.0,
49 doc="Clipping threshold for background",
50 )
51 nIter = pexConfig.Field(
52 dtype=int,
53 default=3,
54 doc="Clipping iterations for background",
55 )
56 badMask = pexConfig.ListField(
57 dtype=str,
58 default=["BAD", "INTRP", "SAT"],
59 doc="Mask planes to ignore when identifying source pixels."
60 )
61
62
63class IsrStatisticsTask(pipeBase.Task):
64 """Task to measure arbitrary statistics on ISR processed exposures.
65
66 The goal is to wrap a number of optional measurements that are
67 useful for calibration production and detector stability.
68 """
69 ConfigClass = IsrStatisticsTaskConfig
70 _DefaultName = "isrStatistics"
71
72 def __init__(self, statControl=None, **kwargs):
73 super().__init__(**kwargs)
74 self.statControl = afwMath.StatisticsControl(self.config.nSigmaClip, self.config.nIter,
75 afwImage.Mask.getPlaneBitMask(self.config.badMask))
77
78 def run(self, inputExp, ptc=None, overscanResults=None, **kwargs):
79 """Task to run arbitrary statistics.
80
81 The statistics should be measured by individual methods, and
82 add to the dictionary in the return struct.
83
84 Parameters
85 ----------
86 inputExp : `lsst.afw.image.Exposure`
87 The exposure to measure.
88 ptc : `lsst.ip.isr.PtcDataset`, optional
89 A PTC object containing gains to use.
90 overscanResults : `list` [`lsst.pipe.base.Struct`], optional
91 List of overscan results. Expected fields are:
92
93 ``imageFit``
94 Value or fit subtracted from the amplifier image data
95 (scalar or `lsst.afw.image.Image`).
96 ``overscanFit``
97 Value or fit subtracted from the overscan image data
98 (scalar or `lsst.afw.image.Image`).
99 ``overscanImage``
100 Image of the overscan region with the overscan
101 correction applied (`lsst.afw.image.Image`). This
102 quantity is used to estimate the amplifier read noise
103 empirically.
104
105 Returns
106 -------
107 resultStruct : `lsst.pipe.base.Struct`
108 Contains the measured statistics as a dict stored in a
109 field named ``results``.
110
111 Raises
112 ------
113 RuntimeError
114 Raised if the amplifier gains could not be found.
115 """
116 # Find gains.
117 detector = inputExp.getDetector()
118 if ptc is not None:
119 gains = ptc.gain
120 elif detector is not None:
121 gains = {amp.getName(): amp.getGain() for amp in detector.getAmplifiers()}
122 else:
123 raise RuntimeError("No source of gains provided.")
124 if self.config.doCtiStatistics:
125 ctiResults = self.measureCti(inputExp, overscanResults, gains)
126
127 return pipeBase.Struct(
128 results={'CTI': ctiResults, },
129 )
130
131 def measureCti(self, inputExp, overscans, gains):
132 """Task to measure CTI statistics.
133
134 Parameters
135 ----------
136 inputExp : `lsst.afw.image.Exposure`
137 Exposure to measure.
138 overscans : `list` [`lsst.pipe.base.Struct`]
139 List of overscan results. Expected fields are:
140
141 ``imageFit``
142 Value or fit subtracted from the amplifier image data
143 (scalar or `lsst.afw.image.Image`).
144 ``overscanFit``
145 Value or fit subtracted from the overscan image data
146 (scalar or `lsst.afw.image.Image`).
147 ``overscanImage``
148 Image of the overscan region with the overscan
149 correction applied (`lsst.afw.image.Image`). This
150 quantity is used to estimate the amplifier read noise
151 empirically.
152 gains : `dict` [`str` `float`]
153 Dictionary of per-amplifier gains, indexed by amplifier name.
154
155 Returns
156 -------
157 outputStats : `dict` [`str`, [`dict` [`str`,`float]]
158 Dictionary of measurements, keyed by amplifier name and
159 statistics segment.
160 """
161 outputStats = {}
162
163 detector = inputExp.getDetector()
164 image = inputExp.image
165
166 # Ensure we have the same number of overscans as amplifiers.
167 assert len(overscans) == len(detector.getAmplifiers())
168
169 for ampIter, amp in enumerate(detector.getAmplifiers()):
170 ampStats = {}
171 gain = gains[amp.getName()]
172 readoutCorner = amp.getReadoutCorner()
173 # Full data region.
174 dataRegion = image[amp.getBBox()]
175 ampStats['IMAGE_MEAN'] = afwMath.makeStatistics(dataRegion, self.statType,
176 self.statControl).getValue()
177
178 # First and last image columns.
179 pixelA = afwMath.makeStatistics(dataRegion.array[:, 0],
180 self.statType,
181 self.statControl).getValue()
182 pixelZ = afwMath.makeStatistics(dataRegion.array[:, -1],
183 self.statType,
184 self.statControl).getValue()
185
186 # We want these relative to the readout corner. If that's
187 # on the right side, we need to swap them.
188 if readoutCorner in (ReadoutCorner.LR, ReadoutCorner.UR):
189 ampStats['FIRST_MEAN'] = pixelZ
190 ampStats['LAST_MEAN'] = pixelA
191 else:
192 ampStats['FIRST_MEAN'] = pixelA
193 ampStats['LAST_MEAN'] = pixelZ
194
195 # Measure the columns of the overscan.
196 if overscans[ampIter] is None:
197 # The amplifier is likely entirely bad, and needs to
198 # be skipped.
199 self.log.warn("No overscan information available for ISR statistics for amp %s.",
200 amp.getName())
201 nCols = amp.getSerialOverscanBBox().getWidth()
202 ampStats['OVERSCAN_COLUMNS'] = np.full((nCols, ), np.nan)
203 ampStats['OVERSCAN_VALUES'] = np.full((nCols, ), np.nan)
204 else:
205 overscanImage = overscans[ampIter].overscanImage
206 columns = []
207 values = []
208 for column in range(0, overscanImage.getWidth()):
209 osMean = afwMath.makeStatistics(overscanImage.image.array[:, column],
210 self.statType, self.statControl).getValue()
211 columns.append(column)
212 values.append(gain * osMean)
213
214 # We want these relative to the readout corner. If that's
215 # on the right side, we need to swap them.
216 if readoutCorner in (ReadoutCorner.LR, ReadoutCorner.UR):
217 ampStats['OVERSCAN_COLUMNS'] = list(reversed(columns))
218 ampStats['OVERSCAN_VALUES'] = list(reversed(values))
219 else:
220 ampStats['OVERSCAN_COLUMNS'] = columns
221 ampStats['OVERSCAN_VALUES'] = values
222
223 outputStats[amp.getName()] = ampStats
224
225 return outputStats
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Definition: Exposure.h:72
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51
Pass parameters to a Statistics object.
Definition: Statistics.h:83
def __init__(self, statControl=None, **kwargs)
def measureCti(self, inputExp, overscans, gains)
daf::base::PropertyList * list
Definition: fits.cc:928
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)
Definition: Statistics.cc:762