23 """Make a look-up-table (LUT) for FGCM calibration. 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. 31 Computing a LUT requires running MODTRAN or with a pre-generated 32 atmosphere table packaged with fgcm. 40 import lsst.pex.config
as pexConfig
41 import lsst.pipe.base
as pipeBase
42 import lsst.afw.table
as afwTable
43 import lsst.afw.cameraGeom
as afwCameraGeom
44 from lsst.afw.image
import Filter
45 from lsst.daf.persistence
import NoResults
49 __all__ = [
'FgcmMakeLutParametersConfig',
'FgcmMakeLutConfig',
'FgcmMakeLutTask',
54 """Config for parameters if atmosphereTableName not available""" 57 elevation = pexConfig.Field(
58 doc=
"Telescope elevation (m)",
62 pmbRange = pexConfig.ListField(
63 doc=(
"Barometric Pressure range (millibar) " 64 "Recommended range depends on the site."),
68 pmbSteps = pexConfig.Field(
69 doc=
"Barometric Pressure number of steps",
73 pwvRange = pexConfig.ListField(
74 doc=(
"Precipitable Water Vapor range (mm) " 75 "Recommended range depends on the site."),
79 pwvSteps = pexConfig.Field(
80 doc=
"Precipitable Water Vapor number of steps",
84 o3Range = pexConfig.ListField(
85 doc=
"Ozone range (dob)",
87 default=[220.0, 310.0],
89 o3Steps = pexConfig.Field(
90 doc=
"Ozone number of steps",
94 tauRange = pexConfig.ListField(
95 doc=
"Aerosol Optical Depth range (unitless)",
97 default=[0.002, 0.35],
99 tauSteps = pexConfig.Field(
100 doc=
"Aerosol Optical Depth number of steps",
104 alphaRange = pexConfig.ListField(
105 doc=
"Aerosol alpha range (unitless)",
109 alphaSteps = pexConfig.Field(
110 doc=
"Aerosol alpha number of steps",
114 zenithRange = pexConfig.ListField(
115 doc=
"Zenith angle range (degree)",
119 zenithSteps = pexConfig.Field(
120 doc=
"Zenith angle number of steps",
126 pmbStd = pexConfig.Field(
127 doc=(
"Standard Atmosphere pressure (millibar); " 128 "Recommended default depends on the site."),
132 pwvStd = pexConfig.Field(
133 doc=(
"Standard Atmosphere PWV (mm); " 134 "Recommended default depends on the site."),
138 o3Std = pexConfig.Field(
139 doc=
"Standard Atmosphere O3 (dob)",
143 tauStd = pexConfig.Field(
144 doc=
"Standard Atmosphere aerosol optical depth",
148 alphaStd = pexConfig.Field(
149 doc=
"Standard Atmosphere aerosol alpha",
153 airmassStd = pexConfig.Field(
154 doc=(
"Standard Atmosphere airmass; " 155 "Recommended default depends on the survey strategy."),
159 lambdaNorm = pexConfig.Field(
160 doc=
"Aerosol Optical Depth normalization wavelength (Angstrom)",
164 lambdaStep = pexConfig.Field(
165 doc=
"Wavelength step for generating atmospheres (nm)",
169 lambdaRange = pexConfig.ListField(
170 doc=
"Wavelength range for LUT (Angstrom)",
172 default=[3000.0, 11000.0],
177 """Config for FgcmMakeLutTask""" 179 filterNames = pexConfig.ListField(
180 doc=
"Filter names to build LUT ('short' names)",
184 stdFilterNames = pexConfig.ListField(
185 doc=(
"Standard filterNames ('short' names). " 186 "Each filter in filterName will be calibrated to a matched " 187 "stdFilterName. In regular usage, one has g->g, r->r, ... " 188 "In the case of HSC, one would have g->g, r->r2, r2->r2, ... " 189 "which allows replacement (or time-variable) filters to be " 190 "properly cross-calibrated."),
194 atmosphereTableName = pexConfig.Field(
195 doc=
"FGCM name or filename of precomputed atmospheres",
200 parameters = pexConfig.ConfigField(
201 doc=
"Atmosphere parameters (required if no atmosphereTableName)",
202 dtype=FgcmMakeLutParametersConfig,
208 Validate the config parameters. 210 This method behaves differently from the parent validate in the case 211 that atmosphereTableName is set. In this case, the config values 212 for standard values, step sizes, and ranges are loaded 213 directly from the specified atmosphereTableName. 216 self._fields[
'filterNames'].
validate(self)
217 self._fields[
'stdFilterNames'].
validate(self)
224 raise RuntimeError(
"Could not find atmosphereTableName: %s" %
228 self._fields[
'parameters'].
validate(self)
232 """Subclass of TaskRunner for fgcmMakeLutTask 234 fgcmMakeLutTask.run() takes one argument, the butler, and 235 does not run on any data in the repository. 236 This runner does not use any parallelization. 242 Return a list with one element, the butler. 244 return [parsedCmd.butler]
250 butler: `lsst.daf.persistence.Butler` 254 exitStatus: `list` with `pipeBase.Struct` 255 exitStatus (0: success; 1: failure) 257 task = self.TaskClass(config=self.config, log=self.log)
261 task.runDataRef(butler)
264 task.runDataRef(butler)
265 except Exception
as e:
267 task.log.fatal(
"Failed: %s" % e)
268 if not isinstance(e, pipeBase.TaskError):
269 traceback.print_exc(file=sys.stderr)
271 task.writeMetadata(butler)
274 return [pipeBase.Struct(exitStatus=exitStatus)]
278 Run the task, with no multiprocessing 282 parsedCmd: ArgumentParser parsed command line 287 if self.precall(parsedCmd):
290 resultList = self(targetList[0])
297 Make Look-Up Table for FGCM. 299 This task computes a look-up-table for the range in expected atmosphere 300 variation and variation in instrumental throughput (as tracked by the 301 transmission_filter products). By pre-computing linearized integrals, 302 the FGCM fit is orders of magnitude faster for stars with a broad range 303 of colors and observing bands, yielding precision at the 1-2 mmag level. 305 Computing a LUT requires running MODTRAN or with a pre-generated 306 atmosphere table packaged with fgcm. 309 ConfigClass = FgcmMakeLutConfig
310 RunnerClass = FgcmMakeLutRunner
311 _DefaultName =
"fgcmMakeLut" 315 Instantiate an fgcmMakeLutTask. 319 butler: `lsst.daf.persistence.Butler` 322 pipeBase.CmdLineTask.__init__(self, **kwargs)
325 def _getMetadataName(self):
331 Make a Look-Up Table for FGCM 335 butler: `lsst.daf.persistence.Butler` 340 def _fgcmMakeLut(self, butler):
342 Make a FGCM Look-up Table 346 butler: `lsst.daf.persistence.Butler` 350 camera = butler.get(
'camera')
354 self.log.info(
"Found %d ccds for look-up table" % (nCcd))
362 self.log.info(
"Making the LUT maker object")
370 throughputLambda = np.arange(self.
fgcmLutMaker.lambdaRange[0],
374 self.log.info(
"Built throughput lambda, %.1f-%.1f, step %.2f" %
375 (throughputLambda[0], throughputLambda[-1],
376 throughputLambda[1]-throughputLambda[0]))
379 for i, filterName
in enumerate(self.config.filterNames):
381 tDict[
'LAMBDA'] = throughputLambda
382 for ccdIndex, detector
in enumerate(camera):
384 throughputDict[filterName] = tDict
390 self.log.info(
"Making LUT")
397 filterNameString = comma.join(self.config.filterNames)
398 stdFilterNameString = comma.join(self.config.stdFilterNames)
400 atmosphereTableName =
'NoTableWasUsed' 401 if self.config.atmosphereTableName
is not None:
402 atmosphereTableName = self.config.atmosphereTableName
404 lutSchema = self.
_makeLutSchema(filterNameString, stdFilterNameString,
407 lutCat = self.
_makeLutCat(lutSchema, filterNameString,
408 stdFilterNameString, atmosphereTableName)
409 butler.put(lutCat,
'fgcmLookUpTable')
411 def _createLutConfig(self, nCcd):
413 Create the fgcmLut config dictionary 418 Number of CCDs in the camera 423 lutConfig[
'logger'] = self.log
424 lutConfig[
'filterNames'] = self.config.filterNames
425 lutConfig[
'stdFilterNames'] = self.config.stdFilterNames
426 lutConfig[
'nCCD'] = nCcd
429 if self.config.atmosphereTableName
is not None:
430 lutConfig[
'atmosphereTableName'] = self.config.atmosphereTableName
433 lutConfig[
'elevation'] = self.config.parameters.elevation
434 lutConfig[
'pmbRange'] = self.config.parameters.pmbRange
435 lutConfig[
'pmbSteps'] = self.config.parameters.pmbSteps
436 lutConfig[
'pwvRange'] = self.config.parameters.pwvRange
437 lutConfig[
'pwvSteps'] = self.config.parameters.pwvSteps
438 lutConfig[
'o3Range'] = self.config.parameters.o3Range
439 lutConfig[
'o3Steps'] = self.config.parameters.o3Steps
440 lutConfig[
'tauRange'] = self.config.parameters.tauRange
441 lutConfig[
'tauSteps'] = self.config.parameters.tauSteps
442 lutConfig[
'alphaRange'] = self.config.parameters.alphaRange
443 lutConfig[
'alphaSteps'] = self.config.parameters.alphaSteps
444 lutConfig[
'zenithRange'] = self.config.parameters.zenithRange
445 lutConfig[
'zenithSteps'] = self.config.parameters.zenithSteps
446 lutConfig[
'pmbStd'] = self.config.parameters.pmbStd
447 lutConfig[
'pwvStd'] = self.config.parameters.pwvStd
448 lutConfig[
'o3Std'] = self.config.parameters.o3Std
449 lutConfig[
'tauStd'] = self.config.parameters.tauStd
450 lutConfig[
'alphaStd'] = self.config.parameters.alphaStd
451 lutConfig[
'airmassStd'] = self.config.parameters.airmassStd
452 lutConfig[
'lambdaRange'] = self.config.parameters.lambdaRange
453 lutConfig[
'lambdaStep'] = self.config.parameters.lambdaStep
454 lutConfig[
'lambdaNorm'] = self.config.parameters.lambdaNorm
458 def _loadThroughputs(self, butler, camera):
459 """Internal method to load throughput data for filters 463 butler: `lsst.daf.persistence.butler.Butler` 464 A butler with the transmission info 465 camera: `lsst.afw.cameraGeom.Camera` 470 for detector
in camera:
472 dataId={
'ccd': detector.getId()})
474 for filterName
in self.config.filterNames:
475 f = Filter(filterName)
478 aliases = f.getAliases()
479 aliases.extend(filterName)
480 for alias
in f.getAliases():
483 dataId={
'filter': alias})
489 raise ValueError(
"Could not find transmission for filter %s via any alias." % (filterName))
491 def _getThroughputDetector(self, detector, filterName, throughputLambda):
492 """Internal method to get throughput for a detector. 494 Returns the throughput at the center of the detector for a given filter. 498 detector: `lsst.afw.cameraGeom._detector.Detector` 501 Short name for filter 502 throughputLambda: `np.array(dtype=np.float64)` 503 Wavelength steps (Angstrom) 507 throughput: `np.array(dtype=np.float64)` 508 Throughput (max 1.0) at throughputLambda 511 c = detector.getCenter(afwCameraGeom.FOCAL_PLANE)
512 c.scale(1.0/detector.getPixelSize()[0])
515 wavelengths=throughputLambda)
518 wavelengths=throughputLambda)
521 wavelengths=throughputLambda)
524 throughput = np.clip(throughput, 0.0, 1.0)
528 def _makeLutSchema(self, filterNameString, stdFilterNameString,
529 atmosphereTableName):
535 filterNameString: `str` 536 Combined string of all the filterNames 537 stdFilterNameString: `str` 538 Combined string of all the standard filterNames 539 atmosphereTableName: `str` 540 Name of the atmosphere table used to generate LUT 544 lutSchema: `afwTable.schema` 547 lutSchema = afwTable.Schema()
549 lutSchema.addField(
'tablename', type=str, doc=
'Atmosphere table name',
550 size=len(atmosphereTableName))
551 lutSchema.addField(
'elevation', type=float, doc=
"Telescope elevation used for LUT")
552 lutSchema.addField(
'filterNames', type=str, doc=
'filterNames in LUT',
553 size=len(filterNameString))
554 lutSchema.addField(
'stdFilterNames', type=str, doc=
'Standard filterNames in LUT',
555 size=len(stdFilterNameString))
556 lutSchema.addField(
'pmb', type=
'ArrayD', doc=
'Barometric Pressure',
558 lutSchema.addField(
'pmbFactor', type=
'ArrayD', doc=
'PMB scaling factor',
560 lutSchema.addField(
'pmbElevation', type=np.float64, doc=
'PMB Scaling at elevation')
561 lutSchema.addField(
'pwv', type=
'ArrayD', doc=
'Preciptable Water Vapor',
563 lutSchema.addField(
'o3', type=
'ArrayD', doc=
'Ozone',
565 lutSchema.addField(
'tau', type=
'ArrayD', doc=
'Aerosol optical depth',
567 lutSchema.addField(
'lambdaNorm', type=np.float64, doc=
'AOD wavelength')
568 lutSchema.addField(
'alpha', type=
'ArrayD', doc=
'Aerosol alpha',
570 lutSchema.addField(
'zenith', type=
'ArrayD', doc=
'Zenith angle',
572 lutSchema.addField(
'nCcd', type=np.int32, doc=
'Number of CCDs')
575 lutSchema.addField(
'pmbStd', type=np.float64, doc=
'PMB Standard')
576 lutSchema.addField(
'pwvStd', type=np.float64, doc=
'PWV Standard')
577 lutSchema.addField(
'o3Std', type=np.float64, doc=
'O3 Standard')
578 lutSchema.addField(
'tauStd', type=np.float64, doc=
'Tau Standard')
579 lutSchema.addField(
'alphaStd', type=np.float64, doc=
'Alpha Standard')
580 lutSchema.addField(
'zenithStd', type=np.float64, doc=
'Zenith angle Standard')
581 lutSchema.addField(
'lambdaRange', type=
'ArrayD', doc=
'Wavelength range',
583 lutSchema.addField(
'lambdaStep', type=np.float64, doc=
'Wavelength step')
584 lutSchema.addField(
'lambdaStd', type=
'ArrayD', doc=
'Standard Wavelength',
586 lutSchema.addField(
'lambdaStdFilter', type=
'ArrayD', doc=
'Standard Wavelength (raw)',
588 lutSchema.addField(
'i0Std', type=
'ArrayD', doc=
'I0 Standard',
590 lutSchema.addField(
'i1Std', type=
'ArrayD', doc=
'I1 Standard',
592 lutSchema.addField(
'i10Std', type=
'ArrayD', doc=
'I10 Standard',
594 lutSchema.addField(
'i2Std', type=
'ArrayD', doc=
'I2 Standard',
596 lutSchema.addField(
'lambdaB', type=
'ArrayD', doc=
'Wavelength for passband (no atm)',
598 lutSchema.addField(
'atmLambda', type=
'ArrayD', doc=
'Atmosphere wavelengths (Angstrom)',
600 lutSchema.addField(
'atmStdTrans', type=
'ArrayD', doc=
'Standard Atmosphere Throughput',
604 lutSchema.addField(
'luttype', type=str, size=20, doc=
'Look-up table type')
605 lutSchema.addField(
'lut', type=
'ArrayF', doc=
'Look-up table for luttype',
610 def _makeLutCat(self, lutSchema, filterNameString, stdFilterNameString,
611 atmosphereTableName):
617 lutSchema: `afwTable.schema` 619 filterNameString: `str` 620 Combined string of all the filterNames 621 stdFilterNameString: `str` 622 Combined string of all the standard filterNames 623 atmosphereTableName: `str` 624 Name of the atmosphere table used to generate LUT 628 lutCat: `afwTable.BaseCatalog` 629 Lut catalog for persistence 636 lutCat = afwTable.BaseCatalog(lutSchema)
637 lutCat.table.preallocate(14)
640 rec = lutCat.addNew()
642 rec[
'tablename'] = atmosphereTableName
643 rec[
'elevation'] = self.
fgcmLutMaker.atmosphereTable.elevation
644 rec[
'filterNames'] = filterNameString
645 rec[
'stdFilterNames'] = stdFilterNameString
666 rec[
'lambdaStdFilter'][:] = self.
fgcmLutMaker.lambdaStdFilter
675 rec[
'luttype'] =
'I0' 679 rec = lutCat.addNew()
680 rec[
'luttype'] =
'I1' 683 derivTypes = [
'D_PMB',
'D_LNPWV',
'D_O3',
'D_LNTAU',
'D_ALPHA',
'D_SECZENITH',
684 'D_PMB_I1',
'D_LNPWV_I1',
'D_O3_I1',
'D_LNTAU_I1',
'D_ALPHA_I1',
686 for derivType
in derivTypes:
687 rec = lutCat.addNew()
688 rec[
'luttype'] = derivType
689 rec[
'lut'][:] = self.
fgcmLutMaker.lutDeriv[derivType].flatten()
def _createLutConfig(self, nCcd)
def _makeLutSchema(self, filterNameString, stdFilterNameString, atmosphereTableName)
def __call__(self, butler)
def _loadThroughputs(self, butler, camera)
def _makeLutCat(self, lutSchema, filterNameString, stdFilterNameString, atmosphereTableName)
def runDataRef(self, butler)
def _getThroughputDetector(self, detector, filterName, throughputLambda)
def __init__(self, butler=None, kwargs)
def _fgcmMakeLut(self, butler)
def getTargetList(parsedCmd)