25import scipy.optimize
as sciOpt
30from lsst.meas.base import SingleFramePlugin, SingleFramePluginConfig
31from lsst.meas.base import FlagHandler, FlagDefinitionList, SafeCentroidExtractor
34from ._trailedSources
import VeresModel
35from .NaivePlugin
import SingleFrameNaiveTrailPlugin
36from .utils
import getMeasurementCutout
38__all__ = (
"SingleFrameVeresTrailConfig",
"SingleFrameVeresTrailPlugin")
42 """Config class for SingleFrameVeresTrailPlugin
45 optimizerMethod = Field(
46 doc="Optimizer method for scipy.optimize.minimize",
52@register("ext_trailedSources_Veres")
54 """Veres trailed source characterization plugin.
56 Measures the length, angle, flux, centroid, and end points of a trailed
57 source using the Veres et al. 2012 model [1]_.
61 config: `SingleFrameNaiveTrailConfig`
66 Schema
for the output catalog.
68 Metadata to be attached to output catalog.
72 This plugin
is designed to refine the measurements of trail length,
73 angle,
and end points
from `NaivePlugin`,
and of flux
and centroid
from
74 previous measurement algorithms. Vereš et al. 2012 [1]_ derive a model
for
75 the flux
in a given image pixel by convolving an axisymmetric Gaussian
with
76 a line. The model
is parameterized by the total flux, trail length, angle
77 from the x-axis,
and the centroid. The best estimates are computed using a
78 chi-squared minimization.
82 .. [1] Vereš, P., et al.
"Improved Asteroid Astrometry and Photometry with
83 Trail Fitting" PASP, vol. 124, 2012.
87 lsst.meas.base.SingleFramePlugin
90 ConfigClass = SingleFrameVeresTrailConfig
96 return SingleFrameNaiveTrailPlugin.getExecutionOrder() + 0.1
98 def __init__(self, config, name, schema, metadata):
99 super().
__init__(config, name, schema, metadata)
102 name +
"_centroid_x", type=
"D", doc=
"Trail centroid X coordinate.", units=
"pixel")
104 name +
"_centroid_y", type=
"D", doc=
"Trail centroid Y coordinate.", units=
"pixel")
105 self.
keyX0 = schema.addField(name +
"_x0", type=
"D", doc=
"Trail head X coordinate.", units=
"pixel")
106 self.
keyY0 = schema.addField(name +
"_y0", type=
"D", doc=
"Trail head Y coordinate.", units=
"pixel")
107 self.
keyX1 = schema.addField(name +
"_x1", type=
"D", doc=
"Trail tail X coordinate.", units=
"pixel")
108 self.
keyY1 = schema.addField(name +
"_y1", type=
"D", doc=
"Trail tail Y coordinate.", units=
"pixel")
109 self.
keyLength = schema.addField(name +
"_length", type=
"D", doc=
"Length of trail.", units=
"pixel")
110 self.
keyTheta = schema.addField(name +
"_angle", type=
"D", doc=
"Angle of trail from +x-axis.")
111 self.
keyFlux = schema.addField(name +
"_flux", type=
"D", doc=
"Trailed source flux.", units=
"count")
112 self.
keyRChiSq = schema.addField(name +
"_rChiSq", type=
"D", doc=
"Reduced chi-squared of fit")
115 flagDefs.addFailureFlag(
"No trailed-sources measured")
116 self.
NON_CONVERGE = flagDefs.add(
"flag_nonConvergence",
"Optimizer did not converge")
117 self.
NO_NAIVE = flagDefs.add(
"flag_noNaive",
"Naive measurement contains NaNs")
123 """Run the Veres trailed source measurement plugin.
128 Record describing the object being measured.
130 Pixel data to be measured.
134 lsst.meas.base.SingleFramePlugin.measure
140 flux = measRecord.get(
"ext_trailedSources_Naive_flux")
141 length = measRecord.get(
"ext_trailedSources_Naive_length")
142 theta = measRecord.get(
"ext_trailedSources_Naive_angle")
143 if not np.isfinite(flux)
or not np.isfinite(length)
or not np.isfinite(theta):
152 model = VeresModel(cutout)
155 params = np.array([xc, yc, flux, length, theta])
156 results = sciOpt.minimize(
157 model, params, method=self.config.optimizerMethod, jac=model.gradient)
160 if not results.success:
164 xc_fit, yc_fit, flux_fit, length_fit, theta_fit = results.x
166 x0_fit = xc_fit - a * np.cos(theta_fit)
167 y0_fit = yc_fit - a * np.sin(theta_fit)
168 x1_fit = xc_fit + a * np.cos(theta_fit)
169 y1_fit = yc_fit + a * np.sin(theta_fit)
170 rChiSq = results.fun / (cutout.image.array.size - 6)
173 measRecord.set(self.
keyXC, xc_fit)
174 measRecord.set(self.
keyYC, yc_fit)
175 measRecord.set(self.
keyX0, x0_fit)
176 measRecord.set(self.
keyY0, y0_fit)
177 measRecord.set(self.
keyX1, x1_fit)
178 measRecord.set(self.
keyY1, y1_fit)
179 measRecord.set(self.
keyFlux, flux_fit)
180 measRecord.set(self.
keyLength, length_fit)
181 measRecord.set(self.
keyTheta, theta_fit)
184 def fail(self, measRecord, error=None):
189 lsst.meas.base.SingleFramePlugin.fail
194 self.
flagHandler.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)
def getMeasurementCutout(measRecord, exposure)