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 astromRefObjLoader = pexConfig.ConfigurableField(
67 target=LoadAstrometryNetObjectsTask,
68 doc=
"reference object loader for astrometric calibration",
70 photoRefObjLoader = pexConfig.ConfigurableField(
71 target=LoadAstrometryNetObjectsTask,
72 doc=
"reference object loader for photometric calibration",
74 astrometry = pexConfig.ConfigurableField(
75 target=AstrometryTask,
76 doc=
"Perform astrometric calibration to refine the WCS",
78 requireAstrometry = pexConfig.Field(
81 doc=
"Raise an exception if astrometry fails? Ignored if doAstrometry false.",
83 doPhotoCal = pexConfig.Field(
86 doc=
"Perform phometric calibration?",
88 requirePhotoCal = pexConfig.Field(
91 doc=
"Raise an exception if photoCal fails? Ignored if doPhotoCal false.",
93 photoCal = pexConfig.ConfigurableField(
95 doc=
"Perform photometric calibration",
97 icSourceFieldsToCopy = pexConfig.ListField(
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."
104 matchRadiusPix = pexConfig.Field(
107 doc=
"Match radius for matching icSourceCat objects to sourceCat objects (pixels)",
109 checkUnitsParseStrict = pexConfig.Field(
110 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
114 detection = pexConfig.ConfigurableField(
115 target=SourceDetectionTask,
118 doDeblend = pexConfig.Field(
121 doc=
"Run deblender input exposure"
123 deblend = pexConfig.ConfigurableField(
124 target=SourceDeblendTask,
125 doc=
"Split blended sources into their components"
127 measurement = pexConfig.ConfigurableField(
128 target=SingleFrameMeasurementTask,
129 doc=
"Measure sources"
131 doApCorr = pexConfig.Field(
134 doc=
"Run subtask to apply aperture correction"
136 applyApCorr = pexConfig.ConfigurableField(
137 target=ApplyApCorrTask,
138 doc=
"Subtask to apply aperture corrections"
142 catalogCalculation = pexConfig.ConfigurableField(
143 target=CatalogCalculationTask,
144 doc=
"Subtask to run catalogCalculation plugins on catalog"
148 pexConfig.Config.setDefaults(self)
160 """!Calibrate an exposure: measure sources and perform astrometric and photometric calibration
162 @anchor CalibrateTask_
164 @section pipe_tasks_calibrate_Contents Contents
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
174 @section pipe_tasks_calibrate_Purpose Description
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
182 @section pipe_tasks_calibrate_Initialize Task initialisation
184 @copydoc \_\_init\_\_
186 @section pipe_tasks_calibrate_IO Invoking the Task
188 If you want this task to unpersist inputs or persist outputs, then call
189 the `run` method (a wrapper around the `calibrate` method).
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:
194 @section pipe_tasks_calibrate_Config Configuration parameters
196 See @ref CalibrateConfig
198 @section pipe_tasks_calibrate_Metadata Quantities set in exposure Metadata
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)
209 @section pipe_tasks_calibrate_Debug Debug variables
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`.
214 CalibrateTask has a debug dictionary containing one key:
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.
221 For example, put something like:
225 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
226 if name == "lsst.pipe.tasks.calibrate":
233 lsstDebug.Info = DebugInfo
235 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
237 Some subtasks may have their own debug variables; see individual Task documentation.
242 ConfigClass = CalibrateConfig
243 _DefaultName =
"calibrate"
244 RunnerClass = pipeBase.ButlerInitializedTaskRunner
246 def __init__(self, butler=None, astromRefObjLoader=None, photoRefObjLoader=None,
247 icSourceSchema=
None, **kwargs):
248 """!Construct a CalibrateTask
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
265 pipeBase.CmdLineTask.__init__(self, **kwargs)
267 if icSourceSchema
is None and butler
is not None:
269 icSourceSchema = butler.get(
"icSrc_schema", immediate=
True).schema
271 if icSourceSchema
is not None:
274 self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(),
False)
281 afwTable.Field[
"Flag"](
"calib_detected",
"Source was detected as an icSource"))
282 missingFieldNames = []
283 for fieldName
in self.config.icSourceFieldsToCopy:
285 schemaItem = icSourceSchema.find(fieldName)
287 missingFieldNames.append(fieldName)
290 self.schemaMapper.addMapping(schemaItem.getKey())
292 if missingFieldNames:
293 raise RuntimeError(
"isSourceCat is missing fields {} specified in icSourceFieldsToCopy"
294 .
format(missingFieldNames))
297 self.
schema = self.schemaMapper.editOutputSchema()
300 self.
schema = afwTable.SourceTable.makeMinimalSchema()
301 self.makeSubtask(
'detection', schema=self.
schema)
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)
312 if self.config.doAstrometry:
313 if astromRefObjLoader
is None:
314 self.makeSubtask(
'astromRefObjLoader', butler=butler)
315 astromRefObjLoader = self.astromRefObjLoader
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)
327 self.
schema = self.schemaMapper.getOutputSchema()
328 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
331 def run(self, dataRef, exposure=None, background=None, icSourceCat=None, doUnpersist=True):
332 """!Calibrate an exposure, optionally unpersisting inputs and persisting outputs.
334 This is a wrapper around the `calibrate` method that unpersists inputs
335 (if `doUnpersist` true) and persists outputs (if `config.doWrite` true)
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
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
353 @return same data as the calibrate method
355 self.log.info(
"Processing %s" % (dataRef.dataId))
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")
366 if self.config.doWrite
and self.config.doAstrometry:
371 exposureIdInfo = dataRef.get(
"expIdInfo")
375 exposureIdInfo=exposureIdInfo,
376 background=background,
377 icSourceCat=icSourceCat,
380 if self.config.doWrite:
383 exposure=calRes.exposure,
384 background=calRes.background,
385 sourceCat=calRes.sourceCat,
386 astromMatches=calRes.astromMatches,
392 def calibrate(self, exposure, exposureIdInfo=None, background=None, icSourceCat=None):
393 """!Calibrate an exposure (science image or coadd)
395 @param[in,out] exposure exposure to calibrate (an lsst.afw.image.ExposureF or similar);
400 - MaskedImage has background subtracted
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
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
419 if exposureIdInfo
is None:
420 exposureIdInfo = ExposureIdInfo()
422 if background
is None:
423 background = BackgroundList()
424 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
425 table = SourceTable.make(self.
schema, sourceIdFactory)
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(
437 exposureId=exposureIdInfo.expId
439 if self.config.doApCorr:
440 self.applyApCorr.run(
442 apCorrMap=exposure.getInfo().getApCorrMap()
444 self.catalogCalculation.run(sourceCat)
446 if icSourceCat
is not None and len(self.config.icSourceFieldsToCopy) > 0:
452 if self.config.doAstrometry:
454 astromRes = self.astrometry.run(
458 astromMatches = astromRes.matches
459 except Exception
as e:
460 if self.config.requireAstrometry:
462 self.log.warn(
"Unable to perform astrometric calibration (%s): attempting to proceed" % e)
465 if self.config.doPhotoCal:
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:
474 self.log.warn(
"Unable to perform photometric calibration (%s): attempting to proceed" % e)
482 matches=astromMatches,
487 return pipeBase.Struct(
489 background=background,
491 astromMatches=astromMatches,
494 def writeOutputs(self, dataRef, exposure, background, sourceCat, astromMatches, matchMeta):
495 """Write output data to the output repository
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
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:
508 normalizedMatches.table.setMetadata(matchMeta)
509 dataRef.put(normalizedMatches,
"srcMatch")
510 dataRef.put(exposure,
"calexp")
511 dataRef.put(background,
"calexpBackground")
514 """Return a dict of empty catalogs for each catalog dataset produced by this task.
518 return {
"src": sourceCat}
521 """!Set task and exposure metadata
523 Logs a warning and continues if needed data is missing.
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
533 exposureTime = exposure.getInfo().getVisitInfo().getExposureTime()
534 magZero = photoRes.zp - 2.5*math.log10(exposureTime)
535 self.metadata.set(
'MAGZERO', magZero)
537 self.log.warn(
"Could not set normalized MAGZERO in header: no exposure time")
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,))
550 """!Match sources in icSourceCat and sourceCat and copy the specified fields
552 @param[in] icSourceCat catalog from which to copy fields
553 @param[in,out] sourceCat catalog to which to copy fields
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.
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")
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]
579 for m0, m1, d
in matches:
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())
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))
594 self.log.info(
"Copying flags from icSourceCat to sourceCat for %s sources" % (numMatches,))
597 for icSrc, src, d
in matches:
601 icSrcFootprint = icSrc.getFootprint()
603 icSrc.setFootprint(src.getFootprint())
606 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)
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.
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)
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.