Coverage for python/lsst/fgcmcal/fgcmMakeLut.py: 23%
236 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-04 10:59 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-12-04 10:59 +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.
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 lsst.afw.image import TransmissionCurve
43from .utilities import lookupStaticCalibrations
45import fgcm
47__all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask']
50class FgcmMakeLutConnections(pipeBase.PipelineTaskConnections,
51 dimensions=('instrument',),
52 defaultTemplates={}):
53 camera = connectionTypes.PrerequisiteInput(
54 doc="Camera instrument",
55 name="camera",
56 storageClass="Camera",
57 dimensions=("instrument",),
58 lookupFunction=lookupStaticCalibrations,
59 isCalibration=True,
60 )
62 transmission_optics = connectionTypes.PrerequisiteInput(
63 doc="Optics transmission curve information",
64 name="transmission_optics",
65 storageClass="TransmissionCurve",
66 dimensions=("instrument",),
67 lookupFunction=lookupStaticCalibrations,
68 isCalibration=True,
69 deferLoad=True,
70 )
72 transmission_sensor = connectionTypes.PrerequisiteInput(
73 doc="Sensor transmission curve information",
74 name="transmission_sensor",
75 storageClass="TransmissionCurve",
76 dimensions=("instrument", "detector",),
77 lookupFunction=lookupStaticCalibrations,
78 isCalibration=True,
79 deferLoad=True,
80 multiple=True,
81 )
83 transmission_filter = connectionTypes.PrerequisiteInput(
84 doc="Filter transmission curve information",
85 name="transmission_filter",
86 storageClass="TransmissionCurve",
87 dimensions=("band", "instrument", "physical_filter",),
88 lookupFunction=lookupStaticCalibrations,
89 isCalibration=True,
90 deferLoad=True,
91 multiple=True,
92 )
94 fgcmLookUpTable = connectionTypes.Output(
95 doc=("Atmosphere + instrument look-up-table for FGCM throughput and "
96 "chromatic corrections."),
97 name="fgcmLookUpTable",
98 storageClass="Catalog",
99 dimensions=("instrument",),
100 )
102 fgcmStandardAtmosphere = connectionTypes.Output(
103 doc="Standard atmosphere used for FGCM calibration.",
104 name="fgcm_standard_atmosphere",
105 storageClass="TransmissionCurve",
106 dimensions=("instrument",),
107 )
109 fgcmStandardPassbands = connectionTypes.Output(
110 doc="Standard passbands used for FGCM calibration.",
111 name="fgcm_standard_passband",
112 storageClass="TransmissionCurve",
113 dimensions=("instrument", "physical_filter"),
114 multiple=True,
115 )
118class FgcmMakeLutParametersConfig(pexConfig.Config):
119 """Config for parameters if atmosphereTableName not available"""
120 # TODO: When DM-16511 is done, it will be possible to get the
121 # telescope elevation directly from the camera.
122 elevation = pexConfig.Field(
123 doc="Telescope elevation (m)",
124 dtype=float,
125 default=None,
126 )
127 pmbRange = pexConfig.ListField(
128 doc=("Barometric Pressure range (millibar) "
129 "Recommended range depends on the site."),
130 dtype=float,
131 default=None,
132 )
133 pmbSteps = pexConfig.Field(
134 doc="Barometric Pressure number of steps",
135 dtype=int,
136 default=5,
137 )
138 pwvRange = pexConfig.ListField(
139 doc=("Precipitable Water Vapor range (mm) "
140 "Recommended range depends on the site."),
141 dtype=float,
142 default=None,
143 )
144 pwvSteps = pexConfig.Field(
145 doc="Precipitable Water Vapor number of steps",
146 dtype=int,
147 default=15,
148 )
149 o3Range = pexConfig.ListField(
150 doc="Ozone range (dob)",
151 dtype=float,
152 default=[220.0, 310.0],
153 )
154 o3Steps = pexConfig.Field(
155 doc="Ozone number of steps",
156 dtype=int,
157 default=3,
158 )
159 tauRange = pexConfig.ListField(
160 doc="Aerosol Optical Depth range (unitless)",
161 dtype=float,
162 default=[0.002, 0.35],
163 )
164 tauSteps = pexConfig.Field(
165 doc="Aerosol Optical Depth number of steps",
166 dtype=int,
167 default=11,
168 )
169 alphaRange = pexConfig.ListField(
170 doc="Aerosol alpha range (unitless)",
171 dtype=float,
172 default=[0.0, 2.0],
173 )
174 alphaSteps = pexConfig.Field(
175 doc="Aerosol alpha number of steps",
176 dtype=int,
177 default=9,
178 )
179 zenithRange = pexConfig.ListField(
180 doc="Zenith angle range (degree)",
181 dtype=float,
182 default=[0.0, 70.0],
183 )
184 zenithSteps = pexConfig.Field(
185 doc="Zenith angle number of steps",
186 dtype=int,
187 default=21,
188 )
189 # Note that the standard atmosphere parameters depend on the observatory
190 # and elevation, and so these should be set on a per-camera basis.
191 pmbStd = pexConfig.Field(
192 doc=("Standard Atmosphere pressure (millibar); "
193 "Recommended default depends on the site."),
194 dtype=float,
195 default=None,
196 )
197 pwvStd = pexConfig.Field(
198 doc=("Standard Atmosphere PWV (mm); "
199 "Recommended default depends on the site."),
200 dtype=float,
201 default=None,
202 )
203 o3Std = pexConfig.Field(
204 doc="Standard Atmosphere O3 (dob)",
205 dtype=float,
206 default=263.0,
207 )
208 tauStd = pexConfig.Field(
209 doc="Standard Atmosphere aerosol optical depth",
210 dtype=float,
211 default=0.03,
212 )
213 alphaStd = pexConfig.Field(
214 doc="Standard Atmosphere aerosol alpha",
215 dtype=float,
216 default=1.0,
217 )
218 airmassStd = pexConfig.Field(
219 doc=("Standard Atmosphere airmass; "
220 "Recommended default depends on the survey strategy."),
221 dtype=float,
222 default=None,
223 )
224 lambdaNorm = pexConfig.Field(
225 doc="Aerosol Optical Depth normalization wavelength (Angstrom)",
226 dtype=float,
227 default=7750.0,
228 )
229 lambdaStep = pexConfig.Field(
230 doc="Wavelength step for generating atmospheres (nm)",
231 dtype=float,
232 default=0.5,
233 )
234 lambdaRange = pexConfig.ListField(
235 doc="Wavelength range for LUT (Angstrom)",
236 dtype=float,
237 default=[3000.0, 11000.0],
238 )
241class FgcmMakeLutConfig(pipeBase.PipelineTaskConfig,
242 pipelineConnections=FgcmMakeLutConnections):
243 """Config for FgcmMakeLutTask"""
244 physicalFilters = pexConfig.ListField(
245 doc="List of physicalFilter labels to generate look-up table.",
246 dtype=str,
247 default=[],
248 )
249 stdPhysicalFilterOverrideMap = pexConfig.DictField(
250 doc=("Override mapping from physical filter labels to 'standard' physical "
251 "filter labels. The 'standard' physical filter defines the transmission "
252 "curve that the FGCM standard bandpass will be based on. "
253 "Any filter not listed here will be mapped to "
254 "itself (e.g. g->g or HSC-G->HSC-G). Use this override for cross-"
255 "filter calibration such as HSC-R->HSC-R2 and HSC-I->HSC-I2."),
256 keytype=str,
257 itemtype=str,
258 default={},
259 )
260 atmosphereTableName = pexConfig.Field(
261 doc="FGCM name or filename of precomputed atmospheres",
262 dtype=str,
263 default=None,
264 optional=True,
265 )
266 parameters = pexConfig.ConfigField(
267 doc="Atmosphere parameters (required if no atmosphereTableName)",
268 dtype=FgcmMakeLutParametersConfig,
269 default=None,
270 check=None)
272 def validate(self):
273 """
274 Validate the config parameters.
276 This method behaves differently from the parent validate in the case
277 that atmosphereTableName is set. In this case, the config values
278 for standard values, step sizes, and ranges are loaded
279 directly from the specified atmosphereTableName.
280 """
281 # check that filterNames and stdFilterNames are okay
282 self._fields['physicalFilters'].validate(self)
283 self._fields['stdPhysicalFilterOverrideMap'].validate(self)
285 if self.atmosphereTableName is None:
286 # Validate the parameters
287 self._fields['parameters'].validate(self)
290class FgcmMakeLutTask(pipeBase.PipelineTask):
291 """
292 Make Look-Up Table for FGCM.
294 This task computes a look-up-table for the range in expected atmosphere
295 variation and variation in instrumental throughput (as tracked by the
296 transmission_filter products). By pre-computing linearized integrals,
297 the FGCM fit is orders of magnitude faster for stars with a broad range
298 of colors and observing bands, yielding precision at the 1-2 mmag level.
300 Computing a LUT requires running MODTRAN or with a pre-generated
301 atmosphere table packaged with fgcm.
302 """
304 ConfigClass = FgcmMakeLutConfig
305 _DefaultName = "fgcmMakeLut"
307 def __init__(self, butler=None, initInputs=None, **kwargs):
308 super().__init__(**kwargs)
310 def runQuantum(self, butlerQC, inputRefs, outputRefs):
311 camera = butlerQC.get(inputRefs.camera)
313 opticsHandle = butlerQC.get(inputRefs.transmission_optics)
315 sensorHandles = butlerQC.get(inputRefs.transmission_sensor)
316 sensorHandleDict = {sensorHandle.dataId.byName()['detector']: sensorHandle for
317 sensorHandle in sensorHandles}
319 filterHandles = butlerQC.get(inputRefs.transmission_filter)
320 filterHandleDict = {filterHandle.dataId['physical_filter']: filterHandle for
321 filterHandle in filterHandles}
323 struct = self._fgcmMakeLut(camera,
324 opticsHandle,
325 sensorHandleDict,
326 filterHandleDict)
328 butlerQC.put(struct.fgcmLookUpTable, outputRefs.fgcmLookUpTable)
329 butlerQC.put(struct.fgcmStandardAtmosphere, outputRefs.fgcmStandardAtmosphere)
331 refDict = {passbandRef.dataId['physical_filter']: passbandRef for
332 passbandRef in outputRefs.fgcmStandardPassbands}
333 for physical_filter, passband in struct.fgcmStandardPassbands.items():
334 butlerQC.put(passband, refDict[physical_filter])
336 def _fgcmMakeLut(self, camera, opticsHandle, sensorHandleDict,
337 filterHandleDict):
338 """
339 Make a FGCM Look-up Table
341 Parameters
342 ----------
343 camera : `lsst.afw.cameraGeom.Camera`
344 Camera from the butler.
345 opticsHandle : `lsst.daf.butler.DeferredDatasetHandle`
346 Reference to optics transmission curve.
347 sensorHandleDict : `dict` of [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
348 Dictionary of references to sensor transmission curves. Key will
349 be detector id.
350 filterHandleDict : `dict` of [`str`, `lsst.daf.butler.DeferredDatasetHandle`]
351 Dictionary of references to filter transmission curves. Key will
352 be physical filter label.
354 Returns
355 -------
356 retStruct : `lsst.pipe.base.Struct`
357 Output structure with keys:
359 fgcmLookUpTable : `BaseCatalog`
360 The FGCM look-up table.
361 fgcmStandardAtmosphere : `lsst.afw.image.TransmissionCurve`
362 Transmission curve for the FGCM standard atmosphere.
363 fgcmStandardPassbands : `dict` [`str`, `lsst.afw.image.TransmissionCurve`]
364 Dictionary of standard passbands, with the key as the
365 physical filter name.
366 """
367 # number of ccds from the length of the camera iterator
368 nCcd = len(camera)
369 self.log.info("Found %d ccds for look-up table" % (nCcd))
371 # Load in optics, etc.
372 self._loadThroughputs(camera,
373 opticsHandle,
374 sensorHandleDict,
375 filterHandleDict)
377 lutConfig = self._createLutConfig(nCcd)
379 # make the lut object
380 self.log.info("Making the LUT maker object")
381 self.fgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig)
383 # generate the throughput dictionary.
385 # these will be in Angstroms
386 # note that lambdaStep is currently in nm, because of historical
387 # reasons in the code. Convert to Angstroms here.
388 throughputLambda = np.arange(self.fgcmLutMaker.lambdaRange[0],
389 self.fgcmLutMaker.lambdaRange[1]+self.fgcmLutMaker.lambdaStep*10,
390 self.fgcmLutMaker.lambdaStep*10.)
392 self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" %
393 (throughputLambda[0], throughputLambda[-1],
394 throughputLambda[1] - throughputLambda[0]))
396 throughputDict = {}
397 for i, physicalFilter in enumerate(self.config.physicalFilters):
398 tDict = {}
399 tDict['LAMBDA'] = throughputLambda
400 for ccdIndex, detector in enumerate(camera):
401 tDict[ccdIndex] = self._getThroughputDetector(detector, physicalFilter, throughputLambda)
402 throughputDict[physicalFilter] = tDict
404 # set the throughputs
405 self.fgcmLutMaker.setThroughputs(throughputDict)
407 # make the LUT
408 self.log.info("Making LUT")
409 self.fgcmLutMaker.makeLUT()
411 # and save the LUT
413 # build the index values
414 comma = ','
415 physicalFilterString = comma.join(self.config.physicalFilters)
416 stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList())
418 atmosphereTableName = 'NoTableWasUsed'
419 if self.config.atmosphereTableName is not None:
420 atmosphereTableName = self.config.atmosphereTableName
422 lutSchema = self._makeLutSchema(physicalFilterString, stdPhysicalFilterString,
423 atmosphereTableName)
425 lutCat = self._makeLutCat(lutSchema, physicalFilterString,
426 stdPhysicalFilterString, atmosphereTableName)
428 atmStd = TransmissionCurve.makeSpatiallyConstant(
429 throughput=self.fgcmLutMaker.atmStdTrans.astype(np.float64),
430 wavelengths=self.fgcmLutMaker.atmLambda.astype(np.float64),
431 throughputAtMin=self.fgcmLutMaker.atmStdTrans[0],
432 throughputAtMax=self.fgcmLutMaker.atmStdTrans[1],
433 )
435 fgcmStandardPassbands = {}
436 for i, physical_filter in enumerate(self.fgcmLutMaker.filterNames):
437 passband = self.fgcmLutMaker.throughputs[i]['THROUGHPUT_AVG']*self.fgcmLutMaker.atmStdTrans
438 fgcmStandardPassbands[physical_filter] = TransmissionCurve.makeSpatiallyConstant(
439 throughput=passband.astype(np.float64),
440 wavelengths=self.fgcmLutMaker.atmLambda.astype(np.float64),
441 throughputAtMin=passband[0],
442 throughputAtMax=passband[-1],
443 )
445 retStruct = pipeBase.Struct(
446 fgcmLookUpTable=lutCat,
447 fgcmStandardAtmosphere=atmStd,
448 fgcmStandardPassbands=fgcmStandardPassbands,
449 )
451 return retStruct
453 def _getStdPhysicalFilterList(self):
454 """Get the standard physical filter lists from config.physicalFilters
455 and config.stdPhysicalFilterOverrideMap
457 Returns
458 -------
459 stdPhysicalFilters : `list`
460 """
461 override = self.config.stdPhysicalFilterOverrideMap
462 return [override.get(physicalFilter, physicalFilter) for
463 physicalFilter in self.config.physicalFilters]
465 def _createLutConfig(self, nCcd):
466 """
467 Create the fgcmLut config dictionary
469 Parameters
470 ----------
471 nCcd: `int`
472 Number of CCDs in the camera
473 """
475 # create the common stub of the lutConfig
476 lutConfig = {}
477 lutConfig['logger'] = self.log
478 lutConfig['filterNames'] = self.config.physicalFilters
479 lutConfig['stdFilterNames'] = self._getStdPhysicalFilterList()
480 lutConfig['nCCD'] = nCcd
482 # atmosphereTable already validated if available
483 if self.config.atmosphereTableName is not None:
484 lutConfig['atmosphereTableName'] = self.config.atmosphereTableName
485 else:
486 # use the regular paramters (also validated if needed)
487 lutConfig['elevation'] = self.config.parameters.elevation
488 lutConfig['pmbRange'] = self.config.parameters.pmbRange
489 lutConfig['pmbSteps'] = self.config.parameters.pmbSteps
490 lutConfig['pwvRange'] = self.config.parameters.pwvRange
491 lutConfig['pwvSteps'] = self.config.parameters.pwvSteps
492 lutConfig['o3Range'] = self.config.parameters.o3Range
493 lutConfig['o3Steps'] = self.config.parameters.o3Steps
494 lutConfig['tauRange'] = self.config.parameters.tauRange
495 lutConfig['tauSteps'] = self.config.parameters.tauSteps
496 lutConfig['alphaRange'] = self.config.parameters.alphaRange
497 lutConfig['alphaSteps'] = self.config.parameters.alphaSteps
498 lutConfig['zenithRange'] = self.config.parameters.zenithRange
499 lutConfig['zenithSteps'] = self.config.parameters.zenithSteps
500 lutConfig['pmbStd'] = self.config.parameters.pmbStd
501 lutConfig['pwvStd'] = self.config.parameters.pwvStd
502 lutConfig['o3Std'] = self.config.parameters.o3Std
503 lutConfig['tauStd'] = self.config.parameters.tauStd
504 lutConfig['alphaStd'] = self.config.parameters.alphaStd
505 lutConfig['airmassStd'] = self.config.parameters.airmassStd
506 lutConfig['lambdaRange'] = self.config.parameters.lambdaRange
507 lutConfig['lambdaStep'] = self.config.parameters.lambdaStep
508 lutConfig['lambdaNorm'] = self.config.parameters.lambdaNorm
510 return lutConfig
512 def _loadThroughputs(self, camera, opticsHandle, sensorHandleDict, filterHandleDict):
513 """Internal method to load throughput data for filters
515 Parameters
516 ----------
517 camera: `lsst.afw.cameraGeom.Camera`
518 Camera from the butler
519 opticsHandle : `lsst.daf.butler.DeferredDatasetHandle`
520 Reference to optics transmission curve.
521 sensorHandleDict : `dict` of [`int`, `lsst.daf.butler.DeferredDatasetHandle`]
522 Dictionary of references to sensor transmission curves. Key will
523 be detector id.
524 filterHandleDict : `dict` of [`str`, `lsst.daf.butler.DeferredDatasetHandle`]
525 Dictionary of references to filter transmission curves. Key will
526 be physical filter label.
528 Raises
529 ------
530 ValueError : Raised if configured filter name does not match any of the
531 available filter transmission curves.
532 """
533 self._opticsTransmission = opticsHandle.get()
535 self._sensorsTransmission = {}
536 for detector in camera:
537 self._sensorsTransmission[detector.getId()] = sensorHandleDict[detector.getId()].get()
539 self._filtersTransmission = {}
540 for physicalFilter in self.config.physicalFilters:
541 self._filtersTransmission[physicalFilter] = filterHandleDict[physicalFilter].get()
543 def _getThroughputDetector(self, detector, physicalFilter, throughputLambda):
544 """Internal method to get throughput for a detector.
546 Returns the throughput at the center of the detector for a given filter.
548 Parameters
549 ----------
550 detector: `lsst.afw.cameraGeom._detector.Detector`
551 Detector on camera
552 physicalFilter: `str`
553 Physical filter label
554 throughputLambda: `np.array(dtype=np.float64)`
555 Wavelength steps (Angstrom)
557 Returns
558 -------
559 throughput: `np.array(dtype=np.float64)`
560 Throughput (max 1.0) at throughputLambda
561 """
563 c = detector.getCenter(afwCameraGeom.FOCAL_PLANE)
564 c.scale(1.0/detector.getPixelSize()[0]) # Assumes x and y pixel sizes in arcsec are the same
566 throughput = self._opticsTransmission.sampleAt(position=c,
567 wavelengths=throughputLambda)
569 throughput *= self._sensorsTransmission[detector.getId()].sampleAt(position=c,
570 wavelengths=throughputLambda)
572 throughput *= self._filtersTransmission[physicalFilter].sampleAt(position=c,
573 wavelengths=throughputLambda)
575 # Clip the throughput from 0 to 1
576 throughput = np.clip(throughput, 0.0, 1.0)
578 return throughput
580 def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString,
581 atmosphereTableName):
582 """
583 Make the LUT schema
585 Parameters
586 ----------
587 physicalFilterString: `str`
588 Combined string of all the physicalFilters
589 stdPhysicalFilterString: `str`
590 Combined string of all the standard physicalFilters
591 atmosphereTableName: `str`
592 Name of the atmosphere table used to generate LUT
594 Returns
595 -------
596 lutSchema: `afwTable.schema`
597 """
599 lutSchema = afwTable.Schema()
601 lutSchema.addField('tablename', type=str, doc='Atmosphere table name',
602 size=len(atmosphereTableName))
603 lutSchema.addField('elevation', type=float, doc="Telescope elevation used for LUT")
604 lutSchema.addField('physicalFilters', type=str, doc='physicalFilters in LUT',
605 size=len(physicalFilterString))
606 lutSchema.addField('stdPhysicalFilters', type=str, doc='Standard physicalFilters in LUT',
607 size=len(stdPhysicalFilterString))
608 lutSchema.addField('pmb', type='ArrayD', doc='Barometric Pressure',
609 size=self.fgcmLutMaker.pmb.size)
610 lutSchema.addField('pmbFactor', type='ArrayD', doc='PMB scaling factor',
611 size=self.fgcmLutMaker.pmb.size)
612 lutSchema.addField('pmbElevation', type=np.float64, doc='PMB Scaling at elevation')
613 lutSchema.addField('pwv', type='ArrayD', doc='Preciptable Water Vapor',
614 size=self.fgcmLutMaker.pwv.size)
615 lutSchema.addField('o3', type='ArrayD', doc='Ozone',
616 size=self.fgcmLutMaker.o3.size)
617 lutSchema.addField('tau', type='ArrayD', doc='Aerosol optical depth',
618 size=self.fgcmLutMaker.tau.size)
619 lutSchema.addField('lambdaNorm', type=np.float64, doc='AOD wavelength')
620 lutSchema.addField('alpha', type='ArrayD', doc='Aerosol alpha',
621 size=self.fgcmLutMaker.alpha.size)
622 lutSchema.addField('zenith', type='ArrayD', doc='Zenith angle',
623 size=self.fgcmLutMaker.zenith.size)
624 lutSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
626 # and the standard values
627 lutSchema.addField('pmbStd', type=np.float64, doc='PMB Standard')
628 lutSchema.addField('pwvStd', type=np.float64, doc='PWV Standard')
629 lutSchema.addField('o3Std', type=np.float64, doc='O3 Standard')
630 lutSchema.addField('tauStd', type=np.float64, doc='Tau Standard')
631 lutSchema.addField('alphaStd', type=np.float64, doc='Alpha Standard')
632 lutSchema.addField('zenithStd', type=np.float64, doc='Zenith angle Standard')
633 lutSchema.addField('lambdaRange', type='ArrayD', doc='Wavelength range',
634 size=2)
635 lutSchema.addField('lambdaStep', type=np.float64, doc='Wavelength step')
636 lutSchema.addField('lambdaStd', type='ArrayD', doc='Standard Wavelength',
637 size=len(self.fgcmLutMaker.filterNames))
638 lutSchema.addField('lambdaStdFilter', type='ArrayD', doc='Standard Wavelength (raw)',
639 size=len(self.fgcmLutMaker.filterNames))
640 lutSchema.addField('i0Std', type='ArrayD', doc='I0 Standard',
641 size=len(self.fgcmLutMaker.filterNames))
642 lutSchema.addField('i1Std', type='ArrayD', doc='I1 Standard',
643 size=len(self.fgcmLutMaker.filterNames))
644 lutSchema.addField('i10Std', type='ArrayD', doc='I10 Standard',
645 size=len(self.fgcmLutMaker.filterNames))
646 lutSchema.addField('i2Std', type='ArrayD', doc='I2 Standard',
647 size=len(self.fgcmLutMaker.filterNames))
648 lutSchema.addField('lambdaB', type='ArrayD', doc='Wavelength for passband (no atm)',
649 size=len(self.fgcmLutMaker.filterNames))
650 lutSchema.addField('atmLambda', type='ArrayD', doc='Atmosphere wavelengths (Angstrom)',
651 size=self.fgcmLutMaker.atmLambda.size)
652 lutSchema.addField('atmStdTrans', type='ArrayD', doc='Standard Atmosphere Throughput',
653 size=self.fgcmLutMaker.atmStdTrans.size)
655 # and the look-up-tables
656 lutSchema.addField('luttype', type=str, size=20, doc='Look-up table type')
657 lutSchema.addField('lut', type='ArrayF', doc='Look-up table for luttype',
658 size=self.fgcmLutMaker.lut['I0'].size)
660 return lutSchema
662 def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString,
663 atmosphereTableName):
664 """
665 Make the LUT schema
667 Parameters
668 ----------
669 lutSchema: `afwTable.schema`
670 Lut catalog schema
671 physicalFilterString: `str`
672 Combined string of all the physicalFilters
673 stdPhysicalFilterString: `str`
674 Combined string of all the standard physicalFilters
675 atmosphereTableName: `str`
676 Name of the atmosphere table used to generate LUT
678 Returns
679 -------
680 lutCat: `afwTable.BaseCatalog`
681 Look-up table catalog for persistence.
682 """
684 # The somewhat strange format is to make sure that
685 # the rows of the afwTable do not get too large
686 # (see DM-11419)
688 lutCat = afwTable.BaseCatalog(lutSchema)
689 lutCat.table.preallocate(14)
691 # first fill the first index
692 rec = lutCat.addNew()
694 rec['tablename'] = atmosphereTableName
695 rec['elevation'] = self.fgcmLutMaker.atmosphereTable.elevation
696 rec['physicalFilters'] = physicalFilterString
697 rec['stdPhysicalFilters'] = stdPhysicalFilterString
698 rec['pmb'][:] = self.fgcmLutMaker.pmb
699 rec['pmbFactor'][:] = self.fgcmLutMaker.pmbFactor
700 rec['pmbElevation'] = self.fgcmLutMaker.pmbElevation
701 rec['pwv'][:] = self.fgcmLutMaker.pwv
702 rec['o3'][:] = self.fgcmLutMaker.o3
703 rec['tau'][:] = self.fgcmLutMaker.tau
704 rec['lambdaNorm'] = self.fgcmLutMaker.lambdaNorm
705 rec['alpha'][:] = self.fgcmLutMaker.alpha
706 rec['zenith'][:] = self.fgcmLutMaker.zenith
707 rec['nCcd'] = self.fgcmLutMaker.nCCD
709 rec['pmbStd'] = self.fgcmLutMaker.pmbStd
710 rec['pwvStd'] = self.fgcmLutMaker.pwvStd
711 rec['o3Std'] = self.fgcmLutMaker.o3Std
712 rec['tauStd'] = self.fgcmLutMaker.tauStd
713 rec['alphaStd'] = self.fgcmLutMaker.alphaStd
714 rec['zenithStd'] = self.fgcmLutMaker.zenithStd
715 rec['lambdaRange'][:] = self.fgcmLutMaker.lambdaRange
716 rec['lambdaStep'] = self.fgcmLutMaker.lambdaStep
717 rec['lambdaStd'][:] = self.fgcmLutMaker.lambdaStd
718 rec['lambdaStdFilter'][:] = self.fgcmLutMaker.lambdaStdFilter
719 rec['i0Std'][:] = self.fgcmLutMaker.I0Std
720 rec['i1Std'][:] = self.fgcmLutMaker.I1Std
721 rec['i10Std'][:] = self.fgcmLutMaker.I10Std
722 rec['i2Std'][:] = self.fgcmLutMaker.I2Std
723 rec['lambdaB'][:] = self.fgcmLutMaker.lambdaB
724 rec['atmLambda'][:] = self.fgcmLutMaker.atmLambda
725 rec['atmStdTrans'][:] = self.fgcmLutMaker.atmStdTrans
727 rec['luttype'] = 'I0'
728 rec['lut'][:] = self.fgcmLutMaker.lut['I0'].flatten()
730 # and add the rest
731 rec = lutCat.addNew()
732 rec['luttype'] = 'I1'
733 rec['lut'][:] = self.fgcmLutMaker.lut['I1'].flatten()
735 derivTypes = ['D_PMB', 'D_LNPWV', 'D_O3', 'D_LNTAU', 'D_ALPHA', 'D_SECZENITH',
736 'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1',
737 'D_SECZENITH_I1']
738 for derivType in derivTypes:
739 rec = lutCat.addNew()
740 rec['luttype'] = derivType
741 rec['lut'][:] = self.fgcmLutMaker.lutDeriv[derivType].flatten()
743 return lutCat