LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
plugins.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2016 AURA/LSST.
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 """
24 Definitions and registration of pure-Python plugins with trivial implementations,
25 and automatic plugin-from-algorithm calls for those implemented in C++.
26 """
27 import numpy
28 
30 import lsst.afw.detection
31 import lsst.afw.geom
32 
33 from .pluginRegistry import register
34 from . import baseLib as bl
35 from .pluginsBase import BasePlugin
36 from .sfm import SingleFramePluginConfig, SingleFramePlugin
37 from .forcedMeasurement import ForcedPluginConfig, ForcedPlugin
38 from .wrappers import wrapSimpleAlgorithm
39 from .transforms import SimpleCentroidTransform
40 
41 __all__ = (
42  "SingleFrameFPPositionConfig", "SingleFrameFPPositionPlugin",
43  "SingleFrameJacobianConfig", "SingleFrameJacobianPlugin",
44  "SingleFrameVarianceConfig", "SingleFrameVariancePlugin",
45  "SingleFrameInputCountConfig", "SingleFrameInputCountPlugin",
46  "SingleFramePeakCentroidConfig", "SingleFramePeakCentroidPlugin",
47  "SingleFrameSkyCoordConfig", "SingleFrameSkyCoordPlugin",
48  "ForcedPeakCentroidConfig", "ForcedPeakCentroidPlugin",
49  "ForcedTransformedCentroidConfig", "ForcedTransformedCentroidPlugin",
50  "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin",
51 )
52 
53 # --- Wrapped C++ Plugins ---
54 
55 wrapSimpleAlgorithm(bl.PsfFluxAlgorithm, Control=bl.PsfFluxControl,
56  TransformClass=bl.PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
57  shouldApCorr=True)
58 wrapSimpleAlgorithm(bl.PeakLikelihoodFluxAlgorithm, Control=bl.PeakLikelihoodFluxControl,
59  TransformClass=bl.PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
60 wrapSimpleAlgorithm(bl.GaussianFluxAlgorithm, Control=bl.GaussianFluxControl,
61  TransformClass=bl.GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
62  shouldApCorr=True)
63 wrapSimpleAlgorithm(bl.GaussianCentroidAlgorithm, Control=bl.GaussianCentroidControl,
64  TransformClass=bl.GaussianCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
65 wrapSimpleAlgorithm(bl.NaiveCentroidAlgorithm, Control=bl.NaiveCentroidControl,
66  TransformClass=bl.NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
67 wrapSimpleAlgorithm(bl.SdssCentroidAlgorithm, Control=bl.SdssCentroidControl,
68  TransformClass=bl.SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
69 wrapSimpleAlgorithm(bl.PixelFlagsAlgorithm, Control=bl.PixelFlagsControl,
70  executionOrder=BasePlugin.FLUX_ORDER)
71 wrapSimpleAlgorithm(bl.SdssShapeAlgorithm, Control=bl.SdssShapeControl,
72  TransformClass=bl.SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
73 wrapSimpleAlgorithm(bl.ScaledApertureFluxAlgorithm, Control=bl.ScaledApertureFluxControl,
74  TransformClass=bl.ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
75 
76 wrapSimpleAlgorithm(bl.CircularApertureFluxAlgorithm, needsMetadata=True, Control=bl.ApertureFluxControl,
77  TransformClass=bl.ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
78 wrapSimpleAlgorithm(bl.BlendednessAlgorithm, Control=bl.BlendednessControl,
79  TransformClass=bl.BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
80 
81 # --- Single-Frame Measurement Plugins ---
82 
83 
84 class SingleFrameFPPositionConfig(SingleFramePluginConfig):
85  pass
86 
87 
88 @register("base_FPPosition")
89 class SingleFrameFPPositionPlugin(SingleFramePlugin):
90  '''
91  Algorithm to calculate the position of a centroid on the focal plane
92  '''
93 
94  ConfigClass = SingleFrameFPPositionConfig
95 
96  @classmethod
98  return cls.SHAPE_ORDER
99 
100  def __init__(self, config, name, schema, metadata):
101  SingleFramePlugin.__init__(self, config, name, schema, metadata)
102  self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
103  "mm")
104  self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
105  self.detectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag",
106  doc="Set to True if detector object is missing")
107 
108  def measure(self, measRecord, exposure):
109  det = exposure.getDetector()
110  if not det:
111  measRecord.set(self.detectorFlag, True)
112  fp = lsst.afw.geom.Point2D(numpy.nan, numpy.nan)
113  else:
114  center = measRecord.getCentroid()
115  posInPix = det.makeCameraPoint(center, lsst.afw.cameraGeom.PIXELS)
116  fp = det.transform(posInPix, lsst.afw.cameraGeom.FOCAL_PLANE).getPoint()
117  measRecord.set(self.focalValue, fp)
118 
119  def fail(self, measRecord, error=None):
120  measRecord.set(self.focalFlag, True)
121 
122 
123 class SingleFrameJacobianConfig(SingleFramePluginConfig):
124  pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)")
125 
126 
127 @register("base_Jacobian")
128 class SingleFrameJacobianPlugin(SingleFramePlugin):
129  '''
130  Algorithm which computes the Jacobian about a source and computes its ratio with a nominal pixel area.
131  This allows one to compare relative instead of absolute areas of pixels.
132  '''
133 
134  ConfigClass = SingleFrameJacobianConfig
135 
136  @classmethod
138  return cls.SHAPE_ORDER
139 
140  def __init__(self, config, name, schema, metadata):
141  SingleFramePlugin.__init__(self, config, name, schema, metadata)
142  self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
143  self.jacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure")
144  # Calculate one over the area of a nominal reference pixel
145  self.scale = pow(self.config.pixelScale, -2)
146 
147  def measure(self, measRecord, exposure):
148  center = measRecord.getCentroid()
149  # Compute the area of a pixel at the center of a source records centroid, and take the
150  # ratio of that with the defined reference pixel area.
151  result = numpy.abs(self.scale*exposure.getWcs().linearizePixelToSky(
152  center,
153  lsst.afw.geom.arcseconds).getLinear().computeDeterminant())
154  measRecord.set(self.jacValue, result)
155 
156  def fail(self, measRecord, error=None):
157  measRecord.set(self.jacFlag, True)
158 
159 
160 class SingleFrameVarianceConfig(SingleFramePluginConfig):
161  scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
162  doc="Scale factor to apply to shape for aperture")
163  mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
164  default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
165 
166 
167 @register("base_Variance")
168 class SingleFrameVariancePlugin(SingleFramePlugin):
169  '''
170  Calculate the median variance within a Footprint scaled from the object shape so
171  the value is not terribly influenced by the object and instead represents the
172  variance in the background near the object.
173  '''
174  ConfigClass = SingleFrameVarianceConfig
175  FAILURE_BAD_CENTROID = 1
176  FAILURE_EMPTY_FOOTPRINT = 2
177 
178  @classmethod
180  return cls.FLUX_ORDER
181 
182  def __init__(self, config, name, schema, metadata):
183  SingleFramePlugin.__init__(self, config, name, schema, metadata)
184  self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
185  self.varFlag = schema.addField(name + '_flag', type="Flag", doc="Set to True for any fatal failure")
186  self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
187  doc="Set to True when the footprint has no usable pixels")
188 
189  # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
190  # We do not simply rely on the alias because that could be changed post-measurement.
191  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
192 
193  def measure(self, measRecord, exposure):
194  if measRecord.getCentroidFlag():
195  raise bl.MeasurementError("Source record has a bad centroid.", self.FAILURE_BAD_CENTROID)
196  # Create an aperture and grow it by scale value defined in config to ensure there are enough
197  # pixels around the object to get decent statistics
198  aperture = lsst.afw.geom.ellipses.Ellipse(measRecord.getShape(), measRecord.getCentroid())
199  aperture.scale(self.config.scale)
200  foot = lsst.afw.detection.Footprint(aperture)
201  foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
202  # Filter out any pixels which have mask bits set corresponding to the planes to be excluded
203  # (defined in config.mask)
204  maskedImage = exposure.getMaskedImage()
205  pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
206  maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask)
207  logicalMask = numpy.logical_not(pixels.getMaskArray() & maskBits)
208  # Compute the median variance value for each pixel not excluded by the mask and write the record.
209  # Numpy median is used here instead of afw.math makeStatistics because of an issue with data types
210  # being passed into the C++ layer (DM-2379).
211  if numpy.any(logicalMask):
212  medVar = numpy.median(pixels.getVarianceArray()[logicalMask])
213  measRecord.set(self.varValue, medVar)
214  else:
215  raise bl.MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
217 
218  def fail(self, measRecord, error=None):
219  # Check that we have a error object and that it is of type MeasurementError
220  if isinstance(error, bl.MeasurementError):
221  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINT)
222  # FAILURE_BAD_CENTROID handled by alias to centroid record.
223  if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT:
224  measRecord.set(self.emptyFootprintFlag, True)
225  measRecord.set(self.varValue, numpy.nan)
226  measRecord.set(self.varFlag, True)
227 
228 
229 class SingleFrameInputCountConfig(SingleFramePluginConfig):
230  pass
231 
232 
233 @register("base_InputCount")
234 class SingleFrameInputCountPlugin(SingleFramePlugin):
235  """
236  Plugin to count how many input images contributed to each source. This information
237  is in the exposure's coaddInputs. Some limitations:
238  * This is only for the pixel containing the center, not for all the pixels in the
239  Footprint
240  * This does not account for any clipping in the coadd
241  """
242 
243  ConfigClass = SingleFrameInputCountConfig
244  FAILURE_BAD_CENTROID = 1
245  FAILURE_NO_INPUTS = 2
246 
247  @classmethod
249  return cls.SHAPE_ORDER
250 
251  def __init__(self, config, name, schema, metadata):
252  SingleFramePlugin.__init__(self, config, name, schema, metadata)
253  self.numberKey = schema.addField(name + '_value', type="I",
254  doc="Number of images contributing at center, not including any"
255  "clipping")
256  self.numberFlag = schema.addField(name + '_flag', type="Flag", doc="Set to True for fatal failure")
257  self.noInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag",
258  doc="No coadd inputs available")
259  # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
260  # We do not simply rely on the alias because that could be changed post-measurement.
261  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
262 
263  def measure(self, measRecord, exposure):
264  if not exposure.getInfo().getCoaddInputs():
265  raise bl.MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTS)
266  if measRecord.getCentroidFlag():
267  raise bl.MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID)
268 
269  center = measRecord.getCentroid()
270  ccds = exposure.getInfo().getCoaddInputs().ccds
271  measRecord.set(self.numberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
272 
273  def fail(self, measRecord, error=None):
274  measRecord.set(self.numberFlag, True)
275  if error is not None:
276  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTS)
277  # FAILURE_BAD_CENTROID handled by alias to centroid record.
278  if error.getFlagBit() == self.FAILURE_NO_INPUTS:
279  measRecord.set(self.noInputsFlag, True)
280 
281 
282 class SingleFramePeakCentroidConfig(SingleFramePluginConfig):
283  pass
284 
285 
286 @register("base_PeakCentroid")
287 class SingleFramePeakCentroidPlugin(SingleFramePlugin):
288  """
289  A centroid algorithm that simply uses the first (i.e. highest) Peak in the Source's
290  Footprint as the centroid. This is of course a relatively poor measure of the true
291  centroid of the object; this algorithm is provided mostly for testing and debugging.
292  """
293 
294  ConfigClass = SingleFramePeakCentroidConfig
295 
296  @classmethod
298  return cls.CENTROID_ORDER
299 
300  def __init__(self, config, name, schema, metadata):
301  SingleFramePlugin.__init__(self, config, name, schema, metadata)
302  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
303  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
304  self.flag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed")
305 
306  def measure(self, measRecord, exposure):
307  peak = measRecord.getFootprint().getPeaks()[0]
308  measRecord.set(self.keyX, peak.getFx())
309  measRecord.set(self.keyY, peak.getFy())
310 
311  def fail(self, measRecord, error=None):
312  measRecord.set(self.flag, True)
313 
314  @staticmethod
316  return SimpleCentroidTransform
317 
318 
319 class SingleFrameSkyCoordConfig(SingleFramePluginConfig):
320  pass
321 
322 
323 @register("base_SkyCoord")
324 class SingleFrameSkyCoordPlugin(SingleFramePlugin):
325  """
326  A measurement plugin that sets the "coord" field (part of the Source minimal schema)
327  using the slot centroid and the Wcs attached to the Exposure.
328  """
329 
330  ConfigClass = SingleFrameSkyCoordConfig
331 
332  @classmethod
334  return cls.SHAPE_ORDER
335 
336  def measure(self, measRecord, exposure):
337  # there should be a base class method for handling this exception. Put this on a later ticket
338  # Also, there should be a python Exception of the appropriate type for this error
339  if not exposure.hasWcs():
340  raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm")
341  measRecord.updateCoord(exposure.getWcs())
342 
343  def fail(self, measRecord, error=None):
344  # Override fail() to do nothing in the case of an exception: this is not ideal,
345  # but we don't have a place to put failures because we don't allocate any fields.
346  # Should consider fixing as part of DM-1011
347  pass
348 
349 
350 # --- Forced Plugins ---
351 
352 class ForcedPeakCentroidConfig(ForcedPluginConfig):
353  pass
354 
355 
356 @register("base_PeakCentroid")
357 class ForcedPeakCentroidPlugin(ForcedPlugin):
358  """
359  The forced peak centroid is like the SFM peak centroid plugin, except that it must transform
360  the peak coordinate from the original (reference) coordinate system to the coordinate system
361  of the exposure being measured.
362  """
363 
364  ConfigClass = ForcedPeakCentroidConfig
365 
366  @classmethod
368  return cls.CENTROID_ORDER
369 
370  def __init__(self, config, name, schemaMapper, metadata):
371  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
372  schema = schemaMapper.editOutputSchema()
373  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
374  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
375 
376  def measure(self, measRecord, exposure, refRecord, refWcs):
377  targetWcs = exposure.getWcs()
378  peak = refRecord.getFootprint().getPeaks()[0]
379  result = lsst.afw.geom.Point2D(peak.getFx(), peak.getFy())
380  if not refWcs == targetWcs:
381  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
382  measRecord.set(self.keyX, result.getX())
383  measRecord.set(self.keyY, result.getY())
384 
385  @staticmethod
387  return SimpleCentroidTransform
388 
389 
390 class ForcedTransformedCentroidConfig(ForcedPluginConfig):
391  pass
392 
393 
394 @register("base_TransformedCentroid")
396  """A centroid pseudo-algorithm for forced measurement that simply transforms the centroid
397  from the reference catalog to the measurement coordinate system. This is used as
398  the slot centroid by default in forced measurement, allowing subsequent measurements
399  to simply refer to the slot value just as they would in single-frame measurement.
400  """
401 
402  ConfigClass = ForcedTransformedCentroidConfig
403 
404  @classmethod
406  return cls.CENTROID_ORDER
407 
408  def __init__(self, config, name, schemaMapper, metadata):
409  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
410  schema = schemaMapper.editOutputSchema()
411  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
412  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
413  units="pixel")
414  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
415  units="pixel")
417  # Because we're taking the reference position as given, we don't bother transforming its
418  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
419  # the flag field, if it exists.
420  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
421  self.flagKey = schema.addField(name + "_flag", type="Flag",
422  doc="whether the reference centroid is marked as bad")
423  else:
424  self.flagKey = None
425 
426  def measure(self, measRecord, exposure, refRecord, refWcs):
427  targetWcs = exposure.getWcs()
428  if not refWcs == targetWcs:
429  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
430  measRecord.set(self.centroidKey, targetPos)
431  else:
432  measRecord.set(self.centroidKey, refRecord.getCentroid())
433  if self.flagKey is not None:
434  measRecord.set(self.flagKey, refRecord.getCentroidFlag())
435 
436 
437 class ForcedTransformedShapeConfig(ForcedPluginConfig):
438  pass
439 
440 
441 @register("base_TransformedShape")
442 class ForcedTransformedShapePlugin(ForcedPlugin):
443  """A shape pseudo-algorithm for forced measurement that simply transforms the shape
444  from the reference catalog to the measurement coordinate system. This is used as
445  the slot shape by default in forced measurement, allowing subsequent measurements
446  to simply refer to the slot value just as they would in single-frame measurement.
447  """
448 
449  ConfigClass = ForcedTransformedShapeConfig
450 
451  @classmethod
453  return cls.SHAPE_ORDER
454 
455  def __init__(self, config, name, schemaMapper, metadata):
456  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
457  schema = schemaMapper.editOutputSchema()
458  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
459  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
460  units="pixel^2")
461  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
462  units="pixel^2")
463  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
464  units="pixel^2")
465  self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
466  # Because we're taking the reference position as given, we don't bother transforming its
467  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
468  # the flag field, if it exists.
469  if "slot_Shape_flag" in schemaMapper.getInputSchema():
470  self.flagKey = schema.addField(name + "_flag", type="Flag",
471  doc="whether the reference shape is marked as bad")
472  else:
473  self.flagKey = None
474 
475  def measure(self, measRecord, exposure, refRecord, refWcs):
476  targetWcs = exposure.getWcs()
477  if not refWcs == targetWcs:
478  fullTransform = lsst.afw.image.XYTransformFromWcsPair(targetWcs, refWcs)
479  localTransform = fullTransform.linearizeForwardTransform(refRecord.getCentroid())
480  measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
481  else:
482  measRecord.set(self.shapeKey, refRecord.getShape())
483  if self.flagKey is not None:
484  measRecord.set(self.flagKey, refRecord.getShapeFlag())
XYTransformFromWcsPair: An XYTransform obtained by putting two Wcs objects &quot;back to back&quot;...
Definition: Wcs.h:449
An ellipse defined by an arbitrary BaseCore and a center point.
Definition: Ellipse.h:50
A set of pixels in an Image.
Definition: Footprint.h:62
def wrapSimpleAlgorithm
Wrap a C++ SimpleAlgorithm class into both a Python SingleFramePlugin and ForcedPlugin classes...
Definition: wrappers.py:329
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys...
Definition: aggregates.h:188
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)