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