Coverage for python/lsst/ip/isr/ptcDataset.py: 7%

263 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-04 02:27 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008-2017 AURA/LSST. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22""" 

23Define dataset class for MeasurePhotonTransferCurve task 

24""" 

25 

26__all__ = ['PhotonTransferCurveDataset'] 

27 

28import numpy as np 

29from astropy.table import Table 

30import warnings 

31 

32from lsst.ip.isr import IsrCalib 

33 

34 

35class PhotonTransferCurveDataset(IsrCalib): 

36 """A simple class to hold the output data from the PTC task. 

37 

38 The dataset is made up of a dictionary for each item, keyed by the 

39 amplifiers' names, which much be supplied at construction time. 

40 New items cannot be added to the class to save accidentally saving to the 

41 wrong property, and the class can be frozen if desired. 

42 inputExpIdPairs records the exposures used to produce the data. 

43 When fitPtc() or fitCovariancesAstier() is run, a mask is built up, which 

44 is by definition always the same length as inputExpIdPairs, rawExpTimes, 

45 rawMeans and rawVars, and is a list of bools, which are incrementally set 

46 to False as points are discarded from the fits. 

47 PTC fit parameters for polynomials are stored in a list in ascending order 

48 of polynomial term, i.e. par[0]*x^0 + par[1]*x + par[2]*x^2 etc 

49 with the length of the list corresponding to the order of the polynomial 

50 plus one. 

51 

52 Parameters 

53 ---------- 

54 ampNames : `list` 

55 List with the names of the amplifiers of the detector at hand. 

56 ptcFitType : `str` 

57 Type of model fitted to the PTC: "POLYNOMIAL", "EXPAPPROXIMATION", 

58 or "FULLCOVARIANCE". 

59 covMatrixSide : `int` 

60 Maximum lag of covariances (size of square covariance matrices). 

61 kwargs : `dict`, optional 

62 Other keyword arguments to pass to the parent init. 

63 

64 Notes 

65 ----- 

66 The stored attributes are: 

67 

68 badAmps : `list` [`str`] 

69 List with bad amplifiers names. 

70 inputExpIdPairs : `dict`, [`str`, `list`] 

71 Dictionary keyed by amp names containing the input exposures IDs. 

72 expIdMask : `dict`, [`str`, `np.ndarray`] 

73 Dictionary keyed by amp names containing the mask produced after 

74 outlier rejection. The mask produced by the "FULLCOVARIANCE" 

75 option may differ from the one produced in the other two PTC 

76 fit types. 

77 rawExpTimes : `dict`, [`str`, `np.ndarray`] 

78 Dictionary keyed by amp names containing the unmasked exposure times. 

79 rawMeans : `dict`, [`str`, `np.ndarray`] 

80 Dictionary keyed by amp namescontaining the unmasked average of the 

81 means of the exposures in each flat pair. 

82 rawVars : `dict`, [`str`, `np.ndarray`] 

83 Dictionary keyed by amp names containing the variance of the 

84 difference image of the exposures in each flat pair. 

85 gain : `dict`, [`str`, `float`] 

86 Dictionary keyed by amp names containing the fitted gains. 

87 gainErr : `dict`, [`str`, `float`] 

88 Dictionary keyed by amp names containing the errors on the 

89 fitted gains. 

90 noise : `dict`, [`str`, `float`] 

91 Dictionary keyed by amp names containing the fitted noise. 

92 noiseErr : `dict`, [`str`, `float`] 

93 Dictionary keyed by amp names containing the errors on the fitted 

94 noise. 

95 ptcFitPars : `dict`, [`str`, `np.ndarray`] 

96 Dictionary keyed by amp names containing the fitted parameters of the 

97 PTC model for ptcFitTye in ["POLYNOMIAL", "EXPAPPROXIMATION"]. 

98 ptcFitParsError : `dict`, [`str`, `np.ndarray`] 

99 Dictionary keyed by amp names containing the errors on the fitted 

100 parameters of the PTC model for ptcFitTye in 

101 ["POLYNOMIAL", "EXPAPPROXIMATION"]. 

102 ptcFitChiSq : `dict`, [`str`, `float`] 

103 Dictionary keyed by amp names containing the reduced chi squared 

104 of the fit for ptcFitTye in ["POLYNOMIAL", "EXPAPPROXIMATION"]. 

105 ptcTurnoff : `dict` [`str, `float`] 

106 Flux value (in ADU) where the variance of the PTC curve starts 

107 decreasing consistently. 

108 covariances : `dict`, [`str`, `np.ndarray`] 

109 Dictionary keyed by amp names containing a list of measured 

110 covariances per mean flux. 

111 covariancesModel : `dict`, [`str`, `np.ndarray`] 

112 Dictionary keyed by amp names containinging covariances model 

113 (Eq. 20 of Astier+19) per mean flux. 

114 covariancesSqrtWeights : `dict`, [`str`, `np.ndarray`] 

115 Dictionary keyed by amp names containinging sqrt. of covariances 

116 weights. 

117 aMatrix : `dict`, [`str`, `np.ndarray`] 

118 Dictionary keyed by amp names containing the "a" parameters from 

119 the model in Eq. 20 of Astier+19. 

120 bMatrix : `dict`, [`str`, `np.ndarray`] 

121 Dictionary keyed by amp names containing the "b" parameters from 

122 the model in Eq. 20 of Astier+19. 

123 covariancesModelNoB : `dict`, [`str`, `np.ndarray`] 

124 Dictionary keyed by amp names containing covariances model 

125 (with 'b'=0 in Eq. 20 of Astier+19) 

126 per mean flux. 

127 aMatrixNoB : `dict`, [`str`, `np.ndarray`] 

128 Dictionary keyed by amp names containing the "a" parameters from the 

129 model in Eq. 20 of Astier+19 

130 (and 'b' = 0). 

131 finalVars : `dict`, [`str`, `np.ndarray`] 

132 Dictionary keyed by amp names containing the masked variance of the 

133 difference image of each flat 

134 pair. If needed, each array will be right-padded with 

135 np.nan to match the length of rawExpTimes. 

136 finalModelVars : `dict`, [`str`, `np.ndarray`] 

137 Dictionary keyed by amp names containing the masked modeled 

138 variance of the difference image of each flat pair. If needed, each 

139 array will be right-padded with np.nan to match the length of 

140 rawExpTimes. 

141 finalMeans : `dict`, [`str`, `np.ndarray`] 

142 Dictionary keyed by amp names containing the masked average of the 

143 means of the exposures in each flat pair. If needed, each array 

144 will be right-padded with np.nan to match the length of 

145 rawExpTimes. 

146 photoCharges : `dict`, [`str`, `np.ndarray`] 

147 Dictionary keyed by amp names containing the integrated photocharge 

148 for linearity calibration. 

149 

150 Version 1.1 adds the `ptcTurnoff` attribute. 

151 """ 

152 

153 _OBSTYPE = 'PTC' 

154 _SCHEMA = 'Gen3 Photon Transfer Curve' 

155 _VERSION = 1.1 

156 

157 def __init__(self, ampNames=[], ptcFitType=None, covMatrixSide=1, **kwargs): 

158 self.ptcFitType = ptcFitType 

159 self.ampNames = ampNames 

160 self.covMatrixSide = covMatrixSide 

161 

162 self.badAmps = [] 

163 

164 self.inputExpIdPairs = {ampName: [] for ampName in ampNames} 

165 self.expIdMask = {ampName: np.array([], dtype=bool) for ampName in ampNames} 

166 self.rawExpTimes = {ampName: np.array([]) for ampName in ampNames} 

167 self.rawMeans = {ampName: np.array([]) for ampName in ampNames} 

168 self.rawVars = {ampName: np.array([]) for ampName in ampNames} 

169 self.photoCharges = {ampName: np.array([]) for ampName in ampNames} 

170 

171 self.gain = {ampName: np.nan for ampName in ampNames} 

172 self.gainErr = {ampName: np.nan for ampName in ampNames} 

173 self.noise = {ampName: np.nan for ampName in ampNames} 

174 self.noiseErr = {ampName: np.nan for ampName in ampNames} 

175 

176 self.ptcFitPars = {ampName: np.array([]) for ampName in ampNames} 

177 self.ptcFitParsError = {ampName: np.array([]) for ampName in ampNames} 

178 self.ptcFitChiSq = {ampName: np.nan for ampName in ampNames} 

179 self.ptcTurnoff = {ampName: np.nan for ampName in ampNames} 

180 

181 self.covariances = {ampName: np.array([]) for ampName in ampNames} 

182 self.covariancesModel = {ampName: np.array([]) for ampName in ampNames} 

183 self.covariancesSqrtWeights = {ampName: np.array([]) for ampName in ampNames} 

184 self.aMatrix = {ampName: np.array([]) for ampName in ampNames} 

185 self.bMatrix = {ampName: np.array([]) for ampName in ampNames} 

186 self.covariancesModelNoB = {ampName: np.array([]) for ampName in ampNames} 

187 self.aMatrixNoB = {ampName: np.array([]) for ampName in ampNames} 

188 

189 self.finalVars = {ampName: np.array([]) for ampName in ampNames} 

190 self.finalModelVars = {ampName: np.array([]) for ampName in ampNames} 

191 self.finalMeans = {ampName: np.array([]) for ampName in ampNames} 

192 

193 super().__init__(**kwargs) 

194 self.requiredAttributes.update(['badAmps', 'inputExpIdPairs', 'expIdMask', 'rawExpTimes', 

195 'rawMeans', 'rawVars', 'gain', 'gainErr', 'noise', 'noiseErr', 

196 'ptcFitPars', 'ptcFitParsError', 'ptcFitChiSq', 'ptcTurnoff', 

197 'aMatrixNoB', 'covariances', 'covariancesModel', 

198 'covariancesSqrtWeights', 'covariancesModelNoB', 

199 'aMatrix', 'bMatrix', 'finalVars', 'finalModelVars', 'finalMeans', 

200 'photoCharges']) 

201 

202 self.updateMetadata(setCalibInfo=True, setCalibId=True, **kwargs) 

203 

204 def setAmpValuesPartialDataset( 

205 self, 

206 ampName, 

207 inputExpIdPair=(-1, -1), 

208 rawExpTime=np.nan, 

209 rawMean=np.nan, 

210 rawVar=np.nan, 

211 photoCharge=np.nan, 

212 expIdMask=False, 

213 covariance=None, 

214 covSqrtWeights=None, 

215 gain=np.nan, 

216 noise=np.nan, 

217 ): 

218 """ 

219 Set the amp values for a partial PTC Dataset (from cpExtractPtcTask). 

220 

221 Parameters 

222 ---------- 

223 ampName : `str` 

224 Name of the amp to set the values. 

225 inputExpIdPair : `tuple` [`int`] 

226 Exposure IDs of input pair. 

227 rawExpTime : `float`, optional 

228 Exposure time for this exposure pair. 

229 rawMean : `float`, optional 

230 Average of the means of the exposures in this pair. 

231 rawVar : `float`, optional 

232 Variance of the difference of the exposures in this pair. 

233 photoCharge : `float`, optional 

234 Integrated photocharge for flat pair for linearity calibration. 

235 expIdMask : `bool`, optional 

236 Flag setting if this exposure pair should be used (True) 

237 or not used (False). 

238 covariance : `np.ndarray` or None, optional 

239 Measured covariance for this exposure pair. 

240 covSqrtWeights : `np.ndarray` or None, optional 

241 Measured sqrt of covariance weights in this exposure pair. 

242 gain : `float`, optional 

243 Estimated gain for this exposure pair. 

244 noise : `float`, optional 

245 Estimated read noise for this exposure pair. 

246 """ 

247 nanMatrix = np.full((self.covMatrixSide, self.covMatrixSide), np.nan) 

248 if covariance is None: 

249 covariance = nanMatrix 

250 if covSqrtWeights is None: 

251 covSqrtWeights = nanMatrix 

252 

253 self.inputExpIdPairs[ampName] = [inputExpIdPair] 

254 self.rawExpTimes[ampName] = np.array([rawExpTime]) 

255 self.rawMeans[ampName] = np.array([rawMean]) 

256 self.rawVars[ampName] = np.array([rawVar]) 

257 self.photoCharges[ampName] = np.array([photoCharge]) 

258 self.expIdMask[ampName] = np.array([expIdMask]) 

259 self.covariances[ampName] = np.array([covariance]) 

260 self.covariancesSqrtWeights[ampName] = np.array([covSqrtWeights]) 

261 self.gain[ampName] = gain 

262 self.noise[ampName] = noise 

263 

264 self.covariancesModel[ampName] = np.array([nanMatrix]) 

265 self.covariancesModelNoB[ampName] = np.array([nanMatrix]) 

266 self.aMatrix[ampName] = nanMatrix 

267 self.bMatrix[ampName] = nanMatrix 

268 self.aMatrixNoB[ampName] = nanMatrix 

269 

270 def updateMetadata(self, **kwargs): 

271 """Update calibration metadata. 

272 This calls the base class's method after ensuring the required 

273 calibration keywords will be saved. 

274 

275 Parameters 

276 ---------- 

277 setDate : `bool`, optional 

278 Update the CALIBDATE fields in the metadata to the current 

279 time. Defaults to False. 

280 kwargs : 

281 Other keyword parameters to set in the metadata. 

282 """ 

283 super().updateMetadata(PTC_FIT_TYPE=self.ptcFitType, **kwargs) 

284 

285 @classmethod 

286 def fromDict(cls, dictionary): 

287 """Construct a calibration from a dictionary of properties. 

288 Must be implemented by the specific calibration subclasses. 

289 

290 Parameters 

291 ---------- 

292 dictionary : `dict` 

293 Dictionary of properties. 

294 

295 Returns 

296 ------- 

297 calib : `lsst.ip.isr.PhotonTransferCurveDataset` 

298 Constructed calibration. 

299 

300 Raises 

301 ------ 

302 RuntimeError 

303 Raised if the supplied dictionary is for a different 

304 calibration. 

305 """ 

306 calib = cls() 

307 if calib._OBSTYPE != dictionary['metadata']['OBSTYPE']: 

308 raise RuntimeError(f"Incorrect Photon Transfer Curve dataset supplied. " 

309 f"Expected {calib._OBSTYPE}, found {dictionary['metadata']['OBSTYPE']}") 

310 calib.setMetadata(dictionary['metadata']) 

311 calib.ptcFitType = dictionary['ptcFitType'] 

312 calib.covMatrixSide = dictionary['covMatrixSide'] 

313 calib.badAmps = np.array(dictionary['badAmps'], 'str').tolist() 

314 calib.ampNames = [] 

315 

316 # The cov matrices are square 

317 covMatrixSide = calib.covMatrixSide 

318 # Number of final signal levels 

319 covDimensionsProduct = len(np.array(list(dictionary['covariances'].values())[0]).ravel()) 

320 nSignalPoints = int(covDimensionsProduct/(covMatrixSide*covMatrixSide)) 

321 

322 for ampName in dictionary['ampNames']: 

323 calib.ampNames.append(ampName) 

324 calib.inputExpIdPairs[ampName] = dictionary['inputExpIdPairs'][ampName] 

325 calib.expIdMask[ampName] = np.array(dictionary['expIdMask'][ampName]) 

326 calib.rawExpTimes[ampName] = np.array(dictionary['rawExpTimes'][ampName], dtype=np.float64) 

327 calib.rawMeans[ampName] = np.array(dictionary['rawMeans'][ampName], dtype=np.float64) 

328 calib.rawVars[ampName] = np.array(dictionary['rawVars'][ampName], dtype=np.float64) 

329 calib.gain[ampName] = float(dictionary['gain'][ampName]) 

330 calib.gainErr[ampName] = float(dictionary['gainErr'][ampName]) 

331 calib.noise[ampName] = float(dictionary['noise'][ampName]) 

332 calib.noiseErr[ampName] = float(dictionary['noiseErr'][ampName]) 

333 calib.ptcFitPars[ampName] = np.array(dictionary['ptcFitPars'][ampName], dtype=np.float64) 

334 calib.ptcFitParsError[ampName] = np.array(dictionary['ptcFitParsError'][ampName], 

335 dtype=np.float64) 

336 calib.ptcFitChiSq[ampName] = float(dictionary['ptcFitChiSq'][ampName]) 

337 calib.ptcTurnoff[ampName] = float(dictionary['ptcTurnoff'][ampName]) 

338 if nSignalPoints > 0: 

339 # Regular dataset 

340 calib.covariances[ampName] = np.array(dictionary['covariances'][ampName], 

341 dtype=np.float64).reshape( 

342 (nSignalPoints, covMatrixSide, covMatrixSide)) 

343 calib.covariancesModel[ampName] = np.array( 

344 dictionary['covariancesModel'][ampName], 

345 dtype=np.float64).reshape( 

346 (nSignalPoints, covMatrixSide, covMatrixSide)) 

347 calib.covariancesSqrtWeights[ampName] = np.array( 

348 dictionary['covariancesSqrtWeights'][ampName], 

349 dtype=np.float64).reshape( 

350 (nSignalPoints, covMatrixSide, covMatrixSide)) 

351 calib.aMatrix[ampName] = np.array(dictionary['aMatrix'][ampName], 

352 dtype=np.float64).reshape( 

353 (covMatrixSide, covMatrixSide)) 

354 calib.bMatrix[ampName] = np.array(dictionary['bMatrix'][ampName], 

355 dtype=np.float64).reshape( 

356 (covMatrixSide, covMatrixSide)) 

357 calib.covariancesModelNoB[ampName] = np.array( 

358 dictionary['covariancesModelNoB'][ampName], dtype=np.float64).reshape( 

359 (nSignalPoints, covMatrixSide, covMatrixSide)) 

360 calib.aMatrixNoB[ampName] = np.array( 

361 dictionary['aMatrixNoB'][ampName], 

362 dtype=np.float64).reshape((covMatrixSide, covMatrixSide)) 

363 else: 

364 # Empty dataset 

365 calib.covariances[ampName] = np.array([], dtype=np.float64) 

366 calib.covariancesModel[ampName] = np.array([], dtype=np.float64) 

367 calib.covariancesSqrtWeights[ampName] = np.array([], dtype=np.float64) 

368 calib.aMatrix[ampName] = np.array([], dtype=np.float64) 

369 calib.bMatrix[ampName] = np.array([], dtype=np.float64) 

370 calib.covariancesModelNoB[ampName] = np.array([], dtype=np.float64) 

371 calib.aMatrixNoB[ampName] = np.array([], dtype=np.float64) 

372 

373 calib.finalVars[ampName] = np.array(dictionary['finalVars'][ampName], dtype=np.float64) 

374 calib.finalModelVars[ampName] = np.array(dictionary['finalModelVars'][ampName], dtype=np.float64) 

375 calib.finalMeans[ampName] = np.array(dictionary['finalMeans'][ampName], dtype=np.float64) 

376 calib.photoCharges[ampName] = np.array(dictionary['photoCharges'][ampName], dtype=np.float64) 

377 

378 calib.updateMetadata() 

379 return calib 

380 

381 def toDict(self): 

382 """Return a dictionary containing the calibration properties. 

383 The dictionary should be able to be round-tripped through 

384 `fromDict`. 

385 

386 Returns 

387 ------- 

388 dictionary : `dict` 

389 Dictionary of properties. 

390 """ 

391 self.updateMetadata() 

392 

393 outDict = dict() 

394 metadata = self.getMetadata() 

395 outDict['metadata'] = metadata 

396 

397 def _dictOfArraysToDictOfLists(dictOfArrays): 

398 dictOfLists = {} 

399 for key, value in dictOfArrays.items(): 

400 dictOfLists[key] = value.ravel().tolist() 

401 

402 return dictOfLists 

403 

404 outDict['ptcFitType'] = self.ptcFitType 

405 outDict['covMatrixSide'] = self.covMatrixSide 

406 outDict['ampNames'] = self.ampNames 

407 outDict['badAmps'] = self.badAmps 

408 outDict['inputExpIdPairs'] = self.inputExpIdPairs 

409 outDict['expIdMask'] = _dictOfArraysToDictOfLists(self.expIdMask) 

410 outDict['rawExpTimes'] = _dictOfArraysToDictOfLists(self.rawExpTimes) 

411 outDict['rawMeans'] = _dictOfArraysToDictOfLists(self.rawMeans) 

412 outDict['rawVars'] = _dictOfArraysToDictOfLists(self.rawVars) 

413 outDict['gain'] = self.gain 

414 outDict['gainErr'] = self.gainErr 

415 outDict['noise'] = self.noise 

416 outDict['noiseErr'] = self.noiseErr 

417 outDict['ptcFitPars'] = _dictOfArraysToDictOfLists(self.ptcFitPars) 

418 outDict['ptcFitParsError'] = _dictOfArraysToDictOfLists(self.ptcFitParsError) 

419 outDict['ptcFitChiSq'] = self.ptcFitChiSq 

420 outDict['ptcTurnoff'] = self.ptcTurnoff 

421 outDict['covariances'] = _dictOfArraysToDictOfLists(self.covariances) 

422 outDict['covariancesModel'] = _dictOfArraysToDictOfLists(self.covariancesModel) 

423 outDict['covariancesSqrtWeights'] = _dictOfArraysToDictOfLists(self.covariancesSqrtWeights) 

424 outDict['aMatrix'] = _dictOfArraysToDictOfLists(self.aMatrix) 

425 outDict['bMatrix'] = _dictOfArraysToDictOfLists(self.bMatrix) 

426 outDict['covariancesModelNoB'] = _dictOfArraysToDictOfLists(self.covariancesModelNoB) 

427 outDict['aMatrixNoB'] = _dictOfArraysToDictOfLists(self.aMatrixNoB) 

428 outDict['finalVars'] = _dictOfArraysToDictOfLists(self.finalVars) 

429 outDict['finalModelVars'] = _dictOfArraysToDictOfLists(self.finalModelVars) 

430 outDict['finalMeans'] = _dictOfArraysToDictOfLists(self.finalMeans) 

431 outDict['photoCharges'] = _dictOfArraysToDictOfLists(self.photoCharges) 

432 

433 return outDict 

434 

435 @classmethod 

436 def fromTable(cls, tableList): 

437 """Construct calibration from a list of tables. 

438 This method uses the `fromDict` method to create the 

439 calibration, after constructing an appropriate dictionary from 

440 the input tables. 

441 

442 Parameters 

443 ---------- 

444 tableList : `list` [`lsst.afw.table.Table`] 

445 List of tables to use to construct the datasetPtc. 

446 

447 Returns 

448 ------- 

449 calib : `lsst.ip.isr.PhotonTransferCurveDataset` 

450 The calibration defined in the tables. 

451 """ 

452 ptcTable = tableList[0] 

453 

454 metadata = ptcTable.meta 

455 inDict = dict() 

456 inDict['metadata'] = metadata 

457 inDict['ampNames'] = [] 

458 inDict['ptcFitType'] = [] 

459 inDict['covMatrixSide'] = [] 

460 inDict['inputExpIdPairs'] = dict() 

461 inDict['expIdMask'] = dict() 

462 inDict['rawExpTimes'] = dict() 

463 inDict['rawMeans'] = dict() 

464 inDict['rawVars'] = dict() 

465 inDict['gain'] = dict() 

466 inDict['gainErr'] = dict() 

467 inDict['noise'] = dict() 

468 inDict['noiseErr'] = dict() 

469 inDict['ptcFitPars'] = dict() 

470 inDict['ptcFitParsError'] = dict() 

471 inDict['ptcFitChiSq'] = dict() 

472 inDict['ptcTurnoff'] = dict() 

473 inDict['covariances'] = dict() 

474 inDict['covariancesModel'] = dict() 

475 inDict['covariancesSqrtWeights'] = dict() 

476 inDict['aMatrix'] = dict() 

477 inDict['bMatrix'] = dict() 

478 inDict['covariancesModelNoB'] = dict() 

479 inDict['aMatrixNoB'] = dict() 

480 inDict['finalVars'] = dict() 

481 inDict['finalModelVars'] = dict() 

482 inDict['finalMeans'] = dict() 

483 inDict['badAmps'] = [] 

484 inDict['photoCharges'] = dict() 

485 

486 calibVersion = metadata['PTC_VERSION'] 

487 if calibVersion == 1.0: 

488 cls().log.warning(f"Previous version found for PTC dataset: {calibVersion}. " 

489 f"Setting 'ptcTurnoff' in all amps to last value in 'finalMeans'.") 

490 for record in ptcTable: 

491 ampName = record['AMPLIFIER_NAME'] 

492 

493 inDict['ptcFitType'] = record['PTC_FIT_TYPE'] 

494 inDict['covMatrixSide'] = record['COV_MATRIX_SIDE'] 

495 inDict['ampNames'].append(ampName) 

496 inDict['inputExpIdPairs'][ampName] = record['INPUT_EXP_ID_PAIRS'].tolist() 

497 inDict['expIdMask'][ampName] = record['EXP_ID_MASK'] 

498 inDict['rawExpTimes'][ampName] = record['RAW_EXP_TIMES'] 

499 inDict['rawMeans'][ampName] = record['RAW_MEANS'] 

500 inDict['rawVars'][ampName] = record['RAW_VARS'] 

501 inDict['gain'][ampName] = record['GAIN'] 

502 inDict['gainErr'][ampName] = record['GAIN_ERR'] 

503 inDict['noise'][ampName] = record['NOISE'] 

504 inDict['noiseErr'][ampName] = record['NOISE_ERR'] 

505 inDict['ptcFitPars'][ampName] = record['PTC_FIT_PARS'] 

506 inDict['ptcFitParsError'][ampName] = record['PTC_FIT_PARS_ERROR'] 

507 inDict['ptcFitChiSq'][ampName] = record['PTC_FIT_CHI_SQ'] 

508 inDict['covariances'][ampName] = record['COVARIANCES'] 

509 inDict['covariancesModel'][ampName] = record['COVARIANCES_MODEL'] 

510 inDict['covariancesSqrtWeights'][ampName] = record['COVARIANCES_SQRT_WEIGHTS'] 

511 inDict['aMatrix'][ampName] = record['A_MATRIX'] 

512 inDict['bMatrix'][ampName] = record['B_MATRIX'] 

513 inDict['covariancesModelNoB'][ampName] = record['COVARIANCES_MODEL_NO_B'] 

514 inDict['aMatrixNoB'][ampName] = record['A_MATRIX_NO_B'] 

515 inDict['finalVars'][ampName] = record['FINAL_VARS'] 

516 inDict['finalModelVars'][ampName] = record['FINAL_MODEL_VARS'] 

517 inDict['finalMeans'][ampName] = record['FINAL_MEANS'] 

518 inDict['badAmps'] = record['BAD_AMPS'].tolist() 

519 inDict['photoCharges'][ampName] = record['PHOTO_CHARGE'] 

520 if calibVersion == 1.0: 

521 mask = record['FINAL_MEANS'].mask 

522 array = record['FINAL_MEANS'][~mask] 

523 if len(array) > 0: 

524 inDict['ptcTurnoff'][ampName] = record['FINAL_MEANS'][~mask][-1] 

525 else: 

526 inDict['ptcTurnoff'][ampName] = np.nan 

527 else: 

528 inDict['ptcTurnoff'][ampName] = record['PTC_TURNOFF'] 

529 return cls().fromDict(inDict) 

530 

531 def toTable(self): 

532 """Construct a list of tables containing the information in this 

533 calibration. 

534 

535 The list of tables should create an identical calibration 

536 after being passed to this class's fromTable method. 

537 

538 Returns 

539 ------- 

540 tableList : `list` [`astropy.table.Table`] 

541 List of tables containing the linearity calibration 

542 information. 

543 """ 

544 tableList = [] 

545 self.updateMetadata() 

546 

547 badAmps = np.array(self.badAmps) if len(self.badAmps) else np.array([], dtype="U3") 

548 

549 catalogList = [] 

550 for ampName in self.ampNames: 

551 ampDict = { 

552 'AMPLIFIER_NAME': ampName, 

553 'PTC_FIT_TYPE': self.ptcFitType, 

554 'COV_MATRIX_SIDE': self.covMatrixSide, 

555 'INPUT_EXP_ID_PAIRS': self.inputExpIdPairs[ampName], 

556 'EXP_ID_MASK': self.expIdMask[ampName], 

557 'RAW_EXP_TIMES': self.rawExpTimes[ampName], 

558 'RAW_MEANS': self.rawMeans[ampName], 

559 'RAW_VARS': self.rawVars[ampName], 

560 'GAIN': self.gain[ampName], 

561 'GAIN_ERR': self.gainErr[ampName], 

562 'NOISE': self.noise[ampName], 

563 'NOISE_ERR': self.noiseErr[ampName], 

564 'PTC_FIT_PARS': np.array(self.ptcFitPars[ampName]), 

565 'PTC_FIT_PARS_ERROR': np.array(self.ptcFitParsError[ampName]), 

566 'PTC_FIT_CHI_SQ': self.ptcFitChiSq[ampName], 

567 'PTC_TURNOFF': self.ptcTurnoff[ampName], 

568 'A_MATRIX': self.aMatrix[ampName].ravel(), 

569 'B_MATRIX': self.bMatrix[ampName].ravel(), 

570 'A_MATRIX_NO_B': self.aMatrixNoB[ampName].ravel(), 

571 'BAD_AMPS': badAmps, 

572 'PHOTO_CHARGE': self.photoCharges[ampName], 

573 'COVARIANCES': self.covariances[ampName].ravel(), 

574 'COVARIANCES_MODEL': self.covariancesModel[ampName].ravel(), 

575 'COVARIANCES_SQRT_WEIGHTS': self.covariancesSqrtWeights[ampName].ravel(), 

576 'COVARIANCES_MODEL_NO_B': self.covariancesModelNoB[ampName].ravel(), 

577 'FINAL_VARS': self.finalVars[ampName], 

578 'FINAL_MODEL_VARS': self.finalModelVars[ampName], 

579 'FINAL_MEANS': self.finalMeans[ampName], 

580 } 

581 catalogList.append(ampDict) 

582 

583 catalog = Table(catalogList) 

584 

585 inMeta = self.getMetadata().toDict() 

586 outMeta = {k: v for k, v in inMeta.items() if v is not None} 

587 outMeta.update({k: "" for k, v in inMeta.items() if v is None}) 

588 catalog.meta = outMeta 

589 tableList.append(catalog) 

590 

591 return(tableList) 

592 

593 def fromDetector(self, detector): 

594 """Read metadata parameters from a detector. 

595 

596 Parameters 

597 ---------- 

598 detector : `lsst.afw.cameraGeom.detector` 

599 Input detector with parameters to use. 

600 

601 Returns 

602 ------- 

603 calib : `lsst.ip.isr.PhotonTransferCurveDataset` 

604 The calibration constructed from the detector. 

605 """ 

606 

607 pass 

608 

609 def getExpIdsUsed(self, ampName): 

610 """Get the exposures used, i.e. not discarded, for a given amp. 

611 If no mask has been created yet, all exposures are returned. 

612 

613 Parameters 

614 ---------- 

615 ampName : `str` 

616 

617 Returns 

618 ------- 

619 expIdsUsed : `list` [`tuple`] 

620 List of pairs of exposure ids used in PTC. 

621 """ 

622 if len(self.expIdMask[ampName]) == 0: 

623 return self.inputExpIdPairs[ampName] 

624 

625 # if the mask exists it had better be the same length as the expIdPairs 

626 assert len(self.expIdMask[ampName]) == len(self.inputExpIdPairs[ampName]) 

627 

628 pairs = self.inputExpIdPairs[ampName] 

629 mask = self.expIdMask[ampName] 

630 # cast to bool required because numpy 

631 try: 

632 expIdsUsed = [(exp1, exp2) for ((exp1, exp2), m) in zip(pairs, mask) if m] 

633 except ValueError: 

634 warnings.warn("The PTC file was written incorrectly; you should rerun the " 

635 "PTC solve task if possible.", RuntimeWarning) 

636 expIdsUsed = [] 

637 for pairList, m in zip(pairs, mask): 

638 if m: 

639 expIdsUsed.append(pairList[0]) 

640 

641 return expIdsUsed 

642 

643 def getGoodAmps(self): 

644 """Get the good amps from this PTC.""" 

645 return [amp for amp in self.ampNames if amp not in self.badAmps] 

646 

647 def getGoodPoints(self, ampName): 

648 """Get the good points used for a given amp in the PTC. 

649 

650 Parameters 

651 ---------- 

652 ampName : `str` 

653 

654 Returns 

655 ------- 

656 goodPoints : `np.ndarray` 

657 Boolean array of good points used in PTC. 

658 """ 

659 return self.expIdMask[ampName]