21"""Base class for BuildStars using src tables or sourceTable_visit tables.
35from .fgcmLoadReferenceCatalog
import FgcmLoadReferenceCatalogTask
39REFSTARS_FORMAT_VERSION = 1
41__all__ = [
'FgcmBuildStarsConfigBase',
'FgcmBuildStarsBaseTask']
45 """Base config for FgcmBuildStars tasks"""
47 instFluxField = pexConfig.Field(
48 doc=(
"Faull name of the source instFlux field to use, including 'instFlux'. "
49 "The associated flag will be implicitly included in badFlags"),
51 default=
'slot_CalibFlux_instFlux',
53 minPerBand = pexConfig.Field(
54 doc=
"Minimum observations per band",
58 matchRadius = pexConfig.Field(
59 doc=
"Match radius (arcseconds)",
63 isolationRadius = pexConfig.Field(
64 doc=
"Isolation radius (arcseconds)",
68 densityCutNside = pexConfig.Field(
69 doc=
"Density cut healpix nside",
73 densityCutMaxPerPixel = pexConfig.Field(
74 doc=
"Density cut number of stars per pixel",
78 randomSeed = pexConfig.Field(
79 doc=
"Random seed for high density down-sampling.",
84 matchNside = pexConfig.Field(
85 doc=
"Healpix Nside for matching",
89 coarseNside = pexConfig.Field(
90 doc=
"Healpix coarse Nside for partitioning matches",
94 physicalFilterMap = pexConfig.DictField(
95 doc=
"Mapping from 'physicalFilter' to band.",
100 requiredBands = pexConfig.ListField(
101 doc=
"Bands required for each star",
105 primaryBands = pexConfig.ListField(
106 doc=(
"Bands for 'primary' star matches. "
107 "A star must be observed in one of these bands to be considered "
108 "as a calibration star."),
112 doApplyWcsJacobian = pexConfig.Field(
113 doc=
"Apply the jacobian of the WCS to the star observations prior to fit?",
117 doModelErrorsWithBackground = pexConfig.Field(
118 doc=
"Model flux errors with background term?",
122 psfCandidateName = pexConfig.Field(
123 doc=
"Name of field with psf candidate flag for propagation",
125 default=
"calib_psf_candidate"
127 doSubtractLocalBackground = pexConfig.Field(
128 doc=(
"Subtract the local background before performing calibration? "
129 "This is only supported for circular aperture calibration fluxes."),
133 localBackgroundFluxField = pexConfig.Field(
134 doc=
"Full name of the local background instFlux field to use.",
136 default=
'base_LocalBackground_instFlux'
138 sourceSelector = sourceSelectorRegistry.makeField(
139 doc=
"How to select sources",
142 apertureInnerInstFluxField = pexConfig.Field(
143 doc=(
"Full name of instFlux field that contains inner aperture "
144 "flux for aperture correction proxy"),
146 default=
'base_CircularApertureFlux_12_0_instFlux'
148 apertureOuterInstFluxField = pexConfig.Field(
149 doc=(
"Full name of instFlux field that contains outer aperture "
150 "flux for aperture correction proxy"),
152 default=
'base_CircularApertureFlux_17_0_instFlux'
154 doReferenceMatches = pexConfig.Field(
155 doc=
"Match reference catalog as additional constraint on calibration",
159 fgcmLoadReferenceCatalog = pexConfig.ConfigurableField(
160 target=FgcmLoadReferenceCatalogTask,
161 doc=
"FGCM reference object loader",
163 nVisitsPerCheckpoint = pexConfig.Field(
164 doc=
"Number of visits read between checkpoints",
171 sourceSelector.setDefaults()
173 sourceSelector.doFlags =
True
174 sourceSelector.doUnresolved =
True
175 sourceSelector.doSignalToNoise =
True
176 sourceSelector.doIsolated =
True
177 sourceSelector.doRequireFiniteRaDec =
True
179 sourceSelector.signalToNoise.minimum = 10.0
180 sourceSelector.signalToNoise.maximum = 1000.0
184 sourceSelector.unresolved.maximum = 0.5
189 Base task to build stars for FGCM
global calibration
191 def __init__(self, initInputs=None, **kwargs):
192 super().__init__(**kwargs)
194 self.makeSubtask(
"sourceSelector")
196 self.sourceSelector.log.setLevel(self.sourceSelector.log.WARN)
201 calibFluxApertureRadius=None):
203 Compile all good star observations from visits
in visitCat.
207 groupedHandles : `dict` [`list` [`lsst.daf.butler.DeferredDatasetHandle`]]
208 Dataset handles, grouped by visit.
210 Catalog
with visit data
for FGCM
212 Schema
for the input src catalogs.
214 calibFluxApertureRadius : `float`, optional
215 Aperture radius
for calibration flux.
217 Input observation catalog. If this
is incomplete, observations
218 will be appended
from when it was cut off.
223 Full catalog of good observations.
227 RuntimeError: Raised
if doSubtractLocalBackground
is True and
228 calibFluxApertureRadius
is not set.
230 raise NotImplementedError(
"fgcmMakeAllStarObservations not implemented.")
234 Make a visit catalog with all the keys
from each visit
239 Camera
from the butler
240 groupedHandles: `dict` [`list` [`lsst.daf.butler.DeferredDatasetHandle`]]
241 Dataset handles, grouped by visit.
248 self.log.info("Assembling visitCatalog from %d visits", len(groupedHandles))
255 visitCat.reserve(len(groupedHandles))
256 visitCat.resize(len(groupedHandles))
258 visitCat[
'visit'] =
list(groupedHandles.keys())
260 visitCat[
'sources_read'] =
False
270 Fill the visit catalog with visit metadata
276 groupedHandles : `dict` [`list` [`lsst.daf.butler.DeferredDatasetHandle`]]
277 Dataset handles, grouped by visit.
281 for i, visit
in enumerate(sorted(groupedHandles)):
282 if (i % self.config.nVisitsPerCheckpoint) == 0:
283 self.log.info(
"Retrieving metadata for visit %d (%d/%d)", visit, i, len(groupedHandles))
285 handle = groupedHandles[visit][0]
286 summary = handle.get()
288 summaryRow = summary.find(self.config.referenceCCD)
289 if summaryRow
is None:
291 summaryRow = summary[0]
293 visitInfo = summaryRow.getVisitInfo()
294 physicalFilter = summaryRow[
'physical_filter']
296 goodSigma, = np.where(summary[
'psfSigma'] > 0)
297 if goodSigma.size > 2:
298 psfSigma = np.median(summary[
'psfSigma'][goodSigma])
299 elif goodSigma.size > 0:
300 psfSigma = summary[
'psfSigma'][goodSigma[0]]
302 self.log.warning(
"Could not find any good summary psfSigma for visit %d", visit)
305 goodBackground, = np.where(np.nan_to_num(summary[
'skyBg']) > 0.0)
306 if goodBackground.size > 2:
307 skyBackground = np.median(summary[
'skyBg'][goodBackground])
308 elif goodBackground.size > 0:
309 skyBackground = summary[
'skyBg'][goodBackground[0]]
311 self.log.warning(
'Could not find any good summary skyBg for visit %d', visit)
316 rec[
'physicalFilter'] = physicalFilter
318 radec = visitInfo.getBoresightRaDec()
319 rec[
'telra'] = radec.getRa().asDegrees()
320 rec[
'teldec'] = radec.getDec().asDegrees()
321 rec[
'telha'] = visitInfo.getBoresightHourAngle().asDegrees()
322 rec[
'telrot'] = visitInfo.getBoresightRotAngle().asDegrees()
323 rec[
'mjd'] = visitInfo.getDate().get(system=DateTime.MJD)
324 rec[
'exptime'] = visitInfo.getExposureTime()
327 rec[
'pmb'] = visitInfo.getWeather().getAirPressure() / 100
331 rec[
'scaling'][:] = 1.0
333 rec[
'deltaAper'] = 0.0
334 rec[
'psfSigma'] = psfSigma
335 rec[
'skyBackground'] = skyBackground
340 Make a schema mapper for fgcm sources
345 Default source schema
from the butler
349 sourceMapper: `afwTable.schemaMapper`
350 Mapper to the FGCM source schema
357 sourceMapper.addMapping(sourceSchema[
'coord_ra'].asKey(),
'ra')
358 sourceMapper.addMapping(sourceSchema[
'coord_dec'].asKey(),
'dec')
359 sourceMapper.addMapping(sourceSchema[
'slot_Centroid_x'].asKey(),
'x')
360 sourceMapper.addMapping(sourceSchema[
'slot_Centroid_y'].asKey(),
'y')
366 sourceMapper.addMapping(sourceSchema[self.config.psfCandidateName].asKey(),
369 sourceMapper.editOutputSchema().addField(
370 "psf_candidate", type=
'Flag',
371 doc=(
"Flag set if the source was a candidate for PSF determination, "
372 "as determined by the star selector."))
375 sourceMapper.editOutputSchema().addField(
376 "visit", type=np.int64, doc=
"Visit number")
377 sourceMapper.editOutputSchema().addField(
378 "ccd", type=np.int32, doc=
"CCD number")
379 sourceMapper.editOutputSchema().addField(
380 "instMag", type=np.float32, doc=
"Instrumental magnitude")
381 sourceMapper.editOutputSchema().addField(
382 "instMagErr", type=np.float32, doc=
"Instrumental magnitude error")
383 sourceMapper.editOutputSchema().addField(
384 "jacobian", type=np.float32, doc=
"Relative pixel scale from wcs jacobian")
385 sourceMapper.editOutputSchema().addField(
386 "deltaMagBkg", type=np.float32, doc=
"Change in magnitude due to local background offset")
387 sourceMapper.editOutputSchema().addField(
388 "deltaMagAper", type=np.float32, doc=
"Change in magnitude from larger to smaller aperture")
394 Use FGCM code to match observations into unique stars.
399 Catalog with visit data
for fgcm
401 Full catalog of star observations
for fgcm
402 lutHandle: `lsst.daf.butler.DeferredDatasetHandle`, optional
403 Data reference to fgcm look-up table (used
if matching reference stars).
408 Catalog of unique star identifiers
and index keys
410 Catalog of unique star indices
412 Catalog of matched reference stars.
413 Will be
None if `config.doReferenceMatches`
is False.
417 visitFilterNames = np.zeros(len(visitCat), dtype=
'a30')
418 for i
in range(len(visitCat)):
419 visitFilterNames[i] = visitCat[i][
'physicalFilter']
422 visitIndex = np.searchsorted(visitCat[
'visit'],
425 obsFilterNames = visitFilterNames[visitIndex]
427 if self.config.doReferenceMatches:
429 lutCat = lutHandle.get()
431 stdFilterDict = {filterName: stdFilter
for (filterName, stdFilter)
in
432 zip(lutCat[0][
'physicalFilters'].split(
','),
433 lutCat[0][
'stdPhysicalFilters'].split(
','))}
434 stdLambdaDict = {stdFilter: stdLambda
for (stdFilter, stdLambda)
in
435 zip(lutCat[0][
'stdPhysicalFilters'].split(
','),
436 lutCat[0][
'lambdaStdFilter'])}
443 self.log.info(
"Using the following reference filters: %s" %
444 (
', '.join(referenceFilterNames)))
448 referenceFilterNames = []
451 starConfig = {
'logger': self.log,
453 'filterToBand': self.config.physicalFilterMap,
454 'requiredBands': self.config.requiredBands,
455 'minPerBand': self.config.minPerBand,
456 'matchRadius': self.config.matchRadius,
457 'isolationRadius': self.config.isolationRadius,
458 'matchNSide': self.config.matchNside,
459 'coarseNSide': self.config.coarseNside,
460 'densNSide': self.config.densityCutNside,
461 'densMaxPerPixel': self.config.densityCutMaxPerPixel,
462 'randomSeed': self.config.randomSeed,
463 'primaryBands': self.config.primaryBands,
464 'referenceFilterNames': referenceFilterNames}
467 fgcmMakeStars = fgcm.FgcmMakeStars(starConfig)
475 conv = obsCat[0][
'ra'].asDegrees() / float(obsCat[0][
'ra'])
476 fgcmMakeStars.makePrimaryStars(obsCat[
'ra'] * conv,
477 obsCat[
'dec'] * conv,
478 filterNameArray=obsFilterNames,
482 fgcmMakeStars.makeMatchedStars(obsCat[
'ra'] * conv,
483 obsCat[
'dec'] * conv,
486 if self.config.doReferenceMatches:
495 fgcmStarIdCat.reserve(fgcmMakeStars.objIndexCat.size)
496 for i
in range(fgcmMakeStars.objIndexCat.size):
497 fgcmStarIdCat.addNew()
500 fgcmStarIdCat[
'fgcm_id'][:] = fgcmMakeStars.objIndexCat[
'fgcm_id']
501 fgcmStarIdCat[
'ra'][:] = fgcmMakeStars.objIndexCat[
'ra']
502 fgcmStarIdCat[
'dec'][:] = fgcmMakeStars.objIndexCat[
'dec']
503 fgcmStarIdCat[
'obsArrIndex'][:] = fgcmMakeStars.objIndexCat[
'obsarrindex']
504 fgcmStarIdCat[
'nObs'][:] = fgcmMakeStars.objIndexCat[
'nobs']
509 fgcmStarIndicesCat.reserve(fgcmMakeStars.obsIndexCat.size)
510 for i
in range(fgcmMakeStars.obsIndexCat.size):
511 fgcmStarIndicesCat.addNew()
513 fgcmStarIndicesCat[
'obsIndex'][:] = fgcmMakeStars.obsIndexCat[
'obsindex']
515 if self.config.doReferenceMatches:
519 fgcmRefCat.reserve(fgcmMakeStars.referenceCat.size)
521 for i
in range(fgcmMakeStars.referenceCat.size):
524 fgcmRefCat[
'fgcm_id'][:] = fgcmMakeStars.referenceCat[
'fgcm_id']
525 fgcmRefCat[
'refMag'][:, :] = fgcmMakeStars.referenceCat[
'refMag']
526 fgcmRefCat[
'refMagErr'][:, :] = fgcmMakeStars.referenceCat[
'refMagErr']
529 md.set(
"REFSTARS_FORMAT_VERSION", REFSTARS_FORMAT_VERSION)
530 md.set(
"FILTERNAMES", referenceFilterNames)
531 fgcmRefCat.setMetadata(md)
536 return fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat
540 Make a schema for an fgcmVisitCatalog
545 Number of CCDs
in the camera
553 schema.addField('visit', type=np.int64, doc=
"Visit number")
554 schema.addField(
'physicalFilter', type=str, size=30, doc=
"Physical filter")
555 schema.addField(
'telra', type=np.float64, doc=
"Pointing RA (deg)")
556 schema.addField(
'teldec', type=np.float64, doc=
"Pointing Dec (deg)")
557 schema.addField(
'telha', type=np.float64, doc=
"Pointing Hour Angle (deg)")
558 schema.addField(
'telrot', type=np.float64, doc=
"Camera rotation (deg)")
559 schema.addField(
'mjd', type=np.float64, doc=
"MJD of visit")
560 schema.addField(
'exptime', type=np.float32, doc=
"Exposure time")
561 schema.addField(
'pmb', type=np.float32, doc=
"Pressure (millibar)")
562 schema.addField(
'psfSigma', type=np.float32, doc=
"PSF sigma (reference CCD)")
563 schema.addField(
'deltaAper', type=np.float32, doc=
"Delta-aperture")
564 schema.addField(
'skyBackground', type=np.float32, doc=
"Sky background (ADU) (reference CCD)")
566 schema.addField(
'deepFlag', type=np.int32, doc=
"Deep observation")
567 schema.addField(
'scaling', type=
'ArrayD', doc=
"Scaling applied due to flat adjustment",
569 schema.addField(
'used', type=np.int32, doc=
"This visit has been ingested.")
570 schema.addField(
'sources_read', type=
'Flag', doc=
"This visit had sources read.")
576 Make a schema for the objIndexCat
from fgcmMakeStars
584 objSchema.addField('fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
586 objSchema.addField(
'ra', type=np.float64, doc=
'Mean object RA (deg)')
587 objSchema.addField(
'dec', type=np.float64, doc=
'Mean object Dec (deg)')
588 objSchema.addField(
'obsArrIndex', type=np.int32,
589 doc=
'Index in obsIndexTable for first observation')
590 objSchema.addField(
'nObs', type=np.int32, doc=
'Total number of observations')
596 Make a schema for the obsIndexCat
from fgcmMakeStars
604 obsSchema.addField('obsIndex', type=np.int32, doc=
'Index in observation table')
610 Make a schema for the referenceCat
from fgcmMakeStars
614 nReferenceBands: `int`
615 Number of reference bands
623 refSchema.addField('fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
624 refSchema.addField(
'refMag', type=
'ArrayF', doc=
'Reference magnitude array (AB)',
625 size=nReferenceBands)
626 refSchema.addField(
'refMagErr', type=
'ArrayF', doc=
'Reference magnitude error array',
627 size=nReferenceBands)
633 Get the reference filter names, in wavelength order,
from the visitCat
and
634 information
from the look-up-table.
639 Catalog
with visit data
for FGCM
640 stdFilterDict: `dict`
641 Mapping of filterName to stdFilterName
from LUT
642 stdLambdaDict: `dict`
643 Mapping of stdFilterName to stdLambda
from LUT
647 referenceFilterNames: `list`
648 Wavelength-ordered list of reference filter names
652 filterNames = np.unique(visitCat.asAstropy()[
'physicalFilter'])
655 stdFilterNames = {stdFilterDict[filterName]
for filterName
in filterNames}
658 referenceFilterNames = sorted(stdFilterNames, key=stdLambdaDict.get)
660 return referenceFilterNames
An immutable representation of a camera.
Defines the fields and offsets for a table.
A mapping between the keys of two Schemas, used to copy data between them.
Class for storing ordered metadata with comments.
_makeSourceMapper(self, sourceSchema)
fgcmMatchStars(self, visitCat, obsCat, lutHandle=None)
_fillVisitCatalog(self, visitCat, groupedHandles)
_getReferenceFilterNames(self, visitCat, stdFilterDict, stdLambdaDict)
fgcmMakeVisitCatalog(self, camera, groupedHandles)
_makeFgcmRefSchema(self, nReferenceBands)
_makeFgcmVisitSchema(self, nCcd)
fgcmMakeAllStarObservations(self, groupedHandles, visitCat, sourceSchema, camera, calibFluxApertureRadius=None)
daf::base::PropertyList * list