Coverage for python/lsst/analysis/tools/atools/diffMatched.py: 30%
157 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-21 12:07 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-21 12:07 +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, MultiplyVector, 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 = "_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 if self.parameterizedBand:
155 action.name_prefix = f"{{band}}_{action.name_prefix}"
156 action.name_suffix = name_suffix.format(minimum=minimum)
158 units.update(
159 {
160 action.name_median: unit,
161 action.name_sigmaMad: unit,
162 action.name_count: "count",
163 action.name_select_minimum: unit_select,
164 action.name_select_median: unit_select,
165 action.name_select_maximum: unit_select,
166 }
167 )
168 return units
170 def setDefaults(self):
171 super().setDefaults()
173 for name in self._names:
174 name_capital = name.capitalize()
175 for minimum in range(self._mag_low_min, self._mag_low_max + 1):
176 setattr(
177 self.process.calculateActions,
178 f"{name}{minimum}",
179 CalcBinnedStatsAction(key_vector=f"y{name_capital}"),
180 )
182 def __call__(self, data: KeyedData, **kwargs):
183 return super().__call__(data=data, **kwargs)
186class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase):
187 """Base tool for diffs between reference and measured coadd mags.
189 Notes
190 -----
191 The default model flux is cModel.
192 """
194 def matchedRefDiffContext(self):
195 self.process.buildActions.diff = SubtractVector(
196 actionA=ConvertFluxToMag(
197 vectorKey=self.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True
198 ),
199 actionB=DivideVector(
200 actionA=self.process.buildActions.mags_ref,
201 # To convert to mmag
202 actionB=ConstantValue(value=1e-3),
203 ),
204 )
206 def matchedRefChiContext(self):
207 self.process.buildActions.diff = DivideVector(
208 actionA=SubtractVector(
209 actionA=LoadVector(vectorKey=self.process.buildActions.fluxes_meas.vectorKey),
210 actionB=LoadVector(vectorKey=self.process.buildActions.fluxes_ref.vectorKey),
211 ),
212 actionB=LoadVector(vectorKey=f"{self.process.buildActions.fluxes_meas.vectorKey}Err"),
213 )
215 def setDefaults(self):
216 super().setDefaults()
218 self.process.buildActions.fluxes_meas = LoadVector(vectorKey="{band}_cModelFlux")
219 self.process.filterActions.yAll = DownselectVector(
220 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector")
221 )
222 self.process.filterActions.yGalaxies = DownselectVector(
223 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector")
224 )
225 self.process.filterActions.yStars = DownselectVector(
226 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector")
227 )
230# The diamond inheritance on MatchedRefCoaddTool seems ok
231class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric):
232 """Metric for diffs between reference and CModel coadd mags."""
234 def matchedRefDiffContext(self):
235 super().matchedRefDiffContext()
236 self.unit = "mmag"
237 self.name_prefix = "photom_mag_cModelFlux_{name_class}_diff_"
238 self.produce.metric.units = self.configureMetrics()
240 def matchedRefChiContext(self):
241 super().matchedRefChiContext()
242 self.unit = ""
243 self.name_prefix = "photom_mag_cModelFlux_{name_class}_chi_"
244 self.produce.metric.units = self.configureMetrics()
246 def setDefaults(self):
247 super().setDefaults()
250class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase):
251 """Base tool for diffs between reference and measured coadd astrometry."""
253 scale_factor = Field[float](
254 doc="The factor to multiply positions by (i.e. the pixel scale if coordinates have pixel units)",
255 default=200,
256 )
257 variable = ChoiceField[str](
258 doc="The astrometric variable to compute metrics for",
259 allowed={
260 "x": "x",
261 "y": "y",
262 },
263 optional=False,
264 )
266 # TODO: Determine if this can be put back into setDefaults w/o this:
267 # lsst.pex.config.config.FieldValidationError:
268 # Field 'process.buildActions.pos_meas.vectorKey' failed validation:
269 # Required value cannot be None
270 def _setPos(self):
271 self.process.buildActions.pos_meas = LoadVector(vectorKey=self.variable)
272 self.process.buildActions.pos_ref = LoadVector(vectorKey=f"refcat_{self.variable}")
274 def matchedRefDiffContext(self):
275 self._setPos()
276 self.process.buildActions.diff = MultiplyVector(
277 actionA=ConstantValue(value=self.scale_factor),
278 actionB=SubtractVector(
279 actionA=self.process.buildActions.pos_meas,
280 actionB=self.process.buildActions.pos_ref,
281 ),
282 )
284 def matchedRefChiContext(self):
285 self._setPos()
286 self.process.buildActions.diff = DivideVector(
287 actionA=SubtractVector(
288 actionA=self.process.buildActions.pos_meas,
289 actionB=self.process.buildActions.pos_ref,
290 ),
291 actionB=LoadVector(vectorKey=f"{self.process.buildActions.pos_meas.vectorKey}Err"),
292 )
294 def setDefaults(self):
295 super().setDefaults()
297 self.process.filterActions.yAll = DownselectVector(
298 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector")
299 )
300 self.process.filterActions.yGalaxies = DownselectVector(
301 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector")
302 )
303 self.process.filterActions.yStars = DownselectVector(
304 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector")
305 )
308class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric):
309 """Metric for diffs between reference and base coadd centroids."""
311 def matchedRefDiffContext(self):
312 super().matchedRefDiffContext()
313 self.unit = "mas"
314 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_"
315 self.produce.metric.units = self.configureMetrics()
317 def matchedRefChiContext(self):
318 super().matchedRefChiContext()
319 self.unit = ""
320 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_chi_"
321 self.produce.metric.units = self.configureMetrics()
323 def setDefaults(self):
324 super().setDefaults()
327class MatchedRefCoaddPlot(AnalysisTool):
328 def setDefaults(self):
329 super().setDefaults()
330 self.produce.plot = ScatterPlotWithTwoHists()
332 self.produce.plot.plotTypes = ["galaxies", "stars"]
333 self.produce.plot.xAxisLabel = "Reference Magnitude (mag)"
336class MatchedRefCoaddCModelPlot(MatchedRefCoaddPlot):
337 def setDefaults(self):
338 super().setDefaults()
339 self.produce.plot.magLabel = "cModel mag"
341 # downselect the cModelFlux as well
342 for prefix, plural in (("star", "Stars"), ("galaxy", "Galaxies")):
343 for suffix in ("", "Err"):
344 setattr(
345 self.process.filterActions,
346 f"{prefix}_cModelFlux{suffix}",
347 DownselectVector(
348 vectorKey=f"{{band}}_cModelFlux{suffix}",
349 selector=VectorSelector(vectorKey=f"{prefix}Selector"),
350 ),
351 )
353 statAction = ScatterPlotStatsAction(vectorKey=f"y{plural.capitalize()}")
354 fluxType = f"{prefix}_cModelFlux"
355 statAction.highSNSelector.fluxType = fluxType
356 statAction.highSNSelector.threshold = 200
357 statAction.lowSNSelector.fluxType = fluxType
358 statAction.lowSNSelector.threshold = 10
359 statAction.fluxType = fluxType
360 setattr(self.process.calculateActions, plural, statAction)
363class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool):
364 def matchedRefDiffContext(self):
365 super().matchedRefDiffContext()
366 self.produce.plot.yAxisLabel = "cModel - Reference mmag"
368 def matchedRefChiContext(self):
369 super().matchedRefChiContext()
370 self.produce.plot.yAxisLabel = "chi = (cModel - Reference mag)/error"
372 def setDefaults(self):
373 super().setDefaults()
376class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool):
377 def matchedRefDiffContext(self):
378 super().matchedRefDiffContext()
379 self.produce.plot.yAxisLabel = f"{self.variable} position (pix)"
381 def matchedRefChiContext(self):
382 super().matchedRefChiContext()
383 self.produce.plot.yAxisLabel = f"chi = (slot - Reference {self.variable} position)/error"
385 def setDefaults(self):
386 super().setDefaults()