LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
forcedMeasurement.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 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 """Base classes for forced measurement plugins and the driver task for these.
24 
25 In forced measurement, a reference catalog is used to define restricted measurements (usually just fluxes)
26 on an image. As the reference catalog may be deeper than the detection limit of the measurement image, we
27 do not assume that we can use detection and deblend information from the measurement image. Instead, we
28 assume this information is present in the reference catalog and can be "transformed" in some sense to
29 the measurement frame. At the very least, this means that Footprints from the reference catalog should
30 be transformed and installed as Footprints in the output measurement catalog. If we have a procedure that
31 can transform HeavyFootprints, we can then proceed with measurement as usual, but using the reference
32 catalog's id and parent fields to define deblend families. If this transformation does not preserve
33 HeavyFootprints (this is currently the case), then we will only be able to replace objects with noise
34 one deblend family at a time, and hence measurements run in single-object mode may be contaminated by
35 neighbors when run on objects with parent != 0.
36 
37 Measurements are generally recorded in the coordinate system of the image being measured (and all
38 slot-eligible fields must be), but non-slot fields may be recorded in other coordinate systems if necessary
39 to avoid information loss (this should, of course, be indicated in the field documentation). Note that
40 the reference catalog may be in a different coordinate system; it is the responsibility of plugins
41 to transform the data they need themselves, using the reference WCS provided. However, for plugins
42 that only require a position or shape, they may simply use output SourceCatalog's centroid or shape slots,
43 which will generally be set to the transformed position of the reference object before any other plugins are
44 run, and hence avoid using the reference catalog at all.
45 
46 Command-line driver tasks for forced measurement can be found in forcedPhotImage.py, including
47 ForcedPhotImageTask, ForcedPhotCcdTask, and ForcedPhotCoaddTask.
48 """
49 
50 import lsst.pex.config
51 import lsst.pipe.base
52 
53 from .base import *
54 
55 __all__ = ("ForcedPluginConfig", "ForcedPlugin",
56  "ForcedMeasurementConfig", "ForcedMeasurementTask")
57 
58 class ForcedPluginConfig(BasePluginConfig):
59  """Base class for configs of forced measurement plugins."""
60  pass
61 
62 class ForcedPlugin(BasePlugin):
63 
64  # All subclasses of ForcedPlugin should be registered here
65  registry = PluginRegistry(ForcedPluginConfig)
66 
67  ConfigClass = ForcedPluginConfig
68 
69  def __init__(self, config, name, schemaMapper, metadata):
70  """Initialize the measurement object.
71 
72  @param[in] config An instance of this class's ConfigClass.
73  @param[in] name The string the plugin was registered with.
74  @param[in,out] schemaMapper A SchemaMapper that maps reference catalog fields to output
75  catalog fields. Output fields should be added to the
76  output schema. While most plugins will not need to map
77  fields from the reference schema, if they do so, those fields
78  will be transferred before any plugins are run.
79  @param[in] metadata Plugin metadata that will be attached to the output catalog
80  """
81  BasePlugin.__init__(self, config, name)
82 
83  def measure(self, measRecord, exposure, refRecord, refWcs):
84  """Measure the properties of a source on a single image, given data from a
85  reference record.
86 
87  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
88  be measured and the associated Psf, Wcs, etc. All
89  other sources in the image will have been replaced by
90  noise according to deblender outputs.
91  @param[in,out] measRecord lsst.afw.table.SourceRecord to be filled with outputs,
92  and from which previously-measured quantities can be
93  retreived.
94  @param[in] refRecord lsst.afw.table.SimpleRecord that contains additional
95  parameters to define the fit, as measured elsewhere.
96  @param[in] refWcs The coordinate system for the reference catalog values.
97  An afw.geom.Angle may be passed, indicating that a
98  local tangent Wcs should be created for each object
99  using afw.image.makeLocalWcs and the given angle as
100  a pixel scale.
101 
102  In the normal mode of operation, the source centroid will be set to the
103  WCS-transformed position of the reference object, so plugins that only
104  require a reference position should not have to access the reference object
105  at all.
106  """
107  raise NotImplementedError()
108 
109  def measureN(self, measCat, exposure, refCat, refWcs):
110  """Measure the properties of a group of blended sources on a single image,
111  given data from a reference record.
112 
113  @param[in] exposure lsst.afw.image.ExposureF, containing the pixel data to
114  be measured and the associated Psf, Wcs, etc. Sources
115  not in the blended hierarchy to be measured will have
116  been replaced with noise using deblender outputs.
117  @param[in,out] measCat lsst.afw.table.SourceCatalog to be filled with outputs,
118  and from which previously-measured quantities can be
119  retrieved, containing only the sources that should be
120  measured together in this call.
121  @param[in] refCat lsst.afw.table.SimpleCatalog that contains additional
122  parameters to define the fit, as measured elsewhere.
123  Ordered such that zip(sources, references) may be used.
124  @param[in] refWcs The coordinate system for the reference catalog values.
125  An afw.geom.Angle may be passed, indicating that a
126  local tangent Wcs should be created for each object
127  using afw.image.makeLocalWcs and the given Angle as
128  a pixel scale.
129 
130  In the normal mode of operation, the source centroids will be set to the
131  WCS-transformed position of the reference object, so plugins that only
132  require a reference position should not have to access the reference object
133  at all.
134  """
135  raise NotImplementedError()
136 
137 class ForcedMeasurementConfig(BaseMeasurementConfig):
138  """Config class for forced measurement driver task."""
139 
140  plugins = ForcedPlugin.registry.makeField(
141  multi=True,
142  default=["base_TransformedCentroid",
143  "base_TransformedShape",
144  "base_GaussianFlux",
145  "base_CircularApertureFlux",
146  "base_PsfFlux",
147  ],
148  doc="Plugins to be run and their configuration"
149  )
150  algorithms = property(lambda self: self.plugins, doc="backwards-compatibility alias for plugins")
151 
152  copyColumns = lsst.pex.config.DictField(
153  keytype=str, itemtype=str, doc="Mapping of reference columns to source columns",
154  default={"id": "objectId", "parent":"parentObjectId"}
155  )
156 
157  def setDefaults(self):
158  self.slots.centroid = "base_TransformedCentroid"
159  self.slots.shape = "base_TransformedShape"
160  self.slots.apFlux = None
161  self.slots.modelFlux = None
162  self.slots.psfFlux = None
163  self.slots.instFlux = None
164 
165 ## @addtogroup LSST_task_documentation
166 ## @{
167 ## @page pageForcedMeasurementTask ForcedMeasurementTask
168 ## ForcedMeasurementTask
169 ## @copybrief ForcedMeasurementTask
170 ## @}
171 
172 class ForcedMeasurementTask(BaseMeasurementTask):
173  """!
174  A subtask for measuring the properties of sources on a single exposure, using an existing
175  "reference" catalog to constrain some aspects of the measurement.
176 
177  The task is configured with a list of "plugins": each plugin defines the values it
178  measures (i.e. the columns in a table it will fill) and conducts that measurement
179  on each detected source (see ForcedPlugin). The job of the
180  measurement task is to initialize the set of plugins (which includes setting up the
181  catalog schema) from their configuration, and then invoke each plugin on each
182  source.
183 
184  Most of the time, ForcedMeasurementTask will be used via one of the subclasses of
185  ForcedPhotImageTask, ForcedPhotCcdTask and ForcedPhotCoaddTask. These combine
186  this measurement subtask with a "references" subtask (see BaseReferencesTask and
187  CoaddSrcReferencesTask) to perform forced measurement using measurements performed on
188  another image as the references. There is generally little reason to use
189  ForcedMeasurementTask outside of one of these drivers, unless it is necessary to avoid
190  using the Butler for I/O.
191 
192  ForcedMeasurementTask has only three methods: __init__(), run(), and generateSources().
193  For configuration options, see SingleFrameMeasurementConfig.
194  """
195 
196  ConfigClass = ForcedMeasurementConfig
197 
198  def __init__(self, refSchema, algMetadata=None, **kwds):
199  """!
200  Initialize the task. Set up the execution order of the plugins and initialize
201  the plugins, giving each plugin an opportunity to add its measurement fields to
202  the output schema and to record information in the task metadata.
203 
204  Note that while SingleFrameMeasurementTask is passed an initial Schema that is
205  appended to in order to create the output Schema, ForcedMeasurementTask is
206  initialized with the Schema of the reference catalog, from which a new Schema
207  for the output catalog is created. Fields to be copied directly from the
208  reference Schema are added before Plugin fields are added.
209 
210  @param[in] refSchema Schema of the reference catalog. Must match the catalog
211  later passed to generateSources() and/or run().
212  @param[in,out] algMetadata lsst.daf.base.PropertyList used to record information about
213  each algorithm. An empty PropertyList will be created if None.
214  @param[in] **kwds Keyword arguments passed from lsst.pipe.base.Task.__init__
215  """
216  super(ForcedMeasurementTask, self).__init__(algMetadata=algMetadata, **kwds)
218  self.mapper.addMinimalSchema(lsst.afw.table.SourceTable.makeMinimalSchema())
219  self.config.slots.setupSchema(self.mapper.editOutputSchema())
220  for refName, targetName in self.config.copyColumns.items():
221  refItem = refSchema.find(refName)
222  self.mapper.addMapping(refItem.key, targetName)
223  self.initializePlugins(schemaMapper=self.mapper)
224  self.schema = self.mapper.getOutputSchema()
225  self.config.slots.setupSchema(self.schema)
226 
227  def run(self, exposure, refCat, refWcs, idFactory=None, exposureId=None):
228  """!
229  Perform forced measurement.
230 
231  @param[in] exposure lsst.afw.image.ExposureF to be measured; must have at least a Wcs attached.
232  @param[in] refCat A sequence of SourceRecord objects that provide reference information
233  for the measurement. These will be passed to each Plugin in addition
234  to the output SourceRecord.
235  @param[in] refWcs Wcs that defines the X,Y coordinate system of refCat
236  @param[in] idFactory factory for creating IDs for sources
237  @param[in] exposureId optional unique exposureId used to calculate random number
238  generator seed in the NoiseReplacer.
239 
240  @return SourceCatalog containing measurement results.
241 
242  Delegates creating the initial empty SourceCatalog to generateSources(), then fills it.
243  """
244  # First create a refList from the original which excludes children when a member
245  # of the parent chain is not within the list. This can occur at boundaries when
246  # the parent is outside and one of the children is within. Currently, the parent
247  # chain is always only one deep, but just in case, this code removes any reference
248  # when the parent chain to its topmost parent is broken
249 
250  # Construct a footprints dict which does not contain the footprint, just the parentID
251  # We will add the footprint from the transformed sources from generateSources later.
252  footprints = {ref.getId(): ref.getParent() for ref in refCat}
253  refList = list()
254  for ref in refCat:
255  refId = ref.getId()
256  topId = refId
257  while(topId > 0):
258  if not topId in footprints.keys():
259  footprints.pop(refId)
260  break
261  topId = footprints[topId]
262  if topId == 0:
263  refList.append(ref)
264  # now generate transformed source corresponding to the cleanup up refLst
265  sources = self.generateSources(exposure, refList, refWcs, idFactory)
266 
267  # Steal the transformed source footprint and use it to complete the footprints dict,
268  # which then looks like {ref.getId(): (ref.getParent(), source.getFootprint())}
269  for (reference, source) in zip(refList, sources):
270  footprints[reference.getId()] = (reference.getParent(), source.getFootprint())
271 
272  self.log.info("Performing forced measurement on %d sources" % len(refList))
273 
274  # Build a catalog of just the references we intend to measure
275  referenceCat = lsst.afw.table.SourceCatalog(self.mapper.getInputSchema())
276  referenceCat.extend(refList)
277 
278  # convert the footprints to the coordinate system of the exposure
279  if self.config.doReplaceWithNoise:
280  noiseReplacer = NoiseReplacer(self.config.noiseReplacer, exposure, footprints, log=self.log, exposureId=exposureId)
281  algMetadata = sources.getTable().getMetadata()
282  if not algMetadata is None:
283  algMetadata.addInt("NOISE_SEED_MULTIPLIER", self.config.noiseReplacer.noiseSeedMultiplier)
284  algMetadata.addString("NOISE_SOURCE", self.config.noiseReplacer.noiseSource)
285  algMetadata.addDouble("NOISE_OFFSET", self.config.noiseReplacer.noiseOffset)
286  if not exposureId is None:
287  algMetadata.addLong("NOISE_EXPOSURE_ID", exposureId)
288  else:
289  noiseReplacer = DummyNoiseReplacer()
290 
291  # Create parent cat which slices both the referenceCat and measCat (sources)
292  # first, get the reference and source records which have no parent
293  refParentCat, measParentCat = referenceCat.getChildren(0, sources)
294  for parentIdx, (refParentRecord, measParentRecord) in enumerate(zip(refParentCat,measParentCat)):
295 
296  # first process the records which have the current parent as children
297  refChildCat, measChildCat = referenceCat.getChildren(refParentRecord.getId(), sources)
298  # TODO: skip this loop if there are no plugins configured for single-object mode
299  for refChildRecord, measChildRecord in zip(refChildCat, measChildCat):
300  noiseReplacer.insertSource(refChildRecord.getId())
301  self.callMeasure(measChildRecord, exposure, refChildRecord, refWcs)
302  noiseReplacer.removeSource(refChildRecord.getId())
303 
304  # then process the parent record
305  noiseReplacer.insertSource(refParentRecord.getId())
306  self.callMeasure(measParentRecord, exposure, refParentRecord, refWcs)
307  self.callMeasureN(measParentCat[parentIdx:parentIdx+1], exposure,
308  refParentCat[parentIdx:parentIdx+1])
309  # measure all the children simultaneously
310  self.callMeasureN(measChildCat, exposure, refChildCat)
311  noiseReplacer.removeSource(refParentRecord.getId())
312  noiseReplacer.end()
313  return lsst.pipe.base.Struct(sources=sources)
314 
315  def generateSources(self, exposure, refCat, refWcs, idFactory=None):
316  """!
317  Initialize an output SourceCatalog using information from the reference catalog.
318 
319  This generate a new blank SourceRecord for each record in refCat, copying any
320  fields in ForcedMeasurementConfig.copyColumns. This also transforms the Footprints
321  in refCat to the measurement coordinate system if it differs from refWcs, and attaches
322  these to the new SourceRecords. Note that we do not currently have the ability to
323  transform heavy footprints, so when the reference and measure WCSs are different,
324  HeavyFootprints will be converted to regular Footprints, which makes it impossible
325  to properly measure blended objects.
326 
327  @param[in] exposure Exposure to be measured
328  @param[in] refCat Sequence (not necessarily a SourceCatalog) of reference SourceRecords.
329  @param[in] refWcs Wcs that defines the X,Y coordinate system of refCat
330  @param[in] idFactory factory for creating IDs for sources
331 
332  @return Source catalog ready for measurement
333  """
334  if idFactory == None:
336  table = lsst.afw.table.SourceTable.make(self.schema, idFactory)
337  sources = lsst.afw.table.SourceCatalog(table)
338  table = sources.table
339  table.setMetadata(self.algMetadata)
340  table.preallocate(len(refCat))
341  expRegion = exposure.getBBox()
342  targetWcs = exposure.getWcs()
343  for ref in refCat:
344  newSource = sources.addNew()
345  newSource.assign(ref, self.mapper)
346  footprint = newSource.getFootprint()
347  if footprint is not None:
348  # if heavy, just transform the "light footprint" and leave the rest behind
349  if footprint.isHeavy():
350  footprint = lsst.afw.detection.Footprint(footprint)
351  if not refWcs == targetWcs:
352  footprint = footprint.transform(refWcs, targetWcs, expRegion, True)
353  newSource.setFootprint(footprint)
354  return sources
Defines the fields and offsets for a table.
Definition: Schema.h:46
def generateSources
Initialize an output SourceCatalog using information from the reference catalog.
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
A subtask for measuring the properties of sources on a single exposure, using an existing &quot;reference...
static boost::shared_ptr< SourceTable > make(Schema const &schema, boost::shared_ptr< IdFactory > const &idFactory)
Construct a new table.
static boost::shared_ptr< IdFactory > makeSimple()
Return a simple IdFactory that simply counts from 1.
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Definition: Source.h:233
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 set of pixels in an Image.
Definition: Footprint.h:70