Beispiel #1
0
def bandwidth(data, fs, smoothie, fk):
    """
    Bandwidth of a signal.

    Computes the bandwidth of the given data which can be windowed or not.
    The bandwidth corresponds to the level where the power of the spectrum is
    half its maximum value. It is determined as the level of 1/sqrt(2) times
    the maximum Fourier amplitude.

    If data are windowed the bandwidth of each window is returned.

    :type data: :class:`~numpy.ndarray`
    :param data: Data to make envelope of.
    :param fs: Sampling frequency in Hz.
    :param smoothie: Factor for smoothing the result.
    :param fk: Coefficients for calculating time derivatives
        (calculated via central difference).
    :return: **bandwidth[, dbwithd]** - Bandwidth, Time derivative of
        predominant period (windowed only).
    """
    new_dtype = np.float32 if data.dtype.itemsize == 4 else np.float64
    data = np.require(data, dtype=new_dtype)

    nfft = util.next_pow_2(data.shape[1])
    freqaxis = np.linspace(0, fs, nfft + 1)
    bwith = np.zeros(data.shape[0])
    f = fftpack.fft(data, nfft)
    f_sm = util.smooth(abs(f[:, 0:nfft // 2]), 10)
    if np.size(data.shape) > 1:
        i = 0
        for row in f_sm:
            minfc = abs(row - max(abs(row * (1 / np.sqrt(2)))))
            [mdist_ind, _mindist] = min(enumerate(minfc), key=itemgetter(1))
            bwith[i] = freqaxis[mdist_ind]
            i = i + 1
        # bwith_add = \
        # np.append(np.append([bandwidth[0]] * (np.size(fk) // 2), bandwidth),
        #        [bandwidth[np.size(bandwidth) - 1]] * (np.size(fk) // 2))
        # faster alternative
        bwith_add = np.hstack(
            ([bwith[0]] * (np.size(fk) // 2), bwith,
             [bwith[np.size(bwith) - 1]] * (np.size(fk) // 2)))
        dbwith = signal.lfilter(fk, 1, bwith_add)
        # dbwith = dbwith[np.size(fk) // 2:(np.size(dbwith) -
        #         np.size(fk) // 2)]
        # correct start and end values of time derivative
        dbwith = dbwith[np.size(fk) - 1:]
        bwith = util.smooth(bwith, smoothie)
        dbwith = util.smooth(dbwith, smoothie)
        return bwith, dbwith
    else:
        minfc = abs(data - max(abs(data * (1 / np.sqrt(2)))))
        [mdist_ind, _mindist] = min(enumerate(minfc), key=itemgetter(1))
        bwith = freqaxis[mdist_ind]
        return bwith
def calc_fft(str1, type, shift):
    if type is not None:
        data = str1[0].data
        N1 = len(data)
        data = data.astype(type)
        # Always use 2**n-sized FFT, perform xcorr
        size = max(2 * shift + 1, (N1) + shift)
        nfft = next_pow_2(size)
        print ("station %s and network %s - size in calc_fft %s " % (str1[0].stats['station'], str1[0].stats['network'], size)) 
        #Calculate fft of data1 and data2
        IN1 = fft(data, nfft)
        return IN1
    else:
        return str1[0].data    
Beispiel #3
0
def whiten(tr,freq1,freq2,taper_samples):
    
    # zeropadding should make things faster
    n_pad = next_pow_2(tr.stats.npts)

    data = my_centered(tr.data,n_pad)

    freqaxis=np.fft.rfftfreq(tr.stats.npts,tr.stats.delta)

    ind_fw = np.where( ( freqaxis > freq1 ) & ( freqaxis < freq2 ) )[0]

    if len(ind_fw) == 0:
        return(np.zeros(tr.stats.npts))

    ind_fw1 = ind_fw[0]
    
    ind_fw2 = ind_fw[-1]
    
    # Build a cosine taper for the frequency domain
    #df = 1/(tr.stats.npts*tr.stats.delta)
    
    # Taper 
    white_tape = whiten_taper(ind_fw1,ind_fw2,len(freqaxis),taper_samples)
    
    # Transform data to frequency domain
    tr.taper(max_percentage=0.05, type='cosine')
    spec = np.fft.rfft(tr.data)
    
    # Don't divide by 0
    #tol = np.max(np.abs(spec)) / 1e5
    #spec /= np.abs(spec+tol)
    
    # whiten. This elegant solution is from MSNoise:
    spec =  white_tape * np.exp(1j * np.angle(spec))
    
    # Go back to time domain
    # Difficulty here: The time fdomain signal might no longer be real.
    # Hence, irfft cannot be used.
    spec_neg = np.conjugate(spec)[::-1]
    spec = np.concatenate((spec,spec_neg[:-1]))

    tr.data = np.real(np.fft.ifft(spec))
Beispiel #4
0
def log_cepstrum(data, fs, nc, p, n, w):  # @UnusedVariable: n is never used!!!
    """
    Cepstrum of a signal.

    Computes the cepstral coefficient on a logarithmic scale of the given data
    which can be windowed or not.

    If data are windowed the analytic signal and the envelope of each window is
    returned.

    :type data: :class:`~numpy.ndarray`
    :param data: Data to make envelope of.
    :param fs: Sampling frequency in Hz.
    :param nc: number of cepstral coefficients.
    :param p: Number of filters in filterbank.
    :param n: Number of data windows.
    :return: Cepstral coefficients.
    """
    new_dtype = np.float32 if data.dtype.itemsize == 4 else np.float64
    data = np.require(data, dtype=new_dtype)

    dataT = np.transpose(data)
    nfft = util.next_pow_2(dataT.shape[0])
    fc = fftpack.fft(dataT, nfft, 0)
    f = fc[1:len(fc) // 2 + 1, :]
    m, a, b = log_spaced_filterbank_matrix(p, nfft, fs, w)
    pw = np.real(np.multiply(f[a:b, :], np.conj(f[a:b, :])))
    pth = np.max(pw) * 1E-20
    ath = np.sqrt(pth)
    # h1 = np.transpose(np.array([[ath] * int(b + 1 - a)]))
    # h2 = m * abs(f[a - 1:b, :])
    y = np.log(np.maximum(m * abs(f[a - 1:b, :]), ath))
    z = util.rdct(y)
    z = z[1:, :]
    # nc = nc + 1
    nf = np.size(z, 1)
    if (p > nc):
        z = z[:nc, :]
    elif (p < nc):
        z = np.vstack([z, np.zeros(nf, nc - p)])
    return z
def spectralwhitening(stream):
    """
    Apply spectral whitening to data.
    Data is divided by its smoothed (Default: None) amplitude spectrum.
    """
    stream2 = copy.deepcopy(stream)
    
    for trace in arange(len(stream2)):
        data = stream2[trace].data
        
        n = len(data)
        nfft = next_pow_2(n)
        
        spec = fft(data, nfft)
        spec_ampl = sqrt(abs(multiply(spec, conjugate(spec))))
        
        spec /= spec_ampl  #Do we need to do some smoothing here?
        ret = real(ifft(spec, nfft)[:n])
        
        stream2[trace].data = ret
        
    return stream2
Beispiel #6
0
def central_frequency_unwindowed(data, fs):
    """
    Central frequency of a signal.

    Computes the central frequency of the given data (a single waveform).
    The central frequency is a measure of the frequency where the
    power is concentrated. It corresponds to the second moment of the power
    spectral density function.

    The central frequency is returned in Hz.

    :type data: :class:`~numpy.ndarray`
    :param data: Data to estimate central frequency from.
    :param fs: Sampling frequency in Hz.
    :return: **cfreq** - Central frequency in Hz
    """
    nfft = util.next_pow_2(len(data))
    freq = np.linspace(0, fs, nfft + 1)
    freqaxis = freq[0:nfft // 2]
    Px_wm = welch(data, np.hamming(len(data)), nfft)
    Px = Px_wm[0:len(Px_wm) // 2]
    cfreq = np.sqrt(np.sum(freqaxis ** 2 * Px) / (sum(Px)))
    return cfreq
def compute_signal_spectrum(tr, bandwidth):
    """
    Compute raw and smoothed signal spectrum.

    Args:
        tr (StationTrace):
           Trace of data.
        bandwidth (float):
           Konno-Omachi smoothing bandwidth parameter.

    Returns:
        StationTrace with signal spectrum dictionaries added as trace
        parameters.

    """
    # Transform to frequency domain and smooth spectra using
    # konno-ohmachi smoothing
    nfft = next_pow_2(tr.stats.npts)

    dt = tr.stats.delta
    sig_spec = abs(np.fft.rfft(tr.data, n=nfft)) * dt
    sig_spec_freqs = np.fft.rfftfreq(nfft, dt)

    sig_dict = {
        'spec': sig_spec.tolist(),
        'freq': sig_spec_freqs.tolist()
    }
    tr.setParameter('signal_spectrum', sig_dict)

    sig_spec_smooth, freqs_signal = fft_smooth(
        tr, nfft, bandwidth)
    smooth_dict = {
        'spec': sig_spec_smooth.tolist(),
        'freq': freqs_signal.tolist()
    }
    tr.setParameter('smooth_signal_spectrum', smooth_dict)
    return tr
def xcorrf(trace1, trace2, shift=None):
    """
    Cross-correlation of numpy arrays data1 and data2 in frequency domain.
    """
    data1 = trace1.data
    data2 = trace2.data

    complex_result = (data1.dtype == complex or data2.dtype == complex)
    N1 = len(data1)
    N2 = len(data2)

    data1 = data1.astype('float64')
    data2 = data2.astype('float64')

    # Always use 2**n-sized FFT, perform xcorr
    size = max(2 * shift + 1, (N1 + N2) // 2 + shift)
    nfft = next_pow_2(size)
    print size
    #Calculate fft of data1 and data2

    IN1 = fft(data1, nfft)
    IN2 = fft(data2, nfft)

    IN1 *= conjugate(IN2)

    ret = ifft(IN1)

    del IN1, IN2

    if not complex_result:
        ret = ret.real
    # shift data for time lag 0 to index 'shift'

    ret = roll(ret, -(N1 - N2) // 2 + shift)[:2 * shift + 1]

    return copy(ret)
Beispiel #9
0
def backproject(st, v, tshifts, freqmin, freqmax, winlen, overlap, gridx, gridy, gridz=None, starttime=None, endtime=None, relshift=0., coords=None, ds=False):
    ## COULD EASILY OUTPUT ALL FREQUENCIES, BUT ONLY VALID FOR THE ONE THE TIMESHIFTS OR SPEED ARE FOR
    """
    st should have coordinates embedded, if not, coords need to be supplied in teh form of ?
    v, constant velocity to use to estimate travel times in half space set to None if tshifts are specified
    tshifts, time shifts, in seconds, from each station to each grid point in same order as traces in st, set to None if using constant velocity
    freqmin - lowest frequency to consider
    freqmax - higher frequency to consider
    gridxyz - grid of points to search over for backprojection
    winlen - window length in seconds
    overlap - amount to overlap windows from 0 (no overlap) to 1 (completely overlapped)
    coords - station coordinates, if not embedded in stream, only need for mapping and for computing travel times, not required if tshifts are provided. Should be in form of dictionary {sta: (x,y,elev)}, be sure units are consistent between v and coords
    ds - If true, will downsample to 1/(fhigh*2)
    starttime - start time in UTC of analysis, if None, will use start time of st
    endtime - same as above but end time
    relshift - relative shift to apply to all stations to account for travel time to first station
    """
    # Initial data processing
    samprates = [trace.stats.sampling_rate for trace in st]
    if np.mean(samprates) != samprates[0] and ds is False:
        print('sample rates are not all equal, resampling to minimum sample rate')
        st = st.resample(np.min(samprates))
    if ds is True:
        st = st.resample(1./(freqmax*2.))
    dt = st[0].stats.sampling_rate
    if starttime is None:
        starttime = np.min([trace.stats.starttime for trace in st])
    if endtime is None:
        endtime = np.max([trace.stats.endtime for trace in st])
    st.trim(starttime, endtime, pad=True)  # turns into masked array if needs to pad, might need to check if this breaks things

    nsta = len(st)

    if gridz is None:
        gridz = np.zeros(np.shape(gridx))  # if no elevations provided, assume all at zero elevation
    # Pull out coords for later usage
    sx = []
    sy = []
    selev = []
    names = []
    chan = []
    if coords is None:
        for trace in st:
            sx.append(trace.stats.coordinates['x'])
            sy.append(trace.stats.coordinates['y'])
            selev.append(trace.stats.coordinates['elevation'])
            names.append(trace.stats.station)
            chan.append(trace.stats.channel)
    else:
        for trace in st:
            sx.append(coords[trace.stats.station][0])
            sy.append(coords[trace.stats.station][1])
            selev.append(coords[trace.stats.station][2])
            names.append(trace.stats.station)
            chan.append(trace.stats.channel)

    nwins = int((endtime-starttime)/(winlen*(1.-overlap))) - 3  # look into why 3
    incr = (1.-overlap)*winlen
    sttm = np.arange(0., incr*nwins, incr)

    winlensamp = int(np.round(winlen*dt))
    nfft = 2*next_pow_2(winlen*dt)
    freqs = np.fft.fftfreq(nfft, 1/dt)
    freqsubl = len(freqs[(freqs >= freqmin) & (freqs <= freqmax)])

    power = np.zeros((len(gridx), nwins, freqsubl))
    powernon = power.copy()
    meanpow = np.zeros((len(gridx), nwins))
    # Compute stack power at each hammer location for each point in time (need to figure out how to do ARF for this)
    for n in np.arange(nwins):
        reftime = starttime + sttm[n]
        for i, (gx, gy, gz) in enumerate(zip(gridx, gridy, gridz)):
            # Figure out what shifts are
            if v is not None and tshifts is None:
                shifts = np.sqrt((sx-gx)**2 + (sy-gy)**2 + (selev-gz)**2)/v
            elif tshifts is not None and v is None:
                shifts = tshifts + relshift
            else:
                print('neither tshifts or v defined properly')
            # Cut out window at relative start time
            sttemp = st.copy().trim(starttime=reftime)
            # Apply shifts, in time domain for grid point i
            extract = np.zeros((len(sttemp), freqsubl)) + 1j*np.zeros((len(sttemp), freqsubl))
            extractnon = extract.copy()
            k = 0
            for tr, shift in zip(sttemp, shifts):
                tr.detrend('demean')
                tr.taper(max_percentage=0.05, type='cosine')
                x = tr.data[:winlensamp]
                s = int(np.round(shift * tr.stats.sampling_rate))
                N = nfft
                r = np.floor(N/2)+1
                f = (np.arange(1, N+1)-r)/(N/2)
                p = np.exp(-1j*s*np.pi*f)  # seems like it needs negative sign to shift in the right direction...
                y = fft(x, nfft)*ifftshift(p)
                ynon = fft(x, nfft)
                # whitening?
                #y = y/np.abs(y)
                #indx = np.where((freqs >= freqmin) & (freqs <= freqmax))
                extract[k, :] = y[(freqs >= freqmin) & (freqs <= freqmax)]
                extractnon[k, :] = ynon[(freqs >= freqmin) & (freqs <= freqmax)]
                #import pdb;pdb.set_trace()
                k += 1
                # extract mean beam power over this frequency range and add to cumulative total for this point
            power[i, n, :] = ((1./nsta) * np.abs(np.sum(extract, 0)))**2
            powernon[i, n, :] = ((1./nsta) * np.abs(np.sum(extractnon, 0)))**2
            meanpow[i, n] = np.mean(power[i, n, :])/np.mean(powernon[i, n, :])
        print('%i of %i' % (n, nwins))

    fvec = freqs[(freqs >= freqmin) & (freqs <= freqmax)]
    tvec = sttm + incr/2.
    return power, meanpow, tvec, fvec
Beispiel #10
0
def compute_snr_trace(tr, bandwidth, mag=None, check=None):
    if tr.hasParameter('signal_split'):
        # Split the noise and signal into two separate traces
        split_prov = tr.getParameter('signal_split')
        if isinstance(split_prov, list):
            split_prov = split_prov[0]
        split_time = split_prov['split_time']
        noise = tr.copy().trim(endtime=split_time)
        signal = tr.copy().trim(starttime=split_time)

        noise.detrend('demean')
        signal.detrend('demean')

        # Taper both windows
        noise.taper(max_percentage=TAPER_WIDTH,
                    type=TAPER_TYPE,
                    side=TAPER_SIDE)
        signal.taper(max_percentage=TAPER_WIDTH,
                     type=TAPER_TYPE,
                     side=TAPER_SIDE)

        # Check that there are a minimum number of points in the noise window
        if noise.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            # ** only fail here if it hasn't already failed; we do not yet
            # ** support tracking multiple fail reasons and I think it is
            # ** better to know the FIRST reason if I have to pick one.
            if not tr.hasParameter('failure'):
                tr.fail('Failed SNR check; Not enough points in noise window.')
            compute_and_smooth_spectrum(tr, bandwidth, 'signal')
            return tr

        # Check that there are a minimum number of points in the noise window
        if signal.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            if not tr.hasParameter('failure'):
                tr.fail(
                    'Failed SNR check; Not enough points in signal window.')
            compute_and_smooth_spectrum(tr, bandwidth, 'signal')
            return tr

        nfft = max(next_pow_2(signal.stats.npts), next_pow_2(noise.stats.npts))

        compute_and_smooth_spectrum(tr, bandwidth, 'noise', noise, nfft)
        compute_and_smooth_spectrum(tr, bandwidth, 'signal', signal, nfft)

        # For both the raw and smoothed spectra, subtract the noise spectrum
        # from the signal spectrum
        tr.setCached(
            'signal_spectrum', {
                'spec':
                tr.getCached('signal_spectrum')['spec'] -
                tr.getCached('noise_spectrum')['spec'],
                'freq':
                tr.getCached('signal_spectrum')['freq']
            })
        tr.setCached(
            'smooth_signal_spectrum', {
                'spec':
                tr.getCached('smooth_signal_spectrum')['spec'] -
                tr.getCached('smooth_noise_spectrum')['spec'],
                'freq':
                tr.getCached('smooth_signal_spectrum')['freq']
            })

        smooth_signal_spectrum = tr.getCached('smooth_signal_spectrum')['spec']
        smooth_noise_spectrum = tr.getCached('smooth_noise_spectrum')['spec']
        snr = smooth_signal_spectrum / smooth_noise_spectrum

        snr_dict = {
            'snr': snr,
            'freq': tr.getCached('smooth_signal_spectrum')['freq']
        }
        tr.setCached('snr', snr_dict)

    else:
        # We do not have an estimate of the signal split time for this trace
        compute_and_smooth_spectrum(tr, bandwidth, 'signal')
    if check is not None:
        tr = snr_check(tr, mag, **check)

    return tr
Beispiel #11
0
def deconwater(uin,
               win,
               dt,
               tshift=10.,
               wlevel=0.05,
               f0=2.0,
               normalize=False,
               phase='P'):
    """
    Frequency-domain deconvolution using waterlevel method.

    :param uin: R or Q component for the response function
    :type uin: np.ndarray
    :param win: Z or L component for the source function
    :type win: np.ndarray
    :param dt: sample interval in second 
    :type dt: float
    :param tshift: Time shift before P arrival, defaults to 10.
    :type tshift: float, optional
    :param wlevel: Waterlevel to stabilize the deconvolution, defaults to 0.05
    :type wlevel: float, optional
    :param f0: Gauss factor, defaults to 2.0
    :type f0: float, optional
    :param normalize: If normalize the amplitude of the RF, defaults to False
    :type normalize: bool, optional

    :return: (rf, rms) RF and final rms.
    :rtype: (np.ndarray, float)
    """
    if uin.size != win.size:
        raise ValueError(
            'The length of the \'uin\' must be same as the \'win\'')
    nt = uin.size
    nft = next_pow_2(nt)
    nfpts = nft / 2 + 1  # number of freq samples
    fny = 1. / (2. * dt)
    # nyquist
    delf = fny / (0.5 * nft)
    freq = delf * np.arange(nfpts)
    w = 2 * pi * freq

    # containers
    # rff = np.zeros(nft); # Rfn in freq domain
    upf = np.zeros(nft)
    # predicted numer in freq domain

    # Convert seismograms to freq domain
    uf = fft(uin, nft)
    wf = fft(win, nft)

    # denominator
    df = wf * wf.conjugate()
    dmax = max(df.real)

    # add water level correction
    phi1 = wlevel * dmax  # water level
    # nwl = length( find(df<phi1) ) # number corrected
    df[np.where(df.real < phi1)[0]] = phi1
    gaussF = gaussFilter(dt, nft, f0)
    nf = gaussF * uf * wf.conjugate()

    # compute RF
    rff = nf / df

    # compute predicted numerator
    upf = rff * wf

    # add phase shift to RF
    w = np.append(w, -np.flipud(w[1:-1]))
    rff = rff * np.exp(-1j * w * tshift)

    # back to time domain
    rft = ifft(rff, nft)
    rft = rft[0:nt].real

    # compute the fit
    uf = gaussF * uf  # compare to filtered numerator
    ut = ifft(uf, nft).real
    upt = ifft(upf, nft).real

    powerU = np.sum(ut[0:nt]**2)
    rms = np.sum((upt[0:nt] - ut[0:nt])**2) / powerU

    if normalize:
        gnorm = np.sum(gaussF) * delf * dt
        rft = rft.real / gnorm

    return rft, rms
Beispiel #12
0
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,
                     sl_corr=[0., 0.],
                     normalize_waveforms=False):
    """
    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
                if normalize_waveforms:
                    dat = dat / np.max(np.abs(dat))
                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_x = slow_x - sl_corr[0]
        slow_y = slow_y - sl_corr[1]
        # ---------

        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)
Beispiel #13
0
def compute_snr_trace(tr, bandwidth, check=None):
    if tr.hasParameter('signal_split'):
        # Split the noise and signal into two separate traces
        split_prov = tr.getParameter('signal_split')
        if isinstance(split_prov, list):
            split_prov = split_prov[0]
        split_time = split_prov['split_time']
        noise = tr.copy().trim(endtime=split_time)
        signal = tr.copy().trim(starttime=split_time)

        # Taper both windows
        noise.taper(max_percentage=TAPER_WIDTH,
                    type=TAPER_TYPE,
                    side=TAPER_SIDE)
        signal.taper(max_percentage=TAPER_WIDTH,
                     type=TAPER_TYPE,
                     side=TAPER_SIDE)

        # Check that there are a minimum number of points in the noise window
        if noise.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            # ** only fail here if it hasn't already failed; we do not yet
            # ** support tracking multiple fail reasons and I think it is
            # ** better to know the FIRST reason if I have to pick one.
            if not tr.hasParameter('failure'):
                tr.fail('Failed SNR check; Not enough points in noise window.')
            tr = compute_signal_spectrum(tr, bandwidth)
            return tr

        # Check that there are a minimum number of points in the noise window
        if signal.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            if not tr.hasParameter('failure'):
                tr.fail(
                    'Failed SNR check; Not enough points in signal window.')
            tr = compute_signal_spectrum(tr, bandwidth)
            return tr

        nfft = max(next_pow_2(signal.stats.npts),
                   next_pow_2(noise.stats.npts))

        # Transform to frequency domain and smooth spectra using
        # konno-ohmachi smoothing
        dt = signal.stats.delta
        sig_spec = abs(np.fft.rfft(signal.data, n=nfft)) * dt
        sig_spec_freqs = np.fft.rfftfreq(nfft, dt)
        dt = noise.stats.delta
        noise_spec = abs(np.fft.rfft(noise.data, n=nfft)) * dt
        sig_spec -= noise_spec

        sig_dict = {
            'spec': sig_spec.tolist(),
            'freq': sig_spec_freqs.tolist()
        }
        tr.setParameter('signal_spectrum', sig_dict)

        noise_dict = {
            'spec': noise_spec.tolist(),
            'freq': sig_spec_freqs.tolist()  # same as signal
        }
        tr.setParameter('noise_spectrum', noise_dict)

        sig_spec_smooth, freqs_signal = fft_smooth(
            signal, nfft, bandwidth)
        smooth_dict = {
            'spec': sig_spec_smooth.tolist(),
            'freq': freqs_signal.tolist()
        }
        tr.setParameter('smooth_signal_spectrum', smooth_dict)

        noise_spec_smooth, freqs_noise = fft_smooth(noise, nfft)
        noise_smooth_dict = {
            'spec': noise_spec_smooth.tolist(),
            'freq': freqs_noise.tolist()
        }
        tr.setParameter('smooth_noise_spectrum', noise_smooth_dict)

        # remove the noise level from the spectrum of the signal window
        sig_spec_smooth -= noise_spec_smooth

        snr = sig_spec_smooth / noise_spec_smooth
        snr_dict = {
            'snr': snr.tolist(),
            'freq': freqs_signal.tolist()
        }
        tr.setParameter('snr', snr_dict)
    else:
        # We do not have an estimate of the signal split time for this trace
        tr = compute_signal_spectrum(tr, bandwidth)
    if check is not None:
        tr = snr_check(tr, **check)

    return tr
Beispiel #14
0
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)
Beispiel #15
0
        wdata = np.real(scipy.fftpack.ifft(FFTRawSign))
        plt.subplot(414)
        plt.plot(np.arange(len(wdata)) * delta, wdata)
        plt.xlim(0, nt * delta)
        plt.title("Output trace after Whiten and filter")
        plt.show()
    
    return FFTRawSign


if __name__ == '__main__':
    import time
    a = obspy.read('IC.BJT.00.BHZ.norm')[0]
    nt = a.stats.npts
    N = next_pow_2(nt)
#    a = np.sin(a) + np.sin(a / 4.) + np.sin(a / 16.)
#    a -= a.mean()
    t = time.clock()
    fftdata = whiten(a.copy(), nt, N, a.stats.delta, 0.01, 0.02, 0.067, 0.08, plot=True)
    print("whiten the trace:", (time.clock()-t) * 1000, "ms")
'''
    wdata = np.real(scipy.fftpack.ifft(fftdata))
    wdata = wdata[0:nt]/smooth(np.abs(wdata[0:nt]), half_len=20)
    plt.plot(np.arange(len(wdata)) * a.stats.delta, wdata)
    plt.xlim(0, nt*a.stats.delta)
    plt.show()
'''


Beispiel #16
0
def simulate_seismometer(data,
                         samp_rate,
                         paz_remove=None,
                         paz_simulate=None,
                         remove_sensitivity=True,
                         simulate_sensitivity=True,
                         water_level=600.0,
                         zero_mean=True,
                         taper=True,
                         taper_fraction=0.05,
                         pre_filt=None,
                         seedresp=None,
                         nfft_pow2=False,
                         pitsasim=True,
                         sacsim=False,
                         shsim=False):
    """
    Simulate/Correct seismometer.

    :type data: NumPy :class:`~numpy.ndarray`
    :param data: Seismogram, detrend before hand (e.g. zero mean)
    :type samp_rate: float
    :param samp_rate: Sample Rate of Seismogram
    :type paz_remove: dict, None
    :param paz_remove: Dictionary containing keys 'poles', 'zeros', 'gain'
        (A0 normalization factor). poles and zeros must be a list of complex
        floating point numbers, gain must be of type float. Poles and Zeros are
        assumed to correct to m/s, SEED convention. Use None for no inverse
        filtering.
    :type paz_simulate: dict, None
    :param paz_simulate: Dictionary containing keys 'poles', 'zeros', 'gain'.
        Poles and zeros must be a list of complex floating point numbers, gain
        must be of type float. Or None for no simulation.
    :type remove_sensitivity: bool
    :param remove_sensitivity: Determines if data is divided by
        `paz_remove['sensitivity']` to correct for overall sensitivity of
        recording instrument (seismometer/digitizer) during instrument
        correction.
    :type simulate_sensitivity: bool
    :param simulate_sensitivity: Determines if data is multiplied with
        `paz_simulate['sensitivity']` to simulate overall sensitivity of
        new instrument (seismometer/digitizer) during instrument simulation.
    :type water_level: float
    :param water_level: Water_Level for spectrum to simulate
    :type zero_mean: bool
    :param zero_mean: If true the mean of the data is subtracted
    :type taper: bool
    :param taper: If true a cosine taper is applied.
    :type taper_fraction: float
    :param taper_fraction: Taper fraction of cosine taper to use
    :type pre_filt: list or tuple of floats
    :param pre_filt: Apply a bandpass filter to the data trace 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 seedresp: dict, None
    :param seedresp: Dictionary contains keys 'filename', 'date', 'units'.
        'filename' is the path to a RESP-file generated from a dataless SEED
        volume (or a file like object with RESP information);
        'date' is a :class:`~obspy.core.utcdatetime.UTCDateTime` object for the
        date that the response function should be extracted for (can be omitted
        when calling simulate() on Trace/Stream. the Trace's starttime will
        then be used);
        'units' defines the units of the response function.
        Can be either 'DIS', 'VEL' or 'ACC'.
    :type nfft_pow2: bool
    :param nfft_pow2: Number of frequency points to use for FFT. If True,
        the exact power of two is taken (default in PITSA). If False the
        data are not zero-padded to the next power of two which makes a
        slower FFT but is then much faster for e.g. evalresp which scales
        with the FFT points.
    :type pitsasim: bool
    :param pitsasim: Choose parameters to match
        instrument correction as done by PITSA.
    :type sacsim: bool
    :param sacsim: Choose parameters to match
        instrument correction as done by SAC.
    :type shsim: bool
    :param shsim: Choose parameters to match
        instrument correction as done by Seismic Handler.
    :return: The corrected data are returned as :class:`numpy.ndarray` float64
        array. float64 is chosen to avoid numerical instabilities.

    This function works in the frequency domain, where nfft is the next power
    of len(data) to avoid wrap around effects during convolution. The inverse
    of the frequency response of the seismometer (``paz_remove``) is
    convolved with the spectrum of the data and with the frequency response
    of the seismometer to simulate (``paz_simulate``). A 5% cosine taper is
    taken before simulation. The data must be detrended (e.g.) zero mean
    beforehand. If paz_simulate=None only the instrument correction is done.
    In the latter case, a broadband filter can be applied to the data trace
    using pre_filt. This restricts the signal to the valid frequency band and
    thereby avoids artifacts due to amplification of frequencies outside of the
    instrument's passband (for a detailed discussion see
    *Of Poles and Zeros*, F. Scherbaum, Kluwer Academic Publishers).

    .. versionchanged:: 0.5.1
        The default for `remove_sensitivity` and `simulate_sensitivity` has
        been changed to ``True``. Old deprecated keyword arguments `paz`,
        `inst_sim`, `no_inverse_filtering` have been removed.
    """
    # Checking the types
    if not paz_remove and not paz_simulate and not seedresp:
        msg = "Neither inverse nor forward instrument simulation specified."
        raise TypeError(msg)

    for d in [paz_remove, paz_simulate]:
        if d is None:
            continue
        for key in ['poles', 'zeros', 'gain']:
            if key not in d:
                raise KeyError("Missing key: %s" % key)
    # Translated from PITSA: spr_resg.c
    delta = 1.0 / samp_rate
    #
    ndat = len(data)
    data = data.astype(np.float64)
    if zero_mean:
        data -= data.mean()
    if taper:
        if sacsim:
            data *= cosine_taper(ndat,
                                 taper_fraction,
                                 sactaper=sacsim,
                                 halfcosine=False)
        else:
            data *= cosine_taper(ndat, taper_fraction)
    # The number of points for the FFT has to be at least 2 * ndat (in
    # order to prohibit wrap around effects during convolution) cf.
    # Numerical Recipes p. 429 calculate next power of 2.
    if nfft_pow2:
        nfft = util.next_pow_2(2 * ndat)
    # evalresp scales directly with nfft, therefore taking the next power of
    # two has a greater negative performance impact than the slow down of a
    # not power of two in the FFT
    else:
        nfft = _npts2nfft(ndat)
    # Transform data in Fourier domain
    data = np.fft.rfft(data, n=nfft)
    # Inverse filtering = Instrument correction
    if paz_remove:
        freq_response, freqs = paz_to_freq_resp(paz_remove['poles'],
                                                paz_remove['zeros'],
                                                paz_remove['gain'],
                                                delta,
                                                nfft,
                                                freq=True)
    if seedresp:
        freq_response, freqs = evalresp(delta,
                                        nfft,
                                        seedresp['filename'],
                                        seedresp['date'],
                                        units=seedresp['units'],
                                        freq=True,
                                        network=seedresp['network'],
                                        station=seedresp['station'],
                                        locid=seedresp['location'],
                                        channel=seedresp['channel'])
        if not remove_sensitivity:
            msg = "remove_sensitivity is set to False, but since seedresp " + \
                  "is selected the overall sensitivity will be corrected " + \
                  " for anyway!"
            warnings.warn(msg)
    if paz_remove or seedresp:
        if pre_filt:
            # make cosine taper
            fl1, fl2, fl3, fl4 = pre_filt
            if sacsim:
                cos_win = cosine_sac_taper(freqs, flimit=(fl1, fl2, fl3, fl4))
            else:
                cos_win = cosine_taper(freqs.size,
                                       freqs=freqs,
                                       flimit=(fl1, fl2, fl3, fl4))
            data *= cos_win
        invert_spectrum(freq_response, water_level)
        data *= freq_response
        del freq_response
    # Forward filtering = Instrument simulation
    if paz_simulate:
        data *= paz_to_freq_resp(paz_simulate['poles'], paz_simulate['zeros'],
                                 paz_simulate['gain'], delta, nfft)

    data[-1] = abs(data[-1]) + 0.0j
    # transform data back into the time domain
    data = np.fft.irfft(data)[0:ndat]
    if pitsasim:
        # linear detrend
        data = simple_detrend(data)
    if shsim:
        # detrend using least squares
        data = scipy.signal.detrend(data, type="linear")
    # correct for involved overall sensitivities
    if paz_remove and remove_sensitivity and not seedresp:
        data /= paz_remove['sensitivity']
    if paz_simulate and simulate_sensitivity:
        data *= paz_simulate['sensitivity']
    return data
Beispiel #17
0
def deconvf(rsp_list,
            src,
            sampling_rate,
            waterlevel=0.05,
            gauss=2.,
            tshift=10.,
            pad=0,
            length=None,
            normalize=0,
            return_info=False):
    """
    Frequency-domain deconvolution using waterlevel method.

    Deconvolve src from arrays in rsp_list.

    :param rsp_list: either a list of arrays containing the response functions
        or a single array
    :param src: array with source function
    :param sampling_rate: sampling rate of the data
    :param waterlevel: waterlevel to stabilize the deconvolution
    :param gauss: Gauss parameter of Low-pass filter
    :param tshift: delay time 0s will be at time tshift afterwards
    :param pad: multiply number of samples used for fft by 2**pad
    :param length: number of data points in results, optional
    :param normalize: normalize all results so that the maximum of the trace
        with supplied index is 1. Set normalize to 'src' to normalize
        for the maximum of the prepared source. Set normalize to None for no
        normalization.
    :param return_info: return additionally a lot of different parameters in a
        dict for debugging purposes

    :return: (list of) array(s) with deconvolution(s)
    """
    if length is None:
        length = __get_length(rsp_list)
    N = length
    nfft = next_pow_2(N) * 2**pad
    freq = np.fft.fftfreq(nfft, d=1. / sampling_rate)
    gauss = np.exp(
        np.maximum(-(0.5 * 2 * pi * freq / gauss)**2, -700.) -
        1j * tshift * 2 * pi * freq)

    spec_src = fft(src, nfft)
    spec_src_conj = np.conjugate(spec_src)
    spec_src_water = np.abs(spec_src * spec_src_conj)
    spec_src_water = np.maximum(spec_src_water,
                                max(spec_src_water) * waterlevel)

    if normalize == 'src':
        spec_src = gauss * spec_src * spec_src_conj / spec_src_water
        rf_src = ifft(spec_src, nfft)[:N]
        norm = 1 / max(rf_src)
        rf_src = norm * rf_src

    flag = False
    if not isinstance(rsp_list, (list, tuple)):
        flag = True
        rsp_list = [rsp_list]
    rf_list = [
        ifft(gauss * fft(rsp, nfft) * spec_src_conj / spec_src_water, nfft)[:N]
        for rsp in rsp_list
    ]
    if normalize not in (None, 'src'):
        norm = 1. / max(rf_list[normalize])
    if normalize is not None:
        for rf in rf_list:
            rf *= norm
    if return_info:
        if normalize not in (None, 'src'):
            spec_src = gauss * spec_src * spec_src_conj / spec_src_water
            rf_src = ifft(spec_src, nfft)[:N]
            norm = 1 / max(rf_src)
            rf_src = norm * rf_src
        info = {
            'rf_src': rf_src,
            'rf_src_conj': spec_src_conj,
            'spec_src_water': spec_src_water,
            'freq': freq,
            'gauss': gauss,
            'norm': norm,
            'N': N,
            'nfft': nfft
        }
        return rf_list, info
    elif flag:
        return rf
    else:
        return rf_list
Beispiel #18
0
def decovit(uin,
            win,
            dt,
            nt=None,
            tshift=10,
            f0=2.0,
            itmax=400,
            minderr=0.001,
            info=False):
    """
    Created on Wed Sep 10 14:21:38 2014
    [RFI, rms, it]=makeRFitdecon(uin,win,dt,nt,tshift,f0,itmax,minderr)

    In:
    uin = numerator (radial for PdS)
    win = denominator (vertical component for PdS)
    dt = sample interval (s)
    nt = number of samples
    tshift = Time until beginning of receiver function (s)
    f0 = width of gaussian filter
    itmax = max # iterations
    minderr = Min change in error required for stopping iterations

    Out:
    RFI = receiver function
    rms = Root mean square error for predicting numerator after each iteration

    @author: Mijian Xu @ NJU
    """
    # print('Iterative Decon (Ligorria & Ammon):\n')
    if len(uin) != len(win):
        raise ValueError('The two input trace must be in same length')
    elif nt is None:
        nt = len(uin)
    else:
        pass

    rms = np.zeros(itmax)
    nfft = next_pow_2(nt)
    p0 = np.zeros(nfft)

    u0 = np.zeros(nfft)
    w0 = np.zeros(nfft)

    u0[0:nt] = uin
    w0[0:nt] = win

    gaussF = gaussFilter(dt, nfft, f0)

    u_flt = gfilter(u0, nfft, gaussF, dt)
    w_flt = gfilter(w0, nfft, gaussF, dt)

    wf = fft(w0, nfft)
    r_flt = u_flt

    powerU = np.sum(u_flt**2)

    it = 0
    sumsq_i = 1
    d_error = 100 * powerU + minderr
    maxlag = 0.5 * nfft
    # print('\tMax Spike Display is ' + str((maxlag) * dt))

    while np.abs(d_error) > minderr and it < itmax:
        rw = correl(r_flt, w_flt, nfft)
        rw = rw / np.sum(w_flt**2)

        i1 = np.argmax(np.abs(rw[0:int(maxlag) - 1]))
        amp = rw[i1] / dt

        p0[i1] = p0[i1] + amp
        p_flt = gfilter(p0, nfft, gaussF, dt)
        p_flt = gfilter(p_flt, nfft, wf, dt)

        r_flt = u_flt - p_flt
        sumsq = np.sum(r_flt**2) / powerU
        rms[it] = sumsq
        d_error = 100 * (sumsq_i - sumsq)

        sumsq_i = sumsq

        it = it + 1

    p_flt = gfilter(p0, nfft, gaussF, dt)
    p_flt = phaseshift(p_flt, nfft, dt, tshift)
    RFI = p_flt[0:nt]
    rms = rms[0:it - 1]

    return RFI, rms, it
Beispiel #19
0
def compute_snr_trace(tr, bandwidth, mag=None, check=None):
    if tr.hasParameter("signal_split"):
        # Split the noise and signal into two separate traces
        split_prov = tr.getParameter("signal_split")
        if isinstance(split_prov, list):
            split_prov = split_prov[0]
        split_time = split_prov["split_time"]
        noise = tr.copy().trim(endtime=split_time)
        signal = tr.copy().trim(starttime=split_time)

        tr.setCached("noise_trace", {
            "times": noise.times(),
            "data": noise.data
        })

        noise.detrend("demean")
        signal.detrend("demean")

        # Taper both windows
        noise.taper(max_percentage=TAPER_WIDTH,
                    type=TAPER_TYPE,
                    side=TAPER_SIDE)
        signal.taper(max_percentage=TAPER_WIDTH,
                     type=TAPER_TYPE,
                     side=TAPER_SIDE)

        # Check that there are a minimum number of points in the noise window
        if noise.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            # ** only fail here if it hasn't already failed; we do not yet
            # ** support tracking multiple fail reasons and I think it is
            # ** better to know the FIRST reason if I have to pick one.
            if not tr.hasParameter("failure"):
                tr.fail("Failed SNR check; Not enough points in noise window.")
            compute_and_smooth_spectrum(tr, bandwidth, "signal")
            return tr

        # Check that there are a minimum number of points in the noise window
        if signal.stats.npts < MIN_POINTS_IN_WINDOW:
            # Fail the trace, but still compute the signal spectra
            if not tr.hasParameter("failure"):
                tr.fail(
                    "Failed SNR check; Not enough points in signal window.")
            compute_and_smooth_spectrum(tr, bandwidth, "signal")
            return tr

        nfft = max(next_pow_2(signal.stats.npts), next_pow_2(noise.stats.npts))

        compute_and_smooth_spectrum(tr, bandwidth, "noise", noise, nfft)
        compute_and_smooth_spectrum(tr, bandwidth, "signal", signal, nfft)

        # For both the raw and smoothed spectra, subtract the noise spectrum
        # from the signal spectrum
        tr.setCached(
            "signal_spectrum",
            {
                "spec": (tr.getCached("signal_spectrum")["spec"] -
                         tr.getCached("noise_spectrum")["spec"]),
                "freq":
                tr.getCached("signal_spectrum")["freq"],
            },
        )
        tr.setCached(
            "smooth_signal_spectrum",
            {
                "spec": (tr.getCached("smooth_signal_spectrum")["spec"] -
                         tr.getCached("smooth_noise_spectrum")["spec"]),
                "freq":
                tr.getCached("smooth_signal_spectrum")["freq"],
            },
        )

        smooth_signal_spectrum = tr.getCached("smooth_signal_spectrum")["spec"]
        smooth_noise_spectrum = tr.getCached("smooth_noise_spectrum")["spec"]
        snr = smooth_signal_spectrum / smooth_noise_spectrum

        snr_dict = {
            "snr": snr,
            "freq": tr.getCached("smooth_signal_spectrum")["freq"]
        }
        tr.setCached("snr", snr_dict)

    else:
        # We do not have an estimate of the signal split time for this trace
        compute_and_smooth_spectrum(tr, bandwidth, "signal")
    if check is not None:
        tr = snr_check(tr, mag, **check)

    return tr
Beispiel #20
0
def rel_calib_stack(st1, st2, calib_file, window_len, overlap_frac=0.5, smooth=0, save_data=True):
    """
    Method for relative calibration of sensors using a sensor with known
    transfer function

    :param st1: Stream or Trace object, (known)
    :param st2: Stream or Trace object, (unknown)
    :type calib_file: str
    :param calib_file: file name of calibration file containing the PAZ of the
        known instrument in GSE2 standard.
    :type window_len: float
    :param window_len: length of sliding window in seconds
    :type overlap_frac: float
    :param overlap_frac: fraction of overlap, defaults to fifty percent (0.5)
    :type smooth: float
    :param smooth: variable that defines if the Konno-Ohmachi taper is used or
        not. default = 0 -> no taper generally used in geopsy: smooth = 40
    :type save_data: bool
    :param save_data: Whether or not to save the result to a file. If True, two
        output files will be created:
        * The new response in station_name.window_length.resp
        * The ref response in station_name.refResp
        Defaults to True
    :returns: frequency, amplitude and phase spectrum

    implemented after rel_calib_stack.c by M.Ohrnberger and J.Wassermann.
    """
    # transform given trace objects to streams
    if isinstance(st1, Trace):
        st1 = Stream([st1])
    if isinstance(st2, Trace):
        st2 = Stream([st2])
    # check if sampling rate and trace length is the same
    if st1[0].stats.npts != st2[0].stats.npts:
        msg = "Traces don't have the same length!"
        raise ValueError(msg)
    elif st1[0].stats.sampling_rate != st2[0].stats.sampling_rate:
        msg = "Traces don't have the same sampling rate!"
        raise ValueError(msg)
    else:
        ndat1 = st1[0].stats.npts
        sampfreq = st1[0].stats.sampling_rate

    # read waveforms
    tr1 = st1[0].data.astype(np.float64)
    tr2 = st2[0].data.astype(np.float64)

    # get window length, nfft and frequency step
    ndat = int(window_len * sampfreq)
    nfft = next_pow_2(ndat)

    # read calib file and calculate response function
    gg, _freq = _calc_resp(calib_file, nfft, sampfreq)

    # calculate number of windows and overlap
    nwin = int(np.floor((ndat1 - nfft) / (nfft / 2)) + 1)
    noverlap = nfft * overlap_frac

    auto, _freq, _t = spectral_helper(tr1, tr1, NFFT=nfft, Fs=sampfreq, noverlap=noverlap)
    cross, freq, _t = spectral_helper(tr2, tr1, NFFT=nfft, Fs=sampfreq, noverlap=noverlap)

    res = (cross / auto).sum(axis=1) * gg

    # The first item might be zero. Problems with phase calculations.
    res = res[1:]
    freq = freq[1:]
    gg = gg[1:]

    res /= nwin
    # apply Konno-Ohmachi smoothing taper if chosen
    if smooth > 0:
        # Write in one matrix for performance reasons.
        spectra = np.empty((2, len(res.real)))
        spectra[0] = res.real
        spectra[1] = res.imag
        new_spectra = konno_ohmachi_smoothing(
            spectra, freq, bandwidth=smooth, count=1, max_memory_usage=1024, normalize=True
        )
        res.real = new_spectra[0]
        res.imag = new_spectra[1]

    amp = np.abs(res)
    # include phase unwrapping
    phase = np.unwrap(np.angle(res))  # + 2.0 * np.pi
    ra = np.abs(gg)
    rpha = np.unwrap(np.angle(gg))

    if save_data:
        trans_new = st2[0].stats.station + "." + st2[0].stats.channel + "." + str(window_len) + ".resp"
        trans_ref = st1[0].stats.station + ".refResp"
        # Create empty array for easy saving
        temp = np.empty((len(freq), 3))
        temp[:, 0] = freq
        temp[:, 1] = amp
        temp[:, 2] = phase
        np.savetxt(trans_new, temp, fmt=native_str("%.10f"))
        temp[:, 1] = ra
        temp[:, 2] = rpha
        np.savetxt(trans_ref, temp, fmt=native_str("%.10f"))

    return freq, amp, phase
    def _get_info(self):
        """
        Returns a dictionary with information about the currently loaded
        database.
        """
        # Get the size of all netCDF files.
        filesize = 0
        for m in self.meshes:
            if m:
                filesize += os.path.getsize(m.filename)

        if self._is_reciprocal:
            if hasattr(self.meshes, "merged"):
                # The number of dimensions determines the available components.
                dims = self.meshes.merged.f["MergedSnapshots"].shape[1]
                if dims == 5:
                    components = 'vertical and horizontal'
                elif dims == 3:
                    components = 'horizontal only'
                elif dims == 2:
                    components = 'vertical only'
                else:  # pragma: no cover
                    raise NotImplementedError
            elif self.meshes.pz is not None and self.meshes.px is not None:
                components = 'vertical and horizontal'
            elif self.meshes.pz is None and self.meshes.px is not None:
                components = 'horizontal only'
            elif self.meshes.pz is not None and self.meshes.px is None:
                components = 'vertical only'
        else:
            components = '4 elemental moment tensors'

        return dict(
            is_reciprocal=self._is_reciprocal,
            components=components,
            source_depth=float(self.parsed_mesh.source_depth)
            if self._is_reciprocal is False else None,
            velocity_model=self.parsed_mesh.background_model,
            external_model_name=self.parsed_mesh.external_model_name,
            attenuation=self.parsed_mesh.attenuation,
            period=float(self.parsed_mesh.dominant_period),
            dump_type=self.parsed_mesh.dump_type,
            excitation_type=self.parsed_mesh.excitation_type,
            dt=float(self.parsed_mesh.dt),
            sampling_rate=float(1.0 / self.parsed_mesh.dt),
            npts=int(self.parsed_mesh.ndumps),
            nfft=int(next_pow_2(self.parsed_mesh.ndumps) * 2),
            length=float(self.parsed_mesh.dt * (self.parsed_mesh.ndumps - 1)),
            stf=self.parsed_mesh.stf_kind,
            src_shift=float(self.parsed_mesh.source_shift),
            src_shift_samples=int(self.parsed_mesh.source_shift_samp),
            slip=self.parsed_mesh.stf_norm,
            sliprate=self.parsed_mesh.stf_d_norm,
            spatial_order=int(self.parsed_mesh.npol),
            min_radius=float(self.parsed_mesh.kwf_rmin) * 1e3,
            max_radius=float(self.parsed_mesh.kwf_rmax) * 1e3,
            planet_radius=float(self.parsed_mesh.planet_radius),
            min_d=float(self.parsed_mesh.kwf_colatmin),
            max_d=float(self.parsed_mesh.kwf_colatmax),
            time_scheme=self.parsed_mesh.time_scheme,
            directory=os.path.relpath(self.db_path),
            filesize=filesize,
            compiler=self.parsed_mesh.axisem_compiler,
            user=self.parsed_mesh.axisem_user,
            format_version=int(self.parsed_mesh.file_version),
            axisem_version=self.parsed_mesh.axisem_version,
            datetime=self.parsed_mesh.creation_time
        )
Beispiel #22
0
def deconvf(rsp_list, src, sampling_rate, waterlevel=0.05, gauss=2.,
            tshift=10., pad=0, length=None, normalize=0, return_info=False):
    """
    Frequency-domain deconvolution using waterlevel method.

    Deconvolve src from arrays in rsp_list.

    :param rsp_list: either a list of arrays containing the response functions
        or a single array
    :param src: array with source function
    :param sampling_rate: sampling rate of the data
    :param waterlevel: waterlevel to stabilize the deconvolution
    :param gauss: Gauss parameter of Low-pass filter
    :param tshift: delay time 0s will be at time tshift afterwards
    :param pad: multiply number of samples used for fft by 2**pad
    :param length: number of data points in results, optional
    :param normalize: normalize all results so that the maximum of the trace
        with supplied index is 1. Set normalize to 'src' to normalize
        for the maximum of the prepared source. Set normalize to None for no
        normalization.
    :param return_info: return additionally a lot of different parameters in a
        dict for debugging purposes

    :return: (list of) array(s) with deconvolution(s)
    """
    if length is None:
        length = __get_length(rsp_list)
    N = length
    nfft = next_pow_2(N) * 2 ** pad
    freq = np.fft.fftfreq(nfft, d=1. / sampling_rate)
    gauss = np.exp(np.maximum(-(0.5 * 2 * pi * freq / gauss) ** 2, -700.) -
                   1j * tshift * 2 * pi * freq)

    spec_src = fft(src, nfft)
    spec_src_conj = np.conjugate(spec_src)
    spec_src_water = np.abs(spec_src * spec_src_conj)
    spec_src_water = np.maximum(
        spec_src_water, max(spec_src_water) * waterlevel)

    if normalize == 'src':
        spec_src = gauss * spec_src * spec_src_conj / spec_src_water
        rf_src = ifft(spec_src, nfft)[:N]
        norm = 1 / max(rf_src)
        rf_src = norm * rf_src

    flag = False
    if not isinstance(rsp_list, (list, tuple)):
        flag = True
        rsp_list = [rsp_list]
    rf_list = [ifft(gauss * fft(rsp, nfft) * spec_src_conj / spec_src_water,
                    nfft)[:N] for rsp in rsp_list]
    if normalize not in (None, 'src'):
        norm = 1. / max(rf_list[normalize])
    if normalize is not None:
        for rf in rf_list:
            rf *= norm
    if return_info:
        if normalize not in (None, 'src'):
            spec_src = gauss * spec_src * spec_src_conj / spec_src_water
            rf_src = ifft(spec_src, nfft)[:N]
            norm = 1 / max(rf_src)
            rf_src = norm * rf_src
        info = {'rf_src': rf_src, 'rf_src_conj': spec_src_conj,
                'spec_src_water': spec_src_water, 'freq': freq,
                'gauss': gauss, 'norm': norm, 'N': N, 'nfft': nfft}
        return rf_list, info
    elif flag:
        return rf
    else:
        return rf_list
Beispiel #23
0
def get_corner_frequencies(trace,
                           event_time,
                           epi_dist,
                           ratio=3.0,
                           split_method='velocity',
                           vsplit=7.0,
                           max_low_corner=0.1,
                           min_high_corner=5.0,
                           taper_type='hann',
                           taper_percentage=0.05,
                           taper_side='both'):
    """
    Returns the corner frequencies for a trace.

    Args:
        trace (obspy.core.trace.Trace): Trace of strong motion data.
        event_time (UTCDateTime): Event origin time.
        epi_dist (float): Distance from event epicenter to station.
        split_method (str): Method for splitting signal and noise.
            Either 'p_arrival' or 'velocity'
        vsplit (float): Velocity (km/s) for splitting noise and signal.
        ratio (float): Required signal-to-noise ratio.
            Default is 3.0.
        max_low_corner (float): Maxmimum low corner frequency allowed.
            Default is 0.1.
        min_high_corner(float): Minimum low corner frequency allowed.
            Default is 5.0.
        taper_type (str): Taper types allowed for filtering. Must be an Obspy
            supported tapering method.
            Default is 'hann' (Hanning taper).
        taper_percentage (float): Decimal percentage of taper.
            Default is 0.05 (5%).
        taper_side (str): Speicfy which sides should be tapered. Either 'left',
            'right', or 'both'.
            Default is 'both'.

    Returns:
        list : List of floats representing corner frequencies.
        Returns two -1 values if inadequate signal to noise ratio.
    """

    # Split the noise and signal into two separate traces
    signal, noise = split_signal_and_noise(trace, event_time, epi_dist,
                                           split_method, vsplit)

    # Check if signal and noise splitting failed
    if (signal == -1 and noise == -1):
        return [-1, -1]

    # Taper the noise and signal traces
    noise = taper(noise,
                  taper_type=taper_type,
                  max_percentage=taper_percentage,
                  side=taper_side)

    signal = taper(signal,
                   taper_type=taper_type,
                   max_percentage=taper_percentage,
                   side=taper_side)

    # Find the number of points for the Fourier transform
    nfft = max(next_pow_2(signal.stats.npts), next_pow_2(noise.stats.npts))

    # Transform to frequency domain and smooth spectra using
    # konno-ohmachi smoothing
    sig_spec_smooth, freqs_signal = fft_smooth(signal, nfft)
    noise_spec_smooth, freqs_noise = fft_smooth(noise, nfft)

    # remove the noise level from the spectrum of the signal window
    sig_spec_smooth -= noise_spec_smooth

    # Loop through frequencies to find low corner and high corner
    corner_frequencies = []
    lows = []
    highs = []
    have_low = False
    for idx, freq in enumerate(freqs_signal):
        if have_low is False:
            if (sig_spec_smooth[idx] / noise_spec_smooth[idx]) >= ratio:
                lows.append(freq)
                have_low = True
            else:
                continue
        else:
            if (sig_spec_smooth[idx] / noise_spec_smooth[idx]) < ratio:
                highs.append(freq)
                have_low = False
            else:
                continue

    # If we didn't find any corners
    if not lows:
        return [-2, -2]

    # If we find an extra low, add another high for the maximum frequency
    if len(lows) > len(highs):
        highs.append(max(freqs_signal))

    # Check if any of the low/high pairs are valid
    found_valid = False
    for idx, val in enumerate(lows):
        if (val <= max_low_corner and highs[idx] > min_high_corner):
            low_corner = val
            high_corner = highs[idx]
            found_valid = True

    # Check if we found any valid pairs
    if not found_valid:
        return [-3, -3]
    else:
        corner_frequencies = [low_corner, high_corner]
        corners = {
            'get_dynamically': True,
            'ratio': ratio,
            'default_high_frequency': corner_frequencies[1],
            'default_low_frequency': corner_frequencies[0]
        }
        # retrieving corner frequencies does not alter the data
        # trace = _update_params(trace, 'corners', corners)
        return corner_frequencies
    def _get_info(self):
        """
        Returns a dictionary with information about the currently loaded
        database.
        """
        # Get the size of all netCDF files.
        filesize = 0
        for m in self.meshes:
            if m:
                filesize += os.path.getsize(m.filename)

        if self._is_reciprocal:
            if hasattr(self.meshes, "merged"):
                # The number of dimensions determines the available components.
                dims = self.meshes.merged.f["MergedSnapshots"].shape[1]
                if dims == 5:
                    components = 'vertical and horizontal'
                elif dims == 3:
                    components = 'horizontal only'
                elif dims == 2:
                    components = 'vertical only'
                else:  # pragma: no cover
                    raise NotImplementedError
            elif self.meshes.pz is not None and self.meshes.px is not None:
                components = 'vertical and horizontal'
            elif self.meshes.pz is None and self.meshes.px is not None:
                components = 'horizontal only'
            elif self.meshes.pz is not None and self.meshes.px is None:
                components = 'vertical only'
        else:
            components = '4 elemental moment tensors'

        return dict(is_reciprocal=self._is_reciprocal,
                    components=components,
                    source_depth=float(self.parsed_mesh.source_depth)
                    if self._is_reciprocal is False else None,
                    velocity_model=self.parsed_mesh.background_model,
                    external_model_name=self.parsed_mesh.external_model_name,
                    attenuation=self.parsed_mesh.attenuation,
                    period=float(self.parsed_mesh.dominant_period),
                    dump_type=self.parsed_mesh.dump_type,
                    excitation_type=self.parsed_mesh.excitation_type,
                    dt=float(self.parsed_mesh.dt),
                    sampling_rate=float(1.0 / self.parsed_mesh.dt),
                    npts=int(self.parsed_mesh.ndumps),
                    nfft=int(next_pow_2(self.parsed_mesh.ndumps) * 2),
                    length=float(self.parsed_mesh.dt *
                                 (self.parsed_mesh.ndumps - 1)),
                    stf=self.parsed_mesh.stf_kind,
                    src_shift=float(self.parsed_mesh.source_shift),
                    src_shift_samples=int(self.parsed_mesh.source_shift_samp),
                    slip=self.parsed_mesh.stf_norm,
                    sliprate=self.parsed_mesh.stf_d_norm,
                    spatial_order=int(self.parsed_mesh.npol),
                    min_radius=float(self.parsed_mesh.kwf_rmin) * 1e3,
                    max_radius=float(self.parsed_mesh.kwf_rmax) * 1e3,
                    planet_radius=float(self.parsed_mesh.planet_radius),
                    min_d=float(self.parsed_mesh.kwf_colatmin),
                    max_d=float(self.parsed_mesh.kwf_colatmax),
                    time_scheme=self.parsed_mesh.time_scheme,
                    directory=os.path.relpath(self.db_path),
                    filesize=filesize,
                    compiler=self.parsed_mesh.axisem_compiler,
                    user=self.parsed_mesh.axisem_user,
                    format_version=int(self.parsed_mesh.file_version),
                    axisem_version=self.parsed_mesh.axisem_version,
                    datetime=self.parsed_mesh.creation_time)
Beispiel #25
0
def rel_calib_stack(st1,
                    st2,
                    calib_file,
                    window_len,
                    overlap_frac=0.5,
                    smooth=0,
                    save_data=True):
    """
    Method for relative calibration of sensors using a sensor with known
    transfer function

    :param st1: Stream or Trace object, (known)
    :param st2: Stream or Trace object, (unknown)
    :type calib_file: str or dict
    :param calib_file: file name of calibration file containing the PAZ of the
        known instrument in GSE2 standard or a dictionary with poles and zeros
        information (with keys ``'poles'``, ``'zeros'`` and ``'sensitivity'``).
    :type window_len: float
    :param window_len: length of sliding window in seconds
    :type overlap_frac: float
    :param overlap_frac: fraction of overlap, defaults to fifty percent (0.5)
    :type smooth: float
    :param smooth: variable that defines if the Konno-Ohmachi taper is used or
        not. default = 0 -> no taper generally used in geopsy: smooth = 40
    :type save_data: bool
    :param save_data: Whether or not to save the result to a file. If True, two
        output files will be created:
        * The new response in station_name.window_length.resp
        * The ref response in station_name.refResp
        Defaults to True
    :returns: frequency, amplitude and phase spectrum

    implemented after rel_calib_stack.c by M.Ohrnberger and J.Wassermann.
    """
    # transform given trace objects to streams
    if isinstance(st1, Trace):
        st1 = Stream([st1])
    if isinstance(st2, Trace):
        st2 = Stream([st2])
    # check if sampling rate and trace length is the same
    if st1[0].stats.npts != st2[0].stats.npts:
        msg = "Traces don't have the same length!"
        raise ValueError(msg)
    elif st1[0].stats.sampling_rate != st2[0].stats.sampling_rate:
        msg = "Traces don't have the same sampling rate!"
        raise ValueError(msg)
    else:
        ndat1 = st1[0].stats.npts
        sampfreq = st1[0].stats.sampling_rate

    # read waveforms
    tr1 = st1[0].data.astype(np.float64)
    tr2 = st2[0].data.astype(np.float64)

    # get window length, nfft and frequency step
    ndat = int(window_len * sampfreq)
    nfft = next_pow_2(ndat)

    # read calib file and calculate response function
    gg, _freq = _calc_resp(calib_file, nfft, sampfreq)

    # calculate number of windows and overlap
    nwin = int(np.floor((ndat1 - nfft) / (nfft / 2)) + 1)
    noverlap = nfft * overlap_frac

    auto, _freq, _t = \
        spectral_helper(tr1, tr1, NFFT=nfft, Fs=sampfreq, noverlap=noverlap)
    cross, freq, _t = \
        spectral_helper(tr2, tr1, NFFT=nfft, Fs=sampfreq, noverlap=noverlap)

    res = (cross / auto).sum(axis=1) * gg

    # The first item might be zero. Problems with phase calculations.
    res = res[1:]
    freq = freq[1:]
    gg = gg[1:]

    res /= nwin
    # apply Konno-Ohmachi smoothing taper if chosen
    if smooth > 0:
        # Write in one matrix for performance reasons.
        spectra = np.empty((2, len(res.real)))
        spectra[0] = res.real
        spectra[1] = res.imag
        new_spectra = \
            konno_ohmachi_smoothing(spectra, freq, bandwidth=smooth, count=1,
                                    max_memory_usage=1024, normalize=True)
        res.real = new_spectra[0]
        res.imag = new_spectra[1]

    amp = np.abs(res)
    # include phase unwrapping
    phase = np.unwrap(np.angle(res))  # + 2.0 * np.pi
    ra = np.abs(gg)
    rpha = np.unwrap(np.angle(gg))

    if save_data:
        trans_new = (st2[0].stats.station + "." + st2[0].stats.channel + "." +
                     str(window_len) + ".resp")
        trans_ref = st1[0].stats.station + ".refResp"
        # Create empty array for easy saving
        temp = np.empty((len(freq), 3))
        temp[:, 0] = freq
        temp[:, 1] = amp
        temp[:, 2] = phase
        np.savetxt(trans_new, temp, fmt=native_str('%.10f'))
        temp[:, 1] = ra
        temp[:, 2] = rpha
        np.savetxt(trans_ref, temp, fmt=native_str('%.10f'))

    return freq, amp, phase
def plot_tfr(st,
             dt=0.01,
             t0=0.,
             fmin=1.,
             fmax=10.,
             nf=100,
             w0=6,
             left=0.1,
             bottom=0.1,
             h_1=0.2,
             h_2=0.6,
             w_1=0.2,
             w_2=0.6,
             w_cb=0.01,
             d_cb=0.0,
             show=True,
             plot_args=['k', 'k'],
             clim=0.0,
             cmap=obspy_sequential,
             mode='absolute',
             fft_zero_pad_fac=0):

    import matplotlib.pyplot as plt
    from matplotlib.ticker import NullFormatter
    npts = st.shape[-1]
    tmax = (npts - 1) * dt
    t = np.linspace(0., tmax, npts) + t0

    if fft_zero_pad_fac == 0:
        nfft = npts
    else:
        nfft = util.next_pow_2(npts) * fft_zero_pad_fac

    f_lin = np.linspace(0, 0.5 / dt, nfft // 2 + 1)

    if len(st.shape) == 1:
        _w = np.zeros((1, nf, npts), dtype=complex)
        _w[0] = cwt(st, dt, w0, fmin, fmax, nf)
        ntr = 1

        spec = np.zeros((1, nfft // 2 + 1), dtype=complex)
        spec[0] = np.fft.rfft(st, n=nfft) * dt

        st = st.reshape((1, npts))
    else:
        _w = np.zeros((st.shape[0], nf, npts), dtype=complex)
        spec = np.zeros((st.shape[0], nfft // 2 + 1), dtype=complex)

        for i in np.arange(st.shape[0]):
            _w[i] = cwt(st[i], dt, w0, fmin, fmax, nf)
            spec[i] = np.fft.rfft(st[i], n=nfft) * dt

        ntr = st.shape[0]

    if mode == 'absolute':
        _tfr = np.abs(_w)
        spec = np.abs(spec)
    elif mode == 'power':
        _tfr = np.abs(_w)**2
        spec = np.abs(spec)**2
    else:
        raise ValueError('mode "' + mode + '" not defined!')

    figs = []

    for itr in np.arange(ntr):
        fig = plt.figure()

        # plot signals
        ax_sig = fig.add_axes([left + w_1, bottom, w_2, h_1])
        ax_sig.plot(t, st[itr], plot_args[0])

        # plot TFR
        ax_tfr = fig.add_axes([left + w_1, bottom + h_1, w_2, h_2])

        x, y = np.meshgrid(
            t, np.logspace(np.log10(fmin), np.log10(fmax), _tfr[itr].shape[0]))
        img_tfr = _pcolormesh_same_dim(ax_tfr, x, y, _tfr[itr], cmap=cmap)
        img_tfr.set_rasterized(True)
        ax_tfr.set_yscale("log")
        ax_tfr.set_ylim(fmin, fmax)
        ax_tfr.set_xlim(t[0], t[-1])

        # plot spectrum
        ax_spec = fig.add_axes([left, bottom + h_1, w_1, h_2])
        ax_spec.semilogy(spec[itr], f_lin, plot_args[1])

        # add colorbars
        ax_cb_tfr = fig.add_axes(
            [left + w_1 + w_2 + d_cb + w_cb, bottom + h_1, w_cb, h_2])
        fig.colorbar(img_tfr, cax=ax_cb_tfr)

        # set limits
        ax_sig.set_ylim(st.min() * 1.1, st.max() * 1.1)
        ax_sig.set_xlim(t[0], t[-1])

        xlim = spec.max() * 1.1

        ax_spec.set_xlim(xlim, 0.)
        ax_spec.set_ylim(fmin, fmax)

        if clim == 0.:
            clim = _tfr.max()

        img_tfr.set_clim(0., clim)

        ax_sig.set_xlabel(
            'Tiempo [seg] después de 2020-03-04T17:20:10.000000Z')
        ax_spec.set_ylabel('Frecuencia [Hz]')

        # remove axis labels
        ax_tfr.xaxis.set_major_formatter(NullFormatter())
        ax_tfr.yaxis.set_major_formatter(NullFormatter())

        figs.append(fig)

    if show:
        plt.show()
    else:
        if ntr == 1:
            return figs[0]
        else:
            return figs
Beispiel #27
0
def simulate_seismometer(
        data, samp_rate, paz_remove=None, paz_simulate=None,
        remove_sensitivity=True, simulate_sensitivity=True, water_level=600.0,
        zero_mean=True, taper=True, taper_fraction=0.05, pre_filt=None,
        seedresp=None, nfft_pow2=False, pitsasim=True, sacsim=False,
        shsim=False):
    """
    Simulate/Correct seismometer.

    :type data: NumPy :class:`~numpy.ndarray`
    :param data: Seismogram, detrend before hand (e.g. zero mean)
    :type samp_rate: float
    :param samp_rate: Sample Rate of Seismogram
    :type paz_remove: dict, None
    :param paz_remove: Dictionary containing keys 'poles', 'zeros', 'gain'
        (A0 normalization factor). poles and zeros must be a list of complex
        floating point numbers, gain must be of type float. Poles and Zeros are
        assumed to correct to m/s, SEED convention. Use None for no inverse
        filtering.
    :type paz_simulate: dict, None
    :param paz_simulate: Dictionary containing keys 'poles', 'zeros', 'gain'.
        Poles and zeros must be a list of complex floating point numbers, gain
        must be of type float. Or None for no simulation.
    :type remove_sensitivity: bool
    :param remove_sensitivity: Determines if data is divided by
        `paz_remove['sensitivity']` to correct for overall sensitivity of
        recording instrument (seismometer/digitizer) during instrument
        correction.
    :type simulate_sensitivity: bool
    :param simulate_sensitivity: Determines if data is multiplied with
        `paz_simulate['sensitivity']` to simulate overall sensitivity of
        new instrument (seismometer/digitizer) during instrument simulation.
    :type water_level: float
    :param water_level: Water_Level for spectrum to simulate
    :type zero_mean: bool
    :param zero_mean: If true the mean of the data is subtracted
    :type taper: bool
    :param taper: If true a cosine taper is applied.
    :type taper_fraction: float
    :param taper_fraction: Taper fraction of cosine taper to use
    :type pre_filt: list or tuple of floats
    :param pre_filt: Apply a bandpass filter to the data trace 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 seedresp: dict, None
    :param seedresp: Dictionary contains keys 'filename', 'date', 'units'.
        'filename' is the path to a RESP-file generated from a dataless SEED
        volume (or a file like object with RESP information);
        'date' is a :class:`~obspy.core.utcdatetime.UTCDateTime` object for the
        date that the response function should be extracted for (can be omitted
        when calling simulate() on Trace/Stream. the Trace's starttime will
        then be used);
        'units' defines the units of the response function.
        Can be either 'DIS', 'VEL' or 'ACC'.
    :type nfft_pow2: bool
    :param nfft_pow2: Number of frequency points to use for FFT. If True,
        the exact power of two is taken (default in PITSA). If False the
        data are not zero-padded to the next power of two which makes a
        slower FFT but is then much faster for e.g. evalresp which scales
        with the FFT points.
    :type pitsasim: bool
    :param pitsasim: Choose parameters to match
        instrument correction as done by PITSA.
    :type sacsim: bool
    :param sacsim: Choose parameters to match
        instrument correction as done by SAC.
    :type shsim: bool
    :param shsim: Choose parameters to match
        instrument correction as done by Seismic Handler.
    :return: The corrected data are returned as :class:`numpy.ndarray` float64
        array. float64 is chosen to avoid numerical instabilities.

    This function works in the frequency domain, where nfft is the next power
    of len(data) to avoid wrap around effects during convolution. The inverse
    of the frequency response of the seismometer (``paz_remove``) is
    convolved with the spectrum of the data and with the frequency response
    of the seismometer to simulate (``paz_simulate``). A 5% cosine taper is
    taken before simulation. The data must be detrended (e.g.) zero mean
    beforehand. If paz_simulate=None only the instrument correction is done.
    In the latter case, a broadband filter can be applied to the data trace
    using pre_filt. This restricts the signal to the valid frequency band and
    thereby avoids artifacts due to amplification of frequencies outside of the
    instrument's passband (for a detailed discussion see
    *Of Poles and Zeros*, F. Scherbaum, Kluwer Academic Publishers).

    .. versionchanged:: 0.5.1
        The default for `remove_sensitivity` and `simulate_sensitivity` has
        been changed to ``True``. Old deprecated keyword arguments `paz`,
        `inst_sim`, `no_inverse_filtering` have been removed.
    """
    # Checking the types
    if not paz_remove and not paz_simulate and not seedresp:
        msg = "Neither inverse nor forward instrument simulation specified."
        raise TypeError(msg)

    for d in [paz_remove, paz_simulate]:
        if d is None:
            continue
        for key in ['poles', 'zeros', 'gain']:
            if key not in d:
                raise KeyError("Missing key: %s" % key)
    # Translated from PITSA: spr_resg.c
    delta = 1.0 / samp_rate
    #
    ndat = len(data)
    data = data.astype(np.float64)
    if zero_mean:
        data -= data.mean()
    if taper:
        if sacsim:
            data *= cosine_taper(ndat, taper_fraction,
                                 sactaper=sacsim, halfcosine=False)
        else:
            data *= cosine_taper(ndat, taper_fraction)
    # The number of points for the FFT has to be at least 2 * ndat (in
    # order to prohibit wrap around effects during convolution) cf.
    # Numerical Recipes p. 429 calculate next power of 2.
    if nfft_pow2:
        nfft = util.next_pow_2(2 * ndat)
    # evalresp scales directly with nfft, therefore taking the next power of
    # two has a greater negative performance impact than the slow down of a
    # not power of two in the FFT
    else:
        nfft = _npts2nfft(ndat)
    # Transform data in Fourier domain
    data = np.fft.rfft(data, n=nfft)
    # Inverse filtering = Instrument correction
    if paz_remove:
        freq_response, freqs = paz_to_freq_resp(
            paz_remove['poles'], paz_remove['zeros'], paz_remove['gain'],
            delta, nfft, freq=True)
    if seedresp:
        freq_response, freqs = evalresp(delta, nfft, seedresp['filename'],
                                        seedresp['date'],
                                        units=seedresp['units'], freq=True,
                                        network=seedresp['network'],
                                        station=seedresp['station'],
                                        locid=seedresp['location'],
                                        channel=seedresp['channel'])
        if not remove_sensitivity:
            msg = "remove_sensitivity is set to False, but since seedresp " + \
                  "is selected the overall sensitivity will be corrected " + \
                  " for anyway!"
            warnings.warn(msg)
    if paz_remove or seedresp:
        if pre_filt:
            # make cosine taper
            fl1, fl2, fl3, fl4 = pre_filt
            if sacsim:
                cos_win = cosine_sac_taper(freqs, flimit=(fl1, fl2, fl3, fl4))
            else:
                cos_win = cosine_taper(freqs.size, freqs=freqs,
                                       flimit=(fl1, fl2, fl3, fl4))
            data *= cos_win
        invert_spectrum(freq_response, water_level)
        data *= freq_response
        del freq_response
    # Forward filtering = Instrument simulation
    if paz_simulate:
        data *= paz_to_freq_resp(paz_simulate['poles'], paz_simulate['zeros'],
                                 paz_simulate['gain'], delta, nfft)

    data[-1] = abs(data[-1]) + 0.0j
    # transform data back into the time domain
    data = np.fft.irfft(data)[0:ndat]
    if pitsasim:
        # linear detrend
        data = simple_detrend(data)
    if shsim:
        # detrend using least squares
        data = scipy.signal.detrend(data, type="linear")
    # correct for involved overall sensitivities
    if paz_remove and remove_sensitivity and not seedresp:
        data /= paz_remove['sensitivity']
    if paz_simulate and simulate_sensitivity:
        data *= paz_simulate['sensitivity']
    return data
def plotFK(st,
           startTime,
           endTime,
           frqlow,
           frqhigh,
           sll_x=-3.6,
           slm_x=3.6,
           sll_y=-3.6,
           slm_y=3.6,
           sl_s=0.18,
           beam='bartlett',
           prewhiten=0,
           coordsys='lonlat',
           verbose=False,
           plot=True,
           normalize=True,
           cmap='inferno_r',
           sl_circle=True,
           interpolation=None,
           vmin=None,
           vmax=None,
           plot_normalize=False,
           sl_corr=[0., 0.]):
    '''
    Modified from Stephen Arrowsmith's ROSES 2020 class

    Computes and displays an FK plot for an ObsPy Stream object, st, given
    a start time and end time (as UTCDateTime objects) and a frequency band
    defined by frqlow and frqhigh. The slowness grid is defined as optional
    parameters (in s/km).

    This function implements code directly from ObsPy, which has been optimized,
    for simply plotting the FK spectrum

    It includes the option to normalize the data in the time window before running FK

    It also includes the option to apply a slowness correction, defined by sl_corr
    '''

    stream = st.copy()
    stream = stream.trim(startTime, endTime)
    nstat = len(stream)

    fk_methods = dict(bartlett=0, capon=1)

    if nstat > 0:
        if normalize:
            for ms in stream:
                ms.data = ms.data / np.max(np.abs(ms.data))

        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)

        time_shift_table = get_timeshift(geometry, sll_x, sll_y, sl_s,
                                         grdpts_x, grdpts_y)

        fs = stream[0].stats.sampling_rate
        nsamp = stream[0].stats.npts

        # 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)

        # 0.22 matches 0.2 of historical C bbfk.c
        tap = cosine_taper(nsamp, p=0.22)
        relpow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64)
        abspow_map = np.empty((grdpts_x, grdpts_y), dtype=np.float64)

        for i, tr in enumerate(stream):
            dat = tr.data
            dat = (dat - dat.mean()) * tap
            ft[i, :] = np.fft.rfft(dat, nfft)[nlow:nlow + nf]

        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 i != j:
                    _r[:, j, i] = _r[:, i, j].conjugate()
                else:
                    dpow += np.abs(_r[:, i, j].sum())
        dpow *= nstat

        clibsignal.generalizedBeamformer(relpow_map, abspow_map, steer, _r,
                                         nstat, prewhiten, grdpts_x, grdpts_y,
                                         nf, dpow, fk_methods[beam])
        fisher_map = (nstat - 1) * relpow_map / (1 - relpow_map)

        (ix, iy) = np.unravel_index(relpow_map.argmax(), relpow_map.shape)

        # here we compute baz, slow
        slow_x = sll_x + ix * sl_s
        slow_y = sll_y + iy * sl_s

        # ---------
        slow_x = slow_x - sl_corr[0]
        slow_y = slow_y - sl_corr[1]
        #print(slow_x, slow_y)
        # ---------

        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 plot:
            n_frames = 3
            (fig, ax) = plt.subplots(1,
                                     n_frames,
                                     sharey=True,
                                     figsize=(8, 3.5),
                                     constrained_layout=True)

            extent = extent = [sll_x, slm_x + sl_s, sll_y, slm_y + sl_s]

            # FK power
            i = 0
            H = np.flipud(np.fliplr(abspow_map.T))
            if plot_normalize:
                H = H / H.max()
            im = ax[i].imshow(H,
                              extent=extent,
                              origin='lower',
                              aspect='auto',
                              cmap=cmap,
                              interpolation=interpolation)
            plt.colorbar(im,
                         ax=ax[i],
                         orientation="horizontal",
                         label='FK Power')

            # Semblance
            i += 1
            H = np.flipud(np.fliplr(relpow_map.T))
            if plot_normalize:
                H = H / H.max()
            im = ax[i].imshow(H,
                              extent=extent,
                              origin='lower',
                              aspect='auto',
                              cmap=cmap,
                              interpolation=interpolation)
            plt.colorbar(im,
                         ax=ax[i],
                         orientation="horizontal",
                         label='Semblance')

            # Fisher ratio
            i += 1
            H = np.flipud(np.fliplr(fisher_map.T))
            if plot_normalize:
                H = H / H.max()
            im = ax[i].imshow(H,
                              extent=extent,
                              origin='lower',
                              aspect='auto',
                              cmap=cmap,
                              interpolation=interpolation)
            plt.colorbar(im,
                         ax=ax[i],
                         orientation="horizontal",
                         label='Fisher ratio')

            for i in range(0, n_frames):
                if sl_circle:
                    angles = np.deg2rad(np.arange(0., 360, 1.))
                    slowness = dict(seismic_P=6.0,
                                    Rayleigh=3.0,
                                    infrasound=0.34)
                    for (key, radius) in slowness.items():
                        x_circle = np.sin(angles) / radius
                        y_circle = np.cos(angles) / radius
                        ax[i].plot(x_circle,
                                   y_circle,
                                   linestyle='solid',
                                   label=key,
                                   alpha=0.6)

                ax[i].plot(0, 0, 'k+')
                ax[i].plot(-slow_x, -slow_y, 'w+')
                ax[i].set_xlabel('x-slowness [s/km]')

            ax[0].set_ylabel('y-slowness [s/km]')

            baz_max = round(baz % 360., 2)
            appvel_max = round(1 / slow, 2)
            title_str = (f'Peak semblance at {baz_max:.2f} deg. '
                         f'and {appvel_max:.2f} km/s '
                         f'between [ {frqlow:.2f} - {frqhigh:.2f} ] Hz')
            fig.suptitle(title_str)

            return fig, ax

        # # only flipping left-right, when using imshow to plot the matrix is takes
        # # points top to bottom points are now starting at top-left in row major
        # return np.fliplr(relpow_map.T), baz % 360, 1. / slow

    else:
        print(f'No data present for timerange {startTime} - {endTime}')
        return