Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/io/suds.py: 89%
138 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-04 09:52 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-04 09:52 +0000
1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Reader for `SUDS <https://seiscode.iris.washington.edu/projects/suds>`_
8waveforms and station information.
9'''
11import sys
12import struct
13import logging
14import numpy as num
15from collections import namedtuple, defaultdict
17from pyrocko import util, trace, model
18from .io_common import FileLoadError
21g_suds_zero = None
24def get_suds_zero():
25 global g_suds_zero
26 if g_suds_zero is None:
27 g_suds_zero = util.str_to_time('1970-01-01 00:00:00')
29 return g_suds_zero
32logger = logging.getLogger('pyrocko.io.suds')
35class SudsError(Exception):
36 pass
39class SudsStructBase(object):
41 @classmethod
42 def unpack(cls, s):
43 return cls._make(struct.unpack(cls.fmt, s))
46class SudsStructtag(SudsStructBase, namedtuple(
47 'SudsStructtag',
48 'sync, machine, struct_type, struct_length, data_length')):
50 __slots__ = ()
51 fmt = '<cchll'
54class SudsStatident(SudsStructBase, namedtuple(
55 'SudsStatident',
56 'network, st_name, component, inst_type')):
58 def nslc(self):
59 return (
60 str(self.network.rstrip(b'\0 ').decode('ascii')),
61 str(self.st_name.rstrip(b'\0 ').decode('ascii')),
62 '',
63 str(self.component.rstrip(b'\0 ').decode('ascii')))
65 __slots__ = ()
66 fmt = '<4s5sch'
69class SudsStationcomp(SudsStructBase, namedtuple(
70 'SudsStationcomp',
71 'sc_name, azim, incid, st_lat, st_long, elev, enclosure, annotation, '
72 'recorder, rockclass, rocktype, sitecondition, sensor_type, '
73 'data_type, data_units, polarity, st_status, max_gain, clip_value, '
74 'con_mvolts, channel, atod_gain, effective, clock_correct, '
75 'station_delay')):
77 __slots__ = ()
78 fmt = '<%ishhddfcccchccccccfffhhlff' % struct.calcsize(SudsStatident.fmt)
80 @classmethod
81 def unpack(cls, s):
82 v = struct.unpack(cls.fmt, s)
83 v = (SudsStatident.unpack(v[0]),) + v[1:]
84 return cls._make(v)
86 def to_station(self):
87 net, sta, loc, cha = self.sc_name.nslc()
88 station = model.Station(
89 network=net,
90 station=sta,
91 location=loc,
92 lat=self.st_lat,
93 lon=self.st_long,
94 elevation=self.elev)
96 station.add_channel(
97 model.Channel(
98 name=cha,
99 azimuth=self.azim,
100 dip=self.incid - 90.))
102 return station
105class SudsDescriptrace(SudsStructBase, namedtuple(
106 'SudsDescriptrace',
107 'dt_name, begintime, localtime, datatype, descriptor, digi_by, '
108 'processed, length, rate, mindata, maxdata, avenoise, numclip, '
109 'time_correct, rate_correct')):
111 __slots__ = ()
112 fmt = '<%isdhcchhlffffldf' % struct.calcsize(SudsStatident.fmt)
114 @classmethod
115 def unpack(cls, s):
116 v = struct.unpack(cls.fmt, s)
117 v = (SudsStatident.unpack(v[0]),) + v[1:]
118 return cls._make(v)
120 def to_trace(self, data):
121 tmin = self.begintime - get_suds_zero()
122 deltat = 1.0 / self.rate
124 if data is None:
125 tmax = tmin + (self.length - 1) * deltat
126 arr = None
127 else:
128 tmax = None
129 if self.datatype == b'l':
130 arr = num.frombuffer(data, dtype=num.int32)
131 elif self.datatype == b'i':
132 arr = num.frombuffer(data, dtype=num.int16)
133 elif self.datatype == b'f':
134 arr = num.frombuffer(data, dtype=num.float32)
135 elif self.datatype == b'd':
136 arr = num.frombuffer(data, dtype=num.float64)
137 else:
138 raise SudsError(
139 'data type "%s" not implemented yet' % self.datatype)
141 if self.length != arr.size:
142 raise SudsError('found and reported number of samples differ')
144 return trace.Trace(
145 self.dt_name.network.rstrip(b'\0 '),
146 self.dt_name.st_name.rstrip(b'\0 '),
147 '',
148 self.dt_name.component.rstrip(b'\0 '),
149 ydata=arr,
150 deltat=deltat,
151 tmin=tmin,
152 tmax=tmax)
155struct_names = {
156 0: 'no_struct',
157 1: 'statident',
158 2: 'structtag',
159 3: 'terminator',
160 4: 'equipment',
161 5: 'stationcomp',
162 6: 'muxdata',
163 7: 'descriptrace',
164 8: 'loctrace',
165 9: 'calibration',
166 10: 'feature',
167 11: 'residual',
168 12: 'event',
169 13: 'ev_descript',
170 14: 'origin',
171 15: 'error',
172 16: 'focalmech',
173 17: 'moment',
174 18: 'velmodel',
175 19: 'layers',
176 20: 'comment',
177 21: 'profile',
178 22: 'shotgather',
179 23: 'calib',
180 24: 'complex',
181 25: 'triggers',
182 26: 'trigsetting',
183 27: 'eventsetting',
184 28: 'detector',
185 29: 'atodinfo',
186 30: 'timecorrection',
187 31: 'instrument',
188 32: 'chanset'}
190max_struct_type = max(struct_names.keys())
193struct_classes = {}
194for struct_id, struct_name in struct_names.items():
195 g = globals()
196 if struct_id > 0:
197 class_name = 'Suds' + struct_name.capitalize()
198 if class_name in g:
199 struct_classes[struct_id] = g[class_name]
202def read_suds_struct(f, cls, end_ok=False):
203 size = struct.calcsize(cls.fmt)
204 s = f.read(size)
205 if end_ok and len(s) == 0:
206 return None
208 if size != len(s):
209 raise SudsError('premature end of file')
210 o = cls.unpack(s)
212 return o
215def _iload(filename, load_data=True, want=('traces', 'stations')):
216 try:
217 f = open(filename, 'rb')
218 while True:
219 tag = read_suds_struct(f, SudsStructtag, end_ok=True)
220 if tag is None:
221 break
223 if tag.struct_type in struct_classes:
224 cls = struct_classes[tag.struct_type]
225 if tag.struct_length != struct.calcsize(cls.fmt):
226 raise SudsError(
227 'expected and reported struct lengths differ')
229 s = read_suds_struct(f, cls)
230 if isinstance(s, SudsStationcomp) and 'stations' in want:
231 station = s.to_station()
232 yield station
234 if tag.data_length > 0:
235 if isinstance(s, SudsDescriptrace) and 'traces' in want:
236 if load_data:
237 data = f.read(tag.data_length)
238 if tag.data_length != len(data):
239 raise SudsError('premature end of file')
241 tr = s.to_trace(data)
242 else:
243 f.seek(tag.data_length, 1)
244 tr = s.to_trace(None)
246 yield tr
247 else:
248 f.seek(tag.data_length, 1)
250 else:
251 logger.warning(
252 'skipping unsupported SUDS struct type %s (%s)' % (
253 tag.struct_type,
254 struct_names.get(tag.struct_type, '?')))
256 f.seek(tag.struct_length, 1)
258 if tag.data_length > 0:
259 f.seek(tag.data_length, 1)
261 except (OSError, SudsError) as e:
262 raise FileLoadError(e)
264 finally:
265 f.close()
268def iload(filename, load_data=True):
269 for tr in _iload(filename, load_data=load_data, want=('traces',)):
270 yield tr
273def load_stations(filename):
274 stations = list(_iload(filename, load_data=False, want=('stations',)))
276 gathered = defaultdict(list)
277 for s in stations:
278 nsl = s.nsl()
279 gathered[nsl].append(s)
281 stations_out = []
282 for nsl, group in gathered.items():
283 meta = [
284 ('.'.join(s.nsl()) + '.' + s.get_channels()[0].name,
285 s.lat, s.lon, s.elevation)
286 for s in group]
288 util.consistency_check(meta)
290 channels = [s.get_channels()[0] for s in group]
291 group[0].set_channels(channels)
292 stations_out.append(group[0])
294 return stations_out
297def detect(first512):
299 s = first512[:12]
300 if len(s) != 12:
301 return False
303 tag = SudsStructtag.unpack(s)
304 if tag.sync != b'S' \
305 or tag.machine != b'6' \
306 or tag.struct_type < 0 \
307 or tag.struct_type > max_struct_type:
309 return False
311 return True
314if __name__ == '__main__':
315 util.setup_logging('pyrocko.suds')
317 trs = list(iload(sys.argv[1], 'rb'))
319 stations = load_stations(sys.argv[1])
321 for station in stations:
322 print(station)
324 trace.snuffle(trs, stations=stations)