Coverage for python/lsst/analysis/tools/atools/calibQuantityProfile.py: 62%

140 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-17 10:03 +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 

23from lsst.analysis.tools.interfaces._interfaces import KeyedDataSchema 

24 

25__all__ = ( 

26 "CalibQuantityBaseTool", 

27 "CalibQuantityAmpProfileScatterTool", 

28 "CalibQuantityAmpProfileHistTool", 

29 "CalibAmpScatterTool", 

30 "CalibDivisaderoScatterTool", 

31 "CalibPtcCovarScatterTool", 

32) 

33 

34from typing import cast 

35 

36import numpy as np 

37 

38from lsst.pex.config import Field 

39from lsst.pex.config.configurableActions import ConfigurableActionField 

40 

41from ..actions.plot.elements import HistElement, ScatterElement 

42from ..actions.plot.gridPlot import GridPanelConfig, GridPlot 

43from ..interfaces import AnalysisTool, KeyedData, KeyedDataAction, PlotElement, Vector 

44 

45 

46class PrepRepacker(KeyedDataAction): 

47 """Prep action to repack data.""" 

48 

49 panelKey = Field[str]( 

50 doc="Panel selector. Data will be separated into multiple panels based on this key.", 

51 ) 

52 dataKey = Field[str]( 

53 doc="Data selector. Data will be separated into multiple groups in a single panel based on this key.", 

54 ) 

55 quantityKey = Field[str]( 

56 doc="Quantity selector. The actual data quantities to be plotted.", 

57 ) 

58 

59 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

60 repackedData = {} 

61 # Loop over the length of the data vector and repack row by row 

62 for i in range(len(cast(Vector, data[self.panelKey]))): 

63 panelVec = cast(Vector, data[self.panelKey]) 

64 dataVec = cast(Vector, data[self.dataKey]) 

65 quantityVec = cast(Vector, data[self.quantityKey]) 

66 repackedData[f"{panelVec[i]}_{dataVec[i]}_{self.quantityKey}"] = quantityVec[i] 

67 return repackedData 

68 

69 def getInputSchema(self) -> KeyedDataSchema: 

70 return ( 

71 (self.panelKey, Vector), 

72 (self.dataKey, Vector), 

73 (self.quantityKey, Vector), 

74 ) 

75 

76 def addInputSchema(self, inputSchema: KeyedDataSchema) -> None: 

77 pass 

78 

79 

80class SingleValueRepacker(KeyedDataAction): 

81 """Prep action to repack data.""" 

82 

83 panelKey = Field[str]( 

84 doc="Panel selector. Data will be separated into multiple panels based on this key.", 

85 ) 

86 dataKey = Field[str]( 

87 doc="Data selector. Data will be separated into multiple groups in a single panel based on this key.", 

88 ) 

89 quantityKey = Field[str]( 

90 doc="Quantity selector. The actual data quantities to be plotted.", 

91 ) 

92 

93 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

94 repackedData: dict[str, list] = {} 

95 

96 panelVec = cast(Vector, data[self.panelKey]) 

97 dataVec = cast(Vector, data[self.dataKey]) 

98 quantityVec = cast(Vector, data[self.quantityKey]) 

99 # cp_verify's results tables also hold per-detector and per-exposure 

100 # rows, which have no amplifier and so belong in no panel. 

101 panelMask = np.ma.getmaskarray(panelVec) 

102 

103 for i in range(len(panelVec)): 

104 if panelMask[i]: 

105 continue 

106 repackedData.setdefault(f"{panelVec[i]}_x", []).append(dataVec[i]) 

107 repackedData.setdefault(f"{panelVec[i]}", []).append(quantityVec[i]) 

108 

109 return repackedData 

110 

111 def getInputSchema(self) -> KeyedDataSchema: 

112 return ( 

113 (self.panelKey, Vector), 

114 (self.dataKey, Vector), 

115 (self.quantityKey, Vector), 

116 ) 

117 

118 def addInputSchema(self, inputSchema: KeyedDataSchema) -> None: 

119 pass 

120 

121 

122class PassThrough(KeyedDataAction): 

123 def __call__(self, data: KeyedData, **kwargs) -> KeyedData: 

124 return data 

125 

126 def getInputSchema(self) -> KeyedDataSchema: 

127 # In general this method should be implemented, but here we are ALSO 

128 # implementing the prep step. We therefore know the method results are 

129 # not needed because it is doing something special with the inputs. 

130 return () 

131 

132 

133class CalibQuantityBaseTool(AnalysisTool): 

134 parameterizedBand: bool = False 

135 

136 plotElement = ConfigurableActionField[PlotElement]( 

137 doc="Plot element.", 

138 ) 

139 

140 def setDefaults(self): 

141 super().setDefaults() 

142 

143 # Repack the input data into a usable format 

144 self.prep = PrepRepacker() 

145 

146 # A simple pass-through process action to keep the data unchanged 

147 self.process = PassThrough() 

148 

149 # Plot the repacked data in a 4x4 grid 

150 self.produce.plot = GridPlot() 

151 self.produce.plot.panels = {} 

152 self.produce.plot.numRows = 4 

153 self.produce.plot.numCols = 4 

154 

155 # Values to group by to distinguish between data in differing panels 

156 self.produce.plot.valsGroupBy = { 

157 0: "C00", 

158 1: "C01", 

159 2: "C02", 

160 3: "C03", 

161 4: "C04", 

162 5: "C05", 

163 6: "C06", 

164 7: "C07", 

165 8: "C10", 

166 9: "C11", 

167 10: "C12", 

168 11: "C13", 

169 12: "C14", 

170 13: "C15", 

171 14: "C16", 

172 15: "C17", 

173 } 

174 

175 def finalize(self): 

176 super().finalize() 

177 

178 # Configure each panel 

179 for key, value in self.produce.plot.valsGroupBy.items(): 

180 gridPanelConfig = GridPanelConfig( 

181 plotElement=self.plotElement, 

182 title={"label": str(value), "fontsize": "10"}, 

183 titleY=0.85, 

184 ) 

185 self.produce.plot.panels[key] = gridPanelConfig 

186 

187 

188class CalibQuantityAmpProfileScatterTool(CalibQuantityBaseTool): 

189 def setDefaults(self): 

190 super().setDefaults() 

191 self.plotElement = ScatterElement() 

192 self.prep.panelKey = "amplifier" 

193 self.prep.dataKey = "mjd" 

194 

195 

196class CalibQuantityAmpProfileHistTool(CalibQuantityBaseTool): 

197 def setDefaults(self): 

198 super().setDefaults() 

199 self.plotElement = HistElement() 

200 self.prep.panelKey = "amplifier" 

201 self.prep.dataKey = "mjd" 

202 

203 

204class CalibAmpScatterTool(CalibQuantityBaseTool): 

205 def setDefaults(self): 

206 super().setDefaults() 

207 self.plotElement = ScatterElement() 

208 

209 # Repack the input data into a usable format 

210 self.prep = SingleValueRepacker() 

211 

212 self.produce.plot = GridPlot() 

213 self.produce.plot.panels = {} 

214 self.produce.plot.numRows = 4 

215 self.produce.plot.numCols = 4 

216 

217 # Values to use for x-axis data 

218 self.produce.plot.xDataKeys = { 

219 0: "C00_x", 

220 1: "C01_x", 

221 2: "C02_x", 

222 3: "C03_x", 

223 4: "C04_x", 

224 5: "C05_x", 

225 6: "C06_x", 

226 7: "C07_x", 

227 8: "C10_x", 

228 9: "C11_x", 

229 10: "C12_x", 

230 11: "C13_x", 

231 12: "C14_x", 

232 13: "C15_x", 

233 14: "C16_x", 

234 15: "C17_x", 

235 } 

236 

237 # Values to group by to distinguish between data in differing panels 

238 self.produce.plot.valsGroupBy = { 

239 0: "C00", 

240 1: "C01", 

241 2: "C02", 

242 3: "C03", 

243 4: "C04", 

244 5: "C05", 

245 6: "C06", 

246 7: "C07", 

247 8: "C10", 

248 9: "C11", 

249 10: "C12", 

250 11: "C13", 

251 12: "C14", 

252 13: "C15", 

253 14: "C16", 

254 15: "C17", 

255 } 

256 

257 self.prep.panelKey = "amplifier" 

258 self.prep.dataKey = "mjd" 

259 

260 def finalize(self): 

261 super().finalize() 

262 

263 # Configure each panel 

264 for key, value in self.produce.plot.valsGroupBy.items(): 

265 gridPanelConfig = GridPanelConfig( 

266 plotElement=self.plotElement, 

267 title={"label": str(value), "fontsize": "10"}, 

268 titleY=0.85, 

269 ) 

270 self.produce.plot.panels[key] = gridPanelConfig 

271 self.produce.plot.panels[key].plotElement.xKey = f"{value}_x" 

272 self.produce.plot.panels[key].plotElement.valsKey = value 

273 

274 

275class CalibDivisaderoScatterTool(CalibQuantityBaseTool): 

276 def setDefaults(self): 

277 super().setDefaults() 

278 self.plotElement = ScatterElement() 

279 

280 # Repack the input data into a usable format 

281 self.prep = SingleValueRepacker() 

282 

283 self.produce.plot = GridPlot() 

284 self.produce.plot.panels = {} 

285 self.produce.plot.numRows = 2 

286 self.produce.plot.numCols = 1 

287 

288 # Values to group by to distinguish between data in differing panels 

289 self.produce.plot.valsGroupBy = {0: "bank1", 1: "bank0"} 

290 

291 self.produce.plot.xDataKeys = { 

292 0: "bank1_x", 

293 1: "bank0_x", 

294 } 

295 self.prep.panelKey = "amplifier" 

296 self.prep.dataKey = "mjd" 

297 

298 def finalize(self): 

299 super().finalize() 

300 

301 # Configure each panel 

302 for key, value in self.produce.plot.valsGroupBy.items(): 

303 gridPanelConfig = GridPanelConfig( 

304 plotElement=self.plotElement, 

305 title={"label": str(value), "fontsize": "10"}, 

306 titleY=0.85, 

307 ) 

308 self.produce.plot.panels[key] = gridPanelConfig 

309 self.produce.plot.panels[key].plotElement.xKey = f"{value}_x" 

310 self.produce.plot.panels[key].plotElement.valsKey = value 

311 

312 

313class CalibPtcCovarScatterTool(CalibQuantityBaseTool): 

314 def setDefaults(self): 

315 super().setDefaults() 

316 self.plotElement = ScatterElement() 

317 

318 # Repack the input data into a usable format 

319 self.prep = PrepRepacker() 

320 

321 self.produce.plot = GridPlot() 

322 self.produce.plot.panels = {} 

323 self.produce.plot.numRows = 3 

324 self.produce.plot.numCols = 2 

325 

326 # Values to group by to distinguish between data in differing panels 

327 self.produce.plot.valsGroupBy = { 

328 0: "PTC_COV_10", 

329 1: "PTC_COV_20", 

330 2: "PTC_COV_01", 

331 3: "PTC_COV_02", 

332 4: "PTC_COV_11", 

333 5: None, 

334 } 

335 

336 self.produce.plot.xDataKeys = { 

337 0: "PTC_PTC_RAW_MEANS", 

338 1: "PTC_PTC_RAW_MEANS", 

339 2: "PTC_PTC_RAW_MEANS", 

340 3: "PTC_PTC_RAW_MEANS", 

341 4: "PTC_PTC_RAW_MEANS", 

342 5: None, 

343 } 

344 self.prep.panelKey = "amplifier" 

345 self.prep.dataKey = "mjd" 

346 

347 def finalize(self): 

348 super().finalize() 

349 

350 # Configure each panel 

351 for key, value in self.produce.plot.valsGroupBy.items(): 

352 gridPanelConfig = GridPanelConfig( 

353 plotElement=self.plotElement, 

354 title={"label": str(value), "fontsize": "10"}, 

355 titleY=0.85, 

356 ) 

357 self.produce.plot.panels[key] = gridPanelConfig 

358 self.produce.plot.panels[key].plotElement.xKey = "PTC_PTC_RAW_MEANS" 

359 self.produce.plot.panels[key].plotElement.valsKey = f"PTC_{value}"