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. 
   39 import lsst.pex.config 
as pexConfig
 
   47 from .fgcmLoadReferenceCatalog 
import FgcmLoadReferenceCatalogTask
 
   48 from .utilities 
import computeApproxPixelAreaFields, computeApertureRadius
 
   52 REFSTARS_FORMAT_VERSION = 1
 
   54 __all__ = [
'FgcmBuildStarsConfig', 
'FgcmBuildStarsTask', 
'FgcmBuildStarsRunner']
 
   58     """Config for FgcmBuildStarsTask""" 
   60     instFluxField = pexConfig.Field(
 
   61         doc=(
"Name of the source instFlux field to use.  The associated flag field " 
   62              "('<name>_flag') will be implicitly included in badFlags"),
 
   64         default=
'slot_CalibFlux_instFlux',
 
   66     minPerBand = pexConfig.Field(
 
   67         doc=
"Minimum observations per band",
 
   71     matchRadius = pexConfig.Field(
 
   72         doc=
"Match radius (arcseconds)",
 
   76     isolationRadius = pexConfig.Field(
 
   77         doc=
"Isolation radius (arcseconds)",
 
   81     densityCutNside = pexConfig.Field(
 
   82         doc=
"Density cut healpix nside",
 
   86     densityCutMaxPerPixel = pexConfig.Field(
 
   87         doc=
"Density cut number of stars per pixel",
 
   91     matchNside = pexConfig.Field(
 
   92         doc=
"Healpix Nside for matching",
 
   96     coarseNside = pexConfig.Field(
 
   97         doc=
"Healpix coarse Nside for partitioning matches",
 
  101     filterMap = pexConfig.DictField(
 
  102         doc=
"Mapping from 'filterName' to band.",
 
  107     requiredBands = pexConfig.ListField(
 
  108         doc=
"Bands required for each star",
 
  112     primaryBands = pexConfig.ListField(
 
  113         doc=(
"Bands for 'primary' star matches. " 
  114              "A star must be observed in one of these bands to be considered " 
  115              "as a calibration star."),
 
  119     referenceCCD = pexConfig.Field(
 
  120         doc=
"Reference CCD for scanning visits",
 
  124     checkAllCcds = pexConfig.Field(
 
  125         doc=(
"Check repo for all CCDs for each visit specified.  To be used when the " 
  126              "full set of ids (visit/ccd) are not specified on the command line.  For " 
  127              "Gen2, specifying one ccd and setting checkAllCcds=True is significantly " 
  128              "faster than the alternatives."),
 
  132     visitDataRefName = pexConfig.Field(
 
  133         doc=
"dataRef name for the 'visit' field",
 
  137     ccdDataRefName = pexConfig.Field(
 
  138         doc=
"dataRef name for the 'ccd' field",
 
  142     applyJacobian = pexConfig.Field(
 
  143         doc=
"Apply Jacobian correction?",
 
  145         deprecated=(
"This field is no longer used, and has been deprecated by DM-20163. " 
  146                     "It will be removed after v20."),
 
  149     jacobianName = pexConfig.Field(
 
  150         doc=
"Name of field with jacobian correction",
 
  152         deprecated=(
"This field is no longer used, and has been deprecated by DM-20163. " 
  153                     "It will be removed after v20."),
 
  154         default=
"base_Jacobian_value" 
  156     doApplyWcsJacobian = pexConfig.Field(
 
  157         doc=
"Apply the jacobian of the WCS to the star observations prior to fit?",
 
  161     psfCandidateName = pexConfig.Field(
 
  162         doc=
"Name of field with psf candidate flag for propagation",
 
  164         default=
"calib_psf_candidate" 
  166     doSubtractLocalBackground = pexConfig.Field(
 
  167         doc=(
"Subtract the local background before performing calibration? " 
  168              "This is only supported for circular aperture calibration fluxes."),
 
  172     localBackgroundFluxField = pexConfig.Field(
 
  173         doc=
"Name of the local background instFlux field to use.",
 
  175         default=
'base_LocalBackground_instFlux' 
  177     sourceSelector = sourceSelectorRegistry.makeField(
 
  178         doc=
"How to select sources",
 
  181     apertureInnerInstFluxField = pexConfig.Field(
 
  182         doc=
"Field that contains inner aperture for aperture correction proxy",
 
  184         default=
'base_CircularApertureFlux_12_0_instFlux' 
  186     apertureOuterInstFluxField = pexConfig.Field(
 
  187         doc=
"Field that contains outer aperture for aperture correction proxy",
 
  189         default=
'base_CircularApertureFlux_17_0_instFlux' 
  191     doReferenceMatches = pexConfig.Field(
 
  192         doc=
"Match reference catalog as additional constraint on calibration",
 
  196     fgcmLoadReferenceCatalog = pexConfig.ConfigurableField(
 
  197         target=FgcmLoadReferenceCatalogTask,
 
  198         doc=
"FGCM reference object loader",
 
  200     nVisitsPerCheckpoint = pexConfig.Field(
 
  201         doc=
"Number of visits read between checkpoints",
 
  208         sourceSelector.setDefaults()
 
  210         fluxFlagName = self.
instFluxField[0: -len(
'instFlux')] + 
'flag' 
  212         sourceSelector.flags.bad = [
'base_PixelFlags_flag_edge',
 
  213                                     'base_PixelFlags_flag_interpolatedCenter',
 
  214                                     'base_PixelFlags_flag_saturatedCenter',
 
  215                                     'base_PixelFlags_flag_crCenter',
 
  216                                     'base_PixelFlags_flag_bad',
 
  217                                     'base_PixelFlags_flag_interpolated',
 
  218                                     'base_PixelFlags_flag_saturated',
 
  219                                     'slot_Centroid_flag',
 
  224             sourceSelector.flags.bad.append(localBackgroundFlagName)
 
  226         sourceSelector.doFlags = 
True 
  227         sourceSelector.doUnresolved = 
True 
  228         sourceSelector.doSignalToNoise = 
True 
  229         sourceSelector.doIsolated = 
True 
  232         sourceSelector.signalToNoise.errField = self.
instFluxField + 
'Err' 
  233         sourceSelector.signalToNoise.minimum = 10.0
 
  234         sourceSelector.signalToNoise.maximum = 1000.0
 
  238         sourceSelector.unresolved.maximum = 0.5
 
  242     """Subclass of TaskRunner for fgcmBuildStarsTask 
  244     fgcmBuildStarsTask.run() takes a number of arguments, one of which is the 
  245     butler (for persistence and mapper data), and a list of dataRefs 
  246     extracted from the command line.  Note that FGCM runs on a large set of 
  247     dataRefs, and not on single dataRef/tract/patch. 
  248     This class transforms the process arguments generated by the ArgumentParser 
  249     into the arguments expected by FgcmBuildStarsTask.run(). 
  250     This runner does not use any parallelization. 
  257         Return a list with one element: a tuple with the butler and 
  261         return [(parsedCmd.butler, parsedCmd.id.refList)]
 
  267         args: `tuple` with (butler, dataRefList) 
  271         exitStatus: `list` with `lsst.pipe.base.Struct` 
  272            exitStatus (0: success; 1: failure) 
  274         butler, dataRefList = args
 
  276         task = self.TaskClass(config=self.config, log=self.log)
 
  280             task.runDataRef(butler, dataRefList)
 
  283                 task.runDataRef(butler, dataRefList)
 
  284             except Exception 
as e:
 
  286                 task.log.fatal(
"Failed: %s" % e)
 
  287                 if not isinstance(e, pipeBase.TaskError):
 
  288                     traceback.print_exc(file=sys.stderr)
 
  290         task.writeMetadata(butler)
 
  293         return [pipeBase.Struct(exitStatus=exitStatus)]
 
  297         Run the task, with no multiprocessing 
  301         parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line 
  306         if self.precall(parsedCmd):
 
  308             resultList = self(targetList[0])
 
  315     Build stars for the FGCM global calibration 
  318     ConfigClass = FgcmBuildStarsConfig
 
  319     RunnerClass = FgcmBuildStarsRunner
 
  320     _DefaultName = 
"fgcmBuildStars" 
  324         Instantiate an `FgcmBuildStarsTask`. 
  328         butler : `lsst.daf.persistence.Butler` 
  331         pipeBase.CmdLineTask.__init__(self, **kwargs)
 
  332         self.makeSubtask(
"sourceSelector")
 
  334         self.sourceSelector.log.setLevel(self.sourceSelector.log.WARN)
 
  337     def _makeArgumentParser(cls):
 
  338         """Create an argument parser""" 
  341         parser.add_id_argument(
"--id", 
"calexp", help=
"Data ID, e.g. --id visit=6789")
 
  346     def _getMetadataName(self):
 
  352         Cross-match and make star list for FGCM Input 
  356         butler:  `lsst.daf.persistence.Butler` 
  357         dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef` 
  358            Data references for the input visits. 
  359            If this is an empty list, all visits with src catalogs in 
  360            the repository are used. 
  361            Only one individual dataRef from a visit need be specified 
  362            and the code will find the other source catalogs from 
  367         RuntimeErrror: Raised if `config.doReferenceMatches` is set and 
  368            an fgcmLookUpTable is not available, or if computeFluxApertureRadius() 
  369            fails if the calibFlux is not a CircularAperture flux. 
  372         self.log.
info(
"Running with %d dataRefs" % (len(dataRefs)))
 
  374         if self.config.doReferenceMatches:
 
  376             if not butler.datasetExists(
'fgcmLookUpTable'):
 
  377                 raise RuntimeError(
"Must have fgcmLookUpTable if using config.doReferenceMatches")
 
  380         calibFluxApertureRadius = 
None 
  381         if self.config.doSubtractLocalBackground:
 
  382             sourceSchema = butler.get(
'src_schema').schema
 
  385                                                                 self.config.instFluxField)
 
  386             except (RuntimeError, LookupError):
 
  387                 raise RuntimeError(
"Could not determine aperture radius from %s. " 
  388                                    "Cannot use doSubtractLocalBackground." %
 
  389                                    (self.config.instFluxField))
 
  393         camera = butler.get(
'camera')
 
  397         visitCatDataRef = butler.dataRef(
'fgcmVisitCatalog')
 
  398         filename = visitCatDataRef.get(
'fgcmVisitCatalog_filename')[0]
 
  399         if os.path.exists(filename):
 
  401             inVisitCat = visitCatDataRef.get()
 
  402             if len(inVisitCat) != len(groupedDataRefs):
 
  403                 raise RuntimeError(
"Existing visitCatalog found, but has an inconsistent " 
  404                                    "number of visits.  Cannot continue.")
 
  409                                              visitCatDataRef=visitCatDataRef,
 
  410                                              inVisitCat=inVisitCat)
 
  413         visitCatDataRef.put(visitCat)
 
  415         starObsDataRef = butler.dataRef(
'fgcmStarObservations')
 
  416         filename = starObsDataRef.get(
'fgcmStarObservations_filename')[0]
 
  417         if os.path.exists(filename):
 
  418             inStarObsCat = starObsDataRef.get()
 
  422         rad = calibFluxApertureRadius
 
  425                                                                   calibFluxApertureRadius=rad,
 
  426                                                                   starObsDataRef=starObsDataRef,
 
  427                                                                   visitCatDataRef=visitCatDataRef,
 
  428                                                                   inStarObsCat=inStarObsCat)
 
  429         visitCatDataRef.put(visitCat)
 
  430         starObsDataRef.put(fgcmStarObservationCat)
 
  433         fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = self.
fgcmMatchStars(butler,
 
  435                                                                             fgcmStarObservationCat)
 
  438         butler.put(fgcmStarIdCat, 
'fgcmStarIds')
 
  439         butler.put(fgcmStarIndicesCat, 
'fgcmStarIndices')
 
  440         if fgcmRefCat 
is not None:
 
  441             butler.put(fgcmRefCat, 
'fgcmReferenceStars')
 
  444                              visitCatDataRef=None, inVisitCat=None):
 
  446         Make a visit catalog with all the keys from each visit 
  450         camera: `lsst.afw.cameraGeom.Camera` 
  451            Camera from the butler 
  452         groupedDataRefs: `dict` 
  453            Dictionary with visit keys, and `list`s of 
  454            `lsst.daf.persistence.ButlerDataRef` 
  455         visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional 
  456            Dataref to write visitCat for checkpoints 
  457         inVisitCat: `afw.table.BaseCatalog` 
  458            Input (possibly incomplete) visit catalog 
  462         visitCat: `afw.table.BaseCatalog` 
  465         self.log.
info(
"Assembling visitCatalog from %d %ss" %
 
  466                       (len(groupedDataRefs), self.config.visitDataRefName))
 
  470         if inVisitCat 
is None:
 
  474             visitCat.reserve(len(groupedDataRefs))
 
  476             for i, visit 
in enumerate(sorted(groupedDataRefs)):
 
  477                 rec = visitCat.addNew()
 
  480                 rec[
'sources_read'] = 0
 
  482             visitCat = inVisitCat
 
  487                                visitCatDataRef=visitCatDataRef)
 
  491     def _fillVisitCatalog(self, visitCat, groupedDataRefs,
 
  492                           visitCatDataRef=None):
 
  494         Fill the visit catalog with visit metadata 
  498         visitCat: `afw.table.BaseCatalog` 
  499            Catalog with schema from _makeFgcmVisitSchema() 
  500         groupedDataRefs: `dict` 
  501            Dictionary with visit keys, and `list`s of 
  502            `lsst.daf.persistence.ButlerDataRef 
  503         visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional 
  504            Dataref to write visitCat for checkpoints 
  509         for i, visit 
in enumerate(sorted(groupedDataRefs)):
 
  516             if visitCat[
'used'][i]:
 
  519             if (i % self.config.nVisitsPerCheckpoint) == 0:
 
  520                 self.log.
info(
"Retrieving metadata for %s %d (%d/%d)" %
 
  521                               (self.config.visitDataRefName, visit, i, len(groupedDataRefs)))
 
  523                 if visitCatDataRef 
is not None:
 
  524                     visitCatDataRef.put(visitCat)
 
  529             dataRef = groupedDataRefs[visit][0]
 
  531             exp = dataRef.get(datasetType=
'calexp_sub', bbox=bbox,
 
  532                               flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
 
  534             visitInfo = exp.getInfo().getVisitInfo()
 
  540             rec[
'filtername'] = f.getName()
 
  541             radec = visitInfo.getBoresightRaDec()
 
  542             rec[
'telra'] = radec.getRa().asDegrees()
 
  543             rec[
'teldec'] = radec.getDec().asDegrees()
 
  544             rec[
'telha'] = visitInfo.getBoresightHourAngle().asDegrees()
 
  545             rec[
'telrot'] = visitInfo.getBoresightRotAngle().asDegrees()
 
  546             rec[
'mjd'] = visitInfo.getDate().get(system=DateTime.MJD)
 
  547             rec[
'exptime'] = visitInfo.getExposureTime()
 
  550             rec[
'pmb'] = visitInfo.getWeather().getAirPressure() / 100
 
  554             rec[
'scaling'][:] = 1.0
 
  556             rec[
'deltaAper'] = 0.0
 
  558             rec[
'psfSigma'] = psf.computeShape().getDeterminantRadius()
 
  560             if dataRef.datasetExists(datasetType=
'calexpBackground'):
 
  563                 bgStats = (bg[0].getStatsImage().getImage().array
 
  564                            for bg 
in dataRef.get(datasetType=
'calexpBackground'))
 
  565                 rec[
'skyBackground'] = sum(np.median(bg[np.isfinite(bg)]) 
for bg 
in bgStats)
 
  567                 self.log.
warn(
'Sky background not found for visit %d / ccd %d' %
 
  568                               (visit, dataRef.dataId[self.config.ccdDataRefName]))
 
  569                 rec[
'skyBackground'] = -1.0
 
  575         Find and group dataRefs (by visit).  If dataRefs is an empty list, 
  576         this will look for all source catalogs in a given repo. 
  580         butler: `lsst.daf.persistence.Butler` 
  581         dataRefs: `list` of `lsst.daf.persistence.ButlerDataRef` 
  582            Data references for the input visits. 
  583            If this is an empty list, all visits with src catalogs in 
  584            the repository are used. 
  588         groupedDataRefs: `dict` 
  589            Dictionary with visit keys, and `list`s of `lsst.daf.persistence.ButlerDataRef` 
  592         self.log.
info(
"Grouping dataRefs by %s" % (self.config.visitDataRefName))
 
  594         camera = butler.get(
'camera')
 
  597         for detector 
in camera:
 
  598             ccdIds.append(detector.getId())
 
  607         for dataRef 
in dataRefs:
 
  608             visit = dataRef.dataId[self.config.visitDataRefName]
 
  610             if not dataRef.datasetExists(datasetType=
'src'):
 
  613             if self.config.checkAllCcds:
 
  614                 if visit 
in groupedDataRefs:
 
  617                 dataId = dataRef.dataId.copy()
 
  620                     dataId[self.config.ccdDataRefName] = ccdId
 
  621                     if butler.datasetExists(
'src', dataId=dataId):
 
  622                         goodDataRef = butler.dataRef(
'src', dataId=dataId)
 
  623                         if visit 
in groupedDataRefs:
 
  624                             if (goodDataRef.dataId[self.config.ccdDataRefName] 
not in 
  625                                [d.dataId[self.config.ccdDataRefName] 
for d 
in groupedDataRefs[visit]]):
 
  626                                 groupedDataRefs[visit].
append(goodDataRef)
 
  630                             groupedDataRefs[visit] = [goodDataRef]
 
  634                 if visit 
in groupedDataRefs:
 
  635                     if (dataRef.dataId[self.config.ccdDataRefName] 
not in 
  636                        [d.dataId[self.config.ccdDataRefName] 
for d 
in groupedDataRefs[visit]]):
 
  637                         groupedDataRefs[visit].
append(dataRef)
 
  641                     groupedDataRefs[visit] = [dataRef]
 
  643             if (nVisits % 100) == 0 
and nVisits > 0:
 
  644                 self.log.
info(
"Found %d unique %ss..." % (nVisits,
 
  645                                                           self.config.visitDataRefName))
 
  647         self.log.
info(
"Found %d unique %ss total." % (nVisits,
 
  648                                                       self.config.visitDataRefName))
 
  651         def ccdSorter(dataRef):
 
  652             ccdId = dataRef.dataId[self.config.ccdDataRefName]
 
  653             if ccdId == self.config.referenceCCD:
 
  659         if not self.config.checkAllCcds:
 
  660             for visit 
in groupedDataRefs:
 
  661                 groupedDataRefs[visit] = sorted(groupedDataRefs[visit], key=ccdSorter)
 
  663         return groupedDataRefs
 
  666                                     calibFluxApertureRadius=None,
 
  667                                     visitCatDataRef=None,
 
  671         Compile all good star observations from visits in visitCat.  Checkpoint files 
  672         will be stored if both visitCatDataRef and starObsDataRef are not None. 
  676         groupedDataRefs: `dict` of `list`s 
  677            Lists of `lsst.daf.persistence.ButlerDataRef`, grouped by visit. 
  678         visitCat: `afw.table.BaseCatalog` 
  679            Catalog with visit data for FGCM 
  680         calibFluxApertureRadius: `float`, optional 
  681            Aperture radius for calibration flux.  Default is None. 
  682         visitCatDataRef: `lsst.daf.persistence.ButlerDataRef`, optional 
  683            Dataref to write visitCat for checkpoints 
  684         starObsDataRef: `lsst.daf.persistence.ButlerDataRef`, optional 
  685            Dataref to write the star observation catalog for checkpoints. 
  686         inStarObsCat: `afw.table.BaseCatalog` 
  687            Input (possibly incomplete) observation catalog 
  691         fgcmStarObservations: `afw.table.BaseCatalog` 
  692            Full catalog of good observations. 
  696         RuntimeError: Raised if doSubtractLocalBackground is True and 
  697            calibFluxApertureRadius is not set. 
  699         startTime = time.time()
 
  701         if (visitCatDataRef 
is not None and starObsDataRef 
is None or 
  702            visitCatDataRef 
is None and starObsDataRef 
is not None):
 
  703             self.log.
warn(
"Only one of visitCatDataRef and starObsDataRef are set, so " 
  704                           "no checkpoint files will be persisted.")
 
  706         if self.config.doSubtractLocalBackground 
and calibFluxApertureRadius 
is None:
 
  707             raise RuntimeError(
"Must set calibFluxApertureRadius if doSubtractLocalBackground is True.")
 
  710         dataRef = groupedDataRefs[
list(groupedDataRefs.keys())[0]][0]
 
  711         sourceSchema = dataRef.get(
'src_schema', immediate=
True).schema
 
  714         camera = dataRef.get(
'camera')
 
  716         for ccdIndex, detector 
in enumerate(camera):
 
  717             ccdMapping[detector.getId()] = ccdIndex
 
  726         outputSchema = sourceMapper.getOutputSchema()
 
  728         if inStarObsCat 
is not None:
 
  729             fullCatalog = inStarObsCat
 
  730             comp1 = fullCatalog.schema.compare(outputSchema, outputSchema.EQUAL_KEYS)
 
  731             comp2 = fullCatalog.schema.compare(outputSchema, outputSchema.EQUAL_NAMES)
 
  732             if not comp1 
or not comp2:
 
  733                 raise RuntimeError(
"Existing fgcmStarObservations file found with mismatched schema.")
 
  739         instFluxKey = sourceSchema[self.config.instFluxField].asKey()
 
  740         instFluxErrKey = sourceSchema[self.config.instFluxField + 
'Err'].asKey()
 
  741         visitKey = outputSchema[
'visit'].asKey()
 
  742         ccdKey = outputSchema[
'ccd'].asKey()
 
  743         instMagKey = outputSchema[
'instMag'].asKey()
 
  744         instMagErrKey = outputSchema[
'instMagErr'].asKey()
 
  747         if self.config.doSubtractLocalBackground:
 
  748             localBackgroundFluxKey = sourceSchema[self.config.localBackgroundFluxField].asKey()
 
  749             localBackgroundArea = np.pi*calibFluxApertureRadius**2.
 
  751             localBackground = 0.0
 
  753         aperOutputSchema = aperMapper.getOutputSchema()
 
  755         instFluxAperInKey = sourceSchema[self.config.apertureInnerInstFluxField].asKey()
 
  756         instFluxErrAperInKey = sourceSchema[self.config.apertureInnerInstFluxField + 
'Err'].asKey()
 
  757         instFluxAperOutKey = sourceSchema[self.config.apertureOuterInstFluxField].asKey()
 
  758         instFluxErrAperOutKey = sourceSchema[self.config.apertureOuterInstFluxField + 
'Err'].asKey()
 
  759         instMagInKey = aperOutputSchema[
'instMag_aper_inner'].asKey()
 
  760         instMagErrInKey = aperOutputSchema[
'instMagErr_aper_inner'].asKey()
 
  761         instMagOutKey = aperOutputSchema[
'instMag_aper_outer'].asKey()
 
  762         instMagErrOutKey = aperOutputSchema[
'instMagErr_aper_outer'].asKey()
 
  764         k = 2.5 / np.log(10.)
 
  767         for ctr, visit 
in enumerate(visitCat):
 
  768             if visit[
'sources_read']:
 
  771             expTime = visit[
'exptime']
 
  778             for dataRef 
in groupedDataRefs[visit[
'visit']]:
 
  780                 ccdId = dataRef.dataId[self.config.ccdDataRefName]
 
  782                 sources = dataRef.get(datasetType=
'src', flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
 
  788                 if self.config.doSubtractLocalBackground:
 
  800                     localBackground = localBackgroundArea*sources[localBackgroundFluxKey]
 
  801                     sources[instFluxKey] -= localBackground
 
  803                 goodSrc = self.sourceSelector.selectSources(sources)
 
  806                 tempCat.reserve(goodSrc.selected.sum())
 
  807                 tempCat.extend(sources[goodSrc.selected], mapper=sourceMapper)
 
  808                 tempCat[visitKey][:] = visit[
'visit']
 
  809                 tempCat[ccdKey][:] = ccdId
 
  812                 scaledInstFlux = (sources[instFluxKey][goodSrc.selected] *
 
  813                                   visit[
'scaling'][ccdMapping[ccdId]])
 
  814                 tempCat[instMagKey][:] = (-2.5*np.log10(scaledInstFlux) + 2.5*np.log10(expTime))
 
  819                 tempCat[instMagErrKey][:] = k*(sources[instFluxErrKey][goodSrc.selected] /
 
  820                                                sources[instFluxKey][goodSrc.selected])
 
  823                 tempCat[
'jacobian'] = approxPixelAreaFields[ccdId].evaluate(tempCat[
'x'],
 
  827                 if self.config.doApplyWcsJacobian:
 
  828                     tempCat[instMagKey][:] -= 2.5*np.log10(tempCat[
'jacobian'][:])
 
  830                 fullCatalog.extend(tempCat)
 
  835                 tempAperCat.reserve(goodSrc.selected.sum())
 
  836                 tempAperCat.extend(sources[goodSrc.selected], mapper=aperMapper)
 
  838                 with np.warnings.catch_warnings():
 
  841                     np.warnings.simplefilter(
"ignore")
 
  843                     tempAperCat[instMagInKey][:] = -2.5*np.log10(
 
  844                         sources[instFluxAperInKey][goodSrc.selected])
 
  845                     tempAperCat[instMagErrInKey][:] = (2.5/np.log(10.))*(
 
  846                         sources[instFluxErrAperInKey][goodSrc.selected] /
 
  847                         sources[instFluxAperInKey][goodSrc.selected])
 
  848                     tempAperCat[instMagOutKey][:] = -2.5*np.log10(
 
  849                         sources[instFluxAperOutKey][goodSrc.selected])
 
  850                     tempAperCat[instMagErrOutKey][:] = (2.5/np.log(10.))*(
 
  851                         sources[instFluxErrAperOutKey][goodSrc.selected] /
 
  852                         sources[instFluxAperOutKey][goodSrc.selected])
 
  854                 aperVisitCatalog.extend(tempAperCat)
 
  856                 nStarInVisit += len(tempCat)
 
  859             if not aperVisitCatalog.isContiguous():
 
  860                 aperVisitCatalog = aperVisitCatalog.copy(deep=
True)
 
  862             instMagIn = aperVisitCatalog[instMagInKey]
 
  863             instMagErrIn = aperVisitCatalog[instMagErrInKey]
 
  864             instMagOut = aperVisitCatalog[instMagOutKey]
 
  865             instMagErrOut = aperVisitCatalog[instMagErrOutKey]
 
  867             ok = (np.isfinite(instMagIn) & np.isfinite(instMagErrIn) &
 
  868                   np.isfinite(instMagOut) & np.isfinite(instMagErrOut))
 
  870             visit[
'deltaAper'] = np.median(instMagIn[ok] - instMagOut[ok])
 
  871             visit[
'sources_read'] = 1
 
  873             self.log.
info(
"  Found %d good stars in visit %d (deltaAper = %.3f)" %
 
  874                           (nStarInVisit, visit[
'visit'], visit[
'deltaAper']))
 
  876             if ((ctr % self.config.nVisitsPerCheckpoint) == 0 
and 
  877                starObsDataRef 
is not None and visitCatDataRef 
is not None):
 
  880                 starObsDataRef.put(fullCatalog)
 
  881                 visitCatDataRef.put(visitCat)
 
  883         self.log.
info(
"Found all good star observations in %.2f s" %
 
  884                       (time.time() - startTime))
 
  890         Use FGCM code to match observations into unique stars. 
  894         butler: `lsst.daf.persistence.Butler` 
  895         visitCat: `afw.table.BaseCatalog` 
  896            Catalog with visit data for fgcm 
  897         obsCat: `afw.table.BaseCatalog` 
  898            Full catalog of star observations for fgcm 
  902         fgcmStarIdCat: `afw.table.BaseCatalog` 
  903            Catalog of unique star identifiers and index keys 
  904         fgcmStarIndicesCat: `afwTable.BaseCatalog` 
  905            Catalog of unique star indices 
  906         fgcmRefCat: `afw.table.BaseCatalog` 
  907            Catalog of matched reference stars. 
  908            Will be None if `config.doReferenceMatches` is False. 
  911         if self.config.doReferenceMatches:
 
  913             self.makeSubtask(
"fgcmLoadReferenceCatalog", butler=butler)
 
  917         visitFilterNames = np.zeros(len(visitCat), dtype=
'a10')
 
  918         for i 
in range(len(visitCat)):
 
  919             visitFilterNames[i] = visitCat[i][
'filtername']
 
  922         visitIndex = np.searchsorted(visitCat[
'visit'],
 
  925         obsFilterNames = visitFilterNames[visitIndex]
 
  927         if self.config.doReferenceMatches:
 
  929             lutCat = butler.get(
'fgcmLookUpTable')
 
  931             stdFilterDict = {filterName: stdFilter 
for (filterName, stdFilter) 
in 
  932                              zip(lutCat[0][
'filterNames'].split(
','),
 
  933                                  lutCat[0][
'stdFilterNames'].split(
','))}
 
  934             stdLambdaDict = {stdFilter: stdLambda 
for (stdFilter, stdLambda) 
in 
  935                              zip(lutCat[0][
'stdFilterNames'].split(
','),
 
  936                                  lutCat[0][
'lambdaStdFilter'])}
 
  943             self.log.
info(
"Using the following reference filters: %s" %
 
  944                           (
', '.join(referenceFilterNames)))
 
  948             referenceFilterNames = []
 
  952         starConfig = {
'logger': self.log,
 
  953                       'filterToBand': self.config.filterMap,
 
  954                       'requiredBands': self.config.requiredBands,
 
  955                       'minPerBand': self.config.minPerBand,
 
  956                       'matchRadius': self.config.matchRadius,
 
  957                       'isolationRadius': self.config.isolationRadius,
 
  958                       'matchNSide': self.config.matchNside,
 
  959                       'coarseNSide': self.config.coarseNside,
 
  960                       'densNSide': self.config.densityCutNside,
 
  961                       'densMaxPerPixel': self.config.densityCutMaxPerPixel,
 
  962                       'primaryBands': self.config.primaryBands,
 
  963                       'referenceFilterNames': referenceFilterNames}
 
  966         fgcmMakeStars = fgcm.FgcmMakeStars(starConfig)
 
  974         conv = obsCat[0][
'ra'].asDegrees() / float(obsCat[0][
'ra'])
 
  975         fgcmMakeStars.makePrimaryStars(obsCat[
'ra'] * conv,
 
  976                                        obsCat[
'dec'] * conv,
 
  977                                        filterNameArray=obsFilterNames,
 
  981         fgcmMakeStars.makeMatchedStars(obsCat[
'ra'] * conv,
 
  982                                        obsCat[
'dec'] * conv,
 
  985         if self.config.doReferenceMatches:
 
  986             fgcmMakeStars.makeReferenceMatches(self.fgcmLoadReferenceCatalog)
 
  994         fgcmStarIdCat.reserve(fgcmMakeStars.objIndexCat.size)
 
  995         for i 
in range(fgcmMakeStars.objIndexCat.size):
 
  996             fgcmStarIdCat.addNew()
 
  999         fgcmStarIdCat[
'fgcm_id'][:] = fgcmMakeStars.objIndexCat[
'fgcm_id']
 
 1000         fgcmStarIdCat[
'ra'][:] = fgcmMakeStars.objIndexCat[
'ra']
 
 1001         fgcmStarIdCat[
'dec'][:] = fgcmMakeStars.objIndexCat[
'dec']
 
 1002         fgcmStarIdCat[
'obsArrIndex'][:] = fgcmMakeStars.objIndexCat[
'obsarrindex']
 
 1003         fgcmStarIdCat[
'nObs'][:] = fgcmMakeStars.objIndexCat[
'nobs']
 
 1008         fgcmStarIndicesCat.reserve(fgcmMakeStars.obsIndexCat.size)
 
 1009         for i 
in range(fgcmMakeStars.obsIndexCat.size):
 
 1010             fgcmStarIndicesCat.addNew()
 
 1012         fgcmStarIndicesCat[
'obsIndex'][:] = fgcmMakeStars.obsIndexCat[
'obsindex']
 
 1014         if self.config.doReferenceMatches:
 
 1018             fgcmRefCat.reserve(fgcmMakeStars.referenceCat.size)
 
 1020             for i 
in range(fgcmMakeStars.referenceCat.size):
 
 1023             fgcmRefCat[
'fgcm_id'][:] = fgcmMakeStars.referenceCat[
'fgcm_id']
 
 1024             fgcmRefCat[
'refMag'][:, :] = fgcmMakeStars.referenceCat[
'refMag']
 
 1025             fgcmRefCat[
'refMagErr'][:, :] = fgcmMakeStars.referenceCat[
'refMagErr']
 
 1028             md.set(
"REFSTARS_FORMAT_VERSION", REFSTARS_FORMAT_VERSION)
 
 1029             md.set(
"FILTERNAMES", referenceFilterNames)
 
 1030             fgcmRefCat.setMetadata(md)
 
 1035         return fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat
 
 1037     def _makeFgcmVisitSchema(self, nCcd):
 
 1039         Make a schema for an fgcmVisitCatalog 
 1044            Number of CCDs in the camera 
 1048         schema: `afwTable.Schema` 
 1052         schema.addField(
'visit', type=np.int32, doc=
"Visit number")
 
 1054         schema.addField(
'filtername', type=str, size=10, doc=
"Filter name")
 
 1055         schema.addField(
'telra', type=np.float64, doc=
"Pointing RA (deg)")
 
 1056         schema.addField(
'teldec', type=np.float64, doc=
"Pointing Dec (deg)")
 
 1057         schema.addField(
'telha', type=np.float64, doc=
"Pointing Hour Angle (deg)")
 
 1058         schema.addField(
'telrot', type=np.float64, doc=
"Camera rotation (deg)")
 
 1059         schema.addField(
'mjd', type=np.float64, doc=
"MJD of visit")
 
 1060         schema.addField(
'exptime', type=np.float32, doc=
"Exposure time")
 
 1061         schema.addField(
'pmb', type=np.float32, doc=
"Pressure (millibar)")
 
 1062         schema.addField(
'psfSigma', type=np.float32, doc=
"PSF sigma (reference CCD)")
 
 1063         schema.addField(
'deltaAper', type=np.float32, doc=
"Delta-aperture")
 
 1064         schema.addField(
'skyBackground', type=np.float32, doc=
"Sky background (ADU) (reference CCD)")
 
 1066         schema.addField(
'deepFlag', type=np.int32, doc=
"Deep observation")
 
 1067         schema.addField(
'scaling', type=
'ArrayD', doc=
"Scaling applied due to flat adjustment",
 
 1069         schema.addField(
'used', type=np.int32, doc=
"This visit has been ingested.")
 
 1070         schema.addField(
'sources_read', type=np.int32, doc=
"This visit had sources read.")
 
 1074     def _makeSourceMapper(self, sourceSchema):
 
 1076         Make a schema mapper for fgcm sources 
 1080         sourceSchema: `afwTable.Schema` 
 1081            Default source schema from the butler 
 1085         sourceMapper: `afwTable.schemaMapper` 
 1086            Mapper to the FGCM source schema 
 1093         sourceMapper.addMapping(sourceSchema[
'coord_ra'].asKey(), 
'ra')
 
 1094         sourceMapper.addMapping(sourceSchema[
'coord_dec'].asKey(), 
'dec')
 
 1095         sourceMapper.addMapping(sourceSchema[
'slot_Centroid_x'].asKey(), 
'x')
 
 1096         sourceMapper.addMapping(sourceSchema[
'slot_Centroid_y'].asKey(), 
'y')
 
 1097         sourceMapper.addMapping(sourceSchema[self.config.psfCandidateName].asKey(),
 
 1101         sourceMapper.editOutputSchema().addField(
 
 1102             "visit", type=np.int32, doc=
"Visit number")
 
 1103         sourceMapper.editOutputSchema().addField(
 
 1104             "ccd", type=np.int32, doc=
"CCD number")
 
 1105         sourceMapper.editOutputSchema().addField(
 
 1106             "instMag", type=np.float32, doc=
"Instrumental magnitude")
 
 1107         sourceMapper.editOutputSchema().addField(
 
 1108             "instMagErr", type=np.float32, doc=
"Instrumental magnitude error")
 
 1109         sourceMapper.editOutputSchema().addField(
 
 1110             "jacobian", type=np.float32, doc=
"Relative pixel scale from wcs jacobian")
 
 1114     def _makeAperMapper(self, sourceSchema):
 
 1116         Make a schema mapper for fgcm aperture measurements 
 1120         sourceSchema: `afwTable.Schema` 
 1121            Default source schema from the butler 
 1125         aperMapper: `afwTable.schemaMapper` 
 1126            Mapper to the FGCM aperture schema 
 1130         aperMapper.addMapping(sourceSchema[
'coord_ra'].asKey(), 
'ra')
 
 1131         aperMapper.addMapping(sourceSchema[
'coord_dec'].asKey(), 
'dec')
 
 1132         aperMapper.editOutputSchema().addField(
'instMag_aper_inner', type=np.float64,
 
 1133                                                doc=
"Magnitude at inner aperture")
 
 1134         aperMapper.editOutputSchema().addField(
'instMagErr_aper_inner', type=np.float64,
 
 1135                                                doc=
"Magnitude error at inner aperture")
 
 1136         aperMapper.editOutputSchema().addField(
'instMag_aper_outer', type=np.float64,
 
 1137                                                doc=
"Magnitude at outer aperture")
 
 1138         aperMapper.editOutputSchema().addField(
'instMagErr_aper_outer', type=np.float64,
 
 1139                                                doc=
"Magnitude error at outer aperture")
 
 1143     def _makeFgcmObjSchema(self):
 
 1145         Make a schema for the objIndexCat from fgcmMakeStars 
 1149         schema: `afwTable.Schema` 
 1153         objSchema.addField(
'fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
 
 1155         objSchema.addField(
'ra', type=np.float64, doc=
'Mean object RA (deg)')
 
 1156         objSchema.addField(
'dec', type=np.float64, doc=
'Mean object Dec (deg)')
 
 1157         objSchema.addField(
'obsArrIndex', type=np.int32,
 
 1158                            doc=
'Index in obsIndexTable for first observation')
 
 1159         objSchema.addField(
'nObs', type=np.int32, doc=
'Total number of observations')
 
 1163     def _makeFgcmObsSchema(self):
 
 1165         Make a schema for the obsIndexCat from fgcmMakeStars 
 1169         schema: `afwTable.Schema` 
 1173         obsSchema.addField(
'obsIndex', type=np.int32, doc=
'Index in observation table')
 
 1177     def _makeFgcmRefSchema(self, nReferenceBands):
 
 1179         Make a schema for the referenceCat from fgcmMakeStars 
 1183         nReferenceBands: `int` 
 1184            Number of reference bands 
 1188         schema: `afwTable.Schema` 
 1192         refSchema.addField(
'fgcm_id', type=np.int32, doc=
'FGCM Unique ID')
 
 1193         refSchema.addField(
'refMag', type=
'ArrayF', doc=
'Reference magnitude array (AB)',
 
 1194                            size=nReferenceBands)
 
 1195         refSchema.addField(
'refMagErr', type=
'ArrayF', doc=
'Reference magnitude error array',
 
 1196                            size=nReferenceBands)
 
 1200     def _getReferenceFilterNames(self, visitCat, stdFilterDict, stdLambdaDict):
 
 1202         Get the reference filter names, in wavelength order, from the visitCat and 
 1203         information from the look-up-table. 
 1207         visitCat: `afw.table.BaseCatalog` 
 1208            Catalog with visit data for FGCM 
 1209         stdFilterDict: `dict` 
 1210            Mapping of filterName to stdFilterName from LUT 
 1211         stdLambdaDict: `dict` 
 1212            Mapping of stdFilterName to stdLambda from LUT 
 1216         referenceFilterNames: `list` 
 1217            Wavelength-ordered list of reference filter names 
 1221         filterNames = np.unique(visitCat.asAstropy()[
'filtername'])
 
 1224         stdFilterNames = {stdFilterDict[filterName] 
for filterName 
in filterNames}
 
 1227         referenceFilterNames = sorted(stdFilterNames, key=stdLambdaDict.get)
 
 1229         return referenceFilterNames