Coverage for python/lsst/cp/verify/verifyDefects.py: 24%

81 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-28 10:03 +0000

1# This file is part of cp_verify. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21import numpy as np 

22import scipy.stats 

23import lsst.pipe.base.connectionTypes as cT 

24from lsst.ip.isr.isrFunctions import countMaskedPixels 

25 

26from .verifyStats import ( 

27 CpVerifyStatsConfig, 

28 CpVerifyStatsTask, 

29 CpVerifyStatsConnections, 

30) 

31 

32 

33__all__ = ["CpVerifyDefectsConfig", "CpVerifyDefectsTask"] 

34 

35 

36class CpVerifyDefectsConnections( 

37 CpVerifyStatsConnections, dimensions={"instrument", "visit", "detector"} 

38): 

39 inputExp = cT.Input( 

40 name="icExp", 

41 doc="Input exposure to calculate statistics for.", 

42 storageClass="ExposureF", 

43 dimensions=["instrument", "visit", "detector"], 

44 ) 

45 uncorrectedExp = cT.Input( 

46 name="uncorrectedExp", 

47 doc="Uncorrected input exposure to calculate statistics for.", 

48 storageClass="ExposureF", 

49 dimensions=["instrument", "visit", "detector"], 

50 ) 

51 inputCatalog = cT.Input( 

52 name="icSrc", 

53 doc="Input catalog to calculate statistics from.", 

54 storageClass="SourceCatalog", 

55 dimensions=["instrument", "visit", "detector"], 

56 ) 

57 uncorrectedCatalog = cT.Input( 

58 name="uncorrectedSrc", 

59 doc="Input catalog without correction applied.", 

60 storageClass="SourceCatalog", 

61 dimensions=["instrument", "visit", "detector"], 

62 ) 

63 camera = cT.PrerequisiteInput( 

64 name="camera", 

65 storageClass="Camera", 

66 doc="Input camera.", 

67 dimensions=["instrument", ], 

68 isCalibration=True, 

69 ) 

70 outputStats = cT.Output( 

71 name="detectorStats", 

72 doc="Output statistics from cp_verify.", 

73 storageClass="StructuredDataDict", 

74 dimensions=["instrument", "visit", "detector"], 

75 ) 

76 

77 

78class CpVerifyDefectsConfig( 

79 CpVerifyStatsConfig, pipelineConnections=CpVerifyDefectsConnections 

80): 

81 """Inherits from base CpVerifyStatsConfig.""" 

82 

83 def setDefaults(self): 

84 super().setDefaults() 

85 self.maskNameList = ["BAD"] # noqa F821 

86 

87 self.imageStatKeywords = { 

88 "DEFECT_PIXELS": "NMASKED", # noqa F821 

89 "OUTLIERS": "NCLIPPED", 

90 "MEDIAN": "MEDIAN", 

91 "STDEV": "STDEVCLIP", 

92 "MIN": "MIN", 

93 "MAX": "MAX", 

94 } 

95 self.unmaskedImageStatKeywords = { 

96 "UNMASKED_MIN": "MIN", # noqa F821 

97 "UNMASKED_MAX": "MAX", 

98 "UNMASKED_STDEV": "STDEVCLIP", 

99 "UNMASKED_OUTLIERS": "NCLIPPED", 

100 } 

101 self.uncorrectedImageStatKeywords = { 

102 "UNC_DEFECT_PIXELS": "NMASKED", # noqa F821 

103 "UNC_OUTLIERS": "NCLIPPED", 

104 "UNC_MEDIAN": "MEDIAN", 

105 "UNC_STDEV": "STDEVCLIP", 

106 "UNC_MIN": "MIN", 

107 "UNC_MAX": "MAX", 

108 } 

109 # These config options need to have a key/value pair 

110 # to run verification analysis, but the contents of 

111 # that pair are not used. 

112 self.catalogStatKeywords = {"empty": "dictionary"} 

113 self.detectorStatKeywords = {"empty": "dictionary"} 

114 

115 

116class CpVerifyDefectsTask(CpVerifyStatsTask): 

117 """Defects verification sub-class, implementing the verify method. 

118 

119 This also applies additional image processing statistics. 

120 """ 

121 

122 ConfigClass = CpVerifyDefectsConfig 

123 _DefaultName = "cpVerifyDefects" 

124 

125 def catalogStatistics(self, exposure, catalog, uncorrectedCatalog, statControl): 

126 """Measure the catalog statistics. 

127 

128 Parameters 

129 ---------- 

130 exposure : `lsst.afw.image.Exposure` 

131 The exposure to measure. 

132 catalog : `lsst.afw.table.Table` 

133 The catalog to measure. 

134 uncorrectedCatalog : `lsst.afw.table.Table` 

135 The uncorrected catalog to measure. 

136 statControl : `lsst.afw.math.StatisticsControl` 

137 Statistics control object with parameters defined by 

138 the config. 

139 

140 Returns 

141 ------- 

142 outputStatistics : `dict` [`str`, `dict` [`str`, scalar]] 

143 A dictionary indexed by the amplifier name, containing 

144 dictionaries of the statistics measured and their values. 

145 

146 Notes 

147 ----- 

148 Number of detections test: running with defects would have fewer 

149 detections 

150 """ 

151 outputStatistics = {} 

152 

153 # Number of detections test 

154 outputStatistics["NUM_OBJECTS_BEFORE"] = len(uncorrectedCatalog) 

155 outputStatistics["NUM_OBJECTS_AFTER"] = len(catalog) 

156 

157 return outputStatistics 

158 

159 def detectorStatistics(self, statsDict, statControl, exposure, uncorrectedExposure): 

160 """Measure the detector statistics. 

161 

162 Parameters 

163 ---------- 

164 statsDict : `dict` [`str`, scalar] 

165 Dictionary with detector tests. 

166 exposure : `lsst.afw.image.Exposure` 

167 Exposure containing the ISR processed data to measure. 

168 statControl : `lsst.afw.math.StatControl` 

169 Statistics control object with parameters defined by 

170 the config. 

171 exposure : `lsst.afw.image.Exposure` 

172 Exposure containing the ISR-processed data to measure. 

173 uncorrectedExposure : `lsst.afw.image.Exposure` 

174 uncorrected esposure (no defects) containing the 

175 ISR-processed data to measure. 

176 

177 Returns 

178 ------- 

179 outputStatistics : `dict` [`str`, scalar] 

180 A dictionary containing statistics measured and their values. 

181 

182 Notes 

183 ----- 

184 Number of cosmic rays test: If there are defects in our data that 

185 we didn't properly identify and cover, they might appear similar 

186 to cosmic rays because they have sharp edges compared to the point 

187 spread function (PSF). When we process the data, if these defects 

188 aren't marked in our defect mask, the software might mistakenly think 

189 they are cosmic rays and try to remove them. However, if we've already 

190 included these defects in the defect mask, the software won't treat 

191 them as cosmic rays, so we'll have fewer pixels that are falsely 

192 identified and removed as cosmic rays when we compare two sets of 

193 data reductions. 

194 """ 

195 outputStatistics = {} 

196 # Cosmic Rays test: Count number of cosmic rays before 

197 # and after masking with defects 

198 nCosmicsBefore = countMaskedPixels(uncorrectedExposure, ["CR"]) 

199 nCosmicsAfter = countMaskedPixels(exposure, ["CR"]) 

200 

201 outputStatistics["NUM_COSMICS_BEFORE"] = nCosmicsBefore 

202 outputStatistics["NUM_COSMICS_AFTER"] = nCosmicsAfter 

203 

204 return outputStatistics 

205 

206 def imageStatistics(self, exposure, uncorrectedExposure, statControl): 

207 """Measure additional defect statistics. 

208 

209 This calls the parent class method first, then adds additional 

210 measurements. 

211 

212 Parameters 

213 ---------- 

214 exposure : `lsst.afw.image.Exposure` 

215 Exposure containing the ISR processed data to measure. 

216 statControl : `lsst.afw.math.StatControl` 

217 Statistics control object with parameters defined by 

218 the config. 

219 

220 Returns 

221 ------- 

222 outputStatistics : `dict` [`str`, `dict` [`str`, scalar]] 

223 A dictionary indexed by the amplifier name, containing 

224 dictionaries of the statistics measured and their values. 

225 

226 Notes 

227 ----- 

228 Number of cosmic rays test: If there are defects in our data that 

229 we didn't properly identify and cover, they might appear similar 

230 to cosmic rays because they have sharp edges compared to the point 

231 spread function (PSF). When we process the data, if these defects 

232 aren't marked in our defect mask, the software might mistakenly think 

233 they are cosmic rays and try to remove them. However, if we've already 

234 included these defects in the defect mask, the software won't treat 

235 them as cosmic rays, so we'll have fewer pixels that are falsely 

236 identified and removed as cosmic rays when we compare two sets of 

237 data reductions. 

238 """ 

239 outputStatistics = super().imageStatistics( 

240 exposure, uncorrectedExposure, statControl 

241 ) 

242 

243 # Is this a useful test? It saves having to do chi^2 fits, 

244 # which are going to be biased by the bulk of points. 

245 for amp in exposure.getDetector(): 

246 ampName = amp.getName() 

247 ampExp = exposure.Factory(exposure, amp.getBBox()) 

248 

249 normImage = ampExp.getImage() 

250 normArray = normImage.getArray() 

251 

252 normArray -= outputStatistics[ampName]["MEDIAN"] 

253 normArray /= outputStatistics[ampName]["STDEV"] 

254 

255 probability = scipy.stats.norm.pdf(normArray) 

256 outliers = np.where(probability < 1.0 / probability.size, 1.0, 0.0) 

257 outputStatistics[ampName]["STAT_OUTLIERS"] = int(np.sum(outliers)) 

258 

259 return outputStatistics 

260 

261 def verify(self, exposure, statisticsDict): 

262 """Verify that the measured statistics meet the verification criteria. 

263 

264 Parameters 

265 ---------- 

266 exposure : `lsst.afw.image.Exposure` 

267 The exposure the statistics are from. 

268 statisticsDictionary : `dict` [`str`, `dict` [`str`, scalar]], 

269 Dictionary of measured statistics. The inner dictionary 

270 should have keys that are statistic names (`str`) with 

271 values that are some sort of scalar (`int` or `float` are 

272 the mostly likely types). 

273 

274 Returns 

275 ------- 

276 outputStatistics : `dict` [`str`, `dict` [`str`, `bool`]] 

277 A dictionary indexed by the amplifier name, containing 

278 dictionaries of the verification criteria. 

279 success : `bool` 

280 A boolean indicating if all tests have passed. 

281 """ 

282 # Amplifier statistics 

283 ampStats = statisticsDict["AMP"] 

284 verifyStats = {} 

285 successAmp = True 

286 for ampName, stats in ampStats.items(): 

287 verify = {} 

288 

289 # These are not defined in DMTN-101 yet. 

290 verify["OUTLIERS"] = bool(stats["UNMASKED_OUTLIERS"] >= stats["OUTLIERS"]) 

291 verify["STDEV"] = bool(stats["UNMASKED_STDEV"] >= stats["STDEV"]) 

292 verify["MIN"] = bool(stats["UNMASKED_MIN"] <= stats["MIN"]) 

293 verify["MAX"] = bool(stats["UNMASKED_MAX"] >= stats["MAX"]) 

294 

295 # This test is bad, and should be made not bad. 

296 verify["PROB_TEST"] = bool(stats["STAT_OUTLIERS"] == stats["DEFECT_PIXELS"]) 

297 

298 verify["SUCCESS"] = bool(np.all(list(verify.values()))) 

299 if verify["SUCCESS"] is False: 

300 successAmp = False 

301 

302 verifyStats[ampName] = verify 

303 

304 # Detector statistics 

305 detStats = statisticsDict["DET"] 

306 verifyStatsDet = {} 

307 successDet = True 

308 # Cosmic rays test from DM-38563, before and after defects. 

309 verifyStatsDet["NUMBER_COSMIC_RAYS"] = bool( 

310 detStats["NUM_COSMICS_BEFORE"] > detStats["NUM_COSMICS_AFTER"] 

311 ) 

312 

313 verifyStatsDet["SUCCESS"] = bool(np.all(list(verifyStatsDet.values()))) 

314 if verifyStatsDet["SUCCESS"] is False: 

315 successDet = False 

316 

317 # Catalog statistics 

318 catStats = statisticsDict["CATALOG"] 

319 verifyStatsCat = {} 

320 successCat = True 

321 # Detection tests from DM-38563, before and after defects. 

322 verifyStatsCat["NUMBER_DETECTIONS"] = bool( 

323 catStats["NUM_OBJECTS_BEFORE"] > catStats["NUM_OBJECTS_AFTER"] 

324 ) 

325 

326 verifyStatsCat["SUCCESS"] = bool(np.all(list(verifyStatsCat.values()))) 

327 if verifyStatsCat["SUCCESS"] is False: 

328 successCat = False 

329 

330 success = successDet & successAmp & successCat 

331 return { 

332 "AMP": verifyStats, 

333 "DET": verifyStatsDet, 

334 "CATALOG": verifyStatsCat, 

335 }, bool(success)