Coverage for python/lsst/analysis/tools/atools/diffMatched.py: 30%
154 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-04 11:09 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-04 11:09 +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__ = (
24 "MatchedRefCoaddToolBase",
25 "MatchedRefCoaddMetric",
26 "MatchedRefCoaddDiffMagTool",
27 "MatchedRefCoaddCModelFluxMetric",
28 "MatchedRefCoaddDiffPositionTool",
29 "MatchedRefCoaddPositionMetric",
30)
32from lsst.pex.config import ChoiceField, Field
34from ..actions.plot.scatterplotWithTwoHists import ScatterPlotStatsAction, ScatterPlotWithTwoHists
35from ..actions.vector.calcBinnedStats import CalcBinnedStatsAction
36from ..actions.vector.selectors import GalaxySelector, RangeSelector, StarSelector
37from ..actions.vector.vectorActions import (
38 ConstantValue,
39 DivideVector,
40 DownselectVector,
41 LoadVector,
42 MagColumnNanoJansky,
43 SubtractVector,
44 VectorSelector,
45)
46from ..interfaces import AnalysisTool, KeyedData
49class MatchedRefCoaddToolBase(AnalysisTool):
50 """Base tool for matched-to-reference metrics/plots on coadds.
52 Metrics/plots are expected to use the reference magnitude and
53 require separate star/galaxy/all source selections.
55 Notes
56 -----
57 The tool does not use a standard coadd flag selector, because
58 it is expected that the matcher has been configured to select
59 appropriate candidates (and stores a match_candidate column).
60 """
62 def setDefaults(self):
63 super().setDefaults()
64 self.process.buildActions.fluxes_ref = LoadVector(vectorKey="refcat_flux_{band}")
65 # TODO: Why won't vectorKey="fluxes_ref" work?
66 # Does it need to be a filterAction?
67 self.process.buildActions.mags_ref = MagColumnNanoJansky(
68 vectorKey=self.process.buildActions.fluxes_ref.vectorKey
69 )
71 # Select any finite extendedness (but still exclude NaNs)
72 self.process.buildActions.allSelector = StarSelector(
73 vectorKey="refExtendedness", extendedness_maximum=1.0
74 )
75 self.process.buildActions.galaxySelector = GalaxySelector(vectorKey="refExtendedness")
76 self.process.buildActions.starSelector = StarSelector(vectorKey="refExtendedness")
78 self.process.filterActions.xAll = DownselectVector(
79 vectorKey="mags_ref", selector=VectorSelector(vectorKey="allSelector")
80 )
81 self.process.filterActions.xGalaxies = DownselectVector(
82 vectorKey="mags_ref", selector=VectorSelector(vectorKey="galaxySelector")
83 )
84 self.process.filterActions.xStars = DownselectVector(
85 vectorKey="mags_ref", selector=VectorSelector(vectorKey="starSelector")
86 )
89class MatchedRefCoaddMetric(MatchedRefCoaddToolBase):
90 """Base tool for matched-to-reference metrics on coadds."""
92 name_prefix = Field[str](default=None, doc="Prefix for metric key")
93 unit = Field[str](default=None, doc="Astropy unit of y-axis values")
95 _mag_low_min: int = 15
96 _mag_low_max: int = 27
97 _mag_interval: int = 1
99 _names = ("stars", "galaxies", "all")
100 _types = ("unresolved", "resolved", "all")
102 def _validate(self):
103 if self.name_prefix is None or self.unit is None:
104 raise ValueError(
105 f"{self.name_prefix=} and {self.unit=} must not be None;"
106 f" did you forget to set a valid context?"
107 )
109 def configureMetrics(
110 self,
111 unit: str | None = None,
112 name_prefix: str | None = None,
113 name_suffix: str = "_mad_ref_mag{minimum}",
114 unit_select: str = "mag",
115 ):
116 """Configure metric actions and return units.
118 Parameters
119 ----------
120 unit : `str`
121 The (astropy) unit of the summary statistic metrics.
122 name_prefix : `str`
123 The prefix for the action (column) name.
124 name_suffix : `str`
125 The sufffix for the action (column) name.
126 unit_select : `str`
127 The (astropy) unit of the selection (x-axis) column. Default "mag".
129 Returns
130 -------
131 units : `dict` [`str`, `str`]
132 A dict of the unit (value) for each metric name (key)
133 """
134 unit_is_none = unit is None
135 name_prefix_is_none = name_prefix is None
137 if unit_is_none or name_prefix_is_none:
138 if unit_is_none:
139 unit = self.unit
140 if name_prefix_is_none:
141 name_prefix = self.name_prefix
142 self._validate()
143 if unit_select is None:
144 unit_select = "mag"
146 assert name_prefix is not None
147 units = {}
148 for name, name_class in zip(self._names, self._types):
149 name_capital = name.capitalize()
150 x_key = f"x{name_capital}"
152 for minimum in range(self._mag_low_min, self._mag_low_max + 1):
153 action = getattr(self.process.calculateActions, f"{name}{minimum}")
154 action.selector_range = RangeSelector(
155 key=x_key,
156 minimum=minimum,
157 maximum=minimum + self._mag_interval,
158 )
160 action.name_prefix = name_prefix.format(name_class=name_class)
161 action.name_suffix = name_suffix.format(minimum=minimum)
163 units.update(
164 {
165 action.name_median: unit,
166 action.name_sigmaMad: unit,
167 action.name_count: "count",
168 action.name_select_minimum: unit_select,
169 action.name_select_median: unit_select,
170 action.name_select_maximum: unit_select,
171 }
172 )
173 return units
175 def setDefaults(self):
176 super().setDefaults()
178 for name in self._names:
179 name_capital = name.capitalize()
180 for minimum in range(self._mag_low_min, self._mag_low_max + 1):
181 setattr(
182 self.process.calculateActions,
183 f"{name}{minimum}",
184 CalcBinnedStatsAction(key_vector=f"y{name_capital}"),
185 )
187 def __call__(self, data: KeyedData, **kwargs):
188 self._validate()
189 return super().__call__(data=data, **kwargs)
192class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase):
193 """Base tool for diffs between reference and measured coadd mags.
195 Notes
196 -----
197 The default model flux is cModel.
198 """
200 def matchedRefDiffContext(self):
201 self.process.buildActions.diff = SubtractVector(
202 actionA=MagColumnNanoJansky(
203 vectorKey=self.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True
204 ),
205 actionB=DivideVector(
206 actionA=self.process.buildActions.mags_ref,
207 # To convert to mmag
208 actionB=ConstantValue(value=1e-3),
209 ),
210 )
212 def matchedRefChiContext(self):
213 self.process.buildActions.diff = DivideVector(
214 actionA=SubtractVector(
215 actionA=LoadVector(vectorKey=self.process.buildActions.fluxes_meas.vectorKey),
216 actionB=LoadVector(vectorKey=self.process.buildActions.fluxes_ref.vectorKey),
217 ),
218 actionB=LoadVector(vectorKey=f"{self.process.buildActions.fluxes_meas.vectorKey}Err"),
219 )
221 def setDefaults(self):
222 super().setDefaults()
224 self.process.buildActions.fluxes_meas = LoadVector(vectorKey="{band}_cModelFlux")
225 self.process.filterActions.yAll = DownselectVector(
226 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector")
227 )
228 self.process.filterActions.yGalaxies = DownselectVector(
229 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector")
230 )
231 self.process.filterActions.yStars = DownselectVector(
232 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector")
233 )
236# The diamond inheritance on MatchedRefCoaddTool seems ok
237class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric):
238 """Metric for diffs between reference and CModel coadd mags."""
240 def matchedRefDiffContext(self):
241 super().matchedRefDiffContext()
242 self.unit = "mmag"
243 self.name_prefix = "photom_mag_cModelFlux_{name_class}_diff_"
244 self.produce.metric.units = self.configureMetrics()
246 def matchedRefChiContext(self):
247 super().matchedRefChiContext()
248 self.unit = ""
249 self.name_prefix = "photom_mag_cModelFlux_{name_class}_chi_"
250 self.produce.metric.units = self.configureMetrics()
252 def setDefaults(self):
253 super().setDefaults()
256class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase):
257 """Base tool for diffs between reference and measured coadd astrometry."""
259 variable = ChoiceField[str](
260 doc="The astrometric variable to compute metrics for",
261 allowed={
262 "x": "x",
263 "y": "y",
264 },
265 optional=False,
266 )
268 # TODO: Determine if this can be put back into setDefaults w/o this:
269 # lsst.pex.config.config.FieldValidationError:
270 # Field 'process.buildActions.pos_meas.vectorKey' failed validation:
271 # Required value cannot be None
272 def _setPos(self):
273 self.process.buildActions.pos_meas = LoadVector(vectorKey=self.variable)
274 self.process.buildActions.pos_ref = LoadVector(vectorKey=f"refcat_{self.variable}")
276 def matchedRefDiffContext(self):
277 self._setPos()
278 self.process.buildActions.diff = SubtractVector(
279 actionA=self.process.buildActions.pos_meas,
280 actionB=self.process.buildActions.pos_ref,
281 )
283 def matchedRefChiContext(self):
284 self._setPos()
285 self.process.buildActions.diff = DivideVector(
286 actionA=SubtractVector(
287 actionA=self.process.buildActions.pos_meas,
288 actionB=self.process.buildActions.pos_ref,
289 ),
290 actionB=LoadVector(vectorKey=f"{self.process.buildActions.pos_meas.vectorKey}Err"),
291 )
293 def setDefaults(self):
294 super().setDefaults()
296 self.process.filterActions.yAll = DownselectVector(
297 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector")
298 )
299 self.process.filterActions.yGalaxies = DownselectVector(
300 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector")
301 )
302 self.process.filterActions.yStars = DownselectVector(
303 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector")
304 )
307class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric):
308 """Metric for diffs between reference and base coadd centroids."""
310 def matchedRefDiffContext(self):
311 super().matchedRefDiffContext()
312 self.unit = "pix"
313 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_"
314 self.produce.metric.units = self.configureMetrics()
316 def matchedRefChiContext(self):
317 super().matchedRefChiContext()
318 self.unit = ""
319 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_"
320 self.produce.metric.units = self.configureMetrics()
322 def setDefaults(self):
323 super().setDefaults()
326class MatchedRefCoaddPlot(AnalysisTool):
327 def setDefaults(self):
328 super().setDefaults()
329 self.produce.plot = ScatterPlotWithTwoHists()
331 self.produce.plot.plotTypes = ["galaxies", "stars"]
332 self.produce.plot.xAxisLabel = "Reference Magnitude (mag)"
335class MatchedRefCoaddCModelPlot(MatchedRefCoaddPlot):
336 def setDefaults(self):
337 super().setDefaults()
338 self.produce.plot.magLabel = "cModel mag"
340 # downselect the cModelFlux as well
341 for prefix, plural in (("star", "Stars"), ("galaxy", "Galaxies")):
342 for suffix in ("", "Err"):
343 setattr(
344 self.process.filterActions,
345 f"{prefix}_cModelFlux{suffix}",
346 DownselectVector(
347 vectorKey=f"{{band}}_cModelFlux{suffix}",
348 selector=VectorSelector(vectorKey=f"{prefix}Selector"),
349 ),
350 )
352 statAction = ScatterPlotStatsAction(vectorKey=f"y{plural.capitalize()}")
353 fluxType = f"{prefix}_cModelFlux"
354 statAction.highSNSelector.fluxType = fluxType
355 statAction.highSNSelector.threshold = 200
356 statAction.lowSNSelector.fluxType = fluxType
357 statAction.lowSNSelector.threshold = 10
358 statAction.fluxType = fluxType
359 setattr(self.process.calculateActions, plural, statAction)
362class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool):
363 def matchedRefDiffContext(self):
364 super().matchedRefDiffContext()
365 self.produce.plot.yAxisLabel = "cModel - Reference mmag"
367 def matchedRefChiContext(self):
368 super().matchedRefChiContext()
369 self.produce.plot.yAxisLabel = "chi = (cModel - Reference mag)/error"
371 def setDefaults(self):
372 super().setDefaults()
375class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool):
376 def matchedRefDiffContext(self):
377 super().matchedRefDiffContext()
378 self.produce.plot.yAxisLabel = f"{self.variable} position (pix)"
380 def matchedRefChiContext(self):
381 super().matchedRefChiContext()
382 self.produce.plot.yAxisLabel = f"chi = (slot - Reference {self.variable} position)/error"
384 def setDefaults(self):
385 super().setDefaults()