Coverage for python/lsst/analysis/tools/atools/astrometricRepeatability.py: 23%

113 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-02 12:29 +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 

22 

23__all__ = ( 

24 "AstrometricRelativeRepeatability", 

25 "StellarAstrometricResidualsRAFocalPlanePlot", 

26 "StellarAstrometricResidualsDecFocalPlanePlot", 

27 "StellarAstrometricResidualStdDevRAFocalPlanePlot", 

28 "StellarAstrometricResidualStdDevDecFocalPlanePlot", 

29 "StellarAstrometricResidualsRASkyPlot", 

30 "StellarAstrometricResidualsDecSkyPlot", 

31) 

32 

33from lsst.pex.config import Field 

34 

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 

50 

51 

52class StellarAstrometricResidualsBase(AnalysisTool): 

53 """Plot mean astrometric residuals. 

54 

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 """ 

60 

61 def setDefaults(self): 

62 super().setDefaults() 

63 

64 # Apply per-source selection criteria 

65 self.prep.selectors.bandSelector = BandSelector() 

66 

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" 

71 

72 self.process.buildActions.residual.buildAction = ResidualWithPerGroupStatistic() 

73 

74 self.process.buildActions.x = LoadVector(vectorKey="x") 

75 self.process.buildActions.y = LoadVector(vectorKey="y") 

76 

77 self.process.buildActions.detector = LoadVector(vectorKey="detector") 

78 

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 ) 

94 

95 self.process.buildActions.statMask = SnSelector() 

96 self.process.buildActions.statMask.threshold = 0 

97 self.process.buildActions.statMask.fluxType = "psfFlux" 

98 

99 

100class StellarAstrometricResidualsRASkyPlot(StellarAstrometricResidualsBase): 

101 """Plot mean astrometric residuals in RA as a function of the position in 

102 RA and Dec. 

103 """ 

104 

105 def setDefaults(self): 

106 super().setDefaults() 

107 

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") 

112 

113 self.produce = SkyPlot() 

114 

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}$" 

120 

121 

122class StellarAstrometricResidualsDecSkyPlot(StellarAstrometricResidualsBase): 

123 """Plot mean astrometric residuals in RA as a function of the position in 

124 RA and Dec. 

125 """ 

126 

127 def setDefaults(self): 

128 super().setDefaults() 

129 

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") 

134 

135 self.produce = SkyPlot() 

136 

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}$" 

142 

143 

144class StellarAstrometricResidualsRAFocalPlanePlot(StellarAstrometricResidualsBase): 

145 """Plot mean astrometric residuals in RA as a function of the focal plane 

146 position. 

147 """ 

148 

149 def setDefaults(self): 

150 super().setDefaults() 

151 

152 # Compute per-group quantities 

153 self.process.buildActions.residual.buildAction.buildAction = RAcosDec() 

154 

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)" 

159 

160 

161class StellarAstrometricResidualStdDevRAFocalPlanePlot(StellarAstrometricResidualsBase): 

162 """Plot mean astrometric residuals in RA as a function of the focal plane 

163 position. 

164 """ 

165 

166 def setDefaults(self): 

167 super().setDefaults() 

168 

169 # Compute per-group quantities 

170 self.process.buildActions.residual.buildAction.buildAction = RAcosDec() 

171 

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)" 

177 

178 

179class StellarAstrometricResidualsDecFocalPlanePlot(StellarAstrometricResidualsBase): 

180 """Plot mean astrometric residuals in RA as a function of the focal plane 

181 position. 

182 """ 

183 

184 def setDefaults(self): 

185 super().setDefaults() 

186 

187 # Compute per-group quantities 

188 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec" 

189 

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)" 

194 

195 

196class StellarAstrometricResidualStdDevDecFocalPlanePlot(StellarAstrometricResidualsBase): 

197 """Plot mean astrometric residuals in RA as a function of the focal plane 

198 position. 

199 """ 

200 

201 def setDefaults(self): 

202 super().setDefaults() 

203 

204 # Compute per-group quantities 

205 self.process.buildActions.residual.buildAction.buildAction.vectorKey = "coord_dec" 

206 

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)" 

212 

213 

214class AstrometricRelativeRepeatability(AnalysisTool): 

215 """Calculate the AMx, ADx, AFx metrics and make histograms showing the data 

216 used to compute the metrics. 

217 """ 

218 

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) 

221 

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 

231 

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( 

235 vectorKey="mags", minimum=17, maximum=21.5 

236 ) 

237 self.process.filterActions.coord_dec = DownselectVector( 

238 vectorKey="coord_dec", selector=self.process.filterActions.coord_ra.selector 

239 ) 

240 self.process.filterActions.obj_index = DownselectVector( 

241 vectorKey="obj_index", selector=self.process.filterActions.coord_ra.selector 

242 ) 

243 self.process.filterActions.visit = DownselectVector( 

244 vectorKey="visit", selector=self.process.filterActions.coord_ra.selector 

245 ) 

246 

247 self.process.calculateActions.rms = CalcRelativeDistances() 

248 

249 self.produce.metric.units = { 

250 "AMx": "mas", 

251 "AFx": "percent", 

252 "ADx": "mas", 

253 } 

254 

255 self.produce.plot = HistPlot() 

256 

257 self.produce.plot.panels["panel_sep"] = HistPanel() 

258 self.produce.plot.panels["panel_sep"].hists = dict(separationResiduals="Source separations") 

259 self.produce.plot.panels["panel_sep"].label = "Separation Distances (marcsec)" 

260 

261 self.produce.plot.panels["panel_rms"] = HistPanel() 

262 self.produce.plot.panels["panel_rms"].hists = dict(rmsDistances="Object RMS") 

263 self.produce.plot.panels["panel_rms"].label = "Per-Object RMS (marcsec)" 

264 # TODO: DM-39163 add reference lines for ADx, AMx, and AFx. 

265 

266 def finalize(self): 

267 super().finalize() 

268 self.prep.selectors.snSelector.fluxType = self.fluxType 

269 self.process.buildActions.mags = ConvertFluxToMag(vectorKey=self.fluxType) 

270 

271 self.produce.metric.newNames = { 

272 "AMx": f"{{band}}_AM{self.xValue}", 

273 "AFx": f"{{band}}_AF{self.xValue}", 

274 "ADx": f"{{band}}_AD{self.xValue}", 

275 }