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

81 statements  

« prev     ^ index     » next       coverage.py v7.3.3, created at 2023-12-16 15:00 +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, statisticsDict, statControl, exposure=None, uncorrectedExposure=None): 

160 """Measure the detector statistics. 

161 

162 Parameters 

163 ---------- 

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

165 Dictionary with detector tests. 

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

167 Statistics control object with parameters defined by 

168 the config. 

169 exposure : `lsst.afw.image.Exposure`, optional 

170 Exposure containing the ISR-processed data to measure. 

171 uncorrectedExposure : `lsst.afw.image.Exposure`, optional 

172 uncorrected esposure (no defects) containing the 

173 ISR-processed data to measure. 

174 

175 Returns 

176 ------- 

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

178 A dictionary containing statistics measured and their values. 

179 

180 Notes 

181 ----- 

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

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

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

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

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

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

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

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

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

191 data reductions. 

192 """ 

193 outputStatistics = {} 

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

195 # and after masking with defects 

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

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

198 

199 outputStatistics["NUM_COSMICS_BEFORE"] = nCosmicsBefore 

200 outputStatistics["NUM_COSMICS_AFTER"] = nCosmicsAfter 

201 

202 return outputStatistics 

203 

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

205 """Measure additional defect statistics. 

206 

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

208 measurements. 

209 

210 Parameters 

211 ---------- 

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

213 Exposure containing the ISR processed data to measure. 

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

215 Statistics control object with parameters defined by 

216 the config. 

217 

218 Returns 

219 ------- 

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

221 A dictionary indexed by the amplifier name, containing 

222 dictionaries of the statistics measured and their values. 

223 

224 Notes 

225 ----- 

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

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

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

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

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

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

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

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

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

235 data reductions. 

236 """ 

237 outputStatistics = super().imageStatistics( 

238 exposure, uncorrectedExposure, statControl 

239 ) 

240 

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

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

243 for amp in exposure.getDetector(): 

244 ampName = amp.getName() 

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

246 

247 normImage = ampExp.getImage() 

248 normArray = normImage.getArray() 

249 

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

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

252 

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

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

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

256 

257 return outputStatistics 

258 

259 def verify(self, exposure, statisticsDict): 

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

261 

262 Parameters 

263 ---------- 

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

265 The exposure the statistics are from. 

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

267 Dictionary of measured statistics. The inner dictionary 

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

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

270 the mostly likely types). 

271 

272 Returns 

273 ------- 

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

275 A dictionary indexed by the amplifier name, containing 

276 dictionaries of the verification criteria. 

277 success : `bool` 

278 A boolean indicating if all tests have passed. 

279 """ 

280 # Amplifier statistics 

281 ampStats = statisticsDict["AMP"] 

282 verifyStats = {} 

283 successAmp = True 

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

285 verify = {} 

286 

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

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

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

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

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

292 

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

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

295 

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

297 if verify["SUCCESS"] is False: 

298 successAmp = False 

299 

300 verifyStats[ampName] = verify 

301 

302 # Detector statistics 

303 detStats = statisticsDict["DET"] 

304 verifyStatsDet = {} 

305 successDet = True 

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

307 verifyStatsDet["NUMBER_COSMIC_RAYS"] = bool( 

308 detStats["NUM_COSMICS_BEFORE"] > detStats["NUM_COSMICS_AFTER"] 

309 ) 

310 

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

312 if verifyStatsDet["SUCCESS"] is False: 

313 successDet = False 

314 

315 # Catalog statistics 

316 catStats = statisticsDict["CATALOG"] 

317 verifyStatsCat = {} 

318 successCat = True 

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

320 verifyStatsCat["NUMBER_DETECTIONS"] = bool( 

321 catStats["NUM_OBJECTS_BEFORE"] > catStats["NUM_OBJECTS_AFTER"] 

322 ) 

323 

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

325 if verifyStatsCat["SUCCESS"] is False: 

326 successCat = False 

327 

328 success = successDet & successAmp & successCat 

329 return { 

330 "AMP": verifyStats, 

331 "DET": verifyStatsDet, 

332 "CATALOG": verifyStatsCat, 

333 }, bool(success)