21 """Base class for BuildStars using src tables or sourceTable_visit tables.
39 from .utilities
import computeApertureRadiusFromDataRef
40 from .fgcmLoadReferenceCatalog
import FgcmLoadReferenceCatalogTask
44 REFSTARS_FORMAT_VERSION = 1
46 __all__ = [
'FgcmBuildStarsConfigBase',
'FgcmBuildStarsRunner',
'FgcmBuildStarsBaseTask']
50 """Base config for FgcmBuildStars tasks"""
52 instFluxField = pexConfig.Field(
53 doc=(
"Faull name of the source instFlux field to use, including 'instFlux'. "
54 "The associated flag will be implicitly included in badFlags"),
56 default=
'slot_CalibFlux_instFlux',
58 minPerBand = pexConfig.Field(
59 doc=
"Minimum observations per band",
63 matchRadius = pexConfig.Field(
64 doc=
"Match radius (arcseconds)",
68 isolationRadius = pexConfig.Field(
69 doc=
"Isolation radius (arcseconds)",
73 densityCutNside = pexConfig.Field(
74 doc=
"Density cut healpix nside",
78 densityCutMaxPerPixel = pexConfig.Field(
79 doc=
"Density cut number of stars per pixel",
83 matchNside = pexConfig.Field(
84 doc=
"Healpix Nside for matching",
88 coarseNside = pexConfig.Field(
89 doc=
"Healpix coarse Nside for partitioning matches",
93 filterMap = pexConfig.DictField(
94 doc=
"Mapping from 'filterName' to band.",
99 requiredBands = pexConfig.ListField(
100 doc=
"Bands required for each star",
104 primaryBands = pexConfig.ListField(
105 doc=(
"Bands for 'primary' star matches. "
106 "A star must be observed in one of these bands to be considered "
107 "as a calibration star."),
111 visitDataRefName = pexConfig.Field(
112 doc=
"dataRef name for the 'visit' field, usually 'visit'.",
116 ccdDataRefName = pexConfig.Field(
117 doc=
"dataRef name for the 'ccd' field, usually 'ccd' or 'detector'.",
121 doApplyWcsJacobian = pexConfig.Field(
122 doc=
"Apply the jacobian of the WCS to the star observations prior to fit?",
126 psfCandidateName = pexConfig.Field(
127 doc=
"Name of field with psf candidate flag for propagation",
129 default=
"calib_psf_candidate"
131 doSubtractLocalBackground = pexConfig.Field(
132 doc=(
"Subtract the local background before performing calibration? "
133 "This is only supported for circular aperture calibration fluxes."),
137 localBackgroundFluxField = pexConfig.Field(
138 doc=
"Full name of the local background instFlux field to use.",
140 default=
'base_LocalBackground_instFlux'
142 sourceSelector = sourceSelectorRegistry.makeField(
143 doc=
"How to select sources",
146 apertureInnerInstFluxField = pexConfig.Field(
147 doc=(
"Full name of instFlux field that contains inner aperture "
148 "flux for aperture correction proxy"),
150 default=
'base_CircularApertureFlux_12_0_instFlux'
152 apertureOuterInstFluxField = pexConfig.Field(
153 doc=(
"Full name of instFlux field that contains outer aperture "
154 "flux for aperture correction proxy"),
156 default=
'base_CircularApertureFlux_17_0_instFlux'
158 doReferenceMatches = pexConfig.Field(
159 doc=
"Match reference catalog as additional constraint on calibration",
163 fgcmLoadReferenceCatalog = pexConfig.ConfigurableField(
164 target=FgcmLoadReferenceCatalogTask,
165 doc=
"FGCM reference object loader",
167 nVisitsPerCheckpoint = pexConfig.Field(
168 doc=
"Number of visits read between checkpoints",
175 sourceSelector.setDefaults()
177 sourceSelector.doFlags =
True
178 sourceSelector.doUnresolved =
True
179 sourceSelector.doSignalToNoise =
True
180 sourceSelector.doIsolated =
True
182 sourceSelector.signalToNoise.minimum = 10.0
183 sourceSelector.signalToNoise.maximum = 1000.0
187 sourceSelector.unresolved.maximum = 0.5
191 """Subclass of TaskRunner for FgcmBuildStars tasks
193 fgcmBuildStarsTask.run() and fgcmBuildStarsTableTask.run() take a number of
194 arguments, one of which is the butler (for persistence and mapper data),
195 and a list of dataRefs extracted from the command line. Note that FGCM
196 runs on a large set of dataRefs, and not on single dataRef/tract/patch.
197 This class transforms the process arguments generated by the ArgumentParser
198 into the arguments expected by FgcmBuildStarsTask.run(). This runner does
199 not use any parallelization.
204 Return a list with one element: a tuple with the butler and
208 return [(parsedCmd.butler, parsedCmd.id.refList)]
214 args: `tuple` with (butler, dataRefList)
218 exitStatus: `list` with `lsst.pipe.base.Struct`
219 exitStatus (0: success; 1: failure)
221 butler, dataRefList = args
223 task = self.TaskClass(config=self.config, log=self.log)
227 task.runDataRef(butler, dataRefList)
230 task.runDataRef(butler, dataRefList)
231 except Exception
as e:
233 task.log.fatal(
"Failed: %s" % e)
234 if not isinstance(e, pipeBase.TaskError):
235 traceback.print_exc(file=sys.stderr)
237 task.writeMetadata(butler)
240 return [pipeBase.Struct(exitStatus=exitStatus)]
244 Run the task, with no multiprocessing
248 parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line
253 if self.precall(parsedCmd):
255 resultList = self(targetList[0])
262 Base task to build stars for FGCM global calibration
266 butler : `lsst.daf.persistence.Butler`
269 pipeBase.CmdLineTask.__init__(self, **kwargs)
270 self.makeSubtask(
"sourceSelector")
272 self.sourceSelector.log.setLevel(self.sourceSelector.log.WARN)
275 def _getMetadataName(self):
281 Cross-match and make star list for FGCM Input
285 butler: `lsst.daf.persistence.Butler`
286 dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef`
287 Source data references for the input visits.
291 RuntimeErrror: Raised if `config.doReferenceMatches` is set and
292 an fgcmLookUpTable is not available, or if computeFluxApertureRadius()
293 fails if the calibFlux is not a CircularAperture flux.
295 datasetType = dataRefs[0].butlerSubset.datasetType
296 self.log.
info(
"Running with %d %s dataRefs", len(dataRefs), datasetType)
298 if self.config.doReferenceMatches:
300 if not butler.datasetExists(
'fgcmLookUpTable'):
301 raise RuntimeError(
"Must have fgcmLookUpTable if using config.doReferenceMatches")
304 calibFluxApertureRadius =
None
305 if self.config.doSubtractLocalBackground:
308 self.config.instFluxField)
309 except RuntimeError
as e:
310 raise RuntimeError(
"Could not determine aperture radius from %s. "
311 "Cannot use doSubtractLocalBackground." %
312 (self.config.instFluxField))
from e
316 camera = butler.get(
'camera')
323 visitCatDataRef = butler.dataRef(
'fgcmVisitCatalog')
324 filename = visitCatDataRef.get(
'fgcmVisitCatalog_filename')[0]
325 if os.path.exists(filename):
327 inVisitCat = visitCatDataRef.get()
328 if len(inVisitCat) != len(groupedDataRefs):
329 raise RuntimeError(
"Existing visitCatalog found, but has an inconsistent "
330 "number of visits. Cannot continue.")
335 visitCatDataRef=visitCatDataRef,
336 inVisitCat=inVisitCat)
339 visitCatDataRef.put(visitCat)
341 starObsDataRef = butler.dataRef(
'fgcmStarObservations')
342 filename = starObsDataRef.get(
'fgcmStarObservations_filename')[0]
343 if os.path.exists(filename):
344 inStarObsCat = starObsDataRef.get()
348 rad = calibFluxApertureRadius
351 calibFluxApertureRadius=rad,
352 starObsDataRef=starObsDataRef,
353 visitCatDataRef=visitCatDataRef,
354 inStarObsCat=inStarObsCat)
355 visitCatDataRef.put(visitCat)
356 starObsDataRef.put(fgcmStarObservationCat)
359 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = self.
fgcmMatchStars(butler,
361 fgcmStarObservationCat)
364 butler.put(fgcmStarIdCat,
'fgcmStarIds')
365 butler.put(fgcmStarIndicesCat,
'fgcmStarIndices')
366 if fgcmRefCat
is not None:
367 butler.put(fgcmRefCat,
'fgcmReferenceStars')
372 Find and group dataRefs (by visit).
376 butler: `lsst.daf.persistence.Butler`
377 dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef`
378 Data references for the input visits.
382 groupedDataRefs: `dict` [`int`, `list`]
383 Dictionary with visit keys, and `list`s of `lsst.daf.persistence.ButlerDataRef`
385 raise NotImplementedError(
"findAndGroupDataRefs not implemented.")
389 calibFluxApertureRadius=None,
390 visitCatDataRef=None,
394 Compile all good star observations from visits in visitCat. Checkpoint files
395 will be stored if both visitCatDataRef and starObsDataRef are not None.
399 groupedDataRefs: `dict` of `list`s
400 Lists of `lsst.daf.persistence.ButlerDataRef`, grouped by visit.
401 visitCat: `afw.table.BaseCatalog`
402 Catalog with visit data for FGCM
403 calibFluxApertureRadius: `float`, optional
404 Aperture radius for calibration flux.
405 visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional
406 Dataref to write visitCat for checkpoints
407 starObsDataRef: `lsst.daf.persistence.ButlerDataRef`, optional
408 Dataref to write the star observation catalog for checkpoints.
409 inStarObsCat: `afw.table.BaseCatalog`
410 Input observation catalog. If this is incomplete, observations
411 will be appended from when it was cut off.
415 fgcmStarObservations: `afw.table.BaseCatalog`
416 Full catalog of good observations.
420 RuntimeError: Raised if doSubtractLocalBackground is True and
421 calibFluxApertureRadius is not set.
423 raise NotImplementedError(
"fgcmMakeAllStarObservations not implemented.")
426 visitCatDataRef=None, inVisitCat=None):
428 Make a visit catalog with all the keys from each visit
432 camera: `lsst.afw.cameraGeom.Camera`
433 Camera from the butler
434 groupedDataRefs: `dict`
435 Dictionary with visit keys, and `list`s of
436 `lsst.daf.persistence.ButlerDataRef`
437 visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional
438 Dataref to write visitCat for checkpoints
439 inVisitCat: `afw.table.BaseCatalog`
440 Input (possibly incomplete) visit catalog
444 visitCat: `afw.table.BaseCatalog`
447 self.log.
info(
"Assembling visitCatalog from %d %ss" %
448 (len(groupedDataRefs), self.config.visitDataRefName))
452 if inVisitCat
is None:
456 visitCat.reserve(len(groupedDataRefs))
458 for i, visit
in enumerate(sorted(groupedDataRefs)):
459 rec = visitCat.addNew()
462 rec[
'sources_read'] = 0
464 visitCat = inVisitCat
469 visitCatDataRef=visitCatDataRef)
473 def _fillVisitCatalog(self, visitCat, groupedDataRefs,
474 visitCatDataRef=None):
476 Fill the visit catalog with visit metadata
480 visitCat: `afw.table.BaseCatalog`
481 Catalog with schema from _makeFgcmVisitSchema()
482 groupedDataRefs: `dict`
483 Dictionary with visit keys, and `list`s of
484 `lsst.daf.persistence.ButlerDataRef
485 visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional
486 Dataref to write visitCat for checkpoints
491 for i, visit
in enumerate(sorted(groupedDataRefs)):
498 if visitCat[
'used'][i]:
501 if (i % self.config.nVisitsPerCheckpoint) == 0:
502 self.log.
info(
"Retrieving metadata for %s %d (%d/%d)" %
503 (self.config.visitDataRefName, visit, i, len(groupedDataRefs)))
505 if visitCatDataRef
is not None:
506 visitCatDataRef.put(visitCat)
511 dataRef = groupedDataRefs[visit][0]
513 exp = dataRef.get(datasetType=
'calexp_sub', bbox=bbox,
514 flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
516 visitInfo = exp.getInfo().getVisitInfo()
522 rec[
'filtername'] = f.getName()
526 radec = visitInfo.getBoresightRaDec()
527 rec[
'telra'] = radec.getRa().asDegrees()
528 rec[
'teldec'] = radec.getDec().asDegrees()
529 rec[
'telha'] = visitInfo.getBoresightHourAngle().asDegrees()
530 rec[
'telrot'] = visitInfo.getBoresightRotAngle().asDegrees()
531 rec[
'mjd'] = visitInfo.getDate().get(system=DateTime.MJD)
532 rec[
'exptime'] = visitInfo.getExposureTime()
535 rec[
'pmb'] = visitInfo.getWeather().getAirPressure() / 100
539 rec[
'scaling'][:] = 1.0
541 rec[
'deltaAper'] = 0.0
543 rec[
'psfSigma'] = psf.computeShape().getDeterminantRadius()
545 if dataRef.datasetExists(datasetType=
'calexpBackground'):
548 bgStats = (bg[0].getStatsImage().getImage().array
549 for bg
in dataRef.get(datasetType=
'calexpBackground'))
550 rec[
'skyBackground'] = sum(np.median(bg[np.isfinite(bg)])
for bg
in bgStats)
552 self.log.
warn(
'Sky background not found for visit %d / ccd %d' %
553 (visit, dataRef.dataId[self.config.ccdDataRefName]))
554 rec[
'skyBackground'] = -1.0
558 def _makeSourceMapper(self, sourceSchema):
560 Make a schema mapper for fgcm sources
564 sourceSchema: `afwTable.Schema`
565 Default source schema from the butler
569 sourceMapper: `afwTable.schemaMapper`
570 Mapper to the FGCM source schema
577 sourceMapper.addMapping(sourceSchema[
'coord_ra'].asKey(),
'ra')
578 sourceMapper.addMapping(sourceSchema[
'coord_dec'].asKey(),
'dec')
579 sourceMapper.addMapping(sourceSchema[
'slot_Centroid_x'].asKey(),
'x')
580 sourceMapper.addMapping(sourceSchema[
'slot_Centroid_y'].asKey(),
'y')
586 sourceMapper.addMapping(sourceSchema[self.config.psfCandidateName].asKey(),
589 sourceMapper.editOutputSchema().addField(
590 "psf_candidate", type=
'Flag',
591 doc=(
"Flag set if the source was a candidate for PSF determination, "
592 "as determined by the star selector."))
595 sourceMapper.editOutputSchema().addField(
596 "visit", type=np.int32, doc=
"Visit number")
597 sourceMapper.editOutputSchema().addField(
598 "ccd", type=np.int32, doc=
"CCD number")
599 sourceMapper.editOutputSchema().addField(
600 "instMag", type=np.float32, doc=
"Instrumental magnitude")
601 sourceMapper.editOutputSchema().addField(
602 "instMagErr", type=np.float32, doc=
"Instrumental magnitude error")
603 sourceMapper.editOutputSchema().addField(
604 "jacobian", type=np.float32, doc=
"Relative pixel scale from wcs jacobian")
605 sourceMapper.editOutputSchema().addField(
606 "deltaMagBkg", type=np.float32, doc=
"Change in magnitude due to local background offset")
612 Use FGCM code to match observations into unique stars.
616 butler: `lsst.daf.persistence.Butler`
617 visitCat: `afw.table.BaseCatalog`
618 Catalog with visit data for fgcm
619 obsCat: `afw.table.BaseCatalog`
620 Full catalog of star observations for fgcm
624 fgcmStarIdCat: `afw.table.BaseCatalog`
625 Catalog of unique star identifiers and index keys
626 fgcmStarIndicesCat: `afwTable.BaseCatalog`
627 Catalog of unique star indices
628 fgcmRefCat: `afw.table.BaseCatalog`
629 Catalog of matched reference stars.
630 Will be None if `config.doReferenceMatches` is False.
633 if self.config.doReferenceMatches:
635 self.makeSubtask(
"fgcmLoadReferenceCatalog", butler=butler)
639 visitFilterNames = np.zeros(len(visitCat), dtype=
'a10')
640 for i
in range(len(visitCat)):
641 visitFilterNames[i] = visitCat[i][
'filtername']
644 visitIndex = np.searchsorted(visitCat[
'visit'],
647 obsFilterNames = visitFilterNames[visitIndex]
649 if self.config.doReferenceMatches:
651 lutCat = butler.get(
'fgcmLookUpTable')
653 stdFilterDict = {filterName: stdFilter
for (filterName, stdFilter)
in
654 zip(lutCat[0][
'filterNames'].split(
','),
655 lutCat[0][
'stdFilterNames'].split(
','))}
656 stdLambdaDict = {stdFilter: stdLambda
for (stdFilter, stdLambda)
in
657 zip(lutCat[0][
'stdFilterNames'].split(
','),
658 lutCat[0][
'lambdaStdFilter'])}
665 self.log.
info(
"Using the following reference filters: %s" %
666 (
', '.join(referenceFilterNames)))
670 referenceFilterNames = []
674 starConfig = {
'logger': self.log,
675 'filterToBand': self.config.filterMap,
676 'requiredBands': self.config.requiredBands,
677 'minPerBand': self.config.minPerBand,
678 'matchRadius': self.config.matchRadius,
679 'isolationRadius': self.config.isolationRadius,
680 'matchNSide': self.config.matchNside,
681 'coarseNSide': self.config.coarseNside,
682 'densNSide': self.config.densityCutNside,
683 'densMaxPerPixel': self.config.densityCutMaxPerPixel,
684 'primaryBands': self.config.primaryBands,
685 'referenceFilterNames': referenceFilterNames}
688 fgcmMakeStars = fgcm.FgcmMakeStars(starConfig)
696 conv = obsCat[0][
'ra'].asDegrees() / float(obsCat[0][
'ra'])
697 fgcmMakeStars.makePrimaryStars(obsCat[
'ra'] * conv,
698 obsCat[
'dec'] * conv,
699 filterNameArray=obsFilterNames,
703 fgcmMakeStars.makeMatchedStars(obsCat[
'ra'] * conv,
704 obsCat[
'dec'] * conv,
707 if self.config.doReferenceMatches:
708 fgcmMakeStars.makeReferenceMatches(self.fgcmLoadReferenceCatalog)
716 fgcmStarIdCat.reserve(fgcmMakeStars.objIndexCat.size)
717 for i
in range(fgcmMakeStars.objIndexCat.size):
718 fgcmStarIdCat.addNew()
721 fgcmStarIdCat[
'fgcm_id'][:] = fgcmMakeStars.objIndexCat[
'fgcm_id']
722 fgcmStarIdCat[
'ra'][:] = fgcmMakeStars.objIndexCat[
'ra']
723 fgcmStarIdCat[
'dec'][:] = fgcmMakeStars.objIndexCat[
'dec']
724 fgcmStarIdCat[
'obsArrIndex'][:] = fgcmMakeStars.objIndexCat[
'obsarrindex']
725 fgcmStarIdCat[
'nObs'][:] = fgcmMakeStars.objIndexCat[
'nobs']
730 fgcmStarIndicesCat.reserve(fgcmMakeStars.obsIndexCat.size)
731 for i
in range(fgcmMakeStars.obsIndexCat.size):
732 fgcmStarIndicesCat.addNew()
734 fgcmStarIndicesCat[
'obsIndex'][:] = fgcmMakeStars.obsIndexCat[
'obsindex']
736 if self.config.doReferenceMatches:
740 fgcmRefCat.reserve(fgcmMakeStars.referenceCat.size)
742 for i
in range(fgcmMakeStars.referenceCat.size):
745 fgcmRefCat[
'fgcm_id'][:] = fgcmMakeStars.referenceCat[
'fgcm_id']
746 fgcmRefCat[
'refMag'][:, :] = fgcmMakeStars.referenceCat[
'refMag']
747 fgcmRefCat[
'refMagErr'][:, :] = fgcmMakeStars.referenceCat[
'refMagErr']
750 md.set(
"REFSTARS_FORMAT_VERSION", REFSTARS_FORMAT_VERSION)
751 md.set(
"FILTERNAMES", referenceFilterNames)
752 fgcmRefCat.setMetadata(md)
757 return fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat
759 def _makeFgcmVisitSchema(self, nCcd):
761 Make a schema for an fgcmVisitCatalog
766 Number of CCDs in the camera
770 schema: `afwTable.Schema`
774 schema.addField(
'visit', type=np.int32, doc=
"Visit number")
776 schema.addField(
'filtername', type=str, size=10, doc=
"Filter name")
777 schema.addField(
'telra', type=np.float64, doc=
"Pointing RA (deg)")
778 schema.addField(
'teldec', type=np.float64, doc=
"Pointing Dec (deg)")
779 schema.addField(
'telha', type=np.float64, doc=
"Pointing Hour Angle (deg)")
780 schema.addField(
'telrot', type=np.float64, doc=
"Camera rotation (deg)")
781 schema.addField(
'mjd', type=np.float64, doc=
"MJD of visit")
782 schema.addField(
'exptime', type=np.float32, doc=
"Exposure time")
783 schema.addField(
'pmb', type=np.float32, doc=
"Pressure (millibar)")
784 schema.addField(
'psfSigma', type=np.float32, doc=
"PSF sigma (reference CCD)")
785 schema.addField(
'deltaAper', type=np.float32, doc=
"Delta-aperture")
786 schema.addField(
'skyBackground', type=np.float32, doc=
"Sky background (ADU) (reference CCD)")
788 schema.addField(
'deepFlag', type=np.int32, doc=
"Deep observation")
789 schema.addField(
'scaling', type=
'ArrayD', doc=
"Scaling applied due to flat adjustment",
791 schema.addField(
'used', type=np.int32, doc=
"This visit has been ingested.")
792 schema.addField(
'sources_read', type=
'Flag', doc=
"This visit had sources read.")
796 def _makeFgcmObjSchema(self):
798 Make a schema for the objIndexCat from fgcmMakeStars
802 schema: `afwTable.Schema`
806 objSchema.addField(
'fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
808 objSchema.addField(
'ra', type=np.float64, doc=
'Mean object RA (deg)')
809 objSchema.addField(
'dec', type=np.float64, doc=
'Mean object Dec (deg)')
810 objSchema.addField(
'obsArrIndex', type=np.int32,
811 doc=
'Index in obsIndexTable for first observation')
812 objSchema.addField(
'nObs', type=np.int32, doc=
'Total number of observations')
816 def _makeFgcmObsSchema(self):
818 Make a schema for the obsIndexCat from fgcmMakeStars
822 schema: `afwTable.Schema`
826 obsSchema.addField(
'obsIndex', type=np.int32, doc=
'Index in observation table')
830 def _makeFgcmRefSchema(self, nReferenceBands):
832 Make a schema for the referenceCat from fgcmMakeStars
836 nReferenceBands: `int`
837 Number of reference bands
841 schema: `afwTable.Schema`
845 refSchema.addField(
'fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
846 refSchema.addField(
'refMag', type=
'ArrayF', doc=
'Reference magnitude array (AB)',
847 size=nReferenceBands)
848 refSchema.addField(
'refMagErr', type=
'ArrayF', doc=
'Reference magnitude error array',
849 size=nReferenceBands)
853 def _getReferenceFilterNames(self, visitCat, stdFilterDict, stdLambdaDict):
855 Get the reference filter names, in wavelength order, from the visitCat and
856 information from the look-up-table.
860 visitCat: `afw.table.BaseCatalog`
861 Catalog with visit data for FGCM
862 stdFilterDict: `dict`
863 Mapping of filterName to stdFilterName from LUT
864 stdLambdaDict: `dict`
865 Mapping of stdFilterName to stdLambda from LUT
869 referenceFilterNames: `list`
870 Wavelength-ordered list of reference filter names
874 filterNames = np.unique(visitCat.asAstropy()[
'filtername'])
877 stdFilterNames = {stdFilterDict[filterName]
for filterName
in filterNames}
880 referenceFilterNames = sorted(stdFilterNames, key=stdLambdaDict.get)
882 return referenceFilterNames