24 from lsstDebug
import getDebugFrame
36 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
38 from .measurePsf
import MeasurePsfTask
39 from .repair
import RepairTask
41 __all__ = [
"CharacterizeImageConfig",
"CharacterizeImageTask"]
46 exposure = pipeBase.InputDatasetField(
47 doc=
"Input exposure data",
50 storageClass=
"ExposureF",
51 dimensions=[
"instrument",
"visit",
"detector"],
53 characterized = pipeBase.OutputDatasetField(
54 doc=
"Output characterized data.",
57 storageClass=
"ExposureF",
58 dimensions=[
"instrument",
"visit",
"detector"],
60 sourceCat = pipeBase.OutputDatasetField(
61 doc=
"Output source catalog.",
64 storageClass=
"SourceCatalog",
65 dimensions=[
"instrument",
"visit",
"detector"],
67 backgroundModel = pipeBase.OutputDatasetField(
68 doc=
"Output background model.",
69 name=
"icExpBackground",
71 storageClass=
"Background",
72 dimensions=[
"instrument",
"visit",
"detector"],
74 outputSchema = pipeBase.InitOutputDatasetField(
75 doc=
"Schema of the catalog produced by CharacterizeImage",
77 storageClass=
"SourceCatalog",
80 """!Config for CharacterizeImageTask""" 81 doMeasurePsf = pexConfig.Field(
84 doc=
"Measure PSF? If False then for all subsequent operations use either existing PSF " 85 "model when present, or install simple PSF model when not (see installSimplePsf " 88 doWrite = pexConfig.Field(
91 doc=
"Persist results?",
93 doWriteExposure = pexConfig.Field(
96 doc=
"Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
98 psfIterations = pexConfig.RangeField(
102 doc=
"Number of iterations of detect sources, measure sources, " 103 "estimate PSF. If useSimplePsf is True then 2 should be plenty; " 104 "otherwise more may be wanted.",
106 background = pexConfig.ConfigurableField(
107 target=SubtractBackgroundTask,
108 doc=
"Configuration for initial background estimation",
110 detection = pexConfig.ConfigurableField(
111 target=SourceDetectionTask,
114 doDeblend = pexConfig.Field(
117 doc=
"Run deblender input exposure" 119 deblend = pexConfig.ConfigurableField(
120 target=SourceDeblendTask,
121 doc=
"Split blended source into their components" 123 measurement = pexConfig.ConfigurableField(
124 target=SingleFrameMeasurementTask,
125 doc=
"Measure sources" 127 doApCorr = pexConfig.Field(
130 doc=
"Run subtasks to measure and apply aperture corrections" 132 measureApCorr = pexConfig.ConfigurableField(
133 target=MeasureApCorrTask,
134 doc=
"Subtask to measure aperture corrections" 136 applyApCorr = pexConfig.ConfigurableField(
137 target=ApplyApCorrTask,
138 doc=
"Subtask to apply aperture corrections" 142 catalogCalculation = pexConfig.ConfigurableField(
143 target=CatalogCalculationTask,
144 doc=
"Subtask to run catalogCalculation plugins on catalog" 146 useSimplePsf = pexConfig.Field(
149 doc=
"Replace the existing PSF model with a simplified version that has the same sigma " 150 "at the start of each PSF determination iteration? Doing so makes PSF determination " 151 "converge more robustly and quickly.",
153 installSimplePsf = pexConfig.ConfigurableField(
154 target=InstallGaussianPsfTask,
155 doc=
"Install a simple PSF model",
157 refObjLoader = pexConfig.ConfigurableField(
158 target=LoadIndexedReferenceObjectsTask,
159 doc=
"reference object loader",
161 ref_match = pexConfig.ConfigurableField(
163 doc=
"Task to load and match reference objects. Only used if measurePsf can use matches. " 164 "Warning: matching will only work well if the initial WCS is accurate enough " 165 "to give good matches (roughly: good to 3 arcsec across the CCD).",
167 measurePsf = pexConfig.ConfigurableField(
168 target=MeasurePsfTask,
171 repair = pexConfig.ConfigurableField(
173 doc=
"Remove cosmic rays",
175 checkUnitsParseStrict = pexConfig.Field(
176 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
186 self.
detection.includeThresholdMultiplier = 10.0
199 "base_CircularApertureFlux",
201 self.quantum.dimensions = (
"instrument",
"visit",
"detector")
205 raise RuntimeError(
"Must measure PSF to measure aperture correction, " 206 "because flags determined by PSF measurement are used to identify " 207 "sources used to measure aperture correction")
218 r"""!Measure bright sources and use this to estimate background and PSF of an exposure 220 @anchor CharacterizeImageTask_ 222 @section pipe_tasks_characterizeImage_Contents Contents 224 - @ref pipe_tasks_characterizeImage_Purpose 225 - @ref pipe_tasks_characterizeImage_Initialize 226 - @ref pipe_tasks_characterizeImage_IO 227 - @ref pipe_tasks_characterizeImage_Config 228 - @ref pipe_tasks_characterizeImage_Debug 231 @section pipe_tasks_characterizeImage_Purpose Description 233 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask): 234 - detect and measure bright sources 236 - measure and subtract background 239 @section pipe_tasks_characterizeImage_Initialize Task initialisation 241 @copydoc \_\_init\_\_ 243 @section pipe_tasks_characterizeImage_IO Invoking the Task 245 If you want this task to unpersist inputs or persist outputs, then call 246 the `runDataRef` method (a thin wrapper around the `run` method). 248 If you already have the inputs unpersisted and do not want to persist the output 249 then it is more direct to call the `run` method: 251 @section pipe_tasks_characterizeImage_Config Configuration parameters 253 See @ref CharacterizeImageConfig 255 @section pipe_tasks_characterizeImage_Debug Debug variables 257 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag 258 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`. 260 CharacterizeImageTask has a debug dictionary with the following keys: 263 <dd>int: if specified, the frame of first debug image displayed (defaults to 1) 265 <dd>bool; if True display image after each repair in the measure PSF loop 267 <dd>bool; if True display image after each background subtraction in the measure PSF loop 269 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop 270 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols. 272 <dd>bool; if True display image and sources after PSF is measured; 273 this will be identical to the final image displayed by measure_iter if measure_iter is true 275 <dd>bool; if True display image and sources after final repair 277 <dd>bool; if True display image and sources after final measurement 280 For example, put something like: 284 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively 285 if name == "lsst.pipe.tasks.characterizeImage": 292 lsstDebug.Info = DebugInfo 294 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag. 296 Some subtasks may have their own debug variables; see individual Task documentation. 301 ConfigClass = CharacterizeImageConfig
302 _DefaultName =
"characterizeImage" 303 RunnerClass = pipeBase.ButlerInitializedTaskRunner
306 if 'exposureIdInfo' not in inputData.keys():
307 packer = butler.registry.makeDataIdPacker(
"visit_detector",
308 inputDataIds[
'exposure'])
310 exposureIdInfo.expId = packer.pack(inputDataIds[
'exposure'])
311 exposureIdInfo.expBits = packer.maxBits
312 inputData[
'exposureIdInfo'] = exposureIdInfo
314 return super().
adaptArgsAndRun(inputData, inputDataIds, outputDataIds, butler)
316 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
317 """!Construct a CharacterizeImageTask 319 @param[in] butler A butler object is passed to the refObjLoader constructor in case 320 it is needed to load catalogs. May be None if a catalog-based star selector is 321 not used, if the reference object loader constructor does not require a butler, 322 or if a reference object loader is passed directly via the refObjLoader argument. 323 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an 324 external reference catalog to a catalog-based star selector. May be None if a 325 catalog star selector is not used or the loader can be constructed from the 327 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None 328 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask 333 schema = SourceTable.makeMinimalSchema()
335 self.makeSubtask(
"background")
336 self.makeSubtask(
"installSimplePsf")
337 self.makeSubtask(
"repair")
338 self.makeSubtask(
"measurePsf", schema=self.
schema)
339 if self.config.doMeasurePsf
and self.measurePsf.usesMatches:
341 self.makeSubtask(
'refObjLoader', butler=butler)
342 refObjLoader = self.refObjLoader
343 self.makeSubtask(
"ref_match", refObjLoader=refObjLoader)
345 self.makeSubtask(
'detection', schema=self.
schema)
346 if self.config.doDeblend:
347 self.makeSubtask(
"deblend", schema=self.
schema)
348 self.makeSubtask(
'measurement', schema=self.
schema, algMetadata=self.
algMetadata)
349 if self.config.doApCorr:
350 self.makeSubtask(
'measureApCorr', schema=self.
schema)
351 self.makeSubtask(
'applyApCorr', schema=self.
schema)
352 self.makeSubtask(
'catalogCalculation', schema=self.
schema)
355 self.
schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
359 outputCatSchema.getTable().setMetadata(self.
algMetadata)
360 return {
'outputSchema': outputCatSchema}
363 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
364 """!Characterize a science image and, if wanted, persist the results 366 This simply unpacks the exposure and passes it to the characterize method to do the work. 368 @param[in] dataRef: butler data reference for science exposure 369 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 370 If None then unpersist from "postISRCCD". 371 The following changes are made, depending on the config: 372 - set psf to the measured PSF 373 - set apCorrMap to the measured aperture correction 374 - subtract background 375 - interpolate over cosmic rays 376 - update detection and cosmic ray mask planes 377 @param[in,out] background initial model of background already subtracted from exposure 378 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 379 which is typical for image characterization. 380 A refined background model is output. 381 @param[in] doUnpersist if True the exposure is read from the repository 382 and the exposure and background arguments must be None; 383 if False the exposure must be provided. 384 True is intended for running as a command-line task, False for running as a subtask 386 @return same data as the characterize method 389 self.log.
info(
"Processing %s" % (dataRef.dataId))
392 if exposure
is not None or background
is not None:
393 raise RuntimeError(
"doUnpersist true; exposure and background must be None")
394 exposure = dataRef.get(
"postISRCCD", immediate=
True)
395 elif exposure
is None:
396 raise RuntimeError(
"doUnpersist false; exposure must be provided")
398 exposureIdInfo = dataRef.get(
"expIdInfo")
402 exposureIdInfo=exposureIdInfo,
403 background=background,
406 if self.config.doWrite:
407 dataRef.put(charRes.sourceCat,
"icSrc")
408 if self.config.doWriteExposure:
409 dataRef.put(charRes.exposure,
"icExp")
410 dataRef.put(charRes.background,
"icExpBackground")
415 def run(self, exposure, exposureIdInfo=None, background=None):
416 """!Characterize a science image 418 Peforms the following operations: 419 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false: 420 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details) 421 - interpolate over cosmic rays 422 - perform final measurement 424 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar). 425 The following changes are made: 428 - update detection and cosmic ray mask planes 429 - subtract background and interpolate over cosmic rays 430 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo). 431 If not provided, returned SourceCatalog IDs will not be globally unique. 432 @param[in,out] background initial model of background already subtracted from exposure 433 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted, 434 which is typical for image characterization. 436 @return pipe_base Struct containing these fields, all from the final iteration 437 of detectMeasureAndEstimatePsf: 438 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays, 439 mask is updated accordingly, and the PSF model is set 440 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog) 441 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 442 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 446 if not self.config.doMeasurePsf
and not exposure.hasPsf():
447 self.log.
warn(
"Source catalog detected and measured with placeholder or default PSF")
448 self.installSimplePsf.
run(exposure=exposure)
450 if exposureIdInfo
is None:
454 background = self.background.
run(exposure).background
456 psfIterations = self.config.psfIterations
if self.config.doMeasurePsf
else 1
457 for i
in range(psfIterations):
460 exposureIdInfo=exposureIdInfo,
461 background=background,
464 psf = dmeRes.exposure.getPsf()
465 psfSigma = psf.computeShape().getDeterminantRadius()
466 psfDimensions = psf.computeImage().getDimensions()
467 medBackground = np.median(dmeRes.background.getImage().getArray())
468 self.log.
info(
"iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
469 (i + 1, psfSigma, psfDimensions, medBackground))
471 self.
display(
"psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
474 self.repair.
run(exposure=dmeRes.exposure)
475 self.
display(
"repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
479 self.measurement.
run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
480 exposureId=exposureIdInfo.expId)
481 if self.config.doApCorr:
482 apCorrMap = self.measureApCorr.
run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
483 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
484 self.applyApCorr.
run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
485 self.catalogCalculation.
run(dmeRes.sourceCat)
487 self.
display(
"measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
489 return pipeBase.Struct(
490 exposure=dmeRes.exposure,
491 sourceCat=dmeRes.sourceCat,
492 background=dmeRes.background,
493 psfCellSet=dmeRes.psfCellSet,
495 characterized=dmeRes.exposure,
496 backgroundModel=dmeRes.background
501 """!Perform one iteration of detect, measure and estimate PSF 503 Performs the following operations: 504 - if config.doMeasurePsf or not exposure.hasPsf(): 505 - install a simple PSF model (replacing the existing one, if need be) 506 - interpolate over cosmic rays with keepCRs=True 507 - estimate background and subtract it from the exposure 508 - detect, deblend and measure sources, and subtract a refined background model; 509 - if config.doMeasurePsf: 512 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar) 513 The following changes are made: 515 - update detection and cosmic ray mask planes 516 - subtract background 517 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo) 518 @param[in,out] background initial model of background already subtracted from exposure 519 (an lsst.afw.math.BackgroundList). 521 @return pipe_base Struct containing these fields, all from the final iteration 522 of detect sources, measure sources and estimate PSF: 523 - exposure characterized exposure; image is repaired by interpolating over cosmic rays, 524 mask is updated accordingly, and the PSF model is set 525 - sourceCat detected sources (an lsst.afw.table.SourceCatalog) 526 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList) 527 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet) 530 if not exposure.hasPsf()
or (self.config.doMeasurePsf
and self.config.useSimplePsf):
531 self.log.
warn(
"Source catalog detected and measured with placeholder or default PSF")
532 self.installSimplePsf.
run(exposure=exposure)
535 self.repair.
run(exposure=exposure, keepCRs=
True)
536 self.
display(
"repair_iter", exposure=exposure)
538 if background
is None:
541 sourceIdFactory = IdFactory.makeSource(exposureIdInfo.expId, exposureIdInfo.unusedBits)
542 table = SourceTable.make(self.
schema, sourceIdFactory)
545 detRes = self.detection.
run(table=table, exposure=exposure, doSmooth=
True)
546 sourceCat = detRes.sources
547 if detRes.fpSets.background:
548 for bg
in detRes.fpSets.background:
549 background.append(bg)
551 if self.config.doDeblend:
552 self.deblend.
run(exposure=exposure, sources=sourceCat)
554 self.measurement.
run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
556 measPsfRes = pipeBase.Struct(cellSet=
None)
557 if self.config.doMeasurePsf:
558 if self.measurePsf.usesMatches:
559 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
562 measPsfRes = self.measurePsf.
run(exposure=exposure, sources=sourceCat, matches=matches,
563 expId=exposureIdInfo.expId)
564 self.
display(
"measure_iter", exposure=exposure, sourceCat=sourceCat)
566 return pipeBase.Struct(
569 background=background,
570 psfCellSet=measPsfRes.cellSet,
574 """Return a dict of empty catalogs for each catalog dataset produced by this task. 578 return {
"icSrc": sourceCat}
580 def display(self, itemName, exposure, sourceCat=None):
581 """Display exposure and sources on next frame, if display of itemName has been requested 583 @param[in] itemName name of item in debugInfo 584 @param[in] exposure exposure to display 585 @param[in] sourceCat source catalog to display
def getInitOutputDatasets(self)
def display(self, itemName, exposure, sourceCat=None)
Class for storing ordered metadata with comments.
def run(self, exposure, exposureIdInfo=None, background=None)
Characterize a science image.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
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 displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
def getDebugFrame(debugDisplay, name)
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.