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 doApplyMeanChromaticCorrection = pexConfig.Field(
103 doc=
"Apply the mean chromatic correction to the zeropoints?",
107 refObjLoader = pexConfig.ConfigurableField(
108 target=LoadIndexedReferenceObjectsTask,
109 doc=
"reference object loader for 'absolute' photometric calibration",
111 photoCal = pexConfig.ConfigurableField(
113 doc=
"task to perform 'absolute' calibration",
115 referencePixelizationNside = pexConfig.Field(
116 doc=
"Healpix nside to pixelize catalog to compare to reference catalog",
120 referencePixelizationMinStars = pexConfig.Field(
121 doc=(
"Minimum number of stars per healpix pixel to select for comparison"
122 "to the specified reference catalog"),
126 referenceMinMatch = pexConfig.Field(
127 doc=
"Minimum number of stars matched to reference catalog to be used in statistics",
131 referencePixelizationNPixels = pexConfig.Field(
132 doc=(
"Number of healpix pixels to sample to do comparison. "
133 "Doing too many will take a long time and not yield any more "
134 "precise results because the final number is the median offset "
135 "(per band) from the set of pixels."),
139 datasetConfig = pexConfig.ConfigField(
141 doc=
"Configuration for writing/reading ingested catalog",
145 pexConfig.Config.setDefaults(self)
155 self.
photoCal.applyColorTerms =
False
156 self.
photoCal.fluxField =
'instFlux'
158 self.
photoCal.match.referenceSelection.doSignalToNoise =
True
159 self.
photoCal.match.referenceSelection.signalToNoise.minimum = 10.0
160 self.
photoCal.match.sourceSelection.doSignalToNoise =
True
161 self.
photoCal.match.sourceSelection.signalToNoise.minimum = 10.0
162 self.
photoCal.match.sourceSelection.signalToNoise.fluxField =
'instFlux'
163 self.
photoCal.match.sourceSelection.signalToNoise.errField =
'instFluxErr'
164 self.
photoCal.match.sourceSelection.doFlags =
True
165 self.
photoCal.match.sourceSelection.flags.good = []
166 self.
photoCal.match.sourceSelection.flags.bad = [
'flag_badStar']
167 self.
photoCal.match.sourceSelection.doUnresolved =
False
173 """Subclass of TaskRunner for fgcmOutputProductsTask
175 fgcmOutputProductsTask.run() takes one argument, the butler, and
176 does not run on any data in the repository.
177 This runner does not use any parallelization.
183 Return a list with one element, the butler.
185 return [parsedCmd.butler]
191 butler: `lsst.daf.persistence.Butler`
195 exitStatus: `list` with `pipeBase.Struct`
196 exitStatus (0: success; 1: failure)
197 if self.doReturnResults also
198 results (`np.array` with absolute zeropoint offsets)
200 task = self.TaskClass(butler=butler, config=self.config, log=self.log)
204 results = task.runDataRef(butler)
207 results = task.runDataRef(butler)
208 except Exception
as e:
210 task.log.fatal(
"Failed: %s" % e)
211 if not isinstance(e, pipeBase.TaskError):
212 traceback.print_exc(file=sys.stderr)
214 task.writeMetadata(butler)
216 if self.doReturnResults:
218 return [pipeBase.Struct(exitStatus=exitStatus,
221 return [pipeBase.Struct(exitStatus=exitStatus)]
225 Run the task, with no multiprocessing
229 parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line
234 if self.precall(parsedCmd):
237 resultList = self(targetList[0])
244 Output products from FGCM global calibration.
247 ConfigClass = FgcmOutputProductsConfig
248 RunnerClass = FgcmOutputProductsRunner
249 _DefaultName =
"fgcmOutputProducts"
253 Instantiate an fgcmOutputProductsTask.
257 butler : `lsst.daf.persistence.Butler`
260 pipeBase.CmdLineTask.__init__(self, **kwargs)
262 if self.config.doReferenceCalibration:
264 self.makeSubtask(
"refObjLoader", butler=butler)
266 if self.config.doRefcatOutput:
267 self.
indexer = IndexerRegistry[self.config.datasetConfig.indexer.name](
268 self.config.datasetConfig.indexer.active)
271 def _getMetadataName(self):
277 Make FGCM output products for use in the stack
281 butler: `lsst.daf.persistence.Butler`
283 Final fit cycle number, override config.
287 offsets: `lsst.pipe.base.Struct`
288 A structure with array of zeropoint offsets
293 Raised if any one of the following is true:
295 - butler cannot find "fgcmBuildStars_config" or
296 "fgcmBuildStarsTable_config".
297 - butler cannot find "fgcmFitCycle_config".
298 - "fgcmFitCycle_config" does not refer to
299 `self.config.cycleNumber`.
300 - butler cannot find "fgcmAtmosphereParameters" and
301 `self.config.doAtmosphereOutput` is `True`.
302 - butler cannot find "fgcmStandardStars" and
303 `self.config.doReferenceCalibration` is `True` or
304 `self.config.doRefcatOutput` is `True`.
305 - butler cannot find "fgcmZeropoints" and
306 `self.config.doZeropointOutput` is `True`.
311 if not butler.datasetExists(
'fgcmBuildStarsTable_config')
and \
312 not butler.datasetExists(
'fgcmBuildStars_config'):
313 raise RuntimeError(
"Cannot find fgcmBuildStarsTable_config or fgcmBuildStars_config, "
314 "which is prereq for fgcmOutputProducts")
316 if butler.datasetExists(
'fgcmBuildStarsTable_config'):
317 fgcmBuildStarsConfig = butler.get(
'fgcmBuildStarsTable_config')
319 fgcmBuildStarsConfig = butler.get(
'fgcmBuildStars_config')
324 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
325 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
326 "in fgcmBuildStarsTask.")
328 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
329 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
332 if not butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber):
333 raise RuntimeError(
"Cannot find fgcmFitCycle_config from cycle %d " % (self.config.cycleNumber) +
334 "which is required for fgcmOutputProducts.")
336 fitCycleConfig = butler.get(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber)
339 if self.config.doReferenceCalibration
and fitCycleConfig.doReferenceCalibration:
340 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
341 "fitCycleConfig.doReferenceCalibration")
344 if (self.config.doAtmosphereOutput
and
345 not butler.datasetExists(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)):
346 raise RuntimeError(
"Atmosphere parameters are missing for cycle %d." %
347 (self.config.cycleNumber))
349 if ((self.config.doReferenceCalibration
or self.config.doRefcatOutput)
and
350 (
not butler.datasetExists(
'fgcmStandardStars',
351 fgcmcycle=self.config.cycleNumber))):
352 raise RuntimeError(
"Standard stars are missing for cycle %d." %
353 (self.config.cycleNumber))
355 if (self.config.doZeropointOutput
and
356 (
not butler.datasetExists(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber))):
357 raise RuntimeError(
"Zeropoints are missing for cycle %d." %
358 (self.config.cycleNumber))
361 if butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber + 1):
362 raise RuntimeError(
"The task fgcmOutputProducts should only be run"
363 "on the final fit cycle products")
365 if self.config.doReferenceCalibration
or self.config.doRefcatOutput:
366 stdCat = butler.get(
'fgcmStandardStars', fgcmcycle=self.config.cycleNumber)
367 md = stdCat.getMetadata()
373 if self.config.doReferenceCalibration:
376 offsets = np.zeros(len(self.
bands))
379 if self.config.doRefcatOutput:
385 if self.config.doZeropointOutput:
386 zptCat = butler.get(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber)
387 visitCat = butler.get(
'fgcmVisitCatalog')
392 if self.config.doAtmosphereOutput:
393 atmCat = butler.get(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)
397 return pipeBase.Struct(offsets=offsets)
400 visitCat, zptCat, atmCat, stdCat,
401 fgcmBuildStarsConfig, fgcmFitCycleConfig):
403 Generate the output products for a given tract, as specified in the config.
405 This method is here to have an alternate entry-point for
410 butler: `lsst.daf.persistence.Butler`
413 visitCat: `lsst.afw.table.BaseCatalog`
414 FGCM visitCat from `FgcmBuildStarsTask`
415 zptCat: `lsst.afw.table.BaseCatalog`
416 FGCM zeropoint catalog from `FgcmFitCycleTask`
417 atmCat: `lsst.afw.table.BaseCatalog`
418 FGCM atmosphere parameter catalog from `FgcmFitCycleTask`
419 stdCat: `lsst.afw.table.SimpleCatalog`
420 FGCM standard star catalog from `FgcmFitCycleTask`
421 fgcmBuildStarsConfig: `lsst.fgcmcal.FgcmBuildStarsConfig`
422 Configuration object from `FgcmBuildStarsTask`
423 fgcmFitCycleConfig: `lsst.fgcmcal.FgcmFitCycleConfig`
424 Configuration object from `FgcmFitCycleTask`
430 self.
filterMap = fgcmBuildStarsConfig.filterMap
432 if stdCat
is not None:
433 md = stdCat.getMetadata()
434 self.
bands = md.getArray(
'BANDS')
438 if self.config.doReferenceCalibration
and fgcmFitCycleConfig.doReferenceCalibration:
439 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
440 "fitCycleConfig.doReferenceCalibration")
442 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
443 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
444 "in fgcmBuildStarsTask.")
446 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
447 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
449 if self.config.doReferenceCalibration:
452 offsets = np.zeros(len(self.
bands))
454 if self.config.doRefcatOutput:
456 datasetConfig = copy.copy(self.config.datasetConfig)
457 datasetConfig.ref_dataset_name =
'%s_%d' % (self.config.datasetConfig.ref_dataset_name,
461 if self.config.doZeropointOutput:
464 if self.config.doAtmosphereOutput:
467 return pipeBase.Struct(offsets=offsets)
469 def _computeReferenceOffsets(self, butler, stdCat):
471 Compute offsets relative to a reference catalog.
473 This method splits the star catalog into healpix pixels
474 and computes the calibration transfer for a sample of
475 these pixels to approximate the 'absolute' calibration
476 values (on for each band) to apply to transfer the
481 butler: `lsst.daf.persistence.Butler`
482 stdCat: `lsst.afw.table.SimpleCatalog`
487 offsets: `numpy.array` of floats
488 Per band zeropoint offsets
494 minObs = stdCat[
'ngood'].min(axis=1)
496 goodStars = (minObs >= 1)
497 stdCat = stdCat[goodStars]
499 self.log.info(
"Found %d stars with at least 1 good observation in each band" %
509 sourceMapper = afwTable.SchemaMapper(stdCat.schema)
510 sourceMapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
511 sourceMapper.editOutputSchema().addField(
'instFlux', type=np.float64,
512 doc=
"instrumental flux (counts)")
513 sourceMapper.editOutputSchema().addField(
'instFluxErr', type=np.float64,
514 doc=
"instrumental flux error (counts)")
515 badStarKey = sourceMapper.editOutputSchema().addField(
'flag_badStar',
523 theta = np.pi/2. - stdCat[
'coord_dec']
524 phi = stdCat[
'coord_ra']
526 ipring = hp.ang2pix(self.config.referencePixelizationNside, theta, phi)
527 h, rev = esutil.stat.histogram(ipring, rev=
True)
529 gdpix, = np.where(h >= self.config.referencePixelizationMinStars)
531 self.log.info(
"Found %d pixels (nside=%d) with at least %d good stars" %
533 self.config.referencePixelizationNside,
534 self.config.referencePixelizationMinStars))
536 if gdpix.size < self.config.referencePixelizationNPixels:
537 self.log.warn(
"Found fewer good pixels (%d) than preferred in configuration (%d)" %
538 (gdpix.size, self.config.referencePixelizationNPixels))
541 gdpix = np.random.choice(gdpix, size=self.config.referencePixelizationNPixels, replace=
False)
543 results = np.zeros(gdpix.size, dtype=[(
'hpix',
'i4'),
544 (
'nstar',
'i4', len(self.
bands)),
545 (
'nmatch',
'i4', len(self.
bands)),
546 (
'zp',
'f4', len(self.
bands)),
547 (
'zpErr',
'f4', len(self.
bands))])
548 results[
'hpix'] = ipring[rev[rev[gdpix]]]
551 selected = np.zeros(len(stdCat), dtype=np.bool)
553 refFluxFields = [
None]*len(self.
bands)
555 for p, pix
in enumerate(gdpix):
556 i1a = rev[rev[pix]: rev[pix + 1]]
564 for b, band
in enumerate(self.
bands):
567 selected, refFluxFields)
568 results[
'nstar'][p, b] = len(i1a)
569 results[
'nmatch'][p, b] = len(struct.arrays.refMag)
570 results[
'zp'][p, b] = struct.zp
571 results[
'zpErr'][p, b] = struct.sigma
574 offsets = np.zeros(len(self.
bands))
576 for b, band
in enumerate(self.
bands):
578 ok, = np.where(results[
'nmatch'][:, b] >= self.config.referenceMinMatch)
579 offsets[b] = np.median(results[
'zp'][ok, b])
582 madSigma = 1.4826*np.median(np.abs(results[
'zp'][ok, b] - offsets[b]))
583 self.log.info(
"Reference catalog offset for %s band: %.12f +/- %.12f" %
584 (band, offsets[b], madSigma))
588 def _computeOffsetOneBand(self, sourceMapper, badStarKey,
589 b, band, stdCat, selected, refFluxFields):
591 Compute the zeropoint offset between the fgcm stdCat and the reference
592 stars for one pixel in one band
596 sourceMapper: `lsst.afw.table.SchemaMapper`
597 Mapper to go from stdCat to calibratable catalog
598 badStarKey: `lsst.afw.table.Key`
599 Key for the field with bad stars
601 Index of the band in the star catalog
603 Name of band for reference catalog
604 stdCat: `lsst.afw.table.SimpleCatalog`
606 selected: `numpy.array(dtype=np.bool)`
607 Boolean array of which stars are in the pixel
608 refFluxFields: `list`
609 List of names of flux fields for reference catalog
612 sourceCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
613 sourceCat.reserve(selected.sum())
614 sourceCat.extend(stdCat[selected], mapper=sourceMapper)
615 sourceCat[
'instFlux'] = 10.**(stdCat[
'mag_std_noabs'][selected, b]/(-2.5))
616 sourceCat[
'instFluxErr'] = (np.log(10.)/2.5)*(stdCat[
'magErr_std'][selected, b] *
617 sourceCat[
'instFlux'])
621 badStar = (stdCat[
'mag_std_noabs'][selected, b] > 90.0)
622 for rec
in sourceCat[badStar]:
623 rec.set(badStarKey,
True)
625 exposure = afwImage.ExposureF()
626 exposure.setFilter(afwImage.Filter(band))
628 if refFluxFields[b]
is None:
631 ctr = stdCat[0].getCoord()
632 rad = 0.05*lsst.geom.degrees
633 refDataTest = self.refObjLoader.loadSkyCircle(ctr, rad, band)
634 refFluxFields[b] = refDataTest.fluxField
637 calConfig = copy.copy(self.config.photoCal.value)
638 calConfig.match.referenceSelection.signalToNoise.fluxField = refFluxFields[b]
639 calConfig.match.referenceSelection.signalToNoise.errField = refFluxFields[b] +
'Err'
640 calTask = self.config.photoCal.target(refObjLoader=self.refObjLoader,
642 schema=sourceCat.getSchema())
644 struct = calTask.run(exposure, sourceCat)
648 def _outputStandardStars(self, butler, stdCat, offsets, datasetConfig):
650 Output standard stars in indexed reference catalog format.
654 butler: `lsst.daf.persistence.Butler`
655 stdCat: `lsst.afw.table.SimpleCatalog`
656 FGCM standard star catalog from fgcmFitCycleTask
657 offsets: `numpy.array` of floats
658 Per band zeropoint offsets
659 datasetConfig: `lsst.meas.algorithms.DatasetConfig`
660 Config for reference dataset
663 self.log.info(
"Outputting standard stars to %s" % (datasetConfig.ref_dataset_name))
670 conv = stdCat[0][
'coord_ra'].asDegrees()/float(stdCat[0][
'coord_ra'])
671 indices = np.array(self.
indexer.indexPoints(stdCat[
'coord_ra']*conv,
672 stdCat[
'coord_dec']*conv))
677 dataId = self.
indexer.makeDataId(
'master_schema',
678 datasetConfig.ref_dataset_name)
679 masterCat = afwTable.SimpleCatalog(formattedCat.schema)
680 addRefCatMetadata(masterCat)
681 butler.put(masterCat,
'ref_cat', dataId=dataId)
684 h, rev = esutil.stat.histogram(indices, rev=
True)
685 gd, = np.where(h > 0)
686 selected = np.zeros(len(formattedCat), dtype=np.bool)
688 i1a = rev[rev[i]: rev[i + 1]]
697 dataId = self.
indexer.makeDataId(indices[i1a[0]],
698 datasetConfig.ref_dataset_name)
699 butler.put(formattedCat[selected],
'ref_cat', dataId=dataId)
702 dataId = self.
indexer.makeDataId(
None, datasetConfig.ref_dataset_name)
703 butler.put(datasetConfig,
'ref_cat_config', dataId=dataId)
705 self.log.info(
"Done outputting standard stars.")
707 def _formatCatalog(self, fgcmStarCat, offsets):
709 Turn an FGCM-formatted star catalog, applying zeropoint offsets.
713 fgcmStarCat: `lsst.afw.Table.SimpleCatalog`
714 SimpleCatalog as output by fgcmcal
715 offsets: `list` with len(self.bands) entries
716 Zeropoint offsets to apply
720 formattedCat: `lsst.afw.table.SimpleCatalog`
721 SimpleCatalog suitable for using as a reference catalog
724 sourceMapper = afwTable.SchemaMapper(fgcmStarCat.schema)
725 minSchema = LoadIndexedReferenceObjectsTask.makeMinimalSchema(self.
bands,
729 sourceMapper.addMinimalSchema(minSchema)
730 for band
in self.
bands:
731 sourceMapper.editOutputSchema().addField(
'%s_nGood' % (band), type=np.int32)
732 sourceMapper.editOutputSchema().addField(
'%s_nTotal' % (band), type=np.int32)
733 sourceMapper.editOutputSchema().addField(
'%s_nPsfCandidate' % (band), type=np.int32)
735 formattedCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
736 formattedCat.reserve(len(fgcmStarCat))
737 formattedCat.extend(fgcmStarCat, mapper=sourceMapper)
741 for b, band
in enumerate(self.
bands):
742 mag = fgcmStarCat[
'mag_std_noabs'][:, b].astype(np.float64) + offsets[b]
745 flux = (mag*units.ABmag).to_value(units.nJy)
746 fluxErr = (np.log(10.)/2.5)*flux*fgcmStarCat[
'magErr_std'][:, b].astype(np.float64)
748 formattedCat[
'%s_flux' % (band)][:] = flux
749 formattedCat[
'%s_fluxErr' % (band)][:] = fluxErr
750 formattedCat[
'%s_nGood' % (band)][:] = fgcmStarCat[
'ngood'][:, b]
751 formattedCat[
'%s_nTotal' % (band)][:] = fgcmStarCat[
'ntotal'][:, b]
752 formattedCat[
'%s_nPsfCandidate' % (band)][:] = fgcmStarCat[
'npsfcand'][:, b]
754 addRefCatMetadata(formattedCat)
758 def _outputZeropoints(self, butler, zptCat, visitCat, offsets, tract=None):
760 Output the zeropoints in fgcm_photoCalib format.
764 butler: `lsst.daf.persistence.Butler`
765 zptCat: `lsst.afw.table.BaseCatalog`
766 FGCM zeropoint catalog from `FgcmFitCycleTask`
767 visitCat: `lsst.afw.table.BaseCatalog`
768 FGCM visitCat from `FgcmBuildStarsTask`
769 offsets: `numpy.array`
770 Float array of absolute calibration offsets, one for each filter
771 tract: `int`, optional
772 Tract number to output. Default is None (global calibration)
776 datasetType =
'fgcm_photoCalib'
778 datasetType =
'fgcm_tract_photoCalib'
780 self.log.info(
"Outputting %s objects" % (datasetType))
785 cannot_compute = fgcm.fgcmUtilities.zpFlagDict[
'CANNOT_COMPUTE_ZEROPOINT']
786 too_few_stars = fgcm.fgcmUtilities.zpFlagDict[
'TOO_FEW_STARS_ON_CCD']
787 selected = (((zptCat[
'fgcmFlag'] & cannot_compute) == 0) &
788 (zptCat[
'fgcmZptVar'] > 0.0))
792 selected_best = (((zptCat[
'fgcmFlag'] & (cannot_compute | too_few_stars)) == 0) &
793 (zptCat[
'fgcmZptVar'] > 0.0))
796 badVisits = np.unique(zptCat[
'visit'][~selected])
797 goodVisits = np.unique(zptCat[
'visit'][selected])
798 allBadVisits = badVisits[~np.isin(badVisits, goodVisits)]
799 for allBadVisit
in allBadVisits:
800 self.log.warn(f
'No suitable photoCalib for {self.visitDataRefName} {allBadVisit}')
805 for rec
in zptCat[selected_best]:
806 if rec[
'filtername']
in filterMapping:
810 dataRef = butler.dataRef(
'raw', dataId=dataId)
811 filterMapping[rec[
'filtername']] = dataRef.dataId[
'filter']
824 camera = butler.get(
'camera')
826 for ccdIndex, detector
in enumerate(camera):
827 ccdMapping[detector.getId()] = ccdIndex
832 scalingMapping[rec[
'visit']] = rec[
'scaling']
834 if self.config.doComposeWcsJacobian:
837 for rec
in zptCat[selected]:
840 scaling = scalingMapping[rec[
'visit']][ccdMapping[rec[
'ccd']]]
847 postCalibrationOffset = offsetMapping[rec[
'filtername']]
848 if self.config.doApplyMeanChromaticCorrection:
849 postCalibrationOffset += rec[
'fgcmDeltaChrom']
852 rec[
'fgcmfZptChebXyMax'])
855 rec[
'fgcmfZptChebXyMax'],
856 offset=postCalibrationOffset,
859 if self.config.doComposeWcsJacobian:
861 fgcmField = afwMath.ProductBoundedField([approxPixelAreaFields[rec[
'ccd']],
867 fgcmField = afwMath.ProductBoundedField([fgcmSuperStarField, fgcmZptField])
870 calibCenter = fgcmField.evaluate(fgcmField.getBBox().getCenter())
871 calibErr = (np.log(10.0)/2.5)*calibCenter*np.sqrt(rec[
'fgcmZptVar'])
872 photoCalib = afwImage.PhotoCalib(calibrationMean=calibCenter,
873 calibrationErr=calibErr,
874 calibration=fgcmField,
878 butler.put(photoCalib, datasetType,
881 'filter': filterMapping[rec[
'filtername']]})
883 butler.put(photoCalib, datasetType,
886 'filter': filterMapping[rec[
'filtername']],
889 self.log.info(
"Done outputting %s objects" % (datasetType))
891 def _getChebyshevBoundedField(self, coefficients, xyMax, offset=0.0, scaling=1.0):
893 Make a ChebyshevBoundedField from fgcm coefficients, with optional offset
898 coefficients: `numpy.array`
899 Flattened array of chebyshev coefficients
900 xyMax: `list` of length 2
901 Maximum x and y of the chebyshev bounding box
902 offset: `float`, optional
903 Absolute calibration offset. Default is 0.0
904 scaling: `float`, optional
905 Flat scaling value from fgcmBuildStars. Default is 1.0
909 boundedField: `lsst.afw.math.ChebyshevBoundedField`
912 orderPlus1 = int(np.sqrt(coefficients.size))
913 pars = np.zeros((orderPlus1, orderPlus1))
915 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0.0, 0.0),
916 lsst.geom.Point2I(*xyMax))
918 pars[:, :] = (coefficients.reshape(orderPlus1, orderPlus1) *
919 (10.**(offset/-2.5))*scaling)
921 boundedField = afwMath.ChebyshevBoundedField(bbox, pars)
925 def _outputAtmospheres(self, butler, atmCat, tract=None):
927 Output the atmospheres.
931 butler: `lsst.daf.persistence.Butler`
932 atmCat: `lsst.afw.table.BaseCatalog`
933 FGCM atmosphere parameter catalog from fgcmFitCycleTask
934 tract: `int`, optional
935 Tract number to output. Default is None (global calibration)
938 self.log.info(
"Outputting atmosphere transmissions")
941 lutCat = butler.get(
'fgcmLookUpTable')
943 atmosphereTableName = lutCat[0][
'tablename']
944 elevation = lutCat[0][
'elevation']
945 atmLambda = lutCat[0][
'atmLambda']
950 atmTable = fgcm.FgcmAtmosphereTable.initWithTableName(atmosphereTableName)
958 modGen = fgcm.ModtranGenerator(elevation)
959 lambdaRange = np.array([atmLambda[0], atmLambda[-1]])/10.
960 lambdaStep = (atmLambda[1] - atmLambda[0])/10.
961 except (ValueError, IOError)
as e:
962 raise RuntimeError(
"FGCM look-up-table generated with modtran, "
963 "but modtran not configured to run.")
from e
965 zenith = np.degrees(np.arccos(1./atmCat[
'secZenith']))
967 for i, visit
in enumerate(atmCat[
'visit']):
968 if atmTable
is not None:
970 atmVals = atmTable.interpolateAtmosphere(pmb=atmCat[i][
'pmb'],
971 pwv=atmCat[i][
'pwv'],
973 tau=atmCat[i][
'tau'],
974 alpha=atmCat[i][
'alpha'],
976 ctranslamstd=[atmCat[i][
'cTrans'],
977 atmCat[i][
'lamStd']])
980 modAtm = modGen(pmb=atmCat[i][
'pmb'],
981 pwv=atmCat[i][
'pwv'],
983 tau=atmCat[i][
'tau'],
984 alpha=atmCat[i][
'alpha'],
986 lambdaRange=lambdaRange,
987 lambdaStep=lambdaStep,
988 ctranslamstd=[atmCat[i][
'cTrans'],
989 atmCat[i][
'lamStd']])
990 atmVals = modAtm[
'COMBINED']
993 curve = TransmissionCurve.makeSpatiallyConstant(throughput=atmVals,
994 wavelengths=atmLambda,
995 throughputAtMin=atmVals[0],
996 throughputAtMax=atmVals[-1])
999 butler.put(curve,
"transmission_atmosphere_fgcm",
1002 butler.put(curve,
"transmission_atmosphere_fgcm_tract",
1006 self.log.info(
"Done outputting atmosphere transmissions")