lsst.pipe.tasks g38dd528b55+8ad9029b72
characterizeImage.py
Go to the documentation of this file.
2# LSST Data Management System
3# Copyright 2008-2015 AURA/LSST.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22import numpy as np
23
24from lsstDebug import getDebugFrame
25import lsst.afw.table as afwTable
26import lsst.pex.config as pexConfig
27import lsst.pipe.base as pipeBase
28import lsst.daf.base as dafBase
29import lsst.pipe.base.connectionTypes as cT
30from lsst.afw.math import BackgroundList
31from lsst.afw.table import SourceTable, SourceCatalog
32from lsst.meas.algorithms import SubtractBackgroundTask, SourceDetectionTask, MeasureApCorrTask
33from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask
34from lsst.meas.astrom import RefMatchTask, displayAstrometry
35from lsst.meas.algorithms import LoadIndexedReferenceObjectsTask
36from lsst.obs.base import ExposureIdInfo
37from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
38from lsst.meas.deblender import SourceDeblendTask
39from .measurePsf import MeasurePsfTask
40from .repair import RepairTask
41from .computeExposureSummaryStats import ComputeExposureSummaryStatsTask
42from lsst.pex.exceptions import LengthError
43from lsst.utils.timer import timeMethod
44
45__all__ = ["CharacterizeImageConfig", "CharacterizeImageTask"]
46
47
48class CharacterizeImageConnections(pipeBase.PipelineTaskConnections,
49 dimensions=("instrument", "visit", "detector")):
50 exposure = cT.Input(
51 doc="Input exposure data",
52 name="postISRCCD",
53 storageClass="Exposure",
54 dimensions=["instrument", "exposure", "detector"],
55 )
56 characterized = cT.Output(
57 doc="Output characterized data.",
58 name="icExp",
59 storageClass="ExposureF",
60 dimensions=["instrument", "visit", "detector"],
61 )
62 sourceCat = cT.Output(
63 doc="Output source catalog.",
64 name="icSrc",
65 storageClass="SourceCatalog",
66 dimensions=["instrument", "visit", "detector"],
67 )
68 backgroundModel = cT.Output(
69 doc="Output background model.",
70 name="icExpBackground",
71 storageClass="Background",
72 dimensions=["instrument", "visit", "detector"],
73 )
74 outputSchema = cT.InitOutput(
75 doc="Schema of the catalog produced by CharacterizeImage",
76 name="icSrc_schema",
77 storageClass="SourceCatalog",
78 )
79
80 def adjustQuantum(self, inputs, outputs, label, dataId):
81 # Docstring inherited from PipelineTaskConnections
82 try:
83 return super().adjustQuantum(inputs, outputs, label, dataId)
84 except pipeBase.ScalarError as err:
85 raise pipeBase.ScalarError(
86 "CharacterizeImageTask can at present only be run on visits that are associated with "
87 "exactly one exposure. Either this is not a valid exposure for this pipeline, or the "
88 "snap-combination step you probably want hasn't been configured to run between ISR and "
89 "this task (as of this writing, that would be because it hasn't been implemented yet)."
90 ) from err
91
92
93class CharacterizeImageConfig(pipeBase.PipelineTaskConfig,
94 pipelineConnections=CharacterizeImageConnections):
95
96 """!Config for CharacterizeImageTask"""
97 doMeasurePsf = pexConfig.Field(
98 dtype=bool,
99 default=True,
100 doc="Measure PSF? If False then for all subsequent operations use either existing PSF "
101 "model when present, or install simple PSF model when not (see installSimplePsf "
102 "config options)"
103 )
104 doWrite = pexConfig.Field(
105 dtype=bool,
106 default=True,
107 doc="Persist results?",
108 )
109 doWriteExposure = pexConfig.Field(
110 dtype=bool,
111 default=True,
112 doc="Write icExp and icExpBackground in addition to icSrc? Ignored if doWrite False.",
113 )
114 psfIterations = pexConfig.RangeField(
115 dtype=int,
116 default=2,
117 min=1,
118 doc="Number of iterations of detect sources, measure sources, "
119 "estimate PSF. If useSimplePsf is True then 2 should be plenty; "
120 "otherwise more may be wanted.",
121 )
122 background = pexConfig.ConfigurableField(
123 target=SubtractBackgroundTask,
124 doc="Configuration for initial background estimation",
125 )
126 detection = pexConfig.ConfigurableField(
127 target=SourceDetectionTask,
128 doc="Detect sources"
129 )
130 doDeblend = pexConfig.Field(
131 dtype=bool,
132 default=True,
133 doc="Run deblender input exposure"
134 )
135 deblend = pexConfig.ConfigurableField(
136 target=SourceDeblendTask,
137 doc="Split blended source into their components"
138 )
139 measurement = pexConfig.ConfigurableField(
140 target=SingleFrameMeasurementTask,
141 doc="Measure sources"
142 )
143 doApCorr = pexConfig.Field(
144 dtype=bool,
145 default=True,
146 doc="Run subtasks to measure and apply aperture corrections"
147 )
148 measureApCorr = pexConfig.ConfigurableField(
149 target=MeasureApCorrTask,
150 doc="Subtask to measure aperture corrections"
151 )
152 applyApCorr = pexConfig.ConfigurableField(
153 target=ApplyApCorrTask,
154 doc="Subtask to apply aperture corrections"
155 )
156 # If doApCorr is False, and the exposure does not have apcorrections already applied, the
157 # active plugins in catalogCalculation almost certainly should not contain the characterization plugin
158 catalogCalculation = pexConfig.ConfigurableField(
159 target=CatalogCalculationTask,
160 doc="Subtask to run catalogCalculation plugins on catalog"
161 )
162 doComputeSummaryStats = pexConfig.Field(
163 dtype=bool,
164 default=True,
165 doc="Run subtask to measure exposure summary statistics",
166 deprecated=("This subtask has been moved to CalibrateTask "
167 "with DM-30701.")
168 )
169 computeSummaryStats = pexConfig.ConfigurableField(
170 target=ComputeExposureSummaryStatsTask,
171 doc="Subtask to run computeSummaryStats on exposure",
172 deprecated=("This subtask has been moved to CalibrateTask "
173 "with DM-30701.")
174 )
175 useSimplePsf = pexConfig.Field(
176 dtype=bool,
177 default=True,
178 doc="Replace the existing PSF model with a simplified version that has the same sigma "
179 "at the start of each PSF determination iteration? Doing so makes PSF determination "
180 "converge more robustly and quickly.",
181 )
182 installSimplePsf = pexConfig.ConfigurableField(
183 target=InstallGaussianPsfTask,
184 doc="Install a simple PSF model",
185 )
186 refObjLoader = pexConfig.ConfigurableField(
187 target=LoadIndexedReferenceObjectsTask,
188 doc="reference object loader",
189 )
190 ref_match = pexConfig.ConfigurableField(
191 target=RefMatchTask,
192 doc="Task to load and match reference objects. Only used if measurePsf can use matches. "
193 "Warning: matching will only work well if the initial WCS is accurate enough "
194 "to give good matches (roughly: good to 3 arcsec across the CCD).",
195 )
196 measurePsf = pexConfig.ConfigurableField(
197 target=MeasurePsfTask,
198 doc="Measure PSF",
199 )
200 repair = pexConfig.ConfigurableField(
201 target=RepairTask,
202 doc="Remove cosmic rays",
203 )
204 requireCrForPsf = pexConfig.Field(
205 dtype=bool,
206 default=True,
207 doc="Require cosmic ray detection and masking to run successfully before measuring the PSF."
208 )
209 checkUnitsParseStrict = pexConfig.Field(
210 doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
211 dtype=str,
212 default="raise",
213 )
214
215 def setDefaults(self):
216 super().setDefaults()
217 # just detect bright stars; includeThresholdMultipler=10 seems large,
218 # but these are the values we have been using
219 self.detectiondetection.thresholdValue = 5.0
220 self.detectiondetection.includeThresholdMultiplier = 10.0
221 self.detectiondetection.doTempLocalBackground = False
222 # do not deblend, as it makes a mess
223 self.doDeblenddoDeblend = False
224 # measure and apply aperture correction; note: measuring and applying aperture
225 # correction are disabled until the final measurement, after PSF is measured
226 self.doApCorrdoApCorr = True
227 # minimal set of measurements needed to determine PSF
228 self.measurementmeasurement.plugins.names = [
229 "base_PixelFlags",
230 "base_SdssCentroid",
231 "base_SdssShape",
232 "base_GaussianFlux",
233 "base_PsfFlux",
234 "base_CircularApertureFlux",
235 ]
236
237 def validate(self):
238 if self.doApCorrdoApCorr and not self.measurePsfmeasurePsf:
239 raise RuntimeError("Must measure PSF to measure aperture correction, "
240 "because flags determined by PSF measurement are used to identify "
241 "sources used to measure aperture correction")
242
243
249
250
251class CharacterizeImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
252 r"""!Measure bright sources and use this to estimate background and PSF of an exposure
253
254 @anchor CharacterizeImageTask_
255
256 @section pipe_tasks_characterizeImage_Contents Contents
257
258 - @ref pipe_tasks_characterizeImage_Purpose
259 - @ref pipe_tasks_characterizeImage_Initialize
260 - @ref pipe_tasks_characterizeImage_IO
261 - @ref pipe_tasks_characterizeImage_Config
262 - @ref pipe_tasks_characterizeImage_Debug
263
264
265 @section pipe_tasks_characterizeImage_Purpose Description
266
267 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
268 - detect and measure bright sources
269 - repair cosmic rays
270 - measure and subtract background
271 - measure PSF
272
273 @section pipe_tasks_characterizeImage_Initialize Task initialisation
274
275 @copydoc \_\_init\_\_
276
277 @section pipe_tasks_characterizeImage_IO Invoking the Task
278
279 If you want this task to unpersist inputs or persist outputs, then call
280 the `runDataRef` method (a thin wrapper around the `run` method).
281
282 If you already have the inputs unpersisted and do not want to persist the output
283 then it is more direct to call the `run` method:
284
285 @section pipe_tasks_characterizeImage_Config Configuration parameters
286
287 See @ref CharacterizeImageConfig
288
289 @section pipe_tasks_characterizeImage_Debug Debug variables
290
291 The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
292 `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
293
294 CharacterizeImageTask has a debug dictionary with the following keys:
295 <dl>
296 <dt>frame
297 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
298 <dt>repair_iter
299 <dd>bool; if True display image after each repair in the measure PSF loop
300 <dt>background_iter
301 <dd>bool; if True display image after each background subtraction in the measure PSF loop
302 <dt>measure_iter
303 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
304 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
305 <dt>psf
306 <dd>bool; if True display image and sources after PSF is measured;
307 this will be identical to the final image displayed by measure_iter if measure_iter is true
308 <dt>repair
309 <dd>bool; if True display image and sources after final repair
310 <dt>measure
311 <dd>bool; if True display image and sources after final measurement
312 </dl>
313
314 For example, put something like:
315 @code{.py}
316 import lsstDebug
317 def DebugInfo(name):
318 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
319 if name == "lsst.pipe.tasks.characterizeImage":
320 di.display = dict(
321 repair = True,
322 )
323
324 return di
325
326 lsstDebug.Info = DebugInfo
327 @endcode
328 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
329
330 Some subtasks may have their own debug variables; see individual Task documentation.
331 """
332
333 # Example description used to live here, removed 2-20-2017 by MSSG
334
335 ConfigClass = CharacterizeImageConfig
336 _DefaultName = "characterizeImage"
337 RunnerClass = pipeBase.ButlerInitializedTaskRunner
338
339 def runQuantum(self, butlerQC, inputRefs, outputRefs):
340 inputs = butlerQC.get(inputRefs)
341 if 'exposureIdInfo' not in inputs.keys():
342 inputs['exposureIdInfo'] = ExposureIdInfo.fromDataId(butlerQC.quantum.dataId, "visit_detector")
343 outputs = self.runrun(**inputs)
344 butlerQC.put(outputs, outputRefs)
345
346 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
347 """!Construct a CharacterizeImageTask
348
349 @param[in] butler A butler object is passed to the refObjLoader constructor in case
350 it is needed to load catalogs. May be None if a catalog-based star selector is
351 not used, if the reference object loader constructor does not require a butler,
352 or if a reference object loader is passed directly via the refObjLoader argument.
353 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
354 external reference catalog to a catalog-based star selector. May be None if a
355 catalog star selector is not used or the loader can be constructed from the
356 butler argument.
357 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
358 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
359 """
360 super().__init__(**kwargs)
361
362 if schema is None:
363 schema = SourceTable.makeMinimalSchema()
364 self.schemaschema = schema
365 self.makeSubtask("background")
366 self.makeSubtask("installSimplePsf")
367 self.makeSubtask("repair")
368 self.makeSubtask("measurePsf", schema=self.schemaschema)
369 if self.config.doMeasurePsf and self.measurePsf.usesMatches:
370 if not refObjLoader:
371 self.makeSubtask('refObjLoader', butler=butler)
372 refObjLoader = self.refObjLoader
373 self.makeSubtask("ref_match", refObjLoader=refObjLoader)
374 self.algMetadataalgMetadata = dafBase.PropertyList()
375 self.makeSubtask('detection', schema=self.schemaschema)
376 if self.config.doDeblend:
377 self.makeSubtask("deblend", schema=self.schemaschema)
378 self.makeSubtask('measurement', schema=self.schemaschema, algMetadata=self.algMetadataalgMetadata)
379 if self.config.doApCorr:
380 self.makeSubtask('measureApCorr', schema=self.schemaschema)
381 self.makeSubtask('applyApCorr', schema=self.schemaschema)
382 self.makeSubtask('catalogCalculation', schema=self.schemaschema)
383 self._initialFrame_initialFrame = getDebugFrame(self._display, "frame") or 1
384 self._frame_frame = self._initialFrame_initialFrame
385 self.schemaschema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
386 self.outputSchemaoutputSchema = afwTable.SourceCatalog(self.schemaschema)
387
389 outputCatSchema = afwTable.SourceCatalog(self.schemaschema)
390 outputCatSchema.getTable().setMetadata(self.algMetadataalgMetadata)
391 return {'outputSchema': outputCatSchema}
392
393 @timeMethod
394 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
395 """!Characterize a science image and, if wanted, persist the results
396
397 This simply unpacks the exposure and passes it to the characterize method to do the work.
398
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
416
417 @return same data as the characterize method
418 """
419 self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
420 self.log.info("Processing %s", dataRef.dataId)
421
422 if doUnpersist:
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")
428
429 exposureIdInfo = dataRef.get("expIdInfo")
430
431 charRes = self.runrun(
432 exposure=exposure,
433 exposureIdInfo=exposureIdInfo,
434 background=background,
435 )
436
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")
442
443 return charRes
444
445 @timeMethod
446 def run(self, exposure, exposureIdInfo=None, background=None):
447 """!Characterize a science image
448
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
454
455 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
456 The following changes are made:
457 - update or set psf
458 - set apCorrMap
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.
466
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)
474 """
475 self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
476
477 if not self.config.doMeasurePsf and not exposure.hasPsf():
478 self.log.info("CharacterizeImageTask initialized with 'simple' PSF.")
479 self.installSimplePsf.run(exposure=exposure)
480
481 if exposureIdInfo is None:
482 exposureIdInfo = ExposureIdInfo()
483
484 # subtract an initial estimate of background level
485 background = self.background.run(exposure).background
486
487 psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
488 for i in range(psfIterations):
489 dmeRes = self.detectMeasureAndEstimatePsfdetectMeasureAndEstimatePsf(
490 exposure=exposure,
491 exposureIdInfo=exposureIdInfo,
492 background=background,
493 )
494
495 psf = dmeRes.exposure.getPsf()
496 # Just need a rough estimate; average positions are fine
497 psfAvgPos = psf.getAveragePosition()
498 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
499 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
500 medBackground = np.median(dmeRes.background.getImage().getArray())
501 self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f",
502 i + 1, psfSigma, psfDimensions, medBackground)
503
504 self.displaydisplay("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
505
506 # perform final repair with final PSF
507 self.repair.run(exposure=dmeRes.exposure)
508 self.displaydisplay("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
509
510 # perform final measurement with final PSF, including measuring and applying aperture correction,
511 # if wanted
512 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
513 exposureId=exposureIdInfo.expId)
514 if self.config.doApCorr:
515 apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
516 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
517 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
518 self.catalogCalculation.run(dmeRes.sourceCat)
519
520 self.displaydisplay("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
521
522 return pipeBase.Struct(
523 exposure=dmeRes.exposure,
524 sourceCat=dmeRes.sourceCat,
525 background=dmeRes.background,
526 psfCellSet=dmeRes.psfCellSet,
527
528 characterized=dmeRes.exposure,
529 backgroundModel=dmeRes.background
530 )
531
532 @timeMethod
533 def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background):
534 """!Perform one iteration of detect, measure and estimate PSF
535
536 Performs the following operations:
537 - if config.doMeasurePsf or not exposure.hasPsf():
538 - install a simple PSF model (replacing the existing one, if need be)
539 - interpolate over cosmic rays with keepCRs=True
540 - estimate background and subtract it from the exposure
541 - detect, deblend and measure sources, and subtract a refined background model;
542 - if config.doMeasurePsf:
543 - measure PSF
544
545 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
546 The following changes are made:
547 - update or set psf
548 - update detection and cosmic ray mask planes
549 - subtract background
550 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
551 @param[in,out] background initial model of background already subtracted from exposure
552 (an lsst.afw.math.BackgroundList).
553
554 @return pipe_base Struct containing these fields, all from the final iteration
555 of detect sources, measure sources and estimate PSF:
556 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
557 mask is updated accordingly, and the PSF model is set
558 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
559 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
560 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
561 """
562 # install a simple PSF model, if needed or wanted
563 if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf):
564 self.log.info("PSF estimation initialized with 'simple' PSF")
565 self.installSimplePsf.run(exposure=exposure)
566
567 # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model)
568 if self.config.requireCrForPsf:
569 self.repair.run(exposure=exposure, keepCRs=True)
570 else:
571 try:
572 self.repair.run(exposure=exposure, keepCRs=True)
573 except LengthError:
574 self.log.warning("Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
575 self.config.repair.cosmicray.nCrPixelMax)
576
577 self.displaydisplay("repair_iter", exposure=exposure)
578
579 if background is None:
580 background = BackgroundList()
581
582 sourceIdFactory = exposureIdInfo.makeSourceIdFactory()
583 table = SourceTable.make(self.schemaschema, sourceIdFactory)
584 table.setMetadata(self.algMetadataalgMetadata)
585
586 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True)
587 sourceCat = detRes.sources
588 if detRes.fpSets.background:
589 for bg in detRes.fpSets.background:
590 background.append(bg)
591
592 if self.config.doDeblend:
593 self.deblend.run(exposure=exposure, sources=sourceCat)
594
595 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
596
597 measPsfRes = pipeBase.Struct(cellSet=None)
598 if self.config.doMeasurePsf:
599 if self.measurePsf.usesMatches:
600 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
601 else:
602 matches = None
603 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
604 expId=exposureIdInfo.expId)
605 self.displaydisplay("measure_iter", exposure=exposure, sourceCat=sourceCat)
606
607 return pipeBase.Struct(
608 exposure=exposure,
609 sourceCat=sourceCat,
610 background=background,
611 psfCellSet=measPsfRes.cellSet,
612 )
613
615 """Return a dict of empty catalogs for each catalog dataset produced by this task.
616 """
617 sourceCat = SourceCatalog(self.schemaschema)
618 sourceCat.getTable().setMetadata(self.algMetadataalgMetadata)
619 return {"icSrc": sourceCat}
620
621 def display(self, itemName, exposure, sourceCat=None):
622 """Display exposure and sources on next frame, if display of itemName has been requested
623
624 @param[in] itemName name of item in debugInfo
625 @param[in] exposure exposure to display
626 @param[in] sourceCat source catalog to display
627 """
628 val = getDebugFrame(self._display, itemName)
629 if not val:
630 return
631
632 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self._frame_frame, pause=False)
633 self._frame_frame += 1
def adjustQuantum(self, inputs, outputs, label, dataId)
Measure bright sources and use this to estimate background and PSF of an exposure.
def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs)
Construct a CharacterizeImageTask.
def runQuantum(self, butlerQC, inputRefs, outputRefs)
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)