LSST Applications g0265f82a02+c6dfa2ddaf,g1162b98a3f+b2075782a9,g2079a07aa2+1b2e822518,g2bbee38e9b+c6dfa2ddaf,g337abbeb29+c6dfa2ddaf,g3ddfee87b4+a60788ef87,g50ff169b8f+2eb0e556e8,g52b1c1532d+90ebb246c7,g555ede804d+a60788ef87,g591dd9f2cf+ba8caea58f,g5ec818987f+864ee9cddb,g858d7b2824+9ee1ab4172,g876c692160+a40945ebb7,g8a8a8dda67+90ebb246c7,g8cdfe0ae6a+4fd9e222a8,g99cad8db69+5e309b7bc6,g9ddcbc5298+a1346535a5,ga1e77700b3+df8f93165b,ga8c6da7877+aa12a14d27,gae46bcf261+c6dfa2ddaf,gb0e22166c9+8634eb87fb,gb3f2274832+d0da15e3be,gba4ed39666+1ac82b564f,gbb8dafda3b+5dfd9c994b,gbeb006f7da+97157f9740,gc28159a63d+c6dfa2ddaf,gc86a011abf+9ee1ab4172,gcf0d15dbbd+a60788ef87,gdaeeff99f8+1cafcb7cd4,gdc0c513512+9ee1ab4172,ge79ae78c31+c6dfa2ddaf,geb67518f79+ba1859f325,geb961e4c1e+f9439d1e6f,gee10cc3b42+90ebb246c7,gf1cff7945b+9ee1ab4172,w.2024.12
LSST Data Management Base Package
Loading...
Searching...
No Matches
Classes | Variables
lsst.fgcmcal.fgcmOutputProducts Namespace Reference

Classes

class  FgcmOutputProductsConnections
 

Variables

 handleDict : `dict`
 
 physicalFilterMap : `dict`
 
 retStruct : `lsst.pipe.base.Struct`
 
 offsets : `np.ndarray`
 
 atmospheres : `generator` [(`int`, `lsst.afw.image.TransmissionCurve`)]
 
 photoCalibCatalogs : `generator` [(`int`, `lsst.afw.table.ExposureCatalog`)]
 
 tract : `int`
 
 visitCat : `lsst.afw.table.BaseCatalog`
 
 zptCat : `lsst.afw.table.BaseCatalog`
 
 atmCat : `lsst.afw.table.BaseCatalog`
 
 stdCat : `lsst.afw.table.SimpleCatalog`
 
 fgcmBuildStarsConfig : `lsst.fgcmcal.FgcmBuildStarsConfig`
 
 lutCat : `lsst.afw.table.SimpleCatalog`
 
 bands : `list` [`str`]
 
 sourceMapper : `lsst.afw.table.SchemaMapper`
 
 badStarKey : `lsst.afw.table.Key`
 
 b_index : `int`
 
 filterLabel : `lsst.afw.image.FilterLabel`
 
 selected : `numpy.array(dtype=bool)`
 
 refFluxFields : `list`
 
 camera : `lsst.afw.cameraGeom.Camera`
 
 coefficients : `numpy.array`
 
int xyMax : `list` of length 2
 
 offset : `float`, optional
 
 scaling : `float`, optional
 
 boundedField : `lsst.afw.math.ChebyshevBoundedField`
 

Detailed Description

Make the final fgcmcal output products.

This task takes the final output from fgcmFitCycle and produces the following
outputs for use in the DM stack: the FGCM standard stars in a reference
catalog format; the model atmospheres in "transmission_atmosphere_fgcm"
format; and the zeropoints in "fgcm_photoCalib" format.  Optionally, the
task can transfer the 'absolute' calibration from a reference catalog
to put the fgcm standard stars in units of Jansky.  This is accomplished
by matching stars in a sample of healpix pixels, and applying the median
offset per band.

Variable Documentation

◆ atmCat

lsst.fgcmcal.fgcmOutputProducts.atmCat : `lsst.afw.table.BaseCatalog`

Definition at line 426 of file fgcmOutputProducts.py.

◆ atmospheres

lsst.fgcmcal.fgcmOutputProducts.atmospheres : `generator` [(`int`, `lsst.afw.image.TransmissionCurve`)]

Definition at line 363 of file fgcmOutputProducts.py.

◆ b_index

lsst.fgcmcal.fgcmOutputProducts.b_index : `int`

Definition at line 636 of file fgcmOutputProducts.py.

◆ badStarKey

lsst.fgcmcal.fgcmOutputProducts.badStarKey : `lsst.afw.table.Key`

Definition at line 634 of file fgcmOutputProducts.py.

◆ bands

lsst.fgcmcal.fgcmOutputProducts.bands : `list` [`str`]

Definition at line 498 of file fgcmOutputProducts.py.

◆ boundedField

lsst.fgcmcal.fgcmOutputProducts.boundedField : `lsst.afw.math.ChebyshevBoundedField`

Definition at line 839 of file fgcmOutputProducts.py.

◆ camera

lsst.fgcmcal.fgcmOutputProducts.camera : `lsst.afw.cameraGeom.Camera`
sourceCat = afwTable.SimpleCatalog(sourceMapper.getOutputSchema())
sourceCat.reserve(selected.sum())
sourceCat.extend(stdCat[selected], mapper=sourceMapper)
sourceCat['instFlux'] = 10.**(stdCat['mag_std_noabs'][selected, b_index]/(-2.5))
sourceCat['instFluxErr'] = (np.log(10.)/2.5)*(stdCat['magErr_std'][selected, b_index]
                                              * sourceCat['instFlux'])
# Make sure we only use stars that have valid measurements
# (This is perhaps redundant with requirements above that the
# stars be observed in all bands, but it can't hurt)
badStar = (stdCat['mag_std_noabs'][selected, b_index] > 90.0)
for rec in sourceCat[badStar]:
    rec.set(badStarKey, True)

exposure = afwImage.ExposureF()
exposure.setFilter(filterLabel)

if refFluxFields[b_index] is None:
    # Need to find the flux field in the reference catalog
    # to work around limitations of DirectMatch in PhotoCal
    ctr = stdCat[0].getCoord()
    rad = 0.05*lsst.geom.degrees
    refDataTest = self.refObjLoader.loadSkyCircle(ctr, rad, filterLabel.bandLabel)
    refFluxFields[b_index] = refDataTest.fluxField

# Make a copy of the config so that we can modify it
calConfig = copy.copy(self.config.photoCal.value)
calConfig.match.referenceSelection.signalToNoise.fluxField = refFluxFields[b_index]
calConfig.match.referenceSelection.signalToNoise.errField = refFluxFields[b_index] + 'Err'
calTask = self.config.photoCal.target(refObjLoader=self.refObjLoader,
                                      config=calConfig,
                                      schema=sourceCat.getSchema())

struct = calTask.run(exposure, sourceCat)

return struct

def _outputZeropoints(self, camera, zptCat, visitCat, offsets, bands,
                  physicalFilterMap, tract=None):

Definition at line 690 of file fgcmOutputProducts.py.

◆ coefficients

lsst.fgcmcal.fgcmOutputProducts.coefficients : `numpy.array`
# Select visit/ccds where we have a calibration
# This includes ccds where we were able to interpolate from neighboring
# ccds.
cannot_compute = fgcm.fgcmUtilities.zpFlagDict['CANNOT_COMPUTE_ZEROPOINT']
selected = (((zptCat['fgcmFlag'] & cannot_compute) == 0)
            & (zptCat['fgcmZptVar'] > 0.0)
            & (zptCat['fgcmZpt'] > FGCM_ILLEGAL_VALUE))

# Log warnings for any visit which has no valid zeropoints
badVisits = np.unique(zptCat['visit'][~selected])
goodVisits = np.unique(zptCat['visit'][selected])
allBadVisits = badVisits[~np.isin(badVisits, goodVisits)]
for allBadVisit in allBadVisits:
    self.log.warning(f'No suitable photoCalib for visit {allBadVisit}')

# Get a mapping from filtername to the offsets
offsetMapping = {}
for f in physicalFilterMap:
    # Not every filter in the map will necesarily have a band.
    if physicalFilterMap[f] in bands:
        offsetMapping[f] = offsets[bands.index(physicalFilterMap[f])]

# Get a mapping from "ccd" to the ccd index used for the scaling
ccdMapping = {}
for ccdIndex, detector in enumerate(camera):
    ccdMapping[detector.getId()] = ccdIndex

# And a mapping to get the flat-field scaling values
scalingMapping = {}
for rec in visitCat:
    scalingMapping[rec['visit']] = rec['scaling']

if self.config.doComposeWcsJacobian:
    approxPixelAreaFields = computeApproxPixelAreaFields(camera)

# The zptCat is sorted by visit, which is useful
lastVisit = -1
zptVisitCatalog = None

metadata = dafBase.PropertyList()
metadata.add("COMMENT", "Catalog id is detector id, sorted.")
metadata.add("COMMENT", "Only detectors with data have entries.")

for rec in zptCat[selected]:
    # Retrieve overall scaling
    scaling = scalingMapping[rec['visit']][ccdMapping[rec['detector']]]

    # The postCalibrationOffset describe any zeropoint offsets
    # to apply after the fgcm calibration.  The first part comes
    # from the reference catalog match (used in testing).  The
    # second part comes from the mean chromatic correction
    # (if configured).
    postCalibrationOffset = offsetMapping[rec['filtername']]
    if self.config.doApplyMeanChromaticCorrection:
        postCalibrationOffset += rec['fgcmDeltaChrom']

    fgcmSuperStarField = self._getChebyshevBoundedField(rec['fgcmfZptSstarCheb'],
                                                        rec['fgcmfZptChebXyMax'])
    # Convert from FGCM AB to nJy
    fgcmZptField = self._getChebyshevBoundedField((rec['fgcmfZptCheb']*units.AB).to_value(units.nJy),
                                                  rec['fgcmfZptChebXyMax'],
                                                  offset=postCalibrationOffset,
                                                  scaling=scaling)

    if self.config.doComposeWcsJacobian:

        fgcmField = afwMath.ProductBoundedField([approxPixelAreaFields[rec['detector']],
                                                 fgcmSuperStarField,
                                                 fgcmZptField])
    else:
        # The photoCalib is just the product of the fgcmSuperStarField and the
        # fgcmZptField
        fgcmField = afwMath.ProductBoundedField([fgcmSuperStarField, fgcmZptField])

    # The "mean" calibration will be set to the center of the ccd for reference
    calibCenter = fgcmField.evaluate(fgcmField.getBBox().getCenter())
    calibErr = (np.log(10.0)/2.5)*calibCenter*np.sqrt(rec['fgcmZptVar'])
    photoCalib = afwImage.PhotoCalib(calibrationMean=calibCenter,
                                     calibrationErr=calibErr,
                                     calibration=fgcmField,
                                     isConstant=False)

    # Return full per-visit exposure catalogs
    if rec['visit'] != lastVisit:
        # This is a new visit.  If the last visit was not -1, yield
        # the ExposureCatalog
        if lastVisit > -1:
            # ensure that the detectors are in sorted order, for fast lookups
            zptVisitCatalog.sort()
            yield (int(lastVisit), zptVisitCatalog)
        else:
            # We need to create a new schema
            zptExpCatSchema = afwTable.ExposureTable.makeMinimalSchema()
            zptExpCatSchema.addField('visit', type='L', doc='Visit number')

        # And start a new one
        zptVisitCatalog = afwTable.ExposureCatalog(zptExpCatSchema)
        zptVisitCatalog.setMetadata(metadata)

        lastVisit = int(rec['visit'])

    catRecord = zptVisitCatalog.addNew()
    catRecord['id'] = int(rec['detector'])
    catRecord['visit'] = rec['visit']
    catRecord.setPhotoCalib(photoCalib)

# Final output of last exposure catalog
# ensure that the detectors are in sorted order, for fast lookups
zptVisitCatalog.sort()
yield (int(lastVisit), zptVisitCatalog)

def _getChebyshevBoundedField(self, coefficients, xyMax, offset=0.0, scaling=1.0):

Definition at line 828 of file fgcmOutputProducts.py.

◆ fgcmBuildStarsConfig

lsst.fgcmcal.fgcmOutputProducts.fgcmBuildStarsConfig : `lsst.fgcmcal.FgcmBuildStarsConfig`

Definition at line 430 of file fgcmOutputProducts.py.

◆ filterLabel

lsst.fgcmcal.fgcmOutputProducts.filterLabel : `lsst.afw.image.FilterLabel`

Definition at line 638 of file fgcmOutputProducts.py.

◆ handleDict

lsst.fgcmcal.fgcmOutputProducts.handleDict : `dict`
    physicalFilterMap = pexConfig.DictField(
        doc="Mapping from 'physicalFilter' to band.",
        keytype=str,
        itemtype=str,
        default={},
    )
    # The following fields refer to calibrating from a reference
    # catalog, but in the future this might need to be expanded
    doReferenceCalibration = pexConfig.Field(
        doc=("Transfer 'absolute' calibration from reference catalog? "
             "This afterburner step is unnecessary if reference stars "
             "were used in the full fit in FgcmFitCycleTask."),
        dtype=bool,
        default=False,
    )
    doAtmosphereOutput = pexConfig.Field(
        doc="Output atmospheres in transmission_atmosphere_fgcm format",
        dtype=bool,
        default=True,
    )
    doZeropointOutput = pexConfig.Field(
        doc="Output zeropoints in fgcm_photoCalib format",
        dtype=bool,
        default=True,
    )
    doComposeWcsJacobian = pexConfig.Field(
        doc="Compose Jacobian of WCS with fgcm calibration for output photoCalib?",
        dtype=bool,
        default=True,
    )
    doApplyMeanChromaticCorrection = pexConfig.Field(
        doc="Apply the mean chromatic correction to the zeropoints?",
        dtype=bool,
        default=True,
    )
    photoCal = pexConfig.ConfigurableField(
        target=PhotoCalTask,
        doc="task to perform 'absolute' calibration",
    )
    referencePixelizationNside = pexConfig.Field(
        doc="Healpix nside to pixelize catalog to compare to reference catalog",
        dtype=int,
        default=64,
    )
    referencePixelizationMinStars = pexConfig.Field(
        doc=("Minimum number of stars per healpix pixel to select for comparison"
             "to the specified reference catalog"),
        dtype=int,
        default=200,
    )
    referenceMinMatch = pexConfig.Field(
        doc="Minimum number of stars matched to reference catalog to be used in statistics",
        dtype=int,
        default=50,
    )
    referencePixelizationNPixels = pexConfig.Field(
        doc=("Number of healpix pixels to sample to do comparison. "
             "Doing too many will take a long time and not yield any more "
             "precise results because the final number is the median offset "
             "(per band) from the set of pixels."),
        dtype=int,
        default=100,
    )

    def setDefaults(self):
        pexConfig.Config.setDefaults(self)

        # In order to transfer the "absolute" calibration from a reference
        # catalog to the relatively calibrated FGCM standard stars (one number
        # per band), we use the PhotoCalTask to match stars in a sample of healpix
        # pixels.  These basic settings ensure that only well-measured, good stars
        # from the source and reference catalogs are used for the matching.

        # applyColorTerms needs to be False if doReferenceCalibration is False,
        # as is the new default after DM-16702
        self.photoCal.applyColorTerms = False
        self.photoCal.fluxField = 'instFlux'
        self.photoCal.magErrFloor = 0.003
        self.photoCal.match.referenceSelection.doSignalToNoise = True
        self.photoCal.match.referenceSelection.signalToNoise.minimum = 10.0
        self.photoCal.match.sourceSelection.doSignalToNoise = True
        self.photoCal.match.sourceSelection.signalToNoise.minimum = 10.0
        self.photoCal.match.sourceSelection.signalToNoise.fluxField = 'instFlux'
        self.photoCal.match.sourceSelection.signalToNoise.errField = 'instFluxErr'
        self.photoCal.match.sourceSelection.doFlags = True
        self.photoCal.match.sourceSelection.flags.good = []
        self.photoCal.match.sourceSelection.flags.bad = ['flag_badStar']
        self.photoCal.match.sourceSelection.doUnresolved = False
        self.photoCal.match.sourceSelection.doRequirePrimary = False


class FgcmOutputProductsTask(pipeBase.PipelineTask):
ConfigClass = FgcmOutputProductsConfig
_DefaultName = "fgcmOutputProducts"

def __init__(self, **kwargs):
    super().__init__(**kwargs)

def runQuantum(self, butlerQC, inputRefs, outputRefs):
    handleDict = {}
    handleDict['camera'] = butlerQC.get(inputRefs.camera)
    handleDict['fgcmLookUpTable'] = butlerQC.get(inputRefs.fgcmLookUpTable)
    handleDict['fgcmVisitCatalog'] = butlerQC.get(inputRefs.fgcmVisitCatalog)
    handleDict['fgcmStandardStars'] = butlerQC.get(inputRefs.fgcmStandardStars)

    if self.config.doZeropointOutput:
        handleDict['fgcmZeropoints'] = butlerQC.get(inputRefs.fgcmZeropoints)
        photoCalibRefDict = {photoCalibRef.dataId['visit']:
                             photoCalibRef for photoCalibRef in outputRefs.fgcmPhotoCalib}

    if self.config.doAtmosphereOutput:
        handleDict['fgcmAtmosphereParameters'] = butlerQC.get(inputRefs.fgcmAtmosphereParameters)
        atmRefDict = {atmRef.dataId['visit']: atmRef for
                      atmRef in outputRefs.fgcmTransmissionAtmosphere}

    if self.config.doReferenceCalibration:
        refConfig = LoadReferenceObjectsConfig()
        self.refObjLoader = ReferenceObjectLoader(dataIds=[ref.datasetRef.dataId
                                                           for ref in inputRefs.refCat],
                                                  refCats=butlerQC.get(inputRefs.refCat),
                                                  name=self.config.connections.refCat,
                                                  log=self.log,
                                                  config=refConfig)
    else:
        self.refObjLoader = None

    struct = self.run(handleDict, self.config.physicalFilterMap)

    # Output the photoCalib exposure catalogs
    if struct.photoCalibCatalogs is not None:
        self.log.info("Outputting photoCalib catalogs.")
        for visit, expCatalog in struct.photoCalibCatalogs:
            butlerQC.put(expCatalog, photoCalibRefDict[visit])
        self.log.info("Done outputting photoCalib catalogs.")

    # Output the atmospheres
    if struct.atmospheres is not None:
        self.log.info("Outputting atmosphere transmission files.")
        for visit, atm in struct.atmospheres:
            butlerQC.put(atm, atmRefDict[visit])
        self.log.info("Done outputting atmosphere files.")

    if self.config.doReferenceCalibration:
        # Turn offset into simple catalog for persistence if necessary
        schema = afwTable.Schema()
        schema.addField('offset', type=np.float64,
                        doc="Post-process calibration offset (mag)")
        offsetCat = afwTable.BaseCatalog(schema)
        offsetCat.resize(len(struct.offsets))
        offsetCat['offset'][:] = struct.offsets

        butlerQC.put(offsetCat, outputRefs.fgcmOffsets)

    return

def run(self, handleDict, physicalFilterMap):
stdCat = handleDict['fgcmStandardStars'].get()
md = stdCat.getMetadata()
bands = md.getArray('BANDS')

if self.config.doReferenceCalibration:
    lutCat = handleDict['fgcmLookUpTable'].get()
    offsets = self._computeReferenceOffsets(stdCat, lutCat, physicalFilterMap, bands)
else:
    offsets = np.zeros(len(bands))

del stdCat

if self.config.doZeropointOutput:
    zptCat = handleDict['fgcmZeropoints'].get()
    visitCat = handleDict['fgcmVisitCatalog'].get()

    pcgen = self._outputZeropoints(handleDict['camera'], zptCat, visitCat, offsets, bands,
                                   physicalFilterMap)
else:
    pcgen = None

if self.config.doAtmosphereOutput:
    atmCat = handleDict['fgcmAtmosphereParameters'].get()
    atmgen = self._outputAtmospheres(handleDict, atmCat)
else:
    atmgen = None

retStruct = pipeBase.Struct(offsets=offsets,
                            atmospheres=atmgen)
retStruct.photoCalibCatalogs = pcgen

return retStruct

def generateTractOutputProducts(self, handleDict, tract,
                            visitCat, zptCat, atmCat, stdCat,
                            fgcmBuildStarsConfig):
orderPlus1 = int(np.sqrt(coefficients.size))
pars = np.zeros((orderPlus1, orderPlus1))

bbox = lsst.geom.Box2I(lsst.geom.Point2I(0.0, 0.0),
                       lsst.geom.Point2I(*xyMax))

pars[:, :] = (coefficients.reshape(orderPlus1, orderPlus1)
              * (10.**(offset/-2.5))*scaling)

boundedField = afwMath.ChebyshevBoundedField(bbox, pars)

return boundedField

def _outputAtmospheres(self, handleDict, atmCat):

Definition at line 335 of file fgcmOutputProducts.py.

◆ lutCat

lsst.fgcmcal.fgcmOutputProducts.lutCat : `lsst.afw.table.SimpleCatalog`

Definition at line 494 of file fgcmOutputProducts.py.

◆ offset

lsst.fgcmcal.fgcmOutputProducts.offset : `float`, optional

Definition at line 832 of file fgcmOutputProducts.py.

◆ offsets

lsst.fgcmcal.fgcmOutputProducts.offsets : `np.ndarray`

Definition at line 361 of file fgcmOutputProducts.py.

◆ photoCalibCatalogs

lsst.fgcmcal.fgcmOutputProducts.photoCalibCatalogs : `generator` [(`int`, `lsst.afw.table.ExposureCatalog`)]

Definition at line 365 of file fgcmOutputProducts.py.

◆ physicalFilterMap

lsst.fgcmcal.fgcmOutputProducts.physicalFilterMap : `dict`

Definition at line 353 of file fgcmOutputProducts.py.

◆ refFluxFields

lsst.fgcmcal.fgcmOutputProducts.refFluxFields : `list`

Definition at line 644 of file fgcmOutputProducts.py.

◆ retStruct

lsst.fgcmcal.fgcmOutputProducts.retStruct : `lsst.pipe.base.Struct`

Definition at line 358 of file fgcmOutputProducts.py.

◆ scaling

lsst.fgcmcal.fgcmOutputProducts.scaling : `float`, optional

Definition at line 834 of file fgcmOutputProducts.py.

◆ selected

lsst.fgcmcal.fgcmOutputProducts.selected : `numpy.array(dtype=bool)`

Definition at line 642 of file fgcmOutputProducts.py.

◆ sourceMapper

lsst.fgcmcal.fgcmOutputProducts.sourceMapper : `lsst.afw.table.SchemaMapper`
# Only use stars that are observed in all the bands that were actually used
# This will ensure that we use the same healpix pixels for the absolute
# calibration of each band.
minObs = stdCat['ngood'].min(axis=1)

goodStars = (minObs >= 1)
stdCat = stdCat[goodStars]

self.log.info("Found %d stars with at least 1 good observation in each band" %
              (len(stdCat)))

# Associate each band with the appropriate physicalFilter and make
# filterLabels
filterLabels = []

lutPhysicalFilters = lutCat[0]['physicalFilters'].split(',')
lutStdPhysicalFilters = lutCat[0]['stdPhysicalFilters'].split(',')
physicalFilterMapBands = list(physicalFilterMap.values())
physicalFilterMapFilters = list(physicalFilterMap.keys())
for band in bands:
    # Find a physical filter associated from the band by doing
    # a reverse lookup on the physicalFilterMap dict
    physicalFilterMapIndex = physicalFilterMapBands.index(band)
    physicalFilter = physicalFilterMapFilters[physicalFilterMapIndex]
    # Find the appropriate fgcm standard physicalFilter
    lutPhysicalFilterIndex = lutPhysicalFilters.index(physicalFilter)
    stdPhysicalFilter = lutStdPhysicalFilters[lutPhysicalFilterIndex]
    filterLabels.append(afwImage.FilterLabel(band=band,
                                             physical=stdPhysicalFilter))

# We have to make a table for each pixel with flux/fluxErr
# This is a temporary table generated for input to the photoCal task.
# These fluxes are not instFlux (they are top-of-the-atmosphere approximate and
# have had chromatic corrections applied to get to the standard system
# specified by the atmosphere/instrumental parameters), nor are they
# in Jansky (since they don't have a proper absolute calibration: the overall
# zeropoint is estimated from the telescope size, etc.)
sourceMapper = afwTable.SchemaMapper(stdCat.schema)
sourceMapper.addMinimalSchema(afwTable.SimpleTable.makeMinimalSchema())
sourceMapper.editOutputSchema().addField('instFlux', type=np.float64,
                                         doc="instrumental flux (counts)")
sourceMapper.editOutputSchema().addField('instFluxErr', type=np.float64,
                                         doc="instrumental flux error (counts)")
badStarKey = sourceMapper.editOutputSchema().addField('flag_badStar',
                                                      type='Flag',
                                                      doc="bad flag")

# Split up the stars
# Note that there is an assumption here that the ra/dec coords stored
# on-disk are in radians, and therefore that starObs['coord_ra'] /
# starObs['coord_dec'] return radians when used as an array of numpy float64s.
ipring = hpg.angle_to_pixel(
    self.config.referencePixelizationNside,
    stdCat['coord_ra'],
    stdCat['coord_dec'],
    degrees=False,
)
h, rev = fgcm.fgcmUtilities.histogram_rev_sorted(ipring)

gdpix, = np.where(h >= self.config.referencePixelizationMinStars)

self.log.info("Found %d pixels (nside=%d) with at least %d good stars" %
              (gdpix.size,
               self.config.referencePixelizationNside,
               self.config.referencePixelizationMinStars))

if gdpix.size < self.config.referencePixelizationNPixels:
    self.log.warning("Found fewer good pixels (%d) than preferred in configuration (%d)" %
                     (gdpix.size, self.config.referencePixelizationNPixels))
else:
    # Sample out the pixels we want to use
    gdpix = np.random.choice(gdpix, size=self.config.referencePixelizationNPixels, replace=False)

results = np.zeros(gdpix.size, dtype=[('hpix', 'i4'),
                                      ('nstar', 'i4', len(bands)),
                                      ('nmatch', 'i4', len(bands)),
                                      ('zp', 'f4', len(bands)),
                                      ('zpErr', 'f4', len(bands))])
results['hpix'] = ipring[rev[rev[gdpix]]]

# We need a boolean index to deal with catalogs...
selected = np.zeros(len(stdCat), dtype=bool)

refFluxFields = [None]*len(bands)

for p_index, pix in enumerate(gdpix):
    i1a = rev[rev[pix]: rev[pix + 1]]

    # the stdCat afwTable can only be indexed with boolean arrays,
    # and not numpy index arrays (see DM-16497).  This little trick
    # converts the index array into a boolean array
    selected[:] = False
    selected[i1a] = True

    for b_index, filterLabel in enumerate(filterLabels):
        struct = self._computeOffsetOneBand(sourceMapper, badStarKey, b_index,
                                            filterLabel, stdCat,
                                            selected, refFluxFields)
        results['nstar'][p_index, b_index] = len(i1a)
        results['nmatch'][p_index, b_index] = len(struct.arrays.refMag)
        results['zp'][p_index, b_index] = struct.zp
        results['zpErr'][p_index, b_index] = struct.sigma

# And compute the summary statistics
offsets = np.zeros(len(bands))

for b_index, band in enumerate(bands):
    # make configurable
    ok, = np.where(results['nmatch'][:, b_index] >= self.config.referenceMinMatch)
    offsets[b_index] = np.median(results['zp'][ok, b_index])
    # use median absolute deviation to estimate Normal sigma
    # see https://en.wikipedia.org/wiki/Median_absolute_deviation
    madSigma = 1.4826*np.median(np.abs(results['zp'][ok, b_index] - offsets[b_index]))
    self.log.info("Reference catalog offset for %s band: %.12f +/- %.12f",
                  band, offsets[b_index], madSigma)

return offsets

def _computeOffsetOneBand(self, sourceMapper, badStarKey,
                      b_index, filterLabel, stdCat, selected, refFluxFields):

Definition at line 632 of file fgcmOutputProducts.py.

◆ stdCat

lsst.fgcmcal.fgcmOutputProducts.stdCat : `lsst.afw.table.SimpleCatalog`
physicalFilterMap = fgcmBuildStarsConfig.physicalFilterMap

md = stdCat.getMetadata()
bands = md.getArray('BANDS')

if self.config.doComposeWcsJacobian and not fgcmBuildStarsConfig.doApplyWcsJacobian:
    raise RuntimeError("Cannot compose the WCS jacobian if it hasn't been applied "
                       "in fgcmBuildStarsTask.")

if not self.config.doComposeWcsJacobian and fgcmBuildStarsConfig.doApplyWcsJacobian:
    self.log.warning("Jacobian was applied in build-stars but doComposeWcsJacobian is not set.")

if self.config.doReferenceCalibration:
    lutCat = handleDict['fgcmLookUpTable'].get()
    offsets = self._computeReferenceOffsets(stdCat, lutCat, bands, physicalFilterMap)
else:
    offsets = np.zeros(len(bands))

if self.config.doZeropointOutput:
    pcgen = self._outputZeropoints(handleDict['camera'], zptCat, visitCat, offsets, bands,
                                   physicalFilterMap)
else:
    pcgen = None

if self.config.doAtmosphereOutput:
    atmgen = self._outputAtmospheres(handleDict, atmCat)
else:
    atmgen = None

retStruct = pipeBase.Struct(offsets=offsets,
                            atmospheres=atmgen)
retStruct.photoCalibCatalogs = pcgen

return retStruct

def _computeReferenceOffsets(self, stdCat, lutCat, physicalFilterMap, bands):

Definition at line 428 of file fgcmOutputProducts.py.

◆ tract

lsst.fgcmcal.fgcmOutputProducts.tract : `int`

Definition at line 420 of file fgcmOutputProducts.py.

◆ visitCat

lsst.fgcmcal.fgcmOutputProducts.visitCat : `lsst.afw.table.BaseCatalog`

Definition at line 422 of file fgcmOutputProducts.py.

◆ xyMax

int lsst.fgcmcal.fgcmOutputProducts.xyMax : `list` of length 2

Definition at line 830 of file fgcmOutputProducts.py.

◆ zptCat

lsst.fgcmcal.fgcmOutputProducts.zptCat : `lsst.afw.table.BaseCatalog`

Definition at line 424 of file fgcmOutputProducts.py.