def call(self): '''Main work routine of the snuffling.''' by_nslc, times, tinc = self.extract() fframe = self.figure_frame() fig = fframe.gcf() nslcs = sorted(by_nslc.keys()) p = None ncols = int(len(nslcs) / 5 + 1) nrows = (len(nslcs) - 1) / ncols + 1 tmin = min(times) tmax = max(times) nt = int(round((tmax - tmin) / tinc)) + 1 t = num.linspace(tmin, tmax, nt) if (tmax - tmin) < 60: tref = util.day_start(tmin) tref += math.floor((tmin - tref) / 60.) * 60. t -= tref tunit = 's' elif (tmax - tmin) < 3600: tref = util.day_start(tmin) tref += math.floor((tmin - tref) / 3600.) * 3600. t -= tref t /= 60. tunit = 'min' else: tref = util.day_start(tmin) t -= tref t /= 3600. tunit = 'h' axes = [] for i, nslc in enumerate(nslcs): p = fig.add_subplot(nrows, ncols, i + 1, sharex=p, sharey=p) axes.append(p) group = by_nslc[nslc] f = group[0][1] nf = f.size a = num.zeros((nf, nt), dtype=num.float) a.fill(num.nan) for (t1, _, a1) in group: it = int(round((t1 - tmin) / tinc)) if it < 0 or nt <= it: continue a[:, it] = a1 if self.color_scale == 'log': a = num.log(a) label = 'log PSD' elif self.color_scale == 'sqrt': a = num.sqrt(a) label = 'sqrt PSD' else: label = 'PSD' a = num.ma.masked_invalid(a) min_a = num.min(a) max_a = num.max(a) mean_a = num.mean(a) std_a = num.std(a) zmin = max(min_a, mean_a - 3.0 * std_a) zmax = min(max_a, mean_a + 3.0 * std_a) pcm = p.pcolormesh(t, f, a, cmap=get_cmap(self.ctb_name), vmin=zmin, vmax=zmax) fmin = 2.0 / self.twin fmax = f[-1] p.set_title('.'.join(x for x in nslc if x), ha='right', va='top', x=0.99, y=0.9) p.grid() p.set_yscale('log') divider = make_axes_locatable(p) cax = divider.append_axes('right', size='2%', pad=0.2) cbar = fig.colorbar(pcm, cax=cax) cbar.set_label(label) if i / ncols == (len(nslcs) - 1) / ncols: p.set_xlabel( 'Time since %s [%s]' % (util.time_to_str(tref, format='%Y-%m-%d %H:%M'), tunit)) if i % ncols == 0: p.set_ylabel('Frequency [Hz]') p.set_xlim(t[0], t[-1]) p.set_ylim(fmin, fmax) for i, p in enumerate(axes): if i / ncols != (len(nslcs) - 1) / ncols: for t in p.get_xticklabels(): t.set_visible(False) if i % ncols != 0: for t in p.get_yticklabels(): t.set_visible(False) else: tls = p.get_yticklabels() if len(tls) > 8: for t in tls[1::2]: t.set_visible(False) try: fig.tight_layout() except AttributeError: pass if self.save: fig.savefig(self.output_filename(dir='psd.pdf')) fig.canvas.draw()
def make_time_line(self, events): if self.cli_mode: import matplotlib.pyplot as plt self.fig = plt.figure() else: fframe = self.figure_frame() self.fig = fframe.gcf() gs = gridspec.GridSpec( 2, 1, figure=self.fig, hspace=0.005, top=0.95) gs1 = gridspec.GridSpec( 2, 2, figure=self.fig) ax = self.fig.add_subplot(gs[0]) ax1 = ax.twinx() ax2 = self.fig.add_subplot(gs1[-1]) ax3 = self.fig.add_subplot(gs1[-2]) events.sort(key=lambda x: x.time) magnitudes = [] i_has_magnitude = [] for i, e in enumerate(events): if e.moment_tensor is not None: magnitude = e.moment_tensor.magnitude else: magnitude = e.magnitude if magnitude is not None: magnitudes.append(magnitude) i_has_magnitude.append(i) cum_events = num.cumsum(num.ones(len(i_has_magnitude))) moments = moment_tensor.magnitude_to_moment(num.array(magnitudes)) cum_events_magnitude = num.cumsum(moments) times = num.array([events[i].time for i in i_has_magnitude]) timeslabels = [datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=t) for t in times] ax.plot(timeslabels, cum_events) ax.set_ylabel('Cumulative number of events') ax.axes.get_xaxis().set_ticklabels([]) ax1.plot(timeslabels, cum_events_magnitude, '-b') ax1.set_ylabel('Cumulative moment [Nm]') xfmt = md.DateFormatter('%Y-%m-%d') ax1.xaxis.set_major_formatter(xfmt) ax.yaxis.label.set_color('r') ax1.yaxis.label.set_color('b') ax.grid(True) ax1.grid(True) t0 = min(times) if self.variation == 'daily': normalization = 60 * 60 nbins = 24 t0_day = util.day_start(t0) ax2.set_xlabel('hour of day') elif self.variation == 'annual': normalization = 60 * 60 * 24 nbins = 365 t0_day = util.year_start(t0) ax2.set_xlabel('day of year') binned = (times - t0_day) % (nbins * normalization) ax2.hist(binned/normalization, bins=nbins, color='grey') ax2.set_ylabel('Number of events') ax2.set_xlim((0, nbins)) ax3.hist(magnitudes, bins=21, histtype='stepfilled') ax3.set_xlabel('Magnitude') ax3.set_ylabel('Number of events') if self.cli_mode: plt.show() else: self.fig.canvas.draw()
def call(self): '''Main work routine of the snuffling.''' self.cleanup() viewer = self.get_viewer() figs = {} fig_width_inch = viewer.width() npixel_hori = float(fig_width_inch * 50) xminutes = int(self.xminutes) xseconds = xminutes * 60 self.nhours = 24 nrows = int(self.nhours) * 60 / xminutes ynormalizations = {} lines_data = {} for traces in self.chopper_selected_traces(tinc=60 * 60, fallback=True): for tr in traces: t0 = util.day_start(tr.tmin) key = (tr.nslc_id, t0) if key not in figs: fig = self.pylab(get='figure') ax = fig.add_subplot(111) figs[key] = (fig, ax) ynormalizations[key] = 0 lines_data[key] = [] tr = tr.copy(data=True) ndecimate = int((xseconds / tr.deltat) / npixel_hori) tr.downsample(ndecimate) if self.prescale == 'max': ynormalizations[key] = max(num.max(tr.ydata), ynormalizations[key]) else: ynormalizations[key] = max(num.std(tr.ydata), ynormalizations[key]) if viewer.highpass: tr.highpass(4, viewer.highpass) if viewer.lowpass and 1. / tr.deltat > 2. * viewer.lowpass: tr.lowpass(4, viewer.lowpass) t = tr.get_xdata() - t0 y = num.asarray(tr.get_ydata(), dtype=num.float) nskip = t / 3600. x = t % xseconds xdiff = num.diff(x) itmp = num.where( num.logical_or(xdiff < 0, num.abs(xdiff - tr.deltat) > 1E-4))[0] indices = num.zeros(len(itmp) + 2, dtype=num.int) indices[1:-1] = itmp indices[-1] = len(y) - 1 for i in range(len(indices) - 1): istart = indices[i] + 1 istop = indices[i + 1] lines_data[key].append( (t0, x[istart:istop], y[istart:istop], nskip[istart:istop])) ynorm = None if self.scale_global: ynorm = max(ynormalizations.values()) for key, lines in lines_data.items(): if not self.scale_global: ynorm = float(ynormalizations.get(key, 1.)) for (t0, x, y, shifts) in lines: fig, ax = figs[key] ax.plot(x / 60., y / (ynorm / self.yscale) + shifts, color='black') ax.set_title(util.tts(t0, format='%Y-%m-%d')) yticks = range(0, self.nhours + 2, 2) xticks = range(0, xminutes + 1, 1) for key, (fig, ax) in figs.items(): ax.set_xlim(0, xminutes) ax.set_ylabel('Hour') ax.set_xlabel('Minute') ax.yaxis.set_ticks(yticks) ax.xaxis.set_ticks(xticks) ax.set_ylim(-0.1, 24.1) fig.canvas.draw()
def call(self): '''Main work routine of the snuffling.''' by_nslc, times, tinc = self.extract() fframe = self.figure_frame() fig = fframe.gcf() nslcs = sorted(by_nslc.keys()) p = None ncols = int(len(nslcs) / 5 + 1) nrows = (len(nslcs)-1) / ncols + 1 tmin = min(times) tmax = max(times) nt = int(round((tmax - tmin) / tinc)) + 1 t = num.linspace(tmin, tmax, nt) if (tmax - tmin) < 60: tref = util.day_start(tmin) tref += math.floor((tmin-tref) / 60.) * 60. t -= tref tunit = 's' elif (tmax - tmin) < 3600: tref = util.day_start(tmin) tref += math.floor((tmin-tref) / 3600.) * 3600. t -= tref t /= 60. tunit = 'min' else: tref = util.day_start(tmin) t -= tref t /= 3600. tunit = 'h' axes = [] for i, nslc in enumerate(nslcs): p = fig.add_subplot(nrows, ncols, i+1, sharex=p, sharey=p) axes.append(p) group = by_nslc[nslc] f = group[0][1] nf = f.size a = num.zeros((nf, nt), dtype=num.float) a.fill(num.nan) for (t1, _, a1) in group: it = int(round((t1 - tmin) / tinc)) if it < 0 or nt <= it: continue a[:, it] = a1 if self.color_scale == 'log': a = num.log(a) label = 'log PSD' elif self.color_scale == 'sqrt': a = num.sqrt(a) label = 'sqrt PSD' else: label = 'PSD' a = num.ma.masked_invalid(a) min_a = num.min(a) max_a = num.max(a) mean_a = num.mean(a) std_a = num.std(a) zmin = max(min_a, mean_a - 3.0 * std_a) zmax = min(max_a, mean_a + 3.0 * std_a) pcm = p.pcolormesh(t, f, a, cmap=get_cmap(self.ctb_name), vmin=zmin, vmax=zmax) fmin = 2.0 / self.twin fmax = f[-1] p.set_title( '.'.join(x for x in nslc if x), ha='right', va='top', x=0.99, y=0.9) p.grid() p.set_yscale('log') divider = make_axes_locatable(p) cax = divider.append_axes('right', size='2%', pad=0.2) cbar = fig.colorbar(pcm, cax=cax) cbar.set_label(label) if i/ncols == (len(nslcs)-1)/ncols: p.set_xlabel('Time since %s [%s]' % (util.time_to_str(tref, format='%Y-%m-%d %H:%M'), tunit)) if i % ncols == 0: p.set_ylabel('Frequency [Hz]') p.set_xlim(t[0], t[-1]) p.set_ylim(fmin, fmax) for i, p in enumerate(axes): if i/ncols != (len(nslcs)-1)/ncols: for t in p.get_xticklabels(): t.set_visible(False) if i % ncols != 0: for t in p.get_yticklabels(): t.set_visible(False) else: tls = p.get_yticklabels() if len(tls) > 8: for t in tls[1::2]: t.set_visible(False) try: fig.tight_layout() except AttributeError: pass if self.save: fig.savefig(self.output_filename(dir='psd.pdf')) fig.canvas.draw()
def make_time_line(self, events): if self.cli_mode: import matplotlib.pyplot as plt self.fig = plt.figure() else: fframe = self.figure_frame() self.fig = fframe.gcf() gs = gridspec.GridSpec(2, 1) gs.update(hspace=0.005, top=0.95) gs1 = gridspec.GridSpec(2, 1) gs1.update(bottom=0.06, hspace=0.01) ax = self.fig.add_subplot(gs[0]) ax1 = ax.twinx() ax2 = self.fig.add_subplot(gs1[-1]) events.sort(key=lambda x: x.time) magnitudes = [] cum_events = num.cumsum(num.ones(len(events))) for e in events: if e.moment_tensor is not None: magnitudes.append(e.moment_tensor.magnitude) else: magnitudes.append(e.magnitude) if magnitudes[-1] is None: magnitudes.pop() magnitudes.append(0.) magnitudes = moment_tensor.magnitude_to_moment(num.array(magnitudes)) cum_events_magnitude = num.cumsum(magnitudes) times = num.array([e.time for e in events]) timeslabels = [datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=t) for t in times] ax.plot(timeslabels, cum_events) ax.set_ylabel('Cumulative number of events') ax.axes.get_xaxis().set_ticklabels([]) ax1.plot(timeslabels, cum_events_magnitude, '-b') ax1.set_ylabel('Cumulative moment [Nm]') xfmt = md.DateFormatter('%Y-%m-%d') ax1.xaxis.set_major_formatter(xfmt) ax.yaxis.label.set_color('r') ax1.yaxis.label.set_color('b') ax.grid(True) ax1.grid(True) t0 = min(times) if self.variation == 'daily': normalization = 60 * 60 nbins = 24 t0_day = util.day_start(t0) ax2.set_xlabel('hour of day') elif self.variation == 'annual': normalization = 60 * 60 * 24 nbins = 365 t0_day = util.year_start(t0) ax2.set_xlabel('day of year') binned = (times - t0_day) % (nbins * normalization) ax2.hist(binned/normalization, bins=nbins, color='grey') ax2.set_ylabel('Number of events') ax2.set_xlim((0, nbins)) if self.cli_mode: plt.show() else: self.fig.canvas.draw()
def analyse_gps_tags(header, gps_tags, offset, nsamples): ipos, t, fix, nsvs = gps_tags deltat = 1.0 / int(header['S_RATE']) tquartz = offset + ipos * deltat toff = t - tquartz toff_median = num.median(toff) n = t.size dtdt = (t[1:n] - t[0:n-1]) / (tquartz[1:n] - tquartz[0:n-1]) ok = abs(toff_median - toff) < 10. xok = num.abs(dtdt - 1.0) < 0.00001 ok[0] = False ok[1:n] &= xok ok[0:n-1] &= xok ok[n-1] = False ipos = ipos[ok] t = t[ok] fix = fix[ok] nsvs = nsvs[ok] blocksize = N_GPS_TAGS_WANTED // 2 try: if ipos.size < blocksize: raise ControlPointError( 'could not safely determine time corrections from gps') j = 0 control_points = [] tref = num.median(t - ipos*deltat) while j < ipos.size - blocksize: ipos_block = ipos[j:j+blocksize] t_block = t[j:j+blocksize] try: ic, tc = make_control_point(ipos_block, t_block, tref, deltat) control_points.append((ic, tc)) except ControlPointError: pass j += blocksize ipos_last = ipos[-blocksize:] t_last = t[-blocksize:] try: ic, tc = make_control_point(ipos_last, t_last, tref, deltat) control_points.append((ic, tc)) except ControlPointError: pass if len(control_points) < 2: raise ControlPointError( 'could not safely determine time corrections from gps') i0, t0 = control_points[0] i1, t1 = control_points[1] i2, t2 = control_points[-2] i3, t3 = control_points[-1] if len(control_points) == 2: tmin = t0 - i0 * deltat - offset * deltat tmax = t3 + (nsamples - i3 - 1) * deltat else: icontrol = num.array( [x[0] for x in control_points], dtype=num.int64) tcontrol = num.array( [x[1] for x in control_points], dtype=num.float) # robust against steps: slope = num.median( (tcontrol[1:] - tcontrol[:-1]) / (icontrol[1:] - icontrol[:-1])) tmin = t0 + (offset - i0) * slope tmax = t2 + (offset + nsamples - 1 - i2) * slope if offset < i0: control_points[0:0] = [(offset, tmin)] if offset + nsamples - 1 > i3: control_points.append((offset + nsamples - 1, tmax)) icontrol = num.array([x[0] for x in control_points], dtype=num.int64) tcontrol = num.array([x[1] for x in control_points], dtype=num.float) return tmin, tmax, icontrol, tcontrol, ok except ControlPointError: tmin = util.str_to_time(header['S_DATE'] + header['S_TIME'], format='%y/%m/%d%H:%M:%S') idat = int(header['DAT_NO']) if idat == 0: tmin = tmin + util.gps_utc_offset(tmin) else: tmin = util.day_start(tmin + idat * 24.*3600.) \ + util.gps_utc_offset(tmin) tmax = tmin + (nsamples - 1) * deltat icontrol, tcontrol = None, None return tmin, tmax, icontrol, tcontrol, None
def call(self): '''Main work routine of the snuffling.''' by_nslc = {} tpad = self.twin * self.overlap/100. * 0.5 tinc = self.twin - 2 * tpad times = [] for traces in self.chopper_selected_traces( tinc=tinc, tpad=tpad, want_incomplete=False, fallback=True): for tr in traces: nslc = tr.nslc_id nwant = int(math.floor((tinc + 2*tpad) / tr.deltat)) if nwant != tr.data_len(): if tr.data_len() == nwant + 1: tr.set_ydata(tr.get_ydata()[:-1]) else: continue tr.ydata = tr.ydata.astype(num.float) tr.ydata -= tr.ydata.mean() win = self.get_taper(self.taper_name, tr.data_len()) tr.ydata *= win f, a = tr.spectrum(pad_to_pow2=True) df = f[1] - f[0] a = num.abs(a)**2 a *= tr.deltat * 2. / (df*num.sum(win**2)) a[0] /= 2. a[a.size/2] /= 2. if nslc not in by_nslc: by_nslc[nslc] = [] tmid = 0.5*(tr.tmax + tr.tmin) by_nslc[nslc].append((tmid, f, a)) times.append(tmid) if not by_nslc: self.fail('No complete data windows could be exctracted for ' 'given selection') fframe = self.figure_frame() fig = fframe.gcf() nslcs = sorted(by_nslc.keys()) p = None ncols = len(nslcs) / 5 + 1 nrows = (len(nslcs)-1) / ncols + 1 tmin = min(times) tmax = max(times) nt = int(round((tmax - tmin) / tinc)) + 1 t = num.linspace(tmin, tmax, nt) if (tmax - tmin) < 60: tref = util.day_start(tmin) tref += math.floor((tmin-tref) / 60.) * 60. t -= tref tunit = 's' elif (tmax - tmin) < 3600: tref = util.day_start(tmin) tref += math.floor((tmin-tref) / 3600.) * 3600. t -= tref t /= 60. tunit = 'min' else: tref = util.day_start(tmin) t -= tref t /= 3600. tunit = 'h' axes = [] for i, nslc in enumerate(nslcs): p = fig.add_subplot(nrows, ncols, i+1, sharex=p, sharey=p) axes.append(p) group = by_nslc[nslc] f = group[0][1] nf = f.size a = num.zeros((nf, nt), dtype=num.float) a.fill(num.nan) for (t1, _, a1) in group: it = int(round((t1 - tmin) / tinc)) if it < 0 or nt <= it: continue a[:, it] = a1 if self.color_scale == 'log': a = num.log(a) label = 'log PSD' elif self.color_scale == 'sqrt': a = num.sqrt(a) label = 'sqrt PSD' else: label = 'PSD' a = num.ma.masked_invalid(a) min_a = num.min(a) max_a = num.max(a) mean_a = num.mean(a) std_a = num.std(a) zmin = max(min_a, mean_a - 3.0 * std_a) zmax = min(max_a, mean_a + 3.0 * std_a) pcm = p.pcolormesh(t, f, a, cmap=get_cmap(self.ctb_name), vmin=zmin, vmax=zmax) fmin = 2.0 / self.twin fmax = f[-1] p.set_title( '.'.join(x for x in nslc if x), ha='right', va='top', x=0.99, y=0.9) p.grid() p.set_yscale('log') divider = make_axes_locatable(p) cax = divider.append_axes('right', size='2%', pad=0.2) cbar = fig.colorbar(pcm, cax=cax) cbar.set_label(label) if i/ncols == (len(nslcs)-1)/ncols: p.set_xlabel('Time since %s [%s]' % (util.time_to_str(tref, format='%Y-%m-%d %H:%M'), tunit)) if i % ncols == 0: p.set_ylabel('Frequency [Hz]') p.set_xlim(t[0], t[-1]) p.set_ylim(fmin, fmax) for i, p in enumerate(axes): if i/ncols != (len(nslcs)-1)/ncols: for t in p.get_xticklabels(): t.set_visible(False) if i % ncols != 0: for t in p.get_yticklabels(): t.set_visible(False) else: tls = p.get_yticklabels() if len(tls) > 8: for t in tls[1::2]: t.set_visible(False) try: fig.tight_layout() except AttributeError: pass if self.save: fig.savefig(self.output_filename(dir='psd.pdf')) fig.canvas.draw()
# Download example data get_example_data('data_conversion', recursive=True) input_path = 'data_conversion/mseed' output_path = 'data_conversion/sac/' \ '%(dirhz)s/%(station)s_%(channel)s_%(tmin)s.sac' fn_stations = 'data_conversion/stations.txt' stations_list = model.load_stations(fn_stations) stations = {} for s in stations_list: stations[s.network, s.station, s.location] = s s.set_channels_by_name(*'BHN BHE BHZ BLN BLE BLZ'.split()) p = pile.make_pile(input_path) h = 3600. tinc = 1 * h tmin = util.day_start(p.tmin) for traces in p.chopper_grouped(tmin=tmin, tinc=tinc, gather=lambda tr: tr.nslc_id): for tr in traces: dirhz = '%ihz' % int(round(1. / tr.deltat)) io.save([tr], output_path, format='sac', additional={'dirhz': dirhz}, stations=stations)
def call(self): '''Main work routine of the snuffling.''' self.cleanup() viewer = self.get_viewer() figs = {} fig_width_inch = viewer.width() npixel_hori = float(fig_width_inch*50) xminutes = int(self.xminutes) xseconds = xminutes * 60 self.nhours = 24 nrows = int(self.nhours) * 60 / xminutes ynormalizations = {} lines_data = {} for traces in self.chopper_selected_traces(tinc=60*60, fallback=True): for tr in traces: t0 = util.day_start(tr.tmin) key = (tr.nslc_id, t0) if key not in figs: fig = self.pylab(get='figure') ax = fig.add_subplot(111) figs[key] = (fig, ax) ynormalizations[key] = 0 lines_data[key] = [] tr = tr.copy(data=True) ndecimate = int((xseconds/tr.deltat) / npixel_hori) tr.downsample(ndecimate) if self.prescale == 'max': ynormalizations[key] = max(num.max(tr.ydata), ynormalizations[key]) else: ynormalizations[key] = max(num.std(tr.ydata), ynormalizations[key]) if viewer.highpass: tr.highpass(4, viewer.highpass) if viewer.lowpass and 1./tr.deltat>2.*viewer.lowpass: tr.lowpass(4, viewer.lowpass) t = tr.get_xdata() - t0 y = num.asarray(tr.get_ydata(), dtype=num.float) nskip = t / 3600. x = t % xseconds xdiff = num.diff(x) itmp = num.where(num.logical_or(xdiff < 0, num.abs(xdiff-tr.deltat) > 1E-4))[0] indices = num.zeros(len(itmp)+2, dtype=num.int) indices[1:-1] = itmp indices[-1] = len(y)-1 for i in range(len(indices)-1): istart = indices[i] + 1 istop = indices[i+1] lines_data[key].append( (t0, x[istart: istop], y[istart: istop], nskip[istart: istop]) ) ynorm = None if self.scale_global: ynorm = max(ynormalizations.values()) for key, lines in lines_data.items(): if not self.scale_global: ynorm = float(ynormalizations.get(key, 1.)) for (t0, x, y, shifts) in lines: fig, ax = figs[key] ax.plot( x/60., y/(ynorm/self.yscale) + shifts, color='black') ax.set_title(util.tts(t0, format='%Y-%m-%d')) yticks = range(0, self.nhours+2, 2) xticks = range(0, xminutes+1, 1) for key, (fig, ax) in figs.items(): ax.set_xlim(0, xminutes) ax.set_ylabel('Hour') ax.set_xlabel('Minute') ax.yaxis.set_ticks(yticks) ax.xaxis.set_ticks(xticks) ax.set_ylim(-0.1, 24.1) fig.canvas.draw()
# Download example data get_example_data('data_conversion', recursive=True) input_path = 'data_conversion/mseed' output_path = 'data_conversion/sac/' \ '%(dirhz)s/%(station)s_%(channel)s_%(tmin)s.sac' fn_stations = 'data_conversion/stations.txt' stations_list = model.load_stations(fn_stations) stations = {} for s in stations_list: stations[s.network, s.station, s.location] = s s.set_channels_by_name(*'BHN BHE BHZ BLN BLE BLZ'.split()) p = pile.make_pile(input_path) h = 3600. tinc = 1*h tmin = util.day_start(p.tmin) for traces in p.chopper_grouped(tmin=tmin, tinc=tinc, gather=lambda tr: tr.nslc_id): for tr in traces: dirhz = '%ihz' % int(round(1./tr.deltat)) io.save( [tr], output_path, format='sac', additional={'dirhz': dirhz}, stations=stations )