23 """Make the final fgcmcal output products.
25 This task takes the final output from fgcmFitCycle and produces the following
26 outputs for use in the DM stack: the FGCM standard stars in a reference
27 catalog format; the model atmospheres in "transmission_atmosphere_fgcm"
28 format; and the zeropoints in "fgcm_photoCalib" format. Optionally, the
29 task can transfer the 'absolute' calibration from a reference catalog
30 to put the fgcm standard stars in units of Jansky. This is accomplished
31 by matching stars in a sample of healpix pixels, and applying the median
42 from astropy
import units
44 import lsst.pex.config
as pexConfig
45 import lsst.pipe.base
as pipeBase
46 from lsst.afw.image
import TransmissionCurve
47 from lsst.meas.algorithms
import LoadIndexedReferenceObjectsTask
48 from lsst.pipe.tasks.photoCal
import PhotoCalTask
50 import lsst.afw.image
as afwImage
51 import lsst.afw.math
as afwMath
52 import lsst.afw.table
as afwTable
53 from lsst.meas.algorithms
import IndexerRegistry
54 from lsst.meas.algorithms
import DatasetConfig
55 from lsst.meas.algorithms.ingestIndexReferenceTask
import addRefCatMetadata
57 from .utilities
import computeApproxPixelAreaFields
61 __all__ = [
'FgcmOutputProductsConfig',
'FgcmOutputProductsTask',
'FgcmOutputProductsRunner']
65 """Config for FgcmOutputProductsTask"""
67 cycleNumber = pexConfig.Field(
68 doc=
"Final fit cycle from FGCM fit",
75 doReferenceCalibration = pexConfig.Field(
76 doc=(
"Transfer 'absolute' calibration from reference catalog? "
77 "This afterburner step is unnecessary if reference stars "
78 "were used in the full fit in FgcmFitCycleTask."),
82 doRefcatOutput = pexConfig.Field(
83 doc=
"Output standard stars in reference catalog format",
87 doAtmosphereOutput = pexConfig.Field(
88 doc=
"Output atmospheres in transmission_atmosphere_fgcm format",
92 doZeropointOutput = pexConfig.Field(
93 doc=
"Output zeropoints in fgcm_photoCalib format",
97 doComposeWcsJacobian = pexConfig.Field(
98 doc=
"Compose Jacobian of WCS with fgcm calibration for output photoCalib?",
102 refObjLoader = pexConfig.ConfigurableField(
103 target=LoadIndexedReferenceObjectsTask,
104 doc=
"reference object loader for 'absolute' photometric calibration",
106 photoCal = pexConfig.ConfigurableField(
108 doc=
"task to perform 'absolute' calibration",
110 referencePixelizationNside = pexConfig.Field(
111 doc=
"Healpix nside to pixelize catalog to compare to reference catalog",
115 referencePixelizationMinStars = pexConfig.Field(
116 doc=(
"Minimum number of stars per healpix pixel to select for comparison"
117 "to the specified reference catalog"),
121 referenceMinMatch = pexConfig.Field(
122 doc=
"Minimum number of stars matched to reference catalog to be used in statistics",
126 referencePixelizationNPixels = pexConfig.Field(
127 doc=(
"Number of healpix pixels to sample to do comparison. "
128 "Doing too many will take a long time and not yield any more "
129 "precise results because the final number is the median offset "
130 "(per band) from the set of pixels."),
134 datasetConfig = pexConfig.ConfigField(
136 doc=
"Configuration for writing/reading ingested catalog",
140 pexConfig.Config.setDefaults(self)
150 self.
photoCal.applyColorTerms =
False
151 self.
photoCal.fluxField =
'instFlux'
153 self.
photoCal.match.referenceSelection.doSignalToNoise =
True
154 self.
photoCal.match.referenceSelection.signalToNoise.minimum = 10.0
155 self.
photoCal.match.sourceSelection.doSignalToNoise =
True
156 self.
photoCal.match.sourceSelection.signalToNoise.minimum = 10.0
157 self.
photoCal.match.sourceSelection.signalToNoise.fluxField =
'instFlux'
158 self.
photoCal.match.sourceSelection.signalToNoise.errField =
'instFluxErr'
159 self.
photoCal.match.sourceSelection.doFlags =
True
160 self.
photoCal.match.sourceSelection.flags.good = []
161 self.
photoCal.match.sourceSelection.flags.bad = [
'flag_badStar']
162 self.
photoCal.match.sourceSelection.doUnresolved =
False
168 """Subclass of TaskRunner for fgcmOutputProductsTask
170 fgcmOutputProductsTask.run() takes one argument, the butler, and
171 does not run on any data in the repository.
172 This runner does not use any parallelization.
178 Return a list with one element, the butler.
180 return [parsedCmd.butler]
186 butler: `lsst.daf.persistence.Butler`
190 exitStatus: `list` with `pipeBase.Struct`
191 exitStatus (0: success; 1: failure)
192 if self.doReturnResults also
193 results (`np.array` with absolute zeropoint offsets)
195 task = self.TaskClass(butler=butler, config=self.config, log=self.log)
199 results = task.runDataRef(butler)
202 results = task.runDataRef(butler)
203 except Exception
as e:
205 task.log.fatal(
"Failed: %s" % e)
206 if not isinstance(e, pipeBase.TaskError):
207 traceback.print_exc(file=sys.stderr)
209 task.writeMetadata(butler)
211 if self.doReturnResults:
213 return [pipeBase.Struct(exitStatus=exitStatus,
216 return [pipeBase.Struct(exitStatus=exitStatus)]
220 Run the task, with no multiprocessing
224 parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line
229 if self.precall(parsedCmd):
232 resultList = self(targetList[0])
239 Output products from FGCM global calibration.
242 ConfigClass = FgcmOutputProductsConfig
243 RunnerClass = FgcmOutputProductsRunner
244 _DefaultName =
"fgcmOutputProducts"
248 Instantiate an fgcmOutputProductsTask.
252 butler : `lsst.daf.persistence.Butler`
255 pipeBase.CmdLineTask.__init__(self, **kwargs)
257 if self.config.doReferenceCalibration:
259 self.makeSubtask(
"refObjLoader", butler=butler)
261 if self.config.doRefcatOutput:
262 self.
indexer = IndexerRegistry[self.config.datasetConfig.indexer.name](
263 self.config.datasetConfig.indexer.active)
266 def _getMetadataName(self):
272 Make FGCM output products for use in the stack
276 butler: `lsst.daf.persistence.Butler`
278 Final fit cycle number, override config.
282 offsets: `lsst.pipe.base.Struct`
283 A structure with array of zeropoint offsets
287 RuntimeError: Raised if butler cannot find fgcmBuildStars_config, or
288 fgcmFitCycle_config, or fgcmAtmosphereParameters (and
289 `self.config.doAtmosphereOutput` is true), or fgcmStandardStars (and
290 `self.config.doReferenceCalibration or `self.config.doRefcatOutput`
291 is true), or fgcmZeropoints (and self.config.doZeropointOutput is true).
292 Also will raise if the fgcmFitCycle_config does not refer to the
298 if not butler.datasetExists(
'fgcmBuildStars_config'):
299 raise RuntimeError(
"Cannot find fgcmBuildStars_config, which is prereq for fgcmOutputProducts")
301 fgcmBuildStarsConfig = butler.get(
'fgcmBuildStars_config')
306 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
307 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
308 "in fgcmBuildStarsTask.")
310 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
311 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
314 if not butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber):
315 raise RuntimeError(
"Cannot find fgcmFitCycle_config from cycle %d " % (self.config.cycleNumber) +
316 "which is required for fgcmOutputProducts.")
318 fitCycleConfig = butler.get(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber)
321 if self.config.doReferenceCalibration
and fitCycleConfig.doReferenceCalibration:
322 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
323 "fitCycleConfig.doReferenceCalibration")
326 if (self.config.doAtmosphereOutput
and
327 not butler.datasetExists(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)):
328 raise RuntimeError(
"Atmosphere parameters are missing for cycle %d." %
329 (self.config.cycleNumber))
331 if ((self.config.doReferenceCalibration
or self.config.doRefcatOutput)
and
332 (
not butler.datasetExists(
'fgcmStandardStars',
333 fgcmcycle=self.config.cycleNumber))):
334 raise RuntimeError(
"Standard stars are missing for cycle %d." %
335 (self.config.cycleNumber))
337 if (self.config.doZeropointOutput
and
338 (
not butler.datasetExists(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber))):
339 raise RuntimeError(
"Zeropoints are missing for cycle %d." %
340 (self.config.cycleNumber))
343 if butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber + 1):
344 raise RuntimeError(
"The task fgcmOutputProducts should only be run"
345 "on the final fit cycle products")
347 if self.config.doReferenceCalibration
or self.config.doRefcatOutput:
348 stdCat = butler.get(
'fgcmStandardStars', fgcmcycle=self.config.cycleNumber)
349 md = stdCat.getMetadata()
355 if self.config.doReferenceCalibration:
358 offsets = np.zeros(len(self.
bands))
361 if self.config.doRefcatOutput:
367 if self.config.doZeropointOutput:
368 zptCat = butler.get(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber)
369 visitCat = butler.get(
'fgcmVisitCatalog')
374 if self.config.doAtmosphereOutput:
375 atmCat = butler.get(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)
379 return pipeBase.Struct(offsets=offsets)
382 visitCat, zptCat, atmCat, stdCat,
383 fgcmBuildStarsConfig, fgcmFitCycleConfig):
385 Generate the output products for a given tract, as specified in the config.
387 This method is here to have an alternate entry-point for
392 butler: `lsst.daf.persistence.Butler`
395 visitCat: `lsst.afw.table.BaseCatalog`
396 FGCM visitCat from `FgcmBuildStarsTask`
397 zptCat: `lsst.afw.table.BaseCatalog`
398 FGCM zeropoint catalog from `FgcmFitCycleTask`
399 atmCat: `lsst.afw.table.BaseCatalog`
400 FGCM atmosphere parameter catalog from `FgcmFitCycleTask`
401 stdCat: `lsst.afw.table.SimpleCatalog`
402 FGCM standard star catalog from `FgcmFitCycleTask`
403 fgcmBuildStarsConfig: `lsst.fgcmcal.FgcmBuildStarsConfig`
404 Configuration object from `FgcmBuildStarsTask`
405 fgcmFitCycleConfig: `lsst.fgcmcal.FgcmFitCycleConfig`
406 Configuration object from `FgcmFitCycleTask`
412 self.
filterMap = fgcmBuildStarsConfig.filterMap
414 if stdCat
is not None:
415 md = stdCat.getMetadata()
416 self.
bands = md.getArray(
'BANDS')
420 if self.config.doReferenceCalibration
and fgcmFitCycleConfig.doReferenceCalibration:
421 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
422 "fitCycleConfig.doReferenceCalibration")
424 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
425 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
426 "in fgcmBuildStarsTask.")
428 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
429 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
431 if self.config.doReferenceCalibration:
434 offsets = np.zeros(len(self.
bands))
436 if self.config.doRefcatOutput:
438 datasetConfig = copy.copy(self.config.datasetConfig)
439 datasetConfig.ref_dataset_name =
'%s_%d' % (self.config.datasetConfig.ref_dataset_name,
443 if self.config.doZeropointOutput:
446 if self.config.doAtmosphereOutput:
449 return pipeBase.Struct(offsets=offsets)
451 def _computeReferenceOffsets(self, butler, stdCat):
453 Compute offsets relative to a reference catalog.
455 This method splits the star catalog into healpix pixels
456 and computes the calibration transfer for a sample of
457 these pixels to approximate the 'absolute' calibration
458 values (on for each band) to apply to transfer the
463 butler: `lsst.daf.persistence.Butler`
464 stdCat: `lsst.afw.table.SimpleCatalog`
469 offsets: `numpy.array` of floats
470 Per band zeropoint offsets
476 minObs = stdCat[
'ngood'].min(axis=1)
478 goodStars = (minObs >= 1)
479 stdCat = stdCat[goodStars]
481 self.log.info(
"Found %d stars with at least 1 good observation in each band" %
491 sourceMapper = afwTable.SchemaMapper(stdCat.schema)
492 sourceMapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
493 sourceMapper.editOutputSchema().addField(
'instFlux', type=np.float64,
494 doc=
"instrumental flux (counts)")
495 sourceMapper.editOutputSchema().addField(
'instFluxErr', type=np.float64,
496 doc=
"instrumental flux error (counts)")
497 badStarKey = sourceMapper.editOutputSchema().addField(
'flag_badStar',
505 theta = np.pi/2. - stdCat[
'coord_dec']
506 phi = stdCat[
'coord_ra']
508 ipring = hp.ang2pix(self.config.referencePixelizationNside, theta, phi)
509 h, rev = esutil.stat.histogram(ipring, rev=
True)
511 gdpix, = np.where(h >= self.config.referencePixelizationMinStars)
513 self.log.info(
"Found %d pixels (nside=%d) with at least %d good stars" %
515 self.config.referencePixelizationNside,
516 self.config.referencePixelizationMinStars))
518 if gdpix.size < self.config.referencePixelizationNPixels:
519 self.log.warn(
"Found fewer good pixels (%d) than preferred in configuration (%d)" %
520 (gdpix.size, self.config.referencePixelizationNPixels))
523 gdpix = np.random.choice(gdpix, size=self.config.referencePixelizationNPixels, replace=
False)
525 results = np.zeros(gdpix.size, dtype=[(
'hpix',
'i4'),
526 (
'nstar',
'i4', len(self.
bands)),
527 (
'nmatch',
'i4', len(self.
bands)),
528 (
'zp',
'f4', len(self.
bands)),
529 (
'zpErr',
'f4', len(self.
bands))])
530 results[
'hpix'] = ipring[rev[rev[gdpix]]]
533 selected = np.zeros(len(stdCat), dtype=np.bool)
535 refFluxFields = [
None]*len(self.
bands)
537 for p, pix
in enumerate(gdpix):
538 i1a = rev[rev[pix]: rev[pix + 1]]
546 for b, band
in enumerate(self.
bands):
549 selected, refFluxFields)
550 results[
'nstar'][p, b] = len(i1a)
551 results[
'nmatch'][p, b] = len(struct.arrays.refMag)
552 results[
'zp'][p, b] = struct.zp
553 results[
'zpErr'][p, b] = struct.sigma
556 offsets = np.zeros(len(self.
bands))
558 for b, band
in enumerate(self.
bands):
560 ok, = np.where(results[
'nmatch'][:, b] >= self.config.referenceMinMatch)
561 offsets[b] = np.median(results[
'zp'][ok, b])
564 madSigma = 1.4826*np.median(np.abs(results[
'zp'][ok, b] - offsets[b]))
565 self.log.info(
"Reference catalog offset for %s band: %.12f +/- %.12f" %
566 (band, offsets[b], madSigma))
570 def _computeOffsetOneBand(self, sourceMapper, badStarKey,
571 b, band, stdCat, selected, refFluxFields):
573 Compute the zeropoint offset between the fgcm stdCat and the reference
574 stars for one pixel in one band
578 sourceMapper: `lsst.afw.table.SchemaMapper`
579 Mapper to go from stdCat to calibratable catalog
580 badStarKey: `lsst.afw.table.Key`
581 Key for the field with bad stars
583 Index of the band in the star catalog
585 Name of band for reference catalog
586 stdCat: `lsst.afw.table.SimpleCatalog`
588 selected: `numpy.array(dtype=np.bool)`
589 Boolean array of which stars are in the pixel
590 refFluxFields: `list`
591 List of names of flux fields for reference catalog
594 sourceCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
595 sourceCat.reserve(selected.sum())
596 sourceCat.extend(stdCat[selected], mapper=sourceMapper)
597 sourceCat[
'instFlux'] = 10.**(stdCat[
'mag_std_noabs'][selected, b]/(-2.5))
598 sourceCat[
'instFluxErr'] = (np.log(10.)/2.5)*(stdCat[
'magErr_std'][selected, b] *
599 sourceCat[
'instFlux'])
603 badStar = (stdCat[
'mag_std_noabs'][selected, b] > 90.0)
604 for rec
in sourceCat[badStar]:
605 rec.set(badStarKey,
True)
607 exposure = afwImage.ExposureF()
608 exposure.setFilter(afwImage.Filter(band))
610 if refFluxFields[b]
is None:
613 ctr = stdCat[0].getCoord()
614 rad = 0.05*lsst.geom.degrees
615 refDataTest = self.refObjLoader.loadSkyCircle(ctr, rad, band)
616 refFluxFields[b] = refDataTest.fluxField
619 calConfig = copy.copy(self.config.photoCal.value)
620 calConfig.match.referenceSelection.signalToNoise.fluxField = refFluxFields[b]
621 calConfig.match.referenceSelection.signalToNoise.errField = refFluxFields[b] +
'Err'
622 calTask = self.config.photoCal.target(refObjLoader=self.refObjLoader,
624 schema=sourceCat.getSchema())
626 struct = calTask.run(exposure, sourceCat)
630 def _outputStandardStars(self, butler, stdCat, offsets, datasetConfig):
632 Output standard stars in indexed reference catalog format.
636 butler: `lsst.daf.persistence.Butler`
637 stdCat: `lsst.afw.table.SimpleCatalog`
638 FGCM standard star catalog from fgcmFitCycleTask
639 offsets: `numpy.array` of floats
640 Per band zeropoint offsets
641 datasetConfig: `lsst.meas.algorithms.DatasetConfig`
642 Config for reference dataset
645 self.log.info(
"Outputting standard stars to %s" % (datasetConfig.ref_dataset_name))
652 conv = stdCat[0][
'coord_ra'].asDegrees()/float(stdCat[0][
'coord_ra'])
653 indices = np.array(self.
indexer.indexPoints(stdCat[
'coord_ra']*conv,
654 stdCat[
'coord_dec']*conv))
659 dataId = self.
indexer.makeDataId(
'master_schema',
660 datasetConfig.ref_dataset_name)
661 masterCat = afwTable.SimpleCatalog(formattedCat.schema)
662 addRefCatMetadata(masterCat)
663 butler.put(masterCat,
'ref_cat', dataId=dataId)
666 h, rev = esutil.stat.histogram(indices, rev=
True)
667 gd, = np.where(h > 0)
668 selected = np.zeros(len(formattedCat), dtype=np.bool)
670 i1a = rev[rev[i]: rev[i + 1]]
679 dataId = self.
indexer.makeDataId(indices[i1a[0]],
680 datasetConfig.ref_dataset_name)
681 butler.put(formattedCat[selected],
'ref_cat', dataId=dataId)
684 dataId = self.
indexer.makeDataId(
None, datasetConfig.ref_dataset_name)
685 butler.put(datasetConfig,
'ref_cat_config', dataId=dataId)
687 self.log.info(
"Done outputting standard stars.")
689 def _formatCatalog(self, fgcmStarCat, offsets):
691 Turn an FGCM-formatted star catalog, applying zeropoint offsets.
695 fgcmStarCat: `lsst.afw.Table.SimpleCatalog`
696 SimpleCatalog as output by fgcmcal
697 offsets: `list` with len(self.bands) entries
698 Zeropoint offsets to apply
702 formattedCat: `lsst.afw.table.SimpleCatalog`
703 SimpleCatalog suitable for using as a reference catalog
706 sourceMapper = afwTable.SchemaMapper(fgcmStarCat.schema)
707 minSchema = LoadIndexedReferenceObjectsTask.makeMinimalSchema(self.
bands,
711 sourceMapper.addMinimalSchema(minSchema)
712 for band
in self.
bands:
713 sourceMapper.editOutputSchema().addField(
'%s_nGood' % (band), type=np.int32)
714 sourceMapper.editOutputSchema().addField(
'%s_nTotal' % (band), type=np.int32)
715 sourceMapper.editOutputSchema().addField(
'%s_nPsfCandidate' % (band), type=np.int32)
717 formattedCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
718 formattedCat.reserve(len(fgcmStarCat))
719 formattedCat.extend(fgcmStarCat, mapper=sourceMapper)
723 for b, band
in enumerate(self.
bands):
724 mag = fgcmStarCat[
'mag_std_noabs'][:, b].astype(np.float64) + offsets[b]
727 flux = (mag*units.ABmag).to_value(units.nJy)
728 fluxErr = (np.log(10.)/2.5)*flux*fgcmStarCat[
'magErr_std'][:, b].astype(np.float64)
730 formattedCat[
'%s_flux' % (band)][:] = flux
731 formattedCat[
'%s_fluxErr' % (band)][:] = fluxErr
732 formattedCat[
'%s_nGood' % (band)][:] = fgcmStarCat[
'ngood'][:, b]
733 formattedCat[
'%s_nTotal' % (band)][:] = fgcmStarCat[
'ntotal'][:, b]
734 formattedCat[
'%s_nPsfCandidate' % (band)][:] = fgcmStarCat[
'npsfcand'][:, b]
736 addRefCatMetadata(formattedCat)
740 def _outputZeropoints(self, butler, zptCat, visitCat, offsets, tract=None):
742 Output the zeropoints in fgcm_photoCalib format.
746 butler: `lsst.daf.persistence.Butler`
747 zptCat: `lsst.afw.table.BaseCatalog`
748 FGCM zeropoint catalog from `FgcmFitCycleTask`
749 visitCat: `lsst.afw.table.BaseCatalog`
750 FGCM visitCat from `FgcmBuildStarsTask`
751 offsets: `numpy.array`
752 Float array of absolute calibration offsets, one for each filter
753 tract: `int`, optional
754 Tract number to output. Default is None (global calibration)
758 datasetType =
'fgcm_photoCalib'
760 datasetType =
'fgcm_tract_photoCalib'
762 self.log.info(
"Outputting %s objects" % (datasetType))
767 cannot_compute = fgcm.fgcmUtilities.zpFlagDict[
'CANNOT_COMPUTE_ZEROPOINT']
768 too_few_stars = fgcm.fgcmUtilities.zpFlagDict[
'TOO_FEW_STARS_ON_CCD']
769 selected = (((zptCat[
'fgcmFlag'] & cannot_compute) == 0) &
770 (zptCat[
'fgcmZptVar'] > 0.0))
774 selected_best = (((zptCat[
'fgcmFlag'] & (cannot_compute | too_few_stars)) == 0) &
775 (zptCat[
'fgcmZptVar'] > 0.0))
778 badVisits = np.unique(zptCat[
'visit'][~selected])
779 goodVisits = np.unique(zptCat[
'visit'][selected])
780 allBadVisits = badVisits[~np.isin(badVisits, goodVisits)]
781 for allBadVisit
in allBadVisits:
782 self.log.warn(f
'No suitable photoCalib for {self.visitDataRefName} {allBadVisit}')
787 for rec
in zptCat[selected_best]:
788 if rec[
'filtername']
in filterMapping:
792 dataRef = butler.dataRef(
'raw', dataId=dataId)
793 filterMapping[rec[
'filtername']] = dataRef.dataId[
'filter']
806 camera = butler.get(
'camera')
808 for ccdIndex, detector
in enumerate(camera):
809 ccdMapping[detector.getId()] = ccdIndex
814 scalingMapping[rec[
'visit']] = rec[
'scaling']
816 if self.config.doComposeWcsJacobian:
819 for rec
in zptCat[selected]:
822 scaling = scalingMapping[rec[
'visit']][ccdMapping[rec[
'ccd']]]
825 rec[
'fgcmfZptChebXyMax'])
828 rec[
'fgcmfZptChebXyMax'],
829 offset=offsetMapping[rec[
'filtername']],
832 if self.config.doComposeWcsJacobian:
834 fgcmField = afwMath.ProductBoundedField([approxPixelAreaFields[rec[
'ccd']],
840 fgcmField = afwMath.ProductBoundedField([fgcmSuperStarField, fgcmZptField])
843 calibCenter = fgcmField.evaluate(fgcmField.getBBox().getCenter())
844 calibErr = (np.log(10.0)/2.5)*calibCenter*np.sqrt(rec[
'fgcmZptVar'])
845 photoCalib = afwImage.PhotoCalib(calibrationMean=calibCenter,
846 calibrationErr=calibErr,
847 calibration=fgcmField,
851 butler.put(photoCalib, datasetType,
854 'filter': filterMapping[rec[
'filtername']]})
856 butler.put(photoCalib, datasetType,
859 'filter': filterMapping[rec[
'filtername']],
862 self.log.info(
"Done outputting %s objects" % (datasetType))
864 def _getChebyshevBoundedField(self, coefficients, xyMax, offset=0.0, scaling=1.0):
866 Make a ChebyshevBoundedField from fgcm coefficients, with optional offset
871 coefficients: `numpy.array`
872 Flattened array of chebyshev coefficients
873 xyMax: `list` of length 2
874 Maximum x and y of the chebyshev bounding box
875 offset: `float`, optional
876 Absolute calibration offset. Default is 0.0
877 scaling: `float`, optional
878 Flat scaling value from fgcmBuildStars. Default is 1.0
882 boundedField: `lsst.afw.math.ChebyshevBoundedField`
885 orderPlus1 = int(np.sqrt(coefficients.size))
886 pars = np.zeros((orderPlus1, orderPlus1))
888 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0.0, 0.0),
889 lsst.geom.Point2I(*xyMax))
891 pars[:, :] = (coefficients.reshape(orderPlus1, orderPlus1) *
892 (10.**(offset/-2.5))*scaling)
894 boundedField = afwMath.ChebyshevBoundedField(bbox, pars)
898 def _outputAtmospheres(self, butler, atmCat, tract=None):
900 Output the atmospheres.
904 butler: `lsst.daf.persistence.Butler`
905 atmCat: `lsst.afw.table.BaseCatalog`
906 FGCM atmosphere parameter catalog from fgcmFitCycleTask
907 tract: `int`, optional
908 Tract number to output. Default is None (global calibration)
911 self.log.info(
"Outputting atmosphere transmissions")
914 lutCat = butler.get(
'fgcmLookUpTable')
916 atmosphereTableName = lutCat[0][
'tablename']
917 elevation = lutCat[0][
'elevation']
918 atmLambda = lutCat[0][
'atmLambda']
923 atmTable = fgcm.FgcmAtmosphereTable.initWithTableName(atmosphereTableName)
931 modGen = fgcm.ModtranGenerator(elevation)
932 lambdaRange = np.array([atmLambda[0], atmLambda[-1]])/10.
933 lambdaStep = (atmLambda[1] - atmLambda[0])/10.
934 except (ValueError, IOError)
as e:
935 raise RuntimeError(
"FGCM look-up-table generated with modtran, "
936 "but modtran not configured to run.")
from e
938 zenith = np.degrees(np.arccos(1./atmCat[
'secZenith']))
940 for i, visit
in enumerate(atmCat[
'visit']):
941 if atmTable
is not None:
943 atmVals = atmTable.interpolateAtmosphere(pmb=atmCat[i][
'pmb'],
944 pwv=atmCat[i][
'pwv'],
946 tau=atmCat[i][
'tau'],
947 alpha=atmCat[i][
'alpha'],
949 ctranslamstd=[atmCat[i][
'cTrans'],
950 atmCat[i][
'lamStd']])
953 modAtm = modGen(pmb=atmCat[i][
'pmb'],
954 pwv=atmCat[i][
'pwv'],
956 tau=atmCat[i][
'tau'],
957 alpha=atmCat[i][
'alpha'],
959 lambdaRange=lambdaRange,
960 lambdaStep=lambdaStep,
961 ctranslamstd=[atmCat[i][
'cTrans'],
962 atmCat[i][
'lamStd']])
963 atmVals = modAtm[
'COMBINED']
966 curve = TransmissionCurve.makeSpatiallyConstant(throughput=atmVals,
967 wavelengths=atmLambda,
968 throughputAtMin=atmVals[0],
969 throughputAtMax=atmVals[-1])
972 butler.put(curve,
"transmission_atmosphere_fgcm",
975 butler.put(curve,
"transmission_atmosphere_fgcm_tract",
979 self.log.info(
"Done outputting atmosphere transmissions")