LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
plugins.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010, 2014 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 """
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 
39 # --- Wrapped C++ Plugins ---
40 
41 wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl)
42 wrapSimpleAlgorithm(NaiveFluxAlgorithm, Control=NaiveFluxControl)
43 wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl)
44 wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl)
45 wrapSimpleAlgorithm(SincFluxAlgorithm, Control=SincFluxControl)
46 wrapSimpleAlgorithm(GaussianCentroidAlgorithm, Control=GaussianCentroidControl, executionOrder=0.0)
47 wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl, executionOrder=0.0)
48 wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl, executionOrder=0.0)
49 wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl, executionOrder=2.0)
50 wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl, executionOrder=1.0)
51 
52 wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl)
53 
54 # --- Single-Frame Measurement Plugins ---
55 
56 class SingleFramePeakCentroidConfig(SingleFramePluginConfig):
57 
58  def setDefaults(self):
59  SingleFramePluginConfig.setDefaults(self)
60  self.executionOrder = 0.0
61 
62 @register("base_PeakCentroid")
63 class SingleFramePeakCentroidPlugin(SingleFramePlugin):
64  """
65  A centroid algorithm that simply uses the first (i.e. highest) Peak in the Source's
66  Footprint as the centroid. This is of course a relatively poor measure of the true
67  centroid of the object; this algorithm is provided mostly for testing and debugging.
68  """
69 
70  ConfigClass = SingleFramePeakCentroidConfig
71 
72  def __init__(self, config, name, schema, metadata):
73  SingleFramePlugin.__init__(self, config, name, schema, metadata)
74  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixels")
75  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixels")
76 
77  def measure(self, measRecord, exposure):
78  peak = measRecord.getFootprint().getPeaks()[0]
79  measRecord.set(self.keyX, peak.getFx())
80  measRecord.set(self.keyY, peak.getFy())
81 
82 
83 class SingleFrameSkyCoordConfig(SingleFramePluginConfig):
84 
85  def setDefaults(self):
86  SingleFramePluginConfig.setDefaults(self)
87  self.executionOrder = 5.0
88 
89 @register("base_SkyCoord")
90 class SingleFrameSkyCoordPlugin(SingleFramePlugin):
91  """
92  A measurement plugin that sets the "coord" field (part of the Source minimal schema)
93  using the slot centroid and the Wcs attached to the Exposure.
94  """
95  ConfigClass = SingleFrameSkyCoordConfig
96 
97  def measure(self, measRecord, exposure):
98  # there should be a base class method for handling this exception. Put this on a later ticket
99  # Also, there should be a python Exception of the appropriate type for this error
100  if not exposure.hasWcs():
101  raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm")
102  measRecord.updateCoord(exposure.getWcs())
103 
104  def fail(self, measRecord, error=None):
105  # Override fail() to do nothing in the case of an exception: this is not ideal,
106  # but we don't have a place to put failures because we don't allocate any fields.
107  # Should consider fixing as part of DM-1011
108  pass
109 
110 class SingleFrameClassificationConfig(SingleFramePluginConfig):
111 
112  fluxRatio = lsst.pex.config.Field(dtype=float, default=.925, optional=True,
113  doc="critical ratio of model to psf flux")
114  modelErrFactor = lsst.pex.config.Field(dtype=float, default=0.0, optional=True,
115  doc="correction factor for modelFlux error")
116  psfErrFactor = lsst.pex.config.Field(dtype=float, default=0.0, optional=True,
117  doc="correction factor for psfFlux error")
118  def setDefaults(self):
119  SingleFramePluginConfig.setDefaults(self)
120  self.executionOrder = 5.0
121 
122 @register("base_ClassificationExtendedness")
123 class SingleFrameClassificationPlugin(SingleFramePlugin):
124  """
125  A binary measure of the extendedness of a source, based a simple cut on the ratio of the
126  PSF flux to the model flux.
127 
128  Because the fluxes on which this algorithm is based are slot measurements, they can be provided
129  by different algorithms, and the "fluxRatio" threshold used by this algorithm should generally
130  be set differently for different algorithms. To do this, plot the difference between the PSF
131  magnitude and the model magnitude vs. the PSF magnitude, and look for where the cloud of galaxies
132  begins.
133  """
134  ConfigClass = SingleFrameClassificationConfig
135 
136  def __init__(self, config, name, schema, metadata):
137  SingleFramePlugin.__init__(self, config, name, schema, metadata)
138  self.keyProbability = schema.addField(name + "_value", type="D",
139  doc="Set to 1 for extended sources, 0 for point sources.")
140 
141  def measure(self, measRecord, exposure):
142  modelFlux = measRecord.getModelFlux()
143  modelFluxErr = measRecord.getModelFluxErr()
144  psfFlux = measRecord.getPsfFlux()
145  psfFluxErr = measRecord.getPsfFluxErr()
146  flux1 = self.config.fluxRatio*modelFlux + self.config.modelErrFactor*modelFluxErr
147  flux2 = psfFlux + self.config.psfErrFactor*psfFluxErr
148  if flux1 < flux2:
149  measRecord.set(self.keyProbability, 0.0)
150  else:
151  measRecord.set(self.keyProbability, 1.0);
152 
153  def fail(self, measRecord, error=None):
154  # Override fail() to do nothing in the case of an exception. We should be setting a flag
155  # instead.
156  pass
157 
158 
159 # --- Forced Plugins ---
160 
161 class ForcedPeakCentroidConfig(ForcedPluginConfig):
162 
163  def setDefaults(self):
164  ForcedPluginConfig.setDefaults(self)
165  self.executionOrder = 0.0
166 
167 @register("base_PeakCentroid")
168 class ForcedPeakCentroidPlugin(ForcedPlugin):
169  """
170  The forced peak centroid is like the SFM peak centroid plugin, except that it must transform
171  the peak coordinate from the original (reference) coordinate system to the coordinate system
172  of the exposure being measured.
173  """
174  ConfigClass = ForcedPeakCentroidConfig
175 
176  def __init__(self, config, name, schemaMapper, metadata):
177  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
178  schema = schemaMapper.editOutputSchema()
179  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixels")
180  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixels")
181 
182  def measure(self, measRecord, exposure, refRecord, refWcs):
183  targetWcs = exposure.getWcs()
184  peak = refRecord.getFootprint().getPeaks()[0]
185  result = lsst.afw.geom.Point2D(peak.getFx(), peak.getFy())
186  if not refWcs == targetWcs:
187  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
188  measRecord.set(self.keyX, result.getX())
189  measRecord.set(self.keyY, result.getY())
190 
191 
192 class ForcedTransformedCentroidConfig(ForcedPluginConfig):
193 
194  def setDefaults(self):
195  ForcedPluginConfig.setDefaults(self)
196  self.executionOrder = 0.0
197 
198 @register("base_TransformedCentroid")
200  """A centroid pseudo-algorithm for forced measurement that simply transforms the centroid
201  from the reference catalog to the measurement coordinate system. This is used as
202  the slot centroid by default in forced measurement, allowing subsequent measurements
203  to simply refer to the slot value just as they would in single-frame measurement.
204  """
205 
206  ConfigClass = ForcedTransformedCentroidConfig
207 
208  def __init__(self, config, name, schemaMapper, metadata):
209  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
210  schema = schemaMapper.editOutputSchema()
211  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
212  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
213  units="pixels")
214  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
215  units="pixels")
217  # Because we're taking the reference position as given, we don't bother transforming its
218  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
219  # the flag field, if it exists.
220  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
221  self.flagKey = schema.addField(name + "_flag", type="Flag",
222  doc="whether the reference centroid is marked as bad")
223  else:
224  self.flagKey = None
225 
226  def measure(self, measRecord, exposure, refRecord, refWcs):
227  targetWcs = exposure.getWcs()
228  if not refWcs == targetWcs:
229  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
230  measRecord.set(self.centroidKey, targetPos)
231  else:
232  measRecord.set(self.centroidKey, refRecord.getCentroid())
233  if self.flagKey is not None:
234  measRecord.set(self.flagKey, refRecord.getCentroidFlag())
235 
236 
237 class ForcedTransformedShapeConfig(ForcedPluginConfig):
238 
239  def setDefaults(self):
240  ForcedPluginConfig.setDefaults(self)
241  self.executionOrder = 1.0
242 
243 @register("base_TransformedShape")
244 class ForcedTransformedShapePlugin(ForcedPlugin):
245  """A shape pseudo-algorithm for forced measurement that simply transforms the shape
246  from the reference catalog to the measurement coordinate system. This is used as
247  the slot shape by default in forced measurement, allowing subsequent measurements
248  to simply refer to the slot value just as they would in single-frame measurement.
249  """
250 
251  ConfigClass = ForcedTransformedShapeConfig
252 
253  def __init__(self, config, name, schemaMapper, metadata):
254  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
255  schema = schemaMapper.editOutputSchema()
256  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
257  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
258  units="pixels^2")
259  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
260  units="pixels^2")
261  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
262  units="pixels^2")
263  self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
264  # Because we're taking the reference position as given, we don't bother transforming its
265  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
266  # the flag field, if it exists.
267  if "slot_Shape_flag" in schemaMapper.getInputSchema():
268  self.flagKey = schema.addField(name + "_flag", type="Flag",
269  doc="whether the reference shape is marked as bad")
270  else:
271  self.flagKey = None
272 
273  def measure(self, measRecord, exposure, refRecord, refWcs):
274  targetWcs = exposure.getWcs()
275  if not refWcs == targetWcs:
276  fullTransform = lsst.afw.image.XYTransformFromWcsPair(targetWcs, refWcs)
277  localTransform = fullTransform.linearizeForwardTransform(refRecord.getCentroid())
278  measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
279  else:
280  measRecord.set(self.shapeKey, refRecord.getShape())
281  if self.flagKey is not None:
282  measRecord.set(self.flagKey, refRecord.getShapeFlag())
XYTransformFromWcsPair: Represents an XYTransform obtained by putting two Wcs&#39;s &quot;back to back&quot;...
Definition: Wcs.h:394
def wrapSimpleAlgorithm
Wrap a C++ SimpleAlgorithm class into both a Python SingleFramePlugin and ForcedPlugin classes...
Definition: wrappers.py:270
def register
A Python decorator that registers a class, using the given name, in its base class&#39;s PluginRegistry...
Definition: base.py:155
A FunctorKey used to get or set a geom::ellipses::Quadrupole from an (xx,yy,xy) tuple of Keys...
Definition: aggregates.h:125