1# http://pyrocko.org - GPLv3 

2# 

3# The Pyrocko Developers, 21st Century 

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

5from __future__ import division, absolute_import 

6 

7import os 

8import logging 

9from pyrocko import util, trace 

10 

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

12 datacube, suds, css, gse2) 

13from .io_common import FileLoadError, FileSaveError 

14 

15import numpy as num 

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'] 

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 for mod, fmt in [ 

83 (yaff, 'yaff'), 

84 (mseed, 'mseed'), 

85 (sac, 'sac'), 

86 (gse1, 'gse1'), 

87 (gse2, 'gse2'), 

88 (datacube, 'datacube'), 

89 (suds, 'suds')]: 

90 

91 if mod.detect(data): 

92 return fmt 

93 

94 raise FileLoadError(UnknownFormat(filename)) 

95 

96 

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

98 ''' 

99 Load traces from file (iterator version). 

100 

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

102 yields the loaded traces. 

103 ''' 

104 load_data = getdata 

105 

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

107 if len(toks) == 2: 

108 format, subformat = toks 

109 else: 

110 subformat = None 

111 

112 try: 

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

114 except OSError as e: 

115 raise FileLoadError(e) 

116 

117 def subs(tr): 

118 make_substitutions(tr, substitutions) 

119 tr.set_mtime(mtime) 

120 return tr 

121 

122 extension_to_format = { 

123 '.yaff': 'yaff', 

124 '.sac': 'sac', 

125 '.kan': 'kan', 

126 '.segy': 'segy', 

127 '.sgy': 'segy', 

128 '.gse': 'gse2', 

129 '.wfdisc': 'css'} 

130 

131 if format == 'from_extension': 

132 format = 'mseed' 

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

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

135 

136 if format == 'detect': 

137 format = detect_format(filename) 

138 

139 format_to_module = { 

140 'kan': kan, 

141 'segy': segy, 

142 'yaff': yaff, 

143 'sac': sac, 

144 'mseed': mseed, 

145 'seisan': seisan_waveform, 

146 'gse1': gse1, 

147 'gse2': gse2, 

148 'gcf': gcf, 

149 'datacube': datacube, 

150 'suds': suds, 

151 'css': css, 

152 } 

153 

154 add_args = { 

155 'seisan': {'subformat': subformat}, 

156 } 

157 

158 if format not in format_to_module: 

159 raise UnsupportedFormat(format) 

160 

161 mod = format_to_module[format] 

162 

163 for tr in mod.iload( 

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

165 

166 yield subs(tr) 

167 

168 

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

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

171 ''' 

172 Save traces to file(s). 

173 

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

175 :param filename_template: filename template with placeholders for trace 

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

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

178 ``station``, ``location``, ``channel``, ``tmin`` 

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

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

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

182 microseconds. 

183 :param format: %s 

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

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

186 :returns: list of generated filenames 

187 

188 .. note:: 

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

190 to file format specific maximum lengthes. 

191 ''' 

192 

193 if isinstance(traces, trace.Trace): 

194 traces = [traces] 

195 

196 if format == 'from_extension': 

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

198 

199 if format == 'mseed': 

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

201 overwrite=overwrite, **kwargs) 

202 

203 elif format == 'gse2': 

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

205 overwrite=overwrite, **kwargs) 

206 

207 elif format == 'sac': 

208 fns = [] 

209 for tr in traces: 

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

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

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

213 

214 if fn in fns: 

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

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

217 % fn) 

218 

219 util.ensuredirs(fn) 

220 

221 f = sac.SacFile(from_trace=tr) 

222 if stations: 

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

224 f.stla = s.lat 

225 f.stlo = s.lon 

226 f.stel = s.elevation 

227 f.stdp = s.depth 

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

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

230 

231 f.write(fn) 

232 fns.append(fn) 

233 

234 return fns 

235 

236 elif format == 'text': 

237 fns = [] 

238 for tr in traces: 

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

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

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

242 

243 if fn in fns: 

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

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

246 % fn) 

247 

248 util.ensuredirs(fn) 

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

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

251 fns.append(fn) 

252 return fns 

253 

254 elif format == 'yaff': 

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

256 overwrite=overwrite, **kwargs) 

257 else: 

258 raise UnsupportedFormat(format) 

259 

260 

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

262 

263 

264class UnknownFormat(Exception): 

265 def __init__(self, filename): 

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

267 

268 

269class UnsupportedFormat(Exception): 

270 def __init__(self, format): 

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

272 

273 

274def make_substitutions(tr, substitutions): 

275 if substitutions: 

276 tr.set_codes(**substitutions)