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 

11 

12logger = logging.getLogger('pyrocko.io.css') 

13 

14 

15''' 

16See http://nappe.wustl.edu/antelope/css-formats/wfdisc.htm for file format 

17reference. 

18''' 

19 

20storage_types = { 

21 's4': ('>%ii'), 

22 'i4': ('<%ii'), 

23} 

24 

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] 

47 

48 

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) 

61 

62 error_str += '\nFailed to parse the marked section:' 

63 

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 

76 

77 

78class CSSHeaderFile(object): 

79 ''' 

80 CSS Header File 

81 

82 :param filename: filename of css header file 

83 

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): 

89 

90 self.fn = filename 

91 self.data = [] 

92 self.read() 

93 

94 def read_wf_file(self, fn, nbytes, dtype, foff=0): 

95 ''' 

96 Read binary waveform file 

97 

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 

112 

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) 

130 

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)) 

138 

139 @property 

140 def superdir(self): 

141 return os.path.dirname(self.fn) 

142 

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 

155 

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) 

169 

170 

171def iload(file_name, load_data, **kwargs): 

172 ''' 

173 Iteratively load traces from file. 

174 

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