Coverage for python/lsst/analysis/tools/atools/diffMatched.py: 30%

157 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-22 04:02 -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, MultiplyVector, 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 = "_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 if self.parameterizedBand: 

155 action.name_prefix = f"{{band}}_{action.name_prefix}" 

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

157 

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 

169 

170 def setDefaults(self): 

171 super().setDefaults() 

172 

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 ) 

181 

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

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

184 

185 

186class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase): 

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

188 

189 Notes 

190 ----- 

191 The default model flux is cModel. 

192 """ 

193 

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 ) 

205 

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 ) 

214 

215 def setDefaults(self): 

216 super().setDefaults() 

217 

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 ) 

228 

229 

230# The diamond inheritance on MatchedRefCoaddTool seems ok 

231class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric): 

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

233 

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

239 

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

245 

246 def setDefaults(self): 

247 super().setDefaults() 

248 

249 

250class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase): 

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

252 

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 ) 

265 

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

273 

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 ) 

283 

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 ) 

293 

294 def setDefaults(self): 

295 super().setDefaults() 

296 

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 ) 

306 

307 

308class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric): 

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

310 

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

316 

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

322 

323 def setDefaults(self): 

324 super().setDefaults() 

325 

326 

327class MatchedRefCoaddPlot(AnalysisTool): 

328 def setDefaults(self): 

329 super().setDefaults() 

330 self.produce.plot = ScatterPlotWithTwoHists() 

331 

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

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

334 

335 

336class MatchedRefCoaddCModelPlot(MatchedRefCoaddPlot): 

337 def setDefaults(self): 

338 super().setDefaults() 

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

340 

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 ) 

352 

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) 

361 

362 

363class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool): 

364 def matchedRefDiffContext(self): 

365 super().matchedRefDiffContext() 

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

367 

368 def matchedRefChiContext(self): 

369 super().matchedRefChiContext() 

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

371 

372 def setDefaults(self): 

373 super().setDefaults() 

374 

375 

376class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool): 

377 def matchedRefDiffContext(self): 

378 super().matchedRefDiffContext() 

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

380 

381 def matchedRefChiContext(self): 

382 super().matchedRefChiContext() 

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

384 

385 def setDefaults(self): 

386 super().setDefaults()