├── .gitignore ├── example_data ├── ecg_data.mat ├── eeg_data.mat ├── p234_004.wav ├── msqi_ecg_data.mat └── info.txt ├── ama_toolbox ├── key_pressed_fcn.m ├── cosinewin.m ├── is_octave.m ├── plot_signal.m ├── varea.m ├── iwavelet_spectrogram.m ├── irfft_psd.m ├── iepoching.m ├── rfft.m ├── istrfft_spectrogram.m ├── conv_fft.m ├── iwavelet_modulation_spectrogram.m ├── plot_psd_data.m ├── irfft.m ├── istrfft_modulation_spectrogram.m ├── epoching.m ├── msqi_ama.m ├── plot_spectrogram_data.m ├── wavelet_spectrogram.m ├── rfft_psd.m ├── plot_modulation_spectrogram_data.m ├── cmorlet_wavelet.m ├── strfft_spectrogram.m ├── wavelet_modulation_spectrogram.m ├── strfft_modulation_spectrogram.m ├── explore_wavelet_ama_gui.m ├── viridis.m └── explore_stfft_ama_gui.m ├── LICENSE.md ├── CITATION.cff ├── CONTRIBUTING.md ├── example_msqi.m ├── example_01.m ├── example_04.m ├── README.md ├── example_05.m ├── example_02.m └── example_03.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows default autosave extension 2 | *.asv 3 | 4 | # OSX / *nix default autosave extension 5 | *.m~ 6 | -------------------------------------------------------------------------------- /example_data/ecg_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuSAELab/amplitude-modulation-analysis-matlab/HEAD/example_data/ecg_data.mat -------------------------------------------------------------------------------- /example_data/eeg_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuSAELab/amplitude-modulation-analysis-matlab/HEAD/example_data/eeg_data.mat -------------------------------------------------------------------------------- /example_data/p234_004.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuSAELab/amplitude-modulation-analysis-matlab/HEAD/example_data/p234_004.wav -------------------------------------------------------------------------------- /ama_toolbox/key_pressed_fcn.m: -------------------------------------------------------------------------------- 1 | function key_pressed_fcn( fig_obj , eventDat) 2 | global key_pressed 3 | key_pressed = eventDat.Key; -------------------------------------------------------------------------------- /example_data/msqi_ecg_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MuSAELab/amplitude-modulation-analysis-matlab/HEAD/example_data/msqi_ecg_data.mat -------------------------------------------------------------------------------- /ama_toolbox/cosinewin.m: -------------------------------------------------------------------------------- 1 | function w = cosinewin(n) 2 | %COSINEWIN Returns a cosine window with N elements 3 | n_vct = (0.5 : 1 : n-0.5)'; 4 | w = sin(pi* n_vct ./ n ); 5 | end -------------------------------------------------------------------------------- /ama_toolbox/is_octave.m: -------------------------------------------------------------------------------- 1 | function retval = is_octave 2 | % Return: true if the environment is Octave. 3 | % 4 | persistent cacheval; % speeds up repeated calls 5 | if isempty (cacheval) 6 | cacheval = (exist ('OCTAVE_VERSION', 'builtin') > 0); 7 | end 8 | retval = cacheval; 9 | end -------------------------------------------------------------------------------- /example_data/info.txt: -------------------------------------------------------------------------------- 1 | The speech signal (p234_004.wav) consists in one sample from the: 2 | CSTR VCTK Corpus: English Multi-speaker Corpus for CSTR Voice Cloning Toolkit 3 | avialable in: https://datashare.is.ed.ac.uk/handle/10283/2651 4 | 5 | p234_004.wav says: "We also need a small plastic snake and a big toy frog for the kids". -------------------------------------------------------------------------------- /ama_toolbox/plot_signal.m: -------------------------------------------------------------------------------- 1 | function plot_signal(x, fs, name) 2 | %PLOT_SIGNAL Behaves as matplotlib.pyplot.plot(x) but X axis is definded by `fs` [Hz] 3 | % 4 | % Parameters 5 | % ---------- 6 | % x : 7 | % 1D or 2D Signals as column vectors 8 | % fs : 9 | % Sampling frequency in Hz 10 | % name : 11 | % Name of the signal (Default 'Signal-01') 12 | 13 | % Create time vector 14 | time_vector = (0:size(x,1)-1)./fs; 15 | 16 | plot(time_vector,x) 17 | xlabel('time (s)') 18 | 19 | if ~exist('name', 'var') || isempty(name) 20 | name = 'Signal-01'; 21 | end 22 | 23 | set(gca, 'XLim', [0, time_vector(end)]); 24 | 25 | title(name); 26 | 27 | end 28 | 29 | -------------------------------------------------------------------------------- /ama_toolbox/varea.m: -------------------------------------------------------------------------------- 1 | function h = varea(X, color_cell, alpha_v) 2 | %VAREA Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | if size(X, 2) ~= 2 6 | error('X must be of the size [n, 2]') 7 | end 8 | 9 | % Validate 'alpha_v' argumet 10 | if ~exist('alpha_v','var') || isempty(alpha_v) 11 | alpha_v = 0.2; 12 | end; 13 | 14 | y_limits = get(gca,'ylim'); 15 | y_limits2 = [y_limits, fliplr(y_limits)]; 16 | 17 | n_areas = size(X, 1); 18 | 19 | for i_area = 1 : n_areas 20 | x1 = X(i_area, 1); 21 | x2 = X(i_area, 2); 22 | hold on; 23 | h = fill([x1, x1, x2, x2], y_limits2, color_cell{i_area}); 24 | if ~is_octave() 25 | alpha(h, alpha_v); 26 | end 27 | hold off 28 | end 29 | 30 | 31 | end 32 | 33 | -------------------------------------------------------------------------------- /ama_toolbox/iwavelet_spectrogram.m: -------------------------------------------------------------------------------- 1 | function x = iwavelet_spectrogram(spectrogram_data) 2 | %[x, x_epoched] = iwavelet_spectrogram(spectrogram_data) 3 | % 4 | % Compute the inverse CWT Spectrogram for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % spectrogram_data : Structure with CWT Spectrogram data, created with wavelet_spectrogram() 9 | % 10 | % Returns 11 | % ------- 12 | % x : 1D array with shape (n_samples) or 13 | % 2D array with shape (n_samples, n_channels) 14 | % 15 | % Example: 16 | % xi = randn(1000,1); 17 | % xi_cwt = wavelet_spectrogram(xi, 1000); 18 | % xo = iwavelet_spectrogram(xi_cwt); 19 | 20 | % compute the scaling factor for each wavelet kernel 21 | s = spectrogram_data.n_cycles ./ ( 2 * pi * spectrogram_data.freq_axis); 22 | A = 1 ./ ((s.^2) * pi).^(1/4); 23 | 24 | % compute the mean across scaled "filtered" signals 25 | x = squeeze(mean( bsxfun(@rdivide, real(spectrogram_data.wavelet_coefficients) , A ), 2)); 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Raymundo Cassani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Cassani" 5 | given-names: "Raymundo" 6 | orcid: "https://orcid.org/0000-0002-3509-5543" 7 | - family-names: "Albuquerque" 8 | given-names: "Isabela" 9 | - family-names: "Monteiro" 10 | given-names: "João" 11 | - family-names: "Falk" 12 | given-names: "Tiago H." 13 | orcid: "https://orcid.org/0000-0002-5739-2514" 14 | title: "Amplitude Modulation Analysis Toolbox" 15 | url: "https://github.com/MuSAELab/amplitude-modulation-analysis-matlab" 16 | preferred-citation: 17 | type: conference-paper 18 | authors: 19 | - family-names: "Cassani" 20 | given-names: "Raymundo" 21 | orcid: "https://orcid.org/0000-0002-3509-5543" 22 | - family-names: "Albuquerque" 23 | given-names: "Isabela" 24 | - family-names: "Monteiro" 25 | given-names: "João" 26 | - family-names: "Falk" 27 | given-names: "Tiago H." 28 | orcid: "https://orcid.org/0000-0002-5739-2514" 29 | title: "AMA: An Open-source Amplitude Modulation Analysis Toolkit for Signal Processing Applications" 30 | year: 2019 31 | collection-title: "2019 IEEE Global Conference on Signal and Information Processing (GlobalSIP)" 32 | start: "1" # First page number 33 | end: "4" # Last page number 34 | doi: "10.1109/GlobalSIP45357.2019.8969210" 35 | -------------------------------------------------------------------------------- /ama_toolbox/irfft_psd.m: -------------------------------------------------------------------------------- 1 | function x = irfft_psd(psd_data) 2 | %x = irfft_psd(psd_data) 3 | % 4 | % Compute the inverse PSD for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % psd_data : Structure with PSD data, created with rfft_psd() 9 | % 10 | % Returns 11 | % ------- 12 | % x : 1D array with shape (n_samples) or 13 | % 2D array with shape (n_samples, n_channels) 14 | % 15 | % Example: 16 | % xi = randn(100,1); 17 | % xi_psd = rfft_psd(xi, 10); 18 | % xo = irfft_psd(xi_psd); 19 | 20 | % Load data from PSD structure 21 | rFFT_data = psd_data.rFFT; 22 | f_ax = psd_data.freq_axis; 23 | fs = psd_data.fs; 24 | win_function = psd_data.win_function; 25 | n_samples = psd_data.n_samples; 26 | n_channels = size(rFFT_data, 2); 27 | 28 | % Find the number of elements used for the rFFT 29 | if f_ax(end) < fs/2 30 | % elements for FFT was odd 31 | n_fft = (numel(f_ax) * 2) - 1; 32 | elseif f_ax(end) - fs/2 < 1000 * eps 33 | % elements for FFT was even 34 | n_fft = (numel(f_ax) - 1) * 2; 35 | end 36 | 37 | % Window RMS 38 | win = window(win_function, n_samples); 39 | win_rms = sqrt(sum(win.^2) / n_samples); 40 | 41 | % IRFFT 42 | X = rFFT_data * win_rms; 43 | x_tmp = irfft(X, n_fft); 44 | 45 | % Keep only n_samples points 46 | x = x_tmp(1 : n_samples, :); 47 | 48 | % Un-Windowing 49 | win = window(win_function,n_samples); 50 | win_mat = repmat(win, 1, n_channels); 51 | x = x ./ win_mat; 52 | -------------------------------------------------------------------------------- /ama_toolbox/iepoching.m: -------------------------------------------------------------------------------- 1 | function x = iepoching(epochs, shift_epoch) 2 | % x = IEPOCHING(epochs, shift_epochs) 3 | % 4 | % Merges a set of epochs [n_samples_epoch, n_channels] into 5 | % the complete signal(s) x [n_samples, n_channels] taking into account 6 | % the shift between consecutive epochs 7 | % 8 | % Parameters 9 | % ---------- 10 | % epochs : 2D array_like with shape (n_samples, n_channels) 11 | % shift_epoch : number of samples in smaller epochs 12 | % 13 | % Returns 14 | % ------- 15 | % x : 2D array with shape (samples_epoch, n_channels, n_epochs) 16 | 17 | % Verify the number of Input arguments 18 | if nargin < 2 19 | error('iepoching funtion requires 2 arguments'); 20 | end 21 | 22 | % Obtain parameters 23 | [size_epoch, n_channels, n_epochs] = size(epochs); 24 | n_samples = (shift_epoch * (n_epochs - 1)) + size_epoch; 25 | ix = 1 + [0:n_epochs - 1 ] * shift_epoch; 26 | 27 | % merging matrix 28 | merging = zeros(n_samples, n_channels, 2); 29 | % Number of epochs that contribute for a specific point 30 | n_merging = merging; 31 | 32 | for i_epoch = 1 : n_epochs 33 | merging(ix(i_epoch) : ix(i_epoch) + size_epoch - 1, :, 2 ) = squeeze(epochs(:, :, i_epoch)); 34 | n_merging(ix(i_epoch) : ix(i_epoch) + size_epoch - 1, :, 2) = 1; 35 | merging(:,:,1) = sum(merging, 3); 36 | n_merging(:,:,1) = sum(n_merging, 3); 37 | merging(ix(i_epoch) : ix(i_epoch) + size_epoch - 1, :, 2 ) = 0; 38 | n_merging(ix(i_epoch) : ix(i_epoch) + size_epoch - 1, :, 2 ) = 0; 39 | end 40 | 41 | x = merging(:,:,1)./n_merging(:,:,1); -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for contributing to this repository. 4 | 5 | When contributing, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. 6 | 7 | ## How to contribute. 8 | The general process to contribute to an open source project can be found in detail in this great post: [https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/). 9 | 10 | In summary, the process consists on 7 steps: 11 | 12 | 1. Fork the repository to your GitHub account and clone it to your computer. 13 | 14 | ``` 15 | $ git clone https://github.com/USERNAME/FORK.git 16 | ``` 17 | 18 | 2. Create a upstream remote (to this repository) and sync your local copy. 19 | 20 | ``` 21 | $ git remote add upstream https://github.com/MuSAELab/amplitude-modulation-analysis-toolbox.git 22 | ``` 23 | 24 | At this point `origin` refers to your forked repository (in your account) and `upstream` to the repository in [https://github.com/MuSAELab](https://github.com/MuSAELab) 25 | 26 | ``` 27 | $ git checkout master 28 | $ git pull upstream master && git push origin master 29 | ``` 30 | 3. Create a branch in your local copy. 31 | 32 | ``` 33 | git checkout -b fixing-something 34 | ``` 35 | 36 | 4. Perform the change, write good commit messages. 37 | 38 | 5. Push your branch to your fork in GitHub. 39 | 40 | ``` 41 | git push -u origin fixing-something 42 | ``` 43 | 44 | 6. Create a new Pull Request. 45 | 46 | 7. Feedback and to merge your work to the `upstream` repository 47 | -------------------------------------------------------------------------------- /example_msqi.m: -------------------------------------------------------------------------------- 1 | %% Example MSQI 2 | % This example shows the use of the amplitude modulation analysis toolkit 3 | % to compute the Modulation Spectrum-Based ECG Quality Index (MSQI) as a 4 | % blind metric to measure the signal-to-noise (SNR) ration in ECG signals 5 | % 6 | % The MSQI was originally presented in: 7 | % 8 | % D. P. Tobon V., T. H. Falk, and M. Maier, "MS-QI: A Modulation 9 | % Spectrum-Based ECG Quality Index for Telehealth Applications", IEEE 10 | % Transactions on Biomedical Engineering, vol. 63, no. 8, pp. 1613-1622, 11 | % Aug. 2016 12 | 13 | %% ECG signal 14 | % load ECG signal 15 | % x_clean is a 5-s segment of clean ECG signal 16 | % x_noisy is the x_clean signal contaminated with pink noise to have a 0db SNR 17 | load('./example_data/msqi_ecg_data.mat'); 18 | 19 | %% Compute MSQI and heart rate (HR), and plot modulation spectrogram 20 | [msqi_clean, hr_clean, modulation_spectrogram_clean] = msqi_ama(x_clean, fs); 21 | [msqi_noisy, hr_noisy, modulation_spectrogram_noisy] = msqi_ama(x_noisy, fs); 22 | 23 | fprintf('HR = %f bpm \r\n', hr_clean); 24 | fprintf('MSQI for clean ECG = %f \r\n', msqi_clean); 25 | fprintf('MSQI for noisy ECG = %f \r\n', msqi_noisy); 26 | 27 | %% Plot modulation spectrograms 28 | figure('units','normalized','outerposition',[0 0 1 1]) 29 | % clean 30 | subplot(3,2,1) 31 | plot_signal(x_clean, fs) 32 | title('Clean ECG'); 33 | subplot(3,2,[3,5]) 34 | plot_modulation_spectrogram_data(modulation_spectrogram_clean, ... 35 | [], [0, 60], [], [-90, -40]) 36 | title('Modulation spectrogram clean ECG'); 37 | axis square 38 | subplot(3,2,2) 39 | plot_signal(x_noisy, fs) 40 | title('Noisy ECG'); 41 | subplot(3,2,[4,6]) 42 | plot_modulation_spectrogram_data(modulation_spectrogram_noisy, ... 43 | [], [0, 60], [], [-90, -40]) 44 | title('Modulation spectrogram noisy ECG'); 45 | axis square 46 | -------------------------------------------------------------------------------- /ama_toolbox/rfft.m: -------------------------------------------------------------------------------- 1 | function y = rfft(x, n, dim) 2 | %y = RFFT(x, n, dim) Real Fast Fourier Transform. 3 | % Considering a real signal A with B = fft(A), B is Hermitian symmetric, 4 | % i.e. B(-1) = conj(B(1)), therefore the complete spectrum B 5 | % can be found by using with only the non-negative frequencies in B 6 | % 7 | % Parameters 8 | % ---------- 9 | % x : 1D array with shape (n_samples) or 2D array with shape (n_samples, n_channels) 10 | % n : Number of samples to compute the FFT 11 | % dim : Dimension to compute the RFFT (Default: first array dimension whose size does not equal 1) 12 | % 13 | % Returns 14 | % ------- 15 | % y : Non-negative complex spectrum of `x`, with shape as `x` 16 | % 17 | % 18 | % See also FFT 19 | % 20 | % Example: 21 | % xi = rand(100,1); 22 | % xi_rfft = rfft(xi); 23 | % xo = irfft(xi_rfft, 100); 24 | % 25 | 26 | % verify X 27 | shape_X = size(x); 28 | if numel(shape_X) > 2 29 | error('RFFT only accepts 1D or 2D arrays as X input'); 30 | end 31 | 32 | % check shape of X, and set n and dim defaults 33 | if isvector(x) 34 | if shape_X(1) == 1 35 | % X is a row vector 36 | dim_def = 2; 37 | else 38 | % X is a column vector 39 | dim_def = 1; 40 | end 41 | else 42 | % X is a 2D Matrix, a shape [n_samples, n_channels] is asummed 43 | dim_def = 1; 44 | end 45 | 46 | % verify 'dim' dimension argument 47 | if ~exist('dim','var') || isempty(dim) 48 | dim = dim_def; 49 | end 50 | 51 | % verify 'n' 52 | if ~exist('n','var') || isempty(n) 53 | n = shape_X(dim); 54 | end 55 | 56 | % FFT 57 | Yc = fft(x, n, dim); 58 | 59 | % points to keep 60 | if ~mod(n,2) 61 | % number of samples is even 62 | n_new = (n / 2) + 1; 63 | else 64 | % number of samples is odd 65 | n_new = (n + 1) / 2; 66 | end 67 | 68 | % remove negative frequencies 69 | if dim == 1 70 | y = Yc(1:n_new, :); 71 | else 72 | y = Yc(:, 1: n_new); 73 | end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /ama_toolbox/istrfft_spectrogram.m: -------------------------------------------------------------------------------- 1 | function [x, x_epoched] = istrfft_spectrogram(spectrogram_data) 2 | %[x, x_epoched] = istrfft_spectrogram(spectrogram_data) 3 | % 4 | % Compute the inverse STFT spectrogram for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % spectrogram_data : Structure with STFT spectrogram data, created with strfft_spectrogram() 9 | % 10 | % Returns 11 | % ------- 12 | % x : 1D array with shape (n_samples) or 13 | % 2D array with shape (n_samples, n_channels) 14 | % x_epoched = Segments form the signal or set of signals utilized to 15 | % create the spectrogram in spectrogram_struct 16 | % 17 | % Example: 18 | % xi = randn(100,1); 19 | % xi_stft = strfft_spectrogram(xi, 10, 10, 5); 20 | % xo = istrfft_spectrogram(xi_stft); 21 | 22 | % Load data from Spectrogram structure 23 | rFFT_data = spectrogram_data.rFFT_spectrogram; 24 | win_size = spectrogram_data.win_size_samples; 25 | win_shift = spectrogram_data.win_shift_samples; 26 | 27 | % Generate psd_struct, to use irfft_psd() 28 | psd_struct.fs = spectrogram_data.fs; 29 | psd_struct.channel_names = spectrogram_data.channel_names; 30 | psd_struct.freq_axis = spectrogram_data.freq_axis; 31 | psd_struct.win_function = spectrogram_data.win_function; 32 | psd_struct.n_samples = win_size; 33 | 34 | % Initialize rFFT_slice and x_epoched variables 35 | [n_windows, n_freqs, n_channels] = size(rFFT_data); 36 | rfft_slide = zeros(n_freqs ,n_channels); 37 | x_epoched = zeros(win_size, n_channels ,n_windows); 38 | 39 | for i_window = 1 : n_windows 40 | % rFFT slice from spectrogram 41 | rfft_slide(:,:) = rFFT_data(i_window, :, :); 42 | % Generate psd_struct, to use irfft_psd() 43 | psd_struct.rFFT = rfft_slide; 44 | % ifft_psd from the rFFT data recovers the signal or set of signals 'x' 45 | x_tmp = irfft_psd(psd_struct); 46 | x_epoched(:, :, i_window) = x_tmp; 47 | end 48 | % Merge epoched data 49 | x = iepoching(x_epoched, win_shift); 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /ama_toolbox/conv_fft.m: -------------------------------------------------------------------------------- 1 | function c = conv_fft(a, b, mode) 2 | %CONV_FFT Convolve the a vector with collection of vectors. 3 | % 4 | % c = CONV_FFT(a, b) 5 | % 6 | % Convolve a 1D array `a` with each column of the 2D array `b`. 7 | % `c` has the same class as `a` and `b` 8 | % This function is based in the Convolution Theorem. 9 | % c = ifft(fft(a).*fft(b(:,k)) for k = 1, ... K kernels 10 | % 11 | % Paramters 12 | % a : 1D array 13 | % b : 2D array 14 | % mode : string 'full' (default) | 'same' 15 | % 'full' - The output is the full discrete linear convolution 16 | % of the inputs. (Default) 17 | % 'same' - The output is the same size as `a`, centered 18 | % with respect to the 'full' output. 19 | % Returns 20 | % c : A 2D array where each columns corresponds to the 21 | % convolution of `a` and a column of `b` 22 | % 23 | % See also DECONV, CONV2, CONV, FILTER and, 24 | % in the Signal Processing Toolbox, XCORR, CONVMTX. 25 | 26 | 27 | if ~isvector(a) || ~ismatrix(b) 28 | error(message('MATLAB:conv:ANotVector or BNotMatrix')); 29 | end 30 | 31 | % Validate 'shape' argument 32 | if ~exist('mode','var') || isempty(mode) 33 | mode = 'full'; 34 | end 35 | 36 | if ~ischar(mode) 37 | error(message('MATLAB:conv:unknownShapeParameter')); 38 | end 39 | 40 | % Input A vector to row vector 41 | a = a(:); 42 | sza = numel(a); 43 | 44 | % Full convolution is computed 45 | N = sza + size(b,1) -1; 46 | 47 | ffta = fft(a,N); 48 | 49 | K = size(b,2); 50 | ctmp = zeros(N,K); 51 | 52 | if K == 1 53 | ctmp = ifft(ffta.*fft(b,N),N); 54 | else 55 | for ik=1:K 56 | ctmp(:,ik) = ifft(ffta.*fft(b(:,ik),N),N); 57 | end 58 | end 59 | 60 | if mode(1) == 'f' 61 | c = ctmp; 62 | return 63 | end 64 | 65 | if mode(1) == 's' 66 | tocut = (N-sza)/2; 67 | if K==1 68 | c = ctmp(1+ceil(tocut):end-floor(tocut)); 69 | return 70 | else 71 | c = ctmp(1+ceil(tocut):end-floor(tocut),:); 72 | return 73 | end 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /example_01.m: -------------------------------------------------------------------------------- 1 | %% Example 01 2 | % This example shows the use of the GUI to explore Amplitude Modulation 3 | % for ECG data and EEG data 4 | % 5 | % The 'explore_strfft_ama_gui()' computes the Modulation Spectrogram. 6 | % It uses the Short Time Real Fourier Fast Transform (STRFFT) to compute 7 | % the Spectrogram, after rFFT is used to obtain the Modulation Spectrogram 8 | % 9 | % The 'explore_wavelet_ama_gui()' computes the Modulation Spectrogram using 10 | % It uses the Wavelet transform with Complex Morlet wavelet to compute 11 | % the Spectrogram, after rFFT is used to obtain the Modulation Spectrogram 12 | % 13 | % Usage for explore_*_ama_gui() 14 | % 15 | % Once the GUI is executed, it accepts the following commands 16 | % 17 | % Key Action 18 | % Up Arrow Previous channel (-1 channel) 19 | % Down Arrow Next channel (+1 channel) 20 | % Left Arrow Go back to the previous segment (-1 segment shift) 21 | % Right Arrow Advance to the next segment (+1 segment shift) 22 | % 'W' Previous channel (-5 channels) 23 | % 'S' Next channel (+5 channel) 24 | % 'A' Go back to the previous segment (-5 segment shift) 25 | % 'D' Advance to the next segment (+5 segment shift) 26 | % 27 | % 'U' Menu to update: 28 | % parameters for Modulation Spectrogram 29 | % ranges for conventional and modulation frequency axes 30 | % ranges for power in Spectrogram and Modulation Spectrogram 31 | % ESC Close the GUI 32 | % 33 | 34 | %% ECG data (1 channel) using STFFT-based Modulation Spectrogram 35 | load('./example_data/ecg_data.mat'); 36 | 37 | % STFFT Modulation Spectrogram 38 | explore_stfft_ama_gui(x, fs, 'ECG', 'jet'); 39 | 40 | %% ECG data (1 channel) using wavelet-based Modulation Spectrogram 41 | load('./example_data/ecg_data.mat'); 42 | 43 | % Wavelet Modulation Spectrogram 44 | explore_wavelet_ama_gui(x, fs, 'ECG'); 45 | 46 | %% EEG data (7 channels) using STFFT-based Modulation Spectrogram 47 | load('./example_data/eeg_data.mat'); 48 | 49 | % STFFT Modulation Spectrogram 50 | explore_stfft_ama_gui(x, fs, ch_names); 51 | 52 | %% EEG data (7 channels) using wavelet-based Modulation Spectrogram 53 | load('./example_data/eeg_data.mat'); 54 | 55 | % Wavelet Modulation Spectrogram 56 | explore_wavelet_ama_gui(x, fs, ch_names); 57 | -------------------------------------------------------------------------------- /ama_toolbox/iwavelet_modulation_spectrogram.m: -------------------------------------------------------------------------------- 1 | function x = iwavelet_modulation_spectrogram(modulation_spectrogram_data) 2 | % x = istrfft_modspectrogram(modulation_spectrogram_data) 3 | % 4 | % Compute the inverse CWT-based modulation spectrogram for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % modulation_spectrogram_data : Structure with CWT-based modulation spectrogram data, 9 | % created with wavelet_modulation_spectrogram() 10 | % 11 | % Returns 12 | % ------- 13 | % x : 1D array with shape (n_samples) or 14 | % 2D array with shape (n_samples, n_channels) 15 | % 16 | % Example: 17 | % xi = randn(1000,1); 18 | % xi_cwt = wavelet_modulation_spectrogram(xi, 1000); 19 | % xo = iwavelet_modulation_spectrogram(xi_cwt); 20 | 21 | % Number of channels from Modspectrogram structure 22 | n_channels = size(modulation_spectrogram_data.rFFT_modulation_spectrogram, 3); 23 | 24 | % Prepare psd_tmp_struct to perform irFFT on Modulation Spectogram 25 | psd_tmp_data.freq_axis = modulation_spectrogram_data.freq_mod_axis; 26 | psd_tmp_data.fs = modulation_spectrogram_data.fs_mod; 27 | psd_tmp_data.win_function = modulation_spectrogram_data.win_function_x; 28 | psd_tmp_data.n_samples = modulation_spectrogram_data.n_samples; 29 | 30 | 31 | for i_channel = 1 : n_channels 32 | % Slide with the rFFT coeffients of the 2nd FFT 33 | psd_tmp_data.rFFT = transpose(modulation_spectrogram_data.rFFT_modulation_spectrogram(:,:,i_channel)); 34 | % Recovers the Square Root of the Power Spectrogram 35 | sqrt_pwr_spectrogram = irfft_psd(psd_tmp_data); 36 | 37 | % Recovers the Magnitude of the Wavelet Coefficients 38 | pwr_spectrogram = sqrt_pwr_spectrogram .^2; 39 | pwr_spectrogram = pwr_spectrogram * modulation_spectrogram_data.fs_mod * modulation_spectrogram_data.n_samples; 40 | pwr_spectrogram = pwr_spectrogram ./2 ; 41 | spectrogram_abs = sqrt(pwr_spectrogram); 42 | 43 | % Recovers the Angle values of the Spectrogram 44 | spectrogram_angle = angle(modulation_spectrogram_data.spectrogram_data.wavelet_coefficients(:,:,i_channel)); 45 | 46 | % Creates the rFFT coefficients of the 1st FFTs 47 | modulation_spectrogram_data.spectrogram_data.wavelet_coefficients(:,:,i_channel) = spectrogram_abs .* exp(1i .* spectrogram_angle ); 48 | end 49 | 50 | % Recovers the origial signal or set of signals 51 | x = iwavelet_spectrogram(modulation_spectrogram_data.spectrogram_data); 52 | 53 | -------------------------------------------------------------------------------- /ama_toolbox/plot_psd_data.m: -------------------------------------------------------------------------------- 1 | function plot_psd_data(psd_data, ix, a_range, f_range) 2 | % PLOT_PSD_DATA(spectrogram_struct, ix, a_range, f_range) 3 | % Plot the PSD related to the `psd_data` 4 | % 5 | % Parameters 6 | % ---------- 7 | % psd_data : 8 | % Structure with PSD data 9 | % ix : Index of the signal (channel) to plot 10 | % (Default, all the channels, a new figure for each) 11 | % p_range : Power range 12 | % (Default [minimum power, maximum power]) 13 | % f_range : Frequency range 14 | % (Default [minimum frequency, maximum frequency]) 15 | % 16 | % Returns 17 | % ------- 18 | % If only a plot is requested, it is plotted in the existen axes (created if needed) 19 | % If many plots are requested, a new figure is created for each plot 20 | 21 | % Validate 'type' argumet 22 | if ~exist('ix','var') || isempty(ix) 23 | ix = 1 : size(psd_data.PSD, 2); 24 | end 25 | 26 | % Check if ix has ONLY one element 27 | if numel(ix) == 1 28 | new_figure = false; 29 | % Retrieve Current Axes handle from the Current Figure, if there is not 30 | % Current Figure, it's generated here 31 | h_ax = get(gcf, 'CurrentAxes'); 32 | % If there is not Current Axes handle, create new Axes in current Figure 33 | if size(h_ax, 1) == 0 34 | h_ax = axes(); 35 | end 36 | else 37 | new_figure = true; 38 | end 39 | 40 | % Validate 'a_range' argumet 41 | if ~exist('a_range','var') 42 | a_range = []; 43 | end 44 | 45 | % Validate 'f_range' argumet 46 | if ~exist('f_range','var') 47 | f_range = []; 48 | end 49 | 50 | for i_channel = ix 51 | if new_figure 52 | figure(); 53 | h_ax = axes(); 54 | end 55 | plot_one_psd(h_ax, ... 56 | psd_data.PSD(:, i_channel), ... 57 | psd_data.freq_axis, ... 58 | psd_data.channel_names{i_channel},... 59 | a_range, f_range); 60 | end 61 | 62 | function plot_one_psd(h_ax, X_pwr, f_ax, title_str, a_range, f_range) 63 | X_plot = 10*log10(X_pwr(:,1)' + eps); 64 | plot(h_ax, f_ax, X_plot ); 65 | xlabel('frequency (Hz)') 66 | ylabel('power (dB/Hz)') 67 | set(h_ax, 'XMinorTick','on'); 68 | set(h_ax, 'YMinorTick','on'); 69 | if not(isempty(f_range)) 70 | xlim(f_range); 71 | end 72 | if not(isempty(a_range)) 73 | ylim(a_range); 74 | end 75 | title(title_str); 76 | end 77 | end 78 | 79 | -------------------------------------------------------------------------------- /ama_toolbox/irfft.m: -------------------------------------------------------------------------------- 1 | function X = irfft(Y, n, dim) 2 | %x = IRFFT(y, n, dim) Inverse Real Fast Fourier Transform. 3 | % The IRFFT function returns the Inverse DFT (using the RFFT algorithm)of 4 | % a spectrum Y containing ONLY the positive frequencies, with the 5 | % assumption than Y is the positive half of a Hermitian Symmetric spectrum 6 | % from a real signal X. 7 | % 8 | % Parameters 9 | % ---------- 10 | % y : 1D or 2D array with the positive spectrum of 11 | % real-valued signals with shape (n_samples, n_channels) 12 | % n : Number of samples in the original x signals 13 | % N not provided. Y is assumed be obtained from a signal X with even number fo samples 14 | % dim : Dimension to compute the IRFFT (Default: Last dimension in `y`) 15 | % 16 | % Returns 17 | % ------- 18 | % x : Real-valued signal(s) 19 | % 20 | % 21 | % See also IFFT 22 | % 23 | % Example: 24 | % 25 | % n = size(X ,1); %Assuming one or more column-vector signals 26 | % Y = RFFT(X); 27 | % X_recover = IRFFT(Y, n); 28 | % 29 | 30 | 31 | % verify Y 32 | shape_Y = size(Y); 33 | if numel(shape_Y) > 2 34 | error('IRFFT only accepts 1D or 2D arrays as Y input'); 35 | end 36 | 37 | % check shape of Y, and set n and dim defaults 38 | if isvector(Y) 39 | if shape_Y(1) == 1 40 | % Y is a row vector 41 | dim_def = 2; 42 | else 43 | % Y is a column vector 44 | dim_def = 1; 45 | end 46 | else 47 | % Y is a 2D Matrix, a shape [n_samples, n_channels] is asummed 48 | dim_def = 1; 49 | end 50 | 51 | % verify 'dim' dimension parameter 52 | if ~exist('dim','var') || isempty(dim) 53 | dim = dim_def; 54 | end 55 | 56 | % verify 'n' number-of-samples parameter 57 | if ~exist('n','var') || isempty(n) 58 | warning('N not provided. Y is assumed be obtained from a signal X with even number fo samples'); 59 | n_half = size(Y, dim); 60 | n = (n_half - 1) * 2; 61 | end 62 | 63 | % reconstruct missing half of Spectrum 64 | if ~mod(n, 2) 65 | % number of samples is even 66 | n_half = (n / 2) + 1; 67 | ix_limit = (2 : n_half - 1 ); 68 | else 69 | % number of samples is odd 70 | n_half = (n + 1) / 2; 71 | ix_limit = (2 : n_half ); 72 | end 73 | 74 | % check shape of Y, and add negative frequencies 75 | if dim == 1 76 | % spectra in Y are column wise 77 | Y_neg = conj(flipud(Y(ix_limit, :))); 78 | Yc = [Y; Y_neg]; 79 | else 80 | % spectra in Y are row-wise 81 | Y_neg = conj(fliplr(Y(:, ix_limit))); 82 | Yc = [Y, Y_neg]; 83 | end 84 | 85 | X = real(ifft(Yc, n, dim)); 86 | 87 | end 88 | -------------------------------------------------------------------------------- /example_04.m: -------------------------------------------------------------------------------- 1 | %% Example 04 2 | % This example shows the amplitude modulation analysis toolbox for 3 | % speech data 4 | % 5 | % rfft_psd() Compute PSD using rFFT 6 | % strfft_spectrogram() Compute Spectrogram using STFFT 7 | % strfft_modulation_spectrogram() Compute Modulation Spectrogram using STFFT 8 | % wavelet_spectrogram() Compute Spectrogram using wavelet transformation 9 | % wavelet_modulation_spectrogram() Compute Modulation Spectrogram using wavelet transformation 10 | % 11 | % plot_signal() Plot a signal in time domain 12 | % plot_psd_data() Plot PSD data obtained with rfft_psd() 13 | % plot_spectrogram_data() Plot Spectrogram data obtained with 14 | % strfft_spectrogram() or wavelet_spectrogram() 15 | % plot_modulation_spectrogram_data() Plot Modulation Spectrogram data obtained with 16 | % strfft_modspectrogram() or wavelet_modspectrogram() 17 | 18 | %% Speech signal 19 | % The speech signal p234_004.wav is one sample from the: 20 | % CSTR VCTK Corpus: English Multi-speaker Corpus for CSTR Voice Cloning Toolkit 21 | % avialable in: https://datashare.is.ed.ac.uk/handle/10283/2651 22 | [x,fs] = audioread('./example_data/p234_004.wav'); 23 | x_name = 'speech'; 24 | % 1s segment to analyze 25 | x = x((fs*1.6)+1 : fs*3.6); 26 | sound(x,fs) 27 | 28 | %% STFT-based 29 | % Parameters 30 | win_size_sec = 0.04; % window length for the STFFT (seconds) 31 | win_shft_sec = 0.01; % shift between consecutive windows (seconds) 32 | 33 | figure() 34 | subplot(4,5,[1:5]) 35 | plot_signal(x, fs, x_name ); colorbar; 36 | 37 | stft_spectrogram = strfft_spectrogram(x, fs, round(win_size_sec*fs), round(win_shft_sec*fs), [], [], {x_name}); 38 | subplot(4,5,[6:10]) 39 | plot_spectrogram_data(stft_spectrogram) 40 | 41 | stft_modulation_spectrogram = strfft_modulation_spectrogram(x, fs, round(win_size_sec*fs), round(win_shft_sec*fs), [], [], 1, [], {x_name}); 42 | subplot(4,5,[12:14, 17:19]) 43 | plot_modulation_spectrogram_data(stft_modulation_spectrogram,1 ,[], [0,20],[-90, -50]) 44 | 45 | 46 | %% Parameters for CWT for speech signal 47 | n_cycles = 6; % number of cycles (for Complex Morlet) 48 | up_lim = floor(log(fs/2) / log(2)); 49 | frequency_vector = 2.^[1:0.2:up_lim]; % vector of frequencies to compute the CWT 50 | 51 | figure() 52 | subplot(4,5,[1:5]) 53 | plot_signal(x, fs, x_name ); colorbar; 54 | 55 | cwt_spectrogram = wavelet_spectrogram(x, fs, n_cycles, frequency_vector, {x_name}); 56 | subplot(4,5,[6:10]) 57 | plot_spectrogram_data(cwt_spectrogram) 58 | 59 | cwt_modulation_spectrogram = wavelet_modulation_spectrogram(x, fs, n_cycles, frequency_vector, [], [], {x_name}); 60 | subplot(4,5,[12:14, 17:19]) 61 | plot_modulation_spectrogram_data(cwt_modulation_spectrogram, 1, [], [0,20],[-90, -50]) 62 | -------------------------------------------------------------------------------- /ama_toolbox/istrfft_modulation_spectrogram.m: -------------------------------------------------------------------------------- 1 | function x = istrfft_modulation_spectrogram(modulation_spectrogram_data) 2 | % x = istrfft_modulation_spectrogram(modulation_spectrogram_data) 3 | % 4 | % Compute the inverse STFT-based modulation spectrogram for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % modulation_spectrogram_data : Structure with STFT-based modulation spectrogram data, 9 | % created with strfft_modulation_spectrogram() 10 | % 11 | % Returns 12 | % ------- 13 | % x : 1D array with shape (n_samples) or 14 | % 2D array with shape (n_samples, n_channels) 15 | % 16 | % Example: 17 | % xi = randn(100,1); 18 | % xi_mod_stft = strfft_modulation_spectrogram(xi, 10, 10, 5); 19 | % xo = istrfft_modulation_spectrogram(xi_mod_stft); 20 | 21 | % Number of channels from Modspectrogram structure 22 | n_channels = size(modulation_spectrogram_data.rFFT_modulation_spectrogram, 3); 23 | 24 | % Prepare psd_tmp_data to perform irFFT on Modulation Spectogram 25 | psd_tmp_data.freq_axis = modulation_spectrogram_data.freq_mod_axis; 26 | psd_tmp_data.fs = modulation_spectrogram_data.fs_mod; 27 | psd_tmp_data.win_function = modulation_spectrogram_data.win_function_x; 28 | psd_tmp_data.n_samples = modulation_spectrogram_data.n_windows; 29 | 30 | 31 | for i_channel = 1 : n_channels 32 | % Slide with the rFFT coeffients of the 2nd FFT 33 | psd_tmp_data.rFFT = transpose(modulation_spectrogram_data.rFFT_modulation_spectrogram(:,:,i_channel)); 34 | % Recovers the Square Root of the Power Spectrogram 35 | sqrt_pwr_spectrogram = irfft_psd(psd_tmp_data); 36 | % Power Spectrogram 37 | pwr_spectrogram = sqrt_pwr_spectrogram .^ 2; 38 | % Scale Power Spectrogram by (n_windows * time_delta) 39 | pwr_spectrogram = pwr_spectrogram * modulation_spectrogram_data.spectrogram_data.n_windows * modulation_spectrogram_data.spectrogram_data.time_delta; 40 | % Scale Power Spectrogram by (freq_delta) 41 | pwr_spectrogram = pwr_spectrogram * modulation_spectrogram_data.spectrogram_data.freq_delta; 42 | % Scale Power Spectrogram by the number of samples used 43 | pwr_spectrogram = pwr_spectrogram / (1 / modulation_spectrogram_data.spectrogram_data.n_fft .^2); 44 | % Divde by 2 all the elements except DC and the Nyquist point (in even case) 45 | pwr_spectrogram = pwr_spectrogram / 2; 46 | pwr_spectrogram(:, 1) = pwr_spectrogram(:, 1) * 2; 47 | if ~mod(modulation_spectrogram_data.spectrogram_data.n_fft, 2) 48 | % NFFT was even, then 49 | pwr_spectrogram(:, end) = pwr_spectrogram(:, end) * 2; 50 | end 51 | spectrogram_abs = sqrt(pwr_spectrogram); 52 | % Recovers the Angle values of the Spectrogram 53 | spectrogram_angle = angle(modulation_spectrogram_data.spectrogram_data.rFFT_spectrogram(:,:,i_channel)); 54 | % Creates the rFFT coefficients of the 1st FFTs 55 | modulation_spectrogram_data.spectrogram_data.rFFT_spectrogram(:,:,i_channel) = spectrogram_abs .* exp(1i .* spectrogram_angle ); 56 | end 57 | 58 | % Recovers the origial signal or set of signals 59 | x = istrfft_spectrogram(modulation_spectrogram_data.spectrogram_data); 60 | 61 | -------------------------------------------------------------------------------- /ama_toolbox/epoching.m: -------------------------------------------------------------------------------- 1 | function [epochs, remainder, ix_center] = epoching(data, samples_epoch, samples_overlap) 2 | % [epochs, remainder, ix_center] = EPOCHING(data, size_epoch, overlap_epoch) 3 | % Divides the `data` provided as [n_samples, n_channels] using the 4 | % `size_epoch` indicated (in samples) and the `overlap_epoch` between 5 | % consecutive epochs. 6 | % 7 | % Parameters 8 | % ---------- 9 | % data : 2D array_like with shape (n_samples, n_channels) 10 | % samples_epochs : number of samples in smaller epochs 11 | % samples_overlap : number of samples for ovelap between epochs (Default 0) 12 | % 13 | % 14 | % Returns 15 | % ------- 16 | % epochs : 3D array with shape (samples_epoch, n_channels, n_epochs) 17 | % remainder : 2D array with the remaining data after last complete epoch 18 | % ix_center : 1D array, indicates the index tha corresponds to the center of the 19 | % nth epoch. 20 | % 21 | % 22 | % 23 | % e.g 24 | % samples_epoch = 5, samples_overlap = 0 25 | % 26 | % n_epochs = floor(n_samples / samples_epoch) = floor (23 / 5) = 4 27 | % 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 (Data array) 28 | % |E1 |E2 |E3 |E4 |Remainder 29 | % * * * * (* = center of the epoch) 30 | % ix_center = [3, 8, 13, 18] 31 | % 32 | % e.g 33 | % samples_epoch = 5 and samples_overlap = 2 34 | % samples_shift = samples_epoch - samples_overlap 35 | % 36 | % n_epochs = floor( (n_samples - samples_epoch) / samples_shift ) + 1 ; 37 | % n_epochs = floor( (24 - 5) / 3 ) + 1 = 7 38 | % 39 | % 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 (Data array) 40 | % |E1-------| |E3-------| |E5-------| |E7-------| 41 | % |E2-------| |E4-------| |E6-------| |R| 42 | % * * * * 43 | % * * * (* = center of the epoch) 44 | % ix_center = [3, 6, 9, 12, 15, 18, 21] 45 | % remainder = [4] 46 | 47 | % Get class of data 48 | data_class = class(data); 49 | 50 | % Verify the number of Input arguments 51 | if nargin < 3 52 | samples_overlap = 0; 53 | end 54 | 55 | % Obtain parameters of the data 56 | n_samples = size(data,1); 57 | n_channels = size(data,2); 58 | 59 | % Size of half epoch 60 | half_epoch = ceil(samples_epoch / 2); 61 | 62 | % Epoch shift 63 | samples_shift = samples_epoch - samples_overlap; 64 | 65 | % Number of epochs 66 | n_epochs = floor( (n_samples - samples_epoch) / samples_shift ) + 1 ; 67 | 68 | %markers indicates where the epoch starts, and the epoch contains 69 | %size_epoch elements 70 | 71 | markers = ((1:n_epochs-1)'*samples_shift)+1; 72 | markers = [1; markers]; 73 | 74 | %Divide data in epochs 75 | epochs = zeros(samples_epoch,n_channels,n_epochs, data_class); 76 | ix_center = zeros(n_epochs,1, data_class); 77 | 78 | if n_epochs == 0 79 | remainder = data; 80 | return 81 | end 82 | 83 | for i_epoch = 1:n_epochs 84 | epochs(:,:,i_epoch) = data( markers(i_epoch) : markers(i_epoch) + samples_epoch -1 ,:); 85 | ix_center(i_epoch) = markers(i_epoch) -1 + half_epoch; 86 | end 87 | 88 | if (markers(end) + samples_epoch - 1 < n_samples) 89 | remainder = data(markers(end) + samples_epoch : n_samples,:); 90 | else 91 | remainder = []; 92 | end -------------------------------------------------------------------------------- /ama_toolbox/msqi_ama.m: -------------------------------------------------------------------------------- 1 | function [msqi_value, hr_value, modulation_spectrogram] = msqi_ama(x, fs) 2 | % [msqi_value, hr_value, modulation_spectrogram] = msqi_ama(x, fs) 3 | % 4 | % Computes the Modulation Spectrum-Based ECG Quality Index (MSQI) for one or 5 | % many ECG signals defined in x, sampled with a sampling frequency fs 6 | % 7 | % Parameters 8 | % ---------- 9 | % x : 1D array with shape (n_samples) or 10 | % 2D array with shape (n_samples, n_signals) 11 | % fs : Sampling frequency in Hz 12 | % 13 | % Returns 14 | % ------- 15 | % msqi_value : MSQI value or values 16 | % hr_value : HR values or values 17 | % modulation_spectrogram : Structure or structures of modulation spectrogram 18 | % 19 | % The MSQI is presented in: 20 | % 21 | % D. P. Tobon V., T. H. Falk, and M. Maier, "MS-QI: A Modulation 22 | % Spectrum-Based ECG Quality Index for Telehealth Applications", IEEE 23 | % Transactions on Biomedical Engineering, vol. 63, no. 8, pp. 1613-1622, 24 | % Aug. 2016 25 | 26 | % values for the STFFT transformation 27 | win_size_sec = 0.125; % seconds 28 | win_over_sec = 0.09375; % seconds 29 | nfft_factor_1 = 16; 30 | nfft_factor_2 = 4; 31 | 32 | win_size_smp = round(win_size_sec * fs); % samples 33 | win_over_smp = round(win_over_sec * fs); % samples 34 | win_shft_smp = win_size_smp - win_over_smp; 35 | 36 | % computes modulation spectrogram 37 | modulation_spectrogram = strfft_modulation_spectrogram(x, fs, win_size_smp, win_shft_smp, ... 38 | nfft_factor_1, 'cosinewin', nfft_factor_2, 'cosinewin' ); 39 | 40 | % find fundamental frequency (HR) 41 | % f = (0, 40)Hz 42 | [~, ix_f_00] = min(abs(modulation_spectrogram.freq_axis - 0)); 43 | [~, ix_f_40] = min(abs(modulation_spectrogram.freq_axis - 40)); 44 | 45 | % look for the maximum only from 0.6 to 3 Hz (36 to 180 bpm) 46 | valid_f_ix = and(modulation_spectrogram.freq_mod_axis > 0.6, modulation_spectrogram.freq_mod_axis < 3); 47 | 48 | % number of epochs 49 | n_epochs = size(modulation_spectrogram.power_modulation_spectrogram, 3); 50 | 51 | msqi_value = zeros(n_epochs, 1); 52 | hr_value = zeros(n_epochs, 1); 53 | 54 | for ix_epoch = 1 : n_epochs 55 | 56 | B = sqrt(modulation_spectrogram.power_modulation_spectrogram(:, :, ix_epoch)); 57 | 58 | % scale to maximun of B 59 | B = B / max(B(:)); 60 | 61 | % add B in the conventional frequency axis from 0 to 40 Hz 62 | tmp = sum(B(ix_f_00:ix_f_40, :)); 63 | 64 | % look for the maximum only from 0.6 to 3 Hz (36 to 180 bpm) 65 | tmp(~valid_f_ix) = 0; 66 | [~, ix_fm_max] = max(tmp); 67 | freq_funda = modulation_spectrogram.freq_mod_axis(ix_fm_max); 68 | 69 | % 'energy' in lobes 70 | eme = 0; 71 | for ix_harm = 1 : 4 72 | [~, ix_fm] = min(abs(modulation_spectrogram.freq_mod_axis - (ix_harm * freq_funda) )); 73 | ix_b = round(.3125 / modulation_spectrogram.freq_mod_delta ); %.3125Hz, half lobe used in Diana's code 74 | % EME 75 | eme = eme + sum(sum(B(ix_f_00 : ix_f_40 , ix_fm-ix_b : ix_fm+ix_b ))); 76 | end 77 | 78 | % total 'energy' 79 | tme = sum(B(:)); 80 | 81 | % RME 82 | rme = tme - eme; 83 | 84 | % MS-QI 85 | msqi_value(ix_epoch) = eme / rme; 86 | hr_value(ix_epoch) = freq_funda * 60; 87 | end 88 | 89 | end 90 | 91 | -------------------------------------------------------------------------------- /ama_toolbox/plot_spectrogram_data.m: -------------------------------------------------------------------------------- 1 | function plot_spectrogram_data(spectrogram_data, ix, t_range, f_range, c_range, c_map) 2 | % PLOT_SPECTROGRAM_DATA(spectrogram_data, ix, t_range, f_range, c_range, c_map) 3 | % Plot the Power Spectrogram related to the `spectrogram_data` 4 | % 5 | % Parameters 6 | % ---------- 7 | % spectrogram_data : 8 | % Structure with Spectrogram data 9 | % ix : Index of the signal (channel) to plot 10 | % (Default, all the channels, a new figure for each) 11 | % t_range : Time range 12 | % (Default [minimum time, maximum time]) 13 | % f_range : Frequency range 14 | % (Default [minimum frequency, maximum frequency]) 15 | % c_range : Color (power) range 16 | % (Default [mean power, maximum power]) 17 | % c_map : Colot Map 18 | % (Default viridis) 19 | % 20 | % Returns 21 | % ------- 22 | % If only a plot is requested, it is plotted in the existen axes (created if needed) 23 | % If many plots are requested, a new figure is created for each plot 24 | 25 | % Validate 'ix' argumet 26 | if ~exist('ix','var') || isempty(ix) 27 | ix = 1 : size(spectrogram_data.power_spectrogram, 3); 28 | end 29 | 30 | % Validate 'c_map' argumet 31 | if ~exist('c_map','var') || isempty(c_map) 32 | c_map = 'viridis'; 33 | end 34 | 35 | % Check if ix has ONLY one element 36 | if numel(ix) == 1 37 | new_figure = false; 38 | % Retrieve Current Axes handle from the Current Figure, if there is not 39 | % Current Figure, it's generated here 40 | h_ax = get(gcf, 'CurrentAxes'); 41 | % If there is not Current Axes handle, create new Axes in current Figure 42 | if size(h_ax, 1) == 0 43 | h_ax = axes(); 44 | end 45 | else 46 | new_figure = true; 47 | end 48 | 49 | % Validate 't_range' argumet 50 | if ~exist('t_range','var') 51 | t_range = []; 52 | end 53 | 54 | % Validate 'f_range' argumet 55 | if ~exist('f_range','var') 56 | f_range = []; 57 | end 58 | 59 | % Validate 'c_range' argumet 60 | if ~exist('c_range','var') 61 | c_range = []; 62 | end 63 | 64 | 65 | for i_channel = ix 66 | if new_figure 67 | figure(); 68 | h_ax = axes(); 69 | end 70 | plot_one_spectrogram(h_ax, ... 71 | spectrogram_data.power_spectrogram(:, :, i_channel), ... 72 | spectrogram_data.time_axis, ... 73 | spectrogram_data.freq_axis, ... 74 | spectrogram_data.channel_names{i_channel},... 75 | t_range, f_range, c_range, c_map); 76 | end 77 | 78 | function plot_one_spectrogram(h_ax, X_pwr, t_ax, f_ax, title_str, t_range, f_range, c_range, c_map) 79 | X_plot = double(10*log10(X_pwr(:,:,1)' + eps)); 80 | surf(h_ax, t_ax, f_ax, X_plot,'LineStyle','none') 81 | colormap(c_map); 82 | view(0,90) 83 | xlabel('time (s)'); 84 | ylabel('frequency (Hz)'); 85 | set(h_ax, 'XMinorTick','on'); 86 | set(h_ax, 'YMinorTick','on'); 87 | if not(isempty(t_range)) 88 | xlim(t_range); 89 | else 90 | xlim([min(t_ax), max(t_ax)]); 91 | end 92 | if not(isempty(f_range)) 93 | ylim(f_range); 94 | else 95 | ylim([min(f_ax), max(f_ax)]); 96 | end 97 | if not(isempty(c_range)) 98 | caxis(c_range); 99 | else 100 | caxis([mean(mean(X_plot)) max(max(X_plot))]); 101 | end 102 | colorbar; 103 | title(title_str); 104 | end 105 | end 106 | 107 | -------------------------------------------------------------------------------- /ama_toolbox/wavelet_spectrogram.m: -------------------------------------------------------------------------------- 1 | function spectrogram_data = wavelet_spectrogram(x, fs, n_cycles, freq_vct, channel_names) 2 | %spectrogram_data = WAVELET_SPECTROGRAM(x, fs, n_cycles, freq_vct, channel_names) 3 | % Compute the Spectrogram using the Complex Morlet wavelet for one or a set of REAL signals 'x'. 4 | % 5 | % Parameters 6 | % ---------- 7 | % x : 1D array with shape (n_samples) or 8 | % 2D array with shape (n_samples, n_channels) 9 | % fs : Sampling frequency 10 | % in Hz 11 | % n : Number of cicles inside the Gaussian curve 12 | % (Default 6) 13 | % freq_vct : 1D array 14 | % with frequencies to compute the CWT (Default = [1 : 1 : fs/2] ) 15 | % channel_names : Names of the signals 16 | % (Default Signal-XX with XX 1, 2, ... n_channels) 17 | % 18 | % Returns 19 | % ------- 20 | % spectrogram_data : Structure with Spectrogram data, with the elements: 21 | % wavelet_coefficients 22 | % Coefficients of the Wavelet transformation (u) 23 | % power_spectrogram : 24 | % Power spectrogram (u^2 / Hz) 25 | % fs : 26 | % Sampling frequency (Hz) 27 | % freq_axis : 28 | % Frequency axis for rFFT and PSD (Hz) 29 | % freq_delta : 30 | % Frequency axis step (Hz) 31 | % time_axis : 32 | % Time axis for rFFT_spectrogram and power_spectrogram (s) 33 | % time_delta : 34 | % Time axis step (s) 35 | % n_cycles : 36 | % Number of cicles used inside the Gaussian curve 37 | % wavelet_kernels : 38 | % Wavelet kernels used to obtain the wavelet coefficients 39 | % n_samples : 40 | % Number of samples of the signal or signals 'x' 41 | % channel_names : 42 | % Names of channels 43 | 44 | % validate 'freq_vct' argument 45 | if ~exist('freq_vct','var') || isempty(freq_vct) 46 | freq_vct = 1 : floor(fs / 2); 47 | end 48 | 49 | % validate 'n_cycles' argument 50 | if ~exist('n_cycles','var') || isempty(n_cycles) 51 | n_cycles = 6; 52 | end 53 | 54 | % validate 'channel_names' argument 55 | if ~exist('channel_names','var') || isempty(channel_names) 56 | channel_names = {}; 57 | end 58 | 59 | % time delta 60 | t_delta = 1 / fs; 61 | % frequency delta 62 | f_delta = freq_vct(2) - freq_vct(1); 63 | 64 | % create time vector 'time_vct' for signal 'x' 65 | time_vct = (0 : size(x,1) - 1) / fs; 66 | 67 | % time axis for spectrogram 68 | t_ax = time_vct; 69 | % frequency axis for spectrogram 70 | f_ax = freq_vct; 71 | 72 | % number of channels and number of samples 73 | n_channels = size(x, 2); 74 | n_samples = size(x, 1); 75 | 76 | % generate default channel names 77 | if isempty(channel_names) 78 | for ic = 1 : n_channels 79 | channel_names{ic} = sprintf('Signal-%02d',ic); 80 | end 81 | end 82 | 83 | % wavelet transform 84 | [wcoef,wfam] = cmorlet_wavelet(x, fs, freq_vct, n_cycles, true); 85 | % power from Wavelet coefficients 86 | pwr_spectrogram = abs(wcoef).^2; 87 | pwr_spectrogram = pwr_spectrogram * 2 / (fs * n_samples); 88 | 89 | % output 'spectrogram_data' structure 90 | spectrogram_data.wavelet_coefficients = wcoef; 91 | spectrogram_data.power_spectrogram = pwr_spectrogram; 92 | spectrogram_data.fs = fs; 93 | spectrogram_data.freq_axis = f_ax; 94 | spectrogram_data.freq_delta = f_delta; 95 | spectrogram_data.time_axis = t_ax; 96 | spectrogram_data.time_delta = t_delta; 97 | spectrogram_data.n_cycles = n_cycles; 98 | spectrogram_data.wavelet_kernels = wfam; 99 | spectrogram_data.n_samples = n_samples; 100 | spectrogram_data.channel_names = channel_names; 101 | 102 | end 103 | -------------------------------------------------------------------------------- /ama_toolbox/rfft_psd.m: -------------------------------------------------------------------------------- 1 | function psd_data = rfft_psd(x, fs, n_fft, win_function, channel_names) 2 | %psd_data = RFFT_PSD(x, fs, n_fft, win_function, channel_names) 3 | % 4 | % Compute the PSD for one or a set of REAL signals. 5 | % 6 | % Parameters 7 | % ---------- 8 | % x : 1D array with shape (n_samples) or 9 | % 2D array with shape (n_samples, n_channels) 10 | % fs : Sampling frequency 11 | % in Hz 12 | % n_fft : Number of samples to compute the FFT 13 | % (Default = n_samples in array x) 14 | % win_function : Window function applied to the signal 15 | % (Default 'Hamming') 16 | % channel_names : Names of the signals 17 | % (Default Signal-XX with XX 1, 2, ... n_channels) 18 | % 19 | % Returns 20 | % ------- 21 | % psd_data : Structure with PSD data, with the elements: 22 | % rFFT 23 | % First half of the FFT(x) (u), scaled by the Window RMS 24 | % PSD 25 | % Power Spectrum Density (u^2 / Hz) 26 | % fs 27 | % Sampling frequency (Hz) 28 | % freq_axis 29 | % Frequency axis for rFFT and PSD (Hz) 30 | % freq_delta 31 | % Frequency axis step (Hz) 32 | % n_samples 33 | % Number of samples of the signal or signals 'x' 34 | % n_fft 35 | % Number of elements utilized to perform FFT 36 | % win_function 37 | % Window applied to the data in 'x' 38 | % channel_names 39 | % Names of channels 40 | 41 | 42 | % validate 'n_fft' argument 43 | if ~exist('n_fft','var') || isempty(n_fft) 44 | n_fft = size(x,1); 45 | end 46 | 47 | % validate 'win_name' argument 48 | if ~exist('win_function','var') || isempty(win_function) 49 | win_function = 'hamming'; 50 | end 51 | 52 | % validate 'channel_names' argument 53 | if ~exist('channel_names','var') || isempty(channel_names) 54 | channel_names = {}; 55 | end 56 | 57 | % number of channels and number of samples 58 | [n_samples, n_channels] = size(x); 59 | 60 | % generate default channel names, if needed 61 | if isempty(channel_names) 62 | for ic = 1 : n_channels 63 | channel_names{ic} = sprintf('Signal-%02d',ic); 64 | end 65 | end 66 | 67 | % windowing data 68 | win = window(win_function, n_samples); 69 | win_rms = sqrt(sum(win.^2) / n_samples); 70 | win_mat = repmat(win, 1, n_channels); 71 | x = x.*win_mat; 72 | 73 | % real FFT with zero padding if n_fft ~= n_samples 74 | X = rfft(x, n_fft); 75 | % spectrum scaled by window RMS 76 | X = X / win_rms; 77 | % power spectrum 78 | X_pwr = X .* conj(X); 79 | X_pwr = X_pwr * (1/n_fft.^2); 80 | 81 | % adjust for even and odd number of elements 82 | if mod(n_fft,2) 83 | % odd case 84 | n_freqs = (n_fft + 1) / 2; 85 | % double all frequency components except DC component 86 | X_pwr(2:end,:) = X_pwr(2:end,:) * 2; 87 | else 88 | % even case 89 | n_freqs = (n_fft / 2) + 1; 90 | % double all frequency components except DC and fs/2 components 91 | X_pwr(2:end-1,:) = X_pwr(2:end-1,:) * 2; 92 | end 93 | 94 | % frequency axis step 95 | f_delta = (fs / n_fft); 96 | 97 | % scale PSD with the frequency step 98 | psd = X_pwr ./ f_delta; 99 | 100 | % frequency axis for spectrum 101 | f_ax = (0: n_freqs-1) * f_delta; 102 | 103 | % power of the signal can be computed as: 104 | % power_x = sum(psd) * f_delta == sum(x.^2) / n_samples 105 | % which agrees with Parseval's Theorem 106 | 107 | % output 'psd_struct' structure 108 | psd_data.rFFT = X; 109 | psd_data.PSD = psd; 110 | psd_data.fs = fs; 111 | psd_data.freq_axis = f_ax; 112 | psd_data.freq_delta = f_delta; 113 | psd_data.n_samples = n_samples; 114 | psd_data.n_fft = n_fft; 115 | psd_data.win_function = win_function; 116 | psd_data.channel_names = channel_names; 117 | -------------------------------------------------------------------------------- /ama_toolbox/plot_modulation_spectrogram_data.m: -------------------------------------------------------------------------------- 1 | function plot_modulation_spectrogram_data(modulation_spectrogram_data, ix, f_range, fm_range, c_range, c_map) 2 | % PLOT_MODULATION_SPECTROGRAM(modulation_spectrogram_data, ix, f_range, fm_range, c_range) 3 | % Plot the Power Modulation Spectrogram related to the `modulation_spectrogram_data` 4 | % 5 | % Parameters 6 | % ---------- 7 | % modulation_spectrogram_data : 8 | % Structure with Modulation Spectrogram data 9 | % ix : Index of the signal (channel) to plot 10 | % (Default, all the channels, a new figure for each) 11 | % f_range : Frequency range 12 | % (Default [minimum frequency, maximum frequency]) 13 | % fm_range : Modulation frequency range 14 | % (Default [minimum mod_frequency, maximum mod_frequency]) 15 | % c_range : Color (power) range 16 | % (Default [mean power, maximum power]) 17 | % c_map : Colot Map 18 | % (Default viridis) 19 | % 20 | % Returns 21 | % ------- 22 | % If only a plot is requested, it is plotted in the existen axes (created if needed) 23 | % If many plots are requested, a new figure is created for each plot 24 | 25 | % Validate 'ix' argumet 26 | if ~exist('ix','var') || isempty(ix) 27 | ix = 1 : size (modulation_spectrogram_data.power_modulation_spectrogram, 3); 28 | end 29 | 30 | % Validate 'c_map' argumet 31 | if ~exist('c_map','var') || isempty(c_map) 32 | c_map = 'viridis'; 33 | end 34 | 35 | % Check if ix has ONLY one element 36 | if numel(ix) == 1 37 | new_figure = false; 38 | % Retrieve Current Axes handle from the Current Figure, if there is not 39 | % Current Figure, it's generated here 40 | h_ax = get(gcf, 'CurrentAxes'); 41 | % If there is not Current Axes handle, create new Axes in current Figure 42 | if size(h_ax, 1) == 0 43 | h_ax = axes(); 44 | end 45 | else 46 | new_figure = true; 47 | end 48 | 49 | % Validate 'f_range' argumet 50 | if ~exist('f_range','var') 51 | f_range = []; 52 | end 53 | 54 | % Validate 'fm_range' argumet 55 | if ~exist('fm_range','var') 56 | fm_range = []; 57 | end 58 | 59 | % Validate 'c_range' argumet 60 | if ~exist('c_range','var') 61 | c_range = []; 62 | end 63 | 64 | for i_channel = ix 65 | if new_figure 66 | figure(); 67 | h_ax = axes(); 68 | end 69 | plot_one_modspectrogram(h_ax, ... 70 | modulation_spectrogram_data.power_modulation_spectrogram(:, :, i_channel), ... 71 | modulation_spectrogram_data.freq_axis, ... 72 | modulation_spectrogram_data.freq_mod_axis, ... 73 | modulation_spectrogram_data.channel_names{i_channel}, ... 74 | f_range, fm_range, c_range, c_map); 75 | end 76 | 77 | function plot_one_modspectrogram(h_ax, X_pwr, f_ax, fmod_ax, title_str, f_range, fm_range, c_range, c_map) 78 | X_plot = double(10*log10(X_pwr(:,:,1) + eps)); 79 | surf(h_ax, fmod_ax, f_ax, X_plot,'LineStyle','none') 80 | colormap(c_map); 81 | view(0,90) 82 | xlabel('modulation frequency (Hz)'); 83 | ylabel('conventional frequency (Hz)'); 84 | set(h_ax, 'XMinorTick','on'); 85 | set(h_ax, 'YMinorTick','on'); 86 | if not(isempty(fm_range)) 87 | xlim(fm_range); 88 | else 89 | xlim([min(fmod_ax), max(fmod_ax)]); 90 | end 91 | if not(isempty(f_range)) 92 | ylim(f_range); 93 | else 94 | ylim([min(f_ax), max(f_ax)]); 95 | end 96 | if not(isempty(c_range)) 97 | caxis(c_range); 98 | else 99 | caxis([mean(mean(X_plot)) max(max(X_plot))]); 100 | end 101 | colorbar; 102 | title(title_str); 103 | end 104 | end 105 | 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amplitude Modulation Analysis Toolbox 2 | 3 | The Amplitude Modulation Analysis Toolbox, for **MATLAB / Octave**, provides functions to compute and visualize the time-frequency and frequency-frequency domain representations of real signals. The **Python 3** version of this toolbox can be found here: https://github.com/MuSAELab/amplitude-modulation-analysis-module 4 | 5 | The Toolbox includes a GUI implementation, which facilitates the exploration of the amplitude modulation by allowing changing parameters online. 6 | 7 | In summary, the frequency-frequency representation of a signal is computed by performing two transformations: 8 | 9 | 1. Time domain (real signal) to time-frequency (spectrogram) 10 | 2. Time-frequency (spectrogram) to frequency-frequency (modulation spectrogram) 11 | 12 | ![diagram](https://user-images.githubusercontent.com/8238803/32670991-b1a4e542-c613-11e7-8408-bdc1cc3e0bf1.png) 13 | Signal processing steps involved in the calculation of the modulation spectrogram from the amplitude spectrogram of signal. The block |abs| indicates the absolute value, and the FT indicates the use of the Fourier transform. 14 | 15 | This Toolbox provides two implementations for the time to time-frequency transformation, one based on the short-time FFT (STFFT) and the other on the continuous wavelet transform (CWF) using the Complex Morlet wavelet. The time-frequency to frequency-frequency transformation is carried out with the FFT. 16 | 17 | ## Examples 18 | Besides the functions to compute and visualize the frequency-frequency representation of real signals, example data and scripts are provided. 19 | 20 | ### Example 1: `example_01.m` 21 | This example shows the usage and the commands accepted by the high level GUI to explore Amplitude Modulation in ECG and EEG data. The GUI can be called with the functions: 22 | `explore_strfft_am_gui()` which uses STFFT, and `explore_wavelet_am_gui()` based on wavelet transformation. Further details in their use refer to the comments in `example_01.m`. 23 | 24 | ![stfft](https://cloud.githubusercontent.com/assets/8238803/25900142/67a297da-3560-11e7-8112-16a7f6c3e637.png) 25 | STFFT-based amplitude modulation analysis GUI 26 |
27 | 28 | ![wavelet](https://cloud.githubusercontent.com/assets/8238803/25900150/6bf2b93c-3560-11e7-8dd4-084b23c925b5.png) 29 | CWT-based amplitude modulation analysis GUI 30 | 31 | ### Example 2: `example_02.m` 32 | This script shows presents the details in the usage of the low level functions to carry on the signal transformations, as well as plotting functions. Refer to the comments in `example_02.m` 33 | 34 | ### Citation 35 | If you use this software, please cite the companion [conference article](https://doi.org/10.1109/GlobalSIP45357.2019.8969210). 36 | 37 | **BibTeX** 38 | ``` 39 | @inproceedings{2019_cassani_amAn_Opensource_2019, 40 | author = {Cassani, Raymundo and Albuquerque, Isabela and Monteiro, João and Falk, Tiago H.}, 41 | booktitle = {2019 IEEE Global Conference on Signal and Information Processing (GlobalSIP)}, 42 | doi = {10.1109/GlobalSIP45357.2019.8969210}, 43 | pages = {1--4}, 44 | title = {{AMA: An Open-source Amplitude Modulation Analysis Toolkit for Signal Processing Applications}}, 45 | year = {2019} 46 | } 47 | ``` 48 | In addition, the conference poster related to the toolbox can be found [here](https://www.castoriscausa.com/files/cassani_2019_amaposter.pdf). 49 | 50 | 51 | ### Acknowledgement 52 | The research is based upon work supported by the Office of the Director of National Intelligence (ODNI), Intelligence Advanced Research Projects Activity (IARPA), via IARPA Contract N°2017 - 17042800005 . The views and conclusions con - tained herein are thos e of the authors and should not be interpreted as necessarily representing the official policies or endorsements, either expressed or implied, of the ODNI, IARPA, or the U.S. Government. The U.S. Government is authorized to reproduce and distribute reprint s for Governmental purposes notwithstanding any copyright annotation thereon. 53 | -------------------------------------------------------------------------------- /example_05.m: -------------------------------------------------------------------------------- 1 | %% Example 05 2 | % This example shows the use of the transfomrs and their inverses 3 | % 4 | % rfft() Fourier transform for real-valued signals 5 | % irfft() Inverse Fourier transform for real-valued signals 6 | % 7 | % rfft_psd() Computes PSD data from x(f) 8 | % irfft_psd() Recovers x(t) from its PSD data 9 | % 10 | % strfft_spectrogram() Computes Spectrogram data using STFFT 11 | % istrfft_modulation_spectrogram() Recovers x(t) from its STFFT Spectrogram data 12 | % 13 | % wavelet_spectrogram() Computes Spectrogram using CWT 14 | % iwavelet_modulation_spectrogram() Recovers x(t) from its CWT Spectrogram data 15 | % 16 | 17 | %% signal 18 | fs = 2000; 19 | time_v = (0:(10*fs)-1)'/fs; 20 | f1 = sin(2 * pi * 100 * time_v); 21 | f2 = sin(2 * pi * 325 * time_v); 22 | m1 = sin(2 * pi * 5 * time_v) + 2; 23 | m2 = sin(2 * pi * 3 * time_v) + 2; 24 | 25 | xi = [f1.*m1 + f2.*m2]; 26 | n = size(xi,1); 27 | 28 | figure() 29 | plot_signal(xi, fs) 30 | 31 | %% time <--> frequency 32 | %% FFT and IFFT of a real-valued signal 33 | xi_rfft = rfft(xi); 34 | xo = irfft(xi_rfft, n); 35 | 36 | figure() 37 | fi = subplot(2,1,1); 38 | plot_signal(xi, fs, 'Original x(t)') 39 | fo = subplot(2,1,2); 40 | plot_signal(xo, fs, 'Recovered x(t)') 41 | linkaxes([fi, fo], 'xy') 42 | r = corrcoef(xi, xo); 43 | fprintf('Correlation: %0.3f \r\n', r(2) ); 44 | 45 | %% PSD data obtained with rFFT, and its inverse 46 | xi_psd = rfft_psd(xi, fs); 47 | xo = irfft_psd(xi_psd); 48 | 49 | figure() 50 | fi = subplot(4,1,1); 51 | plot_signal(xi, fs, 'Original x(t)') 52 | fo = subplot(4,1,4); 53 | plot_signal(xo, fs, 'Recovered x(t)') 54 | subplot(4,1,[2,3]); 55 | plot_psd_data(xi_psd) 56 | title('PSD of x(t)') 57 | linkaxes([fi, fo], 'xy') 58 | r = corrcoef(xi, xo); 59 | fprintf('Correlation: %0.3f \r\n', r(2) ); 60 | 61 | 62 | %% time <--> time-frequency 63 | %% STFT Spectrogram 64 | xi_strfft = strfft_spectrogram(xi, fs, round(fs * 0.1), round(fs * 0.05)); 65 | xo = istrfft_spectrogram(xi_strfft); 66 | 67 | figure() 68 | fi = subplot(4,1,1); 69 | plot_signal(xi, fs, 'Original x(t)') 70 | fo = subplot(4,1,4); 71 | plot_signal(xo, fs, 'Recovered x(t)') 72 | subplot(4,1,[2,3]); 73 | plot_spectrogram_data(xi_strfft) 74 | title('STFT Spectrogram of x(t)') 75 | linkaxes([fi, fo], 'xy') 76 | r = corrcoef(xi, xo); 77 | fprintf('Correlation: %0.3f \r\n', r(2) ); 78 | 79 | %% CWT Spectrogram 80 | xi_cwt = wavelet_spectrogram(xi, fs); 81 | xo = iwavelet_spectrogram(xi_cwt); 82 | 83 | figure() 84 | fi = subplot(4,1,1); 85 | plot_signal(xi, fs, 'Original x(t)') 86 | fo = subplot(4,1,4); 87 | plot_signal(xo, fs, 'Recovered x(t)') 88 | subplot(4,1,[2,3]); 89 | plot_spectrogram_data(xi_cwt) 90 | title('CWT Spectrogram of x(t)') 91 | linkaxes([fi, fo], 'xy') 92 | r = corrcoef(xi, xo); 93 | fprintf('Correlation: %0.3f \r\n', r(2) ); 94 | 95 | 96 | %% time <--> frequency-modulation-frequency 97 | %% STFT Modulation Spectrogram 98 | xi_mod_strfft = strfft_modulation_spectrogram(xi, fs, round(fs * 0.1), round(fs * 0.05)); 99 | xo = istrfft_modulation_spectrogram(xi_mod_strfft); 100 | 101 | figure() 102 | fi = subplot(4,1,1); 103 | plot_signal(xi, fs, 'Original x(t)') 104 | fo = subplot(4,1,4); 105 | plot_signal(xo, fs, 'Recovered x(t)') 106 | subplot(4,1,[2,3]); 107 | plot_modulation_spectrogram_data(xi_mod_strfft, [], [0, 1000], [0, 10]) 108 | title('STFT Modulation Spectrogram of x(t)') 109 | linkaxes([fi, fo], 'xy') 110 | r = corrcoef(xi, xo); 111 | fprintf('Correlation: %0.3f \r\n', r(2) ); 112 | 113 | %% cwt Modulation Spectrogram 114 | xi_mod_cwt = wavelet_modulation_spectrogram(xi, fs); 115 | xo = iwavelet_modulation_spectrogram(xi_mod_cwt); 116 | 117 | figure() 118 | fi = subplot(4,1,1); 119 | plot_signal(xi, fs, 'Original x(t)') 120 | fo = subplot(4,1,4); 121 | plot_signal(xo, fs, 'Recovered x(t)') 122 | subplot(4,1,[2,3]); 123 | plot_modulation_spectrogram_data(xi_mod_cwt, [], [0, 1000], [0, 10]) 124 | title('CWT Modulation Spectrogram of x(t)') 125 | linkaxes([fi, fo], 'xy') 126 | r = corrcoef(xi, xo); 127 | fprintf('Correlation: %0.3f \r\n', r(2) ); 128 | -------------------------------------------------------------------------------- /ama_toolbox/cmorlet_wavelet.m: -------------------------------------------------------------------------------- 1 | function [wcoef,wfam] = cmorlet_wavelet(x, fs, freq_vct, n, normalization) 2 | %[wcoef,wfam] = cmorlet_wavelet(x, fs, freqs, n, normalization) 3 | %CMORLET_WAVELET Perform the continuous wavelet (CWT) tranform using the complex Morlet wavelet. 4 | % 5 | % Parameters 6 | % ---------- 7 | % x : 1D array with shape (n_samples) or 2D array with shape (n_samples, n_channels) 8 | % fs : Sampling frequency in Hz 9 | % freq : 1D array with frequencies to compute the CWT 10 | % (Default = [1 : 1 : fs/2]) 11 | % n : Number of cicles inside the Gaussian curve (Default 6) 12 | % normalization: (Default True) Scale each wavelet to have energy equal to 1 13 | % 14 | % 15 | % Returns 16 | % ------- 17 | % wcoef : Complex wavelet coefficients 18 | % 2D array with shape [n_samples, n_freqs] if `x` is 1D array 19 | % 3D array with shape [n_samples, n_freqs, n_channels] if `x` is 2D array 20 | % 21 | % wfam : 2D array with shape [n_wavelet_samples, n_freqs] where each column 22 | % corresponds to the a member of the wavelet family 23 | % 24 | % 25 | % Example-1: 26 | % Test signal [x]: 5 seconds 10Hz sine followed by 8 seconds 25Hz sine 27 | % sampled at 256Hz 28 | % Spectrogram is computed for frequencies from 1 to 100Hz, Normalization = true 29 | % and n equal to 6 30 | % 31 | % Code: 32 | % fs = 256; 33 | % t_5s = (0:(5*fs)-1)'/fs; 34 | % freqs = 1:100; 35 | % x = [sin(8 * 2 * pi * t_5s); sin(25 * 2 * pi * t_5s)] ; 36 | % t = (0:numel(x)-1)/fs; 37 | % wcoef = cmorlet_wavelet(x,fs,freqs); 38 | % surf(t,freqs,20*log10(abs(wcoef')+eps),'LineStyle','none'); 39 | % view(0,90); 40 | % xlabel('seconds'); 41 | % ylabel('Hz') 42 | % 43 | 44 | % Get class of x 45 | x_class = class(x); 46 | 47 | % Validate 'n' argument, number of cicles in the Gaussian 48 | if ~exist('n','var') || isempty(n) 49 | n = 6; 50 | end 51 | 52 | % Validate 'normalization' argument, number of cicles in the Gaussian 53 | if ~exist('normalization','var') || isempty(normalization) 54 | normalization = true; 55 | end 56 | 57 | % validate 'freq_vct' argument 58 | if ~exist('freq_vct','var') || isempty(freq_vct) 59 | freq_vct = 1 : floor(fs / 2); 60 | end 61 | 62 | % Number of samples 63 | n_samples = size(x , 1); 64 | % Number of channels 65 | n_channels = size(x , 2); 66 | % Number of Wavelets 67 | n_freqs = numel(freq_vct); 68 | 69 | % Number of samples for Wavetet family 70 | % This is equal to the number of samples needed to represent 2n cycles 71 | % of a sine with frequency = fres(1)[Hz], sampled at fs [Hz]. 72 | % This is done to ensure that every wavelet in the wavalet family will be 73 | % close to 0 in the negative and positive edges 74 | n_samples_wav = round( (2 * n / freq_vct(1)) * fs); 75 | 76 | % The wavelet will be symmetrical around 0 77 | if mod(n_samples_wav,2) == 0 % even samples 78 | n_samples_wav = n_samples_wav +1; 79 | end 80 | 81 | % Create time vector for Wavelet family 82 | half = floor(n_samples_wav/2); 83 | time = (-(half):half)/fs; 84 | 85 | 86 | % initialize Wavelet family matrix 87 | wfam = zeros(numel(time), n_freqs, x_class); 88 | 89 | % for each frequency defined in FREQ, create its respective Wavelet 90 | for iwav = 1 : n_freqs 91 | s(iwav) = n/(2*pi*freq_vct(iwav)); 92 | gaussian_win = exp(-time.^2./(2*s(iwav)^2)); 93 | sinwave = exp(2*pi*1i*freq_vct(iwav).*time); 94 | if normalization 95 | % each wavelet has unit energy sum(abs(wavelet).^2)) = 1 96 | A = 1 / ((s(iwav)^2) * pi)^(1/4); 97 | else 98 | A = 1; 99 | end 100 | 101 | % Complex morlet wavelet 102 | wfam(:,iwav) = A * sinwave .* gaussian_win; 103 | end 104 | 105 | for i_channel = 1 : n_channels 106 | % one channel 107 | x_tmp = x(: , i_channel); 108 | % convolution between signal X and the each Wavelt in the Wavelet family 109 | tmp = conv_fft(x_tmp , wfam , 'same'); 110 | if i_channel == 1 111 | % initialize wcoef 112 | wcoef = zeros(n_samples, n_freqs, n_channels, x_class); 113 | end 114 | wcoef(: , : , i_channel) = tmp; 115 | 116 | end 117 | 118 | end 119 | -------------------------------------------------------------------------------- /example_02.m: -------------------------------------------------------------------------------- 1 | %% Example 02 2 | % Script to show the use of the functions: 3 | % 4 | % rfft_psd() Compute PSD using rFFT 5 | % strfft_spectrogram() Compute Spectrogram using STFFT 6 | % strfft_modulation_spectrogram() Compute Modulation Spectrogram using STFFT 7 | % wavelet_spectrogram() Compute Spectrogram using wavelet transformation 8 | % wavelet_modulation_spectrogram() Compute Modulation Spectrogram using wavelet transformation 9 | % 10 | % plot_signal() Plot a signal in time domain 11 | % plot_psd_data() Plot PSD data obtained with rfft_psd() 12 | % plot_spectrogram_data() Plot Spectrogram data obtained with 13 | % strfft_spectrogram() or wavelet_spectrogram() 14 | % plot_modulation_spectrogram_data() Plot Modulation Spectrogram data obtained with 15 | % strfft_modspectrogram() or wavelet_modspectrogram() 16 | % 17 | % Moreover, this script compares diverse ways to compute the power of a 18 | % signal in the Time, Frequency, Time-Frequency and Frequency-Frequency domains 19 | % 20 | close all 21 | clear 22 | 23 | %% Test signal 24 | 25 | fs = 500; 26 | T = 1/fs; 27 | t1_vct = (0 : 1/fs : 10 - 1/fs)'; 28 | 29 | x1 = 3 * sin (2 * pi * 10 * t1_vct); 30 | x2 = 2 * sin (2 * pi * 24 * t1_vct); 31 | x3 = 1 * randn([numel(t1_vct), 1]); 32 | 33 | x = [x1; x2; x3]; 34 | n = numel(x); 35 | 36 | %% Plot signal 37 | plot_signal(x, fs, 'test-signal'); 38 | 39 | %% Power in Time Domain 40 | % Energy of the signal 41 | energy_x = T .* sum(x.^2); 42 | duration = T .* n; 43 | 44 | % Power of the signal 45 | power_x = energy_x ./ duration; 46 | 47 | % A simpler way is 48 | power_x_2 = (1 / n) * sum(x.^2); 49 | 50 | %% Power in Frequency domain 51 | % Power using FFT 52 | X = fft(x); 53 | power_X = (1 / n.^2) * sum(X.*conj(X)); 54 | 55 | % Power using its PSD from rFFT 56 | psd_rfft_r = rfft_psd(x, fs, [], 'rectwin'); 57 | f_step = psd_rfft_r.freq_axis(2); 58 | power_psd_rfft_x_rw = f_step * sum(psd_rfft_r.PSD); 59 | figure() 60 | plot_psd_data(psd_rfft_r) 61 | 62 | % Power using its PSD from rFFT 63 | psd_rfft_b = rfft_psd(x, fs, [], 'blackmanharris'); 64 | f_step = psd_rfft_b.freq_axis(2); 65 | power_psd_rfft_x_bh = f_step * sum(psd_rfft_b.PSD); 66 | figure() 67 | plot_psd_data(psd_rfft_b) 68 | 69 | %% Power from STFFT Spectrogram (Hamming window) 70 | w_size = 1 * fs; 71 | w_shift = 0.5 * w_size; 72 | win_funct = 'hamming'; 73 | rfft_spect_h = strfft_spectrogram(x, fs, w_size, w_shift, [], win_funct ); 74 | power_spect_h = sum(sum(rfft_spect_h.power_spectrogram)) * rfft_spect_h.freq_delta * rfft_spect_h.time_delta; 75 | figure() 76 | plot_spectrogram_data(rfft_spect_h); 77 | 78 | %% Power from STFFT Spectrogram (Rectangular window) 79 | w_size = 1 * fs; 80 | w_shift = 0.5 * w_size; 81 | win_funct = 'rectwin'; 82 | rfft_spect_r = strfft_spectrogram(x, fs, w_size, w_shift, [], win_funct ); 83 | power_spect_r = sum(sum(rfft_spect_r.power_spectrogram)) * rfft_spect_r.freq_delta * rfft_spect_r.time_delta; 84 | figure() 85 | plot_spectrogram_data(rfft_spect_r); 86 | 87 | %% Power from Wavelet Spectrogram N = 6 88 | wav_spect_6 = wavelet_spectrogram(x, fs, 6); 89 | power_wav_6 = sum(sum(wav_spect_6.power_spectrogram)) * wav_spect_6.freq_delta * wav_spect_6.time_delta ; 90 | figure() 91 | plot_spectrogram_data(wav_spect_6); 92 | 93 | %% Power from Wavelet Spectrogram N = 10 94 | wav_spect_10 = wavelet_spectrogram(x, fs, 10); 95 | power_wav_10 = sum(sum(wav_spect_10.power_spectrogram)) * wav_spect_10.freq_delta * wav_spect_10.time_delta ; 96 | figure() 97 | plot_spectrogram_data(wav_spect_10); 98 | 99 | %% Power from Modulation Spectrogram STFFT 100 | w_size = 1 * fs; 101 | w_shift = 0.5 * w_size; 102 | rfft_mod_b = strfft_modulation_spectrogram(x, fs, w_size, w_shift, [], win_funct, [], win_funct); 103 | power_mod = sum(sum(rfft_mod_b.power_modulation_spectrogram) * rfft_mod_b.freq_delta * rfft_mod_b.freq_mod_delta); 104 | figure() 105 | plot_modulation_spectrogram_data(rfft_mod_b) 106 | 107 | %% Power from Modulation Spectrogram Wavelet 108 | wav_mod_6 = wavelet_modulation_spectrogram(x, fs, 6, [], [], win_funct); 109 | power_mod_w = sum(sum(wav_mod_6.power_modulation_spectrogram) * wav_mod_6.freq_delta * wav_mod_6.freq_mod_delta); 110 | figure() 111 | plot_modulation_spectrogram_data(wav_mod_6) 112 | -------------------------------------------------------------------------------- /ama_toolbox/strfft_spectrogram.m: -------------------------------------------------------------------------------- 1 | function spectrogram_data = strfft_spectrogram(x, fs, win_size, win_shift, n_fft, win_function, channel_names) 2 | %spectrogram_data = STRFFT_SPECTROGRAM(x, fs, win_size, win_shift, n_fft, win_function, channel_names) 3 | % Compute the Short Time real FFT Spectrogram for one or a set of REAL signals 'x'. 4 | % 5 | % Parameters 6 | % ---------- 7 | % x : 1D array with shape (n_samples) or 8 | % 2D array with shape (n_samples, n_channels) 9 | % fs : Sampling frequency 10 | % in Hz 11 | % win_size : 12 | % Size of the sliding window for STFFF (samples) 13 | % win_shift : 14 | % Shift between consecutive windows (samples) 15 | % n_fft : Number of samples to compute the FFT 16 | % (Default = n_samples in array x) 17 | % win_function : Window function applied to the signal 18 | % (Default 'Hamming') 19 | % channel_names : Names of the signals 20 | % (Default Signal-XX with XX 1, 2, ... n_channels) 21 | % 22 | % Returns 23 | % ------- 24 | % spectrogram_data : Structure with Spectrogram data, with the elements: 25 | % rFFT_spectrogram 26 | % rFFT values for each window (u), scaled by the Window RMS 27 | % power_spectrogram : 28 | % PSD values for each window (u^2 / Hz) 29 | % fs : 30 | % Sampling frequency (Hz) 31 | % freq_axis : 32 | % Frequency axis for rFFT and PSD (Hz) 33 | % freq_delta : 34 | % Frequency axis step (Hz) 35 | % time_axis : 36 | % Time axis for rFFT_spectrogram and power_spectrogram (s) 37 | % time_delta : 38 | % Time axis step (s) 39 | % win_size_samples : 40 | % Size of the sliding window for STFFF (samples) 41 | % win_shift_samples : 42 | % Shift between consecutive windows (samples) 43 | % n_fft : 44 | % Number of elements utilized to perform FFT 45 | % win_function : 46 | % Window applied to the data in 'x' 47 | % n_windows : 48 | % Number of ST windows 49 | % n_samples : 50 | % Number of samples of the signal or signals 'x' 51 | % channel_names 52 | % Names of channels 53 | 54 | % get class of x 55 | x_class = class(x); 56 | 57 | % validate 'window_funct' argument 58 | if ~exist('win_function','var') || isempty(win_function) 59 | win_function = 'hamming'; 60 | end 61 | 62 | % validate 'n_fft' argument 63 | if ~exist('n_fft','var') || isempty(n_fft) 64 | n_fft = win_size; 65 | end 66 | 67 | % validate 'channel_names' argument 68 | if ~exist('channel_names','var') || isempty(channel_names) 69 | channel_names = {}; 70 | end 71 | 72 | % round win_size and win_shift 73 | win_size = round(win_size); 74 | win_shift = round(win_shift); 75 | 76 | % time axis step for Spectrogram 77 | t_delta = win_shift / fs; 78 | 79 | % number of samples for original signals 80 | n_samples = size(x , 1); 81 | 82 | % create time vector 'time_vct' for signal 'x' 83 | time_vct = (0 : n_samples - 1) / fs; 84 | 85 | % epoch signal or signals 'x' 86 | [x_epoched, ~, ix] = epoching(x, win_size, win_size - win_shift); 87 | 88 | % time axis for Spectrogram 89 | t_ax = time_vct(ix); 90 | 91 | % spectrogram parameters 92 | n_windows = size(x_epoched, 3); 93 | n_channels = size(x_epoched, 2); 94 | n_samples_win = size(x_epoched, 1); 95 | 96 | % generate default channel names, if needed 97 | if isempty(channel_names) 98 | for ic = 1 : n_channels 99 | channel_names{ic} = sprintf('Signal-%02d',ic); 100 | end 101 | end 102 | 103 | % compute PSD per window 104 | for i_window = 1 : n_windows 105 | % ith epoch of the signal or signals 106 | x_epoch = squeeze(x_epoched(:, :, i_window)); 107 | psd_struct = rfft_psd(x_epoch, fs, n_fft, win_function, channel_names); 108 | 109 | % initialize arrays for spectrogram data 110 | if i_window == 1 111 | % frequency Axis for spectrogram 112 | f_ax = psd_struct.freq_axis; 113 | % delta Frequency 114 | f_delta = psd_struct.freq_delta; 115 | % initialize 'rFFT_spectrogram' and 'pwr_spectrogram' 116 | rFFT_spectrogram = zeros(n_windows, numel(f_ax), n_channels, x_class); 117 | pwr_spectrogram = zeros(n_windows, numel(f_ax), n_channels, x_class); 118 | end 119 | % rFFT data 120 | rFFT_spectrogram(i_window, :, :) = psd_struct.rFFT; 121 | % power data 122 | pwr_spectrogram(i_window, :, :) = psd_struct.PSD; 123 | end 124 | 125 | % scale 'pwr_spectrogram' by number of windows and time delta 126 | pwr_spectrogram = pwr_spectrogram / (n_windows * t_delta); 127 | 128 | % output 'spectrogram_data' structure 129 | spectrogram_data.rFFT_spectrogram = rFFT_spectrogram; 130 | spectrogram_data.power_spectrogram = pwr_spectrogram; 131 | spectrogram_data.fs = fs; 132 | spectrogram_data.freq_axis = f_ax; 133 | spectrogram_data.freq_delta = f_delta; 134 | spectrogram_data.time_axis = t_ax; 135 | spectrogram_data.time_delta = t_delta; 136 | spectrogram_data.win_size_samples = win_size; 137 | spectrogram_data.win_shift_samples = win_shift; 138 | spectrogram_data.n_fft = n_fft; 139 | spectrogram_data.win_function = win_function; 140 | spectrogram_data.n_windows = n_windows; 141 | spectrogram_data.n_samples = n_samples; 142 | spectrogram_data.channel_names = channel_names; 143 | 144 | end 145 | -------------------------------------------------------------------------------- /example_03.m: -------------------------------------------------------------------------------- 1 | %% Example 03 2 | % Script to show the computation of several Modulation Spectrograms from a signal 3 | % 4 | % The Modulation Spectrogram are computed with the Wavelet transform 5 | % 6 | % Method A. 7 | % The signal, is segmented an a Modulation Spectrogram computed per Segment 8 | % 9 | % Method B. 10 | % The Spectrogram of the Full signal is obtained, after this Spectrogram 11 | % is segmented, and from each Segment the Modulation Spectrogram is 12 | % derived 13 | % 14 | % 15 | % Moreover, this script compares diverse ways to compute the power of a 16 | % signal in the Time, Time-Frequency and Frequency-Frequency domains 17 | % 18 | close all 19 | clear 20 | clc; 21 | 22 | %% ECG data (1 channel) 23 | load('./example_data/ecg_data.mat'); 24 | 25 | %% Segment parameters 26 | segment_length = 5; % seconds 27 | segment_overlap = 0; % seconds 28 | 29 | %% Power in Time Domain 30 | T = 1/fs; 31 | n = numel(x); 32 | 33 | % Energy of the signal 34 | energy_x = T .* sum(x.^2); 35 | duration = T .* n; 36 | 37 | % Power of the signal 38 | power_x = energy_x ./ duration; 39 | 40 | %% A. Epoching > Spectrogram > Modulation 41 | tic 42 | 43 | % Epochiong data 44 | x_segmented = epoching(x, segment_length * fs ); 45 | n_segments = size(x_segmented, 3); 46 | 47 | % For each Segment, compute its Spectrogram and Modulation Spectrogram 48 | for i_segment = 1 : n_segments 49 | x_tmp_wavelet = squeeze(x_segmented(:,:, i_segment)); 50 | 51 | % Wavelet-based Spectrogram and Modulation Spectrogram 52 | wavelet_spectrogram_data_a(i_segment) = wavelet_spectrogram(x_tmp_wavelet, fs); 53 | wavelet_modulation_spectrogram_data_a(i_segment) = wavelet_modulation_spectrogram(x_tmp_wavelet, fs); 54 | end 55 | toc 56 | 57 | %% B. Spectrogram > Epoching > Modulation 58 | tic 59 | % Spectrogram of the Full Signal with STFFT and Wavelets 60 | wavelet_spect_data = wavelet_spectrogram(x ,fs); 61 | 62 | % Epoching the Spectrogram 63 | wavelet_spect_segmented = epoching(wavelet_spect_data.power_spectrogram, segment_length * fs); 64 | n_segments = size(wavelet_spect_segmented, 3); 65 | 66 | % The Spectograms are scaled to represent the power of the full signal 67 | wavelet_spect_segmented = n_segments * wavelet_spect_segmented; 68 | 69 | % From each Segment of the Spectrogram, compute the Modulation Spectrogram 70 | for i_segment = 1 : n_segments 71 | wavelet_spectrogram_power_b{i_segment} = wavelet_spect_segmented(:,:,i_segment); 72 | 73 | % Square Root is obtained to work with the Instantaneous Amplitude 74 | x_tmp_wavelet = sqrt(squeeze(wavelet_spect_segmented(:,:, i_segment ))); 75 | 76 | % PSD of the Spectrogram Segment 77 | mod_psd_wavelet = rfft_psd(x_tmp_wavelet, fs); 78 | 79 | % Place results in corresponding index 80 | wavelet_modulation_spectrogram_power_b{i_segment} = mod_psd_wavelet.PSD' / mod_psd_wavelet.freq_delta; 81 | end 82 | 83 | toc 84 | %% Comparison 85 | 86 | % Create structures for Method B, for sake of plotting 87 | wavelet_spectrogram_data_b = wavelet_spectrogram_data_a; 88 | wavelet_modulation_spectrogram_data_b = wavelet_modulation_spectrogram_data_a; 89 | 90 | for i_segment = 1 : n_segments 91 | wavelet_spectrogram_data_b(i_segment).power_spectrogram = wavelet_spectrogram_power_b{i_segment}; 92 | wavelet_modulation_spectrogram_data_b(i_segment).power_modulation_spectrogram = wavelet_modulation_spectrogram_power_b{i_segment}; 93 | end 94 | 95 | % One segment is randomly chosen 96 | random_segment = randi(n_segments); 97 | 98 | for i_segment = 1 : n_segments 99 | if i_segment == random_segment 100 | figure() 101 | subplot(1,2,1) 102 | plot_spectrogram_data(wavelet_spectrogram_data_a(i_segment), 1); 103 | subplot(1,2,2) 104 | plot_spectrogram_data(wavelet_spectrogram_data_b(i_segment), 1); 105 | 106 | figure() 107 | subplot(1,2,1) 108 | plot_modulation_spectrogram_data(wavelet_modulation_spectrogram_data_a(i_segment), 1); 109 | subplot(1,2,2) 110 | plot_modulation_spectrogram_data(wavelet_modulation_spectrogram_data_b(i_segment), 1); 111 | end 112 | 113 | pwr_spectrogram_wavelet_a(i_segment) = sum(sum(wavelet_spectrogram_data_a(i_segment).power_spectrogram) * wavelet_spectrogram_data_a(1).freq_delta * wavelet_spectrogram_data_a(1).time_delta) ; 114 | pwr_spectrogram_wavelet_b(i_segment) = sum(sum(wavelet_spectrogram_data_b(i_segment).power_spectrogram) * wavelet_spectrogram_data_b(1).freq_delta * wavelet_spectrogram_data_b(1).time_delta) ; 115 | 116 | pwr_modulation_spectrogram_wavelet_a(i_segment) = sum(sum(wavelet_modulation_spectrogram_data_a(i_segment).power_modulation_spectrogram) * wavelet_modulation_spectrogram_data_a(1).freq_delta * wavelet_modulation_spectrogram_data_a(1).freq_mod_delta); 117 | pwr_modulation_spectrogram_wavelet_b(i_segment) = sum(sum(wavelet_modulation_spectrogram_data_b(i_segment).power_modulation_spectrogram) * wavelet_modulation_spectrogram_data_b(1).freq_delta * wavelet_modulation_spectrogram_data_b(1).freq_mod_delta); 118 | end 119 | 120 | %% Power comparison Spectrogram and Modulation Spectrogram 121 | figure() 122 | title('Total Power per Epoch, based on Spectrogram') 123 | plot([pwr_spectrogram_wavelet_a', pwr_spectrogram_wavelet_b']) 124 | legend('Wavelet Spectrogram A', 'Wavelet Spectrogram B') 125 | mean([pwr_spectrogram_wavelet_a', pwr_spectrogram_wavelet_b']) 126 | 127 | figure() 128 | title('Total Power per Epoch, based on Modulation Spectrogram') 129 | plot([pwr_modulation_spectrogram_wavelet_a', pwr_modulation_spectrogram_wavelet_b']) 130 | legend('Wavelet Modulation Spectrogram A', 'Wavelet Modulation Spectrogram B') 131 | mean([pwr_modulation_spectrogram_wavelet_a', pwr_modulation_spectrogram_wavelet_b']) 132 | -------------------------------------------------------------------------------- /ama_toolbox/wavelet_modulation_spectrogram.m: -------------------------------------------------------------------------------- 1 | function modulation_spectrogram_data = wavelet_modulation_spectrogram( x, fs, n_cycles, freq_vct, fft_factor_x, win_function_x, channel_names) 2 | %modulation_spectrogram_data = WAVELET_MODSPECTROGRAM( x, fs, n_cycles, freq_vct, fft_factor_x, win_function_x, channel_names) 3 | % Compute the Modulation Spectrogram using the Wavelet for one or a set of REAL signals 'x'. 4 | % 5 | % Parameters 6 | % ---------- 7 | % x : 1D array with shape (n_samples) or 8 | % 2D array with shape (n_samples, n_channels) 9 | % fs : Sampling frequency 10 | % in Hz 11 | % n : Number of cicles inside the Gaussian curve 12 | % (Default 6) 13 | % freq_vct : 1D array 14 | % with frequencies to compute the CWT (Default = [1 : 1 : fs/2] ) 15 | % fft_factor_x : Number of elements to perform the FFT is given as: 16 | % n_fft_x = fft_factor_x * n_samples, (default, fft_factor_x = 1) 17 | % win_function_x : Window to apply in the rFFT 18 | % (Default 'Hamming') 19 | % channel_names : Names of the signals 20 | % (Default Signal-XX with XX 1, 2, ... n_channels) 21 | % 22 | % Returns 23 | % ------- 24 | % modulation_spectrogram_data : Dictionary with Modulation Spectrogram data, with the elements: 25 | % rFFT_modulation_spectrogram 26 | % rFFT values for each window (u), scaled by the Window RMS 27 | % power_modulation_spectrogram : 28 | % Power modulation spectrogram (u^2 / Hz) 29 | % fs : 30 | % Sampling frequency (Hz) 31 | % fs_mod : 32 | % Sampling frequency of modulation-frequency (Hz) 33 | % freq_axis : 34 | % Frequency axis for rFFT and PSD (Hz) 35 | % freq_delta : 36 | % Frequency axis step (Hz) 37 | % freq_mod_axis : 38 | % Modulation-frequency axis for rFFT_modspec and pwr_modspec (Hz) 39 | % freq_mod_delta : 40 | % Modulation-frequency step (Hz) 41 | % n_fft_x : 42 | % Number of elements utilized to perform the FFT 43 | % win_function_x : 44 | % Window to apply in the 2nd rFFT 45 | % n_samples : 46 | % Number of samples of the signal or signals 'x' 47 | % spectrogram_data : 48 | % Dictionary with Spectrogram data 49 | % channel_names : 50 | % Names of channels 51 | 52 | % get class of x 53 | x_class = class(x); 54 | 55 | % validate 'freq_vct' argument 56 | if ~exist('freq_vct','var') || isempty(freq_vct) 57 | freq_vct = 1 : floor(fs / 2); 58 | end 59 | 60 | % validate 'n_cycles' argument 61 | if ~exist('n_cycles','var') || isempty(n_cycles) 62 | n_cycles = 6; 63 | end 64 | 65 | % validate 'win_function_x' argument 66 | if ~exist('win_function_x','var') || isempty(win_function_x) 67 | win_function_x = 'hamming'; 68 | end 69 | 70 | % validate 'fft_factor_x' argument 71 | if ~exist('fft_factor_x','var') || isempty(fft_factor_x) 72 | fft_factor_x = 1; 73 | end 74 | 75 | % validate 'channel_names' argument 76 | if ~exist('channel_names','var') || isempty(channel_names) 77 | channel_names = {}; 78 | end 79 | 80 | % compute wavelet spectrogram 81 | spectrogram_data = wavelet_spectrogram(x, fs, n_cycles, freq_vct, channel_names); 82 | [n_windows, n_freqs, n_channels] = size(spectrogram_data.wavelet_coefficients); 83 | 84 | % number of elements for FFT of the spectrogram 85 | n_fft_x = fft_factor_x * n_windows; 86 | 87 | % generate default channel names 88 | if isempty(channel_names) 89 | for ic = 1 : n_channels 90 | channel_names{ic} = sprintf('Signal-%02d',ic); 91 | end 92 | end 93 | 94 | % modulation sampling frequency 95 | fs_mod = fs; 96 | 97 | % the AM analysis is made in the Amplitude derived from the Power Spectrogram 98 | for i_channel = 1 : n_channels 99 | % data to generate the Modulation Spectrogram 100 | spectrogram_1ch = sqrt(spectrogram_data.power_spectrogram(:,:,i_channel)) ; 101 | %spectrogram_1ch = abs(spectrogram_data.rFFT_spectrogram(:,:,i_channel)); 102 | %spectrogram_1ch = angle(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 103 | %spectrogram_1ch = imag(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 104 | %spectrogram_1ch = real(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 105 | 106 | % Compute rfft_psd on each frequency timeseries 107 | mod_psd_struct = rfft_psd(spectrogram_1ch, fs_mod, n_fft_x, win_function_x, channel_names ); 108 | 109 | if i_channel == 1 110 | % modulation frequency axis 111 | fmod_ax = mod_psd_struct.freq_axis; 112 | % modulation frequency delta 113 | fmod_delta = mod_psd_struct.freq_delta; 114 | 115 | % initialize 'rFFT_modspec' and 'pwr_modspec' 116 | n_freqsmod = numel(fmod_ax); 117 | rFFT_modspec = zeros(n_freqs, n_freqsmod ,n_channels, x_class); 118 | pwr_modspec = zeros(n_freqs, n_freqsmod ,n_channels, x_class); 119 | end 120 | % rFFT data 121 | rFFT_modspec(:, :, i_channel) = transpose(mod_psd_struct.rFFT); 122 | % power data 123 | pwr_modspec(:, :, i_channel) = transpose(mod_psd_struct.PSD); 124 | end 125 | 126 | % scale 'pwr_modspec' by modulation-frequency delta 127 | pwr_modspec = pwr_modspec / fmod_delta; 128 | 129 | % output 'modulation_spectrogram_data' structure 130 | modulation_spectrogram_data.rFFT_modulation_spectrogram = rFFT_modspec; 131 | modulation_spectrogram_data.power_modulation_spectrogram = pwr_modspec; 132 | modulation_spectrogram_data.fs = fs; 133 | modulation_spectrogram_data.fs_mod = fs_mod; 134 | modulation_spectrogram_data.freq_axis = spectrogram_data.freq_axis; 135 | modulation_spectrogram_data.freq_delta = spectrogram_data.freq_delta; 136 | modulation_spectrogram_data.freq_mod_axis = fmod_ax; 137 | modulation_spectrogram_data.freq_mod_delta = fmod_delta; 138 | modulation_spectrogram_data.n_fft_x = n_fft_x; 139 | modulation_spectrogram_data.win_function_x = win_function_x; 140 | modulation_spectrogram_data.n_samples = spectrogram_data.n_samples; 141 | modulation_spectrogram_data.spectrogram_data = spectrogram_data; 142 | modulation_spectrogram_data.channel_names = channel_names; 143 | end 144 | -------------------------------------------------------------------------------- /ama_toolbox/strfft_modulation_spectrogram.m: -------------------------------------------------------------------------------- 1 | function modulation_spectrogram_data = strfft_modulation_spectrogram( x, fs, win_size, win_shift, fft_factor_y, win_function_y, fft_factor_x, win_function_x, channel_names) 2 | %modulation_spectrogram_data = STRFFT_MODSPECTROGRAM( x, fs, win_size, win_shift, fft_factor_y, win_function_y, fft_factor_x, win_function_x, channel_names) 3 | % Compute the Modulation Spectrogram for one or a set of REAL signals 'x'. 4 | % 5 | % Parameters 6 | % ---------- 7 | % x : 1D array with shape (n_samples) or 8 | % 2D array with shape (n_samples, n_channels) 9 | % fs : Sampling frequency 10 | % in Hz 11 | % win_size : 12 | % Size of the sliding window for STFFF (samples) 13 | % win_shift : 14 | % Shift between consecutive windows (samples) 15 | % fft_factor_y : Number of elements to perform the 1st FFT is given as: 16 | % n_fft_y = fft_factor_y * n_samples, (default, fft_factor_y = 1) 17 | % win_function_y : Window to apply in the 1st FFT 18 | % (Default 'Hamming') 19 | % fft_factor_x : Number of elements to perform the 2nd FFT is given as: 20 | % n_fft_x = fft_factor_x * n_samples, (default, fft_factor_x = 1) 21 | % win_function_x : Window to apply in the 2nd rFFT 22 | % (Default 'Hamming') 23 | % n_fft : Number of samples to compute the FFT 24 | % (Default = n_samples in array x) 25 | % channel_names : Names of the signals 26 | % (Default Signal-XX with XX 1, 2, ... n_channels) 27 | % 28 | % Returns 29 | % ------- 30 | % modulation_spectrogram_data : Structure with Modulation Spectrogram data, with the elements: 31 | % rFFT_modulation_spectrogram 32 | % rFFT values for each window (u), scaled by the Window RMS 33 | % power_modulation_spectrogram : 34 | % Power modulation spectrogram (u^2 / Hz) 35 | % fs : 36 | % Sampling frequency (Hz) 37 | % fs_mod : 38 | % Sampling frequency of modulation-frequency (Hz) 39 | % freq_axis : 40 | % Frequency axis for rFFT and PSD (Hz) 41 | % freq_delta : 42 | % Frequency axis step (Hz) 43 | % freq_mod_axis : 44 | % Modulation-frequency axis for rFFT_modspec and pwr_modspec (Hz) 45 | % freq_mod_delta : 46 | % Modulation-frequency step (Hz) 47 | % win_size_samples : 48 | % Size of the sliding window for STFFF (samples) 49 | % win_shift_samples : 50 | % Shift between consecutive windows (samples) 51 | % n_fft_y : 52 | % Number of elements utilized to perform the 1st FFT 53 | % n_fft_x : 54 | % Number of elements utilized to perform the 2nd FFT 55 | % win_function_y : 56 | % Window to apply in the 1st rFFT 57 | % win_function_x : 58 | % Window to apply in the 2nd rFFT 59 | % n_windows : 60 | % Number of ST windows 61 | % n_samples : 62 | % Number of samples of the signal or signals 'x' 63 | % spectrogram_data : 64 | % Structure with Spectrogram data 65 | % channel_names : 66 | % Names of channels 67 | 68 | % get class of x 69 | x_class = class(x); 70 | 71 | % validate 'win_function_y' argument 72 | if ~exist('win_function_y','var') || isempty(win_function_y) 73 | win_function_y = 'hamming'; 74 | end 75 | 76 | % validate 'win_function_x' argument 77 | if ~exist('win_function_x','var') || isempty(win_function_x) 78 | win_function_x = 'hamming'; 79 | end 80 | 81 | % validate 'fft_factor_y' argument 82 | if ~exist('fft_factor_y','var') || isempty(fft_factor_y) 83 | fft_factor_y = 1; 84 | end 85 | 86 | % number of elements for the 1st FFT 87 | n_fft_y = fft_factor_y * win_size; 88 | 89 | % validate 'fft_factor_x' argument 90 | if ~exist('fft_factor_x','var') || isempty(fft_factor_x) 91 | fft_factor_x = 1; 92 | end 93 | 94 | % validate 'channel_names' argument 95 | if ~exist('channel_names','var') || isempty(channel_names) 96 | channel_names = {}; 97 | end 98 | 99 | % compute STFFT spectrogram 100 | spectrogram_data = strfft_spectrogram(x, fs, win_size, win_shift, n_fft_y, win_function_y, channel_names); 101 | [n_windows, n_freqs, n_channels] = size(spectrogram_data.rFFT_spectrogram); 102 | % Number of elements for the 2nd FFT 103 | n_fft_x = fft_factor_x * n_windows; 104 | 105 | % Generate default channel names, if needed 106 | if isempty(channel_names) 107 | for ic = 1 : n_channels 108 | channel_names{ic} = sprintf('Signal-%02d',ic); 109 | end 110 | end 111 | 112 | % modulation sampling frequency 113 | fs_mod = 1 / (win_shift / fs); 114 | 115 | % the AM analysis is made in the Amplitude derived from the Power Spectrogram 116 | for i_channel = 1 : n_channels 117 | % data to generate the Modulation Spectrogram 118 | spectrogram_1ch = sqrt(spectrogram_data.power_spectrogram(:,:,i_channel)) ; 119 | %spectrogram_1ch = abs(spectrogram_data.rFFT_spectrogram(:,:,i_channel)); 120 | %spectrogram_1ch = angle(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 121 | %spectrogram_1ch = imag(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 122 | %spectrogram_1ch = real(squeeze(spectrogram_data.rFFT_spectrogram(:,:,i_channel))); 123 | 124 | % compute 'rfft_psd' on each frequency timeseries 125 | mod_psd_struct = rfft_psd(spectrogram_1ch, fs_mod, n_fft_x, win_function_x, channel_names ); 126 | 127 | if i_channel == 1 128 | % modulation frequency axis 129 | fmod_ax = mod_psd_struct.freq_axis; 130 | % modulation frequency delta 131 | fmod_delta = mod_psd_struct.freq_delta; 132 | 133 | % initialize 'rFFT_modspec' and 'pwr_modspec' 134 | n_freqsmod = numel(fmod_ax); 135 | rFFT_modspec = zeros(n_freqs, n_freqsmod ,n_channels, x_class); 136 | pwr_modspec = zeros(n_freqs, n_freqsmod ,n_channels, x_class); 137 | end 138 | % rFFT data 139 | rFFT_modspec(:, :, i_channel) = transpose(mod_psd_struct.rFFT); 140 | % power data 141 | pwr_modspec(:, :, i_channel) = transpose(mod_psd_struct.PSD); 142 | end 143 | 144 | % scale 'pwr_modspec' by modulation-frequency delta 145 | pwr_modspec = pwr_modspec / fmod_delta; 146 | 147 | % output 'modulation_spectrogram_data' structure 148 | modulation_spectrogram_data.rFFT_modulation_spectrogram = rFFT_modspec; 149 | modulation_spectrogram_data.power_modulation_spectrogram = pwr_modspec; 150 | modulation_spectrogram_data.fs = fs; 151 | modulation_spectrogram_data.fs_mod = fs_mod; 152 | modulation_spectrogram_data.freq_axis = spectrogram_data.freq_axis; 153 | modulation_spectrogram_data.freq_delta = spectrogram_data.freq_delta; 154 | modulation_spectrogram_data.freq_mod_axis = fmod_ax; 155 | modulation_spectrogram_data.freq_mod_delta = fmod_delta; 156 | modulation_spectrogram_data.win_size_samples = win_size; 157 | modulation_spectrogram_data.win_shift_samples = win_shift; 158 | modulation_spectrogram_data.n_fft_x = n_fft_x; 159 | modulation_spectrogram_data.n_fft_y = n_fft_y; 160 | modulation_spectrogram_data.win_function_y = win_function_y; 161 | modulation_spectrogram_data.win_function_x = win_function_x; 162 | modulation_spectrogram_data.n_windows = n_windows; 163 | modulation_spectrogram_data.n_samples = spectrogram_data.n_samples; 164 | modulation_spectrogram_data.spectrogram_data = spectrogram_data; 165 | modulation_spectrogram_data.channel_names = channel_names; 166 | end 167 | -------------------------------------------------------------------------------- /ama_toolbox/explore_wavelet_ama_gui.m: -------------------------------------------------------------------------------- 1 | function explore_wavelet_ama_gui(X, fs, Names, c_map) 2 | % Analysis of a Signal in Frequency-Frequency Domain 3 | % Time -> Time-Frequency transformation performed with Wavelet Transform (Complex Morlet) 4 | % 5 | % INPUTS: 6 | % X Real-valued column-vector signal or set of signals [n_samples, n_channels] 7 | % fs Sampling frequency (Hz) 8 | % Optional: 9 | % Names (Optional) Name of the signal(s), String or Cell Array (Strings) 10 | % c_map (Optional) Colormap, Default 'viridis' 11 | % 12 | 13 | clc; 14 | 15 | % Global variable with the value of the Key Pressed 16 | global key_pressed 17 | key_pressed = ''; 18 | 19 | %% verify input arguments 20 | if ~exist('X','var') 21 | error('Variable X is requiered'); 22 | end 23 | 24 | if ~exist('fs','var') 25 | error('Variable fs is requiered'); 26 | end 27 | 28 | % number of channels 29 | n_channels = size(X, 2); 30 | 31 | % Validate 'c_map' argumet 32 | if ~exist('c_map','var') || isempty(c_map) 33 | c_map = 'viridis'; 34 | end 35 | 36 | % verify channel names 37 | if exist('Names','var') 38 | if ischar(Names) && n_channels == 1 39 | Names = {Names}; 40 | end 41 | end 42 | if ~exist('Names','var') || numel(Names) ~= n_channels 43 | Names = cell(1, n_channels); 44 | for ix = 1 : n_channels 45 | Names{ix} = sprintf('Signal-%02d', ix); 46 | end 47 | end 48 | 49 | % check if 'am_functions' folder exist, if does, add it to the MATLAB path 50 | if exist('./am_functions', 'dir') 51 | addpath('./am_functions'); 52 | end 53 | 54 | % verify dependencies 55 | if ~exist('wavelet_modulation_spectrogram.m','file') 56 | error('Dependencies for explore_wavelet_am_gui were not satisfied'); 57 | end 58 | 59 | %% Amplitude Modulation Analysis 60 | % default Modulation Analysis parameters 61 | n_cycles = 6; % number of cycles (for Complex Morlet) 62 | seg_size_sec = 8; % segment of signal to compute the Modulation Spectrogram (seconds) 63 | seg_shft_sec = 8; % shift between consecutive segments (seconds) 64 | freq_range = []; % limits [min, max] for the conventional frequency axis (Hz) 65 | mfreq_range = []; % limits [min, max] for the modulation frequency axis (Hz) 66 | freq_color = []; % limits [min, max] for the power in Spectrogram (dB) 67 | mfreq_color = []; % limits [min, max] for the power in Modulation Spectrogram (dB) 68 | 69 | % initial channel and segment 70 | ix_channel = 1; 71 | ix_segment = 1; 72 | 73 | % other variables 74 | n_segments = []; 75 | h_ts = []; 76 | h_tf = []; 77 | h_area1 = []; 78 | h_area2 = []; 79 | x_segments = []; 80 | 81 | %% Live GUI 82 | h_fig = figure('Name', 'Explore Wavelet Amplitude Modulation', 'KeyPressFcn', {@key_pressed_fcn}); 83 | first_run(); 84 | 85 | while true 86 | pause(0.01); 87 | % handles user request in GUI 88 | switch key_pressed 89 | case 'escape' %ESC: Exit 90 | break 91 | case {'leftarrow', 'left'} %Left arrow: Previous Segment 92 | ix_segment = ix_segment - 1; 93 | key_pressed = ''; 94 | update_plots(); 95 | case {'rightarrow', 'right'} %Right arrow: Next Segment 96 | ix_segment = ix_segment + 1; 97 | key_pressed = ''; 98 | update_plots(); 99 | case {'uparrow', 'up'} %Up arrow: Previous 100 | ix_channel = ix_channel - 1; 101 | key_pressed = ''; 102 | first_run(); 103 | case {'downarrow', 'down'} %Down arrow: Next Channel 104 | ix_channel = ix_channel + 1; 105 | key_pressed = ''; 106 | first_run(); 107 | case {'a', 'A'} %A: Back 5 Segments 108 | ix_segment = ix_segment - 5; 109 | key_pressed = ''; 110 | update_plots(); 111 | case {'d', 'D'} %D: Advance 5 Segment 112 | ix_segment = ix_segment + 5; 113 | key_pressed = ''; 114 | update_plots(); 115 | case {'w', 'W'} %W Previous 5 channels 116 | ix_channel = ix_channel - 5; 117 | key_pressed = ''; 118 | first_run(); 119 | case {'s', 'S'} %S Next 5 Channels 120 | ix_channel = ix_channel + 5; 121 | key_pressed = ''; 122 | first_run(); 123 | case {'u', 'U'} 124 | key_pressed = ''; 125 | update_parameters(); 126 | end 127 | end 128 | 129 | % executed at first time running or when an update in parameters is performed 130 | function first_run() 131 | ix_segment = 1; 132 | clc; 133 | disp('computing full-signal spectrogram...'); 134 | 135 | % constrain ix_channel to [1 : n_channels] 136 | ix_channel = max([1, ix_channel]); 137 | ix_channel = min([n_channels, ix_channel]); 138 | 139 | % wavelet modulation spectrogram parameters in samples 140 | seg_size_smp = round(seg_size_sec * fs); % (samples) 141 | seg_shft_smp = round(seg_shft_sec * fs); % (samples) 142 | 143 | % signal for analysis 144 | x_probe = X(:, ix_channel); 145 | 146 | % segment of signal under analysis 147 | x_segments = epoching(x_probe, seg_size_smp, seg_size_smp - seg_shft_smp); 148 | n_segments = size(x_segments,3); 149 | 150 | % plot complete time series 151 | h_ts = subplot(4,2,[1,2]); 152 | plot_signal(x_probe(:), fs, Names{ix_channel}); 153 | colorbar(); 154 | time_lim = get(gca, 'XLim' ); 155 | h_area1 = []; 156 | 157 | % compute and plot complete spectrogram 158 | x_spectrogram = wavelet_spectrogram(x_probe, fs, n_cycles, [], Names(ix_channel)); 159 | h_tf = subplot(4,2,[3,4]); 160 | plot_spectrogram_data(x_spectrogram, [], [], freq_range, freq_color, c_map) 161 | set(gca, 'XLim', time_lim); 162 | h_area2 = []; 163 | 164 | % update plots 165 | update_plots(); 166 | end 167 | 168 | % update plots when a new segment is selected 169 | function update_plots() 170 | % constrain ix_segment to [1 : n_segments] 171 | ix_segment = max([1, ix_segment]); 172 | ix_segment = min([n_segments, ix_segment]); 173 | 174 | % delete old areas (if existent) 175 | if ~isempty(h_area1) 176 | delete(h_area1); 177 | delete(h_area2); 178 | end 179 | 180 | % select segment 181 | x = x_segments(:, :, ix_segment); 182 | 183 | % compute and plot Modulation Spectrogram 184 | clc; 185 | disp('computing modulation spectrogram...'); 186 | x_wavelet_modspec = wavelet_modulation_spectrogram(x, fs, n_cycles, [], 2, [], Names(ix_channel)); 187 | subplot(4,2,[6,8]) 188 | plot_modulation_spectrogram_data(x_wavelet_modspec, [], freq_range, mfreq_range, mfreq_color,c_map) 189 | % Uncomment for log axes 190 | %set(gca,'XScale','log'); 191 | %set(gca,'YScale','log'); 192 | 193 | % plot time series for segment 194 | subplot(4,2,5) 195 | plot_signal(x, fs, Names{ix_channel}); 196 | colorbar(); 197 | time_lim = get(gca, 'XLim' ); 198 | 199 | % plot spectrogram for segment 200 | subplot(4,2,7) 201 | plot_spectrogram_data(x_wavelet_modspec.spectrogram_data, [], [], freq_range, freq_color,c_map) 202 | set(gca, 'XLim', time_lim); 203 | 204 | % highlight area under analysis in time series 205 | seg_ini_sec = (ix_segment - 1) * seg_shft_sec; 206 | seg_end_sec = seg_ini_sec + seg_size_sec; 207 | subplot(h_ts) 208 | h_area1 = varea([seg_ini_sec, seg_end_sec ],{'g'}); 209 | % highlight area under analysis in Spectrogram 210 | subplot(h_tf) 211 | h_area2 = varea([seg_ini_sec, seg_end_sec ],{'g'}, 0.8); 212 | disp('done!'); 213 | 214 | % display information about analysis 215 | clc; 216 | fprintf('signal name : %s\n', Names{ix_channel} ); 217 | fprintf('segment size (seconds): %0.3f\n', seg_size_sec); 218 | fprintf('segment shift (seconds): %0.3f\n', seg_shft_sec); 219 | fprintf('segment position (sec): %0.3f\n', seg_ini_sec) 220 | fprintf('n cycles Complex Morlet: %d\n', n_cycles); 221 | 222 | drawnow(); 223 | end 224 | 225 | % update parameters for AM analysis 226 | function update_parameters() 227 | % GUI to get new parameters 228 | prompt = {'Segment (seconds): ', ... 229 | 'Segment shift (seconds): ', ... 230 | 'N Cycles : ', ... 231 | 'Freq Conv. min,Max (Hz) : ', ... 232 | 'Spectr Pwr min,Max (dB) : ', ... 233 | 'Freq Mod. min,Max (Hz) : ', ... 234 | 'ModSpec Pwr min,Max (dB) : ', ... 235 | }; 236 | 237 | win_name = 'Modulation Analysis Parameters'; numlines = 1; 238 | 239 | defaultanswer = {num2str(seg_size_sec), num2str(seg_shft_sec), ... 240 | num2str(n_cycles), ... 241 | num2str(freq_range), num2str(freq_color), ... 242 | num2str(mfreq_range), num2str(mfreq_color),... 243 | }; 244 | 245 | options.Resize = 'off'; 246 | 247 | answer = inputdlg(prompt,win_name,numlines,defaultanswer,options); 248 | 249 | % if the user clicks the 'Cancel' button, 'answer' is empty 250 | if ~isempty(answer) 251 | seg_size_sec = str2double(answer{1}); % (seconds) 252 | seg_shft_sec = str2double(answer{2}); % (seconds) 253 | n_cycles = str2double(answer{3}); % (cycles) 254 | freq_range = validate_range(answer{4}); % (Hz) 255 | freq_color = validate_range(answer{5}); % (dB) 256 | mfreq_range = validate_range(answer{6}); % (Hz) 257 | mfreq_color = validate_range(answer{7}); % (dB) 258 | 259 | % call first_run() 260 | first_run(); 261 | end 262 | end 263 | 264 | function out = validate_range(inStr) 265 | out = []; 266 | strParts = strsplit(inStr); 267 | if length(strParts) ~= 2 268 | return 269 | end 270 | numParts = str2double(strParts); 271 | if any(isnan(numParts)) 272 | return 273 | end 274 | out = numParts; 275 | end 276 | 277 | % if the user press ESC, close the GUI and clean console 278 | close(h_fig); 279 | clc; 280 | 281 | end 282 | -------------------------------------------------------------------------------- /ama_toolbox/viridis.m: -------------------------------------------------------------------------------- 1 | function C = viridis(N) 2 | %VIRIDIS Blue-green-yellow colour map 3 | % VIRIDIS(N) returns an N-by-3 matrix containing a colormap. 4 | % The colors begin with dark purplish-blue and blue, range 5 | % through green and end with yellow. 6 | % 7 | % VIRIDIS is the new default colormap for matplotlib 8 | % 9 | 10 | 11 | viridi = [ 12 | 0.26700401 0.00487433 0.32941519 13 | 0.26851048 0.00960483 0.33542652 14 | 0.26994384 0.01462494 0.34137895 15 | 0.27130489 0.01994186 0.34726862 16 | 0.27259384 0.02556309 0.35309303 17 | 0.27380934 0.03149748 0.35885256 18 | 0.27495242 0.03775181 0.36454323 19 | 0.27602238 0.04416723 0.37016418 20 | 0.2770184 0.05034437 0.37571452 21 | 0.27794143 0.05632444 0.38119074 22 | 0.27879067 0.06214536 0.38659204 23 | 0.2795655 0.06783587 0.39191723 24 | 0.28026658 0.07341724 0.39716349 25 | 0.28089358 0.07890703 0.40232944 26 | 0.28144581 0.0843197 0.40741404 27 | 0.28192358 0.08966622 0.41241521 28 | 0.28232739 0.09495545 0.41733086 29 | 0.28265633 0.10019576 0.42216032 30 | 0.28291049 0.10539345 0.42690202 31 | 0.28309095 0.11055307 0.43155375 32 | 0.28319704 0.11567966 0.43611482 33 | 0.28322882 0.12077701 0.44058404 34 | 0.28318684 0.12584799 0.44496 35 | 0.283072 0.13089477 0.44924127 36 | 0.28288389 0.13592005 0.45342734 37 | 0.28262297 0.14092556 0.45751726 38 | 0.28229037 0.14591233 0.46150995 39 | 0.28188676 0.15088147 0.46540474 40 | 0.28141228 0.15583425 0.46920128 41 | 0.28086773 0.16077132 0.47289909 42 | 0.28025468 0.16569272 0.47649762 43 | 0.27957399 0.17059884 0.47999675 44 | 0.27882618 0.1754902 0.48339654 45 | 0.27801236 0.18036684 0.48669702 46 | 0.27713437 0.18522836 0.48989831 47 | 0.27619376 0.19007447 0.49300074 48 | 0.27519116 0.1949054 0.49600488 49 | 0.27412802 0.19972086 0.49891131 50 | 0.27300596 0.20452049 0.50172076 51 | 0.27182812 0.20930306 0.50443413 52 | 0.27059473 0.21406899 0.50705243 53 | 0.26930756 0.21881782 0.50957678 54 | 0.26796846 0.22354911 0.5120084 55 | 0.26657984 0.2282621 0.5143487 56 | 0.2651445 0.23295593 0.5165993 57 | 0.2636632 0.23763078 0.51876163 58 | 0.26213801 0.24228619 0.52083736 59 | 0.26057103 0.2469217 0.52282822 60 | 0.25896451 0.25153685 0.52473609 61 | 0.25732244 0.2561304 0.52656332 62 | 0.25564519 0.26070284 0.52831152 63 | 0.25393498 0.26525384 0.52998273 64 | 0.25219404 0.26978306 0.53157905 65 | 0.25042462 0.27429024 0.53310261 66 | 0.24862899 0.27877509 0.53455561 67 | 0.2468114 0.28323662 0.53594093 68 | 0.24497208 0.28767547 0.53726018 69 | 0.24311324 0.29209154 0.53851561 70 | 0.24123708 0.29648471 0.53970946 71 | 0.23934575 0.30085494 0.54084398 72 | 0.23744138 0.30520222 0.5419214 73 | 0.23552606 0.30952657 0.54294396 74 | 0.23360277 0.31382773 0.54391424 75 | 0.2316735 0.3181058 0.54483444 76 | 0.22973926 0.32236127 0.54570633 77 | 0.22780192 0.32659432 0.546532 78 | 0.2258633 0.33080515 0.54731353 79 | 0.22392515 0.334994 0.54805291 80 | 0.22198915 0.33916114 0.54875211 81 | 0.22005691 0.34330688 0.54941304 82 | 0.21812995 0.34743154 0.55003755 83 | 0.21620971 0.35153548 0.55062743 84 | 0.21429757 0.35561907 0.5511844 85 | 0.21239477 0.35968273 0.55171011 86 | 0.2105031 0.36372671 0.55220646 87 | 0.20862342 0.36775151 0.55267486 88 | 0.20675628 0.37175775 0.55311653 89 | 0.20490257 0.37574589 0.55353282 90 | 0.20306309 0.37971644 0.55392505 91 | 0.20123854 0.38366989 0.55429441 92 | 0.1994295 0.38760678 0.55464205 93 | 0.1976365 0.39152762 0.55496905 94 | 0.19585993 0.39543297 0.55527637 95 | 0.19410009 0.39932336 0.55556494 96 | 0.19235719 0.40319934 0.55583559 97 | 0.19063135 0.40706148 0.55608907 98 | 0.18892259 0.41091033 0.55632606 99 | 0.18723083 0.41474645 0.55654717 100 | 0.18555593 0.4185704 0.55675292 101 | 0.18389763 0.42238275 0.55694377 102 | 0.18225561 0.42618405 0.5571201 103 | 0.18062949 0.42997486 0.55728221 104 | 0.17901879 0.43375572 0.55743035 105 | 0.17742298 0.4375272 0.55756466 106 | 0.17584148 0.44128981 0.55768526 107 | 0.17427363 0.4450441 0.55779216 108 | 0.17271876 0.4487906 0.55788532 109 | 0.17117615 0.4525298 0.55796464 110 | 0.16964573 0.45626209 0.55803034 111 | 0.16812641 0.45998802 0.55808199 112 | 0.1666171 0.46370813 0.55811913 113 | 0.16511703 0.4674229 0.55814141 114 | 0.16362543 0.47113278 0.55814842 115 | 0.16214155 0.47483821 0.55813967 116 | 0.16066467 0.47853961 0.55811466 117 | 0.15919413 0.4822374 0.5580728 118 | 0.15772933 0.48593197 0.55801347 119 | 0.15626973 0.4896237 0.557936 120 | 0.15481488 0.49331293 0.55783967 121 | 0.15336445 0.49700003 0.55772371 122 | 0.1519182 0.50068529 0.55758733 123 | 0.15047605 0.50436904 0.55742968 124 | 0.14903918 0.50805136 0.5572505 125 | 0.14760731 0.51173263 0.55704861 126 | 0.14618026 0.51541316 0.55682271 127 | 0.14475863 0.51909319 0.55657181 128 | 0.14334327 0.52277292 0.55629491 129 | 0.14193527 0.52645254 0.55599097 130 | 0.14053599 0.53013219 0.55565893 131 | 0.13914708 0.53381201 0.55529773 132 | 0.13777048 0.53749213 0.55490625 133 | 0.1364085 0.54117264 0.55448339 134 | 0.13506561 0.54485335 0.55402906 135 | 0.13374299 0.54853458 0.55354108 136 | 0.13244401 0.55221637 0.55301828 137 | 0.13117249 0.55589872 0.55245948 138 | 0.1299327 0.55958162 0.55186354 139 | 0.12872938 0.56326503 0.55122927 140 | 0.12756771 0.56694891 0.55055551 141 | 0.12645338 0.57063316 0.5498411 142 | 0.12539383 0.57431754 0.54908564 143 | 0.12439474 0.57800205 0.5482874 144 | 0.12346281 0.58168661 0.54744498 145 | 0.12260562 0.58537105 0.54655722 146 | 0.12183122 0.58905521 0.54562298 147 | 0.12114807 0.59273889 0.54464114 148 | 0.12056501 0.59642187 0.54361058 149 | 0.12009154 0.60010387 0.54253043 150 | 0.11973756 0.60378459 0.54139999 151 | 0.11951163 0.60746388 0.54021751 152 | 0.11942341 0.61114146 0.53898192 153 | 0.11948255 0.61481702 0.53769219 154 | 0.11969858 0.61849025 0.53634733 155 | 0.12008079 0.62216081 0.53494633 156 | 0.12063824 0.62582833 0.53348834 157 | 0.12137972 0.62949242 0.53197275 158 | 0.12231244 0.63315277 0.53039808 159 | 0.12344358 0.63680899 0.52876343 160 | 0.12477953 0.64046069 0.52706792 161 | 0.12632581 0.64410744 0.52531069 162 | 0.12808703 0.64774881 0.52349092 163 | 0.13006688 0.65138436 0.52160791 164 | 0.13226797 0.65501363 0.51966086 165 | 0.13469183 0.65863619 0.5176488 166 | 0.13733921 0.66225157 0.51557101 167 | 0.14020991 0.66585927 0.5134268 168 | 0.14330291 0.66945881 0.51121549 169 | 0.1466164 0.67304968 0.50893644 170 | 0.15014782 0.67663139 0.5065889 171 | 0.15389405 0.68020343 0.50417217 172 | 0.15785146 0.68376525 0.50168574 173 | 0.16201598 0.68731632 0.49912906 174 | 0.1663832 0.69085611 0.49650163 175 | 0.1709484 0.69438405 0.49380294 176 | 0.17570671 0.6978996 0.49103252 177 | 0.18065314 0.70140222 0.48818938 178 | 0.18578266 0.70489133 0.48527326 179 | 0.19109018 0.70836635 0.48228395 180 | 0.19657063 0.71182668 0.47922108 181 | 0.20221902 0.71527175 0.47608431 182 | 0.20803045 0.71870095 0.4728733 183 | 0.21400015 0.72211371 0.46958774 184 | 0.22012381 0.72550945 0.46622638 185 | 0.2263969 0.72888753 0.46278934 186 | 0.23281498 0.73224735 0.45927675 187 | 0.2393739 0.73558828 0.45568838 188 | 0.24606968 0.73890972 0.45202405 189 | 0.25289851 0.74221104 0.44828355 190 | 0.25985676 0.74549162 0.44446673 191 | 0.26694127 0.74875084 0.44057284 192 | 0.27414922 0.75198807 0.4366009 193 | 0.28147681 0.75520266 0.43255207 194 | 0.28892102 0.75839399 0.42842626 195 | 0.29647899 0.76156142 0.42422341 196 | 0.30414796 0.76470433 0.41994346 197 | 0.31192534 0.76782207 0.41558638 198 | 0.3198086 0.77091403 0.41115215 199 | 0.3277958 0.77397953 0.40664011 200 | 0.33588539 0.7770179 0.40204917 201 | 0.34407411 0.78002855 0.39738103 202 | 0.35235985 0.78301086 0.39263579 203 | 0.36074053 0.78596419 0.38781353 204 | 0.3692142 0.78888793 0.38291438 205 | 0.37777892 0.79178146 0.3779385 206 | 0.38643282 0.79464415 0.37288606 207 | 0.39517408 0.79747541 0.36775726 208 | 0.40400101 0.80027461 0.36255223 209 | 0.4129135 0.80304099 0.35726893 210 | 0.42190813 0.80577412 0.35191009 211 | 0.43098317 0.80847343 0.34647607 212 | 0.44013691 0.81113836 0.3409673 213 | 0.44936763 0.81376835 0.33538426 214 | 0.45867362 0.81636288 0.32972749 215 | 0.46805314 0.81892143 0.32399761 216 | 0.47750446 0.82144351 0.31819529 217 | 0.4870258 0.82392862 0.31232133 218 | 0.49661536 0.82637633 0.30637661 219 | 0.5062713 0.82878621 0.30036211 220 | 0.51599182 0.83115784 0.29427888 221 | 0.52577622 0.83349064 0.2881265 222 | 0.5356211 0.83578452 0.28190832 223 | 0.5455244 0.83803918 0.27562602 224 | 0.55548397 0.84025437 0.26928147 225 | 0.5654976 0.8424299 0.26287683 226 | 0.57556297 0.84456561 0.25641457 227 | 0.58567772 0.84666139 0.24989748 228 | 0.59583934 0.84871722 0.24332878 229 | 0.60604528 0.8507331 0.23671214 230 | 0.61629283 0.85270912 0.23005179 231 | 0.62657923 0.85464543 0.22335258 232 | 0.63690157 0.85654226 0.21662012 233 | 0.64725685 0.85839991 0.20986086 234 | 0.65764197 0.86021878 0.20308229 235 | 0.66805369 0.86199932 0.19629307 236 | 0.67848868 0.86374211 0.18950326 237 | 0.68894351 0.86544779 0.18272455 238 | 0.69941463 0.86711711 0.17597055 239 | 0.70989842 0.86875092 0.16925712 240 | 0.72039115 0.87035015 0.16260273 241 | 0.73088902 0.87191584 0.15602894 242 | 0.74138803 0.87344918 0.14956101 243 | 0.75188414 0.87495143 0.14322828 244 | 0.76237342 0.87642392 0.13706449 245 | 0.77285183 0.87786808 0.13110864 246 | 0.78331535 0.87928545 0.12540538 247 | 0.79375994 0.88067763 0.12000532 248 | 0.80418159 0.88204632 0.11496505 249 | 0.81457634 0.88339329 0.11034678 250 | 0.82494028 0.88472036 0.10621724 251 | 0.83526959 0.88602943 0.1026459 252 | 0.84556056 0.88732243 0.09970219 253 | 0.8558096 0.88860134 0.09745186 254 | 0.86601325 0.88986815 0.09595277 255 | 0.87616824 0.89112487 0.09525046 256 | 0.88627146 0.89237353 0.09537439 257 | 0.89632002 0.89361614 0.09633538 258 | 0.90631121 0.89485467 0.09812496 259 | 0.91624212 0.89609127 0.1007168 260 | 0.92610579 0.89732977 0.10407067 261 | 0.93590444 0.8985704 0.10813094 262 | 0.94563626 0.899815 0.11283773 263 | 0.95529972 0.90106534 0.11812832 264 | 0.96489353 0.90232311 0.12394051 265 | 0.97441665 0.90358991 0.13021494 266 | 0.98386829 0.90486726 0.13689671 267 | 0.99324789 0.90615657 0.1439362]; 268 | 269 | P = size(viridi,1); 270 | 271 | if nargin < 1 272 | N = P; 273 | end 274 | 275 | N = min(N,P); 276 | C = interp1(1:P, viridi, linspace(1,P,N), 'linear'); 277 | -------------------------------------------------------------------------------- /ama_toolbox/explore_stfft_ama_gui.m: -------------------------------------------------------------------------------- 1 | function explore_stfft_ama_gui(X, fs, Names, c_map) 2 | % Analysis of a Signal in Frequency-Frequency Domain 3 | % Time -> Time-Frequency transformation performed with STFFT 4 | % 5 | % INPUTS: 6 | % X Real-valued column-vector signal or set of signals [n_samples, n_channels] 7 | % fs Sampling frequency (Hz) 8 | % Optional: 9 | % Names (Optional) Name of the signal(s), String or Cell Array (Strings) 10 | % c_map (Optional) Colormap, Default 'viridis' 11 | % 12 | 13 | clc; 14 | 15 | % Global variable with the value of the Key Pressed 16 | global key_pressed 17 | key_pressed = ''; 18 | 19 | %% verify input arguments 20 | if ~exist('X','var') 21 | error('Variable X is requiered'); 22 | end 23 | 24 | if ~exist('fs','var') 25 | error('Variable fs is requiered'); 26 | end 27 | 28 | % number of channels 29 | n_channels = size(X, 2); 30 | 31 | % Validate 'c_map' argumet 32 | if ~exist('c_map','var') || isempty(c_map) 33 | c_map = 'viridis'; 34 | end 35 | 36 | % verify channel names 37 | if exist('Names','var') 38 | if ischar(Names) && n_channels == 1 39 | Names = {Names}; 40 | end 41 | end 42 | if ~exist('Names','var') || numel(Names) ~= n_channels 43 | Names = cell(1, n_channels); 44 | for ix = 1 : n_channels 45 | Names{ix} = sprintf('Signal-%02d', ix); 46 | end 47 | end 48 | 49 | % % verify channel names 50 | % if exist('Name','var') && iscell(Name) && numel(Name) == n_channels 51 | % Names = Name; 52 | % end 53 | % 54 | % % verify channel names 55 | % if exist('Name','var') && ischar(Name) && n_channels == 1 56 | % Names = {Name}; 57 | % end 58 | % 59 | % % generate channel names if needed 60 | % if ~exist('Name','var') || isempty(Name) || numel(Name) ~= n_channels 61 | % Names = cell(1, n_channels); 62 | % for ix = 1 : n_channels 63 | % Names{ix} = sprintf('Signal-%02d', ix); 64 | % end 65 | % end 66 | 67 | % check if 'am_functions' folder exist, if does, add it to the MATLAB path 68 | if exist('./am_functions', 'dir') 69 | addpath('./am_functions'); 70 | end 71 | 72 | % verify dependencies 73 | if ~exist('strfft_modulation_spectrogram.m','file'); 74 | error('Dependencies for explore_stfft_am_gui were not satisfied'); 75 | end 76 | 77 | %% Amplitude Modulation Analysis 78 | % default Modulation Analysis parameters 79 | win_size_sec = 0.5; % window length for the STFFT 80 | win_shft_sec = 0.02; % shift between consecutive windows (seconds) 81 | seg_size_sec = 8; % segment of signal to compute the Modulation Spectrogram (seconds) 82 | seg_shft_sec = 8; % shift between consecutive segments (seconds) 83 | freq_range = []; % limits [min, max] for the conventional frequency axis (Hz) 84 | mfreq_range = []; % limits [min, max] for the modulation frequency axis (Hz) 85 | freq_color = []; % limits [min, max] for the power in Spectrogram (dB) 86 | mfreq_color = []; % limits [min, max] for the power in Modulation Spectrogram (dB) 87 | 88 | % initial channel and segment 89 | ix_channel = 1; 90 | ix_segment = 1; 91 | 92 | % other variables 93 | n_segments = []; 94 | h_ts = []; 95 | h_tf = []; 96 | h_area1 = []; 97 | h_area2 = []; 98 | x_segments = []; 99 | win_size_smp = []; 100 | win_shft_smp = []; 101 | 102 | %% Live GUI 103 | h_fig = figure('Name', 'Explore STFFT Amplitude Modulation', 'KeyPressFcn', {@key_pressed_fcn}); 104 | first_run(); 105 | 106 | while true 107 | pause(0.01); 108 | % handles user request in GUI 109 | switch key_pressed 110 | case 'escape' %ESC: Exit 111 | break 112 | case {'leftarrow', 'left'} %Left arrow: Previous Segment 113 | ix_segment = ix_segment - 1; 114 | key_pressed = ''; 115 | update_plots(); 116 | case {'rightarrow', 'right'} %Right arrow: Next Segment 117 | ix_segment = ix_segment + 1; 118 | key_pressed = ''; 119 | update_plots(); 120 | case {'uparrow', 'up'} %Up arrow: Previous 121 | ix_channel = ix_channel - 1; 122 | key_pressed = ''; 123 | first_run(); 124 | case {'downarrow', 'down'} %Down arrow: Next Channel 125 | ix_channel = ix_channel + 1; 126 | key_pressed = ''; 127 | first_run(); 128 | case {'a', 'A'} %A: Back 5 Segments 129 | ix_segment = ix_segment - 5; 130 | key_pressed = ''; 131 | update_plots(); 132 | case {'d', 'D'} %D: Advance 5 Segment 133 | ix_segment = ix_segment + 5; 134 | key_pressed = ''; 135 | update_plots(); 136 | case {'w', 'W'} %W Previous 5 channels 137 | ix_channel = ix_channel - 5; 138 | key_pressed = ''; 139 | first_run(); 140 | case {'s', 'S'} %S Next 5 Channels 141 | ix_channel = ix_channel + 5; 142 | key_pressed = ''; 143 | first_run(); 144 | case {'u', 'U'} 145 | key_pressed = ''; 146 | update_parameters(); 147 | end 148 | end 149 | 150 | % executed at first time running or when an update in parameters is performed 151 | function first_run() 152 | ix_segment = 1; 153 | clc; 154 | disp('computing full-signal spectrogram...'); 155 | 156 | % constrain ix_channel to [1 : n_channels] 157 | ix_channel = max([1, ix_channel]); 158 | ix_channel = min([n_channels, ix_channel]); 159 | 160 | % STFFT modulation spectrogram parameters in samples 161 | win_size_smp = round(win_size_sec * fs); % (samples) 162 | win_shft_smp = round(win_shft_sec * fs); % (samples) 163 | seg_size_smp = round(seg_size_sec * fs); % (samples) 164 | seg_shft_smp = round(seg_shft_sec * fs); % (samples) 165 | 166 | % signal for analysis 167 | x_probe = X(:, ix_channel); 168 | 169 | % segment of signal under analysis 170 | x_segments = epoching(x_probe, seg_size_smp, seg_size_smp - seg_shft_smp); 171 | n_segments = size(x_segments,3); 172 | 173 | % plot complete time series 174 | h_ts = subplot(4,2,[1,2]); 175 | plot_signal(x_probe(:), fs, Names{ix_channel}); 176 | colorbar(); 177 | time_lim = get(gca, 'XLim' ); 178 | h_area1 = []; 179 | 180 | % compute and plot complete spectrogram 181 | x_spectrogram = strfft_spectrogram(x_probe, fs, win_size_smp, win_shft_smp, [], [], Names(ix_channel)); 182 | h_tf = subplot(4,2,[3,4]); 183 | plot_spectrogram_data(x_spectrogram, [], [], freq_range, freq_color, c_map) 184 | set(gca, 'XLim', time_lim); 185 | h_area2 = []; 186 | 187 | % update plots 188 | update_plots(); 189 | end 190 | 191 | % update plots when a new segment is selected 192 | function update_plots() 193 | % constrain ix_segment to [1 : n_segments] 194 | ix_segment = max([1, ix_segment]); 195 | ix_segment = min([n_segments, ix_segment]); 196 | 197 | % delete old areas (if existent) 198 | if ~isempty(h_area1) 199 | delete(h_area1); 200 | delete(h_area2); 201 | end 202 | 203 | % select segment 204 | x = x_segments(:, :, ix_segment); 205 | 206 | % compute and plot Modulation Spectrogram 207 | clc; 208 | disp('computing modulation spectrogram...'); 209 | x_stft_modspec = strfft_modulation_spectrogram(x, fs, win_size_smp, win_shft_smp, 2, [], 2, [], Names(ix_channel)); 210 | subplot(4,2,[6,8]) 211 | plot_modulation_spectrogram_data(x_stft_modspec, [], freq_range, mfreq_range, mfreq_color, c_map) 212 | % Uncomment for log axes 213 | %set(gca,'XScale','log'); 214 | %set(gca,'YScale','log'); 215 | 216 | % plot time series for segment 217 | subplot(4,2,5) 218 | plot_signal(x, fs, Names{ix_channel}); 219 | colorbar(); 220 | time_lim = get(gca, 'XLim' ); 221 | 222 | % plot spectrogram for segment 223 | subplot(4,2,7) 224 | plot_spectrogram_data(x_stft_modspec.spectrogram_data, [], [], freq_range, freq_color, c_map) 225 | set(gca, 'XLim', time_lim); 226 | 227 | % highlight area under analysis in time series 228 | seg_ini_sec = (ix_segment - 1) * seg_shft_sec; 229 | seg_end_sec = seg_ini_sec + seg_size_sec; 230 | subplot(h_ts) 231 | h_area1 = varea([seg_ini_sec, seg_end_sec ],{'g'}); 232 | % highlight area under analysis in Spectrogram 233 | subplot(h_tf) 234 | h_area2 = varea([seg_ini_sec, seg_end_sec ],{'g'}); 235 | disp('done!'); 236 | 237 | % display information about analysis 238 | clc; 239 | fprintf('signal name : %s\n', Names{ix_channel} ); 240 | fprintf('segment size (seconds): %0.3f\n', seg_size_sec); 241 | fprintf('segment shift (seconds): %0.3f\n', seg_shft_sec); 242 | fprintf('segment position (sec): %0.3f\n', seg_ini_sec) 243 | fprintf('window size (seconds): %0.3f\n', win_size_sec); 244 | fprintf('window shift (seconds): %0.3f\n', win_shft_sec); 245 | fprintf('windows per segment : %d\n', x_stft_modspec.n_windows); 246 | 247 | drawnow(); 248 | end 249 | 250 | % update parameters for AM analysis 251 | function update_parameters() 252 | % GUI to get new parameters 253 | prompt = {'Segment (seconds): ', ... 254 | 'Segment shift (seconds): ', ... 255 | 'Window size (seconds): ', ... 256 | 'Window shift (seconds): ', ... 257 | 'Freq Conv. min,Max (Hz): ', ... 258 | 'Spectr Pwr min,Max (dB): ', ... 259 | 'Freq Mod. min,Max (Hz): ', ... 260 | 'ModSpec Pwr min,Max(dB): ', ... 261 | }; 262 | 263 | win_name = 'Modulation Analysis Parameters'; numlines = 1; 264 | 265 | defaultanswer = {num2str(seg_size_sec), num2str(seg_shft_sec), ... 266 | num2str(win_size_sec), num2str(win_shft_sec), ... 267 | num2str(freq_range), num2str(freq_color), ... 268 | num2str(mfreq_range), num2str(mfreq_color),... 269 | }; 270 | 271 | options.Resize = 'off'; 272 | 273 | answer = inputdlg(prompt,win_name,numlines,defaultanswer,options); 274 | 275 | % if the user clicks the 'Cancel' button, 'answer' is empty 276 | if ~isempty(answer) 277 | seg_size_sec = str2double(answer{1}); % (seconds) 278 | seg_shft_sec = str2double(answer{2}); % (seconds) 279 | win_size_sec = str2double(answer{3}); % (seconds) 280 | win_shft_sec = str2double(answer{4}); % (seconds) 281 | freq_range = validate_range(answer{5}); % (Hz) 282 | freq_color = validate_range(answer{6}); % (dB) 283 | mfreq_range = validate_range(answer{7}); % (Hz) 284 | mfreq_color = validate_range(answer{8}); % (dB) 285 | 286 | % call first_run() 287 | first_run(); 288 | end 289 | end 290 | 291 | function out = validate_range(inStr) 292 | out = []; 293 | strParts = strsplit(inStr); 294 | if length(strParts) ~= 2 295 | return 296 | end 297 | numParts = str2double(strParts); 298 | if any(isnan(numParts)) 299 | return 300 | end 301 | out = numParts; 302 | end 303 | 304 | % if the user press ESC, close the GUI and clean console 305 | close(h_fig); 306 | clc; 307 | 308 | end 309 | --------------------------------------------------------------------------------