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