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.")
126 doc=
"Algorithm key indicating which algorithm is "
127 "used to measure trailed source.")
130 self.
FAILURE = flagDefs.addFailureFlag(
"No trailed-source measured")
131 self.
NO_FLUX = flagDefs.add(
"flag_noFlux",
"No suitable prior flux measurement")
132 self.
NO_CONVERGE = flagDefs.add(
"flag_noConverge",
"The root finder did not converge")
133 self.
NO_SIGMA = flagDefs.add(
"flag_noSigma",
"No PSF width (sigma)")
134 self.
EDGE = flagDefs.add(
"flag_edge",
"Trail contains edge pixels")
135 self.
OFFIMAGE = flagDefs.add(
"flag_off_image",
"Trail extends off image")
136 self.
NAN = flagDefs.add(
"flag_nan",
"One or more trail coordinates are missing")
138 "Trail length is greater than three times the psf radius")
139 self.
SHAPE = flagDefs.add(
"flag_shape",
"Shape flag is set, trail length not calculated")
141 "Centroid flag is set, trail length not calculated")
144 self.
log = logging.getLogger(self.logName)
147 """Run the Naive trailed source measurement algorithm.
151 measRecord : `lsst.afw.table.SourceRecord`
152 Record describing the object being measured.
153 exposure : `lsst.afw.image.Exposure`
154 Pixel data to be measured.
158 lsst.meas.base.SingleFramePlugin.measure
161 use_sdss_shape =
True
162 if measRecord[
'base_SdssShape_flag']:
163 if measRecord.getShapeFlag():
164 self.
log.debug(
"HSM shape flag is also set for measRecord: %s. Trail measurement "
165 "will not be made. All trail values will be set to nan.", measRecord.getId())
170 use_sdss_shape =
False
173 "SDSS Shape flag is set for measRecord: %s. Falling back"
174 "to HSMshape. No error measurements will be made.", measRecord.getId())
175 xc = measRecord[
"slot_Shape_x"]
176 yc = measRecord[
"slot_Shape_y"]
177 Ixx, Iyy, Ixy = measRecord.getShape().getParameterVector()
181 xc = measRecord[
"base_SdssShape_x"]
182 yc = measRecord[
"base_SdssShape_y"]
183 Ixx = measRecord[
"base_SdssShape_xx"]
184 Iyy = measRecord[
"base_SdssShape_yy"]
185 Ixy = measRecord[
"base_SdssShape_xy"]
187 if not np.isfinite(xc)
or not np.isfinite(yc):
198 a2 = 0.5 * (xpy +
sqrt(xmy2 + 4.0*xy2))
199 b2 = 0.5 * (xpy -
sqrt(xmy2 + 4.0*xy2))
202 length, gradLength, results = self.
findLength(a2, b2)
203 if not results.converged:
204 self.
log.info(
"Results not converged: %s", results.flag)
210 theta = 0.5 * np.arctan2(2.0 * Ixy, xmy)
214 dydtheta = radius*np.cos(theta)
215 dxdtheta = radius*np.sin(theta)
221 self.
check_trail(measRecord, exposure, x0, y0, x1, y1, length)
224 cutout = getMeasurementCutout(measRecord, exposure)
227 params = np.array([xc, yc, 1.0, length, theta])
229 flux, gradFlux = model.computeFluxWithGradient(params)
232 if (
not np.isfinite(flux)) | (np.abs(flux) > self.config.maxFlux):
233 if np.isfinite(measRecord.getApInstFlux()):
234 flux = measRecord.getApInstFlux()
246 IxxErr2 = measRecord[
"base_SdssShape_xxErr"]**2
247 IyyErr2 = measRecord[
"base_SdssShape_yyErr"]**2
248 IxyErr2 = measRecord[
"base_SdssShape_xyErr"]**2
251 xcErr2 = measRecord[
"base_SdssCentroid_xErr"]**2
252 ycErr2 = measRecord[
"base_SdssCentroid_yErr"]**2
255 desc =
sqrt(xmy2 + 4.0*xy2)
256 da2dIxx = 0.5*(1.0 + (xmy/desc))
257 da2dIyy = 0.5*(1.0 - (xmy/desc))
258 da2dIxy = 2.0*Ixy / desc
259 a2Err2 = IxxErr2*da2dIxx*da2dIxx + IyyErr2*da2dIyy*da2dIyy + IxyErr2*da2dIxy*da2dIxy
260 b2Err2 = IxxErr2*da2dIyy*da2dIyy + IyyErr2*da2dIxx*da2dIxx + IxyErr2*da2dIxy*da2dIxy
261 dLda2, dLdb2 = gradLength
262 lengthErr = np.sqrt(dLda2*dLda2*a2Err2 + dLdb2*dLdb2*b2Err2)
265 dThetadIxx = -Ixy / (xmy2 + 4.0*xy2)
266 dThetadIxy = xmy / (xmy2 + 4.0*xy2)
267 thetaErr =
sqrt(dThetadIxx*dThetadIxx*(IxxErr2 + IyyErr2) + dThetadIxy*dThetadIxy*IxyErr2)
270 dFdxc, dFdyc, _, dFdL, dFdTheta = gradFlux
271 fluxErr =
sqrt(dFdL*dFdL*lengthErr*lengthErr + dFdTheta*dFdTheta*thetaErr*thetaErr
272 + dFdxc*dFdxc*xcErr2 + dFdyc*dFdyc*ycErr2)
275 dxdradius = np.cos(theta)
276 dydradius = np.sin(theta)
277 radiusErr2 = lengthErr*lengthErr/4.0
278 xErr2 =
sqrt(xcErr2 + radiusErr2*dxdradius*dxdradius + thetaErr*thetaErr*dxdtheta*dxdtheta)
279 yErr2 =
sqrt(ycErr2 + radiusErr2*dydradius*dydradius + thetaErr*thetaErr*dydtheta*dydtheta)
284 measRecord.set(self.
keyX0Err, x0Err)
285 measRecord.set(self.
keyY0Err, y0Err)
286 measRecord.set(self.
keyX1Err, x0Err)
287 measRecord.set(self.
keyY1Err, y0Err)
293 measRecord.set(self.
keyRa, ra)
294 measRecord.set(self.
keyDec, dec)
295 measRecord.set(self.
keyX0, x0)
296 measRecord.set(self.
keyY0, y0)
297 measRecord.set(self.
keyX1, x1)
298 measRecord.set(self.
keyY1, y1)
299 measRecord.set(self.
keyFlux, flux)
301 measRecord.set(self.
keyAngle, theta)
303 def check_trail(self, measRecord, exposure, x0, y0, x1, y1, length):
304 """ Set flags for edge pixels, off chip, and nan trail coordinates and
305 flag if trail length is three times larger than psf.
307 Check if the coordinates of the beginning and ending of the trail fall
308 inside the exposures bounding box. If not, set the off_chip flag.
309 If the beginning or ending falls within a pixel marked as edge, set the
310 edge flag. If any of the coordinates happens to fall on a nan, then
312 Additionally, check if the trail is three times larger than the psf. If
313 so, set the suspect trail flag.
317 measRecord: `lsst.afw.MeasurementRecord`
318 Record describing the object being measured.
319 exposure: `lsst.afw.Exposure`
320 Pixel data to be measured.
323 x coordinate of the beginning of the trail.
325 y coordinate of the beginning of the trail.
327 x coordinate of the end of the trail.
329 y coordinate of the end of the trail.
336 if np.isnan(x_coords).any()
or np.isnan(y_coords).any():
338 x_coords = [x
for x
in x_coords
if not np.isnan(x)]
339 y_coords = [y
for y
in y_coords
if not np.isnan(y)]
342 if not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
343 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
349 for (x_val, y_val)
in zip(x_coords, y_coords):
350 if x_val
is not np.nan
and y_val
is not np.nan:
351 if exposure.mask[
Point2I(int(x_val),
352 int(y_val))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0:
356 elif not (all(exposure.getBBox().beginX <= x <= exposure.getBBox().endX
for x
in x_coords)
357 and all(exposure.getBBox().beginY <= y <= exposure.getBBox().endY
for y
in y_coords)):
364 if exposure.mask[
Point2I(int(x0), int(y0))]
and exposure.mask[
Point2I(int(x1), int(y1))]:
365 if ((exposure.mask[
Point2I(int(x0), int(y0))] & exposure.mask.getPlaneBitMask(
'EDGE') != 0)
366 or (exposure.mask[
Point2I(int(x1), int(y1))]
367 & exposure.mask.getPlaneBitMask(
'EDGE') != 0)):
370 psfShape = exposure.psf.computeShape(exposure.getBBox().getCenter())
371 psfRadius = psfShape.getDeterminantRadius()
373 if length > psfRadius*3.0: