90 def __init__(self, config, name, schema, metadata, logName=None):
93 super().
__init__(config, name, schema, metadata, logName=logName)
96 self.
keyRa = schema.addField(name +
"_ra", type=
"D", doc=
"Trail centroid right ascension.")
97 self.
keyDec = schema.addField(name +
"_dec", type=
"D", doc=
"Trail centroid declination.")
98 self.
keyX0 = schema.addField(name +
"_x0", type=
"D", doc=
"Trail head X coordinate.", units=
"pixel")
99 self.
keyY0 = schema.addField(name +
"_y0", type=
"D", doc=
"Trail head Y coordinate.", units=
"pixel")
100 self.
keyX1 = schema.addField(name +
"_x1", type=
"D", doc=
"Trail tail X coordinate.", units=
"pixel")
101 self.
keyY1 = schema.addField(name +
"_y1", type=
"D", doc=
"Trail tail Y coordinate.", units=
"pixel")
102 self.
keyFlux = schema.addField(name +
"_flux", type=
"D", doc=
"Trailed source flux.", units=
"count")
103 self.
keyLength = schema.addField(name +
"_length", type=
"D", doc=
"Trail length.", units=
"pixel")
104 self.
keyAngle = schema.addField(name +
"_angle", type=
"D", doc=
"Angle measured from +x-axis.")
107 self.
keyX0Err = schema.addField(name +
"_x0Err", type=
"D",
108 doc=
"Trail head X coordinate error.", units=
"pixel")
109 self.
keyY0Err = schema.addField(name +
"_y0Err", type=
"D",
110 doc=
"Trail head Y coordinate error.", units=
"pixel")
111 self.
keyX1Err = schema.addField(name +
"_x1Err", type=
"D",
112 doc=
"Trail tail X coordinate error.", units=
"pixel")
113 self.
keyY1Err = schema.addField(name +
"_y1Err", type=
"D",
114 doc=
"Trail tail Y coordinate error.", units=
"pixel")
115 self.
keyFluxErr = schema.addField(name +
"_fluxErr", type=
"D",
116 doc=
"Trail flux error.", units=
"count")
118 doc=
"Trail length error.", units=
"pixel")
119 self.
keyAngleErr = schema.addField(name +
"_angleErr", type=
"D", doc=
"Trail angle error.")
122 self.
FAILURE = flagDefs.addFailureFlag(
"No trailed-source measured")
123 self.
NO_FLUX = flagDefs.add(
"flag_noFlux",
"No suitable prior flux measurement")
124 self.
NO_CONVERGE = flagDefs.add(
"flag_noConverge",
"The root finder did not converge")
125 self.
NO_SIGMA = flagDefs.add(
"flag_noSigma",
"No PSF width (sigma)")
126 self.
SAFE_CENTROID = flagDefs.add(
"flag_safeCentroid",
"Fell back to safe centroid extractor")
127 self.
EDGE = flagDefs.add(
"flag_edge",
"Trail contains edge pixels")
128 self.
OFFIMAGE = flagDefs.add(
"flag_off_image",
"Trail extends off image")
129 self.
NAN = flagDefs.add(
"flag_nan",
"One or more trail coordinates are missing")
131 "Trail length is greater than three times the psf radius")
135 self.
log = logging.getLogger(self.logName)
138 """Run the Naive trailed source measurement algorithm.
142 measRecord : `lsst.afw.table.SourceRecord`
143 Record describing the object being measured.
144 exposure : `lsst.afw.image.Exposure`
145 Pixel data to be measured.
149 lsst.meas.base.SingleFramePlugin.measure
153 xc = measRecord.get(
"base_SdssShape_x")
154 yc = measRecord.get(
"base_SdssShape_y")
155 if not np.isfinite(xc)
or not np.isfinite(yc):
163 if measRecord.getShapeFlag():
164 self.
log.warning(
"Shape flag is set for measRecord: %s. Trail measurement "
165 "will not be made.", measRecord.getId())
167 self.
flagHandler.setValue(measRecord, self.SHAPE.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))
181 if measRecord.get(
"base_SdssShape_flag_unweighted"):
182 self.
log.debug(
"Unweighted")
185 self.
log.debug(
"Weighted")
186 length, gradLength, results = self.
findLength(a2, b2)
187 if not results.converged:
188 self.
log.info(
"Results not converged: %s", results.flag)
194 theta = 0.5 * np.arctan2(2.0 * Ixy, xmy)
198 dydtheta = radius*np.cos(theta)
199 dxdtheta = radius*np.sin(theta)
205 self.
check_trail(measRecord, exposure, x0, y0, x1, y1, length)
208 cutout = getMeasurementCutout(measRecord, exposure)
211 params = np.array([xc, yc, 1.0, length, theta])
212 model = VeresModel(cutout)
213 flux, gradFlux = model.computeFluxWithGradient(params)
216 if not np.isfinite(flux):
217 if np.isfinite(measRecord.getApInstFlux()):
218 flux = measRecord.getApInstFlux()
225 IxxErr2, IyyErr2, IxyErr2 = np.diag(measRecord.getShapeErr())
229 xcErr2, ycErr2 = np.diag(measRecord.getCentroidErr())
232 desc = sqrt(xmy2 + 4.0*xy2)
233 da2dIxx = 0.5*(1.0 + (xmy/desc))
234 da2dIyy = 0.5*(1.0 - (xmy/desc))
235 da2dIxy = 2.0*Ixy / desc
236 a2Err2 = IxxErr2*da2dIxx*da2dIxx + IyyErr2*da2dIyy*da2dIyy + IxyErr2*da2dIxy*da2dIxy
237 b2Err2 = IxxErr2*da2dIyy*da2dIyy + IyyErr2*da2dIxx*da2dIxx + IxyErr2*da2dIxy*da2dIxy
238 dLda2, dLdb2 = gradLength
239 lengthErr = np.sqrt(dLda2*dLda2*a2Err2 + dLdb2*dLdb2*b2Err2)
242 dThetadIxx = -Ixy / (xmy2 + 4.0*xy2)
243 dThetadIxy = xmy / (xmy2 + 4.0*xy2)
244 thetaErr = sqrt(dThetadIxx*dThetadIxx*(IxxErr2 + IyyErr2) + dThetadIxy*dThetadIxy*IxyErr2)
247 dFdxc, dFdyc, _, dFdL, dFdTheta = gradFlux
248 fluxErr = sqrt(dFdL*dFdL*lengthErr*lengthErr + dFdTheta*dFdTheta*thetaErr*thetaErr
249 + dFdxc*dFdxc*xcErr2 + dFdyc*dFdyc*ycErr2)
252 dxdradius = np.cos(theta)
253 dydradius = np.sin(theta)
254 radiusErr2 = lengthErr*lengthErr/4.0
255 xErr2 = sqrt(xcErr2 + radiusErr2*dxdradius*dxdradius + thetaErr*thetaErr*dxdtheta*dxdtheta)
256 yErr2 = sqrt(ycErr2 + radiusErr2*dydradius*dydradius + thetaErr*thetaErr*dydtheta*dydtheta)
261 measRecord.set(self.
keyRa, ra)
262 measRecord.set(self.
keyDec, dec)
263 measRecord.set(self.
keyX0, x0)
264 measRecord.set(self.
keyY0, y0)
265 measRecord.set(self.
keyX1, x1)
266 measRecord.set(self.
keyY1, y1)
267 measRecord.set(self.
keyFlux, flux)
269 measRecord.set(self.
keyAngle, theta)
270 measRecord.set(self.
keyX0Err, x0Err)
271 measRecord.set(self.
keyY0Err, y0Err)
272 measRecord.set(self.
keyX1Err, x0Err)
273 measRecord.set(self.
keyY1Err, y0Err)
278 def check_trail(self, measRecord, exposure, x0, y0, x1, y1, length):
279 """ Set flags for edge pixels, off chip, and nan trail coordinates and
280 flag if trail length is three times larger than psf.
282 Check if the coordinates of the beginning and ending of the trail fall
283 inside the exposures bounding box. If not, set the off_chip flag.
284 If the beginning or ending falls within a pixel marked as edge, set the
285 edge flag. If any of the coordinates happens to fall on a nan, then
287 Additionally, check if the trail is three times larger than the psf. If
288 so, set the suspect trail flag.
292 measRecord: `lsst.afw.MeasurementRecord`
293 Record describing the object being measured.
294 exposure: `lsst.afw.Exposure`
295 Pixel data to be measured.
298 x coordinate of the beginning of the trail.
300 y coordinate of the beginning of the trail.
302 x coordinate of the end of the trail.
304 y coordinate of the end of the trail.
311 if np.isnan(x_coords).any()
or np.isnan(y_coords).any():
313 x_coords = [x
for x
in x_coords
if not np.isnan(x)]
314 y_coords = [y
for y
in y_coords
if not np.isnan(y)]
317 if not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
318 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
324 for (x_val, y_val)
in zip(x_coords, y_coords):
325 if x_val
is not np.nan
and y_val
is not np.nan:
326 if exposure.mask[Point2I(int(x_val),
327 int(y_val))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0:
331 elif not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
332 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
339 if exposure.mask[Point2I(int(x0), int(y0))]
and exposure.mask[Point2I(int(x1), int(y1))]:
340 if ((exposure.mask[Point2I(int(x0), int(y0))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0)
341 or (exposure.mask[Point2I(int(x1), int(y1))]
342 & exposure.mask.getPlaneBitMask(
'EDGE') != 0)):
345 psfShape = exposure.psf.computeShape(exposure.getBBox().getCenter())
346 psfRadius = psfShape.getDeterminantRadius()
348 if length > psfRadius*3.0: