Coverage for python/lsst/analysis/tools/atools/astrometricRepeatability.py: 23%
113 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-23 09:42 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-23 09:42 +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 "AstrometricRelativeRepeatability",
25 "StellarAstrometricResidualsRAFocalPlanePlot",
26 "StellarAstrometricResidualsDecFocalPlanePlot",
27 "StellarAstrometricResidualStdDevRAFocalPlanePlot",
28 "StellarAstrometricResidualStdDevDecFocalPlanePlot",
29 "StellarAstrometricResidualsRASkyPlot",
30 "StellarAstrometricResidualsDecSkyPlot",
31)
33from lsst.pex.config import Field
35from ..actions.keyedData import CalcRelativeDistances
36from ..actions.plot import FocalPlanePlot, HistPanel, HistPlot, SkyPlot
37from ..actions.vector import (
38 BandSelector,
39 ConvertFluxToMag,
40 ConvertUnits,
41 DownselectVector,
42 LoadVector,
43 RAcosDec,
44 RangeSelector,
45 ResidualWithPerGroupStatistic,
46 SnSelector,
47 ThresholdSelector,
48)
49from ..interfaces import AnalysisTool
52class StellarAstrometricResidualsBase(AnalysisTool):
53 """Plot mean astrometric residuals.
55 The individual source measurements are grouped together by object index
56 and the per-group centroid is computed. The residuals between the
57 individual sources and these centroids are then used to construct a plot
58 showing the mean residual as a function of the focal-plane or sky position.
59 """
61 def setDefaults(self):
62 super().setDefaults()
64 # Apply per-source selection criteria
65 self.prep.selectors.bandSelector = BandSelector()
67 self.process.buildActions.mags = ConvertFluxToMag(vectorKey="psfFlux")
68 self.process.buildActions.residual = ConvertUnits()
69 self.process.buildActions.residual.inUnit = "degree"
70 self.process.buildActions.residual.outUnit = "marcsec"
72 self.process.buildActions.residual.buildAction = ResidualWithPerGroupStatistic()
74 self.process.buildActions.x = LoadVector(vectorKey="x")
75 self.process.buildActions.y = LoadVector(vectorKey="y")
77 self.process.buildActions.detector = LoadVector(vectorKey="detector")
79 self.process.filterActions.x = DownselectVector(vectorKey="x")
80 self.process.filterActions.x.selector = ThresholdSelector(
81 vectorKey="mags",
82 op="le",
83 threshold=24,
84 )
85 self.process.filterActions.y = DownselectVector(
86 vectorKey="y", selector=self.process.filterActions.x.selector
87 )
88 self.process.filterActions.z = DownselectVector(
89 vectorKey="residual", selector=self.process.filterActions.x.selector
90 )
91 self.process.filterActions.detector = DownselectVector(
92 vectorKey="detector", selector=self.process.filterActions.x.selector
93 )
95 self.process.buildActions.statMask = SnSelector()
96 self.process.buildActions.statMask.threshold = 0
97 self.process.buildActions.statMask.fluxType = "psfFlux"
100class StellarAstrometricResidualsRASkyPlot(StellarAstrometricResidualsBase):
101 """Plot mean astrometric residuals in RA as a function of the position in
102 RA and Dec.
103 """
105 def setDefaults(self):
106 super().setDefaults()
108 # Compute per-group quantities
109 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
110 self.process.buildActions.x = LoadVector(vectorKey="coord_ra")
111 self.process.buildActions.y = LoadVector(vectorKey="coord_dec")
113 self.produce = SkyPlot()
115 self.produce.plotTypes = ["any"]
116 self.produce.plotName = "ra_residuals"
117 self.produce.xAxisLabel = "R.A. (degrees)"
118 self.produce.yAxisLabel = "Dec. (degrees)"
119 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$"
122class StellarAstrometricResidualsDecSkyPlot(StellarAstrometricResidualsBase):
123 """Plot mean astrometric residuals in RA as a function of the position in
124 RA and Dec.
125 """
127 def setDefaults(self):
128 super().setDefaults()
130 # Compute per-group quantities
131 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
132 self.process.buildActions.x = LoadVector(vectorKey="coord_ra")
133 self.process.buildActions.y = LoadVector(vectorKey="coord_dec")
135 self.produce = SkyPlot()
137 self.produce.plotTypes = ["any"]
138 self.produce.plotName = "ra_residuals"
139 self.produce.xAxisLabel = "R.A. (degrees)"
140 self.produce.yAxisLabel = "Dec. (degrees)"
141 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$"
144class StellarAstrometricResidualsRAFocalPlanePlot(StellarAstrometricResidualsBase):
145 """Plot mean astrometric residuals in RA as a function of the focal plane
146 position.
147 """
149 def setDefaults(self):
150 super().setDefaults()
152 # Compute per-group quantities
153 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
155 self.produce = FocalPlanePlot()
156 self.produce.xAxisLabel = "x (focal plane)"
157 self.produce.yAxisLabel = "y (focal plane)"
158 self.produce.zAxisLabel = "RAcos(Dec) - RAcos(Dec)$_{mean}$ (mArcsec)"
161class StellarAstrometricResidualStdDevRAFocalPlanePlot(StellarAstrometricResidualsBase):
162 """Plot mean astrometric residuals in RA as a function of the focal plane
163 position.
164 """
166 def setDefaults(self):
167 super().setDefaults()
169 # Compute per-group quantities
170 self.process.buildActions.residual.buildAction.buildAction = RAcosDec()
172 self.produce = FocalPlanePlot()
173 self.produce.statistic = "std"
174 self.produce.xAxisLabel = "x (focal plane)"
175 self.produce.yAxisLabel = "y (focal plane)"
176 self.produce.zAxisLabel = "Std(RAcos(Dec) - RAcos(Dec)$_{mean}$) (mArcsec)"
179class StellarAstrometricResidualsDecFocalPlanePlot(StellarAstrometricResidualsBase):
180 """Plot mean astrometric residuals in RA as a function of the focal plane
181 position.
182 """
184 def setDefaults(self):
185 super().setDefaults()
187 # Compute per-group quantities
188 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
190 self.produce = FocalPlanePlot()
191 self.produce.xAxisLabel = "x (focal plane)"
192 self.produce.yAxisLabel = "y (focal plane)"
193 self.produce.zAxisLabel = "Dec - Dec$_{mean}$ (mArcsec)"
196class StellarAstrometricResidualStdDevDecFocalPlanePlot(StellarAstrometricResidualsBase):
197 """Plot mean astrometric residuals in RA as a function of the focal plane
198 position.
199 """
201 def setDefaults(self):
202 super().setDefaults()
204 # Compute per-group quantities
205 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec"
207 self.produce = FocalPlanePlot()
208 self.produce.statistic = "std"
209 self.produce.xAxisLabel = "x (focal plane)"
210 self.produce.yAxisLabel = "y (focal plane)"
211 self.produce.zAxisLabel = "Std(Dec - Dec$_{mean}$) (mArcsec)"
214class AstrometricRelativeRepeatability(AnalysisTool):
215 """Calculate the AMx, ADx, AFx metrics and make histograms showing the data
216 used to compute the metrics.
217 """
219 fluxType = Field[str](doc="Flux type to calculate repeatability with", default="psfFlux")
220 xValue = Field[int](doc="Metric suffix corresponding to annulus size (1, 2, or 3)", default=1)
222 def setDefaults(self):
223 super().setDefaults()
224 self.prep.selectors.bandSelector = BandSelector()
225 # Following what was done in faro, only sources with S/N between 50
226 # and 50000 are included. The other filtering that was done in faro
227 # is now covered by only including sources from isolated_star_sources.
228 self.prep.selectors.snSelector = SnSelector()
229 self.prep.selectors.snSelector.threshold = 50
230 self.prep.selectors.snSelector.maxSN = 50000
232 # Select only sources with magnitude between 17 and 21.5
233 self.process.filterActions.coord_ra = DownselectVector(vectorKey="coord_ra")
234 self.process.filterActions.coord_ra.selector = RangeSelector(key="mags", minimum=17, maximum=21.5)
235 self.process.filterActions.coord_dec = DownselectVector(
236 vectorKey="coord_dec", selector=self.process.filterActions.coord_ra.selector
237 )
238 self.process.filterActions.obj_index = DownselectVector(
239 vectorKey="obj_index", selector=self.process.filterActions.coord_ra.selector
240 )
241 self.process.filterActions.visit = DownselectVector(
242 vectorKey="visit", selector=self.process.filterActions.coord_ra.selector
243 )
245 self.process.calculateActions.rms = CalcRelativeDistances()
247 self.produce.metric.units = {
248 "AMx": "mas",
249 "AFx": "percent",
250 "ADx": "mas",
251 }
253 self.produce.plot = HistPlot()
255 self.produce.plot.panels["panel_sep"] = HistPanel()
256 self.produce.plot.panels["panel_sep"].hists = dict(separationResiduals="Source separations")
257 self.produce.plot.panels["panel_sep"].label = "Separation Distances (marcsec)"
259 self.produce.plot.panels["panel_rms"] = HistPanel()
260 self.produce.plot.panels["panel_rms"].hists = dict(rmsDistances="Object RMS")
261 self.produce.plot.panels["panel_rms"].label = "Per-Object RMS (marcsec)"
262 # TODO: DM-39163 add reference lines for ADx, AMx, and AFx.
264 def finalize(self):
265 super().finalize()
266 self.prep.selectors.snSelector.fluxType = self.fluxType
267 self.process.buildActions.mags = ConvertFluxToMag(vectorKey=self.fluxType)
269 self.produce.metric.newNames = {
270 "AMx": f"{{band}}_AM{self.xValue}",
271 "AFx": f"{{band}}_AF{self.xValue}",
272 "ADx": f"{{band}}_AD{self.xValue}",
273 }