Coverage for python/lsst/fgcmcal/fgcmMakeLut.py: 26%
223 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-30 03:36 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-30 03:36 -0700
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.
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.
31Computing a LUT requires running MODTRAN or with a pre-generated
32atmosphere table packaged with fgcm.
33"""
35import numpy as np
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
44import fgcm
46__all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask']
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 )
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 )
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 )
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 )
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 )
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 )
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)
256 def validate(self):
257 """
258 Validate the config parameters.
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)
269 if self.atmosphereTableName is None:
270 # Validate the parameters
271 self._fields['parameters'].validate(self)
274class FgcmMakeLutTask(pipeBase.PipelineTask):
275 """
276 Make Look-Up Table for FGCM.
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.
284 Computing a LUT requires running MODTRAN or with a pre-generated
285 atmosphere table packaged with fgcm.
286 """
288 ConfigClass = FgcmMakeLutConfig
289 _DefaultName = "fgcmMakeLut"
291 def __init__(self, butler=None, initInputs=None, **kwargs):
292 super().__init__(**kwargs)
294 def runQuantum(self, butlerQC, inputRefs, outputRefs):
295 camera = butlerQC.get(inputRefs.camera)
297 opticsHandle = butlerQC.get(inputRefs.transmission_optics)
299 sensorHandles = butlerQC.get(inputRefs.transmission_sensor)
300 sensorHandleDict = {sensorHandle.dataId.byName()['detector']: sensorHandle for
301 sensorHandle in sensorHandles}
303 filterHandles = butlerQC.get(inputRefs.transmission_filter)
304 filterHandleDict = {filterHandle.dataId['physical_filter']: filterHandle for
305 filterHandle in filterHandles}
307 lutCat = self._fgcmMakeLut(camera,
308 opticsHandle,
309 sensorHandleDict,
310 filterHandleDict)
311 butlerQC.put(lutCat, outputRefs.fgcmLookUpTable)
313 def _fgcmMakeLut(self, camera, opticsHandle, sensorHandleDict,
314 filterHandleDict):
315 """
316 Make a FGCM Look-up Table
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.
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))
340 # Load in optics, etc.
341 self._loadThroughputs(camera,
342 opticsHandle,
343 sensorHandleDict,
344 filterHandleDict)
346 lutConfig = self._createLutConfig(nCcd)
348 # make the lut object
349 self.log.info("Making the LUT maker object")
350 self.fgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig)
352 # generate the throughput dictionary.
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.)
361 self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" %
362 (throughputLambda[0], throughputLambda[-1],
363 throughputLambda[1] - throughputLambda[0]))
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
373 # set the throughputs
374 self.fgcmLutMaker.setThroughputs(throughputDict)
376 # make the LUT
377 self.log.info("Making LUT")
378 self.fgcmLutMaker.makeLUT()
380 # and save the LUT
382 # build the index values
383 comma = ','
384 physicalFilterString = comma.join(self.config.physicalFilters)
385 stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList())
387 atmosphereTableName = 'NoTableWasUsed'
388 if self.config.atmosphereTableName is not None:
389 atmosphereTableName = self.config.atmosphereTableName
391 lutSchema = self._makeLutSchema(physicalFilterString, stdPhysicalFilterString,
392 atmosphereTableName)
394 lutCat = self._makeLutCat(lutSchema, physicalFilterString,
395 stdPhysicalFilterString, atmosphereTableName)
396 return lutCat
398 def _getStdPhysicalFilterList(self):
399 """Get the standard physical filter lists from config.physicalFilters
400 and config.stdPhysicalFilterOverrideMap
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]
410 def _createLutConfig(self, nCcd):
411 """
412 Create the fgcmLut config dictionary
414 Parameters
415 ----------
416 nCcd: `int`
417 Number of CCDs in the camera
418 """
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
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
455 return lutConfig
457 def _loadThroughputs(self, camera, opticsHandle, sensorHandleDict, filterHandleDict):
458 """Internal method to load throughput data for filters
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.
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()
480 self._sensorsTransmission = {}
481 for detector in camera:
482 self._sensorsTransmission[detector.getId()] = sensorHandleDict[detector.getId()].get()
484 self._filtersTransmission = {}
485 for physicalFilter in self.config.physicalFilters:
486 self._filtersTransmission[physicalFilter] = filterHandleDict[physicalFilter].get()
488 def _getThroughputDetector(self, detector, physicalFilter, throughputLambda):
489 """Internal method to get throughput for a detector.
491 Returns the throughput at the center of the detector for a given filter.
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)
502 Returns
503 -------
504 throughput: `np.array(dtype=np.float64)`
505 Throughput (max 1.0) at throughputLambda
506 """
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
511 throughput = self._opticsTransmission.sampleAt(position=c,
512 wavelengths=throughputLambda)
514 throughput *= self._sensorsTransmission[detector.getId()].sampleAt(position=c,
515 wavelengths=throughputLambda)
517 throughput *= self._filtersTransmission[physicalFilter].sampleAt(position=c,
518 wavelengths=throughputLambda)
520 # Clip the throughput from 0 to 1
521 throughput = np.clip(throughput, 0.0, 1.0)
523 return throughput
525 def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString,
526 atmosphereTableName):
527 """
528 Make the LUT schema
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
539 Returns
540 -------
541 lutSchema: `afwTable.schema`
542 """
544 lutSchema = afwTable.Schema()
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')
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)
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)
605 return lutSchema
607 def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString,
608 atmosphereTableName):
609 """
610 Make the LUT schema
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
623 Returns
624 -------
625 lutCat: `afwTable.BaseCatalog`
626 Lut catalog for persistence
627 """
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)
633 lutCat = afwTable.BaseCatalog(lutSchema)
634 lutCat.table.preallocate(14)
636 # first fill the first index
637 rec = lutCat.addNew()
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
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
672 rec['luttype'] = 'I0'
673 rec['lut'][:] = self.fgcmLutMaker.lut['I0'].flatten()
675 # and add the rest
676 rec = lutCat.addNew()
677 rec['luttype'] = 'I1'
678 rec['lut'][:] = self.fgcmLutMaker.lut['I1'].flatten()
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()
688 return lutCat