24 from lsstDebug
import getDebugFrame
32 from lsst.meas.astrom import AstrometryTask, displayAstrometry, LoadAstrometryNetObjectsTask
35 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
37 from .measurePsf
import MeasurePsfTask
38 from .repair
import RepairTask
40 __all__ = [
"CharacterizeImageConfig",
"CharacterizeImageTask"]
44 """!Config for CharacterizeImageTask"""
45 doMeasurePsf = pexConfig.Field(
48 doc=
"Measure PSF? If False then keep the existing PSF model (which must exist) "
49 "and use that model for all operations."
51 doWrite = pexConfig.Field(
54 doc=
"Persist results?",
56 doWriteExposure = pexConfig.Field(
59 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
61 psfIterations = pexConfig.RangeField(
65 doc=
"Number of iterations of detect sources, measure sources, estimate PSF. "
66 "If useSimplePsf='all_iter' then 2 should be plenty; otherwise more may be wanted.",
68 background = pexConfig.ConfigurableField(
69 target=SubtractBackgroundTask,
70 doc=
"Configuration for initial background estimation",
72 detection = pexConfig.ConfigurableField(
73 target=SourceDetectionTask,
76 doDeblend = pexConfig.Field(
79 doc=
"Run deblender input exposure"
81 deblend = pexConfig.ConfigurableField(
82 target=SourceDeblendTask,
83 doc=
"Split blended source into their components"
85 measurement = pexConfig.ConfigurableField(
86 target=SingleFrameMeasurementTask,
89 doApCorr = pexConfig.Field(
92 doc=
"Run subtasks to measure and apply aperture corrections"
94 measureApCorr = pexConfig.ConfigurableField(
95 target=MeasureApCorrTask,
96 doc=
"Subtask to measure aperture corrections"
98 applyApCorr = pexConfig.ConfigurableField(
99 target=ApplyApCorrTask,
100 doc=
"Subtask to apply aperture corrections"
104 catalogCalculation = pexConfig.ConfigurableField(
105 target=CatalogCalculationTask,
106 doc=
"Subtask to run catalogCalculation plugins on catalog"
108 useSimplePsf = pexConfig.Field(
111 doc=
"Replace the existing PSF model with a simplified version that has the same sigma "
112 "at the start of each PSF determination iteration? Doing so makes PSF determination "
113 "converge more robustly and quickly.",
115 installSimplePsf = pexConfig.ConfigurableField(
116 target=InstallGaussianPsfTask,
117 doc=
"Install a simple PSF model",
119 refObjLoader = pexConfig.ConfigurableField(
120 target = LoadAstrometryNetObjectsTask,
121 doc =
"reference object loader",
123 astrometry = pexConfig.ConfigurableField(
124 target=AstrometryTask,
125 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. "
126 "Warning: matching will only work well if the initial WCS is accurate enough "
127 "to give good matches (roughly: good to 3 arcsec across the CCD).",
129 measurePsf = pexConfig.ConfigurableField(
130 target=MeasurePsfTask,
133 repair = pexConfig.ConfigurableField(
135 doc=
"Remove cosmic rays",
137 checkUnitsParseStrict = pexConfig.Field(
138 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
144 pexConfig.Config.setDefaults(self)
147 self.detection.thresholdValue = 5.0
148 self.detection.includeThresholdMultiplier = 10.0
155 self.measurement.plugins.names = [
161 "base_CircularApertureFlux",
166 raise RuntimeError(
"Must measure PSF to measure aperture correction, "
167 "because flags determined by PSF measurement are used to identify "
168 "sources used to measure aperture correction")
179 """!Measure bright sources and use this to estimate background and PSF of an exposure
181 @anchor CharacterizeImageTask_
183 @section pipe_tasks_characterizeImage_Contents Contents
185 - @ref pipe_tasks_characterizeImage_Purpose
186 - @ref pipe_tasks_characterizeImage_Initialize
187 - @ref pipe_tasks_characterizeImage_IO
188 - @ref pipe_tasks_characterizeImage_Config
189 - @ref pipe_tasks_characterizeImage_Debug
190 - @ref pipe_tasks_characterizeImage_Example
192 @section pipe_tasks_characterizeImage_Purpose Description
194 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
195 - detect and measure bright sources
197 - measure and subtract background
200 @section pipe_tasks_characterizeImage_Initialize Task initialisation
202 @copydoc \_\_init\_\_
204 @section pipe_tasks_characterizeImage_IO Invoking the Task
206 If you want this task to unpersist inputs or persist outputs, then call
207 the `run` method (a thin wrapper around the `characterize` method).
209 If you already have the inputs unpersisted and do not want to persist the output
210 then it is more direct to call the `characterize` method:
212 @section pipe_tasks_characterizeImage_Config Configuration parameters
214 See @ref CharacterizeImageConfig
216 @section pipe_tasks_characterizeImage_Debug Debug variables
218 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
219 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
221 CharacterizeImageTask has a debug dictionary with the following keys:
224 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
226 <dd>bool; if True display image after each repair in the measure PSF loop
228 <dd>bool; if True display image after each background subtraction in the measure PSF loop
230 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
231 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
233 <dd>bool; if True display image and sources after PSF is measured;
234 this will be identical to the final image displayed by measure_iter if measure_iter is true
236 <dd>bool; if True display image and sources after final repair
238 <dd>bool; if True display image and sources after final measurement
241 For example, put something like:
245 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
246 if name == "lsst.pipe.tasks.characterizeImage":
253 lsstDebug.Info = DebugInfo
255 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
257 Some subtasks may have their own debug variables; see individual Task documentation.
259 @section pipe_tasks_characterizeImage_Example A complete example of using CharacterizeImageTask
261 This code is in @link calibrateTask.py@endlink (which calls CharacterizeImageTask
262 before calling CalibrateTask) in the examples directory, and can be run as, e.g.:
264 python examples/calibrateTask.py --display
266 @dontinclude calibrateTask.py
268 Import the task (there are some other standard imports; read the file if you're curious)
269 @skipline CharacterizeImageTask
272 @skip CharacterizeImageTask.ConfigClass
275 We're now ready to process the data. This occurs in two steps:
276 - Characterize the image: measure bright sources, fit a background and PSF, and repairs cosmic rays
277 - Calibrate the exposure: measure faint sources, fit an improved WCS and photometric zero-point
281 ConfigClass = CharacterizeImageConfig
282 _DefaultName =
"characterizeImage"
283 RunnerClass = pipeBase.ButlerInitializedTaskRunner
285 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
286 """!Construct a CharacterizeImageTask
288 @param[in] butler A butler object is passed to the refObjLoader constructor in case
289 it is needed to load catalogs. May be None if a catalog-based star selector is
290 not used, if the reference object loader constructor does not require a butler,
291 or if a reference object loader is passed directly via the refObjLoader argument.
292 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
293 external reference catalog to a catalog-based star selector. May be None if a
294 catalog star selector is not used or the loader can be constructed from the
296 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
297 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
299 pipeBase.CmdLineTask.__init__(self, **kwargs)
301 schema = SourceTable.makeMinimalSchema()
303 self.makeSubtask(
"background")
304 self.makeSubtask(
"installSimplePsf")
305 self.makeSubtask(
"repair")
306 self.makeSubtask(
"measurePsf", schema=self.
schema)
307 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
309 self.makeSubtask(
'refObjLoader', butler=butler)
310 refObjLoader = self.refObjLoader
311 self.makeSubtask(
"astrometry", refObjLoader=refObjLoader)
313 self.makeSubtask(
'detection', schema=self.
schema)
314 if self.config.doDeblend:
315 self.makeSubtask(
"deblend", schema=self.
schema)
316 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
317 if self.config.doApCorr:
318 self.makeSubtask(
'measureApCorr', schema=self.
schema)
319 self.makeSubtask(
'applyApCorr', schema=self.
schema)
320 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
323 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
326 def run(self, dataRef, exposure=None, background=None, doUnpersist=True):
327 """!Characterize a science image and, if wanted, persist the results
329 This simply unpacks the exposure and passes it to the characterize method to do the work.
331 @param[in] dataRef: butler data reference for science exposure
332 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
333 If None then unpersist from "postISRCCD".
334 The following changes are made, depending on the config:
335 - set psf to the measured PSF
336 - set apCorrMap to the measured aperture correction
337 - subtract background
338 - interpolate over cosmic rays
339 - update detection and cosmic ray mask planes
340 @param[in,out] background initial model of background already subtracted from exposure
341 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
342 which is typical for image characterization.
343 A refined background model is output.
344 @param[in] doUnpersist if True the exposure is read from the repository
345 and the exposure and background arguments must be None;
346 if False the exposure must be provided.
347 True is intended for running as a command-line task, False for running as a subtask
349 @return same data as the characterize method
352 self.log.info(
"Processing %s" % (dataRef.dataId))
355 if exposure
is not None or background
is not None:
356 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
357 exposure = dataRef.get(
"postISRCCD", immediate=
True)
358 elif exposure
is None:
359 raise RuntimeError(
"doUnpersist false; exposure must be provided")
361 exposureIdInfo = dataRef.get(
"expIdInfo")
365 exposureIdInfo=exposureIdInfo,
366 background=background,
369 if self.config.doWrite:
370 dataRef.put(charRes.sourceCat,
"icSrc")
371 if self.config.doWriteExposure:
372 dataRef.put(charRes.exposure,
"icExp")
373 dataRef.put(charRes.background,
"icExpBackground")
378 def characterize(self, exposure, exposureIdInfo=None, background=None):
379 """!Characterize a science image
381 Peforms the following operations:
382 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
383 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
384 - interpolate over cosmic rays
385 - perform final measurement
387 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
388 The following changes are made:
391 - update detection and cosmic ray mask planes
392 - subtract background and interpolate over cosmic rays
393 @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo).
394 If not provided, returned SourceCatalog IDs will not be globally unique.
395 @param[in,out] background initial model of background already subtracted from exposure
396 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
397 which is typical for image characterization.
399 @return pipe_base Struct containing these fields, all from the final iteration
400 of detectMeasureAndEstimatePsf:
401 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
402 mask is updated accordingly, and the PSF model is set
403 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
404 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
405 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
409 if not self.config.doMeasurePsf
and not exposure.hasPsf():
410 raise RuntimeError(
"exposure has no PSF model and config.doMeasurePsf false")
412 if exposureIdInfo
is None:
413 exposureIdInfo = ExposureIdInfo()
416 background = self.background.run(exposure).background
418 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
419 for i
in range(psfIterations):
422 exposureIdInfo=exposureIdInfo,
423 background=background,
426 psf = dmeRes.exposure.getPsf()
427 psfSigma = psf.computeShape().getDeterminantRadius()
428 psfDimensions = psf.computeImage().getDimensions()
429 medBackground = np.median(dmeRes.background.getImage().getArray())
430 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
431 (i + 1, psfSigma, psfDimensions, medBackground))
433 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
436 self.repair.run(exposure=dmeRes.exposure)
437 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
441 self.measurement.run(
442 measCat=dmeRes.sourceCat,
443 exposure=dmeRes.exposure,
444 exposureId=exposureIdInfo.expId
446 if self.config.doApCorr:
447 apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
448 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
449 self.applyApCorr.run(
450 catalog=dmeRes.sourceCat,
451 apCorrMap=exposure.getInfo().getApCorrMap()
453 self.catalogCalculation.run(dmeRes.sourceCat)
455 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
461 """!Perform one iteration of detect, measure and estimate PSF
463 Performs the following operations:
464 - if config.doMeasurePsf:
465 - install a simple PSF model (replacing the existing one, if need be)
466 - interpolate over cosmic rays with keepCRs=True
467 - estimate background and subtract it from the exposure
468 - detect, deblend and measure sources, and subtract a refined background model;
469 - if config.doMeasurePsf:
472 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
473 The following changes are made:
475 - update detection and cosmic ray mask planes
476 - subtract background
477 @param[in] exposureIdInfo ID info for exposure (an lsst.daf.butlerUtils.ExposureIdInfo)
478 @param[in,out] background initial model of background already subtracted from exposure
479 (an lsst.afw.math.BackgroundList).
481 @return pipe_base Struct containing these fields, all from the final iteration
482 of detect sources, measure sources and estimate PSF:
483 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
484 mask is updated accordingly, and the PSF model is set
485 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
486 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
487 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
490 if self.config.doMeasurePsf:
491 if self.config.useSimplePsf
or not exposure.hasPsf():
492 self.installSimplePsf.run(exposure=exposure)
493 elif not exposure.hasPsf():
494 raise RuntimeError(
"exposure has no PSF model and config.doMeasurePsf false")
497 self.repair.run(exposure=exposure, keepCRs=
True)
498 self.
display(
"repair_iter", exposure=exposure)
500 if background
is None:
501 background = BackgroundList()
503 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
504 table = SourceTable.make(self.
schema, sourceIdFactory)
507 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=
True)
508 sourceCat = detRes.sources
509 if detRes.fpSets.background:
510 background.append(detRes.fpSets.background)
512 if self.config.doDeblend:
513 self.deblend.run(exposure=exposure, sources=sourceCat)
515 self.measurement.run(
518 exposureId=exposureIdInfo.expId
521 measPsfRes = pipeBase.Struct(
524 if self.config.doMeasurePsf:
525 if self.measurePsf.usesMatches:
526 matches = self.astrometry.loadAndMatch(
532 measPsfRes = self.measurePsf.run(
537 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
539 return pipeBase.Struct(
542 background=background,
543 psfCellSet=measPsfRes.cellSet,
547 """Return a dict of empty catalogs for each catalog dataset produced by this task.
551 return {
"icSrc": sourceCat}
553 def display(self, itemName, exposure, sourceCat=None):
554 """Display exposure and sources on next frame, if display of itemName has been requested
556 @param[in] itemName name of item in debugInfo
557 @param[in] exposure exposure to display
558 @param[in] sourceCat source catalog to display
def detectMeasureAndEstimatePsf
Perform one iteration of detect, measure and estimate PSF.
def __init__
Construct a CharacterizeImageTask.
Class for storing ordered metadata with comments.
Measure bright sources and use this to estimate background and PSF of an exposure.
def characterize
Characterize a science image.
def run
Characterize a science image and, if wanted, persist the results.
Config for CharacterizeImageTask.
SortedCatalogT< SourceRecord > SourceCatalog