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"]
45 exposure = pipeBase.InputDatasetField(
46 doc=
"Input exposure data",
49 storageClass=
"ExposureF",
50 dimensions=[
"Instrument",
"Visit",
"Detector"],
52 characterized = pipeBase.OutputDatasetField(
53 doc=
"Output characterized data.",
56 storageClass=
"ExposureF",
57 dimensions=[
"Instrument",
"Visit",
"Detector"],
59 sourceCat = pipeBase.OutputDatasetField(
60 doc=
"Output source catalog.",
63 storageClass=
"SourceCatalog",
64 dimensions=[
"Instrument",
"Visit",
"Detector"],
66 backgroundModel = pipeBase.OutputDatasetField(
67 doc=
"Output background model.",
68 name=
"icExpBackground",
70 storageClass=
"Background",
71 dimensions=[
"Instrument",
"Visit",
"Detector"],
74 """!Config for CharacterizeImageTask""" 75 doMeasurePsf = pexConfig.Field(
78 doc=
"Measure PSF? If False then for all subsequent operations use either existing PSF " 79 "model when present, or install simple PSF model when not (see installSimplePsf " 82 doWrite = pexConfig.Field(
85 doc=
"Persist results?",
87 doWriteExposure = pexConfig.Field(
90 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
92 psfIterations = pexConfig.RangeField(
96 doc=
"Number of iterations of detect sources, measure sources, " 97 "estimate PSF. If useSimplePsf is True then 2 should be plenty; " 98 "otherwise more may be wanted.",
100 background = pexConfig.ConfigurableField(
101 target=SubtractBackgroundTask,
102 doc=
"Configuration for initial background estimation",
104 detection = pexConfig.ConfigurableField(
105 target=SourceDetectionTask,
108 doDeblend = pexConfig.Field(
111 doc=
"Run deblender input exposure" 113 deblend = pexConfig.ConfigurableField(
114 target=SourceDeblendTask,
115 doc=
"Split blended source into their components" 117 measurement = pexConfig.ConfigurableField(
118 target=SingleFrameMeasurementTask,
119 doc=
"Measure sources" 121 doApCorr = pexConfig.Field(
124 doc=
"Run subtasks to measure and apply aperture corrections" 126 measureApCorr = pexConfig.ConfigurableField(
127 target=MeasureApCorrTask,
128 doc=
"Subtask to measure aperture corrections" 130 applyApCorr = pexConfig.ConfigurableField(
131 target=ApplyApCorrTask,
132 doc=
"Subtask to apply aperture corrections" 136 catalogCalculation = pexConfig.ConfigurableField(
137 target=CatalogCalculationTask,
138 doc=
"Subtask to run catalogCalculation plugins on catalog" 140 useSimplePsf = pexConfig.Field(
143 doc=
"Replace the existing PSF model with a simplified version that has the same sigma " 144 "at the start of each PSF determination iteration? Doing so makes PSF determination " 145 "converge more robustly and quickly.",
147 installSimplePsf = pexConfig.ConfigurableField(
148 target=InstallGaussianPsfTask,
149 doc=
"Install a simple PSF model",
151 refObjLoader = pexConfig.ConfigurableField(
152 target=LoadAstrometryNetObjectsTask,
153 doc=
"reference object loader",
155 ref_match = pexConfig.ConfigurableField(
157 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. " 158 "Warning: matching will only work well if the initial WCS is accurate enough " 159 "to give good matches (roughly: good to 3 arcsec across the CCD).",
161 measurePsf = pexConfig.ConfigurableField(
162 target=MeasurePsfTask,
165 repair = pexConfig.ConfigurableField(
167 doc=
"Remove cosmic rays",
169 checkUnitsParseStrict = pexConfig.Field(
170 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
180 self.
detection.includeThresholdMultiplier = 10.0
193 "base_CircularApertureFlux",
195 self.quantum.dimensions = (
"Instrument",
"Visit",
"Detector")
199 raise RuntimeError(
"Must measure PSF to measure aperture correction, " 200 "because flags determined by PSF measurement are used to identify " 201 "sources used to measure aperture correction")
212 r"""!Measure bright sources and use this to estimate background and PSF of an exposure 214 @anchor CharacterizeImageTask_ 216 @section pipe_tasks_characterizeImage_Contents Contents 218 - @ref pipe_tasks_characterizeImage_Purpose 219 - @ref pipe_tasks_characterizeImage_Initialize 220 - @ref pipe_tasks_characterizeImage_IO 221 - @ref pipe_tasks_characterizeImage_Config 222 - @ref pipe_tasks_characterizeImage_Debug 225 @section pipe_tasks_characterizeImage_Purpose Description 227 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask): 228 - detect and measure bright sources 230 - measure and subtract background 233 @section pipe_tasks_characterizeImage_Initialize Task initialisation 235 @copydoc \_\_init\_\_ 237 @section pipe_tasks_characterizeImage_IO Invoking the Task 239 If you want this task to unpersist inputs or persist outputs, then call 240 the `runDataRef` method (a thin wrapper around the `run` method). 242 If you already have the inputs unpersisted and do not want to persist the output 243 then it is more direct to call the `run` method: 245 @section pipe_tasks_characterizeImage_Config Configuration parameters 247 See @ref CharacterizeImageConfig 249 @section pipe_tasks_characterizeImage_Debug Debug variables 251 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 252 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 254 CharacterizeImageTask has a debug dictionary with the following keys: 257 <dd>int: if specified, the frame of first debug image displayed (defaults to 1) 259 <dd>bool; if True display image after each repair in the measure PSF loop 261 <dd>bool; if True display image after each background subtraction in the measure PSF loop 263 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop 264 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols. 266 <dd>bool; if True display image and sources after PSF is measured; 267 this will be identical to the final image displayed by measure_iter if measure_iter is true 269 <dd>bool; if True display image and sources after final repair 271 <dd>bool; if True display image and sources after final measurement 274 For example, put something like: 278 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 279 if name == "lsst.pipe.tasks.characterizeImage": 286 lsstDebug.Info = DebugInfo 288 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag. 290 Some subtasks may have their own debug variables; see individual Task documentation. 295 ConfigClass = CharacterizeImageConfig
296 _DefaultName =
"characterizeImage" 297 RunnerClass = pipeBase.ButlerInitializedTaskRunner
300 if 'exposureIdInfo' not in inputData.keys():
301 packer = butler.registry.makeDataIdPacker(
"VisitDetector",
302 inputDataIds[
'exposure'])
303 exposureIdInfo = ExposureIdInfo()
304 exposureIdInfo.expId = packer.pack(inputDataIds[
'exposure'])
305 exposureIdInfo.expBits = packer.maxBits
306 inputData[
'exposureIdInfo'] = exposureIdInfo
308 return super().
adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
310 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
311 """!Construct a CharacterizeImageTask 313 @param[in] butler A butler object is passed to the refObjLoader constructor in case 314 it is needed to load catalogs. May be None if a catalog-based star selector is 315 not used, if the reference object loader constructor does not require a butler, 316 or if a reference object loader is passed directly via the refObjLoader argument. 317 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an 318 external reference catalog to a catalog-based star selector. May be None if a 319 catalog star selector is not used or the loader can be constructed from the 321 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None 322 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask 327 schema = SourceTable.makeMinimalSchema()
329 self.makeSubtask(
"background")
330 self.makeSubtask(
"installSimplePsf")
331 self.makeSubtask(
"repair")
332 self.makeSubtask(
"measurePsf", schema=self.
schema)
333 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
335 self.makeSubtask(
'refObjLoader', butler=butler)
336 refObjLoader = self.refObjLoader
337 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
339 self.makeSubtask(
'detection', schema=self.
schema)
340 if self.config.doDeblend:
341 self.makeSubtask(
"deblend", schema=self.
schema)
342 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
343 if self.config.doApCorr:
344 self.makeSubtask(
'measureApCorr', schema=self.
schema)
345 self.makeSubtask(
'applyApCorr', schema=self.
schema)
346 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
347 self.
_initialFrame = getDebugFrame(self._display,
"frame")
or 1
349 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
352 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
353 """!Characterize a science image and, if wanted, persist the results 355 This simply unpacks the exposure and passes it to the characterize method to do the work. 357 @param[in] dataRef: butler data reference for science exposure 358 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 359 If None then unpersist from "postISRCCD". 360 The following changes are made, depending on the config: 361 - set psf to the measured PSF 362 - set apCorrMap to the measured aperture correction 363 - subtract background 364 - interpolate over cosmic rays 365 - update detection and cosmic ray mask planes 366 @param[in,out] background initial model of background already subtracted from exposure 367 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 368 which is typical for image characterization. 369 A refined background model is output. 370 @param[in] doUnpersist if True the exposure is read from the repository 371 and the exposure and background arguments must be None; 372 if False the exposure must be provided. 373 True is intended for running as a command-line task, False for running as a subtask 375 @return same data as the characterize method 378 self.log.info(
"Processing %s" % (dataRef.dataId))
381 if exposure
is not None or background
is not None:
382 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
383 exposure = dataRef.get(
"postISRCCD", immediate=
True)
384 elif exposure
is None:
385 raise RuntimeError(
"doUnpersist false; exposure must be provided")
387 exposureIdInfo = dataRef.get(
"expIdInfo")
391 exposureIdInfo=exposureIdInfo,
392 background=background,
395 if self.config.doWrite:
396 dataRef.put(charRes.sourceCat,
"icSrc")
397 if self.config.doWriteExposure:
398 dataRef.put(charRes.exposure,
"icExp")
399 dataRef.put(charRes.background,
"icExpBackground")
404 def run(self, exposure, exposureIdInfo=None, background=None):
405 """!Characterize a science image 407 Peforms the following operations: 408 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: 409 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) 410 - interpolate over cosmic rays 411 - perform final measurement 413 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 414 The following changes are made: 417 - update detection and cosmic ray mask planes 418 - subtract background and interpolate over cosmic rays 419 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo). 420 If not provided, returned SourceCatalog IDs will not be globally unique. 421 @param[in,out] background initial model of background already subtracted from exposure 422 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 423 which is typical for image characterization. 425 @return pipe_base Struct containing these fields, all from the final iteration 426 of detectMeasureAndEstimatePsf: 427 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, 428 mask is updated accordingly, and the PSF model is set 429 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) 430 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 431 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 435 if not self.config.doMeasurePsf
and not exposure.hasPsf():
436 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
437 self.installSimplePsf.
run(exposure=exposure)
439 if exposureIdInfo
is None:
440 exposureIdInfo = ExposureIdInfo()
443 background = self.background.
run(exposure).background
445 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
446 for i
in range(psfIterations):
449 exposureIdInfo=exposureIdInfo,
450 background=background,
453 psf = dmeRes.exposure.getPsf()
454 psfSigma = psf.computeShape().getDeterminantRadius()
455 psfDimensions = psf.computeImage().getDimensions()
456 medBackground = np.median(dmeRes.background.getImage().getArray())
457 self.log.info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
458 (i + 1, psfSigma, psfDimensions, medBackground))
460 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
463 self.repair.
run(exposure=dmeRes.exposure)
464 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
468 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
469 exposureId=exposureIdInfo.expId)
470 if self.config.doApCorr:
471 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
472 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
473 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
474 self.catalogCalculation.
run(dmeRes.sourceCat)
476 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
478 return pipeBase.Struct(
479 exposure=dmeRes.exposure,
480 sourceCat=dmeRes.sourceCat,
481 background=dmeRes.background,
482 psfCellSet=dmeRes.psfCellSet,
484 characterized=dmeRes.exposure,
485 backgroundModel=dmeRes.background
490 """!Perform one iteration of detect, measure and estimate PSF 492 Performs the following operations: 493 - if config.doMeasurePsf or not exposure.hasPsf(): 494 - install a simple PSF model (replacing the existing one, if need be) 495 - interpolate over cosmic rays with keepCRs=True 496 - estimate background and subtract it from the exposure 497 - detect, deblend and measure sources, and subtract a refined background model; 498 - if config.doMeasurePsf: 501 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) 502 The following changes are made: 504 - update detection and cosmic ray mask planes 505 - subtract background 506 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) 507 @param[in,out] background initial model of background already subtracted from exposure 508 (an lsst.afw.math.BackgroundList). 510 @return pipe_base Struct containing these fields, all from the final iteration 511 of detect sources, measure sources and estimate PSF: 512 - exposure characterized exposure; image is repaired by interpolating over cosmic rays, 513 mask is updated accordingly, and the PSF model is set 514 - sourceCat detected sources (an lsst.afw.table.SourceCatalog) 515 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 516 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 519 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
520 self.log.warn(
"Source catalog detected and measured with placeholder or default PSF")
521 self.installSimplePsf.
run(exposure=exposure)
524 self.repair.
run(exposure=exposure, keepCRs=
True)
525 self.
display(
"repair_iter", exposure=exposure)
527 if background
is None:
528 background = BackgroundList()
530 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
531 table = SourceTable.make(self.
schema, sourceIdFactory)
534 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
535 sourceCat = detRes.sources
536 if detRes.fpSets.background:
537 for bg
in detRes.fpSets.background:
538 background.append(bg)
540 if self.config.doDeblend:
541 self.deblend.
run(exposure=exposure, sources=sourceCat)
543 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
545 measPsfRes = pipeBase.Struct(cellSet=
None)
546 if self.config.doMeasurePsf:
547 if self.measurePsf.usesMatches:
548 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
551 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
552 expId=exposureIdInfo.expId)
553 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
555 return pipeBase.Struct(
558 background=background,
559 psfCellSet=measPsfRes.cellSet,
563 """Return a dict of empty catalogs for each catalog dataset produced by this task. 565 sourceCat = SourceCatalog(self.
schema)
567 return {
"icSrc": sourceCat}
569 def display(self, itemName, exposure, sourceCat=None):
570 """Display exposure and sources on next frame, if display of itemName has been requested 572 @param[in] itemName name of item in debugInfo 573 @param[in] exposure exposure to display 574 @param[in] sourceCat source catalog to display 576 val = getDebugFrame(self._display, itemName)
580 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 adaptArgsAndRun(self, inputData, inputDataIds, outputDataIds, butler)
def getSchemaCatalogs(self)
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.