def stft_classification( rsl, window_length, threshold, f_divide, t_dry_start=None, t_dry_stop=None, dry_length=None, mirror=False, window=None, Pxx=None, f=None, f_sampling=1 / 60, ): """Perform wet/dry classification with Rolling Fourier-transform method Parameters ---------- rsl : iterable of float Time series of received signal level window_length : int Length of the sliding window threshold : int Threshold which has to be surpassed to classifiy a period as 'wet' f_divide : float Parameter for classification with method Fourier transformation t_dry_start : int Index of starting point dry period t_dry_stop : int Index of end of dry period dry_length : int Length of dry period that will be automatically identified in the provided rsl time series mirror : bool (defaults to False) Mirroring values in window at end of time series window : array of float, optional Values of window function. If not given a Hamming window function is applied (Default is None) Pxx : 2-D array of float, optional Spectrogram used for the wet/dry classification. Gets computed if not given (Default is None) f : array of float, optional Frequencies corresponding to the rows in Pxx. Gets computed if not given. (Default is None) f_sampling : float, optional Sampling frequency (samples per time unit). It is used to calculate the Fourier frequencies, freqs, in cycles per time unit. (Default is 1/60.0) mirror : bool Returns ------- iterable of int Time series of wet/dry classification dict Dictionary holding information about the classification Note ---- Implementation of Rolling Fourier-transform method [2]_ References ---------- .. [2] Chwala, C., Gmeiner, A., Qiu, W., Hipp, S., Nienaber, D., Siart, U., Eibert, T., Pohl, M., Seltmann, J., Fritz, J. and Kunstmann, H.: "Precipitation observation using microwave backhaul links in the alpine and pre-alpine region of Southern Germany", Hydrology and Earth System Sciences, 16, 2647-2661, 2012 """ # Calculate spectrogram Pxx if it is not supplied as function argument if Pxx is None: # Set up sliding window for STFT if mirror: # Window length has to be even if window_length % 2 == 0: NFFT = window_length else: NFFT = window_length + 1 else: NFFT = window_length if window is None: window = np.hamming(window_length) # Calculate spectrogram using STFT Pxx, f, t = specg(rsl, NFFT=NFFT, Fs=f_sampling, noverlap=NFFT - 1, window=window) elif Pxx is not None and f is not None: print("Skipping spectrogram calculation and using supplied Pxx") # # TODO: check that Pxx has the correct size # # ..... assert len(Pxx[0]) == len(rsl) - window_length elif Pxx is not None and f is None: raise ValueError("You have to supply f if you supply Pxx") else: raise ValueError("This should be impossible") # Add NaNs as the missing spectral data at the beginning and end of # the time series (stemming from the window length) N_diff = len(rsl) - len(Pxx[0]) N_missing_start = np.floor(N_diff / 2) if mirror: for i in range((len(rsl) - 1) - (NFFT / 2 - 1), len(rsl)): rsl_mirr = np.concatenate( (rsl[i - (NFFT / 2 - 1):i], rsl[i - (NFFT / 2 - 1):i][::-1])) Pxx_mirr, f, t = specg(rsl_mirr, NFFT=NFFT, Fs=f_sampling, noverlap=NFFT - 1, window=window) if i == (len(rsl) - 1) - (NFFT / 2 - 1): Pxx_end = Pxx_mirr else: Pxx_end = np.append(Pxx_end, Pxx_mirr, 1) Pxx_extended = np.concatenate( (nans([len(Pxx), N_missing_start]), Pxx, Pxx_end), 1) else: N_missing_end = N_diff - N_missing_start Pxx_extended = np.concatenate( (nans([len(Pxx), N_missing_start ]), Pxx, nans([len(Pxx), N_missing_end])), 1) if (t_dry_start is None) and (t_dry_stop is None) and (dry_length is not None): # Find dry period t_dry_start, t_dry_stop = find_lowest_std_dev_period(rsl, dry_length) elif ((t_dry_start is not None) and (t_dry_stop is not None) and (dry_length is None)): # Do nothing, since t_dry_start and t_dry_stop are defined pass else: raise AttributeError("Either `t_dry_start` and `t_dry_stop` or " "`dry_length` have to be supplied.") # Calculate mean dry spectrum P_dry_mean = np.nanmean(Pxx_extended[:, t_dry_start:t_dry_stop], axis=1) # Normalize the power spectrogram with the mean dry spectrum. # The array([...]) syntax is needed to transpose P_dry_mean to # a column vector (1D arrays cannot be transposed in Numpy) P_norm = Pxx_extended / np.array([P_dry_mean]).T i_f_divide_low = np.where(f <= f_divide) i_f_divide_high = np.where(f > f_divide) N_f_divide_low = len(i_f_divide_low) N_f_divide_high = len(i_f_divide_high) P_norm_low = np.mean(P_norm[i_f_divide_low], axis=0) P_norm_high = np.mean(P_norm[i_f_divide_high], axis=0) P_sum_diff = P_norm_low / N_f_divide_low - P_norm_high / N_f_divide_high nan_index = np.isnan(P_sum_diff) wet = np.zeros_like(P_sum_diff, dtype=np.bool) wet[~nan_index] = P_sum_diff[~nan_index] > threshold info = { "P_norm": P_norm, "P_sum_diff": P_sum_diff, "Pxx": Pxx_extended, "P_dry_mean": P_dry_mean, "f": f, } return wet, info
def wet_dry_stft(rsl, window_length, threshold, f_divide, t_dry_start, t_dry_stop, window=None, Pxx=None, f=None, f_sampling=1 / 60.0): """Perform wet/dry classification with Rolling Fourier-transform method Parameters ---------- rsl : iterable of float Time series of received signal level window_length : int Length of the sliding window threshold : int Threshold which has to be surpassed to classifiy a period as 'wet' f_divide : float Parameter for classification with method Fourier transformation t_dry_start : int Index of starting point dry period t_dry_stop : int Index of end of dry period window : array of float, optional Values of window function. If not given a Hamming window function is applied (Default is None) Pxx : 2-D array of float, optional Spectogram used for the wet/dry classification. Gets computed if not given (Default is None) f : array of float, optional Frequencies corresponding to the rows in Pxx. Gets computed if not given. (Default is None) f_sampling : float, optional Sampling frequency (samples per time unit). It is used to calculate the Fourier frequencies, freqs, in cycles per time unit. (Default is 1/60.0) mirror : bool Returns ------- iterable of int Time series of wet/dry classification dict Dictionary holding information about the classification Note ---- Implementation of Rolling Fourier-transform method [2]_ References ---------- .. [2] Chwala, C., Gmeiner, A., Qiu, W., Hipp, S., Nienaber, D., Siart, U., Eibert, T., Pohl, M., Seltmann, J., Fritz, J. and Kunstmann, H.: "Precipitation observation using microwave backhaul links in the alpine and pre-alpine region of Southern Germany", Hydrology and Earth System Sciences, 16, 2647-2661, 2012 """ import numpy as np #from pylab import specgram from matplotlib.mlab import specgram as specg # Calculate spectrogram Pxx if it is not supplied as function argument if Pxx is None: # Set up sliding window for STFT NFFT = window_length if window == None: window = np.hamming(window_length) # Calculate spectrogram using STFT Pxx, f, t = specg(rsl, NFFT=NFFT, Fs=f_sampling, noverlap=NFFT - 1, window=window) elif Pxx is not None and f is not None: print 'Skipping spectrogram calculation and using supplied Pxx' # # TODO: check that Pxx has the correct size # #..... assert len(Pxx[0]) == len(rsl) - window_length elif Pxx is not None and f is None: raise ValueError('You have to supply f if you supply Pxx') else: raise ValueError('This should be imposible') # Add NaNs as the missing spectral data at the begining and end of # the time series (stemming from the window length) N_diff = len(rsl) - len(Pxx[0]) N_missing_start = np.floor(N_diff / 2.0) N_missing_end = N_diff - N_missing_start Pxx_extended = np.concatenate((nans( [len(Pxx), N_missing_start]), Pxx, nans([len(Pxx), N_missing_end])), 1) # Calculate mean dry spectrum P_dry_mean = np.nanmean(Pxx_extended[:, t_dry_start:t_dry_stop], axis=1) # Normalize the power spectrogram with the mean dry spectrum. # The arrary([...]) syntax is needed to transpose P_dry_mean to # a column vector (1D arrays cannot be transposed in Numpy) P_norm = Pxx_extended / np.array([P_dry_mean]).T i_f_divide_low = np.where(f <= f_divide) i_f_divide_high = np.where(f > f_divide) N_f_divide_low = len(i_f_divide_low) N_f_divide_high = len(i_f_divide_high) P_norm_low = np.mean(P_norm[i_f_divide_low], axis=0) P_norm_high = np.mean(P_norm[i_f_divide_high], axis=0) P_sum_diff = P_norm_low / N_f_divide_low - P_norm_high / N_f_divide_high wet = P_sum_diff > threshold info = { 'P_norm': P_norm, 'P_sum_diff': P_sum_diff, 'Pxx': Pxx_extended, 'P_dry_mean': P_dry_mean, 'f': f } return wet, info