25 import scipy.optimize
as sciOpt
30 from lsst.meas.base import SingleFramePlugin, SingleFramePluginConfig
31 from lsst.meas.base import FlagHandler, FlagDefinitionList, SafeCentroidExtractor
34 from ._trailedSources
import VeresModel
35 from .NaivePlugin
import SingleFrameNaiveTrailPlugin
37 __all__ = (
"SingleFrameVeresTrailConfig",
"SingleFrameVeresTrailPlugin")
41 """Config class for SingleFrameVeresTrailPlugin
44 optimizerMethod = Field(
45 doc=
"Optimizer method for scipy.optimize.minimize",
51 @register("ext_trailedSources_Veres")
53 """Veres trailed source characterization plugin.
55 Measures the length, angle, flux, centroid, and end points of a trailed
56 source using the Veres et al. 2012 model [1]_.
60 config: `SingleFrameNaiveTrailConfig`
64 schema: `lsst.afw.table.Schema`
65 Schema for the output catalog.
66 metadata: `lsst.daf.base.PropertySet`
67 Metadata to be attached to output catalog.
71 This plugin is designed to refine the measurements of trail length,
72 angle, and end points from `NaivePlugin`, and of flux and centroid from
73 previous measurement algorithms. Vereš et al. 2012 [1]_ derive a model for
74 the flux in a given image pixel by convolving an axisymmetric Gaussian with
75 a line. The model is parameterized by the total flux, trail length, angle
76 from the x-axis, and the centroid. The best estimates are computed using a
77 chi-squared minimization.
81 .. [1] Vereš, P., et al. "Improved Asteroid Astrometry and Photometry with
82 Trail Fitting" PASP, vol. 124, 2012.
86 lsst.meas.base.SingleFramePlugin
89 ConfigClass = SingleFrameVeresTrailConfig
95 return SingleFrameNaiveTrailPlugin.getExecutionOrder() + 0.1
97 def __init__(self, config, name, schema, metadata):
98 super().
__init__(config, name, schema, metadata)
101 name +
"_centroid_x", type=
"D", doc=
"Trail centroid X coordinate.", units=
"pixel")
103 name +
"_centroid_y", type=
"D", doc=
"Trail centroid Y coordinate.", units=
"pixel")
104 self.
keyX0keyX0 = schema.addField(name +
"_x0", type=
"D", doc=
"Trail head X coordinate.", units=
"pixel")
105 self.
keyY0keyY0 = schema.addField(name +
"_y0", type=
"D", doc=
"Trail head Y coordinate.", units=
"pixel")
106 self.
keyX1keyX1 = schema.addField(name +
"_x1", type=
"D", doc=
"Trail tail X coordinate.", units=
"pixel")
107 self.
keyY1keyY1 = schema.addField(name +
"_y1", type=
"D", doc=
"Trail tail Y coordinate.", units=
"pixel")
108 self.
keyLkeyL = schema.addField(name +
"_length", type=
"D", doc=
"Length of trail.", units=
"pixel")
109 self.
keyThetakeyTheta = schema.addField(name +
"_angle", type=
"D", doc=
"Angle of trail from +x-axis.")
110 self.
keyFluxkeyFlux = schema.addField(name +
"_flux", type=
"D", doc=
"Trailed source flux.", units=
"count")
111 self.
keyRChiSqkeyRChiSq = schema.addField(name +
"_rChiSq", type=
"D", doc=
"Reduced chi-squared of fit")
114 flagDefs.addFailureFlag(
"No trailed-sources measured")
115 self.
NON_CONVERGENON_CONVERGE = flagDefs.add(
"flag_nonConvergence",
"Optimizer did not converge")
116 self.
NO_NAIVENO_NAIVE = flagDefs.add(
"flag_noNaive",
"Naive measurement contains NaNs")
117 self.
flagHandlerflagHandler = FlagHandler.addFields(schema, name, flagDefs)
122 """Run the Veres trailed source measurement plugin.
126 measRecord : `lsst.afw.table.SourceRecord`
127 Record describing the object being measured.
128 exposure : `lsst.afw.image.Exposure`
129 Pixel data to be measured.
133 lsst.meas.base.SingleFramePlugin.measure
139 F = measRecord.get(
"ext_trailedSources_Naive_flux")
140 L = measRecord.get(
"ext_trailedSources_Naive_length")
141 theta = measRecord.get(
"ext_trailedSources_Naive_angle")
142 if not np.isfinite(F)
or not np.isfinite(L)
or not np.isfinite(theta):
146 model = VeresModel(exposure)
149 params = np.array([xc, yc, F, L, theta])
150 results = sciOpt.minimize(
151 model, params, method=self.config.optimizerMethod, jac=model.gradient)
154 if not results.success:
158 xc_fit, yc_fit, F_fit, L_fit, theta_fit = results.x
160 x0_fit = xc_fit - a * np.cos(theta_fit)
161 y0_fit = yc_fit - a * np.sin(theta_fit)
162 x1_fit = xc_fit + a * np.cos(theta_fit)
163 y1_fit = yc_fit + a * np.sin(theta_fit)
164 rChiSq = results.fun / (exposure.image.array.size - 6)
167 measRecord.set(self.
keyXCkeyXC, xc_fit)
168 measRecord.set(self.
keyYCkeyYC, yc_fit)
169 measRecord.set(self.
keyX0keyX0, x0_fit)
170 measRecord.set(self.
keyY0keyY0, y0_fit)
171 measRecord.set(self.
keyX1keyX1, x1_fit)
172 measRecord.set(self.
keyY1keyY1, y1_fit)
173 measRecord.set(self.
keyFluxkeyFlux, F_fit)
174 measRecord.set(self.
keyLkeyL, L_fit)
175 measRecord.set(self.
keyThetakeyTheta, theta_fit)
176 measRecord.set(self.
keyRChiSqkeyRChiSq, rChiSq)
178 def fail(self, measRecord, error=None):
183 lsst.meas.base.SingleFramePlugin.fail
186 self.
flagHandlerflagHandler.handleFailure(measRecord)
188 self.
flagHandlerflagHandler.handleFailure(measRecord, error.cpp)
def fail(self, measRecord, error=None)
def getExecutionOrder(cls)
def __init__(self, config, name, schema, metadata)
def measure(self, measRecord, exposure)