Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mth5 \ mth5 \ io \ nims \ response_filters.py: 100%

65 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-10 00:01 -0800

1# -*- coding: utf-8 -*- 

2""" 

3Created on Fri Sep 2 13:50:51 2022 

4 

5@author: jpeacock 

6""" 

7 

8# ============================================================================= 

9# Imports 

10# ============================================================================= 

11from mt_metadata.timeseries.filters import ( 

12 ChannelResponse, 

13 CoefficientFilter, 

14 PoleZeroFilter, 

15 TimeDelayFilter, 

16) 

17 

18 

19# ============================================================================= 

20 

21 

22class ResponseError(Exception): 

23 pass 

24 

25 

26class Response(object): 

27 """ 

28 Common NIMS response filters for electric and magnetic channels 

29 

30 """ 

31 

32 def __init__(self, system_id=None, **kwargs): 

33 self.system_id = system_id 

34 self.hardware = "PC" 

35 self.instrument_type = "backbone" 

36 self.sample_rate = 1 

37 

38 self.e_conversion_factor = 409600000.0 

39 self.h_conversion_factor = 100 

40 

41 self.time_delays_dict = { 

42 "hp200": { 

43 "hx": -0.0055, 

44 "hy": -0.0145, 

45 "hz": -0.0235, 

46 "ex": -0.1525, 

47 "ey": -0.0275, 

48 }, 

49 1: { 

50 "hx": -0.1920, 

51 "hy": -0.2010, 

52 "hz": -0.2100, 

53 "ex": -0.2850, 

54 "ey": -0.2850, 

55 }, 

56 8: { 

57 "hx": -0.2455, 

58 "hy": -0.2365, 

59 "hz": -0.2275, 

60 "ex": -0.1525, 

61 "ey": -0.1525, 

62 }, 

63 } 

64 

65 for key, value in kwargs.items(): 

66 setattr(self, key, value) 

67 

68 @property 

69 def magnetic_low_pass(self): 

70 """ 

71 Low pass 3 pole filter 

72 

73 :return: DESCRIPTION 

74 :rtype: TYPE 

75 

76 """ 

77 return PoleZeroFilter( 

78 name="nims_3_pole_butterworth", 

79 zeros=[], 

80 poles=[ 

81 complex(-6.283185, 10.882477), 

82 complex(-6.283185, -10.882477), 

83 complex(-12.566371, 0), 

84 ], 

85 units_out="Volt", 

86 units_in="nanoTesla", 

87 normalization_factor=2002.26936395594, 

88 ) 

89 

90 @property 

91 def magnetic_conversion(self): 

92 """ 

93 

94 :return: DESCRIPTION 

95 :rtype: TYPE 

96 

97 """ 

98 

99 return CoefficientFilter( 

100 name="h_analog_to_digital", 

101 gain=self.h_conversion_factor, 

102 units_in="Volt", 

103 units_out="count", 

104 ) 

105 

106 @property 

107 def electric_low_pass(self): 

108 """ 

109 5 pole electric low pass filter 

110 :return: DESCRIPTION 

111 :rtype: TYPE 

112 

113 """ 

114 return PoleZeroFilter( 

115 name="nims_5_pole_butterworth", 

116 zeros=[], 

117 poles=[ 

118 complex(-3.88301, 11.9519), 

119 complex(-3.88301, -11.9519), 

120 complex(-10.1662, 7.38651), 

121 complex(-10.1662, -7.38651), 

122 complex(-12.5664, 0.0), 

123 ], 

124 units_in="Volt", 

125 units_out="Volt", 

126 normalization_factor=313383.493219835, 

127 ) 

128 

129 @property 

130 def electric_high_pass_pc(self): 

131 """ 

132 1-pole low pass filter for 8 hz instruments 

133 :return: DESCRIPTION 

134 :rtype: TYPE 

135 

136 """ 

137 return PoleZeroFilter( 

138 name="nims_1_pole_butterworth", 

139 zeros=[complex(0.0, 0.0)], 

140 poles=[complex(-3.333333e-05, 0.0)], 

141 normalization_factor=1, 

142 units_in="Volt", 

143 units_out="Volt", 

144 ) 

145 

146 @property 

147 def electric_high_pass_hp(self): 

148 """ 

149 1-pole low pass for 1 hz instuments 

150 :return: DESCRIPTION 

151 :rtype: TYPE 

152 

153 """ 

154 return PoleZeroFilter( 

155 name="nims_1_pole_butterworth", 

156 zeros=[complex(0.0, 0.0)], 

157 poles=[complex(-1.66667e-04, 0.0)], 

158 normalization_factor=1, 

159 units_in="Volt", 

160 units_out="Volt", 

161 ) 

162 

163 @property 

164 def electric_conversion(self): 

165 """ 

166 electric channel conversion from counts to Volts 

167 :return: DESCRIPTION 

168 :rtype: TYPE 

169 

170 """ 

171 return CoefficientFilter( 

172 name="e_analog_to_digital", 

173 gain=self.e_conversion_factor, 

174 units_in="Volt", 

175 units_out="count", 

176 ) 

177 

178 @property 

179 def electric_physical_units(self): 

180 """ 

181 

182 :return: DESCRIPTION 

183 :rtype: TYPE 

184 

185 """ 

186 return CoefficientFilter( 

187 name="to_mt_units", 

188 gain=1e-6, 

189 units_in="milliVolt per kilometer", 

190 units_out="Volt per meter", 

191 ) 

192 

193 def get_electric_high_pass(self, hardware="pc"): 

194 """ 

195 get the electric high pass filter based on the hardware 

196 """ 

197 

198 self.hardware = hardware 

199 if "pc" in hardware.lower(): 

200 return self.electric_high_pass_pc 

201 elif "hp" in hardware.lower(): 

202 return self.electric_high_pass_hp 

203 else: 

204 raise ResponseError(f"Hardware value {hardware} not understood") 

205 

206 def _get_dt_filter(self, channel, sample_rate): 

207 """ 

208 get the DT filter based on channel ans sampling rate 

209 """ 

210 dt_filter = TimeDelayFilter( 

211 name=f"{channel}_time_offset", 

212 delay=self.time_delays_dict[sample_rate][channel], 

213 units_in="count", 

214 units_out="count", 

215 ) 

216 return dt_filter 

217 

218 def dipole_filter(self, length): 

219 """ 

220 Make a dipole filter 

221 

222 :param length: dipole length in meters 

223 :type length: TYPE 

224 :return: DESCRIPTION 

225 :rtype: TYPE 

226 

227 """ 

228 

229 return CoefficientFilter( 

230 name=f"dipole_{length:.2f}", 

231 gain=length, 

232 units_in="Volt per meter", 

233 units_out="Volt", 

234 ) 

235 

236 def _get_magnetic_filter(self, channel): 

237 """ 

238 get mag filter, seems to be the same no matter what 

239 """ 

240 

241 return [ 

242 self.magnetic_low_pass, 

243 self.magnetic_conversion, 

244 self._get_dt_filter(channel, self.sample_rate), 

245 ] 

246 

247 def _get_electric_filter(self, channel, dipole_length): 

248 """ 

249 Get electric filter 

250 """ 

251 

252 filter_list = [] 

253 filter_list.append(self.electric_physical_units) 

254 filter_list.append(self.dipole_filter(dipole_length)) 

255 filter_list.append(self.electric_low_pass) 

256 if self.instrument_type in ["backbone"]: 

257 filter_list.append(self.get_electric_high_pass(self.hardware)) 

258 

259 filter_list.append(self.electric_conversion) 

260 filter_list.append(self._get_dt_filter(channel, self.sample_rate)) 

261 

262 return filter_list 

263 

264 def get_channel_response(self, channel, dipole_length=1): 

265 """ 

266 Get the full channel response filter 

267 :param channel: DESCRIPTION 

268 :type channel: TYPE 

269 :param dipole_length: DESCRIPTION, defaults to 1 

270 :type dipole_length: TYPE, optional 

271 :return: DESCRIPTION 

272 :rtype: TYPE 

273 

274 """ 

275 

276 if channel[0] in ["e"]: 

277 return ChannelResponse( 

278 filters_list=self._get_electric_filter(channel, dipole_length) 

279 ) 

280 

281 elif channel[0] in ["b", "h"]: 

282 return ChannelResponse(filters_list=self._get_magnetic_filter(channel)) 

283 

284 else: 

285 raise ValueError(f"Channel {channel} not supported.")