LSSTApplications  20.0.0
LSSTDataManagementBasePackage
fgcmBuildStars.py
Go to the documentation of this file.
1 # See COPYRIGHT file at the top of the source tree.
2 #
3 # This file is part of fgcmcal.
4 #
5 # Developed for the LSST Data Management System.
6 # This product includes software developed by the LSST Project
7 # (https://www.lsst.org).
8 # See the COPYRIGHT file at the top-level directory of this distribution
9 # for details of code ownership.
10 #
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <https://www.gnu.org/licenses/>.
23 """Build star observations for input to FGCM.
24 
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.
30 """
31 
32 import os
33 import sys
34 import time
35 import traceback
36 
37 import numpy as np
38 
39 import lsst.pex.config as pexConfig
40 import lsst.pipe.base as pipeBase
41 import lsst.afw.table as afwTable
42 import lsst.geom as geom
43 from lsst.daf.base import PropertyList
44 from lsst.daf.base.dateTime import DateTime
45 from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
46 
47 from .fgcmLoadReferenceCatalog import FgcmLoadReferenceCatalogTask
48 from .utilities import computeApproxPixelAreaFields, computeApertureRadius
49 
50 import fgcm
51 
52 REFSTARS_FORMAT_VERSION = 1
53 
54 __all__ = ['FgcmBuildStarsConfig', 'FgcmBuildStarsTask', 'FgcmBuildStarsRunner']
55 
56 
57 class FgcmBuildStarsConfig(pexConfig.Config):
58  """Config for FgcmBuildStarsTask"""
59 
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"),
63  dtype=str,
64  default='slot_CalibFlux_instFlux',
65  )
66  minPerBand = pexConfig.Field(
67  doc="Minimum observations per band",
68  dtype=int,
69  default=2,
70  )
71  matchRadius = pexConfig.Field(
72  doc="Match radius (arcseconds)",
73  dtype=float,
74  default=1.0,
75  )
76  isolationRadius = pexConfig.Field(
77  doc="Isolation radius (arcseconds)",
78  dtype=float,
79  default=2.0,
80  )
81  densityCutNside = pexConfig.Field(
82  doc="Density cut healpix nside",
83  dtype=int,
84  default=128,
85  )
86  densityCutMaxPerPixel = pexConfig.Field(
87  doc="Density cut number of stars per pixel",
88  dtype=int,
89  default=1000,
90  )
91  matchNside = pexConfig.Field(
92  doc="Healpix Nside for matching",
93  dtype=int,
94  default=4096,
95  )
96  coarseNside = pexConfig.Field(
97  doc="Healpix coarse Nside for partitioning matches",
98  dtype=int,
99  default=8,
100  )
101  filterMap = pexConfig.DictField(
102  doc="Mapping from 'filterName' to band.",
103  keytype=str,
104  itemtype=str,
105  default={},
106  )
107  requiredBands = pexConfig.ListField(
108  doc="Bands required for each star",
109  dtype=str,
110  default=(),
111  )
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."),
116  dtype=str,
117  default=None
118  )
119  referenceCCD = pexConfig.Field(
120  doc="Reference CCD for scanning visits",
121  dtype=int,
122  default=13,
123  )
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."),
129  dtype=bool,
130  default=True,
131  )
132  visitDataRefName = pexConfig.Field(
133  doc="dataRef name for the 'visit' field",
134  dtype=str,
135  default="visit"
136  )
137  ccdDataRefName = pexConfig.Field(
138  doc="dataRef name for the 'ccd' field",
139  dtype=str,
140  default="ccd"
141  )
142  applyJacobian = pexConfig.Field(
143  doc="Apply Jacobian correction?",
144  dtype=bool,
145  deprecated=("This field is no longer used, and has been deprecated by DM-20163. "
146  "It will be removed after v20."),
147  default=False
148  )
149  jacobianName = pexConfig.Field(
150  doc="Name of field with jacobian correction",
151  dtype=str,
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"
155  )
156  doApplyWcsJacobian = pexConfig.Field(
157  doc="Apply the jacobian of the WCS to the star observations prior to fit?",
158  dtype=bool,
159  default=True
160  )
161  psfCandidateName = pexConfig.Field(
162  doc="Name of field with psf candidate flag for propagation",
163  dtype=str,
164  default="calib_psf_candidate"
165  )
166  doSubtractLocalBackground = pexConfig.Field(
167  doc=("Subtract the local background before performing calibration? "
168  "This is only supported for circular aperture calibration fluxes."),
169  dtype=bool,
170  default=False
171  )
172  localBackgroundFluxField = pexConfig.Field(
173  doc="Name of the local background instFlux field to use.",
174  dtype=str,
175  default='base_LocalBackground_instFlux'
176  )
177  sourceSelector = sourceSelectorRegistry.makeField(
178  doc="How to select sources",
179  default="science"
180  )
181  apertureInnerInstFluxField = pexConfig.Field(
182  doc="Field that contains inner aperture for aperture correction proxy",
183  dtype=str,
184  default='base_CircularApertureFlux_12_0_instFlux'
185  )
186  apertureOuterInstFluxField = pexConfig.Field(
187  doc="Field that contains outer aperture for aperture correction proxy",
188  dtype=str,
189  default='base_CircularApertureFlux_17_0_instFlux'
190  )
191  doReferenceMatches = pexConfig.Field(
192  doc="Match reference catalog as additional constraint on calibration",
193  dtype=bool,
194  default=True,
195  )
196  fgcmLoadReferenceCatalog = pexConfig.ConfigurableField(
197  target=FgcmLoadReferenceCatalogTask,
198  doc="FGCM reference object loader",
199  )
200  nVisitsPerCheckpoint = pexConfig.Field(
201  doc="Number of visits read between checkpoints",
202  dtype=int,
203  default=500,
204  )
205 
206  def setDefaults(self):
207  sourceSelector = self.sourceSelector["science"]
208  sourceSelector.setDefaults()
209 
210  fluxFlagName = self.instFluxField[0: -len('instFlux')] + 'flag'
211 
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',
220  fluxFlagName]
221 
223  localBackgroundFlagName = self.localBackgroundFluxField[0: -len('instFlux')] + 'flag'
224  sourceSelector.flags.bad.append(localBackgroundFlagName)
225 
226  sourceSelector.doFlags = True
227  sourceSelector.doUnresolved = True
228  sourceSelector.doSignalToNoise = True
229  sourceSelector.doIsolated = True
230 
231  sourceSelector.signalToNoise.fluxField = self.instFluxField
232  sourceSelector.signalToNoise.errField = self.instFluxField + 'Err'
233  sourceSelector.signalToNoise.minimum = 10.0
234  sourceSelector.signalToNoise.maximum = 1000.0
235 
236  # FGCM operates on unresolved sources, and this setting is
237  # appropriate for the current base_ClassificationExtendedness
238  sourceSelector.unresolved.maximum = 0.5
239 
240 
241 class FgcmBuildStarsRunner(pipeBase.ButlerInitializedTaskRunner):
242  """Subclass of TaskRunner for fgcmBuildStarsTask
243 
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.
251 
252  """
253 
254  @staticmethod
255  def getTargetList(parsedCmd):
256  """
257  Return a list with one element: a tuple with the butler and
258  list of dataRefs
259  """
260  # we want to combine the butler with any (or no!) dataRefs
261  return [(parsedCmd.butler, parsedCmd.id.refList)]
262 
263  def __call__(self, args):
264  """
265  Parameters
266  ----------
267  args: `tuple` with (butler, dataRefList)
268 
269  Returns
270  -------
271  exitStatus: `list` with `lsst.pipe.base.Struct`
272  exitStatus (0: success; 1: failure)
273  """
274  butler, dataRefList = args
275 
276  task = self.TaskClass(config=self.config, log=self.log)
277 
278  exitStatus = 0
279  if self.doRaise:
280  task.runDataRef(butler, dataRefList)
281  else:
282  try:
283  task.runDataRef(butler, dataRefList)
284  except Exception as e:
285  exitStatus = 1
286  task.log.fatal("Failed: %s" % e)
287  if not isinstance(e, pipeBase.TaskError):
288  traceback.print_exc(file=sys.stderr)
289 
290  task.writeMetadata(butler)
291 
292  # The task does not return any results:
293  return [pipeBase.Struct(exitStatus=exitStatus)]
294 
295  def run(self, parsedCmd):
296  """
297  Run the task, with no multiprocessing
298 
299  Parameters
300  ----------
301  parsedCmd: `lsst.pipe.base.ArgumentParser` parsed command line
302  """
303 
304  resultList = []
305 
306  if self.precall(parsedCmd):
307  targetList = self.getTargetList(parsedCmd)
308  resultList = self(targetList[0])
309 
310  return resultList
311 
312 
313 class FgcmBuildStarsTask(pipeBase.CmdLineTask):
314  """
315  Build stars for the FGCM global calibration
316  """
317 
318  ConfigClass = FgcmBuildStarsConfig
319  RunnerClass = FgcmBuildStarsRunner
320  _DefaultName = "fgcmBuildStars"
321 
322  def __init__(self, butler=None, **kwargs):
323  """
324  Instantiate an `FgcmBuildStarsTask`.
325 
326  Parameters
327  ----------
328  butler : `lsst.daf.persistence.Butler`
329  """
330 
331  pipeBase.CmdLineTask.__init__(self, **kwargs)
332  self.makeSubtask("sourceSelector")
333  # Only log warning and fatal errors from the sourceSelector
334  self.sourceSelector.log.setLevel(self.sourceSelector.log.WARN)
335 
336  @classmethod
337  def _makeArgumentParser(cls):
338  """Create an argument parser"""
339 
340  parser = pipeBase.ArgumentParser(name=cls._DefaultName)
341  parser.add_id_argument("--id", "calexp", help="Data ID, e.g. --id visit=6789")
342 
343  return parser
344 
345  # no saving of metadata for now
346  def _getMetadataName(self):
347  return None
348 
349  @pipeBase.timeMethod
350  def runDataRef(self, butler, dataRefs):
351  """
352  Cross-match and make star list for FGCM Input
353 
354  Parameters
355  ----------
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
363  each visit.
364 
365  Raises
366  ------
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.
370  """
371 
372  self.log.info("Running with %d dataRefs" % (len(dataRefs)))
373 
374  if self.config.doReferenceMatches:
375  # Ensure that we have a LUT
376  if not butler.datasetExists('fgcmLookUpTable'):
377  raise RuntimeError("Must have fgcmLookUpTable if using config.doReferenceMatches")
378  # Compute aperture radius if necessary. This is useful to do now before
379  # any heavy lifting has happened (fail early).
380  calibFluxApertureRadius = None
381  if self.config.doSubtractLocalBackground:
382  sourceSchema = butler.get('src_schema').schema
383  try:
384  calibFluxApertureRadius = computeApertureRadius(sourceSchema,
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))
390 
391  groupedDataRefs = self.findAndGroupDataRefs(butler, dataRefs)
392 
393  camera = butler.get('camera')
394 
395  # Make the visit catalog if necessary
396  # First check if the visit catalog is in the current path
397  visitCatDataRef = butler.dataRef('fgcmVisitCatalog')
398  filename = visitCatDataRef.get('fgcmVisitCatalog_filename')[0]
399  if os.path.exists(filename):
400  # This file exists and we should continue processing
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.")
405  else:
406  inVisitCat = None
407 
408  visitCat = self.fgcmMakeVisitCatalog(camera, groupedDataRefs,
409  visitCatDataRef=visitCatDataRef,
410  inVisitCat=inVisitCat)
411 
412  # Persist the visitCat as a checkpoint file.
413  visitCatDataRef.put(visitCat)
414 
415  starObsDataRef = butler.dataRef('fgcmStarObservations')
416  filename = starObsDataRef.get('fgcmStarObservations_filename')[0]
417  if os.path.exists(filename):
418  inStarObsCat = starObsDataRef.get()
419  else:
420  inStarObsCat = None
421 
422  rad = calibFluxApertureRadius
423  fgcmStarObservationCat = self.fgcmMakeAllStarObservations(groupedDataRefs,
424  visitCat,
425  calibFluxApertureRadius=rad,
426  starObsDataRef=starObsDataRef,
427  visitCatDataRef=visitCatDataRef,
428  inStarObsCat=inStarObsCat)
429  visitCatDataRef.put(visitCat)
430  starObsDataRef.put(fgcmStarObservationCat)
431 
432  # Always do the matching.
433  fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat = self.fgcmMatchStars(butler,
434  visitCat,
435  fgcmStarObservationCat)
436 
437  # Persist catalogs via the butler
438  butler.put(fgcmStarIdCat, 'fgcmStarIds')
439  butler.put(fgcmStarIndicesCat, 'fgcmStarIndices')
440  if fgcmRefCat is not None:
441  butler.put(fgcmRefCat, 'fgcmReferenceStars')
442 
443  def fgcmMakeVisitCatalog(self, camera, groupedDataRefs,
444  visitCatDataRef=None, inVisitCat=None):
445  """
446  Make a visit catalog with all the keys from each visit
447 
448  Parameters
449  ----------
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
459 
460  Returns
461  -------
462  visitCat: `afw.table.BaseCatalog`
463  """
464 
465  self.log.info("Assembling visitCatalog from %d %ss" %
466  (len(groupedDataRefs), self.config.visitDataRefName))
467 
468  nCcd = len(camera)
469 
470  if inVisitCat is None:
471  schema = self._makeFgcmVisitSchema(nCcd)
472 
473  visitCat = afwTable.BaseCatalog(schema)
474  visitCat.reserve(len(groupedDataRefs))
475 
476  for i, visit in enumerate(sorted(groupedDataRefs)):
477  rec = visitCat.addNew()
478  rec['visit'] = visit
479  rec['used'] = 0
480  rec['sources_read'] = 0
481  else:
482  visitCat = inVisitCat
483 
484  # No matter what, fill the catalog. This will check if it was
485  # already read.
486  self._fillVisitCatalog(visitCat, groupedDataRefs,
487  visitCatDataRef=visitCatDataRef)
488 
489  return visitCat
490 
491  def _fillVisitCatalog(self, visitCat, groupedDataRefs,
492  visitCatDataRef=None):
493  """
494  Fill the visit catalog with visit metadata
495 
496  Parameters
497  ----------
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
505  """
506 
507  bbox = geom.BoxI(geom.PointI(0, 0), geom.PointI(1, 1))
508 
509  for i, visit in enumerate(sorted(groupedDataRefs)):
510  # We don't use the bypasses since we need the psf info which does
511  # not have a bypass
512  # TODO: When DM-15500 is implemented in the Gen3 Butler, this
513  # can be fixed
514 
515  # Do not read those that have already been read
516  if visitCat['used'][i]:
517  continue
518 
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)))
522  # Save checkpoint if desired
523  if visitCatDataRef is not None:
524  visitCatDataRef.put(visitCat)
525 
526  # Note that the reference ccd is first in the list (if available).
527 
528  # The first dataRef in the group will be the reference ccd (if available)
529  dataRef = groupedDataRefs[visit][0]
530 
531  exp = dataRef.get(datasetType='calexp_sub', bbox=bbox,
532  flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
533 
534  visitInfo = exp.getInfo().getVisitInfo()
535  f = exp.getFilter()
536  psf = exp.getPsf()
537 
538  rec = visitCat[i]
539  rec['visit'] = visit
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()
548  # convert from Pa to millibar
549  # Note that I don't know if this unit will need to be per-camera config
550  rec['pmb'] = visitInfo.getWeather().getAirPressure() / 100
551  # Flag to signify if this is a "deep" field. Not currently used
552  rec['deepFlag'] = 0
553  # Relative flat scaling (1.0 means no relative scaling)
554  rec['scaling'][:] = 1.0
555  # Median delta aperture, to be measured from stars
556  rec['deltaAper'] = 0.0
557 
558  rec['psfSigma'] = psf.computeShape().getDeterminantRadius()
559 
560  if dataRef.datasetExists(datasetType='calexpBackground'):
561  # Get background for reference CCD
562  # This approximation is good enough for now
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)
566  else:
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
570 
571  rec['used'] = 1
572 
573  def findAndGroupDataRefs(self, butler, dataRefs):
574  """
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.
577 
578  Parameters
579  ----------
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.
585 
586  Returns
587  -------
588  groupedDataRefs: `dict`
589  Dictionary with visit keys, and `list`s of `lsst.daf.persistence.ButlerDataRef`
590  """
591 
592  self.log.info("Grouping dataRefs by %s" % (self.config.visitDataRefName))
593 
594  camera = butler.get('camera')
595 
596  ccdIds = []
597  for detector in camera:
598  ccdIds.append(detector.getId())
599 
600  # TODO: related to DM-13730, this dance of looking for source visits
601  # will be unnecessary with Gen3 Butler. This should be part of
602  # DM-13730.
603 
604  nVisits = 0
605 
606  groupedDataRefs = {}
607  for dataRef in dataRefs:
608  visit = dataRef.dataId[self.config.visitDataRefName]
609  # If we don't have the dataset, just continue
610  if not dataRef.datasetExists(datasetType='src'):
611  continue
612  # If we need to check all ccds, do it here
613  if self.config.checkAllCcds:
614  if visit in groupedDataRefs:
615  # We already have found this visit
616  continue
617  dataId = dataRef.dataId.copy()
618  # For each ccd we must check that a valid source catalog exists.
619  for ccdId in ccdIds:
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)
627  else:
628  # This is a new visit
629  nVisits += 1
630  groupedDataRefs[visit] = [goodDataRef]
631  else:
632  # We have already confirmed that the dataset exists, so no need
633  # to check here.
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)
638  else:
639  # This is a new visit
640  nVisits += 1
641  groupedDataRefs[visit] = [dataRef]
642 
643  if (nVisits % 100) == 0 and nVisits > 0:
644  self.log.info("Found %d unique %ss..." % (nVisits,
645  self.config.visitDataRefName))
646 
647  self.log.info("Found %d unique %ss total." % (nVisits,
648  self.config.visitDataRefName))
649 
650  # Put them in ccd order, with the reference ccd first (if available)
651  def ccdSorter(dataRef):
652  ccdId = dataRef.dataId[self.config.ccdDataRefName]
653  if ccdId == self.config.referenceCCD:
654  return -100
655  else:
656  return ccdId
657 
658  # If we did not check all ccds, put them in ccd order
659  if not self.config.checkAllCcds:
660  for visit in groupedDataRefs:
661  groupedDataRefs[visit] = sorted(groupedDataRefs[visit], key=ccdSorter)
662 
663  return groupedDataRefs
664 
665  def fgcmMakeAllStarObservations(self, groupedDataRefs, visitCat,
666  calibFluxApertureRadius=None,
667  visitCatDataRef=None,
668  starObsDataRef=None,
669  inStarObsCat=None):
670  """
671  Compile all good star observations from visits in visitCat. Checkpoint files
672  will be stored if both visitCatDataRef and starObsDataRef are not None.
673 
674  Parameters
675  ----------
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
688 
689  Returns
690  -------
691  fgcmStarObservations: `afw.table.BaseCatalog`
692  Full catalog of good observations.
693 
694  Raises
695  ------
696  RuntimeError: Raised if doSubtractLocalBackground is True and
697  calibFluxApertureRadius is not set.
698  """
699  startTime = time.time()
700 
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.")
705 
706  if self.config.doSubtractLocalBackground and calibFluxApertureRadius is None:
707  raise RuntimeError("Must set calibFluxApertureRadius if doSubtractLocalBackground is True.")
708 
709  # create our source schema. Use the first valid dataRef
710  dataRef = groupedDataRefs[list(groupedDataRefs.keys())[0]][0]
711  sourceSchema = dataRef.get('src_schema', immediate=True).schema
712 
713  # Construct a mapping from ccd number to index
714  camera = dataRef.get('camera')
715  ccdMapping = {}
716  for ccdIndex, detector in enumerate(camera):
717  ccdMapping[detector.getId()] = ccdIndex
718 
719  approxPixelAreaFields = computeApproxPixelAreaFields(camera)
720 
721  sourceMapper = self._makeSourceMapper(sourceSchema)
722 
723  # We also have a temporary catalog that will accumulate aperture measurements
724  aperMapper = self._makeAperMapper(sourceSchema)
725 
726  outputSchema = sourceMapper.getOutputSchema()
727 
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.")
734  else:
735  fullCatalog = afwTable.BaseCatalog(outputSchema)
736 
737  # FGCM will provide relative calibration for the flux in config.instFluxField
738 
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()
745 
746  # Prepare local background if desired
747  if self.config.doSubtractLocalBackground:
748  localBackgroundFluxKey = sourceSchema[self.config.localBackgroundFluxField].asKey()
749  localBackgroundArea = np.pi*calibFluxApertureRadius**2.
750  else:
751  localBackground = 0.0
752 
753  aperOutputSchema = aperMapper.getOutputSchema()
754 
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()
763 
764  k = 2.5 / np.log(10.)
765 
766  # loop over visits
767  for ctr, visit in enumerate(visitCat):
768  if visit['sources_read']:
769  continue
770 
771  expTime = visit['exptime']
772 
773  nStarInVisit = 0
774 
775  # Reset the aperture catalog (per visit)
776  aperVisitCatalog = afwTable.BaseCatalog(aperOutputSchema)
777 
778  for dataRef in groupedDataRefs[visit['visit']]:
779 
780  ccdId = dataRef.dataId[self.config.ccdDataRefName]
781 
782  sources = dataRef.get(datasetType='src', flags=afwTable.SOURCE_IO_NO_FOOTPRINTS)
783 
784  # If we are subtracting the local background, then correct here
785  # before we do the s/n selection. This ensures we do not have
786  # bad stars after local background subtraction.
787 
788  if self.config.doSubtractLocalBackground:
789  # At the moment we only adjust the flux and not the flux
790  # error by the background because the error on
791  # base_LocalBackground_instFlux is the rms error in the
792  # background annulus, not the error on the mean in the
793  # background estimate (which is much smaller, by sqrt(n)
794  # pixels used to estimate the background, which we do not
795  # have access to in this task). In the default settings,
796  # the annulus is sufficiently large such that these
797  # additional errors are are negligibly small (much less
798  # than a mmag in quadrature).
799 
800  localBackground = localBackgroundArea*sources[localBackgroundFluxKey]
801  sources[instFluxKey] -= localBackground
802 
803  goodSrc = self.sourceSelector.selectSources(sources)
804 
805  tempCat = afwTable.BaseCatalog(fullCatalog.schema)
806  tempCat.reserve(goodSrc.selected.sum())
807  tempCat.extend(sources[goodSrc.selected], mapper=sourceMapper)
808  tempCat[visitKey][:] = visit['visit']
809  tempCat[ccdKey][:] = ccdId
810 
811  # Compute "instrumental magnitude" by scaling flux with exposure time.
812  scaledInstFlux = (sources[instFluxKey][goodSrc.selected] *
813  visit['scaling'][ccdMapping[ccdId]])
814  tempCat[instMagKey][:] = (-2.5*np.log10(scaledInstFlux) + 2.5*np.log10(expTime))
815 
816  # Compute instMagErr from instFluxErr / instFlux, any scaling
817  # will cancel out.
818 
819  tempCat[instMagErrKey][:] = k*(sources[instFluxErrKey][goodSrc.selected] /
820  sources[instFluxKey][goodSrc.selected])
821 
822  # Compute the jacobian from an approximate PixelAreaBoundedField
823  tempCat['jacobian'] = approxPixelAreaFields[ccdId].evaluate(tempCat['x'],
824  tempCat['y'])
825 
826  # Apply the jacobian if configured
827  if self.config.doApplyWcsJacobian:
828  tempCat[instMagKey][:] -= 2.5*np.log10(tempCat['jacobian'][:])
829 
830  fullCatalog.extend(tempCat)
831 
832  # And the aperture information
833  # This does not need the jacobian because it is all locally relative
834  tempAperCat = afwTable.BaseCatalog(aperVisitCatalog.schema)
835  tempAperCat.reserve(goodSrc.selected.sum())
836  tempAperCat.extend(sources[goodSrc.selected], mapper=aperMapper)
837 
838  with np.warnings.catch_warnings():
839  # Ignore warnings, we will filter infinities and
840  # nans below.
841  np.warnings.simplefilter("ignore")
842 
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])
853 
854  aperVisitCatalog.extend(tempAperCat)
855 
856  nStarInVisit += len(tempCat)
857 
858  # Compute the median delta-aper
859  if not aperVisitCatalog.isContiguous():
860  aperVisitCatalog = aperVisitCatalog.copy(deep=True)
861 
862  instMagIn = aperVisitCatalog[instMagInKey]
863  instMagErrIn = aperVisitCatalog[instMagErrInKey]
864  instMagOut = aperVisitCatalog[instMagOutKey]
865  instMagErrOut = aperVisitCatalog[instMagErrOutKey]
866 
867  ok = (np.isfinite(instMagIn) & np.isfinite(instMagErrIn) &
868  np.isfinite(instMagOut) & np.isfinite(instMagErrOut))
869 
870  visit['deltaAper'] = np.median(instMagIn[ok] - instMagOut[ok])
871  visit['sources_read'] = 1
872 
873  self.log.info(" Found %d good stars in visit %d (deltaAper = %.3f)" %
874  (nStarInVisit, visit['visit'], visit['deltaAper']))
875 
876  if ((ctr % self.config.nVisitsPerCheckpoint) == 0 and
877  starObsDataRef is not None and visitCatDataRef is not None):
878  # We need to persist both the stars and the visit catalog which gets
879  # additional metadata from each visit.
880  starObsDataRef.put(fullCatalog)
881  visitCatDataRef.put(visitCat)
882 
883  self.log.info("Found all good star observations in %.2f s" %
884  (time.time() - startTime))
885 
886  return fullCatalog
887 
888  def fgcmMatchStars(self, butler, visitCat, obsCat):
889  """
890  Use FGCM code to match observations into unique stars.
891 
892  Parameters
893  ----------
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
899 
900  Returns
901  -------
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.
909  """
910 
911  if self.config.doReferenceMatches:
912  # Make a subtask for reference loading
913  self.makeSubtask("fgcmLoadReferenceCatalog", butler=butler)
914 
915  # get filter names into a numpy array...
916  # This is the type that is expected by the fgcm code
917  visitFilterNames = np.zeros(len(visitCat), dtype='a10')
918  for i in range(len(visitCat)):
919  visitFilterNames[i] = visitCat[i]['filtername']
920 
921  # match to put filterNames with observations
922  visitIndex = np.searchsorted(visitCat['visit'],
923  obsCat['visit'])
924 
925  obsFilterNames = visitFilterNames[visitIndex]
926 
927  if self.config.doReferenceMatches:
928  # Get the reference filter names, using the LUT
929  lutCat = butler.get('fgcmLookUpTable')
930 
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'])}
937 
938  del lutCat
939 
940  referenceFilterNames = self._getReferenceFilterNames(visitCat,
941  stdFilterDict,
942  stdLambdaDict)
943  self.log.info("Using the following reference filters: %s" %
944  (', '.join(referenceFilterNames)))
945 
946  else:
947  # This should be an empty list
948  referenceFilterNames = []
949 
950  # make the fgcm starConfig dict
951 
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}
964 
965  # initialize the FgcmMakeStars object
966  fgcmMakeStars = fgcm.FgcmMakeStars(starConfig)
967 
968  # make the primary stars
969  # note that the ra/dec native Angle format is radians
970  # We determine the conversion from the native units (typically
971  # radians) to degrees for the first observation. This allows us
972  # to treate ra/dec as numpy arrays rather than Angles, which would
973  # be approximately 600x slower.
974  conv = obsCat[0]['ra'].asDegrees() / float(obsCat[0]['ra'])
975  fgcmMakeStars.makePrimaryStars(obsCat['ra'] * conv,
976  obsCat['dec'] * conv,
977  filterNameArray=obsFilterNames,
978  bandSelected=False)
979 
980  # and match all the stars
981  fgcmMakeStars.makeMatchedStars(obsCat['ra'] * conv,
982  obsCat['dec'] * conv,
983  obsFilterNames)
984 
985  if self.config.doReferenceMatches:
986  fgcmMakeStars.makeReferenceMatches(self.fgcmLoadReferenceCatalog)
987 
988  # now persist
989 
990  objSchema = self._makeFgcmObjSchema()
991 
992  # make catalog and records
993  fgcmStarIdCat = afwTable.BaseCatalog(objSchema)
994  fgcmStarIdCat.reserve(fgcmMakeStars.objIndexCat.size)
995  for i in range(fgcmMakeStars.objIndexCat.size):
996  fgcmStarIdCat.addNew()
997 
998  # fill the catalog
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']
1004 
1005  obsSchema = self._makeFgcmObsSchema()
1006 
1007  fgcmStarIndicesCat = afwTable.BaseCatalog(obsSchema)
1008  fgcmStarIndicesCat.reserve(fgcmMakeStars.obsIndexCat.size)
1009  for i in range(fgcmMakeStars.obsIndexCat.size):
1010  fgcmStarIndicesCat.addNew()
1011 
1012  fgcmStarIndicesCat['obsIndex'][:] = fgcmMakeStars.obsIndexCat['obsindex']
1013 
1014  if self.config.doReferenceMatches:
1015  refSchema = self._makeFgcmRefSchema(len(referenceFilterNames))
1016 
1017  fgcmRefCat = afwTable.BaseCatalog(refSchema)
1018  fgcmRefCat.reserve(fgcmMakeStars.referenceCat.size)
1019 
1020  for i in range(fgcmMakeStars.referenceCat.size):
1021  fgcmRefCat.addNew()
1022 
1023  fgcmRefCat['fgcm_id'][:] = fgcmMakeStars.referenceCat['fgcm_id']
1024  fgcmRefCat['refMag'][:, :] = fgcmMakeStars.referenceCat['refMag']
1025  fgcmRefCat['refMagErr'][:, :] = fgcmMakeStars.referenceCat['refMagErr']
1026 
1027  md = PropertyList()
1028  md.set("REFSTARS_FORMAT_VERSION", REFSTARS_FORMAT_VERSION)
1029  md.set("FILTERNAMES", referenceFilterNames)
1030  fgcmRefCat.setMetadata(md)
1031 
1032  else:
1033  fgcmRefCat = None
1034 
1035  return fgcmStarIdCat, fgcmStarIndicesCat, fgcmRefCat
1036 
1037  def _makeFgcmVisitSchema(self, nCcd):
1038  """
1039  Make a schema for an fgcmVisitCatalog
1040 
1041  Parameters
1042  ----------
1043  nCcd: `int`
1044  Number of CCDs in the camera
1045 
1046  Returns
1047  -------
1048  schema: `afwTable.Schema`
1049  """
1050 
1051  schema = afwTable.Schema()
1052  schema.addField('visit', type=np.int32, doc="Visit number")
1053  # Note that the FGCM code currently handles filternames up to 2 characters long
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)")
1065  # the following field is not used yet
1066  schema.addField('deepFlag', type=np.int32, doc="Deep observation")
1067  schema.addField('scaling', type='ArrayD', doc="Scaling applied due to flat adjustment",
1068  size=nCcd)
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.")
1071 
1072  return schema
1073 
1074  def _makeSourceMapper(self, sourceSchema):
1075  """
1076  Make a schema mapper for fgcm sources
1077 
1078  Parameters
1079  ----------
1080  sourceSchema: `afwTable.Schema`
1081  Default source schema from the butler
1082 
1083  Returns
1084  -------
1085  sourceMapper: `afwTable.schemaMapper`
1086  Mapper to the FGCM source schema
1087  """
1088 
1089  # create a mapper to the preferred output
1090  sourceMapper = afwTable.SchemaMapper(sourceSchema)
1091 
1092  # map to ra/dec
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(),
1098  'psf_candidate')
1099 
1100  # and add the fields we want
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")
1111 
1112  return sourceMapper
1113 
1114  def _makeAperMapper(self, sourceSchema):
1115  """
1116  Make a schema mapper for fgcm aperture measurements
1117 
1118  Parameters
1119  ----------
1120  sourceSchema: `afwTable.Schema`
1121  Default source schema from the butler
1122 
1123  Returns
1124  -------
1125  aperMapper: `afwTable.schemaMapper`
1126  Mapper to the FGCM aperture schema
1127  """
1128 
1129  aperMapper = afwTable.SchemaMapper(sourceSchema)
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")
1140 
1141  return aperMapper
1142 
1143  def _makeFgcmObjSchema(self):
1144  """
1145  Make a schema for the objIndexCat from fgcmMakeStars
1146 
1147  Returns
1148  -------
1149  schema: `afwTable.Schema`
1150  """
1151 
1152  objSchema = afwTable.Schema()
1153  objSchema.addField('fgcm_id', type=np.int32, doc='FGCM Unique ID')
1154  # Will investigate making these angles...
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')
1160 
1161  return objSchema
1162 
1163  def _makeFgcmObsSchema(self):
1164  """
1165  Make a schema for the obsIndexCat from fgcmMakeStars
1166 
1167  Returns
1168  -------
1169  schema: `afwTable.Schema`
1170  """
1171 
1172  obsSchema = afwTable.Schema()
1173  obsSchema.addField('obsIndex', type=np.int32, doc='Index in observation table')
1174 
1175  return obsSchema
1176 
1177  def _makeFgcmRefSchema(self, nReferenceBands):
1178  """
1179  Make a schema for the referenceCat from fgcmMakeStars
1180 
1181  Parameters
1182  ----------
1183  nReferenceBands: `int`
1184  Number of reference bands
1185 
1186  Returns
1187  -------
1188  schema: `afwTable.Schema`
1189  """
1190 
1191  refSchema = 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)
1197 
1198  return refSchema
1199 
1200  def _getReferenceFilterNames(self, visitCat, stdFilterDict, stdLambdaDict):
1201  """
1202  Get the reference filter names, in wavelength order, from the visitCat and
1203  information from the look-up-table.
1204 
1205  Parameters
1206  ----------
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
1213 
1214  Returns
1215  -------
1216  referenceFilterNames: `list`
1217  Wavelength-ordered list of reference filter names
1218  """
1219 
1220  # Find the unique list of filter names in visitCat
1221  filterNames = np.unique(visitCat.asAstropy()['filtername'])
1222 
1223  # Find the unique list of "standard" filters
1224  stdFilterNames = {stdFilterDict[filterName] for filterName in filterNames}
1225 
1226  # And sort these by wavelength
1227  referenceFilterNames = sorted(stdFilterNames, key=stdLambdaDict.get)
1228 
1229  return referenceFilterNames
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig
Definition: fgcmBuildStars.py:57
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.runDataRef
def runDataRef(self, butler, dataRefs)
Definition: fgcmBuildStars.py:350
lsst::log.log.logContinued.warn
def warn(fmt, *args)
Definition: logContinued.py:202
lsst::log.log.logContinued.info
def info(fmt, *args)
Definition: logContinued.py:198
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._getReferenceFilterNames
def _getReferenceFilterNames(self, visitCat, stdFilterDict, stdLambdaDict)
Definition: fgcmBuildStars.py:1200
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig.setDefaults
def setDefaults(self)
Definition: fgcmBuildStars.py:206
lsst::meas::algorithms.sourceSelector
Definition: sourceSelector.py:1
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.fgcmMakeVisitCatalog
def fgcmMakeVisitCatalog(self, camera, groupedDataRefs, visitCatDataRef=None, inVisitCat=None)
Definition: fgcmBuildStars.py:443
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig.instFluxField
instFluxField
Definition: fgcmBuildStars.py:60
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.__init__
def __init__(self, butler=None, **kwargs)
Definition: fgcmBuildStars.py:322
ast::append
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeFgcmRefSchema
def _makeFgcmRefSchema(self, nReferenceBands)
Definition: fgcmBuildStars.py:1177
lsst::daf::base::PropertyList
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsRunner.run
def run(self, parsedCmd)
Definition: fgcmBuildStars.py:295
lsst::afw::table::Schema
Defines the fields and offsets for a table.
Definition: Schema.h:50
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask
Definition: fgcmBuildStars.py:313
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.fgcmMakeAllStarObservations
def fgcmMakeAllStarObservations(self, groupedDataRefs, visitCat, calibFluxApertureRadius=None, visitCatDataRef=None, starObsDataRef=None, inStarObsCat=None)
Definition: fgcmBuildStars.py:665
lsst.fgcmcal.utilities.computeApproxPixelAreaFields
def computeApproxPixelAreaFields(camera)
Definition: utilities.py:474
lsst.fgcmcal.utilities.computeApertureRadius
def computeApertureRadius(schema, fluxField)
Definition: utilities.py:783
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsRunner.getTargetList
def getTargetList(parsedCmd)
Definition: fgcmBuildStars.py:255
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig.localBackgroundFluxField
localBackgroundFluxField
Definition: fgcmBuildStars.py:172
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeAperMapper
def _makeAperMapper(self, sourceSchema)
Definition: fgcmBuildStars.py:1114
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeFgcmVisitSchema
def _makeFgcmVisitSchema(self, nCcd)
Definition: fgcmBuildStars.py:1037
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._DefaultName
string _DefaultName
Definition: fgcmBuildStars.py:320
lsst::afw::table::SchemaMapper
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:21
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeFgcmObjSchema
def _makeFgcmObjSchema(self)
Definition: fgcmBuildStars.py:1143
lsst::afw::table
Definition: table.dox:3
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.findAndGroupDataRefs
def findAndGroupDataRefs(self, butler, dataRefs)
Definition: fgcmBuildStars.py:573
lsst::geom
Definition: geomOperators.dox:4
lsst::daf::base
Definition: Utils.h:47
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask.fgcmMatchStars
def fgcmMatchStars(self, butler, visitCat, obsCat)
Definition: fgcmBuildStars.py:888
list
daf::base::PropertyList * list
Definition: fits.cc:913
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsRunner
Definition: fgcmBuildStars.py:241
lsst::geom::Point< int, 2 >
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeSourceMapper
def _makeSourceMapper(self, sourceSchema)
Definition: fgcmBuildStars.py:1074
lsst::daf::base.dateTime
Definition: __init__.py:1
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig.doSubtractLocalBackground
doSubtractLocalBackground
Definition: fgcmBuildStars.py:166
lsst::geom::Box2I
An integer coordinate rectangle.
Definition: Box.h:55
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._makeFgcmObsSchema
def _makeFgcmObsSchema(self)
Definition: fgcmBuildStars.py:1163
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsTask._fillVisitCatalog
def _fillVisitCatalog(self, visitCat, groupedDataRefs, visitCatDataRef=None)
Definition: fgcmBuildStars.py:491
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsConfig.sourceSelector
sourceSelector
Definition: fgcmBuildStars.py:177
lsst.fgcmcal.fgcmBuildStars.FgcmBuildStarsRunner.__call__
def __call__(self, args)
Definition: fgcmBuildStars.py:263
lsst.pipe.base
Definition: __init__.py:1
lsst::afw::table::CatalogT< BaseRecord >