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

154 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-05 04:42 -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.selectors import GalaxySelector, RangeSelector, StarSelector 

37from ..actions.vector.vectorActions import ( 

38 ConstantValue, 

39 DivideVector, 

40 DownselectVector, 

41 LoadVector, 

42 MagColumnNanoJansky, 

43 SubtractVector, 

44 VectorSelector, 

45) 

46from ..interfaces import AnalysisTool, KeyedData 

47 

48 

49class MatchedRefCoaddToolBase(AnalysisTool): 

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

51 

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

53 require separate star/galaxy/all source selections. 

54 

55 Notes 

56 ----- 

57 The tool does not use a standard coadd flag selector, because 

58 it is expected that the matcher has been configured to select 

59 appropriate candidates (and stores a match_candidate column). 

60 """ 

61 

62 def setDefaults(self): 

63 super().setDefaults() 

64 self.process.buildActions.fluxes_ref = LoadVector(vectorKey="refcat_flux_{band}") 

65 # TODO: Why won't vectorKey="fluxes_ref" work? 

66 # Does it need to be a filterAction? 

67 self.process.buildActions.mags_ref = MagColumnNanoJansky( 

68 vectorKey=self.process.buildActions.fluxes_ref.vectorKey 

69 ) 

70 

71 # Select any finite extendedness (but still exclude NaNs) 

72 self.process.buildActions.allSelector = StarSelector( 

73 vectorKey="refExtendedness", extendedness_maximum=1.0 

74 ) 

75 self.process.buildActions.galaxySelector = GalaxySelector(vectorKey="refExtendedness") 

76 self.process.buildActions.starSelector = StarSelector(vectorKey="refExtendedness") 

77 

78 self.process.filterActions.xAll = DownselectVector( 

79 vectorKey="mags_ref", selector=VectorSelector(vectorKey="allSelector") 

80 ) 

81 self.process.filterActions.xGalaxies = DownselectVector( 

82 vectorKey="mags_ref", selector=VectorSelector(vectorKey="galaxySelector") 

83 ) 

84 self.process.filterActions.xStars = DownselectVector( 

85 vectorKey="mags_ref", selector=VectorSelector(vectorKey="starSelector") 

86 ) 

87 

88 

89class MatchedRefCoaddMetric(MatchedRefCoaddToolBase): 

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

91 

92 name_prefix = Field[str](default=None, doc="Prefix for metric key") 

93 unit = Field[str](default=None, doc="Astropy unit of y-axis values") 

94 

95 _mag_low_min: int = 15 

96 _mag_low_max: int = 27 

97 _mag_interval: int = 1 

98 

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

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

101 

102 def _validate(self): 

103 if self.name_prefix is None or self.unit is None: 

104 raise ValueError( 

105 f"{self.name_prefix=} and {self.unit=} must not be None;" 

106 f" did you forget to set a valid context?" 

107 ) 

108 

109 def configureMetrics( 

110 self, 

111 unit: str | None = None, 

112 name_prefix: str | None = None, 

113 name_suffix: str = "_mad_ref_mag{minimum}", 

114 unit_select: str = "mag", 

115 ): 

116 """Configure metric actions and return units. 

117 

118 Parameters 

119 ---------- 

120 unit : `str` 

121 The (astropy) unit of the summary statistic metrics. 

122 name_prefix : `str` 

123 The prefix for the action (column) name. 

124 name_suffix : `str` 

125 The sufffix for the action (column) name. 

126 unit_select : `str` 

127 The (astropy) unit of the selection (x-axis) column. Default "mag". 

128 

129 Returns 

130 ------- 

131 units : `dict` [`str`, `str`] 

132 A dict of the unit (value) for each metric name (key) 

133 """ 

134 unit_is_none = unit is None 

135 name_prefix_is_none = name_prefix is None 

136 

137 if unit_is_none or name_prefix_is_none: 

138 if unit_is_none: 

139 unit = self.unit 

140 if name_prefix_is_none: 

141 name_prefix = self.name_prefix 

142 self._validate() 

143 if unit_select is None: 

144 unit_select = "mag" 

145 

146 assert name_prefix is not None 

147 units = {} 

148 for name, name_class in zip(self._names, self._types): 

149 name_capital = name.capitalize() 

150 x_key = f"x{name_capital}" 

151 

152 for minimum in range(self._mag_low_min, self._mag_low_max + 1): 

153 action = getattr(self.process.calculateActions, f"{name}{minimum}") 

154 action.selector_range = RangeSelector( 

155 key=x_key, 

156 minimum=minimum, 

157 maximum=minimum + self._mag_interval, 

158 ) 

159 

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

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

162 

163 units.update( 

164 { 

165 action.name_median: unit, 

166 action.name_sigmaMad: unit, 

167 action.name_count: "count", 

168 action.name_select_minimum: unit_select, 

169 action.name_select_median: unit_select, 

170 action.name_select_maximum: unit_select, 

171 } 

172 ) 

173 return units 

174 

175 def setDefaults(self): 

176 super().setDefaults() 

177 

178 for name in self._names: 

179 name_capital = name.capitalize() 

180 for minimum in range(self._mag_low_min, self._mag_low_max + 1): 

181 setattr( 

182 self.process.calculateActions, 

183 f"{name}{minimum}", 

184 CalcBinnedStatsAction(key_vector=f"y{name_capital}"), 

185 ) 

186 

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

188 self._validate() 

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

190 

191 

192class MatchedRefCoaddDiffMagTool(MatchedRefCoaddToolBase): 

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

194 

195 Notes 

196 ----- 

197 The default model flux is cModel. 

198 """ 

199 

200 def matchedRefDiffContext(self): 

201 self.process.buildActions.diff = SubtractVector( 

202 actionA=MagColumnNanoJansky( 

203 vectorKey=self.process.buildActions.fluxes_meas.vectorKey, returnMillimags=True 

204 ), 

205 actionB=DivideVector( 

206 actionA=self.process.buildActions.mags_ref, 

207 # To convert to mmag 

208 actionB=ConstantValue(value=1e-3), 

209 ), 

210 ) 

211 

212 def matchedRefChiContext(self): 

213 self.process.buildActions.diff = DivideVector( 

214 actionA=SubtractVector( 

215 actionA=LoadVector(vectorKey=self.process.buildActions.fluxes_meas.vectorKey), 

216 actionB=LoadVector(vectorKey=self.process.buildActions.fluxes_ref.vectorKey), 

217 ), 

218 actionB=LoadVector(vectorKey=f"{self.process.buildActions.fluxes_meas.vectorKey}Err"), 

219 ) 

220 

221 def setDefaults(self): 

222 super().setDefaults() 

223 

224 self.process.buildActions.fluxes_meas = LoadVector(vectorKey="{band}_cModelFlux") 

225 self.process.filterActions.yAll = DownselectVector( 

226 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector") 

227 ) 

228 self.process.filterActions.yGalaxies = DownselectVector( 

229 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector") 

230 ) 

231 self.process.filterActions.yStars = DownselectVector( 

232 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector") 

233 ) 

234 

235 

236# The diamond inheritance on MatchedRefCoaddTool seems ok 

237class MatchedRefCoaddCModelFluxMetric(MatchedRefCoaddDiffMagTool, MatchedRefCoaddMetric): 

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

239 

240 def matchedRefDiffContext(self): 

241 super().matchedRefDiffContext() 

242 self.unit = "mmag" 

243 self.name_prefix = "photom_mag_cModelFlux_{name_class}_diff_" 

244 self.produce.metric.units = self.configureMetrics() 

245 

246 def matchedRefChiContext(self): 

247 super().matchedRefChiContext() 

248 self.unit = "" 

249 self.name_prefix = "photom_mag_cModelFlux_{name_class}_chi_" 

250 self.produce.metric.units = self.configureMetrics() 

251 

252 def setDefaults(self): 

253 super().setDefaults() 

254 

255 

256class MatchedRefCoaddDiffPositionTool(MatchedRefCoaddToolBase): 

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

258 

259 variable = ChoiceField[str]( 

260 doc="The astrometric variable to compute metrics for", 

261 allowed={ 

262 "x": "x", 

263 "y": "y", 

264 }, 

265 optional=False, 

266 ) 

267 

268 # TODO: Determine if this can be put back into setDefaults w/o this: 

269 # lsst.pex.config.config.FieldValidationError: 

270 # Field 'process.buildActions.pos_meas.vectorKey' failed validation: 

271 # Required value cannot be None 

272 def _setPos(self): 

273 self.process.buildActions.pos_meas = LoadVector(vectorKey=self.variable) 

274 self.process.buildActions.pos_ref = LoadVector(vectorKey=f"refcat_{self.variable}") 

275 

276 def matchedRefDiffContext(self): 

277 self._setPos() 

278 self.process.buildActions.diff = SubtractVector( 

279 actionA=self.process.buildActions.pos_meas, 

280 actionB=self.process.buildActions.pos_ref, 

281 ) 

282 

283 def matchedRefChiContext(self): 

284 self._setPos() 

285 self.process.buildActions.diff = DivideVector( 

286 actionA=SubtractVector( 

287 actionA=self.process.buildActions.pos_meas, 

288 actionB=self.process.buildActions.pos_ref, 

289 ), 

290 actionB=LoadVector(vectorKey=f"{self.process.buildActions.pos_meas.vectorKey}Err"), 

291 ) 

292 

293 def setDefaults(self): 

294 super().setDefaults() 

295 

296 self.process.filterActions.yAll = DownselectVector( 

297 vectorKey="diff", selector=VectorSelector(vectorKey="allSelector") 

298 ) 

299 self.process.filterActions.yGalaxies = DownselectVector( 

300 vectorKey="diff", selector=VectorSelector(vectorKey="galaxySelector") 

301 ) 

302 self.process.filterActions.yStars = DownselectVector( 

303 vectorKey="diff", selector=VectorSelector(vectorKey="starSelector") 

304 ) 

305 

306 

307class MatchedRefCoaddPositionMetric(MatchedRefCoaddDiffPositionTool, MatchedRefCoaddMetric): 

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

309 

310 def matchedRefDiffContext(self): 

311 super().matchedRefDiffContext() 

312 self.unit = "pix" 

313 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" 

314 self.produce.metric.units = self.configureMetrics() 

315 

316 def matchedRefChiContext(self): 

317 super().matchedRefChiContext() 

318 self.unit = "" 

319 self.name_prefix = f"astrom_{self.variable}_{{name_class}}_diff_" 

320 self.produce.metric.units = self.configureMetrics() 

321 

322 def setDefaults(self): 

323 super().setDefaults() 

324 

325 

326class MatchedRefCoaddPlot(AnalysisTool): 

327 def setDefaults(self): 

328 super().setDefaults() 

329 self.produce.plot = ScatterPlotWithTwoHists() 

330 

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

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

333 

334 

335class MatchedRefCoaddCModelPlot(MatchedRefCoaddPlot): 

336 def setDefaults(self): 

337 super().setDefaults() 

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

339 

340 # downselect the cModelFlux as well 

341 for prefix, plural in (("star", "Stars"), ("galaxy", "Galaxies")): 

342 for suffix in ("", "Err"): 

343 setattr( 

344 self.process.filterActions, 

345 f"{prefix}_cModelFlux{suffix}", 

346 DownselectVector( 

347 vectorKey=f"{{band}}_cModelFlux{suffix}", 

348 selector=VectorSelector(vectorKey=f"{prefix}Selector"), 

349 ), 

350 ) 

351 

352 statAction = ScatterPlotStatsAction(vectorKey=f"y{plural.capitalize()}") 

353 fluxType = f"{prefix}_cModelFlux" 

354 statAction.highSNSelector.fluxType = fluxType 

355 statAction.highSNSelector.threshold = 200 

356 statAction.lowSNSelector.fluxType = fluxType 

357 statAction.lowSNSelector.threshold = 10 

358 statAction.fluxType = fluxType 

359 setattr(self.process.calculateActions, plural, statAction) 

360 

361 

362class MatchedRefCoaddCModelFluxPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffMagTool): 

363 def matchedRefDiffContext(self): 

364 super().matchedRefDiffContext() 

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

366 

367 def matchedRefChiContext(self): 

368 super().matchedRefChiContext() 

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

370 

371 def setDefaults(self): 

372 super().setDefaults() 

373 

374 

375class MatchedRefCoaddPositionPlot(MatchedRefCoaddCModelPlot, MatchedRefCoaddDiffPositionTool): 

376 def matchedRefDiffContext(self): 

377 super().matchedRefDiffContext() 

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

379 

380 def matchedRefChiContext(self): 

381 super().matchedRefChiContext() 

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

383 

384 def setDefaults(self): 

385 super().setDefaults()