Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/io/rdseed.py: 20%
241 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'''
7Access to SEED volumes by interfacing `rdseed
8<https://ds.iris.edu/ds/nodes/dmc/software/downloads/rdseed/>`_ (*deprecated*).
9'''
11import os
12import sys
13import subprocess
14import tempfile
15import calendar
16import time
17import re
18import logging
19import shutil
21from pyrocko import ExternalProgramMissing
22from pyrocko import pile, model, util, response
23from pyrocko.io import eventdata
25pjoin = os.path.join
26logger = logging.getLogger('pyrocko.io.rdseed')
29def cmp(a, b):
30 return (a > b) - (a < b)
33def read_station_header_file(fn):
35 def m(i, *args):
36 if len(ltoks) >= i + len(args) \
37 and (tuple(ltoks[i:i+len(args)]) == args):
38 return True
39 return False
41 def f(i):
42 return float(toks[i])
44 def s(i):
45 if len(toks) > i:
46 return toks[i]
47 else:
48 return ''
50 with open(fn, 'r') as fi:
52 stations = []
53 atsec, station, channel = None, None, None
55 for line in fi:
56 toks = line.split()
57 ltoks = line.lower().split()
58 if m(2, 'station', 'header'):
59 atsec = 'station'
60 station = {'channels': []}
61 stations.append(station)
62 continue
64 if m(2, 'station') and m(5, 'channel'):
65 atsec = 'channel'
66 channel = {}
67 station['channels'].append(channel)
68 continue
70 if atsec == 'station':
71 if m(1, 'station', 'code:'):
72 station['station'] = s(3)
74 elif m(1, 'network', 'code:'):
75 station['network'] = s(3)
77 elif m(1, 'name:'):
78 station['name'] = ' '.join(toks[2:])
80 if atsec == 'channel':
81 if m(1, 'channel:'):
82 channel['channel'] = s(2)
84 elif m(1, 'location:'):
85 channel['location'] = s(2)
87 elif m(1, 'latitude:'):
88 station['lat'] = f(2)
90 elif m(1, 'longitude:'):
91 station['lon'] = f(2)
93 elif m(1, 'elevation:'):
94 station['elevation'] = f(2)
96 elif m(1, 'local', 'depth:'):
97 channel['depth'] = f(3)
99 elif m(1, 'azimuth:'):
100 channel['azimuth'] = f(2)
102 elif m(1, 'dip:'):
103 channel['dip'] = f(2)
105 nsl_stations = {}
106 for station in stations:
107 for channel in station['channels']:
108 def cs(k, default=None):
109 return channel.get(k, station.get(k, default))
111 nsl = station['network'], station['station'], channel['location']
112 if nsl not in nsl_stations:
113 nsl_stations[nsl] = model.Station(
114 network=station['network'],
115 station=station['station'],
116 location=channel['location'],
117 lat=cs('lat'),
118 lon=cs('lon'),
119 elevation=cs('elevation'),
120 depth=cs('depth', None),
121 name=station['name'])
123 nsl_stations[nsl].add_channel(model.Channel(
124 channel['channel'],
125 azimuth=channel['azimuth'],
126 dip=channel['dip']))
128 return list(nsl_stations.values())
131def cmp_version(a, b):
132 ai = [int(x) for x in a.split('.')]
133 bi = [int(x) for x in b.split('.')]
134 return cmp(ai, bi)
137def dumb_parser(data):
139 (in_ws, in_kw, in_str) = (1, 2, 3)
141 state = in_ws
143 rows = []
144 cols = []
145 accu = ''
146 for c in data:
147 if state == in_ws:
148 if c == '"':
149 new_state = in_str
151 elif c not in (' ', '\t', '\n', '\r'):
152 new_state = in_kw
154 if state == in_kw:
155 if c in (' ', '\t', '\n', '\r'):
156 cols.append(accu)
157 accu = ''
158 if c in ('\n', '\r'):
159 rows.append(cols)
160 cols = []
161 new_state = in_ws
163 if state == in_str:
164 if c == '"':
165 accu += c
166 cols.append(accu[1:-1])
167 accu = ''
168 if c in ('\n', '\r'):
169 rows.append(cols)
170 cols = []
171 new_state = in_ws
173 state = new_state
175 if state in (in_kw, in_str):
176 accu += c
178 if len(cols) != 0:
179 rows.append(cols)
181 return rows
184class Programs(object):
185 rdseed = 'rdseed'
186 avail = None
188 @staticmethod
189 def check():
190 if Programs.avail is not None:
191 return Programs.avail
193 else:
194 try:
195 rdseed_proc = subprocess.Popen(
196 [Programs.rdseed],
197 stdin=subprocess.PIPE,
198 stdout=subprocess.PIPE,
199 stderr=subprocess.PIPE)
201 (out, err) = rdseed_proc.communicate()
203 except OSError as e:
204 if e.errno == 2:
205 reason = "Could not find executable: '%s'." \
206 % Programs.rdseed
207 else:
208 reason = str(e)
210 logging.debug('Failed to run rdseed program. %s' % reason)
211 Programs.avail = False
212 return Programs.avail
214 ms = [re.search(
215 r'Release (\d+(\.\d+(\.\d+)?)?)', s.decode())
216 for s in (err, out)]
217 ms = list(filter(bool, ms))
218 if not ms:
219 logger.error('Cannot determine rdseed version number.')
220 else:
221 version = ms[0].group(1)
222 if cmp_version('4.7.5', version) == 1 \
223 or cmp_version(version, '5.1') == 1:
225 logger.warning(
226 'Module pyrocko.rdseed has not been tested with '
227 'version %s of rdseed.' % version)
229 Programs.avail = True
230 return Programs.avail
233class SeedVolumeNotFound(Exception):
234 pass
237class SeedVolumeAccess(eventdata.EventDataAccess):
239 def __init__(self, seedvolume, datapile=None):
241 '''
242 Create new SEED Volume access object.
244 :param seedvolume: filename of seed volume
245 :param datapile: if not ``None``, this should be a
246 :py:class:`pile.Pile` object with data traces which are then used
247 instead of the data provided by the SEED volume.
248 (This is useful for dataless SEED volumes.)
249 '''
251 eventdata.EventDataAccess.__init__(self, datapile=datapile)
252 self.tempdir = None
253 Programs.check()
255 self.tempdir = None
256 self.seedvolume = seedvolume
257 if not os.path.isfile(self.seedvolume):
258 raise SeedVolumeNotFound()
260 self.tempdir = tempfile.mkdtemp('', 'SeedVolumeAccess-')
261 self.station_headers_file = os.path.join(
262 self.tempdir, 'station_header_infos')
263 self._unpack()
264 self.shutil = shutil
266 def __del__(self):
267 if self.tempdir:
268 self.shutil.rmtree(self.tempdir)
270 def get_pile(self):
271 if self._pile is None:
272 fns = util.select_files([self.tempdir], include=r'\.SAC$')
273 self._pile = pile.Pile()
274 self._pile.load_files(fns, fileformat='sac')
276 return self._pile
278 def get_resp_file(self, tr):
279 respfile = pjoin(self.tempdir, 'RESP.%s.%s.%s.%s' % tr.nslc_id)
280 if not os.path.exists(respfile):
281 raise eventdata.NoRestitution(
282 'no response information available for trace %s.%s.%s.%s'
283 % tr.nslc_id)
285 return respfile
287 def get_stationxml(self):
288 stations = self.get_pyrocko_stations()
289 respfiles = []
290 for station in stations:
291 for channel in station.get_channels():
292 nslc = station.nsl() + (channel.name,)
293 respfile = pjoin(self.tempdir, 'RESP.%s.%s.%s.%s' % nslc)
294 respfiles.append(respfile)
296 from pyrocko.client.fdsn import resp
297 sxml = resp.make_stationxml(stations, resp.iload(respfiles))
298 return sxml
300 def get_restitution(self, tr, allowed_methods):
301 if 'evalresp' in allowed_methods:
302 respfile = pjoin(self.tempdir, 'RESP.%s.%s.%s.%s' % tr.nslc_id)
303 if not os.path.exists(respfile):
304 raise eventdata.NoRestitution(
305 'no response information available for trace %s.%s.%s.%s'
306 % tr.nslc_id)
308 trans = response.InverseEvalresp(respfile, tr)
309 return trans
310 else:
311 raise eventdata.NoRestitution(
312 'no response information available for trace %s.%s.%s.%s'
313 % tr.nslc_id)
315 def get_pyrocko_response(self, tr, target):
316 '''
317 Extract the frequency response as :py:class:`response.EvalResp`
318 instance for *tr*.
320 :param tr: :py:class:`trace.Trace` instance
321 '''
322 respfile = self.get_resp_file(tr)
323 return response.Evalresp(respfile, tr, target)
325 def _unpack(self):
326 input_fn = self.seedvolume
327 output_dir = self.tempdir
329 def strerr(s):
330 return '\n'.join(['rdseed: '+line.decode()
331 for line in s.splitlines()])
332 try:
334 # seismograms:
335 if self._pile is None:
336 rdseed_proc = subprocess.Popen(
337 [Programs.rdseed, '-f', input_fn, '-d', '-z', '3', '-o',
338 '1', '-p', '-R', '-q', output_dir],
339 stdin=subprocess.PIPE,
340 stdout=subprocess.PIPE,
341 stderr=subprocess.PIPE)
343 (out, err) = rdseed_proc.communicate()
344 logging.info(strerr(err))
345 else:
346 rdseed_proc = subprocess.Popen(
347 [Programs.rdseed, '-f', input_fn, '-z', '3', '-p', '-R',
348 '-q', output_dir],
349 stdin=subprocess.PIPE,
350 stdout=subprocess.PIPE,
351 stderr=subprocess.PIPE)
353 (out, err) = rdseed_proc.communicate()
354 logging.info(strerr(err))
356 # event data:
357 rdseed_proc = subprocess.Popen(
358 [Programs.rdseed, '-f', input_fn, '-e', '-q', output_dir],
359 stdin=subprocess.PIPE,
360 stdout=subprocess.PIPE,
361 stderr=subprocess.PIPE)
363 (out, err) = rdseed_proc.communicate()
364 logging.info(strerr(err))
366 # station summary information:
367 rdseed_proc = subprocess.Popen(
368 [Programs.rdseed, '-f', input_fn, '-S', '-q', output_dir],
369 stdin=subprocess.PIPE,
370 stdout=subprocess.PIPE,
371 stderr=subprocess.PIPE)
373 (out, err) = rdseed_proc.communicate()
374 logging.info(strerr(err))
376 # station headers:
377 rdseed_proc = subprocess.Popen(
378 [Programs.rdseed, '-f', input_fn, '-s', '-q', output_dir],
379 stdin=subprocess.PIPE,
380 stdout=subprocess.PIPE,
381 stderr=subprocess.PIPE)
383 (out, err) = rdseed_proc.communicate()
384 with open(self.station_headers_file, 'w') as fout:
385 fout.write(out.decode())
386 logging.info(strerr(err))
388 except OSError as e:
389 if e.errno == 2:
390 reason = "Could not find executable: '%s'." % Programs.rdseed
391 else:
392 reason = str(e)
394 logging.fatal('Failed to unpack SEED volume. %s' % reason)
395 sys.exit(1)
397 def _get_events_from_file(self):
398 rdseed_event_file = os.path.join(self.tempdir, 'rdseed.events')
399 if not os.path.isfile(rdseed_event_file):
400 return []
402 with open(rdseed_event_file, 'r') as f:
403 events = []
404 for line in f:
405 toks = line.split(', ')
406 if len(toks) == 9:
407 datetime = toks[1].split('.')[0]
408 format = '%Y/%m/%d %H:%M:%S'
409 secs = util.to_time_float(calendar.timegm(
410 time.strptime(datetime, format)))
411 e = model.Event(
412 lat=float(toks[2]),
413 lon=float(toks[3]),
414 depth=float(toks[4])*1000.,
415 magnitude=float(toks[8]),
416 time=secs)
418 events.append(e)
419 else:
420 raise Exception('Event description in unrecognized format')
422 return events
424 def _get_stations_from_file(self):
425 stations = read_station_header_file(self.station_headers_file)
426 return stations
428 def _insert_channel_descriptions(self, stations):
429 # this is done beforehand in this class
430 pass
433def check_have_rdseed():
434 if not Programs.check():
435 raise ExternalProgramMissing(
436 'rdseed is not installed or cannot be found')
439if __name__ == '__main__':
440 print(SeedVolumeAccess(sys.argv[1]).get_stationxml().dump_xml())