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