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