LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
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  "ForcedTransformedCentroidFromCoordConfig",
71  "ForcedTransformedCentroidFromCoordPlugin",
72  "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin",
73  "EvaluateLocalPhotoCalibPlugin", "EvaluateLocalPhotoCalibPluginConfig",
74  "EvaluateLocalWcsPlugin", "EvaluateLocalWcsPluginConfig",
75 )
76 
77 
78 wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl,
79  TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
80  shouldApCorr=True, hasLogName=True)
81 wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl,
82  TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
83 wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl,
84  TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
85  shouldApCorr=True)
86 wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl,
87  TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
88 wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
89  TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
90 wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
91  executionOrder=BasePlugin.FLUX_ORDER)
92 wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
93  TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
94 wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
95  TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
96 
97 wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
98  TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
99 wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
100  TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
101 
102 wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
103  TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
104 
105 wrapTransform(PsfFluxTransform)
106 wrapTransform(PeakLikelihoodFluxTransform)
107 wrapTransform(GaussianFluxTransform)
108 wrapTransform(NaiveCentroidTransform)
109 wrapTransform(SdssCentroidTransform)
110 wrapTransform(SdssShapeTransform)
111 wrapTransform(ScaledApertureFluxTransform)
112 wrapTransform(ApertureFluxTransform)
113 wrapTransform(LocalBackgroundTransform)
114 
115 
117  """Configuration for the focal plane position measurment algorithm.
118  """
119 
120  pass
121 
122 
123 @register("base_FPPosition")
125  """Algorithm to calculate the position of a centroid on the focal plane.
126 
127  Parameters
128  ----------
129  config : `SingleFrameFPPositionConfig`
130  Plugin configuraion.
131  name : `str`
132  Plugin name.
133  schema : `lsst.afw.table.Schema`
134  The schema for the measurement output catalog. New fields will be
135  added to hold measurements produced by this plugin.
136  metadata : `lsst.daf.base.PropertySet`
137  Plugin metadata that will be attached to the output catalog
138  """
139 
140  ConfigClass = SingleFrameFPPositionConfig
141 
142  @classmethod
144  return cls.SHAPE_ORDERSHAPE_ORDER
145 
146  def __init__(self, config, name, schema, metadata):
147  SingleFramePlugin.__init__(self, config, name, schema, metadata)
148  self.focalValuefocalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
149  "mm")
150  self.focalFlagfocalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
151  self.detectorFlagdetectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag",
152  doc="Set to True if detector object is missing")
153 
154  def measure(self, measRecord, exposure):
155  det = exposure.getDetector()
156  if not det:
157  measRecord.set(self.detectorFlagdetectorFlag, True)
158  fp = lsst.geom.Point2D(np.nan, np.nan)
159  else:
160  center = measRecord.getCentroid()
161  fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
162  measRecord.set(self.focalValuefocalValue, fp)
163 
164  def fail(self, measRecord, error=None):
165  measRecord.set(self.focalFlagfocalFlag, True)
166 
167 
169  """Configuration for the Jacobian calculation plugin.
170  """
171 
172  pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)")
173 
174 
175 @register("base_Jacobian")
177  """Compute the Jacobian and its ratio with a nominal pixel area.
178 
179  This enables one to compare relative, rather than absolute, pixel areas.
180 
181  Parameters
182  ----------
183  config : `SingleFrameJacobianConfig`
184  Plugin configuraion.
185  name : `str`
186  Plugin name.
187  schema : `lsst.afw.table.Schema`
188  The schema for the measurement output catalog. New fields will be
189  added to hold measurements produced by this plugin.
190  metadata : `lsst.daf.base.PropertySet`
191  Plugin metadata that will be attached to the output catalog
192  """
193 
194  ConfigClass = SingleFrameJacobianConfig
195 
196  @classmethod
198  return cls.SHAPE_ORDERSHAPE_ORDER
199 
200  def __init__(self, config, name, schema, metadata):
201  SingleFramePlugin.__init__(self, config, name, schema, metadata)
202  self.jacValuejacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
203  self.jacFlagjacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure")
204  # Calculate one over the area of a nominal reference pixel, where area is in arcsec^2
205  self.scalescale = pow(self.configconfig.pixelScale, -2)
206 
207  def measure(self, measRecord, exposure):
208  center = measRecord.getCentroid()
209  # Compute the area of a pixel at a source record's centroid, and take
210  # the ratio of that with the defined reference pixel area.
211  result = np.abs(self.scalescale*exposure.getWcs().linearizePixelToSky(
212  center,
213  lsst.geom.arcseconds).getLinear().computeDeterminant())
214  measRecord.set(self.jacValuejacValue, result)
215 
216  def fail(self, measRecord, error=None):
217  measRecord.set(self.jacFlagjacFlag, True)
218 
219 
221  """Configuration for the variance calculation plugin.
222  """
223  scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
224  doc="Scale factor to apply to shape for aperture")
225  mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
226  default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
227 
228 
230  """Compute the median variance corresponding to a footprint.
231 
232  The aim here is to measure the background variance, rather than that of
233  the object itself. In order to achieve this, the variance is calculated
234  over an area scaled up from the shape of the input footprint.
235 
236  Parameters
237  ----------
238  config : `VarianceConfig`
239  Plugin configuraion.
240  name : `str`
241  Plugin name.
242  schema : `lsst.afw.table.Schema`
243  The schema for the measurement output catalog. New fields will be
244  added to hold measurements produced by this plugin.
245  metadata : `lsst.daf.base.PropertySet`
246  Plugin metadata that will be attached to the output catalog
247  """
248 
249  ConfigClass = VarianceConfig
250 
251  FAILURE_BAD_CENTROID = 1
252  """Denotes failures due to bad centroiding (`int`).
253  """
254 
255  FAILURE_EMPTY_FOOTPRINT = 2
256  """Denotes failures due to a lack of usable pixels (`int`).
257  """
258 
259  @classmethod
261  return BasePlugin.FLUX_ORDER
262 
263  def __init__(self, config, name, schema, metadata):
264  GenericPlugin.__init__(self, config, name, schema, metadata)
265  self.varValuevarValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
266  self.emptyFootprintFlagemptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
267  doc="Set to True when the footprint has no usable pixels")
268 
269  # Alias the badCentroid flag to that which is defined for the target
270  # of the centroid slot. We do not simply rely on the alias because
271  # that could be changed post-measurement.
272  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
273 
274  def measure(self, measRecord, exposure, center):
275  # Create an aperture and grow it by scale value defined in config to
276  # ensure there are enough pixels around the object to get decent
277  # statistics
278  if not np.all(np.isfinite(measRecord.getCentroid())):
279  raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID)
280  aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
281  aperture.scale(self.configconfig.scale)
282  ellipse = lsst.afw.geom.SpanSet.fromShape(aperture)
283  foot = lsst.afw.detection.Footprint(ellipse)
284  foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
285  # Filter out any pixels which have mask bits set corresponding to the
286  # planes to be excluded (defined in config.mask)
287  maskedImage = exposure.getMaskedImage()
288  pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
289  maskBits = maskedImage.getMask().getPlaneBitMask(self.configconfig.mask)
290  logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
291  # Compute the median variance value for each pixel not excluded by the
292  # mask and write the record. Numpy median is used here instead of
293  # afw.math makeStatistics because of an issue with data types being
294  # passed into the C++ layer (DM-2379).
295  if np.any(logicalMask):
296  medVar = np.median(pixels.getVarianceArray()[logicalMask])
297  measRecord.set(self.varValuevarValue, medVar)
298  else:
299  raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
300  self.FAILURE_EMPTY_FOOTPRINTFAILURE_EMPTY_FOOTPRINT)
301 
302  def fail(self, measRecord, error=None):
303  # Check that we have an error object and that it is of type
304  # MeasurementError
305  if isinstance(error, MeasurementError):
306  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINTFAILURE_EMPTY_FOOTPRINT)
307  # FAILURE_BAD_CENTROID handled by alias to centroid record.
308  if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINTFAILURE_EMPTY_FOOTPRINT:
309  measRecord.set(self.emptyFootprintFlagemptyFootprintFlag, True)
310  measRecord.set(self.varValuevarValue, np.nan)
311  GenericPlugin.fail(self, measRecord, error)
312 
313 
314 SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
315 """Single-frame version of `VariancePlugin`.
316 """
317 
318 ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance")
319 """Forced version of `VariancePlugin`.
320 """
321 
322 
324  """Configuration for the input image counting plugin.
325  """
326  pass
327 
328 
329 class InputCountPlugin(GenericPlugin):
330  """Count the number of input images which contributed to a a source.
331 
332  Parameters
333  ----------
334  config : `InputCountConfig`
335  Plugin configuraion.
336  name : `str`
337  Plugin name.
338  schema : `lsst.afw.table.Schema`
339  The schema for the measurement output catalog. New fields will be
340  added to hold measurements produced by this plugin.
341  metadata : `lsst.daf.base.PropertySet`
342  Plugin metadata that will be attached to the output catalog
343 
344  Notes
345  -----
346  Information is derived from the image's `~lsst.afw.image.CoaddInputs`.
347  Note these limitation:
348 
349  - This records the number of images which contributed to the pixel in the
350  center of the source footprint, rather than to any or all pixels in the
351  source.
352  - Clipping in the coadd is not taken into account.
353  """
354 
355  ConfigClass = InputCountConfig
356 
357  FAILURE_BAD_CENTROID = 1
358  """Denotes failures due to bad centroiding (`int`).
359  """
360 
361  FAILURE_NO_INPUTS = 2
362  """Denotes failures due to the image not having coadd inputs. (`int`)
363  """
364 
365  @classmethod
367  return BasePlugin.SHAPE_ORDER
368 
369  def __init__(self, config, name, schema, metadata):
370  GenericPlugin.__init__(self, config, name, schema, metadata)
371  self.numberKeynumberKey = schema.addField(name + '_value', type="I",
372  doc="Number of images contributing at center, not including any"
373  "clipping")
374  self.noInputsFlagnoInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag",
375  doc="No coadd inputs available")
376  # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
377  # We do not simply rely on the alias because that could be changed post-measurement.
378  schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
379 
380  def measure(self, measRecord, exposure, center):
381  if not exposure.getInfo().getCoaddInputs():
382  raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTSFAILURE_NO_INPUTS)
383  if not np.all(np.isfinite(center)):
384  raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID)
385 
386  ccds = exposure.getInfo().getCoaddInputs().ccds
387  measRecord.set(self.numberKeynumberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
388 
389  def fail(self, measRecord, error=None):
390  if error is not None:
391  assert error.getFlagBit() in (self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTSFAILURE_NO_INPUTS)
392  # FAILURE_BAD_CENTROID handled by alias to centroid record.
393  if error.getFlagBit() == self.FAILURE_NO_INPUTSFAILURE_NO_INPUTS:
394  measRecord.set(self.noInputsFlagnoInputsFlag, True)
395  GenericPlugin.fail(self, measRecord, error)
396 
397 
398 SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount")
399 """Single-frame version of `InputCoutPlugin`.
400 """
401 
402 ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount")
403 """Forced version of `InputCoutPlugin`.
404 """
405 
406 
408  """Configuration for the variance calculation plugin.
409  """
410  pass
411 
412 
413 class EvaluateLocalPhotoCalibPlugin(GenericPlugin):
414  """Evaluate the local value of the Photometric Calibration in the exposure.
415 
416  The aim is to store the local calib value within the catalog for later
417  use in the Science Data Model functors.
418  """
419  ConfigClass = EvaluateLocalPhotoCalibPluginConfig
420 
421  @classmethod
423  return BasePlugin.FLUX_ORDER
424 
425  def __init__(self, config, name, schema, metadata):
426  GenericPlugin.__init__(self, config, name, schema, metadata)
427  self.photoKeyphotoKey = schema.addField(
428  name,
429  type="D",
430  doc="Local approximation of the PhotoCalib calibration factor at "
431  "the location of the src.")
432  self.photoErrKeyphotoErrKey = schema.addField(
433  "%sErr" % name,
434  type="D",
435  doc="Error on the local approximation of the PhotoCalib "
436  "calibration factor at the location of the src.")
437 
438  def measure(self, measRecord, exposure, center):
439 
440  photoCalib = exposure.getPhotoCalib()
441  calib = photoCalib.getLocalCalibration(center)
442  measRecord.set(self.photoKeyphotoKey, calib)
443 
444  calibErr = photoCalib.getCalibrationErr()
445  measRecord.set(self.photoErrKeyphotoErrKey, calibErr)
446 
447 
448 SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
449  "base_LocalPhotoCalib")
450 """Single-frame version of `EvaluatePhotoCalibPlugin`.
451 """
452 
453 ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin(
454  "base_LocalPhotoCalib")
455 """Forced version of `EvaluatePhotoCalibPlugin`.
456 """
457 
458 
460  """Configuration for the variance calculation plugin.
461  """
462  pass
463 
464 
465 class EvaluateLocalWcsPlugin(GenericPlugin):
466  """Evaluate the local, linear approximation of the Wcs.
467 
468  The aim is to store the local calib value within the catalog for later
469  use in the Science Data Model functors.
470  """
471  ConfigClass = EvaluateLocalWcsPluginConfig
472  _scale = (1.0 * lsst.geom.arcseconds).asDegrees()
473 
474  @classmethod
476  return BasePlugin.FLUX_ORDER
477 
478  def __init__(self, config, name, schema, metadata):
479  GenericPlugin.__init__(self, config, name, schema, metadata)
480  self.cdMatrix11KeycdMatrix11Key = schema.addField(
481  f"{name}_CDMatrix_1_1",
482  type="D",
483  doc="(1, 1) element of the CDMatrix for the linear approximation "
484  "of the WCS at the src location. Gives units in radians.")
485  self.cdMatrix12KeycdMatrix12Key = schema.addField(
486  f"{name}_CDMatrix_1_2",
487  type="D",
488  doc="(1, 2) element of the CDMatrix for the linear approximation "
489  "of the WCS at the src location. Gives units in radians.")
490  self.cdMatrix21KeycdMatrix21Key = schema.addField(
491  f"{name}_CDMatrix_2_1",
492  type="D",
493  doc="(2, 1) element of the CDMatrix for the linear approximation "
494  "of the WCS at the src location. Gives units in radians.")
495  self.cdMatrix22KeycdMatrix22Key = schema.addField(
496  f"{name}_CDMatrix_2_2",
497  type="D",
498  doc="(2, 2) element of the CDMatrix for the linear approximation "
499  "of the WCS at the src location. Gives units in radians.")
500 
501  def measure(self, measRecord, exposure, center):
502  wcs = exposure.getWcs()
503  localMatrix = self.makeLocalTransformMatrixmakeLocalTransformMatrix(wcs, center)
504  measRecord.set(self.cdMatrix11KeycdMatrix11Key, localMatrix[0, 0])
505  measRecord.set(self.cdMatrix12KeycdMatrix12Key, localMatrix[0, 1])
506  measRecord.set(self.cdMatrix21KeycdMatrix21Key, localMatrix[1, 0])
507  measRecord.set(self.cdMatrix22KeycdMatrix22Key, localMatrix[1, 1])
508 
509  def makeLocalTransformMatrix(self, wcs, center):
510  """Create a local, linear approximation of the wcs transformation
511  matrix.
512 
513  The approximation is created as if the center is at RA=0, DEC=0. All
514  comparing x,y coordinate are relative to the position of center. Matrix
515  is initially calculated with units arcseconds and then converted to
516  radians. This yields higher precision results due to quirks in AST.
517 
518  Parameters
519  ----------
520  wcs : `lsst.afw.geom.SkyWcs`
521  Wcs to approximate
522  center : `lsst.geom.Point2D`
523  Point at which to evaluate the LocalWcs.
524 
525  Returns
526  -------
527  localMatrix : `numpy.ndarray`
528  Matrix representation the local wcs approximation with units
529  radians.
530  """
531  skyCenter = wcs.pixelToSky(center)
532  localGnomonicWcs = lsst.afw.geom.makeSkyWcs(
533  center, skyCenter, np.diag((self._scale_scale, self._scale_scale)))
534  measurementToLocalGnomonic = wcs.getTransform().then(
535  localGnomonicWcs.getTransform().inverted()
536  )
537  localMatrix = measurementToLocalGnomonic.getJacobian(center)
538  return np.radians(localMatrix / 3600)
539 
540 
541 SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs")
542 """Single-frame version of `EvaluateLocalWcsPlugin`.
543 """
544 
545 ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin("base_LocalWcs")
546 """Forced version of `EvaluateLocalWcsPlugin`.
547 """
548 
549 
551  """Configuration for the single frame peak centroiding algorithm.
552  """
553  pass
554 
555 
556 @register("base_PeakCentroid")
558  """Record the highest peak in a source footprint as its centroid.
559 
560  This is of course a relatively poor measure of the true centroid of the
561  object; this algorithm is provided mostly for testing and debugging.
562 
563  Parameters
564  ----------
565  config : `SingleFramePeakCentroidConfig`
566  Plugin configuraion.
567  name : `str`
568  Plugin name.
569  schema : `lsst.afw.table.Schema`
570  The schema for the measurement output catalog. New fields will be
571  added to hold measurements produced by this plugin.
572  metadata : `lsst.daf.base.PropertySet`
573  Plugin metadata that will be attached to the output catalog
574  """
575 
576  ConfigClass = SingleFramePeakCentroidConfig
577 
578  @classmethod
580  return cls.CENTROID_ORDERCENTROID_ORDER
581 
582  def __init__(self, config, name, schema, metadata):
583  SingleFramePlugin.__init__(self, config, name, schema, metadata)
584  self.keyXkeyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
585  self.keyYkeyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
586  self.flagflag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed")
587 
588  def measure(self, measRecord, exposure):
589  peak = measRecord.getFootprint().getPeaks()[0]
590  measRecord.set(self.keyXkeyX, peak.getFx())
591  measRecord.set(self.keyYkeyY, peak.getFy())
592 
593  def fail(self, measRecord, error=None):
594  measRecord.set(self.flagflag, True)
595 
596  @staticmethod
598  return SimpleCentroidTransform
599 
600 
602  """Configuration for the sky coordinates algorithm.
603  """
604  pass
605 
606 
607 @register("base_SkyCoord")
609  """Record the sky position of an object based on its centroid slot and WCS.
610 
611  The position is record in the ``coord`` field, which is part of the
612  `~lsst.afw.table.SourceCatalog` minimal schema.
613 
614  Parameters
615  ----------
616  config : `SingleFrameSkyCoordConfig`
617  Plugin configuraion.
618  name : `str`
619  Plugin name.
620  schema : `lsst.afw.table.Schema`
621  The schema for the measurement output catalog. New fields will be
622  added to hold measurements produced by this plugin.
623  metadata : `lsst.daf.base.PropertySet`
624  Plugin metadata that will be attached to the output catalog
625  """
626 
627  ConfigClass = SingleFrameSkyCoordConfig
628 
629  @classmethod
631  return cls.SHAPE_ORDERSHAPE_ORDER
632 
633  def measure(self, measRecord, exposure):
634  # There should be a base class method for handling this exception. Put
635  # this on a later ticket. Also, there should be a python Exception of
636  # the appropriate type for this error
637  if not exposure.hasWcs():
638  raise RuntimeError("Wcs not attached to exposure. Required for " + self.namename + " algorithm")
639  measRecord.updateCoord(exposure.getWcs())
640 
641  def fail(self, measRecord, error=None):
642  # Override fail() to do nothing in the case of an exception: this is
643  # not ideal, but we don't have a place to put failures because we
644  # don't allocate any fields. Should consider fixing as part of
645  # DM-1011
646  pass
647 
648 
649 class ForcedPeakCentroidConfig(ForcedPluginConfig):
650  """Configuration for the forced peak centroid algorithm.
651  """
652  pass
653 
654 
655 @register("base_PeakCentroid")
657  """Record the highest peak in a source footprint as its centroid.
658 
659  This is of course a relatively poor measure of the true centroid of the
660  object; this algorithm is provided mostly for testing and debugging.
661 
662  This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
663  the peak coordinate from the original (reference) coordinate system to the
664  coordinate system of the exposure being measured.
665 
666  Parameters
667  ----------
668  config : `ForcedPeakCentroidConfig`
669  Plugin configuraion.
670  name : `str`
671  Plugin name.
672  schemaMapper : `lsst.afw.table.SchemaMapper`
673  A mapping from reference catalog fields to output
674  catalog fields. Output fields are added to the output schema.
675  metadata : `lsst.daf.base.PropertySet`
676  Plugin metadata that will be attached to the output catalog.
677  """
678 
679  ConfigClass = ForcedPeakCentroidConfig
680 
681  @classmethod
683  return cls.CENTROID_ORDERCENTROID_ORDER
684 
685  def __init__(self, config, name, schemaMapper, metadata):
686  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
687  schema = schemaMapper.editOutputSchema()
688  self.keyXkeyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
689  self.keyYkeyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
690 
691  def measure(self, measRecord, exposure, refRecord, refWcs):
692  targetWcs = exposure.getWcs()
693  peak = refRecord.getFootprint().getPeaks()[0]
694  result = lsst.geom.Point2D(peak.getFx(), peak.getFy())
695  result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
696  measRecord.set(self.keyXkeyX, result.getX())
697  measRecord.set(self.keyYkeyY, result.getY())
698 
699  @staticmethod
701  return SimpleCentroidTransform
702 
703 
705  """Configuration for the forced transformed centroid algorithm.
706  """
707  pass
708 
709 
710 @register("base_TransformedCentroid")
712  """Record the transformation of the reference catalog centroid.
713 
714  The centroid recorded in the reference catalog is tranformed to the
715  measurement coordinate system and stored.
716 
717  Parameters
718  ----------
719  config : `ForcedTransformedCentroidConfig`
720  Plugin configuration
721  name : `str`
722  Plugin name
723  schemaMapper : `lsst.afw.table.SchemaMapper`
724  A mapping from reference catalog fields to output
725  catalog fields. Output fields are added to the output schema.
726  metadata : `lsst.daf.base.PropertySet`
727  Plugin metadata that will be attached to the output catalog.
728 
729  Notes
730  -----
731  This is used as the slot centroid by default in forced measurement,
732  allowing subsequent measurements to simply refer to the slot value just as
733  they would in single-frame measurement.
734  """
735 
736  ConfigClass = ForcedTransformedCentroidConfig
737 
738  @classmethod
740  return cls.CENTROID_ORDERCENTROID_ORDER
741 
742  def __init__(self, config, name, schemaMapper, metadata):
743  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
744  schema = schemaMapper.editOutputSchema()
745  # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
746  xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
747  units="pixel")
748  yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
749  units="pixel")
750  self.centroidKeycentroidKey = lsst.afw.table.Point2DKey(xKey, yKey)
751  # Because we're taking the reference position as given, we don't bother transforming its
752  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
753  # the flag field, if it exists.
754  if "slot_Centroid_flag" in schemaMapper.getInputSchema():
755  self.flagKeyflagKey = schema.addField(name + "_flag", type="Flag",
756  doc="whether the reference centroid is marked as bad")
757  else:
758  self.flagKeyflagKey = None
759 
760  def measure(self, measRecord, exposure, refRecord, refWcs):
761  targetWcs = exposure.getWcs()
762  if not refWcs == targetWcs:
763  targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
764  measRecord.set(self.centroidKeycentroidKey, targetPos)
765  else:
766  measRecord.set(self.centroidKeycentroidKey, refRecord.getCentroid())
767  if self.flagKeyflagKey is not None:
768  measRecord.set(self.flagKeyflagKey, refRecord.getCentroidFlag())
769 
770 
772  """Configuration for the forced transformed coord algorithm.
773  """
774  pass
775 
776 
777 @register("base_TransformedCentroidFromCoord")
779  """Record the transformation of the reference catalog coord.
780 
781  The coord recorded in the reference catalog is tranformed to the
782  measurement coordinate system and stored.
783 
784  Parameters
785  ----------
786  config : `ForcedTransformedCentroidFromCoordConfig`
787  Plugin configuration
788  name : `str`
789  Plugin name
790  schemaMapper : `lsst.afw.table.SchemaMapper`
791  A mapping from reference catalog fields to output
792  catalog fields. Output fields are added to the output schema.
793  metadata : `lsst.daf.base.PropertySet`
794  Plugin metadata that will be attached to the output catalog.
795 
796  Notes
797  -----
798  This can be used as the slot centroid in forced measurement when only a
799  reference coord exist, allowing subsequent measurements to simply refer to
800  the slot value just as they would in single-frame measurement.
801  """
802 
803  ConfigClass = ForcedTransformedCentroidFromCoordConfig
804 
805  def measure(self, measRecord, exposure, refRecord, refWcs):
806  targetWcs = exposure.getWcs()
807 
808  targetPos = targetWcs.skyToPixel(refRecord.getCoord())
809  measRecord.set(self.centroidKeycentroidKey, targetPos)
810 
811  if self.flagKeyflagKey is not None:
812  measRecord.set(self.flagKeyflagKey, refRecord.getCentroidFlag())
813 
814 
816  """Configuration for the forced transformed shape algorithm.
817  """
818  pass
819 
820 
821 @register("base_TransformedShape")
823  """Record the transformation of the reference catalog shape.
824 
825  The shape recorded in the reference catalog is tranformed to the
826  measurement coordinate system and stored.
827 
828  Parameters
829  ----------
830  config : `ForcedTransformedShapeConfig`
831  Plugin configuration
832  name : `str`
833  Plugin name
834  schemaMapper : `lsst.afw.table.SchemaMapper`
835  A mapping from reference catalog fields to output
836  catalog fields. Output fields are added to the output schema.
837  metadata : `lsst.daf.base.PropertySet`
838  Plugin metadata that will be attached to the output catalog.
839 
840  Notes
841  -----
842  This is used as the slot shape by default in forced measurement, allowing
843  subsequent measurements to simply refer to the slot value just as they
844  would in single-frame measurement.
845  """
846 
847  ConfigClass = ForcedTransformedShapeConfig
848 
849  @classmethod
851  return cls.SHAPE_ORDERSHAPE_ORDER
852 
853  def __init__(self, config, name, schemaMapper, metadata):
854  ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
855  schema = schemaMapper.editOutputSchema()
856  # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
857  xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
858  units="pixel^2")
859  yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
860  units="pixel^2")
861  xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
862  units="pixel^2")
863  self.shapeKeyshapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
864  # Because we're taking the reference position as given, we don't bother transforming its
865  # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
866  # the flag field, if it exists.
867  if "slot_Shape_flag" in schemaMapper.getInputSchema():
868  self.flagKeyflagKey = schema.addField(name + "_flag", type="Flag",
869  doc="whether the reference shape is marked as bad")
870  else:
871  self.flagKeyflagKey = None
872 
873  def measure(self, measRecord, exposure, refRecord, refWcs):
874  targetWcs = exposure.getWcs()
875  if not refWcs == targetWcs:
876  fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs)
877  localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid())
878  measRecord.set(self.shapeKeyshapeKey, refRecord.getShape().transform(localTransform.getLinear()))
879  else:
880  measRecord.set(self.shapeKeyshapeKey, refRecord.getShape())
881  if self.flagKeyflagKey is not None:
882  measRecord.set(self.flagKeyflagKey, refRecord.getShapeFlag())
Class to describe the properties of a detected object from an image.
Definition: Footprint.h:63
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:688
An ellipse defined by an arbitrary BaseCore and a center point.
Definition: Ellipse.h:51
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys.
Definition: aggregates.h:282
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:425
def measure(self, measRecord, exposure, center)
Definition: plugins.py:438
def measure(self, measRecord, exposure, center)
Definition: plugins.py:501
def makeLocalTransformMatrix(self, wcs, center)
Definition: plugins.py:509
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:478
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:685
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:691
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:805
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:760
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:742
def __init__(self, config, name, schemaMapper, metadata)
Definition: plugins.py:853
def measure(self, measRecord, exposure, refRecord, refWcs)
Definition: plugins.py:873
def fail(self, measRecord, error=None)
Definition: plugins.py:389
def measure(self, measRecord, exposure, center)
Definition: plugins.py:380
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:369
def measure(self, measRecord, exposure)
Definition: plugins.py:154
def fail(self, measRecord, error=None)
Definition: plugins.py:164
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:146
def fail(self, measRecord, error=None)
Definition: plugins.py:216
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:200
def measure(self, measRecord, exposure)
Definition: plugins.py:207
def fail(self, measRecord, error=None)
Definition: plugins.py:593
def measure(self, measRecord, exposure)
Definition: plugins.py:588
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:582
def fail(self, measRecord, error=None)
Definition: plugins.py:641
def measure(self, measRecord, exposure)
Definition: plugins.py:633
def __init__(self, config, name, schema, metadata)
Definition: plugins.py:263
def measure(self, measRecord, exposure, center)
Definition: plugins.py:274
def fail(self, measRecord, error=None)
Definition: plugins.py:302
daf::base::PropertySet * set
Definition: fits.cc:912
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:521
void wrapTransform(lsst::utils::python::WrapperCollection &)
Definition: _transform.cc:130
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:146
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)
Approximate a Transform by its local linearization.
def wrapSimpleAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, hasMeasureN=False, hasLogName=False, **kwds)
Definition: wrappers.py:380