LSST Applications g0265f82a02+0e5473021a,g02d81e74bb+bd2ed33bd6,g1470d8bcf6+de7501a2e0,g14a832a312+ff425fae3c,g2079a07aa2+86d27d4dc4,g2305ad1205+91a32aca49,g295015adf3+762506a1ad,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g3ddfee87b4+c34e8be1fa,g487adcacf7+5fae3daba8,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+ea1711114f,g5a732f18d5+53520f316c,g64a986408d+bd2ed33bd6,g858d7b2824+bd2ed33bd6,g8a8a8dda67+585e252eca,g99cad8db69+016a06b37a,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+ef4e3a5875,gb0e22166c9+60f28cb32d,gb6a65358fc+0e5473021a,gba4ed39666+c2a2e4ac27,gbb8dafda3b+09e12c87ab,gc120e1dc64+bc2e06c061,gc28159a63d+0e5473021a,gcf0d15dbbd+c34e8be1fa,gdaeeff99f8+f9a426f77a,ge6526c86ff+508d0e0a30,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gf18bd8381d+8d59551888,gf1cff7945b+bd2ed33bd6,w.2024.16
LSST Data Management Base Package
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,
43 # Remove these three on DM-41701
44 NaiveCentroidAlgorithm, NaiveCentroidControl, NaiveCentroidTransform,
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",
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)
90# Remove this line on DM-41701
91wrapSimpleAlgorithm(NaiveCentroidAlgorithm, Control=NaiveCentroidControl,
92 TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER,
93 deprecated="Plugin 'NaiveCentroid' is deprecated and will be removed after v27.")
94wrapSimpleAlgorithm(SdssCentroidAlgorithm, Control=SdssCentroidControl,
95 TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
96wrapSimpleAlgorithm(PixelFlagsAlgorithm, Control=PixelFlagsControl,
97 executionOrder=BasePlugin.FLUX_ORDER)
98wrapSimpleAlgorithm(SdssShapeAlgorithm, Control=SdssShapeControl,
99 TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
100wrapSimpleAlgorithm(ScaledApertureFluxAlgorithm, Control=ScaledApertureFluxControl,
101 TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
102
103wrapSimpleAlgorithm(CircularApertureFluxAlgorithm, needsMetadata=True, Control=ApertureFluxControl,
104 TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
105wrapSimpleAlgorithm(BlendednessAlgorithm, Control=BlendednessControl,
106 TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
107
108wrapSimpleAlgorithm(LocalBackgroundAlgorithm, Control=LocalBackgroundControl,
109 TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
110
111wrapTransform(PsfFluxTransform)
112wrapTransform(PeakLikelihoodFluxTransform)
113wrapTransform(GaussianFluxTransform)
114# Remove this on DM-41701
115wrapTransform(NaiveCentroidTransform)
116wrapTransform(SdssCentroidTransform)
117wrapTransform(SdssShapeTransform)
118wrapTransform(ScaledApertureFluxTransform)
119wrapTransform(ApertureFluxTransform)
120wrapTransform(LocalBackgroundTransform)
121
122
124 """Configuration for the focal plane position measurment algorithm.
125 """
126
127
128@register("base_FPPosition")
130 """Algorithm to calculate the position of a centroid on the focal plane.
131
132 Parameters
133 ----------
134 config : `SingleFrameFPPositionConfig`
135 Plugin configuraion.
136 name : `str`
137 Plugin name.
138 schema : `lsst.afw.table.Schema`
139 The schema for the measurement output catalog. New fields will be
140 added to hold measurements produced by this plugin.
141 metadata : `lsst.daf.base.PropertySet`
142 Plugin metadata that will be attached to the output catalog
143 """
144
145 ConfigClass = SingleFrameFPPositionConfig
146
147 @classmethod
149 return cls.SHAPE_ORDER
150
151 def __init__(self, config, name, schema, metadata):
152 SingleFramePlugin.__init__(self, config, name, schema, metadata)
153 self.focalValue = lsst.afw.table.Point2DKey.addFields(schema, name, "Position on the focal plane",
154 "mm")
155 self.focalFlag = schema.addField(name + "_flag", type="Flag", doc="Set to True for any fatal failure")
156 self.detectorFlag = schema.addField(name + "_missingDetector_flag", type="Flag",
157 doc="Set to True if detector object is missing")
158
159 def measure(self, measRecord, exposure):
160 det = exposure.getDetector()
161 if not det:
162 measRecord.set(self.detectorFlag, True)
163 fp = lsst.geom.Point2D(np.nan, np.nan)
164 else:
165 center = measRecord.getCentroid()
166 fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
167 measRecord.set(self.focalValue, fp)
168
169 def fail(self, measRecord, error=None):
170 measRecord.set(self.focalFlag, True)
171
172
174 """Configuration for the Jacobian calculation plugin.
175 """
176
177 pixelScale = lsst.pex.config.Field(dtype=float, default=0.5, doc="Nominal pixel size (arcsec)")
178
179
180@register("base_Jacobian")
182 """Compute the Jacobian and its ratio with a nominal pixel area.
183
184 This enables one to compare relative, rather than absolute, pixel areas.
185
186 Parameters
187 ----------
188 config : `SingleFrameJacobianConfig`
189 Plugin configuraion.
190 name : `str`
191 Plugin name.
192 schema : `lsst.afw.table.Schema`
193 The schema for the measurement output catalog. New fields will be
194 added to hold measurements produced by this plugin.
195 metadata : `lsst.daf.base.PropertySet`
196 Plugin metadata that will be attached to the output catalog
197 """
198
199 ConfigClass = SingleFrameJacobianConfig
200
201 @classmethod
203 return cls.SHAPE_ORDER
204
205 def __init__(self, config, name, schema, metadata):
206 SingleFramePlugin.__init__(self, config, name, schema, metadata)
207 self.jacValue = schema.addField(name + '_value', type="D", doc="Jacobian correction")
208 self.jacFlag = schema.addField(name + '_flag', type="Flag", doc="Set to 1 for any fatal failure")
209 # Calculate one over the area of a nominal reference pixel, where area is in arcsec^2
210 self.scale = pow(self.config.pixelScale, -2)
211
212 def measure(self, measRecord, exposure):
213 center = measRecord.getCentroid()
214 # Compute the area of a pixel at a source record's centroid, and take
215 # the ratio of that with the defined reference pixel area.
216 result = np.abs(self.scale*exposure.getWcs().linearizePixelToSky(
217 center,
218 lsst.geom.arcseconds).getLinear().computeDeterminant())
219 measRecord.set(self.jacValue, result)
220
221 def fail(self, measRecord, error=None):
222 measRecord.set(self.jacFlag, True)
223
224
226 """Configuration for the variance calculation plugin.
227 """
228 scale = lsst.pex.config.Field(dtype=float, default=5.0, optional=True,
229 doc="Scale factor to apply to shape for aperture")
230 mask = lsst.pex.config.ListField(doc="Mask planes to ignore", dtype=str,
231 default=["DETECTED", "DETECTED_NEGATIVE", "BAD", "SAT"])
232
233
235 """Compute the median variance corresponding to a footprint.
236
237 The aim here is to measure the background variance, rather than that of
238 the object itself. In order to achieve this, the variance is calculated
239 over an area scaled up from the shape of the input footprint.
240
241 Parameters
242 ----------
243 config : `VarianceConfig`
244 Plugin configuraion.
245 name : `str`
246 Plugin name.
247 schema : `lsst.afw.table.Schema`
248 The schema for the measurement output catalog. New fields will be
249 added to hold measurements produced by this plugin.
250 metadata : `lsst.daf.base.PropertySet`
251 Plugin metadata that will be attached to the output catalog
252 """
253
254 ConfigClass = VarianceConfig
255
256 FAILURE_BAD_CENTROID = 1
257 """Denotes failures due to bad centroiding (`int`).
258 """
259
260 FAILURE_EMPTY_FOOTPRINT = 2
261 """Denotes failures due to a lack of usable pixels (`int`).
262 """
263
264 @classmethod
266 return BasePlugin.FLUX_ORDER
267
268 def __init__(self, config, name, schema, metadata):
269 GenericPlugin.__init__(self, config, name, schema, metadata)
270 self.varValue = schema.addField(name + '_value', type="D", doc="Variance at object position")
271 self.emptyFootprintFlag = schema.addField(name + '_flag_emptyFootprint', type="Flag",
272 doc="Set to True when the footprint has no usable pixels")
273
274 # Alias the badCentroid flag to that which is defined for the target
275 # of the centroid slot. We do not simply rely on the alias because
276 # that could be changed post-measurement.
277 schema.getAliasMap().set(name + '_flag_badCentroid', schema.getAliasMap().apply("slot_Centroid_flag"))
278
279 def measure(self, measRecord, exposure, center):
280 # Create an aperture and grow it by scale value defined in config to
281 # ensure there are enough pixels around the object to get decent
282 # statistics
283 if not np.all(np.isfinite(measRecord.getCentroid())):
284 raise MeasurementError("Bad centroid and/or shape", self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID)
285 aperture = lsst.afw.geom.Ellipse(measRecord.getShape(), measRecord.getCentroid())
286 aperture.scale(self.config.scale)
287 ellipse = lsst.afw.geom.SpanSet.fromShape(aperture)
288 foot = lsst.afw.detection.Footprint(ellipse)
289 foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
290 # Filter out any pixels which have mask bits set corresponding to the
291 # planes to be excluded (defined in config.mask)
292 maskedImage = exposure.getMaskedImage()
293 pixels = lsst.afw.detection.makeHeavyFootprint(foot, maskedImage)
294 maskBits = maskedImage.getMask().getPlaneBitMask(self.config.mask)
295 logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
296 # Compute the median variance value for each pixel not excluded by the
297 # mask and write the record. Numpy median is used here instead of
298 # afw.math makeStatistics because of an issue with data types being
299 # passed into the C++ layer (DM-2379).
300 if np.any(logicalMask):
301 medVar = np.median(pixels.getVarianceArray()[logicalMask])
302 measRecord.set(self.varValue, medVar)
303 else:
304 raise MeasurementError("Footprint empty, or all pixels are masked, can't compute median",
306
307 def fail(self, measRecord, error=None):
308 # Check that we have an error object and that it is of type
309 # MeasurementError
310 if isinstance(error, MeasurementError):
312 # FAILURE_BAD_CENTROID handled by alias to centroid record.
313 if error.getFlagBit() == self.FAILURE_EMPTY_FOOTPRINTFAILURE_EMPTY_FOOTPRINT:
314 measRecord.set(self.emptyFootprintFlag, True)
315 measRecord.set(self.varValue, np.nan)
316 GenericPlugin.fail(self, measRecord, error)
317
318
319SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin("base_Variance")
320"""Single-frame version of `VariancePlugin`.
321"""
322
323ForcedVariancePlugin = VariancePlugin.makeForcedPlugin("base_Variance")
324"""Forced version of `VariancePlugin`.
325"""
326
327
329 """Configuration for the input image counting plugin.
330 """
331
332
333class InputCountPlugin(GenericPlugin):
334 """Count the number of input images which contributed to a source.
335
336 Parameters
337 ----------
338 config : `InputCountConfig`
339 Plugin configuration.
340 name : `str`
341 Plugin name.
342 schema : `lsst.afw.table.Schema`
343 The schema for the measurement output catalog. New fields will be
344 added to hold measurements produced by this plugin.
345 metadata : `lsst.daf.base.PropertySet`
346 Plugin metadata that will be attached to the output catalog
347
348 Notes
349 -----
350 Information is derived from the image's `~lsst.afw.image.CoaddInputs`.
351 Note these limitation:
352
353 - This records the number of images which contributed to the pixel in the
354 center of the source footprint, rather than to any or all pixels in the
355 source.
356 - Clipping in the coadd is not taken into account.
357 """
358
359 ConfigClass = InputCountConfig
360
361 FAILURE_BAD_CENTROID = 1
362 """Denotes failures due to bad centroiding (`int`).
363 """
364
365 FAILURE_NO_INPUTS = 2
366 """Denotes failures due to the image not having coadd inputs. (`int`)
367 """
368
369 @classmethod
371 return BasePlugin.SHAPE_ORDER
372
373 def __init__(self, config, name, schema, metadata):
374 GenericPlugin.__init__(self, config, name, schema, metadata)
375 self.numberKey = schema.addField(name + '_value', type="I",
376 doc="Number of images contributing at center, not including any"
377 "clipping")
378 self.noInputsFlag = schema.addField(name + '_flag_noInputs', type="Flag",
379 doc="No coadd inputs available")
380 # Alias the badCentroid flag to that which is defined for the target of the centroid slot.
381 # We do not simply rely on the alias because that 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 exposure.getInfo().getCoaddInputs():
386 raise MeasurementError("No coadd inputs defined.", self.FAILURE_NO_INPUTSFAILURE_NO_INPUTS)
387 if not np.all(np.isfinite(center)):
388 raise MeasurementError("Source has a bad centroid.", self.FAILURE_BAD_CENTROIDFAILURE_BAD_CENTROID)
389
390 ccds = exposure.getInfo().getCoaddInputs().ccds
391 measRecord.set(self.numberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
392
393 def fail(self, measRecord, error=None):
394 if error is not None:
396 # FAILURE_BAD_CENTROID handled by alias to centroid record.
397 if error.getFlagBit() == self.FAILURE_NO_INPUTSFAILURE_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
443 photoCalib = exposure.getPhotoCalib()
444 calib = photoCalib.getLocalCalibration(center)
445 measRecord.set(self.photoKey, calib)
446
447 calibErr = photoCalib.getCalibrationErr()
448 measRecord.set(self.photoErrKey, calibErr)
449
450
451SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
452 "base_LocalPhotoCalib")
453"""Single-frame version of `EvaluatePhotoCalibPlugin`.
454"""
455
456ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin(
457 "base_LocalPhotoCalib")
458"""Forced version of `EvaluatePhotoCalibPlugin`.
459"""
460
461
463 """Configuration for the variance calculation plugin.
464 """
465
466
467class EvaluateLocalWcsPlugin(GenericPlugin):
468 """Evaluate the local, linear approximation of the Wcs.
469
470 The aim is to store the local calib value within the catalog for later
471 use in the Science Data Model functors.
472 """
473 ConfigClass = EvaluateLocalWcsPluginConfig
474 _scale = (1.0 * lsst.geom.arcseconds).asDegrees()
475
476 @classmethod
478 return BasePlugin.FLUX_ORDER
479
480 def __init__(self, config, name, schema, metadata):
481 GenericPlugin.__init__(self, config, name, schema, metadata)
482 self.cdMatrix11Key = schema.addField(
483 f"{name}_CDMatrix_1_1",
484 type="D",
485 doc="(1, 1) element of the CDMatrix for the linear approximation "
486 "of the WCS at the src location. Gives units in radians.")
487 self.cdMatrix12Key = schema.addField(
488 f"{name}_CDMatrix_1_2",
489 type="D",
490 doc="(1, 2) element of the CDMatrix for the linear approximation "
491 "of the WCS at the src location. Gives units in radians.")
492 self.cdMatrix21Key = schema.addField(
493 f"{name}_CDMatrix_2_1",
494 type="D",
495 doc="(2, 1) element of the CDMatrix for the linear approximation "
496 "of the WCS at the src location. Gives units in radians.")
497 self.cdMatrix22Key = schema.addField(
498 f"{name}_CDMatrix_2_2",
499 type="D",
500 doc="(2, 2) element of the CDMatrix for the linear approximation "
501 "of the WCS at the src location. Gives units in radians.")
502
503 def measure(self, measRecord, exposure, center):
504 wcs = exposure.getWcs()
505 localMatrix = self.makeLocalTransformMatrix(wcs, center)
506 measRecord.set(self.cdMatrix11Key, localMatrix[0, 0])
507 measRecord.set(self.cdMatrix12Key, localMatrix[0, 1])
508 measRecord.set(self.cdMatrix21Key, localMatrix[1, 0])
509 measRecord.set(self.cdMatrix22Key, localMatrix[1, 1])
510
511 def makeLocalTransformMatrix(self, wcs, center):
512 """Create a local, linear approximation of the wcs transformation
513 matrix.
514
515 The approximation is created as if the center is at RA=0, DEC=0. All
516 comparing x,y coordinate are relative to the position of center. Matrix
517 is initially calculated with units arcseconds and then converted to
518 radians. This yields higher precision results due to quirks in AST.
519
520 Parameters
521 ----------
522 wcs : `lsst.afw.geom.SkyWcs`
523 Wcs to approximate
524 center : `lsst.geom.Point2D`
525 Point at which to evaluate the LocalWcs.
526
527 Returns
528 -------
529 localMatrix : `numpy.ndarray`
530 Matrix representation the local wcs approximation with units
531 radians.
532 """
533 skyCenter = wcs.pixelToSky(center)
534 localGnomonicWcs = lsst.afw.geom.makeSkyWcs(
535 center, skyCenter, np.diag((self._scale_scale, self._scale_scale)))
536 measurementToLocalGnomonic = wcs.getTransform().then(
537 localGnomonicWcs.getTransform().inverted()
538 )
539 localMatrix = measurementToLocalGnomonic.getJacobian(center)
540 return np.radians(localMatrix / 3600)
541
542
543SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin("base_LocalWcs")
544"""Single-frame version of `EvaluateLocalWcsPlugin`.
545"""
546
547ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin("base_LocalWcs")
548"""Forced version of `EvaluateLocalWcsPlugin`.
549"""
550
551
553 """Configuration for the single frame peak centroiding algorithm.
554 """
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
606
607@register("base_SkyCoord")
609 """Record the sky position of an object based on its centroid slot and WCS.
610
611 The position is record in the ``coord`` field, which is part of the
612 `~lsst.afw.table.SourceCatalog` minimal schema.
613
614 Parameters
615 ----------
616 config : `SingleFrameSkyCoordConfig`
617 Plugin configuraion.
618 name : `str`
619 Plugin name.
620 schema : `lsst.afw.table.Schema`
621 The schema for the measurement output catalog. New fields will be
622 added to hold measurements produced by this plugin.
623 metadata : `lsst.daf.base.PropertySet`
624 Plugin metadata that will be attached to the output catalog
625 """
626
627 ConfigClass = SingleFrameSkyCoordConfig
628
629 @classmethod
631 return cls.SHAPE_ORDER
632
633 def measure(self, measRecord, exposure):
634 # There should be a base class method for handling this exception. Put
635 # this on a later ticket. Also, there should be a python Exception of
636 # the appropriate type for this error
637 if not exposure.hasWcs():
638 raise RuntimeError("Wcs not attached to exposure. Required for " + self.name + " algorithm")
639 measRecord.updateCoord(exposure.getWcs())
640
641 def fail(self, measRecord, error=None):
642 # Override fail() to do nothing in the case of an exception: this is
643 # not ideal, but we don't have a place to put failures because we
644 # don't allocate any fields. Should consider fixing as part of
645 # DM-1011
646 pass
647
648
649class SingleFrameMomentsClassifierConfig(SingleFramePluginConfig):
650 """Configuration for moments-based star-galaxy classifier."""
651
652 exponent = lsst.pex.config.Field[float](
653 doc="Exponent to raise the PSF size squared (Ixx + Iyy) to, "
654 "in the likelihood normalization",
655 default=0.5,
656 )
657
658
659@register("base_ClassificationSizeExtendedness")
661 """Classify objects by comparing their moments-based trace radius to PSF's.
662
663 The plugin computes chi^2 as ((T_obj - T_psf)/T_psf^exponent)^2, where
664 T_obj is the sum of Ixx and Iyy moments of the object, and T_psf is the
665 sum of Ixx and Iyy moments of the PSF. The exponent is configurable.
666 The measure of being a galaxy is then 1 - exp(-0.5*chi^2).
667
668 Parameters
669 ----------
670 config : `MomentsClassifierConfig`
671 Plugin configuration.
672 name : `str`
673 Plugin name.
674 schema : `~lsst.afw.table.Schema`
675 The schema for the measurement output catalog. New fields will be
676 added to hold measurements produced by this plugin.
677 metadata : `~lsst.daf.base.PropertySet`
678 Plugin metadata that will be attached to the output catalog.
679
680 Notes
681 -----
682 The ``measure`` method of the plugin requires a value for the ``exposure``
683 argument to maintain consistent API, but it is not used in the measurement.
684 """
685
686 ConfigClass = SingleFrameMomentsClassifierConfig
687
688 FAILURE_BAD_SHAPE = 1
689 """Denotes failures due to bad shape (`int`).
690 """
691
692 @classmethod
694 return cls.FLUX_ORDER
695
696 def __init__(self, config, name, schema, metadata):
697 SingleFramePlugin.__init__(self, config, name, schema, metadata)
698 self.key = schema.addField(name + "_value",
699 type="D",
700 doc="Measure of being a galaxy based on trace of second order moments",
701 )
702 self.flag = schema.addField(name + "_flag", type="Flag", doc="Moments-based classification failed")
703
704 def measure(self, measRecord, exposure) -> None:
705 # Docstring inherited.
706
707 if measRecord.getShapeFlag():
708 raise MeasurementError(
709 "Shape flag is set. Required for " + self.name + " algorithm",
711 )
712
713 shape = measRecord.getShape()
714 psf_shape = measRecord.getPsfShape()
715
716 ixx = shape.getIxx()
717 iyy = shape.getIyy()
718 ixx_psf = psf_shape.getIxx()
719 iyy_psf = psf_shape.getIyy()
720
721 object_t = ixx + iyy
722 psf_t = ixx_psf + iyy_psf
723
724 chi_sq = ((object_t - psf_t)/(psf_t**self.config.exponent))**2.
725 likelihood = 1. - np.exp(-0.5*chi_sq)
726 measRecord.set(self.key, likelihood)
727
728 def fail(self, measRecord, error=None) -> None:
729 # Docstring inherited.
730 measRecord.set(self.key, np.nan)
731 measRecord.set(self.flag, True)
732
733
735 """Configuration for the forced peak centroid algorithm.
736 """
737
738
739@register("base_PeakCentroid")
741 """Record the highest peak in a source footprint as its centroid.
742
743 This is of course a relatively poor measure of the true centroid of the
744 object; this algorithm is provided mostly for testing and debugging.
745
746 This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
747 the peak coordinate from the original (reference) coordinate system to the
748 coordinate system of the exposure being measured.
749
750 Parameters
751 ----------
752 config : `ForcedPeakCentroidConfig`
753 Plugin configuraion.
754 name : `str`
755 Plugin name.
756 schemaMapper : `lsst.afw.table.SchemaMapper`
757 A mapping from reference catalog fields to output
758 catalog fields. Output fields are added to the output schema.
759 metadata : `lsst.daf.base.PropertySet`
760 Plugin metadata that will be attached to the output catalog.
761 """
762
763 ConfigClass = ForcedPeakCentroidConfig
764
765 @classmethod
767 return cls.CENTROID_ORDER
768
769 def __init__(self, config, name, schemaMapper, metadata):
770 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
771 schema = schemaMapper.editOutputSchema()
772 self.keyX = schema.addField(name + "_x", type="D", doc="peak centroid", units="pixel")
773 self.keyY = schema.addField(name + "_y", type="D", doc="peak centroid", units="pixel")
774
775 def measure(self, measRecord, exposure, refRecord, refWcs):
776 targetWcs = exposure.getWcs()
777 peak = refRecord.getFootprint().getPeaks()[0]
778 result = lsst.geom.Point2D(peak.getFx(), peak.getFy())
779 result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
780 measRecord.set(self.keyX, result.getX())
781 measRecord.set(self.keyY, result.getY())
782
783 @staticmethod
785 return SimpleCentroidTransform
786
787
789 """Configuration for the forced transformed centroid algorithm.
790 """
791
792
793@register("base_TransformedCentroid")
795 """Record the transformation of the reference catalog centroid.
796
797 The centroid recorded in the reference catalog is tranformed to the
798 measurement coordinate system and stored.
799
800 Parameters
801 ----------
802 config : `ForcedTransformedCentroidConfig`
803 Plugin configuration
804 name : `str`
805 Plugin name
806 schemaMapper : `lsst.afw.table.SchemaMapper`
807 A mapping from reference catalog fields to output
808 catalog fields. Output fields are added to the output schema.
809 metadata : `lsst.daf.base.PropertySet`
810 Plugin metadata that will be attached to the output catalog.
811
812 Notes
813 -----
814 This is used as the slot centroid by default in forced measurement,
815 allowing subsequent measurements to simply refer to the slot value just as
816 they would in single-frame measurement.
817 """
818
819 ConfigClass = ForcedTransformedCentroidConfig
820
821 @classmethod
823 return cls.CENTROID_ORDER
824
825 def __init__(self, config, name, schemaMapper, metadata):
826 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
827 schema = schemaMapper.editOutputSchema()
828 # Allocate x and y fields, join these into a single FunctorKey for ease-of-use.
829 xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column",
830 units="pixel")
831 yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row",
832 units="pixel")
834 # Because we're taking the reference position as given, we don't bother transforming its
835 # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
836 # the flag field, if it exists.
837 if "slot_Centroid_flag" in schemaMapper.getInputSchema():
838 self.flagKey = schema.addField(name + "_flag", type="Flag",
839 doc="whether the reference centroid is marked as bad")
840 else:
841 self.flagKey = None
842
843 def measure(self, measRecord, exposure, refRecord, refWcs):
844 targetWcs = exposure.getWcs()
845 if not refWcs == targetWcs:
846 targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
847 measRecord.set(self.centroidKey, targetPos)
848 else:
849 measRecord.set(self.centroidKey, refRecord.getCentroid())
850 if self.flagKey is not None:
851 measRecord.set(self.flagKey, refRecord.getCentroidFlag())
852
853
855 """Configuration for the forced transformed coord algorithm.
856 """
857
858
859@register("base_TransformedCentroidFromCoord")
861 """Record the transformation of the reference catalog coord.
862
863 The coord recorded in the reference catalog is tranformed to the
864 measurement coordinate system and stored.
865
866 Parameters
867 ----------
868 config : `ForcedTransformedCentroidFromCoordConfig`
869 Plugin configuration
870 name : `str`
871 Plugin name
872 schemaMapper : `lsst.afw.table.SchemaMapper`
873 A mapping from reference catalog fields to output
874 catalog fields. Output fields are added to the output schema.
875 metadata : `lsst.daf.base.PropertySet`
876 Plugin metadata that will be attached to the output catalog.
877
878 Notes
879 -----
880 This can be used as the slot centroid in forced measurement when only a
881 reference coord exist, allowing subsequent measurements to simply refer to
882 the slot value just as they would in single-frame measurement.
883 """
884
885 ConfigClass = ForcedTransformedCentroidFromCoordConfig
886
887 def measure(self, measRecord, exposure, refRecord, refWcs):
888 targetWcs = exposure.getWcs()
889
890 targetPos = targetWcs.skyToPixel(refRecord.getCoord())
891 measRecord.set(self.centroidKeycentroidKey, targetPos)
892
893 if self.flagKeyflagKey is not None:
894 measRecord.set(self.flagKeyflagKey, refRecord.getCentroidFlag())
895
896
898 """Configuration for the forced transformed shape algorithm.
899 """
900
901
902@register("base_TransformedShape")
904 """Record the transformation of the reference catalog shape.
905
906 The shape recorded in the reference catalog is tranformed to the
907 measurement coordinate system and stored.
908
909 Parameters
910 ----------
911 config : `ForcedTransformedShapeConfig`
912 Plugin configuration
913 name : `str`
914 Plugin name
915 schemaMapper : `lsst.afw.table.SchemaMapper`
916 A mapping from reference catalog fields to output
917 catalog fields. Output fields are added to the output schema.
918 metadata : `lsst.daf.base.PropertySet`
919 Plugin metadata that will be attached to the output catalog.
920
921 Notes
922 -----
923 This is used as the slot shape by default in forced measurement, allowing
924 subsequent measurements to simply refer to the slot value just as they
925 would in single-frame measurement.
926 """
927
928 ConfigClass = ForcedTransformedShapeConfig
929
930 @classmethod
932 return cls.SHAPE_ORDER
933
934 def __init__(self, config, name, schemaMapper, metadata):
935 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
936 schema = schemaMapper.editOutputSchema()
937 # Allocate xx, yy, xy fields, join these into a single FunctorKey for ease-of-use.
938 xxKey = schema.addField(name + "_xx", type="D", doc="transformed reference shape x^2 moment",
939 units="pixel^2")
940 yyKey = schema.addField(name + "_yy", type="D", doc="transformed reference shape y^2 moment",
941 units="pixel^2")
942 xyKey = schema.addField(name + "_xy", type="D", doc="transformed reference shape xy moment",
943 units="pixel^2")
944 self.shapeKey = lsst.afw.table.QuadrupoleKey(xxKey, yyKey, xyKey)
945 # Because we're taking the reference position as given, we don't bother transforming its
946 # uncertainty and reporting that here, so there are no sigma or cov fields. We do propagate
947 # the flag field, if it exists.
948 if "slot_Shape_flag" in schemaMapper.getInputSchema():
949 self.flagKey = schema.addField(name + "_flag", type="Flag",
950 doc="whether the reference shape is marked as bad")
951 else:
952 self.flagKey = None
953
954 def measure(self, measRecord, exposure, refRecord, refWcs):
955 targetWcs = exposure.getWcs()
956 if not refWcs == targetWcs:
957 fullTransform = lsst.afw.geom.makeWcsPairTransform(refWcs, targetWcs)
958 localTransform = lsst.afw.geom.linearizeTransform(fullTransform, refRecord.getCentroid())
959 measRecord.set(self.shapeKey, refRecord.getShape().transform(localTransform.getLinear()))
960 else:
961 measRecord.set(self.shapeKey, refRecord.getShape())
962 if self.flagKey is not None:
963 measRecord.set(self.flagKey, refRecord.getShapeFlag())
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
static std::shared_ptr< geom::SpanSet > fromShape(int r, Stencil s=Stencil::CIRCLE, lsst::geom::Point2I offset=lsst::geom::Point2I())
Factory function for creating SpanSets from a Stencil.
Definition SpanSet.cc:688
An ellipse defined by an arbitrary BaseCore and a center point.
Definition Ellipse.h:51
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys.
Definition aggregates.h:369
__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:480
measure(self, measRecord, exposure, center)
Definition plugins.py:503
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:775
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:769
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:887
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:825
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:843
measure(self, measRecord, exposure, refRecord, refWcs)
Definition plugins.py:954
__init__(self, config, name, schemaMapper, metadata)
Definition plugins.py:934
__init__(self, config, name, schema, metadata)
Definition plugins.py:373
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:151
__init__(self, config, name, schema, metadata)
Definition plugins.py:205
__init__(self, config, name, schema, metadata)
Definition plugins.py:696
__init__(self, config, name, schema, metadata)
Definition plugins.py:583
__init__(self, config, name, schema, metadata)
Definition plugins.py:268
fail(self, measRecord, error=None)
Definition plugins.py:307
measure(self, measRecord, exposure, center)
Definition plugins.py:279
daf::base::PropertySet * set
Definition fits.cc:931
HeavyFootprint< ImagePixelT, MaskPixelT, VariancePixelT > makeHeavyFootprint(Footprint const &foot, lsst::afw::image::MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > const &img, HeavyFootprintCtrl const *ctrl=nullptr)
Create a HeavyFootprint with footprint defined by the given Footprint and pixel values from the given...
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition SkyWcs.cc:521
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
Definition SkyWcs.cc:146
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)
Approximate a Transform by its local linearization.