LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
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 import lsst.pex.config as pexConfig
41 import lsst.pipe.base as pipeBase
42 from lsst.pipe.base import connectionTypes
43 import lsst.afw.table as afwTable
44 import lsst.afw.cameraGeom as afwCameraGeom
45 from lsst.utils.timer import timeMethod
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  @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
Defines the fields and offsets for a table.
Definition: Schema.h:51
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