24 from lsstDebug
import getDebugFrame
33 from lsst.meas.extensions.astrometryNet
import 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 for all subsequent operations use either existing PSF " 49 "model when present, or install simple PSF model when not (see installSimplePsf " 52 doWrite = pexConfig.Field(
55 doc=
"Persist results?",
57 doWriteExposure = pexConfig.Field(
60 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
62 psfIterations = pexConfig.RangeField(
66 doc=
"Number of iterations of detect sources, measure sources, " 67 "estimate PSF. If useSimplePsf is True then 2 should be plenty; " 68 "otherwise more may be wanted.",
70 background = pexConfig.ConfigurableField(
71 target=SubtractBackgroundTask,
72 doc=
"Configuration for initial background estimation",
74 detection = pexConfig.ConfigurableField(
75 target=SourceDetectionTask,
78 doDeblend = pexConfig.Field(
81 doc=
"Run deblender input exposure" 83 deblend = pexConfig.ConfigurableField(
84 target=SourceDeblendTask,
85 doc=
"Split blended source into their components" 87 measurement = pexConfig.ConfigurableField(
88 target=SingleFrameMeasurementTask,
91 doApCorr = pexConfig.Field(
94 doc=
"Run subtasks to measure and apply aperture corrections" 96 measureApCorr = pexConfig.ConfigurableField(
97 target=MeasureApCorrTask,
98 doc=
"Subtask to measure aperture corrections" 100 applyApCorr = pexConfig.ConfigurableField(
101 target=ApplyApCorrTask,
102 doc=
"Subtask to apply aperture corrections" 106 catalogCalculation = pexConfig.ConfigurableField(
107 target=CatalogCalculationTask,
108 doc=
"Subtask to run catalogCalculation plugins on catalog" 110 useSimplePsf = pexConfig.Field(
113 doc=
"Replace the existing PSF model with a simplified version that has the same sigma " 114 "at the start of each PSF determination iteration? Doing so makes PSF determination " 115 "converge more robustly and quickly.",
117 installSimplePsf = pexConfig.ConfigurableField(
118 target=InstallGaussianPsfTask,
119 doc=
"Install a simple PSF model",
121 refObjLoader = pexConfig.ConfigurableField(
122 target=LoadAstrometryNetObjectsTask,
123 doc=
"reference object loader",
125 ref_match = pexConfig.ConfigurableField(
127 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. " 128 "Warning: matching will only work well if the initial WCS is accurate enough " 129 "to give good matches (roughly: good to 3 arcsec across the CCD).",
131 measurePsf = pexConfig.ConfigurableField(
132 target=MeasurePsfTask,
135 repair = pexConfig.ConfigurableField(
137 doc=
"Remove cosmic rays",
139 checkUnitsParseStrict = pexConfig.Field(
140 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
146 pexConfig.Config.setDefaults(self)
150 self.
detection.includeThresholdMultiplier = 10.0
163 "base_CircularApertureFlux",
168 raise RuntimeError(
"Must measure PSF to measure aperture correction, " 169 "because flags determined by PSF measurement are used to identify " 170 "sources used to measure aperture correction")
181 """!Measure bright sources and use this to estimate background and PSF of an exposure 183 @anchor CharacterizeImageTask_ 185 @section pipe_tasks_characterizeImage_Contents Contents 187 - @ref pipe_tasks_characterizeImage_Purpose 188 - @ref pipe_tasks_characterizeImage_Initialize 189 - @ref pipe_tasks_characterizeImage_IO 190 - @ref pipe_tasks_characterizeImage_Config 191 - @ref pipe_tasks_characterizeImage_Debug 194 @section pipe_tasks_characterizeImage_Purpose Description 196 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask): 197 - detect and measure bright sources 199 - measure and subtract background 202 @section pipe_tasks_characterizeImage_Initialize Task initialisation 204 @copydoc \_\_init\_\_ 206 @section pipe_tasks_characterizeImage_IO Invoking the Task 208 If you want this task to unpersist inputs or persist outputs, then call 209 the `runDataRef` method (a thin wrapper around the `run` method). 211 If you already have the inputs unpersisted and do not want to persist the output 212 then it is more direct to call the `run` method: 214 @section pipe_tasks_characterizeImage_Config Configuration parameters 216 See @ref CharacterizeImageConfig 218 @section pipe_tasks_characterizeImage_Debug Debug variables 220 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 221 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 223 CharacterizeImageTask has a debug dictionary with the following keys: 226 <dd>int: if specified, the frame of first debug image displayed (defaults to 1) 228 <dd>bool; if True display image after each repair in the measure PSF loop 230 <dd>bool; if True display image after each background subtraction in the measure PSF loop 232 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop 233 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols. 235 <dd>bool; if True display image and sources after PSF is measured; 236 this will be identical to the final image displayed by measure_iter if measure_iter is true 238 <dd>bool; if True display image and sources after final repair 240 <dd>bool; if True display image and sources after final measurement 243 For example, put something like: 247 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 248 if name == "lsst.pipe.tasks.characterizeImage": 255 lsstDebug.Info = DebugInfo 257 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag. 259 Some subtasks may have their own debug variables; see individual Task documentation. 264 ConfigClass = CharacterizeImageConfig
265 _DefaultName =
"characterizeImage" 266 RunnerClass = pipeBase.ButlerInitializedTaskRunner
268 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
269 """!Construct a CharacterizeImageTask 271 @param[in] butler A butler object is passed to the refObjLoader constructor in case 272 it is needed to load catalogs. May be None if a catalog-based star selector is 273 not used, if the reference object loader constructor does not require a butler, 274 or if a reference object loader is passed directly via the refObjLoader argument. 275 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an 276 external reference catalog to a catalog-based star selector. May be None if a 277 catalog star selector is not used or the loader can be constructed from the 279 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None 280 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask 282 pipeBase.CmdLineTask.__init__(self, **kwargs)
284 schema = SourceTable.makeMinimalSchema()
286 self.makeSubtask(
"background")
287 self.makeSubtask(
"installSimplePsf")
288 self.makeSubtask(
"repair")
289 self.makeSubtask(
"measurePsf", schema=self.
schema)
290 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
292 self.makeSubtask(
'refObjLoader', butler=butler)
293 refObjLoader = self.refObjLoader
294 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
296 self.makeSubtask(
'detection', schema=self.
schema)
297 if self.config.doDeblend:
298 self.makeSubtask(
"deblend", schema=self.
schema)
299 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
300 if self.config.doApCorr:
301 self.makeSubtask(
'measureApCorr', schema=self.
schema)
302 self.makeSubtask(
'applyApCorr', schema=self.
schema)
303 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
304 self.
_initialFrame = getDebugFrame(self._display,
"frame")
or 1
306 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
309 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
310 """!Characterize a science image and, if wanted, persist the results 312 This simply unpacks the exposure and passes it to the characterize method to do the work. 314 @param[in] dataRef: butler data reference for science exposure 315 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 316 If None then unpersist from "postISRCCD". 317 The following changes are made, depending on the config: 318 - set psf to the measured PSF 319 - set apCorrMap to the measured aperture correction 320 - subtract background 321 - interpolate over cosmic rays 322 - update detection and cosmic ray mask planes 323 @param[in,out] background initial model of background already subtracted from exposure 324 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 325 which is typical for image characterization. 326 A refined background model is output. 327 @param[in] doUnpersist if True the exposure is read from the repository 328 and the exposure and background arguments must be None; 329 if False the exposure must be provided. 330 True is intended for running as a command-line task, False for running as a subtask 332 @return same data as the characterize method 335 self.log.info(
"Processing %s" % (dataRef.dataId))
338 if exposure
is not None or background
is not None:
339 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
340 exposure = dataRef.get(
"postISRCCD", immediate=
True)
341 elif exposure
is None:
342 raise RuntimeError(
"doUnpersist false; exposure must be provided")
344 exposureIdInfo = dataRef.get(
"expIdInfo")
348 exposureIdInfo=exposureIdInfo,
349 background=background,
352 if self.config.doWrite:
353 dataRef.put(charRes.sourceCat,
"icSrc")
354 if self.config.doWriteExposure:
355 dataRef.put(charRes.exposure,
"icExp")
356 dataRef.put(charRes.background,
"icExpBackground")
361 def run(self, exposure, exposureIdInfo=None, background=None):
362 """!Characterize a science image 364 Peforms the following operations: 365 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: 366 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) 367 - interpolate over cosmic rays 368 - perform final measurement 370 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 371 The following changes are made: 374 - update detection and cosmic ray mask planes 375 - subtract background and interpolate over cosmic rays 376 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo). 377 If not provided, returned SourceCatalog IDs will not be globally unique. 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. 382 @return pipe_base Struct containing these fields, all from the final iteration 383 of detectMeasureAndEstimatePsf: 384 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, 385 mask is updated accordingly, and the PSF model is set 386 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) 387 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 388 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 392 if not self.config.doMeasurePsf
and not exposure.hasPsf():
393 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
394 self.installSimplePsf.
run(exposure=exposure)
396 if exposureIdInfo
is None:
397 exposureIdInfo = ExposureIdInfo()
400 background = self.background.
run(exposure).background
402 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
403 for i
in range(psfIterations):
406 exposureIdInfo=exposureIdInfo,
407 background=background,
410 psf = dmeRes.exposure.getPsf()
411 psfSigma = psf.computeShape().getDeterminantRadius()
412 psfDimensions = psf.computeImage().getDimensions()
413 medBackground = np.median(dmeRes.background.getImage().getArray())
414 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
415 (i + 1, psfSigma, psfDimensions, medBackground))
417 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
420 self.repair.
run(exposure=dmeRes.exposure)
421 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
425 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
426 exposureId=exposureIdInfo.expId)
427 if self.config.doApCorr:
428 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
429 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
430 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
431 self.catalogCalculation.
run(dmeRes.sourceCat)
433 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
439 """!Perform one iteration of detect, measure and estimate PSF 441 Performs the following operations: 442 - if config.doMeasurePsf or not exposure.hasPsf(): 443 - install a simple PSF model (replacing the existing one, if need be) 444 - interpolate over cosmic rays with keepCRs=True 445 - estimate background and subtract it from the exposure 446 - detect, deblend and measure sources, and subtract a refined background model; 447 - if config.doMeasurePsf: 450 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) 451 The following changes are made: 453 - update detection and cosmic ray mask planes 454 - subtract background 455 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) 456 @param[in,out] background initial model of background already subtracted from exposure 457 (an lsst.afw.math.BackgroundList). 459 @return pipe_base Struct containing these fields, all from the final iteration 460 of detect sources, measure sources and estimate PSF: 461 - exposure characterized exposure; image is repaired by interpolating over cosmic rays, 462 mask is updated accordingly, and the PSF model is set 463 - sourceCat detected sources (an lsst.afw.table.SourceCatalog) 464 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 465 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 468 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
469 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
470 self.installSimplePsf.
run(exposure=exposure)
473 self.repair.
run(exposure=exposure, keepCRs=
True)
474 self.
display(
"repair_iter", exposure=exposure)
476 if background
is None:
477 background = BackgroundList()
479 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
480 table = SourceTable.make(self.
schema, sourceIdFactory)
483 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
484 sourceCat = detRes.sources
485 if detRes.fpSets.background:
486 for bg
in detRes.fpSets.background:
487 background.append(bg)
489 if self.config.doDeblend:
490 self.deblend.
run(exposure=exposure, sources=sourceCat)
492 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
494 measPsfRes = pipeBase.Struct(cellSet=
None)
495 if self.config.doMeasurePsf:
496 if self.measurePsf.usesMatches:
497 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
500 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
501 expId=exposureIdInfo.expId)
502 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
504 return pipeBase.Struct(
507 background=background,
508 psfCellSet=measPsfRes.cellSet,
512 """Return a dict of empty catalogs for each catalog dataset produced by this task. 514 sourceCat = SourceCatalog(self.
schema)
516 return {
"icSrc": sourceCat}
518 def display(self, itemName, exposure, sourceCat=None):
519 """Display exposure and sources on next frame, if display of itemName has been requested 521 @param[in] itemName name of item in debugInfo 522 @param[in] exposure exposure to display 523 @param[in] sourceCat source catalog to display 525 val = getDebugFrame(self._display, itemName)
529 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self.
_frame, pause=
False)
def display(self, itemName, exposure, sourceCat=None)
def run(self, exposure, exposureIdInfo=None, background=None)
Characterize a science image.
Measure bright sources and use this to estimate background and PSF of an exposure.
def getSchemaCatalogs(self)
Config for CharacterizeImageTask.
def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True)
Characterize a science image and, if wanted, persist the results.
def __init__(self, butler=None, refObjLoader=None, schema=None, kwargs)
Construct a CharacterizeImageTask.
def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background)
Perform one iteration of detect, measure and estimate PSF.