Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/io/css.py: 67%

76 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-09-28 11:43 +0000

1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

4# ---|P------/S----------~Lg---------- 

5 

6''' 

7Reader of CSS wfdisc waveform data. 

8 

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

10reference. 

11''' 

12 

13import os 

14import numpy as num 

15import logging 

16from struct import unpack 

17from pyrocko import trace, util 

18 

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

20 

21 

22storage_types = { 

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

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

25} 

26 

27template = [ 

28 ('sta', str, (0, 6), 'station code'), 

29 ('chan', str, (7, 15), 'channel code'), 

30 ('time', float, (16, 33), 'epoch time of first sample in file'), 

31 ('wfid', int, (34, 43), 'waveform identifier'), 

32 ('chanid', int, (44, 52), 'channel identifier'), 

33 ('jdate', int, (53, 61), 'julian date'), 

34 ('endtime', float, (62, 79), 'time +(nsamp -1 )/samles'), 

35 ('nsamp', int, (80, 88), 'number of samples'), 

36 ('samprate', float, (89, 100), 'sampling rate in samples/sec'), 

37 ('calib', float, (101, 117), 'nominal calibration'), 

38 ('calper', float, (118, 134), 'nominal calibration period'), 

39 ('instype', str, (135, 141), 'instrument code'), 

40 ('segtype', str, (142, 143), 'indexing method'), 

41 ('datatype', str, (144, 146), 'numeric storage'), 

42 ('clip', str, (147, 148), 'clipped flag'), 

43 ('dir', str, (149, 213), 'directory'), 

44 ('dfile', str, (214, 246), 'data file'), 

45 ('foff', int, (247, 257), 'byte offset of data segment within file'), 

46 ('commid', int, (258, 267), 'comment identifier'), 

47 ('Iddate', util.stt, (268, 287), 'load date') 

48] 

49 

50 

51class CSSWfError(Exception): 

52 def __init__(self, **kwargs): 

53 f2str = { 

54 str: 'string', 

55 int: 'integer', 

56 float: 'float', 

57 util.stt: 'time' 

58 } 

59 kwargs['convert'] = f2str[kwargs['convert']] 

60 error_str = 'Successfully parsed this:\n' 

61 for k, v in kwargs['d'].items(): 

62 error_str += '%s: %s\n' % (k, v) 

63 

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

65 

66 istart = kwargs['istart'] 

67 istop = kwargs['istop'] 

68 npad = 12 

69 error_mark = ' ' * npad 

70 error_mark += '^' * (istop - istart) 

71 error_str += '\n%s\n%s\n' % (kwargs['data'][istart-npad: istop+npad], 

72 error_mark) 

73 error_str += 'Expected {desc} (format: {convert})\n'.format(**kwargs) 

74 error_str += \ 

75 'checkout http://nappe.wustl.edu/antelope/css-formats/wfdisc.htm' 

76 Exception.__init__(self, error_str) 

77 self.error_arguments = kwargs 

78 

79 

80class CSSHeaderFile(object): 

81 ''' 

82 CSS Header File 

83 

84 :param filename: filename of css header file 

85 

86 Note, that all binary data files to which the underlying header file points 

87 to will be loaded at once. It is therefore recommended to split header 

88 files for large data sets 

89 ''' 

90 def __init__(self, filename): 

91 

92 self.fn = filename 

93 self.data = [] 

94 self.read() 

95 

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

97 ''' 

98 Read binary waveform file 

99 

100 :param fn: filename 

101 :param nbytes: number of bytes to be read 

102 :param dtype: datatype string 

103 ''' 

104 with open(fn, 'rb') as f: 

105 fmt = dtype % nbytes 

106 f.seek(foff) 

107 try: 

108 data = num.array(unpack(fmt, f.read(nbytes * 4)), 

109 dtype=num.int32) 

110 except Exception: 

111 logger.exception('Error while unpacking %s' % fn) 

112 return 

113 return data 

114 

115 def read(self): 

116 ''' 

117 Read header file. 

118 ''' 

119 with open(self.fn, 'rb') as f: 

120 lines = f.readlines() 

121 for iline, line in enumerate(lines): 

122 line = str(line.decode('ascii')) 

123 d = {} 

124 for (ident, convert, (istart, istop), desc) in template: 

125 try: 

126 d[ident] = convert(line[istart: istop].strip()) 

127 except Exception: 

128 raise CSSWfError(iline=iline+1, data=line, 

129 ident=ident, convert=convert, 

130 istart=istart+1, istop=istop+1, 

131 desc=desc, d=d) 

132 

133 fn = os.path.join(self.superdir, d['dir'], d['dfile']) 

134 if os.path.isfile(fn): 

135 self.data.append(d) 

136 else: 

137 logger.error( 

138 'no such file: %s (see header file: %s, line %s)' % ( 

139 fn, self.fn, iline+1)) 

140 

141 @property 

142 def superdir(self): 

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

144 

145 def iter_pyrocko_traces(self, load_data=True): 

146 for idata, d in enumerate(self.data): 

147 fn = os.path.join(d['dir'], d['dfile']) 

148 logger.debug('converting %s', d['dfile']) 

149 try: 

150 if load_data: 

151 ydata = self.read_wf_file( 

152 os.path.join(self.superdir, fn), d['nsamp'], 

153 storage_types[d['datatype']], 

154 d['foff']) 

155 else: 

156 ydata = None 

157 

158 except IOError as e: 

159 if e.errno == 2: 

160 logger.debug(e) 

161 continue 

162 else: 

163 raise e 

164 dt = 1./d['samprate'] 

165 yield trace.Trace(station=d['sta'], 

166 channel=d['chan'], 

167 deltat=dt, 

168 tmin=d['time'], 

169 tmax=d['time'] + d['nsamp']/d['samprate'], 

170 ydata=ydata) 

171 

172 

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

174 ''' 

175 Iteratively load traces from file. 

176 

177 :param file_name: css header file name 

178 :param load_data: whether or not to load binary data 

179 ''' 

180 wfdisc = CSSHeaderFile(file_name) 

181 for pyrocko_trace in wfdisc.iter_pyrocko_traces(load_data=load_data): 

182 yield pyrocko_trace