├── kaiserbeta.m ├── invkaiserbeta.m ├── private └── firgauss.m ├── README.md ├── findboundaries.m ├── pop_kaiserbeta.m ├── minphaserceps.m ├── eegplugin_firfilt.m ├── invfirwsord.m ├── firwsord.m ├── firfiltsplit.m ├── changelog.txt ├── fir_filterdcpadded.m ├── pop_firpmord.m ├── windows.m ├── firws.m ├── pop_firma.m ├── firfiltreport.m ├── firfilt.m ├── pop_firwsord.m ├── plotfresp.m ├── pop_firpm.m ├── pop_xfirws.m ├── pop_firws.m └── pop_eegfiltnew.m /kaiserbeta.m: -------------------------------------------------------------------------------- 1 | % kaiserbeta() - Estimate Kaiser window beta 2 | % 3 | % Usage: 4 | % >> beta = pop_kaiserbeta(dev); 5 | % 6 | % Inputs: 7 | % dev - scalar maximum passband deviation/ripple 8 | % 9 | % Output: 10 | % beta - scalar Kaiser window beta 11 | % 12 | % References: 13 | % [1] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 14 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 15 | % Englewood Cliffs, NJ: Prentice-Hall 16 | % 17 | % Author: Andreas Widmann, University of Leipzig, 2005 18 | 19 | %123456789012345678901234567890123456789012345678901234567890123456789012 20 | 21 | % Copyright (C) 2005-2014 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 22 | % 23 | % This program is free software; you can redistribute it and/or modify 24 | % it under the terms of the GNU General Public License as published by 25 | % the Free Software Foundation; either version 2 of the License, or 26 | % (at your option) any later version. 27 | % 28 | % This program is distributed in the hope that it will be useful, 29 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | % GNU General Public License for more details. 32 | % 33 | % You should have received a copy of the GNU General Public License 34 | % along with this program; if not, write to the Free Software 35 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 36 | % 37 | % $Id$ 38 | 39 | function [ beta ] = kaiserbeta(dev) 40 | 41 | devdb = -20 * log10(dev); 42 | 43 | if devdb > 50 44 | beta = 0.1102 * (devdb - 8.7); 45 | elseif devdb >= 21 46 | beta = 0.5842 * (devdb - 21)^0.4 + 0.07886 * (devdb - 21); 47 | else 48 | beta = 0; 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /invkaiserbeta.m: -------------------------------------------------------------------------------- 1 | % kaiserbeta() - Estimate maximum passband deviation/ripple for Kaiser beta 2 | % 3 | % Usage: 4 | % >> dev = invkaiserbeta( beta ); 5 | % 6 | % Input: 7 | % beta - scalar Kaiser window beta 8 | % 9 | % Output: 10 | % dev - scalar maximum passband deviation/ripple 11 | % 12 | % References: 13 | % [1] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 14 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 15 | % Englewood Cliffs, NJ: Prentice-Hall 16 | % 17 | % Author: Andreas Widmann, University of Leipzig, 2015 18 | 19 | %123456789012345678901234567890123456789012345678901234567890123456789012 20 | 21 | % Copyright (C) 2005-2015 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 22 | % 23 | % This program is free software; you can redistribute it and/or modify 24 | % it under the terms of the GNU General Public License as published by 25 | % the Free Software Foundation; either version 2 of the License, or 26 | % (at your option) any later version. 27 | % 28 | % This program is distributed in the hope that it will be useful, 29 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | % GNU General Public License for more details. 32 | % 33 | % You should have received a copy of the GNU General Public License 34 | % along with this program; if not, write to the Free Software 35 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 36 | % 37 | % $Id$ 38 | 39 | function [ dev ] = invkaiserbeta( beta ) 40 | 41 | if beta > 0.1102 * (50 - 8.7) 42 | devdb = beta / 0.1102 + 8.7; 43 | else 44 | devdb = fzero( @( x ) 0.5842 * ( x - 21 )^0.4 + 0.07886 * ( x - 21 ) - beta, [ 21 51 ] ); 45 | end 46 | 47 | dev = 10 ^ ( -devdb / 20 ); 48 | 49 | end 50 | -------------------------------------------------------------------------------- /private/firgauss.m: -------------------------------------------------------------------------------- 1 | %firgauss() - Gaussian low-pass FIR filter 2 | % 3 | % Usage: 4 | % >> b = firgauss( fc, fs ); 5 | % 6 | % Inputs: 7 | % fc - scalar low-pass cutoff frequency (-6 dB) 8 | % fs - scalar sampling frequency 9 | % 10 | % Output: 11 | % b - filter coefficients 12 | % 13 | % Example: 14 | % fs = 500; fc = 25 15 | % b = firgauss( fc, fs ); 16 | % 17 | % References: 18 | % http://en.wikipedia.org/wiki/Gaussian_filter 19 | % 20 | % Author: Andreas Widmann, University of Leipzig, 2015 21 | % 22 | % See also: 23 | % firws 24 | 25 | %123456789012345678901234567890123456789012345678901234567890123456789012 26 | 27 | % Copyright (C) 2015 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 28 | % 29 | % This program is free software; you can redistribute it and/or modify 30 | % it under the terms of the GNU General Public License as published by 31 | % the Free Software Foundation; either version 2 of the License, or 32 | % (at your option) any later version. 33 | % 34 | % This program is distributed in the hope that it will be useful, 35 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 36 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 37 | % GNU General Public License for more details. 38 | % 39 | % You should have received a copy of the GNU General Public License 40 | % along with this program; if not, write to the Free Software 41 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 42 | % 43 | % $Id$ 44 | 45 | function [ b ] = firgauss( fc, fs ) 46 | 47 | if nargin < 2 48 | fs = 2; 49 | end 50 | 51 | sigf = fc / sqrt( 2 * log( 2 ) ); 52 | sigt = fs / ( 2 * pi * sigf ); 53 | 54 | order = ceil( 3 * sigt ) * 2; % Even for type 1 FIR; 55 | 56 | x = -order / 2:order / 2; 57 | b = ( 1 / ( sqrt( 2 * pi ) * sigt ) ) * exp( -x .^ 2 / ( 2 * sigt ^ 2 ) ); 58 | 59 | end 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FIRFILT EEGLAB plugin 2 | ------------- 3 | The FIRfilt EEGLAB plugin is a tool used within the EEGLAB environment. FIRfilt is specifically designed for filtering EEG data using Finite Impulse Response (FIR) filters. Here are the key features and functionalities of the FIRfilt plugin: 4 | 5 | * FIR filtering: FIRfilt provides a straightforward interface for applying FIR filters to EEG data. FIR filters are commonly used due to their stability and linear phase properties. 6 | * Filter types: Users can create various types of FIR filters, including low-pass, high-pass, band-pass, and band-stop filters. This flexibility allows users to isolate specific frequency bands of interest. 7 | * Design methods: FIRfilt offers several methods for designing FIR filters, such as the windowed sinc, equiripple, or moving average. Each method has its own advantages depending on the specific filtering requirements. 8 | * Graphical interface: The plugin integrates with the EEGLAB GUI, making it accessible for users who prefer graphical user interfaces for their data processing tasks. 9 | * Command line support: For more advanced users, FIRfilt also supports command-line operations, allowing for script-based automation and integration into larger data processing pipelines. 10 | 11 | See [this page](https://eeglab.org/others/Firfilt_FAQ.html) or the [paper](https://home.uni-leipzig.de/biocog/eprints/widmann_a2015jneuroscimeth250_34.pdf) for additional documentation. 12 | 13 | Reference 14 | ------------- 15 | Please cite 16 | 17 | > Widmann, A., Schröger, E., & Maess, B. (2015). Digital filter design for electrophysiological data - a practical approach. Journal of Neuroscience Methods, 250, 34-46. 18 | 19 | if you have used functions from the EEGLAB firfilt plugin in your manuscript. 20 | 21 | Version History 22 | --------------- 23 | v2.8 - Added usefftfilt option to pop_eegfiltnew() 24 | 25 | v2.7 - handle better boundary events 26 | 27 | v2.7.1 - better handling of boundary events 28 | -------------------------------------------------------------------------------- /findboundaries.m: -------------------------------------------------------------------------------- 1 | % findboundaries() - Find boundaries (data discontinuities) in event 2 | % structure of continuous EEG dataset 3 | % 4 | % Usage: 5 | % >> boundaries = findboundaries(EEG.event); 6 | % 7 | % Inputs: 8 | % EEG.event - EEGLAB EEG event structure 9 | % 10 | % Outputs: 11 | % boundaries - scalar or vector of boundary event latencies 12 | % 13 | % Author: Andreas Widmann, University of Leipzig, 2005 14 | 15 | %123456789012345678901234567890123456789012345678901234567890123456789012 16 | 17 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 18 | % 19 | % This program is free software; you can redistribute it and/or modify 20 | % it under the terms of the GNU General Public License as published by 21 | % the Free Software Foundation; either version 2 of the License, or 22 | % (at your option) any later version. 23 | % 24 | % This program is distributed in the hope that it will be useful, 25 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | % GNU General Public License for more details. 28 | % 29 | % You should have received a copy of the GNU General Public License 30 | % along with this program; if not, write to the Free Software 31 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 32 | 33 | function boundaries = findboundaries(event) 34 | 35 | if isfield(event, 'type') && isfield(event, 'latency') 36 | 37 | % Boundary event indices 38 | if all(cellfun('isclass', {event.type}, 'char')) 39 | boundaries = strmatch('boundary', {event.type}); 40 | else 41 | boundaries = find([ event.type ] == -99); 42 | end 43 | 44 | % Boundary event latencies 45 | boundaries = [event(boundaries).latency]; 46 | 47 | % Shift boundary events to epoch onset 48 | boundaries = fix(boundaries + 0.5); 49 | 50 | % Remove duplicate boundary events 51 | boundaries = unique(boundaries); 52 | 53 | % Epoch onset at first sample? 54 | if isempty(boundaries) || boundaries(1) ~= 1 55 | boundaries = [1 boundaries]; 56 | end 57 | 58 | else 59 | 60 | boundaries = 1; 61 | 62 | end 63 | -------------------------------------------------------------------------------- /pop_kaiserbeta.m: -------------------------------------------------------------------------------- 1 | % pop_kaiserbeta() - Estimate Kaiser window beta 2 | % 3 | % Usage: 4 | % >> [beta, dev] = pop_kaiserbeta; % pop-up window mode 5 | % >> beta = pop_kaiserbeta(dev); 6 | % 7 | % Inputs: 8 | % dev - scalar maximum passband deviation/ripple 9 | % 10 | % Output: 11 | % beta - scalar Kaiser window beta 12 | % dev - scalar maximum passband deviation/ripple 13 | % 14 | % References: 15 | % [1] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 16 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 17 | % Englewood Cliffs, NJ: Prentice-Hall 18 | % 19 | % Author: Andreas Widmann, University of Leipzig, 2005 20 | % 21 | % See also: 22 | % kaiserbeta, pop_firws, firws, pop_firwsord, windows 23 | 24 | %123456789012345678901234567890123456789012345678901234567890123456789012 25 | 26 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 27 | % 28 | % This program is free software; you can redistribute it and/or modify 29 | % it under the terms of the GNU General Public License as published by 30 | % the Free Software Foundation; either version 2 of the License, or 31 | % (at your option) any later version. 32 | % 33 | % This program is distributed in the hope that it will be useful, 34 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 35 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 36 | % GNU General Public License for more details. 37 | % 38 | % You should have received a copy of the GNU General Public License 39 | % along with this program; if not, write to the Free Software 40 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 41 | 42 | function [beta, dev] = pop_kaiserbeta(dev) 43 | 44 | beta = []; 45 | 46 | if nargin < 1 || isempty(dev) 47 | drawnow; 48 | uigeom = {[1 1]}; 49 | uilist = {{'style' 'text' 'string' 'Max passband deviation/ripple:'} ... 50 | {'style' 'edit' 'string' ''}}; 51 | result = inputgui(uigeom, uilist, 'pophelp(''pop_kaiserbeta'')', 'Estimate Kaiser window beta -- pop_kaiserbeta()'); 52 | if length(result) == 0, return, end 53 | if ~isempty(result{1}) 54 | dev = str2num(result{1}); 55 | else 56 | error('Not enough input arguments.'); 57 | end 58 | end 59 | 60 | [ beta ] = kaiserbeta( dev ); 61 | 62 | end 63 | -------------------------------------------------------------------------------- /minphaserceps.m: -------------------------------------------------------------------------------- 1 | % rcepsminphase() - Convert FIR filter coefficient to minimum phase 2 | % 3 | % Usage: 4 | % >> b = minphaserceps(b); 5 | % 6 | % Inputs: 7 | % b - FIR filter coefficients 8 | % 9 | % Outputs: 10 | % bMinPhase - minimum phase FIR filter coefficients 11 | % 12 | % Author: Andreas Widmann, University of Leipzig, 2013 13 | % 14 | % References: 15 | % [1] Smith III, O. J. (2007). Introduction to Digital Filters with Audio 16 | % Applications. W3K Publishing. Retrieved Nov 11 2013, from 17 | % https://ccrma.stanford.edu/~jos/fp/Matlab_listing_mps_m.html 18 | % [2] Vetter, K. (2013, Nov 11). Long FIR filters with low latency. 19 | % Retrieved Nov 11 2013, from 20 | % http://www.katjaas.nl/minimumphase/minimumphase.html 21 | 22 | %123456789012345678901234567890123456789012345678901234567890123456789012 23 | 24 | % Copyright (C) 2013 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 25 | % 26 | % This program is free software; you can redistribute it and/or modify 27 | % it under the terms of the GNU General Public License as published by 28 | % the Free Software Foundation; either version 2 of the License, or 29 | % (at your option) any later version. 30 | % 31 | % This program is distributed in the hope that it will be useful, 32 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | % GNU General Public License for more details. 35 | % 36 | % You should have received a copy of the GNU General Public License 37 | % along with this program; if not, write to the Free Software 38 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 39 | % 40 | % $Id$ 41 | 42 | function [bMinPhase] = minphaserceps(b) 43 | 44 | % Line vector 45 | b = b(:)'; 46 | 47 | n = length(b); 48 | upsamplingFactor = 1e3; % Impulse response upsampling/zero padding to reduce time-aliasing 49 | nFFT = 2^ceil(log2(n * upsamplingFactor)); % Power of 2 50 | clipThresh = 1e-8; % -160 dB 51 | 52 | % Spectrum 53 | s = abs(fft(b, nFFT)); 54 | s(s < clipThresh) = clipThresh; % Clip spectrum to reduce time-aliasing 55 | 56 | % Real cepstrum 57 | c = real(ifft(log(s))); 58 | 59 | % Fold 60 | c = [c(1) [c(2:nFFT / 2) 0] + conj(c(nFFT:-1:nFFT / 2 + 1)) zeros(1, nFFT / 2 - 1)]; 61 | 62 | % Minimum phase 63 | bMinPhase = real(ifft(exp(fft(c)))); 64 | 65 | % Remove zero-padding 66 | bMinPhase = bMinPhase(1:n); 67 | 68 | end 69 | -------------------------------------------------------------------------------- /eegplugin_firfilt.m: -------------------------------------------------------------------------------- 1 | % eegplugin_firfilt() - EEGLAB plugin for filtering data using linear- 2 | % phase FIR filters 3 | % 4 | % Usage: 5 | % >> eegplugin_firfilt(fig, trystrs, catchstrs); 6 | % 7 | % Inputs: 8 | % fig - [integer] EEGLAB figure 9 | % trystrs - [struct] "try" strings for menu callbacks. 10 | % catchstrs - [struct] "catch" strings for menu callbacks. 11 | % 12 | % Author: Andreas Widmann, University of Leipzig, Germany, 2005 13 | 14 | %123456789012345678901234567890123456789012345678901234567890123456789012 15 | 16 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 17 | % 18 | % This program is free software; you can redistribute it and/or modify 19 | % it under the terms of the GNU General Public License as published by 20 | % the Free Software Foundation; either version 2 of the License, or 21 | % (at your option) any later version. 22 | % 23 | % This program is distributed in the hope that it will be useful, 24 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | % GNU General Public License for more details. 27 | % 28 | % You should have received a copy of the GNU General Public License 29 | % along with this program; if not, write to the Free Software 30 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 31 | 32 | function vers = eegplugin_firfilt(fig, trystrs, catchstrs) 33 | 34 | vers = 'firfilt2.8'; 35 | if nargin < 3 36 | error('eegplugin_firfilt requires 3 arguments'); 37 | end 38 | 39 | % add folder to path 40 | % ----------------------- 41 | if ~exist('pop_firws') 42 | p = which('eegplugin_firfilt'); 43 | p = p(1:findstr(p,'eegplugin_firfilt.m')-1); 44 | addpath([p vers]); 45 | end 46 | 47 | % find import data menu 48 | % --------------------- 49 | menu = findobj(fig, 'tag', 'filter'); 50 | 51 | % menu callbacks 52 | % -------------- 53 | comfirfiltnew = [trystrs.no_check '[EEG LASTCOM] = pop_eegfiltnew(EEG);' catchstrs.new_and_hist]; 54 | comfirws = [trystrs.no_check '[EEG LASTCOM] = pop_firws(EEG);' catchstrs.new_and_hist]; 55 | comfirpm = [trystrs.no_check '[EEG LASTCOM] = pop_firpm(EEG);' catchstrs.new_and_hist]; 56 | comfirma = [trystrs.no_check '[EEG LASTCOM] = pop_firma(EEG);' catchstrs.new_and_hist]; 57 | 58 | % create menus if necessary 59 | % ------------------------- 60 | uimenu( menu, 'Label', 'Basic FIR filter (new, default)', 'CallBack', comfirfiltnew, 'position', 1, 'userdata', 'study:on'); 61 | uimenu( menu, 'Label', 'Windowed sinc FIR filter', 'CallBack', comfirws, 'position', 2); 62 | uimenu( menu, 'Label', 'Parks-McClellan (equiripple) FIR filter', 'CallBack', comfirpm, 'position', 3); 63 | uimenu( menu, 'Label', 'Moving average FIR filter', 'CallBack', comfirma, 'position', 4); 64 | 65 | -------------------------------------------------------------------------------- /invfirwsord.m: -------------------------------------------------------------------------------- 1 | % invfirwsord() - Estimate windowed sinc FIR filter transition band width 2 | % depending on filter order and window type 3 | % 4 | % Usage: 5 | % >> [df, dev] = invfirwsord(wtype, fs, m); 6 | % >> df = invfirwsord('kaiser', fs, m, dev); 7 | % 8 | % Inputs: 9 | % wtype - char array window type. 'rectangular', 'hann', 'hamming', 10 | % 'blackman', or 'kaiser' 11 | % fs - scalar sampling frequency} 12 | % m - scalar filter order 13 | % dev - scalar maximum passband deviation/ripple (Kaiser window 14 | % only) 15 | % 16 | % Output: 17 | % df - scalar estimated transition band width 18 | % dev - scalar maximum passband deviation/ripple 19 | % 20 | % References: 21 | % [1] Smith, S. W. (1999). The scientist and engineer's guide to 22 | % digital signal processing (2nd ed.). San Diego, CA: California 23 | % Technical Publishing. 24 | % [2] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 25 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 26 | % Englewood Cliffs, NJ: Prentice-Hall 27 | % [3] Ifeachor E. C., & Jervis B. W. (1993). Digital Signal 28 | % Processing: A Practical Approach. Wokingham, UK: Addison-Wesley 29 | % 30 | % Author: Andreas Widmann, University of Leipzig, 2005 31 | % 32 | % See also: 33 | % firws, firwsord 34 | 35 | %123456789012345678901234567890123456789012345678901234567890123456789012 36 | 37 | % Copyright (C) 2005-2014 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 38 | % 39 | % This program is free software; you can redistribute it and/or modify 40 | % it under the terms of the GNU General Public License as published by 41 | % the Free Software Foundation; either version 2 of the License, or 42 | % (at your option) any later version. 43 | % 44 | % This program is distributed in the hope that it will be useful, 45 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | % GNU General Public License for more details. 48 | % 49 | % You should have received a copy of the GNU General Public License 50 | % along with this program; if not, write to the Free Software 51 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 52 | % 53 | % $Id$ 54 | 55 | function [ df, dev ] = invfirwsord(wintype, fs, m, dev) 56 | 57 | winTypeArray = {'rectangular', 'hann', 'hamming', 'blackman', 'kaiser'}; 58 | winDfArray = [0.9 3.1 3.3 5.5]; 59 | winDevArray = [0.089 0.0063 0.0022 0.0002]; 60 | 61 | % Check arguments 62 | if nargin < 3 || isempty(fs) || isempty(m) || isempty(wintype) 63 | error('Not enough input arguments.') 64 | end 65 | 66 | % Window type 67 | wintype = find(strcmp(wintype, winTypeArray)); 68 | if isempty(wintype) 69 | error('Unknown window type.') 70 | end 71 | 72 | if wintype == 5 % Kaiser window 73 | if nargin < 4 || isempty(dev) 74 | error('Not enough input arguments.') 75 | end 76 | devdb = -20 * log10(dev); 77 | df = (devdb - 8) / (2.285 * 2 * pi * (m - 1)); 78 | else 79 | df = winDfArray(wintype) / m; 80 | dev = winDevArray(wintype); 81 | end 82 | 83 | % df is normalized 84 | df = df * fs; 85 | 86 | end 87 | -------------------------------------------------------------------------------- /firwsord.m: -------------------------------------------------------------------------------- 1 | % firwsord() - Estimate windowed sinc FIR filter order depending on 2 | % window type and requested transition band width 3 | % 4 | % Usage: 5 | % >> [m, dev] = firwsord(wtype, fs, df); 6 | % >> m = firwsord('kaiser', fs, df, dev); 7 | % 8 | % Inputs: 9 | % wtype - char array window type. 'rectangular', 'hann', 'hamming', 10 | % 'blackman', or 'kaiser' 11 | % fs - scalar sampling frequency 12 | % df - scalar requested transition band width 13 | % dev - scalar maximum passband deviation/ripple (Kaiser window 14 | % only) 15 | % 16 | % Output: 17 | % m - scalar estimated filter order 18 | % dev - scalar maximum passband deviation/ripple 19 | % 20 | % References: 21 | % [1] Smith, S. W. (1999). The scientist and engineer's guide to 22 | % digital signal processing (2nd ed.). San Diego, CA: California 23 | % Technical Publishing. 24 | % [2] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 25 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 26 | % Englewood Cliffs, NJ: Prentice-Hall 27 | % [3] Ifeachor E. C., & Jervis B. W. (1993). Digital Signal 28 | % Processing: A Practical Approach. Wokingham, UK: Addison-Wesley 29 | % 30 | % Author: Andreas Widmann, University of Leipzig, 2005 31 | % 32 | % See also: 33 | % pop_firwsord, firws, invfirwsord 34 | 35 | %123456789012345678901234567890123456789012345678901234567890123456789012 36 | 37 | % Copyright (C) 2005-2015 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 38 | % 39 | % This program is free software; you can redistribute it and/or modify 40 | % it under the terms of the GNU General Public License as published by 41 | % the Free Software Foundation; either version 2 of the License, or 42 | % (at your option) any later version. 43 | % 44 | % This program is distributed in the hope that it will be useful, 45 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 46 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 47 | % GNU General Public License for more details. 48 | % 49 | % You should have received a copy of the GNU General Public License 50 | % along with this program; if not, write to the Free Software 51 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 52 | % 53 | % $Id$ 54 | 55 | function [ m, dev ] = firwsord(wintype, fs, df, dev) 56 | 57 | winTypeArray = {'rectangular', 'hann', 'hamming', 'blackman', 'kaiser'}; 58 | winDfArray = [0.9 3.1 3.3 5.5]; 59 | winDevArray = [0.089 0.0063 0.0022 0.0002]; 60 | 61 | % Check arguments 62 | if nargin < 3 || isempty(fs) || isempty(df) || isempty(wintype) 63 | error('Not enough input arguments.') 64 | end 65 | 66 | % Window type 67 | wintype = find(strcmp(wintype, winTypeArray)); 68 | if isempty(wintype) 69 | error('Unknown window type.') 70 | end 71 | 72 | df = df / fs; % Normalize transition band width 73 | 74 | if wintype == 5 % Kaiser window 75 | if nargin < 4 || isempty(dev) 76 | error('Not enough input arguments.') 77 | end 78 | devdb = -20 * log10(dev); 79 | m = 1 + (devdb - 8) / (2.285 * 2 * pi * df); 80 | else 81 | m = winDfArray(wintype) / df; 82 | dev = winDevArray(wintype); 83 | end 84 | 85 | m = ceil(m / 2) * 2; % Make filter order even (FIR type I) 86 | 87 | end 88 | -------------------------------------------------------------------------------- /firfiltsplit.m: -------------------------------------------------------------------------------- 1 | % firfiltsplit() - Split data at discontinuities and forward to dc padded 2 | % filter function 3 | % 4 | % Usage: 5 | % >> EEG = firfiltsplit(EEG, b); 6 | % >> EEG = firfiltsplit(EEG, b, causal); 7 | % >> EEG = firfiltsplit(EEG, b, causal, usefftfilt); 8 | % 9 | % Inputs: 10 | % EEG - EEGLAB EEG structure 11 | % b - vector of filter coefficients 12 | % 13 | % Optional inputs: 14 | % causal - scalar boolean perform causal filtering {default false} 15 | % usefftfilt - scalar boolean use fftfilt frequency domain filtering 16 | % {default false} 17 | % chaninds - channel indices {default all} 18 | % 19 | % Outputs: 20 | % EEG - EEGLAB EEG structure 21 | % 22 | % Note: 23 | % This function is (in combination with fir_filterdcpadded) just a 24 | % non-memory optimized version of the firfilt function allowing causal 25 | % filtering. Will possibly replace firfilt in the future. 26 | % 27 | % Author: Andreas Widmann, University of Leipzig, 2013 28 | % 29 | % See also: 30 | % fir_filterdcpadded, findboundaries 31 | 32 | %123456789012345678901234567890123456789012345678901234567890123456789012 33 | 34 | % Copyright (C) 2013 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 35 | % 36 | % This program is free software; you can redistribute it and/or modify 37 | % it under the terms of the GNU General Public License as published by 38 | % the Free Software Foundation; either version 2 of the License, or 39 | % (at your option) any later version. 40 | % 41 | % This program is distributed in the hope that it will be useful, 42 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 43 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 44 | % GNU General Public License for more details. 45 | % 46 | % You should have received a copy of the GNU General Public License 47 | % along with this program; if not, write to the Free Software 48 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 49 | 50 | function EEG = firfiltsplit(EEG, b, causal, usefftfilt, chaninds) 51 | 52 | % Defaults 53 | if nargin < 4 || isempty(usefftfilt) 54 | usefftfilt = 0; 55 | end 56 | if nargin < 3 || isempty(causal) 57 | causal = 0; 58 | end 59 | if nargin < 5 || isempty( chaninds ) 60 | chaninds = 1:size(EEG.data,1); 61 | end 62 | if nargin < 2 63 | error('Not enough input arguments.'); 64 | end 65 | 66 | % Find data discontinuities and reshape epoched data 67 | if EEG.trials > 1 % Epoched data 68 | EEG.data = reshape(EEG.data, [EEG.nbchan EEG.pnts * EEG.trials]); 69 | dcArray = 1 : EEG.pnts : EEG.pnts * (EEG.trials + 1); 70 | else % Continuous data 71 | dcArray = findboundaries(EEG.event); 72 | dcArray = dcArray( dcArray >= 1 & dcArray <= EEG.pnts ); % Assert DC offset events are within data range 73 | dcArray = [ dcArray EEG.pnts + 1 ]; 74 | end 75 | 76 | % Loop over continuous segments 77 | for iDc = 1:(length(dcArray) - 1) 78 | 79 | % Filter segment 80 | EEG.data(chaninds, dcArray(iDc):dcArray(iDc + 1) - 1) = fir_filterdcpadded(b, 1, EEG.data(chaninds, dcArray(iDc):dcArray(iDc + 1) - 1)', causal, usefftfilt)'; 81 | 82 | end 83 | 84 | % Reshape epoched data 85 | if EEG.trials > 1 86 | EEG.data = reshape(EEG.data, [EEG.nbchan EEG.pnts EEG.trials]); 87 | end 88 | 89 | end 90 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Version 2.7 2 | - ENH: added usefftfilt option to pop_eegfiltnew.m 3 | - BUG: recompute transition band width after ceiling filter order to even integer. Fixes #3. 4 | - BUG: Adjust fft length for extreme cutoffs. 5 | 6 | Version 2.6.1 7 | - BUG: fixed handling of boundary events outside data range. 8 | 9 | Version 2.6 10 | - BUG: fixed filter order not converted to numeric by GUI. 11 | 12 | Version 2.5.1 13 | - CHG: Moved firgauss.m to private subdir. Fixes #9. 14 | 15 | Version 2.5 16 | - ENH: handles events of type -99 if EEGLAB option set 17 | - ENH: further checks when processing datasets with boundaries 18 | 19 | Version 2.4 20 | - BUG: new fix command line call for backward compatibility in pop_eegfiltnew.m 21 | 22 | Version 2.3 23 | - BUG: fix command line call for backward compatibility in pop_eegfiltnew.m 24 | 25 | Version 2.2 26 | - ENH: allow processing multiple datasets 27 | 28 | Version 2.1 29 | - ENH: allow channel selection 30 | - ENH: 'key', val parameters for function pop_eegfiltnew.m 31 | - BUG: prevent crash when signal processing toolbox absent 32 | 33 | Version 2.0 34 | - ENH: usefftfilt option for frequency domain filtering (faster for very high filter orders) 35 | - ENH: reporting of filter parameters for pop_firws 36 | - ENH: added command line parameter for plotting of filter responses for pop_firws 37 | - CHG: sync existing low level functions to Fieldtrip (windows, fir_filterdcpadded, firws, plotfresp, minphaserceps) 38 | - CHG: removed Bartlett window 39 | - CHG: default Hamming window (for consistency with fir1 and Fieldtrip) 40 | - BUG: pop_firpm compatibility with R2015a 41 | 42 | Version 1.6.1 43 | - ENH: documentation improvements for pop_firws GUI and pop_firpmord help text 44 | 45 | Version 1.6 46 | - ENH: minimum-phase conversion and causal filtering 47 | 48 | Version 1.5.5 49 | - CHG: menu 50 | 51 | Version 1.5.4 52 | - CHG: menu order 53 | 54 | Version 1.5.3 55 | - CHG: new TBW heuristic for pop_eegfiltnew.m 56 | 57 | Version 1.5.2 58 | - CHG: warning message 59 | 60 | Version 1.5.1 61 | - CHG: removed dependency on EEGLAB fastif function 62 | - CHG: removed pop_xfirws dependency on EEGLAB for command line use 63 | - BUG: plotting of frequency response with empty window parameters 64 | - BUG: error in firws documentation 65 | - ENH: improved pop_xfirws command line usability 66 | 67 | Version 1.5 68 | - BUG: pop_firpm compatibility with R14 and earlier. 69 | - CHG: command line progress indicator. 70 | 71 | Version 1.4 72 | - BUG: duplicate boundary event handling. 73 | - CHG: waitbar replaced by command line progress indicator. 74 | 75 | Version 1.3 76 | - NEW: pop_eegfiltnew. 77 | - BUG: ETA calculation. 78 | 79 | Version 1.2 80 | - CHG: filter with double precision. 81 | 82 | Version 1.1 83 | - NEW: added pop_xfirws to design and export EEProbe compatible filters. 84 | - BUG: compatibility with R14 and earlier. 85 | - NEW: Tukey windows. 86 | 87 | Version 1.0 88 | - CHG: removed misleading default value for filter order. 89 | - CHG: moved to EEGLAB filter menu. 90 | - CHG: code cleaning and formatting. 91 | 92 | Version 0.92 93 | - ENH: optimized filter strategy. 94 | - ENH: waitbar. 95 | - CHG: avoid eval function. 96 | 97 | Version 0.91 98 | - BUG: check for empty boundaries vector. 99 | 100 | Version 0.9 101 | - starting, initial public release. 102 | -------------------------------------------------------------------------------- /fir_filterdcpadded.m: -------------------------------------------------------------------------------- 1 | % fir_filterdcpadded() - Pad data with DC constant and filter 2 | % 3 | % Usage: 4 | % >> data = fir_filterdcpadded(b, a, data, causal, usefftfilt); 5 | % 6 | % Inputs: 7 | % b - vector of filter coefficients 8 | % a - 1 9 | % data - raw data (times x chans) 10 | % causal - boolean perform causal filtering {default 0} 11 | % usefftfilt - boolean use fftfilt instead of filter 12 | % 13 | % Outputs: 14 | % data - smoothed data 15 | % 16 | % Note: 17 | % fir_filterdcpadded always operates (pads, filters) along first dimension. 18 | % Not memory optimized. 19 | % 20 | % Author: Andreas Widmann, University of Leipzig, 2014 21 | 22 | %123456789012345678901234567890123456789012345678901234567890123456789012 23 | 24 | % Copyright (C) 2013 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 25 | % 26 | % This program is free software; you can redistribute it and/or modify 27 | % it under the terms of the GNU General Public License as published by 28 | % the Free Software Foundation; either version 2 of the License, or 29 | % (at your option) any later version. 30 | % 31 | % This program is distributed in the hope that it will be useful, 32 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | % GNU General Public License for more details. 35 | % 36 | % You should have received a copy of the GNU General Public License 37 | % along with this program; if not, write to the Free Software 38 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 39 | % 40 | % $Id$ 41 | 42 | function [ data ] = fir_filterdcpadded(b, a, data, causal, usefftfilt) 43 | 44 | % Defaults 45 | if nargin < 5 || isempty(usefftfilt) 46 | usefftfilt = 0; 47 | end 48 | if nargin < 4 || isempty(causal) 49 | causal = 0; 50 | end 51 | 52 | % Check arguments 53 | if nargin < 3 54 | error('Not enough input arguments.'); 55 | end 56 | 57 | % Is FIR? 58 | if ~isscalar(a) || a ~= 1 59 | error('Not a FIR filter. onepass-zerophase and onepass-minphase filtering is available for FIR filters only.') 60 | end 61 | 62 | % Group delay 63 | if mod(length(b), 2) ~= 1 64 | error('Filter order is not even.'); 65 | end 66 | groupDelay = (length(b) - 1) / 2; 67 | 68 | % Filter symmetry 69 | isSym = all(b(1:groupDelay) == b(end:-1:groupDelay + 2)); 70 | isAntisym = all([b(1:groupDelay) == -b(end:-1:groupDelay + 2) b(groupDelay + 1) == 0]); 71 | if causal == 0 && ~(isSym || isAntisym) 72 | error('Filter is not anti-/symmetric. For onepass-zerophase filtering the filter must be anti-/symmetric.') 73 | end 74 | 75 | % Padding 76 | if causal 77 | startPad = repmat(data(1, :), [2 * groupDelay 1]); 78 | endPad = []; 79 | else 80 | startPad = repmat(data(1, :), [groupDelay 1]); 81 | endPad = repmat(data(end, :), [groupDelay 1]); 82 | end 83 | 84 | % Filter data (with double precision) 85 | isSingle = isa(data, 'single'); 86 | 87 | if usefftfilt 88 | data = fftfilt(double(b), double([startPad; data; endPad])); 89 | else 90 | data = filter(double(b), 1, double([startPad; data; endPad])); % Pad and filter with double precision 91 | end 92 | 93 | % Convert to single 94 | if isSingle 95 | data = single(data); 96 | end 97 | 98 | % Remove padded data 99 | data = data(2 * groupDelay + 1:end, :); 100 | 101 | end 102 | -------------------------------------------------------------------------------- /pop_firpmord.m: -------------------------------------------------------------------------------- 1 | % pop_firpmord() - Estimate Parks-McClellan filter order and weights 2 | % 3 | % Usage: 4 | % >> [m, wtpass, wtstop] = pop_firpmord(f, a); % pop-up window mode 5 | % >> [m, wtpass, wtstop] = pop_firpmord(f, a, dev); 6 | % >> [m, wtpass, wtstop] = pop_firpmord(f, a, dev, fs); 7 | % 8 | % Inputs: 9 | % f - vector frequency band edges 10 | % a - vector desired amplitudes on bands defined by f 11 | % dev - vector allowable deviations on bands defined by f 12 | % 13 | % Optional inputs: 14 | % fs - scalar sampling frequency {default 2} 15 | % 16 | % Output: 17 | % m - scalar estimated filter order 18 | % wtpass - scalar passband weight 19 | % wtstop - scalar stopband weight 20 | % 21 | % Note: 22 | % Requires the signal processing toolbox. Convert passband ripple from 23 | % dev to peak-to-peak dB: rp = 20 * log10((1 + dev) / (1 - dev)). 24 | % Convert stopband attenuation from dev to dB: rs = 20 * log10(dev). 25 | % 26 | % Author: Andreas Widmann, University of Leipzig, 2005 27 | % 28 | % See also: 29 | % pop_firpm, firpm, firpmord 30 | 31 | %123456789012345678901234567890123456789012345678901234567890123456789012 32 | 33 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 34 | % 35 | % This program is free software; you can redistribute it and/or modify 36 | % it under the terms of the GNU General Public License as published by 37 | % the Free Software Foundation; either version 2 of the License, or 38 | % (at your option) any later version. 39 | % 40 | % This program is distributed in the hope that it will be useful, 41 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 43 | % GNU General Public License for more details. 44 | % 45 | % You should have received a copy of the GNU General Public License 46 | % along with this program; if not, write to the Free Software 47 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 48 | 49 | function [m, wtpass, wtstop] = pop_firpmord(f, a, dev, fs) 50 | 51 | m = []; 52 | wtpass = []; 53 | wtstop = []; 54 | 55 | if ~(exist('firpmord', 'file') == 2 || exist('firpmord', 'file') == 6) 56 | error('Requires the signal processing toolbox.'); 57 | end 58 | 59 | if nargin < 2 || isempty(f) || isempty(a) 60 | error('Not enough input arguments'); 61 | end 62 | 63 | % Sampling frequency 64 | if nargin < 4 || isempty(fs) 65 | fs = 2; 66 | end 67 | 68 | % GUI 69 | if nargin < 3 || isempty(dev) 70 | drawnow; 71 | uigeom = {[1 1] [1 1]}; 72 | uilist = {{'style' 'text' 'string' 'Peak-to-peak passband ripple (dB):'} ... 73 | {'style' 'edit'} ... 74 | {'style' 'text' 'string' 'Stopband attenuation (dB):'} ... 75 | {'style' 'edit'}}; 76 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firpmord'')', 'Estimate filter order and weights -- pop_firpmord()'); 77 | if length(result) == 0, return, end 78 | 79 | if ~isempty(result{1}) 80 | rp = str2num(result{1}); 81 | rp = (10^(rp / 20) - 1) / (10^(rp / 20) + 1); 82 | dev(find(a == 1)) = rp; 83 | else 84 | error('Not enough input arguments.'); 85 | end 86 | if ~isempty(result{2}) 87 | rs = str2num(result{2}); 88 | rs = 10^(-abs(rs) / 20); 89 | dev(find(a == 0)) = rs; 90 | else 91 | error('Not enough input arguments.'); 92 | end 93 | end 94 | 95 | [m, fo, ao, w] = firpmord(f, a, dev, fs); 96 | wtpass = w(find(a == 1, 1)); 97 | wtstop = w(find(a == 0, 1)); 98 | -------------------------------------------------------------------------------- /windows.m: -------------------------------------------------------------------------------- 1 | % windows() - Symmetric window functions 2 | % 3 | % Usage: 4 | % >> h = windows(t, m); 5 | % >> h = windows(t, m, a); 6 | % 7 | % Inputs: 8 | % t - char array 'rectangular', 'bartlett', 'hann', 'hamming', 9 | % 'blackman', 'blackmanharris', 'kaiser', or 'tukey' 10 | % m - scalar window length 11 | % 12 | % Optional inputs: 13 | % a - scalar or vector with window parameter(s) 14 | % 15 | % Output: 16 | % w - column vector window 17 | % 18 | % Author: Andreas Widmann, University of Leipzig, 2014 19 | 20 | %123456789012345678901234567890123456789012345678901234567890123456789012 21 | 22 | % Copyright (C) 2014 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 23 | % 24 | % This program is free software; you can redistribute it and/or modify 25 | % it under the terms of the GNU General Public License as published by 26 | % the Free Software Foundation; either version 2 of the License, or 27 | % (at your option) any later version. 28 | % 29 | % This program is distributed in the hope that it will be useful, 30 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | % GNU General Public License for more details. 33 | % 34 | % You should have received a copy of the GNU General Public License 35 | % along with this program; if not, write to the Free Software 36 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 37 | % 38 | % $Id$ 39 | 40 | function w = windows(t, m, a) 41 | 42 | if nargin < 2 || isempty(t) || isempty(m) 43 | error('Not enough input arguments.'); 44 | end 45 | 46 | % Check window length 47 | if m ~= round(m) 48 | m = round(m); 49 | warning('firfilt:nonIntegerWindowLength', 'Non-integer window length. Rounding to integer.') 50 | end 51 | if m < 1 52 | error('Invalid window length.') 53 | end 54 | 55 | % Length 1 56 | if m == 1 57 | w = 1; 58 | return; 59 | end 60 | 61 | % Even/odd? 62 | isOddLength = mod(m, 2); 63 | if isOddLength 64 | x = (0:(m - 1) / 2)' / (m - 1); 65 | else 66 | x = (0:m / 2 - 1)' / (m - 1); 67 | end 68 | 69 | switch t 70 | case 'rectangular' 71 | w = ones(length(x), 1); 72 | case 'bartlett' 73 | w = 2 * x; 74 | case 'hann' 75 | a = 0.5; 76 | w = a - (1 - a) * cos(2 * pi * x); 77 | case 'hamming' 78 | a = 0.54; 79 | w = a - (1 - a) * cos(2 * pi * x); 80 | case 'blackman' 81 | a = [0.42 0.5 0.08 0]; 82 | w = a(1) - a(2) * cos (2 * pi * x) + a(3) * cos(4 * pi * x) - a(4) * cos(6 * pi * x); 83 | case 'blackmanharris' 84 | a = [0.35875 0.48829 0.14128 0.01168]; 85 | w = a(1) - a(2) * cos (2 * pi * x) + a(3) * cos(4 * pi * x) - a(4) * cos(6 * pi * x); 86 | case 'kaiser' 87 | if nargin < 3 || isempty(a) 88 | a = 0.5; 89 | end 90 | w = besseli(0, a * sqrt(1 - (2 * x - 1).^2)) / besseli(0, a); 91 | case 'tukey' 92 | if nargin < 3 || isempty(a) 93 | a = 0.5; 94 | end 95 | if a <= 0 % Rectangular 96 | w = ones(length(x), 1); 97 | elseif a >= 1 % Hann 98 | w = 0.5 - (1 - 0.5) * cos(2 * pi * x); 99 | else 100 | mTaper = floor((m - 1) * a / 2) + 1; 101 | xTaper = 2 * (0:mTaper - 1)' / (a * (m - 1)) - 1; 102 | w = [0.5 * (1 + cos(pi * xTaper)); ones(length(x) - mTaper, 1)]; 103 | end 104 | otherwise 105 | error('Unkown window type') 106 | end 107 | 108 | % Make symmetric 109 | if isOddLength 110 | w = [w; w(end - 1:-1:1)]; 111 | else 112 | w = [w; w(end:-1:1)]; 113 | end 114 | 115 | end 116 | -------------------------------------------------------------------------------- /firws.m: -------------------------------------------------------------------------------- 1 | %firws() - Designs windowed sinc type I linear phase FIR filter 2 | % 3 | % Usage: 4 | % >> b = firws(m, f); 5 | % >> b = firws(m, f, w); 6 | % >> b = firws(m, f, t); 7 | % >> b = firws(m, f, t, w); 8 | % 9 | % Inputs: 10 | % m - filter order (mandatory even) 11 | % f - vector or scalar of cutoff frequency/ies (-6 dB; 12 | % pi rad / sample) 13 | % 14 | % Optional inputs: 15 | % w - vector of length m + 1 defining window {default hamming} 16 | % t - 'high' for highpass, 'stop' for bandstop filter {default low-/ 17 | % bandpass} 18 | % 19 | % Output: 20 | % b - filter coefficients 21 | % 22 | % Example: 23 | % fs = 500; cutoff = 0.5; df = 1; 24 | % m = firwsord('hamming', fs, df); 25 | % b = firws(m, cutoff / (fs / 2), 'high', windows('hamming', m + 1)); 26 | % 27 | % References: 28 | % Smith, S. W. (1999). The scientist and engineer's guide to digital 29 | % signal processing (2nd ed.). San Diego, CA: California Technical 30 | % Publishing. 31 | % 32 | % Author: Andreas Widmann, University of Leipzig, 2005 33 | % 34 | % See also: 35 | % firwsord, invfirwsord, kaiserbeta, windows 36 | 37 | %123456789012345678901234567890123456789012345678901234567890123456789012 38 | 39 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 40 | % 41 | % This program is free software; you can redistribute it and/or modify 42 | % it under the terms of the GNU General Public License as published by 43 | % the Free Software Foundation; either version 2 of the License, or 44 | % (at your option) any later version. 45 | % 46 | % This program is distributed in the hope that it will be useful, 47 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 48 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 49 | % GNU General Public License for more details. 50 | % 51 | % You should have received a copy of the GNU General Public License 52 | % along with this program; if not, write to the Free Software 53 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 54 | % 55 | % $Id$ 56 | 57 | function [b, a] = firws(m, f, t, w) 58 | 59 | a = 1; 60 | 61 | if nargin < 2 62 | error('Not enough input arguments'); 63 | end 64 | if length(m) > 1 || ~isnumeric(m) || ~isreal(m) || mod(m, 2) ~= 0 || m < 2 65 | error('Filter order must be a real, even, positive integer.'); 66 | end 67 | f = f / 2; 68 | if any(f <= 0) || any(f >= 0.5) 69 | error('Frequencies must fall in range between 0 and 1.'); 70 | end 71 | if nargin < 3 || isempty(t) 72 | t = ''; 73 | end 74 | if nargin < 4 || isempty(w) 75 | if ~isempty(t) && ~ischar(t) 76 | w = t; 77 | t = ''; 78 | else 79 | w = windows('hamming', (m + 1)); 80 | end 81 | end 82 | w = w(:)'; % Make window row vector 83 | 84 | b = fkernel(m, f(1), w); 85 | 86 | if length(f) == 1 && strcmpi(t, 'high') 87 | b = fspecinv(b); 88 | end 89 | 90 | if length(f) == 2 91 | b = b + fspecinv(fkernel(m, f(2), w)); 92 | if isempty(t) || ~strcmpi(t, 'stop') 93 | b = fspecinv(b); 94 | end 95 | end 96 | 97 | % Compute filter kernel 98 | function b = fkernel(m, f, w) 99 | m = -m / 2 : m / 2; 100 | b(m == 0) = 2 * pi * f; % No division by zero 101 | b(m ~= 0) = sin(2 * pi * f * m(m ~= 0)) ./ m(m ~= 0); % Sinc 102 | b = b .* w; % Window 103 | b = b / sum(b); % Normalization to unity gain at DC 104 | 105 | % Spectral inversion 106 | function b = fspecinv(b) 107 | b = -b; 108 | b(1, (length(b) - 1) / 2 + 1) = b(1, (length(b) - 1) / 2 + 1) + 1; 109 | -------------------------------------------------------------------------------- /pop_firma.m: -------------------------------------------------------------------------------- 1 | % pop_firma() - Filter data using moving average FIR filter 2 | % 3 | % Usage: 4 | % >> [EEG, com] = pop_firma(EEG); % pop-up window mode 5 | % >> [EEG, com] = pop_firma(EEG, 'forder', order); 6 | % 7 | % Inputs: 8 | % EEG - EEGLAB EEG structure 9 | % 'forder' - scalar filter order. Mandatory even 10 | % 11 | % Outputs: 12 | % EEG - filtered EEGLAB EEG structure 13 | % com - history string 14 | % 15 | % Author: Andreas Widmann, University of Leipzig, 2005 16 | % 17 | % See also: 18 | % firfilt, plotfresp 19 | 20 | %123456789012345678901234567890123456789012345678901234567890123456789012 21 | 22 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 23 | % 24 | % This program is free software; you can redistribute it and/or modify 25 | % it under the terms of the GNU General Public License as published by 26 | % the Free Software Foundation; either version 2 of the License, or 27 | % (at your option) any later version. 28 | % 29 | % This program is distributed in the hope that it will be useful, 30 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | % GNU General Public License for more details. 33 | % 34 | % You should have received a copy of the GNU General Public License 35 | % along with this program; if not, write to the Free Software 36 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 37 | 38 | function [EEG, com] = pop_firma(EEG, varargin) 39 | 40 | com = ''; 41 | if nargin < 1 42 | help pop_firma; 43 | return; 44 | end 45 | if isempty(EEG.data) 46 | error('Cannot process empty dataset'); 47 | end 48 | 49 | if nargin < 2 50 | drawnow; 51 | uigeom = {[1 1 1] [1] [1 1 1]}; 52 | uilist = {{'style' 'text' 'string' 'Filter order (mandatory even):'} ... 53 | {'style' 'edit' 'string' '' 'tag' 'forderedit'} {} ... 54 | {} ... 55 | {} {} {'Style' 'pushbutton' 'string' 'Plot filter responses' 'callback' {@complot, EEG.srate}}}; 56 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firma'')', 'Filter the data -- pop_firma()'); 57 | if length(result) == 0, return; end 58 | 59 | if ~isempty(result{1}) 60 | args = [{'forder'} {str2num(result{1})}]; 61 | else 62 | error('Not enough input arguments'); 63 | end 64 | else 65 | args = varargin; 66 | end 67 | 68 | % Convert args to structure 69 | args = struct(args{:}); 70 | 71 | % Filter coefficients 72 | b = ones(1, args.forder + 1) / (args.forder + 1); 73 | 74 | % Filter 75 | disp('pop_firma() - filtering the data'); 76 | EEG = firfilt(EEG, b); 77 | 78 | % History string 79 | com = sprintf('%s = pop_firma(%s', inputname(1), inputname(1)); 80 | for c = fieldnames(args)' 81 | if ischar(args.(c{:})) 82 | com = [com sprintf(', ''%s'', ''%s''', c{:}, args.(c{:}))]; 83 | else 84 | com = [com sprintf(', ''%s'', %s', c{:}, mat2str(args.(c{:})))]; 85 | end 86 | end 87 | com = [com ');']; 88 | 89 | % Callback plot filter properties 90 | function complot(obj, evt, srate) 91 | args.forder = str2num(get(findobj(gcbf, 'tag', 'forderedit'), 'string')); 92 | if isempty(args.forder) 93 | error('Not enough input arguments'); 94 | end 95 | b = ones(1, args.forder + 1) / (args.forder + 1); 96 | H = findobj('tag', 'plotfiltresp', 'type', 'figure'); 97 | if ~isempty(H) 98 | figure(H); 99 | else 100 | H = figure; 101 | set(H, 'color', [.93 .96 1], 'tag', 'plotfiltresp'); 102 | end 103 | plotfresp(b, 1, [], srate, 'onepass-zerophase'); 104 | -------------------------------------------------------------------------------- /firfiltreport.m: -------------------------------------------------------------------------------- 1 | % firfiltreport() - Reporting of filter parameters 2 | % 3 | % Usage: 4 | % >> firfiltreport('key1', value1, 'key2', value2, 'keyn', valuen); 5 | % 6 | % Inputs: 7 | % 'func' - string filter function 8 | % 'family' - string filter family 9 | % 'type' - string filter type 10 | % 'dir' - string filter direction/phase 11 | % 'order' - scalar integer filter order 12 | % 13 | % Optional inputs: 14 | % 'fs' - scalar sampling frequency 15 | % 'fc' - scalar or vector cutoff frequency(ies) 16 | % 'df' - scalar transition band width 17 | % 'pbdev' - scalar passband deviation 18 | % 'sbatt' - scalar stopband attenuation 19 | % 20 | % Author: Andreas Widmann, University of Leipzig, 2015 21 | % 22 | % See also: 23 | % pop_firws 24 | 25 | %123456789012345678901234567890123456789012345678901234567890123456789012 26 | 27 | % Copyright (C) 2015 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 28 | % 29 | % This program is free software; you can redistribute it and/or modify 30 | % it under the terms of the GNU General Public License as published by 31 | % the Free Software Foundation; either version 2 of the License, or 32 | % (at your option) any later version. 33 | % 34 | % This program is distributed in the hope that it will be useful, 35 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 36 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 37 | % GNU General Public License for more details. 38 | % 39 | % You should have received a copy of the GNU General Public License 40 | % along with this program; if not, write to the Free Software 41 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 42 | 43 | function firfiltreport( varargin ) 44 | 45 | Arg = struct( varargin{ : } ); 46 | 47 | % Basic reporting 48 | if ~all( isfield( Arg, { 'func', 'type', 'dir', 'order', 'family' } ) ) 49 | error( 'Not enough input arguments.' ) 50 | end 51 | if strncmp( Arg.dir, 'twopass', 7 ) 52 | isTwopass = true; 53 | Arg.order = 2 * Arg.order; 54 | fcatt = -12; 55 | else 56 | isTwopass = false; 57 | fcatt = -6; 58 | end 59 | reportArray{ 1 } = sprintf( '%s() - %s filtering data: %s, order %d, %s\n', Arg.func, Arg.type, Arg.dir, Arg.order, Arg.family ); 60 | 61 | 62 | % Detailed reporting 63 | if all( isfield( Arg, { 'fs', 'fc', 'df', 'pbdev', 'sbatt' } ) ) && ~isempty( Arg.fs ) && ~isempty( Arg.fc ) && ~isempty( Arg.df ) && ~isempty( Arg.pbdev ) && ~isempty( Arg.sbatt ) 64 | 65 | % Transition band edges 66 | fn = Arg.fs / 2; 67 | for iFc = 1:length( Arg.fc ) 68 | dflim( :, iFc ) = [ max( [ Arg.fc(iFc) - Arg.df / 2, 0 ] ), min( [ Arg.fc(iFc) + Arg.df / 2, fn ] ) ]; %#ok 69 | end 70 | 71 | switch Arg.type 72 | case 'lowpass' 73 | reportArray{ 2 } = sprintf( ' cutoff (%d dB) %g Hz\n', fcatt, Arg.fc ); 74 | reportArray{ 3 } = sprintf( ' transition width %.1f Hz, passband 0-%.1f Hz, stopband %.1f-%.0f Hz\n', Arg.df, dflim( : ), fn ); 75 | case 'highpass' 76 | reportArray{ 2 } = sprintf( ' cutoff (%d dB) %g Hz\n', fcatt, Arg.fc ); 77 | reportArray{ 3 } = sprintf( ' transition width %.1f Hz, stopband 0-%.1f Hz, passband %.1f-%.0f Hz\n', Arg.df, dflim( : ), fn ); 78 | case 'bandpass' 79 | reportArray{ 2 } = sprintf( ' cutoff (%d dB) %g Hz and %g Hz\n', fcatt, Arg.fc ); 80 | reportArray{ 3 } = sprintf( ' transition width %.1f Hz, stopband 0-%.1f Hz, passband %.1f-%.1f Hz, stopband %.1f-%.0f Hz\n', Arg.df, dflim( : ), fn ); 81 | case 'bandstop' 82 | reportArray{ 2 } = sprintf( ' cutoff (%d dB) %g Hz and %g Hz\n', fcatt, Arg.fc ); 83 | reportArray{ 3 } = sprintf( ' transition width %.1f Hz, passband 0-%.1f Hz, stopband %.1f-%.1f Hz, passband %.1f-%.0f Hz\n', Arg.df, dflim( : ), fn ); 84 | end 85 | 86 | if isTwopass % Adjust deviation/ripple for twopass filtering 87 | Arg.pbdev = ( Arg.pbdev + 1 ) ^ 2 - 1; 88 | Arg.sbatt = Arg.sbatt ^ 2; 89 | end 90 | reportArray{ 4 } = sprintf( ' max. passband deviation %.4f (%.2f%%), stopband attenuation %.0f dB\n', Arg.pbdev, Arg.pbdev * 100, 20 * log10( Arg.sbatt ) ); 91 | 92 | end 93 | 94 | % Fieldtrip or EEGLAB 95 | if strncmp( 'ft_', Arg.func, 3 ) 96 | for iLine = 1:length( reportArray ) 97 | print_once( reportArray{ iLine } ); 98 | end 99 | else 100 | for iLine = 1:length( reportArray ) 101 | fprintf( strrep( reportArray{ iLine }, '%', '%%' ) ); 102 | end 103 | end 104 | 105 | end 106 | 107 | -------------------------------------------------------------------------------- /firfilt.m: -------------------------------------------------------------------------------- 1 | % firfilt() - Pad data with DC constant, filter data with FIR filter, 2 | % and shift data by the filter's group delay 3 | % 4 | % Usage: 5 | % >> EEG = firfilt(EEG, b, nFrames); 6 | % >> EEG = firfilt(EEG, b, nFrames, chaninds); 7 | % 8 | % Inputs: 9 | % EEG - EEGLAB EEG structure 10 | % b - vector of filter coefficients 11 | % 12 | % Optional inputs: 13 | % nFrames - number of frames to filter per block {default 1000} 14 | % chaninds - channel indices {default all} 15 | % 16 | % Outputs: 17 | % EEG - EEGLAB EEG structure 18 | % 19 | % Note: 20 | % Higher values for nFrames increase speed and working memory 21 | % requirements. 22 | % 23 | % Author: Andreas Widmann, University of Leipzig, 2005 24 | % 25 | % See also: 26 | % filter, findboundaries 27 | 28 | %123456789012345678901234567890123456789012345678901234567890123456789012 29 | 30 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 31 | % 32 | % This program is free software; you can redistribute it and/or modify 33 | % it under the terms of the GNU General Public License as published by 34 | % the Free Software Foundation; either version 2 of the License, or 35 | % (at your option) any later version. 36 | % 37 | % This program is distributed in the hope that it will be useful, 38 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 39 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 40 | % GNU General Public License for more details. 41 | % 42 | % You should have received a copy of the GNU General Public License 43 | % along with this program; if not, write to the Free Software 44 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 45 | 46 | function EEG = firfilt(EEG, b, nFrames, chaninds) 47 | 48 | if nargin < 2 49 | error('Not enough input arguments.'); 50 | end 51 | if nargin < 3 || isempty(nFrames) 52 | nFrames = 1000; 53 | end 54 | if nargin < 4 55 | chaninds = 1:size(EEG.data,1); 56 | end 57 | 58 | % Filter's group delay 59 | if mod(length(b), 2) ~= 1 60 | error('Filter order is not even.'); 61 | end 62 | groupDelay = (length(b) - 1) / 2; 63 | 64 | % Find data discontinuities and reshape epoched data 65 | if EEG.trials > 1 % Epoched data 66 | EEG.data = reshape(EEG.data, [EEG.nbchan EEG.pnts * EEG.trials]); 67 | dcArray = 1 : EEG.pnts : EEG.pnts * (EEG.trials + 1); 68 | else % Continuous data 69 | dcArray = findboundaries(EEG.event); 70 | dcArray = dcArray( dcArray >= 1 & dcArray <= EEG.pnts ); % Assert DC offset events are within data range 71 | dcArray = [ dcArray EEG.pnts + 1 ]; 72 | end 73 | 74 | % Initialize progress indicator 75 | nSteps = 20; 76 | step = 0; 77 | fprintf(1, 'firfilt(): |'); 78 | strLength = fprintf(1, [repmat(' ', 1, nSteps - step) '| 0%%']); 79 | tic 80 | 81 | for iDc = 1:(length(dcArray) - 1) 82 | 83 | % Pad beginning of data with DC constant and get initial conditions 84 | ziDataDur = min(groupDelay, dcArray(iDc + 1) - dcArray(iDc)); 85 | [temp, zi] = filter(b, 1, double([EEG.data(chaninds, ones(1, groupDelay) * dcArray(iDc)) ... 86 | EEG.data(chaninds, dcArray(iDc):(dcArray(iDc) + ziDataDur - 1))]), [], 2); 87 | 88 | blockArray = [(dcArray(iDc) + groupDelay):nFrames:(dcArray(iDc + 1) - 1) dcArray(iDc + 1)]; 89 | for iBlock = 1:(length(blockArray) - 1) 90 | 91 | % Filter the data 92 | [EEG.data(chaninds, (blockArray(iBlock) - groupDelay):(blockArray(iBlock + 1) - groupDelay - 1)), zi] = ... 93 | filter(b, 1, double(EEG.data(chaninds, blockArray(iBlock):(blockArray(iBlock + 1) - 1))), zi, 2); 94 | 95 | % Update progress indicator 96 | [step, strLength] = mywaitbar((blockArray(iBlock + 1) - groupDelay - 1), size(EEG.data, 2), step, nSteps, strLength); 97 | end 98 | 99 | % Pad end of data with DC constant 100 | temp = filter(b, 1, double(EEG.data(chaninds, ones(1, groupDelay) * (dcArray(iDc + 1) - 1))), zi, 2); 101 | EEG.data(chaninds, (dcArray(iDc + 1) - ziDataDur):(dcArray(iDc + 1) - 1)) = ... 102 | temp(:, (end - ziDataDur + 1):end); 103 | 104 | % Update progress indicator 105 | [step, strLength] = mywaitbar((dcArray(iDc + 1) - 1), size(EEG.data, 2), step, nSteps, strLength); 106 | 107 | end 108 | 109 | % Reshape epoched data 110 | if EEG.trials > 1 111 | EEG.data = reshape(EEG.data, [EEG.nbchan EEG.pnts EEG.trials]); 112 | end 113 | 114 | % Deinitialize progress indicator 115 | fprintf(1, '\n') 116 | 117 | end 118 | 119 | function [step, strLength] = mywaitbar(compl, total, step, nSteps, strLength) 120 | 121 | progStrArray = '/-\|'; 122 | tmp = floor(compl / total * nSteps); 123 | if tmp > step 124 | fprintf(1, [repmat('\b', 1, strLength) '%s'], repmat('=', 1, tmp - step)) 125 | step = tmp; 126 | ete = ceil(toc / step * (nSteps - step)); 127 | strLength = fprintf(1, [repmat(' ', 1, nSteps - step) '%s %3d%%, ETE %02d:%02d'], progStrArray(mod(step - 1, 4) + 1), floor(step * 100 / nSteps), floor(ete / 60), mod(ete, 60)); 128 | end 129 | 130 | end 131 | -------------------------------------------------------------------------------- /pop_firwsord.m: -------------------------------------------------------------------------------- 1 | % pop_firwsord() - Estimate windowed sinc filter order depending on 2 | % window type and requested transition band width 3 | % 4 | % Usage: 5 | % >> [m, dev] = pop_firwsord; % pop-up window mode 6 | % >> m = pop_firwsord(wtype, fs, df); 7 | % >> m = pop_firwsord('kaiser', fs, df, dev); 8 | % 9 | % Inputs: 10 | % wtype - char array window type. 'rectangular', 'hann', 'hamming', 11 | % 'blackman', or 'kaiser' {default 'hamming'} 12 | % fs - scalar sampling frequency {default 2} 13 | % df - scalar requested transition band width 14 | % dev - scalar maximum passband deviation/ripple (Kaiser window 15 | % only) 16 | % 17 | % Output: 18 | % m - scalar estimated filter order 19 | % dev - scalar maximum passband deviation/ripple 20 | % 21 | % References: 22 | % [1] Smith, S. W. (1999). The scientist and engineer's guide to 23 | % digital signal processing (2nd ed.). San Diego, CA: California 24 | % Technical Publishing. 25 | % [2] Proakis, J. G., & Manolakis, D. G. (1996). Digital Signal 26 | % Processing: Principles, Algorithms, and Applications (3rd ed.). 27 | % Englewood Cliffs, NJ: Prentice-Hall 28 | % [3] Ifeachor E. C., & Jervis B. W. (1993). Digital Signal 29 | % Processing: A Practical Approach. Wokingham, UK: Addison-Wesley 30 | % 31 | % Author: Andreas Widmann, University of Leipzig, 2005 32 | % 33 | % See also: 34 | % firwsord, pop_firws, firws, pop_kaiserbeta, windows 35 | 36 | %123456789012345678901234567890123456789012345678901234567890123456789012 37 | 38 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 39 | % 40 | % This program is free software; you can redistribute it and/or modify 41 | % it under the terms of the GNU General Public License as published by 42 | % the Free Software Foundation; either version 2 of the License, or 43 | % (at your option) any later version. 44 | % 45 | % This program is distributed in the hope that it will be useful, 46 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 47 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 48 | % GNU General Public License for more details. 49 | % 50 | % You should have received a copy of the GNU General Public License 51 | % along with this program; if not, write to the Free Software 52 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 53 | 54 | function [m, dev] = pop_firwsord(wtype, fs, df, dev) 55 | 56 | m = []; 57 | 58 | wtypes = {'rectangular' 'hann' 'hamming' 'blackman' 'kaiser'}; 59 | 60 | % Window type 61 | if nargin < 1 || isempty(wtype) 62 | wtype = 3; 63 | elseif ~ischar(wtype) || isempty(strmatch(wtype, wtypes)) 64 | error('Unknown window type'); 65 | else 66 | wtype = strmatch(wtype, wtypes); 67 | end 68 | 69 | % Sampling frequency 70 | if nargin < 2 || isempty(fs) 71 | fs = 2; 72 | end 73 | 74 | % Transition band width 75 | if nargin < 3 76 | df = []; 77 | end 78 | 79 | % Maximum passband deviation/ripple 80 | if nargin < 4 || isempty(dev) 81 | devs = {0.089 0.0063 0.0022 0.0002 []}; 82 | dev = devs{wtype}; 83 | end 84 | 85 | % GUI 86 | if nargin < 3 || isempty(df) || (wtype == 5 && isempty(dev)) 87 | drawnow; 88 | uigeom = {[1 1] [1 1] [1 1] [1 1]}; 89 | uilist = {{'style' 'text' 'string' 'Sampling frequency:'} ... 90 | {'style' 'edit' 'string' fs} ... 91 | {'style' 'text' 'string' 'Window type:'} ... 92 | {'style' 'popupmenu' 'string' wtypes 'tag' 'wtypepop' 'value' wtype 'callback' {@comwtype, dev}} ... 93 | {'style' 'text' 'string' 'Transition bandwidth (Hz):'} ... 94 | {'style' 'edit' 'string' df} ... 95 | {'style' 'text' 'string' 'Max passband deviation/ripple:' 'tag' 'devtext'} ... 96 | {'style' 'edit' 'tag' 'devedit' 'createfcn' {@comwtype, dev}}}; 97 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firwsord'')', 'Estimate filter order -- pop_firwsord()'); 98 | 99 | if length(result) == 0, return, end 100 | if ~isempty(result{1}) 101 | fs = str2num(result{1}); 102 | else 103 | fs = 2; 104 | end 105 | wtype = result{2}; 106 | if ~isempty(result{3}) 107 | df = str2num(result{3}); 108 | else 109 | error('Not enough input arguments.'); 110 | end 111 | if ~isempty(result{4}) 112 | dev = str2num(result{4}); 113 | elseif wtype == 5 114 | error('Not enough input arguments.'); 115 | end 116 | end 117 | 118 | if length(fs) > 1 || ~isnumeric(fs) || ~isreal(fs) || fs <= 0 119 | error('Sampling frequency must be a positive real scalar.'); 120 | end 121 | if length(df) > 1 || ~isnumeric(df) || ~isreal(df) || fs <= 0 122 | error('Transition bandwidth must be a positive real scalar.'); 123 | end 124 | 125 | % Compute filter order with low-level firwsord 126 | [ m, dev ] = firwsord( wtypes{ wtype }, fs, df, dev ); 127 | 128 | function comwtype(obj, evt, dev) 129 | enable = {'off' 'off' 'off' 'off' 'on'}; 130 | devs = {0.089 0.0063 0.0022 0.0002 dev}; 131 | wtype = get(findobj(gcbf, 'tag', 'wtypepop'), 'value'); 132 | set(findobj(gcbf, 'tag', 'devtext'), 'enable', enable{wtype}); 133 | set(findobj(gcbf, 'tag', 'devedit'), 'enable', enable{wtype}, 'string', devs{wtype}); 134 | -------------------------------------------------------------------------------- /plotfresp.m: -------------------------------------------------------------------------------- 1 | % plotfresp() - Plot a filter's impulse, step, magnitude, and phase response 2 | % 3 | % Usage: 4 | % >> plotfresp(b, a, nfft, fs, causal); 5 | % 6 | % Inputs: 7 | % b - vector numerator coefficients 8 | % 9 | % Optional inputs: 10 | % a - scalar or vector denominator coefficients (IIR support is 11 | % experimental!) {default 1} 12 | % nfft - scalar number of points {default 512} 13 | % fs - scalar sampling frequency {default 1} 14 | % dir - string filter direction {default 'onepass'} 15 | % 16 | % Author: Andreas Widmann, University of Leipzig, 2005 17 | % 18 | % See also: 19 | % firws 20 | 21 | %123456789012345678901234567890123456789012345678901234567890123456789012 22 | 23 | % Copyright (C) 2005-2014 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 24 | % 25 | % This program is free software; you can redistribute it and/or modify 26 | % it under the terms of the GNU General Public License as published by 27 | % the Free Software Foundation; either version 2 of the License, or 28 | % (at your option) any later version. 29 | % 30 | % This program is distributed in the hope that it will be useful, 31 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | % GNU General Public License for more details. 34 | % 35 | % You should have received a copy of the GNU General Public License 36 | % along with this program; if not, write to the Free Software 37 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 38 | % 39 | % $Id$ 40 | 41 | function plotfresp(b, a, nfft, fs, dir) 42 | 43 | if nargin < 5 || isempty(dir) 44 | dir = 'onepass'; 45 | end 46 | if nargin < 4 || isempty(fs) 47 | fs = 1; 48 | end 49 | if nargin < 3 || isempty(nfft) 50 | nfft = 512; 51 | end 52 | if nargin < 2 || isempty(a) 53 | a = 1; 54 | end 55 | if nargin < 1 56 | error('Not enough input arguments.'); 57 | end 58 | 59 | % FIR? 60 | if isscalar(a) && a == 1 61 | isFIR = true; 62 | else 63 | isFIR = false; 64 | end 65 | 66 | % Linear phase FIR 67 | if isFIR && all(b(:)' == fliplr(b(:)')) % TODO: antisymmetric 68 | isLinPhaseFir = true; 69 | else 70 | isLinPhaseFir = false; 71 | end 72 | 73 | % Twopass/zerophase? 74 | if strncmp('twopass', dir, 7) 75 | isTwopass = true; 76 | isZerophase = true; 77 | elseif strcmp('onepass-zerophase', dir) 78 | if ~isLinPhaseFir 79 | error('Onepass-zerophase filtering is only allowed for linear-phase FIR filters.') 80 | end 81 | isTwopass = false; 82 | isZerophase = true; 83 | else 84 | isTwopass = false; 85 | isZerophase = false; 86 | end 87 | 88 | % Impulse response 89 | if isFIR 90 | impresp = b(:)'; 91 | else 92 | if ~exist('impz', 'file') 93 | warning('Plotting IIR filter responses requires signal processing toolbox.') 94 | return 95 | end 96 | impresp = impz(b, a)'; 97 | end 98 | 99 | % Twopass 100 | if isTwopass 101 | impresp = conv(impresp, fliplr(impresp)); 102 | end 103 | n = length(impresp); 104 | 105 | % Zerophase 106 | if isZerophase 107 | groupdelay = (n - 1) / 2; 108 | x = -groupdelay:groupdelay; 109 | else 110 | x = 0:n - 1; 111 | end 112 | 113 | nfft = max([2^ceil(log2(n)) nfft]); % Do not truncate impulse response 114 | f = linspace(0, fs / 2, nfft / 2 + 1); 115 | z = fft(impresp, nfft); 116 | z = z(1:nfft / 2 + 1); 117 | 118 | % Find open figure window 119 | H = findobj('Tag', 'plotfiltresp', 'type', 'figure'); 120 | if ~isempty(H) 121 | figure(H); 122 | else 123 | H = figure; 124 | set(H, 'Tag', 'plotfiltresp'); 125 | posArray = get(H, 'Position'); 126 | posArray(3) = posArray(4) * 1.6; 127 | set(H, 'Position', posArray); 128 | end 129 | 130 | % Formatting 131 | titlePropArray = {'Fontweight', 'bold'}; 132 | axisPropArray = {'NextPlot', 'add', 'XGrid', 'on', 'YGrid', 'on', 'Box', 'on'}; 133 | 134 | % Impulse resonse 135 | ax(1) = subplot(2, 3, 1, axisPropArray{:}); 136 | stem(x, impresp, 'fill') 137 | title('Impulse response', titlePropArray{:}); 138 | ylabel('Amplitude'); 139 | 140 | % Step response 141 | ax(4) = subplot(2, 3, 4, axisPropArray{:}); 142 | stem(x, cumsum(impresp), 'fill'); 143 | title('Step response', titlePropArray{:}); 144 | ylimArray = ylim; 145 | if ylimArray(2) < -ylimArray(1) + 1; 146 | ylimArray(2) = -ylimArray(1) + 1; 147 | ylim(ylimArray); 148 | end 149 | xMin = []; xMax = []; 150 | childrenArray = get(ax(4), 'Children'); 151 | for iChild =1:length(childrenArray) 152 | xData = get(childrenArray(iChild), 'XData'); 153 | xMin = min([xMin min(xData)]); 154 | xMax = max([xMax max(xData)]); 155 | end 156 | set(ax([1 4]), 'XLim', [xMin xMax]); 157 | ylabel('Amplitude'); 158 | 159 | % Magnitude response 160 | ax(2) = subplot(2, 3, 2, axisPropArray{:}); 161 | plot(f, abs(z)); 162 | title('Magnitude response', titlePropArray{:}); 163 | ylabel('Magnitude (linear)'); 164 | 165 | ax(5) = subplot(2, 3, 5, axisPropArray{:}); 166 | plot(f, 20 * log10(abs(z))); 167 | title('Magnitude response', titlePropArray{:}); 168 | ylimArray = ylim; 169 | if ylimArray(1) < -200 170 | ylimArray(1) = -200; 171 | ylim(ylimArray); 172 | end 173 | ylabel('Magnitude (dB)'); 174 | 175 | % Phase response 176 | ax(3) = subplot(2, 3, 3, axisPropArray{:}); 177 | phaseresp = unwrap(angle(z)); 178 | if isZerophase % Correct delay for zero-phase FIR filter? 179 | delay = -f / fs * groupdelay * 2 * pi; 180 | phaseresp = phaseresp - delay; 181 | phaseresp = mod(round(phaseresp / pi), 2) * pi; % Avoid rounding errors; linear-phase FIR only! 182 | end 183 | plot(f, phaseresp); 184 | title('Phase response', titlePropArray{:}); 185 | ylabel('Phase (rad)'); 186 | 187 | % Formatting 188 | xlabelArray = get(ax(1:5), 'XLabel'); 189 | if fs == 1 190 | set([xlabelArray{[2 3 5]}], 'String', 'Normalized frequency (2 \pi rad / sample)'); 191 | else 192 | set([xlabelArray{[2 3 5]}], 'String', 'Frequency (Hz)'); 193 | end 194 | set([xlabelArray{[1 4]}], 'String', 'n (samples)'); 195 | set(ax([2 3 5]), 'XLim', [0 fs / 2]); 196 | set(ax(1:5), 'ColorOrder', circshift(get(ax(1), 'ColorOrder'), -1)); 197 | 198 | end 199 | -------------------------------------------------------------------------------- /pop_firpm.m: -------------------------------------------------------------------------------- 1 | % pop_firpm() - Filter data using Parks-McClellan FIR filter 2 | % 3 | % Usage: 4 | % >> [EEG, com, b] = pop_firpm(EEG); % pop-up window mode 5 | % >> [EEG, com, b] = pop_firpm(EEG, 'key1', value1, 'key2', ... 6 | % value2, 'keyn', valuen); 7 | % 8 | % Inputs: 9 | % EEG - EEGLAB EEG structure 10 | % 'fcutoff' - vector or scalar of cutoff frequency/ies (~-6 dB; Hz) 11 | % 'ftrans' - scalar transition band width 12 | % 'ftype' - char array filter type. 'bandpass', 'highpass', 13 | % 'lowpass', or 'bandstop' 14 | % 'forder' - scalar filter order. Mandatory even 15 | % 16 | % Optional inputs: 17 | % 'wtpass' - scalar passband weight 18 | % 'wtstop' - scalar stopband weight 19 | % 20 | % Outputs: 21 | % EEG - filtered EEGLAB EEG structure 22 | % com - history string 23 | % b - filter coefficients 24 | % 25 | % Note: 26 | % Requires the signal processing toolbox. 27 | % 28 | % Author: Andreas Widmann, University of Leipzig, 2005 29 | % 30 | % See also: 31 | % firfilt, pop_firpmord, plotfresp, firpm, firpmord 32 | 33 | %123456789012345678901234567890123456789012345678901234567890123456789012 34 | 35 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 36 | % 37 | % This program is free software; you can redistribute it and/or modify 38 | % it under the terms of the GNU General Public License as published by 39 | % the Free Software Foundation; either version 2 of the License, or 40 | % (at your option) any later version. 41 | % 42 | % This program is distributed in the hope that it will be useful, 43 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 44 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 45 | % GNU General Public License for more details. 46 | % 47 | % You should have received a copy of the GNU General Public License 48 | % along with this program; if not, write to the Free Software 49 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 50 | 51 | function [EEG, com, b] = pop_firpm(EEG, varargin) 52 | 53 | if ~(exist('firpm', 'file') == 2 || exist('firpm', 'file') == 6) 54 | error('Requires the signal processing toolbox.'); 55 | end 56 | 57 | com = ''; 58 | if nargin < 1 59 | help pop_firpm; 60 | return; 61 | end 62 | if isempty(EEG.data) 63 | error('Cannot process empty dataset'); 64 | end 65 | 66 | if nargin < 2 67 | drawnow; 68 | ftypes = {'bandpass' 'highpass' 'lowpass' 'bandstop'}; 69 | uigeom = {[1 0.75 0.75] [1 0.75 0.75] [1 0.75 0.75] 1 [1 0.75 0.75] [1 0.75 0.75] [1 0.75 0.75] 1 [1 0.75 0.75]}; 70 | uilist = {{'Style' 'text' 'String' 'Cutoff frequency(ies) [hp lp] (~-6 dB; Hz):'} ... 71 | {'Style' 'edit' 'String' '' 'Tag' 'fcutoffedit'} {} ... 72 | {'Style' 'text' 'String' 'Transition band width:'} ... 73 | {'Style' 'edit' 'String' '' 'Tag' 'ftransedit'} {} ... 74 | {'Style' 'text' 'String' 'Filter type:'} ... 75 | {'Style' 'popupmenu' 'String' ftypes 'Tag' 'ftypepop'} {} ... 76 | {} ... 77 | {'Style' 'text' 'String' 'Passband weight:'} ... 78 | {'Style' 'edit' 'String' '' 'Tag' 'wtpassedit'} {} ... 79 | {'Style' 'text' 'String' 'Stopband weight:'} ... 80 | {'Style' 'edit' 'String' '' 'Tag' 'wtstopedit'} {} ... 81 | {'Style' 'text' 'String' 'Filter order (mandatory even):'} ... 82 | {'Style' 'edit' 'String' '' 'Tag' 'forderedit'} ... 83 | {'Style' 'pushbutton' 'String' 'Estimate' 'Tag' 'orderpush' 'Callback' {@comcb, ftypes, EEG.srate}} ... 84 | {} ... 85 | {} {} {'Style' 'pushbutton' 'String', 'Plot filter responses' 'Tag' 'plotpush' 'Callback' {@comcb, ftypes, EEG.srate}}}; 86 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firpm'')', 'Filter the data -- pop_firpm()'); 87 | if isempty(result), return; end 88 | 89 | args = {}; 90 | if ~isempty(result{1}) 91 | args = [args {'fcutoff'} {str2num(result{1})}]; 92 | end 93 | if ~isempty(result{2}) 94 | args = [args {'ftrans'} {str2double(result{2})}]; 95 | end 96 | args = [args {'ftype'} ftypes(result{3})]; 97 | if ~isempty(result{4}) 98 | args = [args {'wtpass'} {str2double(result{4})}]; 99 | end 100 | if ~isempty(result{5}) 101 | args = [args {'wtstop'} {str2double(result{5})}]; 102 | end 103 | if ~isempty(result{6}) 104 | args = [args {'forder'} {str2double(result{6})}]; 105 | end 106 | else 107 | args = varargin; 108 | end 109 | 110 | % Convert args to structure 111 | args = struct(args{:}); 112 | 113 | c = parseargs(args, EEG.srate); 114 | if ~isfield(args, 'forder') || isempty(args.forder) 115 | error('Not enough input arguments'); 116 | end 117 | b = firpm(args.forder, c{:}); 118 | 119 | % Filter 120 | disp('pop_firpm() - filtering the data'); 121 | EEG = firfilt(EEG, b); 122 | 123 | % History string 124 | com = sprintf('%s = pop_firpm(%s', inputname(1), inputname(1)); 125 | for c = fieldnames(args)' 126 | if ischar(args.(c{:})) 127 | com = [com sprintf(', ''%s'', ''%s''', c{:}, args.(c{:}))]; 128 | else 129 | com = [com sprintf(', ''%s'', %s', c{:}, mat2str(args.(c{:})))]; 130 | end 131 | end 132 | com = [com ');']; 133 | 134 | % Convert structure args to cell array firpm parameters 135 | function c = parseargs(args, srate) 136 | 137 | if ~isfield(args, 'fcutoff') || ~isfield(args, 'ftype') || ~isfield(args, 'ftrans') || isempty(args.fcutoff) || isempty(args.ftype) || isempty(args.ftrans) 138 | error('Not enough input arguments.'); 139 | end 140 | 141 | % Cutoff frequencies 142 | args.fcutoff = [args.fcutoff - args.ftrans / 2 args.fcutoff + args.ftrans / 2]; 143 | args.fcutoff = sort(args.fcutoff / (srate / 2)); % Sorting and normalization 144 | if any(args.fcutoff < 0) 145 | error('Cutoff frequencies - transition band width / 2 must not be < DC'); 146 | elseif any(args.fcutoff > 1) 147 | error('Cutoff frequencies + transition band width / 2 must not be > Nyquist'); 148 | end 149 | c = {[0 args.fcutoff 1]}; 150 | 151 | % Filter type 152 | switch args.ftype 153 | case 'bandpass' 154 | c = [c {[0 0 1 1 0 0]}]; 155 | case 'bandstop' 156 | c = [c {[1 1 0 0 1 1]}]; 157 | case 'highpass' 158 | c = [c {[0 0 1 1]}]; 159 | case 'lowpass' 160 | c = [c {[1 1 0 0]}]; 161 | end 162 | 163 | %Filter weights 164 | if all(isfield(args, {'wtpass', 'wtstop'})) && ~isempty(args.wtpass) && ~isempty(args.wtstop) 165 | w = [args.wtstop args.wtpass]; 166 | c{3} = w(c{2}(1:2:end) + 1); 167 | end 168 | 169 | % Callback 170 | function comcb(obj, evt, ftypes, srate) 171 | 172 | args.fcutoff = str2num(get(findobj(gcbf, 'Tag', 'fcutoffedit'), 'String')); 173 | args.ftype = ftypes{get(findobj(gcbf, 'Tag', 'ftypepop'), 'Value')}; 174 | args.ftrans = str2double(get(findobj(gcbf, 'Tag', 'ftransedit'), 'String')); 175 | args.wtpass = str2double(get(findobj(gcbf, 'Tag', 'wtpassedit'), 'String')); 176 | args.wtstop = str2double(get(findobj(gcbf, 'Tag', 'wtstopedit'), 'String')); 177 | c = parseargs(args, srate); 178 | 179 | switch get(obj, 'Tag') 180 | case 'orderpush' 181 | [args.forder, args.wtpass, args.wtstop] = pop_firpmord(c{1}(2:end - 1), c{2}(1:2:end)); 182 | if ~isempty(args.forder) || ~isempty(args.wtpass) || ~isempty(args.wtstop) 183 | set(findobj(gcbf, 'Tag', 'forderedit'), 'String', ceil(args.forder / 2) * 2); 184 | set(findobj(gcbf, 'Tag', 'wtpassedit'), 'String', args.wtpass); 185 | set(findobj(gcbf, 'Tag', 'wtstopedit'), 'String', args.wtstop); 186 | end 187 | 188 | case 'plotpush' 189 | args.forder = str2double(get(findobj(gcbf, 'Tag', 'forderedit'), 'String')); 190 | if isempty(args.forder) 191 | error('Not enough input arguments'); 192 | end 193 | b = firpm(args.forder, c{:}); 194 | H = findobj('Tag', 'plotfiltresp', 'Type', 'figure'); 195 | if ~isempty(H) 196 | figure(H); 197 | else 198 | H = figure; 199 | set(H, 'color', [.93 .96 1], 'Tag', 'plotfiltresp'); 200 | end 201 | plotfresp(b, 1, [], srate, 'onepass-zerophase'); 202 | end 203 | -------------------------------------------------------------------------------- /pop_xfirws.m: -------------------------------------------------------------------------------- 1 | % pop_xfirws() - Design and export xfir compatible windowed sinc FIR filter 2 | % 3 | % Usage: 4 | % >> pop_xfirws; % pop-up window mode 5 | % >> [b, a] = pop_xfirws; % pop-up window mode 6 | % >> pop_xfirws('key1', value1, 'key2', value2, 'keyn', valuen); 7 | % >> [b, a] = pop_xfirws('key1', value1, 'key2', value2, 'keyn', valuen); 8 | % 9 | % Inputs: 10 | % 'srate' - scalar sampling rate (Hz) 11 | % 'fcutoff' - vector or scalar of cutoff frequency/ies (-6 dB; Hz) 12 | % 'forder' - scalar filter order. Mandatory even 13 | % 14 | % Optional inputs: 15 | % 'ftype' - char array filter type. 'bandpass', 'highpass', 16 | % 'lowpass', or 'bandstop' {default 'bandpass' or 17 | % 'lowpass', depending on number of cutoff frequencies} 18 | % 'wtype' - char array window type. 'rectangular', 'hann', 19 | % 'hamming', 'blackman', or 'kaiser' {default 'hamming'} 20 | % 'warg' - scalar kaiser beta 21 | % 'filename' - char array export filename 22 | % 'pathname' - char array export pathname {default '.'} 23 | % 24 | % Outputs: 25 | % b - filter coefficients 26 | % a - filter coefficients 27 | % 28 | % Note: 29 | % Window based filters' transition band width is defined by filter 30 | % order and window type/parameters. Stopband attenuation equals 31 | % passband ripple and is defined by the window type/parameters. Refer 32 | % to table below for typical parameters. (Windowed sinc) FIR filters 33 | % are zero phase in passband when shifted by the filters group delay 34 | % (what firfilt does). Pi phase jumps noticable in the phase reponse 35 | % reflect a negative frequency response and only occur in the 36 | % stopband. 37 | % 38 | % Beta Max stopband Max passband Max passband Transition width Mainlobe width 39 | % attenuation deviation ripple (dB) (normalized freq) (normalized rad freq) 40 | % (dB) 41 | % Rectangular -21 0.0891 1.552 0.9 / m* 4 * pi / m 42 | % Hann -44 0.0063 0.109 3.1 / m 8 * pi / m 43 | % Hamming -53 0.0022 0.038 3.3 / m 8 * pi / m 44 | % Blackman -74 0.0002 0.003 5.5 / m 12 * pi / m 45 | % Kaiser 5.653 -60 0.001 0.017 3.6 / m 46 | % Kaiser 7.857 -80 0.0001 0.002 5.0 / m 47 | % * m = filter order 48 | % 49 | % Example: 50 | % fs = 500; tbw = 2; dev = 0.001; 51 | % beta = pop_kaiserbeta(dev); 52 | % m = pop_firwsord('kaiser', fs, tbw, dev); 53 | % pop_xfirws('srate', fs, 'fcutoff', [1 25], 'ftype', 'bandpass', 'wtype', 'kaiser', 'warg', beta, 'forder', m, 'filename', 'foo.fir') 54 | % 55 | % Author: Andreas Widmann, University of Leipzig, 2011 56 | % 57 | % See also: 58 | % firfilt, firws, pop_firwsord, pop_kaiserbeta, plotfresp, windows 59 | 60 | %123456789012345678901234567890123456789012345678901234567890123456789012 61 | 62 | % Copyright (C) 2011 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 63 | % 64 | % This program is free software; you can redistribute it and/or modify 65 | % it under the terms of the GNU General Public License as published by 66 | % the Free Software Foundation; either version 2 of the License, or 67 | % (at your option) any later version. 68 | % 69 | % This program is distributed in the hope that it will be useful, 70 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 71 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 72 | % GNU General Public License for more details. 73 | % 74 | % You should have received a copy of the GNU General Public License 75 | % along with this program; if not, write to the Free Software 76 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 77 | 78 | function [varargout] = pop_xfirws(varargin) 79 | 80 | % Pop-up window mode 81 | if nargin < 1 82 | 83 | drawnow; 84 | ftypes = {'bandpass' 'highpass' 'lowpass' 'bandstop'}; 85 | wtypes = {'rectangular' 'hann' 'hamming' 'blackman' 'kaiser'}; 86 | uigeom = {[1 0.75 0.75] 1 [1 0.75 0.75] [1 0.75 0.75] 1 [1 0.75 0.75] [1 0.75 0.75] [1 0.75 0.75] 1 [1 0.75 0.75]}; 87 | uilist = {{'Style' 'text' 'String' 'Sampling frequency (Hz):'} ... 88 | {'Style' 'edit' 'String' '2' 'Tag' 'srateedit'} {} ... 89 | {} ... 90 | {'Style' 'text' 'String' 'Cutoff frequency(ies) [hp lp] (-6 dB; Hz):'} ... 91 | {'Style' 'edit' 'String' '' 'Tag' 'fcutoffedit'} {} ... 92 | {'Style' 'text' 'String' 'Filter type:'} ... 93 | {'Style' 'popupmenu' 'String' ftypes 'Tag' 'ftypepop'} {} ... 94 | {} ... 95 | {'Style' 'text' 'String' 'Window type:'} ... 96 | {'Style' 'popupmenu' 'String' wtypes 'Tag' 'wtypepop' 'Value' 3 'Callback' 'temp = {''off'', ''on''}; set(findobj(gcbf, ''-regexp'', ''Tag'', ''^warg''), ''Enable'', temp{double(get(gcbo, ''Value'') == 5) + 1}), set(findobj(gcbf, ''Tag'', ''wargedit''), ''String'', '''')'} {} ... 97 | {'Style' 'text' 'String' 'Kaiser window beta:' 'Tag' 'wargtext' 'Enable' 'off'} ... 98 | {'Style' 'edit' 'String' '' 'Tag' 'wargedit' 'Enable' 'off'} ... 99 | {'Style' 'pushbutton' 'String' 'Estimate' 'Tag' 'wargpush' 'Enable' 'off' 'Callback' @comwarg} ... 100 | {'Style' 'text' 'String' 'Filter order (mandatory even):'} ... 101 | {'Style' 'edit' 'String' '' 'Tag' 'forderedit'} ... 102 | {'Style' 'pushbutton' 'String' 'Estimate' 'Callback' {@comforder, wtypes}} ... 103 | {'Style' 'edit' 'Tag' 'devedit' 'Visible' 'off'} ... 104 | {} {} {'Style' 'pushbutton' 'String', 'Plot filter responses' 'Callback' {@comfresp, wtypes, ftypes}}}; 105 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firws'')', 'Filter the data -- pop_firws()'); 106 | if isempty(result), return; end 107 | 108 | Arg = struct; 109 | Arg.srate = str2double(result{1}); 110 | Arg.fcutoff = str2num(result{2}); 111 | Arg.ftype = ftypes{result{3}}; 112 | Arg.wtype = wtypes{result{4}}; 113 | Arg.warg = str2num(result{5}); 114 | Arg.forder = str2double(result{6}); 115 | 116 | % Command line mode 117 | else 118 | Arg = struct(varargin{:}); 119 | end 120 | 121 | % Sampling rate 122 | if ~isfield(Arg, 'srate') || isempty(Arg.srate) % Use default 123 | Arg.srate = 2; 124 | end 125 | 126 | % Filter order and cutoff frequencies 127 | if ~isfield(Arg, 'fcutoff') || ~isfield(Arg, 'forder') || isempty(Arg.fcutoff) || isempty(Arg.forder) 128 | error('Not enough input arguments.'); 129 | end 130 | firwsArgArray = {Arg.forder sort(Arg.fcutoff / Arg.srate * 2)}; % Sorting and normalization 131 | 132 | % Filter type 133 | if ~isfield(Arg, 'ftype') || isempty(Arg.ftype) % Use default 134 | switch length(Arg.fcutoff) 135 | case 1 136 | Arg.ftype = 'lowpass'; 137 | case 2 138 | Arg.ftype = 'bandpass'; 139 | otherwise 140 | error('Wrong number of arguments.') 141 | end 142 | else 143 | if any(strcmpi(Arg.ftype, {'bandpass' 'bandstop'})) && length(Arg.fcutoff) ~= 2 144 | error('Not enough input arguments.'); 145 | elseif any(strcmpi(Arg.ftype, {'highpass' 'lowpass'})) && length(Arg.fcutoff) ~= 1 146 | error('Too many input arguments.'); 147 | end 148 | switch Arg.ftype 149 | case 'bandstop' 150 | firwsArgArray(end + 1) = {'stop'}; 151 | case 'highpass' 152 | firwsArgArray(end + 1) = {'high'}; 153 | end 154 | end 155 | 156 | % Window type 157 | if ~isfield(Arg, 'wtype') || isempty(Arg.wtype) % Use default 158 | Arg.wtype = 'hamming'; 159 | end 160 | 161 | % Window parameter 162 | if ~isfield(Arg, 'warg') || isempty(Arg.warg) 163 | Arg.warg = []; 164 | firwsArgArray(end + 1) = {windows(Arg.wtype, Arg.forder + 1)}; 165 | else 166 | firwsArgArray(end + 1) = {windows(Arg.wtype, Arg.forder + 1, Arg.warg)}; 167 | end 168 | 169 | b = firws(firwsArgArray{:}); 170 | a = 1; 171 | 172 | if nargout == 0 || isfield(Arg, 'filename') 173 | 174 | % Open file 175 | if ~isfield(Arg, 'filename') || isempty(Arg.filename) 176 | [Arg.filename Arg.pathname] = uiputfile('*.fir', 'Save filter -- pop_xfirws'); 177 | end 178 | if ~isfield(Arg, 'pathname') || isempty(Arg.pathname) 179 | Arg.pathname = '.'; 180 | end 181 | [fid message] = fopen(fullfile(Arg.pathname, Arg.filename), 'w', 'l'); 182 | if fid == -1 183 | error(message) 184 | end 185 | 186 | % Author 187 | fprintf(fid, '[author]\n'); 188 | fprintf(fid, '%s\n\n', 'pop_xfirws 2.0'); 189 | 190 | % FIR design 191 | fprintf(fid, '[fir design]\n'); 192 | fprintf(fid, 'method %s\n', 'fourier'); 193 | fprintf(fid, 'type %s\n', Arg.ftype); 194 | fprintf(fid, 'fsample %f\n', Arg.srate); 195 | fprintf(fid, 'length %d\n', Arg.forder + 1); 196 | fprintf(fid, 'fcrit%d %f\n', [1:length(Arg.fcutoff); Arg.fcutoff]); 197 | fprintf(fid, 'window %s %s\n\n', Arg.wtype, num2str(Arg.warg)); % fprintf bug 198 | 199 | % FIR 200 | fprintf(fid, '[fir]\n'); 201 | fprintf(fid, '%d\n', Arg.forder + 1); 202 | fprintf(fid, '% 18.10e\n', b); 203 | 204 | % Close file 205 | fclose(fid); 206 | 207 | end 208 | 209 | if nargout > 0 210 | varargout = {b a}; 211 | end 212 | 213 | % Callback estimate Kaiser beta 214 | function comwarg(varargin) 215 | [warg, dev] = pop_kaiserbeta; 216 | set(findobj(gcbf, 'Tag', 'wargedit'), 'String', warg); 217 | set(findobj(gcbf, 'Tag', 'devedit'), 'String', dev); 218 | 219 | % Callback estimate filter order 220 | function comforder(obj, evt, wtypes) 221 | srate = str2double(get(findobj(gcbf, 'Tag', 'srateedit'), 'String')); 222 | wtype = wtypes{get(findobj(gcbf, 'Tag', 'wtypepop'), 'Value')}; 223 | dev = str2double(get(findobj(gcbf, 'Tag', 'devedit'), 'String')); 224 | [forder, dev] = pop_firwsord(wtype, srate, [], dev); 225 | set(findobj(gcbf, 'Tag', 'forderedit'), 'String', forder); 226 | set(findobj(gcbf, 'Tag', 'devedit'), 'String', dev); 227 | 228 | % Callback plot filter responses 229 | function comfresp(obj, evt, wtypes, ftypes) 230 | Arg.srate = str2double(get(findobj(gcbf, 'Tag', 'srateedit'), 'String')); 231 | Arg.fcutoff = str2num(get(findobj(gcbf, 'Tag', 'fcutoffedit'), 'String')); 232 | Arg.ftype = ftypes{get(findobj(gcbf, 'Tag', 'ftypepop'), 'Value')}; 233 | Arg.wtype = wtypes{get(findobj(gcbf, 'Tag', 'wtypepop'), 'Value')}; 234 | Arg.warg = str2num(get(findobj(gcbf, 'Tag', 'wargedit'), 'String')); 235 | Arg.forder = str2double(get(findobj(gcbf, 'Tag', 'forderedit'), 'String')); 236 | xfirwsArgArray(1, :) = fieldnames(Arg); 237 | xfirwsArgArray(2, :) = struct2cell(Arg); 238 | [b a] = pop_xfirws(xfirwsArgArray{:}); 239 | H = findobj('Tag', 'plotfiltresp', 'type', 'figure'); 240 | if ~isempty(H) 241 | figure(H); 242 | else 243 | H = figure; 244 | set(H, 'color', [.93 .96 1], 'Tag', 'plotfiltresp'); 245 | end 246 | plotfresp(b, a, [], Arg.srate, 'onepass-zerophase'); 247 | -------------------------------------------------------------------------------- /pop_firws.m: -------------------------------------------------------------------------------- 1 | % pop_firws() - Filter data using windowed sinc FIR filter 2 | % 3 | % Usage: 4 | % >> [EEG, com, b] = pop_firws(EEG); % pop-up window mode 5 | % >> [EEG, com, b] = pop_firws(EEG, 'key1', value1, 'key2', ... 6 | % value2, 'keyn', valuen); 7 | % 8 | % Inputs: 9 | % EEG - EEGLAB EEG structure 10 | % 'fcutoff' - vector or scalar of cutoff frequency/ies (-6 dB; Hz) 11 | % 'forder' - scalar filter order. Mandatory even 12 | % 13 | % Optional inputs: 14 | % 'ftype' - char array filter type. 'bandpass', 'highpass', 15 | % 'lowpass', or 'bandstop' {default 'bandpass' or 16 | % 'lowpass', depending on number of cutoff frequencies} 17 | % 'wtype' - char array window type. 'rectangular', 'hann', 18 | % 'hamming', 'blackman', or 'kaiser' {default 'hamming'} 19 | % 'warg' - scalar kaiser beta 20 | % 'minphase' - scalar boolean minimum-phase converted causal filter 21 | % {default false} 22 | % 'usefftfilt' - scalar boolean use fftfilt frequency domain filtering 23 | % {default false} 24 | % 'plotfresp' - scalar boolean plot filter responses {default false} 25 | % 26 | % Outputs: 27 | % EEG - filtered EEGLAB EEG structure 28 | % com - history string 29 | % b - filter coefficients 30 | % 31 | % Note: 32 | % Window based filters' transition band width is defined by filter 33 | % order and window type/parameters. Stopband attenuation equals 34 | % passband ripple and is defined by the window type/parameters. Refer 35 | % to table below for typical parameters. (Windowed sinc) symmetric FIR 36 | % filters have linear phase and can be made zero phase (non-causal) by 37 | % shifting the data by the filters group delay (what firfilt does by 38 | % default). Pi phase jumps noticable in the phase reponse reflect a 39 | % negative frequency response and only occur in the stopband. pop_firws 40 | % also allows causal filtering with minimum-phase (non-linear!) converted 41 | % filter coefficients with similar properties. Non-linear causal 42 | % filtering is NOT recommended for most use cases. 43 | % 44 | % Beta Max stopband Max passband Max passband Transition width Mainlobe width 45 | % attenuation deviation ripple (dB) (normalized freq) (normalized rad freq) 46 | % (dB) 47 | % Rectangular -21 0.0891 1.552 0.9 / m* 4 * pi / m 48 | % Hann -44 0.0063 0.109 3.1 / m 8 * pi / m 49 | % Hamming -53 0.0022 0.038 3.3 / m 8 * pi / m 50 | % Blackman -74 0.0002 0.003 5.5 / m 12 * pi / m 51 | % Kaiser 5.653 -60 0.001 0.017 3.6 / m 52 | % Kaiser 7.857 -80 0.0001 0.002 5.0 / m 53 | % * m = filter order 54 | % 55 | % Author: Andreas Widmann, University of Leipzig, 2005 56 | % 57 | % See also: 58 | % firfilt, firws, pop_firwsord, pop_kaiserbeta, plotfresp, windows 59 | 60 | %123456789012345678901234567890123456789012345678901234567890123456789012 61 | 62 | % Copyright (C) 2005 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 63 | % 64 | % This program is free software; you can redistribute it and/or modify 65 | % it under the terms of the GNU General Public License as published by 66 | % the Free Software Foundation; either version 2 of the License, or 67 | % (at your option) any later version. 68 | % 69 | % This program is distributed in the hope that it will be useful, 70 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 71 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 72 | % GNU General Public License for more details. 73 | % 74 | % You should have received a copy of the GNU General Public License 75 | % along with this program; if not, write to the Free Software 76 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 77 | 78 | function [EEG, com, b] = pop_firws(EEG, varargin) 79 | 80 | com = ''; 81 | if nargin < 1 82 | help pop_firws; 83 | return; 84 | end 85 | if isempty(EEG.data) 86 | error('Cannot process empty dataset'); 87 | end 88 | 89 | if nargin < 2 90 | drawnow; 91 | ftypes = {'bandpass', 'highpass', 'lowpass', 'bandstop'}; 92 | ftypesStr = {'Bandpass', 'Highpass', 'Lowpass', 'Bandstop'}; 93 | wtypes = {'rectangular', 'hann', 'hamming', 'blackman', 'kaiser'}; 94 | wtypesStr = {'Rectangular (PB dev=0.089, SB att=-21dB)', 'Hann (PB dev=0.006, SB att=-44dB)', 'Hamming (PB dev=0.002, SB att=-53dB)', 'Blackman (PB dev=0.0002, SB att=-74dB)', 'Kaiser'}; 95 | uigeom = {[1 0.75 0.75] [1 0.75 0.75] 1 [1 0.75 0.75] [1 0.75 0.75] [1 0.75 0.75] [1 1.5] [1 1.5] 1 [1 0.75 0.75]}; 96 | uilist = {{'Style' 'text' 'String' 'Cutoff frequency(ies) [hp lp] (-6 dB; Hz):'} ... 97 | {'Style' 'edit' 'String' '' 'Tag' 'fcutoffedit'} {} ... 98 | {'Style' 'text' 'String' 'Filter type:'} ... 99 | {'Style' 'popupmenu' 'String' ftypesStr 'Tag' 'ftypepop'} {} ... 100 | {} ... 101 | {'Style' 'text' 'String' 'Window type:'} ... 102 | {'Style' 'popupmenu' 'String' wtypesStr 'Tag' 'wtypepop' 'Value' 3 'Callback' 'temp = {''off'', ''on''}; set(findobj(gcbf, ''-regexp'', ''Tag'', ''^warg''), ''Enable'', temp{double(get(gcbo, ''Value'') == 5) + 1}), set(findobj(gcbf, ''Tag'', ''wargedit''), ''String'', '''')'} {} ... 103 | {'Style' 'text' 'String' 'Kaiser window beta:' 'Tag' 'wargtext' 'Enable' 'off'} ... 104 | {'Style' 'edit' 'String' '' 'Tag' 'wargedit' 'Enable' 'off'} ... 105 | {'Style' 'pushbutton' 'String' 'Estimate' 'Tag' 'wargpush' 'Enable' 'off' 'Callback' @comwarg} ... 106 | {'Style' 'text' 'String' 'Filter order (mandatory even):'} ... 107 | {'Style' 'edit' 'String' '' 'Tag' 'forderedit'} ... 108 | {'Style' 'pushbutton' 'String' 'Estimate' 'Callback' {@comforder, wtypes, EEG.srate}} ... 109 | {} {'Style' 'checkbox', 'String', 'Use minimum-phase converted causal filter (non-linear!)', 'Tag' 'minphase', 'Value', 0} ... 110 | {} {'Style' 'checkbox', 'String', 'Use frequency domain filtering (faster for high filter orders > ~2000)', 'Tag' 'usefftfilt', 'Value', 0} ... 111 | {'Style' 'edit' 'Tag' 'devedit' 'Visible' 'off'} ... 112 | {} {} {'Style' 'pushbutton' 'String', 'Plot filter responses' 'Callback' {@comfresp, wtypes, ftypes, EEG.srate}}}; 113 | result = inputgui(uigeom, uilist, 'pophelp(''pop_firws'')', 'Filter the data -- pop_firws()'); 114 | if isempty(result), return; end 115 | 116 | Args = {}; 117 | if ~isempty(result{1}) 118 | Args = [Args {'fcutoff'} {str2num(result{1})}]; 119 | end 120 | Args = [Args {'ftype'} ftypes(result{2})]; 121 | Args = [Args {'wtype'} wtypes(result{3})]; 122 | if ~isempty(result{4}) 123 | Args = [Args {'warg'} {str2double(result{4})}]; 124 | end 125 | if ~isempty(result{5}) 126 | Args = [Args {'forder'} {str2double(result{5})}]; 127 | end 128 | Args = [Args {'minphase'} result{6}]; 129 | Args = [Args {'usefftfilt'} result{7}]; 130 | else 131 | Args = varargin; 132 | end 133 | 134 | % Convert args to structure 135 | Args = struct(Args{:}); 136 | 137 | c = parseargs(Args, EEG.srate); 138 | b = firws(c{:}); 139 | 140 | % Defaults 141 | if ~isfield(Args, 'minphase') || isempty(Args.minphase) 142 | Args.minphase = 0; 143 | end 144 | if ~isfield(Args, 'usefftfilt') || isempty(Args.usefftfilt) 145 | Args.usefftfilt = 0; 146 | end 147 | if ~isfield(Args, 'plotfresp') || isempty(Args.plotfresp) 148 | Args.plotfresp = 0; 149 | end 150 | 151 | % Prepare reporting 152 | if ~isfield(Args, 'ftype') || isempty(Args.ftype) 153 | if length(Args.fcutoff) == 1, ftype = 'lowpass'; else ftype = 'bandpass'; end 154 | else 155 | ftype = Args.ftype; 156 | end 157 | if Args.minphase, dir = 'onepass-minphase'; else dir = 'onepass-zerophase'; end 158 | if ~isfield(Args, 'wtype') || isempty(Args.wtype), Args.wtype = 'hamming'; else wtype = Args.wtype; end 159 | if strcmp(wtype, 'kaiser'), dev = invkaiserbeta(Args.warg); else dev = []; end 160 | [df, dev] = invfirwsord(wtype, EEG.srate, Args.forder, dev); 161 | 162 | % Check for low filter order and report 163 | maxDf = min( [ Args.fcutoff * 2, ( EEG.srate / 2 - Args.fcutoff ) * 2, diff( sort( Args.fcutoff ) ) ] ); 164 | if df > maxDf 165 | nOpt = firwsord(wtype, EEG.srate, maxDf, dev); 166 | warning('firfilt:filterOrderLow', 'Filter order too low. For better results a minimum filter order of %d is recommended. Effective cutoff frequency might deviate from requested cutoff frequency.', nOpt) 167 | firfiltreport('func', mfilename, 'family', [wtype '-windowed sinc FIR'], 'type', ftype, 'dir', dir, 'order', Args.forder) 168 | else 169 | firfiltreport('func', mfilename, 'family', [wtype '-windowed sinc FIR'], 'type', ftype, 'dir', dir, 'order', Args.forder, 'fs', EEG.srate, 'fc', Args.fcutoff, 'df', df, 'pbdev', dev, 'sbatt', dev) 170 | end 171 | 172 | % Filter 173 | if Args.minphase 174 | b = minphaserceps(b); 175 | Args.causal = 1; 176 | else 177 | Args.causal = 0; 178 | end 179 | if Args.minphase || Args.usefftfilt % New code path 180 | EEG = firfiltsplit(EEG, b, Args.causal, Args.usefftfilt); 181 | else % Old code path 182 | EEG = firfilt(EEG, b); 183 | end 184 | 185 | % Plot filter responses 186 | if Args.plotfresp 187 | plotfresp(b, 1, [], EEG.srate, dir); 188 | end 189 | 190 | % History string 191 | com = sprintf('%s = pop_firws(%s', inputname(1), inputname(1)); 192 | for c = fieldnames(Args)' 193 | if ischar(Args.(c{:})) 194 | com = [com sprintf(', ''%s'', ''%s''', c{:}, Args.(c{:}))]; 195 | else 196 | com = [com sprintf(', ''%s'', %s', c{:}, mat2str(Args.(c{:})))]; 197 | end 198 | end 199 | com = [com ');']; 200 | 201 | % Convert structure args to cell array firws parameters 202 | function c = parseargs(Args, srate) 203 | 204 | % Filter order and cutoff frequencies 205 | if ~isfield(Args, 'fcutoff') || ~isfield(Args, 'forder') || isempty(Args.fcutoff) || isempty(Args.forder) 206 | error('Not enough input arguments.'); 207 | end 208 | c = [{Args.forder} {sort(Args.fcutoff / (srate / 2))}]; % Sorting and normalization 209 | 210 | % Filter type 211 | if isfield(Args, 'ftype') && ~isempty(Args.ftype) 212 | if (strcmpi(Args.ftype, 'bandpass') || strcmpi(Args.ftype, 'bandstop')) && length(Args.fcutoff) ~= 2 213 | error('Not enough input arguments.'); 214 | elseif (strcmpi(Args.ftype, 'highpass') || strcmpi(Args.ftype, 'lowpass')) && length(Args.fcutoff) ~= 1 215 | error('Too many input arguments.'); 216 | end 217 | switch Args.ftype 218 | case 'bandstop' 219 | c = [c {'stop'}]; 220 | case 'highpass' 221 | c = [c {'high'}]; 222 | end 223 | end 224 | 225 | % Window type 226 | if isfield(Args, 'wtype') && ~isempty(Args.wtype) 227 | if strcmpi(Args.wtype, 'kaiser') 228 | if isfield(Args, 'warg') && ~isempty(Args.warg) 229 | c = [c {windows(Args.wtype, Args.forder + 1, Args.warg)'}]; 230 | else 231 | error('Not enough input arguments.'); 232 | end 233 | else 234 | c = [c {windows(Args.wtype, Args.forder + 1)'}]; 235 | end 236 | end 237 | 238 | % Callback estimate Kaiser beta 239 | function comwarg(varargin) 240 | [warg, dev] = pop_kaiserbeta; 241 | set(findobj(gcbf, 'Tag', 'wargedit'), 'String', warg); 242 | set(findobj(gcbf, 'Tag', 'devedit'), 'String', dev); 243 | 244 | % Callback estimate filter order 245 | function comforder(obj, evt, wtypes, srate) 246 | wtype = wtypes{get(findobj(gcbf, 'Tag', 'wtypepop'), 'Value')}; 247 | dev = get(findobj(gcbf, 'Tag', 'devedit'), 'String'); 248 | [forder, dev] = pop_firwsord(wtype, srate, [], dev); 249 | set(findobj(gcbf, 'Tag', 'forderedit'), 'String', forder); 250 | set(findobj(gcbf, 'Tag', 'devedit'), 'String', dev); 251 | 252 | % Callback plot filter responses 253 | function comfresp(obj, evt, wtypes, ftypes, srate) 254 | Args.fcutoff = str2num(get(findobj(gcbf, 'Tag', 'fcutoffedit'), 'String')); 255 | Args.ftype = ftypes{get(findobj(gcbf, 'Tag', 'ftypepop'), 'Value')}; 256 | Args.wtype = wtypes{get(findobj(gcbf, 'Tag', 'wtypepop'), 'Value')}; 257 | Args.warg = str2num(get(findobj(gcbf, 'Tag', 'wargedit'), 'String')); 258 | Args.forder = str2double(get(findobj(gcbf, 'Tag', 'forderedit'), 'String')); 259 | Args.minphase = get(findobj(gcbf, 'Tag', 'minphase'), 'Value'); 260 | c = parseargs(Args, srate); 261 | b = firws(c{:}); 262 | if Args.minphase 263 | b = minphaserceps(b); 264 | dir = 'onepass-minphase'; 265 | else 266 | dir = 'onepass-zerophase'; 267 | end 268 | H = findobj('Tag', 'plotfiltresp', 'type', 'figure'); 269 | if ~isempty(H) 270 | figure(H); 271 | else 272 | H = figure; 273 | set(H, 'color', [.93 .96 1], 'Tag', 'plotfiltresp'); 274 | end 275 | plotfresp(b, 1, [], srate, dir); 276 | -------------------------------------------------------------------------------- /pop_eegfiltnew.m: -------------------------------------------------------------------------------- 1 | % pop_eegfiltnew() - Filter data using Hamming windowed sinc FIR filter 2 | % 3 | % Usage: 4 | % >> [EEG, com, b] = pop_eegfiltnew(EEG); % pop-up window mode 5 | % >> [EEG, com, b] = pop_eegfiltnew(EEG, 'key', val); 6 | % Legacy call: 7 | % >> [EEG, com, b] = pop_eegfiltnew(EEG, locutoff, hicutoff, filtorder, 8 | % revfilt, usefft, plotfreqz, minphase, 9 | % usefftfilt); 10 | % 11 | % Inputs: 12 | % EEG - EEGLAB EEG structure 13 | % 14 | % Optional inputs: 15 | % 'locutoff' - [float] lower edge of the frequency pass band (Hz) 16 | % {[]/0 -> lowpass} 17 | % 'hicutoff' - [float] higher edge of the frequency pass band (Hz) 18 | % {[]/0 -> highpass} 19 | % 'filtorder' - filter order (filter length - 1). Mandatory even 20 | % 'revfilt' - [0|1] invert filter (from bandpass to notch filter) 21 | % {default 0 (bandpass)} 22 | % 'usefft' - [0|1] ignored (backward compatibility only) 23 | % 'plotfreqz' - [0|1] plot filter's frequency and phase response 24 | % {default 0} 25 | % 'minphase' - scalar boolean minimum-phase converted causal filter 26 | % {default false} 27 | % 'usefftfilt' - [0|1] scalar boolean use fftfilt frequency domain filtering 28 | % {default false or 0} 29 | % 30 | % Outputs: 31 | % EEG - filtered EEGLAB EEG structure 32 | % com - history string 33 | % b - filter coefficients 34 | % 35 | % Note: 36 | % pop_eegfiltnew is intended as a replacement for the deprecated 37 | % pop_eegfilt function. Required filter order/transition band width is 38 | % estimated with the following heuristic in default mode: transition band 39 | % width is 25% of the lower passband edge, but not lower than 2 Hz, where 40 | % possible (for bandpass, highpass, and bandstop) and distance from 41 | % passband edge to critical frequency (DC, Nyquist) otherwise. Window 42 | % type is hardcoded to Hamming. Migration to windowed sinc FIR filters 43 | % (pop_firws) is recommended. pop_firws allows user defined window type 44 | % and estimation of filter order by user defined transition band width. 45 | % 46 | % Author: Andreas Widmann, University of Leipzig, 2012 47 | % Arnaud Delorme, UCSD, CNRS 48 | % 49 | % See also: 50 | % firfilt, firws, windows 51 | 52 | %123456789012345678901234567890123456789012345678901234567890123456789012 53 | 54 | % Copyright (C) 2008 Andreas Widmann, University of Leipzig, widmann@uni-leipzig.de 55 | % 56 | % This program is free software; you can redistribute it and/or modify 57 | % it under the terms of the GNU General Public License as published by 58 | % the Free Software Foundation; either version 2 of the License, or 59 | % (at your option) any later version. 60 | % 61 | % This program is distributed in the hope that it will be useful, 62 | % but WITHOUT ANY WARRANTY; without even the implied warranty of 63 | % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 64 | % GNU General Public License for more details. 65 | % 66 | % You should have received a copy of the GNU General Public License 67 | % along with this program; if not, write to the Free Software 68 | % Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 69 | 70 | function [EEG, com, b] = pop_eegfiltnew(EEG, varargin) 71 | 72 | com = ''; 73 | 74 | if nargin < 1 75 | help pop_eegfiltnew; 76 | return 77 | end 78 | if isempty(EEG(1).data) 79 | error('Cannot filter empty dataset.'); 80 | end 81 | 82 | % GUI 83 | if nargin < 2 84 | 85 | geometry = {[3 1], [3 1], [3 1], 1, 1, 1, 1, [2 1.5 0.5], [2 1.5 0.5], 1}; 86 | geomvert = [1 1 1 2 1 1 1 1 1 1]; 87 | 88 | cb_type = 'pop_chansel(get(gcbf, ''userdata''), ''field'', ''type'', ''handle'', findobj(''parent'', gcbf, ''tag'', ''chantypes''));'; 89 | cb_chan = 'pop_chansel(get(gcbf, ''userdata''), ''field'', ''labels'', ''handle'', findobj(''parent'', gcbf, ''tag'', ''channels''));'; 90 | 91 | uilist = {{'style', 'text', 'string', 'Lower edge of the frequency pass band (Hz)'} ... 92 | {'style', 'edit', 'string', ''} ... 93 | {'style', 'text', 'string', 'Higher edge of the frequency pass band (Hz)'} ... 94 | {'style', 'edit', 'string', ''} ... 95 | {'style', 'text', 'string', 'FIR Filter order (Mandatory even. Default is automatic*)'} ... 96 | {'style', 'edit', 'string', ''} ... 97 | {'style', 'text', 'string', {'*See help text for a description of the default filter order heuristic.', 'Manual definition is recommended.'}} ... 98 | {'style', 'checkbox', 'string', 'Notch filter the data instead of pass band', 'value', 0} ... 99 | {'Style', 'checkbox', 'String', 'Use minimum-phase converted causal filter (non-linear!; beta)', 'Value', 0} ... 100 | {'style', 'checkbox', 'string', 'Plot frequency response', 'value', 1} ... 101 | { 'style' 'text' 'string' 'Channel type(s)' } ... 102 | { 'style' 'edit' 'string' '' 'tag' 'chantypes'} ... 103 | { 'style' 'pushbutton' 'string' '...' 'callback' cb_type } ... 104 | { 'style' 'text' 'string' 'OR channel labels or indices' } ... 105 | { 'style' 'edit' 'string' '' 'tag' 'channels' } ... 106 | { 'style' 'pushbutton' 'string' '...' 'callback' cb_chan } ... 107 | {'Style' 'checkbox', 'String', 'Use frequency domain filtering (faster for high filter orders > ~2000)', 'Tag' 'usefftfilt', 'Value', 0} ... 108 | }; 109 | 110 | % channel labels 111 | % -------------- 112 | if ~isempty(EEG(1).chanlocs) 113 | tmpchanlocs = EEG(1).chanlocs; 114 | else 115 | tmpchanlocs = []; 116 | for index = 1:EEG(1).nbchan 117 | tmpchanlocs(index).labels = int2str(index); 118 | tmpchanlocs(index).type = ''; 119 | end 120 | end 121 | 122 | result = inputgui('geometry', geometry, 'geomvert', geomvert, 'uilist', uilist, 'title', 'Filter the data -- pop_eegfiltnew()', 'helpcom', 'pophelp(''pop_eegfiltnew'')', 'userdata', tmpchanlocs); 123 | 124 | if isempty(result), return; end 125 | options = {}; 126 | if ~isempty(result{1}), options = { options{:} 'locutoff' str2num( result{1}) }; end 127 | if ~isempty(result{2}), options = { options{:} 'hicutoff' str2num( result{2}) }; end 128 | if ~isempty(result{3}), options = { options{:} 'filtorder' str2num( result{3}) }; end 129 | if result{4}, options = { options{:} 'revfilt' result{4} }; end 130 | if result{5}, options = { options{:} 'minphase' result{5} }; end 131 | if result{6}, options = { options{:} 'plotfreqz' result{6} }; end 132 | if ~isempty(result{7} ), options = { options{:} 'chantype' parsetxt(result{7}) }; end 133 | if ~isempty(result{8}) && isempty( result{7} ) 134 | [ chaninds, chanlist ] = eeg_decodechan(EEG(1).chanlocs, result{8}); 135 | if isempty(chanlist), chanlist = chaninds; end 136 | options = { options{:}, 'channels' chanlist }; 137 | end 138 | if result{9}, options = { options{:} 'usefftfilt' result{9} }; end 139 | elseif ~ischar(varargin{1}) 140 | % backward compatibility 141 | options = {}; 142 | if nargin > 1, options = { options{:} 'locutoff' varargin{1} }; end 143 | if nargin > 2, options = { options{:} 'hicutoff' varargin{2} }; end 144 | if nargin > 3, options = { options{:} 'filtorder' varargin{3} }; end 145 | if nargin > 4, options = { options{:} 'revfilt' varargin{4} }; end 146 | if nargin > 5, options = { options{:} 'usefft' varargin{5} }; end 147 | if nargin > 6, options = { options{:} 'plotfreqz' varargin{6} }; end 148 | if nargin > 7, options = { options{:} 'minphase' varargin{7} }; end 149 | if nargin > 8, options = { options{:} 'usefftfilt' varargin{8} }; end 150 | else 151 | options = varargin; 152 | end 153 | 154 | % process multiple datasets 155 | % ------------------------- 156 | if length(EEG) > 1 157 | if nargin < 2 158 | [ EEG, com ] = eeg_eval( 'pop_eegfiltnew', EEG, 'warning', 'on', 'params', options ); 159 | else 160 | [ EEG, com ] = eeg_eval( 'pop_eegfiltnew', EEG, 'params', options ); 161 | end 162 | return; 163 | end 164 | 165 | % decode inputs 166 | % ------------- 167 | fieldlist = { 'locutoff' 'real' [] []; 168 | 'hicutoff' 'real' [] []; 169 | 'filtorder' 'integer' [] []; 170 | 'revfilt' 'integer' [0 1] 0; 171 | 'usefft' 'integer' [0 1] 0; 172 | 'usefftfilt' 'integer' [0 1] 0; 173 | 'minphase' 'integer' [0 1] 0; 174 | 'plotfreqz' 'integer' [0 1] 0; 175 | 'channels' {'cell' 'string' 'integer' } [] {}; 176 | 'chantype' {'cell' 'string'} [] {} }; 177 | g = finputcheck( options, fieldlist, 'pop_eegfiltnew'); 178 | if ischar(g), error(g); end 179 | if isempty(g.minphase), g.minphase = 0; end 180 | if ~isempty(g.chantype) 181 | g.channels = eeg_decodechan(EEG.chanlocs, g.chantype, 'type'); 182 | elseif ~isempty(g.channels) 183 | g.channels = eeg_decodechan(EEG.chanlocs, g.channels); 184 | else 185 | g.channels = [1:EEG.nbchan]; 186 | end 187 | if g.usefft 188 | error('FFT filtering not supported. Argument is provided for backward compatibility in command line mode only.') 189 | end 190 | 191 | % Constants 192 | TRANSWIDTHRATIO = 0.25; 193 | fNyquist = EEG.srate / 2; 194 | 195 | % Check arguments 196 | if g.locutoff == 0, g.locutoff = []; end 197 | if g.hicutoff == 0, g.hicutoff = []; end 198 | if isempty(g.hicutoff) % Convert highpass to inverted lowpass 199 | g.hicutoff = g.locutoff; 200 | g.locutoff = []; 201 | g.revfilt = ~g.revfilt; 202 | end 203 | edgeArray = sort([g.locutoff g.hicutoff]); 204 | 205 | if isempty(edgeArray) 206 | error('Not enough input arguments.'); 207 | end 208 | if any(edgeArray < 0 | edgeArray >= fNyquist) 209 | error('Cutoff frequency out of range'); 210 | end 211 | 212 | if ~isempty(g.filtorder) && (g.filtorder < 2 || mod(g.filtorder, 2) ~= 0) 213 | error('Filter order must be a real, even, positive integer.') 214 | end 215 | 216 | % Max stop-band width 217 | maxTBWArray = edgeArray; % Band-/highpass 218 | if g.revfilt == 0 % Band-/lowpass 219 | maxTBWArray(end) = fNyquist - edgeArray(end); 220 | elseif length(edgeArray) == 2 % Bandstop 221 | maxTBWArray = diff(edgeArray) / 2; 222 | end 223 | maxDf = min(maxTBWArray); 224 | 225 | % Transition band width and filter order 226 | if isempty(g.filtorder) 227 | 228 | % Default filter order heuristic 229 | if g.revfilt == 1 % Highpass and bandstop 230 | df = min([max([maxDf * TRANSWIDTHRATIO 2]) maxDf]); 231 | else % Lowpass and bandpass 232 | df = min([max([edgeArray(1) * TRANSWIDTHRATIO 2]) maxDf]); 233 | end 234 | 235 | g.filtorder = 3.3 / (df / EEG.srate); % Hamming window 236 | g.filtorder = ceil(g.filtorder / 2) * 2; % Filter order must be even. 237 | 238 | df = 3.3 / g.filtorder * EEG.srate; % Correct reported df in case filter order was adjusted. 239 | 240 | else 241 | 242 | df = 3.3 / g.filtorder * EEG.srate; % Hamming window 243 | g.filtorderMin = ceil(3.3 ./ ((maxDf * 2) / EEG.srate) / 2) * 2; 244 | g.filtorderOpt = ceil(3.3 ./ (maxDf / EEG.srate) / 2) * 2; 245 | if g.filtorder < g.filtorderMin 246 | error('Filter order too low. Minimum required filter order is %d.\nFor better results a minimum filter order of %d is recommended.', g.filtorderMin, g.filtorderOpt) 247 | elseif g.filtorder < g.filtorderOpt 248 | warning('firfilt:filterOrderLow', 'Transition band is wider than maximum stop-band width. For better results a minimum filter order of %d is recommended. Reported might deviate from effective -6dB cutoff frequency.', g.filtorderOpt) 249 | end 250 | 251 | end 252 | 253 | filterTypeArray = {'lowpass', 'bandpass'; 'highpass', 'bandstop (notch)'}; 254 | fprintf('pop_eegfiltnew() - performing %d point %s filtering.\n', g.filtorder + 1, filterTypeArray{g.revfilt + 1, length(edgeArray)}) 255 | fprintf('pop_eegfiltnew() - transition band width: %s Hz\n', mat2str(df)) 256 | fprintf('pop_eegfiltnew() - passband edge(s): %s Hz\n', mat2str(edgeArray)) 257 | 258 | % Passband edge to cutoff (transition band center; -6 dB) 259 | dfArray = {df, [-df, df]; -df, [df, -df]}; 260 | cutoffArray = edgeArray + dfArray{g.revfilt + 1, length(edgeArray)} / 2; 261 | fprintf('pop_eegfiltnew() - cutoff frequency(ies) (-6 dB): %s Hz\n', mat2str(cutoffArray)) 262 | 263 | % Window 264 | winArray = windows('hamming', g.filtorder + 1); 265 | 266 | % Filter coefficients 267 | if g.revfilt == 1 268 | filterTypeArray = {'high', 'stop'}; 269 | b = firws(g.filtorder, cutoffArray / fNyquist, filterTypeArray{length(cutoffArray)}, winArray); 270 | else 271 | b = firws(g.filtorder, cutoffArray / fNyquist, winArray); 272 | end 273 | 274 | if g.minphase 275 | disp('pop_eegfiltnew() - converting filter to minimum-phase (non-linear!)'); 276 | b = minphaserceps(b); 277 | causal = 1; 278 | dir = '(causal)'; 279 | else 280 | causal = 0; 281 | dir = '(zero-phase, non-causal)'; 282 | end 283 | 284 | % Plot frequency response 285 | if g.plotfreqz 286 | try 287 | nFFT = max([8192 2 ^ ceil(log2((EEG.srate / 2) / maxDf))]); 288 | figure; freqz(b, 1, nFFT, EEG.srate); 289 | catch 290 | warning( 'Plotting of frequency response requires signal processing toolbox.' ) 291 | end 292 | end 293 | 294 | % Filter 295 | if g.minphase || g.usefftfilt 296 | disp(['pop_eegfiltnew() - filtering the data ' dir]); 297 | EEG = firfiltsplit(EEG, b, causal, g.usefftfilt, g.channels); 298 | else 299 | disp(['pop_eegfiltnew() - filtering the data ' dir]); 300 | EEG = firfilt(EEG, b, [], g.channels); 301 | end 302 | 303 | % History string 304 | com = sprintf('EEG = pop_eegfiltnew(EEG, %s);', vararg2str(options)); 305 | 306 | end 307 | --------------------------------------------------------------------------------