lsst.meas.base g070148d5b3+79e3dbe116
Loading...
Searching...
No Matches
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
24This module defines and registers a series of pure-Python measurement plugins
25which have trivial implementations. It also wraps measurement algorithms
26defined in C++ to expose them to the measurement framework.
27"""
28
29import numpy as np
30
32import lsst.geom
34import lsst.afw.geom
35
36from .pluginRegistry import register
37from .pluginsBase import BasePlugin
38from .baseMeasurement import BaseMeasurementPluginConfig
39from .sfm import SingleFramePluginConfig, SingleFramePlugin
40from .forcedMeasurement import ForcedPluginConfig, ForcedPlugin
41from .wrappers import wrapSimpleAlgorithm, wrapTransform, GenericPlugin
42from .transforms import SimpleCentroidTransform
43
44from .apertureFlux import ApertureFluxControl, ApertureFluxTransform
45from .transform import BaseTransform
46from .blendedness import BlendednessAlgorithm, BlendednessControl
47from .circularApertureFlux import CircularApertureFluxAlgorithm
48from .gaussianFlux import GaussianFluxAlgorithm, GaussianFluxControl, GaussianFluxTransform
49from .exceptions import MeasurementError
50from .localBackground import LocalBackgroundControl, LocalBackgroundAlgorithm, LocalBackgroundTransform
51from .naiveCentroid import NaiveCentroidAlgorithm, NaiveCentroidControl, NaiveCentroidTransform
52from .peakLikelihoodFlux import PeakLikelihoodFluxAlgorithm, PeakLikelihoodFluxControl, \
53 PeakLikelihoodFluxTransform
54from .pixelFlags import PixelFlagsAlgorithm, PixelFlagsControl
55from .psfFlux import PsfFluxAlgorithm, PsfFluxControl, PsfFluxTransform
56from .scaledApertureFlux import ScaledApertureFluxAlgorithm, ScaledApertureFluxControl, \
57 ScaledApertureFluxTransform
58from .sdssCentroid import SdssCentroidAlgorithm, SdssCentroidControl, SdssCentroidTransform
59from .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
78wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl,
79 TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
80 shouldApCorr=True, hasLogName=True)
81wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl,
82 TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
83wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl,
84 TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
85 shouldApCorr=True)
86wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl,
87 TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
88wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
89 TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
90wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
91 executionOrder=BasePlugin.FLUX_ORDER)
92wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
93 TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
94wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
95 TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
96
97wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
98 TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
99wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
100 TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
101
102wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
103 TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
104
105wrapTransform(PsfFluxTransform)
106wrapTransform(PeakLikelihoodFluxTransform)
107wrapTransform(GaussianFluxTransform)
108wrapTransform(NaiveCentroidTransform)
109wrapTransform(SdssCentroidTransform)
110wrapTransform(SdssShapeTransform)
111wrapTransform(ScaledApertureFluxTransform)
112wrapTransform(ApertureFluxTransform)
113wrapTransform(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_ORDER
145
146 def __init__(self, config, name, schema, metadata):
147 SingleFramePlugin.__init__(self, config, name, schema, metadata)
148 self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
149 "mm")
150 self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
151 self.detectorFlag = 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.detectorFlag, 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.focalValue, fp)
163
164 def fail(self, measRecord, error=None):
165 measRecord.set(self.focalFlag, 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_ORDER
199
200 def __init__(self, config, name, schema, metadata):
201 SingleFramePlugin.__init__(self, config, name, schema, metadata)
202 self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
203 self.jacFlag = 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.scale = pow(self.config.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.scale*exposure.getWcs().linearizePixelToSky(
212 center,
213 lsst.geom.arcseconds).getLinear().computeDeterminant())
214 measRecord.set(self.jacValue, result)
215
216 def fail(self, measRecord, error=None):
217 measRecord.set(self.jacFlag, 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.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
266 self.emptyFootprintFlag = 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_CENTROID)
280 aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
281 aperture.scale(self.config.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.config.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.varValue, medVar)
298 else:
299 raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
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_CENTROID, self.FAILURE_EMPTY_FOOTPRINT)
307 # FAILURE_BAD_CENTROID handled by alias to centroid record.
308 if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT:
309 measRecord.set(self.emptyFootprintFlag, True)
310 measRecord.set(self.varValue, np.nan)
311 GenericPlugin.fail(self, measRecord, error)
312
313
314SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
315"""Single-frame version of `VariancePlugin`.
316"""
317
318ForcedVariancePlugin = 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
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.numberKey = schema.addField(name + '_value', type="I",
372 doc="Number of images contributing at center, not including any"
373 "clipping")
374 self.noInputsFlag = 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_INPUTS)
383 if not np.all(np.isfinite(center)):
384 raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID)
385
386 ccds = exposure.getInfo().getCoaddInputs().ccds
387 measRecord.set(self.numberKey, 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_CENTROID, self.FAILURE_NO_INPUTS)
392 # FAILURE_BAD_CENTROID handled by alias to centroid record.
393 if error.getFlagBit() == self.FAILURE_NO_INPUTS:
394 measRecord.set(self.noInputsFlag, True)
395 GenericPlugin.fail(self, measRecord, error)
396
397
398SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount")
399"""Single-frame version of `InputCoutPlugin`.
400"""
401
402ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount")
403"""Forced version of `InputCoutPlugin`.
404"""
405
406
408 """Configuration for the variance calculation plugin.
409 """
410 pass
411
412
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.photoKey = 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.photoErrKey = 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.photoKey, calib)
443
444 calibErr = photoCalib.getCalibrationErr()
445 measRecord.set(self.photoErrKey, calibErr)
446
447
448SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
449 "base_LocalPhotoCalib")
450"""Single-frame version of `EvaluatePhotoCalibPlugin`.
451"""
452
453ForcedEvaluateLocalPhotoCalibPlugin = 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
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.cdMatrix11Key = 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.cdMatrix12Key = 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.cdMatrix21Key = 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.cdMatrix22Key = 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.makeLocalTransformMatrix(wcs, center)
504 measRecord.set(self.cdMatrix11Key, localMatrix[0, 0])
505 measRecord.set(self.cdMatrix12Key, localMatrix[0, 1])
506 measRecord.set(self.cdMatrix21Key, localMatrix[1, 0])
507 measRecord.set(self.cdMatrix22Key, 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 ----------
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, self._scale)))
534 measurementToLocalGnomonic = wcs.getTransform().then(
535 localGnomonicWcs.getTransform().inverted()
536 )
537 localMatrix = measurementToLocalGnomonic.getJacobian(center)
538 return np.radians(localMatrix / 3600)
539
540
541SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs")
542"""Single-frame version of `EvaluateLocalWcsPlugin`.
543"""
544
545ForcedEvaluateLocalWcsPlugin = 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_ORDER
581
582 def __init__(self, config, name, schema, metadata):
583 SingleFramePlugin.__init__(self, config, name, schema, metadata)
584 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
585 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
586 self.flag = 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.keyX, peak.getFx())
591 measRecord.set(self.keyY, peak.getFy())
592
593 def fail(self, measRecord, error=None):
594 measRecord.set(self.flag, 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_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.name + " 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
649class 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_ORDER
684
685 def __init__(self, config, name, schemaMapper, metadata):
686 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
687 schema = schemaMapper.editOutputSchema()
688 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
689 self.keyY = 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.keyX, result.getX())
697 measRecord.set(self.keyY, 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_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")
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.flagKey = schema.addField(name + "_flag", type="Flag",
756 doc="whether the reference centroid is marked as bad")
757 else:
758 self.flagKey = 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.centroidKey, targetPos)
765 else:
766 measRecord.set(self.centroidKey, refRecord.getCentroid())
767 if self.flagKey is not None:
768 measRecord.set(self.flagKey, 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.centroidKey, targetPos)
810
811 if self.flagKey is not None:
812 measRecord.set(self.flagKey, 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_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.shapeKey = 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.flagKey = schema.addField(name + "_flag", type="Flag",
869 doc="whether the reference shape is marked as bad")
870 else:
871 self.flagKey = 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.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
879 else:
880 measRecord.set(self.shapeKey, refRecord.getShape())
881 if self.flagKey is not None:
882 measRecord.set(self.flagKey, refRecord.getShapeFlag())
static std::shared_ptr< geom::SpanSet > fromShape(int r, Stencil s=Stencil::CIRCLE, lsst::geom::Point2I offset=lsst::geom::Point2I())
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
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)