LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
characterizeImage.py
Go to the documentation of this file.
1 #
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 #
22 import numpy as np
23 
24 from lsstDebug import getDebugFrame
25 import lsst.afw.table as afwTable
26 import lsst.pex.config as pexConfig
27 import lsst.pipe.base as pipeBase
28 import lsst.daf.base as dafBase
29 import lsst.pipe.base.connectionTypes as cT
30 from lsst.afw.math import BackgroundList
31 from lsst.afw.table import SourceTable, SourceCatalog
32 from lsst.meas.algorithms import SubtractBackgroundTask, SourceDetectionTask, MeasureApCorrTask
33 from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask
34 from lsst.meas.astrom import RefMatchTask, displayAstrometry
35 from lsst.meas.algorithms import LoadIndexedReferenceObjectsTask
36 from lsst.obs.base import ExposureIdInfo
37 from lsst.meas.base import SingleFrameMeasurementTask, ApplyApCorrTask, CatalogCalculationTask
38 from lsst.meas.deblender import SourceDeblendTask
39 from .measurePsf import MeasurePsfTask
40 from .repair import RepairTask
41 from .computeExposureSummaryStats import ComputeExposureSummaryStatsTask
42 from lsst.pex.exceptions import LengthError
43 from lsst.utils.timer import timeMethod
44 
45 __all__ = ["CharacterizeImageConfig", "CharacterizeImageTask"]
46 
47 
48 class 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 
93 class 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 
251 class 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  psfSigma = psf.computeShape().getDeterminantRadius()
497  psfDimensions = psf.computeImage().getDimensions()
498  medBackground = np.median(dmeRes.background.getImage().getArray())
499  self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f",
500  i + 1, psfSigma, psfDimensions, medBackground)
501 
502  self.displaydisplay("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
503 
504  # perform final repair with final PSF
505  self.repair.run(exposure=dmeRes.exposure)
506  self.displaydisplay("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
507 
508  # perform final measurement with final PSF, including measuring and applying aperture correction,
509  # if wanted
510  self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
511  exposureId=exposureIdInfo.expId)
512  if self.config.doApCorr:
513  apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
514  dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
515  self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
516  self.catalogCalculation.run(dmeRes.sourceCat)
517 
518  self.displaydisplay("measure", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
519 
520  return pipeBase.Struct(
521  exposure=dmeRes.exposure,
522  sourceCat=dmeRes.sourceCat,
523  background=dmeRes.background,
524  psfCellSet=dmeRes.psfCellSet,
525 
526  characterized=dmeRes.exposure,
527  backgroundModel=dmeRes.background
528  )
529 
530  @timeMethod
531  def detectMeasureAndEstimatePsf(self, exposure, exposureIdInfo, background):
532  """!Perform one iteration of detect, measure and estimate PSF
533 
534  Performs the following operations:
535  - if config.doMeasurePsf or not exposure.hasPsf():
536  - install a simple PSF model (replacing the existing one, if need be)
537  - interpolate over cosmic rays with keepCRs=True
538  - estimate background and subtract it from the exposure
539  - detect, deblend and measure sources, and subtract a refined background model;
540  - if config.doMeasurePsf:
541  - measure PSF
542 
543  @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar)
544  The following changes are made:
545  - update or set psf
546  - update detection and cosmic ray mask planes
547  - subtract background
548  @param[in] exposureIdInfo ID info for exposure (an lsst.obs_base.ExposureIdInfo)
549  @param[in,out] background initial model of background already subtracted from exposure
550  (an lsst.afw.math.BackgroundList).
551 
552  @return pipe_base Struct containing these fields, all from the final iteration
553  of detect sources, measure sources and estimate PSF:
554  - exposure characterized exposure; image is repaired by interpolating over cosmic rays,
555  mask is updated accordingly, and the PSF model is set
556  - sourceCat detected sources (an lsst.afw.table.SourceCatalog)
557  - background model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
558  - psfCellSet spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
559  """
560  # install a simple PSF model, if needed or wanted
561  if not exposure.hasPsf() or (self.config.doMeasurePsf and self.config.useSimplePsf):
562  self.log.info("PSF estimation initialized with 'simple' PSF")
563  self.installSimplePsf.run(exposure=exposure)
564 
565  # run repair, but do not interpolate over cosmic rays (do that elsewhere, with the final PSF model)
566  if self.config.requireCrForPsf:
567  self.repair.run(exposure=exposure, keepCRs=True)
568  else:
569  try:
570  self.repair.run(exposure=exposure, keepCRs=True)
571  except LengthError:
572  self.log.warning("Skipping cosmic ray detection: Too many CR pixels (max %0.f)",
573  self.config.repair.cosmicray.nCrPixelMax)
574 
575  self.displaydisplay("repair_iter", exposure=exposure)
576 
577  if background is None:
578  background = BackgroundList()
579 
580  sourceIdFactory = exposureIdInfo.makeSourceIdFactory()
581  table = SourceTable.make(self.schemaschema, sourceIdFactory)
582  table.setMetadata(self.algMetadataalgMetadata)
583 
584  detRes = self.detection.run(table=table, exposure=exposure, doSmooth=True)
585  sourceCat = detRes.sources
586  if detRes.fpSets.background:
587  for bg in detRes.fpSets.background:
588  background.append(bg)
589 
590  if self.config.doDeblend:
591  self.deblend.run(exposure=exposure, sources=sourceCat)
592 
593  self.measurement.run(measCat=sourceCat, exposure=exposure, exposureId=exposureIdInfo.expId)
594 
595  measPsfRes = pipeBase.Struct(cellSet=None)
596  if self.config.doMeasurePsf:
597  if self.measurePsf.usesMatches:
598  matches = self.ref_match.loadAndMatch(exposure=exposure, sourceCat=sourceCat).matches
599  else:
600  matches = None
601  measPsfRes = self.measurePsf.run(exposure=exposure, sources=sourceCat, matches=matches,
602  expId=exposureIdInfo.expId)
603  self.displaydisplay("measure_iter", exposure=exposure, sourceCat=sourceCat)
604 
605  return pipeBase.Struct(
606  exposure=exposure,
607  sourceCat=sourceCat,
608  background=background,
609  psfCellSet=measPsfRes.cellSet,
610  )
611 
612  def getSchemaCatalogs(self):
613  """Return a dict of empty catalogs for each catalog dataset produced by this task.
614  """
615  sourceCat = SourceCatalog(self.schemaschema)
616  sourceCat.getTable().setMetadata(self.algMetadataalgMetadata)
617  return {"icSrc": sourceCat}
618 
619  def display(self, itemName, exposure, sourceCat=None):
620  """Display exposure and sources on next frame, if display of itemName has been requested
621 
622  @param[in] itemName name of item in debugInfo
623  @param[in] exposure exposure to display
624  @param[in] sourceCat source catalog to display
625  """
626  val = getDebugFrame(self._display, itemName)
627  if not val:
628  return
629 
630  displayAstrometry(exposure=exposure, sourceCat=sourceCat, frame=self._frame_frame, pause=False)
631  self._frame_frame += 1
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