Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 sys 

36import traceback 

37 

38import numpy as np 

39 

40import lsst.pex.config as pexConfig 

41import lsst.pipe.base as pipeBase 

42import lsst.afw.table as afwTable 

43import lsst.afw.cameraGeom as afwCameraGeom 

44from lsst.afw.image import Filter 

45from lsst.daf.persistence import NoResults 

46 

47import fgcm 

48 

49__all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask', 

50 'FgcmMakeLutRunner'] 

51 

52 

53class FgcmMakeLutParametersConfig(pexConfig.Config): 

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

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

56 # telescope elevation directly from the camera. 

57 elevation = pexConfig.Field( 

58 doc="Telescope elevation (m)", 

59 dtype=float, 

60 default=None, 

61 ) 

62 pmbRange = pexConfig.ListField( 

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

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

65 dtype=float, 

66 default=None, 

67 ) 

68 pmbSteps = pexConfig.Field( 

69 doc="Barometric Pressure number of steps", 

70 dtype=int, 

71 default=5, 

72 ) 

73 pwvRange = pexConfig.ListField( 

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

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

76 dtype=float, 

77 default=None, 

78 ) 

79 pwvSteps = pexConfig.Field( 

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

81 dtype=int, 

82 default=15, 

83 ) 

84 o3Range = pexConfig.ListField( 

85 doc="Ozone range (dob)", 

86 dtype=float, 

87 default=[220.0, 310.0], 

88 ) 

89 o3Steps = pexConfig.Field( 

90 doc="Ozone number of steps", 

91 dtype=int, 

92 default=3, 

93 ) 

94 tauRange = pexConfig.ListField( 

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

96 dtype=float, 

97 default=[0.002, 0.35], 

98 ) 

99 tauSteps = pexConfig.Field( 

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

101 dtype=int, 

102 default=11, 

103 ) 

104 alphaRange = pexConfig.ListField( 

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

106 dtype=float, 

107 default=[0.0, 2.0], 

108 ) 

109 alphaSteps = pexConfig.Field( 

110 doc="Aerosol alpha number of steps", 

111 dtype=int, 

112 default=9, 

113 ) 

114 zenithRange = pexConfig.ListField( 

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

116 dtype=float, 

117 default=[0.0, 70.0], 

118 ) 

119 zenithSteps = pexConfig.Field( 

120 doc="Zenith angle number of steps", 

121 dtype=int, 

122 default=21, 

123 ) 

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

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

126 pmbStd = pexConfig.Field( 

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

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

129 dtype=float, 

130 default=None, 

131 ) 

132 pwvStd = pexConfig.Field( 

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

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

135 dtype=float, 

136 default=None, 

137 ) 

138 o3Std = pexConfig.Field( 

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

140 dtype=float, 

141 default=263.0, 

142 ) 

143 tauStd = pexConfig.Field( 

144 doc="Standard Atmosphere aerosol optical depth", 

145 dtype=float, 

146 default=0.03, 

147 ) 

148 alphaStd = pexConfig.Field( 

149 doc="Standard Atmosphere aerosol alpha", 

150 dtype=float, 

151 default=1.0, 

152 ) 

153 airmassStd = pexConfig.Field( 

154 doc=("Standard Atmosphere airmass; " 

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

156 dtype=float, 

157 default=None, 

158 ) 

159 lambdaNorm = pexConfig.Field( 

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

161 dtype=float, 

162 default=7750.0, 

163 ) 

164 lambdaStep = pexConfig.Field( 

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

166 dtype=float, 

167 default=0.5, 

168 ) 

169 lambdaRange = pexConfig.ListField( 

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

171 dtype=float, 

172 default=[3000.0, 11000.0], 

173 ) 

174 

175 

176class FgcmMakeLutConfig(pexConfig.Config): 

177 """Config for FgcmMakeLutTask""" 

178 

179 filterNames = pexConfig.ListField( 

180 doc="Filter names to build LUT ('short' names)", 

181 dtype=str, 

182 default=None, 

183 ) 

184 stdFilterNames = pexConfig.ListField( 

185 doc=("Standard filterNames ('short' names). " 

186 "Each filter in filterName will be calibrated to a matched " 

187 "stdFilterName. In regular usage, one has g->g, r->r, ... " 

188 "In the case of HSC, one would have g->g, r->r2, r2->r2, ... " 

189 "which allows replacement (or time-variable) filters to be " 

190 "properly cross-calibrated."), 

191 dtype=str, 

192 default=None, 

193 ) 

194 atmosphereTableName = pexConfig.Field( 

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

196 dtype=str, 

197 default=None, 

198 optional=True, 

199 ) 

200 parameters = pexConfig.ConfigField( 

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

202 dtype=FgcmMakeLutParametersConfig, 

203 default=None, 

204 check=None) 

205 

206 def validate(self): 

207 """ 

208 Validate the config parameters. 

209 

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

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

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

213 directly from the specified atmosphereTableName. 

214 """ 

215 # check that filterNames and stdFilterNames are okay 

216 self._fields['filterNames'].validate(self) 

217 self._fields['stdFilterNames'].validate(self) 

218 

219 # check if we have an atmosphereTableName, and if valid 

220 if self.atmosphereTableName is not None: 

221 try: 

222 fgcm.FgcmAtmosphereTable.initWithTableName(self.atmosphereTableName) 

223 except IOError: 

224 raise RuntimeError("Could not find atmosphereTableName: %s" % 

225 (self.atmosphereTableName)) 

226 else: 

227 # Validate the parameters 

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

229 

230 

231class FgcmMakeLutRunner(pipeBase.ButlerInitializedTaskRunner): 

232 """Subclass of TaskRunner for fgcmMakeLutTask 

233 

234 fgcmMakeLutTask.run() takes one argument, the butler, and 

235 does not run on any data in the repository. 

236 This runner does not use any parallelization. 

237 """ 

238 

239 @staticmethod 

240 def getTargetList(parsedCmd): 

241 """ 

242 Return a list with one element, the butler. 

243 """ 

244 return [parsedCmd.butler] 

245 

246 def __call__(self, butler): 

247 """ 

248 Parameters 

249 ---------- 

250 butler: `lsst.daf.persistence.Butler` 

251 

252 Returns 

253 ------- 

254 exitStatus: `list` with `pipeBase.Struct` 

255 exitStatus (0: success; 1: failure) 

256 """ 

257 task = self.TaskClass(config=self.config, log=self.log) 

258 

259 exitStatus = 0 

260 if self.doRaise: 

261 task.runDataRef(butler) 

262 else: 

263 try: 

264 task.runDataRef(butler) 

265 except Exception as e: 

266 exitStatus = 1 

267 task.log.fatal("Failed: %s" % e) 

268 if not isinstance(e, pipeBase.TaskError): 

269 traceback.print_exc(file=sys.stderr) 

270 

271 task.writeMetadata(butler) 

272 

273 # The task does not return any results: 

274 return [pipeBase.Struct(exitStatus=exitStatus)] 

275 

276 def run(self, parsedCmd): 

277 """ 

278 Run the task, with no multiprocessing 

279 

280 Parameters 

281 ---------- 

282 parsedCmd: ArgumentParser parsed command line 

283 """ 

284 

285 resultList = [] 

286 

287 if self.precall(parsedCmd): 

288 targetList = self.getTargetList(parsedCmd) 

289 # make sure that we only get 1 

290 resultList = self(targetList[0]) 

291 

292 return resultList 

293 

294 

295class FgcmMakeLutTask(pipeBase.CmdLineTask): 

296 """ 

297 Make Look-Up Table for FGCM. 

298 

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

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

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

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

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

304 

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

306 atmosphere table packaged with fgcm. 

307 """ 

308 

309 ConfigClass = FgcmMakeLutConfig 

310 RunnerClass = FgcmMakeLutRunner 

311 _DefaultName = "fgcmMakeLut" 

312 

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

314 """ 

315 Instantiate an fgcmMakeLutTask. 

316 

317 Parameters 

318 ---------- 

319 butler: `lsst.daf.persistence.Butler` 

320 """ 

321 

322 pipeBase.CmdLineTask.__init__(self, **kwargs) 

323 

324 # no saving of metadata for now 

325 def _getMetadataName(self): 

326 return None 

327 

328 @pipeBase.timeMethod 

329 def runDataRef(self, butler): 

330 """ 

331 Make a Look-Up Table for FGCM 

332 

333 Parameters 

334 ---------- 

335 butler: `lsst.daf.persistence.Butler` 

336 """ 

337 

338 self._fgcmMakeLut(butler) 

339 

340 def _fgcmMakeLut(self, butler): 

341 """ 

342 Make a FGCM Look-up Table 

343 

344 Parameters 

345 ---------- 

346 butler: `lsst.daf.persistence.Butler` 

347 """ 

348 

349 # need the camera for the detectors 

350 camera = butler.get('camera') 

351 

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

353 nCcd = len(camera) 

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

355 

356 # Load in optics, etc. 

357 self._loadThroughputs(butler, camera) 

358 

359 lutConfig = self._createLutConfig(nCcd) 

360 

361 # make the lut object 

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

363 self.fgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig) 

364 

365 # generate the throughput dictionary. 

366 

367 # these will be in Angstroms 

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

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

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

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

372 self.fgcmLutMaker.lambdaStep*10.) 

373 

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

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

376 throughputLambda[1]-throughputLambda[0])) 

377 

378 throughputDict = {} 

379 for i, filterName in enumerate(self.config.filterNames): 

380 tDict = {} 

381 tDict['LAMBDA'] = throughputLambda 

382 for ccdIndex, detector in enumerate(camera): 

383 tDict[ccdIndex] = self._getThroughputDetector(detector, filterName, throughputLambda) 

384 throughputDict[filterName] = tDict 

385 

386 # set the throughputs 

387 self.fgcmLutMaker.setThroughputs(throughputDict) 

388 

389 # make the LUT 

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

391 self.fgcmLutMaker.makeLUT() 

392 

393 # and save the LUT 

394 

395 # build the index values 

396 comma = ',' 

397 filterNameString = comma.join(self.config.filterNames) 

398 stdFilterNameString = comma.join(self.config.stdFilterNames) 

399 

400 atmosphereTableName = 'NoTableWasUsed' 

401 if self.config.atmosphereTableName is not None: 

402 atmosphereTableName = self.config.atmosphereTableName 

403 

404 lutSchema = self._makeLutSchema(filterNameString, stdFilterNameString, 

405 atmosphereTableName) 

406 

407 lutCat = self._makeLutCat(lutSchema, filterNameString, 

408 stdFilterNameString, atmosphereTableName) 

409 butler.put(lutCat, 'fgcmLookUpTable') 

410 

411 def _createLutConfig(self, nCcd): 

412 """ 

413 Create the fgcmLut config dictionary 

414 

415 Parameters 

416 ---------- 

417 nCcd: `int` 

418 Number of CCDs in the camera 

419 """ 

420 

421 # create the common stub of the lutConfig 

422 lutConfig = {} 

423 lutConfig['logger'] = self.log 

424 lutConfig['filterNames'] = self.config.filterNames 

425 lutConfig['stdFilterNames'] = self.config.stdFilterNames 

426 lutConfig['nCCD'] = nCcd 

427 

428 # atmosphereTable already validated if available 

429 if self.config.atmosphereTableName is not None: 

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

431 else: 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

455 

456 return lutConfig 

457 

458 def _loadThroughputs(self, butler, camera): 

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

460 

461 Parameters 

462 ---------- 

463 butler: `lsst.daf.persistence.butler.Butler` 

464 A butler with the transmission info 

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

466 """ 

467 

468 self._opticsTransmission = butler.get('transmission_optics') 

469 self._sensorsTransmission = {} 

470 for detector in camera: 

471 self._sensorsTransmission[detector.getId()] = butler.get('transmission_sensor', 

472 dataId={'ccd': detector.getId()}) 

473 self._filtersTransmission = {} 

474 for filterName in self.config.filterNames: 

475 f = Filter(filterName) 

476 foundTrans = False 

477 # Get all possible aliases, and also try the short filterName 

478 aliases = f.getAliases() 

479 aliases.extend(filterName) 

480 for alias in f.getAliases(): 

481 try: 

482 self._filtersTransmission[filterName] = butler.get('transmission_filter', 

483 dataId={'filter': alias}) 

484 foundTrans = True 

485 break 

486 except NoResults: 

487 pass 

488 if not foundTrans: 

489 raise ValueError("Could not find transmission for filter %s via any alias." % (filterName)) 

490 

491 def _getThroughputDetector(self, detector, filterName, throughputLambda): 

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

493 

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

495 

496 Parameters 

497 ---------- 

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

499 Detector on camera 

500 filterName: `str` 

501 Short name for filter 

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

503 Wavelength steps (Angstrom) 

504 

505 Returns 

506 ------- 

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

508 Throughput (max 1.0) at throughputLambda 

509 """ 

510 

511 c = detector.getCenter(afwCameraGeom.FOCAL_PLANE) 

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

513 

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

515 wavelengths=throughputLambda) 

516 

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

518 wavelengths=throughputLambda) 

519 

520 throughput *= self._filtersTransmission[filterName].sampleAt(position=c, 

521 wavelengths=throughputLambda) 

522 

523 # Clip the throughput from 0 to 1 

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

525 

526 return throughput 

527 

528 def _makeLutSchema(self, filterNameString, stdFilterNameString, 

529 atmosphereTableName): 

530 """ 

531 Make the LUT schema 

532 

533 Parameters 

534 ---------- 

535 filterNameString: `str` 

536 Combined string of all the filterNames 

537 stdFilterNameString: `str` 

538 Combined string of all the standard filterNames 

539 atmosphereTableName: `str` 

540 Name of the atmosphere table used to generate LUT 

541 

542 Returns 

543 ------- 

544 lutSchema: `afwTable.schema` 

545 """ 

546 

547 lutSchema = afwTable.Schema() 

548 

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

550 size=len(atmosphereTableName)) 

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

552 lutSchema.addField('filterNames', type=str, doc='filterNames in LUT', 

553 size=len(filterNameString)) 

554 lutSchema.addField('stdFilterNames', type=str, doc='Standard filterNames in LUT', 

555 size=len(stdFilterNameString)) 

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

557 size=self.fgcmLutMaker.pmb.size) 

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

559 size=self.fgcmLutMaker.pmb.size) 

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

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

562 size=self.fgcmLutMaker.pwv.size) 

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

564 size=self.fgcmLutMaker.o3.size) 

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

566 size=self.fgcmLutMaker.tau.size) 

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

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

569 size=self.fgcmLutMaker.alpha.size) 

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

571 size=self.fgcmLutMaker.zenith.size) 

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

573 

574 # and the standard values 

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

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

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

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

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

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

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

582 size=2) 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

599 size=self.fgcmLutMaker.atmLambda.size) 

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

601 size=self.fgcmLutMaker.atmStdTrans.size) 

602 

603 # and the look-up-tables 

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

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

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

607 

608 return lutSchema 

609 

610 def _makeLutCat(self, lutSchema, filterNameString, stdFilterNameString, 

611 atmosphereTableName): 

612 """ 

613 Make the LUT schema 

614 

615 Parameters 

616 ---------- 

617 lutSchema: `afwTable.schema` 

618 Lut catalog schema 

619 filterNameString: `str` 

620 Combined string of all the filterNames 

621 stdFilterNameString: `str` 

622 Combined string of all the standard filterNames 

623 atmosphereTableName: `str` 

624 Name of the atmosphere table used to generate LUT 

625 

626 Returns 

627 ------- 

628 lutCat: `afwTable.BaseCatalog` 

629 Lut catalog for persistence 

630 """ 

631 

632 # The somewhat strange format is to make sure that 

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

634 # (see DM-11419) 

635 

636 lutCat = afwTable.BaseCatalog(lutSchema) 

637 lutCat.table.preallocate(14) 

638 

639 # first fill the first index 

640 rec = lutCat.addNew() 

641 

642 rec['tablename'] = atmosphereTableName 

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

644 rec['filterNames'] = filterNameString 

645 rec['stdFilterNames'] = stdFilterNameString 

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

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

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

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

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

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

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

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

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

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

656 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

674 

675 rec['luttype'] = 'I0' 

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

677 

678 # and add the rest 

679 rec = lutCat.addNew() 

680 rec['luttype'] = 'I1' 

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

682 

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

684 'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1', 

685 'D_SECZENITH_I1'] 

686 for derivType in derivTypes: 

687 rec = lutCat.addNew() 

688 rec['luttype'] = derivType 

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

690 

691 return lutCat