Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1431318

Browse files
authored
Merge pull request #2 from nimamura/develop
read actual dipole length from XML and reflect it to conversion factor
2 parents 3345f65 + 9f892d2 commit 1431318

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

bezpy/mt/io.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ def read_xml(fname):
123123
site.Z = np.vstack([site.data['z_zxx'], site.data['z_zxy'],
124124
site.data['z_zyx'], site.data['z_zyy']])
125125

126+
site.runlist = get_text(xml_site, "RunList").split()
127+
site.runinfo, site.nimsid, site.samplingrate = read_runinfo(root)
128+
126129
try:
127130
site.Z_var = np.vstack([site.data['z.var_zxx'], site.data['z.var_zxy'],
128131
site.data['z.var_zyx'], site.data['z.var_zyy']])
@@ -131,6 +134,7 @@ def read_xml(fname):
131134
site.Z_var = None
132135

133136
site.calc_resisitivity()
137+
site.nim_sys_rsp()
134138
return site
135139

136140

@@ -189,3 +193,36 @@ def get_1d_site(name):
189193
return _SITES1D[newname]
190194

191195
raise ValueError("No 1d site profile with the name: " + name)
196+
197+
198+
def read_runinfo(root):
199+
"""Returns the run info, if present."""
200+
runinfo = {}
201+
202+
# Run through for each runid
203+
for field in root.findall("FieldNotes"):
204+
# runid of fieldnote
205+
runid = field.attrib['run']
206+
runinfo[runid] = {}
207+
208+
try:
209+
nimsid = get_text(field.find('Instrument'), 'Id')
210+
samplingrate = convert_float(field.find('SamplingRate').text)
211+
except KeyError:
212+
nimsid = None
213+
samplingrate = 1.0
214+
215+
# Run through E component
216+
for ecomp in field.findall("Dipole"):
217+
# Electric component name
218+
edir = ecomp.attrib['name'] # Ex or Ey
219+
# Electric dipole length
220+
runinfo[runid][edir] = convert_float(get_text(ecomp, "Length"))
221+
222+
# set start and end datetime
223+
runinfo[runid]['Start'] = datetime.datetime.strptime(field.find('Start').text,
224+
'%Y-%m-%dT%H:%M:%S')
225+
runinfo[runid]['End'] = datetime.datetime.strptime(field.find('End').text,
226+
'%Y-%m-%dT%H:%M:%S')
227+
228+
return runinfo, nimsid, samplingrate

bezpy/mt/site.py

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
"""
55
__all__ = ["Site", "Site1d", "Site3d"]
66

7+
import sys
78
import datetime
89
import numpy as np
910
import pandas as pd
1011
import scipy.interpolate
12+
from scipy.signal import zpk2tf, filtfilt, bilinear_zpk
1113
import matplotlib.pyplot as plt
1214

1315
from .utils import apparent_resistivity
@@ -44,6 +46,11 @@ def __init__(self, name):
4446
self.min_period = None
4547
self.max_period = None
4648

49+
self.samplingrate = None
50+
self.nimsid = None
51+
self.zpk = None
52+
self.timedelays = None
53+
4754
def convolve_fft(self, mag_x, mag_y, dt=60):
4855
"""Convolution in frequency space."""
4956
# pylint: disable=invalid-name
@@ -83,6 +90,122 @@ def calcZ(self, freqs): # pylint: disable=invalid-name
8390
"""Calculates transfer function, Z, from the input frequencies."""
8491
raise NotImplementedError("calcZ not implemented for this object yet.")
8592

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+
86209

87210
# ---------------------------------
88211
# 3d EarthScope Sites
@@ -98,6 +221,9 @@ def __init__(self, name):
98221
self.start_time = None
99222
self.end_time = None
100223

224+
self.runlist = []
225+
self.runinfo = {}
226+
101227
def _repr_html_(self):
102228
return f"<p style=\"font-size:22px; color:blue\"><b>Site 3d: {self.name}</b></p>" + \
103229
self.data._repr_html_() # pylint: disable=protected-access
@@ -243,8 +369,33 @@ def download_waveforms(self):
243369

244370
# Magnetic Field
245371
self.waveforms[["FE", "FN", "FZ"]] *= 0.01 # nT
246-
# Electric Field
372+
# Electric Field (assuming the dipole length as 100m)
247373
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+
248399
# Renaming
249400
self.waveforms.rename(columns={"FE": "BE", "FN": "BN", "FZ": "BZ",
250401
"QE": "EE", "QN": "EN"},

0 commit comments

Comments
 (0)