LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
calibrate.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008-2016 AURA/LSST.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <https://www.lsstcorp.org/LegalNotices/>.
21 #
22 import math
23 
24 from lsstDebug import getDebugFrame
25 import lsst.pex.config as pexConfig
26 import lsst.pipe.base as pipeBase
27 import lsst.afw.table as afwTable
28 from lsst.meas.astrom import AstrometryTask, displayAstrometry, createMatchMetadata,\
29  LoadAstrometryNetObjectsTask
30 from lsst.daf.butlerUtils import ExposureIdInfo
31 import lsst.daf.base as dafBase
32 from lsst.afw.math import BackgroundList
33 from lsst.afw.table import IdFactory, SourceTable
34 from lsst.meas.algorithms import SourceDetectionTask
35 from lsst.meas.astrom import AstrometryTask, displayAstrometry, createMatchMetadata
36 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
37 from lsst.meas.deblender import SourceDeblendTask
38 from .photoCal import PhotoCalTask
39 
40 __all__ = ["CalibrateConfig", "CalibrateTask"]
41 
42 
43 class CalibrateConfig(pexConfig.Config):
44  """Config for CalibrateTask"""
45  doWrite = pexConfig.Field(
46  dtype=bool,
47  default=True,
48  doc="Save calibration results?",
49  )
50  doWriteHeavyFootprintsInSources = pexConfig.Field(
51  dtype=bool,
52  default=True,
53  doc="Include HeavyFootprint data in source table? If false then heavy footprints "
54  "are saved as normal footprints, which saves some space",
55  )
56  doWriteMatches = pexConfig.Field(
57  dtype=bool,
58  default=True,
59  doc="Write reference matches (ignored if doWrite false)?",
60  )
61  doAstrometry = pexConfig.Field(
62  dtype=bool,
63  default=True,
64  doc="Perform astrometric calibration?",
65  )
66  refObjLoader = pexConfig.ConfigurableField(
67  target = LoadAstrometryNetObjectsTask,
68  doc = "reference object loader",
69  )
70  astrometry = pexConfig.ConfigurableField(
71  target=AstrometryTask,
72  doc="Perform astrometric calibration to refine the WCS",
73  )
74  requireAstrometry = pexConfig.Field(
75  dtype=bool,
76  default=True,
77  doc="Raise an exception if astrometry fails? Ignored if doAstrometry false.",
78  )
79  doPhotoCal = pexConfig.Field(
80  dtype=bool,
81  default=True,
82  doc="Perform phometric calibration?",
83  )
84  requirePhotoCal = pexConfig.Field(
85  dtype=bool,
86  default=True,
87  doc="Raise an exception if photoCal fails? Ignored if doPhotoCal false.",
88  )
89  photoCal = pexConfig.ConfigurableField(
90  target=PhotoCalTask,
91  doc="Perform photometric calibration",
92  )
93  icSourceFieldsToCopy = pexConfig.ListField(
94  dtype=str,
95  default=("calib_psfCandidate", "calib_psfUsed", "calib_psfReserved"),
96  doc="Fields to copy from the icSource catalog to the output catalog for matching sources "
97  "Any missing fields will trigger a RuntimeError exception. "
98  "Ignored if icSourceCat is not provided."
99  )
100  matchRadiusPix = pexConfig.Field(
101  dtype=float,
102  default=3,
103  doc="Match radius for matching icSourceCat objects to sourceCat objects (pixels)",
104  )
105  checkUnitsParseStrict = pexConfig.Field(
106  doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
107  dtype=str,
108  default="raise",
109  )
110  detection = pexConfig.ConfigurableField(
111  target=SourceDetectionTask,
112  doc="Detect sources"
113  )
114  doDeblend = pexConfig.Field(
115  dtype=bool,
116  default=True,
117  doc="Run deblender input exposure"
118  )
119  deblend = pexConfig.ConfigurableField(
120  target=SourceDeblendTask,
121  doc="Split blended sources into their components"
122  )
123  measurement = pexConfig.ConfigurableField(
124  target=SingleFrameMeasurementTask,
125  doc="Measure sources"
126  )
127  doApCorr = pexConfig.Field(
128  dtype=bool,
129  default=True,
130  doc="Run subtask to apply aperture correction"
131  )
132  applyApCorr = pexConfig.ConfigurableField(
133  target=ApplyApCorrTask,
134  doc="Subtask to apply aperture corrections"
135  )
136  # If doApCorr is False, and the exposure does not have apcorrections already applied, the
137  # active plugins in catalogCalculation almost certainly should not contain the characterization plugin
138  catalogCalculation = pexConfig.ConfigurableField(
139  target=CatalogCalculationTask,
140  doc="Subtask to run catalogCalculation plugins on catalog"
141  )
142 
143  def setDefaults(self):
144  pexConfig.Config.setDefaults(self)
145  # aperture correction should already be measured
146 
147 
148 ## \addtogroup LSST_task_documentation
149 ## \{
150 ## \page CalibrateTask
151 ## \ref CalibrateTask_ "CalibrateTask"
152 ## \copybrief CalibrateTask
153 ## \}
154 
155 class CalibrateTask(pipeBase.CmdLineTask):
156  """!Calibrate an exposure: measure sources and perform astrometric and photometric calibration
157 
158  @anchor CalibrateTask_
159 
160  @section pipe_tasks_calibrate_Contents Contents
161 
162  - @ref pipe_tasks_calibrate_Purpose
163  - @ref pipe_tasks_calibrate_Initialize
164  - @ref pipe_tasks_calibrate_IO
165  - @ref pipe_tasks_calibrate_Config
166  - @ref pipe_tasks_calibrate_Metadata
167  - @ref pipe_tasks_calibrate_Debug
168  - @ref pipe_tasks_calibrate_Example
169 
170  @section pipe_tasks_calibrate_Purpose Description
171 
172  Given an exposure with a good PSF model and aperture correction map
173  (e.g. as provided by @ref CharacterizeImageTask), perform the following operations:
174  - Run detection and measurement
175  - Run astrometry subtask to fit an improved WCS
176  - Run photoCal subtask to fit the exposure's photometric zero-point
177 
178  @section pipe_tasks_calibrate_Initialize Task initialisation
179 
180  @copydoc \_\_init\_\_
181 
182  @section pipe_tasks_calibrate_IO Invoking the Task
183 
184  If you want this task to unpersist inputs or persist outputs, then call
185  the `run` method (a wrapper around the `calibrate` method).
186 
187  If you already have the inputs unpersisted and do not want to persist the output
188  then it is more direct to call the `calibrate` method:
189 
190  @section pipe_tasks_calibrate_Config Configuration parameters
191 
192  See @ref CalibrateConfig
193 
194  @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
195 
196  Exposure metadata
197  <dl>
198  <dt>MAGZERO_RMS <dd>MAGZERO's RMS == sigma reported by photoCal task
199  <dt>MAGZERO_NOBJ <dd>Number of stars used == ngood reported by photoCal task
200  <dt>COLORTERM1 <dd>?? (always 0.0)
201  <dt>COLORTERM2 <dd>?? (always 0.0)
202  <dt>COLORTERM3 <dd>?? (always 0.0)
203  </dl>
204 
205  @section pipe_tasks_calibrate_Debug Debug variables
206 
207  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
208  `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
209 
210  CalibrateTask has a debug dictionary containing one key:
211  <dl>
212  <dt>calibrate
213  <dd>frame (an int; <= 0 to not display) in which to display the exposure, sources and matches.
214  See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
215  </dl>
216 
217  For example, put something like:
218  @code{.py}
219  import lsstDebug
220  def DebugInfo(name):
221  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
222  if name == "lsst.pipe.tasks.calibrate":
223  di.display = dict(
224  calibrate = 1,
225  )
226 
227  return di
228 
229  lsstDebug.Info = DebugInfo
230  @endcode
231  into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
232 
233  Some subtasks may have their own debug variables; see individual Task documentation.
234 
235  @section pipe_tasks_calibrate_Example A complete example of using CalibrateTask
236 
237  This code is in @link calibrateTask.py@endlink in the examples directory, and can be run as, e.g.:
238  @code
239  python examples/calibrateTask.py --display
240  @endcode
241  @dontinclude calibrateTask.py
242 
243  Import the task (there are some other standard imports; read the file if you're curious)
244  @skipline CalibrateTask
245 
246  Create the task. Note that we're using a custom AstrometryTask (because we don't have a valid
247  astrometric catalogue handy); see \ref calibrate_MyAstrometryTask.
248  @skip CalibrateTask.ConfigClass
249  @until config=config
250 
251  We're now ready to process the data. This occurs in two steps, optionally displaying the data after each:
252  - Characterize the image: measure bright sources, fit a background and PSF, and repairs cosmic rays
253  - Calibrate the exposure: measure faint sources, fit an improved WCS and photometric zero-point
254 
255  @skip loadData
256  @until dot
257  """
258  ConfigClass = CalibrateConfig
259  _DefaultName = "calibrate"
260  RunnerClass = pipeBase.ButlerInitializedTaskRunner
261 
262  def __init__(self, butler=None, refObjLoader=None, icSourceSchema=None, **kwargs):
263  """!Construct a CalibrateTask
264 
265  @param[in] butler The butler is passed to the refObjLoader constructor in case it is
266  needed. Ignored if the refObjLoader argument provides a loader directly.
267  @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
268  external reference catalog. May be None if the desired loader can be constructed
269  from the butler argument or all steps requiring a reference catalog are disabled.
270  @param[in] icSourceSchema schema for icSource catalog, or None.
271  Schema values specified in config.icSourceFieldsToCopy will be taken from this schema.
272  If set to None, no values will be propagated from the icSourceCatalog
273  @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
274  """
275  pipeBase.CmdLineTask.__init__(self, **kwargs)
276 
277  if icSourceSchema is None and butler is not None:
278  # Use butler to read icSourceSchema from disk.
279  icSourceSchema = butler.get("icSrc_schema", immediate=True).schema
280 
281  if icSourceSchema is not None:
282  # use a schema mapper to avoid copying each field separately
283  self.schemaMapper = afwTable.SchemaMapper(icSourceSchema)
284  self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(), False)
285 
286  # Add fields to copy from an icSource catalog
287  # and a field to indicate that the source matched a source in that catalog
288  # If any fields are missing then raise an exception, but first find all missing fields
289  # in order to make the error message more useful.
290  self.calibSourceKey = self.schemaMapper.addOutputField(
291  afwTable.Field["Flag"]("calib_detected", "Source was detected as an icSource"))
292  missingFieldNames = []
293  for fieldName in self.config.icSourceFieldsToCopy:
294  try:
295  schemaItem = icSourceSchema.find(fieldName)
296  except Exception:
297  missingFieldNames.append(fieldName)
298  else:
299  # field found; if addMapping fails then raise an exception
300  self.schemaMapper.addMapping(schemaItem.getKey())
301 
302  if missingFieldNames:
303  raise RuntimeError("isSourceCat is missing fields {} specified in icSourceFieldsToCopy"
304  .format(missingFieldNames))
305 
306  # produce a temporary schema to pass to the subtasks; finalize it later
307  self.schema = self.schemaMapper.editOutputSchema()
308  else:
309  self.schemaMapper = None
310  self.schema = afwTable.SourceTable.makeMinimalSchema()
311  self.makeSubtask('detection', schema=self.schema)
312 
314 
315  if self.config.doDeblend:
316  self.makeSubtask("deblend", schema=self.schema)
317  self.makeSubtask('measurement', schema=self.schema, algMetadata=self.algMetadata)
318  if self.config.doApCorr:
319  self.makeSubtask('applyApCorr', schema=self.schema)
320  self.makeSubtask('catalogCalculation', schema=self.schema)
321 
322  if self.config.doAstrometry or self.config.doPhotoCal:
323  if refObjLoader is None:
324  self.makeSubtask('refObjLoader', butler=butler)
325  refObjLoader = self.refObjLoader
326  self.pixelMargin = refObjLoader.config.pixelMargin
327  self.makeSubtask("astrometry", refObjLoader=refObjLoader, schema=self.schema)
328  if self.config.doPhotoCal:
329  self.makeSubtask("photoCal", schema=self.schema)
330 
331  if self.schemaMapper is not None:
332  # finalize the schema
333  self.schema = self.schemaMapper.getOutputSchema()
334  self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
335 
336  @pipeBase.timeMethod
337  def run(self, dataRef, exposure=None, background=None, icSourceCat=None, doUnpersist=True):
338  """!Calibrate an exposure, optionally unpersisting inputs and persisting outputs.
339 
340  This is a wrapper around the `calibrate` method that unpersists inputs
341  (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
342 
343  @param[in] dataRef butler data reference corresponding to a science image
344  @param[in,out] exposure characterized exposure (an lsst.afw.image.ExposureF or similar),
345  or None to unpersist existing icExp and icBackground.
346  See calibrate method for details of what is read and written.
347  @param[in,out] background initial model of background already subtracted from exposure
348  (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
349  though that is unusual for calibration.
350  A refined background model is output.
351  Ignored if exposure is None.
352  @param[in] icSourceCat catalog from which to copy the fields specified by icSourceKeys, or None;
353  @param[in] doUnpersist unpersist data:
354  - if True, exposure, background and icSourceCat are read from dataRef and those three arguments
355  must all be None;
356  - if False the exposure must be provided; background and icSourceCat are optional.
357  True is intended for running as a command-line task, False for running as a subtask
358 
359  @return same data as the calibrate method
360  """
361  self.log.info("Processing %s" % (dataRef.dataId))
362 
363  if doUnpersist:
364  if any(item is not None for item in (exposure, background, icSourceCat)):
365  raise RuntimeError("doUnpersist true; exposure, background and icSourceCat must all be None")
366  exposure = dataRef.get("icExp", immediate=True)
367  background = dataRef.get("icExpBackground", immediate=True)
368  icSourceCat = dataRef.get("icSrc", immediate=True)
369  elif exposure is None:
370  raise RuntimeError("doUnpersist false; exposure must be provided")
371 
372  if self.config.doWrite and self.config.doAstrometry:
373  matchMeta = createMatchMetadata(exposure, border=self.pixelMargin)
374  else:
375  matchMeta = None
376 
377  exposureIdInfo = dataRef.get("expIdInfo")
378 
379  calRes = self.calibrate(
380  exposure=exposure,
381  exposureIdInfo=exposureIdInfo,
382  background=background,
383  icSourceCat=icSourceCat,
384  )
385 
386  if self.config.doWrite:
387  self.writeOutputs(
388  dataRef=dataRef,
389  exposure=calRes.exposure,
390  background=calRes.background,
391  sourceCat=calRes.sourceCat,
392  astromMatches=calRes.astromMatches,
393  matchMeta=matchMeta,
394  )
395 
396  return calRes
397 
398  def calibrate(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None):
399  """!Calibrate an exposure (science image or coadd)
400 
401  @param[in,out] exposure exposure to calibrate (an lsst.afw.image.ExposureF or similar);
402  in:
403  - MaskedImage
404  - Psf
405  out:
406  - MaskedImage has background subtracted
407  - Wcs is replaced
408  - Calib zero-point is set
409  @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
410  If not provided, returned SourceCatalog IDs will not be globally unique.
411  @param[in,out] background background model already subtracted from exposure
412  (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
413  though that is unusual for calibration.
414  A refined background model is output.
415  @param[in] icSourceCat A SourceCatalog from CharacterizeImageTask from which we can copy
416  some fields.
417 
418  @return pipe_base Struct containing these fields:
419  - exposure calibrate science exposure with refined WCS and Calib
420  - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
421  - sourceCat catalog of measured sources
422  - astromMatches list of source/refObj matches from the astrometry solver
423  """
424  # detect, deblend and measure sources
425  if exposureIdInfo is None:
426  exposureIdInfo = ExposureIdInfo()
427 
428  if background is None:
429  background = BackgroundList()
430  sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
431  table = SourceTable.make(self.schema, sourceIdFactory)
432  table.setMetadata(self.algMetadata)
433 
434  detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True)
435  sourceCat = detRes.sources
436  if detRes.fpSets.background:
437  background.append(detRes.fpSets.background)
438  if self.config.doDeblend:
439  self.deblend.run(exposure=exposure, sources=sourceCat)
440  self.measurement.run(
441  measCat=sourceCat,
442  exposure=exposure,
443  exposureId=exposureIdInfo.expId
444  )
445  if self.config.doApCorr:
446  self.applyApCorr.run(
447  catalog=sourceCat,
448  apCorrMap=exposure.getInfo().getApCorrMap()
449  )
450  self.catalogCalculation.run(sourceCat)
451 
452  if icSourceCat is not None and len(self.config.icSourceFieldsToCopy) > 0:
453  self.copyIcSourceFields(icSourceCat=icSourceCat, sourceCat=sourceCat)
454 
455  # perform astrometry calibration:
456  # fit an improved WCS and update the exposure's WCS in place
457  astromMatches = None
458  if self.config.doAstrometry:
459  try:
460  astromRes = self.astrometry.run(
461  exposure=exposure,
462  sourceCat=sourceCat,
463  )
464  astromMatches = astromRes.matches
465  except Exception as e:
466  if self.config.requireAstrometry:
467  raise
468  self.log.warn("Unable to perform astrometric calibration (%s): attempting to proceed" % e)
469 
470  # compute photometric calibration
471  if self.config.doPhotoCal:
472  try:
473  if astromMatches is None:
474  astromRes = self.astrometry.loadAndMatch(exposure=exposure, sourceCat=sourceCat)
475  photoRes = self.photoCal.run(exposure, astromMatches)
476  exposure.getCalib().setFluxMag0(photoRes.calib.getFluxMag0())
477  self.log.info("Photometric zero-point: %f" % photoRes.calib.getMagnitude(1.0))
478  self.setMetadata(exposure=exposure, photoRes=photoRes)
479  except Exception as e:
480  if self.config.requirePhotoCal:
481  raise
482  self.log.warn("Unable to perform photometric calibration (%s): attempting to proceed" % e)
483  self.setMetadata(exposure=exposure, photoRes=None)
484 
485  frame = getDebugFrame(self._display, "calibrate")
486  if frame:
488  sourceCat=sourceCat,
489  exposure=exposure,
490  matches=astromMatches,
491  frame=frame,
492  pause=False,
493  )
494 
495  return pipeBase.Struct(
496  exposure=exposure,
497  background=background,
498  sourceCat=sourceCat,
499  astromMatches=astromMatches,
500  )
501 
502  def writeOutputs(self, dataRef, exposure, background, sourceCat, astromMatches, matchMeta):
503  """Write output data to the output repository
504 
505  @param[in] dataRef butler data reference corresponding to a science image
506  @param[in] exposure exposure to write
507  @param[in] background background model for exposure
508  @param[in] sourceCat catalog of measured sources
509  @param[in] astromMatches list of source/refObj matches from the astrometry solver
510  """
511  sourceWriteFlags = 0 if self.config.doWriteHeavyFootprintsInSources \
512  else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS
513  dataRef.put(sourceCat, "src")
514  if self.config.doWriteMatches and astromMatches is not None:
515  normalizedMatches = afwTable.packMatches(astromMatches)
516  normalizedMatches.table.setMetadata(matchMeta)
517  dataRef.put(normalizedMatches, "srcMatch")
518  dataRef.put(exposure, "calexp")
519  dataRef.put(background, "calexpBackground")
520 
521  def getSchemaCatalogs(self):
522  """Return a dict of empty catalogs for each catalog dataset produced by this task.
523  """
524  sourceCat = afwTable.SourceCatalog(self.schema)
525  sourceCat.getTable().setMetadata(self.algMetadata)
526  return {"src": sourceCat}
527 
528  def setMetadata(self, exposure, photoRes=None):
529  """!Set task and exposure metadata
530 
531  Logs a warning and continues if needed data is missing.
532 
533  @param[in,out] exposure exposure whose metadata is to be set
534  @param[in] photoRes results of running photoCal; if None then it was not run
535  """
536  if photoRes is None:
537  return
538 
539  # convert zero-point to (mag/sec/adu) for task MAGZERO metadata
540  try:
541  magZero = photoRes.zp - 2.5*math.log10(exposure.getCalib().getExptime())
542  self.metadata.set('MAGZERO', magZero)
543  except Exception:
544  self.log.warn("Could not set normalized MAGZERO in header: no exposure time")
545 
546  try:
547  metadata = exposure.getMetadata()
548  metadata.set('MAGZERO_RMS', photoRes.sigma)
549  metadata.set('MAGZERO_NOBJ', photoRes.ngood)
550  metadata.set('COLORTERM1', 0.0)
551  metadata.set('COLORTERM2', 0.0)
552  metadata.set('COLORTERM3', 0.0)
553  except Exception as e:
554  self.log.warn("Could not set exposure metadata: %s" % (e,))
555 
556  def copyIcSourceFields(self, icSourceCat, sourceCat):
557  """!Match sources in icSourceCat and sourceCat and copy the specified fields
558 
559  @param[in] icSourceCat catalog from which to copy fields
560  @param[in,out] sourceCat catalog to which to copy fields
561 
562  The fields copied are those specified by `config.icSourceFieldsToCopy`
563  that actually exist in the schema. This was set up by the constructor
564  using self.schemaMapper.
565  """
566  if self.schemaMapper is None:
567  raise RuntimeError("To copy icSource fields you must specify icSourceSchema "
568  "and icSourceKeys when constructing this task")
569  if icSourceCat is None or sourceCat is None:
570  raise RuntimeError("icSourceCat and sourceCat must both be specified")
571  if len(self.config.icSourceFieldsToCopy) == 0:
572  self.log.warn("copyIcSourceFields doing nothing because icSourceFieldsToCopy is empty")
573  return
574 
575  closest = False # return all matched objects
576  matches = afwTable.matchXy(icSourceCat, sourceCat, self.config.matchRadiusPix, closest)
577  if self.config.doDeblend:
578  deblendKey = sourceCat.schema["deblend_nChild"].asKey()
579  matches = [m for m in matches if m[1].get(deblendKey) == 0] # if deblended, keep children
580 
581  # Because we had to allow multiple matches to handle parents, we now need to
582  # prune to the best matches
583  # closest matches as a dict of icSourceCat source ID:
584  # (icSourceCat source, sourceCat source, distance in pixels)
585  bestMatches = {}
586  for m0, m1, d in matches:
587  id0 = m0.getId()
588  match = bestMatches.get(id0)
589  if match is None or d <= match[2]:
590  bestMatches[id0] = (m0, m1, d)
591  matches = bestMatches.values()
592 
593  # Check that no sourceCat sources are listed twice (we already know that each match has a unique
594  # icSourceCat source ID, due to using that ID as the key in bestMatches)
595  numMatches = len(matches)
596  numUniqueSources = len(set(m[1].getId() for m in matches))
597  if numUniqueSources != numMatches:
598  self.log.warn("{} icSourceCat sources matched only {} sourceCat sources".format(
599  numMatches, numUniqueSources))
600 
601  self.log.info("Copying flags from icSourceCat to sourceCat for %s sources" % (numMatches,))
602 
603  # For each match: set the calibSourceKey flag and copy the desired fields
604  for icSrc, src, d in matches:
605  src.setFlag(self.calibSourceKey, True)
606  # src.assign copies the footprint from icSrc, which we don't want (DM-407)
607  # so set icSrc's footprint to src's footprint before src.assign, then restore it
608  icSrcFootprint = icSrc.getFootprint()
609  try:
610  icSrc.setFootprint(src.getFootprint())
611  src.assign(icSrc, self.schemaMapper)
612  finally:
613  icSrc.setFootprint(icSrcFootprint)
def __init__
Construct a CalibrateTask.
Definition: calibrate.py:262
Class for storing ordered metadata with comments.
Definition: PropertyList.h:82
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
def setMetadata
Set task and exposure metadata.
Definition: calibrate.py:528
def calibrate
Calibrate an exposure (science image or coadd)
Definition: calibrate.py:398
bool any(CoordinateExpr< N > const &expr)
Return true if any elements are true.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
A description of a field in a table.
Definition: Field.h:22
BaseCatalog packMatches(std::vector< Match< Record1, Record2 > > const &matches)
Return a table representation of a MatchVector that can be used to persist it.
def copyIcSourceFields
Match sources in icSourceCat and sourceCat and copy the specified fields.
Definition: calibrate.py:556
def run
Calibrate an exposure, optionally unpersisting inputs and persisting outputs.
Definition: calibrate.py:337
def getDebugFrame
Definition: lsstDebug.py:66
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric)
Calibrate an exposure: measure sources and perform astrometric and photometric calibration.
Definition: calibrate.py:155