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