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

263 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-01 02:26 -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]) 

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

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

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

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

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

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

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

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

335 calib.ptcFitChiSq[ampName] = np.array(dictionary['ptcFitChiSq'][ampName]) 

336 calib.ptcTurnoff[ampName] = np.array(dictionary['ptcTurnoff'][ampName]) 

337 if nSignalPoints > 0: 

338 # Regular dataset 

339 calib.covariances[ampName] = np.array(dictionary['covariances'][ampName]).reshape( 

340 (nSignalPoints, covMatrixSide, covMatrixSide)) 

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

342 dictionary['covariancesModel'][ampName]).reshape( 

343 (nSignalPoints, covMatrixSide, covMatrixSide)) 

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

345 dictionary['covariancesSqrtWeights'][ampName]).reshape( 

346 (nSignalPoints, covMatrixSide, covMatrixSide)) 

347 calib.aMatrix[ampName] = np.array(dictionary['aMatrix'][ampName]).reshape( 

348 (covMatrixSide, covMatrixSide)) 

349 calib.bMatrix[ampName] = np.array(dictionary['bMatrix'][ampName]).reshape( 

350 (covMatrixSide, covMatrixSide)) 

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

352 dictionary['covariancesModelNoB'][ampName]).reshape( 

353 (nSignalPoints, covMatrixSide, covMatrixSide)) 

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

355 dictionary['aMatrixNoB'][ampName]).reshape((covMatrixSide, covMatrixSide)) 

356 else: 

357 # Empty dataset 

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

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

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

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

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

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

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

365 

366 calib.finalVars[ampName] = np.array(dictionary['finalVars'][ampName]) 

367 calib.finalModelVars[ampName] = np.array(dictionary['finalModelVars'][ampName]) 

368 calib.finalMeans[ampName] = np.array(dictionary['finalMeans'][ampName]) 

369 calib.photoCharges[ampName] = np.array(dictionary['photoCharges'][ampName]) 

370 

371 calib.updateMetadata() 

372 return calib 

373 

374 def toDict(self): 

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

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

377 `fromDict`. 

378 

379 Returns 

380 ------- 

381 dictionary : `dict` 

382 Dictionary of properties. 

383 """ 

384 self.updateMetadata() 

385 

386 outDict = dict() 

387 metadata = self.getMetadata() 

388 outDict['metadata'] = metadata 

389 

390 def _dictOfArraysToDictOfLists(dictOfArrays): 

391 dictOfLists = {} 

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

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

394 

395 return dictOfLists 

396 

397 outDict['ptcFitType'] = self.ptcFitType 

398 outDict['covMatrixSide'] = self.covMatrixSide 

399 outDict['ampNames'] = self.ampNames 

400 outDict['badAmps'] = self.badAmps 

401 outDict['inputExpIdPairs'] = self.inputExpIdPairs 

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

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

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

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

406 outDict['gain'] = self.gain 

407 outDict['gainErr'] = self.gainErr 

408 outDict['noise'] = self.noise 

409 outDict['noiseErr'] = self.noiseErr 

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

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

412 outDict['ptcFitChiSq'] = self.ptcFitChiSq 

413 outDict['ptcTurnoff'] = self.ptcTurnoff 

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

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

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

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

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

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

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

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

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

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

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

425 

426 return outDict 

427 

428 @classmethod 

429 def fromTable(cls, tableList): 

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

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

432 calibration, after constructing an appropriate dictionary from 

433 the input tables. 

434 

435 Parameters 

436 ---------- 

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

438 List of tables to use to construct the datasetPtc. 

439 

440 Returns 

441 ------- 

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

443 The calibration defined in the tables. 

444 """ 

445 ptcTable = tableList[0] 

446 

447 metadata = ptcTable.meta 

448 inDict = dict() 

449 inDict['metadata'] = metadata 

450 inDict['ampNames'] = [] 

451 inDict['ptcFitType'] = [] 

452 inDict['covMatrixSide'] = [] 

453 inDict['inputExpIdPairs'] = dict() 

454 inDict['expIdMask'] = dict() 

455 inDict['rawExpTimes'] = dict() 

456 inDict['rawMeans'] = dict() 

457 inDict['rawVars'] = dict() 

458 inDict['gain'] = dict() 

459 inDict['gainErr'] = dict() 

460 inDict['noise'] = dict() 

461 inDict['noiseErr'] = dict() 

462 inDict['ptcFitPars'] = dict() 

463 inDict['ptcFitParsError'] = dict() 

464 inDict['ptcFitChiSq'] = dict() 

465 inDict['ptcTurnoff'] = dict() 

466 inDict['covariances'] = dict() 

467 inDict['covariancesModel'] = dict() 

468 inDict['covariancesSqrtWeights'] = dict() 

469 inDict['aMatrix'] = dict() 

470 inDict['bMatrix'] = dict() 

471 inDict['covariancesModelNoB'] = dict() 

472 inDict['aMatrixNoB'] = dict() 

473 inDict['finalVars'] = dict() 

474 inDict['finalModelVars'] = dict() 

475 inDict['finalMeans'] = dict() 

476 inDict['badAmps'] = [] 

477 inDict['photoCharges'] = dict() 

478 

479 calibVersion = metadata['PTC_VERSION'] 

480 if calibVersion == 1.0: 

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

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

483 for record in ptcTable: 

484 ampName = record['AMPLIFIER_NAME'] 

485 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

513 if calibVersion == 1.0: 

514 mask = record['FINAL_MEANS'].mask 

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

516 if len(array) > 0: 

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

518 else: 

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

520 else: 

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

522 return cls().fromDict(inDict) 

523 

524 def toTable(self): 

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

526 calibration. 

527 

528 The list of tables should create an identical calibration 

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

530 

531 Returns 

532 ------- 

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

534 List of tables containing the linearity calibration 

535 information. 

536 """ 

537 tableList = [] 

538 self.updateMetadata() 

539 

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

541 

542 catalogList = [] 

543 for ampName in self.ampNames: 

544 ampDict = { 

545 'AMPLIFIER_NAME': ampName, 

546 'PTC_FIT_TYPE': self.ptcFitType, 

547 'COV_MATRIX_SIDE': self.covMatrixSide, 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

564 'BAD_AMPS': badAmps, 

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

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

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

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

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

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

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

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

573 } 

574 catalogList.append(ampDict) 

575 

576 catalog = Table(catalogList) 

577 

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

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

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

581 catalog.meta = outMeta 

582 tableList.append(catalog) 

583 

584 return(tableList) 

585 

586 def fromDetector(self, detector): 

587 """Read metadata parameters from a detector. 

588 

589 Parameters 

590 ---------- 

591 detector : `lsst.afw.cameraGeom.detector` 

592 Input detector with parameters to use. 

593 

594 Returns 

595 ------- 

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

597 The calibration constructed from the detector. 

598 """ 

599 

600 pass 

601 

602 def getExpIdsUsed(self, ampName): 

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

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

605 

606 Parameters 

607 ---------- 

608 ampName : `str` 

609 

610 Returns 

611 ------- 

612 expIdsUsed : `list` [`tuple`] 

613 List of pairs of exposure ids used in PTC. 

614 """ 

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

616 return self.inputExpIdPairs[ampName] 

617 

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

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

620 

621 pairs = self.inputExpIdPairs[ampName] 

622 mask = self.expIdMask[ampName] 

623 # cast to bool required because numpy 

624 try: 

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

626 except ValueError: 

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

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

629 expIdsUsed = [] 

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

631 if m: 

632 expIdsUsed.append(pairList[0]) 

633 

634 return expIdsUsed 

635 

636 def getGoodAmps(self): 

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

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

639 

640 def getGoodPoints(self, ampName): 

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

642 

643 Parameters 

644 ---------- 

645 ampName : `str` 

646 

647 Returns 

648 ------- 

649 goodPoints : `np.ndarray` 

650 Boolean array of good points used in PTC. 

651 """ 

652 return self.expIdMask[ampName]