1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5 

6import os 

7import logging 

8from pyrocko import util, trace 

9 

10from . import (mseed, sac, kan, segy, yaff, seisan_waveform, gse1, gcf, 

11 datacube, suds, css, gse2, tdms_idas, hdf5_idas) 

12from .io_common import FileLoadError, FileSaveError 

13 

14import numpy as num 

15 

16 

17__doc__ = util.parse_md(__file__) 

18 

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

20 

21 

22def allowed_formats(operation, use=None, default=None): 

23 if operation == 'load': 

24 lst = ['detect', 'from_extension', 'mseed', 'sac', 'segy', 'seisan', 

25 'seisan.l', 'seisan.b', 'kan', 'yaff', 'gse1', 'gse2', 'gcf', 

26 'datacube', 'suds', 'css', 'tdms_idas', 'hdf5_idas'] 

27 

28 elif operation == 'save': 

29 lst = ['mseed', 'sac', 'text', 'yaff', 'gse2'] 

30 

31 if use == 'doc': 

32 return ', '.join("``'%s'``" % fmt for fmt in lst) 

33 

34 elif use == 'cli_help': 

35 return ', '.join(fmt + ['', ' [default]'][fmt == default] 

36 for fmt in lst) 

37 

38 else: 

39 return lst 

40 

41 

42def load(filename, format='mseed', getdata=True, substitutions=None): 

43 ''' 

44 Load traces from file. 

45 

46 :param format: format of the file (%s) 

47 :param getdata: if ``True`` (the default), read data, otherwise only read 

48 traces metadata 

49 :param substitutions: dict with substitutions to be applied to the traces 

50 metadata 

51 

52 :returns: list of loaded traces 

53 

54 When *format* is set to ``'detect'``, the file type is guessed from the 

55 first 512 bytes of the file. Only Mini-SEED, SAC, GSE1, and YAFF format are 

56 detected. When *format* is set to ``'from_extension'``, the filename 

57 extension is used to decide what format should be assumed. The filename 

58 extensions considered are (matching is case insensitive): ``'.sac'``, 

59 ``'.kan'``, ``'.sgy'``, ``'.segy'``, ``'.yaff'``, everything else is 

60 assumed to be in Mini-SEED format. 

61 

62 This function calls :py:func:`iload` and aggregates the loaded traces in a 

63 list. 

64 ''' 

65 

66 return list(iload( 

67 filename, format=format, getdata=getdata, substitutions=substitutions)) 

68 

69 

70load.__doc__ %= allowed_formats('load', 'doc') 

71 

72 

73def detect_format(filename): 

74 try: 

75 f = open(filename, 'rb') 

76 data = f.read(512) 

77 except OSError as e: 

78 raise FileLoadError(e) 

79 finally: 

80 f.close() 

81 

82 formats = [ 

83 (yaff, 'yaff'), 

84 (mseed, 'mseed'), 

85 (sac, 'sac'), 

86 (gse1, 'gse1'), 

87 (gse2, 'gse2'), 

88 (datacube, 'datacube'), 

89 (suds, 'suds'), 

90 (tdms_idas, 'tdms_idas'), 

91 (hdf5_idas, 'hdf5_idas')] 

92 

93 for mod, fmt in formats: 

94 if mod.detect(data): 

95 return fmt 

96 

97 raise FileLoadError(UnknownFormat(filename)) 

98 

99 

100def iload(filename, format='mseed', getdata=True, substitutions=None): 

101 ''' 

102 Load traces from file (iterator version). 

103 

104 This function works like :py:func:`load`, but returns an iterator which 

105 yields the loaded traces. 

106 ''' 

107 load_data = getdata 

108 

109 toks = format.split('.', 1) 

110 if len(toks) == 2: 

111 format, subformat = toks 

112 else: 

113 subformat = None 

114 

115 try: 

116 mtime = os.stat(filename)[8] 

117 except OSError as e: 

118 raise FileLoadError(e) 

119 

120 def subs(tr): 

121 make_substitutions(tr, substitutions) 

122 tr.set_mtime(mtime) 

123 return tr 

124 

125 extension_to_format = { 

126 '.yaff': 'yaff', 

127 '.sac': 'sac', 

128 '.kan': 'kan', 

129 '.segy': 'segy', 

130 '.sgy': 'segy', 

131 '.gse': 'gse2', 

132 '.wfdisc': 'css', 

133 '.tdms': 'tdms_idas', 

134 '.h5': 'hdf5_idas' 

135 } 

136 

137 if format == 'from_extension': 

138 format = 'mseed' 

139 extension = os.path.splitext(filename)[1] 

140 format = extension_to_format.get(extension.lower(), 'mseed') 

141 

142 if format == 'detect': 

143 format = detect_format(filename) 

144 

145 format_to_module = { 

146 'kan': kan, 

147 'segy': segy, 

148 'yaff': yaff, 

149 'sac': sac, 

150 'mseed': mseed, 

151 'seisan': seisan_waveform, 

152 'gse1': gse1, 

153 'gse2': gse2, 

154 'gcf': gcf, 

155 'datacube': datacube, 

156 'suds': suds, 

157 'css': css, 

158 'tdms_idas': tdms_idas, 

159 'hdf5_idas': hdf5_idas 

160 } 

161 

162 add_args = { 

163 'seisan': {'subformat': subformat}, 

164 } 

165 

166 if format not in format_to_module: 

167 raise UnsupportedFormat(format) 

168 

169 mod = format_to_module[format] 

170 

171 for tr in mod.iload( 

172 filename, load_data=load_data, **add_args.get(format, {})): 

173 

174 yield subs(tr) 

175 

176 

177def save(traces, filename_template, format='mseed', additional={}, 

178 stations=None, overwrite=True, **kwargs): 

179 ''' 

180 Save traces to file(s). 

181 

182 :param traces: a trace or an iterable of traces to store 

183 :param filename_template: filename template with placeholders for trace 

184 metadata. Uses normal python '%%(placeholder)s' string templates. 

185 The following placeholders are considered: ``network``, 

186 ``station``, ``location``, ``channel``, ``tmin`` 

187 (time of first sample), ``tmax`` (time of last sample), 

188 ``tmin_ms``, ``tmax_ms``, ``tmin_us``, ``tmax_us``. The versions 

189 with '_ms' include milliseconds, the versions with '_us' include 

190 microseconds. 

191 :param format: %s 

192 :param additional: dict with custom template placeholder fillins. 

193 :param overwrite': if ``False``, raise an exception if file exists 

194 :returns: list of generated filenames 

195 

196 .. note:: 

197 Network, station, location, and channel codes may be silently truncated 

198 to file format specific maximum lengthes. 

199 ''' 

200 

201 if isinstance(traces, trace.Trace): 

202 traces = [traces] 

203 

204 if format == 'from_extension': 

205 format = os.path.splitext(filename_template)[1][1:] 

206 

207 if format == 'mseed': 

208 return mseed.save(traces, filename_template, additional, 

209 overwrite=overwrite, **kwargs) 

210 

211 elif format == 'gse2': 

212 return gse2.save(traces, filename_template, additional, 

213 overwrite=overwrite, **kwargs) 

214 

215 elif format == 'sac': 

216 fns = [] 

217 for tr in traces: 

218 fn = tr.fill_template(filename_template, **additional) 

219 if not overwrite and os.path.exists(fn): 

220 raise FileSaveError('file exists: %s' % fn) 

221 

222 if fn in fns: 

223 raise FileSaveError('file just created would be overwritten: ' 

224 '%s (multiple traces map to same filename)' 

225 % fn) 

226 

227 util.ensuredirs(fn) 

228 

229 f = sac.SacFile(from_trace=tr) 

230 if stations: 

231 s = stations[tr.network, tr.station, tr.location] 

232 f.stla = s.lat 

233 f.stlo = s.lon 

234 f.stel = s.elevation 

235 f.stdp = s.depth 

236 f.cmpinc = s.get_channel(tr.channel).dip + 90. 

237 f.cmpaz = s.get_channel(tr.channel).azimuth 

238 

239 f.write(fn) 

240 fns.append(fn) 

241 

242 return fns 

243 

244 elif format == 'text': 

245 fns = [] 

246 for tr in traces: 

247 fn = tr.fill_template(filename_template, **additional) 

248 if not overwrite and os.path.exists(fn): 

249 raise FileSaveError('file exists: %s' % fn) 

250 

251 if fn in fns: 

252 raise FileSaveError('file just created would be overwritten: ' 

253 '%s (multiple traces map to same filename)' 

254 % fn) 

255 

256 util.ensuredirs(fn) 

257 x, y = tr.get_xdata(), tr.get_ydata() 

258 num.savetxt(fn, num.transpose((x, y))) 

259 fns.append(fn) 

260 return fns 

261 

262 elif format == 'yaff': 

263 return yaff.save(traces, filename_template, additional, 

264 overwrite=overwrite, **kwargs) 

265 else: 

266 raise UnsupportedFormat(format) 

267 

268 

269save.__doc__ %= allowed_formats('save', 'doc') 

270 

271 

272class UnknownFormat(Exception): 

273 def __init__(self, filename): 

274 Exception.__init__(self, 'Unknown file format: %s' % filename) 

275 

276 

277class UnsupportedFormat(Exception): 

278 def __init__(self, format): 

279 Exception.__init__(self, 'Unsupported file format: %s' % format) 

280 

281 

282def make_substitutions(tr, substitutions): 

283 if substitutions: 

284 tr.set_codes(**substitutions)