lsst.meas.base  19.0.0-3-g27e4659+13
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  "EvaluateLocalPhotoCalibPlugin", "EvaluateLocalPhotoCalibPluginConfig",
72  "EvaluateLocalWcsPlugin", "EvaluateLocalWcsPluginConfig",
73 )
74 
75 
76 wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl,
77  TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
78  shouldApCorr=True, hasLogName=True)
79 wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl,
80  TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
81 wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl,
82  TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
83  shouldApCorr=True)
84 wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl,
85  TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
86 wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
87  TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
88 wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
89  executionOrder=BasePlugin.FLUX_ORDER)
90 wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
91  TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
92 wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
93  TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
94 
95 wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
96  TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
97 wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
98  TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
99 
100 wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
101  TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
102 
103 wrapTransform(PsfFluxTransform)
104 wrapTransform(PeakLikelihoodFluxTransform)
105 wrapTransform(GaussianFluxTransform)
106 wrapTransform(NaiveCentroidTransform)
107 wrapTransform(SdssCentroidTransform)
108 wrapTransform(SdssShapeTransform)
109 wrapTransform(ScaledApertureFluxTransform)
110 wrapTransform(ApertureFluxTransform)
111 wrapTransform(LocalBackgroundTransform)
112 
113 
115  """Configuration for the focal plane position measurment algorithm.
116  """
117 
118  pass
119 
120 
121 @register("base_FPPosition")
123  """Algorithm to calculate the position of a centroid on the focal plane.
124 
125  Parameters
126  ----------
127  config : `SingleFrameFPPositionConfig`
128  Plugin configuraion.
129  name : `str`
130  Plugin name.
131  schema : `lsst.afw.table.Schema`
132  The schema for the measurement output catalog. New fields will be
133  added to hold measurements produced by this plugin.
134  metadata : `lsst.daf.base.PropertySet`
135  Plugin metadata that will be attached to the output catalog
136  """
137 
138  ConfigClass = SingleFrameFPPositionConfig
139 
140  @classmethod
142  return cls.SHAPE_ORDER
143 
144  def __init__(self, config, name, schema, metadata):
145  SingleFramePlugin.__init__(self, config, name, schema, metadata)
146  self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
147  "mm")
148  self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
149  self.detectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag",
150  doc="Set to True if detector object is missing")
151 
152  def measure(self, measRecord, exposure):
153  det = exposure.getDetector()
154  if not det:
155  measRecord.set(self.detectorFlag, True)
156  fp = lsst.geom.Point2D(np.nan, np.nan)
157  else:
158  center = measRecord.getCentroid()
159  fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
160  measRecord.set(self.focalValue, fp)
161 
162  def fail(self, measRecord, error=None):
163  measRecord.set(self.focalFlag, True)
164 
165 
167  """Configuration for the Jacobian calculation plugin.
168  """
169 
170  pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)")
171 
172 
173 @register("base_Jacobian")
175  """Compute the Jacobian and its ratio with a nominal pixel area.
176 
177  This enables one to compare relative, rather than absolute, pixel areas.
178 
179  Parameters
180  ----------
181  config : `SingleFrameJacobianConfig`
182  Plugin configuraion.
183  name : `str`
184  Plugin name.
185  schema : `lsst.afw.table.Schema`
186  The schema for the measurement output catalog. New fields will be
187  added to hold measurements produced by this plugin.
188  metadata : `lsst.daf.base.PropertySet`
189  Plugin metadata that will be attached to the output catalog
190  """
191 
192  ConfigClass = SingleFrameJacobianConfig
193 
194  @classmethod
196  return cls.SHAPE_ORDER
197 
198  def __init__(self, config, name, schema, metadata):
199  SingleFramePlugin.__init__(self, config, name, schema, metadata)
200  self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
201  self.jacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure")
202  # Calculate one over the area of a nominal reference pixel, where area is in arcsec^2
203  self.scale = pow(self.config.pixelScale, -2)
204 
205  def measure(self, measRecord, exposure):
206  center = measRecord.getCentroid()
207  # Compute the area of a pixel at a source record's centroid, and take
208  # the ratio of that with the defined reference pixel area.
209  result = np.abs(self.scale*exposure.getWcs().linearizePixelToSky(
210  center,
211  lsst.geom.arcseconds).getLinear().computeDeterminant())
212  measRecord.set(self.jacValue, result)
213 
214  def fail(self, measRecord, error=None):
215  measRecord.set(self.jacFlag, True)
216 
217 
219  """Configuration for the variance calculation plugin.
220  """
221  scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
222  doc="Scale factor to apply to shape for aperture")
223  mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
224  default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
225 
226 
228  """Compute the median variance corresponding to a footprint.
229 
230  The aim here is to measure the background variance, rather than that of
231  the object itself. In order to achieve this, the variance is calculated
232  over an area scaled up from the shape of the input footprint.
233 
234  Parameters
235  ----------
236  config : `VarianceConfig`
237  Plugin configuraion.
238  name : `str`
239  Plugin name.
240  schema : `lsst.afw.table.Schema`
241  The schema for the measurement output catalog. New fields will be
242  added to hold measurements produced by this plugin.
243  metadata : `lsst.daf.base.PropertySet`
244  Plugin metadata that will be attached to the output catalog
245  """
246 
247  ConfigClass = VarianceConfig
248 
249  FAILURE_BAD_CENTROID = 1
250  """Denotes failures due to bad centroiding (`int`).
251  """
252 
253  FAILURE_EMPTY_FOOTPRINT = 2
254  """Denotes failures due to a lack of usable pixels (`int`).
255  """
256 
257  @classmethod
259  return BasePlugin.FLUX_ORDER
260 
261  def __init__(self, config, name, schema, metadata):
262  GenericPlugin.__init__(self, config, name, schema, metadata)
263  self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
264  self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
265  doc="Set to True when the footprint has no usable pixels")
266 
267  # Alias the badCentroid flag to that which is defined for the target
268  # of the centroid slot. We do not simply rely on the alias because
269  # that could be changed post-measurement.
270  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
271 
272  def measure(self, measRecord, exposure, center):
273  # Create an aperture and grow it by scale value defined in config to
274  # ensure there are enough pixels around the object to get decent
275  # statistics
276  if not np.all(np.isfinite(measRecord.getCentroid())):
277  raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROID)
278  aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
279  aperture.scale(self.config.scale)
280  ellipse = lsst.afw.geom.SpanSet.fromShape(aperture)
281  foot = lsst.afw.detection.Footprint(ellipse)
282  foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
283  # Filter out any pixels which have mask bits set corresponding to the
284  # planes to be excluded (defined in config.mask)
285  maskedImage = exposure.getMaskedImage()
286  pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
287  maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask)
288  logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
289  # Compute the median variance value for each pixel not excluded by the
290  # mask and write the record. Numpy median is used here instead of
291  # afw.math makeStatistics because of an issue with data types being
292  # passed into the C++ layer (DM-2379).
293  if np.any(logicalMask):
294  medVar = np.median(pixels.getVarianceArray()[logicalMask])
295  measRecord.set(self.varValue, medVar)
296  else:
297  raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
299 
300  def fail(self, measRecord, error=None):
301  # Check that we have an error object and that it is of type
302  # MeasurementError
303  if isinstance(error, MeasurementError):
304  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINT)
305  # FAILURE_BAD_CENTROID handled by alias to centroid record.
306  if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT:
307  measRecord.set(self.emptyFootprintFlag, True)
308  measRecord.set(self.varValue, np.nan)
309  GenericPlugin.fail(self, measRecord, error)
310 
311 
312 SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
313 """Single-frame version of `VariancePlugin`.
314 """
315 
316 ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance")
317 """Forced version of `VariancePlugin`.
318 """
319 
320 
322  """Configuration for the input image counting plugin.
323  """
324  pass
325 
326 
327 class InputCountPlugin(GenericPlugin):
328  """Count the number of input images which contributed to a a source.
329 
330  Parameters
331  ----------
332  config : `InputCountConfig`
333  Plugin configuraion.
334  name : `str`
335  Plugin name.
336  schema : `lsst.afw.table.Schema`
337  The schema for the measurement output catalog. New fields will be
338  added to hold measurements produced by this plugin.
339  metadata : `lsst.daf.base.PropertySet`
340  Plugin metadata that will be attached to the output catalog
341 
342  Notes
343  -----
344  Information is derived from the image's `~lsst.afw.image.CoaddInputs`.
345  Note these limitation:
346 
347  - This records the number of images which contributed to the pixel in the
348  center of the source footprint, rather than to any or all pixels in the
349  source.
350  - Clipping in the coadd is not taken into account.
351  """
352 
353  ConfigClass = InputCountConfig
354 
355  FAILURE_BAD_CENTROID = 1
356  """Denotes failures due to bad centroiding (`int`).
357  """
358 
359  FAILURE_NO_INPUTS = 2
360  """Denotes failures due to the image not having coadd inputs. (`int`)
361  """
362 
363  @classmethod
365  return BasePlugin.SHAPE_ORDER
366 
367  def __init__(self, config, name, schema, metadata):
368  GenericPlugin.__init__(self, config, name, schema, metadata)
369  self.numberKey = schema.addField(name + '_value', type="I",
370  doc="Number of images contributing at center, not including any"
371  "clipping")
372  self.noInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag",
373  doc="No coadd inputs available")
374  # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
375  # We do not simply rely on the alias because that could be changed post-measurement.
376  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
377 
378  def measure(self, measRecord, exposure, center):
379  if not exposure.getInfo().getCoaddInputs():
380  raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTS)
381  if not np.all(np.isfinite(center)):
382  raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID)
383 
384  ccds = exposure.getInfo().getCoaddInputs().ccds
385  measRecord.set(self.numberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
386 
387  def fail(self, measRecord, error=None):
388  if error is not None:
389  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTS)
390  # FAILURE_BAD_CENTROID handled by alias to centroid record.
391  if error.getFlagBit() == self.FAILURE_NO_INPUTS:
392  measRecord.set(self.noInputsFlag, True)
393  GenericPlugin.fail(self, measRecord, error)
394 
395 
396 SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount")
397 """Single-frame version of `InputCoutPlugin`.
398 """
399 
400 ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount")
401 """Forced version of `InputCoutPlugin`.
402 """
403 
404 
406  """Configuration for the variance calculation plugin.
407  """
408  pass
409 
410 
411 class EvaluateLocalPhotoCalibPlugin(GenericPlugin):
412  """Evaluate the local value of the Photometric Calibration in the exposure.
413 
414  The aim is to store the local calib value within the catalog for later
415  use in the Science Data Model functors.
416  """
417  ConfigClass = EvaluateLocalPhotoCalibPluginConfig
418 
419  @classmethod
421  return BasePlugin.FLUX_ORDER
422 
423  def __init__(self, config, name, schema, metadata):
424  GenericPlugin.__init__(self, config, name, schema, metadata)
425  self.photoKey = schema.addField(
426  name,
427  type="D",
428  doc="Local approximation of the PhotoCalib calibration factor at "
429  "the location of the src.")
430  self.photoErrKey = schema.addField(
431  "%sErr" % name,
432  type="D",
433  doc="Error on the local approximation of the PhotoCalib "
434  "calibration factor at the location of the src.")
435 
436  def measure(self, measRecord, exposure, center):
437 
438  photoCalib = exposure.getPhotoCalib()
439  calib = photoCalib.getLocalCalibration(center)
440  measRecord.set(self.photoKey, calib)
441 
442  calibErr = photoCalib.getCalibrationErr()
443  measRecord.set(self.photoErrKey, calibErr)
444 
445 
446 SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
447  "base_LocalPhotoCalib")
448 """Single-frame version of `EvaluatePhotoCalibPlugin`.
449 """
450 
451 ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin(
452  "base_LocalPhotoCalib")
453 """Forced version of `EvaluatePhotoCalibPlugin`.
454 """
455 
456 
458  """Configuration for the variance calculation plugin.
459  """
460  pass
461 
462 
463 class EvaluateLocalWcsPlugin(GenericPlugin):
464  """Evaluate the local, linear approximation of the Wcs.
465 
466  The aim is to store the local calib value within the catalog for later
467  use in the Science Data Model functors.
468  """
469  ConfigClass = EvaluateLocalWcsPluginConfig
470 
471  @classmethod
473  return BasePlugin.FLUX_ORDER
474 
475  def __init__(self, config, name, schema, metadata):
476  GenericPlugin.__init__(self, config, name, schema, metadata)
477  self.cdMatrix11Key = schema.addField(
478  "%s_CDMatrix_1_1" % name,
479  type="D",
480  doc="(1, 1) element of the CDMatrix for the linear approximation "
481  "of the WCS at the src location.")
482  self.cdMatrix12Key = schema.addField(
483  "%s_CDMatrix_1_2" % name,
484  type="D",
485  doc="(1, 2) element of the CDMatrix for the linear approximation "
486  "of the WCS at the src location.")
487  self.cdMatrix21Key = schema.addField(
488  "%s_CDMatrix_2_1" % name,
489  type="D",
490  doc="(2, 1) element of the CDMatrix for the linear approximation "
491  "of the WCS at the src location.")
492  self.cdMatrix22Key = schema.addField(
493  "%s_CDMatrix_2_2" % name,
494  type="D",
495  doc="(2, 2) element of the CDMatrix for the linear approximation "
496  "of the WCS at the src location.")
497 
498  def measure(self, measRecord, exposure, center):
499  localCDMatrix = exposure.getWcs().getCdMatrix(center)
500  measRecord.set(self.cdMatrix11Key, localCDMatrix[0, 0])
501  measRecord.set(self.cdMatrix12Key, localCDMatrix[0, 1])
502  measRecord.set(self.cdMatrix21Key, localCDMatrix[1, 0])
503  measRecord.set(self.cdMatrix22Key, localCDMatrix[1, 1])
504 
505 
506 SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs")
507 """Single-frame version of `EvaluateLocalWcsPlugin`.
508 """
509 
510 ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin("base_LocalWcs")
511 """Forced version of `EvaluateLocalWcsPlugin`.
512 """
513 
514 
516  """Configuration for the single frame peak centroiding algorithm.
517  """
518  pass
519 
520 
521 @register("base_PeakCentroid")
523  """Record the highest peak in a source footprint as its centroid.
524 
525  This is of course a relatively poor measure of the true centroid of the
526  object; this algorithm is provided mostly for testing and debugging.
527 
528  Parameters
529  ----------
530  config : `SingleFramePeakCentroidConfig`
531  Plugin configuraion.
532  name : `str`
533  Plugin name.
534  schema : `lsst.afw.table.Schema`
535  The schema for the measurement output catalog. New fields will be
536  added to hold measurements produced by this plugin.
537  metadata : `lsst.daf.base.PropertySet`
538  Plugin metadata that will be attached to the output catalog
539  """
540 
541  ConfigClass = SingleFramePeakCentroidConfig
542 
543  @classmethod
545  return cls.CENTROID_ORDER
546 
547  def __init__(self, config, name, schema, metadata):
548  SingleFramePlugin.__init__(self, config, name, schema, metadata)
549  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
550  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
551  self.flag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed")
552 
553  def measure(self, measRecord, exposure):
554  peak = measRecord.getFootprint().getPeaks()[0]
555  measRecord.set(self.keyX, peak.getFx())
556  measRecord.set(self.keyY, peak.getFy())
557 
558  def fail(self, measRecord, error=None):
559  measRecord.set(self.flag, True)
560 
561  @staticmethod
563  return SimpleCentroidTransform
564 
565 
567  """Configuration for the sky coordinates algorithm.
568  """
569  pass
570 
571 
572 @register("base_SkyCoord")
574  """Record the sky position of an object based on its centroid slot and WCS.
575 
576  The position is record in the ``coord`` field, which is part of the
577  `~lsst.afw.table.SourceCatalog` minimal schema.
578 
579  Parameters
580  ----------
581  config : `SingleFrameSkyCoordConfig`
582  Plugin configuraion.
583  name : `str`
584  Plugin name.
585  schema : `lsst.afw.table.Schema`
586  The schema for the measurement output catalog. New fields will be
587  added to hold measurements produced by this plugin.
588  metadata : `lsst.daf.base.PropertySet`
589  Plugin metadata that will be attached to the output catalog
590  """
591 
592  ConfigClass = SingleFrameSkyCoordConfig
593 
594  @classmethod
596  return cls.SHAPE_ORDER
597 
598  def measure(self, measRecord, exposure):
599  # There should be a base class method for handling this exception. Put
600  # this on a later ticket. Also, there should be a python Exception of
601  # the appropriate type for this error
602  if not exposure.hasWcs():
603  raise Exception("Wcs not attached to exposure. Required for " + self.name + " algorithm")
604  measRecord.updateCoord(exposure.getWcs())
605 
606  def fail(self, measRecord, error=None):
607  # Override fail() to do nothing in the case of an exception: this is
608  # not ideal, but we don't have a place to put failures because we
609  # don't allocate any fields. Should consider fixing as part of
610  # DM-1011
611  pass
612 
613 
614 class ForcedPeakCentroidConfig(ForcedPluginConfig):
615  """Configuration for the forced peak centroid algorithm.
616  """
617  pass
618 
619 
620 @register("base_PeakCentroid")
622  """Record the highest peak in a source footprint as its centroid.
623 
624  This is of course a relatively poor measure of the true centroid of the
625  object; this algorithm is provided mostly for testing and debugging.
626 
627  This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
628  the peak coordinate from the original (reference) coordinate system to the
629  coordinate system of the exposure being measured.
630 
631  Parameters
632  ----------
633  config : `ForcedPeakCentroidConfig`
634  Plugin configuraion.
635  name : `str`
636  Plugin name.
637  schemaMapper : `lsst.afw.table.SchemaMapper`
638  A mapping from reference catalog fields to output
639  catalog fields. Output fields are added to the output schema.
640  metadata : `lsst.daf.base.PropertySet`
641  Plugin metadata that will be attached to the output catalog.
642  """
643 
644  ConfigClass = ForcedPeakCentroidConfig
645 
646  @classmethod
648  return cls.CENTROID_ORDER
649 
650  def __init__(self, config, name, schemaMapper, metadata):
651  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
652  schema = schemaMapper.editOutputSchema()
653  self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
654  self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
655 
656  def measure(self, measRecord, exposure, refRecord, refWcs):
657  targetWcs = exposure.getWcs()
658  peak = refRecord.getFootprint().getPeaks()[0]
659  result = lsst.geom.Point2D(peak.getFx(), peak.getFy())
660  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
661  measRecord.set(self.keyX, result.getX())
662  measRecord.set(self.keyY, result.getY())
663 
664  @staticmethod
666  return SimpleCentroidTransform
667 
668 
670  """Configuration for the forced transformed centroid algorithm.
671  """
672  pass
673 
674 
675 @register("base_TransformedCentroid")
677  """Record the transformation of the reference catalog centroid.
678 
679  The centroid recorded in the reference catalog is tranformed to the
680  measurement coordinate system and stored.
681 
682  Parameters
683  ----------
684  config : `ForcedTransformedCentroidConfig`
685  Plugin configuration
686  name : `str`
687  Plugin name
688  schemaMapper : `lsst.afw.table.SchemaMapper`
689  A mapping from reference catalog fields to output
690  catalog fields. Output fields are added to the output schema.
691  metadata : `lsst.daf.base.PropertySet`
692  Plugin metadata that will be attached to the output catalog.
693 
694  Notes
695  -----
696  This is used as the slot centroid by default in forced measurement,
697  allowing subsequent measurements to simply refer to the slot value just as
698  they would in single-frame measurement.
699  """
700 
701  ConfigClass = ForcedTransformedCentroidConfig
702 
703  @classmethod
705  return cls.CENTROID_ORDER
706 
707  def __init__(self, config, name, schemaMapper, metadata):
708  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
709  schema = schemaMapper.editOutputSchema()
710  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
711  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
712  units="pixel")
713  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
714  units="pixel")
716  # Because we're taking the reference position as given, we don't bother transforming its
717  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
718  # the flag field, if it exists.
719  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
720  self.flagKey = schema.addField(name + "_flag", type="Flag",
721  doc="whether the reference centroid is marked as bad")
722  else:
723  self.flagKey = None
724 
725  def measure(self, measRecord, exposure, refRecord, refWcs):
726  targetWcs = exposure.getWcs()
727  if not refWcs == targetWcs:
728  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
729  measRecord.set(self.centroidKey, targetPos)
730  else:
731  measRecord.set(self.centroidKey, refRecord.getCentroid())
732  if self.flagKey is not None:
733  measRecord.set(self.flagKey, refRecord.getCentroidFlag())
734 
735 
737  """Configuration for the forced transformed shape algorithm.
738  """
739  pass
740 
741 
742 @register("base_TransformedShape")
744  """Record the transformation of the reference catalog shape.
745 
746  The shape recorded in the reference catalog is tranformed to the
747  measurement coordinate system and stored.
748 
749  Parameters
750  ----------
751  config : `ForcedTransformedShapeConfig`
752  Plugin configuration
753  name : `str`
754  Plugin name
755  schemaMapper : `lsst.afw.table.SchemaMapper`
756  A mapping from reference catalog fields to output
757  catalog fields. Output fields are added to the output schema.
758  metadata : `lsst.daf.base.PropertySet`
759  Plugin metadata that will be attached to the output catalog.
760 
761  Notes
762  -----
763  This is used as the slot shape by default in forced measurement, allowing
764  subsequent measurements to simply refer to the slot value just as they
765  would in single-frame measurement.
766  """
767 
768  ConfigClass = ForcedTransformedShapeConfig
769 
770  @classmethod
772  return cls.SHAPE_ORDER
773 
774  def __init__(self, config, name, schemaMapper, metadata):
775  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
776  schema = schemaMapper.editOutputSchema()
777  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
778  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
779  units="pixel^2")
780  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
781  units="pixel^2")
782  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
783  units="pixel^2")
784  self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
785  # Because we're taking the reference position as given, we don't bother transforming its
786  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
787  # the flag field, if it exists.
788  if "slot_Shape_flag" in schemaMapper.getInputSchema():
789  self.flagKey = schema.addField(name + "_flag", type="Flag",
790  doc="whether the reference shape is marked as bad")
791  else:
792  self.flagKey = None
793 
794  def measure(self, measRecord, exposure, refRecord, refWcs):
795  targetWcs = exposure.getWcs()
796  if not refWcs == targetWcs:
797  fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs)
798  localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid())
799  measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
800  else:
801  measRecord.set(self.shapeKey, refRecord.getShape())
802  if self.flagKey is not None:
803  measRecord.set(self.flagKey, refRecord.getShapeFlag())
def measure(self, measRecord, exposure)
Definition: plugins.py:553
def measure(self, measRecord, exposure, center)
Definition: plugins.py:498
def fail(self, measRecord, error=None)
Definition: plugins.py:606
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:144
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:423
def fail(self, measRecord, error=None)
Definition: plugins.py:558
static std::shared_ptr< geom::SpanSet > fromShape(int r, Stencil s=Stencil::CIRCLE, lsst::geom::Point2I offset=lsst::geom::Point2I())
def fail(self, measRecord, error=None)
Definition: plugins.py:214
def measure(self, measRecord, exposure, center)
Definition: plugins.py:378
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:198
def measure(self, measRecord, exposure)
Definition: plugins.py:152
def fail(self, measRecord, error=None)
Definition: plugins.py:300
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:650
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:367
def fail(self, measRecord, error=None)
Definition: plugins.py:162
def measure(self, measRecord, exposure)
Definition: plugins.py:205
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:475
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:725
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:707
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:774
def wrapTransform(transformClass, hasLogName=False)
Definition: wrappers.py:444
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:656
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:547
def measure(self, measRecord, exposure)
Definition: plugins.py:598
def measure(self, measRecord, exposure, center)
Definition: plugins.py:272
def fail(self, measRecord, error=None)
Definition: plugins.py:387
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:794
def measure(self, measRecord, exposure, center)
Definition: plugins.py:436
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:261
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=NULL)