lsst.fgcmcal g34bb90edf1+d0506499b5
fgcmMakeLut.py
Go to the documentation of this file.
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
42from lsst.pipe.base import connectionTypes
43import lsst.afw.table as afwTable
44import lsst.afw.cameraGeom as afwCameraGeom
45from lsst.utils.timer import timeMethod
46from .utilities import lookupStaticCalibrations
47
48import fgcm
49
50__all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask',
51 'FgcmMakeLutRunner']
52
53
54class FgcmMakeLutConnections(pipeBase.PipelineTaskConnections,
55 dimensions=('instrument',),
56 defaultTemplates={}):
57 camera = connectionTypes.PrerequisiteInput(
58 doc="Camera instrument",
59 name="camera",
60 storageClass="Camera",
61 dimensions=("instrument",),
62 lookupFunction=lookupStaticCalibrations,
63 isCalibration=True,
64 )
65
66 transmission_optics = connectionTypes.PrerequisiteInput(
67 doc="Optics transmission curve information",
68 name="transmission_optics",
69 storageClass="TransmissionCurve",
70 dimensions=("instrument",),
71 lookupFunction=lookupStaticCalibrations,
72 isCalibration=True,
73 deferLoad=True,
74 )
75
76 transmission_sensor = connectionTypes.PrerequisiteInput(
77 doc="Sensor transmission curve information",
78 name="transmission_sensor",
79 storageClass="TransmissionCurve",
80 dimensions=("instrument", "detector",),
81 lookupFunction=lookupStaticCalibrations,
82 isCalibration=True,
83 deferLoad=True,
84 multiple=True,
85 )
86
87 transmission_filter = connectionTypes.PrerequisiteInput(
88 doc="Filter transmission curve information",
89 name="transmission_filter",
90 storageClass="TransmissionCurve",
91 dimensions=("band", "instrument", "physical_filter",),
92 lookupFunction=lookupStaticCalibrations,
93 isCalibration=True,
94 deferLoad=True,
95 multiple=True,
96 )
97
98 fgcmLookUpTable = connectionTypes.Output(
99 doc=("Atmosphere + instrument look-up-table for FGCM throughput and "
100 "chromatic corrections."),
101 name="fgcmLookUpTable",
102 storageClass="Catalog",
103 dimensions=("instrument",),
104 )
105
106
107class FgcmMakeLutParametersConfig(pexConfig.Config):
108 """Config for parameters if atmosphereTableName not available"""
109 # TODO: When DM-16511 is done, it will be possible to get the
110 # telescope elevation directly from the camera.
111 elevation = pexConfig.Field(
112 doc="Telescope elevation (m)",
113 dtype=float,
114 default=None,
115 )
116 pmbRange = pexConfig.ListField(
117 doc=("Barometric Pressure range (millibar) "
118 "Recommended range depends on the site."),
119 dtype=float,
120 default=None,
121 )
122 pmbSteps = pexConfig.Field(
123 doc="Barometric Pressure number of steps",
124 dtype=int,
125 default=5,
126 )
127 pwvRange = pexConfig.ListField(
128 doc=("Precipitable Water Vapor range (mm) "
129 "Recommended range depends on the site."),
130 dtype=float,
131 default=None,
132 )
133 pwvSteps = pexConfig.Field(
134 doc="Precipitable Water Vapor number of steps",
135 dtype=int,
136 default=15,
137 )
138 o3Range = pexConfig.ListField(
139 doc="Ozone range (dob)",
140 dtype=float,
141 default=[220.0, 310.0],
142 )
143 o3Steps = pexConfig.Field(
144 doc="Ozone number of steps",
145 dtype=int,
146 default=3,
147 )
148 tauRange = pexConfig.ListField(
149 doc="Aerosol Optical Depth range (unitless)",
150 dtype=float,
151 default=[0.002, 0.35],
152 )
153 tauSteps = pexConfig.Field(
154 doc="Aerosol Optical Depth number of steps",
155 dtype=int,
156 default=11,
157 )
158 alphaRange = pexConfig.ListField(
159 doc="Aerosol alpha range (unitless)",
160 dtype=float,
161 default=[0.0, 2.0],
162 )
163 alphaSteps = pexConfig.Field(
164 doc="Aerosol alpha number of steps",
165 dtype=int,
166 default=9,
167 )
168 zenithRange = pexConfig.ListField(
169 doc="Zenith angle range (degree)",
170 dtype=float,
171 default=[0.0, 70.0],
172 )
173 zenithSteps = pexConfig.Field(
174 doc="Zenith angle number of steps",
175 dtype=int,
176 default=21,
177 )
178 # Note that the standard atmosphere parameters depend on the observatory
179 # and elevation, and so these should be set on a per-camera basis.
180 pmbStd = pexConfig.Field(
181 doc=("Standard Atmosphere pressure (millibar); "
182 "Recommended default depends on the site."),
183 dtype=float,
184 default=None,
185 )
186 pwvStd = pexConfig.Field(
187 doc=("Standard Atmosphere PWV (mm); "
188 "Recommended default depends on the site."),
189 dtype=float,
190 default=None,
191 )
192 o3Std = pexConfig.Field(
193 doc="Standard Atmosphere O3 (dob)",
194 dtype=float,
195 default=263.0,
196 )
197 tauStd = pexConfig.Field(
198 doc="Standard Atmosphere aerosol optical depth",
199 dtype=float,
200 default=0.03,
201 )
202 alphaStd = pexConfig.Field(
203 doc="Standard Atmosphere aerosol alpha",
204 dtype=float,
205 default=1.0,
206 )
207 airmassStd = pexConfig.Field(
208 doc=("Standard Atmosphere airmass; "
209 "Recommended default depends on the survey strategy."),
210 dtype=float,
211 default=None,
212 )
213 lambdaNorm = pexConfig.Field(
214 doc="Aerosol Optical Depth normalization wavelength (Angstrom)",
215 dtype=float,
216 default=7750.0,
217 )
218 lambdaStep = pexConfig.Field(
219 doc="Wavelength step for generating atmospheres (nm)",
220 dtype=float,
221 default=0.5,
222 )
223 lambdaRange = pexConfig.ListField(
224 doc="Wavelength range for LUT (Angstrom)",
225 dtype=float,
226 default=[3000.0, 11000.0],
227 )
228
229
230class FgcmMakeLutConfig(pipeBase.PipelineTaskConfig,
231 pipelineConnections=FgcmMakeLutConnections):
232 """Config for FgcmMakeLutTask"""
233 physicalFilters = pexConfig.ListField(
234 doc="List of physicalFilter labels to generate look-up table.",
235 dtype=str,
236 default=[],
237 )
238 stdPhysicalFilterOverrideMap = pexConfig.DictField(
239 doc=("Override mapping from physical filter labels to 'standard' physical "
240 "filter labels. The 'standard' physical filter defines the transmission "
241 "curve that the FGCM standard bandpass will be based on. "
242 "Any filter not listed here will be mapped to "
243 "itself (e.g. g->g or HSC-G->HSC-G). Use this override for cross-"
244 "filter calibration such as HSC-R->HSC-R2 and HSC-I->HSC-I2."),
245 keytype=str,
246 itemtype=str,
247 default={},
248 )
249 atmosphereTableName = pexConfig.Field(
250 doc="FGCM name or filename of precomputed atmospheres",
251 dtype=str,
252 default=None,
253 optional=True,
254 )
255 parameters = pexConfig.ConfigField(
256 doc="Atmosphere parameters (required if no atmosphereTableName)",
257 dtype=FgcmMakeLutParametersConfig,
258 default=None,
259 check=None)
260
261 def validate(self):
262 """
263 Validate the config parameters.
264
265 This method behaves differently from the parent validate in the case
266 that atmosphereTableName is set. In this case, the config values
267 for standard values, step sizes, and ranges are loaded
268 directly from the specified atmosphereTableName.
269 """
270 # check that filterNames and stdFilterNames are okay
271 self._fields['physicalFilters'].validate(self)
272 self._fields['stdPhysicalFilterOverrideMap'].validate(self)
273
274 if self.atmosphereTableNameatmosphereTableName is None:
275 # Validate the parameters
276 self._fields['parameters'].validate(self)
277
278
279class FgcmMakeLutRunner(pipeBase.ButlerInitializedTaskRunner):
280 """Subclass of TaskRunner for fgcmMakeLutTask
281
282 fgcmMakeLutTask.run() takes one argument, the butler, and
283 does not run on any data in the repository.
284 This runner does not use any parallelization.
285 """
286
287 @staticmethod
288 def getTargetList(parsedCmd):
289 """
290 Return a list with one element, the butler.
291 """
292 return [parsedCmd.butler]
293
294 def __call__(self, butler):
295 """
296 Parameters
297 ----------
298 butler: `lsst.daf.persistence.Butler`
299
300 Returns
301 -------
302 exitStatus: `list` with `pipeBase.Struct`
303 exitStatus (0: success; 1: failure)
304 """
305 task = self.TaskClass(config=self.config, log=self.log)
306
307 exitStatus = 0
308 if self.doRaise:
309 task.runDataRef(butler)
310 else:
311 try:
312 task.runDataRef(butler)
313 except Exception as e:
314 exitStatus = 1
315 task.log.fatal("Failed: %s" % e)
316 if not isinstance(e, pipeBase.TaskError):
317 traceback.print_exc(file=sys.stderr)
318
319 task.writeMetadata(butler)
320
321 # The task does not return any results:
322 return [pipeBase.Struct(exitStatus=exitStatus)]
323
324 def run(self, parsedCmd):
325 """
326 Run the task, with no multiprocessing
327
328 Parameters
329 ----------
330 parsedCmd: ArgumentParser parsed command line
331 """
332
333 resultList = []
334
335 if self.precall(parsedCmd):
336 targetList = self.getTargetListgetTargetList(parsedCmd)
337 # make sure that we only get 1
338 resultList = self(targetList[0])
339
340 return resultList
341
342
343class FgcmMakeLutTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
344 """
345 Make Look-Up Table for FGCM.
346
347 This task computes a look-up-table for the range in expected atmosphere
348 variation and variation in instrumental throughput (as tracked by the
349 transmission_filter products). By pre-computing linearized integrals,
350 the FGCM fit is orders of magnitude faster for stars with a broad range
351 of colors and observing bands, yielding precision at the 1-2 mmag level.
352
353 Computing a LUT requires running MODTRAN or with a pre-generated
354 atmosphere table packaged with fgcm.
355 """
356
357 ConfigClass = FgcmMakeLutConfig
358 RunnerClass = FgcmMakeLutRunner
359 _DefaultName = "fgcmMakeLut"
360
361 def __init__(self, butler=None, initInputs=None, **kwargs):
362 super().__init__(**kwargs)
363
364 # no saving of metadata for now
365 def _getMetadataName(self):
366 return None
367
368 @timeMethod
369 def runDataRef(self, butler):
370 """
371 Make a Look-Up Table for FGCM
372
373 Parameters
374 ----------
375 butler: `lsst.daf.persistence.Butler`
376
377 Raises
378 ------
379 ValueError : Raised if configured filter name does not match any of the
380 available filter transmission curves.
381 """
382 camera = butler.get('camera')
383 opticsDataRef = butler.dataRef('transmission_optics')
384
385 sensorDataRefDict = {}
386 for detector in camera:
387 sensorDataRefDict[detector.getId()] = butler.dataRef('transmission_sensor',
388 dataId={'ccd': detector.getId()})
389
390 filterDataRefDict = {}
391 for physicalFilter in self.config.physicalFilters:
392 # The physical filters map directly to dataId filter names
393 # for gen2 HSC. This is the only camera that will be supported
394 # by Gen2 fgcmcal, so we do not need to worry about other cases.
395 dataRef = butler.dataRef('transmission_filter', filter=physicalFilter)
396 if not dataRef.datasetExists():
397 raise ValueError(f"Could not find transmission for filter {physicalFilter}.")
398 filterDataRefDict[physicalFilter] = dataRef
399
400 lutCat = self._fgcmMakeLut_fgcmMakeLut(camera,
401 opticsDataRef,
402 sensorDataRefDict,
403 filterDataRefDict)
404 butler.put(lutCat, 'fgcmLookUpTable')
405
406 def runQuantum(self, butlerQC, inputRefs, outputRefs):
407 camera = butlerQC.get(inputRefs.camera)
408
409 opticsDataRef = butlerQC.get(inputRefs.transmission_optics)
410
411 sensorRefs = butlerQC.get(inputRefs.transmission_sensor)
412 sensorDataRefDict = {sensorRef.dataId.byName()['detector']: sensorRef for
413 sensorRef in sensorRefs}
414
415 filterRefs = butlerQC.get(inputRefs.transmission_filter)
416 filterDataRefDict = {filterRef.dataId['physical_filter']: filterRef for
417 filterRef in filterRefs}
418
419 lutCat = self._fgcmMakeLut_fgcmMakeLut(camera,
420 opticsDataRef,
421 sensorDataRefDict,
422 filterDataRefDict)
423 butlerQC.put(lutCat, outputRefs.fgcmLookUpTable)
424
425 def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict,
426 filterDataRefDict):
427 """
428 Make a FGCM Look-up Table
429
430 Parameters
431 ----------
432 camera : `lsst.afw.cameraGeom.Camera`
433 Camera from the butler.
434 opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
435 `lsst.daf.butler.DeferredDatasetHandle`
436 Reference to optics transmission curve.
437 sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
438 `lsst.daf.butler.DeferredDatasetHandle`]
439 Dictionary of references to sensor transmission curves. Key will
440 be detector id.
441 filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
442 `lsst.daf.butler.DeferredDatasetHandle`]
443 Dictionary of references to filter transmission curves. Key will
444 be physical filter label.
445
446 Returns
447 -------
448 fgcmLookUpTable : `BaseCatalog`
449 The FGCM look-up table.
450 """
451 # number of ccds from the length of the camera iterator
452 nCcd = len(camera)
453 self.log.info("Found %d ccds for look-up table" % (nCcd))
454
455 # Load in optics, etc.
456 self._loadThroughputs_loadThroughputs(camera,
457 opticsDataRef,
458 sensorDataRefDict,
459 filterDataRefDict)
460
461 lutConfig = self._createLutConfig_createLutConfig(nCcd)
462
463 # make the lut object
464 self.log.info("Making the LUT maker object")
465 self.fgcmLutMakerfgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig)
466
467 # generate the throughput dictionary.
468
469 # these will be in Angstroms
470 # note that lambdaStep is currently in nm, because of historical
471 # reasons in the code. Convert to Angstroms here.
472 throughputLambda = np.arange(self.fgcmLutMakerfgcmLutMaker.lambdaRange[0],
473 self.fgcmLutMakerfgcmLutMaker.lambdaRange[1]+self.fgcmLutMakerfgcmLutMaker.lambdaStep*10,
474 self.fgcmLutMakerfgcmLutMaker.lambdaStep*10.)
475
476 self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" %
477 (throughputLambda[0], throughputLambda[-1],
478 throughputLambda[1] - throughputLambda[0]))
479
480 throughputDict = {}
481 for i, physicalFilter in enumerate(self.config.physicalFilters):
482 tDict = {}
483 tDict['LAMBDA'] = throughputLambda
484 for ccdIndex, detector in enumerate(camera):
485 tDict[ccdIndex] = self._getThroughputDetector_getThroughputDetector(detector, physicalFilter, throughputLambda)
486 throughputDict[physicalFilter] = tDict
487
488 # set the throughputs
489 self.fgcmLutMakerfgcmLutMaker.setThroughputs(throughputDict)
490
491 # make the LUT
492 self.log.info("Making LUT")
493 self.fgcmLutMakerfgcmLutMaker.makeLUT()
494
495 # and save the LUT
496
497 # build the index values
498 comma = ','
499 physicalFilterString = comma.join(self.config.physicalFilters)
500 stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList_getStdPhysicalFilterList())
501
502 atmosphereTableName = 'NoTableWasUsed'
503 if self.config.atmosphereTableName is not None:
504 atmosphereTableName = self.config.atmosphereTableName
505
506 lutSchema = self._makeLutSchema_makeLutSchema(physicalFilterString, stdPhysicalFilterString,
507 atmosphereTableName)
508
509 lutCat = self._makeLutCat_makeLutCat(lutSchema, physicalFilterString,
510 stdPhysicalFilterString, atmosphereTableName)
511 return lutCat
512
513 def _getStdPhysicalFilterList(self):
514 """Get the standard physical filter lists from config.physicalFilters
515 and config.stdPhysicalFilterOverrideMap
516
517 Returns
518 -------
519 stdPhysicalFilters : `list`
520 """
521 override = self.config.stdPhysicalFilterOverrideMap
522 return [override.get(physicalFilter, physicalFilter) for
523 physicalFilter in self.config.physicalFilters]
524
525 def _createLutConfig(self, nCcd):
526 """
527 Create the fgcmLut config dictionary
528
529 Parameters
530 ----------
531 nCcd: `int`
532 Number of CCDs in the camera
533 """
534
535 # create the common stub of the lutConfig
536 lutConfig = {}
537 lutConfig['logger'] = self.log
538 lutConfig['filterNames'] = self.config.physicalFilters
539 lutConfig['stdFilterNames'] = self._getStdPhysicalFilterList_getStdPhysicalFilterList()
540 lutConfig['nCCD'] = nCcd
541
542 # atmosphereTable already validated if available
543 if self.config.atmosphereTableName is not None:
544 lutConfig['atmosphereTableName'] = self.config.atmosphereTableName
545 else:
546 # use the regular paramters (also validated if needed)
547 lutConfig['elevation'] = self.config.parameters.elevation
548 lutConfig['pmbRange'] = self.config.parameters.pmbRange
549 lutConfig['pmbSteps'] = self.config.parameters.pmbSteps
550 lutConfig['pwvRange'] = self.config.parameters.pwvRange
551 lutConfig['pwvSteps'] = self.config.parameters.pwvSteps
552 lutConfig['o3Range'] = self.config.parameters.o3Range
553 lutConfig['o3Steps'] = self.config.parameters.o3Steps
554 lutConfig['tauRange'] = self.config.parameters.tauRange
555 lutConfig['tauSteps'] = self.config.parameters.tauSteps
556 lutConfig['alphaRange'] = self.config.parameters.alphaRange
557 lutConfig['alphaSteps'] = self.config.parameters.alphaSteps
558 lutConfig['zenithRange'] = self.config.parameters.zenithRange
559 lutConfig['zenithSteps'] = self.config.parameters.zenithSteps
560 lutConfig['pmbStd'] = self.config.parameters.pmbStd
561 lutConfig['pwvStd'] = self.config.parameters.pwvStd
562 lutConfig['o3Std'] = self.config.parameters.o3Std
563 lutConfig['tauStd'] = self.config.parameters.tauStd
564 lutConfig['alphaStd'] = self.config.parameters.alphaStd
565 lutConfig['airmassStd'] = self.config.parameters.airmassStd
566 lutConfig['lambdaRange'] = self.config.parameters.lambdaRange
567 lutConfig['lambdaStep'] = self.config.parameters.lambdaStep
568 lutConfig['lambdaNorm'] = self.config.parameters.lambdaNorm
569
570 return lutConfig
571
572 def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict):
573 """Internal method to load throughput data for filters
574
575 Parameters
576 ----------
577 camera: `lsst.afw.cameraGeom.Camera`
578 Camera from the butler
579 opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
580 `lsst.daf.butler.DeferredDatasetHandle`
581 Reference to optics transmission curve.
582 sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
583 `lsst.daf.butler.DeferredDatasetHandle`]
584 Dictionary of references to sensor transmission curves. Key will
585 be detector id.
586 filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
587 `lsst.daf.butler.DeferredDatasetHandle`]
588 Dictionary of references to filter transmission curves. Key will
589 be physical filter label.
590
591 Raises
592 ------
593 ValueError : Raised if configured filter name does not match any of the
594 available filter transmission curves.
595 """
596 self._opticsTransmission_opticsTransmission = opticsDataRef.get()
597
598 self._sensorsTransmission_sensorsTransmission = {}
599 for detector in camera:
600 self._sensorsTransmission_sensorsTransmission[detector.getId()] = sensorDataRefDict[detector.getId()].get()
601
602 self._filtersTransmission_filtersTransmission = {}
603 for physicalFilter in self.config.physicalFilters:
604 self._filtersTransmission_filtersTransmission[physicalFilter] = filterDataRefDict[physicalFilter].get()
605
606 def _getThroughputDetector(self, detector, physicalFilter, throughputLambda):
607 """Internal method to get throughput for a detector.
608
609 Returns the throughput at the center of the detector for a given filter.
610
611 Parameters
612 ----------
613 detector: `lsst.afw.cameraGeom._detector.Detector`
614 Detector on camera
615 physicalFilter: `str`
616 Physical filter label
617 throughputLambda: `np.array(dtype=np.float64)`
618 Wavelength steps (Angstrom)
619
620 Returns
621 -------
622 throughput: `np.array(dtype=np.float64)`
623 Throughput (max 1.0) at throughputLambda
624 """
625
626 c = detector.getCenter(afwCameraGeom.FOCAL_PLANE)
627 c.scale(1.0/detector.getPixelSize()[0]) # Assumes x and y pixel sizes in arcsec are the same
628
629 throughput = self._opticsTransmission_opticsTransmission.sampleAt(position=c,
630 wavelengths=throughputLambda)
631
632 throughput *= self._sensorsTransmission_sensorsTransmission[detector.getId()].sampleAt(position=c,
633 wavelengths=throughputLambda)
634
635 throughput *= self._filtersTransmission_filtersTransmission[physicalFilter].sampleAt(position=c,
636 wavelengths=throughputLambda)
637
638 # Clip the throughput from 0 to 1
639 throughput = np.clip(throughput, 0.0, 1.0)
640
641 return throughput
642
643 def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString,
644 atmosphereTableName):
645 """
646 Make the LUT schema
647
648 Parameters
649 ----------
650 physicalFilterString: `str`
651 Combined string of all the physicalFilters
652 stdPhysicalFilterString: `str`
653 Combined string of all the standard physicalFilters
654 atmosphereTableName: `str`
655 Name of the atmosphere table used to generate LUT
656
657 Returns
658 -------
659 lutSchema: `afwTable.schema`
660 """
661
662 lutSchema = afwTable.Schema()
663
664 lutSchema.addField('tablename', type=str, doc='Atmosphere table name',
665 size=len(atmosphereTableName))
666 lutSchema.addField('elevation', type=float, doc="Telescope elevation used for LUT")
667 lutSchema.addField('physicalFilters', type=str, doc='physicalFilters in LUT',
668 size=len(physicalFilterString))
669 lutSchema.addField('stdPhysicalFilters', type=str, doc='Standard physicalFilters in LUT',
670 size=len(stdPhysicalFilterString))
671 lutSchema.addField('pmb', type='ArrayD', doc='Barometric Pressure',
672 size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
673 lutSchema.addField('pmbFactor', type='ArrayD', doc='PMB scaling factor',
674 size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
675 lutSchema.addField('pmbElevation', type=np.float64, doc='PMB Scaling at elevation')
676 lutSchema.addField('pwv', type='ArrayD', doc='Preciptable Water Vapor',
677 size=self.fgcmLutMakerfgcmLutMaker.pwv.size)
678 lutSchema.addField('o3', type='ArrayD', doc='Ozone',
679 size=self.fgcmLutMakerfgcmLutMaker.o3.size)
680 lutSchema.addField('tau', type='ArrayD', doc='Aerosol optical depth',
681 size=self.fgcmLutMakerfgcmLutMaker.tau.size)
682 lutSchema.addField('lambdaNorm', type=np.float64, doc='AOD wavelength')
683 lutSchema.addField('alpha', type='ArrayD', doc='Aerosol alpha',
684 size=self.fgcmLutMakerfgcmLutMaker.alpha.size)
685 lutSchema.addField('zenith', type='ArrayD', doc='Zenith angle',
686 size=self.fgcmLutMakerfgcmLutMaker.zenith.size)
687 lutSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
688
689 # and the standard values
690 lutSchema.addField('pmbStd', type=np.float64, doc='PMB Standard')
691 lutSchema.addField('pwvStd', type=np.float64, doc='PWV Standard')
692 lutSchema.addField('o3Std', type=np.float64, doc='O3 Standard')
693 lutSchema.addField('tauStd', type=np.float64, doc='Tau Standard')
694 lutSchema.addField('alphaStd', type=np.float64, doc='Alpha Standard')
695 lutSchema.addField('zenithStd', type=np.float64, doc='Zenith angle Standard')
696 lutSchema.addField('lambdaRange', type='ArrayD', doc='Wavelength range',
697 size=2)
698 lutSchema.addField('lambdaStep', type=np.float64, doc='Wavelength step')
699 lutSchema.addField('lambdaStd', type='ArrayD', doc='Standard Wavelength',
700 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
701 lutSchema.addField('lambdaStdFilter', type='ArrayD', doc='Standard Wavelength (raw)',
702 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
703 lutSchema.addField('i0Std', type='ArrayD', doc='I0 Standard',
704 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
705 lutSchema.addField('i1Std', type='ArrayD', doc='I1 Standard',
706 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
707 lutSchema.addField('i10Std', type='ArrayD', doc='I10 Standard',
708 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
709 lutSchema.addField('i2Std', type='ArrayD', doc='I2 Standard',
710 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
711 lutSchema.addField('lambdaB', type='ArrayD', doc='Wavelength for passband (no atm)',
712 size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
713 lutSchema.addField('atmLambda', type='ArrayD', doc='Atmosphere wavelengths (Angstrom)',
714 size=self.fgcmLutMakerfgcmLutMaker.atmLambda.size)
715 lutSchema.addField('atmStdTrans', type='ArrayD', doc='Standard Atmosphere Throughput',
716 size=self.fgcmLutMakerfgcmLutMaker.atmStdTrans.size)
717
718 # and the look-up-tables
719 lutSchema.addField('luttype', type=str, size=20, doc='Look-up table type')
720 lutSchema.addField('lut', type='ArrayF', doc='Look-up table for luttype',
721 size=self.fgcmLutMakerfgcmLutMaker.lut['I0'].size)
722
723 return lutSchema
724
725 def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString,
726 atmosphereTableName):
727 """
728 Make the LUT schema
729
730 Parameters
731 ----------
732 lutSchema: `afwTable.schema`
733 Lut catalog schema
734 physicalFilterString: `str`
735 Combined string of all the physicalFilters
736 stdPhysicalFilterString: `str`
737 Combined string of all the standard physicalFilters
738 atmosphereTableName: `str`
739 Name of the atmosphere table used to generate LUT
740
741 Returns
742 -------
743 lutCat: `afwTable.BaseCatalog`
744 Lut catalog for persistence
745 """
746
747 # The somewhat strange format is to make sure that
748 # the rows of the afwTable do not get too large
749 # (see DM-11419)
750
751 lutCat = afwTable.BaseCatalog(lutSchema)
752 lutCat.table.preallocate(14)
753
754 # first fill the first index
755 rec = lutCat.addNew()
756
757 rec['tablename'] = atmosphereTableName
758 rec['elevation'] = self.fgcmLutMakerfgcmLutMaker.atmosphereTable.elevation
759 rec['physicalFilters'] = physicalFilterString
760 rec['stdPhysicalFilters'] = stdPhysicalFilterString
761 rec['pmb'][:] = self.fgcmLutMakerfgcmLutMaker.pmb
762 rec['pmbFactor'][:] = self.fgcmLutMakerfgcmLutMaker.pmbFactor
763 rec['pmbElevation'] = self.fgcmLutMakerfgcmLutMaker.pmbElevation
764 rec['pwv'][:] = self.fgcmLutMakerfgcmLutMaker.pwv
765 rec['o3'][:] = self.fgcmLutMakerfgcmLutMaker.o3
766 rec['tau'][:] = self.fgcmLutMakerfgcmLutMaker.tau
767 rec['lambdaNorm'] = self.fgcmLutMakerfgcmLutMaker.lambdaNorm
768 rec['alpha'][:] = self.fgcmLutMakerfgcmLutMaker.alpha
769 rec['zenith'][:] = self.fgcmLutMakerfgcmLutMaker.zenith
770 rec['nCcd'] = self.fgcmLutMakerfgcmLutMaker.nCCD
771
772 rec['pmbStd'] = self.fgcmLutMakerfgcmLutMaker.pmbStd
773 rec['pwvStd'] = self.fgcmLutMakerfgcmLutMaker.pwvStd
774 rec['o3Std'] = self.fgcmLutMakerfgcmLutMaker.o3Std
775 rec['tauStd'] = self.fgcmLutMakerfgcmLutMaker.tauStd
776 rec['alphaStd'] = self.fgcmLutMakerfgcmLutMaker.alphaStd
777 rec['zenithStd'] = self.fgcmLutMakerfgcmLutMaker.zenithStd
778 rec['lambdaRange'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaRange
779 rec['lambdaStep'] = self.fgcmLutMakerfgcmLutMaker.lambdaStep
780 rec['lambdaStd'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStd
781 rec['lambdaStdFilter'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStdFilter
782 rec['i0Std'][:] = self.fgcmLutMakerfgcmLutMaker.I0Std
783 rec['i1Std'][:] = self.fgcmLutMakerfgcmLutMaker.I1Std
784 rec['i10Std'][:] = self.fgcmLutMakerfgcmLutMaker.I10Std
785 rec['i2Std'][:] = self.fgcmLutMakerfgcmLutMaker.I2Std
786 rec['lambdaB'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaB
787 rec['atmLambda'][:] = self.fgcmLutMakerfgcmLutMaker.atmLambda
788 rec['atmStdTrans'][:] = self.fgcmLutMakerfgcmLutMaker.atmStdTrans
789
790 rec['luttype'] = 'I0'
791 rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I0'].flatten()
792
793 # and add the rest
794 rec = lutCat.addNew()
795 rec['luttype'] = 'I1'
796 rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I1'].flatten()
797
798 derivTypes = ['D_PMB', 'D_LNPWV', 'D_O3', 'D_LNTAU', 'D_ALPHA', 'D_SECZENITH',
799 'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1',
800 'D_SECZENITH_I1']
801 for derivType in derivTypes:
802 rec = lutCat.addNew()
803 rec['luttype'] = derivType
804 rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lutDeriv[derivType].flatten()
805
806 return lutCat
def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:726
def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:426
def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:644
def _getThroughputDetector(self, detector, physicalFilter, throughputLambda)
Definition: fgcmMakeLut.py:606
def runQuantum(self, butlerQC, inputRefs, outputRefs)
Definition: fgcmMakeLut.py:406
def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:572
def __init__(self, butler=None, initInputs=None, **kwargs)
Definition: fgcmMakeLut.py:361