Coverage for python/lsst/afw/image/_exposureSummaryStats.py: 54%

129 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-08 03:13 -0700

1# This file is part of afw. 

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 

23import dataclasses 

24from typing import TYPE_CHECKING 

25import yaml 

26import warnings 

27 

28from ..typehandling import Storable, StorableHelperFactory 

29 

30if TYPE_CHECKING: 30 ↛ 31line 30 didn't jump to line 31, because the condition on line 30 was never true

31 from ..table import BaseRecord, Schema 

32 

33__all__ = ("ExposureSummaryStats", ) 

34 

35 

36def _default_corners(): 

37 return [float("nan")] * 4 

38 

39 

40@dataclasses.dataclass 

41class ExposureSummaryStats(Storable): 

42 _persistence_name = 'ExposureSummaryStats' 

43 

44 _factory = StorableHelperFactory(__name__, _persistence_name) 

45 

46 version: int = 0 

47 

48 psfSigma: float = float('nan') 

49 """PSF determinant radius (pixels).""" 

50 

51 psfArea: float = float('nan') 

52 """PSF effective area (pixels**2).""" 

53 

54 psfIxx: float = float('nan') 

55 """PSF shape Ixx (pixels**2).""" 

56 

57 psfIyy: float = float('nan') 

58 """PSF shape Iyy (pixels**2).""" 

59 

60 psfIxy: float = float('nan') 

61 """PSF shape Ixy (pixels**2).""" 

62 

63 ra: float = float('nan') 

64 """Bounding box center Right Ascension (degrees).""" 

65 

66 dec: float = float('nan') 

67 """Bounding box center Declination (degrees).""" 

68 

69 zenithDistance: float = float('nan') 

70 """Bounding box center zenith distance (degrees).""" 

71 

72 zeroPoint: float = float('nan') 

73 """Mean zeropoint in detector (mag).""" 

74 

75 skyBg: float = float('nan') 

76 """Average sky background (ADU).""" 

77 

78 skyNoise: float = float('nan') 

79 """Average sky noise (ADU).""" 

80 

81 meanVar: float = float('nan') 

82 """Mean variance of the weight plane (ADU**2).""" 

83 

84 raCorners: list[float] = dataclasses.field(default_factory=_default_corners) 

85 """Right Ascension of bounding box corners (degrees).""" 

86 

87 decCorners: list[float] = dataclasses.field(default_factory=_default_corners) 

88 """Declination of bounding box corners (degrees).""" 

89 

90 astromOffsetMean: float = float('nan') 

91 """Astrometry match offset mean.""" 

92 

93 astromOffsetStd: float = float('nan') 

94 """Astrometry match offset stddev.""" 

95 

96 nPsfStar: int = 0 

97 """Number of stars used for psf model.""" 

98 

99 psfStarDeltaE1Median: float = float('nan') 

100 """Psf stars median E1 residual (starE1 - psfE1).""" 

101 

102 psfStarDeltaE2Median: float = float('nan') 

103 """Psf stars median E2 residual (starE2 - psfE2).""" 

104 

105 psfStarDeltaE1Scatter: float = float('nan') 

106 """Psf stars MAD E1 scatter (starE1 - psfE1).""" 

107 

108 psfStarDeltaE2Scatter: float = float('nan') 

109 """Psf stars MAD E2 scatter (starE2 - psfE2).""" 

110 

111 psfStarDeltaSizeMedian: float = float('nan') 

112 """Psf stars median size residual (starSize - psfSize).""" 

113 

114 psfStarDeltaSizeScatter: float = float('nan') 

115 """Psf stars MAD size scatter (starSize - psfSize).""" 

116 

117 psfStarScaledDeltaSizeScatter: float = float('nan') 

118 """Psf stars MAD size scatter scaled by psfSize**2.""" 

119 

120 psfTraceRadiusDelta: float = float('nan') 

121 """Delta (max - min) of the model psf trace radius values evaluated on a 

122 grid of unmasked pixels (pixels). 

123 """ 

124 

125 maxDistToNearestPsf: float = float('nan') 

126 """Maximum distance of an unmasked pixel to its nearest model psf star 

127 (pixels). 

128 """ 

129 

130 def __post_init__(self): 

131 Storable.__init__(self) 

132 

133 def isPersistable(self): 

134 return True 

135 

136 def _getPersistenceName(self): 

137 return self._persistence_name 

138 

139 def _getPythonModule(self): 

140 return __name__ 

141 

142 def _write(self): 

143 return yaml.dump(dataclasses.asdict(self), encoding='utf-8') 

144 

145 @staticmethod 

146 def _read(bytes): 

147 yamlDict = yaml.load(bytes, Loader=yaml.SafeLoader) 

148 # For forwards compatibility, filter out any fields that are 

149 # not defined in the dataclass. 

150 droppedFields = [] 

151 for _field in list(yamlDict.keys()): 

152 if _field not in ExposureSummaryStats.__dataclass_fields__: 

153 droppedFields.append(_field) 

154 yamlDict.pop(_field) 

155 if len(droppedFields) > 0: 

156 droppedFieldString = ", ".join([str(f) for f in droppedFields]) 

157 warnings.warn( 

158 ( 

159 f"Could not read summary fields [{droppedFieldString}]. " 

160 "Please use a newer stack." 

161 ), 

162 FutureWarning, 

163 ) 

164 return ExposureSummaryStats(**yamlDict) 

165 

166 @classmethod 

167 def update_schema(cls, schema: Schema) -> None: 

168 """Update an schema to includes for all summary statistic fields. 

169 

170 Parameters 

171 ------- 

172 schema : `lsst.afw.table.Schema` 

173 Schema to add which fields will be added. 

174 """ 

175 schema.addField( 

176 "psfSigma", 

177 type="F", 

178 doc="PSF model second-moments determinant radius (center of chip) (pixel)", 

179 ) 

180 schema.addField( 

181 "psfArea", 

182 type="F", 

183 doc="PSF model effective area (center of chip) (pixel**2)", 

184 ) 

185 schema.addField( 

186 "psfIxx", type="F", doc="PSF model Ixx (center of chip) (pixel**2)" 

187 ) 

188 schema.addField( 

189 "psfIyy", type="F", doc="PSF model Iyy (center of chip) (pixel**2)" 

190 ) 

191 schema.addField( 

192 "psfIxy", type="F", doc="PSF model Ixy (center of chip) (pixel**2)" 

193 ) 

194 schema.addField( 

195 "raCorners", 

196 type="ArrayD", 

197 size=4, 

198 doc="Right Ascension of bounding box corners (degrees)", 

199 ) 

200 schema.addField( 

201 "decCorners", 

202 type="ArrayD", 

203 size=4, 

204 doc="Declination of bounding box corners (degrees)", 

205 ) 

206 schema.addField( 

207 "ra", type="D", doc="Right Ascension of bounding box center (degrees)" 

208 ) 

209 schema.addField( 

210 "dec", type="D", doc="Declination of bounding box center (degrees)" 

211 ) 

212 schema.addField( 

213 "zenithDistance", 

214 type="F", 

215 doc="Zenith distance of bounding box center (degrees)", 

216 ) 

217 schema.addField("zeroPoint", type="F", doc="Mean zeropoint in detector (mag)") 

218 schema.addField("skyBg", type="F", doc="Average sky background (ADU)") 

219 schema.addField("skyNoise", type="F", doc="Average sky noise (ADU)") 

220 schema.addField( 

221 "meanVar", type="F", doc="Mean variance of the weight plane (ADU**2)" 

222 ) 

223 schema.addField( 

224 "astromOffsetMean", 

225 type="F", 

226 doc="Mean offset of astrometric calibration matches (arcsec)", 

227 ) 

228 schema.addField( 

229 "astromOffsetStd", 

230 type="F", 

231 doc="Standard deviation of offsets of astrometric calibration matches (arcsec)", 

232 ) 

233 schema.addField("nPsfStar", type="I", doc="Number of stars used for PSF model") 

234 schema.addField( 

235 "psfStarDeltaE1Median", 

236 type="F", 

237 doc="Median E1 residual (starE1 - psfE1) for psf stars", 

238 ) 

239 schema.addField( 

240 "psfStarDeltaE2Median", 

241 type="F", 

242 doc="Median E2 residual (starE2 - psfE2) for psf stars", 

243 ) 

244 schema.addField( 

245 "psfStarDeltaE1Scatter", 

246 type="F", 

247 doc="Scatter (via MAD) of E1 residual (starE1 - psfE1) for psf stars", 

248 ) 

249 schema.addField( 

250 "psfStarDeltaE2Scatter", 

251 type="F", 

252 doc="Scatter (via MAD) of E2 residual (starE2 - psfE2) for psf stars", 

253 ) 

254 schema.addField( 

255 "psfStarDeltaSizeMedian", 

256 type="F", 

257 doc="Median size residual (starSize - psfSize) for psf stars (pixel)", 

258 ) 

259 schema.addField( 

260 "psfStarDeltaSizeScatter", 

261 type="F", 

262 doc="Scatter (via MAD) of size residual (starSize - psfSize) for psf stars (pixel)", 

263 ) 

264 schema.addField( 

265 "psfStarScaledDeltaSizeScatter", 

266 type="F", 

267 doc="Scatter (via MAD) of size residual scaled by median size squared", 

268 ) 

269 schema.addField( 

270 "psfTraceRadiusDelta", 

271 type="F", 

272 doc="Delta (max - min) of the model psf trace radius values evaluated on a grid of " 

273 "unmasked pixels (pixel).", 

274 ) 

275 schema.addField( 

276 "maxDistToNearestPsf", 

277 type="F", 

278 doc="Maximum distance of an unmasked pixel to its nearest model psf star (pixel).", 

279 ) 

280 

281 def update_record(self, record: BaseRecord) -> None: 

282 """Write summary-statistic columns into a record. 

283 

284 Parameters 

285 ---------- 

286 record : `lsst.afw.table.BaseRecord` 

287 Record to update. This is expected to frequently be an 

288 `ExposureRecord` instance (with higher-level code adding other 

289 columns and objects), but this method can work with any record 

290 type. 

291 """ 

292 for field in dataclasses.fields(self): 

293 value = getattr(self, field.name) 

294 if field.name == "version": 

295 continue 

296 elif field.type.startswith("list"): 

297 record[field.name][:] = value 

298 else: 

299 record[field.name] = value 

300 

301 @classmethod 

302 def from_record(cls, record: BaseRecord) -> ExposureSummaryStats: 

303 """Read summary-statistic columns from a record into ``self``. 

304 

305 Parameters 

306 ---------- 

307 record : `lsst.afw.table.BaseRecord` 

308 Record to read from. This is expected to frequently be an 

309 `ExposureRecord` instance (with higher-level code adding other 

310 columns and objects), but this method can work with any record 

311 type, ignoring any attributes or columns it doesn't recognize. 

312 

313 Returns 

314 ------- 

315 summary : `ExposureSummaryStats` 

316 Summary statistics object created from the given record. 

317 """ 

318 return cls( 

319 **{ 

320 field.name: ( 

321 record[field.name] if not field.type.startswith("list") 

322 else [float(v) for v in record[field.name]] 

323 ) 

324 for field in dataclasses.fields(cls) 

325 if field.name != "version" 

326 } 

327 )