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