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