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 04:22 -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 

22 

23__all__ = ( 

24 "MatchedRefCoaddToolBase", 

25 "MatchedRefCoaddMetric", 

26 "MatchedRefCoaddDiffMagTool", 

27 "MatchedRefCoaddCModelFluxMetric", 

28 "MatchedRefCoaddDiffPositionTool", 

29 "MatchedRefCoaddPositionMetric", 

30) 

31 

32from lsst.pex.config import ChoiceField, Field 

33 

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 

40 

41 

42class MatchedRefCoaddToolBase(AnalysisTool): 

43 """Base tool for matched-to-reference metrics/plots on coadds. 

44 

45 Metrics/plots are expected to use the reference magnitude and 

46 require separate star/galaxy/all source selections. 

47 

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

54 

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 ) 

63 

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

70 

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 ) 

80 

81 

82class MatchedRefCoaddMetric(MatchedRefCoaddToolBase): 

83 """Base tool for matched-to-reference metrics on coadds.""" 

84 

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

87 

88 _mag_low_min: int = 15 

89 _mag_low_max: int = 27 

90 _mag_interval: int = 1 

91 

92 _names = ("stars", "galaxies", "all") 

93 _types = ("unresolved", "resolved", "all") 

94 

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 ) 

101 

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. 

110 

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

121 

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 

129 

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" 

138 

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

144 

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 ) 

152 

153 action.name_prefix = name_prefix.format(name_class=name_class) 

154 action.name_suffix = name_suffix.format(minimum=minimum) 

155 

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 

167 

168 def setDefaults(self): 

169 super().setDefaults() 

170 

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 ) 

179 

180 def __call__(self, data: KeyedData, **kwargs): 

181 self._validate() 

182 return super().__call__(data=data, **kwargs) 

183 

184 

185class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase): 

186 """Base tool for diffs between reference and measured coadd mags. 

187 

188 Notes 

189 ----- 

190 The default model flux is cModel. 

191 """ 

192 

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 ) 

204 

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 ) 

213 

214 def setDefaults(self): 

215 super().setDefaults() 

216 

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 ) 

227 

228 

229# The diamond inheritance on MatchedRefCoaddTool seems ok 

230class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric): 

231 """Metric for diffs between reference and CModel coadd mags.""" 

232 

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

238 

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

244 

245 def setDefaults(self): 

246 super().setDefaults() 

247 

248 

249class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase): 

250 """Base tool for diffs between reference and measured coadd astrometry.""" 

251 

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 ) 

260 

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

268 

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 ) 

275 

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 ) 

285 

286 def setDefaults(self): 

287 super().setDefaults() 

288 

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 ) 

298 

299 

300class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric): 

301 """Metric for diffs between reference and base coadd centroids.""" 

302 

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

308 

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

314 

315 def setDefaults(self): 

316 super().setDefaults() 

317 

318 

319class MatchedRefCoaddPlot(AnalysisTool): 

320 def setDefaults(self): 

321 super().setDefaults() 

322 self.produce.plot = ScatterPlotWithTwoHists() 

323 

324 self.produce.plot.plotTypes = ["galaxies", "stars"] 

325 self.produce.plot.xAxisLabel = "Reference Magnitude (mag)" 

326 

327 

328class MatchedRefCoaddCModelPlot(MatchedRefCoaddPlot): 

329 def setDefaults(self): 

330 super().setDefaults() 

331 self.produce.plot.magLabel = "cModel mag" 

332 

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 ) 

344 

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) 

353 

354 

355class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool): 

356 def matchedRefDiffContext(self): 

357 super().matchedRefDiffContext() 

358 self.produce.plot.yAxisLabel = "cModel - Reference mmag" 

359 

360 def matchedRefChiContext(self): 

361 super().matchedRefChiContext() 

362 self.produce.plot.yAxisLabel = "chi = (cModel - Reference mag)/error" 

363 

364 def setDefaults(self): 

365 super().setDefaults() 

366 

367 

368class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool): 

369 def matchedRefDiffContext(self): 

370 super().matchedRefDiffContext() 

371 self.produce.plot.yAxisLabel = f"{self.variable} position (pix)" 

372 

373 def matchedRefChiContext(self): 

374 super().matchedRefChiContext() 

375 self.produce.plot.yAxisLabel = f"chi = (slot - Reference {self.variable} position)/error" 

376 

377 def setDefaults(self): 

378 super().setDefaults()