lsst.meas.extensions.trailedSources  master-gcc5351303a+f9e8f958a8
NaivePlugin.py
Go to the documentation of this file.
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 #
23 
24 import numpy as np
25 
26 from lsst.meas.base.pluginRegistry import register
27 from lsst.meas.base import SingleFramePlugin, SingleFramePluginConfig
28 from lsst.meas.base import FlagHandler, FlagDefinitionList, SafeCentroidExtractor
29 from lsst.meas.base import MeasurementError
30 
31 __all__ = ("SingleFrameNaiveTrailConfig", "SingleFrameNaiveTrailPlugin")
32 
33 
34 class SingleFrameNaiveTrailConfig(SingleFramePluginConfig):
35  """Config class for SingleFrameNaiveTrailPlugin.
36  """
37  pass
38 
39 
40 @register("ext_trailedSources_Naive")
41 class SingleFrameNaiveTrailPlugin(SingleFramePlugin):
42  """Naive trailed source measurement plugin
43 
44  Measures the length, angle from +x-axis, and end points of an extended
45  source using the second moments.
46 
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.
57 
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.
68 
69  See also
70  --------
71  lsst.meas.base.SingleFramePlugin
72  """
73 
74  ConfigClass = SingleFrameNaiveTrailConfig
75 
76  @classmethod
78  # Needs centroids, shape, and flux measurements.
79  # VeresPlugin is run after, which requires image data.
80  return cls.APCORR_ORDER + 0.1
81 
82  def __init__(self, config, name, schema, metadata):
83  super().__init__(config, name, schema, metadata)
84 
85  # Measurement Keys
86  self.keyX0keyX0 = schema.addField(name + "_x0", type="D", doc="Trail head X coordinate.", units="pixel")
87  self.keyY0keyY0 = schema.addField(name + "_y0", type="D", doc="Trail head Y coordinate.", units="pixel")
88  self.keyX1keyX1 = schema.addField(name + "_x1", type="D", doc="Trail tail X coordinate.", units="pixel")
89  self.keyY1keyY1 = schema.addField(name + "_y1", type="D", doc="Trail tail Y coordinate.", units="pixel")
90  self.keyFluxkeyFlux = schema.addField(name + "_flux", type="D", doc="Trailed source flux.", units="count")
91  self.keyLkeyL = schema.addField(name + "_length", type="D", doc="Trail length.", units="pixel")
92  self.keyAnglekeyAngle = schema.addField(name + "_angle", type="D", doc="Angle measured from +x-axis.")
93 
94  # Measurement Error Keys
95  self.keyX0ErrkeyX0Err = schema.addField(name + "_x0Err", type="D",
96  doc="Trail head X coordinate error.", units="pixel")
97  self.keyY0ErrkeyY0Err = schema.addField(name + "_y0Err", type="D",
98  doc="Trail head Y coordinate error.", units="pixel")
99  self.keyX1ErrkeyX1Err = schema.addField(name + "_x1Err", type="D",
100  doc="Trail tail X coordinate error.", units="pixel")
101  self.keyY1ErrkeyY1Err = schema.addField(name + "_y1Err", type="D",
102  doc="Trail tail Y coordinate error.", units="pixel")
103 
104  flagDefs = FlagDefinitionList()
105  flagDefs.addFailureFlag("No trailed-source measured")
106  self.NO_FLUXNO_FLUX = flagDefs.add("flag_noFlux", "No suitable prior flux measurement")
107  self.flagHandlerflagHandler = FlagHandler.addFields(schema, name, flagDefs)
108 
109  self.centriodExtractorcentriodExtractor = SafeCentroidExtractor(schema, name)
110 
111  def measure(self, measRecord, exposure):
112  """Run the Naive trailed source measurement algorithm.
113 
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.
120 
121  See also
122  --------
123  lsst.meas.base.SingleFramePlugin.measure
124  """
125  xc, yc = self.centriodExtractorcentriodExtractor(measRecord, self.flagHandlerflagHandler)
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
133 
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
141 
142  # For now, use the shape flux.
143  F = measRecord.get("base_SdssShape_instFlux")
144 
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_FLUXNO_FLUX.doc, self.NO_FLUXNO_FLUX.number)
151 
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)
162 
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
169 
170  # Set flags
171  measRecord.set(self.keyX0keyX0, x0)
172  measRecord.set(self.keyY0keyY0, y0)
173  measRecord.set(self.keyX1keyX1, x1)
174  measRecord.set(self.keyY1keyY1, y1)
175  measRecord.set(self.keyFluxkeyFlux, F)
176  measRecord.set(self.keyLkeyL, L)
177  measRecord.set(self.keyAnglekeyAngle, theta)
178  measRecord.set(self.keyX0ErrkeyX0Err, x0Err)
179  measRecord.set(self.keyY0ErrkeyY0Err, y0Err)
180  measRecord.set(self.keyX1ErrkeyX1Err, x0Err)
181  measRecord.set(self.keyY1ErrkeyY1Err, y0Err)
182 
183  def fail(self, measRecord, error=None):
184  """Record failure
185 
186  See also
187  --------
188  lsst.meas.base.SingleFramePlugin.fail
189  """
190  if error is None:
191  self.flagHandlerflagHandler.handleFailure(measRecord)
192  else:
193  self.flagHandlerflagHandler.handleFailure(measRecord, error.cpp)