lsst.meas.base g51c93253c0+c18275eecd
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 logging
30import numpy as np
31
33import lsst.geom
35import lsst.afw.geom
36
37from ._measBaseLib import (ApertureFluxControl, ApertureFluxTransform,
38 BaseTransform, BlendednessAlgorithm,
39 BlendednessControl, CircularApertureFluxAlgorithm,
40 GaussianFluxAlgorithm, GaussianFluxControl,
41 GaussianFluxTransform, LocalBackgroundAlgorithm,
42 LocalBackgroundControl, LocalBackgroundTransform,
43 MeasurementError,
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)
54
55from .baseMeasurement import BaseMeasurementPluginConfig
56from .forcedMeasurement import ForcedPlugin, ForcedPluginConfig
57from .pluginRegistry import register
58from .pluginsBase import BasePlugin
59from .sfm import SingleFramePlugin, SingleFramePluginConfig
60from .transforms import SimpleCentroidTransform
61from .wrappers import GenericPlugin, wrapSimpleAlgorithm, wrapTransform
62
63__all__ = (
64 "SingleFrameFPPositionConfig", "SingleFrameFPPositionPlugin",
65 "SingleFrameJacobianConfig", "SingleFrameJacobianPlugin",
66 "VarianceConfig", "SingleFrameVariancePlugin", "ForcedVariancePlugin",
67 "InputCountConfig", "SingleFrameInputCountPlugin", "ForcedInputCountPlugin",
68 "SingleFramePeakCentroidConfig", "SingleFramePeakCentroidPlugin",
69 "SingleFrameSkyCoordConfig", "SingleFrameSkyCoordPlugin",
70 "SingleFrameClassificationSizeExtendednessConfig",
71 "SingleFrameClassificationSizeExtendednessPlugin",
72 "ForcedPeakCentroidConfig", "ForcedPeakCentroidPlugin",
73 "ForcedTransformedCentroidConfig", "ForcedTransformedCentroidPlugin",
74 "ForcedTransformedCentroidFromCoordConfig",
75 "ForcedTransformedCentroidFromCoordPlugin",
76 "ForcedTransformedShapeConfig", "ForcedTransformedShapePlugin",
77 "EvaluateLocalPhotoCalibPlugin", "EvaluateLocalPhotoCalibPluginConfig",
78 "EvaluateLocalWcsPlugin", "EvaluateLocalWcsPluginConfig",
79)
80
81
82wrapSimpleAlgorithm(PsfFluxAlgorithm, Control=PsfFluxControl,
83 TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
84 shouldApCorr=True, hasLogName=True)
85wrapSimpleAlgorithm(PeakLikelihoodFluxAlgorithm, Control=PeakLikelihoodFluxControl,
86 TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
87wrapSimpleAlgorithm(GaussianFluxAlgorithm, Control=GaussianFluxControl,
88 TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
89 shouldApCorr=True)
90wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
91 TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
92wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
93 executionOrder=BasePlugin.FLUX_ORDER)
94wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
95 TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
96wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
97 TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
98
99wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
100 TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
101wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
102 TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
103
104wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
105 TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
106
107wrapTransform(PsfFluxTransform)
108wrapTransform(PeakLikelihoodFluxTransform)
109wrapTransform(GaussianFluxTransform)
110wrapTransform(SdssCentroidTransform)
111wrapTransform(SdssShapeTransform)
112wrapTransform(ScaledApertureFluxTransform)
113wrapTransform(ApertureFluxTransform)
114wrapTransform(LocalBackgroundTransform)
115
116log = logging.getLogger(__name__)
117
118
120 """Configuration for the focal plane position measurement algorithm.
121 """
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 configuration.
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 configuration.
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
206 # is in arcsec^2.
207 self.scale = pow(self.config.pixelScale, -2)
208
209 def measure(self, measRecord, exposure):
210 center = measRecord.getCentroid()
211 # Compute the area of a pixel at a source record's centroid, and take
212 # the ratio of that with the defined reference pixel area.
213 result = np.abs(self.scale*exposure.getWcs().linearizePixelToSky(
214 center,
215 lsst.geom.arcseconds).getLinear().computeDeterminant())
216 measRecord.set(self.jacValue, result)
217
218 def fail(self, measRecord, error=None):
219 measRecord.set(self.jacFlag, True)
220
221
223 """Configuration for the variance calculation plugin.
224 """
225 scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
226 doc="Scale factor to apply to shape for aperture")
227 mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
228 default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
229
230
232 """Compute the median variance corresponding to a footprint.
233
234 The aim here is to measure the background variance, rather than that of
235 the object itself. In order to achieve this, the variance is calculated
236 over an area scaled up from the shape of the input footprint.
237
238 Parameters
239 ----------
240 config : `VarianceConfig`
241 Plugin configuration.
242 name : `str`
243 Plugin name.
244 schema : `lsst.afw.table.Schema`
245 The schema for the measurement output catalog. New fields will be
246 added to hold measurements produced by this plugin.
247 metadata : `lsst.daf.base.PropertySet`
248 Plugin metadata that will be attached to the output catalog
249 """
250
251 ConfigClass = VarianceConfig
252
253 FAILURE_BAD_CENTROID = 1
254 """Denotes failures due to bad centroiding (`int`).
255 """
256
257 FAILURE_EMPTY_FOOTPRINT = 2
258 """Denotes failures due to a lack of usable pixels (`int`).
259 """
260
261 @classmethod
263 return BasePlugin.FLUX_ORDER
264
265 def __init__(self, config, name, schema, metadata):
266 GenericPlugin.__init__(self, config, name, schema, metadata)
267 self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
268 self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
269 doc="Set to True when the footprint has no usable pixels")
270
271 # Alias the badCentroid flag to that which is defined for the target
272 # of the centroid slot. We do not simply rely on the alias because
273 # that could be changed post-measurement.
274 schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
275
276 def measure(self, measRecord, exposure, center):
277 # Create an aperture and grow it by scale value defined in config to
278 # ensure there are enough pixels around the object to get decent
279 # statistics
280 if not np.all(np.isfinite(measRecord.getCentroid())):
281 raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROID)
282 aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
283 aperture.scale(self.config.scale)
284 ellipse = lsst.afw.geom.SpanSet.fromShape(aperture)
285 foot = lsst.afw.detection.Footprint(ellipse)
286 foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
287 # Filter out any pixels which have mask bits set corresponding to the
288 # planes to be excluded (defined in config.mask)
289 maskedImage = exposure.getMaskedImage()
290 pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
291 maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask)
292 logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
293 # Compute the median variance value for each pixel not excluded by the
294 # mask and write the record. Numpy median is used here instead of
295 # afw.math makeStatistics because of an issue with data types being
296 # passed into the C++ layer (DM-2379).
297 if np.any(logicalMask):
298 medVar = np.median(pixels.getVarianceArray()[logicalMask])
299 measRecord.set(self.varValue, medVar)
300 else:
301 raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
303
304 def fail(self, measRecord, error=None):
305 # Check that we have an error object and that it is of type
306 # MeasurementError
307 if isinstance(error, MeasurementError):
308 assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_EMPTY_FOOTPRINT)
309 # FAILURE_BAD_CENTROID handled by alias to centroid record.
310 if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINT:
311 measRecord.set(self.emptyFootprintFlag, True)
312 measRecord.set(self.varValue, np.nan)
313 GenericPlugin.fail(self, measRecord, error)
314
315
316SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
317"""Single-frame version of `VariancePlugin`.
318"""
319
320ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance")
321"""Forced version of `VariancePlugin`.
322"""
323
324
326 """Configuration for the input image counting plugin.
327 """
328
329
330class InputCountPlugin(GenericPlugin):
331 """Count the number of input images which contributed to a source.
332
333 Parameters
334 ----------
335 config : `InputCountConfig`
336 Plugin configuration.
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
378 # the centroid slot. We do not simply rely on the alias because that
379 # could be changed post-measurement.
380 schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
381
382 def measure(self, measRecord, exposure, center):
383 if not (coaddInputs := exposure.getInfo().getCoaddInputs()):
384 raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTS)
385 if not np.all(np.isfinite(center)):
386 raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROID)
387
388 count = len(coaddInputs.subset_containing_ccds(center, exposure.wcs))
389 measRecord.set(self.numberKey, count)
390
391 def fail(self, measRecord, error=None):
392 if error is not None:
393 assert error.getFlagBit() in (self.FAILURE_BAD_CENTROID, self.FAILURE_NO_INPUTS)
394 # FAILURE_BAD_CENTROID handled by alias to centroid record.
395 if error.getFlagBit() == self.FAILURE_NO_INPUTS:
396 measRecord.set(self.noInputsFlag, True)
397 GenericPlugin.fail(self, measRecord, error)
398
399
400SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin("base_InputCount")
401"""Single-frame version of `InputCoutPlugin`.
402"""
403
404ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin("base_InputCount")
405"""Forced version of `InputCoutPlugin`.
406"""
407
408
410 """Configuration for the variance calculation plugin.
411 """
412
413
414class EvaluateLocalPhotoCalibPlugin(GenericPlugin):
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 photoCalib = exposure.getPhotoCalib()
441 if photoCalib is None:
442 log.debug(
443 "%s: photoCalib is None. Setting localPhotoCalib to NaN for record %d",
444 self.name,
445 measRecord.getId(),
446 )
447 calib = np.nan
448 calibErr = np.nan
449 measRecord.set(self._failKey, True)
450 else:
451 calib = photoCalib.getLocalCalibration(center)
452 calibErr = photoCalib.getCalibrationErr()
453 measRecord.set(self.photoKey, calib)
454 measRecord.set(self.photoErrKey, calibErr)
455
456
457SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
458 "base_LocalPhotoCalib")
459"""Single-frame version of `EvaluatePhotoCalibPlugin`.
460"""
461
462ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin(
463 "base_LocalPhotoCalib")
464"""Forced version of `EvaluatePhotoCalibPlugin`.
465"""
466
467
469 """Configuration for the variance calculation plugin.
470 """
471
472
473class EvaluateLocalWcsPlugin(GenericPlugin):
474 """Evaluate the local, linear approximation of the Wcs.
475
476 The aim is to store the local calib value within the catalog for later
477 use in the Science Data Model functors.
478 """
479 ConfigClass = EvaluateLocalWcsPluginConfig
480 _scale = (1.0 * lsst.geom.arcseconds).asDegrees()
481
482 @classmethod
484 return BasePlugin.FLUX_ORDER
485
486 def __init__(self, config, name, schema, metadata):
487 GenericPlugin.__init__(self, config, name, schema, metadata)
488 self.cdMatrix11Key = schema.addField(
489 f"{name}_CDMatrix_1_1",
490 type="D",
491 doc="(1, 1) element of the CDMatrix for the linear approximation "
492 "of the WCS at the src location. Gives units in radians.")
493 self.cdMatrix12Key = schema.addField(
494 f"{name}_CDMatrix_1_2",
495 type="D",
496 doc="(1, 2) element of the CDMatrix for the linear approximation "
497 "of the WCS at the src location. Gives units in radians.")
498 self.cdMatrix21Key = schema.addField(
499 f"{name}_CDMatrix_2_1",
500 type="D",
501 doc="(2, 1) element of the CDMatrix for the linear approximation "
502 "of the WCS at the src location. Gives units in radians.")
503 self.cdMatrix22Key = schema.addField(
504 f"{name}_CDMatrix_2_2",
505 type="D",
506 doc="(2, 2) element of the CDMatrix for the linear approximation "
507 "of the WCS at the src location. Gives units in radians.")
508
509 def measure(self, measRecord, exposure, center):
510 wcs = exposure.getWcs()
511 if wcs is None:
512 log.debug(
513 "%s: WCS is None. Setting localWcs matrix values to NaN for record %d",
514 self.name,
515 measRecord.getId(),
516 )
517 localMatrix = np.array([[np.nan, np.nan], [np.nan, np.nan]])
518 measRecord.set(self._failKey, True)
519 else:
520 localMatrix = self.makeLocalTransformMatrix(wcs, center)
521 measRecord.set(self.cdMatrix11Key, localMatrix[0, 0])
522 measRecord.set(self.cdMatrix12Key, localMatrix[0, 1])
523 measRecord.set(self.cdMatrix21Key, localMatrix[1, 0])
524 measRecord.set(self.cdMatrix22Key, localMatrix[1, 1])
525
526 def makeLocalTransformMatrix(self, wcs, center):
527 """Create a local, linear approximation of the wcs transformation
528 matrix.
529
530 The approximation is created as if the center is at RA=0, DEC=0. All
531 comparing x,y coordinate are relative to the position of center. Matrix
532 is initially calculated with units arcseconds and then converted to
533 radians. This yields higher precision results due to quirks in AST.
534
535 Parameters
536 ----------
537 wcs : `lsst.afw.geom.SkyWcs`
538 Wcs to approximate
539 center : `lsst.geom.Point2D`
540 Point at which to evaluate the LocalWcs.
541
542 Returns
543 -------
544 localMatrix : `numpy.ndarray`
545 Matrix representation the local wcs approximation with units
546 radians.
547 """
548 skyCenter = wcs.pixelToSky(center)
549 localGnomonicWcs = lsst.afw.geom.makeSkyWcs(
550 center, skyCenter, np.diag((self._scale, self._scale)))
551 measurementToLocalGnomonic = wcs.getTransform().then(
552 localGnomonicWcs.getTransform().inverted()
553 )
554 localMatrix = measurementToLocalGnomonic.getJacobian(center)
555 return np.radians(localMatrix / 3600)
556
557
558SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs")
559"""Single-frame version of `EvaluateLocalWcsPlugin`.
560"""
561
562ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin("base_LocalWcs")
563"""Forced version of `EvaluateLocalWcsPlugin`.
564"""
565
566
568 """Configuration for the single frame peak centroiding algorithm.
569 """
570
571
572@register("base_PeakCentroid")
574 """Record the highest peak in a source footprint as its centroid.
575
576 This is of course a relatively poor measure of the true centroid of the
577 object; this algorithm is provided mostly for testing and debugging.
578
579 Parameters
580 ----------
581 config : `SingleFramePeakCentroidConfig`
582 Plugin configuration.
583 name : `str`
584 Plugin name.
585 schema : `lsst.afw.table.Schema`
586 The schema for the measurement output catalog. New fields will be
587 added to hold measurements produced by this plugin.
588 metadata : `lsst.daf.base.PropertySet`
589 Plugin metadata that will be attached to the output catalog
590 """
591
592 ConfigClass = SingleFramePeakCentroidConfig
593
594 @classmethod
596 return cls.CENTROID_ORDER
597
598 def __init__(self, config, name, schema, metadata):
599 SingleFramePlugin.__init__(self, config, name, schema, metadata)
600 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
601 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
602 self.flag = schema.addField(name + "_flag", type="Flag", doc="Centroiding failed")
603
604 def measure(self, measRecord, exposure):
605 peak = measRecord.getFootprint().getPeaks()[0]
606 measRecord.set(self.keyX, peak.getFx())
607 measRecord.set(self.keyY, peak.getFy())
608
609 def fail(self, measRecord, error=None):
610 measRecord.set(self.flag, True)
611
612 @staticmethod
614 return SimpleCentroidTransform
615
616
618 """Configuration for the sky coordinates algorithm.
619 """
620
621
622@register("base_SkyCoord")
624 """Record the sky position and uncertainties of an object based on its
625 centroid slot and WCS.
626
627 The position is recorded in the ``coord`` field, which is part of the
628 `~lsst.afw.table.SourceCatalog` minimal schema.
629
630 Parameters
631 ----------
632 config : `SingleFrameSkyCoordConfig`
633 Plugin configuration.
634 name : `str`
635 Plugin name.
636 schema : `lsst.afw.table.Schema`
637 The schema for the measurement output catalog. New fields will be
638 added to hold measurements produced by this plugin.
639 metadata : `lsst.daf.base.PropertySet`
640 Plugin metadata that will be attached to the output catalog
641 """
642
643 ConfigClass = SingleFrameSkyCoordConfig
644
645 @classmethod
647 return cls.SHAPE_ORDER
648
649 def __init__(self, config, name, schema, metadata):
650 SingleFramePlugin.__init__(self, config, name, schema, metadata)
651 if "coord_raErr" not in schema:
653
654 def measure(self, measRecord, exposure):
655 # There should be a base class method for handling this exception. Put
656 # this on a later ticket. Also, there should be a python Exception of
657 # the appropriate type for this error
658 if not exposure.hasWcs():
659 raise RuntimeError("Wcs not attached to exposure. Required for " + self.name + " algorithm")
660 measRecord.updateCoord(exposure.getWcs())
661
662 def fail(self, measRecord, error=None):
663 # Override fail() to do nothing in the case of an exception: this is
664 # not ideal, but we don't have a place to put failures because we
665 # don't allocate any fields. Should consider fixing as part of
666 # DM-1011
667 pass
668
669
670class SingleFrameClassificationSizeExtendednessConfig(SingleFramePluginConfig):
671 """Configuration for moments-based star-galaxy classifier."""
672
673 exponent = lsst.pex.config.Field[float](
674 doc="Exponent to raise the PSF size squared (Ixx + Iyy) to, "
675 "in the likelihood normalization",
676 default=0.5,
677 )
678
679
680@register("base_ClassificationSizeExtendedness")
682 """Classify objects by comparing their moments-based trace radius to PSF's.
683
684 The plugin computes chi^2 as ((T_obj - T_psf)/T_psf^exponent)^2, where
685 T_obj is the sum of Ixx and Iyy moments of the object, and T_psf is the
686 sum of Ixx and Iyy moments of the PSF. The exponent is configurable.
687 The measure of being a galaxy is then 1 - exp(-0.5*chi^2).
688
689 Parameters
690 ----------
691 config : `SingleFrameClassificationSizeExtendednessConfig`
692 Plugin configuration.
693 name : `str`
694 Plugin name.
695 schema : `~lsst.afw.table.Schema`
696 The schema for the measurement output catalog. New fields will be
697 added to hold measurements produced by this plugin.
698 metadata : `~lsst.daf.base.PropertySet`
699 Plugin metadata that will be attached to the output catalog.
700
701 Notes
702 -----
703 The ``measure`` method of the plugin requires a value for the ``exposure``
704 argument to maintain consistent API, but it is not used in the measurement.
705 """
706
707 ConfigClass = SingleFrameClassificationSizeExtendednessConfig
708
709 FAILURE_BAD_SHAPE = 1
710 """Denotes failures due to bad shape (`int`).
711 """
712
713 @classmethod
715 return cls.FLUX_ORDER
716
717 def __init__(self, config, name, schema, metadata):
718 SingleFramePlugin.__init__(self, config, name, schema, metadata)
719 self.key = schema.addField(name + "_value",
720 type="D",
721 doc="Measure of being a galaxy based on trace of second order moments",
722 )
723 self.flag = schema.addField(name + "_flag", type="Flag", doc="Moments-based classification failed")
724
725 def measure(self, measRecord, exposure) -> None:
726 # Docstring inherited.
727
728 if measRecord.getShapeFlag():
729 raise MeasurementError(
730 "Shape flag is set. Required for " + self.name + " algorithm",
732 )
733
734 shape = measRecord.getShape()
735 psf_shape = measRecord.getPsfShape()
736
737 ixx = shape.getIxx()
738 iyy = shape.getIyy()
739 ixx_psf = psf_shape.getIxx()
740 iyy_psf = psf_shape.getIyy()
741
742 object_t = ixx + iyy
743 psf_t = ixx_psf + iyy_psf
744
745 chi_sq = ((object_t - psf_t)/(psf_t**self.config.exponent))**2.
746 likelihood = 1. - np.exp(-0.5*chi_sq)
747 measRecord.set(self.key, likelihood)
748
749 def fail(self, measRecord, error=None) -> None:
750 # Docstring inherited.
751 measRecord.set(self.key, np.nan)
752 measRecord.set(self.flag, True)
753
754
756 """Configuration for the forced peak centroid algorithm.
757 """
758
759
760@register("base_PeakCentroid")
762 """Record the highest peak in a source footprint as its centroid.
763
764 This is of course a relatively poor measure of the true centroid of the
765 object; this algorithm is provided mostly for testing and debugging.
766
767 This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
768 the peak coordinate from the original (reference) coordinate system to the
769 coordinate system of the exposure being measured.
770
771 Parameters
772 ----------
773 config : `ForcedPeakCentroidConfig`
774 Plugin configuration.
775 name : `str`
776 Plugin name.
777 schemaMapper : `lsst.afw.table.SchemaMapper`
778 A mapping from reference catalog fields to output
779 catalog fields. Output fields are added to the output schema.
780 metadata : `lsst.daf.base.PropertySet`
781 Plugin metadata that will be attached to the output catalog.
782 """
783
784 ConfigClass = ForcedPeakCentroidConfig
785
786 @classmethod
788 return cls.CENTROID_ORDER
789
790 def __init__(self, config, name, schemaMapper, metadata):
791 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
792 schema = schemaMapper.editOutputSchema()
793 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
794 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
795
796 def measure(self, measRecord, exposure, refRecord, refWcs):
797 targetWcs = exposure.getWcs()
798 peak = refRecord.getFootprint().getPeaks()[0]
799 result = lsst.geom.Point2D(peak.getFx(), peak.getFy())
800 result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
801 measRecord.set(self.keyX, result.getX())
802 measRecord.set(self.keyY, result.getY())
803
804 @staticmethod
806 return SimpleCentroidTransform
807
808
810 """Configuration for the forced transformed centroid algorithm.
811 """
812
813
814@register("base_TransformedCentroid")
816 """Record the transformation of the reference catalog centroid.
817
818 The centroid recorded in the reference catalog is tranformed to the
819 measurement coordinate system and stored.
820
821 Parameters
822 ----------
823 config : `ForcedTransformedCentroidConfig`
824 Plugin configuration
825 name : `str`
826 Plugin name
827 schemaMapper : `lsst.afw.table.SchemaMapper`
828 A mapping from reference catalog fields to output
829 catalog fields. Output fields are added to the output schema.
830 metadata : `lsst.daf.base.PropertySet`
831 Plugin metadata that will be attached to the output catalog.
832
833 Notes
834 -----
835 This is used as the slot centroid by default in forced measurement,
836 allowing subsequent measurements to simply refer to the slot value just as
837 they would in single-frame measurement.
838 """
839
840 ConfigClass = ForcedTransformedCentroidConfig
841
842 @classmethod
844 return cls.CENTROID_ORDER
845
846 def __init__(self, config, name, schemaMapper, metadata):
847 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
848 schema = schemaMapper.editOutputSchema()
849 # Allocate x and y fields, join these into a single FunctorKey for
850 # ease-of-use.
851 xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
852 units="pixel")
853 yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
854 units="pixel")
856 # Because we're taking the reference position as given, we don't bother
857 # transforming its uncertainty and reporting that here, so there are no
858 # sigma or cov fields. We do propagate the flag field, if it exists.
859 if "slot_Centroid_flag" in schemaMapper.getInputSchema():
860 self.flagKey = schema.addField(name + "_flag", type="Flag",
861 doc="whether the reference centroid is marked as bad")
862 else:
863 self.flagKey = None
864
865 def measure(self, measRecord, exposure, refRecord, refWcs):
866 targetWcs = exposure.getWcs()
867 if not refWcs == targetWcs:
868 targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
869 measRecord.set(self.centroidKey, targetPos)
870 else:
871 measRecord.set(self.centroidKey, refRecord.getCentroid())
872 if self.flagKey is not None:
873 measRecord.set(self.flagKey, refRecord.getCentroidFlag())
874
875
877 """Configuration for the forced transformed coord algorithm.
878 """
879
880
881@register("base_TransformedCentroidFromCoord")
883 """Record the transformation of the reference catalog coord.
884
885 The coord recorded in the reference catalog is tranformed to the
886 measurement coordinate system and stored.
887
888 Parameters
889 ----------
890 config : `ForcedTransformedCentroidFromCoordConfig`
891 Plugin configuration
892 name : `str`
893 Plugin name
894 schemaMapper : `lsst.afw.table.SchemaMapper`
895 A mapping from reference catalog fields to output
896 catalog fields. Output fields are added to the output schema.
897 metadata : `lsst.daf.base.PropertySet`
898 Plugin metadata that will be attached to the output catalog.
899
900 Notes
901 -----
902 This can be used as the slot centroid in forced measurement when only a
903 reference coord exist, allowing subsequent measurements to simply refer to
904 the slot value just as they would in single-frame measurement.
905 """
906
907 ConfigClass = ForcedTransformedCentroidFromCoordConfig
908
909 def measure(self, measRecord, exposure, refRecord, refWcs):
910 targetWcs = exposure.getWcs()
911
912 targetPos = targetWcs.skyToPixel(refRecord.getCoord())
913 measRecord.set(self.centroidKey, targetPos)
914
915 if self.flagKey is not None:
916 measRecord.set(self.flagKey, refRecord.getCentroidFlag())
917
918
920 """Configuration for the forced transformed shape algorithm.
921 """
922
923
924@register("base_TransformedShape")
926 """Record the transformation of the reference catalog shape.
927
928 The shape recorded in the reference catalog is tranformed to the
929 measurement coordinate system and stored.
930
931 Parameters
932 ----------
933 config : `ForcedTransformedShapeConfig`
934 Plugin configuration
935 name : `str`
936 Plugin name
937 schemaMapper : `lsst.afw.table.SchemaMapper`
938 A mapping from reference catalog fields to output
939 catalog fields. Output fields are added to the output schema.
940 metadata : `lsst.daf.base.PropertySet`
941 Plugin metadata that will be attached to the output catalog.
942
943 Notes
944 -----
945 This is used as the slot shape by default in forced measurement, allowing
946 subsequent measurements to simply refer to the slot value just as they
947 would in single-frame measurement.
948 """
949
950 ConfigClass = ForcedTransformedShapeConfig
951
952 @classmethod
954 return cls.SHAPE_ORDER
955
956 def __init__(self, config, name, schemaMapper, metadata):
957 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
958 schema = schemaMapper.editOutputSchema()
959 # Allocate xx, yy, xy fields, join these into a single FunctorKey for
960 # ease-of-use.
961 xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
962 units="pixel^2")
963 yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
964 units="pixel^2")
965 xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
966 units="pixel^2")
967 self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
968 # Because we're taking the reference position as given, we don't bother
969 # transforming its uncertainty and reporting that here, so there are no
970 # sigma or cov fields. We do propagate the flag field, if it exists.
971 if "slot_Shape_flag" in schemaMapper.getInputSchema():
972 self.flagKey = schema.addField(name + "_flag", type="Flag",
973 doc="whether the reference shape is marked as bad")
974 else:
975 self.flagKey = None
976
977 def measure(self, measRecord, exposure, refRecord, refWcs):
978 targetWcs = exposure.getWcs()
979 if not refWcs == targetWcs:
980 fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs)
981 localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid())
982 measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
983 else:
984 measRecord.set(self.shapeKey, refRecord.getShape())
985 if self.flagKey is not None:
986 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())
static ErrorKey addErrorFields(Schema &schema)
Exception to be thrown when a measurement algorithm experiences a known failure mode.
Definition exceptions.h:48
__init__(self, config, name, schema, metadata)
Definition plugins.py:426
measure(self, measRecord, exposure, center)
Definition plugins.py:439
__init__(self, config, name, schema, metadata)
Definition plugins.py:486
measure(self, measRecord, exposure, center)
Definition plugins.py:509
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:796
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:790
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:909
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:846
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:865
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:977
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:956
__init__(self, config, name, schema, metadata)
Definition plugins.py:370
fail(self, measRecord, error=None)
Definition plugins.py:391
measure(self, measRecord, exposure, center)
Definition plugins.py:382
__init__(self, config, name, schema, metadata)
Definition plugins.py:147
__init__(self, config, name, schema, metadata)
Definition plugins.py:201
__init__(self, config, name, schema, metadata)
Definition plugins.py:598
__init__(self, config, name, schema, metadata)
Definition plugins.py:649
__init__(self, config, name, schema, metadata)
Definition plugins.py:265
fail(self, measRecord, error=None)
Definition plugins.py:304
measure(self, measRecord, exposure, center)
Definition plugins.py:276
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)
register(name, shouldApCorr=False, apCorrList=())
wrapTransform(transformClass, hasLogName=False)
Definition wrappers.py:473
wrapSimpleAlgorithm(AlgClass, executionOrder, name=None, needsMetadata=False, hasMeasureN=False, hasLogName=False, deprecated=None, **kwds)
Definition wrappers.py:405