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
288 Raised if any one of the following is true:
290 - butler cannot find "fgcmBuildStars_config" or
291 "fgcmBuildStarsTable_config".
292 - butler cannot find "fgcmFitCycle_config".
293 - "fgcmFitCycle_config" does not refer to
294 `self.config.cycleNumber`.
295 - butler cannot find "fgcmAtmosphereParameters" and
296 `self.config.doAtmosphereOutput` is `True`.
297 - butler cannot find "fgcmStandardStars" and
298 `self.config.doReferenceCalibration` is `True` or
299 `self.config.doRefcatOutput` is `True`.
300 - butler cannot find "fgcmZeropoints" and
301 `self.config.doZeropointOutput` is `True`.
306 if not butler.datasetExists(
'fgcmBuildStarsTable_config')
and \
307 not butler.datasetExists(
'fgcmBuildStars_config'):
308 raise RuntimeError(
"Cannot find fgcmBuildStarsTable_config or fgcmBuildStars_config, "
309 "which is prereq for fgcmOutputProducts")
311 if butler.datasetExists(
'fgcmBuildStarsTable_config'):
312 fgcmBuildStarsConfig = butler.get(
'fgcmBuildStarsTable_config')
314 fgcmBuildStarsConfig = butler.get(
'fgcmBuildStars_config')
319 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
320 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
321 "in fgcmBuildStarsTask.")
323 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
324 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
327 if not butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber):
328 raise RuntimeError(
"Cannot find fgcmFitCycle_config from cycle %d " % (self.config.cycleNumber) +
329 "which is required for fgcmOutputProducts.")
331 fitCycleConfig = butler.get(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber)
334 if self.config.doReferenceCalibration
and fitCycleConfig.doReferenceCalibration:
335 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
336 "fitCycleConfig.doReferenceCalibration")
339 if (self.config.doAtmosphereOutput
and
340 not butler.datasetExists(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)):
341 raise RuntimeError(
"Atmosphere parameters are missing for cycle %d." %
342 (self.config.cycleNumber))
344 if ((self.config.doReferenceCalibration
or self.config.doRefcatOutput)
and
345 (
not butler.datasetExists(
'fgcmStandardStars',
346 fgcmcycle=self.config.cycleNumber))):
347 raise RuntimeError(
"Standard stars are missing for cycle %d." %
348 (self.config.cycleNumber))
350 if (self.config.doZeropointOutput
and
351 (
not butler.datasetExists(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber))):
352 raise RuntimeError(
"Zeropoints are missing for cycle %d." %
353 (self.config.cycleNumber))
356 if butler.datasetExists(
'fgcmFitCycle_config', fgcmcycle=self.config.cycleNumber + 1):
357 raise RuntimeError(
"The task fgcmOutputProducts should only be run"
358 "on the final fit cycle products")
360 if self.config.doReferenceCalibration
or self.config.doRefcatOutput:
361 stdCat = butler.get(
'fgcmStandardStars', fgcmcycle=self.config.cycleNumber)
362 md = stdCat.getMetadata()
368 if self.config.doReferenceCalibration:
371 offsets = np.zeros(len(self.
bands))
374 if self.config.doRefcatOutput:
380 if self.config.doZeropointOutput:
381 zptCat = butler.get(
'fgcmZeropoints', fgcmcycle=self.config.cycleNumber)
382 visitCat = butler.get(
'fgcmVisitCatalog')
387 if self.config.doAtmosphereOutput:
388 atmCat = butler.get(
'fgcmAtmosphereParameters', fgcmcycle=self.config.cycleNumber)
392 return pipeBase.Struct(offsets=offsets)
395 visitCat, zptCat, atmCat, stdCat,
396 fgcmBuildStarsConfig, fgcmFitCycleConfig):
398 Generate the output products for a given tract, as specified in the config.
400 This method is here to have an alternate entry-point for
405 butler: `lsst.daf.persistence.Butler`
408 visitCat: `lsst.afw.table.BaseCatalog`
409 FGCM visitCat from `FgcmBuildStarsTask`
410 zptCat: `lsst.afw.table.BaseCatalog`
411 FGCM zeropoint catalog from `FgcmFitCycleTask`
412 atmCat: `lsst.afw.table.BaseCatalog`
413 FGCM atmosphere parameter catalog from `FgcmFitCycleTask`
414 stdCat: `lsst.afw.table.SimpleCatalog`
415 FGCM standard star catalog from `FgcmFitCycleTask`
416 fgcmBuildStarsConfig: `lsst.fgcmcal.FgcmBuildStarsConfig`
417 Configuration object from `FgcmBuildStarsTask`
418 fgcmFitCycleConfig: `lsst.fgcmcal.FgcmFitCycleConfig`
419 Configuration object from `FgcmFitCycleTask`
425 self.
filterMap = fgcmBuildStarsConfig.filterMap
427 if stdCat
is not None:
428 md = stdCat.getMetadata()
429 self.
bands = md.getArray(
'BANDS')
433 if self.config.doReferenceCalibration
and fgcmFitCycleConfig.doReferenceCalibration:
434 self.log.warn(
"doReferenceCalibration is set, and is possibly redundant with "
435 "fitCycleConfig.doReferenceCalibration")
437 if self.config.doComposeWcsJacobian
and not fgcmBuildStarsConfig.doApplyWcsJacobian:
438 raise RuntimeError(
"Cannot compose the WCS jacobian if it hasn't been applied "
439 "in fgcmBuildStarsTask.")
441 if not self.config.doComposeWcsJacobian
and fgcmBuildStarsConfig.doApplyWcsJacobian:
442 self.log.warn(
"Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")
444 if self.config.doReferenceCalibration:
447 offsets = np.zeros(len(self.
bands))
449 if self.config.doRefcatOutput:
451 datasetConfig = copy.copy(self.config.datasetConfig)
452 datasetConfig.ref_dataset_name =
'%s_%d' % (self.config.datasetConfig.ref_dataset_name,
456 if self.config.doZeropointOutput:
459 if self.config.doAtmosphereOutput:
462 return pipeBase.Struct(offsets=offsets)
464 def _computeReferenceOffsets(self, butler, stdCat):
466 Compute offsets relative to a reference catalog.
468 This method splits the star catalog into healpix pixels
469 and computes the calibration transfer for a sample of
470 these pixels to approximate the 'absolute' calibration
471 values (on for each band) to apply to transfer the
476 butler: `lsst.daf.persistence.Butler`
477 stdCat: `lsst.afw.table.SimpleCatalog`
482 offsets: `numpy.array` of floats
483 Per band zeropoint offsets
489 minObs = stdCat[
'ngood'].min(axis=1)
491 goodStars = (minObs >= 1)
492 stdCat = stdCat[goodStars]
494 self.log.info(
"Found %d stars with at least 1 good observation in each band" %
504 sourceMapper = afwTable.SchemaMapper(stdCat.schema)
505 sourceMapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
506 sourceMapper.editOutputSchema().addField(
'instFlux', type=np.float64,
507 doc=
"instrumental flux (counts)")
508 sourceMapper.editOutputSchema().addField(
'instFluxErr', type=np.float64,
509 doc=
"instrumental flux error (counts)")
510 badStarKey = sourceMapper.editOutputSchema().addField(
'flag_badStar',
518 theta = np.pi/2. - stdCat[
'coord_dec']
519 phi = stdCat[
'coord_ra']
521 ipring = hp.ang2pix(self.config.referencePixelizationNside, theta, phi)
522 h, rev = esutil.stat.histogram(ipring, rev=
True)
524 gdpix, = np.where(h >= self.config.referencePixelizationMinStars)
526 self.log.info(
"Found %d pixels (nside=%d) with at least %d good stars" %
528 self.config.referencePixelizationNside,
529 self.config.referencePixelizationMinStars))
531 if gdpix.size < self.config.referencePixelizationNPixels:
532 self.log.warn(
"Found fewer good pixels (%d) than preferred in configuration (%d)" %
533 (gdpix.size, self.config.referencePixelizationNPixels))
536 gdpix = np.random.choice(gdpix, size=self.config.referencePixelizationNPixels, replace=
False)
538 results = np.zeros(gdpix.size, dtype=[(
'hpix',
'i4'),
539 (
'nstar',
'i4', len(self.
bands)),
540 (
'nmatch',
'i4', len(self.
bands)),
541 (
'zp',
'f4', len(self.
bands)),
542 (
'zpErr',
'f4', len(self.
bands))])
543 results[
'hpix'] = ipring[rev[rev[gdpix]]]
546 selected = np.zeros(len(stdCat), dtype=np.bool)
548 refFluxFields = [
None]*len(self.
bands)
550 for p, pix
in enumerate(gdpix):
551 i1a = rev[rev[pix]: rev[pix + 1]]
559 for b, band
in enumerate(self.
bands):
562 selected, refFluxFields)
563 results[
'nstar'][p, b] = len(i1a)
564 results[
'nmatch'][p, b] = len(struct.arrays.refMag)
565 results[
'zp'][p, b] = struct.zp
566 results[
'zpErr'][p, b] = struct.sigma
569 offsets = np.zeros(len(self.
bands))
571 for b, band
in enumerate(self.
bands):
573 ok, = np.where(results[
'nmatch'][:, b] >= self.config.referenceMinMatch)
574 offsets[b] = np.median(results[
'zp'][ok, b])
577 madSigma = 1.4826*np.median(np.abs(results[
'zp'][ok, b] - offsets[b]))
578 self.log.info(
"Reference catalog offset for %s band: %.12f +/- %.12f" %
579 (band, offsets[b], madSigma))
583 def _computeOffsetOneBand(self, sourceMapper, badStarKey,
584 b, band, stdCat, selected, refFluxFields):
586 Compute the zeropoint offset between the fgcm stdCat and the reference
587 stars for one pixel in one band
591 sourceMapper: `lsst.afw.table.SchemaMapper`
592 Mapper to go from stdCat to calibratable catalog
593 badStarKey: `lsst.afw.table.Key`
594 Key for the field with bad stars
596 Index of the band in the star catalog
598 Name of band for reference catalog
599 stdCat: `lsst.afw.table.SimpleCatalog`
601 selected: `numpy.array(dtype=np.bool)`
602 Boolean array of which stars are in the pixel
603 refFluxFields: `list`
604 List of names of flux fields for reference catalog
607 sourceCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
608 sourceCat.reserve(selected.sum())
609 sourceCat.extend(stdCat[selected], mapper=sourceMapper)
610 sourceCat[
'instFlux'] = 10.**(stdCat[
'mag_std_noabs'][selected, b]/(-2.5))
611 sourceCat[
'instFluxErr'] = (np.log(10.)/2.5)*(stdCat[
'magErr_std'][selected, b] *
612 sourceCat[
'instFlux'])
616 badStar = (stdCat[
'mag_std_noabs'][selected, b] > 90.0)
617 for rec
in sourceCat[badStar]:
618 rec.set(badStarKey,
True)
620 exposure = afwImage.ExposureF()
621 exposure.setFilter(afwImage.Filter(band))
623 if refFluxFields[b]
is None:
626 ctr = stdCat[0].getCoord()
627 rad = 0.05*lsst.geom.degrees
628 refDataTest = self.refObjLoader.loadSkyCircle(ctr, rad, band)
629 refFluxFields[b] = refDataTest.fluxField
632 calConfig = copy.copy(self.config.photoCal.value)
633 calConfig.match.referenceSelection.signalToNoise.fluxField = refFluxFields[b]
634 calConfig.match.referenceSelection.signalToNoise.errField = refFluxFields[b] +
'Err'
635 calTask = self.config.photoCal.target(refObjLoader=self.refObjLoader,
637 schema=sourceCat.getSchema())
639 struct = calTask.run(exposure, sourceCat)
643 def _outputStandardStars(self, butler, stdCat, offsets, datasetConfig):
645 Output standard stars in indexed reference catalog format.
649 butler: `lsst.daf.persistence.Butler`
650 stdCat: `lsst.afw.table.SimpleCatalog`
651 FGCM standard star catalog from fgcmFitCycleTask
652 offsets: `numpy.array` of floats
653 Per band zeropoint offsets
654 datasetConfig: `lsst.meas.algorithms.DatasetConfig`
655 Config for reference dataset
658 self.log.info(
"Outputting standard stars to %s" % (datasetConfig.ref_dataset_name))
665 conv = stdCat[0][
'coord_ra'].asDegrees()/float(stdCat[0][
'coord_ra'])
666 indices = np.array(self.
indexer.indexPoints(stdCat[
'coord_ra']*conv,
667 stdCat[
'coord_dec']*conv))
672 dataId = self.
indexer.makeDataId(
'master_schema',
673 datasetConfig.ref_dataset_name)
674 masterCat = afwTable.SimpleCatalog(formattedCat.schema)
675 addRefCatMetadata(masterCat)
676 butler.put(masterCat,
'ref_cat', dataId=dataId)
679 h, rev = esutil.stat.histogram(indices, rev=
True)
680 gd, = np.where(h > 0)
681 selected = np.zeros(len(formattedCat), dtype=np.bool)
683 i1a = rev[rev[i]: rev[i + 1]]
692 dataId = self.
indexer.makeDataId(indices[i1a[0]],
693 datasetConfig.ref_dataset_name)
694 butler.put(formattedCat[selected],
'ref_cat', dataId=dataId)
697 dataId = self.
indexer.makeDataId(
None, datasetConfig.ref_dataset_name)
698 butler.put(datasetConfig,
'ref_cat_config', dataId=dataId)
700 self.log.info(
"Done outputting standard stars.")
702 def _formatCatalog(self, fgcmStarCat, offsets):
704 Turn an FGCM-formatted star catalog, applying zeropoint offsets.
708 fgcmStarCat: `lsst.afw.Table.SimpleCatalog`
709 SimpleCatalog as output by fgcmcal
710 offsets: `list` with len(self.bands) entries
711 Zeropoint offsets to apply
715 formattedCat: `lsst.afw.table.SimpleCatalog`
716 SimpleCatalog suitable for using as a reference catalog
719 sourceMapper = afwTable.SchemaMapper(fgcmStarCat.schema)
720 minSchema = LoadIndexedReferenceObjectsTask.makeMinimalSchema(self.
bands,
724 sourceMapper.addMinimalSchema(minSchema)
725 for band
in self.
bands:
726 sourceMapper.editOutputSchema().addField(
'%s_nGood' % (band), type=np.int32)
727 sourceMapper.editOutputSchema().addField(
'%s_nTotal' % (band), type=np.int32)
728 sourceMapper.editOutputSchema().addField(
'%s_nPsfCandidate' % (band), type=np.int32)
730 formattedCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
731 formattedCat.reserve(len(fgcmStarCat))
732 formattedCat.extend(fgcmStarCat, mapper=sourceMapper)
736 for b, band
in enumerate(self.
bands):
737 mag = fgcmStarCat[
'mag_std_noabs'][:, b].astype(np.float64) + offsets[b]
740 flux = (mag*units.ABmag).to_value(units.nJy)
741 fluxErr = (np.log(10.)/2.5)*flux*fgcmStarCat[
'magErr_std'][:, b].astype(np.float64)
743 formattedCat[
'%s_flux' % (band)][:] = flux
744 formattedCat[
'%s_fluxErr' % (band)][:] = fluxErr
745 formattedCat[
'%s_nGood' % (band)][:] = fgcmStarCat[
'ngood'][:, b]
746 formattedCat[
'%s_nTotal' % (band)][:] = fgcmStarCat[
'ntotal'][:, b]
747 formattedCat[
'%s_nPsfCandidate' % (band)][:] = fgcmStarCat[
'npsfcand'][:, b]
749 addRefCatMetadata(formattedCat)
753 def _outputZeropoints(self, butler, zptCat, visitCat, offsets, tract=None):
755 Output the zeropoints in fgcm_photoCalib format.
759 butler: `lsst.daf.persistence.Butler`
760 zptCat: `lsst.afw.table.BaseCatalog`
761 FGCM zeropoint catalog from `FgcmFitCycleTask`
762 visitCat: `lsst.afw.table.BaseCatalog`
763 FGCM visitCat from `FgcmBuildStarsTask`
764 offsets: `numpy.array`
765 Float array of absolute calibration offsets, one for each filter
766 tract: `int`, optional
767 Tract number to output. Default is None (global calibration)
771 datasetType =
'fgcm_photoCalib'
773 datasetType =
'fgcm_tract_photoCalib'
775 self.log.info(
"Outputting %s objects" % (datasetType))
780 cannot_compute = fgcm.fgcmUtilities.zpFlagDict[
'CANNOT_COMPUTE_ZEROPOINT']
781 too_few_stars = fgcm.fgcmUtilities.zpFlagDict[
'TOO_FEW_STARS_ON_CCD']
782 selected = (((zptCat[
'fgcmFlag'] & cannot_compute) == 0) &
783 (zptCat[
'fgcmZptVar'] > 0.0))
787 selected_best = (((zptCat[
'fgcmFlag'] & (cannot_compute | too_few_stars)) == 0) &
788 (zptCat[
'fgcmZptVar'] > 0.0))
791 badVisits = np.unique(zptCat[
'visit'][~selected])
792 goodVisits = np.unique(zptCat[
'visit'][selected])
793 allBadVisits = badVisits[~np.isin(badVisits, goodVisits)]
794 for allBadVisit
in allBadVisits:
795 self.log.warn(f
'No suitable photoCalib for {self.visitDataRefName} {allBadVisit}')
800 for rec
in zptCat[selected_best]:
801 if rec[
'filtername']
in filterMapping:
805 dataRef = butler.dataRef(
'raw', dataId=dataId)
806 filterMapping[rec[
'filtername']] = dataRef.dataId[
'filter']
819 camera = butler.get(
'camera')
821 for ccdIndex, detector
in enumerate(camera):
822 ccdMapping[detector.getId()] = ccdIndex
827 scalingMapping[rec[
'visit']] = rec[
'scaling']
829 if self.config.doComposeWcsJacobian:
832 for rec
in zptCat[selected]:
835 scaling = scalingMapping[rec[
'visit']][ccdMapping[rec[
'ccd']]]
838 rec[
'fgcmfZptChebXyMax'])
841 rec[
'fgcmfZptChebXyMax'],
842 offset=offsetMapping[rec[
'filtername']],
845 if self.config.doComposeWcsJacobian:
847 fgcmField = afwMath.ProductBoundedField([approxPixelAreaFields[rec[
'ccd']],
853 fgcmField = afwMath.ProductBoundedField([fgcmSuperStarField, fgcmZptField])
856 calibCenter = fgcmField.evaluate(fgcmField.getBBox().getCenter())
857 calibErr = (np.log(10.0)/2.5)*calibCenter*np.sqrt(rec[
'fgcmZptVar'])
858 photoCalib = afwImage.PhotoCalib(calibrationMean=calibCenter,
859 calibrationErr=calibErr,
860 calibration=fgcmField,
864 butler.put(photoCalib, datasetType,
867 'filter': filterMapping[rec[
'filtername']]})
869 butler.put(photoCalib, datasetType,
872 'filter': filterMapping[rec[
'filtername']],
875 self.log.info(
"Done outputting %s objects" % (datasetType))
877 def _getChebyshevBoundedField(self, coefficients, xyMax, offset=0.0, scaling=1.0):
879 Make a ChebyshevBoundedField from fgcm coefficients, with optional offset
884 coefficients: `numpy.array`
885 Flattened array of chebyshev coefficients
886 xyMax: `list` of length 2
887 Maximum x and y of the chebyshev bounding box
888 offset: `float`, optional
889 Absolute calibration offset. Default is 0.0
890 scaling: `float`, optional
891 Flat scaling value from fgcmBuildStars. Default is 1.0
895 boundedField: `lsst.afw.math.ChebyshevBoundedField`
898 orderPlus1 = int(np.sqrt(coefficients.size))
899 pars = np.zeros((orderPlus1, orderPlus1))
901 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0.0, 0.0),
902 lsst.geom.Point2I(*xyMax))
904 pars[:, :] = (coefficients.reshape(orderPlus1, orderPlus1) *
905 (10.**(offset/-2.5))*scaling)
907 boundedField = afwMath.ChebyshevBoundedField(bbox, pars)
911 def _outputAtmospheres(self, butler, atmCat, tract=None):
913 Output the atmospheres.
917 butler: `lsst.daf.persistence.Butler`
918 atmCat: `lsst.afw.table.BaseCatalog`
919 FGCM atmosphere parameter catalog from fgcmFitCycleTask
920 tract: `int`, optional
921 Tract number to output. Default is None (global calibration)
924 self.log.info(
"Outputting atmosphere transmissions")
927 lutCat = butler.get(
'fgcmLookUpTable')
929 atmosphereTableName = lutCat[0][
'tablename']
930 elevation = lutCat[0][
'elevation']
931 atmLambda = lutCat[0][
'atmLambda']
936 atmTable = fgcm.FgcmAtmosphereTable.initWithTableName(atmosphereTableName)
944 modGen = fgcm.ModtranGenerator(elevation)
945 lambdaRange = np.array([atmLambda[0], atmLambda[-1]])/10.
946 lambdaStep = (atmLambda[1] - atmLambda[0])/10.
947 except (ValueError, IOError)
as e:
948 raise RuntimeError(
"FGCM look-up-table generated with modtran, "
949 "but modtran not configured to run.")
from e
951 zenith = np.degrees(np.arccos(1./atmCat[
'secZenith']))
953 for i, visit
in enumerate(atmCat[
'visit']):
954 if atmTable
is not None:
956 atmVals = atmTable.interpolateAtmosphere(pmb=atmCat[i][
'pmb'],
957 pwv=atmCat[i][
'pwv'],
959 tau=atmCat[i][
'tau'],
960 alpha=atmCat[i][
'alpha'],
962 ctranslamstd=[atmCat[i][
'cTrans'],
963 atmCat[i][
'lamStd']])
966 modAtm = modGen(pmb=atmCat[i][
'pmb'],
967 pwv=atmCat[i][
'pwv'],
969 tau=atmCat[i][
'tau'],
970 alpha=atmCat[i][
'alpha'],
972 lambdaRange=lambdaRange,
973 lambdaStep=lambdaStep,
974 ctranslamstd=[atmCat[i][
'cTrans'],
975 atmCat[i][
'lamStd']])
976 atmVals = modAtm[
'COMBINED']
979 curve = TransmissionCurve.makeSpatiallyConstant(throughput=atmVals,
980 wavelengths=atmLambda,
981 throughputAtMin=atmVals[0],
982 throughputAtMax=atmVals[-1])
985 butler.put(curve,
"transmission_atmosphere_fgcm",
988 butler.put(curve,
"transmission_atmosphere_fgcm_tract",
992 self.log.info(
"Done outputting atmosphere transmissions")