1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
5from __future__ import division, absolute_import
7from struct import unpack
8import os
9import re
10import math
11import logging
13from pyrocko import trace
14from pyrocko.util import reuse, ensuredirs
15from .io_common import FileLoadError, FileSaveError
17logger = logging.getLogger('pyrocko.io.mseed')
19MSEED_HEADER_BYTES = 64
20VALID_RECORD_LENGTHS = tuple(2**exp for exp in range(8, 20))
23class CodeTooLong(FileSaveError):
24 pass
27def iload(filename, load_data=True, offset=0, segment_size=0, nsegments=0):
28 from pyrocko import mseed_ext
30 have_zero_rate_traces = False
31 try:
32 isegment = 0
33 while isegment < nsegments or nsegments == 0:
34 tr_tuples = mseed_ext.get_traces(
35 filename, load_data, offset, segment_size)
37 if not tr_tuples:
38 break
40 for tr_tuple in tr_tuples:
41 network, station, location, channel = tr_tuple[1:5]
42 tmin = float(tr_tuple[5])/float(mseed_ext.HPTMODULUS)
43 tmax = float(tr_tuple[6])/float(mseed_ext.HPTMODULUS)
44 try:
45 deltat = reuse(1.0/float(tr_tuple[7]))
46 except ZeroDivisionError:
47 have_zero_rate_traces = True
48 continue
50 ydata = tr_tuple[8]
52 tr = trace.Trace(
53 network.strip(),
54 station.strip(),
55 location.strip(),
56 channel.strip(),
57 tmin,
58 tmax,
59 deltat,
60 ydata)
62 tr.meta = {
63 'offset_start': offset,
64 'offset_end': tr_tuple[9],
65 'last': tr_tuple[10],
66 'segment_size': segment_size
67 }
69 yield tr
71 if tr_tuple[10]:
72 break
74 offset = tr_tuple[9]
75 isegment += 1
77 except (OSError, mseed_ext.MSeedError) as e:
78 raise FileLoadError(str(e)+' (file: %s)' % filename)
80 if have_zero_rate_traces:
81 logger.warning(
82 'Ignoring traces with sampling rate of zero in file %s '
83 '(maybe LOG traces)' % filename)
86def as_tuple(tr, dataquality='D'):
87 from pyrocko import mseed_ext
88 itmin = int(round(tr.tmin*mseed_ext.HPTMODULUS))
89 itmax = int(round(tr.tmax*mseed_ext.HPTMODULUS))
90 srate = 1.0/tr.deltat
91 return (tr.network, tr.station, tr.location, tr.channel,
92 itmin, itmax, srate, dataquality, tr.get_ydata())
95def save(traces, filename_template, additional={}, overwrite=True,
96 dataquality='D', record_length=4096, append=False, steim=1):
97 from pyrocko import mseed_ext
99 assert record_length in VALID_RECORD_LENGTHS
100 assert dataquality in ('D', 'E', 'C', 'O', 'T', 'L'), 'invalid dataquality'
101 overwrite = True if append else overwrite
103 fn_tr = {}
104 for tr in traces:
105 for code, maxlen, val in zip(
106 ['network', 'station', 'location', 'channel'],
107 [2, 5, 2, 3],
108 tr.nslc_id):
110 if len(val) > maxlen:
111 raise CodeTooLong(
112 '%s code too long to be stored in MSeed file: %s' %
113 (code, val))
115 fn = tr.fill_template(filename_template, **additional)
116 if not overwrite and os.path.exists(fn):
117 raise FileSaveError('File exists: %s' % fn)
119 if fn not in fn_tr:
120 fn_tr[fn] = []
122 fn_tr[fn].append(tr)
124 for fn, traces_thisfile in fn_tr.items():
125 trtups = []
126 traces_thisfile.sort(key=lambda a: a.full_id)
127 for tr in traces_thisfile:
128 trtups.append(as_tuple(tr, dataquality))
130 ensuredirs(fn)
131 try:
132 mseed_ext.store_traces(trtups, fn, record_length, append, steim)
133 except mseed_ext.MSeedError as e:
134 raise FileSaveError(
135 str(e) + ' (while storing traces to file \'%s\')' % fn)
137 return list(fn_tr.keys())
140tcs = {}
143def get_bytes(traces, dataquality='D', record_length=4096, steim=1):
144 from pyrocko import mseed_ext
146 assert record_length in VALID_RECORD_LENGTHS
147 assert dataquality in ('D', 'E', 'C', 'O', 'T', 'L'), 'invalid dataquality'
149 nbytes_approx = 0
150 rl = record_length
151 trtups = []
152 for tr in traces:
153 for code, maxlen, val in zip(
154 ['network', 'station', 'location', 'channel'],
155 [2, 5, 2, 3],
156 tr.nslc_id):
158 if len(val) > maxlen:
159 raise CodeTooLong(
160 '%s code too long to be stored in MSeed file: %s' %
161 (code, val))
163 nbytes_approx += math.ceil(
164 tr.ydata.nbytes / (rl-MSEED_HEADER_BYTES)) * rl
165 trtups.append(as_tuple(tr, dataquality))
167 return mseed_ext.mseed_bytes(trtups, nbytes_approx, record_length, steim)
170def detect(first512):
172 if len(first512) < 256:
173 return False
175 rec = first512
177 try:
178 sequence_number = int(rec[:6])
179 except Exception:
180 return False
181 if sequence_number < 0:
182 return False
184 type_code = rec[6:7]
185 if type_code in b'DRQM':
186 bads = []
187 for sex in '<>':
188 bad = False
189 fmt = sex + '6s1s1s5s2s3s2s10sH2h4Bl2H'
190 vals = unpack(fmt, rec[:48])
191 fmt_btime = sex + 'HHBBBBH'
192 tvals = unpack(fmt_btime, vals[7])
193 if tvals[1] < 1 or tvals[1] > 367 or tvals[2] > 23 or \
194 tvals[3] > 59 or tvals[4] > 60 or tvals[6] > 9999:
195 bad = True
197 bads.append(bad)
199 if all(bads):
200 return False
202 else:
203 if type_code not in b'VAST':
204 return False
206 continuation_code = rec[7:8]
207 if continuation_code != b' ':
208 return False
210 blockette_type = rec[8:8+3].decode()
211 if not re.match(r'^\d\d\d$', blockette_type):
212 return False
214 try:
215 blockette_length = int(rec[11:11+4])
216 except Exception:
217 return False
219 if blockette_length < 7:
220 return False
222 return True