Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# 

2# This file is part of meas_extensions_trailedSources. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

6# (http://www.lsst.org). 

7# See the COPYRIGHT file at the top-level directory of this distribution 

8# for details of code ownership. 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# 

23 

24import numpy as np 

25 

26from lsst.meas.base.pluginRegistry import register 

27from lsst.meas.base import SingleFramePlugin, SingleFramePluginConfig 

28from lsst.meas.base import FlagHandler, FlagDefinitionList, SafeCentroidExtractor 

29from lsst.meas.base import MeasurementError 

30 

31__all__ = ("SingleFrameNaiveTrailConfig", "SingleFrameNaiveTrailPlugin") 

32 

33 

34class SingleFrameNaiveTrailConfig(SingleFramePluginConfig): 

35 """Config class for SingleFrameNaiveTrailPlugin. 

36 """ 

37 pass 

38 

39 

40@register("ext_trailedSources_Naive") 

41class SingleFrameNaiveTrailPlugin(SingleFramePlugin): 

42 """Naive trailed source measurement plugin 

43 

44 Measures the length, angle from +x-axis, and end points of an extended 

45 source using the second moments. 

46 

47 Parameters 

48 ---------- 

49 config: `SingleFrameNaiveTrailConfig` 

50 Plugin configuration. 

51 name: `str` 

52 Plugin name. 

53 schema: `lsst.afw.table.Schema` 

54 Schema for the output catalog. 

55 metadata: `lsst.daf.base.PropertySet` 

56 Metadata to be attached to output catalog. 

57 

58 Notes 

59 ----- 

60 This measurement plugin aims to utilize the already measured shape second 

61 moments to naively estimate the length and angle, and thus end-points, of a 

62 fast-moving, trailed source. The estimate for the trail length is 

63 obtained by doubling the semi-major axis, a, of the ellipse defined by 

64 the second moments. The angle, theta, from the x-axis is computed 

65 similarly (via second moments). The end points of the trail are then given 

66 by (xc +/- a*cos(theta), yc +/- a*sin(theta)), with xc and yc being the 

67 centroid coordinates. 

68 

69 See also 

70 -------- 

71 lsst.meas.base.SingleFramePlugin 

72 """ 

73 

74 ConfigClass = SingleFrameNaiveTrailConfig 

75 

76 @classmethod 

77 def getExecutionOrder(cls): 

78 # Needs centroids, shape, and flux measurements. 

79 # VeresPlugin is run after, which requires image data. 

80 return cls.APCORR_ORDER + 0.1 

81 

82 def __init__(self, config, name, schema, metadata): 

83 super().__init__(config, name, schema, metadata) 

84 

85 # Measurement Keys 

86 self.keyX0 = schema.addField(name + "_x0", type="D", doc="Trail head X coordinate.", units="pixel") 

87 self.keyY0 = schema.addField(name + "_y0", type="D", doc="Trail head Y coordinate.", units="pixel") 

88 self.keyX1 = schema.addField(name + "_x1", type="D", doc="Trail tail X coordinate.", units="pixel") 

89 self.keyY1 = schema.addField(name + "_y1", type="D", doc="Trail tail Y coordinate.", units="pixel") 

90 self.keyFlux = schema.addField(name + "_flux", type="D", doc="Trailed source flux.", units="count") 

91 self.keyL = schema.addField(name + "_length", type="D", doc="Trail length.", units="pixel") 

92 self.keyAngle = schema.addField(name + "_angle", type="D", doc="Angle measured from +x-axis.") 

93 

94 # Measurement Error Keys 

95 self.keyX0Err = schema.addField(name + "_x0Err", type="D", 

96 doc="Trail head X coordinate error.", units="pixel") 

97 self.keyY0Err = schema.addField(name + "_y0Err", type="D", 

98 doc="Trail head Y coordinate error.", units="pixel") 

99 self.keyX1Err = schema.addField(name + "_x1Err", type="D", 

100 doc="Trail tail X coordinate error.", units="pixel") 

101 self.keyY1Err = schema.addField(name + "_y1Err", type="D", 

102 doc="Trail tail Y coordinate error.", units="pixel") 

103 

104 flagDefs = FlagDefinitionList() 

105 flagDefs.addFailureFlag("No trailed-source measured") 

106 self.NO_FLUX = flagDefs.add("flag_noFlux", "No suitable prior flux measurement") 

107 self.flagHandler = FlagHandler.addFields(schema, name, flagDefs) 

108 

109 self.centriodExtractor = SafeCentroidExtractor(schema, name) 

110 

111 def measure(self, measRecord, exposure): 

112 """Run the Naive trailed source measurement algorithm. 

113 

114 Parameters 

115 ---------- 

116 measRecord : `lsst.afw.table.SourceRecord` 

117 Record describing the object being measured. 

118 exposure : `lsst.afw.image.Exposure` 

119 Pixel data to be measured. 

120 

121 See also 

122 -------- 

123 lsst.meas.base.SingleFramePlugin.measure 

124 """ 

125 xc, yc = self.centriodExtractor(measRecord, self.flagHandler) 

126 Ixx, Iyy, Ixy = measRecord.getShape().getParameterVector() 

127 xmy = Ixx - Iyy 

128 xpy = Ixx + Iyy 

129 xmy2 = xmy*xmy 

130 xy2 = Ixy*Ixy 

131 a = np.sqrt(0.5 * (xpy + np.sqrt(xmy2 + 4.0*xy2))) 

132 L = 2.0*a 

133 

134 theta = 0.5 * np.arctan2(2.0 * Ixy, xmy) 

135 dydt = a*np.cos(theta) 

136 dxdt = a*np.sin(theta) 

137 x0 = xc - dydt 

138 y0 = yc - dxdt 

139 x1 = xc + dydt 

140 y1 = yc + dxdt 

141 

142 # For now, use the shape flux. 

143 F = measRecord.get("base_SdssShape_instFlux") 

144 

145 # Fall back to aperture flux 

146 if not np.isfinite(F): 

147 if np.isfinite(measRecord.getApInstFlux()): 

148 F = measRecord.getApInstFlux() 

149 else: 

150 raise MeasurementError(self.NO_FLUX.doc, self.NO_FLUX.number) 

151 

152 # Propagate errors from second moments 

153 xcErr2, ycErr2 = np.diag(measRecord.getCentroidErr()) 

154 IxxErr2, IyyErr2, IxyErr2 = np.diag(measRecord.getShapeErr()) 

155 desc = np.sqrt(xmy2 + 4.0*xy2) # Descriminant^1/2 of EV equation 

156 denom = 2*np.sqrt(2.0*(Ixx + np.sqrt(4.0*xy2 + xmy2 + Iyy))) # Denominator for dadIxx and dadIyy 

157 dadIxx = (1.0 + (xmy/desc)) / denom 

158 dadIyy = (1.0 - (xmy/desc)) / denom 

159 dadIxy = (4.0*Ixy) / (desc * denom) 

160 aErr2 = IxxErr2*dadIxx*dadIxx + IyyErr2*dadIyy*dadIyy + IxyErr2*dadIxy*dadIxy 

161 thetaErr2 = ((IxxErr2 + IyyErr2)*xy2 + xmy2*IxyErr2) / (desc*desc*desc*desc) 

162 

163 dxda = np.cos(theta) 

164 dyda = np.sin(theta) 

165 xErr2 = aErr2*dxda*dxda + thetaErr2*dxdt*dxdt 

166 yErr2 = aErr2*dyda*dyda + thetaErr2*dydt*dydt 

167 x0Err = np.sqrt(xErr2 + xcErr2) # Same for x1 

168 y0Err = np.sqrt(yErr2 + ycErr2) # Same for y1 

169 

170 # Set flags 

171 measRecord.set(self.keyX0, x0) 

172 measRecord.set(self.keyY0, y0) 

173 measRecord.set(self.keyX1, x1) 

174 measRecord.set(self.keyY1, y1) 

175 measRecord.set(self.keyFlux, F) 

176 measRecord.set(self.keyL, L) 

177 measRecord.set(self.keyAngle, theta) 

178 measRecord.set(self.keyX0Err, x0Err) 

179 measRecord.set(self.keyY0Err, y0Err) 

180 measRecord.set(self.keyX1Err, x0Err) 

181 measRecord.set(self.keyY1Err, y0Err) 

182 

183 def fail(self, measRecord, error=None): 

184 """Record failure 

185 

186 See also 

187 -------- 

188 lsst.meas.base.SingleFramePlugin.fail 

189 """ 

190 if error is None: 

191 self.flagHandler.handleFailure(measRecord) 

192 else: 

193 self.flagHandler.handleFailure(measRecord, error.cpp)