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