LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
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
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, datasetRefMap: pipeBase.InputQuantizedConnection):
80  # Docstring inherited from PipelineTaskConnections
81  try:
82  return super().adjustQuantum(datasetRefMap)
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  )
166  computeSummaryStats = pexConfig.ConfigurableField(
167  target=ComputeExposureSummaryStatsTask,
168  doc="Subtask to run computeSummaryStats on exposure"
169  )
170  useSimplePsf = pexConfig.Field(
171  dtype=bool,
172  default=True,
173  doc="Replace the existing PSF model with a simplified version that has the same sigma "
174  "at the start of each PSF determination iteration? Doing so makes PSF determination "
175  "converge more robustly and quickly.",
176  )
177  installSimplePsf = pexConfig.ConfigurableField(
178  target=InstallGaussianPsfTask,
179  doc="Install a simple PSF model",
180  )
181  refObjLoader = pexConfig.ConfigurableField(
182  target=LoadIndexedReferenceObjectsTask,
183  doc="reference object loader",
184  )
185  ref_match = pexConfig.ConfigurableField(
186  target=RefMatchTask,
187  doc="Task to load and match reference objects. Only used if measurePsf can use matches. "
188  "Warning: matching will only work well if the initial WCS is accurate enough "
189  "to give good matches (roughly: good to 3 arcsec across the CCD).",
190  )
191  measurePsf = pexConfig.ConfigurableField(
192  target=MeasurePsfTask,
193  doc="Measure PSF",
194  )
195  repair = pexConfig.ConfigurableField(
196  target=RepairTask,
197  doc="Remove cosmic rays",
198  )
199  requireCrForPsf = pexConfig.Field(
200  dtype=bool,
201  default=True,
202  doc="Require cosmic ray detection and masking to run successfully before measuring the PSF."
203  )
204  checkUnitsParseStrict = pexConfig.Field(
205  doc="Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
206  dtype=str,
207  default="raise",
208  )
209 
210  def setDefaults(self):
211  super().setDefaults()
212  # just detect bright stars; includeThresholdMultipler=10 seems large,
213  # but these are the values we have been using
214  self.detectiondetection.thresholdValue = 5.0
215  self.detectiondetection.includeThresholdMultiplier = 10.0
216  self.detectiondetection.doTempLocalBackground = False
217  # do not deblend, as it makes a mess
218  self.doDeblenddoDeblend = False
219  # measure and apply aperture correction; note: measuring and applying aperture
220  # correction are disabled until the final measurement, after PSF is measured
221  self.doApCorrdoApCorr = True
222  # minimal set of measurements needed to determine PSF
223  self.measurementmeasurement.plugins.names = [
224  "base_PixelFlags",
225  "base_SdssCentroid",
226  "base_SdssShape",
227  "base_GaussianFlux",
228  "base_PsfFlux",
229  "base_CircularApertureFlux",
230  ]
231 
232  def validate(self):
233  if self.doApCorrdoApCorr and not self.measurePsfmeasurePsf:
234  raise RuntimeError("Must measure PSF to measure aperture correction, "
235  "because flags determined by PSF measurement are used to identify "
236  "sources used to measure aperture correction")
237 
238 
244 
245 
246 class CharacterizeImageTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
247  r"""!Measure bright sources and use this to estimate background and PSF of an exposure
248 
249  @anchor CharacterizeImageTask_
250 
251  @section pipe_tasks_characterizeImage_Contents Contents
252 
253  - @ref pipe_tasks_characterizeImage_Purpose
254  - @ref pipe_tasks_characterizeImage_Initialize
255  - @ref pipe_tasks_characterizeImage_IO
256  - @ref pipe_tasks_characterizeImage_Config
257  - @ref pipe_tasks_characterizeImage_Debug
258 
259 
260  @section pipe_tasks_characterizeImage_Purpose Description
261 
262  Given an exposure with defects repaired (masked and interpolated over, e.g. as output by IsrTask):
263  - detect and measure bright sources
264  - repair cosmic rays
265  - measure and subtract background
266  - measure PSF
267 
268  @section pipe_tasks_characterizeImage_Initialize Task initialisation
269 
270  @copydoc \_\_init\_\_
271 
272  @section pipe_tasks_characterizeImage_IO Invoking the Task
273 
274  If you want this task to unpersist inputs or persist outputs, then call
275  the `runDataRef` method (a thin wrapper around the `run` method).
276 
277  If you already have the inputs unpersisted and do not want to persist the output
278  then it is more direct to call the `run` method:
279 
280  @section pipe_tasks_characterizeImage_Config Configuration parameters
281 
282  See @ref CharacterizeImageConfig
283 
284  @section pipe_tasks_characterizeImage_Debug Debug variables
285 
286  The @link lsst.pipe.base.cmdLineTask.CmdLineTask command line task@endlink interface supports a flag
287  `--debug` to import `debug.py` from your `$PYTHONPATH`; see @ref baseDebug for more about `debug.py`.
288 
289  CharacterizeImageTask has a debug dictionary with the following keys:
290  <dl>
291  <dt>frame
292  <dd>int: if specified, the frame of first debug image displayed (defaults to 1)
293  <dt>repair_iter
294  <dd>bool; if True display image after each repair in the measure PSF loop
295  <dt>background_iter
296  <dd>bool; if True display image after each background subtraction in the measure PSF loop
297  <dt>measure_iter
298  <dd>bool; if True display image and sources at the end of each iteration of the measure PSF loop
299  See @ref lsst.meas.astrom.displayAstrometry for the meaning of the various symbols.
300  <dt>psf
301  <dd>bool; if True display image and sources after PSF is measured;
302  this will be identical to the final image displayed by measure_iter if measure_iter is true
303  <dt>repair
304  <dd>bool; if True display image and sources after final repair
305  <dt>measure
306  <dd>bool; if True display image and sources after final measurement
307  </dl>
308 
309  For example, put something like:
310  @code{.py}
311  import lsstDebug
312  def DebugInfo(name):
313  di = lsstDebug.getInfo(name) # N.b. lsstDebug.Info(name) would call us recursively
314  if name == "lsst.pipe.tasks.characterizeImage":
315  di.display = dict(
316  repair = True,
317  )
318 
319  return di
320 
321  lsstDebug.Info = DebugInfo
322  @endcode
323  into your `debug.py` file and run `calibrateTask.py` with the `--debug` flag.
324 
325  Some subtasks may have their own debug variables; see individual Task documentation.
326  """
327 
328  # Example description used to live here, removed 2-20-2017 by MSSG
329 
330  ConfigClass = CharacterizeImageConfig
331  _DefaultName = "characterizeImage"
332  RunnerClass = pipeBase.ButlerInitializedTaskRunner
333 
334  def runQuantum(self, butlerQC, inputRefs, outputRefs):
335  inputs = butlerQC.get(inputRefs)
336  if 'exposureIdInfo' not in inputs.keys():
337  inputs['exposureIdInfo'] = ExposureIdInfo.fromDataId(butlerQC.quantum.dataId, "visit_detector")
338  outputs = self.runrun(**inputs)
339  butlerQC.put(outputs, outputRefs)
340 
341  def __init__(self, butler=None, refObjLoader=None, schema=None, **kwargs):
342  """!Construct a CharacterizeImageTask
343 
344  @param[in] butler A butler object is passed to the refObjLoader constructor in case
345  it is needed to load catalogs. May be None if a catalog-based star selector is
346  not used, if the reference object loader constructor does not require a butler,
347  or if a reference object loader is passed directly via the refObjLoader argument.
348  @param[in] refObjLoader An instance of LoadReferenceObjectsTasks that supplies an
349  external reference catalog to a catalog-based star selector. May be None if a
350  catalog star selector is not used or the loader can be constructed from the
351  butler argument.
352  @param[in,out] schema initial schema (an lsst.afw.table.SourceTable), or None
353  @param[in,out] kwargs other keyword arguments for lsst.pipe.base.CmdLineTask
354  """
355  super().__init__(**kwargs)
356 
357  if schema is None:
358  schema = SourceTable.makeMinimalSchema()
359  self.schemaschema = schema
360  self.makeSubtask("background")
361  self.makeSubtask("installSimplePsf")
362  self.makeSubtask("repair")
363  self.makeSubtask("measurePsf", schema=self.schemaschema)
364  if self.config.doMeasurePsf and self.measurePsf.usesMatches:
365  if not refObjLoader:
366  self.makeSubtask('refObjLoader', butler=butler)
367  refObjLoader = self.refObjLoader
368  self.makeSubtask("ref_match", refObjLoader=refObjLoader)
369  self.algMetadataalgMetadata = dafBase.PropertyList()
370  self.makeSubtask('detection', schema=self.schemaschema)
371  if self.config.doDeblend:
372  self.makeSubtask("deblend", schema=self.schemaschema)
373  self.makeSubtask('measurement', schema=self.schemaschema, algMetadata=self.algMetadataalgMetadata)
374  if self.config.doApCorr:
375  self.makeSubtask('measureApCorr', schema=self.schemaschema)
376  self.makeSubtask('applyApCorr', schema=self.schemaschema)
377  self.makeSubtask('catalogCalculation', schema=self.schemaschema)
378  if self.config.doComputeSummaryStats:
379  self.makeSubtask('computeSummaryStats')
380  self._initialFrame_initialFrame = getDebugFrame(self._display, "frame") or 1
381  self._frame_frame = self._initialFrame_initialFrame
382  self.schemaschema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
383  self.outputSchemaoutputSchema = afwTable.SourceCatalog(self.schemaschema)
384 
386  outputCatSchema = afwTable.SourceCatalog(self.schemaschema)
387  outputCatSchema.getTable().setMetadata(self.algMetadataalgMetadata)
388  return {'outputSchema': outputCatSchema}
389 
390  @pipeBase.timeMethod
391  def runDataRef(self, dataRef, exposure=None, background=None, doUnpersist=True):
392  """!Characterize a science image and, if wanted, persist the results
393 
394  This simply unpacks the exposure and passes it to the characterize method to do the work.
395 
396  @param[in] dataRef: butler data reference for science exposure
397  @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
398  If None then unpersist from "postISRCCD".
399  The following changes are made, depending on the config:
400  - set psf to the measured PSF
401  - set apCorrMap to the measured aperture correction
402  - subtract background
403  - interpolate over cosmic rays
404  - update detection and cosmic ray mask planes
405  @param[in,out] background initial model of background already subtracted from exposure
406  (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
407  which is typical for image characterization.
408  A refined background model is output.
409  @param[in] doUnpersist if True the exposure is read from the repository
410  and the exposure and background arguments must be None;
411  if False the exposure must be provided.
412  True is intended for running as a command-line task, False for running as a subtask
413 
414  @return same data as the characterize method
415  """
416  self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
417  self.log.info("Processing %s" % (dataRef.dataId))
418 
419  if doUnpersist:
420  if exposure is not None or background is not None:
421  raise RuntimeError("doUnpersist true; exposure and background must be None")
422  exposure = dataRef.get("postISRCCD", immediate=True)
423  elif exposure is None:
424  raise RuntimeError("doUnpersist false; exposure must be provided")
425 
426  exposureIdInfo = dataRef.get("expIdInfo")
427 
428  charRes = self.runrun(
429  exposure=exposure,
430  exposureIdInfo=exposureIdInfo,
431  background=background,
432  )
433 
434  if self.config.doWrite:
435  dataRef.put(charRes.sourceCat, "icSrc")
436  if self.config.doWriteExposure:
437  dataRef.put(charRes.exposure, "icExp")
438  dataRef.put(charRes.background, "icExpBackground")
439 
440  return charRes
441 
442  @pipeBase.timeMethod
443  def run(self, exposure, exposureIdInfo=None, background=None):
444  """!Characterize a science image
445 
446  Peforms the following operations:
447  - Iterate the following config.psfIterations times, or once if config.doMeasurePsf false:
448  - detect and measure sources and estimate PSF (see detectMeasureAndEstimatePsf for details)
449  - interpolate over cosmic rays
450  - perform final measurement
451 
452  @param[in,out] exposure exposure to characterize (an lsst.afw.image.ExposureF or similar).
453  The following changes are made:
454  - update or set psf
455  - set apCorrMap
456  - update detection and cosmic ray mask planes
457  - subtract background and interpolate over cosmic rays
458  @param[in] exposureIdInfo ID info for exposure (an lsst.obs.base.ExposureIdInfo).
459  If not provided, returned SourceCatalog IDs will not be globally unique.
460  @param[in,out] background initial model of background already subtracted from exposure
461  (an lsst.afw.math.BackgroundList). May be None if no background has been subtracted,
462  which is typical for image characterization.
463 
464  @return pipe_base Struct containing these fields, all from the final iteration
465  of detectMeasureAndEstimatePsf:
466  - exposure: characterized exposure; image is repaired by interpolating over cosmic rays,
467  mask is updated accordingly, and the PSF model is set
468  - sourceCat: detected sources (an lsst.afw.table.SourceCatalog)
469  - background: model of background subtracted from exposure (an lsst.afw.math.BackgroundList)
470  - psfCellSet: spatial cells of PSF candidates (an lsst.afw.math.SpatialCellSet)
471  """
472  self._frame_frame = self._initialFrame_initialFrame # reset debug display frame
473 
474  if not self.config.doMeasurePsf and not exposure.hasPsf():
475  self.log.warn("Source catalog detected and measured with placeholder or default PSF")
476  self.installSimplePsf.run(exposure=exposure)
477 
478  if exposureIdInfo is None:
479  exposureIdInfo = ExposureIdInfo()
480 
481  # subtract an initial estimate of background level
482  background = self.background.run(exposure).background
483 
484  psfIterations = self.config.psfIterations if self.config.doMeasurePsf else 1
485  for i in range(psfIterations):
486  dmeRes = self.detectMeasureAndEstimatePsfdetectMeasureAndEstimatePsf(
487  exposure=exposure,
488  exposureIdInfo=exposureIdInfo,
489  background=background,
490  )
491 
492  psf = dmeRes.exposure.getPsf()
493  psfSigma = psf.computeShape().getDeterminantRadius()
494  psfDimensions = psf.computeImage().getDimensions()
495  medBackground = np.median(dmeRes.background.getImage().getArray())
496  self.log.info("iter %s; PSF sigma=%0.2f, dimensions=%s; median background=%0.2f" %
497  (i + 1, psfSigma, psfDimensions, medBackground))
498 
499  self.displaydisplay("psf", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
500 
501  # perform final repair with final PSF
502  self.repair.run(exposure=dmeRes.exposure)
503  self.displaydisplay("repair", exposure=dmeRes.exposure, sourceCat=dmeRes.sourceCat)
504 
505  # perform final measurement with final PSF, including measuring and applying aperture correction,
506  # if wanted
507  self.measurement.run(measCat=dmeRes.sourceCat, exposure=dmeRes.exposure,
508  exposureId=exposureIdInfo.expId)
509  if self.config.doApCorr:
510  apCorrMap = self.measureApCorr.run(exposure=dmeRes.exposure, catalog=dmeRes.sourceCat).apCorrMap
511  dmeRes.exposure.getInfo().setApCorrMap(apCorrMap)
512  self.applyApCorr.run(catalog=dmeRes.sourceCat, apCorrMap=exposure.getInfo().getApCorrMap())
513  self.catalogCalculation.run(dmeRes.sourceCat)
514  if self.config.doComputeSummaryStats:
515  summary = self.computeSummaryStats.run(exposure=dmeRes.exposure,
516  sources=dmeRes.sourceCat,
517  background=dmeRes.background)
518  dmeRes.exposure.getInfo().setSummaryStats(summary)
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  @pipeBase.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.warn("Source catalog detected and measured with placeholder or default 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.warn("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 
614  def getSchemaCatalogs(self):
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
Class for storing ordered metadata with comments.
Definition: PropertyList.h:68
def adjustQuantum(self, pipeBase.InputQuantizedConnection datasetRefMap)
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