24 from lsstDebug
import getDebugFrame
26 import lsst.pex.config
as pexConfig
37 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
39 from .measurePsf
import MeasurePsfTask
40 from .repair
import RepairTask
42 __all__ = [
"CharacterizeImageConfig",
"CharacterizeImageTask"]
46 dimensions=(
"instrument",
"visit",
"detector")):
48 doc=
"Input exposure data",
50 storageClass=
"ExposureF",
51 dimensions=[
"instrument",
"visit",
"detector"],
53 characterized = cT.Output(
54 doc=
"Output characterized data.",
56 storageClass=
"ExposureF",
57 dimensions=[
"instrument",
"visit",
"detector"],
59 sourceCat = cT.Output(
60 doc=
"Output source catalog.",
62 storageClass=
"SourceCatalog",
63 dimensions=[
"instrument",
"visit",
"detector"],
65 backgroundModel = cT.Output(
66 doc=
"Output background model.",
67 name=
"icExpBackground",
68 storageClass=
"Background",
69 dimensions=[
"instrument",
"visit",
"detector"],
71 outputSchema = cT.InitOutput(
72 doc=
"Schema of the catalog produced by CharacterizeImage",
74 storageClass=
"SourceCatalog",
79 pipelineConnections=CharacterizeImageConnections):
81 """!Config for CharacterizeImageTask"""
82 doMeasurePsf = pexConfig.Field(
85 doc=
"Measure PSF? If False then for all subsequent operations use either existing PSF "
86 "model when present, or install simple PSF model when not (see installSimplePsf "
89 doWrite = pexConfig.Field(
92 doc=
"Persist results?",
94 doWriteExposure = pexConfig.Field(
97 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
99 psfIterations = pexConfig.RangeField(
103 doc=
"Number of iterations of detect sources, measure sources, "
104 "estimate PSF. If useSimplePsf is True then 2 should be plenty; "
105 "otherwise more may be wanted.",
107 background = pexConfig.ConfigurableField(
108 target=SubtractBackgroundTask,
109 doc=
"Configuration for initial background estimation",
111 detection = pexConfig.ConfigurableField(
112 target=SourceDetectionTask,
115 doDeblend = pexConfig.Field(
118 doc=
"Run deblender input exposure"
120 deblend = pexConfig.ConfigurableField(
121 target=SourceDeblendTask,
122 doc=
"Split blended source into their components"
124 measurement = pexConfig.ConfigurableField(
125 target=SingleFrameMeasurementTask,
126 doc=
"Measure sources"
128 doApCorr = pexConfig.Field(
131 doc=
"Run subtasks to measure and apply aperture corrections"
133 measureApCorr = pexConfig.ConfigurableField(
134 target=MeasureApCorrTask,
135 doc=
"Subtask to measure aperture corrections"
137 applyApCorr = pexConfig.ConfigurableField(
138 target=ApplyApCorrTask,
139 doc=
"Subtask to apply aperture corrections"
143 catalogCalculation = pexConfig.ConfigurableField(
144 target=CatalogCalculationTask,
145 doc=
"Subtask to run catalogCalculation plugins on catalog"
147 useSimplePsf = pexConfig.Field(
150 doc=
"Replace the existing PSF model with a simplified version that has the same sigma "
151 "at the start of each PSF determination iteration? Doing so makes PSF determination "
152 "converge more robustly and quickly.",
154 installSimplePsf = pexConfig.ConfigurableField(
155 target=InstallGaussianPsfTask,
156 doc=
"Install a simple PSF model",
158 refObjLoader = pexConfig.ConfigurableField(
159 target=LoadIndexedReferenceObjectsTask,
160 doc=
"reference object loader",
162 ref_match = pexConfig.ConfigurableField(
164 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. "
165 "Warning: matching will only work well if the initial WCS is accurate enough "
166 "to give good matches (roughly: good to 3 arcsec across the CCD).",
168 measurePsf = pexConfig.ConfigurableField(
169 target=MeasurePsfTask,
172 repair = pexConfig.ConfigurableField(
174 doc=
"Remove cosmic rays",
176 checkUnitsParseStrict = pexConfig.Field(
177 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
187 self.
detection.includeThresholdMultiplier = 10.0
188 self.
detection.doTempLocalBackground =
False
201 "base_CircularApertureFlux",
206 raise RuntimeError(
"Must measure PSF to measure aperture correction, "
207 "because flags determined by PSF measurement are used to identify "
208 "sources used to measure aperture correction")
219 r"""!Measure bright sources and use this to estimate background and PSF of an exposure
221 @anchor CharacterizeImageTask_
223 @section pipe_tasks_characterizeImage_Contents Contents
225 - @ref pipe_tasks_characterizeImage_Purpose
226 - @ref pipe_tasks_characterizeImage_Initialize
227 - @ref pipe_tasks_characterizeImage_IO
228 - @ref pipe_tasks_characterizeImage_Config
229 - @ref pipe_tasks_characterizeImage_Debug
232 @section pipe_tasks_characterizeImage_Purpose Description
234 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
235 - detect and measure bright sources
237 - measure and subtract background
240 @section pipe_tasks_characterizeImage_Initialize Task initialisation
242 @copydoc \_\_init\_\_
244 @section pipe_tasks_characterizeImage_IO Invoking the Task
246 If you want this task to unpersist inputs or persist outputs, then call
247 the `runDataRef` method (a thin wrapper around the `run` method).
249 If you already have the inputs unpersisted and do not want to persist the output
250 then it is more direct to call the `run` method:
252 @section pipe_tasks_characterizeImage_Config Configuration parameters
254 See @ref CharacterizeImageConfig
256 @section pipe_tasks_characterizeImage_Debug Debug variables
258 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
259 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
261 CharacterizeImageTask has a debug dictionary with the following keys:
264 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
266 <dd>bool; if True display image after each repair in the measure PSF loop
268 <dd>bool; if True display image after each background subtraction in the measure PSF loop
270 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
271 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
273 <dd>bool; if True display image and sources after PSF is measured;
274 this will be identical to the final image displayed by measure_iter if measure_iter is true
276 <dd>bool; if True display image and sources after final repair
278 <dd>bool; if True display image and sources after final measurement
281 For example, put something like:
285 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
286 if name == "lsst.pipe.tasks.characterizeImage":
293 lsstDebug.Info = DebugInfo
295 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
297 Some subtasks may have their own debug variables; see individual Task documentation.
302 ConfigClass = CharacterizeImageConfig
303 _DefaultName =
"characterizeImage"
304 RunnerClass = pipeBase.ButlerInitializedTaskRunner
307 inputs = butlerQC.get(inputRefs)
308 if 'exposureIdInfo' not in inputs.keys():
309 exposureIdInfo = ExposureIdInfo()
310 exposureIdInfo.expId, exposureIdInfo.expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
312 inputs[
'exposureIdInfo'] = exposureIdInfo
313 outputs = self.
run(**inputs)
314 butlerQC.put(outputs, outputRefs)
316 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
317 """!Construct a CharacterizeImageTask
319 @param[in] butler A butler object is passed to the refObjLoader constructor in case
320 it is needed to load catalogs. May be None if a catalog-based star selector is
321 not used, if the reference object loader constructor does not require a butler,
322 or if a reference object loader is passed directly via the refObjLoader argument.
323 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
324 external reference catalog to a catalog-based star selector. May be None if a
325 catalog star selector is not used or the loader can be constructed from the
327 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
328 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
333 schema = SourceTable.makeMinimalSchema()
335 self.makeSubtask(
"background")
336 self.makeSubtask(
"installSimplePsf")
337 self.makeSubtask(
"repair")
338 self.makeSubtask(
"measurePsf", schema=self.
schema)
339 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
341 self.makeSubtask(
'refObjLoader', butler=butler)
342 refObjLoader = self.refObjLoader
343 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
345 self.makeSubtask(
'detection', schema=self.
schema)
346 if self.config.doDeblend:
347 self.makeSubtask(
"deblend", schema=self.
schema)
348 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
349 if self.config.doApCorr:
350 self.makeSubtask(
'measureApCorr', schema=self.
schema)
351 self.makeSubtask(
'applyApCorr', schema=self.
schema)
352 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
353 self.
_initialFrame = getDebugFrame(self._display,
"frame")
or 1
355 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
359 outputCatSchema = afwTable.SourceCatalog(self.
schema)
360 outputCatSchema.getTable().setMetadata(self.
algMetadata)
361 return {
'outputSchema': outputCatSchema}
364 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
365 """!Characterize a science image and, if wanted, persist the results
367 This simply unpacks the exposure and passes it to the characterize method to do the work.
369 @param[in] dataRef: butler data reference for science exposure
370 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
371 If None then unpersist from "postISRCCD".
372 The following changes are made, depending on the config:
373 - set psf to the measured PSF
374 - set apCorrMap to the measured aperture correction
375 - subtract background
376 - interpolate over cosmic rays
377 - update detection and cosmic ray mask planes
378 @param[in,out] background initial model of background already subtracted from exposure
379 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
380 which is typical for image characterization.
381 A refined background model is output.
382 @param[in] doUnpersist if True the exposure is read from the repository
383 and the exposure and background arguments must be None;
384 if False the exposure must be provided.
385 True is intended for running as a command-line task, False for running as a subtask
387 @return same data as the characterize method
390 self.log.info(
"Processing %s" % (dataRef.dataId))
393 if exposure
is not None or background
is not None:
394 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
395 exposure = dataRef.get(
"postISRCCD", immediate=
True)
396 elif exposure
is None:
397 raise RuntimeError(
"doUnpersist false; exposure must be provided")
399 exposureIdInfo = dataRef.get(
"expIdInfo")
403 exposureIdInfo=exposureIdInfo,
404 background=background,
407 if self.config.doWrite:
408 dataRef.put(charRes.sourceCat,
"icSrc")
409 if self.config.doWriteExposure:
410 dataRef.put(charRes.exposure,
"icExp")
411 dataRef.put(charRes.background,
"icExpBackground")
416 def run(self, exposure, exposureIdInfo=None, background=None):
417 """!Characterize a science image
419 Peforms the following operations:
420 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
421 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
422 - interpolate over cosmic rays
423 - perform final measurement
425 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
426 The following changes are made:
429 - update detection and cosmic ray mask planes
430 - subtract background and interpolate over cosmic rays
431 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo).
432 If not provided, returned SourceCatalog IDs will not be globally unique.
433 @param[in,out] background initial model of background already subtracted from exposure
434 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
435 which is typical for image characterization.
437 @return pipe_base Struct containing these fields, all from the final iteration
438 of detectMeasureAndEstimatePsf:
439 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
440 mask is updated accordingly, and the PSF model is set
441 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
442 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
443 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
447 if not self.config.doMeasurePsf
and not exposure.hasPsf():
448 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
449 self.installSimplePsf.
run(exposure=exposure)
451 if exposureIdInfo
is None:
452 exposureIdInfo = ExposureIdInfo()
455 background = self.background.
run(exposure).background
457 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
458 for i
in range(psfIterations):
461 exposureIdInfo=exposureIdInfo,
462 background=background,
465 psf = dmeRes.exposure.getPsf()
466 psfSigma = psf.computeShape().getDeterminantRadius()
467 psfDimensions = psf.computeImage().getDimensions()
468 medBackground = np.median(dmeRes.background.getImage().getArray())
469 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
470 (i + 1, psfSigma, psfDimensions, medBackground))
472 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
475 self.repair.
run(exposure=dmeRes.exposure)
476 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
480 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
481 exposureId=exposureIdInfo.expId)
482 if self.config.doApCorr:
483 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
484 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
485 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
486 self.catalogCalculation.
run(dmeRes.sourceCat)
488 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
490 return pipeBase.Struct(
491 exposure=dmeRes.exposure,
492 sourceCat=dmeRes.sourceCat,
493 background=dmeRes.background,
494 psfCellSet=dmeRes.psfCellSet,
496 characterized=dmeRes.exposure,
497 backgroundModel=dmeRes.background
502 """!Perform one iteration of detect, measure and estimate PSF
504 Performs the following operations:
505 - if config.doMeasurePsf or not exposure.hasPsf():
506 - install a simple PSF model (replacing the existing one, if need be)
507 - interpolate over cosmic rays with keepCRs=True
508 - estimate background and subtract it from the exposure
509 - detect, deblend and measure sources, and subtract a refined background model;
510 - if config.doMeasurePsf:
513 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
514 The following changes are made:
516 - update detection and cosmic ray mask planes
517 - subtract background
518 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
519 @param[in,out] background initial model of background already subtracted from exposure
520 (an lsst.afw.math.BackgroundList).
522 @return pipe_base Struct containing these fields, all from the final iteration
523 of detect sources, measure sources and estimate PSF:
524 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
525 mask is updated accordingly, and the PSF model is set
526 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
527 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
528 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
531 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
532 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
533 self.installSimplePsf.
run(exposure=exposure)
536 self.repair.
run(exposure=exposure, keepCRs=
True)
537 self.
display(
"repair_iter", exposure=exposure)
539 if background
is None:
540 background = BackgroundList()
542 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
543 table = SourceTable.make(self.
schema, sourceIdFactory)
546 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
547 sourceCat = detRes.sources
548 if detRes.fpSets.background:
549 for bg
in detRes.fpSets.background:
550 background.append(bg)
552 if self.config.doDeblend:
553 self.deblend.
run(exposure=exposure, sources=sourceCat)
555 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
557 measPsfRes = pipeBase.Struct(cellSet=
None)
558 if self.config.doMeasurePsf:
559 if self.measurePsf.usesMatches:
560 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
563 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
564 expId=exposureIdInfo.expId)
565 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
567 return pipeBase.Struct(
570 background=background,
571 psfCellSet=measPsfRes.cellSet,
575 """Return a dict of empty catalogs for each catalog dataset produced by this task.
577 sourceCat = SourceCatalog(self.
schema)
579 return {
"icSrc": sourceCat}
581 def display(self, itemName, exposure, sourceCat=None):
582 """Display exposure and sources on next frame, if display of itemName has been requested
584 @param[in] itemName name of item in debugInfo
585 @param[in] exposure exposure to display
586 @param[in] sourceCat source catalog to display
588 val = getDebugFrame(self._display, itemName)
592 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)