23 """Build star observations for input to FGCM.
25 This task finds all the visits and calexps in a repository (or a subset
26 based on command line parameters) and extract all the potential calibration
27 stars for input into fgcm. This task additionally uses fgcm to match
28 star observations into unique stars, and performs as much cleaning of
29 the input catalog as possible.
40 from .fgcmBuildStarsBase
import FgcmBuildStarsConfigBase, FgcmBuildStarsRunner, FgcmBuildStarsBaseTask
41 from .utilities
import computeApproxPixelAreaFields
43 __all__ = [
'FgcmBuildStarsConfig',
'FgcmBuildStarsTask']
47 """Config for FgcmBuildStarsTask"""
49 referenceCCD = pexConfig.Field(
50 doc=
"Reference CCD for scanning visits",
54 checkAllCcds = pexConfig.Field(
55 doc=(
"Check repo for all CCDs for each visit specified. To be used when the "
56 "full set of ids (visit/ccd) are not specified on the command line. For "
57 "Gen2, specifying one ccd and setting checkAllCcds=True is significantly "
58 "faster than the alternatives."),
72 fluxFlagName = self.
instFluxFieldinstFluxField[0: -len(
'instFlux')] +
'flag'
73 sourceSelector.flags.bad = [
'base_PixelFlags_flag_edge',
74 'base_PixelFlags_flag_interpolatedCenter',
75 'base_PixelFlags_flag_saturatedCenter',
76 'base_PixelFlags_flag_crCenter',
77 'base_PixelFlags_flag_bad',
78 'base_PixelFlags_flag_interpolated',
79 'base_PixelFlags_flag_saturated',
85 sourceSelector.flags.bad.append(localBackgroundFlagName)
87 sourceSelector.signalToNoise.fluxField = self.
instFluxFieldinstFluxField
88 sourceSelector.signalToNoise.errField = self.
instFluxFieldinstFluxField +
'Err'
93 Build stars for the FGCM global calibration, using src catalogs.
95 ConfigClass = FgcmBuildStarsConfig
96 RunnerClass = FgcmBuildStarsRunner
97 _DefaultName =
"fgcmBuildStars"
100 def _makeArgumentParser(cls):
101 """Create an argument parser"""
102 parser = pipeBase.ArgumentParser(name=cls.
_DefaultName_DefaultName)
103 parser.add_id_argument(
"--id",
"src", help=
"Data ID, e.g. --id visit=6789")
107 def _findAndGroupDataRefsGen2(self, butler, camera, dataRefs):
108 self.log.
info(
"Grouping dataRefs by %s" % (self.config.visitDataRefName))
111 for detector
in camera:
112 ccdIds.append(detector.getId())
121 for dataRef
in dataRefs:
122 visit = dataRef.dataId[self.config.visitDataRefName]
124 if not dataRef.datasetExists(datasetType=
'src'):
127 if self.config.checkAllCcds:
128 if visit
in groupedDataRefs:
131 dataId = dataRef.dataId.copy()
134 dataId[self.config.ccdDataRefName] = ccdId
135 if butler.datasetExists(
'src', dataId=dataId):
136 goodDataRef = butler.dataRef(
'src', dataId=dataId)
137 if visit
in groupedDataRefs:
138 if (goodDataRef.dataId[self.config.ccdDataRefName]
not in
139 [d.dataId[self.config.ccdDataRefName]
for d
in groupedDataRefs[visit]]):
140 groupedDataRefs[visit].
append(goodDataRef)
144 groupedDataRefs[visit] = [goodDataRef]
148 if visit
in groupedDataRefs:
149 if (dataRef.dataId[self.config.ccdDataRefName]
not in
150 [d.dataId[self.config.ccdDataRefName]
for d
in groupedDataRefs[visit]]):
151 groupedDataRefs[visit].
append(dataRef)
155 groupedDataRefs[visit] = [dataRef]
157 if (nVisits % 100) == 0
and nVisits > 0:
158 self.log.
info(
"Found %d unique %ss..." % (nVisits,
159 self.config.visitDataRefName))
161 self.log.
info(
"Found %d unique %ss total." % (nVisits,
162 self.config.visitDataRefName))
165 def ccdSorter(dataRef):
166 ccdId = dataRef.dataId[self.config.ccdDataRefName]
167 if ccdId == self.config.referenceCCD:
173 if not self.config.checkAllCcds:
174 for visit
in groupedDataRefs:
175 groupedDataRefs[visit] = sorted(groupedDataRefs[visit], key=ccdSorter)
178 return dict(sorted(groupedDataRefs.items()))
183 calibFluxApertureRadius=None,
184 visitCatDataRef=None,
187 startTime = time.time()
193 if (visitCatDataRef
is not None and starObsDataRef
is None
194 or visitCatDataRef
is None and starObsDataRef
is not None):
195 self.log.
warn(
"Only one of visitCatDataRef and starObsDataRef are set, so "
196 "no checkpoint files will be persisted.")
198 if self.config.doSubtractLocalBackground
and calibFluxApertureRadius
is None:
199 raise RuntimeError(
"Must set calibFluxApertureRadius if doSubtractLocalBackground is True.")
202 dataRef = groupedDataRefs[
list(groupedDataRefs.keys())[0]][0]
203 sourceSchema = dataRef.get(
'src_schema', immediate=
True).schema
207 for ccdIndex, detector
in enumerate(camera):
208 ccdMapping[detector.getId()] = ccdIndex
217 outputSchema = sourceMapper.getOutputSchema()
219 if inStarObsCat
is not None:
220 fullCatalog = inStarObsCat
221 comp1 = fullCatalog.schema.compare(outputSchema, outputSchema.EQUAL_KEYS)
222 comp2 = fullCatalog.schema.compare(outputSchema, outputSchema.EQUAL_NAMES)
223 if not comp1
or not comp2:
224 raise RuntimeError(
"Existing fgcmStarObservations file found with mismatched schema.")
230 instFluxKey = sourceSchema[self.config.instFluxField].asKey()
231 instFluxErrKey = sourceSchema[self.config.instFluxField +
'Err'].asKey()
232 visitKey = outputSchema[
'visit'].asKey()
233 ccdKey = outputSchema[
'ccd'].asKey()
234 instMagKey = outputSchema[
'instMag'].asKey()
235 instMagErrKey = outputSchema[
'instMagErr'].asKey()
236 deltaMagBkgKey = outputSchema[
'deltaMagBkg'].asKey()
239 if self.config.doSubtractLocalBackground:
240 localBackgroundFluxKey = sourceSchema[self.config.localBackgroundFluxField].asKey()
241 localBackgroundArea = np.pi*calibFluxApertureRadius**2.
243 aperOutputSchema = aperMapper.getOutputSchema()
245 instFluxAperInKey = sourceSchema[self.config.apertureInnerInstFluxField].asKey()
246 instFluxErrAperInKey = sourceSchema[self.config.apertureInnerInstFluxField +
'Err'].asKey()
247 instFluxAperOutKey = sourceSchema[self.config.apertureOuterInstFluxField].asKey()
248 instFluxErrAperOutKey = sourceSchema[self.config.apertureOuterInstFluxField +
'Err'].asKey()
249 instMagInKey = aperOutputSchema[
'instMag_aper_inner'].asKey()
250 instMagErrInKey = aperOutputSchema[
'instMagErr_aper_inner'].asKey()
251 instMagOutKey = aperOutputSchema[
'instMag_aper_outer'].asKey()
252 instMagErrOutKey = aperOutputSchema[
'instMagErr_aper_outer'].asKey()
257 for ctr, visit
in enumerate(visitCat):
258 if visit[
'sources_read']:
261 expTime = visit[
'exptime']
268 for dataRef
in groupedDataRefs[visit[
'visit']]:
270 ccdId = dataRef.dataId[self.config.ccdDataRefName]
272 sources = dataRef.get(datasetType=
'src', flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
273 goodSrc = self.sourceSelector.selectSources(sources)
277 if self.config.doSubtractLocalBackground:
278 localBackground = localBackgroundArea*sources[localBackgroundFluxKey]
280 bad, = np.where((sources[instFluxKey] - localBackground) <= 0.0)
281 goodSrc.selected[bad] =
False
284 tempCat.reserve(goodSrc.selected.sum())
285 tempCat.extend(sources[goodSrc.selected], mapper=sourceMapper)
286 tempCat[visitKey][:] = visit[
'visit']
287 tempCat[ccdKey][:] = ccdId
290 scaledInstFlux = (sources[instFluxKey][goodSrc.selected]
291 * visit[
'scaling'][ccdMapping[ccdId]])
292 tempCat[instMagKey][:] = (-2.5*np.log10(scaledInstFlux) + 2.5*np.log10(expTime))
295 if self.config.doSubtractLocalBackground:
309 tempCat[deltaMagBkgKey][:] = (-2.5*np.log10(sources[instFluxKey][goodSrc.selected]
310 - localBackground[goodSrc.selected]) -
311 -2.5*np.log10(sources[instFluxKey][goodSrc.selected]))
313 tempCat[deltaMagBkgKey][:] = 0.0
318 tempCat[instMagErrKey][:] = k*(sources[instFluxErrKey][goodSrc.selected]
319 / sources[instFluxKey][goodSrc.selected])
322 tempCat[
'jacobian'] = approxPixelAreaFields[ccdId].evaluate(tempCat[
'x'],
326 if self.config.doApplyWcsJacobian:
327 tempCat[instMagKey][:] -= 2.5*np.log10(tempCat[
'jacobian'][:])
329 fullCatalog.extend(tempCat)
334 tempAperCat.reserve(goodSrc.selected.sum())
335 tempAperCat.extend(sources[goodSrc.selected], mapper=aperMapper)
337 with np.warnings.catch_warnings():
340 np.warnings.simplefilter(
"ignore")
342 tempAperCat[instMagInKey][:] = -2.5*np.log10(
343 sources[instFluxAperInKey][goodSrc.selected])
344 tempAperCat[instMagErrInKey][:] = k*(
345 sources[instFluxErrAperInKey][goodSrc.selected]
346 / sources[instFluxAperInKey][goodSrc.selected])
347 tempAperCat[instMagOutKey][:] = -2.5*np.log10(
348 sources[instFluxAperOutKey][goodSrc.selected])
349 tempAperCat[instMagErrOutKey][:] = k*(
350 sources[instFluxErrAperOutKey][goodSrc.selected]
351 / sources[instFluxAperOutKey][goodSrc.selected])
353 aperVisitCatalog.extend(tempAperCat)
355 nStarInVisit += len(tempCat)
358 if not aperVisitCatalog.isContiguous():
359 aperVisitCatalog = aperVisitCatalog.copy(deep=
True)
361 instMagIn = aperVisitCatalog[instMagInKey]
362 instMagErrIn = aperVisitCatalog[instMagErrInKey]
363 instMagOut = aperVisitCatalog[instMagOutKey]
364 instMagErrOut = aperVisitCatalog[instMagErrOutKey]
366 ok = (np.isfinite(instMagIn) & np.isfinite(instMagErrIn)
367 & np.isfinite(instMagOut) & np.isfinite(instMagErrOut))
369 visit[
'deltaAper'] = np.median(instMagIn[ok] - instMagOut[ok])
370 visit[
'sources_read'] =
True
372 self.log.
info(
" Found %d good stars in visit %d (deltaAper = %.3f)" %
373 (nStarInVisit, visit[
'visit'], visit[
'deltaAper']))
375 if ((ctr % self.config.nVisitsPerCheckpoint) == 0
376 and starObsDataRef
is not None and visitCatDataRef
is not None):
379 starObsDataRef.put(fullCatalog)
380 visitCatDataRef.put(visitCat)
382 self.log.
info(
"Found all good star observations in %.2f s" %
383 (time.time() - startTime))
387 def _makeAperMapper(self, sourceSchema):
389 Make a schema mapper for fgcm aperture measurements
393 sourceSchema: `afwTable.Schema`
394 Default source schema from the butler
398 aperMapper: `afwTable.schemaMapper`
399 Mapper to the FGCM aperture schema
403 aperMapper.addMapping(sourceSchema[
'coord_ra'].asKey(),
'ra')
404 aperMapper.addMapping(sourceSchema[
'coord_dec'].asKey(),
'dec')
405 aperMapper.editOutputSchema().addField(
'instMag_aper_inner', type=np.float64,
406 doc=
"Magnitude at inner aperture")
407 aperMapper.editOutputSchema().addField(
'instMagErr_aper_inner', type=np.float64,
408 doc=
"Magnitude error at inner aperture")
409 aperMapper.editOutputSchema().addField(
'instMag_aper_outer', type=np.float64,
410 doc=
"Magnitude at outer aperture")
411 aperMapper.editOutputSchema().addField(
'instMagErr_aper_outer', type=np.float64,
412 doc=
"Magnitude error at outer aperture")
A mapping between the keys of two Schemas, used to copy data between them.
def _makeAperMapper(self, sourceSchema)
def fgcmMakeAllStarObservations(self, groupedDataRefs, visitCat, srcSchemaDataRef, camera, calibFluxApertureRadius=None, visitCatDataRef=None, starObsDataRef=None, inStarObsCat=None)
def _makeSourceMapper(self, sourceSchema)
doSubtractLocalBackground
daf::base::PropertyList * list
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def computeApproxPixelAreaFields(camera)