LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
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 deprecated="This field does nothing. Will be removed after v24 (see DM-34768).",
189 doc="reference object loader",
190 )
191 ref_match = pexConfig.ConfigurableField(
192 target=RefMatchTask,
193 deprecated="This field was never usable. Will be removed after v24 (see DM-34768).",
194 doc="Task to load and match reference objects. Only used if measurePsf can use matches. "
195 "Warning: matching will only work well if the initial WCS is accurate enough "
196 "to give good matches (roughly: good to 3 arcsec across the CCD).",
197 )
198 measurePsf = pexConfig.ConfigurableField(
199 target=MeasurePsfTask,
200 doc="Measure PSF",
201 )
202 repair = pexConfig.ConfigurableField(
203 target=RepairTask,
204 doc="Remove cosmic rays",
205 )
206 requireCrForPsf = pexConfig.Field(
207 dtype=bool,
208 default=True,
209 doc="Require cosmic ray detection and masking to run successfully before measuring the PSF."
210 )
211 checkUnitsParseStrict = pexConfig.Field(
212 doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
213 dtype=str,
214 default="raise",
215 )
216
217 def setDefaults(self):
218 super().setDefaults()
219 # just detect bright stars; includeThresholdMultipler=10 seems large,
220 # but these are the values we have been using
221 self.detectiondetection.thresholdValue = 5.0
222 self.detectiondetection.includeThresholdMultiplier = 10.0
223 self.detectiondetection.doTempLocalBackground = False
224 # do not deblend, as it makes a mess
225 self.doDeblenddoDeblend = False
226 # measure and apply aperture correction; note: measuring and applying aperture
227 # correction are disabled until the final measurement, after PSF is measured
228 self.doApCorrdoApCorr = True
229 # minimal set of measurements needed to determine PSF
230 self.measurementmeasurement.plugins.names = [
231 "base_PixelFlags",
232 "base_SdssCentroid",
233 "base_SdssShape",
234 "base_GaussianFlux",
235 "base_PsfFlux",
236 "base_CircularApertureFlux",
237 ]
238
239 def validate(self):
240 if self.doApCorrdoApCorr and not self.measurePsfmeasurePsf:
241 raise RuntimeError("Must measure PSF to measure aperture correction, "
242 "because flags determined by PSF measurement are used to identify "
243 "sources used to measure aperture correction")
244
245
251
252
253class CharacterizeImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
254 r"""!
255 Measure bright sources and use this to estimate background and PSF of an exposure
256
257 @anchor CharacterizeImageTask_
258
259 @section pipe_tasks_characterizeImage_Contents Contents
260
261 - @ref pipe_tasks_characterizeImage_Purpose
262 - @ref pipe_tasks_characterizeImage_Initialize
263 - @ref pipe_tasks_characterizeImage_IO
264 - @ref pipe_tasks_characterizeImage_Config
265 - @ref pipe_tasks_characterizeImage_Debug
266
267 @section pipe_tasks_characterizeImage_Purpose Description
268
269 Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
270 - detect and measure bright sources
271 - repair cosmic rays
272 - measure and subtract background
273 - measure PSF
274
275 @section pipe_tasks_characterizeImage_Initialize Task initialisation
276
277 @copydoc \_\_init\_\_
278
279 @section pipe_tasks_characterizeImage_IO Invoking the Task
280
281 If you want this task to unpersist inputs or persist outputs, then call
282 the `runDataRef` method (a thin wrapper around the `run` method).
283
284 If you already have the inputs unpersisted and do not want to persist the output
285 then it is more direct to call the `run` method:
286
287 @section pipe_tasks_characterizeImage_Config Configuration parameters
288
289 See @ref CharacterizeImageConfig
290
291 @section pipe_tasks_characterizeImage_Debug Debug variables
292
293 The command line task interface supports a flag
294 `--debug` to import `debug.py` from your `$PYTHONPATH`; see
295 <a href="https://pipelines.lsst.io/modules/lsstDebug/">the lsstDebug documentation</a>
296 for more about `debug.py`.
297
298 CharacterizeImageTask has a debug dictionary with the following keys:
299 <dl>
300 <dt>frame
301 <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
302 <dt>repair_iter
303 <dd>bool; if True display image after each repair in the measure PSF loop
304 <dt>background_iter
305 <dd>bool; if True display image after each background subtraction in the measure PSF loop
306 <dt>measure_iter
307 <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
308 See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
309 <dt>psf
310 <dd>bool; if True display image and sources after PSF is measured;
311 this will be identical to the final image displayed by measure_iter if measure_iter is true
312 <dt>repair
313 <dd>bool; if True display image and sources after final repair
314 <dt>measure
315 <dd>bool; if True display image and sources after final measurement
316 </dl>
317
318 For example, put something like:
319 @code{.py}
320 import lsstDebug
321 def DebugInfo(name):
322 di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
323 if name == "lsst.pipe.tasks.characterizeImage":
324 di.display = dict(
325 repair = True,
326 )
327
328 return di
329
330 lsstDebug.Info = DebugInfo
331 @endcode
332 into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
333
334 Some subtasks may have their own debug variables; see individual Task documentation.
335 """
336
337 # Example description used to live here, removed 2-20-2017 by MSSG
338
339 ConfigClass = CharacterizeImageConfig
340 _DefaultName = "characterizeImage"
341 RunnerClass = pipeBase.ButlerInitializedTaskRunner
342
343 def runQuantum(self, butlerQC, inputRefs, outputRefs):
344 inputs = butlerQC.get(inputRefs)
345 if 'exposureIdInfo' not in inputs.keys():
346 inputs['exposureIdInfo'] = ExposureIdInfo.fromDataId(butlerQC.quantum.dataId, "visit_detector")
347 outputs = self.runrun(**inputs)
348 butlerQC.put(outputs, outputRefs)
349
350 def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
351 """!Construct a CharacterizeImageTask
352
353 @param[in] butler A butler object is passed to the refObjLoader constructor in case
354 it is needed to load catalogs. May be None if a catalog-based star selector is
355 not used, if the reference object loader constructor does not require a butler,
356 or if a reference object loader is passed directly via the refObjLoader argument.
357 # TODO DM-34769: remove rebObjLoader kwarg here.
358 @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
359 external reference catalog to a catalog-based star selector. May be None if a
360 catalog star selector is not used or the loader can be constructed from the
361 butler argument.
362 @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
363 @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
364 """
365 super().__init__(**kwargs)
366
367 if schema is None:
368 schema = SourceTable.makeMinimalSchema()
369 self.schemaschema = schema
370 self.makeSubtask("background")
371 self.makeSubtask("installSimplePsf")
372 self.makeSubtask("repair")
373 self.makeSubtask("measurePsf", schema=self.schemaschema)
374 # TODO DM-34769: remove this `if` block
375 if self.config.doMeasurePsf and self.measurePsf.usesMatches:
376 if not refObjLoader:
377 self.makeSubtask('refObjLoader', butler=butler)
378 refObjLoader = self.refObjLoader
379 self.makeSubtask("ref_match", refObjLoader=refObjLoader)
381 self.makeSubtask('detection', schema=self.schemaschema)
382 if self.config.doDeblend:
383 self.makeSubtask("deblend", schema=self.schemaschema)
384 self.makeSubtask('measurement', schema=self.schemaschema, algMetadata=self.algMetadataalgMetadata)
385 if self.config.doApCorr:
386 self.makeSubtask('measureApCorr', schema=self.schemaschema)
387 self.makeSubtask('applyApCorr', schema=self.schemaschema)
388 self.makeSubtask('catalogCalculation', schema=self.schemaschema)
389 self._initialFrame_initialFrame = getDebugFrame(self._display, "frame") or 1
390 self._frame_frame = self._initialFrame_initialFrame
391 self.schemaschema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
392 self.outputSchemaoutputSchema = afwTable.SourceCatalog(self.schemaschema)
393
395 outputCatSchema = afwTable.SourceCatalog(self.schemaschema)
396 outputCatSchema.getTable().setMetadata(self.algMetadataalgMetadata)
397 return {'outputSchema': outputCatSchema}
398
399 @timeMethod
400 def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
401 """!Characterize a science image and, if wanted, persist the results
402
403 This simply unpacks the exposure and passes it to the characterize method to do the work.
404
405 @param[in] dataRef: butler data reference for science exposure
406 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
407 If None then unpersist from "postISRCCD".
408 The following changes are made, depending on the config:
409 - set psf to the measured PSF
410 - set apCorrMap to the measured aperture correction
411 - subtract background
412 - interpolate over cosmic rays
413 - update detection and cosmic ray mask planes
414 @param[in,out] background initial model of background already subtracted from exposure
415 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
416 which is typical for image characterization.
417 A refined background model is output.
418 @param[in] doUnpersist if True the exposure is read from the repository
419 and the exposure and background arguments must be None;
420 if False the exposure must be provided.
421 True is intended for running as a command-line task, False for running as a subtask
422
423 @return same data as the characterize method
424 """
425 self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
426 self.log.info("Processing %s", dataRef.dataId)
427
428 if doUnpersist:
429 if exposure is not None or background is not None:
430 raise RuntimeError("doUnpersist true; exposure and background must be None")
431 exposure = dataRef.get("postISRCCD", immediate=True)
432 elif exposure is None:
433 raise RuntimeError("doUnpersist false; exposure must be provided")
434
435 exposureIdInfo = dataRef.get("expIdInfo")
436
437 charRes = self.runrun(
438 exposure=exposure,
439 exposureIdInfo=exposureIdInfo,
440 background=background,
441 )
442
443 if self.config.doWrite:
444 dataRef.put(charRes.sourceCat, "icSrc")
445 if self.config.doWriteExposure:
446 dataRef.put(charRes.exposure, "icExp")
447 dataRef.put(charRes.background, "icExpBackground")
448
449 return charRes
450
451 @timeMethod
452 def run(self, exposure, exposureIdInfo=None, background=None):
453 """!Characterize a science image
454
455 Peforms the following operations:
456 - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
457 - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
458 - interpolate over cosmic rays
459 - perform final measurement
460
461 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
462 The following changes are made:
463 - update or set psf
464 - set apCorrMap
465 - update detection and cosmic ray mask planes
466 - subtract background and interpolate over cosmic rays
467 @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo).
468 If not provided, returned SourceCatalog IDs will not be globally unique.
469 @param[in,out] background initial model of background already subtracted from exposure
470 (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
471 which is typical for image characterization.
472
473 @return pipe_base Struct containing these fields, all from the final iteration
474 of detectMeasureAndEstimatePsf:
475 - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
476 mask is updated accordingly, and the PSF model is set
477 - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
478 - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
479 - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
480 """
481 self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
482
483 if not self.config.doMeasurePsf and not exposure.hasPsf():
484 self.log.info("CharacterizeImageTask initialized with 'simple' PSF.")
485 self.installSimplePsf.run(exposure=exposure)
486
487 if exposureIdInfo is None:
488 exposureIdInfo = ExposureIdInfo()
489
490 # subtract an initial estimate of background level
491 background = self.background.run(exposure).background
492
493 psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
494 for i in range(psfIterations):
495 dmeRes = self.detectMeasureAndEstimatePsfdetectMeasureAndEstimatePsf(
496 exposure=exposure,
497 exposureIdInfo=exposureIdInfo,
498 background=background,
499 )
500
501 psf = dmeRes.exposure.getPsf()
502 # Just need a rough estimate; average positions are fine
503 psfAvgPos = psf.getAveragePosition()
504 psfSigma = psf.computeShape(psfAvgPos).getDeterminantRadius()
505 psfDimensions = psf.computeImage(psfAvgPos).getDimensions()
506 medBackground = np.median(dmeRes.background.getImage().getArray())
507 self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f",
508 i + 1, psfSigma, psfDimensions, medBackground)
509 if np.isnan(psfSigma):
510 raise RuntimeError("PSF sigma is NaN, cannot continue PSF determination.")
511
512 self.displaydisplay("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
513
514 # perform final repair with final PSF
515 self.repair.run(exposure=dmeRes.exposure)
516 self.displaydisplay("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
517
518 # perform final measurement with final PSF, including measuring and applying aperture correction,
519 # if wanted
520 self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
521 exposureId=exposureIdInfo.expId)
522 if self.config.doApCorr:
523 apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
524 dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
525 self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
526 self.catalogCalculation.run(dmeRes.sourceCat)
527
528 self.displaydisplay("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
529
530 return pipeBase.Struct(
531 exposure=dmeRes.exposure,
532 sourceCat=dmeRes.sourceCat,
533 background=dmeRes.background,
534 psfCellSet=dmeRes.psfCellSet,
535
536 characterized=dmeRes.exposure,
537 backgroundModel=dmeRes.background
538 )
539
540 @timeMethod
541 def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background):
542 """!Perform one iteration of detect, measure and estimate PSF
543
544 Performs the following operations:
545 - if config.doMeasurePsf or not exposure.hasPsf():
546 - install a simple PSF model (replacing the existing one, if need be)
547 - interpolate over cosmic rays with keepCRs=True
548 - estimate background and subtract it from the exposure
549 - detect, deblend and measure sources, and subtract a refined background model;
550 - if config.doMeasurePsf:
551 - measure PSF
552
553 @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
554 The following changes are made:
555 - update or set psf
556 - update detection and cosmic ray mask planes
557 - subtract background
558 @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
559 @param[in,out] background initial model of background already subtracted from exposure
561
562 @return pipe_base Struct containing these fields, all from the final iteration
563 of detect sources, measure sources and estimate PSF:
564 - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
565 mask is updated accordingly, and the PSF model is set
566 - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
567 - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
568 - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
569 """
570 # install a simple PSF model, if needed or wanted
571 if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf):
572 self.log.info("PSF estimation initialized with 'simple' PSF")
573 self.installSimplePsf.run(exposure=exposure)
574
575 # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model)
576 if self.config.requireCrForPsf:
577 self.repair.run(exposure=exposure, keepCRs=True)
578 else:
579 try:
580 self.repair.run(exposure=exposure, keepCRs=True)
581 except LengthError:
582 self.log.warning("Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
583 self.config.repair.cosmicray.nCrPixelMax)
584
585 self.displaydisplay("repair_iter", exposure=exposure)
586
587 if background is None:
588 background = BackgroundList()
589
590 sourceIdFactory = exposureIdInfo.makeSourceIdFactory()
591 table = SourceTable.make(self.schemaschema, sourceIdFactory)
592 table.setMetadata(self.algMetadataalgMetadata)
593
594 detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True)
595 sourceCat = detRes.sources
596 if detRes.fpSets.background:
597 for bg in detRes.fpSets.background:
598 background.append(bg)
599
600 if self.config.doDeblend:
601 self.deblend.run(exposure=exposure, sources=sourceCat)
602
603 self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
604
605 measPsfRes = pipeBase.Struct(cellSet=None)
606 if self.config.doMeasurePsf:
607 # TODO DM-34769: remove this `if` block, and the `matches` kwarg from measurePsf.run below.
608 if self.measurePsf.usesMatches:
609 matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
610 else:
611 matches = None
612 measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
613 expId=exposureIdInfo.expId)
614 self.displaydisplay("measure_iter", exposure=exposure, sourceCat=sourceCat)
615
616 return pipeBase.Struct(
617 exposure=exposure,
618 sourceCat=sourceCat,
619 background=background,
620 psfCellSet=measPsfRes.cellSet,
621 )
622
624 """Return a dict of empty catalogs for each catalog dataset produced by this task.
625 """
626 sourceCat = SourceCatalog(self.schemaschema)
627 sourceCat.getTable().setMetadata(self.algMetadataalgMetadata)
628 return {"icSrc": sourceCat}
629
630 def display(self, itemName, exposure, sourceCat=None):
631 """Display exposure and sources on next frame, if display of itemName has been requested
632
633 @param[in] itemName name of item in debugInfo
634 @param[in] exposure exposure to display
635 @param[in] sourceCat source catalog to display
636 """
637 val = getDebugFrame(self._display, itemName)
638 if not val:
639 return
640
641 displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self._frame_frame, pause=False)
642 self._frame_frame += 1
A collection of SpatialCells covering an entire image.
Definition: SpatialCell.h:383
Table class that contains measurements made on a single exposure.
Definition: Source.h:217
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
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)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def displayAstrometry(refCat=None, sourceCat=None, distortedCentroidKey=None, bbox=None, exposure=None, matches=None, frame=1, title="", pause=True)
Definition: display.py:34
def getDebugFrame(debugDisplay, name)
Definition: lsstDebug.py:95