LSSTApplications  17.0+50,17.0+84,17.0+9,18.0.0+14,18.0.0+2,18.0.0+30,18.0.0+4,18.0.0+9,18.0.0-2-ge43143a+4,18.1.0-1-g0001055,18.1.0-1-g0896a44+6,18.1.0-1-g1349e88+4,18.1.0-1-g2505f39+3,18.1.0-1-g380d4d4+4,18.1.0-1-g5e4b7ea,18.1.0-1-g85f8cd4+3,18.1.0-1-g9a6769a+2,18.1.0-1-ga1a4c1a+2,18.1.0-1-gc037db8,18.1.0-1-gd55f500+1,18.1.0-1-ge10677a+3,18.1.0-10-g73b8679e+7,18.1.0-11-g311e899+3,18.1.0-12-g0d73a3591,18.1.0-12-gc95f69a+3,18.1.0-2-g000ad9a+3,18.1.0-2-g31c43f9+3,18.1.0-2-g9c63283+4,18.1.0-2-gdf0b915+4,18.1.0-2-gf03bb23,18.1.0-3-g2e29e3d+6,18.1.0-3-g52aa583+2,18.1.0-3-g9cb968e+3,18.1.0-4-gd2e8982+6,18.1.0-5-g510c42a+3,18.1.0-5-gaeab27e+4,18.1.0-6-gdda7f3e+6,18.1.0-7-g89824ecc+4,w.2019.32
LSSTDataManagementBasePackage
plugins.py
Go to the documentation of this file.
1 # This file is part of meas_base.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
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 GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 """Definition of measurement plugins.
23 
24 This module defines and registers a series of pure-Python measurement plugins
25 which have trivial implementations. It also wraps measurement algorithms
26 defined in C++ to expose them to the measurement framework.
27 """
28 
29 import numpy as np
30 
32 import lsst.geom
33 import lsst.afw.detection
34 import lsst.afw.geom
35 
36 from .pluginRegistry import register
37 from .pluginsBase import BasePlugin
38 from .baseMeasurement import BaseMeasurementPluginConfig
39 from .sfm import SingleFramePluginConfig, SingleFramePlugin
40 from .forcedMeasurement import ForcedPluginConfig, ForcedPlugin
41 from .wrappers import wrapSimpleAlgorithm, wrapTransform, GenericPlugin
42 from .transforms import SimpleCentroidTransform
43 
44 from .apertureFlux import ApertureFluxControl, ApertureFluxTransform
45 from .transform import BaseTransform
46 from .blendedness import BlendednessAlgorithm, BlendednessControl
47 from .circularApertureFlux import CircularApertureFluxAlgorithm
48 from .gaussianFlux import GaussianFluxAlgorithm, GaussianFluxControl, GaussianFluxTransform
49 from .exceptions import MeasurementError
50 from .localBackground import LocalBackgroundControl, LocalBackgroundAlgorithm, LocalBackgroundTransform
51 from .naiveCentroid import NaiveCentroidAlgorithm, NaiveCentroidControl, NaiveCentroidTransform
52 from .peakLikelihoodFlux import PeakLikelihoodFluxAlgorithm, PeakLikelihoodFluxControl, \
53  PeakLikelihoodFluxTransform
54 from .pixelFlags import PixelFlagsAlgorithm, PixelFlagsControl
55 from .psfFlux import PsfFluxAlgorithm, PsfFluxControl, PsfFluxTransform
56 from .scaledApertureFlux import ScaledApertureFluxAlgorithm, ScaledApertureFluxControl, \
57  ScaledApertureFluxTransform
58 from .sdssCentroid import SdssCentroidAlgorithm, SdssCentroidControl, SdssCentroidTransform
59 from .sdssShape import SdssShapeAlgorithm, SdssShapeControl, SdssShapeTransform
60 
61 __all__ = (
62  "SingleFrameFPPositionConfig", "SingleFrameFPPositionPlugin",
63  "SingleFrameJacobianConfig", "SingleFrameJacobianPlugin",
64  "VarianceConfig", "SingleFrameVariancePlugin", "ForcedVariancePlugin",
65  "InputCountConfig", "SingleFrameInputCountPlugin", "ForcedInputCountPlugin",
66  "SingleFramePeakCentroidConfig", "SingleFramePeakCentroidPlugin",
67  "SingleFrameSkyCoordConfig", "SingleFrameSkyCoordPlugin",
68  "ForcedPeakCentroidConfig", "ForcedPeakCentroidPlugin",
69  "ForcedTransformedCentroidConfig", "ForcedTransformedCentroidPlugin",
70  "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin",
71 )
72 
73 
74 wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl,
75  TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
76  shouldApCorr=True, hasLogName=True)
77 wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl,
78  TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
79 wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl,
80  TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
81  shouldApCorr=True)
82 wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl,
83  TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
84 wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
85  TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
86 wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
87  executionOrder=BasePlugin.FLUX_ORDER)
88 wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
89  TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
90 wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
91  TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
92 
93 wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
94  TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
95 wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
96  TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
97 
98 wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
99  TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
100 
101 wrapTransform(PsfFluxTransform)
102 wrapTransform(PeakLikelihoodFluxTransform)
103 wrapTransform(GaussianFluxTransform)
104 wrapTransform(NaiveCentroidTransform)
105 wrapTransform(SdssCentroidTransform)
106 wrapTransform(SdssShapeTransform)
107 wrapTransform(ScaledApertureFluxTransform)
108 wrapTransform(ApertureFluxTransform)
109 wrapTransform(LocalBackgroundTransform)
110 
111 
113  """Configuration for the focal plane position measurment algorithm.
114  """
115 
116  pass
117 
118 
119 @register("base_FPPosition")
121  """Algorithm to calculate the position of a centroid on the focal plane.
122 
123  Parameters
124  ----------
125  config : `SingleFrameFPPositionConfig`
126  Plugin configuraion.
127  name : `str`
128  Plugin name.
129  schema : `lsst.afw.table.Schema`
130  The schema for the measurement output catalog. New fields will be
131  added to hold measurements produced by this plugin.
132  metadata : `lsst.daf.base.PropertySet`
133  Plugin metadata that will be attached to the output catalog
134  """
135 
136  ConfigClass = SingleFrameFPPositionConfig
137 
138  @classmethod
140  return cls.SHAPE_ORDER
141 
142  def __init__(self, config, name, schema, metadata):
143  SingleFramePlugin.__init__(self, config, name, schema, metadata)
144  self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
145  "mm")
146  self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
147  self.detectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag",
148  doc="Set to True if detector object is missing")
149 
150  def measure(self, measRecord, exposure):
151  det = exposure.getDetector()
152  if not det:
153  measRecord.set(self.detectorFlag, True)
154  fp = lsst.geom.Point2D(np.nan, np.nan)
155  else:
156  center = measRecord.getCentroid()
157  fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
158  measRecord.set(self.focalValue, fp)
159 
160  def fail(self, measRecord, error=None):
161  measRecord.set(self.focalFlag, True)
162 
163 
165  """Configuration for the Jacobian calculation plugin.
166  """
167 
168  pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)")
169 
170 
171 @register("base_Jacobian")
173  """Compute the Jacobian and its ratio with a nominal pixel area.
174 
175  This enables one to compare relative, rather than absolute, pixel areas.
176 
177  Parameters
178  ----------
179  config : `SingleFrameJacobianConfig`
180  Plugin configuraion.
181  name : `str`
182  Plugin name.
183  schema : `lsst.afw.table.Schema`
184  The schema for the measurement output catalog. New fields will be
185  added to hold measurements produced by this plugin.
186  metadata : `lsst.daf.base.PropertySet`
187  Plugin metadata that will be attached to the output catalog
188  """
189 
190  ConfigClass = SingleFrameJacobianConfig
191 
192  @classmethod
194  return cls.SHAPE_ORDER
195 
196  def __init__(self, config, name, schema, metadata):
197  SingleFramePlugin.__init__(self, config, name, schema, metadata)
198  self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
199  self.jacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure")
200  # Calculate one over the area of a nominal reference pixel, where area is in arcsec^2
201  self.scale = pow(self.config.pixelScale, -2)
202 
203  def measure(self, measRecord, exposure):
204  center = measRecord.getCentroid()
205  # Compute the area of a pixel at a source record's centroid, and take
206  # the ratio of that with the defined reference pixel area.
207  result = np.abs(self.scale*exposure.getWcs().linearizePixelToSky(
208  center,
209  lsst.geom.arcseconds).getLinear().computeDeterminant())
210  measRecord.set(self.jacValue, result)
211 
212  def fail(self, measRecord, error=None):
213  measRecord.set(self.jacFlag, True)
214 
215 
217  """Configuration for the variance calculation plugin.
218  """
219  scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
220  doc="Scale factor to apply to shape for aperture")
221  mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
222  default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
223 
224 
226  """Compute the median variance corresponding to a footprint.
227 
228  The aim here is to measure the background variance, rather than that of
229  the object itself. In order to achieve this, the variance is calculated
230  over an area scaled up from the shape of the input footprint.
231 
232  Parameters
233  ----------
234  config : `VarianceConfig`
235  Plugin configuraion.
236  name : `str`
237  Plugin name.
238  schema : `lsst.afw.table.Schema`
239  The schema for the measurement output catalog. New fields will be
240  added to hold measurements produced by this plugin.
241  metadata : `lsst.daf.base.PropertySet`
242  Plugin metadata that will be attached to the output catalog
243  """
244 
245  ConfigClass = VarianceConfig
246 
247  FAILURE_BAD_CENTROID = 1
248  """Denotes failures due to bad centroiding (`int`).
249  """
250 
251  FAILURE_EMPTY_FOOTPRINT = 2
252  """Denotes failures due to a lack of usable pixels (`int`).
253  """
254 
255  @classmethod
257  return BasePlugin.FLUX_ORDER
258 
259  def __init__(self, config, name, schema, metadata):
260  GenericPlugin.__init__(self, config, name, schema, metadata)
261  self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
262  self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
263  doc="Set to True when the footprint has no usable pixels")
264 
265  # Alias the badCentroid flag to that which is defined for the target
266  # of the centroid slot. We do not simply rely on the alias because
267  # that could be changed post-measurement.
268  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
269 
270  def measure(self, measRecord, exposure, center):
271  # Create an aperture and grow it by scale value defined in config to
272  # ensure there are enough pixels around the object to get decent
273  # statistics
274  if not np.all(np.isfinite(measRecord.getCentroid())):
275  raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROID)
276  aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
277  aperture.scale(self.config.scale)
278  ellipse = lsst.afw.geom.SpanSet.fromShape(aperture)
279  foot = lsst.afw.detection.Footprint(ellipse)
280  foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
281  # Filter out any pixels which have mask bits set corresponding to the
282  # planes to be excluded (defined in config.mask)
283  maskedImage = exposure.getMaskedImage()
284  pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
285  maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask)
286  logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
287  # Compute the median variance value for each pixel not excluded by the
288  # mask and write the record. Numpy median is used here instead of
289  # afw.math makeStatistics because of an issue with data types being
290  # passed into the C++ layer (DM-2379).
291  if np.any(logicalMask):
292  medVar = np.median(pixels.getVarianceArray()[logicalMask])
293  measRecord.set(self.varValue, medVar)
294  else:
295  raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
297 
298  def fail(self, measRecord, error=None):
299  # Check that we have an error object and that it is of type
300  # MeasurementError
301  if isinstance(error, MeasurementError):
302  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINT)
303  # FAILURE_BAD_CENTROID handled by alias to centroid record.
304  if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT:
305  measRecord.set(self.emptyFootprintFlag, True)
306  measRecord.set(self.varValue, np.nan)
307  GenericPlugin.fail(self, measRecord, error)
308 
309 
310 SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
311 """Single-frame version of `VariancePlugin`.
312 """
313 
314 ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance")
315 """Forced version of `VariancePlugin`.
316 """
317 
318 
320  """Configuration for the input image counting plugin.
321  """
322  pass
323 
324 
325 class InputCountPlugin(GenericPlugin):
326  """Count the number of input images which contributed to a a source.
327 
328  Parameters
329  ----------
330  config : `InputCountConfig`
331  Plugin configuraion.
332  name : `str`
333  Plugin name.
334  schema : `lsst.afw.table.Schema`
335  The schema for the measurement output catalog. New fields will be
336  added to hold measurements produced by this plugin.
337  metadata : `lsst.daf.base.PropertySet`
338  Plugin metadata that will be attached to the output catalog
339 
340  Notes
341  -----
342  Information is derived from the image's `~lsst.afw.image.CoaddInputs`.
343  Note these limitation:
344 
345  - This records the number of images which contributed to the pixel in the
346  center of the source footprint, rather than to any or all pixels in the
347  source.
348  - Clipping in the coadd is not taken into account.
349  """
350 
351  ConfigClass = InputCountConfig
352 
353  FAILURE_BAD_CENTROID = 1
354  """Denotes failures due to bad centroiding (`int`).
355  """
356 
357  FAILURE_NO_INPUTS = 2
358  """Denotes failures due to the image not having coadd inputs. (`int`)
359  """
360 
361  @classmethod
363  return BasePlugin.SHAPE_ORDER
364 
365  def __init__(self, config, name, schema, metadata):
366  GenericPlugin.__init__(self, config, name, schema, metadata)
367  self.numberKey = schema.addField(name + '_value', type="I",
368  doc="Number of images contributing at center, not including any"
369  "clipping")
370  self.noInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag",
371  doc="No coadd inputs available")
372  # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
373  # We do not simply rely on the alias because that could be changed post-measurement.
374  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
375 
376  def measure(self, measRecord, exposure, center):
377  if not exposure.getInfo().getCoaddInputs():
378  raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTS)
379  if not np.all(np.isfinite(center)):
380  raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID)
381 
382  ccds = exposure.getInfo().getCoaddInputs().ccds
383  measRecord.set(self.numberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
384 
385  def fail(self, measRecord, error=None):
386  if error is not None:
387  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTS)
388  # FAILURE_BAD_CENTROID handled by alias to centroid record.
389  if error.getFlagBit() == self.FAILURE_NO_INPUTS:
390  measRecord.set(self.noInputsFlag, True)
391  GenericPlugin.fail(self, measRecord, error)
392 
393 
394 SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount")
395 """Single-frame version of `InputCoutPlugin`.
396 """
397 
398 ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount")
399 """Forced version of `InputCoutPlugin`.
400 """
401 
402 
404  """Configuration for the single frame peak centroiding algorithm.
405  """
406  pass
407 
408 
409 @register("base_PeakCentroid")
411  """Record the highest peak in a source footprint as its centroid.
412 
413  This is of course a relatively poor measure of the true centroid of the
414  object; this algorithm is provided mostly for testing and debugging.
415 
416  Parameters
417  ----------
418  config : `SingleFramePeakCentroidConfig`
419  Plugin configuraion.
420  name : `str`
421  Plugin name.
422  schema : `lsst.afw.table.Schema`
423  The schema for the measurement output catalog. New fields will be
424  added to hold measurements produced by this plugin.
425  metadata : `lsst.daf.base.PropertySet`
426  Plugin metadata that will be attached to the output catalog
427  """
428 
429  ConfigClass = SingleFramePeakCentroidConfig
430 
431  @classmethod
433  return cls.CENTROID_ORDER
434 
435  def __init__(self, config, name, schema, metadata):
436  SingleFramePlugin.__init__(self, config, name, schema, metadata)
437  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
438  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
439  self.flag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed")
440 
441  def measure(self, measRecord, exposure):
442  peak = measRecord.getFootprint().getPeaks()[0]
443  measRecord.set(self.keyX, peak.getFx())
444  measRecord.set(self.keyY, peak.getFy())
445 
446  def fail(self, measRecord, error=None):
447  measRecord.set(self.flag, True)
448 
449  @staticmethod
451  return SimpleCentroidTransform
452 
453 
455  """Configuration for the sky coordinates algorithm.
456  """
457  pass
458 
459 
460 @register("base_SkyCoord")
462  """Record the sky position of an object based on its centroid slot and WCS.
463 
464  The position is record in the ``coord`` field, which is part of the
465  `~lsst.afw.table.SourceCatalog` minimal schema.
466 
467  Parameters
468  ----------
469  config : `SingleFrameSkyCoordConfig`
470  Plugin configuraion.
471  name : `str`
472  Plugin name.
473  schema : `lsst.afw.table.Schema`
474  The schema for the measurement output catalog. New fields will be
475  added to hold measurements produced by this plugin.
476  metadata : `lsst.daf.base.PropertySet`
477  Plugin metadata that will be attached to the output catalog
478  """
479 
480  ConfigClass = SingleFrameSkyCoordConfig
481 
482  @classmethod
484  return cls.SHAPE_ORDER
485 
486  def measure(self, measRecord, exposure):
487  # There should be a base class method for handling this exception. Put
488  # this on a later ticket. Also, there should be a python Exception of
489  # the appropriate type for this error
490  if not exposure.hasWcs():
491  raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm")
492  measRecord.updateCoord(exposure.getWcs())
493 
494  def fail(self, measRecord, error=None):
495  # Override fail() to do nothing in the case of an exception: this is
496  # not ideal, but we don't have a place to put failures because we
497  # don't allocate any fields. Should consider fixing as part of
498  # DM-1011
499  pass
500 
501 
502 class ForcedPeakCentroidConfig(ForcedPluginConfig):
503  """Configuration for the forced peak centroid algorithm.
504  """
505  pass
506 
507 
508 @register("base_PeakCentroid")
510  """Record the highest peak in a source footprint as its centroid.
511 
512  This is of course a relatively poor measure of the true centroid of the
513  object; this algorithm is provided mostly for testing and debugging.
514 
515  This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
516  the peak coordinate from the original (reference) coordinate system to the
517  coordinate system of the exposure being measured.
518 
519  Parameters
520  ----------
521  config : `ForcedPeakCentroidConfig`
522  Plugin configuraion.
523  name : `str`
524  Plugin name.
525  schemaMapper : `lsst.afw.table.SchemaMapper`
526  A mapping from reference catalog fields to output
527  catalog fields. Output fields are added to the output schema.
528  metadata : `lsst.daf.base.PropertySet`
529  Plugin metadata that will be attached to the output catalog.
530  """
531 
532  ConfigClass = ForcedPeakCentroidConfig
533 
534  @classmethod
536  return cls.CENTROID_ORDER
537 
538  def __init__(self, config, name, schemaMapper, metadata):
539  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
540  schema = schemaMapper.editOutputSchema()
541  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
542  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
543 
544  def measure(self, measRecord, exposure, refRecord, refWcs):
545  targetWcs = exposure.getWcs()
546  peak = refRecord.getFootprint().getPeaks()[0]
547  result = lsst.geom.Point2D(peak.getFx(), peak.getFy())
548  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
549  measRecord.set(self.keyX, result.getX())
550  measRecord.set(self.keyY, result.getY())
551 
552  @staticmethod
554  return SimpleCentroidTransform
555 
556 
558  """Configuration for the forced transformed centroid algorithm.
559  """
560  pass
561 
562 
563 @register("base_TransformedCentroid")
565  """Record the transformation of the reference catalog centroid.
566 
567  The centroid recorded in the reference catalog is tranformed to the
568  measurement coordinate system and stored.
569 
570  Parameters
571  ----------
572  config : `ForcedTransformedCentroidConfig`
573  Plugin configuration
574  name : `str`
575  Plugin name
576  schemaMapper : `lsst.afw.table.SchemaMapper`
577  A mapping from reference catalog fields to output
578  catalog fields. Output fields are added to the output schema.
579  metadata : `lsst.daf.base.PropertySet`
580  Plugin metadata that will be attached to the output catalog.
581 
582  Notes
583  -----
584  This is used as the slot centroid by default in forced measurement,
585  allowing subsequent measurements to simply refer to the slot value just as
586  they would in single-frame measurement.
587  """
588 
589  ConfigClass = ForcedTransformedCentroidConfig
590 
591  @classmethod
593  return cls.CENTROID_ORDER
594 
595  def __init__(self, config, name, schemaMapper, metadata):
596  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
597  schema = schemaMapper.editOutputSchema()
598  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
599  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
600  units="pixel")
601  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
602  units="pixel")
604  # Because we're taking the reference position as given, we don't bother transforming its
605  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
606  # the flag field, if it exists.
607  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
608  self.flagKey = schema.addField(name + "_flag", type="Flag",
609  doc="whether the reference centroid is marked as bad")
610  else:
611  self.flagKey = None
612 
613  def measure(self, measRecord, exposure, refRecord, refWcs):
614  targetWcs = exposure.getWcs()
615  if not refWcs == targetWcs:
616  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
617  measRecord.set(self.centroidKey, targetPos)
618  else:
619  measRecord.set(self.centroidKey, refRecord.getCentroid())
620  if self.flagKey is not None:
621  measRecord.set(self.flagKey, refRecord.getCentroidFlag())
622 
623 
625  """Configuration for the forced transformed shape algorithm.
626  """
627  pass
628 
629 
630 @register("base_TransformedShape")
632  """Record the transformation of the reference catalog shape.
633 
634  The shape recorded in the reference catalog is tranformed to the
635  measurement coordinate system and stored.
636 
637  Parameters
638  ----------
639  config : `ForcedTransformedShapeConfig`
640  Plugin configuration
641  name : `str`
642  Plugin name
643  schemaMapper : `lsst.afw.table.SchemaMapper`
644  A mapping from reference catalog fields to output
645  catalog fields. Output fields are added to the output schema.
646  metadata : `lsst.daf.base.PropertySet`
647  Plugin metadata that will be attached to the output catalog.
648 
649  Notes
650  -----
651  This is used as the slot shape by default in forced measurement, allowing
652  subsequent measurements to simply refer to the slot value just as they
653  would in single-frame measurement.
654  """
655 
656  ConfigClass = ForcedTransformedShapeConfig
657 
658  @classmethod
660  return cls.SHAPE_ORDER
661 
662  def __init__(self, config, name, schemaMapper, metadata):
663  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
664  schema = schemaMapper.editOutputSchema()
665  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
666  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
667  units="pixel^2")
668  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
669  units="pixel^2")
670  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
671  units="pixel^2")
672  self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
673  # Because we're taking the reference position as given, we don't bother transforming its
674  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
675  # the flag field, if it exists.
676  if "slot_Shape_flag" in schemaMapper.getInputSchema():
677  self.flagKey = schema.addField(name + "_flag", type="Flag",
678  doc="whether the reference shape is marked as bad")
679  else:
680  self.flagKey = None
681 
682  def measure(self, measRecord, exposure, refRecord, refWcs):
683  targetWcs = exposure.getWcs()
684  if not refWcs == targetWcs:
685  fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs)
686  localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid())
687  measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
688  else:
689  measRecord.set(self.shapeKey, refRecord.getShape())
690  if self.flagKey is not None:
691  measRecord.set(self.flagKey, refRecord.getShapeFlag())
def measure(self, measRecord, exposure)
Definition: plugins.py:441
def fail(self, measRecord, error=None)
Definition: plugins.py:494
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)
Approximate a Transform by its local linearization.
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:142
def fail(self, measRecord, error=None)
Definition: plugins.py:446
static std::shared_ptr< geom::SpanSet > fromShape(int r, Stencil s=Stencil::CIRCLE, lsst::geom::Point2I offset=lsst::geom::Point2I())
Factory function for creating SpanSets from a Stencil.
Definition: SpanSet.cc:689
def fail(self, measRecord, error=None)
Definition: plugins.py:212
daf::base::PropertySet * set
Definition: fits.cc:884
def measure(self, measRecord, exposure, center)
Definition: plugins.py:376
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:196
def measure(self, measRecord, exposure)
Definition: plugins.py:150
def fail(self, measRecord, error=None)
Definition: plugins.py:298
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:538
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:365
An ellipse defined by an arbitrary BaseCore and a center point.
Definition: Ellipse.h:51
def fail(self, measRecord, error=None)
Definition: plugins.py:160
Class to describe the properties of a detected object from an image.
Definition: Footprint.h:62
def measure(self, measRecord, exposure)
Definition: plugins.py:203
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
Definition: SkyWcs.cc:151
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:613
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:595
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:662
def wrapTransform(transformClass, hasLogName=False)
Definition: wrappers.py:444
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:544
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:435
def measure(self, measRecord, exposure)
Definition: plugins.py:486
def measure(self, measRecord, exposure, center)
Definition: plugins.py:270
def fail(self, measRecord, error=None)
Definition: plugins.py:385
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys...
Definition: aggregates.h:282
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:682
def wrapSimpleAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, hasMeasureN=False, hasLogName=False, kwds)
Definition: wrappers.py:380
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:259
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...