Coverage for python / lsst / analysis / tools / atools / coaddInputCount.py: 22%
97 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 17:41 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-04 17:41 +0000
1# This file is part of analysis_tools.
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/>.
21from __future__ import annotations
23__all__ = ("CoaddInputCount", "CoaddQualityCheck", "CoaddQualityPlot", "CoaddInputFraction")
25from lsst.pex.config import ListField
27from ..actions.plot.calculateRange import MinMax
28from ..actions.plot.coaddDepthPlot import CoaddDepthPlot
29from ..actions.plot.skyPlot import SkyPlot
30from ..actions.scalar.scalarActions import (
31 CountAction,
32 DivideScalar,
33 MeanAction,
34 MedianAction,
35 SigmaMadAction,
36 StdevAction,
37 SumAction,
38)
39from ..actions.vector import BandSelector, CoaddPlotFlagSelector, DownselectVector, LoadVector, SnSelector
40from ..interfaces import AnalysisTool
43class CoaddInputCount(AnalysisTool):
44 """Tract-wide metrics pertaining to how many exposures have gone into
45 a deep coadd, per band.
47 This AnalysisTool is designed to run on an object table, which is only
48 created for deep coadds, not template coadds.
49 """
51 def setDefaults(self):
52 super().setDefaults()
53 self.prep.selectors.flagSelector = CoaddPlotFlagSelector()
54 # Set this to an empty list to look at the band
55 # the plot is being made in.
56 self.prep.selectors.flagSelector.bands = []
58 self.prep.selectors.snSelector = SnSelector()
59 self.prep.selectors.snSelector.fluxType = "{band}_psfFlux"
60 self.prep.selectors.snSelector.threshold = 5
62 self.process.buildActions.x = LoadVector()
63 self.process.buildActions.x.vectorKey = "coord_ra"
64 self.process.buildActions.y = LoadVector()
65 self.process.buildActions.y.vectorKey = "coord_dec"
66 self.process.buildActions.statMask = SnSelector()
67 self.process.buildActions.statMask.fluxType = "{band}_psfFlux"
68 self.process.buildActions.statMask.threshold = 5
70 self.process.buildActions.z = LoadVector()
71 self.process.buildActions.z.vectorKey = "{band}_inputCount"
73 self.process.buildActions.patch = LoadVector()
74 self.process.buildActions.patch.vectorKey = "patch"
76 self.process.calculateActions.median = MedianAction()
77 self.process.calculateActions.median.vectorKey = "z"
79 self.process.calculateActions.mean = MeanAction()
80 self.process.calculateActions.mean.vectorKey = "z"
82 self.process.calculateActions.sigmaMad = SigmaMadAction()
83 self.process.calculateActions.sigmaMad.vectorKey = "z"
85 # SkyPlot of number of contributing exposures in coad, per tract/band:
86 self.produce.plot = SkyPlot()
87 self.produce.plot.plotTypes = ["any"]
88 self.produce.plot.plotName = "{band}_inputCount"
89 self.produce.plot.xAxisLabel = "R.A. (deg)"
90 self.produce.plot.yAxisLabel = "Dec. (deg)"
91 self.produce.plot.zAxisLabel = "Input Count"
92 self.produce.plot.plotOutlines = True
93 self.produce.plot.showExtremeOutliers = False
94 self.produce.plot.colorbarRange = MinMax()
96 # Summary metrics for the whole coadd, per tract/band.
97 self.produce.metric.units = {"median": "ct", "sigmaMad": "ct", "mean": "ct"}
98 self.produce.metric.newNames = {
99 "median": "{band}_inputCount_median",
100 "mean": "{band}_inputCount_mean",
101 "sigmaMad": "{band}_inputCount_sigmaMad",
102 }
105class CoaddQualityCheck(AnalysisTool):
106 """Compute the percentage of each coadd that has a number of input
107 exposures exceeding a threshold.
109 This AnalysisTool is designed to run on any coadd, provided a
110 coadd_depth_table is created first (via CoaddDepthSummaryAnalysisTask).
112 For example, if exactly half of a coadd patch contains 15 overlapping
113 constituent visits and half contains fewer, the value computed for
114 `depth_above_threshold_12` would be 50.
116 These values come from the n_image data product, which is an image
117 identical to the coadd but with pixel values of the number of input
118 images instead of flux or counts. n_images are persisted during
119 coadd assembly.
120 """
122 band_list = ListField(
123 default=["u", "g", "r", "i", "z", "y"],
124 dtype=str,
125 doc="Bands for colors.",
126 )
128 threshold_list = ListField(
129 default=[1, 2, 3, 5, 12],
130 dtype=int,
131 doc="The n_image pixel value thresholds, in ascending order.",
132 )
134 quantile_list = ListField(
135 default=[5, 10, 25, 50, 75, 90, 95],
136 dtype=int,
137 doc="The percentiles at which to compute n_image values, in ascending order.",
138 )
140 def setDefaults(self):
141 super().setDefaults()
143 self.process.buildActions.patch = LoadVector(vectorKey="patch")
144 self.process.buildActions.band = LoadVector(vectorKey="band")
146 def finalize(self):
147 for threshold in self.threshold_list:
148 # This gives a RuntimeWarning whenever the tract doesn't have
149 # a particular band. Need to use a band list derived from the
150 # bands found in the "band" column of a given tract.
151 for band in self.band_list:
152 name = f"depth_above_threshold_{threshold}"
153 setattr(self.process.buildActions, name, LoadVector(vectorKey=name))
154 setattr(
155 self.process.filterActions,
156 f"{name}_{band}",
157 DownselectVector(vectorKey=name, selector=BandSelector(bands=[band])),
158 )
159 setattr(
160 self.process.calculateActions,
161 f"{name}_{band}_median",
162 MedianAction(vectorKey=f"{name}_{band}"),
163 )
164 setattr(
165 self.process.calculateActions,
166 f"{name}_{band}_mean",
167 MeanAction(vectorKey=f"{name}_{band}"),
168 )
169 setattr(
170 self.process.calculateActions,
171 f"{name}_{band}_stdev",
172 StdevAction(vectorKey=f"{name}_{band}"),
173 )
175 # The units for the quantity are dimensionless (percentage)
176 self.produce.metric.units[f"{name}_{band}_median"] = ""
177 self.produce.metric.units[f"{name}_{band}_mean"] = ""
178 self.produce.metric.units[f"{name}_{band}_stdev"] = ""
180 for quantile in self.quantile_list:
181 for band in self.band_list:
182 name = f"depth_{quantile}_percentile"
183 setattr(self.process.buildActions, name, LoadVector(vectorKey=name))
184 setattr(
185 self.process.filterActions,
186 f"{name}_{band}",
187 DownselectVector(vectorKey=name, selector=BandSelector(bands=[band])),
188 )
189 setattr(
190 self.process.calculateActions,
191 f"{name}_{band}_median",
192 MedianAction(vectorKey=f"{name}_{band}"),
193 )
194 setattr(
195 self.process.calculateActions,
196 f"{name}_{band}_mean",
197 MeanAction(vectorKey=f"{name}_{band}"),
198 )
199 setattr(
200 self.process.calculateActions,
201 f"{name}_{band}_stdev",
202 StdevAction(vectorKey=f"{name}_{band}"),
203 )
205 # The units for the quantity are dimensionless (percentage)
206 self.produce.metric.units[f"{name}_{band}_median"] = ""
207 self.produce.metric.units[f"{name}_{band}_mean"] = ""
208 self.produce.metric.units[f"{name}_{band}_stdev"] = ""
211class CoaddQualityPlot(AnalysisTool):
212 """Make a plot of coadd depth."""
214 parameterizedBand: bool = False
216 def setDefaults(self):
217 super().setDefaults()
218 self.process.buildActions.patch = LoadVector(vectorKey="patch")
219 self.process.buildActions.band = LoadVector(vectorKey="band")
220 self.process.buildActions.depth = LoadVector(vectorKey="depth")
221 self.process.buildActions.pixels = LoadVector(vectorKey="pixels")
223 self.produce.plot = CoaddDepthPlot()
226class CoaddInputFraction(AnalysisTool):
227 """Metrics quantifying the number of images that overlap a patch,
228 the number that actually made it into the coadd, and the ratio
229 of the two (as a fraction).
230 """
232 parameterizedBand: bool = False
234 def setDefaults(self):
235 super().setDefaults()
237 # The number of entries is the number of potential raws:
238 self.process.calculateActions.rawCount = CountAction(vectorKey="inCoadd")
239 self.process.calculateActions.overlapCount = SumAction(vectorKey="patchOverlap")
240 self.process.calculateActions.inVisitSummaryCount = SumAction(vectorKey="visitSummaryRecord")
241 self.process.calculateActions.inCoaddCount = SumAction(vectorKey="inCoadd")
243 self.process.calculateActions.inVisitSummaryFraction = DivideScalar(
244 actionA=SumAction(vectorKey="visitSummaryRecord"),
245 actionB=CountAction(vectorKey="inCoadd"),
246 )
248 self.process.calculateActions.overlapFraction = DivideScalar(
249 actionA=SumAction(vectorKey="patchOverlap"),
250 actionB=CountAction(vectorKey="inCoadd"),
251 )
253 self.process.calculateActions.inCoaddFraction = DivideScalar(
254 actionA=SumAction(vectorKey="inCoadd"),
255 actionB=CountAction(vectorKey="inCoadd"),
256 )
258 self.produce.metric.units = {
259 "rawCount": "ct",
260 "inVisitSummaryCount": "ct",
261 "overlapCount": "ct",
262 "inCoaddCount": "ct",
263 "inVisitSummaryFraction": "",
264 "overlapFraction": "",
265 "inCoaddFraction": "",
266 }