24from lsstDebug
import getDebugFrame
29import lsst.pipe.base.connectionTypes
as cT
36from lsst.obs.base
import ExposureIdInfo
37from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
39from .measurePsf
import MeasurePsfTask
40from .repair
import RepairTask
41from .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",
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",
165 deprecated=(
"This subtask has been moved to CalibrateTask "
168 computeSummaryStats = pexConfig.ConfigurableField(
169 target=ComputeExposureSummaryStatsTask,
170 doc=
"Subtask to run computeSummaryStats on exposure",
171 deprecated=(
"This subtask has been moved to CalibrateTask "
174 useSimplePsf = pexConfig.Field(
177 doc=
"Replace the existing PSF model with a simplified version that has the same sigma "
178 "at the start of each PSF determination iteration? Doing so makes PSF determination "
179 "converge more robustly and quickly.",
181 installSimplePsf = pexConfig.ConfigurableField(
182 target=InstallGaussianPsfTask,
183 doc=
"Install a simple PSF model",
185 refObjLoader = pexConfig.ConfigurableField(
186 target=LoadIndexedReferenceObjectsTask,
187 doc=
"reference object loader",
189 ref_match = pexConfig.ConfigurableField(
191 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. "
192 "Warning: matching will only work well if the initial WCS is accurate enough "
193 "to give good matches (roughly: good to 3 arcsec across the CCD).",
195 measurePsf = pexConfig.ConfigurableField(
196 target=MeasurePsfTask,
199 repair = pexConfig.ConfigurableField(
201 doc=
"Remove cosmic rays",
203 requireCrForPsf = pexConfig.Field(
206 doc=
"Require cosmic ray detection and masking to run successfully before measuring the PSF."
208 checkUnitsParseStrict = pexConfig.Field(
209 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
219 self.
detection.includeThresholdMultiplier = 10.0
220 self.
detection.doTempLocalBackground =
False
233 "base_CircularApertureFlux",
238 raise RuntimeError(
"Must measure PSF to measure aperture correction, "
239 "because flags determined by PSF measurement are used to identify "
240 "sources used to measure aperture correction")
251 r"""!Measure bright sources and use this to estimate background and PSF of an exposure
253 @anchor CharacterizeImageTask_
255 @section pipe_tasks_characterizeImage_Contents Contents
257 -
@ref pipe_tasks_characterizeImage_Purpose
258 -
@ref pipe_tasks_characterizeImage_Initialize
259 -
@ref pipe_tasks_characterizeImage_IO
260 -
@ref pipe_tasks_characterizeImage_Config
261 -
@ref pipe_tasks_characterizeImage_Debug
264 @section pipe_tasks_characterizeImage_Purpose Description
266 Given an exposure
with defects repaired (masked
and interpolated over, e.g.
as output by IsrTask):
267 - detect
and measure bright sources
269 - measure
and subtract background
272 @section pipe_tasks_characterizeImage_Initialize Task initialisation
274 @copydoc \_\_init\_\_
276 @section pipe_tasks_characterizeImage_IO Invoking the Task
278 If you want this task to unpersist inputs
or persist outputs, then call
279 the `runDataRef` method (a thin wrapper around the `run` method).
281 If you already have the inputs unpersisted
and do
not want to persist the output
282 then it
is more direct to call the `run` method:
284 @section pipe_tasks_characterizeImage_Config Configuration parameters
286 See
@ref CharacterizeImageConfig
288 @section pipe_tasks_characterizeImage_Debug Debug variables
290 The
@link lsst.pipe.base.cmdLineTask.CmdLineTask command line task
@endlink interface supports a flag
291 `--debug` to
import `debug.py`
from your `$PYTHONPATH`; see
@ref baseDebug
for more about `debug.py`.
293 CharacterizeImageTask has a debug dictionary
with the following keys:
296 <dd>int:
if specified, the frame of first debug image displayed (defaults to 1)
298 <dd>bool;
if True display image after each repair
in the measure PSF loop
300 <dd>bool;
if True display image after each background subtraction
in the measure PSF loop
302 <dd>bool;
if True display image
and sources at the end of each iteration of the measure PSF loop
303 See
@ref lsst.meas.astrom.displayAstrometry
for the meaning of the various symbols.
305 <dd>bool;
if True display image
and sources after PSF
is measured;
306 this will be identical to the final image displayed by measure_iter
if measure_iter
is true
308 <dd>bool;
if True display image
and sources after final repair
310 <dd>bool;
if True display image
and sources after final measurement
313 For example, put something like:
318 if name ==
"lsst.pipe.tasks.characterizeImage":
327 into your `debug.py` file
and run `calibrateTask.py`
with the `--debug` flag.
329 Some subtasks may have their own debug variables; see individual Task documentation.
334 ConfigClass = CharacterizeImageConfig
335 _DefaultName =
"characterizeImage"
336 RunnerClass = pipeBase.ButlerInitializedTaskRunner
339 inputs = butlerQC.get(inputRefs)
340 if 'exposureIdInfo' not in inputs.keys():
341 inputs[
'exposureIdInfo'] = ExposureIdInfo.fromDataId(butlerQC.quantum.dataId,
"visit_detector")
342 outputs = self.
run(**inputs)
343 butlerQC.put(outputs, outputRefs)
345 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
346 """!Construct a CharacterizeImageTask
348 @param[
in] butler A butler object
is passed to the refObjLoader constructor
in case
349 it
is needed to load catalogs. May be
None if a catalog-based star selector
is
350 not used,
if the reference object loader constructor does
not require a butler,
351 or if a reference object loader
is passed directly via the refObjLoader argument.
352 @param[
in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
353 external reference catalog to a catalog-based star selector. May be
None if a
354 catalog star selector
is not used
or the loader can be constructed
from the
357 @param[
in,out] kwargs other keyword arguments
for lsst.pipe.base.CmdLineTask
362 schema = SourceTable.makeMinimalSchema()
364 self.makeSubtask(
"background")
365 self.makeSubtask(
"installSimplePsf")
366 self.makeSubtask(
"repair")
367 self.makeSubtask(
"measurePsf", schema=self.
schema)
368 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
370 self.makeSubtask(
'refObjLoader', butler=butler)
371 refObjLoader = self.refObjLoader
372 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
374 self.makeSubtask(
'detection', schema=self.
schema)
375 if self.config.doDeblend:
376 self.makeSubtask(
"deblend", schema=self.
schema)
377 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
378 if self.config.doApCorr:
379 self.makeSubtask(
'measureApCorr', schema=self.
schema)
380 self.makeSubtask(
'applyApCorr', schema=self.
schema)
381 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
382 self.
_initialFrame = getDebugFrame(self._display,
"frame")
or 1
384 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
388 outputCatSchema = afwTable.SourceCatalog(self.
schema)
389 outputCatSchema.getTable().setMetadata(self.
algMetadata)
390 return {
'outputSchema': outputCatSchema}
393 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
394 """!Characterize a science image and, if wanted, persist the results
396 This simply unpacks the exposure and passes it to the characterize method to do the work.
398 @param[
in] dataRef: butler data reference
for science exposure
399 @param[
in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF
or similar).
400 If
None then unpersist
from "postISRCCD".
401 The following changes are made, depending on the config:
402 - set psf to the measured PSF
403 - set apCorrMap to the measured aperture correction
404 - subtract background
405 - interpolate over cosmic rays
406 - update detection
and cosmic ray mask planes
407 @param[
in,out] background initial model of background already subtracted
from exposure
408 (an lsst.afw.math.BackgroundList). May be
None if no background has been subtracted,
409 which
is typical
for image characterization.
410 A refined background model
is output.
411 @param[
in] doUnpersist
if True the exposure
is read
from the repository
412 and the exposure
and background arguments must be
None;
413 if False the exposure must be provided.
414 True is intended
for running
as a command-line task,
False for running
as a subtask
416 @return same data
as the characterize method
419 self.log.info(
"Processing %s", dataRef.dataId)
422 if exposure
is not None or background
is not None:
423 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
424 exposure = dataRef.get(
"postISRCCD", immediate=
True)
425 elif exposure
is None:
426 raise RuntimeError(
"doUnpersist false; exposure must be provided")
428 exposureIdInfo = dataRef.get(
"expIdInfo")
432 exposureIdInfo=exposureIdInfo,
433 background=background,
436 if self.config.doWrite:
437 dataRef.put(charRes.sourceCat,
"icSrc")
438 if self.config.doWriteExposure:
439 dataRef.put(charRes.exposure,
"icExp")
440 dataRef.put(charRes.background,
"icExpBackground")
445 def run(self, exposure, exposureIdInfo=None, background=None):
446 """!Characterize a science image
448 Peforms the following operations:
449 - Iterate the following config.psfIterations times, or once
if config.doMeasurePsf false:
450 - detect
and measure sources
and estimate PSF (see detectMeasureAndEstimatePsf
for details)
451 - interpolate over cosmic rays
452 - perform final measurement
454 @param[
in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF
or similar).
455 The following changes are made:
458 - update detection
and cosmic ray mask planes
459 - subtract background
and interpolate over cosmic rays
460 @param[
in] exposureIdInfo ID info
for exposure (an lsst.obs.base.ExposureIdInfo).
461 If
not provided, returned SourceCatalog IDs will
not be globally unique.
462 @param[
in,out] background initial model of background already subtracted
from exposure
463 (an lsst.afw.math.BackgroundList). May be
None if no background has been subtracted,
464 which
is typical
for image characterization.
466 @return pipe_base Struct containing these fields, all
from the final iteration
467 of detectMeasureAndEstimatePsf:
468 - exposure: characterized exposure; image
is repaired by interpolating over cosmic rays,
469 mask
is updated accordingly,
and the PSF model
is set
471 - background: model of background subtracted
from exposure (an lsst.afw.math.BackgroundList)
476 if not self.config.doMeasurePsf
and not exposure.hasPsf():
477 self.log.info(
"CharacterizeImageTask initialized with 'simple' PSF.")
478 self.installSimplePsf.
run(exposure=exposure)
480 if exposureIdInfo
is None:
481 exposureIdInfo = ExposureIdInfo()
484 background = self.background.
run(exposure).background
486 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
487 for i
in range(psfIterations):
490 exposureIdInfo=exposureIdInfo,
491 background=background,
494 psf = dmeRes.exposure.getPsf()
495 psfSigma = psf.computeShape().getDeterminantRadius()
496 psfDimensions = psf.computeImage().getDimensions()
497 medBackground = np.median(dmeRes.background.getImage().getArray())
498 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f",
499 i + 1, psfSigma, psfDimensions, medBackground)
501 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
504 self.repair.
run(exposure=dmeRes.exposure)
505 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
509 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
510 exposureId=exposureIdInfo.expId)
511 if self.config.doApCorr:
512 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
513 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
514 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
515 self.catalogCalculation.
run(dmeRes.sourceCat)
517 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
519 return pipeBase.Struct(
520 exposure=dmeRes.exposure,
521 sourceCat=dmeRes.sourceCat,
522 background=dmeRes.background,
523 psfCellSet=dmeRes.psfCellSet,
525 characterized=dmeRes.exposure,
526 backgroundModel=dmeRes.background
531 """!Perform one iteration of detect, measure and estimate PSF
533 Performs the following operations:
534 - if config.doMeasurePsf
or not exposure.hasPsf():
535 - install a simple PSF model (replacing the existing one,
if need be)
536 - interpolate over cosmic rays
with keepCRs=
True
537 - estimate background
and subtract it
from the exposure
538 - detect, deblend
and measure sources,
and subtract a refined background model;
539 -
if config.doMeasurePsf:
542 @param[
in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF
or similar)
543 The following changes are made:
545 - update detection
and cosmic ray mask planes
546 - subtract background
547 @param[
in] exposureIdInfo ID info
for exposure (an lsst.obs_base.ExposureIdInfo)
548 @param[
in,out] background initial model of background already subtracted
from exposure
549 (an lsst.afw.math.BackgroundList).
551 @return pipe_base Struct containing these fields, all
from the final iteration
552 of detect sources, measure sources
and estimate PSF:
553 - exposure characterized exposure; image
is repaired by interpolating over cosmic rays,
554 mask
is updated accordingly,
and the PSF model
is set
556 - background model of background subtracted
from exposure (an lsst.afw.math.BackgroundList)
560 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
561 self.log.info(
"PSF estimation initialized with 'simple' PSF")
562 self.installSimplePsf.
run(exposure=exposure)
565 if self.config.requireCrForPsf:
566 self.repair.
run(exposure=exposure, keepCRs=
True)
569 self.repair.
run(exposure=exposure, keepCRs=
True)
571 self.log.warning(
"Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
572 self.config.repair.cosmicray.nCrPixelMax)
574 self.
display(
"repair_iter", exposure=exposure)
576 if background
is None:
577 background = BackgroundList()
579 sourceIdFactory = exposureIdInfo.makeSourceIdFactory()
580 table = SourceTable.make(self.
schema, sourceIdFactory)
583 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
584 sourceCat = detRes.sources
585 if detRes.fpSets.background:
586 for bg
in detRes.fpSets.background:
587 background.append(bg)
589 if self.config.doDeblend:
590 self.deblend.
run(exposure=exposure, sources=sourceCat)
592 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
594 measPsfRes = pipeBase.Struct(cellSet=
None)
595 if self.config.doMeasurePsf:
596 if self.measurePsf.usesMatches:
597 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
600 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
601 expId=exposureIdInfo.expId)
602 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
604 return pipeBase.Struct(
607 background=background,
608 psfCellSet=measPsfRes.cellSet,
612 """Return a dict of empty catalogs for each catalog dataset produced by this task.
614 sourceCat = SourceCatalog(self.schema)
616 return {
"icSrc": sourceCat}
618 def display(self, itemName, exposure, sourceCat=None):
619 """Display exposure and sources on next frame, if display of itemName has been requested
621 @param[
in] itemName name of item
in debugInfo
622 @param[
in] exposure exposure to display
623 @param[
in] sourceCat source catalog to display
625 val = getDebugFrame(self._display, itemName)
629 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)
Config for CharacterizeImageTask.
def adjustQuantum(self, inputs, outputs, label, dataId)
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)