23 """Base classes for forced measurement plugins and the driver task for these.
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.
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.
46 Command-line driver tasks for forced measurement can be found in forcedPhotImage.py, including
47 ForcedPhotImageTask, ForcedPhotCcdTask, and ForcedPhotCoaddTask.
55 __all__ = (
"ForcedPluginConfig",
"ForcedPlugin",
56 "ForcedMeasurementConfig",
"ForcedMeasurementTask")
59 """Base class for configs of forced measurement plugins."""
65 registry = PluginRegistry(ForcedPluginConfig)
67 ConfigClass = ForcedPluginConfig
69 def __init__(self, config, name, schemaMapper, metadata):
70 """Initialize the measurement object.
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
81 BasePlugin.__init__(self, config, name)
83 def measure(self, measRecord, exposure, refRecord, refWcs):
84 """Measure the properties of a source on a single image, given data from a
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
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
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
107 raise NotImplementedError()
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.
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
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
135 raise NotImplementedError()
138 """Config class for forced measurement driver task."""
140 plugins = ForcedPlugin.registry.makeField(
142 default=[
"base_TransformedCentroid",
143 "base_TransformedShape",
145 "base_CircularApertureFlux",
148 doc=
"Plugins to be run and their configuration"
150 algorithms = property(
lambda self: self.
plugins, doc=
"backwards-compatibility alias for plugins")
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"}
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
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.
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
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.
192 ForcedMeasurementTask has only three methods: __init__(), run(), and generateSources().
193 For configuration options, see SingleFrameMeasurementConfig.
196 ConfigClass = ForcedMeasurementConfig
198 def __init__(self, refSchema, algMetadata=None, **kwds):
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.
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.
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__
216 super(ForcedMeasurementTask, self).
__init__(algMetadata=algMetadata, **kwds)
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)
227 def run(self, exposure, refCat, refWcs, idFactory=None, exposureId=None):
229 Perform forced measurement.
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.
240 @return SourceCatalog containing measurement results.
242 Delegates creating the initial empty SourceCatalog to generateSources(), then fills it.
252 footprints = {ref.getId(): ref.getParent()
for ref
in refCat}
258 if not topId
in footprints.keys():
259 footprints.pop(refId)
261 topId = footprints[topId]
269 for (reference, source)
in zip(refList, sources):
270 footprints[reference.getId()] = (reference.getParent(), source.getFootprint())
272 self.log.info(
"Performing forced measurement on %d sources" % len(refList))
276 referenceCat.extend(refList)
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)
289 noiseReplacer = DummyNoiseReplacer()
293 refParentCat, measParentCat = referenceCat.getChildren(0, sources)
294 for parentIdx, (refParentRecord, measParentRecord)
in enumerate(zip(refParentCat,measParentCat)):
297 refChildCat, measChildCat = referenceCat.getChildren(refParentRecord.getId(), sources)
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())
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])
310 self.callMeasureN(measChildCat, exposure, refChildCat)
311 noiseReplacer.removeSource(refParentRecord.getId())
313 return lsst.pipe.base.Struct(sources=sources)
317 Initialize an output SourceCatalog using information from the reference catalog.
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.
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
332 @return Source catalog ready for measurement
334 if idFactory ==
None:
338 table = sources.table
339 table.setMetadata(self.algMetadata)
340 table.preallocate(len(refCat))
341 expRegion = exposure.getBBox()
342 targetWcs = exposure.getWcs()
344 newSource = sources.addNew()
345 newSource.assign(ref, self.
mapper)
346 footprint = newSource.getFootprint()
347 if footprint
is not None:
349 if footprint.isHeavy():
351 if not refWcs == targetWcs:
352 footprint = footprint.transform(refWcs, targetWcs, expRegion,
True)
353 newSource.setFootprint(footprint)
Defines the fields and offsets for a table.
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.
A subtask for measuring the properties of sources on a single exposure, using an existing "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.
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
def __init__
Initialize the task.
def run
Perform forced measurement.