1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import absolute_import
6import os
7import numpy as num
8import logging
9from struct import unpack
10from pyrocko import trace, util
12logger = logging.getLogger('pyrocko.io.css')
15'''
16See http://nappe.wustl.edu/antelope/css-formats/wfdisc.htm for file format
17reference.
18'''
20storage_types = {
21 's4': ('>%ii'),
22 'i4': ('<%ii'),
23}
25template = [
26 ('sta', str, (0, 6), 'station code'),
27 ('chan', str, (7, 15), 'channel code'),
28 ('time', float, (16, 33), 'epoch time of first sample in file'),
29 ('wfid', int, (34, 43), 'waveform identifier'),
30 ('chanid', int, (44, 52), 'channel identifier'),
31 ('jdate', int, (53, 61), 'julian date'),
32 ('endtime', float, (62, 79), 'time +(nsamp -1 )/samles'),
33 ('nsamp', int, (80, 88), 'number of samples'),
34 ('samprate', float, (89, 100), 'sampling rate in samples/sec'),
35 ('calib', float, (101, 117), 'nominal calibration'),
36 ('calper', float, (118, 134), 'nominal calibration period'),
37 ('instype', str, (135, 141), 'instrument code'),
38 ('segtype', str, (142, 143), 'indexing method'),
39 ('datatype', str, (144, 146), 'numeric storage'),
40 ('clip', str, (147, 148), 'clipped flag'),
41 ('dir', str, (149, 213), 'directory'),
42 ('dfile', str, (214, 246), 'data file'),
43 ('foff', int, (247, 257), 'byte offset of data segment within file'),
44 ('commid', int, (258, 267), 'comment identifier'),
45 ('Iddate', util.stt, (268, 287), 'load date')
46]
49class CSSWfError(Exception):
50 def __init__(self, **kwargs):
51 f2str = {
52 str: 'string',
53 int: 'integer',
54 float: 'float',
55 util.stt: 'time'
56 }
57 kwargs['convert'] = f2str[kwargs['convert']]
58 error_str = 'Successfully parsed this:\n'
59 for k, v in kwargs['d'].items():
60 error_str += '%s: %s\n' % (k, v)
62 error_str += '\nFailed to parse the marked section:'
64 istart = kwargs['istart']
65 istop = kwargs['istop']
66 npad = 12
67 error_mark = ' ' * npad
68 error_mark += '^' * (istop - istart)
69 error_str += '\n%s\n%s\n' % (kwargs['data'][istart-npad: istop+npad],
70 error_mark)
71 error_str += 'Expected {desc} (format: {convert})\n'.format(**kwargs)
72 error_str += \
73 'checkout http://nappe.wustl.edu/antelope/css-formats/wfdisc.htm'
74 Exception.__init__(self, error_str)
75 self.error_arguments = kwargs
78class CSSHeaderFile(object):
79 '''
80 CSS Header File
82 :param filename: filename of css header file
84 Note, that all binary data files to which the underlying header file points
85 to will be loaded at once. It is therefore recommended to split header
86 files for large data sets
87 '''
88 def __init__(self, filename):
90 self.fn = filename
91 self.data = []
92 self.read()
94 def read_wf_file(self, fn, nbytes, dtype, foff=0):
95 '''
96 Read binary waveform file
98 :param fn: filename
99 :param nbytes: number of bytes to be read
100 :param dtype: datatype string
101 '''
102 with open(fn, 'rb') as f:
103 fmt = dtype % nbytes
104 f.seek(foff)
105 try:
106 data = num.array(unpack(fmt, f.read(nbytes * 4)),
107 dtype=num.int32)
108 except Exception:
109 logger.exception('Error while unpacking %s' % fn)
110 return
111 return data
113 def read(self):
114 '''
115 Read header file.
116 '''
117 with open(self.fn, 'rb') as f:
118 lines = f.readlines()
119 for iline, line in enumerate(lines):
120 line = str(line.decode('ascii'))
121 d = {}
122 for (ident, convert, (istart, istop), desc) in template:
123 try:
124 d[ident] = convert(line[istart: istop].strip())
125 except Exception:
126 raise CSSWfError(iline=iline+1, data=line,
127 ident=ident, convert=convert,
128 istart=istart+1, istop=istop+1,
129 desc=desc, d=d)
131 fn = os.path.join(self.superdir, d['dir'], d['dfile'])
132 if os.path.isfile(fn):
133 self.data.append(d)
134 else:
135 logger.error(
136 'no such file: %s (see header file: %s, line %s)' % (
137 fn, self.fn, iline+1))
139 @property
140 def superdir(self):
141 return os.path.dirname(self.fn)
143 def iter_pyrocko_traces(self, load_data=True):
144 for idata, d in enumerate(self.data):
145 fn = os.path.join(d['dir'], d['dfile'])
146 logger.debug('converting %s', d['dfile'])
147 try:
148 if load_data:
149 ydata = self.read_wf_file(
150 os.path.join(self.superdir, fn), d['nsamp'],
151 storage_types[d['datatype']],
152 d['foff'])
153 else:
154 ydata = None
156 except IOError as e:
157 if e.errno == 2:
158 logger.debug(e)
159 continue
160 else:
161 raise e
162 dt = 1./d['samprate']
163 yield trace.Trace(station=d['sta'],
164 channel=d['chan'],
165 deltat=dt,
166 tmin=d['time'],
167 tmax=d['time'] + d['nsamp']/d['samprate'],
168 ydata=ydata)
171def iload(file_name, load_data, **kwargs):
172 '''
173 Iteratively load traces from file.
175 :param file_name: css header file name
176 :param load_data: whether or not to load binary data
177 '''
178 wfdisc = CSSHeaderFile(file_name)
179 for pyrocko_trace in wfdisc.iter_pyrocko_traces(load_data=load_data):
180 yield pyrocko_trace