Coverage for python/lsst/fgcmcal/fgcmMakeLut.py: 23%

223 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-02-08 07:46 +0000

1# See COPYRIGHT file at the top of the source tree. 

2# 

3# This file is part of fgcmcal. 

4# 

5# Developed for the LSST Data Management System. 

6# This product includes software developed by the LSST Project 

7# (https://www.lsst.org). 

8# See the COPYRIGHT file at the top-level directory of this distribution 

9# for details of code ownership. 

10# 

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

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

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

14# (at your option) any later version. 

15# 

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

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

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

19# GNU General Public License for more details. 

20# 

21# You should have received a copy of the GNU General Public License 

22# along with this program. If not, see <https://www.gnu.org/licenses/>. 

23"""Make a look-up-table (LUT) for FGCM calibration. 

24 

25This task computes a look-up-table for the range in expected atmosphere 

26variation and variation in instrumental throughput (as tracked by the 

27transmission_filter products). By pre-computing linearized integrals, 

28the FGCM fit is orders of magnitude faster for stars with a broad range 

29of colors and observing bands, yielding precision at the 1-2 mmag level. 

30 

31Computing a LUT requires running MODTRAN or with a pre-generated 

32atmosphere table packaged with fgcm. 

33""" 

34 

35import numpy as np 

36 

37import lsst.pex.config as pexConfig 

38import lsst.pipe.base as pipeBase 

39from lsst.pipe.base import connectionTypes 

40import lsst.afw.table as afwTable 

41import lsst.afw.cameraGeom as afwCameraGeom 

42from .utilities import lookupStaticCalibrations 

43 

44import fgcm 

45 

46__all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask'] 

47 

48 

49class FgcmMakeLutConnections(pipeBase.PipelineTaskConnections, 

50 dimensions=('instrument',), 

51 defaultTemplates={}): 

52 camera = connectionTypes.PrerequisiteInput( 

53 doc="Camera instrument", 

54 name="camera", 

55 storageClass="Camera", 

56 dimensions=("instrument",), 

57 lookupFunction=lookupStaticCalibrations, 

58 isCalibration=True, 

59 ) 

60 

61 transmission_optics = connectionTypes.PrerequisiteInput( 

62 doc="Optics transmission curve information", 

63 name="transmission_optics", 

64 storageClass="TransmissionCurve", 

65 dimensions=("instrument",), 

66 lookupFunction=lookupStaticCalibrations, 

67 isCalibration=True, 

68 deferLoad=True, 

69 ) 

70 

71 transmission_sensor = connectionTypes.PrerequisiteInput( 

72 doc="Sensor transmission curve information", 

73 name="transmission_sensor", 

74 storageClass="TransmissionCurve", 

75 dimensions=("instrument", "detector",), 

76 lookupFunction=lookupStaticCalibrations, 

77 isCalibration=True, 

78 deferLoad=True, 

79 multiple=True, 

80 ) 

81 

82 transmission_filter = connectionTypes.PrerequisiteInput( 

83 doc="Filter transmission curve information", 

84 name="transmission_filter", 

85 storageClass="TransmissionCurve", 

86 dimensions=("band", "instrument", "physical_filter",), 

87 lookupFunction=lookupStaticCalibrations, 

88 isCalibration=True, 

89 deferLoad=True, 

90 multiple=True, 

91 ) 

92 

93 fgcmLookUpTable = connectionTypes.Output( 

94 doc=("Atmosphere + instrument look-up-table for FGCM throughput and " 

95 "chromatic corrections."), 

96 name="fgcmLookUpTable", 

97 storageClass="Catalog", 

98 dimensions=("instrument",), 

99 ) 

100 

101 

102class FgcmMakeLutParametersConfig(pexConfig.Config): 

103 """Config for parameters if atmosphereTableName not available""" 

104 # TODO: When DM-16511 is done, it will be possible to get the 

105 # telescope elevation directly from the camera. 

106 elevation = pexConfig.Field( 

107 doc="Telescope elevation (m)", 

108 dtype=float, 

109 default=None, 

110 ) 

111 pmbRange = pexConfig.ListField( 

112 doc=("Barometric Pressure range (millibar) " 

113 "Recommended range depends on the site."), 

114 dtype=float, 

115 default=None, 

116 ) 

117 pmbSteps = pexConfig.Field( 

118 doc="Barometric Pressure number of steps", 

119 dtype=int, 

120 default=5, 

121 ) 

122 pwvRange = pexConfig.ListField( 

123 doc=("Precipitable Water Vapor range (mm) " 

124 "Recommended range depends on the site."), 

125 dtype=float, 

126 default=None, 

127 ) 

128 pwvSteps = pexConfig.Field( 

129 doc="Precipitable Water Vapor number of steps", 

130 dtype=int, 

131 default=15, 

132 ) 

133 o3Range = pexConfig.ListField( 

134 doc="Ozone range (dob)", 

135 dtype=float, 

136 default=[220.0, 310.0], 

137 ) 

138 o3Steps = pexConfig.Field( 

139 doc="Ozone number of steps", 

140 dtype=int, 

141 default=3, 

142 ) 

143 tauRange = pexConfig.ListField( 

144 doc="Aerosol Optical Depth range (unitless)", 

145 dtype=float, 

146 default=[0.002, 0.35], 

147 ) 

148 tauSteps = pexConfig.Field( 

149 doc="Aerosol Optical Depth number of steps", 

150 dtype=int, 

151 default=11, 

152 ) 

153 alphaRange = pexConfig.ListField( 

154 doc="Aerosol alpha range (unitless)", 

155 dtype=float, 

156 default=[0.0, 2.0], 

157 ) 

158 alphaSteps = pexConfig.Field( 

159 doc="Aerosol alpha number of steps", 

160 dtype=int, 

161 default=9, 

162 ) 

163 zenithRange = pexConfig.ListField( 

164 doc="Zenith angle range (degree)", 

165 dtype=float, 

166 default=[0.0, 70.0], 

167 ) 

168 zenithSteps = pexConfig.Field( 

169 doc="Zenith angle number of steps", 

170 dtype=int, 

171 default=21, 

172 ) 

173 # Note that the standard atmosphere parameters depend on the observatory 

174 # and elevation, and so these should be set on a per-camera basis. 

175 pmbStd = pexConfig.Field( 

176 doc=("Standard Atmosphere pressure (millibar); " 

177 "Recommended default depends on the site."), 

178 dtype=float, 

179 default=None, 

180 ) 

181 pwvStd = pexConfig.Field( 

182 doc=("Standard Atmosphere PWV (mm); " 

183 "Recommended default depends on the site."), 

184 dtype=float, 

185 default=None, 

186 ) 

187 o3Std = pexConfig.Field( 

188 doc="Standard Atmosphere O3 (dob)", 

189 dtype=float, 

190 default=263.0, 

191 ) 

192 tauStd = pexConfig.Field( 

193 doc="Standard Atmosphere aerosol optical depth", 

194 dtype=float, 

195 default=0.03, 

196 ) 

197 alphaStd = pexConfig.Field( 

198 doc="Standard Atmosphere aerosol alpha", 

199 dtype=float, 

200 default=1.0, 

201 ) 

202 airmassStd = pexConfig.Field( 

203 doc=("Standard Atmosphere airmass; " 

204 "Recommended default depends on the survey strategy."), 

205 dtype=float, 

206 default=None, 

207 ) 

208 lambdaNorm = pexConfig.Field( 

209 doc="Aerosol Optical Depth normalization wavelength (Angstrom)", 

210 dtype=float, 

211 default=7750.0, 

212 ) 

213 lambdaStep = pexConfig.Field( 

214 doc="Wavelength step for generating atmospheres (nm)", 

215 dtype=float, 

216 default=0.5, 

217 ) 

218 lambdaRange = pexConfig.ListField( 

219 doc="Wavelength range for LUT (Angstrom)", 

220 dtype=float, 

221 default=[3000.0, 11000.0], 

222 ) 

223 

224 

225class FgcmMakeLutConfig(pipeBase.PipelineTaskConfig, 

226 pipelineConnections=FgcmMakeLutConnections): 

227 """Config for FgcmMakeLutTask""" 

228 physicalFilters = pexConfig.ListField( 

229 doc="List of physicalFilter labels to generate look-up table.", 

230 dtype=str, 

231 default=[], 

232 ) 

233 stdPhysicalFilterOverrideMap = pexConfig.DictField( 

234 doc=("Override mapping from physical filter labels to 'standard' physical " 

235 "filter labels. The 'standard' physical filter defines the transmission " 

236 "curve that the FGCM standard bandpass will be based on. " 

237 "Any filter not listed here will be mapped to " 

238 "itself (e.g. g->g or HSC-G->HSC-G). Use this override for cross-" 

239 "filter calibration such as HSC-R->HSC-R2 and HSC-I->HSC-I2."), 

240 keytype=str, 

241 itemtype=str, 

242 default={}, 

243 ) 

244 atmosphereTableName = pexConfig.Field( 

245 doc="FGCM name or filename of precomputed atmospheres", 

246 dtype=str, 

247 default=None, 

248 optional=True, 

249 ) 

250 parameters = pexConfig.ConfigField( 

251 doc="Atmosphere parameters (required if no atmosphereTableName)", 

252 dtype=FgcmMakeLutParametersConfig, 

253 default=None, 

254 check=None) 

255 

256 def validate(self): 

257 """ 

258 Validate the config parameters. 

259 

260 This method behaves differently from the parent validate in the case 

261 that atmosphereTableName is set. In this case, the config values 

262 for standard values, step sizes, and ranges are loaded 

263 directly from the specified atmosphereTableName. 

264 """ 

265 # check that filterNames and stdFilterNames are okay 

266 self._fields['physicalFilters'].validate(self) 

267 self._fields['stdPhysicalFilterOverrideMap'].validate(self) 

268 

269 if self.atmosphereTableName is None: 

270 # Validate the parameters 

271 self._fields['parameters'].validate(self) 

272 

273 

274class FgcmMakeLutTask(pipeBase.PipelineTask): 

275 """ 

276 Make Look-Up Table for FGCM. 

277 

278 This task computes a look-up-table for the range in expected atmosphere 

279 variation and variation in instrumental throughput (as tracked by the 

280 transmission_filter products). By pre-computing linearized integrals, 

281 the FGCM fit is orders of magnitude faster for stars with a broad range 

282 of colors and observing bands, yielding precision at the 1-2 mmag level. 

283 

284 Computing a LUT requires running MODTRAN or with a pre-generated 

285 atmosphere table packaged with fgcm. 

286 """ 

287 

288 ConfigClass = FgcmMakeLutConfig 

289 _DefaultName = "fgcmMakeLut" 

290 

291 def __init__(self, butler=None, initInputs=None, **kwargs): 

292 super().__init__(**kwargs) 

293 

294 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

295 camera = butlerQC.get(inputRefs.camera) 

296 

297 opticsHandle = butlerQC.get(inputRefs.transmission_optics) 

298 

299 sensorHandles = butlerQC.get(inputRefs.transmission_sensor) 

300 sensorHandleDict = {sensorHandle.dataId.byName()['detector']: sensorHandle for 

301 sensorHandle in sensorHandles} 

302 

303 filterHandles = butlerQC.get(inputRefs.transmission_filter) 

304 filterHandleDict = {filterHandle.dataId['physical_filter']: filterHandle for 

305 filterHandle in filterHandles} 

306 

307 lutCat = self._fgcmMakeLut(camera, 

308 opticsHandle, 

309 sensorHandleDict, 

310 filterHandleDict) 

311 butlerQC.put(lutCat, outputRefs.fgcmLookUpTable) 

312 

313 def _fgcmMakeLut(self, camera, opticsHandle, sensorHandleDict, 

314 filterHandleDict): 

315 """ 

316 Make a FGCM Look-up Table 

317 

318 Parameters 

319 ---------- 

320 camera : `lsst.afw.cameraGeom.Camera` 

321 Camera from the butler. 

322 opticsHandle : `lsst.daf.butler.DeferredDatasetHandle` 

323 Reference to optics transmission curve. 

324 sensorHandleDict : `dict` of [`int`, `lsst.daf.butler.DeferredDatasetHandle`] 

325 Dictionary of references to sensor transmission curves. Key will 

326 be detector id. 

327 filterHandleDict : `dict` of [`str`, `lsst.daf.butler.DeferredDatasetHandle`] 

328 Dictionary of references to filter transmission curves. Key will 

329 be physical filter label. 

330 

331 Returns 

332 ------- 

333 fgcmLookUpTable : `BaseCatalog` 

334 The FGCM look-up table. 

335 """ 

336 # number of ccds from the length of the camera iterator 

337 nCcd = len(camera) 

338 self.log.info("Found %d ccds for look-up table" % (nCcd)) 

339 

340 # Load in optics, etc. 

341 self._loadThroughputs(camera, 

342 opticsHandle, 

343 sensorHandleDict, 

344 filterHandleDict) 

345 

346 lutConfig = self._createLutConfig(nCcd) 

347 

348 # make the lut object 

349 self.log.info("Making the LUT maker object") 

350 self.fgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig) 

351 

352 # generate the throughput dictionary. 

353 

354 # these will be in Angstroms 

355 # note that lambdaStep is currently in nm, because of historical 

356 # reasons in the code. Convert to Angstroms here. 

357 throughputLambda = np.arange(self.fgcmLutMaker.lambdaRange[0], 

358 self.fgcmLutMaker.lambdaRange[1]+self.fgcmLutMaker.lambdaStep*10, 

359 self.fgcmLutMaker.lambdaStep*10.) 

360 

361 self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" % 

362 (throughputLambda[0], throughputLambda[-1], 

363 throughputLambda[1] - throughputLambda[0])) 

364 

365 throughputDict = {} 

366 for i, physicalFilter in enumerate(self.config.physicalFilters): 

367 tDict = {} 

368 tDict['LAMBDA'] = throughputLambda 

369 for ccdIndex, detector in enumerate(camera): 

370 tDict[ccdIndex] = self._getThroughputDetector(detector, physicalFilter, throughputLambda) 

371 throughputDict[physicalFilter] = tDict 

372 

373 # set the throughputs 

374 self.fgcmLutMaker.setThroughputs(throughputDict) 

375 

376 # make the LUT 

377 self.log.info("Making LUT") 

378 self.fgcmLutMaker.makeLUT() 

379 

380 # and save the LUT 

381 

382 # build the index values 

383 comma = ',' 

384 physicalFilterString = comma.join(self.config.physicalFilters) 

385 stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList()) 

386 

387 atmosphereTableName = 'NoTableWasUsed' 

388 if self.config.atmosphereTableName is not None: 

389 atmosphereTableName = self.config.atmosphereTableName 

390 

391 lutSchema = self._makeLutSchema(physicalFilterString, stdPhysicalFilterString, 

392 atmosphereTableName) 

393 

394 lutCat = self._makeLutCat(lutSchema, physicalFilterString, 

395 stdPhysicalFilterString, atmosphereTableName) 

396 return lutCat 

397 

398 def _getStdPhysicalFilterList(self): 

399 """Get the standard physical filter lists from config.physicalFilters 

400 and config.stdPhysicalFilterOverrideMap 

401 

402 Returns 

403 ------- 

404 stdPhysicalFilters : `list` 

405 """ 

406 override = self.config.stdPhysicalFilterOverrideMap 

407 return [override.get(physicalFilter, physicalFilter) for 

408 physicalFilter in self.config.physicalFilters] 

409 

410 def _createLutConfig(self, nCcd): 

411 """ 

412 Create the fgcmLut config dictionary 

413 

414 Parameters 

415 ---------- 

416 nCcd: `int` 

417 Number of CCDs in the camera 

418 """ 

419 

420 # create the common stub of the lutConfig 

421 lutConfig = {} 

422 lutConfig['logger'] = self.log 

423 lutConfig['filterNames'] = self.config.physicalFilters 

424 lutConfig['stdFilterNames'] = self._getStdPhysicalFilterList() 

425 lutConfig['nCCD'] = nCcd 

426 

427 # atmosphereTable already validated if available 

428 if self.config.atmosphereTableName is not None: 

429 lutConfig['atmosphereTableName'] = self.config.atmosphereTableName 

430 else: 

431 # use the regular paramters (also validated if needed) 

432 lutConfig['elevation'] = self.config.parameters.elevation 

433 lutConfig['pmbRange'] = self.config.parameters.pmbRange 

434 lutConfig['pmbSteps'] = self.config.parameters.pmbSteps 

435 lutConfig['pwvRange'] = self.config.parameters.pwvRange 

436 lutConfig['pwvSteps'] = self.config.parameters.pwvSteps 

437 lutConfig['o3Range'] = self.config.parameters.o3Range 

438 lutConfig['o3Steps'] = self.config.parameters.o3Steps 

439 lutConfig['tauRange'] = self.config.parameters.tauRange 

440 lutConfig['tauSteps'] = self.config.parameters.tauSteps 

441 lutConfig['alphaRange'] = self.config.parameters.alphaRange 

442 lutConfig['alphaSteps'] = self.config.parameters.alphaSteps 

443 lutConfig['zenithRange'] = self.config.parameters.zenithRange 

444 lutConfig['zenithSteps'] = self.config.parameters.zenithSteps 

445 lutConfig['pmbStd'] = self.config.parameters.pmbStd 

446 lutConfig['pwvStd'] = self.config.parameters.pwvStd 

447 lutConfig['o3Std'] = self.config.parameters.o3Std 

448 lutConfig['tauStd'] = self.config.parameters.tauStd 

449 lutConfig['alphaStd'] = self.config.parameters.alphaStd 

450 lutConfig['airmassStd'] = self.config.parameters.airmassStd 

451 lutConfig['lambdaRange'] = self.config.parameters.lambdaRange 

452 lutConfig['lambdaStep'] = self.config.parameters.lambdaStep 

453 lutConfig['lambdaNorm'] = self.config.parameters.lambdaNorm 

454 

455 return lutConfig 

456 

457 def _loadThroughputs(self, camera, opticsHandle, sensorHandleDict, filterHandleDict): 

458 """Internal method to load throughput data for filters 

459 

460 Parameters 

461 ---------- 

462 camera: `lsst.afw.cameraGeom.Camera` 

463 Camera from the butler 

464 opticsHandle : `lsst.daf.butler.DeferredDatasetHandle` 

465 Reference to optics transmission curve. 

466 sensorHandleDict : `dict` of [`int`, `lsst.daf.butler.DeferredDatasetHandle`] 

467 Dictionary of references to sensor transmission curves. Key will 

468 be detector id. 

469 filterHandleDict : `dict` of [`str`, `lsst.daf.butler.DeferredDatasetHandle`] 

470 Dictionary of references to filter transmission curves. Key will 

471 be physical filter label. 

472 

473 Raises 

474 ------ 

475 ValueError : Raised if configured filter name does not match any of the 

476 available filter transmission curves. 

477 """ 

478 self._opticsTransmission = opticsHandle.get() 

479 

480 self._sensorsTransmission = {} 

481 for detector in camera: 

482 self._sensorsTransmission[detector.getId()] = sensorHandleDict[detector.getId()].get() 

483 

484 self._filtersTransmission = {} 

485 for physicalFilter in self.config.physicalFilters: 

486 self._filtersTransmission[physicalFilter] = filterHandleDict[physicalFilter].get() 

487 

488 def _getThroughputDetector(self, detector, physicalFilter, throughputLambda): 

489 """Internal method to get throughput for a detector. 

490 

491 Returns the throughput at the center of the detector for a given filter. 

492 

493 Parameters 

494 ---------- 

495 detector: `lsst.afw.cameraGeom._detector.Detector` 

496 Detector on camera 

497 physicalFilter: `str` 

498 Physical filter label 

499 throughputLambda: `np.array(dtype=np.float64)` 

500 Wavelength steps (Angstrom) 

501 

502 Returns 

503 ------- 

504 throughput: `np.array(dtype=np.float64)` 

505 Throughput (max 1.0) at throughputLambda 

506 """ 

507 

508 c = detector.getCenter(afwCameraGeom.FOCAL_PLANE) 

509 c.scale(1.0/detector.getPixelSize()[0]) # Assumes x and y pixel sizes in arcsec are the same 

510 

511 throughput = self._opticsTransmission.sampleAt(position=c, 

512 wavelengths=throughputLambda) 

513 

514 throughput *= self._sensorsTransmission[detector.getId()].sampleAt(position=c, 

515 wavelengths=throughputLambda) 

516 

517 throughput *= self._filtersTransmission[physicalFilter].sampleAt(position=c, 

518 wavelengths=throughputLambda) 

519 

520 # Clip the throughput from 0 to 1 

521 throughput = np.clip(throughput, 0.0, 1.0) 

522 

523 return throughput 

524 

525 def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString, 

526 atmosphereTableName): 

527 """ 

528 Make the LUT schema 

529 

530 Parameters 

531 ---------- 

532 physicalFilterString: `str` 

533 Combined string of all the physicalFilters 

534 stdPhysicalFilterString: `str` 

535 Combined string of all the standard physicalFilters 

536 atmosphereTableName: `str` 

537 Name of the atmosphere table used to generate LUT 

538 

539 Returns 

540 ------- 

541 lutSchema: `afwTable.schema` 

542 """ 

543 

544 lutSchema = afwTable.Schema() 

545 

546 lutSchema.addField('tablename', type=str, doc='Atmosphere table name', 

547 size=len(atmosphereTableName)) 

548 lutSchema.addField('elevation', type=float, doc="Telescope elevation used for LUT") 

549 lutSchema.addField('physicalFilters', type=str, doc='physicalFilters in LUT', 

550 size=len(physicalFilterString)) 

551 lutSchema.addField('stdPhysicalFilters', type=str, doc='Standard physicalFilters in LUT', 

552 size=len(stdPhysicalFilterString)) 

553 lutSchema.addField('pmb', type='ArrayD', doc='Barometric Pressure', 

554 size=self.fgcmLutMaker.pmb.size) 

555 lutSchema.addField('pmbFactor', type='ArrayD', doc='PMB scaling factor', 

556 size=self.fgcmLutMaker.pmb.size) 

557 lutSchema.addField('pmbElevation', type=np.float64, doc='PMB Scaling at elevation') 

558 lutSchema.addField('pwv', type='ArrayD', doc='Preciptable Water Vapor', 

559 size=self.fgcmLutMaker.pwv.size) 

560 lutSchema.addField('o3', type='ArrayD', doc='Ozone', 

561 size=self.fgcmLutMaker.o3.size) 

562 lutSchema.addField('tau', type='ArrayD', doc='Aerosol optical depth', 

563 size=self.fgcmLutMaker.tau.size) 

564 lutSchema.addField('lambdaNorm', type=np.float64, doc='AOD wavelength') 

565 lutSchema.addField('alpha', type='ArrayD', doc='Aerosol alpha', 

566 size=self.fgcmLutMaker.alpha.size) 

567 lutSchema.addField('zenith', type='ArrayD', doc='Zenith angle', 

568 size=self.fgcmLutMaker.zenith.size) 

569 lutSchema.addField('nCcd', type=np.int32, doc='Number of CCDs') 

570 

571 # and the standard values 

572 lutSchema.addField('pmbStd', type=np.float64, doc='PMB Standard') 

573 lutSchema.addField('pwvStd', type=np.float64, doc='PWV Standard') 

574 lutSchema.addField('o3Std', type=np.float64, doc='O3 Standard') 

575 lutSchema.addField('tauStd', type=np.float64, doc='Tau Standard') 

576 lutSchema.addField('alphaStd', type=np.float64, doc='Alpha Standard') 

577 lutSchema.addField('zenithStd', type=np.float64, doc='Zenith angle Standard') 

578 lutSchema.addField('lambdaRange', type='ArrayD', doc='Wavelength range', 

579 size=2) 

580 lutSchema.addField('lambdaStep', type=np.float64, doc='Wavelength step') 

581 lutSchema.addField('lambdaStd', type='ArrayD', doc='Standard Wavelength', 

582 size=len(self.fgcmLutMaker.filterNames)) 

583 lutSchema.addField('lambdaStdFilter', type='ArrayD', doc='Standard Wavelength (raw)', 

584 size=len(self.fgcmLutMaker.filterNames)) 

585 lutSchema.addField('i0Std', type='ArrayD', doc='I0 Standard', 

586 size=len(self.fgcmLutMaker.filterNames)) 

587 lutSchema.addField('i1Std', type='ArrayD', doc='I1 Standard', 

588 size=len(self.fgcmLutMaker.filterNames)) 

589 lutSchema.addField('i10Std', type='ArrayD', doc='I10 Standard', 

590 size=len(self.fgcmLutMaker.filterNames)) 

591 lutSchema.addField('i2Std', type='ArrayD', doc='I2 Standard', 

592 size=len(self.fgcmLutMaker.filterNames)) 

593 lutSchema.addField('lambdaB', type='ArrayD', doc='Wavelength for passband (no atm)', 

594 size=len(self.fgcmLutMaker.filterNames)) 

595 lutSchema.addField('atmLambda', type='ArrayD', doc='Atmosphere wavelengths (Angstrom)', 

596 size=self.fgcmLutMaker.atmLambda.size) 

597 lutSchema.addField('atmStdTrans', type='ArrayD', doc='Standard Atmosphere Throughput', 

598 size=self.fgcmLutMaker.atmStdTrans.size) 

599 

600 # and the look-up-tables 

601 lutSchema.addField('luttype', type=str, size=20, doc='Look-up table type') 

602 lutSchema.addField('lut', type='ArrayF', doc='Look-up table for luttype', 

603 size=self.fgcmLutMaker.lut['I0'].size) 

604 

605 return lutSchema 

606 

607 def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString, 

608 atmosphereTableName): 

609 """ 

610 Make the LUT schema 

611 

612 Parameters 

613 ---------- 

614 lutSchema: `afwTable.schema` 

615 Lut catalog schema 

616 physicalFilterString: `str` 

617 Combined string of all the physicalFilters 

618 stdPhysicalFilterString: `str` 

619 Combined string of all the standard physicalFilters 

620 atmosphereTableName: `str` 

621 Name of the atmosphere table used to generate LUT 

622 

623 Returns 

624 ------- 

625 lutCat: `afwTable.BaseCatalog` 

626 Lut catalog for persistence 

627 """ 

628 

629 # The somewhat strange format is to make sure that 

630 # the rows of the afwTable do not get too large 

631 # (see DM-11419) 

632 

633 lutCat = afwTable.BaseCatalog(lutSchema) 

634 lutCat.table.preallocate(14) 

635 

636 # first fill the first index 

637 rec = lutCat.addNew() 

638 

639 rec['tablename'] = atmosphereTableName 

640 rec['elevation'] = self.fgcmLutMaker.atmosphereTable.elevation 

641 rec['physicalFilters'] = physicalFilterString 

642 rec['stdPhysicalFilters'] = stdPhysicalFilterString 

643 rec['pmb'][:] = self.fgcmLutMaker.pmb 

644 rec['pmbFactor'][:] = self.fgcmLutMaker.pmbFactor 

645 rec['pmbElevation'] = self.fgcmLutMaker.pmbElevation 

646 rec['pwv'][:] = self.fgcmLutMaker.pwv 

647 rec['o3'][:] = self.fgcmLutMaker.o3 

648 rec['tau'][:] = self.fgcmLutMaker.tau 

649 rec['lambdaNorm'] = self.fgcmLutMaker.lambdaNorm 

650 rec['alpha'][:] = self.fgcmLutMaker.alpha 

651 rec['zenith'][:] = self.fgcmLutMaker.zenith 

652 rec['nCcd'] = self.fgcmLutMaker.nCCD 

653 

654 rec['pmbStd'] = self.fgcmLutMaker.pmbStd 

655 rec['pwvStd'] = self.fgcmLutMaker.pwvStd 

656 rec['o3Std'] = self.fgcmLutMaker.o3Std 

657 rec['tauStd'] = self.fgcmLutMaker.tauStd 

658 rec['alphaStd'] = self.fgcmLutMaker.alphaStd 

659 rec['zenithStd'] = self.fgcmLutMaker.zenithStd 

660 rec['lambdaRange'][:] = self.fgcmLutMaker.lambdaRange 

661 rec['lambdaStep'] = self.fgcmLutMaker.lambdaStep 

662 rec['lambdaStd'][:] = self.fgcmLutMaker.lambdaStd 

663 rec['lambdaStdFilter'][:] = self.fgcmLutMaker.lambdaStdFilter 

664 rec['i0Std'][:] = self.fgcmLutMaker.I0Std 

665 rec['i1Std'][:] = self.fgcmLutMaker.I1Std 

666 rec['i10Std'][:] = self.fgcmLutMaker.I10Std 

667 rec['i2Std'][:] = self.fgcmLutMaker.I2Std 

668 rec['lambdaB'][:] = self.fgcmLutMaker.lambdaB 

669 rec['atmLambda'][:] = self.fgcmLutMaker.atmLambda 

670 rec['atmStdTrans'][:] = self.fgcmLutMaker.atmStdTrans 

671 

672 rec['luttype'] = 'I0' 

673 rec['lut'][:] = self.fgcmLutMaker.lut['I0'].flatten() 

674 

675 # and add the rest 

676 rec = lutCat.addNew() 

677 rec['luttype'] = 'I1' 

678 rec['lut'][:] = self.fgcmLutMaker.lut['I1'].flatten() 

679 

680 derivTypes = ['D_PMB', 'D_LNPWV', 'D_O3', 'D_LNTAU', 'D_ALPHA', 'D_SECZENITH', 

681 'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1', 

682 'D_SECZENITH_I1'] 

683 for derivType in derivTypes: 

684 rec = lutCat.addNew() 

685 rec['luttype'] = derivType 

686 rec['lut'][:] = self.fgcmLutMaker.lutDeriv[derivType].flatten() 

687 

688 return lutCat