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.
37import lsst.pex.config as pexConfig
38import lsst.pipe.base as pipeBase
39from lsst.pipe.base import connectionTypes
40import lsst.afw.table as afwTable
41from lsst.meas.algorithms import ReferenceObjectLoader, LoadReferenceObjectsConfig
43from .fgcmBuildStarsBase import FgcmBuildStarsConfigBase, FgcmBuildStarsBaseTask
44from .utilities import computeApproxPixelAreaFields, computeApertureRadiusFromName
45from .utilities import lookupStaticCalibrations
47__all__ = ['FgcmBuildStarsTableConfig', 'FgcmBuildStarsTableTask']
50class FgcmBuildStarsTableConnections(pipeBase.PipelineTaskConnections,
51 dimensions=("instrument",),
53 camera = connectionTypes.PrerequisiteInput(
54 doc=
"Camera instrument",
56 storageClass=
"Camera",
57 dimensions=(
"instrument",),
58 lookupFunction=lookupStaticCalibrations,
62 fgcmLookUpTable = connectionTypes.PrerequisiteInput(
63 doc=(
"Atmosphere + instrument look-up-table for FGCM throughput and "
64 "chromatic corrections."),
65 name=
"fgcmLookUpTable",
66 storageClass=
"Catalog",
67 dimensions=(
"instrument",),
71 sourceSchema = connectionTypes.InitInput(
72 doc=
"Schema for source catalogs",
74 storageClass=
"SourceCatalog",
77 refCat = connectionTypes.PrerequisiteInput(
78 doc=
"Reference catalog to use for photometric calibration",
80 storageClass=
"SimpleCatalog",
81 dimensions=(
"skypix",),
86 sourceTable_visit = connectionTypes.Input(
87 doc=
"Source table in parquet format, per visit",
88 name=
"sourceTable_visit",
89 storageClass=
"DataFrame",
90 dimensions=(
"instrument",
"visit"),
95 visitSummary = connectionTypes.Input(
96 doc=(
"Per-visit consolidated exposure metadata. These catalogs use "
97 "detector id for the id and must be sorted for fast lookups of a "
100 storageClass=
"ExposureCatalog",
101 dimensions=(
"instrument",
"visit"),
106 background = connectionTypes.Input(
107 doc=
"Calexp background model",
108 name=
"calexpBackground",
109 storageClass=
"Background",
110 dimensions=(
"instrument",
"visit",
"detector"),
115 fgcmVisitCatalog = connectionTypes.Output(
116 doc=
"Catalog of visit information for fgcm",
117 name=
"fgcmVisitCatalog",
118 storageClass=
"Catalog",
119 dimensions=(
"instrument",),
122 fgcmStarObservations = connectionTypes.Output(
123 doc=
"Catalog of star observations for fgcm",
124 name=
"fgcmStarObservations",
125 storageClass=
"Catalog",
126 dimensions=(
"instrument",),
129 fgcmStarIds = connectionTypes.Output(
130 doc=
"Catalog of fgcm calibration star IDs",
132 storageClass=
"Catalog",
133 dimensions=(
"instrument",),
136 fgcmStarIndices = connectionTypes.Output(
137 doc=
"Catalog of fgcm calibration star indices",
138 name=
"fgcmStarIndices",
139 storageClass=
"Catalog",
140 dimensions=(
"instrument",),
143 fgcmReferenceStars = connectionTypes.Output(
144 doc=
"Catalog of fgcm-matched reference stars",
145 name=
"fgcmReferenceStars",
146 storageClass=
"Catalog",
147 dimensions=(
"instrument",),
153 if not config.doReferenceMatches:
154 self.prerequisiteInputs.remove(
"refCat")
155 self.prerequisiteInputs.remove(
"fgcmLookUpTable")
157 if not config.doModelErrorsWithBackground:
158 self.inputs.remove(
"background")
160 if not config.doReferenceMatches:
161 self.outputs.remove(
"fgcmReferenceStars")
165 pipelineConnections=FgcmBuildStarsTableConnections):
166 """Config for FgcmBuildStarsTableTask"""
168 referenceCCD = pexConfig.Field(
169 doc=
"Reference CCD for checking PSF and background",
191 sourceSelector.flags.bad = [
'pixelFlags_edge',
192 'pixelFlags_interpolatedCenter',
193 'pixelFlags_saturatedCenter',
194 'pixelFlags_crCenter',
196 'pixelFlags_interpolated',
197 'pixelFlags_saturated',
203 sourceSelector.flags.bad.append(localBackgroundFlagName)
208 sourceSelector.isolated.parentName =
'parentSourceId'
209 sourceSelector.isolated.nChildName =
'deblend_nChild'
211 sourceSelector.requireFiniteRaDec.raColName =
'ra'
212 sourceSelector.requireFiniteRaDec.decColName =
'decl'
214 sourceSelector.unresolved.name =
'extendedness'
219 Build stars for the FGCM
global calibration, using sourceTable_visit catalogs.
221 ConfigClass = FgcmBuildStarsTableConfig
222 _DefaultName = "fgcmBuildStarsTable"
224 canMultiprocess =
False
226 def __init__(self, initInputs=None, **kwargs):
227 super().__init__(initInputs=initInputs, **kwargs)
228 if initInputs
is not None:
232 inputRefDict = butlerQC.get(inputRefs)
234 sourceTableHandles = inputRefDict[
'sourceTable_visit']
236 self.log.info(
"Running with %d sourceTable_visit handles",
237 len(sourceTableHandles))
239 sourceTableHandleDict = {sourceTableHandle.dataId[
'visit']: sourceTableHandle
for
240 sourceTableHandle
in sourceTableHandles}
242 if self.config.doReferenceMatches:
244 lutHandle = inputRefDict[
'fgcmLookUpTable']
248 refConfig.filterMap = self.config.fgcmLoadReferenceCatalog.filterMap
250 for ref
in inputRefs.refCat],
251 refCats=butlerQC.get(inputRefs.refCat),
252 name=self.config.connections.refCat,
255 self.makeSubtask(
'fgcmLoadReferenceCatalog',
256 refObjLoader=refObjLoader,
257 refCatName=self.config.connections.refCat)
263 calibFluxApertureRadius =
None
264 if self.config.doSubtractLocalBackground:
266 calibFluxApertureRadius = computeApertureRadiusFromName(self.config.instFluxField)
267 except RuntimeError
as e:
268 raise RuntimeError(
"Could not determine aperture radius from %s. "
269 "Cannot use doSubtractLocalBackground." %
270 (self.config.instFluxField))
from e
272 visitSummaryHandles = inputRefDict[
'visitSummary']
273 visitSummaryHandleDict = {visitSummaryHandle.dataId[
'visit']: visitSummaryHandle
for
274 visitSummaryHandle
in visitSummaryHandles}
276 camera = inputRefDict[
'camera']
278 visitSummaryHandleDict)
280 if self.config.doModelErrorsWithBackground:
281 bkgHandles = inputRefDict[
'background']
282 bkgHandleDict = {(bkgHandle.dataId.byName()[
'visit'],
283 bkgHandle.dataId.byName()[
'detector']): bkgHandle
for
284 bkgHandle
in bkgHandles}
289 bkgHandleDict=bkgHandleDict)
291 rad = calibFluxApertureRadius
296 calibFluxApertureRadius=rad)
298 butlerQC.put(visitCat, outputRefs.fgcmVisitCatalog)
299 butlerQC.put(fgcmStarObservationCat, outputRefs.fgcmStarObservations)
301 fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = self.
fgcmMatchStars(visitCat,
302 fgcmStarObservationCat,
305 butlerQC.put(fgcmStarIdCat, outputRefs.fgcmStarIds)
306 butlerQC.put(fgcmStarIndicesCat, outputRefs.fgcmStarIndices)
307 if fgcmRefCat
is not None:
308 butlerQC.put(fgcmRefCat, outputRefs.fgcmReferenceStars)
310 def _groupHandles(self, sourceTableHandleDict, visitSummaryHandleDict):
311 """Group sourceTable and visitSummary handles.
315 sourceTableHandleDict : `dict` [`int`, `str`]
316 Dict of source tables, keyed by visit.
317 visitSummaryHandleDict : `dict` [int, `str`]
318 Dict of visit summary catalogs, keyed by visit.
322 groupedHandles : `dict` [`int`, `list`]
323 Dictionary with sorted visit keys,
and `list`s
with
324 `lsst.daf.butler.DeferredDataSetHandle`. The first
325 item
in the list will be the visitSummary ref,
and
326 the second will be the source table ref.
328 groupedHandles = collections.defaultdict(list)
329 visits = sorted(sourceTableHandleDict.keys())
332 groupedHandles[visit] = [visitSummaryHandleDict[visit],
333 sourceTableHandleDict[visit]]
335 return groupedHandles
340 calibFluxApertureRadius=None):
341 startTime = time.time()
343 if self.config.doSubtractLocalBackground
and calibFluxApertureRadius
is None:
344 raise RuntimeError(
"Must set calibFluxApertureRadius if doSubtractLocalBackground is True.")
349 outputSchema = sourceMapper.getOutputSchema()
353 for ccdIndex, detector
in enumerate(camera):
354 ccdMapping[detector.getId()] = ccdIndex
356 approxPixelAreaFields = computeApproxPixelAreaFields(camera)
360 visitKey = outputSchema[
'visit'].asKey()
361 ccdKey = outputSchema[
'ccd'].asKey()
362 instMagKey = outputSchema[
'instMag'].asKey()
363 instMagErrKey = outputSchema[
'instMagErr'].asKey()
364 deltaMagAperKey = outputSchema[
'deltaMagAper'].asKey()
367 if self.config.doSubtractLocalBackground:
368 localBackgroundArea = np.pi*calibFluxApertureRadius**2.
374 for counter, visit
in enumerate(visitCat):
375 expTime = visit[
'exptime']
377 handle = groupedHandles[visit[
'visit']][-1]
380 inColumns = handle.get(component=
'columns')
382 df = handle.get(parameters={
'columns': columns})
384 goodSrc = self.sourceSelector.selectSources(df)
388 if self.config.doSubtractLocalBackground:
389 localBackground = localBackgroundArea*df[self.config.localBackgroundFluxField].values
390 use, = np.where((goodSrc.selected)
391 & ((df[self.config.instFluxField].values - localBackground) > 0.0))
393 use, = np.where(goodSrc.selected)
396 tempCat.resize(use.size)
398 tempCat[
'ra'][:] = np.deg2rad(df[
'ra'].values[use])
399 tempCat[
'dec'][:] = np.deg2rad(df[
'decl'].values[use])
400 tempCat[
'x'][:] = df[
'x'].values[use]
401 tempCat[
'y'][:] = df[
'y'].values[use]
403 tempCat[visitKey][:] = df[
'visit'].values[use]
404 tempCat[ccdKey][:] = df[
'detector'].values[use]
405 tempCat[
'psf_candidate'] = df[self.config.psfCandidateName].values[use]
407 with np.warnings.catch_warnings():
409 np.warnings.simplefilter(
"ignore")
411 instMagInner = -2.5*np.log10(df[self.config.apertureInnerInstFluxField].values[use])
412 instMagErrInner = k*(df[self.config.apertureInnerInstFluxField +
'Err'].values[use]
413 / df[self.config.apertureInnerInstFluxField].values[use])
414 instMagOuter = -2.5*np.log10(df[self.config.apertureOuterInstFluxField].values[use])
415 instMagErrOuter = k*(df[self.config.apertureOuterInstFluxField +
'Err'].values[use]
416 / df[self.config.apertureOuterInstFluxField].values[use])
417 tempCat[deltaMagAperKey][:] = instMagInner - instMagOuter
419 tempCat[deltaMagAperKey][~np.isfinite(tempCat[deltaMagAperKey][:])] = 99.0
421 if self.config.doSubtractLocalBackground:
435 tempCat[
'deltaMagBkg'] = (-2.5*np.log10(df[self.config.instFluxField].values[use]
436 - localBackground[use]) -
437 -2.5*np.log10(df[self.config.instFluxField].values[use]))
439 tempCat[
'deltaMagBkg'][:] = 0.0
442 for detector
in camera:
443 ccdId = detector.getId()
445 use2 = (tempCat[ccdKey] == ccdId)
446 tempCat[
'jacobian'][use2] = approxPixelAreaFields[ccdId].evaluate(tempCat[
'x'][use2],
448 scaledInstFlux = (df[self.config.instFluxField].values[use[use2]]
449 * visit[
'scaling'][ccdMapping[ccdId]])
450 tempCat[instMagKey][use2] = (-2.5*np.log10(scaledInstFlux) + 2.5*np.log10(expTime))
454 tempCat[instMagErrKey][:] = k*(df[self.config.instFluxField +
'Err'].values[use]
455 / df[self.config.instFluxField].values[use])
458 if self.config.doApplyWcsJacobian:
459 tempCat[instMagKey][:] -= 2.5*np.log10(tempCat[
'jacobian'][:])
461 fullCatalog.extend(tempCat)
463 deltaOk = (np.isfinite(instMagInner) & np.isfinite(instMagErrInner)
464 & np.isfinite(instMagOuter) & np.isfinite(instMagErrOuter))
466 visit[
'deltaAper'] = np.median(instMagInner[deltaOk] - instMagOuter[deltaOk])
467 visit[
'sources_read'] =
True
469 self.log.info(
" Found %d good stars in visit %d (deltaAper = %0.3f)",
470 use.size, visit[
'visit'], visit[
'deltaAper'])
472 self.log.info(
"Found all good star observations in %.2f s" %
473 (time.time() - startTime))
477 def _get_sourceTable_visit_columns(self, inColumns):
479 Get the sourceTable_visit columns from the config.
484 List of columns available
in the sourceTable_visit
489 List of columns to read
from sourceTable_visit.
492 columns = [
'visit',
'detector',
493 'ra',
'decl',
'x',
'y', self.config.psfCandidateName,
494 self.config.instFluxField, self.config.instFluxField +
'Err',
495 self.config.apertureInnerInstFluxField, self.config.apertureInnerInstFluxField +
'Err',
496 self.config.apertureOuterInstFluxField, self.config.apertureOuterInstFluxField +
'Err']
497 if self.sourceSelector.config.doFlags:
498 columns.extend(self.sourceSelector.config.flags.bad)
499 if self.sourceSelector.config.doUnresolved:
500 columns.append(self.sourceSelector.config.unresolved.name)
501 if self.sourceSelector.config.doIsolated:
502 columns.append(self.sourceSelector.config.isolated.parentName)
503 columns.append(self.sourceSelector.config.isolated.nChildName)
504 if self.config.doSubtractLocalBackground:
505 columns.append(self.config.localBackgroundFluxField)
def fgcmMatchStars(self, visitCat, obsCat, lutHandle=None)
def _makeSourceMapper(self, sourceSchema)
def fgcmMakeAllStarObservations(self, groupedHandles, visitCat, sourceSchema, camera, calibFluxApertureRadius=None)
def fgcmMakeVisitCatalog(self, camera, groupedHandles, bkgHandleDict=None)
apertureOuterInstFluxField
apertureInnerInstFluxField
doSubtractLocalBackground
apertureInnerInstFluxField
apertureOuterInstFluxField
def __init__(self, *config=None)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def fgcmMakeAllStarObservations(self, groupedHandles, visitCat, sourceSchema, camera, calibFluxApertureRadius=None)
def _get_sourceTable_visit_columns(self, inColumns)
def _groupHandles(self, sourceTableHandleDict, visitSummaryHandleDict)