Coverage for python/lsst/analysis/tools/atools/astrometricRepeatability.py: 24%
136 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 04:48 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-02 04:48 -0700
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 "AstrometricRepeatability",
25 "AstrometricRelativeRepeatability",
26 "StellarAstrometricResidualsRAFocalPlanePlot",
27 "StellarAstrometricResidualsDecFocalPlanePlot",
28 "StellarAstrometricResidualStdDevRAFocalPlanePlot",
29 "StellarAstrometricResidualStdDevDecFocalPlanePlot",
30 "StellarAstrometricResidualsRASkyPlot",
31 "StellarAstrometricResidualsDecSkyPlot",
32)
34from lsst.pex.config import ChoiceField, Field
36from ..actions.keyedData import CalcRelativeDistances
37from ..actions.plot import FocalPlanePlot, HistPanel, HistPlot, SkyPlot
38from ..actions.scalar import MedianAction
39from ..actions.vector import (
40 BandSelector,
41 ConvertFluxToMag,
42 ConvertUnits,
43 DownselectVector,
44 LoadVector,
45 PerGroupStatistic,
46 RAcosDec,
47 RangeSelector,
48 ResidualWithPerGroupStatistic,
49 SnSelector,
50 ThresholdSelector,
51)
52from ..interfaces import AnalysisTool
55class StellarAstrometricResidualsBase(AnalysisTool):
56 """Plot mean astrometric residuals.
58 The individual source measurements are grouped together by object index
59 and the per-group centroid is computed. The residuals between the
60 individual sources and these centroids are then used to construct a plot
61 showing the mean residual as a function of the focal-plane or sky position.
62 """
64 def setDefaults(self):
65 super().setDefaults()
67 # Apply per-source selection criteria
68 self.prep.selectors.bandSelector = BandSelector()
70 self.process.buildActions.mags = ConvertFluxToMag(vectorKey="psfFlux")
71 self.process.buildActions.residual = ConvertUnits()
72 self.process.buildActions.residual.inUnit = "degree"
73 self.process.buildActions.residual.outUnit = "marcsec"
75 self.process.buildActions.residual.buildAction = ResidualWithPerGroupStatistic()
77 self.process.buildActions.x = LoadVector(vectorKey="x")
78 self.process.buildActions.y = LoadVector(vectorKey="y")
80 self.process.buildActions.detector = LoadVector(vectorKey="detector")
82 self.process.filterActions.x = DownselectVector(vectorKey="x")
83 self.process.filterActions.x.selector = ThresholdSelector(
84 vectorKey="mags",
85 op="le",
86 threshold=24,
87 )
88 self.process.filterActions.y = DownselectVector(
89 vectorKey="y", selector=self.process.filterActions.x.selector
90 )
91 self.process.filterActions.z = DownselectVector(
92 vectorKey="residual", selector=self.process.filterActions.x.selector
93 )
94 self.process.filterActions.detector = DownselectVector(
95 vectorKey="detector", selector=self.process.filterActions.x.selector
96 )
98 self.process.buildActions.statMask = SnSelector()
99 self.process.buildActions.statMask.threshold = 0
100 self.process.buildActions.statMask.fluxType = "psfFlux"
103class StellarAstrometricResidualsRASkyPlot(StellarAstrometricResidualsBase):
104 """Plot mean astrometric residuals in RA as a function of the position in
105 RA and Dec.
106 """
108 def setDefaults(self):
109 super().setDefaults()
111 # Compute per-group quantities
112 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
113 self.process.buildActions.x = LoadVector(vectorKey="coord_ra")
114 self.process.buildActions.y = LoadVector(vectorKey="coord_dec")
116 self.produce = SkyPlot()
118 self.produce.plotTypes = ["any"]
119 self.produce.plotName = "ra_residuals"
120 self.produce.xAxisLabel = "R.A. (degrees)"
121 self.produce.yAxisLabel = "Dec. (degrees)"
122 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$"
125class StellarAstrometricResidualsDecSkyPlot(StellarAstrometricResidualsBase):
126 """Plot mean astrometric residuals in RA as a function of the position in
127 RA and Dec.
128 """
130 def setDefaults(self):
131 super().setDefaults()
133 # Compute per-group quantities
134 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
135 self.process.buildActions.x = LoadVector(vectorKey="coord_ra")
136 self.process.buildActions.y = LoadVector(vectorKey="coord_dec")
138 self.produce = SkyPlot()
140 self.produce.plotTypes = ["any"]
141 self.produce.plotName = "ra_residuals"
142 self.produce.xAxisLabel = "R.A. (degrees)"
143 self.produce.yAxisLabel = "Dec. (degrees)"
144 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$"
147class StellarAstrometricResidualsRAFocalPlanePlot(StellarAstrometricResidualsBase):
148 """Plot mean astrometric residuals in RA as a function of the focal plane
149 position.
150 """
152 def setDefaults(self):
153 super().setDefaults()
155 # Compute per-group quantities
156 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
158 self.produce = FocalPlanePlot()
159 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$ (mArcsec)"
162class StellarAstrometricResidualStdDevRAFocalPlanePlot(StellarAstrometricResidualsBase):
163 """Plot mean astrometric residuals in RA as a function of the focal plane
164 position.
165 """
167 def setDefaults(self):
168 super().setDefaults()
170 # Compute per-group quantities
171 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
173 self.produce = FocalPlanePlot()
174 self.produce.statistic = "std"
175 self.produce.zAxisLabel = "Std(RAcos(Dec) - RAcos(Dec)$_{mean}$) (mArcsec)"
178class StellarAstrometricResidualsDecFocalPlanePlot(StellarAstrometricResidualsBase):
179 """Plot mean astrometric residuals in RA as a function of the focal plane
180 position.
181 """
183 def setDefaults(self):
184 super().setDefaults()
186 # Compute per-group quantities
187 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
189 self.produce = FocalPlanePlot()
190 self.produce.zAxisLabel = "Dec - Dec$_{mean}$ (mArcsec)"
193class StellarAstrometricResidualStdDevDecFocalPlanePlot(StellarAstrometricResidualsBase):
194 """Plot mean astrometric residuals in RA as a function of the focal plane
195 position.
196 """
198 def setDefaults(self):
199 super().setDefaults()
201 # Compute per-group quantities
202 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
204 self.produce = FocalPlanePlot()
205 self.produce.statistic = "std"
206 self.produce.zAxisLabel = "Std(Dec - Dec$_{mean}$) (mArcsec)"
209class AstrometricRelativeRepeatability(AnalysisTool):
210 """Calculate the AMx, ADx, AFx metrics and make histograms showing the data
211 used to compute the metrics.
212 """
214 fluxType = Field[str](doc="Flux type to calculate repeatability with", default="psfFlux")
215 xValue = Field[int](doc="Metric suffix corresponding to annulus size (1, 2, or 3)", default=1)
217 def setDefaults(self):
218 super().setDefaults()
219 self.prep.selectors.bandSelector = BandSelector()
220 # Following what was done in faro, only sources with S/N between 50
221 # and 50000 are included. The other filtering that was done in faro
222 # is now covered by only including sources from isolated_star_sources.
223 self.prep.selectors.snSelector = SnSelector()
224 self.prep.selectors.snSelector.threshold = 50
225 self.prep.selectors.snSelector.maxSN = 50000
227 # Select only sources with magnitude between 17 and 21.5
228 self.process.filterActions.coord_ra = DownselectVector(vectorKey="coord_ra")
229 self.process.filterActions.coord_ra.selector = RangeSelector(
230 vectorKey="mags", minimum=17, maximum=21.5
231 )
232 self.process.filterActions.coord_dec = DownselectVector(
233 vectorKey="coord_dec", selector=self.process.filterActions.coord_ra.selector
234 )
235 self.process.filterActions.obj_index = DownselectVector(
236 vectorKey="obj_index", selector=self.process.filterActions.coord_ra.selector
237 )
238 self.process.filterActions.visit = DownselectVector(
239 vectorKey="visit", selector=self.process.filterActions.coord_ra.selector
240 )
242 self.process.calculateActions.rms = CalcRelativeDistances()
244 self.produce.metric.units = {
245 "AMx": "mas",
246 "AFx": "percent",
247 "ADx": "mas",
248 }
250 self.produce.plot = HistPlot()
252 self.produce.plot.panels["panel_sep"] = HistPanel()
253 self.produce.plot.panels["panel_sep"].hists = dict(separationResiduals="Source separations")
254 self.produce.plot.panels["panel_sep"].label = "Separation Distances (marcsec)"
256 self.produce.plot.panels["panel_rms"] = HistPanel()
257 self.produce.plot.panels["panel_rms"].hists = dict(rmsDistances="Object RMS")
258 self.produce.plot.panels["panel_rms"].label = "Per-Object RMS (marcsec)"
259 # TODO: DM-39163 add reference lines for ADx, AMx, and AFx.
261 def finalize(self):
262 super().finalize()
263 self.prep.selectors.snSelector.fluxType = self.fluxType
264 self.process.buildActions.mags = ConvertFluxToMag(vectorKey=self.fluxType)
266 self.produce.metric.newNames = {
267 "AMx": f"{{band}}_AM{self.xValue}",
268 "AFx": f"{{band}}_AF{self.xValue}",
269 "ADx": f"{{band}}_AD{self.xValue}",
270 }
273class AstrometricRepeatability(AnalysisTool):
274 """Calculate the median position RMS of point sources."""
276 fluxType = Field[str](doc="Flux type to calculate repeatability with", default="psfFlux")
277 level = Field[int](
278 doc="Set metric name for level 1 or 2 data product (1 or 2). Input connections must be set separately"
279 " to correspond with this value.",
280 default=1,
281 )
282 coordinate = ChoiceField[str](
283 doc="RA or Dec",
284 allowed={"RA": "Repeatability in RA direction", "Dec": "Repeatability in Dec direction"},
285 )
287 def setDefaults(self):
288 super().setDefaults()
289 self.prep.selectors.bandSelector = BandSelector()
291 self.process.buildActions.perGroupStd = ConvertUnits()
292 self.process.buildActions.perGroupStd.inUnit = "degree"
293 self.process.buildActions.perGroupStd.outUnit = "marcsec"
294 self.process.buildActions.perGroupStd.buildAction = PerGroupStatistic()
295 self.process.buildActions.perGroupStd.buildAction.func = "std"
296 self.process.buildActions.perGroupStd.buildAction.buildAction.vectorKey = "coord_dec"
298 self.process.buildActions.perGroupMag = PerGroupStatistic()
299 self.process.buildActions.perGroupMag.func = "mean"
300 self.process.buildActions.perGroupMag.buildAction = ConvertFluxToMag(vectorKey=self.fluxType)
302 # Select only sources with magnitude between 17 and 21.5
303 self.process.filterActions.perGroupStdFiltered = DownselectVector(vectorKey="perGroupStd")
304 self.process.filterActions.perGroupStdFiltered.selector = RangeSelector(
305 vectorKey="perGroupMag", minimum=17, maximum=21.5
306 )
308 self.process.calculateActions.astromRepeatStdev = MedianAction(vectorKey="perGroupStdFiltered")
310 self.produce.metric.units = {
311 "astromRepeatStdev": "mas",
312 }
314 self.produce.plot = HistPlot()
316 self.produce.plot.panels["panel_rms"] = HistPanel()
317 self.produce.plot.panels["panel_rms"].hists = dict(perGroupStdFiltered="Per-Object RMS")
318 self.produce.plot.panels["panel_rms"].label = "Per-Object RMS (marcsec)"
320 def finalize(self):
321 super().finalize()
322 self.process.buildActions.perGroupMag.buildAction.vectorKey = self.fluxType
324 if self.coordinate == "RA":
325 self.process.buildActions.perGroupStd.buildAction.buildAction = RAcosDec()
327 self.produce.metric.newNames = {
328 "astromRepeatStdev": f"{{band}}_dmL{self.level}AstroErr_{self.coordinate}"
329 }