Coverage for python/lsst/meas/extensions/trailedSources/NaivePlugin.py : 20%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#
2# This file is part of meas_extensions_trailedSources.
3#
4# Developed for the LSST Data Management System.
5# This product includes software developed by the LSST Project
6# (http://www.lsst.org).
7# See the COPYRIGHT file at the top-level directory of this distribution
8# for details of code ownership.
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22#
24import numpy as np
26from lsst.meas.base.pluginRegistry import register
27from lsst.meas.base import SingleFramePlugin, SingleFramePluginConfig
28from lsst.meas.base import FlagHandler, FlagDefinitionList, SafeCentroidExtractor
29from lsst.meas.base import MeasurementError
31__all__ = ("SingleFrameNaiveTrailConfig", "SingleFrameNaiveTrailPlugin")
34class SingleFrameNaiveTrailConfig(SingleFramePluginConfig):
35 """Config class for SingleFrameNaiveTrailPlugin.
36 """
37 pass
40@register("ext_trailedSources_Naive")
41class SingleFrameNaiveTrailPlugin(SingleFramePlugin):
42 """Naive trailed source measurement plugin
44 Measures the length, angle from +x-axis, and end points of an extended
45 source using the second moments.
47 Parameters
48 ----------
49 config: `SingleFrameNaiveTrailConfig`
50 Plugin configuration.
51 name: `str`
52 Plugin name.
53 schema: `lsst.afw.table.Schema`
54 Schema for the output catalog.
55 metadata: `lsst.daf.base.PropertySet`
56 Metadata to be attached to output catalog.
58 Notes
59 -----
60 This measurement plugin aims to utilize the already measured shape second
61 moments to naively estimate the length and angle, and thus end-points, of a
62 fast-moving, trailed source. The estimate for the trail length is
63 obtained by doubling the semi-major axis, a, of the ellipse defined by
64 the second moments. The angle, theta, from the x-axis is computed
65 similarly (via second moments). The end points of the trail are then given
66 by (xc +/- a*cos(theta), yc +/- a*sin(theta)), with xc and yc being the
67 centroid coordinates.
69 See also
70 --------
71 lsst.meas.base.SingleFramePlugin
72 """
74 ConfigClass = SingleFrameNaiveTrailConfig
76 @classmethod
77 def getExecutionOrder(cls):
78 # Needs centroids, shape, and flux measurements.
79 # VeresPlugin is run after, which requires image data.
80 return cls.APCORR_ORDER + 0.1
82 def __init__(self, config, name, schema, metadata):
83 super().__init__(config, name, schema, metadata)
85 # Measurement Keys
86 self.keyX0 = schema.addField(name + "_x0", type="D", doc="Trail head X coordinate.", units="pixel")
87 self.keyY0 = schema.addField(name + "_y0", type="D", doc="Trail head Y coordinate.", units="pixel")
88 self.keyX1 = schema.addField(name + "_x1", type="D", doc="Trail tail X coordinate.", units="pixel")
89 self.keyY1 = schema.addField(name + "_y1", type="D", doc="Trail tail Y coordinate.", units="pixel")
90 self.keyFlux = schema.addField(name + "_flux", type="D", doc="Trailed source flux.", units="count")
91 self.keyL = schema.addField(name + "_length", type="D", doc="Trail length.", units="pixel")
92 self.keyAngle = schema.addField(name + "_angle", type="D", doc="Angle measured from +x-axis.")
94 # Measurement Error Keys
95 self.keyX0Err = schema.addField(name + "_x0Err", type="D",
96 doc="Trail head X coordinate error.", units="pixel")
97 self.keyY0Err = schema.addField(name + "_y0Err", type="D",
98 doc="Trail head Y coordinate error.", units="pixel")
99 self.keyX1Err = schema.addField(name + "_x1Err", type="D",
100 doc="Trail tail X coordinate error.", units="pixel")
101 self.keyY1Err = schema.addField(name + "_y1Err", type="D",
102 doc="Trail tail Y coordinate error.", units="pixel")
104 flagDefs = FlagDefinitionList()
105 flagDefs.addFailureFlag("No trailed-source measured")
106 self.NO_FLUX = flagDefs.add("flag_noFlux", "No suitable prior flux measurement")
107 self.flagHandler = FlagHandler.addFields(schema, name, flagDefs)
109 self.centriodExtractor = SafeCentroidExtractor(schema, name)
111 def measure(self, measRecord, exposure):
112 """Run the Naive trailed source measurement algorithm.
114 Parameters
115 ----------
116 measRecord : `lsst.afw.table.SourceRecord`
117 Record describing the object being measured.
118 exposure : `lsst.afw.image.Exposure`
119 Pixel data to be measured.
121 See also
122 --------
123 lsst.meas.base.SingleFramePlugin.measure
124 """
125 xc, yc = self.centriodExtractor(measRecord, self.flagHandler)
126 Ixx, Iyy, Ixy = measRecord.getShape().getParameterVector()
127 xmy = Ixx - Iyy
128 xpy = Ixx + Iyy
129 xmy2 = xmy*xmy
130 xy2 = Ixy*Ixy
131 a = np.sqrt(0.5 * (xpy + np.sqrt(xmy2 + 4.0*xy2)))
132 L = 2.0*a
134 theta = 0.5 * np.arctan2(2.0 * Ixy, xmy)
135 dydt = a*np.cos(theta)
136 dxdt = a*np.sin(theta)
137 x0 = xc - dydt
138 y0 = yc - dxdt
139 x1 = xc + dydt
140 y1 = yc + dxdt
142 # For now, use the shape flux.
143 F = measRecord.get("base_SdssShape_instFlux")
145 # Fall back to aperture flux
146 if not np.isfinite(F):
147 if np.isfinite(measRecord.getApInstFlux()):
148 F = measRecord.getApInstFlux()
149 else:
150 raise MeasurementError(self.NO_FLUX.doc, self.NO_FLUX.number)
152 # Propagate errors from second moments
153 xcErr2, ycErr2 = np.diag(measRecord.getCentroidErr())
154 IxxErr2, IyyErr2, IxyErr2 = np.diag(measRecord.getShapeErr())
155 desc = np.sqrt(xmy2 + 4.0*xy2) # Descriminant^1/2 of EV equation
156 denom = 2*np.sqrt(2.0*(Ixx + np.sqrt(4.0*xy2 + xmy2 + Iyy))) # Denominator for dadIxx and dadIyy
157 dadIxx = (1.0 + (xmy/desc)) / denom
158 dadIyy = (1.0 - (xmy/desc)) / denom
159 dadIxy = (4.0*Ixy) / (desc * denom)
160 aErr2 = IxxErr2*dadIxx*dadIxx + IyyErr2*dadIyy*dadIyy + IxyErr2*dadIxy*dadIxy
161 thetaErr2 = ((IxxErr2 + IyyErr2)*xy2 + xmy2*IxyErr2) / (desc*desc*desc*desc)
163 dxda = np.cos(theta)
164 dyda = np.sin(theta)
165 xErr2 = aErr2*dxda*dxda + thetaErr2*dxdt*dxdt
166 yErr2 = aErr2*dyda*dyda + thetaErr2*dydt*dydt
167 x0Err = np.sqrt(xErr2 + xcErr2) # Same for x1
168 y0Err = np.sqrt(yErr2 + ycErr2) # Same for y1
170 # Set flags
171 measRecord.set(self.keyX0, x0)
172 measRecord.set(self.keyY0, y0)
173 measRecord.set(self.keyX1, x1)
174 measRecord.set(self.keyY1, y1)
175 measRecord.set(self.keyFlux, F)
176 measRecord.set(self.keyL, L)
177 measRecord.set(self.keyAngle, theta)
178 measRecord.set(self.keyX0Err, x0Err)
179 measRecord.set(self.keyY0Err, y0Err)
180 measRecord.set(self.keyX1Err, x0Err)
181 measRecord.set(self.keyY1Err, y0Err)
183 def fail(self, measRecord, error=None):
184 """Record failure
186 See also
187 --------
188 lsst.meas.base.SingleFramePlugin.fail
189 """
190 if error is None:
191 self.flagHandler.handleFailure(measRecord)
192 else:
193 self.flagHandler.handleFailure(measRecord, error.cpp)