├── 0_introduction ├── 1_matlabIntro_plotting.m ├── 1_matlabIntro_variables.m ├── 2_indexing.m ├── 3_timedomain.m ├── 4_topomaps.m ├── amsterdam.bmp └── sampleEEGdata.mat ├── 1_discrete-time_Fourier_transform ├── 1_sinewave.m ├── 2_fourier.m ├── 3_complexfourier.m ├── 4_frequencies.m └── 5_fftstationarity.m ├── 2_Morlet_wavelet_convolution ├── 1_convolutionTime.m ├── 2_morletWavelet.m ├── 3_convolutionAsFreqMult.m ├── 4_eulers_and_convolution.m ├── 5_convolution_with_many_trials.m ├── 6_waveletparams.m ├── 7_inter_trial_phase_clustering.m ├── 8_nonphaselocked.m └── my_script_check_reversed_signal.m ├── 3_TF_analysis_other_methods ├── 1_hilbertX.m ├── 2_firfilter.m ├── 3_stfft.m ├── 4_multitaper.m └── 5_freqslide.m ├── 4_Normalization_and_TF_postprocessing ├── 1_powerLawTF.m ├── 2_dbpct.m ├── 3_whichbaseline.m ├── 4_postanalysis_ds.m ├── 5_loglinTF.m └── 6_units.m ├── 5_Data_preprocessing_and_cleaning ├── 1_laplacian.m └── laplacian_perrinX.m ├── 6_Connectivity ├── 1_phase_connectivity.m └── 2_powerconn.m ├── 7_Basic_statistics ├── 1_permutationTesting.m ├── 2_groupFX.m ├── s_tf │ ├── s1_tf.mat │ ├── s2_tf.mat │ ├── s3_tf.mat │ ├── s4_tf.mat │ ├── s5_tf.mat │ └── s6_tf.mat ├── statistics_file.txt ├── statistics_file_subjectSpecific.txt └── v1_laminar.mat └── README.md /0_introduction/1_matlabIntro_plotting.m: -------------------------------------------------------------------------------- 1 | %% Analyzing Neural Time Series Data 2 | % Matlab code for Chapter 4 script B 3 | % Mike X Cohen 4 | % 5 | % This code accompanies the book, titled "Analyzing Neural Time Series Data" 6 | % (MIT Press). Using the code without following the book may lead to confusion, 7 | % incorrect data analyses, and misinterpretations of results. 8 | % Mike X Cohen assumes no responsibility for inappropriate or incorrect use of this code. 9 | 10 | %% Basic plotting 11 | 12 | % Matlab visual windows are called figures. Make a new figure with the command figure. 13 | 14 | figure % opens a new figures 15 | plot(1:10,(1:10).^2); % plot X by Y 16 | 17 | % run this line after the previous one. note that it overwrites the 18 | % previous plot 19 | plot(1:10,log(1:10)) 20 | 21 | % now try this: 22 | plot(1:10,(1:10).^2,'linewidth',3); 23 | hold on % this command enables overwriting 24 | plot(1:10,log(1:10)*30,'r-d') % plot in red and with thicker lines. type "help plot" to learn more 25 | 26 | % Drawing a line is simple, but can be a bit tricky at first. You need to 27 | % define the start and end points in the X and Y (and also Z if you are 28 | % plotting in 3D) axes: 29 | plot([2 9],[60 60],'k') 30 | plot([1 10],[0 100],'m:') 31 | 32 | % now release the hold, and plot something else 33 | hold off 34 | plot(1:10,(1:10)*3) 35 | 36 | % Of course, you can plot the information in variables: 37 | x = 0:.1:1; 38 | y = exp(x); 39 | plot(x,y,'.-') 40 | 41 | % x and y need to be of equal length, otherwise you'll get an error: 42 | x = 0:.1:1; 43 | y = [0 exp(x)]; 44 | plot(x,y,'.-') 45 | 46 | % you can plot multiple lines simultaneously if they are in a matrix 47 | clf % stands for clear-figure 48 | plot(100:100:1000,rand(10,3)) 49 | % now let's add some extra features... 50 | title('Random lines') 51 | xlabel('x-axis label... maybe time? maybe space?') 52 | ylabel('voltage (\muV)') % note that the "\mu" is converted to the greek lower-case character 53 | legend({'line 1';'line 2';'line 3'}) % this is a cell array! 54 | 55 | % close a figure: 56 | close 57 | 58 | % if you know the figure number, or have a handle to it (we'll get to this 59 | % later), you can also open and close specific figures. 60 | figure(10) 61 | figure(100) 62 | figure(103) 63 | 64 | close([100 ... An elipse at the end of a line allows you to write comments and continue the code on the next line. This is convenient for long lines of code that you want to be visible on a single screen without using the horizontal scrollbar. 65 | 103]) 66 | 67 | %% plotting lines in 3D space 68 | 69 | % If you feel constrained by two dimensions, fear not! Matlab also allows you to 70 | % plot data in three dimensions. This becomes useful in chapters 11-13 when 71 | % introducing complex wavelets. 72 | 73 | % Plotting a line in a 3D space is easy. First, define data in 3 dimensions. 74 | n = 10; 75 | dataX = rand(1,n); 76 | dataY = randn(1,n); 77 | dataZ = rand(1,n)*10; 78 | 79 | % Now simply use plot3 the same way you would use plot, 80 | % but with three inputs: 81 | figure 82 | plot3(dataX,dataY,dataZ) 83 | 84 | grid on % sometimes useful 85 | 86 | rotate3d % this command will allow you to click-and-drag on the figure to spin the data around 87 | 88 | % adding other features to the plot works the same as with a normal plot, 89 | % e.g., 90 | xlabel('X-axis') 91 | ylabel('Y-axis') 92 | zlabel('Z-axis') 93 | 94 | 95 | % You might instead have a 3D matrix, e.g., 96 | data3d = randn(3,30); 97 | plot3(data3d) 98 | % Although the previous line seems like it should work, it unfortunately 99 | % doesn't. You'll need to input each dimension separately: 100 | plot3(data3d(1,:),data3d(2,:),data3d(3,:),'ko-','linew',3,'markerface','m') % you can use the same extra inputs to define line features as you would with the normal plot function 101 | axis off 102 | axis square % also try tight and normal 103 | 104 | %% slightly more advanced: get and set 105 | % "set" allows you to access specific properties of graphics. 106 | % set uses "parameter-value pair" operations, which you will use 107 | % often in plotting, as well as in some other advanced functions. 108 | plot(1:10,rand(10,3)) 109 | set(gca,'xtick',1:2:9); % gca = "get current axis"; note the parameter-value pair afterwards 110 | set(gca,'xtick',1:2:9,'xticklabel',{'one';'three';'five';'seven';'nine'}) % can put multiple parameter-value pairs in one function 111 | 112 | % the complement to set is get. type "get(gca)" to see a list of parameters 113 | % you can change 114 | get(gca) 115 | % you can also access (and return output from) axis properties: 116 | axis_ylim = get(gca,'YLim'); % axis_ylim is the lower and upper bounds of the y-axis 117 | 118 | % you can also assign axis properties using variables or functions: 119 | the_ylim_i_want = [-.3 -cos(pi)]; 120 | set(gca,'YLim',the_ylim_i_want); 121 | 122 | % If you have multiple axes or plotting objects, you can assign pointers (called handles) to 123 | % the objects. It is good practice to include a lower-case 'h' in the name 124 | % of the variable, to make it clear that these are handles: 125 | figure 126 | ploth = axes; 127 | plot(ploth,1:10,randn(10,3)); 128 | title('Random lines') 129 | set(ploth,'ydir','reverse') % this makes positive up, useful for ERP plotting 130 | set(gca,'xlim',[5 10]); % you can also change the x-axis limits 131 | 132 | % note that 'gca' points to the "current" axis, or the one most recently used. 133 | % You can mouse-click on an axis to make it current. 134 | 135 | % you can also change properties of figures, e.g.: 136 | set(gcf,'Color',[.6 0 .8],'name','My purple figure!','numberTitle','off') 137 | 138 | % Things can get slightly confusing, so make sure you use informative variable names. 139 | title('Hello there') 140 | titleh = get(gca,'Title'); % the "title" property is a handle to the title object 141 | get(titleh) 142 | set(titleh,'FontSize',40) 143 | set(titleh,'String','LARGE TITLE') 144 | 145 | % Let's touch up the figure with some lines showing the 0 crossings. 146 | % We can do this without needing to know the exact axis limits: 147 | hold on 148 | plot(get(gca,'xlim'),[0 0],'k') 149 | plot([6 6],get(gca,'ylim'),'k:') 150 | 151 | % More generally, type "get(gca)" or "get(gcf)" to see what properties you 152 | % can change and how to access them. 153 | 154 | %% subplots 155 | 156 | % so far we've been putting all the data into one plot in the center of the 157 | % figure. you can also use multiple plots: 158 | figure 159 | subplot(1,2,1) % 1 row, 2 columns, make the first subplot active 160 | plot(randn(10,2)) 161 | subplot(1,2,2) % 1 row, 2 columns, make the second subplot active 162 | plot(randn(10,2)) 163 | 164 | edgecolors='rgmk'; 165 | 166 | clf % clear figure 167 | for subploti=1:4 168 | subplot(2,2,subploti) 169 | plot(1:subploti,(1:subploti)*2+1,'m-p','linewidth',3,'markerEdgeColor',edgecolors(subploti)) 170 | set(gca,'xlim',[.5 4.5],'ylim',[1 10]) % fix X- and Y-axis ranges 171 | title([ 'Subplot ' num2str(subploti) repmat('!',1,subploti) ]) 172 | end 173 | 174 | %% basic image plotting 175 | 176 | % You can also plot images in 2D. This is useful for 2D data such as 177 | % time-frequency maps, connectivity matrices, etc. 178 | 179 | figure 180 | imagesc(randn(100,100)) 181 | 182 | % imagesc can also take x,y,z inputs, to make x- and y-axis values: 183 | imagesc(1:10:100,0:.1:1,randn(100)) 184 | 185 | % now let's make this a bit smoother by convoling it with a 2D gaussian 186 | xyrange = -1:.1:1; 187 | [X,Y] = meshgrid(xyrange); % this creates a grid of X and Y values 188 | gaus2d = exp(-(X.^2 + Y.^2)); 189 | 190 | % let's look at the Gaussian 191 | imagesc(gaus2d) 192 | 193 | imagesc(xyrange,xyrange,conv2(gaus2d,randn(100),'same')); 194 | 195 | % you can toggle the colorbar, which shows you the mapping between color 196 | % and value 197 | colorbar 198 | 199 | % you can also change the colormap (type "help graph3d" to see a list of 200 | % default colormaps; you can also create your own) 201 | colormap bone 202 | colormap spring 203 | colormap hot 204 | colormap jet % this is the default 205 | % hint for how to create your own: type "cmap=jet;" columns are RGB values 206 | 207 | 208 | % there are other functions you can use for 2D data, including: 209 | figure 210 | data = conv2(gaus2d,randn(100),'same'); % 2D convolution 211 | 212 | subplot(221) % that if you don't use variables and have fewer than 10 subplots, commas are not necessary 213 | imagesc(xyrange,xyrange,data) 214 | title('function: imagesc') 215 | 216 | subplot(222) 217 | surf(xyrange,xyrange,data) 218 | shading interp 219 | title('function: surf') 220 | 221 | subplot(223) 222 | contourf(xyrange,xyrange,data) 223 | title('function: contourf') 224 | 225 | subplot(224) 226 | contourf(xyrange,xyrange,data,40,'linecolor','none') 227 | title('function: contourf (with more parameters)') 228 | 229 | % note how imagesc flips the y-axis! (this can be changed: " set(gca,'YDir','normal') ") 230 | 231 | % now let's change the color scaling 232 | set(gca,'clim',[-1 1]) 233 | set(gca,'clim',[-10 2]) 234 | set(gca,'clim',[-10 20]) 235 | 236 | 237 | % There are many more ways to plot data and manipulate plots; 238 | % this should get you started with the basics. 239 | 240 | % to save the figure, go File -> Save As... 241 | % you can save as .fig (readable in matlab only), pixelated formats like 242 | % .bmp or .png, or vector format like .eps (useful for importing into 243 | % Illustrator or Correl Draw) 244 | 245 | %% a bit more about images 246 | 247 | % Images are just matrices of numbers. So are pictures. Load in the picture 248 | % of amsterdam (note: Matlab must be in the directory in which the file lives). 249 | amsterdam = imread('amsterdam.bmp'); 250 | whos amsterdam 251 | 252 | % note that this picture is a 2 (rows) x 2 (columns) x 3 (RGB) matrix 253 | figure 254 | imagesc(amsterdam) 255 | axis image 256 | axis off % or axis on 257 | grid on % only if axis is on 258 | grid minor 259 | 260 | % try plotting the individual values separately: 261 | title_color_components='RGB'; 262 | for subploti=1:4 263 | subplot(2,2,subploti) 264 | if subploti<4 265 | imagesc(amsterdam(:,:,subploti)) 266 | title([ 'Plotting just the ' title_color_components(subploti) ' dimension.' ]) 267 | else 268 | imagesc(amsterdam) 269 | title('Plotting all colors') 270 | end 271 | end 272 | 273 | %% end. 274 | -------------------------------------------------------------------------------- /0_introduction/1_matlabIntro_variables.m: -------------------------------------------------------------------------------- 1 | %% Analyzing Neural Time Series Data 2 | % Matlab code for Chapter 4 script A 3 | % Mike X Cohen 4 | % 5 | % This code accompanies the book, titled "Analyzing Neural Time Series Data" 6 | % (MIT Press). Using the code without following the book may lead to confusion, 7 | % incorrect data analyses, and misinterpretations of results. 8 | % Mike X Cohen assumes no responsibility for inappropriate or incorrect use of this code. 9 | 10 | %% variables, part I 11 | % Let's start with variables. A variable is a place-holder for 12 | % information. To create a variable, you simply assign it information. 13 | % For example, 14 | 15 | mike = 10; 16 | bob = 20; 17 | 18 | % If you type these into the command window (or highlight them here and hit 19 | % F9, or highlight them, right-click, and choose "Evaluate Selection"), you 20 | % will create the variables. 21 | % Because the variables refer to numbers, you can add them, multiple them, 22 | % etc: 23 | mike + bob; % Note: You can also put comments after code 24 | (mike+bob)/(mike-bob) 25 | 26 | % Notice that when the line ends with a semicolon, the output is suppressed, 27 | % and when there is no semicolon, it outputs the results in the command window. 28 | 29 | % variables can also be strings: 30 | mike = 'mike'; 31 | % now we've re-assigned mike from a number to a character array. Type 32 | % 'whos' into the matlab command. 33 | whos 34 | 35 | % You can also assign matrices to variables: 36 | a_simple_matrix=[ 3 4 5; 1 2 3; 9 8 7 ]; 37 | % type this into the command to see how the semicolon was used to delineate 38 | % separate lines. 39 | 40 | % Square brackets concatenate: 41 | mikes_full_name = [ mike ' cohen' ]; 42 | mikes_two_fav_numbers = [ 7 23 ]; 43 | 44 | % type whos in the command to see the properties of our variables. Note the 45 | % difference between double and char (character), and note the sizes of different variables. 46 | 47 | %% Variables, part II 48 | 49 | % Variables can be more sophisticated. Variables can be cells, which 50 | % are like blocks that may contain different kinds of information. 51 | var1{1} = [ 1 2 3 4 5 6 7 ]; 52 | var1{2} = 'hello world'; 53 | var1{3} = [ 1 3 6 7 4 3 5 6 7 87 76 43 4 5 6 767 ]; 54 | 55 | var1 56 | var1{2} 57 | 58 | % The most flexible type of variable is a structure. Structures contain fields that 59 | % are used for different kinds of data. For example: 60 | 61 | ANTS.name = 'mike'; % ANTS = Analyzing Neural Time Series 62 | ANTS.position = 'author'; 63 | ANTS.favorite_toothpaste_flavor = 'cinnamon'; 64 | ANTS.number_of_watches = 18; 65 | ANTS.favorite_color = [ .8 .1 .8 ]; % RGB values 66 | 67 | % You can also have an array of structures 68 | ANTS(2).name = 'Your name here'; 69 | ANTS(2).position = 'reader'; 70 | ANTS(2).favorite_toothpaste_flavor = 'bratworst'; % gross, but true 71 | ANTS(2).number_of_watches = 1; 72 | ANTS(2).favorite_color = [ 1 1 1 ]; 73 | 74 | % now you can get information about all fields from one specific element of 75 | % the structure: 76 | ANTS(1) 77 | 78 | % or information about one field within one member: 79 | ANTS(1).number_of_watches 80 | 81 | % or information about one field from all members: 82 | ANTS.favorite_toothpaste_flavor 83 | 84 | % note that this last result came out as two separate answers. If you want 85 | % to combine them into a single output (e.g., a cell array), use curly 86 | % brackets: 87 | {ANTS.favorite_toothpaste_flavor} 88 | 89 | %% functions 90 | 91 | % functions are modular pieces of code stored in a separate file. Most 92 | % functions can be opened and you can see/modify the code. Some functions 93 | % are compiled and not viewable or editable. 94 | 95 | % Functions may take inputs: 96 | randperm(4) % randperm is a function that randomly permutes integers. 4 is the input. 97 | 98 | % or a vector: 99 | mean([1 3 2 4 3 5 4 6]) 100 | 101 | % to see the guts of this function, highlight "mean" and right-click, 102 | % Open File (or type "edit mean" in the command, or highlight and Ctrl-D) 103 | 104 | % IMPORTANT! Do not modify matlab functions unless you really know what 105 | % you're doing! A better idea is to copy the function into a different file 106 | % and use a different name. 107 | 108 | % Most functions also give outputs: 109 | permuted_integers = randperm(4); % now the output of the function is stored in a new variable 110 | 111 | whos permuted_in* % Note that you can also use the * character for whos 112 | 113 | % some functions can have multiple inputs: 114 | random_number_matrix = rand(4,6); % Here, we asked for a 4 x 6 matrix of random numbers 115 | 116 | % some functions have multiple outputs: 117 | [max_value max_value_index] = max([1 2 3 9 8 7 6]); 118 | % This also shows matrix indexing, which we'll get to soon. 119 | 120 | % IMPORTANT: You can use the output of one function as the input to another 121 | % function. This is a powerful way to make your matlab programming fast and 122 | % efficient. On the other hand, if you embed functions to an extreme you 123 | % might unreadable code. 124 | [max_value max_value_index] = max( randperm( round( rand(1)*10 ) ) ); 125 | % Note that when you put the cursor on a parenthesis, it underlines the 126 | % corresponding other parenthesis. 127 | 128 | % type 'help ' in the matlab command to 129 | % read about a function. 130 | help max % also try: doc max 131 | 132 | %% indexing 133 | 134 | % Indexing is a powerful tool in matlab to access particular parts of a 135 | % variable. Indexing is very simple: Imagine you have 100 twinkies arranged 136 | % in a 10 x 10 square: 137 | twinkies = rand(10,10) 138 | 139 | % If you wanted to eat the twinkie in the 4th row, 8th column, you write: 140 | the_twinkie_i_will_eat = twinkies(4,8); 141 | 142 | %% The colon operator 143 | 144 | % By default, the colon operator increments in integer units from the 145 | % first to the second number. Observe: 146 | 1:10 147 | % You can also increment by a certain amount: 148 | 1:2:10 149 | count2ten=1:.23956:10; 150 | 151 | % the colon operator is also useful when indexing. Let's say you want to 152 | % eat several twinkies: 153 | twinkies_i_will_eat = twinkies(4:8,2:7); 154 | 155 | % Question: How big should the variable twinkies_i_will_eat be? 156 | whos twin* % answer 157 | 158 | % To count backwards, you must specify that you want to skip with a negative number: 159 | rookie_mistake = 10:1; 160 | how_the_pros_do_it = 10:-1:1; 161 | 162 | %% determining the sizes of variables 163 | 164 | % You can see variable sizes using whos, but there are also ways 165 | % to output matrix sizes into variables, which will be useful in many situations. 166 | 167 | length(random_number_matrix) 168 | % important! "length" returns the length of the longest dimension, regardless of how many dimensions there are! 169 | 170 | % you can use size to find the sizes of all dimensions: 171 | size(twinkies) 172 | size(twinkies,1) % or only specific dimensions... 173 | 174 | numel(twinkies) % numel stands for 'total number of elements' 175 | 176 | % these functions also produce outputs: 177 | twinkie_array_size = size(twinkies); 178 | prod(twinkie_array_size) % prod returns the product of input numbers 179 | 180 | % of course, these functions work on non-numeric variables, e.g., 181 | length(ANTS) 182 | 183 | %% for-loops 184 | 185 | % A for-loop is a way to iterate repeatedly: 186 | 187 | for counting_variable = 1:10 188 | disp(counting_variable); % disp stands for display, which prints information in the command window 189 | end 190 | 191 | % another example: 192 | for counting_variable = 1:2:10 193 | disp([ 'The ' num2str(counting_variable) 'th iteration value times 2 divided by 3 and added to 7 is ' num2str(counting_variable*2/3+7) '.' ]) 194 | end 195 | 196 | % You can embed loops within loops 197 | for i = 1:5 198 | for j = 3:7 199 | product_matrix(i,j) = i*j; % Matlab produces a warning here because product_matrix is not initialized. See text in Chapter 4. 200 | end 201 | end 202 | 203 | % Two important things to note here: (1) You can use the same numbers as 204 | % indices AND as variables; (2) Unspecified elements in a matrix are 205 | % automatically created and set to zero. 206 | 207 | number_rows = 5; % having many spaces is allowed and facilitates code aesthetics 208 | number_columns = 7; 209 | % initialize matrix with zeros 210 | product_matrix = zeros(number_rows,number_columns); 211 | 212 | for i=1:number_rows 213 | for j=1:number_columns 214 | product_matrix(i,j)=i*j; 215 | end % end j-loop 216 | end % end i-loop 217 | 218 | % note the comments following end-statements. When you have multiple long 219 | % loops, this kind of commenting will be helpful. Also note that when you 220 | % click on one of the "for" or "end" statements, its pair will be underlined. 221 | 222 | %% if-statements 223 | 224 | % Exactly how it sounds 225 | if 4>5 226 | disp('Something has gone awry in the universe') 227 | end 228 | 229 | if 4>5 230 | disp('Something is still very wrong') 231 | else 232 | disp('Whew! Everything''s normal.') % note the two single-parenthesis marks inside the string 233 | end 234 | 235 | % the 'switch/case' statement is similar to 'if/else' 236 | for counting_variable = 1:2:10 237 | switch counting_variable 238 | case 1 % compares 'counting_variable' to '1' 239 | disp([ 'The ' num2str(counting_variable) 'st iteration value times 2 divided by 3 and added to 7 is ' num2str(counting_variable*2/3+7) '.' ]) 240 | case 2 241 | disp([ 'The ' num2str(counting_variable) 'nd iteration value times 2 divided by 3 and added to 7 is ' num2str(counting_variable*2/3+7) '.' ]) 242 | case 3 243 | disp([ 'The ' num2str(counting_variable) 'rd iteration value times 2 divided by 3 and added to 7 is ' num2str(counting_variable*2/3+7) '.' ]) 244 | otherwise 245 | disp([ 'The ' num2str(counting_variable) 'th iteration value times 2 divided by 3 and added to 7 is ' num2str(counting_variable*2/3+7) '.' ]) 246 | end % end switch 247 | end 248 | 249 | %% boolean (true/false) 250 | 251 | % sometimes, it's useful to know if a statement is TRUE or FALSE. 252 | % You can use a double-equals sign: 253 | 254 | 5==5 255 | 256 | % The answer is '1', which in this case means 'true'. The oppose of true (1) is false (0): 257 | 5==4 258 | 259 | % You can assign these answers to variables: 260 | fourIsFive = 4==5; % see below for disambiguating = and == 261 | 262 | whos fourIsFive 263 | % note that 'fourIsFive' is type "logical", or boolean. 264 | 265 | % Other related useful functions: 266 | matrix0 = false(10,3); 267 | matrix1 = true(4,12); 268 | 269 | % you can use the tilde "~" to negate a statement: 270 | ~(1==1) % false because the negation of 1==1 is false 271 | ~(4>3) % also false because the negation of 4>3 is false 272 | ~(4<3) % true because it is false (~) that 4 is greater than 3. Tricky! 273 | 274 | % things can sometimes get tricky: 275 | truthtest = 1 == 2; 276 | 277 | % Remember: 278 | % One equals sign is a statement ("you have this value"). 279 | % Two equals signs means you are asking a question ("are these the same?"). 280 | % Mnemonic: "Say with one (=), ask with two (==), then you know just who is who!" 281 | % (I didn't say it was a good mnemonic...) 282 | 283 | %% repmat 284 | 285 | % repmat is short for "replicate matrix." It is a handy function that 286 | % is often used in analyses. The problem that repmat solves is matrix 287 | % operations with other matrices. First, consider that adding a scalar 288 | % (a single number) to a matrix is no problem. 289 | 290 | mat = rand(4); 291 | 292 | mat+23 293 | 294 | % Now imagine that mat is EEG data with 4 electrodes and 10 time points: 295 | mat = rand(4,10); 296 | 297 | % now you want to subtract the mean over time: 298 | meanOverTime = mean(mat,2); % second input to mean function is the dimension along which to compute the mean 299 | 300 | mat = mat - meanOverTime; 301 | 302 | % the previous line crashes because the matrices are of unequal size 303 | whos mat meanOverTime 304 | 305 | % repmat will help: 306 | meanOverTimeRepmat = repmat(meanOverTime,1,size(mat,2)); 307 | whos mat meanOverTime* 308 | % repmat takes 3 inputs: the matrix you want to replicate, the number of 309 | % times to replicate it over rows, and the number of times to replicate it 310 | % over columns (you can also input more dimensions). We want to replicate 311 | % this matrix only over time points (the size of the second dimension of 312 | % mat). Now the subtraction works: 313 | 314 | matmean = mat - meanOverTimeRepmat; 315 | 316 | 317 | % Other examples of repmat: 318 | mat = [1 2 3; 10 20 30]; 319 | 320 | repmat(mat,1,1) 321 | repmat(mat,1,2) 322 | repmat(mat,2,1) 323 | 324 | %% bsxfun 325 | 326 | % bsxfun is a useful function for fast and easy array and matrix manipulations. 327 | % It was introduced to Matlab fairly recently, so older versions of Matlab 328 | % do not have this utility. 329 | 330 | % for example, the following function will add 4 to a random matrix: 331 | bsxfun(@plus,randn(10),4) 332 | 333 | % this might not seem any better than "randn(10)+4" and for this small 334 | % case, it isn't. bsxfun is more useful because it performs 335 | % singleton-expansion, which means you may be able to avoid using repmat. 336 | % For example, imagine a dataset with 100 channels and 100,000 time points: 337 | a = rand(100,100000); 338 | 339 | % To subtract the mean of the entire time series: 340 | am = a - repmat(mean(a,2),1,size(a,2)); 341 | 342 | % Notice that the repmat is necessary: 343 | am = a - mean(a,2); 344 | 345 | % The previous line crashes because the sizes of a and its mean are not the 346 | % same. However, bsxfun expands this automatically 347 | am = bsxfun(@minus,a,mean(a,2)); 348 | 349 | % let's do a timing test... 350 | tic 351 | for i=1:100 352 | am = a - repmat(mean(a,2),1,size(a,2)); 353 | end 354 | t(1)=toc; 355 | 356 | tic 357 | for i=1:100 358 | am = bsxfun(@minus,a,mean(a,2)); 359 | end 360 | t(2)=toc; 361 | 362 | figure 363 | bar(t) 364 | set(gca,'xtick',1:2,'xticklabel',{'repmat';'bsxfun'},'xlim',[.5 2.5]) 365 | title([ 'bsxfun took ' num2str(100*t(2)/t(1)) '% of the computation time.' ]) 366 | % you'll learn more about the above lines in part B of this code 367 | 368 | 369 | % Thus, bsxfun is a bit faster but also more convenient to use, and more elegant. 370 | % There are other similar functions to bsxfun, including arrayfun and cellfun. 371 | % In addition to speed, the *fun functions allow you to avoid using loops. 372 | % For example, let's say you want to know the length of items in a cell array. 373 | 374 | % create cell array whose elements have variable lengths 375 | c = cell(1,40); 376 | for i=1:length(c) 377 | c{i} = randn(1,round(rand*100)); 378 | end 379 | 380 | % now you want to know how many elements are in each cell. 381 | % Normally you need a loop: 382 | cell_lengths = zeros(size(c)); 383 | for i=1:length(c) 384 | cell_lengths(i) = numel(c{i}); 385 | end 386 | 387 | % But cellfun is more efficient: 388 | cell_lengths = cellfun(@length,c); 389 | 390 | %% end 391 | -------------------------------------------------------------------------------- /0_introduction/2_indexing.m: -------------------------------------------------------------------------------- 1 | % eeglab's EEG structure and indexing 2 | % mikeXcohen@gmail.com 3 | 4 | %% load in EEG data 5 | 6 | load sampleEEGdata.mat 7 | 8 | % FYI, this would also work: 9 | % [file2load,path4file]=uigetfile('*.mat','Please select EEG data file'); 10 | % load([ path4file file2load ]) 11 | 12 | % take a minute to inspect the EEG structure 13 | EEG 14 | 15 | %% finding time indices based on ms 16 | 17 | % The problem: We want to create a topographical plot at time=300 ms 18 | 19 | time2plot = 300; % in ms! 20 | 21 | % extract the trial-averaged data from requested time point 22 | % (note: this code contains an error!) 23 | data2plot = squeeze(mean( EEG.data(:,time2plot,:) ,3)); 24 | 25 | 26 | % plot 27 | figure(1), clf 28 | topoplot(data2plot,EEG.chanlocs); 29 | title([ 'Topoplot from time=' num2str(EEG.times(time2plot)) ' ms.' ]) 30 | 31 | %% same concept for frequencies 32 | 33 | frex = linspace(2,100,42); 34 | 35 | freqIwant = 23; % in hz 36 | 37 | % use min(abs trick to find closest frequency to 23 Hz 38 | [~,frexidx] = min(abs(frex-freqIwant)); 39 | 40 | % the function dsearchn also works 41 | frexidx = dsearchn(frex',freqIwant); 42 | 43 | %% indexing channels based on names 44 | 45 | % the electrode label that we want to analyze 46 | electrodeName = 'p1'; % case doesn't matter 47 | 48 | % find the channel number that corresponds to this label 49 | electrodeidx = strcmpi(electrodeName,{EEG.chanlocs.labels}); 50 | 51 | % confirm that this electrode is correct 52 | EEG.chanlocs(electrodeidx) 53 | 54 | % plot the ERP from this electrode 55 | figure(1), clf 56 | plot(EEG.times,mean( EEG.data(electrodeidx,:,:),3 )) 57 | 58 | 59 | %% now multiple electrodes 60 | 61 | electrodeNames = {'p1';'fc6';'t18'}; 62 | 63 | % initialize 64 | electrodeidx = zeros(size(electrodeNames)); 65 | 66 | % loop through electrodes and find the index of each one 67 | % (The code below has three errors. Try to find/fix them!) 68 | for chani=1:length(electrodeNames) 69 | electrodeidx(chani) = strcmpi(electrodeNames,{EEG.chanlocs.labels}); 70 | end 71 | 72 | % plot all the ERPs 73 | plot(EEG.times,mean( EEG.data(electrodeidx,:,:),3 )) 74 | legend({EEG.chanlocs(electrodeidx).labels}) 75 | 76 | %% 77 | 78 | -------------------------------------------------------------------------------- /0_introduction/3_timedomain.m: -------------------------------------------------------------------------------- 1 | %% introduction to time-domain analyses 2 | % mikeXcohen@gmail.com 3 | 4 | % load EEG data 5 | load sampleEEGdata.mat 6 | 7 | % plot a few trials from one channel... 8 | 9 | % specify the label of the channel to plot 10 | which_channel_to_plot = 'fcz'; 11 | 12 | % and find the index (channel number) of that label 13 | channel_index = strcmpi(which_channel_to_plot,{EEG.chanlocs.labels}); 14 | 15 | x_axis_limit = [-200 1000]; % in ms 16 | 17 | num_trials2plot = 12; 18 | 19 | 20 | figure(1), clf 21 | set(gcf,'Name',[ num2str(num_trials2plot) ' random trials from channel ' which_channel_to_plot ],'Number','off') 22 | 23 | for i=1:num_trials2plot 24 | 25 | % figure out how many subplots we need 26 | subplot(ceil(num_trials2plot/ceil(sqrt(num_trials2plot))),ceil(sqrt(num_trials2plot)),i) 27 | 28 | % pick a random trial (using randsample, which is in the stats toolbox) 29 | random_trial_to_plot = randsample(EEG.trials,1); 30 | % if you don't have the stats toolbox, use the following two lines: 31 | %random_trial_to_plot = randperm(EEG.trials); 32 | %random_trial_to_plot = random_trial_to_plot(1); 33 | 34 | % plot trial and specify x-axis and title 35 | plot(EEG.times,squeeze(EEG.data(channel_index,:,random_trial_to_plot))); 36 | set(gca,'xlim',x_axis_limit,'ytick',[]) 37 | title([ 'Trial ' num2str(random_trial_to_plot) ]) 38 | end 39 | 40 | %% All trials and trial average (ERP) 41 | 42 | figure(2), clf 43 | % plot all trials 44 | plot(EEG.times,squeeze(EEG.data(channel_index,:,:)),'m') 45 | 46 | hold on 47 | 48 | % compute ERP 49 | erp = 50 | 51 | % and plot it on top of single trials 52 | plot(EEG.times,erp,'k','linew',4) 53 | set(gca,'xlim',[-300 1000],'ylim',[-60 60],'ydir','reverse') 54 | xlabel('Time (ms)'), ylabel('\muV') 55 | 56 | %% focus on the ERP 57 | 58 | % now plot only the ERP 59 | figure(3), clf 60 | plot(EEG.times,erp) % Note the "3" as second input to "mean"; this takes the average of the 3rd dimension. 61 | 62 | % plot lines indicating baseline activity and stim onset 63 | hold on 64 | plot(get(gca,'xlim'),[0 0],'k') 65 | plot([0 0],get(gca,'ylim'),'k:') 66 | 67 | % add axis labels and title 68 | xlabel('Time (ms)') 69 | ylabel('\muV') % note that matlab interprets "\mu" as the Greek character for micro 70 | title([ 'ERP (average of ' num2str(EEG.trials) ' trials) from electrode ' EEG.chanlocs(channel_index).labels ]) 71 | 72 | % plot upside down, following ERP convention 73 | set(gca,'ydir','reverse') 74 | % axis ij % this does the same as the previous trial 75 | 76 | % below is some advanced but flexible code to change the x-axis label 77 | set(gca,'xlim',[-300 1000]) 78 | xticklabel=cellstr(get(gca,'xticklabel')); 79 | xticklabel{str2double(xticklabel)==0}='stim'; 80 | set(gca,'xticklabel',xticklabel) 81 | 82 | 83 | %% Butterfly/topographical variance plots 84 | 85 | figure(4), clf 86 | 87 | % Butterfly plot 88 | subplot(211) 89 | plot(EEG.times,mean(EEG.data,3)) 90 | set(gca,'xlim',[-200 1000],'ydir','reverse') 91 | xlabel('Time (ms)'), ylabel('\muV') 92 | title('ERP from all sensors') 93 | 94 | % topographical variance plot 95 | subplot(212) 96 | plot(EEG.times,var( mean(EEG.data,3) )) % note: change var to std for global field power 97 | set(gca,'xlim',[-200 1000]) 98 | xlabel('Time (ms)'), ylabel('var(\muV)') 99 | title('Topographical variance') 100 | 101 | 102 | %% end. 103 | -------------------------------------------------------------------------------- /0_introduction/4_topomaps.m: -------------------------------------------------------------------------------- 1 | % topographical maps 2 | % mikeXcohen@gmail.com 3 | 4 | %% load in EEG data 5 | 6 | load sampleEEGdata.mat 7 | 8 | %% introduction to topographical plotting 9 | 10 | % get cartesian coordinates 11 | [elocsX,elocsY] = pol2cart(pi/180*[EEG.chanlocs.theta],[EEG.chanlocs.radius]); 12 | 13 | % plot electrode locations 14 | figure(1), clf 15 | scatter(elocsY,elocsX,100,'ro','filled'); 16 | set(gca,'xlim',[-.6 .6],'ylim',[-.6 .6]) 17 | axis square 18 | title('Electrode locations') 19 | 20 | 21 | % define XY points for interpolation 22 | interp_detail = 100; 23 | interpX = linspace(min(elocsX)-.2,max(elocsX)+.25,interp_detail); 24 | interpY = linspace(min(elocsY),max(elocsY),interp_detail); 25 | 26 | % meshgrid is a function that creates 2D grid locations based on 1D inputs 27 | [gridX,gridY] = meshgrid(interpX,interpY); 28 | 29 | % let's look at these matrices 30 | hold on 31 | plot3(gridY(:),gridX(:),-ones(1,interp_detail^2),'k.') 32 | 33 | %% extract data and interpolate to 2D grid 34 | 35 | timepoint2plot = 100; % in ms 36 | 37 | % convert time point from ms to index 38 | [~,timepointidx] = min(abs(EEG.times-timepoint2plot)); 39 | 40 | % data from this frequency from some other matrix 41 | dat = double( mean(EEG.data(:,timepointidx,:),3) ); 42 | 43 | % now interpolate the data on a 2D grid 44 | interpFunction = TriScatteredInterp(elocsY',elocsX',dat); 45 | topodata = interpFunction(gridX,gridY); 46 | 47 | %% plot results 48 | 49 | figure(2), clf 50 | 51 | % contourf 52 | subplot(221) 53 | contourf(interpY,interpX,topodata,100,'linecolor','none'); 54 | axis square 55 | set(gca,'xlim',[-.5 .5],'ylim',[-1 .8]) 56 | title('Interpolated data using ''contourf''') 57 | 58 | % surface 59 | subplot(222) 60 | surf(interpY,interpX,topodata); 61 | xlabel('left-right of scalp'), ylabel('anterior-posterior of scalp'), zlabel('\muV') 62 | shading interp, axis square 63 | set(gca,'xlim',[-.5 .5],'ylim',[-1 .8]) 64 | rotate3d on, view(0,90) 65 | title('Interpolated data using ''surf''') 66 | 67 | % pixelated image 68 | subplot(223) 69 | imagesc(interpY,interpX,topodata); 70 | xlabel('left-right of scalp'), ylabel('anterior-posterior of scalp'), zlabel('\muV') 71 | set(gca,'xlim',[-.5 .5],'ylim',[-1 .8]) 72 | title('Interpolated data using ''imagesc''') 73 | 74 | % eeglab function 75 | subplot(224) 76 | topoplot(dat,EEG.chanlocs); % eeglab's topoplot function 77 | title('Interpolated data using eeglab ''topoplot''') 78 | 79 | 80 | %% useful bit of code to see channel locations 81 | 82 | figure 83 | topoplot([],EEG.chanlocs,'electrodes','ptslabels'); 84 | % hint: click on an electrode to see its corresponding index (number) 85 | 86 | %% end. 87 | -------------------------------------------------------------------------------- /0_introduction/amsterdam.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreiZn/Tutorial_neural_time_series_analysis/a3808e7e156ddcab3544aa805cfeb561b1c8c204/0_introduction/amsterdam.bmp -------------------------------------------------------------------------------- /0_introduction/sampleEEGdata.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreiZn/Tutorial_neural_time_series_analysis/a3808e7e156ddcab3544aa805cfeb561b1c8c204/0_introduction/sampleEEGdata.mat -------------------------------------------------------------------------------- /1_discrete-time_Fourier_transform/1_sinewave.m: -------------------------------------------------------------------------------- 1 | % mikeXcohen@gmail.com 2 | 3 | %% plot a sine wave 4 | 5 | % define some variables 6 | freq = 2; % frequency in Hz 7 | time = -1:.001:1; % -1 to 1 seconds in millisecond steps 8 | 9 | % now create the sinewave 10 | sinewave = 1*sin(2*pi*freq*time + 0); 11 | 12 | % now plot it! 13 | figure(1), clf 14 | plot(time,sinewave) 15 | 16 | % optional prettification of the plot 17 | set(gca,'xlim',[-1.1 1.1],'ylim',[-1.1 1.1]); 18 | xlabel('Time (ms)') 19 | title('My first sine wave plot! Mom will be so proud!') 20 | 21 | %% the sum of sine waves can appear like a complicated time series 22 | 23 | % create a few sine waves and sum 24 | 25 | % define a sampling rate 26 | srate = 1000; 27 | 28 | % list some frequencies 29 | frex = [ 3 10 5 15 35 ]; 30 | 31 | % list some random amplitudes... make sure there are the same number of 32 | % amplitudes as there are frequencies! 33 | amplit = [ 5 15 10 5 7 ]; 34 | 35 | % phases... list some random numbers between -pi and pi 36 | phases = [ pi/7 pi/8 pi pi/2 -pi/4 ]; 37 | %phases = zeros(1,5); 38 | 39 | % define time... 40 | time = -1:1/srate:1; 41 | 42 | 43 | % now we loop through frequencies and create sine waves 44 | sine_waves = zeros(length(frex),length(time)); 45 | for fi=1:length(frex) 46 | sine_waves(fi,:) = amplit(fi) * sin(2*pi*time*frex(fi) + phases(fi)); 47 | end 48 | 49 | % now plot the result 50 | figure(2), clf 51 | plot(time,sum(sine_waves)) 52 | title('sum of sine waves') 53 | xlabel('Time (s)'), ylabel('Amplitude (arb. units)') 54 | 55 | 56 | % now plot each wave separately 57 | figure(3), clf 58 | for fi=1:length(frex) 59 | subplot(length(frex),1,fi) 60 | plot(time, sine_waves(fi,:)) 61 | axis([ time([1 end]) -max(amplit) max(amplit) ]) 62 | end 63 | 64 | %% Now compute Fourier representation of summed sine waves 65 | 66 | % take Fourier transform (via fast-Fourier transform) 67 | sineX = fft( sum(sine_waves) )/length(time); 68 | 69 | % define vector of frequencies in Hz 70 | hz = linspace(0,srate/2,floor(length(time)/2)+1); 71 | 72 | figure(4), clf 73 | plot(hz,2*abs(sineX(1:length(hz))),'ro-') 74 | 75 | % make the plot look a bit nicer 76 | set(gca,'xlim',[0 max(frex)*1.2]) 77 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 78 | 79 | %% end. 80 | -------------------------------------------------------------------------------- /1_discrete-time_Fourier_transform/2_fourier.m: -------------------------------------------------------------------------------- 1 | % mikeXcohen@gmail.com 2 | 3 | %% signal created by a sum of sine waves (same as in previous lecture) 4 | 5 | % create a few sine waves and sum 6 | 7 | % define a sampling rate 8 | srate = 1000; 9 | 10 | % list some frequencies 11 | frex = [ 3 10 5 15 35 ]; 12 | 13 | % list some random amplitudes... make sure there are the same number of 14 | % amplitudes as there are frequencies! 15 | amplit = [ 5 15 10 5 7 ]; 16 | 17 | % phases... list some random numbers between -pi and pi 18 | phases = [ pi/7 pi/8 pi pi/2 -pi/4 ]; 19 | 20 | % define time... 21 | time = -1:1/srate:1; 22 | 23 | 24 | % now we loop through frequencies and create sine waves 25 | sine_waves = zeros(length(frex),length(time)); 26 | for fi=1:length(frex) 27 | sine_waves(fi,:) = amplit(fi) * sin(2*pi*time*frex(fi) + phases(fi)); 28 | end 29 | 30 | %% Compute discrete-time Fourier transform 31 | 32 | signal = sum(sine_waves,1); % sum to simplify 33 | N = length(signal); % length of sequence 34 | fourierTime = ((1:N)-1)/N; % "time" used for sine waves 35 | nyquist = srate/2; % Nyquist frequency -- the highest frequency you can measure in the data 36 | 37 | % initialize Fourier output matrix 38 | fourierCoefs = zeros(size(signal)); 39 | 40 | % These are the actual frequencies in Hz that will be returned by the 41 | % Fourier transform. The number of unique frequencies we can measure is 42 | % exactly 1/2 of the number of data points in the time series (plus DC). 43 | frequencies = linspace(0,nyquist,floor(N/2)+1); 44 | 45 | 46 | % loop over frequencies 47 | for fi=1:N 48 | 49 | % create sine wave for this frequency 50 | fourierSine = exp( -1i*2*pi*(fi-1).*fourierTime ); 51 | 52 | % compute dot product as sum of point-wise elements 53 | fourierCoefs(fi) = fourierSine*signal'; 54 | 55 | % note: this can also be expressed as a vector-vector product 56 | % fourier(fi) = fourierSine*signal'; 57 | end 58 | 59 | % scale Fourier coefficients to original scale 60 | fourierCoefs = fourierCoefs / N; 61 | 62 | 63 | figure(1), clf 64 | subplot(221) 65 | plot(real(exp( -2*pi*1i*(10).*fourierTime ))) 66 | xlabel('time (a.u.)'), ylabel('Amplitude') 67 | title('Sine wave') 68 | 69 | subplot(222) 70 | plot(signal) 71 | title('Data') 72 | 73 | subplot(212) 74 | plot(frequencies,abs(fourierCoefs(1:length(frequencies)))*2,'*-') 75 | xlabel('Frequency (Hz)') 76 | ylabel('Power (\muV)') 77 | title('Power spectrum derived from discrete Fourier transform') 78 | 79 | %% Compare manual Fourier transform with fft 80 | 81 | % Compute fourier transform and scale 82 | fourierCoefsF = fft(signal) / N; 83 | 84 | subplot(212),hold on 85 | plot(frequencies,abs(fourierCoefsF(1:length(frequencies)))*2,'ro') 86 | set(gca,'xlim',[0 40]) 87 | 88 | %% Compare computation times 89 | 90 | % initialize variable for computation times 91 | computationTimes = zeros(1,2); 92 | 93 | % start Matlab's timer 94 | tic; 95 | 96 | % manual DTFT 97 | for fi=1:N 98 | fourierSine = exp( -1i*2*pi*(fi-1).*fourierTime ); 99 | fourierCoefs(fi) = sum( fourierSine .* signal ); 100 | end 101 | fourierCoefs = fourierCoefs / N; 102 | 103 | % end Matlab's timer 104 | computationTimes(1) = toc; 105 | 106 | 107 | % now FFT 108 | tic; 109 | fourierCoefsF = fft(signal) / N; 110 | computationTimes(2) = toc; 111 | 112 | 113 | figure(2), clf 114 | 115 | bar(computationTimes) 116 | set(gca,'xtick',1:2,'xticklabel',{'Manual';'fft'},'xlim',[0 3]) 117 | ylabel('Computation time in seconds') 118 | 119 | 120 | %% Compute inverse discrete-time Fourier transform 121 | 122 | reconSignal = zeros(size(signal)); 123 | 124 | % loop over frequencies 125 | for fi=1:N 126 | 127 | % create sine wave for this frequency 128 | fourierSine = fourierCoefs(fi) * exp( 1i*2*pi*(fi-1).*fourierTime ); 129 | 130 | % compute dot product as sum of point-wise elements 131 | reconSignal = reconSignal + fourierSine; 132 | end 133 | 134 | % note: in practice, the inverse Fourier transform should be done using: 135 | reconSignal = ifft(fourierCoefs) * N; 136 | 137 | figure(3), clf 138 | plot(real(reconSignal),'.-') 139 | hold on 140 | plot(signal,'ro') 141 | 142 | legend({'reconstructed';'original'}) 143 | 144 | %% end. 145 | -------------------------------------------------------------------------------- /1_discrete-time_Fourier_transform/3_complexfourier.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% complex sine waves 4 | 5 | srate = 1000; 6 | time = 0:1/srate:1; 7 | freq = 6; 8 | 9 | 10 | realval_sine = sin(2*pi*freq*time); 11 | complex_sine = exp(1i*2*pi*freq*time); 12 | 13 | figure(1), clf 14 | % plot "normal" sine wave 15 | subplot(311) 16 | plot(time,realval_sine) 17 | title('Real-valued sine wave') 18 | 19 | % plot real part 20 | subplot(312) 21 | plot(time,imag(complex_sine)) 22 | title('Imaginary (sine) component of complex sine wave') 23 | 24 | % plot imaginary part 25 | subplot(313) 26 | plot(time,real(complex_sine)) 27 | title('Real (cosine) component of complex sine wave') 28 | xlabel('Time (s)') 29 | 30 | figure(2), clf 31 | plot(time,real(complex_sine)) 32 | hold on 33 | plot(time,imag(complex_sine),'r--') 34 | set(gca,'ylim',[-1.2 1.2],'xlim',[0 .5]) 35 | legend({'real part';'imaginary part'}) 36 | 37 | %% other ways to look at complex sine waves 38 | 39 | figure(3), clf 40 | 41 | plot3(time,imag(complex_sine),real(complex_sine)) 42 | xlabel('Time (s)'), ylabel('Imaginary part (ampl.)'), zlabel('Real part (ampl.)') 43 | axis image 44 | rotate3d 45 | 46 | 47 | figure(4), clf 48 | plot(imag(complex_sine),real(complex_sine)) 49 | plot(complex_sine) 50 | 51 | %% 52 | 53 | %% Fourier coefficients as complex numbers 54 | 55 | % create sine waves that differ in power and phase 56 | sine1 = 3 * cos(2*pi*freq*time + 0 ); 57 | sine2 = 2 * cos(2*pi*freq*time + pi/6 ); 58 | sine3 = 1 * cos(2*pi*freq*time + pi/3 ); 59 | 60 | % compute Fourier coefficients 61 | fCoefs1 = fft(sine1) / length(time); 62 | fCoefs2 = fft(sine2) / length(time); 63 | fCoefs3 = fft(sine3) / length(time); 64 | 65 | hz = linspace(0,srate/2,floor(length(time)/2)+1); 66 | 67 | % find the frequency of our sine wave 68 | hz6 = dsearchn(hz',freq); 69 | 70 | % let's look at the coefficients for this frequency 71 | disp([ '6 Hz Fourier coefficient for sin1: ' num2str(fCoefs1(hz6)) ]) 72 | disp([ '6 Hz Fourier coefficient for sin2: ' num2str(fCoefs2(hz6)) ]) 73 | disp([ '6 Hz Fourier coefficient for sin3: ' num2str(fCoefs3(hz6)) ]) 74 | 75 | %% complex numbers as vectors in a polar plot 76 | 77 | % make polar plots of fourier coefficients 78 | figure(5), clf 79 | h(1) = polar([0 angle(fCoefs1(hz6))],[0 2*abs(fCoefs1(hz6))],'r'); 80 | hold on 81 | h(2) = polar([0 angle(fCoefs2(hz6))],[0 2*abs(fCoefs2(hz6))],'b'); 82 | h(3) = polar([0 angle(fCoefs3(hz6))],[0 2*abs(fCoefs3(hz6))],'k'); 83 | set(h,'linewidth',5) 84 | legend({'sine1';'sine2';'sine3'}) 85 | 86 | %% phase and power information can be extracted via Euler's formula 87 | 88 | % extract amplitude using Pythagorian theorem 89 | amp1 = sqrt( imag(fCoefs1).^2 + real(fCoefs1).^2 ); 90 | amp2 = sqrt( imag(fCoefs2).^2 + real(fCoefs2).^2 ); 91 | amp3 = sqrt( imag(fCoefs3).^2 + real(fCoefs3).^2 ); 92 | 93 | % extract amplitude using the Matlab function abs 94 | % amp1 = abs( fCoefs1 ); 95 | % amp2 = abs( fCoefs2 ); 96 | % amp3 = abs( fCoefs3 ); 97 | 98 | figure(6), clf 99 | 100 | % plot amplitude spectrum 101 | subplot(211) 102 | plot(hz,2*amp1(1:length(hz)),'ro-','linew',3), hold on 103 | plot(hz,2*amp2(1:length(hz)),'bp-','linew',3) 104 | plot(hz,2*amp3(1:length(hz)),'ks-','linew',3) 105 | set(gca,'xlim',[freq-3 freq+3]) 106 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 107 | 108 | %% and now for phase... 109 | 110 | % extract phase angles using trigonometry 111 | phs1 = atan2( imag(fCoefs1) , real(fCoefs1) ); 112 | phs2 = atan2( imag(fCoefs2) , real(fCoefs2) ); 113 | phs3 = atan2( imag(fCoefs3) , real(fCoefs3) ); 114 | 115 | % extract phase angles using Matlab function angle 116 | % phs1 = angle( fCoefs1 ); 117 | % phs2 = angle( fCoefs2 ); 118 | % phs3 = angle( fCoefs3 ); 119 | 120 | 121 | % plot phase spectrum 122 | subplot(212) 123 | plot(hz,phs1(1:length(hz)),'ro-','linew',3), hold on 124 | plot(hz,phs2(1:length(hz)),'bp-','linew',3) 125 | plot(hz,phs3(1:length(hz)),'ks-','linew',3) 126 | set(gca,'xlim',[freq-3 freq+3]) 127 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 128 | 129 | %% end. 130 | -------------------------------------------------------------------------------- /1_discrete-time_Fourier_transform/4_frequencies.m: -------------------------------------------------------------------------------- 1 | %% More about frequencies resulting from the Fourier transform 2 | % mikexcohen@gmail.com 3 | 4 | % a signal and take its fft 5 | signal = [ 1 0 1 2 -3 1 2 0 -3 0 -1 2 1 -1]; 6 | signalX = fft(signal) / length(signal); 7 | 8 | 9 | % plot (without any frequency-axis scaling) 10 | figure(1), clf 11 | subplot(211) 12 | plot(signal,'o-') 13 | title('Time domain') 14 | ylabel('Amplitude'), xlabel('????') 15 | 16 | subplot(212) 17 | plot(abs(signalX),'p-') 18 | title('Frequency domain') 19 | ylabel('Amplitude'), xlabel('????') 20 | 21 | %% same as above but time units are defined 22 | 23 | srate = 100; % in Hz 24 | hz = linspace( 0, srate/2, floor(length(signal)/2)+1 ); 25 | 26 | figure(2), clf 27 | 28 | % same plot as above 29 | subplot(211) 30 | plot(signal,'o-') 31 | title('Time domain') 32 | ylabel('Amplitude'), xlabel('Time (ms)') 33 | 34 | 35 | % keep only positive frequencies 36 | signalX = signalX(1:length(hz)); 37 | 38 | % similar plot as above, but with an x-axis label 39 | subplot(212) 40 | plot(hz,2*abs(signalX),'p-') 41 | title('Frequency domain') 42 | ylabel('Amplitude'), xlabel('Frequency (Hz)') 43 | 44 | %% DC reflects the mean offset 45 | 46 | figure(3), clf 47 | 48 | % compute the FFT of the same signal with different DC offsets 49 | signalX1 = fft(signal) / length(signal); 50 | signalX2 = fft(signal-mean(signal)) / length(signal); 51 | signalX3 = fft(signal+1) / length(signal); 52 | 53 | 54 | % plot signals in the time domain 55 | subplot(211) 56 | plot(ifft(signalX1)*length(signal),'bo-'), hold on 57 | plot(ifft(signalX2)*length(signal),'rd-'), hold on 58 | plot(ifft(signalX3)*length(signal),'k*-'), hold on 59 | xlabel('Tme (ms)'), ylabel('Amplitude') 60 | 61 | 62 | % plot signals in the frequency domain 63 | subplot(212) 64 | plot(hz,2*abs(signalX1(1:length(hz))),'bo-'), hold on 65 | plot(hz,2*abs(signalX2(1:length(hz))),'rd-') 66 | plot(hz,2*abs(signalX3(1:length(hz))),'k*-') 67 | 68 | xlabel('Frequencies (Hz)'), ylabel('Amplitude') 69 | legend({'original';'de-meaned';'increased mean'}) 70 | 71 | %% difference between sampling rate and number of time points for Fourier frequencies 72 | 73 | % temporal parameters 74 | srates = [100 100 1000]; 75 | timedur = [ 1 10 1]; 76 | 77 | 78 | freq = 5; % in Hz 79 | colors = 'kmb'; 80 | symbols = 'op.'; 81 | 82 | 83 | figure(4), clf 84 | legendText = cell(size(timedur)); 85 | for parami=1:length(colors) 86 | 87 | % define sampling rate in this round 88 | srate = srates(parami); % in Hz 89 | 90 | % define time 91 | time = -1:1/srate:timedur(parami); 92 | 93 | % create signal (Morlet wavelet) 94 | signal = cos(2*pi*freq.*time) .* exp( (-time.^2) / .05 ); 95 | 96 | % compute FFT and normalize 97 | signalX = fft(signal)/length(signal); 98 | signalX = signalX./max(signalX); 99 | 100 | % define vector of frequencies in Hz 101 | hz = linspace( 0, srate/2, floor(length(time)/2)+1 ); 102 | 103 | 104 | % plot time-domain signal 105 | subplot(211) 106 | plot(time,signal,[colors(parami) symbols(parami) '-'],'markersize',10,'markerface',colors(parami)), hold on 107 | set(gca,'xlim',[-1 1]) 108 | xlabel('Time (s)'), ylabel('Amplitude') 109 | title('Time domain') 110 | 111 | % plot frequency-domain signal 112 | subplot(212), hold on 113 | plot(hz,abs(signalX(1:length(hz))),[colors(parami) symbols(parami) '-'],'markersize',10,'markerface',colors(parami)) 114 | set(gca,'xlim',[0 50]) 115 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 116 | title('Frequency domain') 117 | 118 | legendText{parami} = [ 'srate=' num2str(srates(parami)) ', N=' num2str(timedur(parami)+1) 's' ]; 119 | end 120 | 121 | legend(legendText) 122 | 123 | %% zero-padding 124 | 125 | figure(5), clf 126 | 127 | signal = [ 1 0 1 2 -3 1 2 0 -3 0 -1 2 1 -1]; 128 | 129 | % compute the FFT of the same signal with different DC offsets 130 | signalX1 = fft(signal,length(signal)) / length(signal); 131 | signalX2 = fft(signal,length(signal)+10) / length(signal); 132 | signalX3 = fft(signal,length(signal)+100) / length(signal); 133 | 134 | % define frequencies vector 135 | frex1 = linspace( 0, .5, floor(length(signalX1)/2)+1 ); 136 | frex2 = linspace( 0, .5, floor(length(signalX2)/2)+1 ); 137 | frex3 = linspace( 0, .5, floor(length(signalX3)/2)+1 ); 138 | 139 | 140 | % plot signals in the time domain 141 | subplot(211) 142 | plot(ifft(signalX1)*length(signal),'bo-'), hold on 143 | plot(ifft(signalX2)*length(signal),'rd-'), hold on 144 | plot(ifft(signalX3)*length(signal),'k*-'), hold on 145 | 146 | 147 | % plot signals in the frequency domain 148 | subplot(212) 149 | plot(frex1,2*abs(signalX1(1:length(frex1))),'bo-'), hold on 150 | plot(frex2,2*abs(signalX2(1:length(frex2))),'rd-') 151 | plot(frex3,2*abs(signalX3(1:length(frex3))),'k*-') 152 | 153 | xlabel('Normalized frequency units'), ylabel('Amplitude') 154 | legend({'"Native" N';'N+10';'N+100'}) 155 | 156 | 157 | %% end. 158 | -------------------------------------------------------------------------------- /1_discrete-time_Fourier_transform/5_fftstationarity.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% amplitude non-stationarity 4 | 5 | srate = 1000; 6 | t = 0:1/srate:10; 7 | n = length(t); 8 | f = 3; % frequency in Hz 9 | 10 | % sine wave with time-increasing amplitude 11 | ampl1 = linspace(1,10,n); 12 | % ampl1 = abs(interp1(linspace(t(1),t(end),10),10*rand(1,10),t,'spline')); 13 | ampl2 = mean(ampl1); 14 | 15 | signal1 = ampl1 .* sin(2*pi*f*t); 16 | signal2 = ampl2 .* sin(2*pi*f*t); 17 | 18 | 19 | signal1X = fft(signal1)/n; 20 | signal2X = fft(signal2)/n; 21 | hz = linspace(0,srate/2,floor(n/2)+1); 22 | 23 | figure(1), clf 24 | subplot(211) 25 | plot(t,signal2,'r'), hold on 26 | plot(t,signal1) 27 | xlabel('Time'), ylabel('amplitude') 28 | 29 | subplot(212) 30 | plot(hz,2*abs(signal2X(1:length(hz))),'ro-','markerface','r') 31 | hold on 32 | plot(hz,2*abs(signal1X(1:length(hz))),'s-','markerface','k') 33 | 34 | xlabel('Frequency (Hz)'), ylabel('amplitude') 35 | set(gca,'xlim',[1 7]) 36 | legend({'Stationary';'Non-stationary'}) 37 | 38 | %% frequency non-stationarity 39 | 40 | f = [2 10]; 41 | ff = linspace(f(1),mean(f),n); 42 | signal1 = sin(2*pi.*ff.*t); 43 | signal2 = sin(2*pi.*mean(ff).*t); 44 | 45 | 46 | signal1X = fft(signal1)/n; 47 | signal2X = fft(signal2)/n; 48 | hz = linspace(0,srate/2,floor(n/2)); 49 | 50 | figure(2), clf 51 | 52 | subplot(211) 53 | plot(t,signal1), hold on 54 | plot(t,signal2,'r') 55 | xlabel('Time'), ylabel('amplitude') 56 | set(gca,'ylim',[-1.1 1.1]) 57 | 58 | subplot(212) 59 | plot(hz,2*abs(signal1X(1:length(hz))),'.-'), hold on 60 | plot(hz,2*abs(signal2X(1:length(hz))),'r.-') 61 | xlabel('Frequency (Hz)'), ylabel('amplitude') 62 | set(gca,'xlim',[0 20]) 63 | 64 | %% sharp transitions 65 | 66 | a = [10 2 5 8]; 67 | f = [3 1 6 12]; 68 | 69 | timechunks = round(linspace(1,n,length(a)+1)); 70 | 71 | signal = 0; 72 | for i=1:length(a) 73 | signal = cat(2,signal,a(i)* sin(2*pi*f(i)*t(timechunks(i):timechunks(i+1)-1) )); 74 | end 75 | 76 | signalX = fft(signal)/n; 77 | hz = linspace(0,srate/2,floor(n/2)+1); 78 | 79 | figure(3), clf 80 | subplot(211) 81 | plot(t,signal) 82 | xlabel('Time'), ylabel('amplitude') 83 | 84 | subplot(212) 85 | plot(hz,2*abs(signalX(1:length(hz))),'s-','markerface','k') 86 | xlabel('Frequency (Hz)'), ylabel('amplitude') 87 | set(gca,'xlim',[0 20]) 88 | 89 | 90 | %% edges and edge artifacts 91 | 92 | x = (linspace(0,1,n)>.5)+0; % +0 converts from boolean to number 93 | % x = x + .08*sin(2*pi*6*t); 94 | 95 | % plot 96 | figure(4), clf 97 | subplot(211) 98 | plot(t,x) 99 | set(gca,'ylim',[-.1 1.1]) 100 | xlabel('Time (s.)'), ylabel('Amplitude (a.u.)') 101 | 102 | subplot(212) 103 | xX = fft(x)/n; 104 | plot(hz,2*abs(xX(1:length(hz)))) 105 | set(gca,'xlim',[0 20],'ylim',[0 .1]) 106 | xlabel('Frequency (Hz)'), ylabel('Amplitude (a.u.)') 107 | 108 | %% 109 | 110 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/1_convolutionTime.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% convolution in a small example 4 | 5 | % signal 6 | signal = zeros(1,20); 7 | signal(8:15)=1; 8 | 9 | kernel = [1 .8 .6 .4 .2]; 10 | 11 | % matlab's convolution function 12 | matlab_conv_result = conv(signal,kernel,'same'); 13 | 14 | figure(1), clf 15 | % plot the signal (impulse or boxcar) 16 | subplot(311) 17 | plot(signal,'o-','linew',2,'markerface','g','markersize',9) 18 | set(gca,'ylim',[-.1 1.1],'xlim',[1 20]) 19 | title('Signal') 20 | 21 | % plot the kernel 22 | subplot(312) 23 | plot(kernel,'o-','linew',2,'markerface','r','markersize',9) 24 | set(gca,'xlim',[1 20],'ylim',[-.1 1.1]) 25 | title('Kernel') 26 | 27 | % plot the result of convolution 28 | subplot(313) 29 | plot(matlab_conv_result,'o-','linew',2,'markerface','b','markersize',9) 30 | set(gca,'xlim',[1 20],'ylim',[-.1 3.6]) 31 | title('Result of convolution') 32 | 33 | %% "manual" time-domain convolution 34 | 35 | half_kernel = floor(length(kernel)/2); 36 | 37 | % EEG data that we'll use for convolution (must be zero-padded). 38 | dat4conv = [ zeros(1,half_kernel) signal zeros(1,half_kernel) ]; 39 | 40 | % initialize convolution output 41 | conv_res = zeros(1,length(signal)+length(kernel)-1); 42 | 43 | % run convolution 44 | for ti=1:length(dat4conv)-length(kernel) 45 | tempdata = dat4conv(ti:ti+length(kernel)-1); 46 | conv_res(ti+half_kernel) = sum( tempdata .* kernel(end:-1:1) ); 47 | %conv_res(ti+half_kernel) = sum( tempdata .* kernel); 48 | end 49 | 50 | % cut off edges 51 | conv_res = conv_res(half_kernel+1:end-half_kernel); 52 | 53 | figure(2), clf 54 | 55 | plot(conv_res,'o-','linew',2,'markerface','g','markersize',9) 56 | hold on 57 | plot(matlab_conv_result,'o-','linew',2,'markerface','r','markersize',3) 58 | legend({'Time-domain convolution';'Matlab conv function'}) 59 | 60 | %% 61 | 62 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/2_morletWavelet.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% create a Morlet wavelet 4 | 5 | srate = 1000; % in hz 6 | time = -2:1/srate:2; % best practice is to have time=0 at the center of the wavelet 7 | frex = 6.5; % frequency of wavelet, in Hz 8 | 9 | % create complex sine wave 10 | sine_wave = exp( 1i*2*pi*frex.*time ); 11 | 12 | % create Gaussian window 13 | s = 7 / (2*pi*frex); % this is the standard deviation of the gaussian 14 | gaus_win = exp( (-time.^2) ./ (2*s^2) ); 15 | 16 | 17 | % now create Morlet wavelet 18 | cmw = sine_wave.*gaus_win; 19 | 20 | 21 | figure(1), clf 22 | 23 | subplot(211) 24 | plot(time,real(cmw)) 25 | xlabel('Time (s)'), ylabel('Amplitude') 26 | title([ 'Real part of wavelet at ' num2str(frex) ' Hz' ]) 27 | 28 | subplot(212) 29 | plot(time,imag(cmw)) 30 | xlabel('Time (s)'), ylabel('Amplitude') 31 | title([ 'Imaginary part of wavelet at ' num2str(frex) ' Hz' ]) 32 | 33 | 34 | figure(2), clf 35 | plot3(time,real(cmw),imag(cmw),'linew',3) 36 | axis image 37 | xlabel('Time (s)'), ylabel('Real part'), zlabel('Imaginary part') 38 | rotate3d 39 | 40 | %% Morlet wavelet in the frequency domain 41 | 42 | cmwX = fft(cmw)/length(cmw); 43 | 44 | hz = linspace(0,srate/2,floor(length(cmw)/2)+1); 45 | 46 | figure(3), clf 47 | plot(hz,2*abs(cmwX(1:length(hz))),'o-','linew',3,'markerface','k','markersize',8) 48 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 49 | set(gca,'xlim',[0 frex*4]) 50 | 51 | %% 52 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/3_convolutionAsFreqMult.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% load in EEG data and pick and electrode, trial, etc. 4 | 5 | load sampleEEGdata.mat 6 | 7 | chan2use = 'fz'; 8 | trial2use = 34; 9 | 10 | % extract this bit of data for convenience in the rest of this script 11 | data = squeeze(EEG.data( strcmpi(chan2use,{EEG.chanlocs.labels}), :,trial2use)); 12 | 13 | %% create a Morlet wavelet 14 | 15 | srate = EEG.srate; % in hz 16 | time = -2:1/srate:2; % best practice is to have time=0 at the center of the wavelet 17 | frex = 6.5; % frequency of wavelet, in Hz 18 | 19 | % create complex sine wave 20 | sine_wave = exp(1i*2*pi*frex*time); 21 | 22 | % create Gaussian window 23 | s = 7 / (2*pi*frex); % this is the standard deviation of the gaussian 24 | gaus_win = exp(-time.^2 ./ (2*s^2)); 25 | 26 | % now create Morlet wavelet 27 | cmw = sine_wave .* gaus_win; 28 | 29 | %% define convolution parameters 30 | 31 | nData = length(data); 32 | nKern = length(cmw); 33 | nConv = nData + nKern - 1; 34 | 35 | %% FFTs 36 | 37 | % note that the "N" parameter is the length of convolution, NOT the length 38 | % of the original signals! Super-important! 39 | 40 | 41 | % FFT of wavelet, and amplitude-normalize in the frequency domain 42 | cmwX = fft(cmw,nConv); 43 | cmwX = cmwX ./ max(cmwX); 44 | 45 | 46 | % FFT of data 47 | dataX = fft(data,nConv); 48 | 49 | 50 | 51 | % now for convolution... 52 | conv_res = dataX.*cmwX; 53 | 54 | 55 | % compute hz for plotting 56 | hz = linspace(0,srate/2,floor(nConv/2)+1); 57 | 58 | %% some plots... 59 | 60 | figure(1), clf 61 | 62 | % plot power spectrum of data 63 | subplot(311) 64 | plot(hz,2*abs(dataX(1:length(hz))/length(data))) 65 | set(gca,'xlim',[0 30]) 66 | 67 | % plot power spectrum of wavelet 68 | subplot(312) 69 | plot(hz,abs(cmwX(1:length(hz)))) 70 | set(gca,'xlim',[0 30]) 71 | 72 | % plot power spectrum of convolution result 73 | subplot(313) 74 | plot(hz,2*abs(conv_res(1:length(hz))/length(data))) 75 | set(gca,'xlim',[0 30]) 76 | 77 | 78 | %% now back to the time domain 79 | 80 | length(conv_res) 81 | EEG.pnts 82 | 83 | % cut 1/2 of the length of the wavelet from the beginning and from the end 84 | half_wav = floor( length(cmw)/2 )+1; 85 | 86 | % take inverse Fourier transform 87 | conv_res_timedomain = ifft(conv_res); 88 | 89 | conv_res_timedomain = conv_res_timedomain(half_wav-1:end-half_wav); 90 | 91 | figure(2), clf 92 | plot(EEG.times,data,'k') 93 | hold on 94 | plot(EEG.times,real(conv_res_timedomain),'r','linew',2) 95 | set(gca,'xlim',[-200 1200]) 96 | legend({'EEG data';'convolution-filtered data'}) 97 | 98 | %% 99 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/4_eulers_and_convolution.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | 4 | %% load in EEG data and pick and electrode, trial, etc. 5 | 6 | load sampleEEGdata.mat 7 | 8 | chan2use = 'fz'; 9 | trial2use = 34; 10 | 11 | % extract this bit of data for convenience in the rest of this script 12 | data = squeeze(EEG.data( strcmpi(chan2use,{EEG.chanlocs.labels}), :,trial2use)); 13 | 14 | %% create a Morlet wavelet 15 | 16 | srate = EEG.srate; % in hz 17 | time = -2:1/srate:2; % best practice is to have time=0 at the center of the wavelet 18 | frex = 6.5; % frequency of wavelet, in Hz 19 | 20 | % create complex sine wave 21 | sine_wave = exp( 1i*2*pi*frex.*time ); 22 | 23 | % create Gaussian window 24 | s = 7 / (2*pi*frex); % this is the standard deviation of the gaussian 25 | gaus_win = exp( (-time.^2) ./ (2*s^2) ); 26 | 27 | % now create Morlet wavelet 28 | cmw = sine_wave .* gaus_win; 29 | 30 | %% define convolution parameters 31 | 32 | nData = length(data); 33 | nKern = length(cmw); 34 | nConv = nData + nKern - 1; 35 | half_wav = floor( length(cmw)/2 )+1; 36 | 37 | %% FFTs 38 | 39 | % note that the "N" parameter is the length of convolution, NOT the length 40 | % of the original signals! Super-important! 41 | 42 | 43 | % FFT of wavelet, and amplitude-normalize in the frequency domain 44 | cmwX = fft(cmw,nConv); 45 | cmwX = cmwX ./ max(cmwX); 46 | 47 | % FFT of data 48 | dataX = fft(data,nConv); 49 | 50 | % now for convolution... 51 | as = ifft( dataX .* cmwX ); 52 | 53 | % cut 1/2 of the length of the wavelet from the beginning and from the end 54 | as = as(half_wav-1:end-half_wav); 55 | 56 | %% different ways of visualizing the outputs of convolution 57 | 58 | figure(1), clf 59 | plot3(EEG.times,real(as),imag(as),'k') 60 | xlabel('Time (ms)'), ylabel('real part'), zlabel('imaginary part') 61 | rotate3d 62 | set(gca,'xlim',[-300 1200]) 63 | 64 | figure(2), clf 65 | plot3(EEG.times,abs(as),angle(as),'k') 66 | xlabel('Time (ms)'), ylabel('Amplitude'), zlabel('Phase') 67 | rotate3d 68 | set(gca,'xlim',[-300 1200]) 69 | 70 | 71 | 72 | figure(3), clf 73 | % plot the filtered signal (projection onto real axis) 74 | subplot(311) 75 | plot(EEG.times,real(as)) 76 | xlabel('Time (ms)'), ylabel('Amplitude (\muV)') 77 | set(gca,'xlim',[-200 1300]) 78 | 79 | 80 | % plot power (squared magnitude from origin to dot-product location in 81 | % complex space) 82 | subplot(312) 83 | plot(EEG.times,abs(as).^2) 84 | xlabel('Time (ms)'), ylabel('Power \muV^2') 85 | set(gca,'xlim',[-200 1300]) 86 | 87 | 88 | % plot phase (angle of vector to dot-product, relative to positive real 89 | % axis) 90 | subplot(313) 91 | plot(EEG.times,angle(as)) 92 | xlabel('Time (ms)'), ylabel('Phase (rad.)') 93 | set(gca,'xlim',[-200 1300]) 94 | 95 | %% final view: in a polar plot 96 | 97 | figure(4), clf 98 | 99 | % setup the time course plot 100 | subplot(212) 101 | h = plot(EEG.times,real(as),'k','linew',2); 102 | set(gca,'xlim',EEG.times([1 end]),'ylim',[min(real(as)) max(real(as)) ]) 103 | xlabel('Time (ms)'), ylabel('Amplitude (\muV)') 104 | 105 | 106 | for ti=1:5:EEG.pnts 107 | 108 | % draw complex values in polar space 109 | subplot(211) 110 | polar(0,max(abs(as))), hold on 111 | polar(angle(as(max(1,ti-100):ti)),abs(as(max(1,ti-100):ti)),'k') 112 | text(-.75,0,[ num2str(round(EEG.times(ti))) ' ms' ]), hold off 113 | 114 | % now show in 'linear' plot 115 | set(h,'XData',EEG.times(max(1,ti-100):ti),'YData',real(as(max(1,ti-100):ti))) 116 | 117 | drawnow 118 | pause(.1) 119 | end 120 | 121 | %% 122 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/5_convolution_with_many_trials.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | % Process many trials (combine into one long trial and then perform convolution) 3 | %% Load data, define variables 4 | 5 | load sampleEEGdata.mat; 6 | 7 | % frequency parameters 8 | min_freq = 2; 9 | max_freq = 30; 10 | num_frex = 40; 11 | frex = linspace(min_freq,max_freq,num_frex); 12 | 13 | % which channel to plot 14 | channel2use = 'o1'; 15 | 16 | % other wavelet parameters 17 | range_cycles = [4 10]; 18 | s = logspace(log10(range_cycles(1)), log10(range_cycles(end)), num_frex) ./ (2*pi*frex); 19 | wavtime = -2:1/EEG.srate:2; 20 | half_wave = (length(wavtime)-1)/2; 21 | 22 | %% Naive solution 23 | 24 | % Naive solution is to iterate over channels (only one channel here), 25 | % frequencies and trials. However, this approach can be optimized: 26 | % 1) Combine data trials into one long "super trial", process it and then 27 | % split it back into trials 28 | % 2) Perform FFT of EEG.data for specific channel once, i.e. outside of the 29 | % loop over frequencies 30 | 31 | %% Process many trials optimally 32 | 33 | % FFT parameters 34 | nWave = length(wavtime); 35 | nData = EEG.pnts * EEG.trials; 36 | nConv = nWave + nData - 1; 37 | 38 | % initialize output time-frequency data 39 | tf = zeros(length(frex),EEG.pnts); 40 | 41 | % now compute the FFT of all trials concatenated 42 | alldata = reshape(EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:),1,[]); 43 | dataX = fft(alldata, nConv); 44 | 45 | % loop over frequencies 46 | for fi=1:length(frex) 47 | % create wavelet and get its FFT 48 | % the wavelet doesn't change on each trial... 49 | wavelet = exp(1i*2*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 50 | waveletX = fft(wavelet,nConv); 51 | waveletX = waveletX ./ max(waveletX); 52 | 53 | % now run convolution in one step 54 | as = ifft(waveletX .* dataX); 55 | as = as(half_wave+1:end-half_wave); 56 | 57 | % and reshape back to time X trials 58 | as = reshape(as, EEG.pnts, EEG.trials); 59 | 60 | % compute power and average over trials 61 | tf(fi,:) = mean(abs(as).^2, 2); 62 | end 63 | 64 | %% Plots 65 | 66 | figure(1), clf 67 | contourf(EEG.times,frex,tf,40,'linecolor','none') 68 | set(gca, 'CLim', [0,5], 'ydir','normal','xlim',[-300,1000]) 69 | 70 | figure(2), clf 71 | plot(EEG.times,tf(5,:)) 72 | set(gca,'xlim',[-300,1200]) 73 | 74 | title(['Power at ', num2str(round(10*frex(5))/10), ' Hz']) 75 | xlabel('Time (ms)'), ylabel('Power (\muV^2)') 76 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/6_waveletparams.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% show Gaussian with different number of cycles 4 | 5 | load sampleEEGdata.mat 6 | 7 | % set a few different wavelet widths ("number of cycles" parameter) 8 | num_cycles = [ 2 6 8 15 ]; 9 | frex = 6.5; 10 | 11 | time = -2:1/EEG.srate:2; 12 | 13 | figure(1), clf 14 | for i=1:4 15 | subplot(4,1,i) 16 | s = num_cycles(i) / (2*pi*frex); 17 | plot(time, exp( (-time.^2) ./ (2*s^2) ),'k','linew',3) 18 | title([ 'Gaussian with ' num2str(num_cycles(i)) ' cycles' ]) 19 | end 20 | 21 | xlabel('Time (s)'), ylabel('Amplitude') 22 | 23 | %% show frequency spectrum of wavelets with different number of cycles 24 | 25 | figure(2), clf 26 | for i=1:4 27 | subplot(4,1,i) 28 | s = num_cycles(i) / (2*pi*frex); 29 | cmw = exp(1i*2*pi*frex.*time) .* exp( (-time.^2) ./ (2*s^2) ); 30 | 31 | % take its FFT 32 | cmwX = fft(cmw); 33 | cmwX = cmwX./max(cmwX); 34 | hz = linspace(0,EEG.srate/2,floor(length(time)/2)+1); 35 | 36 | % plot it 37 | plot(hz,abs(cmwX(1:length(hz))),'k','linew',3) 38 | set(gca,'xlim',[0 20]) 39 | title([ 'Power of wavelet with ' num2str(num_cycles(i)) ' cycles' ]) 40 | end 41 | 42 | xlabel('Frequency (Hz)'), ylabel('Amplitude') 43 | 44 | %% comparing fixed number of wavelet cycles 45 | 46 | % wavelet parameters 47 | num_frex = 40; 48 | min_freq = 2; 49 | max_freq = 30; 50 | 51 | channel2use = 'o1'; 52 | 53 | baseline_window = [ -500 -200 ]; 54 | 55 | % set a few different wavelet widths ("number of cycles" parameter) 56 | num_cycles = [ 2 6 8 15 ]; 57 | 58 | % other wavelet parameters 59 | frex = linspace(min_freq,max_freq,num_frex); 60 | time = -2:1/EEG.srate:2; 61 | half_wave = (length(time)-1)/2; 62 | 63 | % FFT parameters 64 | nKern = length(time); 65 | nData = EEG.pnts*EEG.trials; 66 | nConv = nKern+nData-1; 67 | 68 | % initialize output time-frequency data 69 | tf = zeros(length(num_cycles),length(frex),EEG.pnts); 70 | 71 | % convert baseline time into indices 72 | [~,baseidx(1)] = min(abs(EEG.times-baseline_window(1))); 73 | [~,baseidx(2)] = min(abs(EEG.times-baseline_window(2))); 74 | 75 | 76 | % FFT of data (doesn't change on frequency iteration) 77 | dataX = fft(reshape(EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:),1,[]),nConv); 78 | 79 | % loop over cycles 80 | for cyclei=1:length(num_cycles) 81 | 82 | for fi=1:length(frex) 83 | 84 | % create wavelet and get its FFT 85 | s = num_cycles(cyclei)/(2*pi*frex(fi)); 86 | 87 | cmw = exp(2*1i*pi*frex(fi).*time) .* exp(-time.^2./(2*s^2)); 88 | cmwX = fft(cmw,nConv); 89 | cmwX = cmwX./max(cmwX); 90 | 91 | % run convolution, trim edges, and reshape to 2D (time X trials) 92 | as = ifft(cmwX.*dataX); 93 | as = as(half_wave+1:end-half_wave); 94 | as = reshape(as, EEG.pnts, EEG.trials); 95 | 96 | % put power data into big matrix 97 | tf(cyclei,fi,:) = mean(abs(as).^2,2); 98 | end 99 | 100 | % db conversion 101 | tf(cyclei,:,:) = 10*log10( bsxfun(@rdivide, squeeze(tf(cyclei,:,:)), mean(tf(cyclei,:,baseidx(1):baseidx(2)),3)' ) ); 102 | 103 | end 104 | 105 | % plot results 106 | figure(3), clf 107 | for cyclei=1:length(num_cycles) 108 | subplot(2,2,cyclei) 109 | 110 | contourf(EEG.times,frex,squeeze(tf(cyclei,:,:)),40,'linecolor','none') 111 | set(gca,'clim',[-3 3],'ydir','normal','xlim',[-300 1000]) 112 | title([ 'Wavelet with ' num2str(num_cycles(cyclei)) ' cycles' ]) 113 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 114 | end 115 | 116 | %% variable number of wavelet cycles 117 | 118 | % set a few different wavelet widths (number of wavelet cycles) 119 | range_cycles = [ 4 10 ]; 120 | 121 | % other wavelet parameters 122 | nCycles = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex); 123 | 124 | % initialize output time-frequency data 125 | tf = zeros(length(frex),EEG.pnts); 126 | 127 | for fi=1:length(frex) 128 | 129 | % create wavelet and get its FFT 130 | s = nCycles(fi)/(2*pi*frex(fi)); 131 | cmw = exp(2*1i*pi*frex(fi).*time) .* exp(-time.^2./(2*s^2)); 132 | 133 | cmwX = fft(cmw,nConv); 134 | 135 | % run convolution 136 | as = ifft(cmwX.*dataX,nConv); 137 | as = as(half_wave+1:end-half_wave); 138 | as = reshape(as,EEG.pnts,EEG.trials); 139 | 140 | % put power data into big matrix 141 | tf(fi,:) = mean(abs(as).^2,2); 142 | end 143 | 144 | % db conversion 145 | tf = 10*log10( bsxfun(@rdivide, tf, mean(tf(:,baseidx(1):baseidx(2)),2)) ); 146 | 147 | % plot results 148 | figure(4), clf 149 | subplot(2,2,1) 150 | 151 | contourf(EEG.times,frex,tf,40,'linecolor','none') 152 | set(gca,'clim',[-3 3],'ydir','normal','xlim',[-300 1000]) 153 | title('Convolution with a range of cycles') 154 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 155 | 156 | %% end 157 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/7_inter_trial_phase_clustering.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% Compute and plot TF-ITPC for one electrode 4 | 5 | load sampleEEGdata.mat 6 | 7 | % wavelet parameters 8 | num_frex = 40; 9 | min_freq = 2; 10 | max_freq = 30; 11 | 12 | channel2use = 'pz'; 13 | 14 | % set range for variable number of wavelet cycles 15 | range_cycles = [ 4 10 ]; 16 | 17 | % other wavelet parameters 18 | frex = logspace(log10(min_freq),log10(max_freq),num_frex); 19 | wavecycles = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex); 20 | time = -2:1/EEG.srate:2; 21 | half_wave_size = (length(time)-1)/2; 22 | 23 | % FFT parameters 24 | nWave = length(time); 25 | nData = EEG.pnts*EEG.trials; 26 | nConv = nWave+nData-1; 27 | 28 | 29 | % FFT of data (doesn't change on frequency iteration) 30 | dataX = fft( reshape(EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:),1,nData) ,nConv); 31 | 32 | % initialize output time-frequency data 33 | tf = zeros(num_frex,EEG.pnts); 34 | 35 | % loop over frequencies 36 | for fi=1:num_frex 37 | 38 | % create wavelet and get its FFT 39 | s = wavecycles(fi)/(2*pi*frex(fi)); 40 | wavelet = exp(2*1i*pi*frex(fi).*time) .* exp(-time.^2./(2*s^2)); 41 | waveletX = fft(wavelet,nConv); 42 | 43 | % run convolution 44 | as = ifft(waveletX.*dataX,nConv); 45 | as = as(half_wave_size+1:end-half_wave_size); 46 | as = reshape(as,EEG.pnts,EEG.trials); 47 | 48 | % compute ITPC 49 | phase = atan2(imag(as), real(as)); % angle(as) 50 | tf(fi,:) = abs(mean(exp(1i*phase),2)); 51 | end 52 | 53 | % plot results 54 | figure(1), clf 55 | contourf(EEG.times,frex,tf,40,'linecolor','none') 56 | set(gca,'clim',[0 .6],'ydir','normal','xlim',[-300 1000]) 57 | title('ITPC') 58 | 59 | 60 | %% illustration of ITPC with different variances 61 | 62 | % specify parameters 63 | circ_prop = .5; % proportion of the circle to fill 64 | N = 100; % number of "trials" 65 | 66 | % generate phase angle distribution 67 | simdata = rand(1,N) * (2*pi) * circ_prop; 68 | 69 | 70 | % compute ITPC and preferred phase angle 71 | itpc = abs(mean(exp(1i*simdata))); 72 | prefAngle = angle(mean(exp(1i*simdata))); 73 | 74 | 75 | % and plot... 76 | figure(2), clf 77 | 78 | % as linear histogram 79 | subplot(3,3,4) 80 | hist(simdata,20) 81 | xlabel('Phase angle'), ylabel('Count') 82 | set(gca,'xlim',[0 2*pi]) 83 | title([ 'Observed ITPC: ' num2str(itpc) ]) 84 | 85 | % and as polar distribution 86 | subplot(1,2,2) 87 | polar([zeros(1,N); simdata],[zeros(1,N); ones(1,N)],'k') 88 | hold on 89 | h = polar([0 prefAngle],[0 itpc],'m'); 90 | set(h,'linew',3) 91 | title([ 'Observed ITPC: ' num2str(itpc) ]) 92 | 93 | %% end. 94 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/8_nonphaselocked.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% prepare details and parameters for time-frequency decomposition 4 | 5 | load sampleEEGdata 6 | 7 | channel2plot = 'o1'; 8 | 9 | % wavelet parameters 10 | min_freq = 2; 11 | max_freq = 30; 12 | num_frex = 20; 13 | 14 | 15 | % baseline time window 16 | baseline_time = [ -400 -100 ]; 17 | % convert baseline from ms to indices 18 | baseidx = dsearchn(EEG.times',baseline_time'); 19 | 20 | 21 | % other wavelet parameters 22 | frex = logspace(log10(min_freq),log10(max_freq),num_frex); 23 | time = -1:1/EEG.srate:1; 24 | half_wave = (length(time)-1)/2; 25 | 26 | % FFT parameters 27 | nKern = length(time); 28 | nData = EEG.pnts*EEG.trials; 29 | nConv(1:2) = nKern+nData-1; 30 | nConv(3) = nKern+EEG.pnts-1; % ERP is only one trial-length 31 | 32 | % find sensor index 33 | chanidx = find(strcmpi(channel2plot,{EEG.chanlocs.labels})); 34 | 35 | % initialize output time-frequency data 36 | tf = zeros(4,length(frex),EEG.pnts); 37 | 38 | %% prepare non-phase-locked activity 39 | 40 | % compute ERP 41 | erp = squeeze(mean(EEG.data(chanidx,:,:),3)); 42 | 43 | % compute non-phase-locked power by subtracting ERP from each trial 44 | nonphase_EEG = squeeze( bsxfun(@minus,EEG.data(chanidx,:,:),erp) ); 45 | 46 | 47 | figure(1), clf 48 | subplot(311) 49 | plot(EEG.times,erp) 50 | xlabel('Time (ms)'), ylabel('Voltage (\muV)') 51 | set(gca,'xlim',[-300 1300]) 52 | 53 | subplot(312) 54 | plot(EEG.times,EEG.data(chanidx,:,10)) 55 | hold on 56 | plot(EEG.times,squeeze(nonphase_EEG(:,10)),'r') 57 | legend({'total';'non-phase-locked'}) 58 | xlabel('Time (ms)'), ylabel('Voltage (\muV)') 59 | set(gca,'xlim',[-300 1300]) 60 | 61 | subplot(313) 62 | plot(EEG.times,erp) 63 | hold on 64 | plot(EEG.times,mean(nonphase_EEG,2),'r') 65 | legend({'total ERP';'non-phase-locked ERP'}) 66 | xlabel('Time (ms)'), ylabel('Voltage (\muV)') 67 | set(gca,'xlim',[-300 1300]) 68 | 69 | %% time-frequency decomposition 70 | 71 | % FFT of total data 72 | fft_EEG{1} = fft( reshape(EEG.data(chanidx,:,:),1,[]), nConv(1)); 73 | % FFT of nonphase-locked data 74 | fft_EEG{2} = fft( reshape(nonphase_EEG,1,[]), nConv(2)); 75 | % FFT of ERP (phase-locked data) 76 | fft_EEG{3} = fft( erp ,nConv(3)); 77 | 78 | 79 | for fi=1:length(frex) 80 | 81 | % create wavelet and get its FFT 82 | s = 6/(2*pi*frex(fi)); 83 | wavelet = exp(2*1i*pi*frex(fi).*time) .* exp(-time.^2./(2*s^2)); 84 | 85 | % run convolution for each of total, induced, and evoked 86 | for methodi=1:3 87 | 88 | % need separate FFT 89 | waveletX = fft(wavelet,nConv(methodi)); 90 | 91 | % notice that the fft_EEG cell changes on each iteration 92 | as = ifft(waveletX.*fft_EEG{methodi},nConv(methodi)); 93 | as = as(half_wave+1:end-half_wave); 94 | if methodi<3 95 | as = reshape(as,EEG.pnts,EEG.trials); 96 | 97 | % compute power 98 | temppow = mean(abs(as).^2,2); 99 | else 100 | temppow = abs(as).^2; 101 | end 102 | 103 | % db correct power 104 | tf(methodi,fi,:) = 10*log10( temppow ./ mean(temppow(baseidx(1):baseidx(2))) ); 105 | 106 | % inter-trial phase consistency on total EEG 107 | if methodi==1 108 | tf(4,fi,:) = abs(mean(exp(1i*angle(as)),2)); 109 | end 110 | end % end loop around total, evoked, induced 111 | end % end frequency loop 112 | 113 | 114 | 115 | analysis_labels = {'Total';'Non-phase-locked';'ERP power';'ITPC'}; 116 | 117 | % color limits 118 | clims = [ -3 3; -3 3; -15 15; 0 .6 ]; 119 | 120 | % next two lines scale the ERP for plotting on top of the TF plot 121 | erpt = (erp-min(erp))./max(erp-min(erp)); 122 | erpt = erpt*(frex(end)-frex(1))+frex(1); 123 | 124 | figure(2), clf 125 | for methodi=1:4 126 | 127 | subplot(2,3,methodi) 128 | contourf(EEG.times,frex,squeeze(tf(methodi,:,:)),40,'linecolor','none') 129 | 130 | set(gca,'clim',clims(methodi,:),'xlim',[-400 1000],'xtick',-200:200:800) 131 | xlabel('Time (ms)') 132 | ylabel('Frequency (Hz)') 133 | title(analysis_labels{methodi}) 134 | 135 | % plot ERP on top 136 | hold on 137 | plot(EEG.times,erpt,'k') 138 | end 139 | 140 | subplot(235) 141 | 142 | % estimate phase-locked part of the signal as the difference 143 | % between the phase-locked and non-phase-locked 144 | phaselockedTF = squeeze(tf(1,:,:) - tf(2,:,:)); 145 | % the next line is equivalent to the previous line, just FYI 146 | % phaselockedTF = squeeze(diff(tf([2 1],:,:),[],1)); 147 | 148 | contourf(EEG.times,frex,phaselockedTF,40,'linecolor','none') 149 | set(gca,'clim',clims(1,:),'xlim',[-400 1000],'xtick',-200:200:800) 150 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 151 | title('Phase-locked') 152 | 153 | % plot ERP on top 154 | hold on 155 | plot(EEG.times,erpt,'k') 156 | 157 | %% end. 158 | -------------------------------------------------------------------------------- /2_Morlet_wavelet_convolution/my_script_check_reversed_signal.m: -------------------------------------------------------------------------------- 1 | % Check that convolution in time domain performed via the convolution 2 | % theorem is time-sensitive (uncomment line 10 to see that the result actually changes for the inverse signal) 3 | %% define signal 4 | srate = 1000; 5 | time = -2:1/srate:2; 6 | 7 | f = [4 10]; 8 | ff = linspace(f(1),f(2),length(time)); 9 | s1 = sin(2*pi*ff.*time); 10 | % s1 = s1(end:-1:1); 11 | 12 | % freq = 5; 13 | % s1 = sin(2*pi*freq*time); 14 | % coef = 1/400*(1:length(time)); 15 | % s1 = coef.*s1; 16 | 17 | figure(1) 18 | plot(time, s1) 19 | 20 | %% define wavelet 21 | 22 | wavelet_freq = 10; % frequency of wavelet, in Hz 23 | 24 | % create complex sine wave 25 | sine_wave = exp( 1i*2*pi*wavelet_freq*time ); 26 | 27 | % create Gaussian window 28 | s = 7 / (2*pi*wavelet_freq); % this is the standard deviation of the gaussian 29 | gaus_win = exp( (-time.^2) ./ (2*s^2) ); 30 | 31 | % now create Morlet wavelet 32 | cmw = sine_wave .* gaus_win; 33 | 34 | %% define convolution parameters 35 | 36 | nData = length(s1); 37 | nKern = length(cmw); 38 | nConv = nData + nKern - 1; 39 | half_wav = floor( length(cmw)/2 )+1; 40 | 41 | %% get fourier components 42 | 43 | % FFT of wavelet, and amplitude-normalize in the frequency domain 44 | cmwX = fft(cmw,nConv); 45 | cmwX = cmwX ./ max(cmwX); 46 | 47 | % FFT of data 48 | dataX = fft(s1,nConv); 49 | 50 | % now for convolution... 51 | conv_res = dataX.*cmwX; 52 | as = ifft(conv_res); 53 | 54 | % cut 1/2 of the length of the wavelet from the beginning and from the end 55 | as = as(half_wav-1:end-half_wav); 56 | 57 | % compute hz for plotting 58 | hz = linspace(0,srate/2,floor(nConv/2)+1); 59 | 60 | %% some plots... 61 | 62 | figure(2), clf 63 | 64 | % plot power spectrum of data 65 | subplot(311) 66 | plot(hz,2*abs(dataX(1:length(hz))/length(s1))) 67 | title('Power spectrum of data') 68 | xlabel('Frequency'), ylabel('Amplitude') 69 | set(gca,'xlim',[0 30]) 70 | 71 | % plot power spectrum of wavelet 72 | subplot(312) 73 | plot(hz,abs(cmwX(1:length(hz)))) 74 | title('Power spectrum of wavelet') 75 | xlabel('Frequency'), ylabel('Amplitude') 76 | set(gca,'xlim',[0 30]) 77 | 78 | % plot power spectrum of convolution result 79 | subplot(313) 80 | plot(hz,2*abs(conv_res(1:length(hz))/length(s1))) 81 | title('Power spectrum of convolution result') 82 | xlabel('Frequency'), ylabel('Amplitude') 83 | set(gca,'xlim',[0 30]) 84 | 85 | %% Plot convolution in time domain 86 | 87 | figure(3), clf 88 | % plot the filtered signal (projection onto real axis) 89 | subplot(311) 90 | plot(time,real(as)) 91 | xlabel('Time (s)'), ylabel('Amplitude (\muV)') 92 | set(gca,'xlim',[-2 2]) 93 | 94 | 95 | % plot power (squared magnitude from origin to dot-product location in 96 | % complex space) 97 | subplot(312) 98 | plot(time,abs(as).^2) 99 | xlabel('Time (s)'), ylabel('Power \muV^2') 100 | set(gca,'xlim',[-2 2]) 101 | 102 | 103 | % plot phase (angle of vector to dot-product, relative to positive real 104 | % axis) 105 | subplot(313) 106 | plot(time,angle(as)) 107 | xlabel('Time (ms)'), ylabel('Phase (rad.)') 108 | set(gca,'xlim',[-2 2]) -------------------------------------------------------------------------------- /3_TF_analysis_other_methods/1_hilbertX.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% the FFT-based hilbert transform 4 | 5 | % generate random numbers 6 | n = 21; 7 | randomnumbers = randn(n,1); 8 | 9 | % take FFT 10 | f = fft(randomnumbers); 11 | % create a copy that is multiplied by the complex operator 12 | complexf = 1i*f; 13 | 14 | % find indices of positive and negative frequencies 15 | posF = 2:floor(n/2)+mod(n,2); 16 | negF = ceil(n/2)+1+~mod(n,2):n; 17 | 18 | % rotate Fourier coefficients 19 | % (note 1: this works by computing the iAsin(2pft) component, i.e., the phase quadrature) 20 | % (note 2: positive frequencies are rotated counter-clockwise; negative frequencies are rotated clockwise) 21 | f(posF) = f(posF) + -1i*complexf(posF); 22 | f(negF) = f(negF) + 1i*complexf(negF); 23 | % The next two lines are an alternative and slightly faster method. 24 | % The book explains why this is equivalent to the previous two lines. 25 | % f(posF) = f(posF)*2; 26 | % f(negF) = f(negF)*0; 27 | 28 | % take inverse FFT 29 | hilbertx = ifft(f); 30 | 31 | % compare with Matlab function hilbert 32 | hilbertm = hilbert(randomnumbers); 33 | 34 | % plot results 35 | figure(1), clf 36 | subplot(211) 37 | plot(abs(hilbertm)) 38 | hold on 39 | plot(abs(hilbertx),'ro') 40 | set(gca,'xlim',[.5 n+.5]) 41 | legend({'Matlab Hilbert function';'"manual" Hilbert'}) 42 | title('magnitude of Hilbert transform') 43 | 44 | subplot(212) 45 | plot(angle(hilbertm)) 46 | hold on 47 | plot(angle(hilbertx),'ro') 48 | set(gca,'xlim',[.5 n+.5]) 49 | legend({'Matlab Hilbert function';'"manual" Hilbert'}) 50 | title('phase of Hilbert transform') 51 | 52 | %% try with real data 53 | 54 | load sampleEEGdata 55 | 56 | % ERP, and its hilbert transform 57 | erp = squeeze(mean(EEG.data(48,:,:),3)); 58 | erpH = hilbert(erp); 59 | 60 | figure(2), clf 61 | 62 | % plot ERP and real part of Hilbert transformed ERP 63 | subplot(311) 64 | plot(EEG.times,erp), hold on 65 | plot(EEG.times,real(erpH),'r') 66 | legend({'ERP';'real(hilbert(erp))'}) 67 | 68 | % plot ERP and magnitude 69 | subplot(312) 70 | plot(EEG.times,erp), hold on 71 | plot(EEG.times,abs(erpH),'r') 72 | legend({'ERP';'abs(hilbert(erp))'}) 73 | 74 | % plot ERP and phase angle time series 75 | subplot(313) 76 | plot(EEG.times,erp), hold on 77 | plot(EEG.times,angle(erpH),'r') 78 | legend({'ERP';'angle(hilbert(erp))'}) 79 | xlabel('Time (ms)'), ylabel('Voltage or radians') 80 | 81 | 82 | 83 | % plot as 3d line 84 | figure(3), clf 85 | plot3(EEG.times,real(erpH),imag(erpH)) 86 | xlabel('Time (ms)'), ylabel('Real part'), zlabel('Imaginary part') 87 | axis tight 88 | rotate3d 89 | 90 | %% done. 91 | -------------------------------------------------------------------------------- /3_TF_analysis_other_methods/2_firfilter.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% band-pass filtering 4 | 5 | load sampleEEGdata 6 | 7 | % specify Nyquist freuqency 8 | nyquist = EEG.srate/2; 9 | 10 | % filter frequency band 11 | filtbound = [4 10]; % Hz 12 | 13 | % transition width 14 | trans_width = 0.2; % fraction of 1, thus 20% 15 | 16 | % filter order 17 | filt_order = round(3*(EEG.srate/filtbound(1))); 18 | 19 | % frequency vector (as fraction of Nyquist 20 | ffrequencies = [ 0 (1-trans_width)*filtbound(1) filtbound (1+trans_width)*filtbound(2) nyquist ]/nyquist; 21 | 22 | % shape of filter (must be the same number of elements as frequency vector 23 | idealresponse = [ 0 0 1 1 0 0 ]; 24 | 25 | % get filter weights 26 | filterweights = firls(filt_order,ffrequencies,idealresponse); 27 | 28 | % plot for visual inspection 29 | figure(1), clf 30 | subplot(211) 31 | plot(ffrequencies*nyquist,idealresponse,'k--o','markerface','m') 32 | set(gca,'ylim',[-.1 1.1],'xlim',[-2 nyquist+2]) 33 | xlabel('Frequencies (Hz)'), ylabel('Response amplitude') 34 | 35 | subplot(212) 36 | plot((0:filt_order)*(1000/EEG.srate),filterweights) 37 | xlabel('Time (ms)'), ylabel('Amplitude') 38 | 39 | % apply filter to data 40 | filtered_data = zeros(EEG.nbchan,EEG.pnts); 41 | for chani=1:EEG.nbchan 42 | filtered_data(chani,:) = filtfilt(filterweights,1,double(EEG.data(chani,:,1))); 43 | end 44 | 45 | figure(2), clf 46 | plot(EEG.times,squeeze(EEG.data(47,:,1))) 47 | hold on 48 | plot(EEG.times,squeeze(filtered_data(47,:)),'r','linew',2) 49 | xlabel('Time (ms)'), ylabel('Voltage (\muV)') 50 | legend({'raw data';'filtered'}) 51 | 52 | %% compare three transition widths 53 | 54 | nyquist = EEG.srate/2; 55 | filtbond = [ 7 12 ]; 56 | t_widths = [.1 .15 .2]; 57 | filt_order = round(10*(EEG.srate/filtbond(1))); 58 | 59 | idealresponse = [ 0 0 1 1 0 0 ]; 60 | 61 | ffrequencies = zeros(3,6); 62 | filterweights = zeros(3,filt_order+1); 63 | 64 | % frequency vector (as fraction of Nyquist) 65 | for i=1:3 66 | ffrequencies(i,:) = [ 0 (1-t_widths(i))*filtbond(1) filtbond (1+t_widths(i))*filtbond(2) nyquist ]/nyquist; 67 | filterweights(i,:) = firls(filt_order,ffrequencies(i,:),idealresponse); 68 | end 69 | 70 | % plot 71 | figure(4), clf 72 | subplot(211) 73 | plot((1:filt_order+1)*(1000/EEG.srate),filterweights') 74 | xlabel('time (ms)') 75 | title('Time-domain filter kernel') 76 | 77 | filterFreqDomain = abs(fft(filterweights,[],2)); 78 | frequenciesHz = linspace(0,nyquist,floor(filt_order/2)+1); 79 | subplot(212) 80 | plot(frequenciesHz,filterFreqDomain(:,1:length(frequenciesHz))) 81 | set(gca,'xlim',[0 60],'ylim',[-.1 1.2]) 82 | xlabel('Frequencies (Hz)') 83 | title('Frequency-domain filter kernel') 84 | legend({'filter 10%','filter 15%','filter 20%'}) 85 | 86 | %% compute and plot power 87 | 88 | chan4filt = strcmpi('o1',{EEG.chanlocs.labels}); 89 | baseidx = dsearchn(EEG.times',[-400 -100]'); 90 | 91 | pow = zeros(3,EEG.pnts); 92 | 93 | for i=1:3 94 | filtered_data = reshape(filtfilt(filterweights(i,:),1,double(reshape(EEG.data(chan4filt,:,:),1,[]))),EEG.pnts,EEG.trials); 95 | 96 | temppow = mean(abs(hilbert(filtered_data)).^2,2); 97 | pow(i,:) = 10*log10( temppow./mean(temppow(baseidx(1):baseidx(2))) ); 98 | end 99 | 100 | figure(5), clf 101 | plot(EEG.times,pow) 102 | xlabel('Time (ms)'), ylabel('power (dB)') 103 | legend({'filter 10%','filter 15%','filter 20%'}) 104 | set(gca,'xlim',[-300 1200]) 105 | 106 | %% -------------------------------------------------------------------------------- /3_TF_analysis_other_methods/3_stfft.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% short-window FFT 4 | 5 | load sampleEEGdata.mat 6 | 7 | chan2plot = 'o1'; 8 | 9 | frex = logspace(log10(10),log10(EEG.srate/5),20); 10 | times2save = -300:25:800; 11 | basetime = [-300 -100]; 12 | timewin = 800; % in ms 13 | 14 | 15 | % convert time points to indices 16 | times2saveidx = dsearchn(EEG.times',times2save'); 17 | % convert time window to points 18 | timewinpnts = round(timewin/(1000/EEG.srate)); 19 | 20 | % find baselinetimepoints 21 | baseidx = dsearchn(times2save',basetime'); 22 | 23 | % define frequencies for FFT 24 | hz = linspace(0,EEG.srate/2,timewinpnts/2+1); 25 | 26 | % hanning window for tapering 27 | hannwin = .5 - .5*cos(2*pi.*linspace(0,1,timewinpnts))'; 28 | 29 | % find logical channel index 30 | chanidx = strcmpi(chan2plot,{EEG.chanlocs.labels}); 31 | 32 | % initialize output matrix 33 | shortFFT_tf = zeros(length(frex),length(times2save)); 34 | 35 | 36 | % loop through time bins 37 | for ti=1:length(times2saveidx) 38 | 39 | % window and taper data, and get power spectrum 40 | data = bsxfun(@times, squeeze(EEG.data(chanidx,times2saveidx(ti)-floor(timewinpnts/2)+1:times2saveidx(ti)+ceil(timewinpnts/2),:)), hannwin); 41 | % uncomment the next line to use non-tapered data 42 | %data = squeeze(EEG.data(chanidx,times2saveidx(ti)-floor(timewinpnts/2)+1:times2saveidx(ti)+ceil(timewinpnts/2),:)); 43 | 44 | y = fft(data,timewinpnts)/timewinpnts; 45 | pow = mean(abs(y).^2,2); 46 | 47 | % finally, get power from closest frequency 48 | closestfreq = dsearchn(hz',frex'); 49 | shortFFT_tf(:,ti) = pow(closestfreq); 50 | end % end time loop 51 | 52 | % db-correct 53 | db_shortFFT_tf = 10*log10( bsxfun(@rdivide,shortFFT_tf,mean(shortFFT_tf(:,baseidx(1):baseidx(2)),2)) ); 54 | 55 | % plot! 56 | figure(1), clf 57 | contourf(times2save,frex,db_shortFFT_tf,40,'linecolor','none') 58 | set(gca,'clim',[-2 2]) 59 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 60 | title([ 'Power via short-window FFT (window=' num2str(timewin) ') from channel ' chan2plot ]) 61 | 62 | %% 63 | -------------------------------------------------------------------------------- /3_TF_analysis_other_methods/4_multitaper.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% show tapers 4 | 5 | load sampleEEGdata 6 | 7 | timewin = 400; % in ms 8 | timewinidx = round(timewin/(1000/EEG.srate)); 9 | tapers = dpss(timewinidx,5); % this line will crash without matlab signal processing toolbox 10 | 11 | % plot tapers 12 | figure(1), clf 13 | for i=1:5 14 | subplot(5,1,i) 15 | plot( 1000*(0:timewinidx-1)/EEG.srate, tapers(:,i)) 16 | end 17 | xlabel('Time (ms)') 18 | 19 | %% Compute multitaper 20 | 21 | timewin = 300; % in ms 22 | timewinidx = round(timewin/(1000/EEG.srate)); 23 | tapers = dpss(timewinidx,3); % this line will crash without matlab signal processing toolbox 24 | 25 | channel2plot = 'o1'; 26 | times2save = -300:25:800; 27 | basetime = [-300 -100]; 28 | 29 | % convert time points to indices 30 | times2saveidx = dsearchn(EEG.times',times2save'); 31 | 32 | % find baseline time point range 33 | baseidx = dsearchn(times2save',basetime'); 34 | 35 | % define frequencies for FFT 36 | hz = linspace(0,EEG.srate/2,timewinidx/2+1); 37 | 38 | % find logical channel index 39 | chanidx = strcmpi(channel2plot,{EEG.chanlocs.labels}); 40 | 41 | % initialize output matrix 42 | tf = zeros(floor(timewinidx/2)+1,length(times2save)); 43 | 44 | % loop through time bins 45 | for ti=1:length(times2saveidx) 46 | 47 | % initialize power vector (over tapers) 48 | taperpow = zeros(floor(timewinidx/2)+1,1); 49 | 50 | % loop through tapers 51 | for tapi = 1:size(tapers,2)-1 52 | 53 | % get data from this time window and taper 54 | tempEEG = squeeze(EEG.data(chanidx,times2saveidx(ti)-floor(timewinidx/2)+1:times2saveidx(ti)+ceil(timewinidx/2),:)); 55 | data = bsxfun(@times,tempEEG,tapers(:,tapi)); 56 | 57 | % compute FFT and extract power 58 | pow = fft(data)/timewinidx; 59 | pow = pow(1:floor(timewinidx/2)+1,:); 60 | taperpow = taperpow + mean(abs(pow).^2,2); 61 | end 62 | 63 | % divide by N tapers for average 64 | tf(:,ti) = taperpow/tapi; 65 | end 66 | 67 | % db-correct 68 | tf = 10*log10( bsxfun(@rdivide,tf,mean(tf(:,baseidx(1):baseidx(2)),2)) ); 69 | 70 | % plot! 71 | figure(2), clf 72 | contourf(times2save,hz,tf,40,'linecolor','none') 73 | set(gca,'clim',[-2 2],'ylim',[10 EEG.srate/5]) 74 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 75 | title([ 'Power via multitaper from channel ' channel2plot ]) 76 | 77 | %% end. 78 | -------------------------------------------------------------------------------- /3_TF_analysis_other_methods/5_freqslide.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% simulate FM signal 4 | 5 | % signal properties 6 | srate = 1000; 7 | time = 0:1/srate:5; 8 | 9 | % time series of instantaneous frequencies, which is created by 10 | % interpolating a few random datapoints 11 | freqTS = abs( interp1(linspace(time(1),time(end),10),10*rand(1,10),time,'spline') ); 12 | 13 | % this is the construction of a frequency-varying sine wave 14 | centfreq = mean(freqTS); 15 | k = (centfreq/srate)*2*pi/centfreq; 16 | signal = sin(2*pi.*centfreq.*time + k*cumsum(freqTS-centfreq)); 17 | 18 | % hilbert transform of signal 19 | hilsig = hilbert(signal); 20 | 21 | 22 | % and plot! 23 | figure(1), clf 24 | subplot(311) 25 | plot(time,signal) 26 | ylabel('Amplitude') 27 | set(gca,'ylim',[-1.1 1.1]) 28 | 29 | subplot(312) 30 | plot(time,angle(hilsig)) 31 | ylabel('Phase (rad.)') 32 | 33 | subplot(313) 34 | freqslide = srate*diff(unwrap(angle(hilsig)))/(2*pi); 35 | plot(time(1:end-1),freqslide) 36 | hold on 37 | plot(time,freqTS,'r') 38 | set(gca,'ylim',[0 15]) 39 | legend({'Estimated';'Simulated'}) 40 | 41 | %% And now with real data and a median filter 42 | 43 | load sampleEEGdata 44 | 45 | data2use = squeeze(EEG.data(28,:,10)); 46 | freq2use = 10; % hz 47 | 48 | 49 | % define convolution parameters 50 | wavt = -2:1/EEG.srate:2; % time vector for wavelet 51 | nData = EEG.pnts; 52 | nKern = length(wavt); 53 | nConv = nData+nKern-1; 54 | hwave = floor((length(wavt)-1)/2); 55 | 56 | s = 8 / (2*pi*freq2use); % for gaussian 57 | cmwX = fft( exp(1i*2*pi*freq2use*wavt) .* exp( -(wavt.^2)/(2*s^2) ) ,nConv); 58 | cmwX = cmwX ./ max(cmwX); 59 | 60 | as = ifft( fft(data2use,nConv) .* cmwX ); 61 | as = as(hwave+1:end-hwave); 62 | 63 | 64 | figure(2), clf 65 | subplot(311) 66 | plot(EEG.times,data2use); 67 | ylabel('Amplitude (\muV)') 68 | 69 | subplot(312) 70 | plot(EEG.times,angle(as)) 71 | ylabel('Phase angles (rad.)') 72 | 73 | subplot(313) 74 | freqslide = EEG.srate*diff(unwrap(angle(as)))/(2*pi); 75 | plot(EEG.times(1:end-1),freqslide) 76 | ylabel('Frequency (Hz)') 77 | set(gca,'ylim',[freq2use*.75 freq2use*1.5]) 78 | 79 | 80 | %% now apply median filter 81 | 82 | n_order = 10; 83 | orders = linspace(10,400,n_order)/2; 84 | orders = round( orders/(1000/EEG.srate) ); 85 | 86 | 87 | phasedmed = zeros(length(orders),EEG.pnts); 88 | 89 | for oi=1:n_order 90 | for ti=1:EEG.pnts 91 | 92 | %% use compiled fast_median if available 93 | % phasedmed(oi,ti) = fast_median(freqslide(max(ti-orders(oi),1):min(ti+orders(oi),EEG.pnts-1))'); 94 | 95 | %% use 'manual median' otherwise 96 | temp = sort(freqslide(max(ti-orders(oi),1):min(ti+orders(oi),EEG.pnts-1))); 97 | phasedmed(oi,ti) = temp(floor(numel(temp)/2)+1); 98 | 99 | end 100 | end 101 | 102 | % the final step is to take the mean of medians 103 | freqslideFilt = mean(phasedmed,1); 104 | 105 | subplot(313) 106 | hold on 107 | plot(EEG.times,freqslideFilt,'r') 108 | 109 | %% 110 | -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/1_powerLawTF.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% show power spectrum of EEG data 4 | 5 | load sampleEEGdata.mat 6 | 7 | % pick a channel 8 | chan2plot = 'tp8'; 9 | 10 | 11 | % compute Fourier spectrum 12 | eegX = fft( squeeze(EEG.data(strcmpi(chan2plot,{EEG.chanlocs.labels}),:,:)) ,[],1)/EEG.pnts; 13 | ampl = 2*abs(eegX); 14 | 15 | 16 | % Compute frequencies in Hz. Note that this is a different way of compute 17 | % frequencies, because frequencies are specified up to the sampling rate, 18 | % not the Nyquist frequency! It's worth knowing this trick, but best to 19 | % avoid it most of the time. 20 | hz = linspace(0,EEG.srate,EEG.pnts); 21 | 22 | % and plot 23 | figure(1), clf 24 | plot(hz,ampl) 25 | hold on 26 | plot(hz,mean(ampl,2),'k','linew',4) 27 | 28 | set(gca,'xlim',[0 50]) 29 | xlabel('Frequency (Hz)'), ylabel('Amplitude (\muV)') 30 | title([ 'Single-trial and trial-average power spectrum from channel ' chan2plot ]) 31 | 32 | % 33 | % set(gca,'yscale','log') 34 | % set(gca,'xscale','log') 35 | 36 | %% end 37 | -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/2_dbpct.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% 4 | 5 | activity = 1:.01:20; % activity 6 | baseline = 10; % baseline 7 | 8 | db = 10*log10(activity./ baseline ); 9 | pc = 100*( activity-baseline)./ baseline; 10 | 11 | 12 | figure(1), clf 13 | plot(activity,'linew',3) 14 | hold on 15 | plot(db,'r','linew',3) 16 | legend({'"activity"','dB'}) 17 | 18 | %% comparing dB and percent change 19 | 20 | figure(2), clf 21 | plot(db,pc) 22 | xlabel('dB'), ylabel('Percent change') 23 | 24 | % find indices where db is closest to -/+2 25 | [~,dbOfplus2] = min(abs(db-+2)); 26 | [~,dbOfminus2] = min(abs(db--2)); 27 | 28 | 29 | hold on 30 | axislim=axis; 31 | plot([db(dbOfplus2) db(dbOfplus2)], [pc(dbOfplus2) axislim(3)],'k',[axislim(1) db(dbOfplus2)], [pc(dbOfplus2) pc(dbOfplus2)], 'k') 32 | plot([db(dbOfminus2) db(dbOfminus2)],[pc(dbOfminus2) axislim(3)],'k',[axislim(1) db(dbOfminus2)],[pc(dbOfminus2) pc(dbOfminus2)],'k') 33 | 34 | %% -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/3_whichbaseline.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | 3 | %% specify baseline periods for dB-conversion 4 | 5 | load sampleEEGdata 6 | 7 | baseline_windows = [ -500 -200; 8 | -100 0; 9 | 0 300; 10 | -800 0; 11 | ]; 12 | 13 | 14 | % convert baseline time into indices 15 | baseidx = reshape( dsearchn(EEG.times',baseline_windows(:)), [],2); 16 | 17 | %% setup wavelet parameters 18 | 19 | % frequency parameters 20 | min_freq = 2; 21 | max_freq = 30; 22 | num_frex = 40; 23 | frex = linspace(min_freq,max_freq,num_frex); 24 | 25 | % which channel to plot 26 | channel2use = 'o1'; 27 | 28 | % other wavelet parameters 29 | range_cycles = [ 4 10 ]; 30 | 31 | s = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex) ./ (2*pi*frex); 32 | wavtime = -2:1/EEG.srate:2; 33 | half_wave = (length(wavtime)-1)/2; 34 | 35 | 36 | % FFT parameters 37 | nWave = length(wavtime); 38 | nData = EEG.pnts * EEG.trials; 39 | nConv = nWave + nData - 1; 40 | 41 | 42 | % now compute the FFT of all trials concatenated 43 | alldata = reshape( EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:) ,1,[]); 44 | dataX = fft( alldata ,nConv ); 45 | 46 | 47 | % initialize output time-frequency data 48 | tf = zeros(size(baseidx,1),length(frex),EEG.pnts); 49 | 50 | %% now perform convolution 51 | 52 | 53 | % loop over frequencies 54 | for fi=1:length(frex) 55 | 56 | % create wavelet and get its FFT 57 | wavelet = exp(2*1i*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 58 | waveletX = fft(wavelet,nConv); 59 | waveletX = waveletX ./ max(waveletX); 60 | 61 | % now run convolution in one step 62 | as = ifft(waveletX .* dataX); 63 | as = as(half_wave+1:end-half_wave); 64 | 65 | % and reshape back to time X trials 66 | as = reshape( as, EEG.pnts, EEG.trials ); 67 | 68 | % compute power and average over trials 69 | tf(4,fi,:) = mean( abs(as).^2 ,2); 70 | end 71 | 72 | %% db conversion and plot results 73 | 74 | % define color limits 75 | climdb = [-3 3]; 76 | climpct = [-90 90]; 77 | 78 | % create new matrix for percent change 79 | tfpct = zeros(size(tf)); 80 | 81 | for basei=1:size(tf,1) 82 | 83 | activity = tf(4,:,:); 84 | baseline = mean( tf(4,:,baseidx(basei,1):baseidx(basei,2)) ,3); 85 | 86 | % decibel 87 | tf(basei,:,:) = 10*log10( bsxfun(@rdivide, activity, baseline) ); 88 | 89 | % percent change 90 | tfpct(basei,:,:) = 100 * bsxfun(@rdivide, bsxfun(@minus,activity,baseline), baseline); 91 | end 92 | 93 | 94 | % plot results 95 | for basei=1:size(baseline_windows,1) 96 | 97 | % first plot dB 98 | figure(1), subplot(2,2,basei) 99 | 100 | contourf(EEG.times,frex,squeeze(tf(basei,:,:)),40,'linecolor','none') 101 | set(gca,'clim',climdb,'ydir','normal','xlim',[-300 1000]) 102 | title([ 'DB baseline of ' num2str(baseline_windows(basei,1)) ' to ' num2str(baseline_windows(basei,2)) ' ms' ]) 103 | 104 | % now plot percent change 105 | figure(2), subplot(2,2,basei) 106 | 107 | contourf(EEG.times,frex,squeeze(tfpct(basei,:,:)),40,'linecolor','none') 108 | set(gca,'clim',climpct,'ydir','normal','xlim',[-300 1000]) 109 | title([ 'PCT baseline of ' num2str(baseline_windows(basei,1)) ' to ' num2str(baseline_windows(basei,2)) ' ms' ]) 110 | end 111 | 112 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 113 | 114 | %% 115 | -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/4_postanalysis_ds.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% TF power decomposition 4 | 5 | load sampleEEGdata.mat 6 | 7 | % vector of time points to save in post-analysis downsampling 8 | times2save = -300:20:1200; % in ms 9 | 10 | 11 | % time vector converted to indices 12 | times2saveidx = dsearchn(EEG.times',times2save'); 13 | 14 | 15 | % frequency parameters 16 | min_freq = 2; 17 | max_freq = 50; 18 | num_frex = 40; 19 | frex = linspace(min_freq,max_freq,num_frex); 20 | 21 | % which channel to plot 22 | channel2use = 'o1'; 23 | 24 | % other wavelet parameters 25 | range_cycles = [ 4 10 ]; 26 | 27 | s = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex) ./ (2*pi*frex); 28 | wavtime = -2:1/EEG.srate:2; 29 | half_wave = (length(wavtime)-1)/2; 30 | 31 | 32 | % FFT parameters 33 | nWave = length(wavtime); 34 | nData = EEG.pnts * EEG.trials; % This line is different from above!! 35 | nConv = nWave + nData - 1; 36 | 37 | % initialize output time-frequency data 38 | tf = zeros(length(frex),EEG.pnts); 39 | 40 | % now compute the FFT of all trials concatenated 41 | alldata = reshape( EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:) ,1,[]); 42 | dataX = fft( alldata ,nConv ); 43 | 44 | 45 | % loop over frequencies 46 | for fi=1:length(frex) 47 | 48 | % create wavelet and get its FFT 49 | % the wavelet doesn't change on each trial... 50 | wavelet = exp(2*1i*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 51 | waveletX = fft(wavelet,nConv); 52 | waveletX = waveletX ./ max(waveletX); 53 | 54 | % now run convolution in one step 55 | as = ifft(waveletX .* dataX); 56 | as = as(half_wave+1:end-half_wave); 57 | 58 | % and reshape back to time X trials 59 | as = reshape( as, EEG.pnts, EEG.trials ); 60 | 61 | % compute power and average over trials 62 | tf(fi,:) = mean( abs(as).^2 ,2); 63 | end 64 | 65 | % plot results 66 | figure(1), clf 67 | subplot(211) 68 | contourf(EEG.times,frex,tf,40,'linecolor','none') 69 | set(gca,'clim',[0 5],'ydir','normal','xlim',[-300 1000]) 70 | title('Full temporal resolution') 71 | 72 | 73 | subplot(212) 74 | contourf(times2save,frex,tf(:,times2saveidx),40,'linecolor','none') 75 | set(gca,'clim',[0 5],'ydir','normal','xlim',[-300 1000]) 76 | title('Temporally down-sampled after convolution') 77 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 78 | 79 | 80 | % now show lines 81 | figure(2), clf 82 | % pick frequencies to isolate 83 | freqs2plot = dsearchn(frex',[5 30]'); 84 | plot(EEG.times,tf(freqs2plot,:),'.-') 85 | hold on 86 | plot(times2save,tf(freqs2plot,times2saveidx),'o-') 87 | xlabel('Time (ms)'), ylabel('Power (\muv^2)') 88 | 89 | legend({[ num2str(round(frex(freqs2plot(1)))) ' Hz full' ];[ num2str(round(frex(freqs2plot(2)))) ' Hz full' ]; ... 90 | [ num2str(round(frex(freqs2plot(1)))) ' Hz downsampled' ];[ num2str(round(frex(freqs2plot(2)))) ' Hz downsampled' ]}) 91 | 92 | %% how to downsample during real analyses 93 | 94 | % initialize output time-frequency data 95 | tfds = zeros(length(frex),length(times2save)); 96 | 97 | % loop over frequencies 98 | for fi=1:length(frex) 99 | 100 | % create wavelet and get its FFT 101 | % the wavelet doesn't change on each trial... 102 | wavelet = exp(2*1i*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 103 | waveletX = fft(wavelet,nConv); 104 | waveletX = waveletX ./ max(waveletX); 105 | 106 | % now run convolution in one step 107 | as = ifft(waveletX .* dataX); 108 | as = as(half_wave+1:end-half_wave); 109 | 110 | % and reshape back to time X trials 111 | as = reshape( as, EEG.pnts, EEG.trials ); 112 | 113 | % compute power and average over trials 114 | % here is where we take only the downsampled times 115 | tfds(fi,:) = mean( abs(as(times2saveidx,:)).^2 ,2); 116 | end 117 | 118 | whos tf* 119 | 120 | %% end 121 | -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/5_loglinTF.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% initial parameters 4 | 5 | % vector of time points to save in post-analysis downsampling 6 | times2save = -300:20:1200; % in ms 7 | 8 | basetime = [-500 -200]; 9 | 10 | % frequency parameters 11 | min_freq = 2; 12 | max_freq = 50; 13 | num_frex = 40; 14 | % other wavelet parameters 15 | range_cycles = [ 4 10 ]; 16 | 17 | % which channel to plot 18 | channel2use = 'pz'; 19 | 20 | %% parameter conversion and other initializations 21 | 22 | load sampleEEGdata.mat 23 | 24 | % time vector converted to indices 25 | times2saveidx = dsearchn(EEG.times',times2save'); 26 | basetimeidx = dsearchn(EEG.times',basetime'); 27 | 28 | % frequencies vector 29 | frex = logspace(log10(min_freq),log10(max_freq),num_frex); 30 | 31 | % wavelet parameters 32 | s = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex) ./ (2*pi*frex); 33 | wavtime = -2:1/EEG.srate:2; 34 | half_wave = (length(wavtime)-1)/2; 35 | 36 | 37 | % FFT parameters 38 | nWave = length(wavtime); 39 | nData = EEG.pnts * EEG.trials; % This line is different from above!! 40 | nConv = nWave + nData - 1; 41 | 42 | % initialize output time-frequency data 43 | tf = zeros(length(frex),length(times2save)); 44 | 45 | %% run convolution 46 | 47 | % now compute the FFT of all trials concatenated 48 | alldata = reshape( EEG.data(strcmpi(channel2use,{EEG.chanlocs.labels}),:,:) ,1,[]); 49 | dataX = fft( alldata ,nConv ); 50 | 51 | % loop over frequencies 52 | for fi=1:length(frex) 53 | 54 | % create wavelet and get its FFT 55 | % the wavelet doesn't change on each trial... 56 | wavelet = exp(2*1i*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 57 | waveletX = fft(wavelet,nConv); 58 | waveletX = waveletX ./ max(waveletX); 59 | 60 | % now run convolution in one step 61 | as = ifft(waveletX .* dataX); 62 | as = as(half_wave+1:end-half_wave); 63 | 64 | % and reshape back to time X trials 65 | as = reshape( as, EEG.pnts, EEG.trials ); 66 | 67 | % compute power and average over trials 68 | temppow = mean( abs(as).^2 ,2); 69 | tf(fi,:) = 10*log10( temppow(times2saveidx) / mean(temppow(basetimeidx)) ); 70 | end 71 | 72 | %% plotting 73 | 74 | clim = [-3 3]; 75 | 76 | figure(1) 77 | subplot(121) 78 | contourf(times2save,frex,tf,40,'linecolor','none') 79 | set(gca,'clim',clim,'yscale','log') 80 | 81 | % two different options for plotting 82 | set(gca,'ytick',1:3:num_frex) 83 | set(gca,'ytick',ceil(logspace(log10(1),log10(num_frex),8))) 84 | 85 | title('Logarithmic frequency scaling') 86 | xlabel('Time (ms)'), ylabel('Frequencies (Hz)') 87 | 88 | subplot(122) 89 | contourf(times2save,frex,tf,40,'linecolor','none') 90 | set(gca,'clim',clim) 91 | title('Linear frequency scaling') 92 | 93 | %% imagesc vs. contourf 94 | 95 | figure(2), clf 96 | subplot(221) 97 | contourf(times2save,frex,tf,40,'linecolor','none') 98 | set(gca,'clim',clim,'xlim',[-200 1000],'yscale','log','ytick',ceil(logspace(log10(1),log10(num_frex),8))) 99 | title('Logarithmic frequency scaling') 100 | ylabel('Frequency (Hz)') 101 | 102 | subplot(222) 103 | contourf(times2save,frex,tf,40,'linecolor','none') 104 | set(gca,'clim',clim,'xlim',[-200 1000]) 105 | title('Linear frequency scaling') 106 | 107 | subplot(223) 108 | imagesc(times2save,frex,tf) 109 | set(gca,'clim',clim,'xlim',[-200 1000],'ydir','norm') 110 | title('WRONG Y-AXIS LABELS!!!!') 111 | ylabel('Frequency (Hz)'), xlabel('Time (ms)') 112 | 113 | subplot(224) 114 | imagesc(times2save,[],tf) 115 | set(gca,'clim',clim,'xlim',[-200 1000],'ydir','norm') 116 | set(gca,'ytick',round(linspace(1,num_frex,6)),'yticklabel',round(frex(round(linspace(1,num_frex,6)))*10)/10) 117 | title('CORRECT Y-AXIS LABELS!!!!') 118 | xlabel('Time (ms)') 119 | 120 | %% end 121 | -------------------------------------------------------------------------------- /4_Normalization_and_TF_postprocessing/6_units.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | %% units in FFT amplitude 4 | 5 | % define parameters 6 | srate = 1000; 7 | time = 0:1/srate:10; 8 | nPnts = length(time); 9 | freq = 14; % Hz 10 | hz = linspace(0,srate/2,floor(nPnts/2)+1); 11 | 12 | % create signal 13 | sinewave = 2*sin(2*pi*freq*time); 14 | 15 | % step 1: FFT and divide by N 16 | sinewaveX = fft(sinewave,nPnts)/nPnts; % divide by number of points 17 | 18 | % step 2: multiply positive-frequency coefficients by 2 19 | posfrex = 2:floor(nPnts/2); 20 | sineampl = abs(sinewaveX); 21 | sineampl(posfrex) = sineampl(posfrex)*2; 22 | 23 | figure(1), clf 24 | subplot(211) 25 | plot(time,sinewave) 26 | set(gca,'xlim',[0 1]) 27 | xlabel('Time (s)'), ylabel('Amplitude (a.u.)') 28 | 29 | subplot(212) 30 | bar(hz,sineampl(1:length(hz))) 31 | set(gca,'xlim',[freq-3 freq+3]) 32 | xlabel('Frequency (Hz)'), ylabel('Amplitude (a.u.)') 33 | 34 | %% units in FFT power 35 | 36 | % define parameters 37 | srate = 1000; 38 | time = 0:1/srate:10; 39 | nPnts = length(time); 40 | freq = 14; % Hz 41 | hz = linspace(0,srate/2,floor(nPnts/2)+1); 42 | 43 | % create signal 44 | sinewave = 2*sin(2*pi*freq*time); 45 | 46 | % step 1: FFT and divide by N 47 | sinewaveX = fft(sinewave, nPnts)/nPnts; % divide by 48 | 49 | % step 2: multiply positive-frequency coefficients by 2 50 | posfrex = 2:floor(nPnts/2); 51 | sineampl = abs(sinewaveX); 52 | sineampl(posfrex) = sineampl(posfrex)*2; 53 | 54 | % step 3: square amplitudes 55 | sineampl = sineampl.^2; 56 | 57 | 58 | figure(2), clf 59 | subplot(211) 60 | plot(time,sinewave) 61 | set(gca,'xlim',[0 1]) 62 | xlabel('Time (s)'), ylabel('Amplitude (a.u.)') 63 | 64 | subplot(212) 65 | bar(hz,sineampl(1:length(hz))) 66 | set(gca,'xlim',[freq-3 freq+3]) 67 | xlabel('Power (Hz)'), ylabel('Amplitude (a.u.)') 68 | 69 | %% setting up wavelet convolution in preparation for extracting correct units 70 | 71 | % create signal 72 | srate = 1000; 73 | time = 0:1/srate:10; 74 | nPnts = length(time); 75 | frex = [25 60]; 76 | signal = 2*sin(2*pi.*linspace(frex(1),frex(2)*mean(frex)/frex(2),nPnts).*time); 77 | 78 | 79 | % specify wavelet parameters 80 | wfreq = 40; % in Hz 81 | nCyc = 20/(2*pi*wfreq); % numerator is number of cycles; denominator is a scaling factor 82 | 83 | % create wavelet 84 | wavet = -1:1/srate:1; 85 | srate = 1000; 86 | halfwav = (length(wavet)-1)/2; 87 | cmw = exp(1i*2*pi*wfreq*wavet) .* exp( -.5*(wavet/nCyc).^2 ); 88 | 89 | % convolution parameters 90 | nData = nPnts; 91 | nKern = length(wavet); 92 | nConv = nData+nKern-1; 93 | 94 | % FFT of wavelet and signal 95 | cmwX = fft(cmw,nConv); 96 | sigX = fft(signal,nConv); 97 | 98 | % step 1: max-value normalize the wavelet in the frequency domain 99 | cmwX = cmwX./max(cmwX); 100 | 101 | % convolution 102 | asX = ifft( cmwX.*sigX ); 103 | 104 | % frequencies in Hz (valid only up to Nyquist) 105 | hz = -(srate/2-1/srate):srate/nConv:srate/2; 106 | 107 | % plotting 108 | figure(3), clf 109 | subplot(311), plot(time,signal) 110 | set(gca,'xlim',[2 5]) 111 | title('Signal in the time domain') 112 | xlabel('Time (sec)'), ylabel('Amplitude (a.u.)') 113 | 114 | subplot(312) 115 | plot(hz,fftshift(2*abs(sigX/nPnts))) 116 | title('Signal in the frequency domain') 117 | xlabel('Frequency (Hz)'), ylabel('Amplitude (normalized)') 118 | 119 | subplot(313) 120 | plot(hz,fftshift(2*abs(cmwX))) 121 | title('Wavelet in the frequency domain') 122 | xlabel('Frequency (Hz)'), ylabel('Amplitude (normalized)') 123 | 124 | %% now get units back 125 | 126 | % trim convolution wings 127 | asX = asX(halfwav+1:end-halfwav); 128 | 129 | tf_amp = 2*abs(asX); 130 | tf_pow = tf_amp.^2; 131 | 132 | 133 | figure(4), clf 134 | subplot(211) 135 | plot(time,signal), hold on 136 | plot(time,tf_amp,'linew',2) 137 | xlabel('Time (s)'), ylabel('Amplitude (a.u.)') 138 | 139 | subplot(212) 140 | plot(time,signal), hold on 141 | plot(time,tf_pow,'linew',2) 142 | xlabel('Time (s)'), ylabel('Power (a.u.)') 143 | 144 | %% end. 145 | -------------------------------------------------------------------------------- /5_Data_preprocessing_and_cleaning/1_laplacian.m: -------------------------------------------------------------------------------- 1 | % mikexcohen.com 2 | 3 | load sampleEEGdata 4 | 5 | %% Simulated data 6 | 7 | % which channels to use as hotspots 8 | chan1 = 'pz'; 9 | chan2 = 'c4'; 10 | chan3 = 'c3'; 11 | 12 | % get XYZ coordinates in convenient variables 13 | X = [EEG.chanlocs.X]; 14 | Y = [EEG.chanlocs.Y]; 15 | Z = [EEG.chanlocs.Z]; 16 | 17 | % initialize distance matrices 18 | eucdist1 = zeros(1,64); 19 | eucdist2 = zeros(1,64); 20 | eucdist3 = zeros(1,64); 21 | 22 | % convert electrode names to indices 23 | chan1idx = strcmpi(chan1,{EEG.chanlocs.labels}); 24 | chan2idx = strcmpi(chan2,{EEG.chanlocs.labels}); 25 | chan3idx = strcmpi(chan3,{EEG.chanlocs.labels}); 26 | 27 | % compute inter-electrode distances, seeded from the three specified electrodes 28 | for chani=1:EEG.nbchan 29 | eucdist1(chani) = sqrt( (EEG.chanlocs(chani).X-EEG.chanlocs(chan1idx).X)^2 + (EEG.chanlocs(chani).Y-EEG.chanlocs(chan1idx).Y)^2 + (EEG.chanlocs(chani).Z-EEG.chanlocs(chan1idx).Z)^2 ); 30 | eucdist2(chani) = sqrt( (EEG.chanlocs(chani).X-EEG.chanlocs(chan2idx).X)^2 + (EEG.chanlocs(chani).Y-EEG.chanlocs(chan2idx).Y)^2 + (EEG.chanlocs(chani).Z-EEG.chanlocs(chan2idx).Z)^2 ); 31 | eucdist3(chani) = sqrt( (EEG.chanlocs(chani).X-EEG.chanlocs(chan3idx).X)^2 + (EEG.chanlocs(chani).Y-EEG.chanlocs(chan3idx).Y)^2 + (EEG.chanlocs(chani).Z-EEG.chanlocs(chan3idx).Z)^2 ); 32 | end 33 | 34 | % The code below defines the "activations," which are really 35 | % just a Gaussian function of distance away from each electrode. 36 | % You can try changing widths to see how it affects the scalp maps. 37 | lo_width = 95; 38 | hi_width = 50; 39 | 40 | lo_spatfreq = 2*exp(- (eucdist1.^2)/(2*lo_width^2) ); 41 | hi_spatfreq = exp(- (eucdist2.^2)/(2*hi_width^2) ) + exp(- (eucdist3.^2)/(2*hi_width^2) ); 42 | 43 | 44 | % and now compute the laplacian of the sum of the aforecreated data 45 | surf_lap_all = laplacian_perrinX(hi_spatfreq+lo_spatfreq,X,Y,Z); 46 | 47 | 48 | 49 | % show plots 50 | figure(1), clf 51 | subplot(221) 52 | topoplot(lo_spatfreq,EEG.chanlocs,'plotrad',.53); 53 | title('Low spatial frequency feature') 54 | 55 | subplot(222) 56 | topoplot(hi_spatfreq,EEG.chanlocs,'plotrad',.53); 57 | title('High spatial frequency features') 58 | 59 | subplot(223) 60 | topoplot(lo_spatfreq+hi_spatfreq,EEG.chanlocs,'plotrad',.53); 61 | title('Low+high features') 62 | 63 | subplot(224) 64 | topoplot(surf_lap_all,EEG.chanlocs,'plotrad',.53); 65 | title('Laplacian of low+high features') 66 | 67 | %% now for real data 68 | 69 | times2plot = 0:200:800; 70 | 71 | figure(2), clf 72 | for i=1:length(times2plot) 73 | 74 | % find time index 75 | [~,timeidx] = min(abs(EEG.times-times2plot(i))); 76 | 77 | % average data from this time point 78 | tempdata = squeeze(mean(EEG.data(:,timeidx,:),3)); 79 | 80 | 81 | % plot voltage map (spatially unfiltered) 82 | subplot(2,length(times2plot),i) 83 | topoplot(tempdata,EEG.chanlocs,'plotrad',.53,'maplimits',[-10 10],'electrodes','off'); 84 | title([ 'Voltage, ' num2str(times2plot(i)) ' ms' ]) 85 | 86 | % plot Laplacian map (spatially filtered) 87 | subplot(2,length(times2plot),i+length(times2plot)) 88 | topoplot(laplacian_perrinX(tempdata,X,Y,Z),EEG.chanlocs,'plotrad',.53,'maplimits',[-40 40],'electrodes','off'); 89 | title([ 'Lap., ' num2str(times2plot(i)) ' ms' ]) 90 | end 91 | 92 | %% 93 | -------------------------------------------------------------------------------- /5_Data_preprocessing_and_cleaning/laplacian_perrinX.m: -------------------------------------------------------------------------------- 1 | %LAPLACIAN_PERRINX Compute surface Laplacian of EEG data. 2 | % [surf_lap,G,H] = laplacian_perrinX(data,x,y,z[,leg_order,smoothing]); 3 | % 4 | % INPUTS : 5 | % data : EEG data (can be N-D, but first dimension must be electrodes) 6 | % x,y,z : x,y,z coordinates of electrode positions (e.g., [EEG.chanlocs.X]) 7 | % 8 | % (optional inputs) 9 | % leg_order : order of Legendre polynomial (default is 50 [80 for >100 electrodes]) 10 | % smoothing : G smoothing parameter (lambda), set to 1e-5 by default 11 | % 12 | % 13 | % OUTPUTS : 14 | % surf_lap : the surface Laplacian (second spatial derivative) 15 | % (optional outputs) 16 | % G,H : G and H matrices 17 | % 18 | % This is an implementation of algorithms described by 19 | % Perrin, Pernier, Bertrand, and Echallier (1989). PubMed #2464490 20 | 21 | % mikexcohen@gmail.com 22 | 23 | function [surf_lap,G,H] = laplacian_perrinX(data,x,y,z,varargin) % vararg order: leg_order,smoothing 24 | 25 | numelectrodes = numel(x); 26 | 27 | if nargin<4 28 | help laplacian_perrinX 29 | error('Read help file!') 30 | end 31 | 32 | %% compute G and H matrices 33 | 34 | % initialize 35 | G=zeros(numelectrodes); 36 | H=zeros(numelectrodes); 37 | cosdist=zeros(numelectrodes); 38 | 39 | % default parameters for +/- 100 electrodes 40 | if numelectrodes>100 41 | m=3; leg_order=80; 42 | else 43 | m=4; leg_order=50; 44 | end 45 | 46 | if numel(varargin)>0 && ~isempty(varargin{1}) 47 | leg_order=varargin{1}; 48 | end 49 | 50 | % scale XYZ coordinates to unit sphere 51 | [junk,junk,spherical_radii] = cart2sph(x,y,z); 52 | maxrad = max(spherical_radii); 53 | x = x./maxrad; 54 | y = y./maxrad; 55 | z = z./maxrad; 56 | 57 | for i=1:numelectrodes 58 | for j=i+1:numelectrodes 59 | cosdist(i,j) = 1 - (( (x(i)-x(j))^2 + (y(i)-y(j))^2 + (z(i)-z(j))^2 ) / 2 ); 60 | end 61 | end 62 | cosdist = cosdist+cosdist' + eye(numelectrodes); 63 | 64 | 65 | % compute Legendre polynomial 66 | legpoly = zeros(leg_order,numelectrodes,numelectrodes); 67 | for ni=1:leg_order 68 | temp = legendre(ni,cosdist); 69 | legpoly(ni,:,:) = temp(1,:,:); 70 | end 71 | 72 | % precompute electrode-independent variables 73 | twoN1 = 2*(1:leg_order)+1; 74 | gdenom = ((1:leg_order).*((1:leg_order)+1)).^m; 75 | hdenom = ((1:leg_order).*((1:leg_order)+1)).^(m-1); 76 | 77 | for i=1:numelectrodes 78 | for j=i:numelectrodes 79 | 80 | g=0; h=0; 81 | 82 | for ni=1:leg_order 83 | % compute G and H terms 84 | g = g + (twoN1(ni) *legpoly(ni,i,j)) / gdenom(ni); 85 | h = h - (twoN1(ni)*2*legpoly(ni,i,j)) / hdenom(ni); 86 | end 87 | G(i,j) = g/(4*pi); 88 | H(i,j) = -h/(4*pi); 89 | end 90 | end 91 | 92 | % mirror matrix 93 | G=G+G'; H=H+H'; 94 | 95 | % correct for diagonal-double 96 | G = G-eye(numelectrodes)*G(1)/2; 97 | H = H-eye(numelectrodes)*H(1)/2; 98 | 99 | %% compute laplacian 100 | 101 | % reshape data to electrodes X time/trials 102 | orig_data_size = squeeze(size(data)); 103 | if any(orig_data_size==1) 104 | data=data(:); 105 | else 106 | data = reshape(data,orig_data_size(1),prod(orig_data_size(2:end))); 107 | end 108 | 109 | % smoothing constant 110 | if numel(varargin)==2 111 | smoothing=varargin{2}; 112 | else 113 | smoothing=1e-5; 114 | end 115 | 116 | % add smoothing constant to diagonal 117 | % (change G so output is unadulterated) 118 | Gs = G + eye(numelectrodes)*smoothing; 119 | 120 | % compute C matrix 121 | GsinvS = sum(inv(Gs)); 122 | dataGs = data'/Gs; 123 | C = dataGs - (sum(dataGs,2)/sum(GsinvS))*GsinvS; 124 | 125 | % compute surface Laplacian (and reshape to original data size) 126 | surf_lap = reshape((C*H')',orig_data_size); 127 | 128 | %% end 129 | -------------------------------------------------------------------------------- /6_Connectivity/1_phase_connectivity.m: -------------------------------------------------------------------------------- 1 | %% mikexcohen.com 2 | % phase-based connectivity 3 | 4 | load sampleEEGdata 5 | 6 | %% setup some stuff and junk 7 | 8 | % names of the channels you want to compute connectivity between 9 | channel1 = 'p1'; 10 | channel2 = 'pz'; 11 | 12 | 13 | % create complex Morlet wavelet 14 | center_freq = 5; 15 | time = -1:1/EEG.srate:1; 16 | wavelet = exp(2*1i*pi*center_freq.*time) .* exp(-time.^2./(2*(4/(2*pi*center_freq))^2)); 17 | half_wavN = (length(time)-1)/2; 18 | 19 | % FFT parameters 20 | n_wavelet = length(time); 21 | n_data = EEG.pnts; 22 | n_conv = n_wavelet+n_data-1; 23 | 24 | % FFT of wavelet 25 | waveletX = fft(wavelet,n_conv); 26 | waveletX = waveletX ./ max(waveletX); 27 | 28 | % initialize output time-frequency data 29 | phase_data = zeros(2,EEG.pnts); 30 | real_data = zeros(2,EEG.pnts); 31 | 32 | % find channel indices 33 | chan1idx = find(strcmpi(channel1,{EEG.chanlocs.labels})); 34 | chan2idx = find(strcmpi(channel2,{EEG.chanlocs.labels})); 35 | 36 | 37 | % analytic signal of channel 1 38 | fft_data = fft(squeeze(EEG.data(chan1idx,:,1)),n_conv); 39 | as = ifft(waveletX.*fft_data,n_conv); 40 | as = as(half_wavN+1:end-half_wavN); 41 | 42 | % collect real and phase data 43 | phase_data(1,:) = angle(as); 44 | real_data(1,:) = real(as); 45 | 46 | % analytic signal of channel 1 47 | fft_data = fft(squeeze(EEG.data(chan2idx,:,1)),n_conv); 48 | as = ifft(waveletX.*fft_data,n_conv); 49 | as = as(half_wavN+1:end-half_wavN); 50 | 51 | % collect real and phase data 52 | phase_data(2,:) = angle(as); 53 | real_data(2,:) = real(as); 54 | 55 | %% setup figure and define plot handles 56 | 57 | % open and name figure 58 | figure, set(gcf,'Name','Movie magic minimizes the magic.'); 59 | 60 | % draw the filtered signals 61 | subplot(221) 62 | filterplotH1 = plot(EEG.times(1),real_data(1,1),'b'); 63 | hold on 64 | filterplotH2 = plot(EEG.times(1),real_data(2,1),'m'); 65 | set(gca,'xlim',[EEG.times(1) EEG.times(end)],'ylim',[min(real_data(:)) max(real_data(:))]) 66 | xlabel('Time (ms)') 67 | ylabel('Voltage (\muV)') 68 | title([ 'Filtered signal at ' num2str(center_freq) ' Hz' ]) 69 | 70 | % draw the phase angle time series 71 | subplot(222) 72 | phaseanglesH1 = plot(EEG.times(1),phase_data(1,1),'b'); 73 | hold on 74 | phaseanglesH2 = plot(EEG.times(1),phase_data(2,1),'m'); 75 | set(gca,'xlim',[EEG.times(1) EEG.times(end)],'ylim',[-pi pi]*1.1) 76 | xlabel('Time (ms)') 77 | ylabel('Phase angle (radian)') 78 | title('Phase angle time series') 79 | 80 | % draw phase angles in polar space 81 | subplot(223) 82 | polar2chanH1 = polar([zeros(1,1) phase_data(1,1)]',repmat([0 1],1,1)','b'); 83 | hold on 84 | polar2chanH2 = polar([zeros(1,1) phase_data(2,1)]',repmat([0 1],1,1)','m'); 85 | title('Phase angles from two channels') 86 | 87 | % draw phase angle differences in polar space 88 | subplot(224) 89 | polarAngleDiffH = polar([zeros(1,1) phase_data(2,1)-phase_data(1,1)]',repmat([0 1],1,1)','k'); 90 | title('Phase angle differences from two channels') 91 | 92 | %% now update plots at each timestep 93 | 94 | for ti=1:5:EEG.pnts 95 | 96 | % update filtered signals 97 | set(filterplotH1,'XData',EEG.times(1:ti),'YData',real_data(1,1:ti)) 98 | set(filterplotH2,'XData',EEG.times(1:ti),'YData',real_data(2,1:ti)) 99 | 100 | % update cartesian plot of phase angles 101 | set(phaseanglesH1,'XData',EEG.times(1:ti),'YData',phase_data(1,1:ti)) 102 | set(phaseanglesH2,'XData',EEG.times(1:ti),'YData',phase_data(2,1:ti)) 103 | 104 | subplot(223) 105 | cla 106 | polar([zeros(1,ti) phase_data(1,1:ti)]',repmat([0 1],1,ti)','b'); 107 | hold on 108 | polar([zeros(1,ti) phase_data(2,1:ti)]',repmat([0 1],1,ti)','m'); 109 | 110 | subplot(224) 111 | cla 112 | polar([zeros(1,ti) phase_data(2,1:ti)-phase_data(1,1:ti)]',repmat([0 1],1,ti)','k'); 113 | 114 | drawnow 115 | end 116 | 117 | %% now quantify phase synchronization between the two channels 118 | 119 | % phase angle differences 120 | phase_angle_differences = phase_data(2,:)-phase_data(1,:); 121 | 122 | % euler representation of angles 123 | euler_phase_differences = exp(1i*phase_angle_differences); 124 | 125 | % mean vector (in complex space) 126 | mean_complex_vector = mean(euler_phase_differences); 127 | 128 | % length of mean vector (this is the "M" from Me^ik, and is the measure of phase synchronization) 129 | phase_synchronization = abs(mean_complex_vector); 130 | 131 | disp([ 'Synchronization between ' channel1 ' and ' channel2 ' is ' num2str(phase_synchronization) '!' ]) 132 | 133 | % of course, this could all be done on one line: 134 | phase_synchronization = abs(mean(exp(1i*(phase_data(2,:)-phase_data(1,:))))); 135 | 136 | % notice that the order of subtraction is meaningless (see below), which means that this measure of synchronization is non-directional! 137 | phase_synchronization_backwards = abs(mean(exp(1i*(phase_data(1,:)-phase_data(2,:))))); 138 | 139 | 140 | % now plot mean vector 141 | subplot(224) 142 | hold on 143 | h=polar([0 angle(mean_complex_vector)],[0 phase_synchronization]); 144 | set(h,'linewidth',6,'color','g') 145 | 146 | %% phase clustering is phase-invariant 147 | 148 | figure(2), clf 149 | subplot(221) 150 | polar(repmat(phase_data(2,:)-phase_data(1,:),1,2)',repmat([0 1],1,EEG.pnts)','k'); 151 | title([ 'Phase synchronization: ' num2str(abs(mean(exp(1i*(diff(phase_data,1)))))) ]) 152 | 153 | new_phase_data = phase_data; 154 | for i=2:4 155 | subplot(2,2,i) 156 | 157 | % add random phase offset 158 | new_phase_data(1,:) = new_phase_data(1,:)+rand*pi; 159 | 160 | % plot again 161 | polar(repmat(new_phase_data(2,:)-new_phase_data(1,:)+pi/2,1,2)',repmat([0 1],1,EEG.pnts)','k'); 162 | title([ 'Phase synchronization: ' num2str(abs(mean(exp(1i*(diff(new_phase_data,1)))))) ]) 163 | end 164 | 165 | %% 166 | 167 | %% ISPC over time vs. over trials 168 | 169 | % FFT parameters 170 | n_wavelet = length(time); 171 | n_data = EEG.pnts*EEG.trials; 172 | n_conv = n_wavelet+n_data-1; 173 | 174 | 175 | % initialize output time-frequency data 176 | phase_data = zeros(2,EEG.pnts,EEG.trials); 177 | 178 | 179 | % FFT of wavelet (need to redo FFT because different n_conv) 180 | waveletX = fft(wavelet,n_conv); 181 | waveletX = waveletX ./ max(waveletX); 182 | 183 | 184 | % analytic signal of channel 1 185 | fft_data = fft(reshape(EEG.data(chan1idx,:,:),1,[]),n_conv); 186 | as = ifft(waveletX.*fft_data,n_conv); 187 | as = as(half_wavN+1:end-half_wavN); 188 | as = reshape(as,EEG.pnts,EEG.trials); 189 | 190 | % collect real and phase data 191 | phase_data(1,:,:) = angle(as); 192 | 193 | % analytic signal of channel 1 194 | fft_data = fft(reshape(EEG.data(chan2idx,:,:),1,[]),n_conv); 195 | as = ifft(waveletX.*fft_data,n_conv); 196 | as = as(half_wavN+1:end-half_wavN); 197 | as = reshape(as,EEG.pnts,EEG.trials); 198 | 199 | % collect real and phase data 200 | phase_data(2,:,:) = angle(as); 201 | 202 | 203 | figure(3), clf 204 | 205 | % ISPC over trials 206 | subplot(211) 207 | ispc_trials = abs(mean(exp(1i*diff(phase_data,[],1)),3)); 208 | plot(EEG.times,ispc_trials) 209 | set(gca,'xlim',[-200 1200]) 210 | xlabel('Time (ms)'), ylabel('ISPC') 211 | 212 | % ISPC over time 213 | subplot(212) 214 | ispc_time = squeeze( abs(mean(exp(1i*diff(phase_data,[],1)),2))); 215 | plot(1:EEG.trials,ispc_time) 216 | xlabel('Trials'), ylabel('ISPC') 217 | 218 | %% end. 219 | -------------------------------------------------------------------------------- /6_Connectivity/2_powerconn.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | %% specify parameters and settings 4 | 5 | load sampleEEGdata.mat 6 | 7 | % select channels, time windows, and frequency ranges 8 | channel1 = 'o1'; 9 | timewin1 = [ -250 0 ]; % in ms 10 | freqwin1 = [ 8 12 ]; % in Hz 11 | 12 | channel2 = 'fz'; 13 | timewin2 = [ 250 500 ]; % in ms 14 | freqwin2 = [ 4 8 ]; % in Hz 15 | 16 | 17 | % frequency parameters for time-frequency decomposition 18 | min_freq = 2; 19 | max_freq = 50; 20 | num_frex = 40; 21 | frex = linspace(min_freq,max_freq,num_frex); 22 | 23 | % other wavelet parameters 24 | range_cycles = [ 4 10 ]; 25 | 26 | %% convert connectivity parameters to indices 27 | 28 | chan1idx = strcmpi({EEG.chanlocs.labels},channel1); 29 | time1idx = dsearchn(EEG.times',timewin1'); 30 | freq1idx = dsearchn(frex',freqwin1'); 31 | 32 | chan2idx = strcmpi({EEG.chanlocs.labels},channel2); 33 | time2idx = dsearchn(EEG.times',timewin2'); 34 | freq2idx = dsearchn(frex',freqwin2'); 35 | 36 | %% setup wavelet and convolution parameters 37 | 38 | s = logspace(log10(range_cycles(1)),log10(range_cycles(end)),num_frex) ./ (2*pi*frex); 39 | wavtime = -2:1/EEG.srate:2; 40 | half_wave = (length(wavtime)-1)/2; 41 | 42 | 43 | % convolution parameters 44 | nWave = length(wavtime); 45 | nData = EEG.pnts * EEG.trials; 46 | nConv = nWave + nData - 1; 47 | 48 | % initialize output time-frequency data 49 | tfall = zeros(2,length(frex),EEG.pnts,EEG.trials); 50 | data4corr = zeros(2,EEG.trials); 51 | 52 | %% run convolution 53 | 54 | for chani=1:2 55 | 56 | % now compute the FFT of all trials concatenated 57 | if chani==1 58 | alldata = reshape( EEG.data(chan1idx,:,:) ,1,[]); 59 | else 60 | alldata = reshape( EEG.data(chan2idx,:,:) ,1,[]); 61 | end 62 | 63 | % this line does the same as the previous if-else statement 64 | eval([ 'alldata = reshape( EEG.data(chan' num2str(chani) 'idx,:,:) ,1,[]);' ]); 65 | 66 | % Fourier spectrum of data 67 | dataX = fft( alldata ,nConv ); 68 | 69 | % loop over frequencies 70 | for fi=1:length(frex) 71 | 72 | % create wavelet and get its FFT 73 | % (does this need to be computed here inside this double-loop?) 74 | wavelet = exp(2*1i*pi*frex(fi).*wavtime) .* exp(-wavtime.^2./(2*s(fi)^2)); 75 | waveletX = fft(wavelet,nConv); 76 | waveletX = waveletX ./ max(waveletX); 77 | 78 | % now run convolution in one step 79 | as = ifft(waveletX .* dataX); 80 | as = as(half_wave+1:end-half_wave); 81 | 82 | % and reshape back to time X trials 83 | as = reshape( as, EEG.pnts, EEG.trials ); 84 | 85 | % compute power and save for all trials 86 | tfall(chani,fi,:,:) = abs(as).^2; 87 | end 88 | end 89 | 90 | %% check that the data look OK 91 | 92 | figure(1), clf 93 | 94 | for chani=1:2 95 | subplot(2,1,chani) 96 | contourf(EEG.times,frex,squeeze(mean( tfall(chani,:,:,:) ,4)),40,'linecolor','none') 97 | set(gca,'clim',[0 5],'xlim',[-200 1300]) 98 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 99 | eval([ 'title([ ''Channel '' channel' num2str(chani) ' ])' ]) 100 | end 101 | 102 | %% and now extract data for connectivity 103 | 104 | data4corr(1,:) = squeeze(mean(mean( tfall(1,freq1idx(1):freq1idx(2),time1idx(1):time1idx(2),:) ,2),3)); 105 | data4corr(2,:) = squeeze(mean(mean( tfall(2,freq2idx(1):freq2idx(2),time2idx(1):time2idx(2),:) ,2),3)); 106 | 107 | figure(2), clf 108 | plot(data4corr(1,:),data4corr(2,:),'o') 109 | xlabel([ 'Power: ' channel1 ', ' num2str(freqwin1(1)) '-' num2str(freqwin1(2)) 'Hz, ' num2str(timewin1(1)) '-' num2str(timewin1(2)) 'ms' ]) 110 | ylabel([ 'Power: ' channel2 ', ' num2str(freqwin2(1)) '-' num2str(freqwin2(2)) 'Hz, ' num2str(timewin2(1)) '-' num2str(timewin2(2)) 'ms' ]) 111 | 112 | [r,p] = corr(data4corr','type','spearman'); 113 | 114 | title([ 'Correlation R=' num2str(r(1,2)) ', p=' num2str(p(1,2)) ]) 115 | 116 | %% end 117 | -------------------------------------------------------------------------------- /7_Basic_statistics/1_permutationTesting.m: -------------------------------------------------------------------------------- 1 | % mikexcohen@gmail.com 2 | 3 | % load in data 4 | load v1_laminar.mat 5 | 6 | % useful variables for later... 7 | npnts = size(csd,2); 8 | ntrials = size(csd,3); 9 | 10 | %% setup some stuff and junk 11 | 12 | % pick channels 13 | % 1 is deep (hippocampus), ~7 is Layer-4 in V1 14 | % hint: try with other channel pairs! 15 | chan1idx = 9; 16 | chan2idx = 5; 17 | 18 | times2save = -.3:.01:1.1; % time vector is in seconds! 19 | xlim = [-.1 1]; % for plotting 20 | 21 | % specify frequency range 22 | min_freq = 10; 23 | max_freq = 100; 24 | num_frex = 50; 25 | 26 | % define frequency and time ranges 27 | frex = linspace(min_freq, max_freq, num_frex); 28 | times2saveidx = dsearchn(timevec',times2save'); 29 | 30 | % parameters for complex Morlet wavelets 31 | wavtime = -2:1/srate:2-1/srate; 32 | half_wav = (length(wavtime)-1)/2; 33 | cycRange = [ 4 10 ]; 34 | nCycles = logspace(log10(cycRange(1)),log10(cycRange(end)),num_frex); 35 | 36 | % FFT parameters 37 | nWave = length(wavtime); 38 | nData = npnts*ntrials; 39 | nConv = nWave+nData-1; 40 | 41 | % and create wavelets 42 | cmwX = zeros(num_frex,nConv); 43 | for fi=1:num_frex 44 | s = nCycles(fi) / (2*pi*frex(fi)); % frequency-normalized width of Gaussian 45 | cmw = exp(1i*2*pi*frex(fi).*wavtime) .* exp( (-wavtime.^2) ./ (2*s^2) ); 46 | tempX = fft(cmw,nConv); 47 | cmwX(fi,:) = tempX ./ max(tempX); 48 | end 49 | 50 | %% run convolution to extract tf power 51 | 52 | % spectra of data 53 | dataX1 = fft( reshape(csd(chan1idx,:,:),1,[]) ,nConv); 54 | dataX2 = fft( reshape(csd(chan2idx,:,:),1,[]) ,nConv); 55 | 56 | 57 | % initialize output time-frequency data 58 | % notice that here we save all trials 59 | tf = zeros(2,num_frex,length(times2save),ntrials); 60 | 61 | for fi=1:num_frex 62 | 63 | % run convolution 64 | as1 = ifft(cmwX(fi,:).*dataX1); 65 | as1 = as1(half_wav+1:end-half_wav); 66 | as1 = reshape(as1,npnts,ntrials); 67 | 68 | % power on all trials from channel "1" 69 | % only from times2saveidx! 70 | tf(1,fi,:,:) = abs(as1(times2saveidx,:)).^2; 71 | 72 | 73 | % run convolution 74 | as2 = ifft(cmwX(fi,:).*dataX2); 75 | as2 = as2(half_wav+1:end-half_wav); 76 | as2 = reshape(as2,npnts,ntrials); 77 | 78 | % power on all trials from channel "2" 79 | % only from times2saveidx! 80 | tf(2,fi,:,:) = abs(as2(times2saveidx,:)).^2; 81 | end 82 | 83 | % for convenience, compute the difference in power between the two channels 84 | diffmap = squeeze(mean(tf(2,:,:,:),4 )) - squeeze(mean(tf(1,:,:,:),4 )); 85 | 86 | 87 | %% plotting the raw data 88 | 89 | clim = [0 20000]; 90 | 91 | figure(1), clf 92 | subplot(221) 93 | imagesc(times2save,frex,squeeze(mean( tf(1,:,:,:),4 ))) 94 | set(gca,'clim',clim,'ydir','n','xlim',xlim) 95 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 96 | title([ 'Channel ' num2str(chan1idx) ]) 97 | 98 | subplot(222) 99 | imagesc(times2save,frex,squeeze(mean( tf(2,:,:,:),4 ))) 100 | set(gca,'clim',clim,'ydir','n','xlim',xlim) 101 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 102 | title([ 'Channel ' num2str(chan2idx) ]) 103 | 104 | subplot(223) 105 | imagesc(times2save,frex,diffmap) 106 | set(gca,'clim',[-mean(clim) mean(clim)],'ydir','n','xlim',xlim) 107 | xlabel('Time (ms)'), ylabel('Frequency (Hz)') 108 | title([ 'Difference: channels ' num2str(chan2idx) ' - ' num2str(chan1idx) ]) 109 | 110 | %% statistics via permutation testing 111 | 112 | % p-value 113 | pval = 0.05; 114 | 115 | % convert p-value to Z value 116 | zval = abs(norminv(pval)); 117 | 118 | % number of permutations 119 | n_permutes = 1000; 120 | 121 | % initialize null hypothesis maps 122 | permmaps = zeros(n_permutes,num_frex,length(times2save)); 123 | 124 | % for convenience, tf power maps are concatenated 125 | % in this matrix, trials 1:ntrials are from channel "1" 126 | % and trials ntrials+1:end are from channel "2" 127 | tf3d = cat(3,squeeze(tf(1,:,:,:)),squeeze(tf(2,:,:,:))); 128 | 129 | 130 | % generate maps under the null hypothesis 131 | for permi = 1:n_permutes 132 | 133 | % randomize trials, which also randomly assigns trials to channels 134 | randorder = randperm(size(tf3d,3)); 135 | temp_tf3d = tf3d(:,:,randorder); 136 | 137 | % compute the "difference" map 138 | % what is the difference under the null hypothesis? 139 | permmaps(permi,:,:) = squeeze( mean(temp_tf3d(:,:,1:ntrials),3) - mean(temp_tf3d(:,:,ntrials+1:end),3) ); 140 | end 141 | 142 | %% show non-corrected thresholded maps 143 | 144 | % compute mean and standard deviation maps 145 | mean_h0 = squeeze(mean(permmaps)); 146 | std_h0 = squeeze(std(permmaps)); 147 | 148 | % now threshold real data... 149 | % first Z-score 150 | zmap = (diffmap-mean_h0) ./ std_h0; 151 | 152 | % threshold image at p-value, by setting subthreshold values to 0 153 | zmap(abs(zmap)0 208 | 209 | % count sizes of clusters 210 | tempclustsizes = cellfun(@length,islands.PixelIdxList); 211 | 212 | % store size of biggest cluster 213 | max_cluster_sizes(permi) = max(tempclustsizes); 214 | end 215 | 216 | 217 | % get extreme values (smallest and largest) 218 | temp = sort( reshape(permmaps(permi,:,:),1,[] )); 219 | max_val(permi,:) = [ min(temp) max(temp) ]; 220 | 221 | end 222 | 223 | %% show histograph of maximum cluster sizes 224 | 225 | figure(3), clf 226 | hist(max_cluster_sizes,20); 227 | xlabel('Maximum cluster sizes'), ylabel('Number of observations') 228 | title('Expected cluster sizes under the null hypothesis') 229 | 230 | 231 | % find cluster threshold (need image processing toolbox for this!) 232 | % based on p-value and null hypothesis distribution 233 | cluster_thresh = prctile(max_cluster_sizes,100-(100*pval)); 234 | 235 | %% plots with multiple comparisons corrections 236 | 237 | % now find clusters in the real thresholded zmap 238 | % if they are "too small" set them to zero 239 | islands = bwconncomp(zmap); 240 | for i=1:islands.NumObjects 241 | % if real clusters are too small, remove them by setting to zero! 242 | if numel(islands.PixelIdxList{i}==i)thresh_lo & zmap