Coverage for /usr/local/lib/python3.13/dist-packages/pyrocko/io/datacube.py: 57%
467 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-12-04 10:41 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-12-04 10:41 +0000
1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Reader for `DiGOS DATA-CUBE³ <https://digos.eu/the-seismic-data-recorder/>`_
8raw data.
9'''
12import os
13import math
14import logging
15import warnings
17import numpy as num
19from pyrocko import trace, util, plot
20from pyrocko.guts import Object, Int, String, Timestamp
21from typing import NamedTuple
23from . import io_common
25logger = logging.getLogger(__name__)
27HOUR = 3600.
28DAY = 24 * HOUR
29WEEK = 7 * DAY
30WNRO = 1024 * WEEK # GPS Week Number Rollover period
32N_GPS_TAGS_WANTED = 200 # must match definition in datacube_ext.c
34APPLY_SUBSAMPLE_SHIFT_CORRECTION = True
37def color(c):
38 c = plot.color(c)
39 return tuple(x/255. for x in c)
42class DataCubeError(io_common.FileLoadError):
43 pass
46class ControlPointError(Exception):
47 pass
50class GPSTags(NamedTuple):
51 position: num.ndarray
52 gps_time: num.ndarray
53 fix: num.ndarray
54 sat_count: num.ndarray
55 latitudes: num.ndarray
56 longitudes: num.ndarray
57 elevations: num.ndarray
58 temperatures: num.ndarray
59 gps_utc_offsets: num.ndarray
61 @classmethod
62 def from_tuple(cls, tup):
63 return cls(*tup)
66def check_WNRO(utc_gps_offsets: num.ndarray, leap_seconds: int) -> bool:
67 """Checks for GPS week number rollover offset."""
68 # give some tolerance, we expect larger offsets from 1024 week rollovers
69 max_utc_gps_offset = utc_gps_offsets.max()
70 if max_utc_gps_offset == 0:
71 return False
72 return abs(leap_seconds + max_utc_gps_offset) > 1
75def make_control_point(ipos_block, t_block, tref, deltat):
77 # reduce time (no drift would mean straight line)
78 tred = (t_block - tref) - ipos_block*deltat
80 # first round, remove outliers
81 q25, q75 = num.percentile(tred, (25., 75.))
82 iok = num.logical_and(q25 <= tred, tred <= q75)
84 # detrend
85 slope, offset = num.polyfit(ipos_block[iok], tred[iok], 1)
86 tred2 = tred - (offset + slope * ipos_block)
88 # second round, remove outliers based on detrended tred, refit
89 q25, q75 = num.percentile(tred2, (25., 75.))
90 iok = num.logical_and(q25 <= tred2, tred2 <= q75)
91 x = ipos_block[iok].copy()
92 ipos0 = x[0]
93 x -= ipos0
94 y = tred[iok].copy()
95 if x.size < 2:
96 raise ControlPointError('Insufficient number control points after QC.')
98 elif x.size < 5: # needed for older numpy versions
99 (slope, offset) = num.polyfit(x, y, 1)
100 else:
101 (slope, offset), cov = num.polyfit(x, y, 1, cov=True)
103 slope_err, offset_err = num.sqrt(num.diag(cov))
105 slope_err_limit = 1.0e-10
106 if ipos_block.size < N_GPS_TAGS_WANTED // 2:
107 slope_err_limit *= (200. / ipos_block.size)
109 offset_err_limit = 5.0e-3
111 if slope_err > slope_err_limit:
112 raise ControlPointError(
113 'Slope error too large: %g (limit: %g' % (
114 slope_err, slope_err_limit))
116 if offset_err > offset_err_limit:
117 raise ControlPointError(
118 'Offset error too large: %g (limit: %g' % (
119 offset_err, offset_err_limit))
121 ic = ipos_block[ipos_block.size//2]
122 tc = offset + slope * (ic - ipos0)
124 return ic, tc + ic * deltat + tref
127def analyse_gps_tags(header, gps_tags, offset, nsamples):
129 ipos, times, fix, sat_count = gps_tags
130 deltat = 1.0 / int(header['S_RATE'])
132 tquartz = offset + ipos * deltat
134 toff = times - tquartz
135 toff_median = num.median(toff)
137 n = times.size
139 dtdt = (times[1:n] - times[0:n-1]) / (tquartz[1:n] - tquartz[0:n-1])
141 ok = abs(toff_median - toff) < 10.
143 xok = num.abs(dtdt - 1.0) < 0.00001
145 if ok.size >= 1:
147 ok[0] = False
148 ok[1:n] &= xok
149 ok[0:n-1] &= xok
150 ok[n-1] = False
152 ipos = ipos[ok]
153 times = times[ok]
154 fix = fix[ok]
155 sat_count = sat_count[ok]
157 blocksize = N_GPS_TAGS_WANTED // 2
158 if ipos.size < blocksize:
159 blocksize = max(10, ipos.size // 4)
160 warnings.warn(
161 'Small number of GPS tags found. '
162 'Reducing analysis block size to %i tags. '
163 'Time correction may be unreliable.' % blocksize)
165 try:
166 if ipos.size < blocksize:
167 raise ControlPointError(
168 'Cannot determine GPS time correction: '
169 'Too few GPS tags found: %i' % ipos.size)
171 j = 0
172 control_points = []
173 tref = num.median(times - ipos*deltat)
174 while j < ipos.size - blocksize:
175 ipos_block = ipos[j:j+blocksize]
176 t_block = times[j:j+blocksize]
177 try:
178 ic, tc = make_control_point(ipos_block, t_block, tref, deltat)
179 control_points.append((ic, tc))
180 except ControlPointError as e:
181 logger.debug(str(e))
183 j += blocksize
185 ipos_last = ipos[-blocksize:]
186 t_last = times[-blocksize:]
187 try:
188 ic, tc = make_control_point(ipos_last, t_last, tref, deltat)
189 control_points.append((ic, tc))
190 except ControlPointError as e:
191 logger.debug(str(e))
193 if len(control_points) < 2:
194 raise ControlPointError(
195 'Could not safely determine time corrections from GPS: '
196 'unable to construct two or more control points')
198 i0, t0 = control_points[0]
199 i1, t1 = control_points[1]
200 i2, t2 = control_points[-2]
201 i3, t3 = control_points[-1]
202 if len(control_points) == 2:
203 tmin = t0 - i0 * deltat - offset * deltat
204 tmax = t3 + (nsamples - i3 - 1) * deltat
205 else:
206 icontrol = num.array(
207 [x[0] for x in control_points], dtype=num.int64)
208 tcontrol = num.array(
209 [x[1] for x in control_points], dtype=float)
210 # robust against steps:
211 slope = num.median(
212 (tcontrol[1:] - tcontrol[:-1])
213 / (icontrol[1:] - icontrol[:-1]))
215 tmin = t0 + (offset - i0) * slope
216 tmax = t2 + (offset + nsamples - 1 - i2) * slope
218 if offset < i0:
219 control_points[0:0] = [(offset, tmin)]
221 if offset + nsamples - 1 > i3:
222 control_points.append((offset + nsamples - 1, tmax))
224 icontrol = num.array([x[0] for x in control_points], dtype=num.int64)
225 tcontrol = num.array([x[1] for x in control_points], dtype=float)
227 # corrected 2021-10-26: This sub-sample time shift introduced by the
228 # Cube's ADC was previously not recognized.
229 if APPLY_SUBSAMPLE_SHIFT_CORRECTION:
230 tcontrol -= 0.199 * deltat + 0.0003
232 return tmin, tmax, icontrol, tcontrol, ok
234 except ControlPointError as e:
235 logger.error(str(e))
237 tmin = util.str_to_time(header['S_DATE'] + header['S_TIME'],
238 format='%y/%m/%d%H:%M:%S')
240 idat = int(header['DAT_NO'])
241 if idat == 0:
242 tmin = tmin + util.gps_utc_offset(tmin)
243 else:
244 tmin = util.day_start(tmin + idat * DAY) \
245 + util.gps_utc_offset(tmin)
247 tmax = tmin + (nsamples - 1) * deltat
248 icontrol = num.array([offset, offset+nsamples - 1], dtype=num.int64)
249 tcontrol = num.array([tmin, tmax])
250 return tmin, tmax, icontrol, tcontrol, ok
253def plot_gnss_location_timeline(fns):
254 from matplotlib import pyplot as plt
255 from pyrocko.orthodrome import latlon_to_ne_numpy
256 not_ = num.logical_not
258 fig = plt.figure()
260 axes = []
261 for i in range(4):
262 axes.append(
263 fig.add_subplot(4, 1, i+1, sharex=axes[-1] if axes else None))
265 background_colors = [
266 color('aluminium1'),
267 color('aluminium2')]
269 tref = None
270 for ifn, fn in enumerate(fns):
271 header, gps_tags, nsamples = get_time_infos(fn)
272 _, t, fix, nsvs, lats, lons, elevations, _, gps_utc_offset = gps_tags
273 leap_seconds = util.utc_gps_offset(num.median(t))
274 if check_WNRO(gps_utc_offset, leap_seconds):
275 t += WNRO
277 fix = fix.astype(bool)
279 if t.size < 2:
280 logger.warning('Need at least 2 gps tags for plotting: %s' % fn)
282 if tref is None:
283 tref = util.day_start(t[0])
284 lat, lon, elevation = coordinates_from_gps(gps_tags)
286 norths, easts = latlon_to_ne_numpy(lat, lon, lats, lons)
288 for ax, data in zip(axes, (norths, easts, elevations, nsvs)):
290 tspan = t[num.array([0, -1])]
292 ax.axvspan(*((tspan - tref) / HOUR),
293 color=background_colors[ifn % 2])
294 med = num.median(data)
295 ax.plot(
296 (tspan - tref) / HOUR,
297 [med, med],
298 ls='--',
299 c='k',
300 lw=3,
301 alpha=0.5)
303 ax.plot(
304 (t[not_(fix)] - tref) / HOUR, data[not_(fix)], 'o',
305 ms=1.5,
306 mew=0,
307 color=color('scarletred2'))
309 ax.plot(
310 (t[fix] - tref) / HOUR, data[fix], 'o',
311 ms=1.5,
312 mew=0,
313 color=color('aluminium6'))
315 for ax in axes:
316 ax.grid(alpha=.3)
318 ax_lat, ax_lon, ax_elev, ax_nsv = axes
320 ax_lat.set_ylabel('Northing [m]')
321 ax_lon.set_ylabel('Easting [m]')
322 ax_elev.set_ylabel('Elevation [m]')
323 ax_nsv.set_ylabel('Number of Satellites')
325 ax_lat.get_xaxis().set_tick_params(labelbottom=False)
326 ax_lon.get_xaxis().set_tick_params(labelbottom=False)
327 ax_nsv.set_xlabel(
328 'Hours after %s' % util.time_to_str(tref, format='%Y-%m-%d'))
330 fig.suptitle(
331 u'Lat: %.5f\u00b0 Lon: %.5f\u00b0 Elevation: %g m' % (
332 lat, lon, elevation))
334 plt.show()
337def coordinates_from_gps(gps_tags):
338 ipos, t, fix, nsvs, lats, lons, elevations, temps, gps_utc_offset = \
339 gps_tags
340 return tuple(num.median(x) for x in (lats, lons, elevations))
343def extract_stations(fns):
344 import io
345 import sys
346 from pyrocko.model import Station
347 from pyrocko.guts import dump_all
349 stations = {}
351 for fn in fns:
352 sta_name = os.path.splitext(fn)[1].lstrip('.')
353 if sta_name in stations:
354 logger.warning('Cube %s already in list!', sta_name)
355 continue
357 header, gps_tags, nsamples = get_time_infos(fn)
359 lat, lon, elevation = coordinates_from_gps(gps_tags)
361 sta = Station(
362 network='',
363 station=sta_name,
364 name=sta_name,
365 location='',
366 lat=lat,
367 lon=lon,
368 elevation=elevation)
370 stations[sta_name] = sta
372 f = io.BytesIO()
373 dump_all(stations.values(), stream=f)
374 sys.stdout.write(f.getvalue().decode())
377def plot_timeline(fns):
378 from matplotlib import pyplot as plt
380 fig = plt.figure()
381 axes = fig.gca()
383 if isinstance(fns, str):
384 fn = fns
385 if os.path.isdir(fn):
386 fns = [
387 os.path.join(fn, entry) for entry in sorted(os.listdir(fn))]
389 ipos, t, fix, nsvs, header, offset, nsamples = \
390 get_timing_context(fns)
392 else:
393 ipos, t, fix, nsvs, header, offset, nsamples = \
394 get_extended_timing_context(fn)
396 else:
397 ipos, t, fix, nsvs, header, offset, nsamples = \
398 get_timing_context(fns)
400 deltat = 1.0 / int(header['S_RATE'])
402 tref = num.median(t - ipos * deltat)
403 tref = round(tref / deltat) * deltat
405 if APPLY_SUBSAMPLE_SHIFT_CORRECTION:
406 tcorr = 0.199 * deltat + 0.0003
407 else:
408 tcorr = 0.0
410 x = ipos*deltat
411 y = (t - tref) - ipos*deltat - tcorr
413 bfix = fix != 0
414 bnofix = fix == 0
416 tmin, tmax, icontrol, tcontrol, ok = analyse_gps_tags(
417 header, (ipos, t, fix, nsvs), offset, nsamples)
419 la = num.logical_and
420 nok = num.logical_not(ok)
422 axes.plot(
423 x[la(bfix, ok)]/HOUR, y[la(bfix, ok)], '+',
424 ms=5, color=color('chameleon3'))
425 axes.plot(
426 x[la(bfix, nok)]/HOUR, y[la(bfix, nok)], '+',
427 ms=5, color=color('aluminium4'))
429 axes.plot(
430 x[la(bnofix, ok)]/HOUR, y[la(bnofix, ok)], 'x',
431 ms=5, color=color('chocolate3'))
432 axes.plot(
433 x[la(bnofix, nok)]/HOUR, y[la(bnofix, nok)], 'x',
434 ms=5, color=color('aluminium4'))
436 tred = tcontrol - icontrol*deltat - tref
437 axes.plot(icontrol*deltat/HOUR, tred, color=color('aluminium6'))
438 axes.plot(icontrol*deltat/HOUR, tred, 'o', ms=5, color=color('aluminium6'))
440 ymin = (math.floor(tred.min() / deltat)-1) * deltat
441 ymax = (math.ceil(tred.max() / deltat)+1) * deltat
442 # ymin = min(ymin, num.min(y))
443 # ymax = max(ymax, num.max(y))
445 if ymax - ymin < 1000 * deltat:
446 ygrid = math.floor(tred.min() / deltat) * deltat
447 while ygrid < ymax:
448 axes.axhline(ygrid, color=color('aluminium4'), alpha=0.3)
449 ygrid += deltat
451 xmin = icontrol[0]*deltat/HOUR
452 xmax = icontrol[-1]*deltat/HOUR
453 xsize = xmax - xmin
454 xmin -= xsize * 0.1
455 xmax += xsize * 0.1
456 axes.set_xlim(xmin, xmax)
458 axes.set_ylim(ymin, ymax)
460 axes.set_xlabel('Uncorrected (quartz) time [h]')
461 axes.set_ylabel('Relative time correction [s]')
463 plt.show()
466g_dir_contexts = {}
469class DirContextEntry(Object):
470 path = String.T()
471 tstart = Timestamp.T()
472 ifile = Int.T()
475class DirContext(Object):
476 path = String.T()
477 mtime = Timestamp.T()
478 entries = DirContextEntry.T()
480 def get_entry(self, fn):
481 path = os.path.abspath(fn)
482 for entry in self.entries:
483 if entry.path == path:
484 return entry
486 raise Exception('entry not found')
488 def iter_entries(self, fn, step=1):
489 current = self.get_entry(fn)
490 by_ifile = dict(
491 (entry.ifile, entry) for entry in self.entries
492 if entry.tstart == current.tstart)
494 icurrent = current.ifile
495 while True:
496 icurrent += step
497 try:
498 yield by_ifile[icurrent]
500 except KeyError:
501 break
504def context(fn):
505 from pyrocko import datacube_ext
507 dpath = os.path.dirname(os.path.abspath(fn))
508 mtimes = [os.stat(dpath)[8]]
510 dentries = sorted([os.path.join(dpath, f) for f in os.listdir(dpath)
511 if os.path.isfile(os.path.join(dpath, f))])
512 for dentry in dentries:
513 fn2 = os.path.join(dpath, dentry)
514 mtimes.append(os.stat(fn2)[8])
516 mtime = float(max(mtimes))
518 if dpath in g_dir_contexts:
519 dir_context = g_dir_contexts[dpath]
520 if dir_context.mtime == mtime:
521 return dir_context
523 del g_dir_contexts[dpath]
525 entries = []
526 for dentry in dentries:
527 fn2 = os.path.join(dpath, dentry)
528 if not os.path.isfile(fn2):
529 continue
531 with open(fn2, 'rb') as f:
532 first512 = f.read(512)
533 if not detect(first512):
534 continue
536 with open(fn2, 'rb') as f:
537 try:
538 header, data_arrays, gps_tags, nsamples, _ = \
539 datacube_ext.load(f.fileno(), 3, 0, -1, None)
541 except datacube_ext.DataCubeError as e:
542 e = DataCubeError(str(e))
543 e.set_context('filename', fn)
544 raise e
546 header = dict(header)
547 entries.append(DirContextEntry(
548 path=os.path.abspath(fn2),
549 tstart=util.str_to_time(
550 '20' + header['S_DATE'] + ' ' + header['S_TIME'],
551 format='%Y/%m/%d %H:%M:%S'),
552 ifile=int(header['DAT_NO'])))
554 dir_context = DirContext(mtime=mtime, path=dpath, entries=entries)
556 return dir_context
559def get_time_infos(fn):
560 from pyrocko import datacube_ext
562 with open(fn, 'rb') as f:
563 try:
564 header, _, gps_tags, nsamples, _ = datacube_ext.load(
565 f.fileno(), 1, 0, -1, None)
567 except datacube_ext.DataCubeError as e:
568 e = DataCubeError(str(e))
569 e.set_context('filename', fn)
570 raise e
572 return dict(header), GPSTags.from_tuple(gps_tags), nsamples
575def get_timing_context(fns):
576 joined = [[], [], [], []]
577 ioff = 0
578 for fn in fns:
579 header, gps_tags, nsamples = get_time_infos(fn)
581 gps_tags = GPSTags.from_tuple(gps_tags)
583 ipos = gps_tags.position
584 ipos += ioff
586 for i in range(4):
587 joined[i].append(gps_tags[i])
589 ioff += nsamples
591 ipos, t, fix, nsvs = [num.concatenate(x) for x in joined]
593 nsamples = ioff
594 return ipos, t, fix, nsvs, header, 0, nsamples
597def get_extended_timing_context(fn):
598 c = context(fn)
600 header, gps_tags, nsamples_base = get_time_infos(fn)
601 gps_tags = GPSTags.from_tuple(gps_tags)
603 ioff = 0
604 aggregated = [gps_tags]
606 nsamples_total = nsamples_base
608 if num.sum(gps_tags.fix) == 0:
610 ioff = nsamples_base
611 for entry in c.iter_entries(fn, 1):
613 _, gps_tags, nsamples = get_time_infos(entry.path)
615 ipos = gps_tags.position
616 ipos += ioff
618 aggregated.append(gps_tags)
619 nsamples_total += nsamples
621 if num.sum(gps_tags.fix) > 0:
622 break
624 ioff += nsamples
626 ioff = 0
627 for entry in c.iter_entries(fn, -1):
629 _, gps_tags, nsamples = get_time_infos(entry.path)
631 ioff -= nsamples
633 ipos = gps_tags.position
634 ipos += ioff
636 aggregated[0:0] = [gps_tags]
638 nsamples_total += nsamples
640 if num.sum(gps_tags.fix) > 0:
641 break
643 concat = num.concatenate
645 ipos = concat([tag.position for tag in aggregated])
646 time = concat([tag.gps_time for tag in aggregated])
647 fix = concat([tag.fix for tag in aggregated])
648 sat_count = concat([tag.sat_count for tag in aggregated])
649 gps_utc_offsets = concat([tag.gps_utc_offsets for tag in aggregated])
651 return ipos, time, fix, sat_count, gps_utc_offsets, header, \
652 0, nsamples_base
655def iload(fn, load_data=True, interpolation='sinc', yield_gps_tags=False):
656 from pyrocko import datacube_ext
657 from pyrocko import signal_ext
659 if interpolation not in ('sinc', 'off'):
660 raise NotImplementedError(
661 'no such interpolation method: %s' % interpolation)
663 with open(fn, 'rb') as f:
664 loadflag = 2 if load_data else 1
666 try:
667 header, data_arrays, gps_tags, nsamples, _ = datacube_ext.load(
668 f.fileno(), loadflag, 0, -1, None)
670 except datacube_ext.DataCubeError as e:
671 e = DataCubeError(str(e))
672 e.set_context('filename', fn)
673 raise e
675 header = dict(header)
676 deltat = 1.0 / int(header['S_RATE'])
677 nchannels = int(header['CH_NUM'])
679 ipos, times, fix, sat_count, utc_gps_offsets, \
680 header_, offset_, nsamples_ = get_extended_timing_context(fn)
682 tmin, tmax, icontrol, tcontrol, _ = analyse_gps_tags(
683 header_, (ipos, times, fix, sat_count), offset_, nsamples_)
685 if icontrol is None:
686 warnings.warn(
687 'No usable GPS timestamps found. Using datacube header '
688 'information to guess time. (file: "%s")' % fn)
690 tmin_ip = round(tmin / deltat) * deltat
691 if interpolation != 'off':
692 tmax_ip = round(tmax / deltat) * deltat
693 else:
694 tmax_ip = tmin_ip + (nsamples-1) * deltat
696 nsamples_ip = int(round((tmax_ip - tmin_ip)/deltat)) + 1
697 # to prevent problems with rounding errors:
698 tmax_ip = tmin_ip + (nsamples_ip-1) * deltat
700 leaps = num.array(
701 [x[0] + util.gps_utc_offset(x[0]) for x in util.read_leap_seconds2()],
702 dtype=float)
704 if load_data and icontrol is not None:
705 ncontrol_this = num.sum(
706 num.logical_and(0 <= icontrol, icontrol < nsamples))
708 if ncontrol_this <= 1:
709 warnings.warn(
710 'Extrapolating GPS time information from directory context '
711 '(insufficient number of GPS timestamps in file: "%s").' % fn)
713 for i in range(nchannels):
714 if load_data:
715 arr = data_arrays[i]
716 assert arr.size == nsamples
718 if interpolation == 'sinc' and icontrol is not None:
720 ydata = num.empty(nsamples_ip, dtype=float)
721 try:
722 signal_ext.antidrift(
723 icontrol, tcontrol,
724 arr.astype(float), tmin_ip, deltat, ydata)
726 except signal_ext.Error as e:
727 e = DataCubeError(str(e))
728 e.set_context('filename', fn)
729 e.set_context('n_control_points', icontrol.size)
730 e.set_context('n_samples_raw', arr.size)
731 e.set_context('n_samples_ip', ydata.size)
732 e.set_context('tmin_ip', util.time_to_str(tmin_ip))
733 raise e
735 ydata = num.round(ydata).astype(arr.dtype)
736 else:
737 ydata = arr
739 tr_tmin = tmin_ip
740 tr_tmax = None
741 else:
742 ydata = None
743 tr_tmin = tmin_ip
744 tr_tmax = tmax_ip
746 tr = trace.Trace('', header['DEV_NO'], '', 'p%i' % i, deltat=deltat,
747 ydata=ydata, tmin=tr_tmin, tmax=tr_tmax, meta=header)
748 bleaps = num.logical_and(tmin_ip <= leaps, leaps < tmax_ip)
750 if num.any(bleaps):
751 assert num.sum(bleaps) == 1
752 tcut = leaps[bleaps][0]
754 for tmin_cut, tmax_cut in [
755 (tr.tmin, tcut), (tcut, tr.tmax+tr.deltat)]:
757 try:
758 tr_cut = tr.chop(tmin_cut, tmax_cut, inplace=False)
759 ref_time = 0.5*(tr_cut.tmin+tr_cut.tmax)
760 leap_second = util.utc_gps_offset(ref_time)
761 if check_WNRO(utc_gps_offsets, leap_second):
762 tr_cut.shift(WNRO)
763 leap_second = util.utc_gps_offset(ref_time + WNRO)
765 tr_cut.shift(leap_second)
766 if yield_gps_tags:
767 yield tr_cut, gps_tags
768 else:
769 yield tr_cut
771 except trace.NoData:
772 pass
774 else:
775 t_ref = 0.5*(tr.tmin+tr.tmax)
776 leap_second = util.utc_gps_offset(t_ref)
777 if check_WNRO(utc_gps_offsets, leap_second):
778 tr.shift(WNRO)
779 leap_second = util.utc_gps_offset(t_ref + WNRO)
781 tr.shift(leap_second)
782 if yield_gps_tags:
783 yield tr, gps_tags
784 else:
785 yield tr
788header_keys = {
789 str: b'GIPP_V DEV_NO E_NAME GPS_PO S_TIME S_DATE DAT_NO'.split(),
790 int: b'''P_AMPL CH_NUM S_RATE D_FILT C_MODE A_CHOP F_TIME GPS_TI GPS_OF
791 A_FILT A_PHAS GPS_ON ACQ_ON V_TCXO D_VOLT E_VOLT'''.split()}
793all_header_keys = header_keys[str] + header_keys[int]
796def detect(first512):
797 s = first512
799 if len(s) < 512:
800 return False
802 if ord(s[0:1]) >> 4 != 15:
803 return False
805 n = s.find(b'\x80')
806 if n == -1:
807 n = len(s)
809 s = s[1:n]
810 s = s.replace(b'\xf0', b'')
811 s = s.replace(b';', b' ')
812 s = s.replace(b'=', b' ')
813 kvs = s.split(b' ')
815 if len([k for k in all_header_keys if k in kvs]) == 0:
816 return False
817 return True
820if __name__ == '__main__':
821 import argparse
822 parser = argparse.ArgumentParser(description='Datacube reader')
824 parser.add_argument(
825 'action', choices=['timeline', 'gnss', 'stations'],
826 help='Action')
827 parser.add_argument(
828 'files', nargs='+')
830 parser.add_argument(
831 '--loglevel', '-l',
832 choices=['critical', 'error', 'warning', 'info', 'debug'],
833 default='info',
834 help='Set logger level. Default: %(default)s')
836 args = parser.parse_args()
838 util.setup_logging('pyrocko.io.datacube', args.loglevel)
839 logging.getLogger('matplotlib.font_manager').disabled = True
841 if args.action == 'timeline':
842 plot_timeline(args.files)
844 elif args.action == 'gnss':
845 plot_gnss_location_timeline(args.files)
847 elif args.action == 'stations':
848 extract_stations(args.files)