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