24 from lsstDebug
import getDebugFrame
28 from lsst.meas.astrom import AstrometryTask, displayAstrometry, createMatchMetadata,\
29 LoadAstrometryNetObjectsTask
35 from lsst.meas.astrom import AstrometryTask, displayAstrometry, createMatchMetadata
36 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
38 from .photoCal
import PhotoCalTask
40 __all__ = [
"CalibrateConfig",
"CalibrateTask"]
44 """Config for CalibrateTask"""
45 doWrite = pexConfig.Field(
48 doc=
"Save calibration results?",
50 doWriteHeavyFootprintsInSources = pexConfig.Field(
53 doc=
"Include HeavyFootprint data in source table? If false then heavy footprints "
54 "are saved as normal footprints, which saves some space",
56 doWriteMatches = pexConfig.Field(
59 doc=
"Write reference matches (ignored if doWrite false)?",
61 doAstrometry = pexConfig.Field(
64 doc=
"Perform astrometric calibration?",
66 refObjLoader = pexConfig.ConfigurableField(
67 target = LoadAstrometryNetObjectsTask,
68 doc =
"reference object loader",
70 astrometry = pexConfig.ConfigurableField(
71 target=AstrometryTask,
72 doc=
"Perform astrometric calibration to refine the WCS",
74 requireAstrometry = pexConfig.Field(
77 doc=
"Raise an exception if astrometry fails? Ignored if doAstrometry false.",
79 doPhotoCal = pexConfig.Field(
82 doc=
"Perform phometric calibration?",
84 requirePhotoCal = pexConfig.Field(
87 doc=
"Raise an exception if photoCal fails? Ignored if doPhotoCal false.",
89 photoCal = pexConfig.ConfigurableField(
91 doc=
"Perform photometric calibration",
93 icSourceFieldsToCopy = pexConfig.ListField(
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."
100 matchRadiusPix = pexConfig.Field(
103 doc=
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)",
105 checkUnitsParseStrict = pexConfig.Field(
106 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
110 detection = pexConfig.ConfigurableField(
111 target=SourceDetectionTask,
114 doDeblend = pexConfig.Field(
117 doc=
"Run deblender input exposure"
119 deblend = pexConfig.ConfigurableField(
120 target=SourceDeblendTask,
121 doc=
"Split blended sources into their components"
123 measurement = pexConfig.ConfigurableField(
124 target=SingleFrameMeasurementTask,
125 doc=
"Measure sources"
127 doApCorr = pexConfig.Field(
130 doc=
"Run subtask to apply aperture correction"
132 applyApCorr = pexConfig.ConfigurableField(
133 target=ApplyApCorrTask,
134 doc=
"Subtask to apply aperture corrections"
138 catalogCalculation = pexConfig.ConfigurableField(
139 target=CatalogCalculationTask,
140 doc=
"Subtask to run catalogCalculation plugins on catalog"
144 pexConfig.Config.setDefaults(self)
156 """!Calibrate an exposure: measure sources and perform astrometric and photometric calibration
158 @anchor CalibrateTask_
160 @section pipe_tasks_calibrate_Contents Contents
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
170 @section pipe_tasks_calibrate_Purpose Description
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
178 @section pipe_tasks_calibrate_Initialize Task initialisation
180 @copydoc \_\_init\_\_
182 @section pipe_tasks_calibrate_IO Invoking the Task
184 If you want this task to unpersist inputs or persist outputs, then call
185 the `run` method (a wrapper around the `calibrate` method).
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:
190 @section pipe_tasks_calibrate_Config Configuration parameters
192 See @ref CalibrateConfig
194 @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
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)
205 @section pipe_tasks_calibrate_Debug Debug variables
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`.
210 CalibrateTask has a debug dictionary containing one key:
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.
217 For example, put something like:
221 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
222 if name == "lsst.pipe.tasks.calibrate":
229 lsstDebug.Info = DebugInfo
231 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
233 Some subtasks may have their own debug variables; see individual Task documentation.
235 @section pipe_tasks_calibrate_Example A complete example of using CalibrateTask
237 This code is in @link calibrateTask.py@endlink in the examples directory, and can be run as, e.g.:
239 python examples/calibrateTask.py --display
241 @dontinclude calibrateTask.py
243 Import the task (there are some other standard imports; read the file if you're curious)
244 @skipline CalibrateTask
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
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
258 ConfigClass = CalibrateConfig
259 _DefaultName =
"calibrate"
260 RunnerClass = pipeBase.ButlerInitializedTaskRunner
262 def __init__(self, butler=None, refObjLoader=None, icSourceSchema=None, **kwargs):
263 """!Construct a CalibrateTask
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
275 pipeBase.CmdLineTask.__init__(self, **kwargs)
277 if icSourceSchema
is None and butler
is not None:
279 icSourceSchema = butler.get(
"icSrc_schema", immediate=
True).schema
281 if icSourceSchema
is not None:
284 self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(),
False)
291 afwTable.Field[
"Flag"](
"calib_detected",
"Source was detected as an icSource"))
292 missingFieldNames = []
293 for fieldName
in self.config.icSourceFieldsToCopy:
295 schemaItem = icSourceSchema.find(fieldName)
297 missingFieldNames.append(fieldName)
300 self.schemaMapper.addMapping(schemaItem.getKey())
302 if missingFieldNames:
303 raise RuntimeError(
"isSourceCat is missing fields {} specified in icSourceFieldsToCopy"
304 .
format(missingFieldNames))
307 self.
schema = self.schemaMapper.editOutputSchema()
310 self.
schema = afwTable.SourceTable.makeMinimalSchema()
311 self.makeSubtask(
'detection', schema=self.
schema)
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)
322 if self.config.doAstrometry
or self.config.doPhotoCal:
323 if refObjLoader
is None:
324 self.makeSubtask(
'refObjLoader', butler=butler)
325 refObjLoader = self.refObjLoader
327 self.makeSubtask(
"astrometry", refObjLoader=refObjLoader, schema=self.
schema)
328 if self.config.doPhotoCal:
329 self.makeSubtask(
"photoCal", schema=self.
schema)
333 self.
schema = self.schemaMapper.getOutputSchema()
334 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
337 def run(self, dataRef, exposure=None, background=None, icSourceCat=None, doUnpersist=True):
338 """!Calibrate an exposure, optionally unpersisting inputs and persisting outputs.
340 This is a wrapper around the `calibrate` method that unpersists inputs
341 (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
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
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
359 @return same data as the calibrate method
361 self.log.info(
"Processing %s" % (dataRef.dataId))
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")
372 if self.config.doWrite
and self.config.doAstrometry:
377 exposureIdInfo = dataRef.get(
"expIdInfo")
381 exposureIdInfo=exposureIdInfo,
382 background=background,
383 icSourceCat=icSourceCat,
386 if self.config.doWrite:
389 exposure=calRes.exposure,
390 background=calRes.background,
391 sourceCat=calRes.sourceCat,
392 astromMatches=calRes.astromMatches,
398 def calibrate(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None):
399 """!Calibrate an exposure (science image or coadd)
401 @param[in,out] exposure exposure to calibrate (an lsst.afw.image.ExposureF or similar);
406 - MaskedImage has background subtracted
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
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
425 if exposureIdInfo
is None:
426 exposureIdInfo = ExposureIdInfo()
428 if background
is None:
429 background = BackgroundList()
430 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
431 table = SourceTable.make(self.
schema, sourceIdFactory)
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(
443 exposureId=exposureIdInfo.expId
445 if self.config.doApCorr:
446 self.applyApCorr.run(
448 apCorrMap=exposure.getInfo().getApCorrMap()
450 self.catalogCalculation.run(sourceCat)
452 if icSourceCat
is not None and len(self.config.icSourceFieldsToCopy) > 0:
458 if self.config.doAstrometry:
460 astromRes = self.astrometry.run(
464 astromMatches = astromRes.matches
465 except Exception
as e:
466 if self.config.requireAstrometry:
468 self.log.warn(
"Unable to perform astrometric calibration (%s): attempting to proceed" % e)
471 if self.config.doPhotoCal:
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:
482 self.log.warn(
"Unable to perform photometric calibration (%s): attempting to proceed" % e)
490 matches=astromMatches,
495 return pipeBase.Struct(
497 background=background,
499 astromMatches=astromMatches,
502 def writeOutputs(self, dataRef, exposure, background, sourceCat, astromMatches, matchMeta):
503 """Write output data to the output repository
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
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:
516 normalizedMatches.table.setMetadata(matchMeta)
517 dataRef.put(normalizedMatches,
"srcMatch")
518 dataRef.put(exposure,
"calexp")
519 dataRef.put(background,
"calexpBackground")
522 """Return a dict of empty catalogs for each catalog dataset produced by this task.
526 return {
"src": sourceCat}
529 """!Set task and exposure metadata
531 Logs a warning and continues if needed data is missing.
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
541 magZero = photoRes.zp - 2.5*math.log10(exposure.getCalib().getExptime())
542 self.metadata.set(
'MAGZERO', magZero)
544 self.log.warn(
"Could not set normalized MAGZERO in header: no exposure time")
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,))
557 """!Match sources in icSourceCat and sourceCat and copy the specified fields
559 @param[in] icSourceCat catalog from which to copy fields
560 @param[in,out] sourceCat catalog to which to copy fields
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.
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")
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]
586 for m0, m1, d
in matches:
588 match = bestMatches.get(id0)
589 if match
is None or d <= match[2]:
590 bestMatches[id0] = (m0, m1, d)
591 matches = bestMatches.values()
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))
601 self.log.info(
"Copying flags from icSourceCat to sourceCat for %s sources" % (numMatches,))
604 for icSrc, src, d
in matches:
608 icSrcFootprint = icSrc.getFootprint()
610 icSrc.setFootprint(src.getFootprint())
613 icSrc.setFootprint(icSrcFootprint)
def __init__
Construct a CalibrateTask.
Class for storing ordered metadata with comments.
A mapping between the keys of two Schemas, used to copy data between them.
def setMetadata
Set task and exposure metadata.
def calibrate
Calibrate an exposure (science image or coadd)
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.
A description of a field in a table.
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.
def run
Calibrate an exposure, optionally unpersisting inputs and persisting outputs.
SourceMatchVector matchXy(SourceCatalog const &cat, double radius, bool symmetric)
Calibrate an exposure: measure sources and perform astrometric and photometric calibration.