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