22 """Definition of measurement plugins.
24 This module defines and registers a series of pure-Python measurement plugins
25 which have trivial implementations. It also wraps measurement algorithms
26 defined in C++ to expose them to the measurement framework.
36 from .pluginRegistry
import register
37 from .pluginsBase
import BasePlugin
38 from .baseMeasurement
import BaseMeasurementPluginConfig
39 from .sfm
import SingleFramePluginConfig, SingleFramePlugin
40 from .forcedMeasurement
import ForcedPluginConfig, ForcedPlugin
41 from .wrappers
import wrapSimpleAlgorithm, wrapTransform, GenericPlugin
42 from .transforms
import SimpleCentroidTransform
44 from .apertureFlux
import ApertureFluxControl, ApertureFluxTransform
45 from .transform
import BaseTransform
46 from .blendedness
import BlendednessAlgorithm, BlendednessControl
47 from .circularApertureFlux
import CircularApertureFluxAlgorithm
48 from .gaussianFlux
import GaussianFluxAlgorithm, GaussianFluxControl, GaussianFluxTransform
49 from .exceptions
import MeasurementError
50 from .localBackground
import LocalBackgroundControl, LocalBackgroundAlgorithm, LocalBackgroundTransform
51 from .naiveCentroid
import NaiveCentroidAlgorithm, NaiveCentroidControl, NaiveCentroidTransform
52 from .peakLikelihoodFlux
import PeakLikelihoodFluxAlgorithm, PeakLikelihoodFluxControl, \
53 PeakLikelihoodFluxTransform
54 from .pixelFlags
import PixelFlagsAlgorithm, PixelFlagsControl
55 from .psfFlux
import PsfFluxAlgorithm, PsfFluxControl, PsfFluxTransform
56 from .scaledApertureFlux
import ScaledApertureFluxAlgorithm, ScaledApertureFluxControl, \
57 ScaledApertureFluxTransform
58 from .sdssCentroid
import SdssCentroidAlgorithm, SdssCentroidControl, SdssCentroidTransform
59 from .sdssShape
import SdssShapeAlgorithm, SdssShapeControl, SdssShapeTransform
62 "SingleFrameFPPositionConfig",
"SingleFrameFPPositionPlugin",
63 "SingleFrameJacobianConfig",
"SingleFrameJacobianPlugin",
64 "VarianceConfig",
"SingleFrameVariancePlugin",
"ForcedVariancePlugin",
65 "InputCountConfig",
"SingleFrameInputCountPlugin",
"ForcedInputCountPlugin",
66 "SingleFramePeakCentroidConfig",
"SingleFramePeakCentroidPlugin",
67 "SingleFrameSkyCoordConfig",
"SingleFrameSkyCoordPlugin",
68 "ForcedPeakCentroidConfig",
"ForcedPeakCentroidPlugin",
69 "ForcedTransformedCentroidConfig",
"ForcedTransformedCentroidPlugin",
70 "ForcedTransformedShapeConfig",
"ForcedTransformedShapePlugin",
71 "EvaluateLocalPhotoCalibPlugin",
"EvaluateLocalPhotoCalibPluginConfig",
72 "EvaluateLocalWcsPlugin",
"EvaluateLocalWcsPluginConfig",
77 TransformClass=PsfFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
78 shouldApCorr=
True, hasLogName=
True)
80 TransformClass=PeakLikelihoodFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
82 TransformClass=GaussianFluxTransform, executionOrder=BasePlugin.FLUX_ORDER,
85 TransformClass=NaiveCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
87 TransformClass=SdssCentroidTransform, executionOrder=BasePlugin.CENTROID_ORDER)
89 executionOrder=BasePlugin.FLUX_ORDER)
91 TransformClass=SdssShapeTransform, executionOrder=BasePlugin.SHAPE_ORDER)
93 TransformClass=ScaledApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
96 TransformClass=ApertureFluxTransform, executionOrder=BasePlugin.FLUX_ORDER)
98 TransformClass=BaseTransform, executionOrder=BasePlugin.SHAPE_ORDER)
101 TransformClass=LocalBackgroundTransform, executionOrder=BasePlugin.FLUX_ORDER)
115 """Configuration for the focal plane position measurment algorithm.
123 """Algorithm to calculate the position of a centroid on the focal plane.
127 config : `SingleFrameFPPositionConfig`
131 schema : `lsst.afw.table.Schema`
132 The schema for the measurement output catalog. New fields will be
133 added to hold measurements produced by this plugin.
134 metadata : `lsst.daf.base.PropertySet`
135 Plugin metadata that will be attached to the output catalog
138 ConfigClass = SingleFrameFPPositionConfig
144 def __init__(self, config, name, schema, metadata):
145 SingleFramePlugin.__init__(self, config, name, schema, metadata)
146 self.
focalValue = lsst.afw.table.Point2DKey.addFields(schema, name,
"Position on the focal plane",
148 self.
focalFlag = schema.addField(name +
"_flag", type=
"Flag", doc=
"Set to True for any fatal failure")
149 self.
detectorFlag = schema.addField(name +
"_missingDetector_flag", type=
"Flag",
150 doc=
"Set to True if detector object is missing")
153 det = exposure.getDetector()
158 center = measRecord.getCentroid()
159 fp = det.transform(center, lsst.afw.cameraGeom.PIXELS, lsst.afw.cameraGeom.FOCAL_PLANE)
162 def fail(self, measRecord, error=None):
167 """Configuration for the Jacobian calculation plugin.
175 """Compute the Jacobian and its ratio with a nominal pixel area.
177 This enables one to compare relative, rather than absolute, pixel areas.
181 config : `SingleFrameJacobianConfig`
185 schema : `lsst.afw.table.Schema`
186 The schema for the measurement output catalog. New fields will be
187 added to hold measurements produced by this plugin.
188 metadata : `lsst.daf.base.PropertySet`
189 Plugin metadata that will be attached to the output catalog
192 ConfigClass = SingleFrameJacobianConfig
198 def __init__(self, config, name, schema, metadata):
199 SingleFramePlugin.__init__(self, config, name, schema, metadata)
200 self.
jacValue = schema.addField(name +
'_value', type=
"D", doc=
"Jacobian correction")
201 self.
jacFlag = schema.addField(name +
'_flag', type=
"Flag", doc=
"Set to 1 for any fatal failure")
206 center = measRecord.getCentroid()
209 result = np.abs(self.
scale*exposure.getWcs().linearizePixelToSky(
211 lsst.geom.arcseconds).getLinear().computeDeterminant())
212 measRecord.set(self.
jacValue, result)
214 def fail(self, measRecord, error=None):
215 measRecord.set(self.
jacFlag,
True)
219 """Configuration for the variance calculation plugin.
222 doc=
"Scale factor to apply to shape for aperture")
224 default=[
"DETECTED",
"DETECTED_NEGATIVE",
"BAD",
"SAT"])
228 """Compute the median variance corresponding to a footprint.
230 The aim here is to measure the background variance, rather than that of
231 the object itself. In order to achieve this, the variance is calculated
232 over an area scaled up from the shape of the input footprint.
236 config : `VarianceConfig`
240 schema : `lsst.afw.table.Schema`
241 The schema for the measurement output catalog. New fields will be
242 added to hold measurements produced by this plugin.
243 metadata : `lsst.daf.base.PropertySet`
244 Plugin metadata that will be attached to the output catalog
247 ConfigClass = VarianceConfig
249 FAILURE_BAD_CENTROID = 1
250 """Denotes failures due to bad centroiding (`int`).
253 FAILURE_EMPTY_FOOTPRINT = 2
254 """Denotes failures due to a lack of usable pixels (`int`).
259 return BasePlugin.FLUX_ORDER
261 def __init__(self, config, name, schema, metadata):
262 GenericPlugin.__init__(self, config, name, schema, metadata)
263 self.
varValue = schema.addField(name +
'_value', type=
"D", doc=
"Variance at object position")
265 doc=
"Set to True when the footprint has no usable pixels")
270 schema.getAliasMap().
set(name +
'_flag_badCentroid', schema.getAliasMap().apply(
"slot_Centroid_flag"))
272 def measure(self, measRecord, exposure, center):
276 if not np.all(np.isfinite(measRecord.getCentroid())):
279 aperture.scale(self.
config.scale)
282 foot.clipTo(exposure.getBBox(lsst.afw.image.PARENT))
285 maskedImage = exposure.getMaskedImage()
287 maskBits = maskedImage.getMask().getPlaneBitMask(self.
config.mask)
288 logicalMask = np.logical_not(pixels.getMaskArray() & maskBits)
293 if np.any(logicalMask):
294 medVar = np.median(pixels.getVarianceArray()[logicalMask])
295 measRecord.set(self.
varValue, medVar)
297 raise MeasurementError(
"Footprint empty, or all pixels are masked, can't compute median",
300 def fail(self, measRecord, error=None):
303 if isinstance(error, MeasurementError):
308 measRecord.set(self.
varValue, np.nan)
309 GenericPlugin.fail(self, measRecord, error)
312 SingleFrameVariancePlugin = VariancePlugin.makeSingleFramePlugin(
"base_Variance")
313 """Single-frame version of `VariancePlugin`.
316 ForcedVariancePlugin = VariancePlugin.makeForcedPlugin(
"base_Variance")
317 """Forced version of `VariancePlugin`.
322 """Configuration for the input image counting plugin.
327 class InputCountPlugin(GenericPlugin):
328 """Count the number of input images which contributed to a a source.
332 config : `InputCountConfig`
336 schema : `lsst.afw.table.Schema`
337 The schema for the measurement output catalog. New fields will be
338 added to hold measurements produced by this plugin.
339 metadata : `lsst.daf.base.PropertySet`
340 Plugin metadata that will be attached to the output catalog
344 Information is derived from the image's `~lsst.afw.image.CoaddInputs`.
345 Note these limitation:
347 - This records the number of images which contributed to the pixel in the
348 center of the source footprint, rather than to any or all pixels in the
350 - Clipping in the coadd is not taken into account.
353 ConfigClass = InputCountConfig
355 FAILURE_BAD_CENTROID = 1
356 """Denotes failures due to bad centroiding (`int`).
359 FAILURE_NO_INPUTS = 2
360 """Denotes failures due to the image not having coadd inputs. (`int`)
365 return BasePlugin.SHAPE_ORDER
367 def __init__(self, config, name, schema, metadata):
368 GenericPlugin.__init__(self, config, name, schema, metadata)
369 self.
numberKey = schema.addField(name +
'_value', type=
"I",
370 doc=
"Number of images contributing at center, not including any"
372 self.
noInputsFlag = schema.addField(name +
'_flag_noInputs', type=
"Flag",
373 doc=
"No coadd inputs available")
376 schema.getAliasMap().
set(name +
'_flag_badCentroid', schema.getAliasMap().apply(
"slot_Centroid_flag"))
378 def measure(self, measRecord, exposure, center):
379 if not exposure.getInfo().getCoaddInputs():
381 if not np.all(np.isfinite(center)):
384 ccds = exposure.getInfo().getCoaddInputs().ccds
385 measRecord.set(self.
numberKey, len(ccds.subsetContaining(center, exposure.getWcs())))
387 def fail(self, measRecord, error=None):
388 if error
is not None:
393 GenericPlugin.fail(self, measRecord, error)
396 SingleFrameInputCountPlugin = InputCountPlugin.makeSingleFramePlugin(
"base_InputCount")
397 """Single-frame version of `InputCoutPlugin`.
400 ForcedInputCountPlugin = InputCountPlugin.makeForcedPlugin(
"base_InputCount")
401 """Forced version of `InputCoutPlugin`.
406 """Configuration for the variance calculation plugin.
411 class EvaluateLocalPhotoCalibPlugin(GenericPlugin):
412 """Evaluate the local value of the Photometric Calibration in the exposure.
414 The aim is to store the local calib value within the catalog for later
415 use in the Science Data Model functors.
417 ConfigClass = EvaluateLocalPhotoCalibPluginConfig
421 return BasePlugin.FLUX_ORDER
423 def __init__(self, config, name, schema, metadata):
424 GenericPlugin.__init__(self, config, name, schema, metadata)
428 doc=
"Local approximation of the PhotoCalib calibration factor at "
429 "the location of the src.")
433 doc=
"Error on the local approximation of the PhotoCalib "
434 "calibration factor at the location of the src.")
436 def measure(self, measRecord, exposure, center):
438 photoCalib = exposure.getPhotoCalib()
439 calib = photoCalib.getLocalCalibration(center)
440 measRecord.set(self.
photoKey, calib)
442 calibErr = photoCalib.getCalibrationErr()
446 SingleFrameEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeSingleFramePlugin(
447 "base_LocalPhotoCalib")
448 """Single-frame version of `EvaluatePhotoCalibPlugin`.
451 ForcedEvaluateLocalPhotoCalibPlugin = EvaluateLocalPhotoCalibPlugin.makeForcedPlugin(
452 "base_LocalPhotoCalib")
453 """Forced version of `EvaluatePhotoCalibPlugin`.
458 """Configuration for the variance calculation plugin.
463 class EvaluateLocalWcsPlugin(GenericPlugin):
464 """Evaluate the local, linear approximation of the Wcs.
466 The aim is to store the local calib value within the catalog for later
467 use in the Science Data Model functors.
469 ConfigClass = EvaluateLocalWcsPluginConfig
470 _scale = (1.0 * lsst.geom.arcseconds).asDegrees()
474 return BasePlugin.FLUX_ORDER
476 def __init__(self, config, name, schema, metadata):
477 GenericPlugin.__init__(self, config, name, schema, metadata)
479 f
"{name}_CDMatrix_1_1",
481 doc=f
"(1, 1) element of the CDMatrix for the linear approximation "
482 "of the WCS at the src location. Gives units in radians.")
484 f
"{name}_CDMatrix_1_2",
486 doc=f
"(1, 2) element of the CDMatrix for the linear approximation "
487 "of the WCS at the src location. Gives units in radians.")
489 f
"{name}_CDMatrix_2_1",
491 doc=f
"(2, 1) element of the CDMatrix for the linear approximation "
492 "of the WCS at the src location. Gives units in radians.")
494 f
"{name}_CDMatrix_2_2",
496 doc=f
"(2, 2) element of the CDMatrix for the linear approximation "
497 "of the WCS at the src location. Gives units in radians.")
499 def measure(self, measRecord, exposure, center):
500 wcs = exposure.getWcs()
508 """Create a local, linear approximation of the wcs transformation
511 The approximation is created as if the center is at RA=0, DEC=0. All
512 comparing x,y coordinate are relative to the position of center. Matrix
513 is initially calculated with units arcseconds and then converted to
514 radians. This yields higher precision results due to quirks in AST.
518 wcs : `lsst.afw.geom.SkyWcs`
520 center : `lsst.geom.Point2D`
521 Point at which to evaluate the LocalWcs.
525 localMatrix : `numpy.ndarray`
526 Matrix representation the local wcs approximation with units
529 skyCenter = wcs.pixelToSky(center)
532 measurementToLocalGnomonic = wcs.getTransform().
then(
533 localGnomonicWcs.getTransform().inverted()
535 localMatrix = measurementToLocalGnomonic.getJacobian(center)
536 return np.radians(localMatrix / 3600)
539 SingleFrameEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeSingleFramePlugin(
"base_LocalWcs")
540 """Single-frame version of `EvaluateLocalWcsPlugin`.
543 ForcedEvaluateLocalWcsPlugin = EvaluateLocalWcsPlugin.makeForcedPlugin(
"base_LocalWcs")
544 """Forced version of `EvaluateLocalWcsPlugin`.
549 """Configuration for the single frame peak centroiding algorithm.
556 """Record the highest peak in a source footprint as its centroid.
558 This is of course a relatively poor measure of the true centroid of the
559 object; this algorithm is provided mostly for testing and debugging.
563 config : `SingleFramePeakCentroidConfig`
567 schema : `lsst.afw.table.Schema`
568 The schema for the measurement output catalog. New fields will be
569 added to hold measurements produced by this plugin.
570 metadata : `lsst.daf.base.PropertySet`
571 Plugin metadata that will be attached to the output catalog
574 ConfigClass = SingleFramePeakCentroidConfig
580 def __init__(self, config, name, schema, metadata):
581 SingleFramePlugin.__init__(self, config, name, schema, metadata)
582 self.
keyX = schema.addField(name +
"_x", type=
"D", doc=
"peak centroid", units=
"pixel")
583 self.
keyY = schema.addField(name +
"_y", type=
"D", doc=
"peak centroid", units=
"pixel")
584 self.
flag = schema.addField(name +
"_flag", type=
"Flag", doc=
"Centroiding failed")
587 peak = measRecord.getFootprint().getPeaks()[0]
588 measRecord.set(self.
keyX, peak.getFx())
589 measRecord.set(self.
keyY, peak.getFy())
591 def fail(self, measRecord, error=None):
592 measRecord.set(self.
flag,
True)
596 return SimpleCentroidTransform
600 """Configuration for the sky coordinates algorithm.
607 """Record the sky position of an object based on its centroid slot and WCS.
609 The position is record in the ``coord`` field, which is part of the
610 `~lsst.afw.table.SourceCatalog` minimal schema.
614 config : `SingleFrameSkyCoordConfig`
618 schema : `lsst.afw.table.Schema`
619 The schema for the measurement output catalog. New fields will be
620 added to hold measurements produced by this plugin.
621 metadata : `lsst.daf.base.PropertySet`
622 Plugin metadata that will be attached to the output catalog
625 ConfigClass = SingleFrameSkyCoordConfig
635 if not exposure.hasWcs():
636 raise RuntimeError(
"Wcs not attached to exposure. Required for " + self.
name +
" algorithm")
637 measRecord.updateCoord(exposure.getWcs())
639 def fail(self, measRecord, error=None):
647 class ForcedPeakCentroidConfig(ForcedPluginConfig):
648 """Configuration for the forced peak centroid algorithm.
655 """Record the highest peak in a source footprint as its centroid.
657 This is of course a relatively poor measure of the true centroid of the
658 object; this algorithm is provided mostly for testing and debugging.
660 This is similar to `SingleFramePeakCentroidPlugin`, except that transforms
661 the peak coordinate from the original (reference) coordinate system to the
662 coordinate system of the exposure being measured.
666 config : `ForcedPeakCentroidConfig`
670 schemaMapper : `lsst.afw.table.SchemaMapper`
671 A mapping from reference catalog fields to output
672 catalog fields. Output fields are added to the output schema.
673 metadata : `lsst.daf.base.PropertySet`
674 Plugin metadata that will be attached to the output catalog.
677 ConfigClass = ForcedPeakCentroidConfig
683 def __init__(self, config, name, schemaMapper, metadata):
684 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
685 schema = schemaMapper.editOutputSchema()
686 self.
keyX = schema.addField(name +
"_x", type=
"D", doc=
"peak centroid", units=
"pixel")
687 self.
keyY = schema.addField(name +
"_y", type=
"D", doc=
"peak centroid", units=
"pixel")
689 def measure(self, measRecord, exposure, refRecord, refWcs):
690 targetWcs = exposure.getWcs()
691 peak = refRecord.getFootprint().getPeaks()[0]
693 result = targetWcs.skyToPixel(refWcs.pixelToSky(result))
694 measRecord.set(self.
keyX, result.getX())
695 measRecord.set(self.
keyY, result.getY())
699 return SimpleCentroidTransform
703 """Configuration for the forced transformed centroid algorithm.
708 @
register(
"base_TransformedCentroid")
710 """Record the transformation of the reference catalog centroid.
712 The centroid recorded in the reference catalog is tranformed to the
713 measurement coordinate system and stored.
717 config : `ForcedTransformedCentroidConfig`
721 schemaMapper : `lsst.afw.table.SchemaMapper`
722 A mapping from reference catalog fields to output
723 catalog fields. Output fields are added to the output schema.
724 metadata : `lsst.daf.base.PropertySet`
725 Plugin metadata that will be attached to the output catalog.
729 This is used as the slot centroid by default in forced measurement,
730 allowing subsequent measurements to simply refer to the slot value just as
731 they would in single-frame measurement.
734 ConfigClass = ForcedTransformedCentroidConfig
740 def __init__(self, config, name, schemaMapper, metadata):
741 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
742 schema = schemaMapper.editOutputSchema()
744 xKey = schema.addField(name +
"_x", type=
"D", doc=
"transformed reference centroid column",
746 yKey = schema.addField(name +
"_y", type=
"D", doc=
"transformed reference centroid row",
752 if "slot_Centroid_flag" in schemaMapper.getInputSchema():
753 self.
flagKey = schema.addField(name +
"_flag", type=
"Flag",
754 doc=
"whether the reference centroid is marked as bad")
758 def measure(self, measRecord, exposure, refRecord, refWcs):
759 targetWcs = exposure.getWcs()
760 if not refWcs == targetWcs:
761 targetPos = targetWcs.skyToPixel(refWcs.pixelToSky(refRecord.getCentroid()))
764 measRecord.set(self.
centroidKey, refRecord.getCentroid())
766 measRecord.set(self.
flagKey, refRecord.getCentroidFlag())
770 """Configuration for the forced transformed shape algorithm.
777 """Record the transformation of the reference catalog shape.
779 The shape recorded in the reference catalog is tranformed to the
780 measurement coordinate system and stored.
784 config : `ForcedTransformedShapeConfig`
788 schemaMapper : `lsst.afw.table.SchemaMapper`
789 A mapping from reference catalog fields to output
790 catalog fields. Output fields are added to the output schema.
791 metadata : `lsst.daf.base.PropertySet`
792 Plugin metadata that will be attached to the output catalog.
796 This is used as the slot shape by default in forced measurement, allowing
797 subsequent measurements to simply refer to the slot value just as they
798 would in single-frame measurement.
801 ConfigClass = ForcedTransformedShapeConfig
807 def __init__(self, config, name, schemaMapper, metadata):
808 ForcedPlugin.__init__(self, config, name, schemaMapper, metadata)
809 schema = schemaMapper.editOutputSchema()
811 xxKey = schema.addField(name +
"_xx", type=
"D", doc=
"transformed reference shape x^2 moment",
813 yyKey = schema.addField(name +
"_yy", type=
"D", doc=
"transformed reference shape y^2 moment",
815 xyKey = schema.addField(name +
"_xy", type=
"D", doc=
"transformed reference shape xy moment",
821 if "slot_Shape_flag" in schemaMapper.getInputSchema():
822 self.
flagKey = schema.addField(name +
"_flag", type=
"Flag",
823 doc=
"whether the reference shape is marked as bad")
827 def measure(self, measRecord, exposure, refRecord, refWcs):
828 targetWcs = exposure.getWcs()
829 if not refWcs == targetWcs:
832 measRecord.set(self.
shapeKey, refRecord.getShape().
transform(localTransform.getLinear()))
834 measRecord.set(self.
shapeKey, refRecord.getShape())
836 measRecord.set(self.
flagKey, refRecord.getShapeFlag())