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