95 def __init__(self, config, name, schema, metadata, logName=None):
98 super().
__init__(config, name, schema, metadata, logName=logName)
101 self.
keyRa = schema.addField(name +
"_ra", type=
"D", doc=
"Trail centroid right ascension.")
102 self.
keyDec = schema.addField(name +
"_dec", type=
"D", doc=
"Trail centroid declination.")
103 self.
keyX0 = schema.addField(name +
"_x0", type=
"D", doc=
"Trail head X coordinate.", units=
"pixel")
104 self.
keyY0 = schema.addField(name +
"_y0", type=
"D", doc=
"Trail head Y coordinate.", units=
"pixel")
105 self.
keyX1 = schema.addField(name +
"_x1", type=
"D", doc=
"Trail tail X coordinate.", units=
"pixel")
106 self.
keyY1 = schema.addField(name +
"_y1", type=
"D", doc=
"Trail tail Y coordinate.", units=
"pixel")
107 self.
keyFlux = schema.addField(name +
"_flux", type=
"D", doc=
"Trailed source flux.", units=
"count")
108 self.
keyLength = schema.addField(name +
"_length", type=
"D", doc=
"Trail length.", units=
"pixel")
109 self.
keyAngle = schema.addField(name +
"_angle", type=
"D", doc=
"Angle measured from +x-axis.")
112 self.
keyX0Err = schema.addField(name +
"_x0Err", type=
"D",
113 doc=
"Trail head X coordinate error.", units=
"pixel")
114 self.
keyY0Err = schema.addField(name +
"_y0Err", type=
"D",
115 doc=
"Trail head Y coordinate error.", units=
"pixel")
116 self.
keyX1Err = schema.addField(name +
"_x1Err", type=
"D",
117 doc=
"Trail tail X coordinate error.", units=
"pixel")
118 self.
keyY1Err = schema.addField(name +
"_y1Err", type=
"D",
119 doc=
"Trail tail Y coordinate error.", units=
"pixel")
120 self.
keyFluxErr = schema.addField(name +
"_fluxErr", type=
"D",
121 doc=
"Trail flux error.", units=
"count")
123 doc=
"Trail length error.", units=
"pixel")
124 self.
keyAngleErr = schema.addField(name +
"_angleErr", type=
"D", doc=
"Trail angle error.")
127 self.
FAILURE = flagDefs.addFailureFlag(
"No trailed-source measured")
128 self.
NO_FLUX = flagDefs.add(
"flag_noFlux",
"No suitable prior flux measurement")
129 self.
NO_CONVERGE = flagDefs.add(
"flag_noConverge",
"The root finder did not converge")
130 self.
NO_SIGMA = flagDefs.add(
"flag_noSigma",
"No PSF width (sigma)")
131 self.
EDGE = flagDefs.add(
"flag_edge",
"Trail contains edge pixels")
132 self.
OFFIMAGE = flagDefs.add(
"flag_off_image",
"Trail extends off image")
133 self.
NAN = flagDefs.add(
"flag_nan",
"One or more trail coordinates are missing")
135 "Trail length is greater than three times the psf radius")
136 self.
SHAPE = flagDefs.add(
"flag_shape",
"Shape flag is set, trail length not calculated")
142 """Run the Naive trailed source measurement algorithm.
146 measRecord : `lsst.afw.table.SourceRecord`
147 Record describing the object being measured.
148 exposure : `lsst.afw.image.Exposure`
149 Pixel data to be measured.
153 lsst.meas.base.SingleFramePlugin.measure
155 if measRecord.getShapeFlag():
156 self.
log.debug(
"Shape flag is set for measRecord: %s. Trail measurement "
157 "will not be made. All trail values will be set to nan.", measRecord.getId())
162 xc = measRecord[
"slot_Shape_x"]
163 yc = measRecord[
"slot_Shape_y"]
164 if not np.isfinite(xc)
or not np.isfinite(yc):
165 self.
flagHandler.setValue(measRecord, self.SAFE_CENTROID.number,
True)
171 Ixx, Iyy, Ixy = measRecord.getShape().getParameterVector()
176 a2 = 0.5 * (xpy + sqrt(xmy2 + 4.0*xy2))
177 b2 = 0.5 * (xpy - sqrt(xmy2 + 4.0*xy2))
180 length, gradLength, results = self.
findLength(a2, b2)
181 if not results.converged:
182 self.
log.info(
"Results not converged: %s", results.flag)
188 theta = 0.5 * np.arctan2(2.0 * Ixy, xmy)
192 dydtheta = radius*np.cos(theta)
193 dxdtheta = radius*np.sin(theta)
199 self.
check_trail(measRecord, exposure, x0, y0, x1, y1, length)
202 cutout = getMeasurementCutout(measRecord, exposure)
205 params = np.array([xc, yc, 1.0, length, theta])
206 model = VeresModel(cutout)
207 flux, gradFlux = model.computeFluxWithGradient(params)
210 if (
not np.isfinite(flux)) | (np.abs(flux) > self.
config.maxFlux):
211 if np.isfinite(measRecord.getApInstFlux()):
212 flux = measRecord.getApInstFlux()
219 IxxErr2, IyyErr2, IxyErr2 = np.diag(measRecord.getShapeErr())
223 xcErr2, ycErr2 = np.diag(measRecord.getCentroidErr())
226 desc = sqrt(xmy2 + 4.0*xy2)
227 da2dIxx = 0.5*(1.0 + (xmy/desc))
228 da2dIyy = 0.5*(1.0 - (xmy/desc))
229 da2dIxy = 2.0*Ixy / desc
230 a2Err2 = IxxErr2*da2dIxx*da2dIxx + IyyErr2*da2dIyy*da2dIyy + IxyErr2*da2dIxy*da2dIxy
231 b2Err2 = IxxErr2*da2dIyy*da2dIyy + IyyErr2*da2dIxx*da2dIxx + IxyErr2*da2dIxy*da2dIxy
232 dLda2, dLdb2 = gradLength
233 lengthErr = np.sqrt(dLda2*dLda2*a2Err2 + dLdb2*dLdb2*b2Err2)
236 dThetadIxx = -Ixy / (xmy2 + 4.0*xy2)
237 dThetadIxy = xmy / (xmy2 + 4.0*xy2)
238 thetaErr = sqrt(dThetadIxx*dThetadIxx*(IxxErr2 + IyyErr2) + dThetadIxy*dThetadIxy*IxyErr2)
241 dFdxc, dFdyc, _, dFdL, dFdTheta = gradFlux
242 fluxErr = sqrt(dFdL*dFdL*lengthErr*lengthErr + dFdTheta*dFdTheta*thetaErr*thetaErr
243 + dFdxc*dFdxc*xcErr2 + dFdyc*dFdyc*ycErr2)
246 dxdradius = np.cos(theta)
247 dydradius = np.sin(theta)
248 radiusErr2 = lengthErr*lengthErr/4.0
249 xErr2 = sqrt(xcErr2 + radiusErr2*dxdradius*dxdradius + thetaErr*thetaErr*dxdtheta*dxdtheta)
250 yErr2 = sqrt(ycErr2 + radiusErr2*dydradius*dydradius + thetaErr*thetaErr*dydtheta*dydtheta)
255 measRecord.set(self.
keyRa, ra)
256 measRecord.set(self.
keyDec, dec)
257 measRecord.set(self.
keyX0, x0)
258 measRecord.set(self.
keyY0, y0)
259 measRecord.set(self.
keyX1, x1)
260 measRecord.set(self.
keyY1, y1)
261 measRecord.set(self.
keyFlux, flux)
263 measRecord.set(self.
keyAngle, theta)
264 measRecord.set(self.
keyX0Err, x0Err)
265 measRecord.set(self.
keyY0Err, y0Err)
266 measRecord.set(self.
keyX1Err, x0Err)
267 measRecord.set(self.
keyY1Err, y0Err)
272 def check_trail(self, measRecord, exposure, x0, y0, x1, y1, length):
273 """ Set flags for edge pixels, off chip, and nan trail coordinates and
274 flag if trail length is three times larger than psf.
276 Check if the coordinates of the beginning and ending of the trail fall
277 inside the exposures bounding box. If not, set the off_chip flag.
278 If the beginning or ending falls within a pixel marked as edge, set the
279 edge flag. If any of the coordinates happens to fall on a nan, then
281 Additionally, check if the trail is three times larger than the psf. If
282 so, set the suspect trail flag.
286 measRecord: `lsst.afw.MeasurementRecord`
287 Record describing the object being measured.
288 exposure: `lsst.afw.Exposure`
289 Pixel data to be measured.
292 x coordinate of the beginning of the trail.
294 y coordinate of the beginning of the trail.
296 x coordinate of the end of the trail.
298 y coordinate of the end of the trail.
305 if np.isnan(x_coords).any()
or np.isnan(y_coords).any():
307 x_coords = [x
for x
in x_coords
if not np.isnan(x)]
308 y_coords = [y
for y
in y_coords
if not np.isnan(y)]
311 if not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
312 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
318 for (x_val, y_val)
in zip(x_coords, y_coords):
319 if x_val
is not np.nan
and y_val
is not np.nan:
320 if exposure.mask[Point2I(int(x_val),
321 int(y_val))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0:
325 elif not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
326 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
333 if exposure.mask[Point2I(int(x0), int(y0))]
and exposure.mask[Point2I(int(x1), int(y1))]:
334 if ((exposure.mask[Point2I(int(x0), int(y0))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0)
335 or (exposure.mask[Point2I(int(x1), int(y1))]
336 & exposure.mask.getPlaneBitMask(
'EDGE') != 0)):
339 psfShape = exposure.psf.computeShape(exposure.getBBox().getCenter())
340 psfRadius = psfShape.getDeterminantRadius()
342 if length > psfRadius*3.0: