lsst.fgcmcal  22.0.1-7-g65f59fa+ece367c4df
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 .utilities import lookupStaticCalibrations
47 
48 import fgcm
49 
50 __all__ = ['FgcmMakeLutParametersConfig', 'FgcmMakeLutConfig', 'FgcmMakeLutTask',
51  'FgcmMakeLutRunner']
52 
53 
54 class 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 
107 class 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 
230 class 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 
279 class 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 
343 class 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  @pipeBase.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  # Instantiate the instrument to load filter information
410  _ = Instrument.fromName(inputRefs.camera.dataId['instrument'],
411  butlerQC.registry)
412  opticsDataRef = butlerQC.get(inputRefs.transmission_optics)
413 
414  sensorRefs = butlerQC.get(inputRefs.transmission_sensor)
415  sensorDataRefDict = {sensorRef.dataId.byName()['detector']: sensorRef for
416  sensorRef in sensorRefs}
417 
418  filterRefs = butlerQC.get(inputRefs.transmission_filter)
419  filterDataRefDict = {filterRef.dataId['physical_filter']: filterRef for
420  filterRef in filterRefs}
421 
422  lutCat = self._fgcmMakeLut_fgcmMakeLut(camera,
423  opticsDataRef,
424  sensorDataRefDict,
425  filterDataRefDict)
426  butlerQC.put(lutCat, outputRefs.fgcmLookUpTable)
427 
428  def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict,
429  filterDataRefDict):
430  """
431  Make a FGCM Look-up Table
432 
433  Parameters
434  ----------
435  camera : `lsst.afw.cameraGeom.Camera`
436  Camera from the butler.
437  opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
438  `lsst.daf.butler.DeferredDatasetHandle`
439  Reference to optics transmission curve.
440  sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
441  `lsst.daf.butler.DeferredDatasetHandle`]
442  Dictionary of references to sensor transmission curves. Key will
443  be detector id.
444  filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
445  `lsst.daf.butler.DeferredDatasetHandle`]
446  Dictionary of references to filter transmission curves. Key will
447  be physical filter label.
448 
449  Returns
450  -------
451  fgcmLookUpTable : `BaseCatalog`
452  The FGCM look-up table.
453  """
454  # number of ccds from the length of the camera iterator
455  nCcd = len(camera)
456  self.log.info("Found %d ccds for look-up table" % (nCcd))
457 
458  # Load in optics, etc.
459  self._loadThroughputs_loadThroughputs(camera,
460  opticsDataRef,
461  sensorDataRefDict,
462  filterDataRefDict)
463 
464  lutConfig = self._createLutConfig_createLutConfig(nCcd)
465 
466  # make the lut object
467  self.log.info("Making the LUT maker object")
468  self.fgcmLutMakerfgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig)
469 
470  # generate the throughput dictionary.
471 
472  # these will be in Angstroms
473  # note that lambdaStep is currently in nm, because of historical
474  # reasons in the code. Convert to Angstroms here.
475  throughputLambda = np.arange(self.fgcmLutMakerfgcmLutMaker.lambdaRange[0],
476  self.fgcmLutMakerfgcmLutMaker.lambdaRange[1]+self.fgcmLutMakerfgcmLutMaker.lambdaStep*10,
477  self.fgcmLutMakerfgcmLutMaker.lambdaStep*10.)
478 
479  self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" %
480  (throughputLambda[0], throughputLambda[-1],
481  throughputLambda[1] - throughputLambda[0]))
482 
483  throughputDict = {}
484  for i, physicalFilter in enumerate(self.config.physicalFilters):
485  tDict = {}
486  tDict['LAMBDA'] = throughputLambda
487  for ccdIndex, detector in enumerate(camera):
488  tDict[ccdIndex] = self._getThroughputDetector_getThroughputDetector(detector, physicalFilter, throughputLambda)
489  throughputDict[physicalFilter] = tDict
490 
491  # set the throughputs
492  self.fgcmLutMakerfgcmLutMaker.setThroughputs(throughputDict)
493 
494  # make the LUT
495  self.log.info("Making LUT")
496  self.fgcmLutMakerfgcmLutMaker.makeLUT()
497 
498  # and save the LUT
499 
500  # build the index values
501  comma = ','
502  physicalFilterString = comma.join(self.config.physicalFilters)
503  stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList_getStdPhysicalFilterList())
504 
505  atmosphereTableName = 'NoTableWasUsed'
506  if self.config.atmosphereTableName is not None:
507  atmosphereTableName = self.config.atmosphereTableName
508 
509  lutSchema = self._makeLutSchema_makeLutSchema(physicalFilterString, stdPhysicalFilterString,
510  atmosphereTableName)
511 
512  lutCat = self._makeLutCat_makeLutCat(lutSchema, physicalFilterString,
513  stdPhysicalFilterString, atmosphereTableName)
514  return lutCat
515 
516  def _getStdPhysicalFilterList(self):
517  """Get the standard physical filter lists from config.physicalFilters
518  and config.stdPhysicalFilterOverrideMap
519 
520  Returns
521  -------
522  stdPhysicalFilters : `list`
523  """
524  override = self.config.stdPhysicalFilterOverrideMap
525  return [override.get(physicalFilter, physicalFilter) for
526  physicalFilter in self.config.physicalFilters]
527 
528  def _createLutConfig(self, nCcd):
529  """
530  Create the fgcmLut config dictionary
531 
532  Parameters
533  ----------
534  nCcd: `int`
535  Number of CCDs in the camera
536  """
537 
538  # create the common stub of the lutConfig
539  lutConfig = {}
540  lutConfig['logger'] = self.log
541  lutConfig['filterNames'] = self.config.physicalFilters
542  lutConfig['stdFilterNames'] = self._getStdPhysicalFilterList_getStdPhysicalFilterList()
543  lutConfig['nCCD'] = nCcd
544 
545  # atmosphereTable already validated if available
546  if self.config.atmosphereTableName is not None:
547  lutConfig['atmosphereTableName'] = self.config.atmosphereTableName
548  else:
549  # use the regular paramters (also validated if needed)
550  lutConfig['elevation'] = self.config.parameters.elevation
551  lutConfig['pmbRange'] = self.config.parameters.pmbRange
552  lutConfig['pmbSteps'] = self.config.parameters.pmbSteps
553  lutConfig['pwvRange'] = self.config.parameters.pwvRange
554  lutConfig['pwvSteps'] = self.config.parameters.pwvSteps
555  lutConfig['o3Range'] = self.config.parameters.o3Range
556  lutConfig['o3Steps'] = self.config.parameters.o3Steps
557  lutConfig['tauRange'] = self.config.parameters.tauRange
558  lutConfig['tauSteps'] = self.config.parameters.tauSteps
559  lutConfig['alphaRange'] = self.config.parameters.alphaRange
560  lutConfig['alphaSteps'] = self.config.parameters.alphaSteps
561  lutConfig['zenithRange'] = self.config.parameters.zenithRange
562  lutConfig['zenithSteps'] = self.config.parameters.zenithSteps
563  lutConfig['pmbStd'] = self.config.parameters.pmbStd
564  lutConfig['pwvStd'] = self.config.parameters.pwvStd
565  lutConfig['o3Std'] = self.config.parameters.o3Std
566  lutConfig['tauStd'] = self.config.parameters.tauStd
567  lutConfig['alphaStd'] = self.config.parameters.alphaStd
568  lutConfig['airmassStd'] = self.config.parameters.airmassStd
569  lutConfig['lambdaRange'] = self.config.parameters.lambdaRange
570  lutConfig['lambdaStep'] = self.config.parameters.lambdaStep
571  lutConfig['lambdaNorm'] = self.config.parameters.lambdaNorm
572 
573  return lutConfig
574 
575  def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict):
576  """Internal method to load throughput data for filters
577 
578  Parameters
579  ----------
580  camera: `lsst.afw.cameraGeom.Camera`
581  Camera from the butler
582  opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
583  `lsst.daf.butler.DeferredDatasetHandle`
584  Reference to optics transmission curve.
585  sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
586  `lsst.daf.butler.DeferredDatasetHandle`]
587  Dictionary of references to sensor transmission curves. Key will
588  be detector id.
589  filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
590  `lsst.daf.butler.DeferredDatasetHandle`]
591  Dictionary of references to filter transmission curves. Key will
592  be physical filter label.
593 
594  Raises
595  ------
596  ValueError : Raised if configured filter name does not match any of the
597  available filter transmission curves.
598  """
599  self._opticsTransmission_opticsTransmission = opticsDataRef.get()
600 
601  self._sensorsTransmission_sensorsTransmission = {}
602  for detector in camera:
603  self._sensorsTransmission_sensorsTransmission[detector.getId()] = sensorDataRefDict[detector.getId()].get()
604 
605  self._filtersTransmission_filtersTransmission = {}
606  for physicalFilter in self.config.physicalFilters:
607  self._filtersTransmission_filtersTransmission[physicalFilter] = filterDataRefDict[physicalFilter].get()
608 
609  def _getThroughputDetector(self, detector, physicalFilter, throughputLambda):
610  """Internal method to get throughput for a detector.
611 
612  Returns the throughput at the center of the detector for a given filter.
613 
614  Parameters
615  ----------
616  detector: `lsst.afw.cameraGeom._detector.Detector`
617  Detector on camera
618  physicalFilter: `str`
619  Physical filter label
620  throughputLambda: `np.array(dtype=np.float64)`
621  Wavelength steps (Angstrom)
622 
623  Returns
624  -------
625  throughput: `np.array(dtype=np.float64)`
626  Throughput (max 1.0) at throughputLambda
627  """
628 
629  c = detector.getCenter(afwCameraGeom.FOCAL_PLANE)
630  c.scale(1.0/detector.getPixelSize()[0]) # Assumes x and y pixel sizes in arcsec are the same
631 
632  throughput = self._opticsTransmission_opticsTransmission.sampleAt(position=c,
633  wavelengths=throughputLambda)
634 
635  throughput *= self._sensorsTransmission_sensorsTransmission[detector.getId()].sampleAt(position=c,
636  wavelengths=throughputLambda)
637 
638  throughput *= self._filtersTransmission_filtersTransmission[physicalFilter].sampleAt(position=c,
639  wavelengths=throughputLambda)
640 
641  # Clip the throughput from 0 to 1
642  throughput = np.clip(throughput, 0.0, 1.0)
643 
644  return throughput
645 
646  def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString,
647  atmosphereTableName):
648  """
649  Make the LUT schema
650 
651  Parameters
652  ----------
653  physicalFilterString: `str`
654  Combined string of all the physicalFilters
655  stdPhysicalFilterString: `str`
656  Combined string of all the standard physicalFilters
657  atmosphereTableName: `str`
658  Name of the atmosphere table used to generate LUT
659 
660  Returns
661  -------
662  lutSchema: `afwTable.schema`
663  """
664 
665  lutSchema = afwTable.Schema()
666 
667  lutSchema.addField('tablename', type=str, doc='Atmosphere table name',
668  size=len(atmosphereTableName))
669  lutSchema.addField('elevation', type=float, doc="Telescope elevation used for LUT")
670  lutSchema.addField('physicalFilters', type=str, doc='physicalFilters in LUT',
671  size=len(physicalFilterString))
672  lutSchema.addField('stdPhysicalFilters', type=str, doc='Standard physicalFilters in LUT',
673  size=len(stdPhysicalFilterString))
674  lutSchema.addField('pmb', type='ArrayD', doc='Barometric Pressure',
675  size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
676  lutSchema.addField('pmbFactor', type='ArrayD', doc='PMB scaling factor',
677  size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
678  lutSchema.addField('pmbElevation', type=np.float64, doc='PMB Scaling at elevation')
679  lutSchema.addField('pwv', type='ArrayD', doc='Preciptable Water Vapor',
680  size=self.fgcmLutMakerfgcmLutMaker.pwv.size)
681  lutSchema.addField('o3', type='ArrayD', doc='Ozone',
682  size=self.fgcmLutMakerfgcmLutMaker.o3.size)
683  lutSchema.addField('tau', type='ArrayD', doc='Aerosol optical depth',
684  size=self.fgcmLutMakerfgcmLutMaker.tau.size)
685  lutSchema.addField('lambdaNorm', type=np.float64, doc='AOD wavelength')
686  lutSchema.addField('alpha', type='ArrayD', doc='Aerosol alpha',
687  size=self.fgcmLutMakerfgcmLutMaker.alpha.size)
688  lutSchema.addField('zenith', type='ArrayD', doc='Zenith angle',
689  size=self.fgcmLutMakerfgcmLutMaker.zenith.size)
690  lutSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
691 
692  # and the standard values
693  lutSchema.addField('pmbStd', type=np.float64, doc='PMB Standard')
694  lutSchema.addField('pwvStd', type=np.float64, doc='PWV Standard')
695  lutSchema.addField('o3Std', type=np.float64, doc='O3 Standard')
696  lutSchema.addField('tauStd', type=np.float64, doc='Tau Standard')
697  lutSchema.addField('alphaStd', type=np.float64, doc='Alpha Standard')
698  lutSchema.addField('zenithStd', type=np.float64, doc='Zenith angle Standard')
699  lutSchema.addField('lambdaRange', type='ArrayD', doc='Wavelength range',
700  size=2)
701  lutSchema.addField('lambdaStep', type=np.float64, doc='Wavelength step')
702  lutSchema.addField('lambdaStd', type='ArrayD', doc='Standard Wavelength',
703  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
704  lutSchema.addField('lambdaStdFilter', type='ArrayD', doc='Standard Wavelength (raw)',
705  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
706  lutSchema.addField('i0Std', type='ArrayD', doc='I0 Standard',
707  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
708  lutSchema.addField('i1Std', type='ArrayD', doc='I1 Standard',
709  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
710  lutSchema.addField('i10Std', type='ArrayD', doc='I10 Standard',
711  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
712  lutSchema.addField('i2Std', type='ArrayD', doc='I2 Standard',
713  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
714  lutSchema.addField('lambdaB', type='ArrayD', doc='Wavelength for passband (no atm)',
715  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
716  lutSchema.addField('atmLambda', type='ArrayD', doc='Atmosphere wavelengths (Angstrom)',
717  size=self.fgcmLutMakerfgcmLutMaker.atmLambda.size)
718  lutSchema.addField('atmStdTrans', type='ArrayD', doc='Standard Atmosphere Throughput',
719  size=self.fgcmLutMakerfgcmLutMaker.atmStdTrans.size)
720 
721  # and the look-up-tables
722  lutSchema.addField('luttype', type=str, size=20, doc='Look-up table type')
723  lutSchema.addField('lut', type='ArrayF', doc='Look-up table for luttype',
724  size=self.fgcmLutMakerfgcmLutMaker.lut['I0'].size)
725 
726  return lutSchema
727 
728  def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString,
729  atmosphereTableName):
730  """
731  Make the LUT schema
732 
733  Parameters
734  ----------
735  lutSchema: `afwTable.schema`
736  Lut catalog schema
737  physicalFilterString: `str`
738  Combined string of all the physicalFilters
739  stdPhysicalFilterString: `str`
740  Combined string of all the standard physicalFilters
741  atmosphereTableName: `str`
742  Name of the atmosphere table used to generate LUT
743 
744  Returns
745  -------
746  lutCat: `afwTable.BaseCatalog`
747  Lut catalog for persistence
748  """
749 
750  # The somewhat strange format is to make sure that
751  # the rows of the afwTable do not get too large
752  # (see DM-11419)
753 
754  lutCat = afwTable.BaseCatalog(lutSchema)
755  lutCat.table.preallocate(14)
756 
757  # first fill the first index
758  rec = lutCat.addNew()
759 
760  rec['tablename'] = atmosphereTableName
761  rec['elevation'] = self.fgcmLutMakerfgcmLutMaker.atmosphereTable.elevation
762  rec['physicalFilters'] = physicalFilterString
763  rec['stdPhysicalFilters'] = stdPhysicalFilterString
764  rec['pmb'][:] = self.fgcmLutMakerfgcmLutMaker.pmb
765  rec['pmbFactor'][:] = self.fgcmLutMakerfgcmLutMaker.pmbFactor
766  rec['pmbElevation'] = self.fgcmLutMakerfgcmLutMaker.pmbElevation
767  rec['pwv'][:] = self.fgcmLutMakerfgcmLutMaker.pwv
768  rec['o3'][:] = self.fgcmLutMakerfgcmLutMaker.o3
769  rec['tau'][:] = self.fgcmLutMakerfgcmLutMaker.tau
770  rec['lambdaNorm'] = self.fgcmLutMakerfgcmLutMaker.lambdaNorm
771  rec['alpha'][:] = self.fgcmLutMakerfgcmLutMaker.alpha
772  rec['zenith'][:] = self.fgcmLutMakerfgcmLutMaker.zenith
773  rec['nCcd'] = self.fgcmLutMakerfgcmLutMaker.nCCD
774 
775  rec['pmbStd'] = self.fgcmLutMakerfgcmLutMaker.pmbStd
776  rec['pwvStd'] = self.fgcmLutMakerfgcmLutMaker.pwvStd
777  rec['o3Std'] = self.fgcmLutMakerfgcmLutMaker.o3Std
778  rec['tauStd'] = self.fgcmLutMakerfgcmLutMaker.tauStd
779  rec['alphaStd'] = self.fgcmLutMakerfgcmLutMaker.alphaStd
780  rec['zenithStd'] = self.fgcmLutMakerfgcmLutMaker.zenithStd
781  rec['lambdaRange'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaRange
782  rec['lambdaStep'] = self.fgcmLutMakerfgcmLutMaker.lambdaStep
783  rec['lambdaStd'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStd
784  rec['lambdaStdFilter'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStdFilter
785  rec['i0Std'][:] = self.fgcmLutMakerfgcmLutMaker.I0Std
786  rec['i1Std'][:] = self.fgcmLutMakerfgcmLutMaker.I1Std
787  rec['i10Std'][:] = self.fgcmLutMakerfgcmLutMaker.I10Std
788  rec['i2Std'][:] = self.fgcmLutMakerfgcmLutMaker.I2Std
789  rec['lambdaB'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaB
790  rec['atmLambda'][:] = self.fgcmLutMakerfgcmLutMaker.atmLambda
791  rec['atmStdTrans'][:] = self.fgcmLutMakerfgcmLutMaker.atmStdTrans
792 
793  rec['luttype'] = 'I0'
794  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I0'].flatten()
795 
796  # and add the rest
797  rec = lutCat.addNew()
798  rec['luttype'] = 'I1'
799  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I1'].flatten()
800 
801  derivTypes = ['D_PMB', 'D_LNPWV', 'D_O3', 'D_LNTAU', 'D_ALPHA', 'D_SECZENITH',
802  'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1',
803  'D_SECZENITH_I1']
804  for derivType in derivTypes:
805  rec = lutCat.addNew()
806  rec['luttype'] = derivType
807  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lutDeriv[derivType].flatten()
808 
809  return lutCat
def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:729
def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:429
def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:647
def _getThroughputDetector(self, detector, physicalFilter, throughputLambda)
Definition: fgcmMakeLut.py:609
def runQuantum(self, butlerQC, inputRefs, outputRefs)
Definition: fgcmMakeLut.py:406
def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:575
def __init__(self, butler=None, initInputs=None, **kwargs)
Definition: fgcmMakeLut.py:361