24 from lsstDebug
import getDebugFrame
37 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
39 from .measurePsf
import MeasurePsfTask
40 from .repair
import RepairTask
41 from .computeExposureSummaryStats
import ComputeExposureSummaryStatsTask
44 __all__ = [
"CharacterizeImageConfig",
"CharacterizeImageTask"]
48 dimensions=(
"instrument",
"visit",
"detector")):
50 doc=
"Input exposure data",
52 storageClass=
"Exposure",
53 dimensions=[
"instrument",
"exposure",
"detector"],
55 characterized = cT.Output(
56 doc=
"Output characterized data.",
58 storageClass=
"ExposureF",
59 dimensions=[
"instrument",
"visit",
"detector"],
61 sourceCat = cT.Output(
62 doc=
"Output source catalog.",
64 storageClass=
"SourceCatalog",
65 dimensions=[
"instrument",
"visit",
"detector"],
67 backgroundModel = cT.Output(
68 doc=
"Output background model.",
69 name=
"icExpBackground",
70 storageClass=
"Background",
71 dimensions=[
"instrument",
"visit",
"detector"],
73 outputSchema = cT.InitOutput(
74 doc=
"Schema of the catalog produced by CharacterizeImage",
76 storageClass=
"SourceCatalog",
79 def adjustQuantum(self, datasetRefMap: pipeBase.InputQuantizedConnection):
83 except pipeBase.ScalarError
as err:
84 raise pipeBase.ScalarError(
85 "CharacterizeImageTask can at present only be run on visits that are associated with "
86 "exactly one exposure. Either this is not a valid exposure for this pipeline, or the "
87 "snap-combination step you probably want hasn't been configured to run between ISR and "
88 "this task (as of this writing, that would be because it hasn't been implemented yet)."
93 pipelineConnections=CharacterizeImageConnections):
95 """!Config for CharacterizeImageTask"""
96 doMeasurePsf = pexConfig.Field(
99 doc=
"Measure PSF? If False then for all subsequent operations use either existing PSF "
100 "model when present, or install simple PSF model when not (see installSimplePsf "
103 doWrite = pexConfig.Field(
106 doc=
"Persist results?",
108 doWriteExposure = pexConfig.Field(
111 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
113 psfIterations = pexConfig.RangeField(
117 doc=
"Number of iterations of detect sources, measure sources, "
118 "estimate PSF. If useSimplePsf is True then 2 should be plenty; "
119 "otherwise more may be wanted.",
121 background = pexConfig.ConfigurableField(
122 target=SubtractBackgroundTask,
123 doc=
"Configuration for initial background estimation",
125 detection = pexConfig.ConfigurableField(
126 target=SourceDetectionTask,
129 doDeblend = pexConfig.Field(
132 doc=
"Run deblender input exposure"
134 deblend = pexConfig.ConfigurableField(
135 target=SourceDeblendTask,
136 doc=
"Split blended source into their components"
138 measurement = pexConfig.ConfigurableField(
139 target=SingleFrameMeasurementTask,
140 doc=
"Measure sources"
142 doApCorr = pexConfig.Field(
145 doc=
"Run subtasks to measure and apply aperture corrections"
147 measureApCorr = pexConfig.ConfigurableField(
148 target=MeasureApCorrTask,
149 doc=
"Subtask to measure aperture corrections"
151 applyApCorr = pexConfig.ConfigurableField(
152 target=ApplyApCorrTask,
153 doc=
"Subtask to apply aperture corrections"
157 catalogCalculation = pexConfig.ConfigurableField(
158 target=CatalogCalculationTask,
159 doc=
"Subtask to run catalogCalculation plugins on catalog"
161 doComputeSummaryStats = pexConfig.Field(
164 doc=
"Run subtask to measure exposure summary statistics"
166 computeSummaryStats = pexConfig.ConfigurableField(
167 target=ComputeExposureSummaryStatsTask,
168 doc=
"Subtask to run computeSummaryStats on exposure"
170 useSimplePsf = pexConfig.Field(
173 doc=
"Replace the existing PSF model with a simplified version that has the same sigma "
174 "at the start of each PSF determination iteration? Doing so makes PSF determination "
175 "converge more robustly and quickly.",
177 installSimplePsf = pexConfig.ConfigurableField(
178 target=InstallGaussianPsfTask,
179 doc=
"Install a simple PSF model",
181 refObjLoader = pexConfig.ConfigurableField(
182 target=LoadIndexedReferenceObjectsTask,
183 doc=
"reference object loader",
185 ref_match = pexConfig.ConfigurableField(
187 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. "
188 "Warning: matching will only work well if the initial WCS is accurate enough "
189 "to give good matches (roughly: good to 3 arcsec across the CCD).",
191 measurePsf = pexConfig.ConfigurableField(
192 target=MeasurePsfTask,
195 repair = pexConfig.ConfigurableField(
197 doc=
"Remove cosmic rays",
199 requireCrForPsf = pexConfig.Field(
202 doc=
"Require cosmic ray detection and masking to run successfully before measuring the PSF."
204 checkUnitsParseStrict = pexConfig.Field(
205 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
214 self.
detectiondetection.thresholdValue = 5.0
215 self.
detectiondetection.includeThresholdMultiplier = 10.0
216 self.
detectiondetection.doTempLocalBackground =
False
229 "base_CircularApertureFlux",
234 raise RuntimeError(
"Must measure PSF to measure aperture correction, "
235 "because flags determined by PSF measurement are used to identify "
236 "sources used to measure aperture correction")
247 r"""!Measure bright sources and use this to estimate background and PSF of an exposure
249 @anchor CharacterizeImageTask_
251 @section pipe_tasks_characterizeImage_Contents Contents
253 - @ref pipe_tasks_characterizeImage_Purpose
254 - @ref pipe_tasks_characterizeImage_Initialize
255 - @ref pipe_tasks_characterizeImage_IO
256 - @ref pipe_tasks_characterizeImage_Config
257 - @ref pipe_tasks_characterizeImage_Debug
260 @section pipe_tasks_characterizeImage_Purpose Description
262 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
263 - detect and measure bright sources
265 - measure and subtract background
268 @section pipe_tasks_characterizeImage_Initialize Task initialisation
270 @copydoc \_\_init\_\_
272 @section pipe_tasks_characterizeImage_IO Invoking the Task
274 If you want this task to unpersist inputs or persist outputs, then call
275 the `runDataRef` method (a thin wrapper around the `run` method).
277 If you already have the inputs unpersisted and do not want to persist the output
278 then it is more direct to call the `run` method:
280 @section pipe_tasks_characterizeImage_Config Configuration parameters
282 See @ref CharacterizeImageConfig
284 @section pipe_tasks_characterizeImage_Debug Debug variables
286 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
287 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
289 CharacterizeImageTask has a debug dictionary with the following keys:
292 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
294 <dd>bool; if True display image after each repair in the measure PSF loop
296 <dd>bool; if True display image after each background subtraction in the measure PSF loop
298 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
299 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
301 <dd>bool; if True display image and sources after PSF is measured;
302 this will be identical to the final image displayed by measure_iter if measure_iter is true
304 <dd>bool; if True display image and sources after final repair
306 <dd>bool; if True display image and sources after final measurement
309 For example, put something like:
313 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
314 if name == "lsst.pipe.tasks.characterizeImage":
321 lsstDebug.Info = DebugInfo
323 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
325 Some subtasks may have their own debug variables; see individual Task documentation.
330 ConfigClass = CharacterizeImageConfig
331 _DefaultName =
"characterizeImage"
332 RunnerClass = pipeBase.ButlerInitializedTaskRunner
335 inputs = butlerQC.get(inputRefs)
336 if 'exposureIdInfo' not in inputs.keys():
337 exposureIdInfo = ExposureIdInfo()
338 exposureIdInfo.expId, exposureIdInfo.expBits = butlerQC.quantum.dataId.pack(
"visit_detector",
340 inputs[
'exposureIdInfo'] = exposureIdInfo
341 outputs = self.
runrun(**inputs)
342 butlerQC.put(outputs, outputRefs)
344 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
345 """!Construct a CharacterizeImageTask
347 @param[in] butler A butler object is passed to the refObjLoader constructor in case
348 it is needed to load catalogs. May be None if a catalog-based star selector is
349 not used, if the reference object loader constructor does not require a butler,
350 or if a reference object loader is passed directly via the refObjLoader argument.
351 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
352 external reference catalog to a catalog-based star selector. May be None if a
353 catalog star selector is not used or the loader can be constructed from the
355 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
356 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
361 schema = SourceTable.makeMinimalSchema()
363 self.makeSubtask(
"background")
364 self.makeSubtask(
"installSimplePsf")
365 self.makeSubtask(
"repair")
366 self.makeSubtask(
"measurePsf", schema=self.
schemaschema)
367 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
369 self.makeSubtask(
'refObjLoader', butler=butler)
370 refObjLoader = self.refObjLoader
371 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
373 self.makeSubtask(
'detection', schema=self.
schemaschema)
374 if self.config.doDeblend:
375 self.makeSubtask(
"deblend", schema=self.
schemaschema)
376 self.makeSubtask(
'measurement', schema=self.
schemaschema, algMetadata=self.
algMetadataalgMetadata)
377 if self.config.doApCorr:
378 self.makeSubtask(
'measureApCorr', schema=self.
schemaschema)
379 self.makeSubtask(
'applyApCorr', schema=self.
schemaschema)
380 self.makeSubtask(
'catalogCalculation', schema=self.
schemaschema)
381 if self.config.doComputeSummaryStats:
382 self.makeSubtask(
'computeSummaryStats')
383 self.
_initialFrame_initialFrame = getDebugFrame(self._display,
"frame")
or 1
385 self.
schemaschema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
389 outputCatSchema = afwTable.SourceCatalog(self.
schemaschema)
390 outputCatSchema.getTable().setMetadata(self.
algMetadataalgMetadata)
391 return {
'outputSchema': outputCatSchema}
394 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
395 """!Characterize a science image and, if wanted, persist the results
397 This simply unpacks the exposure and passes it to the characterize method to do the work.
399 @param[in] dataRef: butler data reference for science exposure
400 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
401 If None then unpersist from "postISRCCD".
402 The following changes are made, depending on the config:
403 - set psf to the measured PSF
404 - set apCorrMap to the measured aperture correction
405 - subtract background
406 - interpolate over cosmic rays
407 - update detection and cosmic ray mask planes
408 @param[in,out] background initial model of background already subtracted from exposure
409 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
410 which is typical for image characterization.
411 A refined background model is output.
412 @param[in] doUnpersist if True the exposure is read from the repository
413 and the exposure and background arguments must be None;
414 if False the exposure must be provided.
415 True is intended for running as a command-line task, False for running as a subtask
417 @return same data as the characterize method
420 self.log.info(
"Processing %s" % (dataRef.dataId))
423 if exposure
is not None or background
is not None:
424 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
425 exposure = dataRef.get(
"postISRCCD", immediate=
True)
426 elif exposure
is None:
427 raise RuntimeError(
"doUnpersist false; exposure must be provided")
429 exposureIdInfo = dataRef.get(
"expIdInfo")
431 charRes = self.
runrun(
433 exposureIdInfo=exposureIdInfo,
434 background=background,
437 if self.config.doWrite:
438 dataRef.put(charRes.sourceCat,
"icSrc")
439 if self.config.doWriteExposure:
440 dataRef.put(charRes.exposure,
"icExp")
441 dataRef.put(charRes.background,
"icExpBackground")
446 def run(self, exposure, exposureIdInfo=None, background=None):
447 """!Characterize a science image
449 Peforms the following operations:
450 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
451 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
452 - interpolate over cosmic rays
453 - perform final measurement
455 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
456 The following changes are made:
459 - update detection and cosmic ray mask planes
460 - subtract background and interpolate over cosmic rays
461 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo).
462 If not provided, returned SourceCatalog IDs will not be globally unique.
463 @param[in,out] background initial model of background already subtracted from exposure
464 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
465 which is typical for image characterization.
467 @return pipe_base Struct containing these fields, all from the final iteration
468 of detectMeasureAndEstimatePsf:
469 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
470 mask is updated accordingly, and the PSF model is set
471 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
472 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
473 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
477 if not self.config.doMeasurePsf
and not exposure.hasPsf():
478 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
479 self.installSimplePsf.
run(exposure=exposure)
481 if exposureIdInfo
is None:
482 exposureIdInfo = ExposureIdInfo()
485 background = self.background.
run(exposure).background
487 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
488 for i
in range(psfIterations):
491 exposureIdInfo=exposureIdInfo,
492 background=background,
495 psf = dmeRes.exposure.getPsf()
496 psfSigma = psf.computeShape().getDeterminantRadius()
497 psfDimensions = psf.computeImage().getDimensions()
498 medBackground = np.median(dmeRes.background.getImage().getArray())
499 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
500 (i + 1, psfSigma, psfDimensions, medBackground))
502 self.
displaydisplay(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
505 self.repair.
run(exposure=dmeRes.exposure)
506 self.
displaydisplay(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
510 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
511 exposureId=exposureIdInfo.expId)
512 if self.config.doApCorr:
513 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
514 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
515 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
516 self.catalogCalculation.
run(dmeRes.sourceCat)
517 if self.config.doComputeSummaryStats:
518 summary = self.computeSummaryStats.
run(exposure=dmeRes.exposure,
519 sources=dmeRes.sourceCat,
520 background=dmeRes.background)
521 dmeRes.exposure.getInfo().setSummaryStats(summary)
523 self.
displaydisplay(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
525 return pipeBase.Struct(
526 exposure=dmeRes.exposure,
527 sourceCat=dmeRes.sourceCat,
528 background=dmeRes.background,
529 psfCellSet=dmeRes.psfCellSet,
531 characterized=dmeRes.exposure,
532 backgroundModel=dmeRes.background
537 """!Perform one iteration of detect, measure and estimate PSF
539 Performs the following operations:
540 - if config.doMeasurePsf or not exposure.hasPsf():
541 - install a simple PSF model (replacing the existing one, if need be)
542 - interpolate over cosmic rays with keepCRs=True
543 - estimate background and subtract it from the exposure
544 - detect, deblend and measure sources, and subtract a refined background model;
545 - if config.doMeasurePsf:
548 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
549 The following changes are made:
551 - update detection and cosmic ray mask planes
552 - subtract background
553 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
554 @param[in,out] background initial model of background already subtracted from exposure
555 (an lsst.afw.math.BackgroundList).
557 @return pipe_base Struct containing these fields, all from the final iteration
558 of detect sources, measure sources and estimate PSF:
559 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
560 mask is updated accordingly, and the PSF model is set
561 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
562 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
563 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
566 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
567 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
568 self.installSimplePsf.
run(exposure=exposure)
571 if self.config.requireCrForPsf:
572 self.repair.
run(exposure=exposure, keepCRs=
True)
575 self.repair.
run(exposure=exposure, keepCRs=
True)
577 self.log.warn(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)" %
578 self.config.repair.cosmicray.nCrPixelMax)
580 self.
displaydisplay(
"repair_iter", exposure=exposure)
582 if background
is None:
583 background = BackgroundList()
585 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
586 table = SourceTable.make(self.
schemaschema, sourceIdFactory)
589 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
590 sourceCat = detRes.sources
591 if detRes.fpSets.background:
592 for bg
in detRes.fpSets.background:
593 background.append(bg)
595 if self.config.doDeblend:
596 self.deblend.
run(exposure=exposure, sources=sourceCat)
598 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
600 measPsfRes = pipeBase.Struct(cellSet=
None)
601 if self.config.doMeasurePsf:
602 if self.measurePsf.usesMatches:
603 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
606 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
607 expId=exposureIdInfo.expId)
608 self.
displaydisplay(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
610 return pipeBase.Struct(
613 background=background,
614 psfCellSet=measPsfRes.cellSet,
618 """Return a dict of empty catalogs for each catalog dataset produced by this task.
620 sourceCat = SourceCatalog(self.
schemaschema)
621 sourceCat.getTable().setMetadata(self.
algMetadataalgMetadata)
622 return {
"icSrc": sourceCat}
624 def display(self, itemName, exposure, sourceCat=None):
625 """Display exposure and sources on next frame, if display of itemName has been requested
627 @param[in] itemName name of item in debugInfo
628 @param[in] exposure exposure to display
629 @param[in] sourceCat source catalog to display
631 val = getDebugFrame(self._display, itemName)
635 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame_frame, pause=
False)
Config for CharacterizeImageTask.
def adjustQuantum(self, pipeBase.InputQuantizedConnection datasetRefMap)
Measure bright sources and use this to estimate background and PSF of an exposure.
def getSchemaCatalogs(self)
def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs)
Construct a CharacterizeImageTask.
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def getInitOutputDatasets(self)
def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True)
Characterize a science image and, if wanted, persist the results.
def run(self, exposure, exposureIdInfo=None, background=None)
Characterize a science image.
def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background)
Perform one iteration of detect, measure and estimate PSF.
def display(self, itemName, exposure, sourceCat=None)