4
4
"""
5
5
__all__ = ["Site" , "Site1d" , "Site3d" ]
6
6
7
+ import sys
7
8
import datetime
8
9
import numpy as np
9
10
import pandas as pd
10
11
import scipy .interpolate
12
+ from scipy .signal import zpk2tf , filtfilt , bilinear_zpk
11
13
import matplotlib .pyplot as plt
12
14
13
15
from .utils import apparent_resistivity
@@ -44,6 +46,11 @@ def __init__(self, name):
44
46
self .min_period = None
45
47
self .max_period = None
46
48
49
+ self .samplingrate = None
50
+ self .nimsid = None
51
+ self .zpk = None
52
+ self .timedelays = None
53
+
47
54
def convolve_fft (self , mag_x , mag_y , dt = 60 ):
48
55
"""Convolution in frequency space."""
49
56
# pylint: disable=invalid-name
@@ -83,6 +90,122 @@ def calcZ(self, freqs): # pylint: disable=invalid-name
83
90
"""Calculates transfer function, Z, from the input frequencies."""
84
91
raise NotImplementedError ("calcZ not implemented for this object yet." )
85
92
93
+ def sysidcheck (self ):
94
+ """Returns logger and hardware, backbone from sysid and sampling rate (Hz)"""
95
+ sysid = self .nimsid
96
+ samplingrate = self .samplingrate
97
+
98
+ # first search for an 'H' for the hourly time series, and get rid of it for
99
+ # the further parsing
100
+ logger = 'MT1'
101
+ i = sysid .find ('H' )
102
+ if not i == - 1 :
103
+ logger = 'HP200'
104
+ sysid = sysid [0 :i ] + sysid [i + 2 :]
105
+
106
+ # parse the system ID. If it does not make sense, return an empty
107
+ # response structure.
108
+ j = sysid .find ('-' )
109
+ if j == - 1 :
110
+ sys .exit ('Invalid system ID in NIMsysRsp' )
111
+
112
+ nim1 = int (sysid [0 :j ])
113
+ # we know the following NIMS series
114
+ nimlist1 = [2106 , 2311 , 2405 , 2406 , 2501 , 2502 , 2503 , 2508 , 2509 , 2606 , 2611 ,
115
+ 2612 , 1303 , 1305 , 1105 ]
116
+
117
+ if np .isnan (nim1 ):
118
+ sys .exit ('NIMS ID ' + sysid + ' does not seem to be valid. Please correct.' )
119
+ elif nim1 not in nimlist1 :
120
+ sys .exit ('We do not know NIMS series ' + str (nim1 ) + '. Check the system ID.' )
121
+
122
+ nim2str = sysid [j + 2 :]
123
+ nim2 = int (nim2str )
124
+
125
+ # and assume that the possible numbers are 1-30
126
+ nimlist2 = range (1 , 31 )
127
+ backbone = 0
128
+
129
+ if nim2str [0 ] == 'B' or nim2str [0 ] == 'b' :
130
+ # recognized backbone NIMS ID
131
+ backbone = 1
132
+ elif nim2str [0 ] == 'A' or nim2str [0 ] == 'a' :
133
+ # recognized new experimental NIMS
134
+ print ('NIMS ID ' + sysid + ' is a new experimental system. Look out for clock drift.' )
135
+ elif np .isnan (nim2 ):
136
+ sys .exit ('NIMS ID ' + sysid + ' does not seem to be valid. Please correct.' )
137
+ elif nim2 not in nimlist2 :
138
+ sys .exit ('NIMS ID ' + sysid + ' does not seem to be valid. Please correct.' )
139
+
140
+ # if 2106-1/10, assume PC104 hardware of v2001 or v2006
141
+ hardware = 'STE'
142
+ if (nim1 == 2106 ) & (nim2 <= 10 ):
143
+ hardware = 'PC104'
144
+
145
+ # if 2106-1/10 and the sampling rate is 4 Hz, assume HP200 (hourly files)
146
+ if (nim1 == 2106 ) & (nim2 <= 10 ) & (samplingrate == 4 ):
147
+ logger = 'HP200'
148
+
149
+ # verify HP200 data logger: assuming these can only be 2106-1/10
150
+ if logger == 'HP200' :
151
+ if (nim1 != 2106 ) or (nim2 > 10 ):
152
+ print ('A possible problem with the system ID detected. HP200 data \
153
+ logger has been inferred, but the system is not 2106-1/10.' )
154
+ sys .exit ('Please make sure ' + sysid + ' does not have an H character.' )
155
+
156
+ return logger , hardware , backbone
157
+
158
+ def nim_sys_rsp (self ):
159
+ """reads NIMS id and sampling rate and set parameters of Butterworth filter."""
160
+
161
+ # get logger and hardware, backbone from sysid and sampling rate (Hz)
162
+ logger , hardware , backbone = self .sysidcheck ()
163
+
164
+ # This overrides anything about time delays in John Booker's nimsread.
165
+ # This is a product of lengthy correspondence between Gary Egbert and
166
+ # Barry Narod, with reference to diagrams on the NIMS firmware, and is
167
+ # believed to be correct.
168
+ if logger == 'HP200' : # 1 hour files, 4 Hz after decimation by nimsread
169
+ timedelays = [- 0.0055 , - 0.0145 , - 0.0235 , 0.1525 , 0.0275 ]
170
+ elif self .samplingrate == 1 : # MT1 data logger
171
+ timedelays = [- 0.1920 , - 0.2010 , - 0.2100 , - 0.2850 , - 0.2850 ]
172
+ elif self .samplingrate == 8 : # MT1 data logger
173
+ timedelays = [0.2455 , 0.2365 , 0.2275 , 0.1525 , 0.1525 ]
174
+ else :
175
+ sys .exit ('Unknown sampling rate, please check!' )
176
+
177
+ z1mag = []
178
+ p1mag = [- 6.28319 + 1j * 10.8825 , - 6.28319 - 1j * 10.8825 , - 12.5664 ]
179
+ k1mag = 1984.31
180
+
181
+ # based on the NIMS hardware, we determine the filter characteristics.
182
+ if hardware == 'PC104' :
183
+ z1ele = [0.0 ]
184
+ p1ele = [- 3.333333E-05 ]
185
+ k1ele = 1.0
186
+ else :
187
+ z1ele = [0.0 ]
188
+ p1ele = [- 1.666670E-04 ]
189
+ k1ele = 1.0
190
+
191
+ z2ele = []
192
+ p2ele = [- 3.88301 + 1j * 11.9519 , - 3.88301 - 1j * 11.9519 , - 10.1662 + 1j * 7.38651 ,
193
+ - 10.1662 - 1j * 7.38651 , - 12.5664 ]
194
+ k2ele = 313384
195
+
196
+ # z: zero, p: pole, k:gain
197
+ self .zpk = dict .fromkeys (['FN' , 'FE' , 'FZ' ], {'F1' : {'z' : z1mag , 'p' : p1mag , 'k' : k1mag }})
198
+
199
+ if backbone : # no high pass filters
200
+ self .zpk .update (dict .fromkeys (['QN' , 'QE' ],
201
+ {'F1' : {'z' : z2ele , 'p' : p2ele , 'k' : k2ele }}))
202
+ else :
203
+ self .zpk .update (dict .fromkeys (['QN' , 'QE' ],
204
+ {'F1' : {'z' : z1ele , 'p' : p1ele , 'k' : k1ele },
205
+ 'F2' : {'z' : z2ele , 'p' : p2ele , 'k' : k2ele }}))
206
+
207
+ self .timedelays = timedelays
208
+
86
209
87
210
# ---------------------------------
88
211
# 3d EarthScope Sites
@@ -98,6 +221,9 @@ def __init__(self, name):
98
221
self .start_time = None
99
222
self .end_time = None
100
223
224
+ self .runlist = []
225
+ self .runinfo = {}
226
+
101
227
def _repr_html_ (self ):
102
228
return f"<p style=\" font-size:22px; color:blue\" ><b>Site 3d: { self .name } </b></p>" + \
103
229
self .data ._repr_html_ () # pylint: disable=protected-access
@@ -243,8 +369,33 @@ def download_waveforms(self):
243
369
244
370
# Magnetic Field
245
371
self .waveforms [["FE" , "FN" , "FZ" ]] *= 0.01 # nT
246
- # Electric Field
372
+ # Electric Field (assuming the dipole length as 100m)
247
373
self .waveforms [["QN" , "QE" ]] *= 2.44141221047903e-05 # mV/km
374
+ # Correcting electric Field with actual length of dipole
375
+ for runid in self .runlist :
376
+ try :
377
+ mask = ((self .waveforms .index > self .runinfo [runid ]['Start' ]) &
378
+ (self .waveforms .index < self .runinfo [runid ]['End' ]))
379
+ self .waveforms ["QN" ].loc [mask ] *= (100.0 / self .runinfo [runid ]['Ex' ]) # mV/km
380
+ self .waveforms ["QE" ].loc [mask ] *= (100.0 / self .runinfo [runid ]['Ey' ]) # mV/km
381
+
382
+ # If there's no info of length, then use default length.
383
+ except KeyError :
384
+ pass
385
+
386
+ # Filtering waveforms by NIMS system response
387
+ for name in self .waveforms : # loop for FE, FN, FZ, QN, QE
388
+ for filt in self .zpk [name ]: # loop for filter
389
+ zval = self .zpk [name ][filt ]['z' ]
390
+ pval = self .zpk [name ][filt ]['p' ]
391
+ kval = self .zpk [name ][filt ]['k' ]
392
+
393
+ # convert analog zpk to digital filter
394
+ zval , pval , kval = bilinear_zpk (zval , pval , kval , self .samplingrate )
395
+ b , a = zpk2tf (zval , pval , kval )
396
+
397
+ self .waveforms [name ] = filtfilt (b , a , self .waveforms [name ].interpolate ())
398
+
248
399
# Renaming
249
400
self .waveforms .rename (columns = {"FE" : "BE" , "FN" : "BN" , "FZ" : "BZ" ,
250
401
"QE" : "EE" , "QN" : "EN" },
0 commit comments