LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
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 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 
234  filterNames = pexConfig.ListField(
235  doc="Filter names to build LUT ('short' names)",
236  dtype=str,
237  default=[],
238  deprecated=("This field is no longer used, and has been deprecated by "
239  "DM-28088. It will be removed after v22. Use "
240  "stdPhysicalFilterMap instead.")
241  )
242  stdFilterNames = pexConfig.ListField(
243  doc=("Standard filterNames ('short' names). "
244  "Each filter in filterName will be calibrated to a matched "
245  "stdFilterName. In regular usage, one has g->g, r->r, ... "
246  "In the case of HSC, one would have g->g, r->r2, r2->r2, ... "
247  "which allows replacement (or time-variable) filters to be "
248  "properly cross-calibrated."),
249  dtype=str,
250  default=[],
251  deprecated=("This field is no longer used, and has been deprecated by "
252  "DM-28088. It will be removed after v22. Use "
253  "stdPhysicalFilterMap instead.")
254  )
255  physicalFilters = pexConfig.ListField(
256  doc="List of physicalFilter labels to generate look-up table.",
257  dtype=str,
258  default=[],
259  )
260  stdPhysicalFilterOverrideMap = pexConfig.DictField(
261  doc=("Override mapping from physical filter labels to 'standard' physical "
262  "filter labels. The 'standard' physical filter defines the transmission "
263  "curve that the FGCM standard bandpass will be based on. "
264  "Any filter not listed here will be mapped to "
265  "itself (e.g. g->g or HSC-G->HSC-G). Use this override for cross-"
266  "filter calibration such as HSC-R->HSC-R2 and HSC-I->HSC-I2."),
267  keytype=str,
268  itemtype=str,
269  default={},
270  )
271  atmosphereTableName = pexConfig.Field(
272  doc="FGCM name or filename of precomputed atmospheres",
273  dtype=str,
274  default=None,
275  optional=True,
276  )
277  parameters = pexConfig.ConfigField(
278  doc="Atmosphere parameters (required if no atmosphereTableName)",
279  dtype=FgcmMakeLutParametersConfig,
280  default=None,
281  check=None)
282 
283  def validate(self):
284  """
285  Validate the config parameters.
286 
287  This method behaves differently from the parent validate in the case
288  that atmosphereTableName is set. In this case, the config values
289  for standard values, step sizes, and ranges are loaded
290  directly from the specified atmosphereTableName.
291  """
292  # check that filterNames and stdFilterNames are okay
293  self._fields['physicalFilters'].validate(self)
294  self._fields['stdPhysicalFilterOverrideMap'].validate(self)
295 
296  if self.atmosphereTableNameatmosphereTableName is None:
297  # Validate the parameters
298  self._fields['parameters'].validate(self)
299 
300 
301 class FgcmMakeLutRunner(pipeBase.ButlerInitializedTaskRunner):
302  """Subclass of TaskRunner for fgcmMakeLutTask
303 
304  fgcmMakeLutTask.run() takes one argument, the butler, and
305  does not run on any data in the repository.
306  This runner does not use any parallelization.
307  """
308 
309  @staticmethod
310  def getTargetList(parsedCmd):
311  """
312  Return a list with one element, the butler.
313  """
314  return [parsedCmd.butler]
315 
316  def __call__(self, butler):
317  """
318  Parameters
319  ----------
320  butler: `lsst.daf.persistence.Butler`
321 
322  Returns
323  -------
324  exitStatus: `list` with `pipeBase.Struct`
325  exitStatus (0: success; 1: failure)
326  """
327  task = self.TaskClass(config=self.config, log=self.log)
328 
329  exitStatus = 0
330  if self.doRaise:
331  task.runDataRef(butler)
332  else:
333  try:
334  task.runDataRef(butler)
335  except Exception as e:
336  exitStatus = 1
337  task.log.fatal("Failed: %s" % e)
338  if not isinstance(e, pipeBase.TaskError):
339  traceback.print_exc(file=sys.stderr)
340 
341  task.writeMetadata(butler)
342 
343  # The task does not return any results:
344  return [pipeBase.Struct(exitStatus=exitStatus)]
345 
346  def run(self, parsedCmd):
347  """
348  Run the task, with no multiprocessing
349 
350  Parameters
351  ----------
352  parsedCmd: ArgumentParser parsed command line
353  """
354 
355  resultList = []
356 
357  if self.precall(parsedCmd):
358  targetList = self.getTargetListgetTargetList(parsedCmd)
359  # make sure that we only get 1
360  resultList = self(targetList[0])
361 
362  return resultList
363 
364 
365 class FgcmMakeLutTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
366  """
367  Make Look-Up Table for FGCM.
368 
369  This task computes a look-up-table for the range in expected atmosphere
370  variation and variation in instrumental throughput (as tracked by the
371  transmission_filter products). By pre-computing linearized integrals,
372  the FGCM fit is orders of magnitude faster for stars with a broad range
373  of colors and observing bands, yielding precision at the 1-2 mmag level.
374 
375  Computing a LUT requires running MODTRAN or with a pre-generated
376  atmosphere table packaged with fgcm.
377  """
378 
379  ConfigClass = FgcmMakeLutConfig
380  RunnerClass = FgcmMakeLutRunner
381  _DefaultName = "fgcmMakeLut"
382 
383  def __init__(self, butler=None, initInputs=None, **kwargs):
384  super().__init__(**kwargs)
385 
386  # no saving of metadata for now
387  def _getMetadataName(self):
388  return None
389 
390  @pipeBase.timeMethod
391  def runDataRef(self, butler):
392  """
393  Make a Look-Up Table for FGCM
394 
395  Parameters
396  ----------
397  butler: `lsst.daf.persistence.Butler`
398 
399  Raises
400  ------
401  ValueError : Raised if configured filter name does not match any of the
402  available filter transmission curves.
403  """
404  camera = butler.get('camera')
405  opticsDataRef = butler.dataRef('transmission_optics')
406 
407  sensorDataRefDict = {}
408  for detector in camera:
409  sensorDataRefDict[detector.getId()] = butler.dataRef('transmission_sensor',
410  dataId={'ccd': detector.getId()})
411 
412  filterDataRefDict = {}
413  for physicalFilter in self.config.physicalFilters:
414  # The physical filters map directly to dataId filter names
415  # for gen2 HSC. This is the only camera that will be supported
416  # by Gen2 fgcmcal, so we do not need to worry about other cases.
417  dataRef = butler.dataRef('transmission_filter', filter=physicalFilter)
418  if not dataRef.datasetExists():
419  raise ValueError(f"Could not find transmission for filter {physicalFilter}.")
420  filterDataRefDict[physicalFilter] = dataRef
421 
422  lutCat = self._fgcmMakeLut_fgcmMakeLut(camera,
423  opticsDataRef,
424  sensorDataRefDict,
425  filterDataRefDict)
426  butler.put(lutCat, 'fgcmLookUpTable')
427 
428  def runQuantum(self, butlerQC, inputRefs, outputRefs):
429  camera = butlerQC.get(inputRefs.camera)
430 
431  # Instantiate the instrument to load filter information
432  _ = Instrument.fromName(inputRefs.camera.dataId['instrument'],
433  butlerQC.registry)
434  opticsDataRef = butlerQC.get(inputRefs.transmission_optics)
435 
436  sensorRefs = butlerQC.get(inputRefs.transmission_sensor)
437  sensorDataRefDict = {sensorRef.dataId.byName()['detector']: sensorRef for
438  sensorRef in sensorRefs}
439 
440  filterRefs = butlerQC.get(inputRefs.transmission_filter)
441  filterDataRefDict = {filterRef.dataId['physical_filter']: filterRef for
442  filterRef in filterRefs}
443 
444  lutCat = self._fgcmMakeLut_fgcmMakeLut(camera,
445  opticsDataRef,
446  sensorDataRefDict,
447  filterDataRefDict)
448  butlerQC.put(lutCat, outputRefs.fgcmLookUpTable)
449 
450  def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict,
451  filterDataRefDict):
452  """
453  Make a FGCM Look-up Table
454 
455  Parameters
456  ----------
457  camera : `lsst.afw.cameraGeom.Camera`
458  Camera from the butler.
459  opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
460  `lsst.daf.butler.DeferredDatasetHandle`
461  Reference to optics transmission curve.
462  sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
463  `lsst.daf.butler.DeferredDatasetHandle`]
464  Dictionary of references to sensor transmission curves. Key will
465  be detector id.
466  filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
467  `lsst.daf.butler.DeferredDatasetHandle`]
468  Dictionary of references to filter transmission curves. Key will
469  be physical filter label.
470 
471  Returns
472  -------
473  fgcmLookUpTable : `BaseCatalog`
474  The FGCM look-up table.
475  """
476  # number of ccds from the length of the camera iterator
477  nCcd = len(camera)
478  self.log.info("Found %d ccds for look-up table" % (nCcd))
479 
480  # Load in optics, etc.
481  self._loadThroughputs_loadThroughputs(camera,
482  opticsDataRef,
483  sensorDataRefDict,
484  filterDataRefDict)
485 
486  lutConfig = self._createLutConfig_createLutConfig(nCcd)
487 
488  # make the lut object
489  self.log.info("Making the LUT maker object")
490  self.fgcmLutMakerfgcmLutMaker = fgcm.FgcmLUTMaker(lutConfig)
491 
492  # generate the throughput dictionary.
493 
494  # these will be in Angstroms
495  # note that lambdaStep is currently in nm, because of historical
496  # reasons in the code. Convert to Angstroms here.
497  throughputLambda = np.arange(self.fgcmLutMakerfgcmLutMaker.lambdaRange[0],
498  self.fgcmLutMakerfgcmLutMaker.lambdaRange[1]+self.fgcmLutMakerfgcmLutMaker.lambdaStep*10,
499  self.fgcmLutMakerfgcmLutMaker.lambdaStep*10.)
500 
501  self.log.info("Built throughput lambda, %.1f-%.1f, step %.2f" %
502  (throughputLambda[0], throughputLambda[-1],
503  throughputLambda[1] - throughputLambda[0]))
504 
505  throughputDict = {}
506  for i, physicalFilter in enumerate(self.config.physicalFilters):
507  tDict = {}
508  tDict['LAMBDA'] = throughputLambda
509  for ccdIndex, detector in enumerate(camera):
510  tDict[ccdIndex] = self._getThroughputDetector_getThroughputDetector(detector, physicalFilter, throughputLambda)
511  throughputDict[physicalFilter] = tDict
512 
513  # set the throughputs
514  self.fgcmLutMakerfgcmLutMaker.setThroughputs(throughputDict)
515 
516  # make the LUT
517  self.log.info("Making LUT")
518  self.fgcmLutMakerfgcmLutMaker.makeLUT()
519 
520  # and save the LUT
521 
522  # build the index values
523  comma = ','
524  physicalFilterString = comma.join(self.config.physicalFilters)
525  stdPhysicalFilterString = comma.join(self._getStdPhysicalFilterList_getStdPhysicalFilterList())
526 
527  atmosphereTableName = 'NoTableWasUsed'
528  if self.config.atmosphereTableName is not None:
529  atmosphereTableName = self.config.atmosphereTableName
530 
531  lutSchema = self._makeLutSchema_makeLutSchema(physicalFilterString, stdPhysicalFilterString,
532  atmosphereTableName)
533 
534  lutCat = self._makeLutCat_makeLutCat(lutSchema, physicalFilterString,
535  stdPhysicalFilterString, atmosphereTableName)
536  return lutCat
537 
538  def _getStdPhysicalFilterList(self):
539  """Get the standard physical filter lists from config.physicalFilters
540  and config.stdPhysicalFilterOverrideMap
541 
542  Returns
543  -------
544  stdPhysicalFilters : `list`
545  """
546  override = self.config.stdPhysicalFilterOverrideMap
547  return [override.get(physicalFilter, physicalFilter) for
548  physicalFilter in self.config.physicalFilters]
549 
550  def _createLutConfig(self, nCcd):
551  """
552  Create the fgcmLut config dictionary
553 
554  Parameters
555  ----------
556  nCcd: `int`
557  Number of CCDs in the camera
558  """
559 
560  # create the common stub of the lutConfig
561  lutConfig = {}
562  lutConfig['logger'] = self.log
563  lutConfig['filterNames'] = self.config.physicalFilters
564  lutConfig['stdFilterNames'] = self._getStdPhysicalFilterList_getStdPhysicalFilterList()
565  lutConfig['nCCD'] = nCcd
566 
567  # atmosphereTable already validated if available
568  if self.config.atmosphereTableName is not None:
569  lutConfig['atmosphereTableName'] = self.config.atmosphereTableName
570  else:
571  # use the regular paramters (also validated if needed)
572  lutConfig['elevation'] = self.config.parameters.elevation
573  lutConfig['pmbRange'] = self.config.parameters.pmbRange
574  lutConfig['pmbSteps'] = self.config.parameters.pmbSteps
575  lutConfig['pwvRange'] = self.config.parameters.pwvRange
576  lutConfig['pwvSteps'] = self.config.parameters.pwvSteps
577  lutConfig['o3Range'] = self.config.parameters.o3Range
578  lutConfig['o3Steps'] = self.config.parameters.o3Steps
579  lutConfig['tauRange'] = self.config.parameters.tauRange
580  lutConfig['tauSteps'] = self.config.parameters.tauSteps
581  lutConfig['alphaRange'] = self.config.parameters.alphaRange
582  lutConfig['alphaSteps'] = self.config.parameters.alphaSteps
583  lutConfig['zenithRange'] = self.config.parameters.zenithRange
584  lutConfig['zenithSteps'] = self.config.parameters.zenithSteps
585  lutConfig['pmbStd'] = self.config.parameters.pmbStd
586  lutConfig['pwvStd'] = self.config.parameters.pwvStd
587  lutConfig['o3Std'] = self.config.parameters.o3Std
588  lutConfig['tauStd'] = self.config.parameters.tauStd
589  lutConfig['alphaStd'] = self.config.parameters.alphaStd
590  lutConfig['airmassStd'] = self.config.parameters.airmassStd
591  lutConfig['lambdaRange'] = self.config.parameters.lambdaRange
592  lutConfig['lambdaStep'] = self.config.parameters.lambdaStep
593  lutConfig['lambdaNorm'] = self.config.parameters.lambdaNorm
594 
595  return lutConfig
596 
597  def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict):
598  """Internal method to load throughput data for filters
599 
600  Parameters
601  ----------
602  camera: `lsst.afw.cameraGeom.Camera`
603  Camera from the butler
604  opticsDataRef : `lsst.daf.persistence.ButlerDataRef` or
605  `lsst.daf.butler.DeferredDatasetHandle`
606  Reference to optics transmission curve.
607  sensorDataRefDict : `dict` of [`int`, `lsst.daf.persistence.ButlerDataRef` or
608  `lsst.daf.butler.DeferredDatasetHandle`]
609  Dictionary of references to sensor transmission curves. Key will
610  be detector id.
611  filterDataRefDict : `dict` of [`str`, `lsst.daf.persistence.ButlerDataRef` or
612  `lsst.daf.butler.DeferredDatasetHandle`]
613  Dictionary of references to filter transmission curves. Key will
614  be physical filter label.
615 
616  Raises
617  ------
618  ValueError : Raised if configured filter name does not match any of the
619  available filter transmission curves.
620  """
621  self._opticsTransmission_opticsTransmission = opticsDataRef.get()
622 
623  self._sensorsTransmission_sensorsTransmission = {}
624  for detector in camera:
625  self._sensorsTransmission_sensorsTransmission[detector.getId()] = sensorDataRefDict[detector.getId()].get()
626 
627  self._filtersTransmission_filtersTransmission = {}
628  for physicalFilter in self.config.physicalFilters:
629  self._filtersTransmission_filtersTransmission[physicalFilter] = filterDataRefDict[physicalFilter].get()
630 
631  def _getThroughputDetector(self, detector, physicalFilter, throughputLambda):
632  """Internal method to get throughput for a detector.
633 
634  Returns the throughput at the center of the detector for a given filter.
635 
636  Parameters
637  ----------
638  detector: `lsst.afw.cameraGeom._detector.Detector`
639  Detector on camera
640  physicalFilter: `str`
641  Physical filter label
642  throughputLambda: `np.array(dtype=np.float64)`
643  Wavelength steps (Angstrom)
644 
645  Returns
646  -------
647  throughput: `np.array(dtype=np.float64)`
648  Throughput (max 1.0) at throughputLambda
649  """
650 
651  c = detector.getCenter(afwCameraGeom.FOCAL_PLANE)
652  c.scale(1.0/detector.getPixelSize()[0]) # Assumes x and y pixel sizes in arcsec are the same
653 
654  throughput = self._opticsTransmission_opticsTransmission.sampleAt(position=c,
655  wavelengths=throughputLambda)
656 
657  throughput *= self._sensorsTransmission_sensorsTransmission[detector.getId()].sampleAt(position=c,
658  wavelengths=throughputLambda)
659 
660  throughput *= self._filtersTransmission_filtersTransmission[physicalFilter].sampleAt(position=c,
661  wavelengths=throughputLambda)
662 
663  # Clip the throughput from 0 to 1
664  throughput = np.clip(throughput, 0.0, 1.0)
665 
666  return throughput
667 
668  def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString,
669  atmosphereTableName):
670  """
671  Make the LUT schema
672 
673  Parameters
674  ----------
675  physicalFilterString: `str`
676  Combined string of all the physicalFilters
677  stdPhysicalFilterString: `str`
678  Combined string of all the standard physicalFilters
679  atmosphereTableName: `str`
680  Name of the atmosphere table used to generate LUT
681 
682  Returns
683  -------
684  lutSchema: `afwTable.schema`
685  """
686 
687  lutSchema = afwTable.Schema()
688 
689  lutSchema.addField('tablename', type=str, doc='Atmosphere table name',
690  size=len(atmosphereTableName))
691  lutSchema.addField('elevation', type=float, doc="Telescope elevation used for LUT")
692  lutSchema.addField('physicalFilters', type=str, doc='physicalFilters in LUT',
693  size=len(physicalFilterString))
694  lutSchema.addField('stdPhysicalFilters', type=str, doc='Standard physicalFilters in LUT',
695  size=len(stdPhysicalFilterString))
696  lutSchema.addField('pmb', type='ArrayD', doc='Barometric Pressure',
697  size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
698  lutSchema.addField('pmbFactor', type='ArrayD', doc='PMB scaling factor',
699  size=self.fgcmLutMakerfgcmLutMaker.pmb.size)
700  lutSchema.addField('pmbElevation', type=np.float64, doc='PMB Scaling at elevation')
701  lutSchema.addField('pwv', type='ArrayD', doc='Preciptable Water Vapor',
702  size=self.fgcmLutMakerfgcmLutMaker.pwv.size)
703  lutSchema.addField('o3', type='ArrayD', doc='Ozone',
704  size=self.fgcmLutMakerfgcmLutMaker.o3.size)
705  lutSchema.addField('tau', type='ArrayD', doc='Aerosol optical depth',
706  size=self.fgcmLutMakerfgcmLutMaker.tau.size)
707  lutSchema.addField('lambdaNorm', type=np.float64, doc='AOD wavelength')
708  lutSchema.addField('alpha', type='ArrayD', doc='Aerosol alpha',
709  size=self.fgcmLutMakerfgcmLutMaker.alpha.size)
710  lutSchema.addField('zenith', type='ArrayD', doc='Zenith angle',
711  size=self.fgcmLutMakerfgcmLutMaker.zenith.size)
712  lutSchema.addField('nCcd', type=np.int32, doc='Number of CCDs')
713 
714  # and the standard values
715  lutSchema.addField('pmbStd', type=np.float64, doc='PMB Standard')
716  lutSchema.addField('pwvStd', type=np.float64, doc='PWV Standard')
717  lutSchema.addField('o3Std', type=np.float64, doc='O3 Standard')
718  lutSchema.addField('tauStd', type=np.float64, doc='Tau Standard')
719  lutSchema.addField('alphaStd', type=np.float64, doc='Alpha Standard')
720  lutSchema.addField('zenithStd', type=np.float64, doc='Zenith angle Standard')
721  lutSchema.addField('lambdaRange', type='ArrayD', doc='Wavelength range',
722  size=2)
723  lutSchema.addField('lambdaStep', type=np.float64, doc='Wavelength step')
724  lutSchema.addField('lambdaStd', type='ArrayD', doc='Standard Wavelength',
725  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
726  lutSchema.addField('lambdaStdFilter', type='ArrayD', doc='Standard Wavelength (raw)',
727  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
728  lutSchema.addField('i0Std', type='ArrayD', doc='I0 Standard',
729  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
730  lutSchema.addField('i1Std', type='ArrayD', doc='I1 Standard',
731  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
732  lutSchema.addField('i10Std', type='ArrayD', doc='I10 Standard',
733  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
734  lutSchema.addField('i2Std', type='ArrayD', doc='I2 Standard',
735  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
736  lutSchema.addField('lambdaB', type='ArrayD', doc='Wavelength for passband (no atm)',
737  size=len(self.fgcmLutMakerfgcmLutMaker.filterNames))
738  lutSchema.addField('atmLambda', type='ArrayD', doc='Atmosphere wavelengths (Angstrom)',
739  size=self.fgcmLutMakerfgcmLutMaker.atmLambda.size)
740  lutSchema.addField('atmStdTrans', type='ArrayD', doc='Standard Atmosphere Throughput',
741  size=self.fgcmLutMakerfgcmLutMaker.atmStdTrans.size)
742 
743  # and the look-up-tables
744  lutSchema.addField('luttype', type=str, size=20, doc='Look-up table type')
745  lutSchema.addField('lut', type='ArrayF', doc='Look-up table for luttype',
746  size=self.fgcmLutMakerfgcmLutMaker.lut['I0'].size)
747 
748  return lutSchema
749 
750  def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString,
751  atmosphereTableName):
752  """
753  Make the LUT schema
754 
755  Parameters
756  ----------
757  lutSchema: `afwTable.schema`
758  Lut catalog schema
759  physicalFilterString: `str`
760  Combined string of all the physicalFilters
761  stdPhysicalFilterString: `str`
762  Combined string of all the standard physicalFilters
763  atmosphereTableName: `str`
764  Name of the atmosphere table used to generate LUT
765 
766  Returns
767  -------
768  lutCat: `afwTable.BaseCatalog`
769  Lut catalog for persistence
770  """
771 
772  # The somewhat strange format is to make sure that
773  # the rows of the afwTable do not get too large
774  # (see DM-11419)
775 
776  lutCat = afwTable.BaseCatalog(lutSchema)
777  lutCat.table.preallocate(14)
778 
779  # first fill the first index
780  rec = lutCat.addNew()
781 
782  rec['tablename'] = atmosphereTableName
783  rec['elevation'] = self.fgcmLutMakerfgcmLutMaker.atmosphereTable.elevation
784  rec['physicalFilters'] = physicalFilterString
785  rec['stdPhysicalFilters'] = stdPhysicalFilterString
786  rec['pmb'][:] = self.fgcmLutMakerfgcmLutMaker.pmb
787  rec['pmbFactor'][:] = self.fgcmLutMakerfgcmLutMaker.pmbFactor
788  rec['pmbElevation'] = self.fgcmLutMakerfgcmLutMaker.pmbElevation
789  rec['pwv'][:] = self.fgcmLutMakerfgcmLutMaker.pwv
790  rec['o3'][:] = self.fgcmLutMakerfgcmLutMaker.o3
791  rec['tau'][:] = self.fgcmLutMakerfgcmLutMaker.tau
792  rec['lambdaNorm'] = self.fgcmLutMakerfgcmLutMaker.lambdaNorm
793  rec['alpha'][:] = self.fgcmLutMakerfgcmLutMaker.alpha
794  rec['zenith'][:] = self.fgcmLutMakerfgcmLutMaker.zenith
795  rec['nCcd'] = self.fgcmLutMakerfgcmLutMaker.nCCD
796 
797  rec['pmbStd'] = self.fgcmLutMakerfgcmLutMaker.pmbStd
798  rec['pwvStd'] = self.fgcmLutMakerfgcmLutMaker.pwvStd
799  rec['o3Std'] = self.fgcmLutMakerfgcmLutMaker.o3Std
800  rec['tauStd'] = self.fgcmLutMakerfgcmLutMaker.tauStd
801  rec['alphaStd'] = self.fgcmLutMakerfgcmLutMaker.alphaStd
802  rec['zenithStd'] = self.fgcmLutMakerfgcmLutMaker.zenithStd
803  rec['lambdaRange'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaRange
804  rec['lambdaStep'] = self.fgcmLutMakerfgcmLutMaker.lambdaStep
805  rec['lambdaStd'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStd
806  rec['lambdaStdFilter'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaStdFilter
807  rec['i0Std'][:] = self.fgcmLutMakerfgcmLutMaker.I0Std
808  rec['i1Std'][:] = self.fgcmLutMakerfgcmLutMaker.I1Std
809  rec['i10Std'][:] = self.fgcmLutMakerfgcmLutMaker.I10Std
810  rec['i2Std'][:] = self.fgcmLutMakerfgcmLutMaker.I2Std
811  rec['lambdaB'][:] = self.fgcmLutMakerfgcmLutMaker.lambdaB
812  rec['atmLambda'][:] = self.fgcmLutMakerfgcmLutMaker.atmLambda
813  rec['atmStdTrans'][:] = self.fgcmLutMakerfgcmLutMaker.atmStdTrans
814 
815  rec['luttype'] = 'I0'
816  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I0'].flatten()
817 
818  # and add the rest
819  rec = lutCat.addNew()
820  rec['luttype'] = 'I1'
821  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lut['I1'].flatten()
822 
823  derivTypes = ['D_PMB', 'D_LNPWV', 'D_O3', 'D_LNTAU', 'D_ALPHA', 'D_SECZENITH',
824  'D_PMB_I1', 'D_LNPWV_I1', 'D_O3_I1', 'D_LNTAU_I1', 'D_ALPHA_I1',
825  'D_SECZENITH_I1']
826  for derivType in derivTypes:
827  rec = lutCat.addNew()
828  rec['luttype'] = derivType
829  rec['lut'][:] = self.fgcmLutMakerfgcmLutMaker.lutDeriv[derivType].flatten()
830 
831  return lutCat
Defines the fields and offsets for a table.
Definition: Schema.h:50
def _makeLutCat(self, lutSchema, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:751
def _fgcmMakeLut(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:451
def _makeLutSchema(self, physicalFilterString, stdPhysicalFilterString, atmosphereTableName)
Definition: fgcmMakeLut.py:669
def _getThroughputDetector(self, detector, physicalFilter, throughputLambda)
Definition: fgcmMakeLut.py:631
def runQuantum(self, butlerQC, inputRefs, outputRefs)
Definition: fgcmMakeLut.py:428
def _loadThroughputs(self, camera, opticsDataRef, sensorDataRefDict, filterDataRefDict)
Definition: fgcmMakeLut.py:597
def __init__(self, butler=None, initInputs=None, **kwargs)
Definition: fgcmMakeLut.py:383