Newer
Older
"""Timeseries Utilities"""
from builtins import range
from datetime import datetime
import numpy
import obspy.core
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def create_empty_trace(starttime, endtime, observatory,
channel, type, interval, network, station, location):
"""create an empty trace filled with nans.
Parameters
----------
starttime: obspy.core.UTCDateTime
the starttime of the requested data
endtime: obspy.core.UTCDateTime
the endtime of the requested data
observatory : str
observatory code
channel : str
single character channel {H, E, D, Z, F}
type : str
data type {definitive, quasi-definitive, variation}
interval : str
interval length {minute, second}
network: str
the network code
station: str
the observatory station code
location: str
the location code
Returns
-------
obspy.core.Trace
trace for the requested channel
"""
if interval == 'second':
delta = 1.
elif interval == 'minute':
delta = 60.
elif interval == 'hourly':
delta = 3600.
elif interval == 'daily':
delta = 86400.
stats = obspy.core.Stats()
stats.network = network
stats.station = station
stats.location = location
stats.channel = channel
# Calculate first valid sample time based on interval
trace_starttime = obspy.core.UTCDateTime(
numpy.ceil(starttime.timestamp / delta) * delta)
stats.starttime = trace_starttime
stats.delta = delta
# Calculate number of valid samples up to or before endtime
length = int((endtime - trace_starttime) / delta)
stats.npts = length + 1
data = numpy.full(stats.npts, numpy.nan, dtype=numpy.float64)
return obspy.core.Trace(data, stats)
def get_stream_start_end_times(timeseries):
"""get start and end times from a stream.
Traverses all traces, and find the earliest starttime, and
the latest endtime.
Parameters
----------
timeseries: obspy.core.stream
The timeseries stream
Returns
-------
tuple: (starttime, endtime)
starttime: obspy.core.UTCDateTime
endtime: obspy.core.UTCDateTime
"""
starttime = obspy.core.UTCDateTime(datetime.now())
endtime = obspy.core.UTCDateTime(0)
for trace in timeseries:
if trace.stats.starttime < starttime:
starttime = trace.stats.starttime
if trace.stats.endtime > endtime:
endtime = trace.stats.endtime
return (starttime, endtime)
def get_stream_gaps(stream, channels=None):
"""Get gaps in a given stream
Parameters
----------
stream: obspy.core.Stream
the stream to check for gaps
channels: array_like
list of channels to check for gaps
Default is None (check all channels).
Returns
-------
dictionary of channel gaps arrays
Notes
-----
Returns a dictionary with channel: gaps array pairs. Where the gaps array
consists of arrays of starttime/endtime pairs representing each gap.
"""
gaps = {}
for trace in stream:
channel = trace.stats.channel
if channels is not None and channel not in channels:
continue
gaps[channel] = get_trace_gaps(trace)
return gaps
def get_trace_gaps(trace):
"""Gets gaps in a trace representing a single channel
Parameters
----------
trace: obspy.core.Trace
a stream containing a single channel of data.
Returns
-------
array of gaps, which is empty when there are no gaps.
each gap is an array [start of gap, end of gap, next sample]
"""
gaps = []
gap = None
data = trace.data
stats = trace.stats
starttime = stats.starttime
length = len(data)
delta = stats.delta
for i in range(0, length):
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
if numpy.isnan(data[i]):
if gap is None:
# start of a gap
gap = [starttime + i * delta]
else:
if gap is not None:
# end of a gap
gap.extend([
starttime + (i - 1) * delta,
starttime + i * delta])
gaps.append(gap)
gap = None
# check for gap at end
if gap is not None:
gap.extend([
starttime + (length - 1) * delta,
starttime + length * delta])
gaps.append(gap)
return gaps
def get_merged_gaps(gaps):
"""Get gaps merged across channels/streams
Parameters
----------
gaps: dictionary
contains channel/gap array pairs
Returns
-------
array_like
an array of startime/endtime arrays representing gaps.
Notes
-----
Takes an dictionary of gaps, and merges those gaps across channels,
returning an array of the merged gaps.
"""
merged_gaps = []
for key in gaps:
merged_gaps.extend(gaps[key])
# sort gaps so earlier gaps are before later gaps
sorted_gaps = sorted(merged_gaps, key=lambda gap: gap[0])
# merge gaps that overlap
merged_gaps = []
merged_gap = None
for gap in sorted_gaps:
if merged_gap is None:
# start of gap
merged_gap = gap
elif gap[0] > merged_gap[2]:
# next gap starts after current gap ends
merged_gaps.append(merged_gap)
merged_gap = gap
elif gap[0] <= merged_gap[2]:
# next gap starts at or before next data
if gap[1] > merged_gap[1]:
# next gap ends after current gap ends, extend current
merged_gap[1] = gap[1]
merged_gap[2] = gap[2]
if merged_gap is not None:
merged_gaps.append(merged_gap)
return merged_gaps
def get_channels(stream):
"""Get a list of channels in a stream.
Parameters
----------
stream : obspy.core.Stream
Returns
-------
channels : array_like
"""
channels = {}
for trace in stream:
channel = trace.stats.channel
if channel:
channels[channel] = True
return [ch for ch in channels]
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def mask_stream(stream):
"""Convert stream traces to masked arrays.
Parameters
----------
stream : obspy.core.Stream
stream to mask
Returns
-------
obspy.core.Stream
stream with new Trace objects with numpy masked array data.
"""
masked = obspy.core.Stream()
for trace in stream:
masked += obspy.core.Trace(
numpy.ma.masked_invalid(trace.data),
trace.stats)
return masked
def unmask_stream(stream):
"""Convert stream traces to unmasked arrays.
Parameters
----------
stream : obspy.core.Stream
stream to unmask
Returns
-------
obspy.core.Stream
stream with new Trace objects with numpy array data, with numpy.nan
as a fill value in a filled array.
"""
unmasked = obspy.core.Stream()
for trace in stream:
unmasked += obspy.core.Trace(
trace.data.filled(fill_value=numpy.nan)
if isinstance(trace.data, numpy.ma.MaskedArray)
else trace.data,
trace.stats)
return unmasked
def merge_streams(*streams):
"""Merge one or more streams.
Parameters
----------
*streams : obspy.core.Stream
one or more streams to merge
Returns
-------
obspy.core.Stream
stream with contiguous traces merged, and gaps filled with numpy.nan
"""
merged = obspy.core.Stream()
arigdon-usgs
committed
# sort out empty
arigdon-usgs
committed
merged += stream
arigdon-usgs
committed
arigdon-usgs
committed
split = mask_stream(merged)
# split traces that contain gaps
arigdon-usgs
committed
split = split.split()
# Re-add any empty traces that were removed by split()
readd = obspy.core.Stream()
for trace in merged:
stats = trace.stats
split_stream = split.select(
channel=stats.channel,
station=stats.station,
network=stats.network,
location=stats.location)
if len(split_stream) == 0:
readd += trace
split += readd
arigdon-usgs
committed
arigdon-usgs
committed
split.merge(
# 1 = do not interpolate
arigdon-usgs
committed
interpolation_samples=0,
# 1 = when there is overlap, use data from trace with last endtime
method=1,
# np.nan = work-around for (problematic) intermediate masked arrays
# convert back to NaN filled array
arigdon-usgs
committed
merged = unmask_stream(split)
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
def pad_timeseries(timeseries, starttime, endtime):
"""Realigns timeseries data so the start and endtimes are the same
as what was originally asked for, even if the data was during
a gap.
Parameters
----------
timeseries: obspy.core.stream
The timeseries stream as returned by the call to getWaveform
starttime: obspy.core.UTCDateTime
the starttime of the requested data
endtime: obspy.core.UTCDateTime
the endtime of the requested data
Notes: the original timeseries object is changed.
"""
for trace in timeseries:
trace_starttime = obspy.core.UTCDateTime(trace.stats.starttime)
trace_endtime = obspy.core.UTCDateTime(trace.stats.endtime)
trace_delta = trace.stats.delta
if trace_starttime > starttime:
cnt = int((trace_starttime - starttime) / trace_delta)
if cnt > 0:
trace.data = numpy.concatenate([
numpy.full(cnt, numpy.nan, dtype=numpy.float64),
trace.data])
trace_starttime = trace_starttime - trace_delta * cnt
trace.stats.starttime = trace_starttime
if trace_endtime < endtime:
cnt = int((endtime - trace_endtime) / trace.stats.delta)
if cnt > 0:
trace.data = numpy.concatenate([
trace.data,
numpy.full(cnt, numpy.nan, dtype=numpy.float64)])