LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
plugins.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 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 
32 from .pluginRegistry import register
33 from . import baseLib as bl
34 from .baseMeasurement import BasePlugin
35 from .sfm import SingleFramePluginConfig, SingleFramePlugin
36 from .forcedMeasurement import ForcedPluginConfig, ForcedPlugin
37 from .wrappers import wrapSimpleAlgorithm
38 from .transforms import SimpleCentroidTransform
39 
40 __all__ = (
41  "SingleFramePeakCentroidConfig", "SingleFramePeakCentroidPlugin",
42  "SingleFrameSkyCoordConfig", "SingleFrameSkyCoordPlugin",
43  "SingleFrameClassificationConfig", "SingleFrameClassificationPlugin",
44  "ForcedPeakCentroidConfig", "ForcedPeakCentroidPlugin",
45  "ForcedTransformedCentroidConfig", "ForcedTransformedCentroidPlugin",
46  "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin",
47 )
48 
49 # --- Wrapped C++ Plugins ---
50 
51 wrapSimpleAlgorithm(bl.PsfFluxAlgorithm, Control=bl.PsfFluxControl,
52  TransformClass=bl.PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER, shouldApCorr=True)
53 wrapSimpleAlgorithm(bl.PeakLikelihoodFluxAlgorithm, Control=bl.PeakLikelihoodFluxControl,
54  TransformClass=bl.PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
55 wrapSimpleAlgorithm(bl.GaussianFluxAlgorithm, Control=bl.GaussianFluxControl,
56  TransformClass=bl.GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER, shouldApCorr=True)
57 wrapSimpleAlgorithm(bl.GaussianCentroidAlgorithm, Control=bl.GaussianCentroidControl,
58  TransformClass=bl.GaussianCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
59 wrapSimpleAlgorithm(bl.NaiveCentroidAlgorithm, Control=bl.NaiveCentroidControl,
60  TransformClass=bl.NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
61 wrapSimpleAlgorithm(bl.SdssCentroidAlgorithm, Control=bl.SdssCentroidControl,
62  TransformClass=bl.SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
63 wrapSimpleAlgorithm(bl.PixelFlagsAlgorithm, Control=bl.PixelFlagsControl, executionOrder=BasePlugin.FLUX_ORDER)
64 wrapSimpleAlgorithm(bl.SdssShapeAlgorithm, Control=bl.SdssShapeControl,
65  TransformClass=bl.SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
66 
67 wrapSimpleAlgorithm(bl.CircularApertureFluxAlgorithm, needsMetadata=True, Control=bl.ApertureFluxControl,
68  TransformClass=bl.ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
69 
70 # --- Single-Frame Measurement Plugins ---
71 
72 class SingleFramePeakCentroidConfig(SingleFramePluginConfig):
73  pass
74 
75 @register("base_PeakCentroid")
76 class SingleFramePeakCentroidPlugin(SingleFramePlugin):
77  """
78  A centroid algorithm that simply uses the first (i.e. highest) Peak in the Source's
79  Footprint as the centroid. This is of course a relatively poor measure of the true
80  centroid of the object; this algorithm is provided mostly for testing and debugging.
81  """
82 
83  ConfigClass = SingleFramePeakCentroidConfig
84 
85  @classmethod
87  return cls.CENTROID_ORDER
88 
89  def __init__(self, config, name, schema, metadata):
90  SingleFramePlugin.__init__(self, config, name, schema, metadata)
91  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixels")
92  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixels")
93 
94  def measure(self, measRecord, exposure):
95  peak = measRecord.getFootprint().getPeaks()[0]
96  measRecord.set(self.keyX, peak.getFx())
97  measRecord.set(self.keyY, peak.getFy())
98 
99  @staticmethod
101  return SimpleCentroidTransform
102 
103 class SingleFrameSkyCoordConfig(SingleFramePluginConfig):
104  pass
105 
106 @register("base_SkyCoord")
107 class SingleFrameSkyCoordPlugin(SingleFramePlugin):
108  """
109  A measurement plugin that sets the "coord" field (part of the Source minimal schema)
110  using the slot centroid and the Wcs attached to the Exposure.
111  """
112 
113  ConfigClass = SingleFrameSkyCoordConfig
114 
115  @classmethod
117  return cls.SHAPE_ORDER
118 
119  def measure(self, measRecord, exposure):
120  # there should be a base class method for handling this exception. Put this on a later ticket
121  # Also, there should be a python Exception of the appropriate type for this error
122  if not exposure.hasWcs():
123  raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm")
124  measRecord.updateCoord(exposure.getWcs())
125 
126  def fail(self, measRecord, error=None):
127  # Override fail() to do nothing in the case of an exception: this is not ideal,
128  # but we don't have a place to put failures because we don't allocate any fields.
129  # Should consider fixing as part of DM-1011
130  pass
131 
132 
133 class SingleFrameClassificationConfig(SingleFramePluginConfig):
134 
135  fluxRatio = lsst.pex.config.Field(dtype=float, default=.925, optional=True,
136  doc="critical ratio of model to psf flux")
137  modelErrFactor = lsst.pex.config.Field(dtype=float, default=0.0, optional=True,
138  doc="correction factor for modelFlux error")
139  psfErrFactor = lsst.pex.config.Field(dtype=float, default=0.0, optional=True,
140  doc="correction factor for psfFlux error")
141 
142 @register("base_ClassificationExtendedness")
143 class SingleFrameClassificationPlugin(SingleFramePlugin):
144  """
145  A binary measure of the extendedness of a source, based a simple cut on the ratio of the
146  PSF flux to the model flux.
147 
148  Because the fluxes on which this algorithm is based are slot measurements, they can be provided
149  by different algorithms, and the "fluxRatio" threshold used by this algorithm should generally
150  be set differently for different algorithms. To do this, plot the difference between the PSF
151  magnitude and the model magnitude vs. the PSF magnitude, and look for where the cloud of galaxies
152  begins.
153  """
154 
155  ConfigClass = SingleFrameClassificationConfig
156 
157  @classmethod
159  return cls.CLASSIFY_ORDER
160 
161  def __init__(self, config, name, schema, metadata):
162  SingleFramePlugin.__init__(self, config, name, schema, metadata)
163  self.keyProbability = schema.addField(name + "_value", type="D",
164  doc="Set to 1 for extended sources, 0 for point sources.")
165  self.keyFlag = schema.addField(name + "_flag", type="Flag", doc="Set to 1 for any fatal failure.")
166 
167  def measure(self, measRecord, exposure):
168  modelFlux = measRecord.getModelFlux()
169  psfFlux = measRecord.getPsfFlux()
170  modelFluxFlag = (measRecord.getModelFluxFlag()
171  if measRecord.table.getModelFluxFlagKey().isValid()
172  else False)
173  psfFluxFlag = (measRecord.getPsfFluxFlag()
174  if measRecord.table.getPsfFluxFlagKey().isValid()
175  else False)
176  flux1 = self.config.fluxRatio*modelFlux
177  if not self.config.modelErrFactor == 0:
178  flux1 += self.config.modelErrFactor*measRecord.getModelFluxErr()
179  flux2 = psfFlux
180  if not self.config.psfErrFactor == 0:
181  flux2 += self.config.psfErrFactor*measRecord.getPsfFluxErr()
182 
183  # A generic failure occurs when either FluxFlag is set to True
184  # A generic failure also occurs if either calculated flux value is NAN:
185  # this can occur if the Flux field itself is NAN,
186  # or the ErrFactor != 0 and the FluxErr is NAN
187  if numpy.isnan(flux1) or numpy.isnan(flux2) or modelFluxFlag or psfFluxFlag:
188  self.fail(measRecord)
189  else:
190  if flux1 < flux2:
191  measRecord.set(self.keyProbability, 0.0)
192  else:
193  measRecord.set(self.keyProbability, 1.0)
194 
195  def fail(self, measRecord, error=None):
196  # Override fail() to do nothing in the case of an exception. We should be setting a flag
197  # instead.
198  measRecord.set(self.keyFlag, True)
199 
200 
201 # --- Forced Plugins ---
202 
203 class ForcedPeakCentroidConfig(ForcedPluginConfig):
204  pass
205 
206 @register("base_PeakCentroid")
207 class ForcedPeakCentroidPlugin(ForcedPlugin):
208  """
209  The forced peak centroid is like the SFM peak centroid plugin, except that it must transform
210  the peak coordinate from the original (reference) coordinate system to the coordinate system
211  of the exposure being measured.
212  """
213 
214  ConfigClass = ForcedPeakCentroidConfig
215 
216  @classmethod
218  return cls.CENTROID_ORDER
219 
220  def __init__(self, config, name, schemaMapper, metadata):
221  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
222  schema = schemaMapper.editOutputSchema()
223  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixels")
224  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixels")
225 
226  def measure(self, measRecord, exposure, refRecord, refWcs):
227  targetWcs = exposure.getWcs()
228  peak = refRecord.getFootprint().getPeaks()[0]
229  result = lsst.afw.geom.Point2D(peak.getFx(), peak.getFy())
230  if not refWcs == targetWcs:
231  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
232  measRecord.set(self.keyX, result.getX())
233  measRecord.set(self.keyY, result.getY())
234 
235  @staticmethod
237  return SimpleCentroidTransform
238 
239 class ForcedTransformedCentroidConfig(ForcedPluginConfig):
240  pass
241 
242 @register("base_TransformedCentroid")
244  """A centroid pseudo-algorithm for forced measurement that simply transforms the centroid
245  from the reference catalog to the measurement coordinate system. This is used as
246  the slot centroid by default in forced measurement, allowing subsequent measurements
247  to simply refer to the slot value just as they would in single-frame measurement.
248  """
249 
250  ConfigClass = ForcedTransformedCentroidConfig
251 
252  @classmethod
254  return cls.CENTROID_ORDER
255 
256  def __init__(self, config, name, schemaMapper, metadata):
257  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
258  schema = schemaMapper.editOutputSchema()
259  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
260  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
261  units="pixels")
262  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
263  units="pixels")
265  # Because we're taking the reference position as given, we don't bother transforming its
266  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
267  # the flag field, if it exists.
268  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
269  self.flagKey = schema.addField(name + "_flag", type="Flag",
270  doc="whether the reference centroid is marked as bad")
271  else:
272  self.flagKey = None
273 
274  def measure(self, measRecord, exposure, refRecord, refWcs):
275  targetWcs = exposure.getWcs()
276  if not refWcs == targetWcs:
277  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
278  measRecord.set(self.centroidKey, targetPos)
279  else:
280  measRecord.set(self.centroidKey, refRecord.getCentroid())
281  if self.flagKey is not None:
282  measRecord.set(self.flagKey, refRecord.getCentroidFlag())
283 
284 
285 class ForcedTransformedShapeConfig(ForcedPluginConfig):
286  pass
287 
288 @register("base_TransformedShape")
289 class ForcedTransformedShapePlugin(ForcedPlugin):
290  """A shape pseudo-algorithm for forced measurement that simply transforms the shape
291  from the reference catalog to the measurement coordinate system. This is used as
292  the slot shape by default in forced measurement, allowing subsequent measurements
293  to simply refer to the slot value just as they would in single-frame measurement.
294  """
295 
296  ConfigClass = ForcedTransformedShapeConfig
297 
298  @classmethod
300  return cls.SHAPE_ORDER
301 
302  def __init__(self, config, name, schemaMapper, metadata):
303  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
304  schema = schemaMapper.editOutputSchema()
305  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
306  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
307  units="pixels^2")
308  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
309  units="pixels^2")
310  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
311  units="pixels^2")
312  self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
313  # Because we're taking the reference position as given, we don't bother transforming its
314  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
315  # the flag field, if it exists.
316  if "slot_Shape_flag" in schemaMapper.getInputSchema():
317  self.flagKey = schema.addField(name + "_flag", type="Flag",
318  doc="whether the reference shape is marked as bad")
319  else:
320  self.flagKey = None
321 
322  def measure(self, measRecord, exposure, refRecord, refWcs):
323  targetWcs = exposure.getWcs()
324  if not refWcs == targetWcs:
325  fullTransform = lsst.afw.image.XYTransformFromWcsPair(targetWcs, refWcs)
326  localTransform = fullTransform.linearizeForwardTransform(refRecord.getCentroid())
327  measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
328  else:
329  measRecord.set(self.shapeKey, refRecord.getShape())
330  if self.flagKey is not None:
331  measRecord.set(self.flagKey, refRecord.getShapeFlag())
XYTransformFromWcsPair: An XYTransform obtained by putting two Wcs objects &quot;back to back&quot;...
Definition: Wcs.h:432
def wrapSimpleAlgorithm
Wrap a C++ SimpleAlgorithm class into both a Python SingleFramePlugin and ForcedPlugin classes...
Definition: wrappers.py:323
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys...
Definition: aggregates.h:188