23"""Build star observations for input to FGCM using sourceTable_visit.
25This task finds all the visits and sourceTable_visits in a repository (or a
26subset based on command line parameters) and extracts all the potential
27calibration stars for input into fgcm. This task additionally uses fgcm to
28match star observations into unique stars, and performs
as much cleaning of the
29input catalog
as possible.
38import lsst.pex.config as pexConfig
39import lsst.pipe.base as pipeBase
40from lsst.pipe.base import connectionTypes
41import lsst.afw.table as afwTable
42from lsst.meas.algorithms import ReferenceObjectLoader, LoadReferenceObjectsConfig
44from .fgcmBuildStarsBase import FgcmBuildStarsConfigBase, FgcmBuildStarsBaseTask
45from .utilities import computeApproxPixelAreaFields, computeApertureRadiusFromName
46from .utilities import lookupStaticCalibrations
48__all__ = ['FgcmBuildStarsTableConfig', 'FgcmBuildStarsTableTask']
51class FgcmBuildStarsTableConnections(pipeBase.PipelineTaskConnections,
52 dimensions=("instrument",),
54 camera = connectionTypes.PrerequisiteInput(
55 doc=
"Camera instrument",
57 storageClass=
"Camera",
58 dimensions=(
"instrument",),
59 lookupFunction=lookupStaticCalibrations,
63 fgcmLookUpTable = connectionTypes.PrerequisiteInput(
64 doc=(
"Atmosphere + instrument look-up-table for FGCM throughput and "
65 "chromatic corrections."),
66 name=
"fgcmLookUpTable",
67 storageClass=
"Catalog",
68 dimensions=(
"instrument",),
72 sourceSchema = connectionTypes.InitInput(
73 doc=
"Schema for source catalogs",
75 storageClass=
"SourceCatalog",
78 refCat = connectionTypes.PrerequisiteInput(
79 doc=
"Reference catalog to use for photometric calibration",
81 storageClass=
"SimpleCatalog",
82 dimensions=(
"skypix",),
87 sourceTable_visit = connectionTypes.Input(
88 doc=
"Source table in parquet format, per visit",
89 name=
"sourceTable_visit",
90 storageClass=
"DataFrame",
91 dimensions=(
"instrument",
"visit"),
96 visitSummary = connectionTypes.Input(
97 doc=(
"Per-visit consolidated exposure metadata. These catalogs use "
98 "detector id for the id and must be sorted for fast lookups of a "
101 storageClass=
"ExposureCatalog",
102 dimensions=(
"instrument",
"visit"),
107 fgcmVisitCatalog = connectionTypes.Output(
108 doc=
"Catalog of visit information for fgcm",
109 name=
"fgcmVisitCatalog",
110 storageClass=
"Catalog",
111 dimensions=(
"instrument",),
114 fgcmStarObservations = connectionTypes.Output(
115 doc=
"Catalog of star observations for fgcm",
116 name=
"fgcmStarObservations",
117 storageClass=
"Catalog",
118 dimensions=(
"instrument",),
121 fgcmStarIds = connectionTypes.Output(
122 doc=
"Catalog of fgcm calibration star IDs",
124 storageClass=
"Catalog",
125 dimensions=(
"instrument",),
128 fgcmStarIndices = connectionTypes.Output(
129 doc=
"Catalog of fgcm calibration star indices",
130 name=
"fgcmStarIndices",
131 storageClass=
"Catalog",
132 dimensions=(
"instrument",),
135 fgcmReferenceStars = connectionTypes.Output(
136 doc=
"Catalog of fgcm-matched reference stars",
137 name=
"fgcmReferenceStars",
138 storageClass=
"Catalog",
139 dimensions=(
"instrument",),
145 if not config.doReferenceMatches:
146 self.prerequisiteInputs.remove(
"refCat")
147 self.prerequisiteInputs.remove(
"fgcmLookUpTable")
149 if not config.doReferenceMatches:
150 self.outputs.remove(
"fgcmReferenceStars")
153 return (
"visitSummary",)
157 pipelineConnections=FgcmBuildStarsTableConnections):
158 """Config for FgcmBuildStarsTableTask"""
160 referenceCCD = pexConfig.Field(
161 doc=
"Reference CCD for checking PSF and background",
183 sourceSelector.flags.bad = [
'pixelFlags_edge',
184 'pixelFlags_interpolatedCenter',
185 'pixelFlags_saturatedCenter',
186 'pixelFlags_crCenter',
188 'pixelFlags_interpolated',
189 'pixelFlags_saturated',
195 sourceSelector.flags.bad.append(localBackgroundFlagName)
200 sourceSelector.isolated.parentName =
'parentSourceId'
201 sourceSelector.isolated.nChildName =
'deblend_nChild'
203 sourceSelector.requireFiniteRaDec.raColName =
'ra'
204 sourceSelector.requireFiniteRaDec.decColName =
'dec'
206 sourceSelector.unresolved.name =
'extendedness'
208 sourceSelector.doRequirePrimary =
True
213 Build stars for the FGCM
global calibration, using sourceTable_visit catalogs.
215 ConfigClass = FgcmBuildStarsTableConfig
216 _DefaultName = "fgcmBuildStarsTable"
218 canMultiprocess =
False
220 def __init__(self, initInputs=None, **kwargs):
221 super().__init__(initInputs=initInputs, **kwargs)
222 if initInputs
is not None:
226 inputRefDict = butlerQC.get(inputRefs)
228 sourceTableHandles = inputRefDict[
'sourceTable_visit']
230 self.log.info(
"Running with %d sourceTable_visit handles",
231 len(sourceTableHandles))
233 sourceTableHandleDict = {sourceTableHandle.dataId[
'visit']: sourceTableHandle
for
234 sourceTableHandle
in sourceTableHandles}
236 if self.config.doReferenceMatches:
238 lutHandle = inputRefDict[
'fgcmLookUpTable']
242 refConfig.filterMap = self.config.fgcmLoadReferenceCatalog.filterMap
244 for ref
in inputRefs.refCat],
245 refCats=butlerQC.get(inputRefs.refCat),
246 name=self.config.connections.refCat,
249 self.makeSubtask(
'fgcmLoadReferenceCatalog',
250 refObjLoader=refObjLoader,
251 refCatName=self.config.connections.refCat)
257 calibFluxApertureRadius =
None
258 if self.config.doSubtractLocalBackground:
260 calibFluxApertureRadius = computeApertureRadiusFromName(self.config.instFluxField)
261 except RuntimeError
as e:
262 raise RuntimeError(
"Could not determine aperture radius from %s. "
263 "Cannot use doSubtractLocalBackground." %
264 (self.config.instFluxField))
from e
266 visitSummaryHandles = inputRefDict[
'visitSummary']
267 visitSummaryHandleDict = {visitSummaryHandle.dataId[
'visit']: visitSummaryHandle
for
268 visitSummaryHandle
in visitSummaryHandles}
270 camera = inputRefDict[
'camera']
272 visitSummaryHandleDict)
276 rad = calibFluxApertureRadius
281 calibFluxApertureRadius=rad)
283 butlerQC.put(visitCat, outputRefs.fgcmVisitCatalog)
284 butlerQC.put(fgcmStarObservationCat, outputRefs.fgcmStarObservations)
286 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = self.
fgcmMatchStars(visitCat,
287 fgcmStarObservationCat,
290 butlerQC.put(fgcmStarIdCat, outputRefs.fgcmStarIds)
291 butlerQC.put(fgcmStarIndicesCat, outputRefs.fgcmStarIndices)
292 if fgcmRefCat
is not None:
293 butlerQC.put(fgcmRefCat, outputRefs.fgcmReferenceStars)
296 """Group sourceTable and visitSummary handles.
300 sourceTableHandleDict : `dict` [`int`, `str`]
301 Dict of source tables, keyed by visit.
302 visitSummaryHandleDict : `dict` [int, `str`]
303 Dict of visit summary catalogs, keyed by visit.
307 groupedHandles : `dict` [`int`, `list`]
308 Dictionary with sorted visit keys,
and `list`s
with
309 `lsst.daf.butler.DeferredDataSetHandle`. The first
310 item
in the list will be the visitSummary ref,
and
311 the second will be the source table ref.
313 groupedHandles = collections.defaultdict(list)
314 visits = sorted(sourceTableHandleDict.keys())
317 groupedHandles[visit] = [visitSummaryHandleDict[visit],
318 sourceTableHandleDict[visit]]
320 return groupedHandles
325 calibFluxApertureRadius=None):
326 startTime = time.time()
328 if self.config.doSubtractLocalBackground
and calibFluxApertureRadius
is None:
329 raise RuntimeError(
"Must set calibFluxApertureRadius if doSubtractLocalBackground is True.")
334 outputSchema = sourceMapper.getOutputSchema()
338 for ccdIndex, detector
in enumerate(camera):
339 ccdMapping[detector.getId()] = ccdIndex
341 approxPixelAreaFields = computeApproxPixelAreaFields(camera)
345 visitKey = outputSchema[
'visit'].asKey()
346 ccdKey = outputSchema[
'ccd'].asKey()
347 instMagKey = outputSchema[
'instMag'].asKey()
348 instMagErrKey = outputSchema[
'instMagErr'].asKey()
349 deltaMagAperKey = outputSchema[
'deltaMagAper'].asKey()
352 if self.config.doSubtractLocalBackground:
353 localBackgroundArea = np.pi*calibFluxApertureRadius**2.
359 for counter, visit
in enumerate(visitCat):
360 expTime = visit[
'exptime']
362 handle = groupedHandles[visit[
'visit']][-1]
365 inColumns = handle.get(component=
'columns')
367 df = handle.get(parameters={
'columns': columns})
369 goodSrc = self.sourceSelector.selectSources(df)
373 if self.config.doSubtractLocalBackground:
374 localBackground = localBackgroundArea*df[self.config.localBackgroundFluxField].values
375 use, = np.where((goodSrc.selected)
376 & ((df[self.config.instFluxField].values - localBackground) > 0.0))
378 use, = np.where(goodSrc.selected)
381 tempCat.resize(use.size)
383 tempCat[
'ra'][:] = np.deg2rad(df[
'ra'].values[use])
384 tempCat[
'dec'][:] = np.deg2rad(df[
'dec'].values[use])
385 tempCat[
'x'][:] = df[
'x'].values[use]
386 tempCat[
'y'][:] = df[
'y'].values[use]
388 tempCat[visitKey][:] = df[
'visit'].values[use]
389 tempCat[ccdKey][:] = df[
'detector'].values[use]
390 tempCat[
'psf_candidate'] = df[self.config.psfCandidateName].values[use]
392 with warnings.catch_warnings():
394 warnings.simplefilter(
"ignore")
396 instMagInner = -2.5*np.log10(df[self.config.apertureInnerInstFluxField].values[use])
397 instMagErrInner = k*(df[self.config.apertureInnerInstFluxField +
'Err'].values[use]
398 / df[self.config.apertureInnerInstFluxField].values[use])
399 instMagOuter = -2.5*np.log10(df[self.config.apertureOuterInstFluxField].values[use])
400 instMagErrOuter = k*(df[self.config.apertureOuterInstFluxField +
'Err'].values[use]
401 / df[self.config.apertureOuterInstFluxField].values[use])
402 tempCat[deltaMagAperKey][:] = instMagInner - instMagOuter
404 tempCat[deltaMagAperKey][~np.isfinite(tempCat[deltaMagAperKey][:])] = 99.0
406 if self.config.doSubtractLocalBackground:
420 tempCat[
'deltaMagBkg'] = (-2.5*np.log10(df[self.config.instFluxField].values[use]
421 - localBackground[use]) -
422 -2.5*np.log10(df[self.config.instFluxField].values[use]))
424 tempCat[
'deltaMagBkg'][:] = 0.0
427 for detector
in camera:
428 ccdId = detector.getId()
430 use2 = (tempCat[ccdKey] == ccdId)
431 tempCat[
'jacobian'][use2] = approxPixelAreaFields[ccdId].evaluate(tempCat[
'x'][use2],
433 scaledInstFlux = (df[self.config.instFluxField].values[use[use2]]
434 * visit[
'scaling'][ccdMapping[ccdId]])
435 tempCat[instMagKey][use2] = (-2.5*np.log10(scaledInstFlux) + 2.5*np.log10(expTime))
439 tempCat[instMagErrKey][:] = k*(df[self.config.instFluxField +
'Err'].values[use]
440 / df[self.config.instFluxField].values[use])
443 if self.config.doApplyWcsJacobian:
444 tempCat[instMagKey][:] -= 2.5*np.log10(tempCat[
'jacobian'][:])
446 fullCatalog.extend(tempCat)
448 deltaOk = (np.isfinite(instMagInner) & np.isfinite(instMagErrInner)
449 & np.isfinite(instMagOuter) & np.isfinite(instMagErrOuter))
451 visit[
'deltaAper'] = np.median(instMagInner[deltaOk] - instMagOuter[deltaOk])
452 visit[
'sources_read'] =
True
454 self.log.info(
" Found %d good stars in visit %d (deltaAper = %0.3f)",
455 use.size, visit[
'visit'], visit[
'deltaAper'])
457 self.log.info(
"Found all good star observations in %.2f s" %
458 (time.time() - startTime))
464 Get the sourceTable_visit columns from the config.
469 List of columns available
in the sourceTable_visit
474 List of columns to read
from sourceTable_visit.
477 columns = [
'visit',
'detector',
478 'ra',
'dec',
'x',
'y', self.config.psfCandidateName,
479 self.config.instFluxField, self.config.instFluxField +
'Err',
480 self.config.apertureInnerInstFluxField, self.config.apertureInnerInstFluxField +
'Err',
481 self.config.apertureOuterInstFluxField, self.config.apertureOuterInstFluxField +
'Err']
482 if self.sourceSelector.config.doFlags:
483 columns.extend(self.sourceSelector.config.flags.bad)
484 if self.sourceSelector.config.doUnresolved:
485 columns.append(self.sourceSelector.config.unresolved.name)
486 if self.sourceSelector.config.doIsolated:
487 columns.append(self.sourceSelector.config.isolated.parentName)
488 columns.append(self.sourceSelector.config.isolated.nChildName)
489 if self.sourceSelector.config.doRequirePrimary:
490 columns.append(self.sourceSelector.config.requirePrimary.primaryColName)
491 if self.config.doSubtractLocalBackground:
492 columns.append(self.config.localBackgroundFluxField)
_makeSourceMapper(self, sourceSchema)
fgcmMatchStars(self, visitCat, obsCat, lutHandle=None)
fgcmMakeVisitCatalog(self, camera, groupedHandles)
fgcmMakeAllStarObservations(self, groupedHandles, visitCat, sourceSchema, camera, calibFluxApertureRadius=None)
apertureOuterInstFluxField
apertureInnerInstFluxField
doSubtractLocalBackground
apertureInnerInstFluxField
apertureOuterInstFluxField
getSpatialBoundsConnections(self)
__init__(self, *config=None)
runQuantum(self, butlerQC, inputRefs, outputRefs)
fgcmMakeAllStarObservations(self, groupedHandles, visitCat, sourceSchema, camera, calibFluxApertureRadius=None)
_get_sourceTable_visit_columns(self, inColumns)
_groupHandles(self, sourceTableHandleDict, visitSummaryHandleDict)