def decimate(self,decimation_factor,outfile,taper_width=0.005): """ Decimate the wavefield and save to a new file """ fs_old = self.stats['Fs'] freq = self.stats['Fs'] * 0.4 / float(decimation_factor) # Get filter coeff sos = filter.cheby2_lowpass(fs_old,freq) # figure out new length temp_trace = integer_decimation(self.data[0,:], decimation_factor) n = len(temp_trace) # Get taper # The default taper is very narrow, because it is expected that the traces are very long. taper = cosine_taper(self.stats['nt'],p=taper_width) # Need a new file, because the length changes. with self.copy_setup(newfile=outfile,nt=n) as newfile: for i in range(self.stats['ntraces']): temp_trace = sosfilt(sos,taper*self.data[i,:]) newfile.data[i,:] = integer_decimation(temp_trace, decimation_factor) newfile.stats['Fs'] = fs_old / float(decimation_factor)
def test_sensitivity_kernel(): class args(object): def __init__(self): self.source_model = os.path.join('test', 'testdata_v1', 'testsource_v1') self.step = 0 self.steplengthrun = False, self.ignore_network = True args = args() all_config = config_params(args, comm, size, rank) ns = get_ns(all_config) p = define_kernel_tasks(all_config, comm, size, rank) assert len(p[0]) == 3 assert p[0][2][1].split()[-1] == 'STA2' input_files = input_files_kernel(p[0][1], all_config) nsrc = os.path.join('test', 'testdata_v1', 'testsource_v1', 'spectral_model.h5') output_file = "test" # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off wherever the solver stopped running. taper = cosine_taper(ns[0], p=0.01) taper[0:ns[0] // 2] = 1.0 kernel = compute_kernel(input_files[0], output_file, all_config, NoiseSource(nsrc), ns, taper) np.save("newtestkernel.npy", kernel) saved_kernel = np.load( os.path.join('test', 'testdata_v1', 'testdata', 'NET.STA1..MXZ--NET.STA2..MXZ.0.npy')) assert np.allclose(kernel / kernel.max(), saved_kernel / saved_kernel.max())
def corrCodas(a, b, shortWin, overlap, df=20, corr_method='crosscoherence', stack_method='linear'): """Moving window correlation function :rtype: numpy.array :param a, b: the codas to be correlated :type a, b: numpy.array :param shortWin: short time window for moving wind :type shortWin: int, float :param step: step to iterate (i.e., for overlap) :type step: int :rparam: the correlated function of lenght equal to shortWin*2-1. """ N = len(a) c = np.zeros(shortWin*2-1) cp = cosine_taper(shortWin, 0.1) data = np.zeros(len(c)) for i in xrange(0, N-shortWin, overlap): winA = a[i: i+shortWin] winB = b[i: i+shortWin] cp *= winA; cp*=winB if corr_method == 'crosscoherence': cc = crosscoherence(winB, winA) elif corr_method == 'correlation': cc = correlation(winB, winA) elif corr_method == 'convolution': cc = convolution(winB, winA) data = np.vstack((data,cc)) corr = stack(data, stack_method=stack_method, df=df) return corr#/np.max(np.abs(c))
def decimate(self, decimation_factor, outfile, taper_width=0.005): """ Decimate the wavefield and save to a new file """ fs_old = self.stats['Fs'] freq = self.stats['Fs'] * 0.4 / float(decimation_factor) # Get filter coeff sos = filter.cheby2_lowpass(fs_old, freq) # figure out new length temp_trace = integer_decimation(self.data[0, :], decimation_factor) n = len(temp_trace) # Get taper # The default taper is very narrow, because it is expected that the traces are very long. taper = cosine_taper(self.stats['nt'], p=taper_width) # Need a new file, because the length changes. with self.copy_setup(newfile=outfile, nt=n) as newfile: for i in range(self.stats['ntraces']): temp_trace = sosfilt(sos, taper * self.data[i, :]) newfile.data[i, :] = integer_decimation( temp_trace, decimation_factor) newfile.stats['Fs'] = fs_old / float(decimation_factor)
def check_and_phase_shift(trace): # print trace taper_length = 20.0 if trace.stats.npts < 4 * taper_length*trace.stats.sampling_rate: trace.data = np.zeros(trace.stats.npts) return trace dt = np.mod(trace.stats.starttime.datetime.microsecond*1.0e-6, trace.stats.delta) if (trace.stats.delta - dt) <= np.finfo(float).eps: dt = 0 if dt != 0: if dt <= (trace.stats.delta / 2.): dt = -dt # direction = "left" else: dt = (trace.stats.delta - dt) # direction = "right" trace.detrend(type="demean") trace.detrend(type="simple") taper_1s = taper_length * float(trace.stats.sampling_rate) / trace.stats.npts cp = cosine_taper(trace.stats.npts, taper_1s) trace.data *= cp n = int(2**nextpow2(len(trace.data))) FFTdata = scipy.fftpack.fft(trace.data, n=n) fftfreq = scipy.fftpack.fftfreq(n, d=trace.stats.delta) FFTdata = FFTdata * np.exp(1j * 2. * np.pi * fftfreq * dt) trace.data = np.real(scipy.fftpack.ifft(FFTdata, n=n)[:len(trace.data)]) trace.stats.starttime += dt return trace else: return trace
def test_cosineTaper(self): # SAC trace was generated with: # taper type cosine width 0.05 for i in [99, 100]: sac_taper = os.path.join(self.path, "ones_trace_%d_tapered.sac" % i) tr = read(sac_taper)[0] tap = cosine_taper(i, p=0.1, halfcosine=False, sactaper=True) np.testing.assert_array_almost_equal(tap, tr.data, decimal=6)
def tapering(st_aux): [ch, samp] = st_aux.shape try: cosVal = inv.cosTaper(int(samp), p=0.1) except: cosVal = inv.cosine_taper(int(samp), p=0.1) for ii in range(ch): st_aux[ii, :] = st_aux[ii, :] * cosVal return st_aux
def fft_taper(data): """ Cosine taper, 10 percent at each end (like done by [McNamara2004]_). .. warning:: Inplace operation, so data should be float. """ data *= cosine_taper(len(data), 0.2) return data
def _fft_taper(data): """ Cosine taper, 10 percent at each end (like done by [McNamara2004]_). .. warning:: Inplace operation, so data should be float. """ data *= cosine_taper(len(data), 0.2) return data
def test_cosine_taper(self): # SAC trace was generated with: # taper type cosine width 0.05 for i in [99, 100]: sac_taper = os.path.join(self.path, 'ones_trace_%d_tapered.sac' % i) tr = read(sac_taper)[0] tap = cosine_taper(i, p=0.1, halfcosine=False, sactaper=True) np.testing.assert_array_almost_equal(tap, tr.data, decimal=6)
def __call__(self, tr): try: if tr.stats.npts > 1: tr.data *= cosine_taper(tr.stats.npts, 0.01) FFT = fft(tr.data) tr.data = ifft(FFT / abs(FFT)).real except ValueError: print "ERROR" print tr.stats.npts return tr
def get_ph_misfit(period,freqmin,freqmax,stacode1,stacode2,vel,indir='/work3/wang/JdF/Denoised_Stack'): """ Calculate phase misfit between the positive and negative lags of a given cross-correlogram """ xcorr_file = indir+'/'+stacode1+'/'+'COR_'+stacode1+'_'+stacode2+'.SAC' if not os.path.isfile(xcorr_file): return 9999.9; tr = obspy.core.read(xcorr_file)[0] npts = tr.stats.sac.npts delta = tr.stats.sac.delta dist = tr.stats.sac.dist L = int((npts-1)/2)+1 arrival = dist/vel data_neg = tr.data[:L] data_neg = data_neg[::-1] data_pos = tr.data[L-1:] #butter_b, butter_a = scipy.signal.butter(4,[freqmin*2*delta,freqmax*2*delta],btype='band') #data_pos = scipy.signal.lfilter(butter_b,butter_a,data_pos) #data_neg = scipy.signal.lfilter(butter_b,butter_a,data_neg) ind0 = max(0, int((arrival-1*period)/delta)) ind1 = min(L, int((arrival+1*period)/delta)) window_length = ind1 - ind0 taper = cosine_taper(window_length,0.85) ccp = data_pos[ind0:ind1] ccp = scipy.signal.detrend(ccp, type='linear') ccp *= taper ccn = data_neg[ind0:ind1] ccn = scipy.signal.detrend(ccn, type='linear') ccn *= taper ns = 1<<(ind1-ind0).bit_length() fpos = scipy.fftpack.fft(ccp, n=ns)[:ns // 2] fneg = scipy.fftpack.fft(ccn, n=ns)[:ns // 2] fpos2 = np.real(fpos) ** 2 + np.imag(fpos) ** 2 fneg2 = np.real(fneg) ** 2 + np.imag(fneg) ** 2 X = fpos*(fneg.conj()) dpos = np.sqrt(fpos2) dneg = np.sqrt(fneg2) dcs = np.abs(X) freq_vec = scipy.fftpack.fftfreq(len(X)*2, delta)[:ns // 2] index_range = np.argwhere(np.logical_and(freq_vec>=freqmin, freq_vec<=freqmax)) n = len(dcs) coh = np.zeros(n).astype('complex') valids = np.argwhere(np.logical_and(np.abs(dpos)>0,np.abs(dneg)>0)) coh[valids] = dcs[valids] / (dpos[valids] * dneg[valids]) coh[coh > (1.+0j)] = 1.0+0j w = 1./(1./(coh[index_range] ** 2)-1.) w[coh[index_range] >= 0.99] = 1./ (1./0.9801 - 1.) w = np.sqrt(w*np.sqrt(dcs[index_range])) w = np.real(w) v = np.real(freq_vec[index_range])*2*np.pi phi = np.angle(X) phi[0] = 0. phi = np.unwrap(phi) phi = phi[index_range] m,_ = linear_regression(v.flatten(),phi.flatten(),w.flatten()) return m
def spectrum_from_parameters(self, freq, parameters): mu = parameters['mean_frequency_Hz'] sig = parameters['standard_deviation_Hz'] taper = cosine_taper(len(freq), parameters['taper_percent'] / 100.) spec = taper * np.exp(-((freq - mu)**2) / (2 * sig**2)) if not parameters['normalize_spectrum_to_unity']: spec = spec / (sig * sqrt(2. * pi)) return (spec)
def run_kern(args, comm, size, rank): args.steplengthrun = False # by default all_conf = config_params(args, comm, size, rank) kernel_tasks, n_p_p, n_p = define_kernel_tasks(all_conf, comm, size, rank) if len(kernel_tasks) == 0: return () if all_conf.config['verbose']: print('Rank number %g' % rank) print('working on pair nr. %g to %g of %g.' % (rank * n_p_p, rank * n_p_p + n_p_p, n_p)) # Current model for the noise source nsrc = os.path.join(all_conf.source_config['project_path'], all_conf.source_config['source_name'], 'spectral_model.h5') # Smart numbers all_ns = get_ns(all_conf) # ntime, n, n_corr, Fs # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off wherever the solver stopped running. taper = cosine_taper(all_ns[0], p=0.01) taper[0:all_ns[0] // 2] = 1.0 with NoiseSource(nsrc) as nsrc: for kp in kernel_tasks: try: input_files = add_input_files(kp, all_conf) output_file = add_output_file(kp, all_conf) except (IOError, IndexError): if all_conf.config['verbose']: print('Could not find input for: %s\ \nCheck if wavefield .h5 file and base_model file are available.' % kp) continue kern = compute_kernel(input_files, all_conf, nsrc, all_ns, taper) if kern is None: continue for ix_f in range(all_conf.filtcnt): if kern[:, ix_f, :, :].sum() != 0: filename = output_file + '.{}.npy'.format(ix_f) np.save(filename, kern[:, ix_f, :, :]) return ()
def filter(self): """ Filter data for each band. """ n_bands = self._N_bands() LEN = self.tr.stats.npts df = self.tr.stats.sampling_rate # create zeros 2D array for BF BF = np.zeros(shape=(n_bands, LEN)) for j in range(n_bands): octave_high = (self.freqmin + self.freqmin * 2.0) / 2.0 * (2 ** j) octave_low = octave_high / 2.0 BF[j] = bandpass(self.tr.data, octave_low, octave_high, df, corners=self.cnr, zerophase=False) BF[j] = cosine_taper(LEN, self.perc_taper) * BF[j] return BF
def source(t,sourcetype='random'): """ returns the frequency domain representation of the source time function. It is possible to introduce also other types of source, for example a wavelet. """ n = len(t) # damp=0.05 if sourcetype=='random': signal = (np.random.rand(n)-0.5) # signal*= t**0.3*exp(-damp*t) signal*= cosine_taper(len(signal),0.01) elif sourcetype=='spike': signal = np.zeros(len(t)) signal[0] = 1. else: raise Exception("Did not understand the source type. Choose either 'random' or 'spike'.") return np.fft.rfft(signal)
def test_stretching_vect(): t = np.linspace(0., 9.95, 2500) # 0.5 % perturbation original_signal = np.sin(t * 10.) * cosine_taper(2500, p=0.75) t_stretch = np.linspace(0., 10.0, 2500) stretched_signal = np.interp(t, t_stretch, original_signal) para = {} para["dt"] = 1. / 250. para["twin"] = [0., 10.] para["freq"] = [9.9, 10.1] dvv, error, cc, cdp = stretching_vect(ref=original_signal, cur=stretched_signal, dv_range=0.05, nbtrial=100, para=para) assert pytest.approx(cc) == 1.0 assert dvv + 0.5 < para["dt"] # assert result is -0.5%
def run_corr(args, comm, size, rank): all_conf = config_params(args, comm, size, rank) # Distributing the tasks correlation_tasks, n_p_p, n_p = define_correlation_tasks( all_conf, comm, size, rank) if len(correlation_tasks) == 0: return () if all_conf.config['verbose']: print('Rank number %g' % rank) print('working on pair nr. %g to %g of %g.' % (rank * n_p_p, rank * n_p_p + n_p_p, n_p)) # Current model for the noise source nsrc = os.path.join(all_conf.source_config['project_path'], all_conf.source_config['source_name'], 'iteration_' + str(all_conf.step), 'starting_model.h5') # Smart numbers all_ns = get_ns(all_conf) # ntime, n, n_corr, Fs # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off wherever the solver stopped running. taper = cosine_taper(all_ns[0], p=0.01) taper[0:all_ns[0] // 2] = 1.0 with NoiseSource(nsrc) as nsrc: for cp in correlation_tasks: try: input_files = add_input_files(cp, all_conf) output_file = add_output_file(cp, all_conf) except (IndexError, FileNotFoundError): if all_conf.config['verbose']: print('Could not determine correlation for: %s\ \nCheck if wavefield .h5 file is available.' % cp) continue correlation, sta1, sta2 = compute_correlation( input_files, all_conf, nsrc, all_ns, taper) add_metadata_and_write(correlation, sta1, sta2, output_file, all_ns[3]) return ()
def stream2mult_array(str,wlen,overlap): """ turn obspy stream into array of short 2D arrays the data from a stream is extracted, sliced, and copied to 2D numpy arrays - ** parameters**, **types**, **return**, and **return types**:: :param str: stream to be converted :param wlen: length of short arrays :param overlap: length of overlap between short arrays :type str: obspy stream :type wlen: length of windows in seconds :type overlap: length of overlap in seconds :return: 2D array with time series for all channels :rtype: numpy array :return: array of times of the beginning of the windows :rtype: list of UTCDateTime :return: array of window lengths :rtype: list of seconds """ arr=stream2array(str) sps=str[0].stats.sampling_rate npts=str[0].stats.npts timeI=str[0].stats.starttime wlen_sam=sps*wlen overlap_sam=sps*overlap curr=0 mat=[] time=[] lenA=[] try: cosVal=inv.cosTaper(int(wlen_sam),p=0.1) except: cosVal=inv.cosine_taper(int(wlen_sam),p=0.1) while curr+wlen_sam<=npts: mat.append(arr[:,int(curr):int(curr+wlen_sam)]*cosVal) time.append(UTCDateTime(float(timeI)+float(curr)/sps)) lenA.append(len(arr[0,int(curr):int(curr+wlen_sam)])) curr=curr+wlen_sam-overlap_sam mat=np.asarray(mat) return mat,time,lenA
def spect_norm(x): """ Computes the spectral normalization of 1D numpy array x This function divides the amplitude of the x spectrum by its absolute value :type x: :class:`~numpy.ndarray` :param x: 1d array :rtype: :class:`~numpy.ndarray` :return: **x_copy**: Whitened version of x """ x_copy = x.copy() x_copy *= cosine_taper(len(x_copy), 0.01) FFT = fft(x_copy) x_copy = ifft(FFT / abs(FFT)).real return x_copy
def test_forward_model(): class args(object): def __init__(self): self.source_model = os.path.join('test', 'testdata_v1', 'testsource_v1') self.step = 0 self.steplengthrun = False, self.ignore_network = True args = args() all_config = config_params(args, comm, size, rank) assert all_config.auto_corr ns = get_ns(all_config) assert (ns[0] == 3600) assert (ns[1] == 7200) p = define_correlationpairs(all_config.source_config['project_path'], all_config.auto_corr) assert len(p) == 3 assert p[0][0].split()[-1] == 'STA1' input_files = add_input_files(p[1], all_config)[0] assert os.path.basename(input_files[0]) == 'NET.STA1..MXZ.h5' nsrc = os.path.join('test', 'testdata_v1', 'testsource_v1', 'iteration_0', 'starting_model.h5') # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off wherever the solver stopped running. taper = cosine_taper(ns[0], p=0.01) taper[0:ns[0] // 2] = 1.0 correlation, sta1, sta2 = compute_correlation(input_files, all_config, NoiseSource(nsrc), ns, taper) corr_saved = np.load( os.path.join('test', 'testdata_v1', 'testdata', 'NET.STA1..MXZ--NET.STA2..MXZ.npy')) assert np.allclose(correlation, corr_saved)
def calc_fourier_window(stream, offset=0, taper = None, taper_param = 4, raw=False, prewhiten = False): spoint = np.zeros(len(stream)) nsamp = len(stream[0]) nfft = next_pow_2(nsamp) nlow = 1 nhigh = nfft // 2 - 1 nf = nhigh - nlow + 1 # include upper and lower frequency freqList = np.arange(nlow, nlow+nf)/(nfft * stream[0].stats.delta) if (taper is None) or (taper.lower() == 'tukey'): taper = cosine_taper(nsamp, p=0.22) ft = np.empty((len(stream), nf), dtype=np.complex128) for i, tr in enumerate(stream): dat = tr.data[int(spoint[i] + offset):int(spoint[i] + offset + nsamp)] if (type(taper) is str) and (taper.lower() == 'multitaper'): ft[i, :] = mtspec(dat, stream[0].stats.delta, time_bandwidth = taper_param, nfft=nfft)[nlow:nlow + nf] else: if not raw: dat = scipy.signal.detrend(dat) * taper ft[i, :] = np.fft.rfft(dat, nfft)[nlow:nlow + nf] ft *= np.sqrt(2*stream[0].stats.delta / nsamp) # one-sided spectrum with physical amplitude units if prewhiten: eps = np.min(np.abs(ft)[ft != 0]) ft /= np.abs(ft) + eps # apply a tiny waterlevel to avoid divide-by-zero return ft, freqList
def mwcs(current, reference, freqmin, freqmax, df, tmin, window_length, step, smoothing_half_win=5): """The `current` time series is compared to the `reference`. Both time series are sliced in several overlapping windows. Each slice is mean-adjusted and cosine-tapered (85% taper) before being Fourier- transformed to the frequency domain. :math:`F_{cur}(\\nu)` and :math:`F_{ref}(\\nu)` are the first halves of the Hermitian symmetric Fourier-transformed segments. The cross-spectrum :math:`X(\\nu)` is defined as :math:`X(\\nu) = F_{ref}(\\nu) F_{cur}^*(\\nu)` in which :math:`{}^*` denotes the complex conjugation. :math:`X(\\nu)` is then smoothed by convolution with a Hanning window. The similarity of the two time-series is assessed using the cross-coherency between energy densities in the frequency domain: :math:`C(\\nu) = \\frac{|\overline{X(\\nu))}|}{\sqrt{|\overline{F_{ref}(\\nu)|^2} |\overline{F_{cur}(\\nu)|^2}}}` in which the over-line here represents the smoothing of the energy spectra for :math:`F_{ref}` and :math:`F_{cur}` and of the spectrum of :math:`X`. The mean coherence for the segment is defined as the mean of :math:`C(\\nu)` in the frequency range of interest. The time-delay between the two cross correlations is found in the unwrapped phase, :math:`\phi(\nu)`, of the cross spectrum and is linearly proportional to frequency: :math:`\phi_j = m. \nu_j, m = 2 \pi \delta t` The time shift for each window between two signals is the slope :math:`m` of a weighted linear regression of the samples within the frequency band of interest. The weights are those introduced by [Clarke2011]_, which incorporate both the cross-spectral amplitude and cross-coherence, unlike [Poupinet1984]_. The errors are estimated using the weights (thus the coherence) and the squared misfit to the modelled slope: :math:`e_m = \sqrt{\sum_j{(\\frac{w_j \\nu_j}{\sum_i{w_i \\nu_i^2}})^2}\sigma_{\phi}^2}` where :math:`w` are weights, :math:`\\nu` are cross-coherences and :math:`\sigma_{\phi}^2` is the squared misfit of the data to the modelled slope and is calculated as :math:`\sigma_{\phi}^2 = \\frac{\sum_j(\phi_j - m \\nu_j)^2}{N-1}` The output of this process is a table containing, for each moving window: the central time lag, the measured delay, its error and the mean coherence of the segment. .. warning:: The time series will not be filtered before computing the cross-spectrum! They should be band-pass filtered around the `freqmin`-`freqmax` band of interest beforehand. :type current: :class:`numpy.ndarray` :param current: The "Current" timeseries :type reference: :class:`numpy.ndarray` :param reference: The "Reference" timeseries :type freqmin: float :param freqmin: The lower frequency bound to compute the dephasing (in Hz) :type freqmax: float :param freqmax: The higher frequency bound to compute the dephasing (in Hz) :type df: float :param df: The sampling rate of the input timeseries (in Hz) :type tmin: float :param tmin: The leftmost time lag (used to compute the "time lags array") :type window_length: float :param window_length: The moving window length (in seconds) :type step: float :param step: The step to jump for the moving window (in seconds) :type smoothing_half_win: int :param smoothing_half_win: If different from 0, defines the half length of the smoothing hanning window. :rtype: :class:`numpy.ndarray` :returns: [time_axis,delta_t,delta_err,delta_mcoh]. time_axis contains the central times of the windows. The three other columns contain dt, error and mean coherence for each window. """ delta_t = [] delta_err = [] delta_mcoh = [] time_axis = [] window_length_samples = np.int(window_length * df) # try: # from scipy.fftpack.helper import next_fast_len # except ImportError: # from obspy.signal.util import next_pow_2 as next_fast_len from msnoise.api import nextpow2 padd = int(2**(nextpow2(window_length_samples) + 2)) # padd = next_fast_len(window_length_samples) count = 0 tp = cosine_taper(window_length_samples, 0.85) minind = 0 maxind = window_length_samples while maxind <= len(current): cci = current[minind:(minind + window_length_samples)] cci = scipy.signal.detrend(cci, type='linear') cci *= tp cri = reference[minind:(minind + window_length_samples)] cri = scipy.signal.detrend(cri, type='linear') cri *= tp minind += int(step * df) maxind += int(step * df) fcur = scipy.fftpack.fft(cci, n=padd)[:padd // 2] fref = scipy.fftpack.fft(cri, n=padd)[:padd // 2] fcur2 = np.real(fcur)**2 + np.imag(fcur)**2 fref2 = np.real(fref)**2 + np.imag(fref)**2 # Calculate the cross-spectrum X = fref * (fcur.conj()) if smoothing_half_win != 0: dcur = np.sqrt( smooth(fcur2, window='hanning', half_win=smoothing_half_win)) dref = np.sqrt( smooth(fref2, window='hanning', half_win=smoothing_half_win)) X = smooth(X, window='hanning', half_win=smoothing_half_win) else: dcur = np.sqrt(fcur2) dref = np.sqrt(fref2) dcs = np.abs(X) # Find the values the frequency range of interest freq_vec = scipy.fftpack.fftfreq(len(X) * 2, 1. / df)[:padd // 2] index_range = np.argwhere( np.logical_and(freq_vec >= freqmin, freq_vec <= freqmax)) # Get Coherence and its mean value coh = getCoherence(dcs, dref, dcur) mcoh = np.mean(coh[index_range]) # Get Weights w = 1.0 / (1.0 / (coh[index_range]**2) - 1.0) w[coh[index_range] >= 0.99] = 1.0 / (1.0 / 0.9801 - 1.0) w = np.sqrt(w * np.sqrt(dcs[index_range])) w = np.real(w) # Frequency array: v = np.real(freq_vec[index_range]) * 2 * np.pi # Phase: phi = np.angle(X) phi[0] = 0. phi = np.unwrap(phi) phi = phi[index_range] # Calculate the slope with a weighted least square linear regression # forced through the origin # weights for the WLS must be the variance ! m, em = linear_regression(v.flatten(), phi.flatten(), w.flatten()) delta_t.append(m) # print phi.shape, v.shape, w.shape e = np.sum((phi - m * v)**2) / (np.size(v) - 1) s2x2 = np.sum(v**2 * w**2) sx2 = np.sum(w * v**2) e = np.sqrt(e * s2x2 / sx2**2) delta_err.append(e) delta_mcoh.append(np.real(mcoh)) time_axis.append(tmin + window_length / 2. + count * step) count += 1 del fcur, fref del X del freq_vec del index_range del w, v, e, s2x2, sx2, m, em if maxind > len(current) + step * df: logging.warning("The last window was too small, but was computed") return np.array([time_axis, delta_t, delta_err, delta_mcoh]).T
def array_processing(stream, win_len, win_frac, sll_x, slm_x, sll_y, slm_y, sl_s, semb_thres, vel_thres, frqlow, frqhigh, stime, etime, prewhiten, verbose=False, coordsys='lonlat', timestamp='mlabday', method=0, store=None): """ Method for Seismic-Array-Beamforming/FK-Analysis/Capon :param stream: Stream object, the trace.stats dict like class must contain an :class:`~obspy.core.util.attribdict.AttribDict` with 'latitude', 'longitude' (in degrees) and 'elevation' (in km), or 'x', 'y', 'elevation' (in km) items/attributes. See param ``coordsys``. :type win_len: float :param win_len: Sliding window length in seconds :type win_frac: float :param win_frac: Fraction of sliding window to use for step :type sll_x: float :param sll_x: slowness x min (lower) :type slm_x: float :param slm_x: slowness x max :type sll_y: float :param sll_y: slowness y min (lower) :type slm_y: float :param slm_y: slowness y max :type sl_s: float :param sl_s: slowness step :type semb_thres: float :param semb_thres: Threshold for semblance :type vel_thres: float :param vel_thres: Threshold for velocity :type frqlow: float :param frqlow: lower frequency for fk/capon :type frqhigh: float :param frqhigh: higher frequency for fk/capon :type stime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param stime: Start time of interest :type etime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param etime: End time of interest :type prewhiten: int :param prewhiten: Do prewhitening, values: 1 or 0 :param coordsys: valid values: 'lonlat' and 'xy', choose which stream attributes to use for coordinates :type timestamp: str :param timestamp: valid values: 'julsec' and 'mlabday'; 'julsec' returns the timestamp in seconds since 1970-01-01T00:00:00, 'mlabday' returns the timestamp in days (decimals represent hours, minutes and seconds) since '0001-01-01T00:00:00' as needed for matplotlib date plotting (see e.g. matplotlib's num2date) :type method: int :param method: the method to use 0 == bf, 1 == capon :type store: function :param store: A custom function which gets called on each iteration. It is called with the relative power map and the time offset as first and second arguments and the iteration number as third argument. Useful for storing or plotting the map for each iteration. For this purpose the dump function of this module can be used. :return: :class:`numpy.ndarray` of timestamp, relative relpow, absolute relpow, backazimuth, slowness """ res = [] eotr = True # check that sampling rates do not vary fs = stream[0].stats.sampling_rate if len(stream) != len(stream.select(sampling_rate=fs)): msg = 'in sonic sampling rates of traces in stream are not equal' raise ValueError(msg) grdpts_x = int(((slm_x - sll_x) / sl_s + 0.5) + 1) grdpts_y = int(((slm_y - sll_y) / sl_s + 0.5) + 1) geometry = get_geometry(stream, coordsys=coordsys, verbose=verbose) if verbose: print("geometry:") print(geometry) print("stream contains following traces:") print(stream) print("stime = " + str(stime) + ", etime = " + str(etime)) time_shift_table = get_timeshift(geometry, sll_x, sll_y, sl_s, grdpts_x, grdpts_y) # offset of arrays spoint, _epoint = get_spoint(stream, stime, etime) # # loop with a sliding window over the dat trace array and apply bbfk # nstat = len(stream) fs = stream[0].stats.sampling_rate nsamp = int(win_len * fs) nstep = int(nsamp * win_frac) # generate plan for rfftr nfft = next_pow_2(nsamp) deltaf = fs / float(nfft) nlow = int(frqlow / float(deltaf) + 0.5) nhigh = int(frqhigh / float(deltaf) + 0.5) nlow = max(1, nlow) # avoid using the offset nhigh = min(nfft // 2 - 1, nhigh) # avoid using nyquist nf = nhigh - nlow + 1 # include upper and lower frequency # to speed up the routine a bit we estimate all steering vectors in advance steer = np.empty((nf, grdpts_x, grdpts_y, nstat), dtype=np.complex128) clibsignal.calcSteer(nstat, grdpts_x, grdpts_y, nf, nlow, deltaf, time_shift_table, steer) _r = np.empty((nf, nstat, nstat), dtype=np.complex128) ft = np.empty((nstat, nf), dtype=np.complex128) newstart = stime # 0.22 matches 0.2 of historical C bbfk.c tap = cosine_taper(nsamp, p=0.22) offset = 0 relpow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64) abspow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64) while eotr: try: for i, tr in enumerate(stream): dat = tr.data[spoint[i] + offset:spoint[i] + offset + nsamp] dat = (dat - dat.mean()) * tap ft[i, :] = np.fft.rfft(dat, nfft)[nlow:nlow + nf] except IndexError: break ft = np.ascontiguousarray(ft, np.complex128) relpow_map.fill(0.) abspow_map.fill(0.) # computing the covariances of the signal at different receivers dpow = 0. for i in range(nstat): for j in range(i, nstat): _r[:, i, j] = ft[i, :] * ft[j, :].conj() if method == 1: _r[:, i, j] /= np.abs(_r[:, i, j].sum()) if i != j: _r[:, j, i] = _r[:, i, j].conjugate() else: dpow += np.abs(_r[:, i, j].sum()) dpow *= nstat if method == 1: # P(f) = 1/(e.H R(f)^-1 e) for n in range(nf): _r[n, :, :] = np.linalg.pinv(_r[n, :, :], rcond=1e-6) errcode = clibsignal.generalizedBeamformer(relpow_map, abspow_map, steer, _r, nstat, prewhiten, grdpts_x, grdpts_y, nf, dpow, method) if errcode != 0: msg = 'generalizedBeamforming exited with error %d' raise Exception(msg % errcode) ix, iy = np.unravel_index(relpow_map.argmax(), relpow_map.shape) relpow, abspow = relpow_map[ix, iy], abspow_map[ix, iy] if store is not None: store(relpow_map, abspow_map, offset) # here we compute baz, slow slow_x = sll_x + ix * sl_s slow_y = sll_y + iy * sl_s slow = np.sqrt(slow_x**2 + slow_y**2) if slow < 1e-8: slow = 1e-8 azimut = 180 * math.atan2(slow_x, slow_y) / math.pi baz = azimut % -360 + 180 if relpow > semb_thres and 1. / slow > vel_thres: res.append( np.array([newstart.timestamp, relpow, abspow, baz, slow])) if verbose: print(newstart, (newstart + (nsamp / fs)), res[-1][1:]) if (newstart + (nsamp + nstep) / fs) > etime: eotr = False offset += nstep newstart += nstep / fs res = np.array(res) if timestamp == 'julsec': pass elif timestamp == 'mlabday': # 719163 == days between 1970 and 0001 + 1 res[:, 0] = res[:, 0] / (24. * 3600) + 719163 else: msg = "Option timestamp must be one of 'julsec', or 'mlabday'" raise ValueError(msg) return np.array(res)
ext = '*.h5' wavefield_path = config['wavefield_path'] wfs = glob(os.path.join(wavefield_path, ext)) if wfs != []: print 'Found wavefield.' with WaveField(wfs[0]) as wf: df = wf.stats['Fs'] nt = wf.stats['nt'] # The number of points for the fft is larger due to zeropadding --> apparent higher frequency sampling\n", n = next_fast_len(2 * nt - 1) freq = np.fft.rfftfreq(n, d=1. / df) taper = cosine_taper(len(freq), 0.01) print 'Determined frequency axis.' def get_distance(grid, location): def f(lat, lon, location): return abs(gps2dist_azimuth(lat, lon, location[0], location[1])[0]) dist = np.array( [f(lat, lon, location) for lat, lon in zip(grid[1], grid[0])]) return dist # Use Basemap to figure out where ocean is def get_ocean_mask(): from mpl_toolkits.basemap import Basemap
stats.attrs['nt'] = int(new_npts) # DATASET NR 2: Source grid sources = f_out.create_dataset('sourcegrid', data=f_sources[0:2]) # DATASET Nr 3: Seismograms itself traces_h5 = f_out.create_dataset('data', (ntraces, new_npts), dtype=np.float32) # Define processing # half-sided tukey taper try: taper = tukey(nstep, 0.1) except NameError: taper = cosine_taper(nstep, 0.1) if process_taper['only_trace_end']: taper[:nstep // 2] = 1. # filter if process_filter['type'] == 'lowpass': sos = lowpass(freq=process_filter['freq_max'], df=Fs, corners=process_filter['corners']) elif process_filter['type'] == 'cheby2_lowpass': sos = cheby2_lowpass(freq=process_filter['freq_max'], df=Fs, maxorder=process_filter['max_order']) elif process_filter['type'] == 'bandpass': sos = bandpass(freqmin=process_filter['freq_min'],
def preprocess(db, stations, comps, goal_day, params, tramef_Z, tramef_E = np.array([]), tramef_N = np.array([])): datafilesZ = {} datafilesE = {} datafilesN = {} for station in stations: datafilesZ[station] = [] datafilesE[station] = [] datafilesN[station] = [] net, sta = station.split('.') gd = datetime.datetime.strptime(goal_day, '%Y-%m-%d') files = get_data_availability( db, net=net, sta=sta, starttime=gd, endtime=gd) for file in files: comp = file.comp fullpath = os.path.join(file.path, file.file) if comp[-1] == 'Z': datafilesZ[station].append(fullpath) elif comp[-1] == 'E': datafilesE[station].append(fullpath) elif comp[-1] == 'N': datafilesN[station].append(fullpath) j = 0 for istation, station in enumerate(stations): for comp in comps: files = eval("datafiles%s['%s']" % (comp, station)) if len(files) != 0: logging.debug("%s.%s Reading %i Files" % (station, comp, len(files))) stream = Stream() for file in sorted(files): st = read(file, dytpe=np.float, starttime=UTCDateTime(gd), endtime=UTCDateTime(gd)+86400) for tr in st: tr.data = tr.data.astype(np.float) stream += st del st logging.debug("Checking sample alignment") for i, trace in enumerate(stream): stream[i] = check_and_phase_shift(trace) stream.sort() logging.debug("Checking Gaps") if len(getGaps(stream)) > 0: max_gap = 10 only_too_long=False while getGaps(stream) and not only_too_long: too_long = 0 gaps = getGaps(stream) for gap in gaps: if int(gap[-1]) <= max_gap: stream[gap[0]] = stream[gap[0]].__add__(stream[gap[1]], method=0, fill_value="interpolate") stream.remove(stream[gap[1]]) break else: too_long += 1 if too_long == len(gaps): only_too_long = True taper_length = 20.0 #seconds for trace in stream: if trace.stats.npts < 4 * taper_length*trace.stats.sampling_rate: trace.data = np.zeros(trace.stats.npts) else: trace.detrend(type="demean") trace.detrend(type="linear") taper_1s = taper_length * float(trace.stats.sampling_rate) / trace.stats.npts cp = cosine_taper(trace.stats.npts, taper_1s) trace.data *= cp try: stream.merge(method=0, fill_value=0.0) except: continue logging.debug("%s.%s Slicing Stream to %s:%s" % (station, comp, utcdatetime.UTCDateTime( goal_day.replace('-', '')), utcdatetime.UTCDateTime(goal_day.replace('-', '')) + params.goal_duration - stream[0].stats.delta)) stream[0].trim(utcdatetime.UTCDateTime(goal_day.replace('-', '')), utcdatetime.UTCDateTime( goal_day.replace('-', '')) + params.goal_duration - stream[0].stats.delta, pad=True, fill_value=0.0, nearest_sample=False) if get_config(db, 'remove_response', isbool=True): logging.debug('Removing instrument response') response_format = get_config(db, 'response_format') response_prefilt = eval(get_config(db, 'response_prefilt')) files = glob.glob(os.path.join(get_config(db, 'response_path'), "*")) if response_format == "inventory": firstinv = True inventory = None for file in files: try: inv = read_inventory(file) if firstinv: inventory = inv firstinv = False else: inventory += inv except: traceback.print_exc() pass if inventory: stream.attach_response(inventory) stream.remove_response(output='VEL', pre_filt=response_prefilt) elif response_format == "dataless": for file in files: p = Parser(file) try: p.getPAZ(stream[0].id, datetime=UTCDateTime(gd)) break except: traceback.print_exc() del p continue stream.simulate(seedresp={'filename': p, "units":"VEL"}, pre_filt=response_prefilt, paz_remove=None, paz_simulate=None,) elif response_format == "paz": msg = "Unexpected type for `response_format`: %s" % \ response_format raise TypeError(msg) elif response_format == "resp": msg = "Unexpected type for `response_format`: %s" % \ response_format raise TypeError(msg) else: msg = "Unexpected type for `response_format`: %s" % \ response_format raise TypeError(msg) trace = stream[0] logging.debug( "%s.%s Highpass at %.2f Hz" % (station, comp, params.preprocess_highpass)) trace.filter("highpass", freq=params.preprocess_highpass, zerophase=True) if trace.stats.sampling_rate != params.goal_sampling_rate: logging.debug( "%s.%s Lowpass at %.2f Hz" % (station, comp, params.preprocess_lowpass)) trace.filter("lowpass", freq=params.preprocess_lowpass, zerophase=True) if params.resampling_method == "Resample": logging.debug("%s.%s Downsample to %.1f Hz" % (station, comp, params.goal_sampling_rate)) trace.data = resample( trace.data, params.goal_sampling_rate / trace.stats.sampling_rate, 'sinc_fastest') elif params.resampling_method == "Decimate": logging.debug("%s.%s Decimate by a factor of %i" % (station, comp, params.decimation_factor)) trace.data = trace.data[::params.decimation_factor] trace.stats.sampling_rate = params.goal_sampling_rate year, month, day, hourf, minf, secf, wday, yday, isdst = trace.stats.starttime.utctimetuple() if j == 0: t = time.strptime("%04i:%02i:%02i:%02i:%02i:%02i" % (year, month, day, hourf, minf, secf), "%Y:%m:%d:%H:%M:%S") basetime = calendar.timegm(t) if len(trace.data) % 2 != 0: trace.data = np.append(trace.data, 0.) if len(trace.data) != len(tramef_Z[istation]): missing = len(tramef_Z[istation])- len(trace.data) for i in range(missing): trace.data = np.append(trace.data, 0.) if comp == "Z": tramef_Z[istation] = trace.data elif comp == "E": tramef_E[istation] = trace.data elif comp == "N": tramef_N[istation] = trace.data del trace, stream if len(tramef_E) != 0: return basetime, tramef_Z, tramef_E, tramef_N else: return basetime, tramef_Z
def deconvolve_traces(signal, divisor, eps, freq=[], residual=False): """ Deconvolve a time series from a set of time series. The function is a wrapper for the :class:`scipy.signal.convolve` function. :type signal: :class:`~obspy.core.stream.Stream` :param signal: signal from which the divisor is to be deconvolved :type divisor: :class:`~obspy.core.trace.Trace` :param divisor: time series that is to be deconvolved from signal :type eps: float :param eps: fraction of spectral mean used as a water level to avoid spectral holes in the deconvolution. :type freq: two element array-like :param freq: frequency range for the estimation of the mean power that is scaled with ``eps`` to obtian the water level :type residual: bool :param residual: return residual if True, defaults to False :rtype: obspy.core.stream :return: **(dcst, rst)**: decorrelated stream and residual (only if ``residual=True`` """ zerotime = UTCDateTime(1971,1,1) # trace length is taken from signal (must be even to use real fft) if signal[0].stats['npts'] % 2: trlen = signal[0].stats['npts']+1 else: trlen = signal[0].stats['npts'] delta = divisor.stats['delta'] # prepare divisor divisor.detrend(type='constant') taper = cosine_taper(divisor.stats['npts'],p=0.05) divisor.data *= taper divisor.trim(starttime=divisor.stats['starttime'],endtime=divisor.stats['starttime']+ (trlen-1)*delta,pad=True,fill_value=0,nearest_sample=False) # FFT divisor fd = np.fft.fftpack.rfft(divisor.data) # estimate the waterlevel to stabilize deconvolution if freq: f = np.linspace(-signal[0].stats['sampling_rate']/2., signal[0].stats['sampling_rate']/2.,len(fd)) ind = np.nonzero(np.all([f>freq[0],f<freq[1]],axis=0)) wl = eps * np.mean((fd*fd.conj())[ind]) else: wl = eps * np.mean((fd*fd.conj())) # create the output stream dcst = Stream() rst = Stream() for tr in signal: if tr.stats['sampling_rate'] != divisor.stats['sampling_rate']: print "Sampling rates don't match for \n %s" % tr continue # prepare nuerator tr.detrend('constant') taper = cosine_taper(tr.stats['npts']) tr.trim(starttime=tr.stats['starttime'], endtime=tr.stats['starttime']+ (trlen-1)*delta,pad=True,fill_value=0,nearest_sample=False) tr.data *= taper # fft numerator sf = np.fft.fftpack.rfft(tr.data) # calculate deconvolution fdc = sf*fd/(fd**2+wl) dc = np.fft.fftpack.irfft(fdc) # template to hold results dctr = tr.copy() # propagate metadata dctr.stats = combine_stats(tr,divisor) dctr.data = dc dctr.stats['npts'] = len(dc) dctr.stats['starttime'] = zerotime - (divisor.stats['starttime']-tr.stats['starttime']) dctr.stats_tr1 = tr.stats dctr.stats_tr2 = divisor.stats # append to output stream dcst.append(dctr) if residual: # residual rtr = dctr.copy() rtr.data = tr.data - np.fft.fftpack.irfft(fdc * fd) # append to output stream rst.append(rtr) return (dcst, rst) return dcst
def fk_freq(data, fs, rij, vmin, vmax, fmin, fmax, nvel, ntheta): r""" :math:`f`–:math:`k` beamforming with loop over frequency bands. Args: data: ``(m, n)`` array; time series with ``m`` samples from ``n`` traces as columns rij: ``(d, n)`` array; ``n`` sensor coordinates as [northing, easting, {elevation}] column vectors in ``d`` dimensions fs (int or float): Sample rate [Hz] vmin (int or float): Min velocity in km/s, suggest 0.25 vmax (int or float): Max velocity in km/s, suggest 0.45 fmin (int or float): Minimum frequency in Hz fmax (int or float): Maximum frequency in Hz nvel (int or float): Number of velocity iterations, suggest 100–200 ntheta (int or float): Number of azimuth iterations, suggest 100–200 Returns: ``(ntheta, nvel)`` array; beamformed slowness map, not normalized. Can find max using .. code-block:: python ix, iy = np.unravel_index(bmpwr.argmax(), bmpwr.shape) """ #reshape rij from standard setup rij = np.transpose(rij) rij[:, 0] = rij[:, 0] - np.mean(rij[:, 0]) rij[:, 1] = rij[:, 1] - np.mean(rij[:, 1]) # Getting the size of the data [m, nsta] = np.shape(data) # set up velocity/slowness and theta vectors sits = np.linspace(1/vmax, 1/vmin, int(nvel)) theta = np.linspace(0, 2*np.pi, ntheta) # Getting initial time shifts # x time delay cost = np.cos(theta) Tx1 = np.outer(sits, np.transpose(cost)) Txm = Tx1[:, :, None] * np.transpose(rij[:, 1]) # y time delay sint = np.sin(theta) Ty1 = np.outer(sits, np.transpose(sint)) Tym = Ty1[:, :, None] * np.transpose(rij[:, 0]) # All possible time delays TT = Txm + Tym # Computing the next power of 2 for fft input n2 = ceil(np.log2(m)) nfft = int(pow(2, n2)) # Frequency increment deltaf = fs / nfft # Getting frequency bands nlow = int(fmin / float(deltaf) + 0.5) nhigh = int(fmax / float(deltaf) + 0.5) nlow = max(1, nlow) # avoid using the offset nhigh = min(nfft // 2 - 1, nhigh) # avoid using Nyquist nf = nhigh - nlow + 1 # include upper and lower frequency # Apply a 22% Cosine Taper taper = cosine_taper(m, p=0.22) # Calculated the FFT of each trace # ft are complex Fourier coefficients # is this faster in scipy? ft = np.empty((nsta, nf), dtype=np.complex128) for jj in range(nsta): data[:, jj] = data[:, jj] - np.mean(data[:, jj]) dat = data[:, jj] * taper ft[jj, :] = np.fft.rfft(dat, nfft)[nlow:nlow + nf] # Change data structure for performance boost --> Check this. ft = np.ascontiguousarray(ft, np.complex128) # Pre-allocating freqrange = np.linspace(fmin, fmax, nf) pow_mapt = np.zeros((int(nvel), int(ntheta)), dtype=np.float64, order='C') pow_mapb = np.zeros((int(nvel), int(ntheta)), dtype=np.float64, order='C') flen = len(freqrange) # loop entire slowness map over frequencies # compute covariance for ii in range(flen): # Generating the exponentials - steering vectors freq = freqrange[ii] expo = -1j * 2 * np.pi * freq * TT Master = np.exp(expo) # Broadcasting the Fourier coefficients at each station fcoeff = ft[:, ii] Master = Master * fcoeff[None, None, :] Top = np.sum(Master, axis=2) Top2 = np.real(np.multiply(Top.conj(), Top)) Bot = np.real(np.multiply(Master.conj(), Master)) Bot = np.sum(Bot, axis=2) pow_mapt += Top2 pow_mapb += Bot pow_map = pow_mapt/pow_mapb return pow_map
def polarization_analysis(stream, win_len, win_frac, frqlow, frqhigh, stime, etime, verbose=False, method="pm", var_noise=0.0): """ Method carrying out polarization analysis with the [Flinn1965b]_, [Jurkevics1988]_, ParticleMotion, or [Vidale1986]_ algorithm. :param stream: 3 component input data. :type stream: :class:`~obspy.core.stream.Stream` :param win_len: Sliding window length in seconds. :type win_len: float :param win_frac: Fraction of sliding window to use for step. :type win_frac: float :param var_noise: resembles a sphere of noise in PM where the 3C is excluded :type var_noise: float :param frqlow: lower frequency for PM :type frqlow: float :param frqhigh: higher frequency for PM :type frqhigh: float :param stime: Start time of interest :type stime: :class:`obspy.core.utcdatetime.UTCDateTime` :param etime: End time of interest :type etime: :class:`obspy.core.utcdatetime.UTCDateTime` :param method: the method to use. one of ``"pm"``, ``"flinn"`` or ``"vidale"``. :type method: str :rtype: dict :returns: Dictionary with keys ``"timestamp"`` (POSIX timestamp, can be used to initialize :class:`~obspy.core.utcdatetime.UTCDateTime` objects), ``"azimuth"``, ``"incidence"`` (incidence angle) and additional keys depending on used method: ``"azimuth_error"`` and ``"incidence_error"`` (for method ``"pm"``), ``"rectilinearity"`` and ``"planarity"`` (for methods ``"flinn"`` and ``"vidale"``) and ``"ellipticity"`` (for method ``"flinn"``). Under each key a :class:`~numpy.ndarray` is stored, giving the respective values corresponding to the ``"timestamp"`` :class:`~numpy.ndarray`. """ if method.lower() not in ["pm", "flinn", "vidale"]: msg = "Invalid method ('%s')" % method raise ValueError(msg) res = [] # check that sampling rates do not vary fs = stream[0].stats.sampling_rate if len(stream) != len(stream.select(sampling_rate=fs)): msg = "sampling rates of traces in stream are not equal" raise ValueError(msg) if verbose: print("stream contains following traces:") print(stream) print("stime = " + str(stime) + ", etime = " + str(etime)) spoint, _epoint = _get_s_point(stream, stime, etime) if method.lower() == "vidale": res = vidale_adapt(stream, var_noise, fs, frqlow, frqhigh, spoint, stime, etime) else: nsamp = int(win_len * fs) nstep = int(nsamp * win_frac) newstart = stime tap = cosine_taper(nsamp, p=0.22) offset = 0 while (newstart + (nsamp + nstep) / fs) < etime: try: data = [] Z = [] N = [] E = [] for i, tr in enumerate(stream): dat = tr.data[spoint[i] + offset: spoint[i] + offset + nsamp] dat = (dat - dat.mean()) * tap if "Z" in tr.stats.channel: Z = dat.copy() if "N" in tr.stats.channel: N = dat.copy() if "E" in tr.stats.channel: E = dat.copy() data.append(Z) data.append(N) data.append(E) except IndexError: break # we plot against the centre of the sliding window if method.lower() == "pm": azimuth, incidence, error_az, error_inc = \ particle_motion_odr(data, var_noise) res.append(np.array([newstart.timestamp + float(nstep) / fs, azimuth, incidence, error_az, error_inc])) if method.lower() == "flinn": azimuth, incidence, reclin, plan = flinn(data, var_noise) res.append(np.array([newstart.timestamp + float(nstep) / fs, azimuth, incidence, reclin, plan])) if verbose: print(newstart, newstart + nsamp / fs, res[-1][1:]) offset += nstep newstart += float(nstep) / fs res = np.array(res) result_dict = {"timestamp": res[:, 0], "azimuth": res[:, 1], "incidence": res[:, 2]} if method.lower() == "pm": result_dict["azimuth_error"] = res[:, 3] result_dict["incidence_error"] = res[:, 4] elif method.lower() == "vidale": result_dict["rectilinearity"] = res[:, 3] result_dict["planarity"] = res[:, 4] result_dict["ellipticity"] = res[:, 5] elif method.lower() == "flinn": result_dict["rectilinearity"] = res[:, 3] result_dict["planarity"] = res[:, 4] return result_dict
def polarization_analysis(stream, win_len, win_frac, frqlow, frqhigh, stime, etime, verbose=False, method="pm", var_noise=0.0, adaptive=True): """ Method carrying out polarization analysis with the [Flinn1965b]_, [Jurkevics1988]_, ParticleMotion, or [Vidale1986]_ algorithm. :param stream: 3 component input data. :type stream: :class:`~obspy.core.stream.Stream` :param win_len: Sliding window length in seconds. :type win_len: float :param win_frac: Fraction of sliding window to use for step. :type win_frac: float :param var_noise: resembles a sphere of noise in PM where the 3C is excluded :type var_noise: float :param frqlow: lower frequency. Only used for ``method='vidale'``. :type frqlow: float :param frqhigh: higher frequency. Only used for ``method='vidale'``. :type frqhigh: float :param stime: Start time of interest :type stime: :class:`obspy.core.utcdatetime.UTCDateTime` :param etime: End time of interest :type etime: :class:`obspy.core.utcdatetime.UTCDateTime` :param method: the method to use. one of ``"pm"``, ``"flinn"`` or ``"vidale"``. :type method: str :param adaptive: switch for adaptive window estimation (defaults to ``True``). If set to ``False``, the window will be estimated as ``3 * max(1/(fhigh-flow), 1/flow)``. :type adaptive: bool :rtype: dict :returns: Dictionary with keys ``"timestamp"`` (POSIX timestamp, can be used to initialize :class:`~obspy.core.utcdatetime.UTCDateTime` objects), ``"azimuth"``, ``"incidence"`` (incidence angle) and additional keys depending on used method: ``"azimuth_error"`` and ``"incidence_error"`` (for method ``"pm"``), ``"rectilinearity"`` and ``"planarity"`` (for methods ``"flinn"`` and ``"vidale"``) and ``"ellipticity"`` (for method ``"flinn"``). Under each key a :class:`~numpy.ndarray` is stored, giving the respective values corresponding to the ``"timestamp"`` :class:`~numpy.ndarray`. """ if method.lower() not in ["pm", "flinn", "vidale"]: msg = "Invalid method ('%s')" % method raise ValueError(msg) res = [] if stream.get_gaps(): msg = 'Input stream must not include gaps:\n' + str(stream) raise ValueError(msg) if len(stream) != 3: msg = 'Input stream expected to be three components:\n' + str(stream) raise ValueError(msg) # check that sampling rates do not vary fs = stream[0].stats.sampling_rate if len(stream) != len(stream.select(sampling_rate=fs)): msg = "sampling rates of traces in stream are not equal" raise ValueError(msg) if verbose: print("stream contains following traces:") print(stream) print("stime = " + str(stime) + ", etime = " + str(etime)) spoint, _epoint = _get_s_point(stream, stime, etime) if method.lower() == "vidale": res = vidale_adapt(stream, var_noise, fs, frqlow, frqhigh, spoint, stime, etime) else: nsamp = int(win_len * fs) nstep = int(nsamp * win_frac) newstart = stime tap = cosine_taper(nsamp, p=0.22) offset = 0 while (newstart + (nsamp + nstep) / fs) < etime: try: for i, tr in enumerate(stream): dat = tr.data[spoint[i] + offset:spoint[i] + offset + nsamp] dat = (dat - dat.mean()) * tap if tr.stats.channel[-1].upper() == "Z": z = dat.copy() elif tr.stats.channel[-1].upper() == "N": n = dat.copy() elif tr.stats.channel[-1].upper() == "E": e = dat.copy() else: msg = "Unexpected channel code '%s'" % tr.stats.channel raise ValueError(msg) data = [z, n, e] except IndexError: break # we plot against the centre of the sliding window if method.lower() == "pm": azimuth, incidence, error_az, error_inc = \ particle_motion_odr(data, var_noise) res.append( np.array([ newstart.timestamp + float(nstep) / fs, azimuth, incidence, error_az, error_inc ])) if method.lower() == "flinn": azimuth, incidence, reclin, plan = flinn(data, var_noise) res.append( np.array([ newstart.timestamp + float(nstep) / fs, azimuth, incidence, reclin, plan ])) if verbose: print(newstart, newstart + nsamp / fs, res[-1][1:]) offset += nstep newstart += float(nstep) / fs res = np.array(res) result_dict = { "timestamp": res[:, 0], "azimuth": res[:, 1], "incidence": res[:, 2] } if method.lower() == "pm": result_dict["azimuth_error"] = res[:, 3] result_dict["incidence_error"] = res[:, 4] elif method.lower() == "vidale": result_dict["rectilinearity"] = res[:, 3] result_dict["planarity"] = res[:, 4] result_dict["ellipticity"] = res[:, 5] elif method.lower() == "flinn": result_dict["rectilinearity"] = res[:, 3] result_dict["planarity"] = res[:, 4] return result_dict
# evaluate (time-domain) forward and backward spectrum separately to check the data quality phasevel_curves = [] for qualitycheck in ["symmetric", "forward", "backward"]: if qualitycheck == "forward": TCORR = A elif qualitycheck == "backward": TCORR = B else: TCORR = np.fft.irfft(np.real(spectrum)) """ applying velocity filter and SNR filter""" if velocity_filter: idx_tmin = int( (dist / max_vel) / dt * 0.95) # 5percent extra for taper idx_tmax = int((dist / min_vel) / dt * 1.05) # 5% extra for taper vel_filt_window = cosine_taper(idx_tmax - idx_tmin, p=0.1) win_samples = int((len(spectrum) - 1) * 2) vel_filt = np.zeros(win_samples) vel_filt[idx_tmin:idx_tmax] = vel_filt_window vel_filt[-idx_tmax:-idx_tmin] = vel_filt_window #TCORR = np.fft.irfft(spectrum[:,1])#+1j*spectrum[:,2]) if snr_filter_time_domain: snr_filt_td = snr_cc( TCORR, 1. / dt, dist, min_vel, max_vel, intervals, plotting=statpair) > snr_filter_threshold_td
def g1g2_kern(wf1str, wf2str, kernel, adjt, src, source_conf, insta): measr_conf = json.load( open(os.path.join(source_conf['source_path'], 'measr_config.json'))) bandpass = measr_conf['bandpass'] if bandpass == None: filtcnt = 1 elif type(bandpass) == list: if type(bandpass[0]) != list: filtcnt = 1 else: filtcnt = len(bandpass) ntime, n, n_corr, Fs = get_ns(wf1str, source_conf, insta) # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off whereever the solver stopped running. taper = cosine_taper(ntime, p=0.01) taper[0:ntime // 2] = 1.0 ######################################################################## # Prepare filenames and adjoint sources ######################################################################## filenames = [] adjt_srcs = [] adjt_srcs_cnt = 0 for ix_f in range(filtcnt): filename = kernel + '.{}.npy'.format(ix_f) filenames.append(filename) #if os.path.exists(filename): # continue f = Stream() for a in adjt: adjtfile = a + '*.{}.sac'.format(ix_f) adjtfile = glob(adjtfile) try: f += read(adjtfile[0])[0] f[-1].data = my_centered(f[-1].data, n_corr) adjt_srcs_cnt += 1 except IndexError: print('No adjoint source found: {}\n'.format(a)) break adjt_srcs.append(f) ######################################################################## # Compute the kernels ######################################################################## with NoiseSource(src) as nsrc: ntraces = nsrc.src_loc[0].shape[0] if insta: # open database dbpath = json.load( open(os.path.join(source_conf['project_path'], 'config.json')))['wavefield_path'] # open and determine Fs, nt db = instaseis.open_db(dbpath) # get receiver locations lat1 = geograph_to_geocent(float(wf1[2])) lon1 = float(wf1[3]) rec1 = instaseis.Receiver(latitude=lat1, longitude=lon1) lat2 = geograph_to_geocent(float(wf2[2])) lon2 = float(wf2[3]) rec2 = instaseis.Receiver(latitude=lat2, longitude=lon2) else: wf1 = WaveField(wf1str) wf2 = WaveField(wf2str) kern = np.zeros((filtcnt, ntraces, len(adjt))) ######################################################################## # Loop over locations ######################################################################## for i in range(ntraces): # noise source spectrum at this location # For the kernel, this contains only the basis functions of the # spectrum without weights; might still be location-dependent, # for example when constraining sensivity to ocean S = nsrc.get_spect(i) if S.sum() == 0.: # The spectrum has 0 phase so only checking absolute value here continue #################################################################### # Get synthetics #################################################################### if insta: # get source locations lat_src = geograph_to_geocent(nsrc.src_loc[1, i]) lon_src = nsrc.src_loc[0, i] fsrc = instaseis.ForceSource(latitude=lat_src, longitude=lon_src, f_r=1.e12) s1 = np.ascontiguousarray( db.get_seismograms( source=fsrc, receiver=rec1, dt=1. / source_conf['sampling_rate'])[0].data * taper) s2 = np.ascontiguousarray( db.get_seismograms( source=fsrc, receiver=rec2, dt=1. / source_conf['sampling_rate'])[0].data * taper) else: s1 = np.ascontiguousarray(wf1.data[i, :] * taper) s2 = np.ascontiguousarray(wf2.data[i, :] * taper) spec1 = np.fft.rfft(s1, n) spec2 = np.fft.rfft(s2, n) g1g2_tr = np.multiply(np.conjugate(spec1), spec2) c = np.multiply(g1g2_tr, S) ####################################################################### # Get Kernel at that location ####################################################################### corr_temp = my_centered(np.fft.ifftshift(np.fft.irfft(c, n)), n_corr) ####################################################################### # Apply the 'adjoint source' ####################################################################### for ix_f in range(filtcnt): f = adjt_srcs[ix_f] if f == None: continue for j in range(len(f)): delta = f[j].stats.delta kern[ix_f, i, j] = np.dot(corr_temp, f[j].data) * delta #elif measr_conf['mtype'] in ['envelope']: # if j == 0: # corr_temp_h = corr_temp # print(corr_temp_h) # if j == 1: # corr_temp_h = hilbert(corr_temp) # print(corr_temp_h) # # kern[ix_f,i,j] = np.dot(corr_temp,f[j].data) * delta if i % 50000 == 0: print("Finished {} source locations.".format(i)) if not insta: wf1.file.close() wf2.file.close() for ix_f in range(filtcnt): filename = filenames[ix_f] if kern[ix_f, :, :].sum() != 0: np.save(filename, kern[ix_f, :, :]) return ()
def fft_taper(data): """ Cosine taper, 10 percent at each end. """ data *= cosine_taper(len(data), 0.2) return data
def main(): logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logging.info('*** Starting: Compute CC ***') # Connection to the DB db = connect() if len(get_filters(db, all=False)) == 0: logging.info("NO FILTERS DEFINED, exiting") sys.exit() # Get Configuration params = Params() params.goal_sampling_rate = float(get_config(db, "cc_sampling_rate")) params.goal_duration = float(get_config(db, "analysis_duration")) params.overlap = float(get_config(db, "overlap")) params.maxlag = float(get_config(db, "maxlag")) params.min30 = float(get_config(db, "corr_duration")) * params.goal_sampling_rate params.windsorizing = float(get_config(db, "windsorizing")) params.resampling_method = get_config(db, "resampling_method") params.decimation_factor = int(get_config(db, "decimation_factor")) params.preprocess_lowpass = float(get_config(db, "preprocess_lowpass")) params.preprocess_highpass = float(get_config(db, "preprocess_highpass")) params.keep_all = get_config(db, 'keep_all', isbool=True) params.keep_days = get_config(db, 'keep_days', isbool=True) params.components_to_compute = get_components_to_compute(db) params.stack_method = get_config(db, 'stack_method') params.pws_timegate = float(get_config(db, 'pws_timegate')) params.pws_power = float(get_config(db, 'pws_power')) logging.info("Will compute %s" % " ".join(params.components_to_compute)) while is_next_job(db, jobtype='CC'): jobs = get_next_job(db, jobtype='CC') stations = [] pairs = [] refs = [] for job in jobs: refs.append(job.ref) pairs.append(job.pair) netsta1, netsta2 = job.pair.split(':') stations.append(netsta1) stations.append(netsta2) goal_day = job.day stations = np.unique(stations) logging.info("New CC Job: %s (%i pairs with %i stations)" % (goal_day, len(pairs), len(stations))) jt = time.time() xlen = int(params.goal_duration * params.goal_sampling_rate) if ''.join(params.components_to_compute).count('R') > 0 or ''.join(params.components_to_compute).count('T') > 0: comps = ['Z', 'E', 'N'] tramef_Z = np.zeros((len(stations), xlen)) tramef_E = np.zeros((len(stations), xlen)) tramef_N = np.zeros((len(stations), xlen)) basetime, tramef_Z, tramef_E, tramef_N = preprocess(db, stations, comps, goal_day, params, tramef_Z, tramef_E, tramef_N) else: comps = ['Z'] tramef_Z = np.zeros((len(stations), xlen)) basetime, tramef_Z = preprocess(db, stations, comps, goal_day, params, tramef_Z) # print '##### STREAMS ARE ALL PREPARED AT goal Hz #####' dt = 1. / params.goal_sampling_rate # Calculate the number of slices slices = int(params.goal_duration * params.goal_sampling_rate / params.min30) begins = [] ends = [] i = 0 while i <= (params.goal_duration - params.min30/params.goal_sampling_rate): begins.append(int(i * params.goal_sampling_rate)) ends.append(int(i * params.goal_sampling_rate + params.min30)) i += int(params.min30/params.goal_sampling_rate * (1.0-params.overlap)) slices = len(begins) # # Computing only ZZ components ? Then we can be much faster: # if False: #if len(params.components_to_compute) == 1 and params.components_to_compute[0] == "ZZ": Nfft = params.min30 if params.min30 / 2 % 2 != 0: Nfft = params.min30 + 2 cp = cosine_taper(int(params.min30), 0.04) logging.info("Pre-Whitening Traces") whitened_slices = np.zeros((len(stations), len(get_filters(db, all=False)), slices, int(Nfft)), dtype=np.complex) for istation, station in enumerate(stations): for islice, (begin, end) in enumerate(zip(begins,ends)): tmp = tramef_Z[istation, begin:end] rmsmat = np.std(np.abs(tmp)) if params.windsorizing == -1: tmp = np.sign(tmp) elif params.windsorizing != 0: indexes = np.where( np.abs(tmp) > (params.windsorizing * rmsmat))[0] tmp[indexes] = (tmp[indexes] / np.abs( tmp[indexes])) * params.windsorizing * rmsmat tmp *= cp for ifilter, filter in enumerate(get_filters(db, all=False)): whitened_slices[istation, ifilter, islice,:] = whiten(tmp, Nfft, dt, float(filter.low), float(filter.high), plot=False) del tmp del tramef_Z logging.info("Processing CC") for ifilter, filter in enumerate(get_filters(db, all=False)): for pair in pairs: orig_pair = pair if params.keep_all: allcorr = {} if params.keep_days: daycorr = np.zeros(get_maxlag_samples(db,)) ndaycorr = 0 station1, station2 = pair.split(':') pair = (np.where(stations == station1) [0][0], np.where(stations == station2)[0][0]) for islice in range(slices): tmp = np.vstack((whitened_slices[pair[0], ifilter, islice], whitened_slices[pair[1], ifilter, islice])) corr = myCorr(tmp, np.ceil(params.maxlag / dt), plot=False) tmptime = time.gmtime(basetime + begins[islice] / params.goal_sampling_rate) thisdate = time.strftime("%Y-%m-%d", tmptime) thistime = time.strftime("%Y-%m-%d %H:%M:%S", tmptime) if not np.any(np.isnan(corr)) and not np.any(np.isinf(corr)): if params.keep_all: ccfid = "%s_%s_%s_%s_%s" % (station1, station2, filter.ref, 'ZZ', thisdate) if ccfid not in allcorr: allcorr[ccfid] = {} allcorr[ccfid][thistime] = corr if params.keep_days: daycorr += corr ndaycorr += 1 if params.keep_all: for ccfid in allcorr.keys(): export_allcorr(db, ccfid, allcorr[ccfid]) if params.keep_days: thisdate = time.strftime( "%Y-%m-%d", time.gmtime(basetime)) thistime = time.strftime( "%H_%M", time.gmtime(basetime)) add_corr( db, station1.replace( '.', '_'), station2.replace('.', '_'), filter.ref, thisdate, thistime, params.min30 / params.goal_sampling_rate, 'ZZ', daycorr, params.goal_sampling_rate, day=True, ncorr=ndaycorr) update_job(db, goal_day, orig_pair, 'CC', 'D') logging.info("Job Finished. It took %.2f seconds" % (time.time() - jt)) else: # ITERATING OVER PAIRS ##### for pair in pairs: orig_pair = pair logging.info('Processing pair: %s' % pair.replace(':', ' vs ')) tt = time.time() station1, station2 = pair.split(':') pair = (np.where(stations == station1) [0][0], np.where(stations == station2)[0][0]) s1 = get_station(db, station1.split('.')[0], station1.split('.')[1]) s2 = get_station(db, station2.split('.')[0], station2.split('.')[1]) if s1.X: X0 = s1.X Y0 = s1.Y c0 = s1.coordinates X1 = s2.X Y1 = s2.Y c1 = s2.coordinates if c0 == c1: coordinates = c0 else: coordinates = 'MIX' cplAz = np.deg2rad(azimuth(coordinates, X0, Y0, X1, Y1)) logging.debug("Azimuth=%.1f"%np.rad2deg(cplAz)) else: # logging.debug('No Coordinates found! Skipping azimuth calculation!') cplAz = 0. for components in params.components_to_compute: if components == "ZZ": t1 = tramef_Z[pair[0]] t2 = tramef_Z[pair[1]] elif components[0] == "Z": t1 = tramef_Z[pair[0]] t2 = tramef_E[pair[1]] elif components[1] == "Z": t1 = tramef_E[pair[0]] t2 = tramef_Z[pair[1]] else: t1 = tramef_E[pair[0]] t2 = tramef_E[pair[1]] if np.all(t1 == 0) or np.all(t2 == 0): logging.debug("%s contains empty trace(s), skipping"%components) continue del t1, t2 if components[0] == "Z": t1 = tramef_Z[pair[0]] elif components[0] == "R": if cplAz != 0: t1 = tramef_N[pair[0]] * np.cos(cplAz) +\ tramef_E[pair[0]] * np.sin(cplAz) else: t1 = tramef_E[pair[0]] elif components[0] == "T": if cplAz != 0: t1 = tramef_N[pair[0]] * np.sin(cplAz) -\ tramef_E[pair[0]] * np.cos(cplAz) else: t1 = tramef_N[pair[0]] if components[1] == "Z": t2 = tramef_Z[pair[1]] elif components[1] == "R": if cplAz != 0: t2 = tramef_N[pair[1]] * np.cos(cplAz) +\ tramef_E[pair[1]] * np.sin(cplAz) else: t2 = tramef_E[pair[1]] elif components[1] == "T": if cplAz != 0: t2 = tramef_N[pair[1]] * np.sin(cplAz) -\ tramef_E[pair[1]] * np.cos(cplAz) else: t2 = tramef_N[pair[1]] trames = np.vstack((t1, t2)) del t1, t2 daycorr = {} ndaycorr = {} allcorr = {} for filterdb in get_filters(db, all=False): filterid = filterdb.ref daycorr[filterid] = np.zeros(get_maxlag_samples(db,)) ndaycorr[filterid] = 0 for islice, (begin, end) in enumerate(zip(begins, ends)): # print "Progress: %#2d/%2d"% (islice+1,slices) trame2h = trames[:, begin:end] rmsmat = np.std(trame2h, axis=1) for filterdb in get_filters(db, all=False): filterid = filterdb.ref low = float(filterdb.low) high = float(filterdb.high) rms_threshold = filterdb.rms_threshold Nfft = int(params.min30) if params.min30 / 2 % 2 != 0: Nfft = params.min30 + 2 trames2hWb = np.zeros((2, int(Nfft)), dtype=np.complex) skip = False for i, station in enumerate(pair): if rmsmat[i] > rms_threshold: cp = cosine_taper(len(trame2h[i]),0.04) trame2h[i] -= trame2h[i].mean() if params.windsorizing == -1: trame2h[i] = np.sign(trame2h[i]) elif params.windsorizing != 0: indexes = np.where( np.abs(trame2h[i]) > (params.windsorizing * rmsmat[i]))[0] # clipping at windsorizing*rms trame2h[i][indexes] = (trame2h[i][indexes] / np.abs( trame2h[i][indexes])) * params.windsorizing * rmsmat[i] trames2hWb[i] = whiten( trame2h[i]*cp, Nfft, dt, low, high, plot=False) else: trames2hWb[i] = np.zeros(int(Nfft)) skip = True logging.debug('Slice is Zeros!') if not skip: corr = myCorr(trames2hWb, np.ceil(params.maxlag / dt), plot=False) tmptime = time.gmtime(basetime + begin / params.goal_sampling_rate) thisdate = time.strftime("%Y-%m-%d", tmptime) thistime = time.strftime("%Y-%m-%d %H:%M:%S", tmptime) if params.keep_all or params.keep_days: ccfid = "%s_%s_%s_%s_%s" % (station1, station2, filterid, components, thisdate) if ccfid not in allcorr: allcorr[ccfid] = {} allcorr[ccfid][thistime] = corr if params.keep_days: if not np.any(np.isnan(corr)) and \ not np.any(np.isinf(corr)): daycorr[filterid] += corr ndaycorr[filterid] += 1 del corr, thistime, trames2hWb if params.keep_all: for ccfid in allcorr.keys(): export_allcorr(db, ccfid, allcorr[ccfid]) if params.keep_days: for ccfid in allcorr.keys(): station1, station2, filterid, components, date = ccfid.split('_') corrs = np.asarray(list(allcorr[ccfid].values())) corr = stack(db, corrs) thisdate = time.strftime( "%Y-%m-%d", time.gmtime(basetime)) thistime = time.strftime( "%H_%M", time.gmtime(basetime)) add_corr( db, station1.replace('.', '_'), station2.replace('.', '_'), int(filterid), thisdate, thistime, params.min30 / params.goal_sampling_rate, components, corr, params.goal_sampling_rate, day=True, ncorr=corrs.shape[0]) # try: # for filterdb in get_filters(db, all=False): # filterid = filterdb.ref # corr = daycorr[filterid] # ncorr = ndaycorr[filterid] # if ncorr > 0: # logging.debug( # "Saving daily CCF for filter %02i, comp %s (stack of %02i CCF)" % (filterid, components, ncorr)) # # thisdate = time.strftime( # "%Y-%m-%d", time.gmtime(basetime)) # thistime = time.strftime( # "%H_%M", time.gmtime(basetime)) # add_corr( # db, station1.replace('.', '_'), # station2.replace('.', '_'), filterid, # thisdate, thistime, params.min30 / # params.goal_sampling_rate, # components, corr, # params.goal_sampling_rate, day=True, # ncorr=ncorr) # del corr, ncorr # except Exception as e: # logging.debug(str(e)) del trames, daycorr, ndaycorr logging.debug("Updating Job") update_job(db, goal_day, orig_pair, 'CC', 'D') logging.info("Finished processing this pair. It took %.2f seconds" % (time.time() - tt)) logging.info("Job Finished. It took %.2f seconds" % (time.time() - jt)) logging.info('*** Finished: Compute CC ***')
def run_corr(args, comm, size, rank): all_conf = config_params(args, comm, size, rank) # Distributing the tasks correlation_tasks, n_p_p, n_p = define_correlation_tasks(all_conf, comm, size, rank) if len(correlation_tasks) == 0: return() if all_conf.config['verbose']: print('Rank number %g' % rank) print('working on pair nr. %g to %g of %g.' % (rank * n_p_p, rank * n_p_p + n_p_p, n_p)) # Current model for the noise source it_dir = os.path.join(all_conf.source_config['project_path'], all_conf.source_config['source_name'], 'iteration_' + str(all_conf.step)) nsrc = os.path.join(it_dir,'starting_model.h5') # Smart numbers all_ns = get_ns(all_conf) # ntime, n, n_corr, Fs # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off wherever the solver stopped running. taper = cosine_taper(all_ns[0], p=0.01) taper[0: all_ns[0] // 2] = 1.0 with NoiseSource(nsrc) as nsrc: for cp in correlation_tasks: try: input_files_list = add_input_files(cp, all_conf) output_files = add_output_files(cp, all_conf) except (IndexError, FileNotFoundError): if all_conf.config['verbose']: print('Could not determine correlation for: %s\ \nCheck if wavefield .h5 file is available.' % cp) continue if type(input_files_list[0]) != list: input_files_list = [input_files_list] for i, input_files in enumerate(input_files_list): correlation, sta1, sta2 = compute_correlation(input_files, all_conf, nsrc, all_ns, taper) add_metadata_and_write(correlation, sta1, sta2, output_files[i], all_ns[3]) if all_conf.source_config["rotate_horizontal_components"]: fls = glob(os.path.join(it_dir, "corr", "*{}*{}*.sac".format(cp[0].split()[1], cp[1].split()[1]))) fls.sort() apply_rotation(fls, stationlistfile=os.path.join(all_conf.source_config['project_path'], "stationlist.csv"), output_directory=os.path.join(it_dir, "corr")) comm.barrier() if rank == 0: if all_conf.source_config["rotate_horizontal_components"]: fls_to_remove = glob(os.path.join(it_dir, "corr", "*MX[E,N]*MX[E,N]*.sac")) fls_to_remove.extend(glob(os.path.join(it_dir, "corr", "*MX[E,N]*MXZ*.sac"))) fls_to_remove.extend(glob(os.path.join(it_dir, "corr", "*MXZ*MX[E,N]*.sac"))) for f in fls_to_remove: os.system("rm " + f) return()
def polarization_analysis(stream, win_len, win_frac, frqlow, frqhigh, stime, etime, verbose=False, timestamp="mlabday", method="pm", var_noise=0.0): """ Method carrying out polarization analysis with the Flinn, Jurkevics, ParticleMotion, or Vidale algorithm. :param stream: 3 component input data. :type stream: :class:`~obspy.core.stream.Stream` :param win_len: Sliding window length in seconds. :type win_len: float :param win_frac: Fraction of sliding window to use for step. :type win_frac: float :param var_noise: resembles a sphere of noise in PM where the 3C is excluded :type var_noise: float :param frqlow: lower frequency for PM :type frqlow: float :param frqhigh: higher frequency for PM :type frqhigh: float :param stime: Start time of interest :type stime: :class:`obspy.core.utcdatetime.UTCDateTime` :param etime: End time of interest :type etime: :class:`obspy.core.utcdatetime.UTCDateTime` :param timestamp: valid values: ``"julsec"`` and ``"mlabday"``; ``"julsec"`` returns the timestamp in seconds since ``1970-01-01T00:00:00``, ``"mlabday"`` returns the timestamp in days (decimals represent hours, minutes and seconds) since ``0001-01-01T00:00:00`` as needed for matplotlib date plotting (see e.g. matplotlibs num2date) :type timestamp: str :param method: the method to use. one of ``"pm"``, ``"flinn"`` or ``"vidale"``. :type method: str :returns: Dictionary with azimuth, incidence angle, errors, rectilinearity, planarity, and/or ellipticity (the returned values depend on the used method). """ if method.lower() not in ["pm", "flinn", "vidale"]: msg = "Invalid method ('%s')" % method raise ValueError(msg) res = [] # check that sampling rates do not vary fs = stream[0].stats.sampling_rate if len(stream) != len(stream.select(sampling_rate=fs)): msg = "in array sampling rates of traces in stream are not equal" raise ValueError(msg) if verbose: print("stream contains following traces:") print(stream) print("stime = " + str(stime) + ", etime = " + str(etime)) # offset of arrays spoint, _epoint = _get_s_point(stream, stime, etime) if method.lower() == "vidale": res = vidale_adapt(stream, var_noise, fs, frqlow, frqhigh, spoint, stime, etime) else: nsamp = int(win_len * fs) nstep = int(nsamp * win_frac) newstart = stime tap = cosine_taper(nsamp, p=0.22) offset = 0 while (newstart + (nsamp + nstep) / fs) < etime: try: data = [] Z = [] N = [] E = [] for i, tr in enumerate(stream): dat = tr.data[spoint[i] + offset: spoint[i] + offset + nsamp] dat = (dat - dat.mean()) * tap if "Z" in tr.stats.channel: Z = dat.copy() if "N" in tr.stats.channel: N = dat.copy() if "E" in tr.stats.channel: E = dat.copy() data.append(Z) data.append(N) data.append(E) except IndexError: break if method.lower() == "pm": azimuth, incidence, error_az, error_inc = \ particle_motion_odr(data, var_noise) if abs(error_az) < 0.1 and abs(error_inc) < 0.1: res.append(np.array([newstart.timestamp + nsamp / fs, azimuth, incidence, error_az, error_inc])) if method.lower() == "flinn": azimuth, incidence, reclin, plan = flinn(data, var_noise) res.append(np.array([newstart.timestamp + nsamp / fs, azimuth, incidence, reclin, plan])) if verbose: print(newstart, newstart + nsamp / fs, res[-1][1:]) offset += nstep newstart += nstep / fs res = np.array(res) # XXX: not used for any return if timestamp == "julsec": pass elif timestamp == "mlabday": # 719163 == hours between 1970 and 0001 + 1 res[:, 0] = res[:, 0] / (24. * 3600) + 719163 else: msg = "Option timestamp must be one of 'julsec', or 'mlabday'" raise ValueError(msg) npt = len(res[:, 0]) npt //= 2 if method.lower() == "pm": return { "azimuth": res[npt, 1], "incidence": res[npt, 2], "azimuth_error": res[npt, 3], "incidence_error": res[npt, 4] } elif method.lower() == "vidale": return { "azimuth": res[npt, 1], "incidence": res[npt, 2], "rectilinearity": res[npt, 3], "planarity": res[npt, 4], "ellipticity": res[npt, 5] } elif method.lower() == "flinn": return { "azimuth": res[npt, 1], "incidence": res[npt, 2], "rectilinearity": res[npt, 3], "planarity": res[npt, 4], } else: raise NotImplementedError
## add another sinusoid to signal with freq2 Hz #dat = dat + np.sin(freq2 * 2.0 * np.pi * temp + np.pi/3) #noise_amplitude = 0.7 ## add noise to the signal #dat = dat + np.random.randn(len(dat)) * noise_amplitude # determine max. amplitude of data (for plotting) maximum = max(dat) print('Before Taper') print('amplitude of first sample point:%6.1f' % dat[0]) print('amplitude of last sample point:%6.1f' % (dat[len(dat) - 1])) # percentage of taper applied [0. ; 1.] (initial: 0.1) taper_percentage = 0.1 # define taper window taper = cosine_taper(samp, taper_percentage) # taper the signal dat_taper = dat * taper print('After Taper') print('amplitude of first sample point:%6.1f' % dat_taper[0]) print('amplitude of last sample point:%6.1f' % (dat_taper[len(dat_taper) - 1])) # FFT data into frequency-domain Fdat = np.fft.rfft(dat, n=samp) Fdat_taper = np.fft.rfft(dat_taper, n=samp) # x-axis in f-domain for plotting xf = np.linspace(0.0, 1.0 / (2.0 * delta), (samp / 2) + 1) # plot plt.subplot(211)
def mwcs(ccCurrent, ccReference, fmin, fmax, sampRate, tmin, windL, step, plot=False): """... :type ccCurrent: :class:`numpy.ndarray` :param ccCurrent: The "Current" timeseries :type ccReference: :class:`numpy.ndarray` :param ccReference: The "Reference" timeseries :type fmin: float :param fmin: The lower frequency bound to compute the dephasing :type fmax: float :param fmax: The higher frequency bound to compute the dephasing :type sampRate: float :param sampRate: The sample rate of the input timeseries :type tmin: float :param tmin: The leftmost time lag (used to compute the "time lags array") :type windL: float :param windL: The moving window length :type step: float :param step: The step to jump for the moving window :type plot: bool :param plot: If True, plots the MWCS result for each window. Defaults to False :rtype: :class:`numpy.ndarray` :returns: [Taxis,deltaT,deltaErr,deltaMcoh]. Taxis contains the central times of the windows. The three other columns contain dt, error and mean coherence for each window. """ windL = np.int(windL * sampRate) step = np.int(step * sampRate) count = 0 deltaT = [] deltaErr = [] deltaMcoh = [] Taxis = [] padd = 2 ** (nextpow2(windL) + 2) padd = next_fast_len(windL) # Tentative checking if enough point are used to compute the FFT freqVec = scipy.fftpack.fftfreq(int(padd), 1. / sampRate)[:int(padd) // 2] indRange = np.argwhere(np.logical_and(freqVec >= fmin, freqVec <= fmax)) if len(indRange) < 2: padd = 2 ** (nextpow2(windL) + 3) tp = cosine_taper(windL, .85) timeaxis = (np.arange(len(ccCurrent)) / float(sampRate)) + tmin minind = 0 maxind = windL while maxind <= len(ccCurrent): ind = minind cci = ccCurrent[ind:(ind + windL)].copy() cci = scipy.signal.detrend(cci, type='linear') cci -= cci.min() cci /= cci.max() cci -= np.mean(cci) cci *= tp cri = ccReference[ind:(ind + windL)].copy() cri = scipy.signal.detrend(cri, type='linear') cri -= cri.min() cri /= cri.max() cri -= np.mean(cri) cri *= tp Fcur = scipy.fftpack.fft(cci, n=int(padd))[:int(padd) // 2] Fref = scipy.fftpack.fft(cri, n=int(padd))[:int(padd) // 2] Fcur2 = np.real(Fcur) ** 2 + np.imag(Fcur) ** 2 Fref2 = np.real(Fref) ** 2 + np.imag(Fref) ** 2 smoother = 5 dcur = np.sqrt(smooth(Fcur2, window='hanning', half_win=smoother)) dref = np.sqrt(smooth(Fref2, window='hanning', half_win=smoother)) # Calculate the cross-spectrum X = Fref * (Fcur.conj()) X = smooth(X, window='hanning', half_win=smoother) dcs = np.abs(X) # Find the values the frequency range of interest freqVec = scipy.fftpack.fftfreq(len(X) * 2, 1. / sampRate)[ :int(padd) // 2] indRange = np.argwhere(np.logical_and(freqVec >= fmin, freqVec <= fmax)) # Get Coherence and its mean value coh = getCoherence(dcs, dref, dcur) mcoh = np.mean(coh[indRange]) # Get Weights w = 1.0 / (1.0 / (coh[indRange] ** 2) - 1.0) w[coh[indRange] >= 0.99] = 1.0 / (1.0 / 0.9801 - 1.0) w = np.sqrt(w * np.sqrt(dcs[indRange])) # w /= (np.sum(w)/len(w)) #normalize w = np.real(w) # Frequency array: v = np.real(freqVec[indRange]) * 2 * np.pi vo = np.real(freqVec) * 2 * np.pi # Phase: phi = np.angle(X) phi[0] = 0. phi = np.unwrap(phi) # phio = phi.copy() phi = phi[indRange] # Calculate the slope with a weighted least square linear regression # forced through the origin # weights for the WLS must be the variance ! res = sm.regression.linear_model.WLS(phi, v, w ** 2).fit() # print "forced", np.real(res.params[0]) # print "!forced", np.real(res2.params[0]) m = np.real(res.params[0]) deltaT.append(m) # print phi.shape, v.shape, w.shape e = np.sum((phi - m * v) ** 2) / (np.size(v) - 1) s2x2 = np.sum(v ** 2 * w ** 2) sx2 = np.sum(w * v ** 2) e = np.sqrt(e * s2x2 / sx2 ** 2) # print w.shape if plot: plt.figure() plt.suptitle('%.1fs' % (timeaxis[ind + windL // 2])) plt.subplot(311) plt.plot(cci) plt.plot(cri) ax = plt.subplot(312) plt.plot(vo / (2 * np.pi), phio) plt.scatter(v / (2 * np.pi), phi, c=w, edgecolor='none', vmin=0.6, vmax=1) plt.subplot(313, sharex=ax) plt.plot(v / (2 * np.pi), coh[indRange]) plt.axhline(mcoh, c='r') plt.axhline(1.0, c='k', ls='--') plt.xlim(-0.1, 1.5) plt.ylim(0, 1.5) plt.show() deltaErr.append(e) deltaMcoh.append(np.real(mcoh)) Taxis.append(timeaxis[ind + windL // 2]) count += 1 minind += step maxind += step del Fcur, Fref del X del freqVec del indRange del w, v, e, s2x2 del res if maxind > len(ccCurrent) + step: logging.warning("The last window was too small, but was computed") return np.array([Taxis, deltaT, deltaErr, deltaMcoh]).T
def array_processing(stream, win_len, win_frac, sll_x, slm_x, sll_y, slm_y, sl_s, semb_thres, vel_thres, frqlow, frqhigh, stime, etime, prewhiten, verbose=False, coordsys='lonlat', timestamp='mlabday', method=0, store=None): """ Method for Seismic-Array-Beamforming/FK-Analysis/Capon :param stream: Stream object, the trace.stats dict like class must contain an :class:`~obspy.core.util.attribdict.AttribDict` with 'latitude', 'longitude' (in degrees) and 'elevation' (in km), or 'x', 'y', 'elevation' (in km) items/attributes. See param ``coordsys``. :type win_len: float :param win_len: Sliding window length in seconds :type win_frac: float :param win_frac: Fraction of sliding window to use for step :type sll_x: float :param sll_x: slowness x min (lower) :type slm_x: float :param slm_x: slowness x max :type sll_y: float :param sll_y: slowness y min (lower) :type slm_y: float :param slm_y: slowness y max :type sl_s: float :param sl_s: slowness step :type semb_thres: float :param semb_thres: Threshold for semblance :type vel_thres: float :param vel_thres: Threshold for velocity :type frqlow: float :param frqlow: lower frequency for fk/capon :type frqhigh: float :param frqhigh: higher frequency for fk/capon :type stime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param stime: Start time of interest :type etime: :class:`~obspy.core.utcdatetime.UTCDateTime` :param etime: End time of interest :type prewhiten: int :param prewhiten: Do prewhitening, values: 1 or 0 :param coordsys: valid values: 'lonlat' and 'xy', choose which stream attributes to use for coordinates :type timestamp: str :param timestamp: valid values: 'julsec' and 'mlabday'; 'julsec' returns the timestamp in seconds since 1970-01-01T00:00:00, 'mlabday' returns the timestamp in days (decimals represent hours, minutes and seconds) since '0001-01-01T00:00:00' as needed for matplotlib date plotting (see e.g. matplotlib's num2date) :type method: int :param method: the method to use 0 == bf, 1 == capon :type store: function :param store: A custom function which gets called on each iteration. It is called with the relative power map and the time offset as first and second arguments and the iteration number as third argument. Useful for storing or plotting the map for each iteration. For this purpose the dump function of this module can be used. :return: :class:`numpy.ndarray` of timestamp, relative relpow, absolute relpow, backazimuth, slowness """ res = [] eotr = True # check that sampling rates do not vary fs = stream[0].stats.sampling_rate if len(stream) != len(stream.select(sampling_rate=fs)): msg = 'in sonic sampling rates of traces in stream are not equal' raise ValueError(msg) grdpts_x = int(((slm_x - sll_x) / sl_s + 0.5) + 1) grdpts_y = int(((slm_y - sll_y) / sl_s + 0.5) + 1) geometry = get_geometry(stream, coordsys=coordsys, verbose=verbose) if verbose: print("geometry:") print(geometry) print("stream contains following traces:") print(stream) print("stime = " + str(stime) + ", etime = " + str(etime)) time_shift_table = get_timeshift(geometry, sll_x, sll_y, sl_s, grdpts_x, grdpts_y) # offset of arrays spoint, _epoint = get_spoint(stream, stime, etime) # # loop with a sliding window over the dat trace array and apply bbfk # nstat = len(stream) fs = stream[0].stats.sampling_rate nsamp = int(win_len * fs) nstep = int(nsamp * win_frac) # generate plan for rfftr nfft = next_pow_2(nsamp) deltaf = fs / float(nfft) nlow = int(frqlow / float(deltaf) + 0.5) nhigh = int(frqhigh / float(deltaf) + 0.5) nlow = max(1, nlow) # avoid using the offset nhigh = min(nfft // 2 - 1, nhigh) # avoid using nyquist nf = nhigh - nlow + 1 # include upper and lower frequency # to speed up the routine a bit we estimate all steering vectors in advance steer = np.empty((nf, grdpts_x, grdpts_y, nstat), dtype=np.complex128) clibsignal.calcSteer(nstat, grdpts_x, grdpts_y, nf, nlow, deltaf, time_shift_table, steer) _r = np.empty((nf, nstat, nstat), dtype=np.complex128) ft = np.empty((nstat, nf), dtype=np.complex128) newstart = stime # 0.22 matches 0.2 of historical C bbfk.c tap = cosine_taper(nsamp, p=0.22) offset = 0 relpow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64) abspow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64) while eotr: try: for i, tr in enumerate(stream): dat = tr.data[spoint[i] + offset: spoint[i] + offset + nsamp] dat = (dat - dat.mean()) * tap ft[i, :] = np.fft.rfft(dat, nfft)[nlow:nlow + nf] except IndexError: break ft = np.ascontiguousarray(ft, np.complex128) relpow_map.fill(0.) abspow_map.fill(0.) # computing the covariances of the signal at different receivers dpow = 0. for i in range(nstat): for j in range(i, nstat): _r[:, i, j] = ft[i, :] * ft[j, :].conj() if method == 1: _r[:, i, j] /= np.abs(_r[:, i, j].sum()) if i != j: _r[:, j, i] = _r[:, i, j].conjugate() else: dpow += np.abs(_r[:, i, j].sum()) dpow *= nstat if method == 1: # P(f) = 1/(e.H R(f)^-1 e) for n in range(nf): _r[n, :, :] = np.linalg.pinv(_r[n, :, :], rcond=1e-6) errcode = clibsignal.generalizedBeamformer( relpow_map, abspow_map, steer, _r, nstat, prewhiten, grdpts_x, grdpts_y, nf, dpow, method) if errcode != 0: msg = 'generalizedBeamforming exited with error %d' raise Exception(msg % errcode) ix, iy = np.unravel_index(relpow_map.argmax(), relpow_map.shape) relpow, abspow = relpow_map[ix, iy], abspow_map[ix, iy] if store is not None: store(relpow_map, abspow_map, offset) # here we compute baz, slow slow_x = sll_x + ix * sl_s slow_y = sll_y + iy * sl_s slow = np.sqrt(slow_x ** 2 + slow_y ** 2) if slow < 1e-8: slow = 1e-8 azimut = 180 * math.atan2(slow_x, slow_y) / math.pi baz = azimut % -360 + 180 if relpow > semb_thres and 1. / slow > vel_thres: res.append(np.array([newstart.timestamp, relpow, abspow, baz, slow])) if verbose: print(newstart, (newstart + (nsamp / fs)), res[-1][1:]) if (newstart + (nsamp + nstep) / fs) > etime: eotr = False offset += nstep newstart += nstep / fs res = np.array(res) if timestamp == 'julsec': pass elif timestamp == 'mlabday': # 719163 == days between 1970 and 0001 + 1 res[:, 0] = res[:, 0] / (24. * 3600) + 719163 else: msg = "Option timestamp must be one of 'julsec', or 'mlabday'" raise ValueError(msg) return np.array(res)
def g1g2_kern(wf1str,wf2str,kernel,adjt, src,source_conf,insta): measr_conf = json.load(open(os.path.join(source_conf['source_path'], 'measr_config.json'))) bandpass = measr_conf['bandpass'] if bandpass == None: filtcnt = 1 elif type(bandpass) == list: if type(bandpass[0]) != list: filtcnt = 1 else: filtcnt = len(bandpass) ntime, n, n_corr, Fs = get_ns(wf1str,source_conf,insta) # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off whereever the solver stopped running. taper = cosine_taper(ntime,p=0.01) taper[0:ntime//2] = 1.0 ######################################################################## # Prepare filenames and adjoint sources ######################################################################## filenames = [] adjt_srcs = [] adjt_srcs_cnt = 0 for ix_f in range(filtcnt): filename = kernel+'.{}.npy'.format(ix_f) filenames.append(filename) #if os.path.exists(filename): # continue f = Stream() for a in adjt: adjtfile = a + '*.{}.sac'.format(ix_f) adjtfile = glob(adjtfile) try: f += read(adjtfile[0])[0] f[-1].data = my_centered(f[-1].data,n_corr) adjt_srcs_cnt += 1 except IndexError: print('No adjoint source found: {}\n'.format(a)) break adjt_srcs.append(f) ######################################################################## # Compute the kernels ######################################################################## with NoiseSource(src) as nsrc: ntraces = nsrc.src_loc[0].shape[0] if insta: # open database dbpath = json.load(open(os.path.join(source_conf['project_path'], 'config.json')))['wavefield_path'] # open and determine Fs, nt db = instaseis.open_db(dbpath) # get receiver locations lat1 = geograph_to_geocent(float(wf1[2])) lon1 = float(wf1[3]) rec1 = instaseis.Receiver(latitude=lat1,longitude=lon1) lat2 = geograph_to_geocent(float(wf2[2])) lon2 = float(wf2[3]) rec2 = instaseis.Receiver(latitude=lat2,longitude=lon2) else: wf1 = WaveField(wf1str) wf2 = WaveField(wf2str) kern = np.zeros((filtcnt,ntraces,len(adjt))) ######################################################################## # Loop over locations ######################################################################## for i in range(ntraces): # noise source spectrum at this location # For the kernel, this contains only the basis functions of the # spectrum without weights; might still be location-dependent, # for example when constraining sensivity to ocean S = nsrc.get_spect(i) if S.sum() == 0.: # The spectrum has 0 phase so only checking absolute value here continue #################################################################### # Get synthetics #################################################################### if insta: # get source locations lat_src = geograph_to_geocent(nsrc.src_loc[1,i]) lon_src = nsrc.src_loc[0,i] fsrc = instaseis.ForceSource(latitude=lat_src, longitude=lon_src,f_r=1.e12) s1 = np.ascontiguousarray(db.get_seismograms(source=fsrc, receiver=rec1, dt=1./source_conf['sampling_rate'])[0].data*taper) s2 = np.ascontiguousarray(db.get_seismograms(source=fsrc, receiver=rec2, dt=1./source_conf['sampling_rate'])[0].data*taper) else: s1 = np.ascontiguousarray(wf1.data[i,:]*taper) s2 = np.ascontiguousarray(wf2.data[i,:]*taper) spec1 = np.fft.rfft(s1,n) spec2 = np.fft.rfft(s2,n) g1g2_tr = np.multiply(np.conjugate(spec1),spec2) c = np.multiply(g1g2_tr,S) ####################################################################### # Get Kernel at that location ####################################################################### corr_temp = my_centered(np.fft.ifftshift(np.fft.irfft(c,n)),n_corr) ####################################################################### # Apply the 'adjoint source' ####################################################################### for ix_f in range(filtcnt): f = adjt_srcs[ix_f] if f==None: continue for j in range(len(f)): delta = f[j].stats.delta kern[ix_f,i,j] = np.dot(corr_temp,f[j].data) * delta #elif measr_conf['mtype'] in ['envelope']: # if j == 0: # corr_temp_h = corr_temp # print(corr_temp_h) # if j == 1: # corr_temp_h = hilbert(corr_temp) # print(corr_temp_h) # # kern[ix_f,i,j] = np.dot(corr_temp,f[j].data) * delta if i%50000 == 0: print("Finished {} source locations.".format(i)) if not insta: wf1.file.close() wf2.file.close() for ix_f in range(filtcnt): filename = filenames[ix_f] if kern[ix_f,:,:].sum() != 0: np.save(filename,kern[ix_f,:,:]) return()
def deconvolve_traces_autocorr(signal, divisor, eps, freq=[], residual=False): """ Deconvolve a time series from a set of time series. This function is imported from miic https://github.com/miic-sw/miic The function is a wrapper for the :class:`scipy.signal.convolve` function. :type signal: :class:`~obspy.core.stream.Stream` :param signal: signal from which the divisor is to be deconvolved :type divisor: :class:`~obspy.core.trace.Trace` :param divisor: time series that is to be deconvolved from signal :type eps: float :param eps: fraction of spectral mean used as a water level to avoid spectral holes in the deconvolution. :type freq: two element array-like :param freq: frequency range for the estimation of the mean power that is scaled with ``eps`` to obtian the water level :type residual: bool :param residual: return residual if True, defaults to False :rtype: obspy.core.stream :return: **(dcst, rst)**: decorrelated stream and residual (only if ``residual=True`` """ #zerotime = UTCDateTime(1971,1,1) # trace length is taken from signal (must be even to use real fft) if signal[0].stats['npts'] % 2: trlen = signal[0].stats['npts']+1 delsamp=True else: trlen = signal[0].stats['npts'] delsamp=False delta = divisor.stats['delta'] # prepare divisor divisor.detrend(type='constant') divisor.trim(starttime=divisor.stats['starttime'],endtime=divisor.stats['starttime']+ (trlen-1)*delta,pad=True,fill_value=0,nearest_sample=False) # FFT divisor fd = np.fft.rfft(divisor.data) # estimate the waterlevel to stabilize deconvolution if freq: f = np.linspace(-signal[0].stats['sampling_rate']/2., signal[0].stats['sampling_rate']/2.,len(fd)) ind = np.nonzero(np.all([f>freq[0],f<freq[1]],axis=0)) wl = eps * np.mean((fd*fd.conj())[ind]) else: wl = eps * np.mean((fd*fd.conj())) # create the output stream dcst = Stream() rst = Stream() for tr in signal: if tr.stats['sampling_rate'] != divisor.stats['sampling_rate']: print("Sampling rates don't match for \n %s") % tr continue # prepare nuerator tr.detrend('constant') tr.trim(starttime=tr.stats['starttime'], endtime=tr.stats['starttime']+ (trlen-1)*delta,pad=True,fill_value=0,nearest_sample=False) taper = cosine_taper(tr.stats['npts']) tr.data *= taper # fft numerator sf = np.fft.rfft(tr.data) # calculate deconvolution fdc = sf*fd/(fd**2+wl) dc = np.fft.irfft(fdc) # template to hold results dctr = tr.copy() # propagate metadata dctr.stats = tr.stats#combine_stats(tr,divisor) if delsamp: dc=np.delete(dc,len(dc)-1) dctr.data = dc dctr.stats['npts'] = len(dc) #dctr.stats['starttime'] = tr.stats['starttime']#zerotime - (divisor.stats['starttime']-tr.stats['starttime']) #dctr.stats_tr1 = tr.stats #dctr.stats_tr2 = divisor.stats # append to output stream dcst.append(dctr) if residual: # residual rtr = dctr.copy() rtr.data = tr.data - np.fft.irfft(fdc * fd) # append to output stream rst.append(rtr) return (dcst, rst) return dcst
def g1g2_corr(wf1,wf2,corr_file,src,source_conf,insta): """ Compute noise cross-correlations from two .h5 'wavefield' files. Noise source distribution and spectrum is given by starting_model.h5 It is assumed that noise sources are delta-correlated in space. """ #ToDo: check whether to include autocorrs from user (now hardcoded off) #ToDo: Parallel loop(s) #ToDo tests # Metainformation: Include the reference station names for both stations # from wavefield files, if possible. Do not include geographic information # from .csv file as this might be error-prone. Just add the geographic # info later if needed. with NoiseSource(src) as nsrc: ntime, n, n_corr, Fs = get_ns(wf1,source_conf,insta) # use a one-sided taper: The seismogram probably has a non-zero end, # being cut off whereever the solver stopped running. taper = cosine_taper(ntime,p=0.01) taper[0:ntime//2] = 1.0 ntraces = nsrc.src_loc[0].shape[0] print(taper.shape) correlation = np.zeros(n_corr) if insta: # open database dbpath = json.load(open(os.path.join(source_conf['project_path'], 'config.json')))['wavefield_path'] # open and determine Fs, nt db = instaseis.open_db(dbpath) # get receiver locations lat1 = geograph_to_geocent(float(wf1[2])) lon1 = float(wf1[3]) rec1 = instaseis.Receiver(latitude=lat1,longitude=lon1) lat2 = geograph_to_geocent(float(wf2[2])) lon2 = float(wf2[3]) rec2 = instaseis.Receiver(latitude=lat2,longitude=lon2) else: wf1 = WaveField(wf1) wf2 = WaveField(wf2) # Loop over source locations for i in range(ntraces): # noise source spectrum at this location S = nsrc.get_spect(i) if S.sum() == 0.: #If amplitude is 0, continue. (Spectrum has 0 phase anyway. ) continue if insta: # get source locations lat_src = geograph_to_geocent(nsrc.src_loc[1,i]) lon_src = nsrc.src_loc[0,i] fsrc = instaseis.ForceSource(latitude=lat_src, longitude=lon_src,f_r=1.e12) s1 = np.ascontiguousarray(db.get_seismograms(source=fsrc, receiver=rec1, dt=1./source_conf['sampling_rate'])[0].data*taper) s2 = np.ascontiguousarray(db.get_seismograms(source=fsrc, receiver=rec2, dt=1./source_conf['sampling_rate'])[0].data*taper) else: # read Green's functions s1 = np.ascontiguousarray(wf1.data[i,:]*taper) s2 = np.ascontiguousarray(wf2.data[i,:]*taper) # Fourier transform for greater ease of convolution spec1 = np.fft.rfft(s1,n) spec2 = np.fft.rfft(s2,n) # convolve G1G2 g1g2_tr = np.multiply(np.conjugate(spec1),spec2) # convolve noise source c = np.multiply(g1g2_tr,S) # transform back correlation += my_centered(np.fft.ifftshift(np.fft.irfft(c,n)), n_corr) * nsrc.surf_area[i] # occasional info if i%50000 == 0: print("Finished {} source locations.".format(i)) ###################### end of loop over all source locations ################### if not insta: wf1.file.close() wf2.file.close() # save output trace = Trace() trace.stats.sampling_rate = Fs trace.data = correlation # try to add some meta data try: sta1 = wf1.stats['reference_station'] sta2 = wf2.stats['reference_station'] trace.stats.station = sta1.split('.')[1] trace.stats.network = sta1.split('.')[0] trace.stats.location = sta1.split('.')[2] trace.stats.channel = sta1.split('.')[3] trace.stats.sac = {} trace.stats.sac['kuser0'] = sta2.split('.')[1] trace.stats.sac['kuser1'] = sta2.split('.')[0] trace.stats.sac['kuser2'] = sta2.split('.')[2] trace.stats.sac['kevnm'] = sta2.split('.')[3] except: pass trace.write(filename=corr_file,format='SAC')
def mwcs(current, reference, freqmin, freqmax, df, tmin, window_length, step, smoothing_half_win=5): """The `current` time series is compared to the `reference`. Both time series are sliced in several overlapping windows. Each slice is mean-adjusted and cosine-tapered (85% taper) before being Fourier- transformed to the frequency domain. :math:`F_{cur}(\\nu)` and :math:`F_{ref}(\\nu)` are the first halves of the Hermitian symmetric Fourier-transformed segments. The cross-spectrum :math:`X(\\nu)` is defined as :math:`X(\\nu) = F_{ref}(\\nu) F_{cur}^*(\\nu)` in which :math:`{}^*` denotes the complex conjugation. :math:`X(\\nu)` is then smoothed by convolution with a Hanning window. The similarity of the two time-series is assessed using the cross-coherency between energy densities in the frequency domain: :math:`C(\\nu) = \\frac{|\overline{X(\\nu))}|}{\sqrt{|\overline{F_{ref}(\\nu)|^2} |\overline{F_{cur}(\\nu)|^2}}}` in which the over-line here represents the smoothing of the energy spectra for :math:`F_{ref}` and :math:`F_{cur}` and of the spectrum of :math:`X`. The mean coherence for the segment is defined as the mean of :math:`C(\\nu)` in the frequency range of interest. The time-delay between the two cross correlations is found in the unwrapped phase, :math:`\phi(\nu)`, of the cross spectrum and is linearly proportional to frequency: :math:`\phi_j = m. \nu_j, m = 2 \pi \delta t` The time shift for each window between two signals is the slope :math:`m` of a weighted linear regression of the samples within the frequency band of interest. The weights are those introduced by [Clarke2011]_, which incorporate both the cross-spectral amplitude and cross-coherence, unlike [Poupinet1984]_. The errors are estimated using the weights (thus the coherence) and the squared misfit to the modelled slope: :math:`e_m = \sqrt{\sum_j{(\\frac{w_j \\nu_j}{\sum_i{w_i \\nu_i^2}})^2}\sigma_{\phi}^2}` where :math:`w` are weights, :math:`\\nu` are cross-coherences and :math:`\sigma_{\phi}^2` is the squared misfit of the data to the modelled slope and is calculated as :math:`\sigma_{\phi}^2 = \\frac{\sum_j(\phi_j - m \\nu_j)^2}{N-1}` The output of this process is a table containing, for each moving window: the central time lag, the measured delay, its error and the mean coherence of the segment. .. warning:: The time series will not be filtered before computing the cross-spectrum! They should be band-pass filtered around the `freqmin`-`freqmax` band of interest beforehand. :type current: :class:`numpy.ndarray` :param current: The "Current" timeseries :type reference: :class:`numpy.ndarray` :param reference: The "Reference" timeseries :type freqmin: float :param freqmin: The lower frequency bound to compute the dephasing (in Hz) :type freqmax: float :param freqmax: The higher frequency bound to compute the dephasing (in Hz) :type df: float :param df: The sampling rate of the input timeseries (in Hz) :type tmin: float :param tmin: The leftmost time lag (used to compute the "time lags array") :type window_length: float :param window_length: The moving window length (in seconds) :type step: float :param step: The step to jump for the moving window (in seconds) :type smoothing_half_win: int :param smoothing_half_win: If different from 0, defines the half length of the smoothing hanning window. :rtype: :class:`numpy.ndarray` :returns: [time_axis,delta_t,delta_err,delta_mcoh]. time_axis contains the central times of the windows. The three other columns contain dt, error and mean coherence for each window. """ delta_t = [] delta_err = [] delta_mcoh = [] time_axis = [] window_length_samples = np.int(window_length * df) # try: # from scipy.fftpack.helper import next_fast_len # except ImportError: # from obspy.signal.util import next_pow_2 as next_fast_len from msnoise.api import nextpow2 padd = int(2 ** (nextpow2(window_length_samples) + 2)) # padd = next_fast_len(window_length_samples) count = 0 tp = cosine_taper(window_length_samples, 0.85) minind = 0 maxind = window_length_samples while maxind <= len(current): cci = current[minind:(minind + window_length_samples)] cci = scipy.signal.detrend(cci, type='linear') cci *= tp cri = reference[minind:(minind + window_length_samples)] cri = scipy.signal.detrend(cri, type='linear') cri *= tp minind += int(step*df) maxind += int(step*df) fcur = scipy.fftpack.fft(cci, n=padd)[:padd // 2] fref = scipy.fftpack.fft(cri, n=padd)[:padd // 2] fcur2 = np.real(fcur) ** 2 + np.imag(fcur) ** 2 fref2 = np.real(fref) ** 2 + np.imag(fref) ** 2 # Calculate the cross-spectrum X = fref * (fcur.conj()) if smoothing_half_win != 0: dcur = np.sqrt(smooth(fcur2, window='hanning', half_win=smoothing_half_win)) dref = np.sqrt(smooth(fref2, window='hanning', half_win=smoothing_half_win)) X = smooth(X, window='hanning', half_win=smoothing_half_win) else: dcur = np.sqrt(fcur2) dref = np.sqrt(fref2) dcs = np.abs(X) # Find the values the frequency range of interest freq_vec = scipy.fftpack.fftfreq(len(X) * 2, 1. / df)[:padd // 2] index_range = np.argwhere(np.logical_and(freq_vec >= freqmin, freq_vec <= freqmax)) # Get Coherence and its mean value coh = getCoherence(dcs, dref, dcur) mcoh = np.mean(coh[index_range]) # Get Weights w = 1.0 / (1.0 / (coh[index_range] ** 2) - 1.0) w[coh[index_range] >= 0.99] = 1.0 / (1.0 / 0.9801 - 1.0) w = np.sqrt(w * np.sqrt(dcs[index_range])) w = np.real(w) # Frequency array: v = np.real(freq_vec[index_range]) * 2 * np.pi # Phase: phi = np.angle(X) phi[0] = 0. phi = np.unwrap(phi) phi = phi[index_range] # Calculate the slope with a weighted least square linear regression # forced through the origin # weights for the WLS must be the variance ! m, em = linear_regression(v.flatten(), phi.flatten(), w.flatten()) delta_t.append(m) # print phi.shape, v.shape, w.shape e = np.sum((phi - m * v) ** 2) / (np.size(v) - 1) s2x2 = np.sum(v ** 2 * w ** 2) sx2 = np.sum(w * v ** 2) e = np.sqrt(e * s2x2 / sx2 ** 2) delta_err.append(e) delta_mcoh.append(np.real(mcoh)) time_axis.append(tmin+window_length/2.+count*step) count += 1 del fcur, fref del X del freq_vec del index_range del w, v, e, s2x2, sx2, m, em if maxind > len(current) + step*df: logging.warning("The last window was too small, but was computed") return np.array([time_axis, delta_t, delta_err, delta_mcoh]).T
df = wf.stats['Fs'] nt = wf.stats['nt'] else: df = float(input('Sampling rate of synthetic Greens functions in Hz?\n')) nt = int(input('Nr of time steps in synthetic Greens functions?\n')) #s for the fft is larger due to zeropadding --> apparent higher frequency sampling\n", # n = next_fast_len(2*nt-1) n = next_fast_len(2*nt-1) freq = np.fft.rfftfreq(n,d=1./df) taper = cosine_taper(len(freq),0.01) print('Determined frequency axis.') def get_distance(grid,location): def f(lat,lon,location): return abs(gps2dist_azimuth(lat,lon,location[0],location[1])[0]) dist = np.array([f(lat,lon,location) for lat,lon in zip(grid[1],grid[0])]) return dist # Use Basemap to figure out where ocean is def get_ocean_mask(): print('Getting ocean mask...') from mpl_toolkits.basemap import Basemap latmin = grd[1].min() latmax = grd[1].max() lonmin = grd[0].min()
def xcorr_pick_correction(pick1, trace1, pick2, trace2, t_before, t_after, cc_maxlag, filter=None, filter_options={}, plot=False, filename=None): """ Calculate the correction for the differential pick time determined by cross correlation of the waveforms in narrow windows around the pick times. For details on the fitting procedure refer to [Deichmann1992]_. The parameters depend on the epicentral distance and magnitude range. For small local earthquakes (Ml ~0-2, distance ~3-10 km) with consistent manual picks the following can be tried:: t_before=0.05, t_after=0.2, cc_maxlag=0.10, filter="bandpass", filter_options={'freqmin': 1, 'freqmax': 20} The appropriate parameter sets can and should be determined/verified visually using the option `plot=True` on a representative set of picks. To get the corrected differential pick time calculate: ``((pick2 + pick2_corr) - pick1)``. To get a corrected differential travel time using origin times for both events calculate: ``((pick2 + pick2_corr - ot2) - (pick1 - ot1))`` :type pick1: :class:`~obspy.core.utcdatetime.UTCDateTime` :param pick1: Time of pick for `trace1`. :type trace1: :class:`~obspy.core.trace.Trace` :param trace1: Waveform data for `pick1`. Add some time at front/back. The appropriate part of the trace is used automatically. :type pick2: :class:`~obspy.core.utcdatetime.UTCDateTime` :param pick2: Time of pick for `trace2`. :type trace2: :class:`~obspy.core.trace.Trace` :param trace2: Waveform data for `pick2`. Add some time at front/back. The appropriate part of the trace is used automatically. :type t_before: float :param t_before: Time to start cross correlation window before pick times in seconds. :type t_after: float :param t_after: Time to end cross correlation window after pick times in seconds. :type cc_maxlag: float :param cc_maxlag: Maximum lag/shift time tested during cross correlation in seconds. :type filter: str :param filter: `None` for no filtering or name of filter type as passed on to :meth:`~obspy.core.Trace.trace.filter` if filter should be used. To avoid artifacts in filtering provide sufficiently long time series for `trace1` and `trace2`. :type filter_options: dict :param filter_options: Filter options that get passed on to :meth:`~obspy.core.Trace.trace.filter` if filtering is used. :type plot: bool :param plot: If `True`, a plot window illustrating the alignment of the two traces at best cross correlation will be shown. This can and should be used to verify the used parameters before running automatedly on large data sets. :type filename: str :param filename: If plot option is selected, specifying a filename here (e.g. 'myplot.pdf' or 'myplot.png') will output the plot to a file instead of opening a plot window. :rtype: (float, float) :returns: Correction time `pick2_corr` for `pick2` pick time as a float and corresponding correlation coefficient. """ # perform some checks on the traces if trace1.stats.sampling_rate != trace2.stats.sampling_rate: msg = "Sampling rates do not match: %s != %s" % \ (trace1.stats.sampling_rate, trace2.stats.sampling_rate) raise Exception(msg) if trace1.id != trace2.id: msg = "Trace ids do not match: %s != %s" % (trace1.id, trace2.id) warnings.warn(msg) samp_rate = trace1.stats.sampling_rate # don't modify existing traces with filters if filter: trace1 = trace1.copy() trace2 = trace2.copy() # check data, apply filter and take correct slice of traces slices = [] for _i, (t, tr) in enumerate(((pick1, trace1), (pick2, trace2))): start = t - t_before - (cc_maxlag / 2.0) end = t + t_after + (cc_maxlag / 2.0) duration = end - start # check if necessary time spans are present in data if tr.stats.starttime > start: msg = "Trace %s starts too late." % _i raise Exception(msg) if tr.stats.endtime < end: msg = "Trace %s ends too early." % _i raise Exception(msg) if filter and start - tr.stats.starttime < duration: msg = "Artifacts from signal processing possible. Trace " + \ "%s should have more additional data at the start." % _i warnings.warn(msg) if filter and tr.stats.endtime - end < duration: msg = "Artifacts from signal processing possible. Trace " + \ "%s should have more additional data at the end." % _i warnings.warn(msg) # apply signal processing and take correct slice of data if filter: tr.data = tr.data.astype(np.float64) tr.detrend(type='demean') tr.data *= cosine_taper(len(tr), 0.1) tr.filter(type=filter, **filter_options) slices.append(tr.slice(start, end)) # cross correlate shift_len = int(cc_maxlag * samp_rate) cc = correlate(slices[0].data, slices[1].data, shift_len, method='direct') _cc_shift, cc_max = xcorr_max(cc) cc_curvature = np.concatenate((np.zeros(1), np.diff(cc, 2), np.zeros(1))) cc_convex = np.ma.masked_where(np.sign(cc_curvature) >= 0, cc) cc_concave = np.ma.masked_where(np.sign(cc_curvature) < 0, cc) # check results of cross correlation if cc_max < 0: msg = "Absolute maximum is negative: %.3f. " % cc_max + \ "Using positive maximum: %.3f" % max(cc) warnings.warn(msg) cc_max = max(cc) if cc_max < 0.8: msg = "Maximum of cross correlation lower than 0.8: %s" % cc_max warnings.warn(msg) # make array with time shifts in seconds corresponding to cc function cc_t = np.linspace(-cc_maxlag, cc_maxlag, shift_len * 2 + 1) # take the subportion of the cross correlation around the maximum that is # convex and fit a parabola. # use vertex as subsample resolution best cc fit. peak_index = cc.argmax() first_sample = peak_index # XXX this could be improved.. while first_sample > 0 and cc_curvature[first_sample - 1] <= 0: first_sample -= 1 last_sample = peak_index while last_sample < len(cc) - 1 and cc_curvature[last_sample + 1] <= 0: last_sample += 1 if first_sample == 0 or last_sample == len(cc) - 1: msg = "Fitting at maximum lag. Maximum lag time should be increased." warnings.warn(msg) # work on subarrays num_samples = last_sample - first_sample + 1 if num_samples < 3: msg = "Less than 3 samples selected for fit to cross " + \ "correlation: %s" % num_samples raise Exception(msg) if num_samples < 5: msg = "Less than 5 samples selected for fit to cross " + \ "correlation: %s" % num_samples warnings.warn(msg) # quadratic fit for small subwindow coeffs, residual = scipy.polyfit( cc_t[first_sample:last_sample + 1], cc[first_sample:last_sample + 1], deg=2, full=True)[:2] # check results of fit if coeffs[0] >= 0: msg = "Fitted parabola opens upwards!" warnings.warn(msg) if residual > 0.1: msg = "Residual in quadratic fit to cross correlation maximum " + \ "larger than 0.1: %s" % residual warnings.warn(msg) # X coordinate of vertex of parabola gives time shift to correct # differential pick time. Y coordinate gives maximum correlation # coefficient. dt = -coeffs[1] / 2.0 / coeffs[0] coeff = (4 * coeffs[0] * coeffs[2] - coeffs[1] ** 2) / (4 * coeffs[0]) # this is the shift to apply on the time axis of `trace2` to align the # traces. Actually we do not want to shift the trace to align it but we # want to correct the time of `pick2` so that the traces align without # shifting. This is the negative of the cross correlation shift. dt = -dt pick2_corr = dt # plot the results if selected if plot is True: with MatplotlibBackend(filename and "AGG" or None, sloppy=True): import matplotlib.pyplot as plt fig = plt.figure() ax1 = fig.add_subplot(211) tmp_t = np.linspace(0, len(slices[0]) / samp_rate, len(slices[0])) ax1.plot(tmp_t, slices[0].data / float(slices[0].data.max()), "k", label="Trace 1") ax1.plot(tmp_t, slices[1].data / float(slices[1].data.max()), "r", label="Trace 2") ax1.plot(tmp_t - dt, slices[1].data / float(slices[1].data.max()), "g", label="Trace 2 (shifted)") ax1.legend(loc="lower right", prop={'size': "small"}) ax1.set_title("%s" % slices[0].id) ax1.set_xlabel("time [s]") ax1.set_ylabel("norm. amplitude") ax2 = fig.add_subplot(212) ax2.plot(cc_t, cc_convex, ls="", marker=".", color="k", label="xcorr (convex)") ax2.plot(cc_t, cc_concave, ls="", marker=".", color="0.7", label="xcorr (concave)") ax2.plot(cc_t[first_sample:last_sample + 1], cc[first_sample:last_sample + 1], "b.", label="used for fitting") tmp_t = np.linspace(cc_t[first_sample], cc_t[last_sample], num_samples * 10) ax2.plot(tmp_t, scipy.polyval(coeffs, tmp_t), "b", label="fit") ax2.axvline(-dt, color="g", label="vertex") ax2.axhline(coeff, color="g") ax2.set_xlabel("%.2f at %.3f seconds correction" % (coeff, -dt)) ax2.set_ylabel("correlation coefficient") ax2.set_ylim(-1, 1) ax2.set_xlim(cc_t[0], cc_t[-1]) ax2.legend(loc="lower right", prop={'size': "x-small"}) # plt.legend(loc="lower left") if filename: fig.savefig(filename) else: plt.show() return (pick2_corr, coeff)
class args(object): def __init__(self): self.source_model = os.path.join('test', 'testdata_v1', 'testsource_v1') self.step = 0 self.steplengthrun = False, self.ignore_network = True args = args() all_config = config_params(args, comm, size, rank) m_a_options = {'g_speed': g_speed, 'window_params': window_params} m_func = rm.get_measure_func(mtype) all_ns = get_ns(all_config) ntime, n, n_corr, Fs = all_ns taper = cosine_taper(ntime, p=0.01) taper[0:ntime // 2] = 1.0 with NoiseSource(source_file) as n: surf_area = n.surf_area # sum the dimension that relates to filters grad = grad.sum(axis=1) # apply surface elements for integration for j in range(grad.shape[0]): grad[j, :, 0] *= surf_area # measurement obs = read(obsfile)[0] syn = read(synfile)[0]
def sleeman(stream): """ Sleeman method calculating the noise of each trace of input stream. :type stream: Stream object from module obspy.core.stream :param stream: Stream containing 3 traces with the same signal recorded. """ #Copy original stream m = stream.copy() #Verify stream if m[0].stats.sampling_rate != m[1].stats.sampling_rate \ or m[0].stats.sampling_rate != m[2].stats.sampling_rate \ or m[1].stats.sampling_rate != m[2].stats.sampling_rate: print("[pyColocSensors.sleeman]: Sampling rates are not identical \ between traces") if m[0].stats.npts != m[1].stats.npts \ or m[0].stats.npts != m[2].stats.npts \ or m[1].stats.npts != m[2].stats.npts: print("[pyColocSensors.sleeman]: Traces does not have the same length") if m[0].stats.starttime-m[1].stats.starttime >= m[0].stats.sampling_rate/2\ or m[1].stats.starttime-m[2].stats.starttime >= m[1].stats.sampling_rate/2\ or m[0].stats.starttime-m[2].stats.starttime >= m[0].stats.sampling_rate/2:\ print("[pyColocSensors.sleeman]: Traces does not have the same start time") #Set psd and csd parameters. Set as McNamara recommands in his paper, \ #except for windows length, staticaly fixed to 1024, to improve resolution #(doi: 10.1785/012003001) n_fft = 1024 n_overlap = int(n_fft*0.75) fs = m[0].stats.sampling_rate #Calculate psd and csd (P00,f) = mlab.psd(m[0].data,Fs=fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) (P11,f) = mlab.psd(m[1].data,Fs=fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) (P22,f) = mlab.psd(m[2].data,Fs =fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) (P01,f) = mlab.csd(m[0].data,m[1].data, Fs=fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) (P02,f) = mlab.csd(m[0].data,m[2].data, Fs=fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) P10 = np.conj(P01) (P12,f) = mlab.csd(m[1].data,m[2].data, Fs=fs,NFFT=n_fft,noverlap=n_overlap,\ detrend=detrend_func,window=cosine_taper(n_fft,p=0.2),scale_by_freq=True) P20 = np.conj(P02) P21 = np.conj(P12) #Apply corrections as McNamara recommends for spectra in [P00,P11,P22,P01,P02,P10,P12,P20,P21]: spectra = spectra*1.142857 #Calculate electronic noises acoording to Sleeman's method N0 = P00-P10*P02/P12 N1 = P11-P21*P10/P20 N2 = P22-P02*P21/P01 #Remove first samples (3%) in order to avoid poor resolution problems. #Samples remaining should be enough to cover one decade in frequency. N0 = N0[int(n_fft*0.03):] N1 = N1[int(n_fft*0.03):] N2 = N2[int(n_fft*0.03):] f = f[int(n_fft*0.03):] return (N0,N1,N2,f)
def remove_response2(self, inventory=None, output="VEL", water_level=60, pre_filt=None, zero_mean=True, taper=True, taper_fraction=0.05, plot=False, fig=None, **kwargs): """ EDIT OF OBSPY'S REMOVE_RESPONSE FUNCTION Including titles etc. Deconvolve instrument response. Uses the adequate :class:`obspy.core.inventory.response.Response` from the provided :class:`obspy.core.inventory.inventory.Inventory` data. Raises an exception if the response is not present. Note that there are two ways to prevent overamplification while convolving the inverted instrument spectrum: One possibility is to specify a water level which represents a clipping of the inverse spectrum and limits amplification to a certain maximum cut-off value (`water_level` in dB). The other possibility is to taper the waveform data in the frequency domain prior to multiplying with the inverse spectrum, i.e. perform a pre-filtering in the frequency domain (specifying the four corner frequencies of the frequency taper as a tuple in `pre_filt`). .. note:: Any additional kwargs will be passed on to :meth:`obspy.core.inventory.response.Response.get_evalresp_response`, see documentation of that method for further customization (e.g. start/stop stage). .. note:: Using :meth:`~Trace.remove_response` is equivalent to using :meth:`~Trace.simulate` with the identical response provided as a (dataless) SEED or RESP file and when using the same `water_level` and `pre_filt` (and options `sacsim=True` and `pitsasim=False` which influence very minor details in detrending and tapering). .. rubric:: Example >>> from obspy import read, read_inventory >>> st = read() >>> tr = st[0].copy() >>> inv = read_inventory() >>> tr.remove_response(inventory=inv) # doctest: +ELLIPSIS <...Trace object at 0x...> >>> tr.plot() # doctest: +SKIP .. plot:: from obspy import read, read_inventory st = read() tr = st[0] inv = read_inventory() tr.remove_response(inventory=inv) tr.plot() Using the `plot` option it is possible to visualize the individual steps during response removal in the frequency domain to check the chosen `pre_filt` and `water_level` options to stabilize the deconvolution of the inverted instrument response spectrum: >>> from obspy import read, read_inventory >>> st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed") >>> tr = st[0] >>> inv = read_inventory("/path/to/IU_ULN_00_LH1.xml") >>> pre_filt = [0.001, 0.005, 45, 50] >>> tr.remove_response(inventory=inv, pre_filt=pre_filt, output="DISP", ... water_level=60, plot=True) # doctest: +SKIP <...Trace object at 0x...> .. plot:: from obspy import read, read_inventory st = read("/path/to/IU_ULN_00_LH1_2015-07-18T02.mseed", "MSEED") tr = st[0] inv = read_inventory("/path/to/IU_ULN_00_LH1.xml", "STATIONXML") pre_filt = [0.001, 0.005, 45, 50] output = "DISP" tr.remove_response(inventory=inv, pre_filt=pre_filt, output=output, water_level=60, plot=True) :type inventory: :class:`~obspy.core.inventory.inventory.Inventory` or None. :param inventory: Station metadata to use in search for adequate response. If inventory parameter is not supplied, the response has to be attached to the trace with :meth:`Trace.attach_response` beforehand. :type output: str :param output: Output units. One of: ``"DISP"`` displacement, output unit is meters ``"VEL"`` velocity, output unit is meters/second ``"ACC"`` acceleration, output unit is meters/second**2 :type water_level: float :param water_level: Water level for deconvolution. :type pre_filt: list or tuple of four float :param pre_filt: Apply a bandpass filter in frequency domain to the data before deconvolution. The list or tuple defines the four corner frequencies `(f1, f2, f3, f4)` of a cosine taper which is one between `f2` and `f3` and tapers to zero for `f1 < f < f2` and `f3 < f < f4`. :type zero_mean: bool :param zero_mean: If `True`, the mean of the waveform data is subtracted in time domain prior to deconvolution. :type taper: bool :param taper: If `True`, a cosine taper is applied to the waveform data in time domain prior to deconvolution. :type taper_fraction: float :param taper_fraction: Taper fraction of cosine taper to use. :type plot: bool or str :param plot: If `True`, brings up a plot that illustrates how the data are processed in the frequency domain in three steps. First by `pre_filt` frequency domain tapering, then by inverting the instrument response spectrum with or without `water_level` and finally showing data with inverted instrument response multiplied on it in frequency domain. It also shows the comparison of raw/corrected data in time domain. If a `str` is provided then the plot is saved to file (filename must have a valid image suffix recognizable by matplotlib e.g. '.png'). """ # limit_numpy_fft_cache() from obspy.core.inventory import PolynomialResponseStage from obspy.signal.invsim import (cosine_taper, cosine_sac_taper, invert_spectrum) if plot: import matplotlib.pyplot as plt if (isinstance(inventory, (str, native_str)) and inventory.upper() in ("DISP", "VEL", "ACC")): from obspy.core.util.deprecation_helpers import ObsPyDeprecationWarning output = inventory inventory = None msg = ("The order of optional parameters in method " "remove_response has changed. 'output' is not accepted " "as first positional argument in the next release.") warnings.warn(msg, category=ObsPyDeprecationWarning, stacklevel=3) response = self._get_response(inventory) # polynomial response using blockette 62 stage 0 if not response.response_stages and response.instrument_polynomial: coefficients = response.instrument_polynomial.coefficients self.data = np.poly1d(coefficients[::-1])(self.data) return self # polynomial response using blockette 62 stage 1 and no other stages if len(response.response_stages) == 1 and isinstance(response.response_stages[0], PolynomialResponseStage): # check for gain if response.response_stages[0].stage_gain is None: msg = 'Stage gain not defined for %s - setting it to 1.0' warnings.warn(msg % self.id) gain = 1 else: gain = response.response_stages[0].stage_gain coefficients = response.response_stages[0].coefficients[:] for i in range(len(coefficients)): coefficients[i] /= math.pow(gain, i) self.data = np.poly1d(coefficients[::-1])(self.data) return self # use evalresp data = self.data.astype(np.float64) npts = len(data) # time domain pre-processing if zero_mean: data -= data.mean() if taper: data *= cosine_taper(npts, taper_fraction, sactaper=True, halfcosine=False) if plot: fontsz = 12 color1 = "blue" color2 = "red" bbox = dict(boxstyle="round", fc="w", alpha=1, ec="w") bbox1 = dict(boxstyle="round", fc="blue", alpha=0.15) bbox2 = dict(boxstyle="round", fc="red", alpha=0.15) if fig is None: fig = plt.figure(figsize=(14, 10)) fig.suptitle(str(self), fontsize=fontsz) ax1 = fig.add_subplot(321) ax1b = ax1.twinx() ax2 = fig.add_subplot(323, sharex=ax1) ax2b = ax2.twinx() ax3 = fig.add_subplot(325, sharex=ax1) ax3b = ax3.twinx() ax4 = fig.add_subplot(322) ax5 = fig.add_subplot(324, sharex=ax4) ax6 = fig.add_subplot(326, sharex=ax4) for ax_ in (ax1, ax2, ax3, ax4, ax5, ax6): ax_.grid(zorder=-10) if pre_filt is None: text = 'pre_filt: None' else: text = 'pre_filt: [{:.3g}, {:.3g}, {:.3g}, {:.3g}]'.format( *pre_filt) ax1.text(0.05, 0.1, text, ha="left", va="bottom", transform=ax1.transAxes, fontsize=fontsz, bbox=bbox, zorder=5) ax1.set_ylabel("Data spectrum, raw", bbox=bbox1, fontsize=fontsz) ax1b.set_ylabel("'pre_filt' taper fraction", bbox=bbox2, fontsize=fontsz) evalresp_info = "\n".join( ['output: %s' % output] + ['%s: %s' % (key, value) for key, value in kwargs.items()]) ax2.text(0.05, 0.1, evalresp_info, ha="left", va="bottom", transform=ax2.transAxes, zorder=5, bbox=bbox, fontsize=fontsz) ax2.set_ylabel("Data spectrum,\n" "'pre_filt' applied", bbox=bbox1, fontsize=fontsz) ax2b.set_ylabel("Instrument response", bbox=bbox2, fontsize=fontsz) ax3.text(0.05, 0.1, 'water_level: %s' % water_level, ha="left", va="bottom", transform=ax3.transAxes, fontsize=fontsz, zorder=5, bbox=bbox) ax3.set_ylabel("Data spectrum,\nmultiplied with inverted\n" "instrument response", bbox=bbox1, fontsize=fontsz) ax3b.set_ylabel("Inverted instrument response,\n" "water level applied", bbox=bbox2, fontsize=fontsz) ax3.set_xlabel("Frequency [Hz]", fontsize=fontsz) times = self.times() ax4.plot(times, self.data, color="k") ax4.set_ylabel("Raw", fontsize=fontsz) ax4.yaxis.set_ticks_position("right") ax4.yaxis.set_label_position("right") ax5.plot(times, data, color="k") ax5.set_ylabel("Raw, after time\ndomain pre-processing", fontsize=fontsz) ax5.yaxis.set_ticks_position("right") ax5.yaxis.set_label_position("right") ax6.set_ylabel("Response removed", fontsize=fontsz) ax6.set_xlabel("Time [s]", fontsize=fontsz) ax6.yaxis.set_ticks_position("right") ax6.yaxis.set_label_position("right") ax1.tick_params(labelsize=fontsz) ax1b.tick_params(labelsize=fontsz) ax2.tick_params(labelsize=fontsz) ax2b.tick_params(labelsize=fontsz) ax3.tick_params(labelsize=fontsz) ax3b.tick_params(labelsize=fontsz) ax4.tick_params(labelsize=fontsz) ax5.tick_params(labelsize=fontsz) ax6.tick_params(labelsize=fontsz) # smart calculation of nfft dodging large primes from obspy.signal.util import _npts2nfft nfft = _npts2nfft(npts) # Transform data to Frequency domain data = np.fft.rfft(data, n=nfft) # calculate and apply frequency response, # optionally prefilter in frequency domain and/or apply water level freq_response, freqs = response.get_evalresp_response(self.stats.delta, nfft, output=output, **kwargs) if plot: ax1.loglog(freqs, np.abs(data), color=color1, zorder=9, label= "Data") ax1.legend(loc = "upper left") # frequency domain pre-filtering of data spectrum # (apply cosine taper in frequency domain) if pre_filt: freq_domain_taper = cosine_sac_taper(freqs, flimit=pre_filt) data *= freq_domain_taper if plot: try: freq_domain_taper except NameError: freq_domain_taper = np.ones(len(freqs)) ax1b.semilogx(freqs, freq_domain_taper, color=color2, zorder=10) ax1b.set_ylim(-0.05, 1.05) ax2.loglog(freqs, np.abs(data), color=color1, zorder=9, label = "Data") ax2b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10, label = "Filter") ax2b.legend(loc = "upper left") if water_level is None: # No water level used, so just directly invert the response. # First entry is at zero frequency and value is zero, too. # Just do not invert the first value (and set to 0 to make sure). freq_response[0] = 0.0 freq_response[1:] = 1.0 / freq_response[1:] else: # Invert spectrum with specified water level. invert_spectrum(freq_response, water_level) data *= freq_response data[-1] = abs(data[-1]) + 0.0j if plot: ax3.loglog(freqs, np.abs(data), color=color1, zorder=9, label = "Data") ax3b.loglog(freqs, np.abs(freq_response), color=color2, zorder=10, label = "Filter") # transform data back into the time domain data = np.fft.irfft(data)[0:npts] if plot: # Oftentimes raises NumPy warnings which we don't want to see. with np.errstate(all="ignore"): ax6.plot(times, data, color="k") plt.subplots_adjust(wspace=0.4) if plot is True and fig is None: plt.show() elif plot is True and fig is not None: pass else: plt.savefig(plot) plt.close(fig) # assign processed data and store processing information self.data = data return self, np.abs(freq_response)