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",
"exposure",
"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",
77 def adjustQuantum(self, datasetRefMap: pipeBase.InputQuantizedConnection):
81 except pipeBase.ScalarError
as err:
82 raise pipeBase.ScalarError(
83 f
"CharacterizeImageTask can at present only be run on visits that are associated with "
84 f
"exactly one exposure. Either this is not a valid exposure for this pipeline, or the "
85 f
"snap-combination step you probably want hasn't been configured to run between ISR and "
86 f
"this task (as of this writing, that would be because it hasn't been implemented yet)."
91 pipelineConnections=CharacterizeImageConnections):
93 """!Config for CharacterizeImageTask"""
94 doMeasurePsf = pexConfig.Field(
97 doc=
"Measure PSF? If False then for all subsequent operations use either existing PSF "
98 "model when present, or install simple PSF model when not (see installSimplePsf "
101 doWrite = pexConfig.Field(
104 doc=
"Persist results?",
106 doWriteExposure = pexConfig.Field(
109 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
111 psfIterations = pexConfig.RangeField(
115 doc=
"Number of iterations of detect sources, measure sources, "
116 "estimate PSF. If useSimplePsf is True then 2 should be plenty; "
117 "otherwise more may be wanted.",
119 background = pexConfig.ConfigurableField(
120 target=SubtractBackgroundTask,
121 doc=
"Configuration for initial background estimation",
123 detection = pexConfig.ConfigurableField(
124 target=SourceDetectionTask,
127 doDeblend = pexConfig.Field(
130 doc=
"Run deblender input exposure"
132 deblend = pexConfig.ConfigurableField(
133 target=SourceDeblendTask,
134 doc=
"Split blended source into their components"
136 measurement = pexConfig.ConfigurableField(
137 target=SingleFrameMeasurementTask,
138 doc=
"Measure sources"
140 doApCorr = pexConfig.Field(
143 doc=
"Run subtasks to measure and apply aperture corrections"
145 measureApCorr = pexConfig.ConfigurableField(
146 target=MeasureApCorrTask,
147 doc=
"Subtask to measure aperture corrections"
149 applyApCorr = pexConfig.ConfigurableField(
150 target=ApplyApCorrTask,
151 doc=
"Subtask to apply aperture corrections"
155 catalogCalculation = pexConfig.ConfigurableField(
156 target=CatalogCalculationTask,
157 doc=
"Subtask to run catalogCalculation plugins on catalog"
159 useSimplePsf = pexConfig.Field(
162 doc=
"Replace the existing PSF model with a simplified version that has the same sigma "
163 "at the start of each PSF determination iteration? Doing so makes PSF determination "
164 "converge more robustly and quickly.",
166 installSimplePsf = pexConfig.ConfigurableField(
167 target=InstallGaussianPsfTask,
168 doc=
"Install a simple PSF model",
170 refObjLoader = pexConfig.ConfigurableField(
171 target=LoadIndexedReferenceObjectsTask,
172 doc=
"reference object loader",
174 ref_match = pexConfig.ConfigurableField(
176 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. "
177 "Warning: matching will only work well if the initial WCS is accurate enough "
178 "to give good matches (roughly: good to 3 arcsec across the CCD).",
180 measurePsf = pexConfig.ConfigurableField(
181 target=MeasurePsfTask,
184 repair = pexConfig.ConfigurableField(
186 doc=
"Remove cosmic rays",
188 checkUnitsParseStrict = pexConfig.Field(
189 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
199 self.
detection.includeThresholdMultiplier = 10.0
200 self.
detection.doTempLocalBackground =
False
213 "base_CircularApertureFlux",
218 raise RuntimeError(
"Must measure PSF to measure aperture correction, "
219 "because flags determined by PSF measurement are used to identify "
220 "sources used to measure aperture correction")
231 r"""!Measure bright sources and use this to estimate background and PSF of an exposure
233 @anchor CharacterizeImageTask_
235 @section pipe_tasks_characterizeImage_Contents Contents
237 - @ref pipe_tasks_characterizeImage_Purpose
238 - @ref pipe_tasks_characterizeImage_Initialize
239 - @ref pipe_tasks_characterizeImage_IO
240 - @ref pipe_tasks_characterizeImage_Config
241 - @ref pipe_tasks_characterizeImage_Debug
244 @section pipe_tasks_characterizeImage_Purpose Description
246 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
247 - detect and measure bright sources
249 - measure and subtract background
252 @section pipe_tasks_characterizeImage_Initialize Task initialisation
254 @copydoc \_\_init\_\_
256 @section pipe_tasks_characterizeImage_IO Invoking the Task
258 If you want this task to unpersist inputs or persist outputs, then call
259 the `runDataRef` method (a thin wrapper around the `run` method).
261 If you already have the inputs unpersisted and do not want to persist the output
262 then it is more direct to call the `run` method:
264 @section pipe_tasks_characterizeImage_Config Configuration parameters
266 See @ref CharacterizeImageConfig
268 @section pipe_tasks_characterizeImage_Debug Debug variables
270 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
271 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
273 CharacterizeImageTask has a debug dictionary with the following keys:
276 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
278 <dd>bool; if True display image after each repair in the measure PSF loop
280 <dd>bool; if True display image after each background subtraction in the measure PSF loop
282 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
283 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
285 <dd>bool; if True display image and sources after PSF is measured;
286 this will be identical to the final image displayed by measure_iter if measure_iter is true
288 <dd>bool; if True display image and sources after final repair
290 <dd>bool; if True display image and sources after final measurement
293 For example, put something like:
297 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
298 if name == "lsst.pipe.tasks.characterizeImage":
305 lsstDebug.Info = DebugInfo
307 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
309 Some subtasks may have their own debug variables; see individual Task documentation.
314 ConfigClass = CharacterizeImageConfig
315 _DefaultName =
"characterizeImage"
316 RunnerClass = pipeBase.ButlerInitializedTaskRunner
319 inputs = butlerQC.get(inputRefs)
320 if 'exposureIdInfo' not in inputs.keys():
322 exposureIdInfo.expId, exposureIdInfo.expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
324 inputs[
'exposureIdInfo'] = exposureIdInfo
325 outputs = self.
run(**inputs)
326 butlerQC.put(outputs, outputRefs)
328 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
329 """!Construct a CharacterizeImageTask
331 @param[in] butler A butler object is passed to the refObjLoader constructor in case
332 it is needed to load catalogs. May be None if a catalog-based star selector is
333 not used, if the reference object loader constructor does not require a butler,
334 or if a reference object loader is passed directly via the refObjLoader argument.
335 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
336 external reference catalog to a catalog-based star selector. May be None if a
337 catalog star selector is not used or the loader can be constructed from the
339 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
340 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
345 schema = SourceTable.makeMinimalSchema()
347 self.makeSubtask(
"background")
348 self.makeSubtask(
"installSimplePsf")
349 self.makeSubtask(
"repair")
350 self.makeSubtask(
"measurePsf", schema=self.
schema)
351 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
353 self.makeSubtask(
'refObjLoader', butler=butler)
354 refObjLoader = self.refObjLoader
355 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
357 self.makeSubtask(
'detection', schema=self.
schema)
358 if self.config.doDeblend:
359 self.makeSubtask(
"deblend", schema=self.
schema)
360 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
361 if self.config.doApCorr:
362 self.makeSubtask(
'measureApCorr', schema=self.
schema)
363 self.makeSubtask(
'applyApCorr', schema=self.
schema)
364 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
367 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
372 outputCatSchema.getTable().setMetadata(self.
algMetadata)
373 return {
'outputSchema': outputCatSchema}
376 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
377 """!Characterize a science image and, if wanted, persist the results
379 This simply unpacks the exposure and passes it to the characterize method to do the work.
381 @param[in] dataRef: butler data reference for science exposure
382 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
383 If None then unpersist from "postISRCCD".
384 The following changes are made, depending on the config:
385 - set psf to the measured PSF
386 - set apCorrMap to the measured aperture correction
387 - subtract background
388 - interpolate over cosmic rays
389 - update detection and cosmic ray mask planes
390 @param[in,out] background initial model of background already subtracted from exposure
391 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
392 which is typical for image characterization.
393 A refined background model is output.
394 @param[in] doUnpersist if True the exposure is read from the repository
395 and the exposure and background arguments must be None;
396 if False the exposure must be provided.
397 True is intended for running as a command-line task, False for running as a subtask
399 @return same data as the characterize method
402 self.log.
info(
"Processing %s" % (dataRef.dataId))
405 if exposure
is not None or background
is not None:
406 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
407 exposure = dataRef.get(
"postISRCCD", immediate=
True)
408 elif exposure
is None:
409 raise RuntimeError(
"doUnpersist false; exposure must be provided")
411 exposureIdInfo = dataRef.get(
"expIdInfo")
415 exposureIdInfo=exposureIdInfo,
416 background=background,
419 if self.config.doWrite:
420 dataRef.put(charRes.sourceCat,
"icSrc")
421 if self.config.doWriteExposure:
422 dataRef.put(charRes.exposure,
"icExp")
423 dataRef.put(charRes.background,
"icExpBackground")
428 def run(self, exposure, exposureIdInfo=None, background=None):
429 """!Characterize a science image
431 Peforms the following operations:
432 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
433 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
434 - interpolate over cosmic rays
435 - perform final measurement
437 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
438 The following changes are made:
441 - update detection and cosmic ray mask planes
442 - subtract background and interpolate over cosmic rays
443 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo).
444 If not provided, returned SourceCatalog IDs will not be globally unique.
445 @param[in,out] background initial model of background already subtracted from exposure
446 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
447 which is typical for image characterization.
449 @return pipe_base Struct containing these fields, all from the final iteration
450 of detectMeasureAndEstimatePsf:
451 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
452 mask is updated accordingly, and the PSF model is set
453 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
454 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
455 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
459 if not self.config.doMeasurePsf
and not exposure.hasPsf():
460 self.log.
warn(
"Source catalog detected and measured with placeholder or default PSF")
461 self.installSimplePsf.
run(exposure=exposure)
463 if exposureIdInfo
is None:
467 background = self.background.
run(exposure).background
469 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
470 for i
in range(psfIterations):
473 exposureIdInfo=exposureIdInfo,
474 background=background,
477 psf = dmeRes.exposure.getPsf()
478 psfSigma = psf.computeShape().getDeterminantRadius()
479 psfDimensions = psf.computeImage().getDimensions()
480 medBackground = np.median(dmeRes.background.getImage().getArray())
481 self.log.
info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
482 (i + 1, psfSigma, psfDimensions, medBackground))
484 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
487 self.repair.
run(exposure=dmeRes.exposure)
488 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
492 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
493 exposureId=exposureIdInfo.expId)
494 if self.config.doApCorr:
495 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
496 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
497 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
498 self.catalogCalculation.
run(dmeRes.sourceCat)
500 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
502 return pipeBase.Struct(
503 exposure=dmeRes.exposure,
504 sourceCat=dmeRes.sourceCat,
505 background=dmeRes.background,
506 psfCellSet=dmeRes.psfCellSet,
508 characterized=dmeRes.exposure,
509 backgroundModel=dmeRes.background
514 """!Perform one iteration of detect, measure and estimate PSF
516 Performs the following operations:
517 - if config.doMeasurePsf or not exposure.hasPsf():
518 - install a simple PSF model (replacing the existing one, if need be)
519 - interpolate over cosmic rays with keepCRs=True
520 - estimate background and subtract it from the exposure
521 - detect, deblend and measure sources, and subtract a refined background model;
522 - if config.doMeasurePsf:
525 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
526 The following changes are made:
528 - update detection and cosmic ray mask planes
529 - subtract background
530 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
531 @param[in,out] background initial model of background already subtracted from exposure
532 (an lsst.afw.math.BackgroundList).
534 @return pipe_base Struct containing these fields, all from the final iteration
535 of detect sources, measure sources and estimate PSF:
536 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
537 mask is updated accordingly, and the PSF model is set
538 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
539 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
540 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
543 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
544 self.log.
warn(
"Source catalog detected and measured with placeholder or default PSF")
545 self.installSimplePsf.
run(exposure=exposure)
548 self.repair.
run(exposure=exposure, keepCRs=
True)
549 self.
display(
"repair_iter", exposure=exposure)
551 if background
is None:
554 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
555 table = SourceTable.make(self.
schema, sourceIdFactory)
558 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
559 sourceCat = detRes.sources
560 if detRes.fpSets.background:
561 for bg
in detRes.fpSets.background:
562 background.append(bg)
564 if self.config.doDeblend:
565 self.deblend.
run(exposure=exposure, sources=sourceCat)
567 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
569 measPsfRes = pipeBase.Struct(cellSet=
None)
570 if self.config.doMeasurePsf:
571 if self.measurePsf.usesMatches:
572 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
575 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
576 expId=exposureIdInfo.expId)
577 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
579 return pipeBase.Struct(
582 background=background,
583 psfCellSet=measPsfRes.cellSet,
587 """Return a dict of empty catalogs for each catalog dataset produced by this task.
591 return {
"icSrc": sourceCat}
593 def display(self, itemName, exposure, sourceCat=None):
594 """Display exposure and sources on next frame, if display of itemName has been requested
596 @param[in] itemName name of item in debugInfo
597 @param[in] exposure exposure to display
598 @param[in] sourceCat source catalog to display