LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
processImage.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 import lsst.pex.config as pexConfig
24 import lsst.pipe.base as pipeBase
25 import lsst.daf.base as dafBase
26 import lsst.afw.geom as afwGeom
27 import lsst.afw.math as afwMath
28 import lsst.afw.table as afwTable
29 from lsst.meas.algorithms import SourceDetectionTask
30 from lsst.meas.base import SingleFrameMeasurementTask
31 from lsst.meas.deblender import SourceDeblendTask
32 from lsst.pipe.tasks.calibrate import CalibrateTask
33 
34 class ProcessImageConfig(pexConfig.Config):
35  """Config for ProcessImage"""
36  doCalibrate = pexConfig.Field(dtype=bool, default=True, doc = "Perform calibration?")
37  doDetection = pexConfig.Field(dtype=bool, default=True, doc = "Detect sources?")
38  ## NOTE, default this to False until it is fully vetted; #2138
39  doDeblend = pexConfig.Field(dtype=bool, default=False, doc = "Deblend sources?")
40  doMeasurement = pexConfig.Field(dtype=bool, default=True, doc = "Measure sources?")
41  doWriteCalibrate = pexConfig.Field(dtype=bool, default=True, doc = "Write calibration results?")
42  persistBackgroundModel = pexConfig.Field(dtype=bool, default=True, doc = "If True persist background model with background subtracted calexp. \
43  If False persist calexp with the background included.")
44  doWriteCalibrateMatches = pexConfig.Field(dtype=bool, default=True,
45  doc = "Write icSrc to reference matches?")
46  doWriteSources = pexConfig.Field(dtype=bool, default=True, doc = "Write sources?")
47  doWriteSourceMatches = pexConfig.Field(dtype=bool, default=False,
48  doc = "Compute and write src to reference matches?")
49  doWriteHeavyFootprintsInSources = pexConfig.Field(dtype=bool, default=False,
50  doc = "Include HeavyFootprint data in source table?")
51  calibrate = pexConfig.ConfigurableField(
52  target = CalibrateTask,
53  doc = "Calibration (inc. high-threshold detection and measurement)",
54  )
55  detection = pexConfig.ConfigurableField(
56  target = SourceDetectionTask,
57  doc = "Low-threshold detection for final measurement",
58  )
59  deblend = pexConfig.ConfigurableField(
60  target = SourceDeblendTask,
61  doc = "Split blended sources into their components",
62  )
63  measurement = pexConfig.ConfigurableField(
64  target = SingleFrameMeasurementTask,
65  doc = "Final source measurement on low-threshold detections",
66  )
67 
68  def validate(self):
69  pexConfig.Config.validate(self)
70  if self.doMeasurement:
71  if not self.doDetection:
72  raise ValueError("Cannot run source measurement without source detection.")
73  if ("skycoord" not in self.measurement.algorithms.names
74  and "base_SkyCoord" not in self.measurement.algorithms.names):
75  raise ValueError("If you run source measurement you must let it run the skycoord algorithm.")
76  if self.measurement.doApplyApCorr.startswith("yes") and not self.doCalibrate:
77  raise ValueError("Cannot apply aperture correction in the final measurement"
78  " without calibration.")
79  if self.measurement.doApplyApCorr.startswith("yes") and not self.calibrate.doMeasureApCorr:
80  raise ValueError("Cannot apply aperture correction in the final measurement"
81  " without measuring it in calibration.")
82  if self.doDeblend and not self.doDetection:
83  raise ValueError("Cannot run source deblending without source detection.")
85  raise ValueError("Cannot write HeavyFootprints (doWriteHeavyFootprintsInSources)"
86  " without doWriteSources")
87 
88  def setDefaults(self):
89  self.measurement.doApplyApCorr = "yes"
90 
91 class ProcessImageTask(pipeBase.CmdLineTask):
92  """An abstract base class for tasks do simple calibration, detection, deblending, and measurement
93  on individual images.
94 
95  Other command-line Process***Tasks (such as ProcessCcdTask and ProcessCoaddTask) rely on
96  ProcessImageTask for their main algorithmic code, and only need to add pre- and post- processing
97  and serialization.
98 
99  Subclasses are responsible for meeting the requirements of CmdLineTask.
100  """
101  ConfigClass = ProcessImageConfig
102  dataPrefix = "" # Name to prepend to all input and output datasets (e.g. 'goodSeeingCoadd_')
103 
104  def __init__(self, **kwargs):
105  pipeBase.CmdLineTask.__init__(self, **kwargs)
106 
107  self.makeSubtask("calibrate")
108 
109  # Setup our schema by starting with fields we want to propagate from icSrc.
110  calibSchema = self.calibrate.schema
112  self.schemaMapper.addMinimalSchema(afwTable.SourceTable.makeMinimalSchema(), False)
113 
114  # Add fields needed to identify stars used in the calibration step
115  self.calibSourceKey = self.schemaMapper.addOutputField(
116  afwTable.Field["Flag"]("calib_detected", "Source was detected as an icSrc")
117  )
118 
119  for key in self.calibrate.getCalibKeys():
120  self.schemaMapper.addMapping(key)
121  self.schema = self.schemaMapper.getOutputSchema()
123  if self.config.doDetection:
124  self.makeSubtask("detection", schema=self.schema)
125  if self.config.doDeblend:
126  self.makeSubtask("deblend", schema=self.schema)
127  if self.config.doMeasurement:
128  self.makeSubtask("measurement", schema=self.schema, algMetadata=self.algMetadata)
129 
130  def makeIdFactory(self, sensorRef):
131  raise NotImplementedError()
132 
133  def getExposureId(self, sensorRef):
134  raise NotImplementedError()
135 
136  @pipeBase.timeMethod
137  def process(self, dataRef, inputExposure, enableWriteSources=True):
138  """Process an Image
139 
140  @param dataRef: data reference that corresponds to the input image
141  @param inputExposure: exposure to process
142  @param enableWriteSources: if True then writing sources is allowed.
143  Set False if you need to postprocess sources before writing them.
144 
145  @return pipe_base Struct containing these fields:
146  - postIsrExposure: exposure after ISR performed if calib.doIsr or config.doCalibrate, else None
147  - exposure: calibrated exposure (calexp): as computed if config.doCalibrate,
148  else as upersisted and updated if config.doDetection, else None
149  - calib: object returned by calibration process if config.doCalibrate, else None
150  - sources: detected source if config.doPhotometry, else None
151  """
152  idFactory = self.makeIdFactory(dataRef)
153 
154  # initialize outputs
155  calExposure = inputExposure
156  calib = None
157  sources = None
158  backgrounds = afwMath.BackgroundList()
159  if self.config.doCalibrate:
160  calib = self.calibrate.run(inputExposure, idFactory=idFactory)
161  calExposure = calib.exposure
162  if self.config.doWriteCalibrate:
163  dataRef.put(calib.sources, self.dataPrefix + "icSrc")
164  if calib.matches is not None and self.config.doWriteCalibrateMatches:
165  normalizedMatches = afwTable.packMatches(calib.matches)
166  normalizedMatches.table.setMetadata(calib.matchMeta)
167  dataRef.put(normalizedMatches, self.dataPrefix + "icMatch")
168  try:
169  for bg in calib.backgrounds:
170  backgrounds.append(bg)
171  except TypeError:
172  backgrounds.append(calib.backgrounds)
173  except AttributeError:
174  self.log.warn("The calibration task did not return any backgrounds. " +
175  "Any background subtracted in the calibration process cannot be persisted.")
176  else:
177  calib = None
178 
179  if self.config.doDetection:
180  table = afwTable.SourceTable.make(self.schema, idFactory)
181  table.setMetadata(self.algMetadata)
182  detections = self.detection.run(table, calExposure)
183  sources = detections.sources
184  fpSets = detections.fpSets
185  if fpSets.background:
186  backgrounds.append(fpSets.background)
187 
188  if self.config.doDeblend:
189  self.deblend.run(calExposure, sources, calExposure.getPsf())
190 
191  if self.config.doMeasurement:
192  self.measurement.run(calExposure, sources, exposureId=self.getExposureId(dataRef))
193 
194  if self.config.doWriteCalibrate:
195  # wait until after detection and measurement, since detection sets detected mask bits
196  # and both require a background subtracted exposure;
197  # note that this overwrites an existing calexp if doCalibrate false
198 
199  if calExposure is None:
200  self.log.warn("calibrated exposure is None; cannot save it")
201  else:
202  if self.config.persistBackgroundModel:
203  self.writeBackgrounds(dataRef, backgrounds)
204  else:
205  self.restoreBackgrounds(calExposure, backgrounds)
206  dataRef.put(calExposure, self.dataPrefix + "calexp")
207 
208  if calib is not None:
209  self.propagateCalibFlags(calib.sources, sources)
210 
211  if sources is not None and self.config.doWriteSources:
212  sourceWriteFlags = (0 if self.config.doWriteHeavyFootprintsInSources
213  else afwTable.SOURCE_IO_NO_HEAVY_FOOTPRINTS)
214  if enableWriteSources:
215  dataRef.put(sources, self.dataPrefix + 'src', flags=sourceWriteFlags)
216 
217  if self.config.doMeasurement and self.config.doWriteSourceMatches:
218  self.log.info("Matching src to reference catalogue" % (dataRef.dataId))
219  srcMatches, srcMatchMeta = self.matchSources(calExposure, sources)
220 
221  normalizedSrcMatches = afwTable.packMatches(srcMatches)
222  normalizedSrcMatches.table.setMetadata(srcMatchMeta)
223  dataRef.put(normalizedSrcMatches, self.dataPrefix + "srcMatch")
224  else:
225  srcMatches = None; srcMatchMeta = None
226 
227  return pipeBase.Struct(
228  inputExposure = inputExposure,
229  exposure = calExposure,
230  calib = calib,
231  sources = sources,
232  matches = srcMatches,
233  matchMeta = srcMatchMeta,
234  backgrounds = backgrounds,
235  )
236 
237  def matchSources(self, exposure, sources):
238  """Match the sources to the reference object loaded by the calibrate task
239 
240  Return two items:
241  - matches list of reference object/source matches (an lsst.afw.table.ReferenceMatchVector)
242  - matchMeta metadata about the field (an lsst.daf.base.PropertyList)
243  """
244  try:
245  astrometry = self.calibrate.astrometry
246  except Exception:
247  self.log.warn("Failed to find an astrometry solver in the calibrate task")
248  return None, None
249 
250  astromRet = astrometry.loadAndMatch(exposure=exposure, sourceCat=sources)
251  return astromRet.matches, astromRet.matchMeta
252 
253  def propagateCalibFlags(self, icSources, sources, matchRadius=1):
254  """Match the icSources and sources, and propagate Interesting Flags (e.g. PSF star) to the sources
255  """
256  self.log.info("Matching icSource and Source catalogs to propagate flags.")
257  if icSources is None or sources is None:
258  return
259 
260  closest = False # return all matched objects
261  matched = afwTable.matchRaDec(icSources, sources, matchRadius*afwGeom.arcseconds, closest)
262  if self.config.doDeblend:
263  matched = [m for m in matched if m[1].get("deblend_nChild") == 0] # if deblended, keep children
264  #
265  # Because we had to allow multiple matches to handle parents, we now need to
266  # prune to the best matches
267  #
268  bestMatches = {}
269  for m0, m1, d in matched:
270  id0 = m0.getId()
271  if bestMatches.has_key(id0):
272  if d > bestMatches[id0][2]:
273  continue
274 
275  bestMatches[id0] = (m0, m1, d)
276 
277  matched = bestMatches.values()
278  #
279  # Check that we got it right
280  #
281  if len(set(m[0].getId() for m in matched)) != len(matched):
282  self.log.warn("At least one icSource is matched to more than one Source")
283  #
284  # Copy over the desired flags
285  #
286  for ics, s, d in matched:
287  s.setFlag(self.calibSourceKey, True)
288  s.assign(ics, self.schemaMapper)
289 
290  return
291 
292  def getSchemaCatalogs(self):
293  """Return a dict of empty catalogs for each catalog dataset produced by this task."""
294  src = afwTable.SourceCatalog(self.schema)
295  src.getTable().setMetadata(self.algMetadata)
296  d = {self.dataPrefix + "src": src}
297  icSrc = None
298  try:
299  icSrc = afwTable.SourceCatalog(self.calibrate.schema)
300  icSrc.getTable().setMetadata(self.calibrate.algMetadata)
301  except AttributeError:
302  pass
303  if icSrc is not None:
304  d[self.dataPrefix + "icSrc"] = icSrc
305  return d
306 
307  def writeBackgrounds(self, dataRef, backgrounds):
308  """Backgrounds are persisted via the butler
309 
310  @param dataRef: Data reference
311  @param backgrounds: List of background models
312  """
313  self.log.warn("Persisting background models")
314 
315  dataRef.put(backgrounds, self.dataPrefix+"calexpBackground")
316 
317  def restoreBackgrounds(self, exp, backgrounds):
318  """Add backgrounds back in to an exposure
319 
320  @param exp: Exposure to which to add backgrounds
321  @param backgrounds: List of background models
322  """
323  mi = exp.getMaskedImage()
324  mi += backgrounds.getImage()
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
A description of a field in a table.
Definition: Field.h:22
tuple doDeblend
NOTE, default this to False until it is fully vetted; #2138.
Definition: processImage.py:39
BaseCatalog packMatches(std::vector< Match< Record1, Record2 > > const &matches)
Return a table representation of a MatchVector that can be used to persist it.
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric=true)