├── metadata ├── B64Channels.mat └── PipelineOverview.png ├── utilities ├── getUuid.m ├── getListString.m ├── getCommonChannelLabels.m ├── vec.m ├── calculateDispersion.m ├── freq2scales.m ├── stdFromMad.m ├── getFrequencyMasks.m ├── reformatBoxPlotData.m ├── getBoxPlotLabels.m ├── plotScalpMap.m ├── getSpectralBandPrints.m ├── insertICA.m ├── selectEEGChannels.m ├── huberMean.m ├── extractAndAddBlinkEvents.m ├── makeGroupBoxPlot.m ├── makeBlinkRatioBoxPlot.m ├── saveFigures.m ├── getBlinkRatio.m ├── robustMean.m ├── convertEEGFromBiosemi256ToB64.m ├── mapBiosemi256ToBiosemi64.m ├── getRandomSpectralSamples.m ├── getSpectrogram.m ├── filterAndResample.m ├── runicaLowrank.m ├── getFileAndFolderList.m ├── cudaicaLowrank.m ├── getSpectralFingerprints.m ├── getEEGBlinkRatios.m ├── cleanWindows.m ├── runSpectralCorrelationBoxPlots.m ├── runRandomSampleCorrelations.m └── runRandomSpectraCorrelations.m ├── runEEGPipelineProjectPaths.m ├── pipelines ├── removeArtifactsMARA.m ├── removeEyeArtifactsLARG.m ├── runASRPipeline.m ├── runMARAPipeline.m ├── runASRaltPipeline.m └── runLARGipeline.m ├── analysis ├── runSpectralFingerprintCorrelations.m ├── runPlotRobustAmplitudes.m ├── runSpectralSampleCorrelations.m └── runEventBlinkRatios.m └── README.md /metadata/B64Channels.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisLab/EEG-Pipelines/HEAD/metadata/B64Channels.mat -------------------------------------------------------------------------------- /metadata/PipelineOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VisLab/EEG-Pipelines/HEAD/metadata/PipelineOverview.png -------------------------------------------------------------------------------- /utilities/getUuid.m: -------------------------------------------------------------------------------- 1 | function uuid = getUuid 2 | uuid = strrep(char(java.util.UUID.randomUUID), '-', ''); 3 | end -------------------------------------------------------------------------------- /utilities/getListString.m: -------------------------------------------------------------------------------- 1 | function listString = getListString(listOfStrings, separator) 2 | %% Return a cell array of strings as a single string separated by comma 3 | 4 | listString = listOfStrings{1}; 5 | for k = 2:length(listOfStrings) 6 | listString = [listString separator listOfStrings{k}]; 7 | end 8 | -------------------------------------------------------------------------------- /utilities/getCommonChannelLabels.m: -------------------------------------------------------------------------------- 1 | function commonLabels = getCommonChannelLabels() 2 | 3 | commonLabels = {'Fp1', 'Fp2', 'F3', 'Fz', 'F4', 'F7', 'F8', ... 4 | 'FC3', 'FCz', 'FC4', 'FT7', 'FT8', ... 5 | 'C3', 'Cz', 'C4', 'TP7', 'TP8', ... 6 | 'CP3', 'CPz', 'CP4', 'P3', 'Pz', 'P4', 'O1', 'Oz', 'O2'}; -------------------------------------------------------------------------------- /utilities/vec.m: -------------------------------------------------------------------------------- 1 | function v = vec( x ) 2 | 3 | % VEC Vectorize. 4 | % VEC(X), where X is a vector, matrix, or N-D array, returns a column vector 5 | % containing all of the elements of X; i.e., VEC(X)=X(:). 6 | 7 | v = reshape( x, numel( x ), 1 ); 8 | 9 | % Copyright 2010 Michael C. Grant and Stephen P. Boyd. 10 | % See the file COPYING.txt for full copyright information. 11 | % The command 'cvx_where' will show where this file is located. 12 | -------------------------------------------------------------------------------- /utilities/calculateDispersion.m: -------------------------------------------------------------------------------- 1 | function dispersion = calculateDispersion(amplitudeMatrix) 2 | %% Computes the disperson of an amplitude matrix 3 | % 4 | % Parameters: 5 | % amplitudeMatrix = channels x recordings array of recording 6 | % channelAmplitude vectors 7 | % dispersion channels x 1 vector with dispersion of amplitudeMatrix 8 | % 9 | 10 | %% Calculate the dispersion 11 | dispersion = stdFromMad(amplitudeMatrix) ./ median(amplitudeMatrix); -------------------------------------------------------------------------------- /utilities/freq2scales.m: -------------------------------------------------------------------------------- 1 | function [scales, freqs] = freq2scales(fmin, fmax, fnum, wname, delta) 2 | if nargin < 5, error('Not enough input arguments.');end 3 | wpool = {'cmor1-1.5','cmor','haar','db','sym','coif','bior','rbio','meyr','dmey','gaus','mexh','morl','cgau','shan','fbsp'}; 4 | if ~any(strcmp(wpool,wname)), error('Unknown wavelet.');end 5 | 6 | fc = centfrq(wname); 7 | smax = fc/(fmin.*delta); 8 | smin = fc/(fmax.*delta); 9 | scales = logspace(log10(smin),log10(smax), fnum); 10 | freqs = scal2frq(scales,wname,delta); -------------------------------------------------------------------------------- /utilities/stdFromMad.m: -------------------------------------------------------------------------------- 1 | function [robustStds, medianValues]= stdFromMad(x, dim) 2 | % [robustStds, medianValues]= std_from_mad(x, dim) 3 | % calculate robust standard deviation using median absolute deviation (MAD) 4 | % acts similar to std and medidan with x being a vector or matrix and 5 | % dim the dimension to act upon. If no vbalue is provided, it acts on the 6 | % first dimension: dim == 1 7 | 8 | if nargin < 2 9 | dim = 1; 10 | end 11 | 12 | medianValues = median(x, dim); 13 | 14 | robustStds = median(abs(bsxfun(@minus, x, medianValues)), dim) * 1.4826; -------------------------------------------------------------------------------- /utilities/getFrequencyMasks.m: -------------------------------------------------------------------------------- 1 | function freqMasks = getFrequencyMasks(freqs, freqBands) 2 | %% Returns the frequency masks for freqs 3 | % 4 | % Parameters: 5 | % freqs f x 1 vector of frequencies in the time-frequency decomposition 6 | % freqBands n x 2 array of frequency band start and end frequencies 7 | % freqMask (output) f x n array of frequency masks for the bands 8 | 9 | numFreqs = length(freqs); 10 | numFreqBands = size(freqBands, 1); 11 | freqMasks = false(numFreqs, numFreqBands); 12 | for b = 1:numFreqBands 13 | freqMasks(:, b) = freqBands(b, 1) <= freqs & freqs < freqBands(b, 2); 14 | end -------------------------------------------------------------------------------- /utilities/reformatBoxPlotData.m: -------------------------------------------------------------------------------- 1 | function [boxValues, boxLabels] = reformatBoxPlotData(boxValues, comboNames) 2 | %% Reformat the data suitable for displaying as a boxplot of correlations 3 | % 4 | % Parameters: 5 | % boxValues (input) n x m array with correlations 6 | % and third column is method 2 7 | % comboNames m X 1 cell array of combo names 8 | % boxValues (output) n*m x 1 array with correlations 9 | % boxLabels (output) n*m x 1 cell array with labels 10 | 11 | %% Compute the labels and values for the box plot 12 | 13 | [n, m] = size(boxValues); 14 | boxNumbers = repmat(1:m, n, 1); 15 | boxNumbers = boxNumbers(:); 16 | boxLabels = comboNames(boxNumbers); 17 | boxValues = boxValues(:); 18 | -------------------------------------------------------------------------------- /utilities/getBoxPlotLabels.m: -------------------------------------------------------------------------------- 1 | function [values, methods, channels] = ... 2 | getBoxPlotLabels(valueArrays, methodNames, channelLabels) 3 | %% Produce a labeled vector from a cell array of values for boxplots 4 | numMethods = length(methodNames); 5 | values = []; 6 | methods = {}; 7 | channels = {}; 8 | for m = 1:numMethods 9 | theseValues = valueArrays{m}; 10 | [numChannels, numItems] = size(theseValues); 11 | theseChannels = repmat(channelLabels(:), 1, numItems); 12 | theseMethods = repmat(methodNames(m), numChannels, numItems); 13 | values = [values(:); theseValues(:)]; 14 | channels = [channels(:); theseChannels(:)]; 15 | methods = [methods(:); theseMethods(:)]; 16 | end 17 | -------------------------------------------------------------------------------- /utilities/plotScalpMap.m: -------------------------------------------------------------------------------- 1 | function h1Fig = plotScalpMap(values, chanlocs, theTitle, axisLimits, ... 2 | theColorMap, electrodeFlag) 3 | %% Plot the median dispersion 4 | 5 | 6 | %% Plot the values 7 | figTitle = [theTitle '[' num2str(median(values)) ']']; 8 | h1Fig = figure('Name', figTitle); 9 | topoplot(values, chanlocs, 'electrodes', electrodeFlag, 'colormap', theColorMap, ... 10 | 'hcolor', [0.55, 0.55, 0.55], 'whitebk', 'on'); 11 | if ~isempty(axisLimits) 12 | caxis(axisLimits) 13 | else 14 | caxis([0 max(values)]); 15 | end 16 | cb = colorbar; 17 | set(cb, 'fontsize', 12); 18 | set(h1Fig, 'Position', [440, 345, 420, 242]); 19 | if ~isempty(theTitle) 20 | title(figTitle, 'Interpreter', 'None') 21 | end 22 | set(h1Fig, 'renderer', 'painter'); -------------------------------------------------------------------------------- /utilities/getSpectralBandPrints.m: -------------------------------------------------------------------------------- 1 | function bandPrints = getSpectralBandPrints(spectrogram, freqMasks) 2 | %% Apply freqMasks to spectrogram to compute mean spectragrams in individual bands 3 | % 4 | % Parameters: 5 | % spectrogram (freqs x times x chans) time-frequency decomposition 6 | % freqMask (freqs x bands) mask of frequencies in each band 7 | % bandPrints (output)(times x chans x bands) mean spectral time decomposition 8 | % 9 | %% Compute the spectral band prints 10 | [numFreqs, numTimes, numChans] = size(spectrogram); %#ok 11 | numFreqBands = size(freqMasks, 2); 12 | bandPrints = zeros(numTimes, numChans, numFreqBands); 13 | for c = 1:numChans 14 | for f = 1:numFreqBands 15 | bandPrints(:, c, f) = mean(spectrogram(freqMasks(:, f), :, c), 1); 16 | end 17 | end -------------------------------------------------------------------------------- /utilities/insertICA.m: -------------------------------------------------------------------------------- 1 | function [EEG, isFrameAnArtifact] = insertICA(EEG, icaType) 2 | 3 | isFrameAnArtifact = []; 4 | if strcmpi(icaType, 'runica') 5 | [cleanEEG, isFrameAnArtifact]= cleanWindows(EEG); 6 | cleanEEG = runicaLowrank(cleanEEG, 'off'); 7 | EEG.icawinv = cleanEEG.icawinv; 8 | EEG.icasphere = cleanEEG.icasphere; 9 | EEG.icaweights = cleanEEG.icaweights; 10 | EEG.icachansind = 1:EEG.nbchan; 11 | elseif strcmpi(icaType, 'infomax') 12 | [cleanEEG, isFrameAnArtifact]= cleanWindows(EEG); 13 | cleanEEG = cudaica_lowrank(cleanEEG, 'off'); 14 | EEG.icawinv = cleanEEG.icawinv; 15 | EEG.icasphere = cleanEEG.icasphere; 16 | EEG.icaweights = cleanEEG.icaweights; 17 | EEG.icachansind = 1:EEG.nbchan; 18 | elseif isempty(EEG.icawinv) 19 | warning('insertICA: ICs were not computed nor did they previously exist') 20 | end 21 | -------------------------------------------------------------------------------- /utilities/selectEEGChannels.m: -------------------------------------------------------------------------------- 1 | function [EEGNew, missing, selectMask] = selectEEGChannels(EEG, channels) 2 | %% Creates a new EEG structure with only channels (in order specified) 3 | % 4 | % Parameters: 5 | % EEG EEG structure 6 | % channels Cell array of channel labels to be selected 7 | % EEGNew (output) EEG structure with only the specified channels 8 | % missing (output) Cell array of missing channel labels 9 | 10 | %% Create the new EEG structure containing only specified channels 11 | myLabels = {EEG.chanlocs.labels}; 12 | [commonChannels, ~, selectMask] = intersect(channels, myLabels, 'stable'); 13 | if length(commonChannels) ~= length(channels) 14 | missing = setdiff(channels, commonChannels); 15 | else 16 | missing = ''; 17 | end 18 | EEGNew = EEG; 19 | EEGNew.chanlocs = EEGNew.chanlocs(selectMask); 20 | EEGNew.data = EEGNew.data(selectMask, :); 21 | EEGNew.nbchan = length(EEGNew.chanlocs); 22 | -------------------------------------------------------------------------------- /runEEGPipelineProjectPaths.m: -------------------------------------------------------------------------------- 1 | %% Run this script to set the project paths 2 | 3 | %%Get the path to this directory 4 | scriptFile = mfilename('fullpath'); 5 | [scriptFolder, scriptName] = fileparts(scriptFile); 6 | addpath(scriptFolder); 7 | 8 | %% Add the path for utilities 9 | thisPath = [scriptFolder filesep 'utilities']; 10 | addpath(genpath(thisPath)); 11 | 12 | %% Add paths for the pipelines 13 | thisPath = [scriptFolder filesep 'pipelines']; 14 | addpath(genpath(thisPath)); 15 | 16 | %% Add paths for analysis scripts 17 | thisPath = [scriptFolder filesep 'analysis']; 18 | addpath(genpath(thisPath)); 19 | 20 | %% Add paths for analysis scripts 21 | thisPath = [scriptFolder filesep 'metadata']; 22 | addpath(genpath(thisPath)); 23 | 24 | %% Eye catch paths 25 | eyeCatchPath = 'D:\Research\EEGPipelineProject\eye-catch'; 26 | addpath(eyeCatchPath); 27 | addpath(genpath([eyeCatchPath filesep 'document'])); 28 | addpath(genpath([eyeCatchPath filesep 'unit_test'])); -------------------------------------------------------------------------------- /utilities/huberMean.m: -------------------------------------------------------------------------------- 1 | function X = huberMean(Y, numberOfStds, iters) 2 | % Perform a Huber mean using the Huber loss function. 3 | % x = huber_mean(Y,rho,iters) 4 | % 5 | % Input: 6 | % Y : MxN matrix over which to average (columnwise) 7 | % numberOfStds : Number of robust standard deviation in which the function acts like regular mean (default: 1) 8 | % iters : number of iterations to perform (default: 1000) 9 | % 10 | % Output: 11 | % x : 1xN vector that is the roust mean of Y 12 | 13 | if ~exist('numberOfStds','var') 14 | numberOfStds = 1; 15 | end 16 | 17 | if ~exist('iters','var') 18 | iters = 1000; 19 | end 20 | 21 | dimensionRobustStd = median(abs(bsxfun(@minus,Y,median(Y,1))),1)*1.4826; 22 | 23 | % first normalize (divide) each dimension by its robust standard deviation, then perform the mean, then 24 | % multiply by the robust standard deviation (to denormalize). 25 | X = bsxfun(@times, robustMean(bsxfun(@times, Y, 1 ./ dimensionRobustStd), numberOfStds, iters), dimensionRobustStd); 26 | 27 | -------------------------------------------------------------------------------- /utilities/extractAndAddBlinkEvents.m: -------------------------------------------------------------------------------- 1 | function [EEG, blinkInfo] = extractAndAddBlinkEvents(EEG) 2 | 3 | % close pop_blinker() figure without affecting others. 4 | allFigureHandles = get(0,'children'); 5 | allFigureHandleVisibilities = get(allFigureHandles, 'handlevisibility'); 6 | set(allFigureHandles, 'handlevisibility', 'off'); 7 | try 8 | [~, ~, blinkInfo.blinks, blinkInfo.blinkFits, blinkInfo.blinkProperties, ... 9 | blinkInfo.blinkStatistics, params] = pop_blinker(EEG, struct()); 10 | if exist(params.blinkerSaveFile, 'file') 11 | delete(params.blinkerSaveFile); 12 | end 13 | catch 14 | blinkInfo = []; 15 | return; 16 | end 17 | close all; 18 | try 19 | set(allFigureHandles, 'handlevisibility', allFigureHandleVisibilities); 20 | catch 21 | end 22 | 23 | [EEG, blinkInfo.blinkSignal] = addBlinkEvents(EEG, blinkInfo.blinks, ... 24 | blinkInfo.blinkFits, blinkInfo.blinkProperties, {'maxFrame', 'leftZero', 'rightZero', 'leftBase', 'rightBase', 'leftZeroHalfHeight', 'rightZeroHalfHeight'}); 25 | EEG = eeg_checkset(EEG); -------------------------------------------------------------------------------- /utilities/makeGroupBoxPlot.m: -------------------------------------------------------------------------------- 1 | function hFig = makeGroupBoxPlot(correlations, labels, theTitle, groupOrder) 2 | 3 | dataLimits = [0, 1]; 4 | hFig = figure('Name', theTitle{1}); 5 | hold on 6 | bxs = boxplot(correlations, labels, 'orientation', 'horizontal', ... 7 | 'DataLim', dataLimits, 'groupOrder', groupOrder); 8 | ylabel('Type') 9 | xlabel('Correlation') 10 | title(theTitle, 'Interpreter', 'none'); 11 | yLimits = get(gca, 'YLim'); 12 | line([0, 0], yLimits, 'Color', [0.8, 0.8, 0.8]); 13 | set(gca, 'XLim', dataLimits, 'XLimMode', 'manual'); 14 | box on 15 | axis ij 16 | line([0.5, 0.5], yLimits, 'Color', [0.2, 0.2, 0.2]); 17 | 18 | [~, cols] = size(bxs); 19 | % colors = lines(5); 20 | % colors(5, :) = [0.4, 0.4, 0.4]; 21 | % patchColors = [0.8, 0.8, 0.8; 0.8, 1, 0.8]; 22 | for j1 = 1:cols 23 | patch(get(bxs(5, j1),'XData'),get(bxs(5, j1),'YData'), ... 24 | [0.8, 0.8, 0.8], 'FaceAlpha', 0.5); 25 | set(bxs(6, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 26 | set(bxs(7, j1), 'MarkerEdgeColor', [0.4, 0.4, 0.4], 'LineWidth', 0.5); 27 | end 28 | hold off 29 | 30 | -------------------------------------------------------------------------------- /utilities/makeBlinkRatioBoxPlot.m: -------------------------------------------------------------------------------- 1 | function hFig = makeBlinkRatioBoxPlot(valueArrays, methodNames, ... 2 | channelLabels, theTitle, theType, dataLimits, f) 3 | %% Plot boxplots of the value arrays 4 | [values, methods] = getBoxPlotLabels(valueArrays, methodNames, channelLabels); 5 | if ~isempty(f) 6 | values = f(values); 7 | end 8 | 9 | hFig = figure('Name', theTitle{1}); 10 | hold on 11 | bxs = boxplot(values, methods, 'orientation', 'vertical', ... 12 | 'DataLim', dataLimits, 'GroupOrder', methodNames); 13 | ylabel(theType) 14 | xlabel('Preprocessing method') 15 | title(theTitle, 'Interpreter', 'none'); 16 | set(gca, 'YLim', dataLimits, 'YLimMode', 'manual', 'XTickLabelMode', 'manual', ... 17 | 'XTickLabel', methodNames); 18 | box on 19 | hold off 20 | [~, cols] = size(bxs); 21 | colors = [0.8, 0.8, 0.8]; 22 | for j1 = 1:cols 23 | patch(get(bxs(5, j1),'XData'),get(bxs(5, j1),'YData'), ... 24 | colors, 'FaceAlpha', 0.5); 25 | set(bxs(6, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 26 | set(bxs(7, j1), 'MarkerEdgeColor', [0.4, 0.4, 0.4], 'LineWidth', 0.5); 27 | end 28 | hold off 29 | -------------------------------------------------------------------------------- /pipelines/removeArtifactsMARA.m: -------------------------------------------------------------------------------- 1 | function [EEG, removalInfo] = removeArtifactsMARA(EEG, icaType) 2 | %% Performs the MARA algorithm to remove artifacts. 3 | % 4 | % Parameters: 5 | % EEG (input/output) EEG may be modified with new ICS 6 | 7 | %% Insert the ICAs if not already there 8 | [EEG, isFrameAnArtifact] = insertICA(EEG, icaType); 9 | 10 | if isempty(EEG.icaweights) 11 | error('removeArtifactsMara: EEG does not have ICs so MARA can not continue'); 12 | end 13 | 14 | %% Perform the MARA algorithms to identify bad ICs 15 | [artcomps, maraInfo] = MARA(EEG); 16 | 17 | %% Remove bad ICs 18 | rejectMask = zeros(1, size(EEG.icawinv, 2)); 19 | rejectMask(artcomps) = 1; 20 | EEG.reject.gcompreject = rejectMask; 21 | EEG.rejectMARAInfo = maraInfo; 22 | if length(artcomps) == length(rejectMask) 23 | error('removeArtifactsMara: MARA detects all components as error'); 24 | end 25 | EEG = pop_subcomp(EEG, artcomps); 26 | 27 | %% Update the removalInfo 28 | removalInfo = struct(); 29 | removalInfo.isFrameAnArtifact = isFrameAnArtifact; 30 | removalInfo.artcmps = artcomps; 31 | removalInfo.chanlocs = EEG.chanlocs; 32 | removalInfo.maraInfo = maraInfo; -------------------------------------------------------------------------------- /utilities/saveFigures.m: -------------------------------------------------------------------------------- 1 | function errorMsgs = saveFigures(hFig, fileName, formats, closeFlag) 2 | %% Save hFig in the specified formats 3 | % 4 | % Parameters: 5 | % hFig figure handle of the figure to be saved 6 | % fileName full path of the file (without extension) for saving 7 | % formats n x 2 cell array first column has the file extension and 8 | % the second column has the matlab format specification 9 | % closeFlag if true close the figure after saving the file 10 | % 11 | % Example format specification: 12 | % formats = {'.png', 'png'; '.fig', 'fig'; '.pdf' 'pdf'; '.eps', 'epsc'}; 13 | % 14 | %% Check the arguments 15 | if nargin < 4 16 | closeFlag = []; 17 | end 18 | if isempty(formats) 19 | warning('%s: not saved because figure format list is empty', fileName); 20 | return 21 | else 22 | errorMsgs = {}; 23 | for m = 1:size(formats, 1) 24 | try 25 | saveas(hFig, [fileName formats{m, 1}], formats{m, 2}); 26 | catch Mex 27 | errorMsgs{end + 1} = [fileName formats{m, 1} ':' Mex.message]; %#ok<*AGROW> 28 | warning(errorMsgs{end}); 29 | end 30 | end 31 | end 32 | 33 | %% Now handle the closing of the figures 34 | if ~isempty(closeFlag) && closeFlag 35 | try 36 | close(hFig); 37 | catch 38 | errorMsgs{end + 1} = [fileName formats{m, 1} ':' Mex.message]; 39 | warning(errorMsgs{end}); 40 | end 41 | end -------------------------------------------------------------------------------- /utilities/getBlinkRatio.m: -------------------------------------------------------------------------------- 1 | function [powerRatio, amplitudeRatio] = getBlinkRatio(signal, tValues, inRange, outRange) 2 | %% Return the ratios of power and amplitude within the blink to outside blink 3 | % 4 | % Parameters: 5 | % signal channels x m vector with signal containing blink (signal or ERP) 6 | % tValues 1 x m vector with corresponding time values 7 | % inRange n x 2 array of start and end times of intervals within blink 8 | % outRange n x 2 array of start and end times of intervals outside blink 9 | % powerRatio (output) positive value of out/in power 10 | % amplitudeRatio (output) positive value of out/in amplitude 11 | 12 | %% Calculate the inMask and outMask 13 | inMask = false(1, size(signal, 2)); 14 | for k = 1:size(inRange, 1) 15 | inMask = inMask | (inRange(k, 1) <= tValues & tValues <= inRange(k, 2)); 16 | end 17 | 18 | outMask = false(1, size(signal, 2)); 19 | for k = 1:size(outRange, 1) 20 | outMask = outMask | (outRange(k, 1) <= tValues & tValues <= outRange(k, 2)); 21 | end 22 | 23 | %% Calculate the ratios 24 | outSignal = signal(:, outMask); 25 | inSignal = signal(:, inMask); 26 | 27 | outMean = mean(outSignal, 2); 28 | outSignal = bsxfun(@minus, outSignal, outMean); 29 | inSignal = bsxfun(@minus, inSignal, outMean); 30 | 31 | powerRatio = mean(inSignal.*inSignal, 2)./mean(outSignal.*outSignal, 2); 32 | amplitudeRatio = mean(abs(inSignal), 2)./mean(abs(outSignal), 2); 33 | -------------------------------------------------------------------------------- /utilities/robustMean.m: -------------------------------------------------------------------------------- 1 | function X = robustMean(Y,rho,iters) 2 | % Perform a robust mean under the Huber loss function. 3 | % x = robust_mean(Y,rho,iters) 4 | % 5 | % Input: 6 | % Y : MxN matrix over which to average (columnwise) 7 | % rho : augmented Lagrangian variable (default: 1) 8 | % iters : number of iterations to perform (default: 1000) 9 | % 10 | % Output: 11 | % x : 1xN vector that is the roust mean of Y 12 | % 13 | % Based on the ADMM Matlab codes also found at: 14 | % http://www.stanford.edu/~boyd/papers/distr_opt_stat_learning_admm.html 15 | % 16 | % Christian Kothe, Swartz Center for Computational Neuroscience, UCSD 2013-09-26 17 | % 18 | 19 | if ~exist('rho','var') 20 | rho = 1; end 21 | if ~exist('iters','var') 22 | iters = 1000; end 23 | 24 | m = size(Y,1); 25 | if m==1 26 | X = Y; 27 | else 28 | mu = sum(Y)/m; 29 | Z = zeros(size(Y)); U = Z; 30 | for k = 1:iters 31 | if k>1 32 | X_old = X; 33 | end 34 | X = mu + sum(Z - U)/m; 35 | if k > 1 36 | maxAbsChange = max(abs(vec((X - X_old) ./ X_old))); 37 | totalChange = sum(vec(X - X_old).^2).^0.5 / sum(vec(X).^2).^0.5; 38 | if maxAbsChange < 1e-10 || totalChange < 1e-14 39 | break; 40 | end 41 | end 42 | D = bsxfun(@minus, X, Y - U); 43 | Z = (rho/(1+rho) + (1/(1+rho))*max(0, (1-(1+1/rho)./abs(D)))).*D; 44 | U = D - Z; 45 | end 46 | end -------------------------------------------------------------------------------- /utilities/convertEEGFromBiosemi256ToB64.m: -------------------------------------------------------------------------------- 1 | function [EEG, chanMap] = convertEEGFromBiosemi256ToB64(EEG, overwriteLocs) 2 | %% Convert 256 channel Biosemi EEG to 64 channels using Biosemi 64 channel map 3 | % 4 | % Parameters: 5 | % EEG (input/output) EEGLAB set file 6 | % chanMap table of Biosemi 64 channels mapping to Biosemi 256 headset 7 | % 8 | %% 9 | %% Load the label correspondence and channel locations 10 | temp = load('B64Channels.mat'); 11 | chanlocs64 = temp.B64Chanlocs; 12 | chanMap = mapBiosemi256ToBiosemi64(); 13 | 14 | %% Set up the B256 channel map 15 | chanlocs256 = EEG.chanlocs; 16 | labelMap = containers.Map('KeyType', 'char', 'ValueType', 'any'); 17 | for k = 1:length(chanlocs256) 18 | labelMap(chanlocs256(k).labels) = k; 19 | end 20 | labelMap64 = containers.Map('KeyType', 'char', 'ValueType', 'any'); 21 | for k = 1:length(chanlocs64) 22 | labelMap64(chanlocs64(k).labels) = k; 23 | end 24 | 25 | %% Now map the channels 26 | chanMask = false(1, length(chanlocs256)); 27 | for k = 1:size(chanMap, 1) 28 | if ~isKey(labelMap, chanMap{k, 2}) 29 | continue; 30 | end 31 | p1 = labelMap(chanMap{k, 2}); 32 | chanMask(p1) = true; 33 | if overwriteLocs 34 | p2 = labelMap64(chanMap{k, 1}); 35 | chanlocs256(p1) = chanlocs64(p2); 36 | else 37 | chanlocs256(p1).labels = chanMap{k, 1}; 38 | end 39 | end 40 | chanlocs256 = chanlocs256(chanMask); 41 | EEG.data = EEG.data(chanMask, :); 42 | EEG.nbchan = length(chanlocs256); 43 | EEG.chanlocs = chanlocs256; 44 | end -------------------------------------------------------------------------------- /utilities/mapBiosemi256ToBiosemi64.m: -------------------------------------------------------------------------------- 1 | function B64ToB256 = mapBiosemi256ToBiosemi64() 2 | 3 | %% Kays labels mapping Biosemi B256 to Biosemi B64 using closest channels 4 | B64ToB256 = ... 5 | {'Fp1', 'E29'; ... 6 | 'AF7', 'F10'; ... 7 | 'AF3', 'E31'; ... 8 | 'F1', 'E23'; ... 9 | 'F3', 'F7'; ... 10 | 'F5', 'F14'; ... 11 | 'F7', 'F28'; ... 12 | 'FT7', 'F31'; ... 13 | 'FC5', 'G8'; ... 14 | 'FC3', 'F23'; ... 15 | 'FC1', 'F3'; ... 16 | 'C1', 'F21'; ... 17 | 'C3', 'G16'; ... % 18 | 'C5', 'G14'; ... 19 | 'T7', 'G11'; ... % 20 | 'TP7', 'G23'; ... 21 | 'CP5', 'G25'; ... 22 | 'CP3', 'H6'; ... 23 | 'CP1', 'H2'; ... 24 | 'P1', 'A7'; ... 25 | 'P3', 'H19'; ... 26 | 'P5', 'H9'; ... 27 | 'P7', 'G30'; ... 28 | 'P9', 'H13'; ... 29 | 'PO7', 'H16'; ... 30 | 'PO3', 'H27'; ... 31 | 'O1', 'H29'; ... 32 | 'Iz', 'A16'; ... 33 | 'Oz', 'A19'; ... % 34 | 'POz', 'A21'; ... 35 | 'Pz', 'A6'; ... % 36 | 'CPz', 'A4'; ... 37 | 'Fpz', 'E12'; ... % 38 | 'Fp2', 'D32'; ... 39 | 'AF8', 'D24'; ... 40 | 'AF4', 'D30'; ... 41 | 'AFz', 'E14'; ... 42 | 'Fz', 'E17'; ... % 43 | 'F2', 'E6'; ... 44 | 'F4', 'D27'; ... 45 | 'F6', 'D20'; ... 46 | 'F8', 'D12'; ... 47 | 'FT8', 'D9'; ... 48 | 'FC6', 'D7'; ... 49 | 'FC4', 'D5'; ... 50 | 'FC2', 'E3'; ... 51 | 'FCz', 'E1'; ... 52 | 'Cz', 'A1'; ... % 53 | 'C2', 'C25'; ... 54 | 'C4', 'C23'; ... % 55 | 'C6', 'C21'; ... 56 | 'T8', 'C18'; ... % 57 | 'TP8', 'C13'; ... 58 | 'CP6', 'C15'; ... 59 | 'CP4', 'B23'; ... 60 | 'CP2', 'B21'; ... 61 | 'P2', 'A24'; ... 62 | 'P4', 'B18'; ... 63 | 'P6', 'B26'; ... 64 | 'P8', 'C11'; ... 65 | 'P10', 'B30'; ... 66 | 'PO8', 'B15'; ... 67 | 'PO4', 'B7'; ... 68 | 'O2', 'B9'}; 69 | -------------------------------------------------------------------------------- /utilities/getRandomSpectralSamples.m: -------------------------------------------------------------------------------- 1 | function [spectralSamples, freqs] = getRandomSpectralSamples(EEG, startingFracs, ... 2 | sampleLength, channels, numFreqs, freqRange) 3 | 4 | %% Compute specified random spectral samples 5 | % 6 | % Parameters: 7 | % EEG EEG set structure 8 | % startingFracs numChans x numSamples array with starting fractions 9 | % sampleLength Number of seconds in each spectral sample 10 | % channels Cell array of labels of the channels to use 11 | % numFreqs Number of frequencies in spectrum 12 | % freqRange Frequency range of the spectral samples 13 | % 14 | % Note: If EEG does not have all the channels, spectralSamples is empty 15 | % 16 | 17 | %% Check channels are all there and return empty if not 18 | [EEGNew, missing] = selectEEGChannels(EEG, channels); 19 | if ~isempty(missing) 20 | warning('EEG is missing channels %s\n-- can not compute spectral samples', ... 21 | getListString(missing, ',')); 22 | spectralSamples = []; 23 | freqs = []; 24 | return; 25 | end 26 | 27 | %% Compute the spectral samples 28 | numSpectra = size(startingFracs, 2); 29 | fBins = linspace(freqRange(1), freqRange(2), numFreqs); 30 | sFrames = sampleLength*EEGNew.srate; 31 | data = EEGNew.data; 32 | [numChans, numFrames] = size(data); 33 | actualFrames = numFrames - sFrames; 34 | startFrames = ceil(startingFracs*actualFrames); 35 | endFrames = startFrames + sFrames - 1; 36 | spectralSamples = zeros(numChans, numFreqs, numSpectra); 37 | 38 | for j = 1:numSpectra 39 | dataSamples = zeros(numChans, sFrames); 40 | for c = 1:numChans 41 | dataSamples(c, :) = data(c, startFrames(c,j):endFrames(c, j)); 42 | end 43 | [x, f] = pmtm(dataSamples', 4, fBins, 128); 44 | spectralSamples(:, :, j) = x'; 45 | end 46 | freqs = f; -------------------------------------------------------------------------------- /utilities/getSpectrogram.m: -------------------------------------------------------------------------------- 1 | %% Create the random spectral points for the common channels 2 | function [spectrogram, freqs] = getSpectrogram(EEG, channels, numFreqs, freqRange) 3 | %% Compute EEG robust z-scaled spectragram 4 | % 5 | % Parameters: 6 | % EEG EEG set structure 7 | % channels Cell array of labels of channels in the spectragram 8 | % numFreqs Number of frequencies in the spectragram 9 | % freqRange 1 x 2 array with smallest and largest frequency in print 10 | % spectragram (output) freqs x times x channels array with spectragram 11 | % freqs (output) vector of frequencies 12 | % 13 | % Note: If EEG does not have all the channels, spectragram is empty 14 | % 15 | 16 | %% Check channels are all there and return empty if not 17 | [EEGNew, missing] = selectEEGChannels(EEG, channels); 18 | if ~isempty(missing) 19 | warning('EEG is missing channels %s\n-- can not compute spectragram', ... 20 | getListString(missing, ',')); 21 | spectrogram = []; 22 | freqs = []; 23 | return; 24 | end 25 | 26 | %% Compute the wavelet scales using Morlet wavelets 27 | T = 1/EEGNew.srate; 28 | wname = 'cmor1-1.5'; 29 | [tScales, tFreqs] = freq2scales(freqRange(1), freqRange(2), numFreqs, wname, T); 30 | [~, sortIds] = sort(tFreqs, 'ascend'); 31 | scales = tScales(sortIds); 32 | freqs = tFreqs(sortIds); 33 | 34 | %% Perform the time-frequency decomposition using wavelets and robust z-score 35 | data = EEGNew.data; 36 | numTimes = size(data, 2); 37 | numChans = length(channels); 38 | spectrogram = zeros(numFreqs, numTimes, numChans); 39 | for c = 1:numChans 40 | tfd = cwt(data(c, :)', scales, wname); 41 | tfd = abs(tfd); % convert to amplitudes 42 | baselineMedian = median(tfd, 2); 43 | tfd = bsxfun(@minus, tfd, baselineMedian); 44 | baselineRobustStd = median(abs(tfd), 2) * 1.4826; 45 | spectrogram(:,:, c) = bsxfun(@times, tfd, 1./ baselineRobustStd); 46 | end 47 | -------------------------------------------------------------------------------- /utilities/filterAndResample.m: -------------------------------------------------------------------------------- 1 | function EEG = filterAndResample(EEG, highPassFrequency, maxSamplingRate) 2 | % Removes channel mean, filters, resamples, and removes extra fields from EEG.etc.noiseDetection 3 | % 4 | % Parameters: 5 | % EEG (input/output) EEG to be filtered (only has EEG channels) 6 | % highPassFrequency high pass filter at this frequency if non empty 7 | % maxSamplingRate resample if non-empty and lower than EEG.srate 8 | % 9 | %% Remove channel mean 10 | EEG.data = bsxfun(@minus, EEG.data, mean(EEG.data, 2)); 11 | 12 | %% High pass filter 13 | if ~isempty(highPassFrequency) 14 | EEG = pop_eegfiltnew(EEG, 'locutoff', highPassFrequency, 'plotfreqz', 0); 15 | % [], [], 0, 0, 0, false, 0); 16 | %EEG = pop_eegfiltnew(EEG, highPassFrequency, [], 'plotfreqz', 0); 17 | % str2num( result{1}) }; end 18 | % if ~isempty(result{2}), options = { options{:} 'hicutoff' str2num( result{2}) }; end 19 | % if ~isempty(result{3}), options = { options{:} 'filtorder' result{3} }; end 20 | % if result{4}, options = { options{:} 'revfilt' result{4} }; end 21 | % if result{5}, options = { options{:} 'minphase' result{5} }; end 22 | % if result{6}, options = { options{:} ' result{6} }; end 23 | % if ~isempty(result{7} ), options = { options{:} 'chantype' parsetxt(result{7}) }; end 24 | % if ~isempty(result{8}) && isempty( result{7} ) 25 | end 26 | %% Resample if necessary 27 | if ~isempty(maxSamplingRate) && EEG.srate > maxSamplingRate 28 | EEG = pop_resample(EEG, maxSamplingRate); 29 | end 30 | 31 | EEG.etc.filterAndResample.maxSamplingRate = maxSamplingRate; 32 | EEG.etc.filterAndResample.filterType = 'highpass'; 33 | EEG.etc.filterAndResample.highPassFrequency = highPassFrequency; 34 | 35 | %% Clean up the reference metadata as appropriate 36 | if isfield(EEG.etc, 'noiseDetection') && isfield(EEG.etc.noiseDetection, 'reference') 37 | EEG.etc.noiseDetection.reference = cleanupReference(EEG.etc.noiseDetection.reference); 38 | end 39 | 40 | -------------------------------------------------------------------------------- /analysis/runSpectralFingerprintCorrelations.m: -------------------------------------------------------------------------------- 1 | %% Calculate the spectral finger print correlations across methods for an EEG file 2 | 3 | %% Set up the files 4 | dataDir = 'D:\Research\EEGPipelineProject\dataOut'; 5 | %eegBaseFile = 'basicGuardSession3Subj3202Rec1'; 6 | %eegBaseFile = 'dasSession16Subj131004Rec1'; 7 | %eegBaseFile = 'speedControlSession1Subj2015Rec1'; 8 | eegBaseFile = 'trafficComplexitySession1Subj2002Rec1'; 9 | methodNames = {'LARG', 'MARA', 'ASR_10', 'ASRalt_10', 'ASR_5', 'ASRalt_5'}; 10 | numMethods = length(methodNames); 11 | 12 | %% Read in the files 13 | eegs = cell(numMethods, 1); 14 | for m = 1:numMethods 15 | fileName = [dataDir filesep eegBaseFile '_' methodNames{m} '.set']; 16 | eegs{m} = pop_loadset(fileName); 17 | end 18 | 19 | %% Spectral parameters 20 | freqRange = [2, 30]; 21 | numFreqs = 50; 22 | freqBands = [2, 4; 4, 7; 7, 12; 12, 30]; 23 | freqBandNames = {'Delta'; 'Theta'; 'Alpha'; 'Beta'}; 24 | channels = getCommonChannelLabels(); 25 | numBands = size(freqBandNames, 1); 26 | 27 | %% Compute the spectragrams and band prints for the EEG for the different methods 28 | spectrograms = cell(numMethods, 1); 29 | for m = 1:numMethods 30 | [spectrograms{m}, freqs] = getSpectrogram(eegs{m}, channels, numFreqs, freqRange); 31 | end 32 | freqMasks = getFrequencyMasks(freqs, freqBands); 33 | bandPrints = cell(numMethods, 1); 34 | for m = 1:numMethods 35 | bandPrints{m} = getSpectralBandPrints(spectrograms{m}, freqMasks); 36 | end 37 | 38 | %% Output the correlations 39 | fprintf('Algorithms All %s\n', getListString(freqBandNames, ' ')); 40 | for m1 = 1:numMethods - 1 41 | bPrintsm1 = bandPrints{m1}; 42 | for m2 = m1 + 1:numMethods 43 | fprintf('%s v %s: %8.5f', methodNames{m1}, methodNames{m2}, ... 44 | corr(spectrograms{m1}(:), spectrograms{m2}(:))); 45 | bPrintsm2 = bandPrints{m2}; 46 | for b = 1:numBands 47 | bP1 = bPrintsm1(:, :, b); 48 | bP2 = bPrintsm2(:, :, b); 49 | fprintf(' %8.5f', corr(bP1(:), bP2(:))); 50 | end 51 | fprintf('\n'); 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /utilities/runicaLowrank.m: -------------------------------------------------------------------------------- 1 | function EEG = runicaLowrank(EEG, verbose) 2 | % Perform ICA on rank defficient EEG data using PCA reduction. 3 | % This function is a minimalistic wrapper for computing the ICA decomposition 4 | % of EEG data on a GPU using cudaica. If no GPU is available it tries to 5 | % run binica. 6 | % 7 | % Input: 8 | % EEG: EEGLAB's EEG structure 9 | % 10 | % EEG: same structure but with the ICA fields 11 | % 12 | % For more information visit http://liaa.dc.uba.ar/?q=node/20 13 | % See also: 14 | % Raimondo, F., Kamienkowski, J.E., Sigman, M., and Slezak, D.F., 2012. CUDAICA: GPU Optimization of Infomax-ICA EEG Analysis. 15 | % Computational Intelligence and Neuroscience Volume 2012 (2012), Article ID 206972, 8 pages doi:10.1155/2012/206972 16 | % http://www.hindawi.com/journals/cin/2012/206972/ 17 | % 18 | % Author: Alejandro Ojeda, SCCN, INC, UCSD, Mar-2013 19 | if nargin < 2 20 | verbose = 'off'; 21 | end 22 | X = EEG.data(:, :); 23 | if ~isa(X,'double') 24 | X = double(X); 25 | end 26 | [n, m] = size(X); 27 | 28 | tol = max(size(EEG.data)) * eps(norm(EEG.data)); % default tolerance for rank() 29 | r = rank(X, tol * 100); % be more strict and prevent higher rank due to roundoff and limited numerical precisions. 30 | 31 | if r < n 32 | disp('Removing null subspace from tha data before running ICA using PCA.'); 33 | Cx = X*X'/m; 34 | [U,S] = eig(Cx); 35 | [~,sorting] = sort(diag(S),'descend'); 36 | Xr = U(:,sorting(1:end-(n-r)))'*X; 37 | [wts, sph] = resilientIca(Xr, verbose); 38 | wts = wts*sph*U(:, sorting(1:end-(n-r)))'; 39 | sph = eye(n); 40 | else 41 | [wts,sph] = resilientIca(X, verbose); 42 | end 43 | 44 | icawinv = pinv(wts*sph); 45 | EEG.icawinv = icawinv; 46 | EEG.icasphere = sph; 47 | EEG.icaweights = wts; 48 | EEG.icachansind = 1:EEG.nbchan; 49 | EEG = eeg_checkset(EEG); 50 | 51 | function [wts,sph] = resilientIca(X, verbose) 52 | try 53 | [wts, sph] = runica(X, 'verbose', verbose, 'extended', 3); 54 | catch 55 | fprintf('runica extended has failed used non-extended'); 56 | [wts, sph] = runica(X, 'verbose', verbose); 57 | end 58 | end 59 | end -------------------------------------------------------------------------------- /utilities/getFileAndFolderList.m: -------------------------------------------------------------------------------- 1 | function fileList = getFileAndFolderList(fileOrFolderList, searchQuery, recursive) 2 | %% Return a list of files and folders meeting specified search query 3 | % 4 | % Parameters: 5 | % fileOrFolderList cell array or string containing a list of files and/or folders 6 | % searchQuery cell array of wildcards, default: {'*.mat' '*.sto'} 7 | % recursive if true, searches subdirectories recursively 8 | % fileList (output) cell array of full paths of files meeting requirements 9 | % 10 | %% Check the parameters 11 | if nargin < 2 12 | searchQuery = {'*.mat' '*.sto'}; 13 | end 14 | 15 | if ischar(searchQuery) 16 | searchQuery = {searchQuery}; 17 | end 18 | 19 | if nargin < 3 20 | recursive = false; 21 | end 22 | 23 | if ischar(fileOrFolderList) 24 | fileOrFolderList = {fileOrFolderList}; 25 | end 26 | 27 | %% Traverse the directories creating the full path list of files and folders 28 | fileList = {}; 29 | counter = 1; 30 | for i=1:length(fileOrFolderList) 31 | if exist(fileOrFolderList{i}, 'dir') 32 | d = dir([fileOrFolderList{i} filesep searchQuery{1}]); 33 | for j = 2:length(searchQuery) 34 | if isempty(d) 35 | d = dir([fileOrFolderList{i} filesep searchQuery{j}]); 36 | else 37 | d = [vec(d); vec(dir([fileOrFolderList{i} filesep searchQuery{j}]))]; 38 | end 39 | end 40 | 41 | fileList = [fileList strcat([fileOrFolderList{i} filesep], {d.name})]; %#ok<*AGROW> 42 | 43 | if recursive 44 | subfolders = dir(fileOrFolderList{i}); 45 | subfolderFileList = {}; %#ok 46 | for j=1:length(subfolders) 47 | if subfolders(j).isdir && ~(strcmp(subfolders(j).name, '.') || strcmp(subfolders(j).name, '..')) 48 | subfolderFileList = getFileAndFolderList([fileOrFolderList{i} filesep subfolders(j).name], searchQuery, true); 49 | fileList = [fileList subfolderFileList]; 50 | end 51 | end 52 | end 53 | 54 | counter = length(fileList) + 1; 55 | 56 | elseif exist(fileOrFolderList{i}, 'file') 57 | fileList{counter} = fileOrFolderList{i}; 58 | counter = counter + 1; 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /pipelines/removeEyeArtifactsLARG.m: -------------------------------------------------------------------------------- 1 | function [EEG, removalInfo] = removeEyeArtifactsLARG(EEG, blinkInfo, ... 2 | icaType, regressBlinkEvents, regressBlinkSignal) 3 | %% Remove eye artifacts from EEG using LARG pipeline 4 | % 5 | % Parameters: 6 | % EEG (input/output) may have ICs added to EEG 7 | % blinkInfo Blink information structure from Blinker output. 8 | % If empty, blink information is not used at all 9 | % icaType Indicates whether ICs should be computed and/or used 10 | % runica uses ordinary runica 11 | % infomax uses cudaica (which must be installed) 12 | % xxx anything else (not empty) assumes ICs computed coming in 13 | % '' empty string indicates don't compute ICs or use eyecatch 14 | % regressBlinkEvents if true, blink events are regressed out 15 | % regressBlinkSignal if true, blink signal is regressed out 16 | % 17 | % Currently regressing out blink events and blink signal are not available. 18 | % The current implementation just subtracts out the blink signal. 19 | 20 | %% Run ICA if needed and perform eyeCatch to remove bad ICS 21 | removalInfo = []; 22 | if ~isempty(icaType) 23 | [EEG, isFrameAnArtifact] = insertICA(EEG, icaType); 24 | eyeDetector = eyeCatch; 25 | [isEye, ~, scalpmapObj] = eyeDetector.detectFromEEG(EEG); 26 | 27 | EEG.etc.eyeICs.icaNumbers = find(isEye); 28 | EEG.etc.eyeICs.icawinv = EEG.icawinv(:,isEye); 29 | EEG.etc.eyeICs.icaweights = EEG.icaweights(isEye,:); 30 | removalInfo = EEG.etc.eyeICs; 31 | 32 | if isempty(EEG.icachansind) 33 | EEG.icachansind = 1:size(EEG.data,1); 34 | end 35 | EEG = pop_subcomp(EEG, find(isEye)); 36 | removalInfo.isFrameAnArtifact = isFrameAnArtifact; 37 | removalInfo.isEye = isEye; 38 | removalInfo.scalpmapObj = scalpmapObj; 39 | removalInfo.chanlocs = EEG.chanlocs; 40 | end 41 | 42 | %% Now regress out blink information if requested 43 | if ~isempty(blinkInfo) 44 | if regressBlinkEvents 45 | warning('Regressing out blink events is currently not implemented for LARG'); 46 | if regressBlinkSignal 47 | warning('Regressing out blink signal is currently not implemented for LARG'); 48 | end 49 | elseif ~isempty(blinkInfo.blinkSignal) 50 | x = EEG.data / blinkInfo.blinkSignal; 51 | EEG.data = EEG.data - x * blinkInfo.blinkSignal; 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /utilities/cudaicaLowrank.m: -------------------------------------------------------------------------------- 1 | function EEG = cudaicaLowrank(EEG, verbose) 2 | % Perform ICA on rank defficient EEG data using PCA reduction. 3 | % This function is a minimalistic wrapper for computing the ICA decomposition 4 | % of EEG data on a GPU using cudaica. If no GPU is available it tries to 5 | % run binica. 6 | % 7 | % Input: 8 | % EEG: EEGLAB's EEG structure 9 | % 10 | % EEG: same structure but with the ICA fields 11 | % 12 | % For more information visit http://liaa.dc.uba.ar/?q=node/20 13 | % See also: 14 | % Raimondo, F., Kamienkowski, J.E., Sigman, M., and Slezak, D.F., 2012. CUDAICA: GPU Optimization of Infomax-ICA EEG Analysis. 15 | % Computational Intelligence and Neuroscience Volume 2012 (2012), Article ID 206972, 8 pages doi:10.1155/2012/206972 16 | % http://www.hindawi.com/journals/cin/2012/206972/ 17 | % 18 | % Author: Alejandro Ojeda, SCCN, INC, UCSD, Mar-2013 19 | if nargin < 2, verbose = 'off';end 20 | X = EEG.data(:,:); 21 | if ~isa(X,'double'), X = double(X);end 22 | [n,m] = size(X); 23 | 24 | tol = max(size(EEG.data)) * eps(norm(EEG.data)); % default tolerance for rank() 25 | r = rank(X, tol * 100); % be more strict and prevent higher rank due to roundoff and limited numerical precisions. 26 | 27 | if r < n 28 | disp('Removing null subspace from tha data before running ICA using PCA.'); 29 | Cx = X*X'/m; 30 | [U,S] = eig(Cx); 31 | [~,sorting] = sort(diag(S),'descend'); 32 | Xr = U(:,sorting(1:end-(n-r)))'*X; 33 | [wts,sph] = resilient_ica(Xr, verbose); 34 | wts = wts*sph*U(:,sorting(1:end-(n-r)))'; 35 | sph = eye(n); 36 | else 37 | [wts,sph] = resilient_ica(X, verbose); 38 | end 39 | 40 | icawinv = pinv(wts*sph); 41 | EEG.icawinv = icawinv; 42 | EEG.icasphere = sph; 43 | EEG.icaweights = wts; 44 | EEG.icachansind = 1:EEG.nbchan; 45 | EEG = eeg_checkset(EEG); 46 | 47 | %% Clean-up 48 | rmfiles = dir('cudaica.*'); 49 | for k=1:length(rmfiles), delete(rmfiles(k).name);end 50 | d = fileparts(which('cudaica_lowrank')); 51 | rmfiles = dir([d filesep '*.sc']); 52 | for k=1:length(rmfiles), delete(fullfile(d,rmfiles(k).name));end 53 | rmfiles = dir([fileparts(which('cudaica_lowrank')) filesep '*.sph']); 54 | for k=1:length(rmfiles), delete(fullfile(d,rmfiles(k).name));end 55 | end 56 | 57 | function [wts,sph] = resilient_ica(X, verbose) 58 | try 59 | try 60 | try 61 | [wts,sph] = cudaica(X, 'verbose',verbose, 'extended', 3); 62 | catch 63 | [wts,sph] = cudaica(X, 'verbose',verbose); 64 | end; 65 | catch 66 | disp('CUDAICA has failed, trying binica...'); 67 | [wts,sph] = binica(Xr, 'extended', 3); 68 | end; 69 | catch 70 | [wts,sph] = binica(X); 71 | end; 72 | end 73 | -------------------------------------------------------------------------------- /utilities/getSpectralFingerprints.m: -------------------------------------------------------------------------------- 1 | %% Create the random spectral points for the common channels 2 | function [spectralPrint, bandPrints] = getSpectralFingerprints(EEG, channels, ... 3 | numFreqs, freqRange, freqBands) 4 | %% Compute EEG spectral fingerprint and spectral band fingerprints 5 | % 6 | % Parameters: 7 | % EEG EEG set structure 8 | % channels cell array of labels of channels in the fingerprint 9 | % numFreqs number of frequencies in the spectral fingerprints 10 | % freqRange 1 x 2 array with smallest and largest frequency in print 11 | % freqBands n x 2 array with frequencies of the n bands to resolve 12 | % spectralPrint (output) freqs x times x channels array with spectral fingerprint 13 | % bandPrints (output) times x channels x bands array with band 14 | % averaged fingerprints 15 | %% Parameters 16 | wname = 'cmor1-1.5'; 17 | numFreqBands = size(freqBands, 1); 18 | numChans = length(channels); 19 | %% Initialize fingerprints and check channels are all there 20 | spectralPrint = []; 21 | bandPrints = []; 22 | [EEGNew, missing] = selectEEGChannels(EEG, channels); 23 | if ~isempty(missing) 24 | warning('EEG is missing channels %s\n-- can not compute spectral fingerprints', ... 25 | getListString(missing, ',')); 26 | return; 27 | end 28 | numChannels = length(channels); 29 | 30 | %% Compute the wavelet scales 31 | T = 1/EEGNew.srate; 32 | [tScales, tFreqs] = freq2scales(freqRange(1), freqRange(2), numFreqs, wname, T); 33 | [~, sortIds] = sort(tFreqs, 'ascend'); 34 | scales = tScales(sortIds); 35 | freqs = tFreqs(sortIds); 36 | 37 | %% Perform the time-frequency decomposition using wavelets 38 | tfdecomp = cell(numChannels, 1); 39 | data = EEGNew.data; 40 | for c = 1:numChannels 41 | tfd = cwt(data(c, :)', scales, wname); 42 | tfd = abs(tfd); % convert to amplitudes 43 | baselineMedian = median(tfd, 2); 44 | tfd = bsxfun(@minus, tfd, baselineMedian); 45 | baselineRobustStd = median(abs(tfd), 2) * 1.4826; 46 | tfdecomp{c} = bsxfun(@times, tfd, 1./ baselineRobustStd); 47 | end 48 | 49 | freqMasks = false(numFreqs, numFreqBands); 50 | for f = 1:numFreqBands 51 | freqMasks(:, f) = freqBands(f, 1) <= freqs & freqs < freqBands(f, 2); 52 | end 53 | numTimes = size(tfdecomp{1, 1}, 2); 54 | 55 | %% Now assemble the data into a single vector and into bands 56 | tfs = zeros(numFreqs, numTimes, numChans); 57 | tfsBands = zeros(numTimes, numChans, numFreqBands); 58 | for c = 1:numChans 59 | tf = tfdecomp{c}; 60 | tfs(:,:, c) = tf; 61 | for f = 1:numFreqBands 62 | tfsBands(:, c, f) = mean(tf(freqMasks(:, f), :), 1); 63 | end 64 | end 65 | 66 | spectralPrint = tfs; 67 | bandPrints = tfsBands; 68 | % -------------------------------------------------------------------------------- /analysis/runPlotRobustAmplitudes.m: -------------------------------------------------------------------------------- 1 | %% Plot the robust amplitudes before and after pipeline as scalp maps 2 | 3 | %% Set up the file names and which methods 4 | dataDir = 'D:\Research\EEGPipelineProject\dataOut'; 5 | imageDir = 'D:\Research\EEGPipelineProject\dataImages'; 6 | %eegBaseFile = 'basicGuardSession3Subj3202Rec1'; 7 | %eegBaseFile = 'dasSession16Subj131004Rec1'; 8 | %eegBaseFile = 'speedControlSession1Subj2015Rec1'; 9 | eegBaseFile = 'trafficComplexitySession1Subj2002Rec1'; 10 | methodNames = {'LARG', 'MARA', 'ASR_10', 'ASRalt_10', 'ASR_5', 'ASRalt_5'}; 11 | numMethods = length(methodNames); 12 | 13 | %% Specify the formats in which to save the data 14 | %figureFormats = {'.png', 'png'; '.fig', 'fig'; '.pdf' 'pdf'; '.eps', 'epsc'}; 15 | figureFormats = {'.png', 'png'}; 16 | figureClose = false; 17 | 18 | %% Make sure that image directory exists 19 | if ~isempty(imageDir) && ~exist(imageDir, 'dir') 20 | mkdir(imageDir); 21 | end 22 | 23 | %% Read in the files 24 | eegs = cell(numMethods, 1); 25 | for m = 1:numMethods 26 | fileName = [dataDir filesep eegBaseFile '_' methodNames{m} '.set']; 27 | eegs{m} = pop_loadset(fileName); 28 | end 29 | 30 | %% Specify the parameters parameters 31 | channels = getCommonChannelLabels(); 32 | numChans = length(channels); 33 | theColorMap = parula(20); 34 | electrodeFlag = 'ptslabels'; 35 | axisLimits = []; 36 | 37 | %% Now plot the before and after scalp maps 38 | hFigsBefore = cell(numMethods, 1); 39 | hFigsAfter = cell(numMethods, 1); 40 | for m = 1:numMethods 41 | [EEG, missing, selectMask] = selectEEGChannels(eegs{m}, channels); 42 | if ~isempty(missing) 43 | warning('EEG is missing channels %s\n-- can not compute spectral fingerprints', ... 44 | getListString(missing, ',')); 45 | continue; 46 | end 47 | beforeValues = EEG.etc.amplitudeInfoBefore.channelRobustStd(selectMask); 48 | afterValues = EEG.etc.amplitudeInfoAfter.channelRobustStd(selectMask); 49 | beforeTitle = [methodNames{m} ': robust amplitude before artifact removal']; 50 | afterTitle = [methodNames{m} ': robust amplitude after artifact removal']; 51 | hFigsBefore{m} = plotScalpMap(beforeValues, EEG.chanlocs, beforeTitle, axisLimits, ... 52 | theColorMap, electrodeFlag); 53 | hFigsAfter{m} = plotScalpMap(afterValues, EEG.chanlocs, afterTitle, axisLimits, ... 54 | theColorMap, electrodeFlag); 55 | 56 | if ~isempty(imageDir) 57 | baseFile = [imageDir filesep 'channelAmplitudeBefore_' methodNames{m} ... 58 | '_' eegBaseFile]; 59 | saveFigures(hFigsBefore{m}, baseFile, figureFormats, figureClose); 60 | baseFile = [imageDir filesep 'channelAmplitudeAfter_' methodNames{m}... 61 | '_' eegBaseFile]; 62 | saveFigures(hFigsAfter{m}, baseFile, figureFormats, figureClose); 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /utilities/getEEGBlinkRatios.m: -------------------------------------------------------------------------------- 1 | function [blinkPowerRatios, nonBlinkPowerRatios, blinkAmpRatios, ... 2 | nonBlinkAmpRatios, numBlinks, numOverlaps,... 3 | erpBlinkPowerRatio, nonBlinkErpPowerRatio, ... 4 | erpBlinkAmpRatio, nonBlinkErpAmpRatio] = ... 5 | getEEGBlinkRatios(EEG, inRange, outRange) 6 | %% Compute the blink ratios of the individual blinks as well as the blink 7 | %% Initialze the calculation 8 | [numChans, numFrames] = size(EEG.data); 9 | srate = EEG.srate; 10 | events = EEG.event; 11 | eventTypes = {EEG.event.type}; 12 | maxMask = strcmpi(eventTypes, 'maxFrame'); 13 | numBlinks = sum(maxMask); 14 | blinkPowerRatios = nan(numChans, numBlinks); 15 | blinkAmpRatios = nan(numChans, numBlinks); 16 | blinkEvents = events(maxMask); 17 | 18 | blinkFrames = round(cell2mat({blinkEvents.latency})); 19 | minTime = min(min(inRange(:)), min(outRange(:))); 20 | maxTime = max(max(inRange(:)), max(outRange(:))); 21 | minFrames = round(minTime*srate); 22 | maxFrames = round(maxTime*srate); 23 | minInFrames = round(min(inRange(:))*srate); 24 | maxInFrames = round(max(inRange(:))*srate); 25 | tValues = (minFrames:maxFrames)/srate; 26 | %% Now process the blinks one at a time 27 | blinkMask = false(numFrames, 1); 28 | data = EEG.data; 29 | overlapMask = false(numBlinks, 1); 30 | blinkErp = 0; 31 | for b = 1:numBlinks 32 | signalFrames = (blinkFrames(b) + minFrames):(blinkFrames(b) + maxFrames); 33 | inRangeFrames = (blinkFrames(b) + minInFrames):(blinkFrames(b) + maxInFrames); 34 | inRangeFrames(inRangeFrames <= 0) = []; 35 | inRangeFrames(inRangeFrames > numFrames) = []; 36 | if sum(signalFrames <= 0) > 0 || sum(signalFrames > numFrames) > 0 ... 37 | || sum(blinkMask(inRangeFrames)) > 0 38 | overlapMask(b) = true; 39 | 40 | else 41 | signal = data(:, signalFrames); 42 | [blinkPowerRatios(:, b), blinkAmpRatios(:, b)] = ... 43 | getBlinkRatio(signal, tValues, inRange, outRange); 44 | blinkErp = blinkErp + signal; 45 | end 46 | blinkMask(inRangeFrames) = true; 47 | end 48 | numOverlaps = sum(overlapMask); 49 | %% Now remove overlapping blinks 50 | blinkPowerRatios(:, overlapMask) = []; 51 | blinkAmpRatios(:, overlapMask) = []; 52 | numBlinks = size(blinkPowerRatios, 2); 53 | nonBlinkPowerRatios = nan(numChans, numBlinks); 54 | nonBlinkAmpRatios = nan(numChans, numBlinks); 55 | %% Now compute ratios for non-blink sections 56 | b = 0; 57 | nonBlinkErp = 0; 58 | while (b < numBlinks) 59 | nonBlinkFrame = round(numFrames*rand(1, 1)); 60 | signalFrames = (nonBlinkFrame + minFrames):(nonBlinkFrame + maxFrames); 61 | inRangeFrames = (nonBlinkFrame + minInFrames):(nonBlinkFrame + maxInFrames); 62 | inRangeFrames(inRangeFrames <= 0) = []; 63 | inRangeFrames(inRangeFrames > numFrames) = []; 64 | if sum(signalFrames <= 0) > 0 || sum(signalFrames > numFrames) > 0 ... 65 | || sum(blinkMask(inRangeFrames)) > 0 66 | continue; 67 | end 68 | b = b + 1; 69 | signal = data(:, signalFrames); 70 | [nonBlinkPowerRatios(:, b), nonBlinkAmpRatios(:, b)] = ... 71 | getBlinkRatio(signal, tValues, inRange, outRange); 72 | nonBlinkErp = nonBlinkErp + signal; 73 | end 74 | 75 | %% Now compute power ratios of erp (averaged) 76 | [erpBlinkPowerRatio, erpBlinkAmpRatio] = getBlinkRatio(blinkErp, tValues, inRange, outRange); 77 | [nonBlinkErpPowerRatio, nonBlinkErpAmpRatio] = getBlinkRatio(nonBlinkErp, tValues, inRange, outRange); -------------------------------------------------------------------------------- /analysis/runSpectralSampleCorrelations.m: -------------------------------------------------------------------------------- 1 | %% Calculate random spectral sample correlations and display in boxplots 2 | 3 | %% Set up the data 4 | dataDir = 'D:\Research\EEGPipelineProject\dataOut'; 5 | imageDir = 'D:\Research\EEGPipelineProject\dataImages'; 6 | %eegBaseFile = 'basicGuardSession3Subj3202Rec1'; 7 | %eegBaseFile = 'dasSession16Subj131004Rec1'; 8 | eegBaseFile = 'speedControlSession1Subj2015Rec1'; 9 | %eegBaseFile = 'trafficComplexitySession1Subj2002Rec1'; 10 | methodNames = {'LARG', 'MARA', 'ASR_10', 'ASRalt_10', 'ASR_5', 'ASRalt_5'}; 11 | numMethods = length(methodNames); 12 | useLogSpectra = false; 13 | 14 | %% Specify the formats in which to save the data 15 | %figureFormats = {'.png', 'png'; '.fig', 'fig'; '.pdf' 'pdf'; '.eps', 'epsc'}; 16 | figureFormats = {'.png', 'png'}; 17 | figureClose = false; 18 | 19 | %% Read in the files 20 | eegs = cell(numMethods, 1); 21 | for m = 1:numMethods 22 | fileName = [dataDir filesep eegBaseFile '_' methodNames{m} '.set']; 23 | eegs{m} = pop_loadset(fileName); 24 | end 25 | 26 | %% Specify the spectral parameters 27 | sampleLength = 4; 28 | numSpectra = 100; 29 | freqRange = [1, 50]; 30 | freqResolution = 256; 31 | fBins = linspace(freqRange(1), freqRange(2), freqResolution); 32 | numFreqs = length(fBins); 33 | freqBands = [2, 4; 4, 7; 7, 12; 12, 30; 30, 50]; 34 | bandNames = {'Delta'; 'Theta'; 'Alpha'; 'Beta'; 'Gamma'}; 35 | numBands = size(freqBands, 1); 36 | channels = getCommonChannelLabels(); 37 | numChans = length(channels); 38 | 39 | %% Now compute the spectral samples 40 | samples = cell(numMethods, 1); 41 | startingFracs = rand(numChans, numSpectra); % Use same for all methods 42 | for m = 1:numMethods 43 | [samples{m}, freqs] = getRandomSpectralSamples(eegs{m}, startingFracs, ... 44 | sampleLength, channels, numFreqs, freqRange); 45 | end 46 | 47 | %% Now assemble into arrays along with bands 48 | freqMasks = getFrequencyMasks(freqs, freqBands); 49 | spectralSamples = zeros(numChans, numFreqs, numSpectra, numMethods); 50 | bandSamples = zeros(numChans, numBands, numSpectra, numMethods); 51 | for m = 1:numMethods 52 | spectralSamples(:, :, :, m) = samples{m}; 53 | for b = 1:numBands 54 | freqMask = freqMasks(:, b); 55 | for j = 1:numSpectra 56 | bandSamples(:, b, j, m) = mean(spectralSamples(:, freqMask, j, m), 2); 57 | end 58 | end 59 | end 60 | 61 | %% Compute the correlations 62 | if useLogSpectra 63 | spectralSamples = 10*log10(spectralSamples); %#ok<*UNRCH> 64 | bandSamples = 10*log10(bandSamples); 65 | end 66 | numCombos = numMethods*(numMethods - 1)/2; 67 | comboNames = cell(numCombos, 1); 68 | correlations = zeros(numChans, numSpectra, numCombos); 69 | bandCorrelations = zeros(numChans, numBands, numCombos); 70 | k = 0; 71 | for m1 = 1:numMethods - 1 72 | for m2 = m1 + 1:numMethods 73 | k = k + 1; 74 | comboNames{k} = [methodNames{m1} ' vs ' methodNames{m2}]; 75 | for c = 1:numChans 76 | spectra1 = squeeze(spectralSamples(c, :, :, m1)); 77 | spectra2 = squeeze(spectralSamples(c, :, :, m2)); 78 | for j = 1:numSpectra 79 | correlations(c, j, k) = ... 80 | corr(spectra1(:, j), spectra2(:, j)); 81 | end 82 | 83 | bSpectra1 = squeeze(bandSamples(c, :, :, m1))'; 84 | bSpectra2 = squeeze(bandSamples(c, :, :, m2))'; 85 | for b = 1:numBands 86 | bandCorrelations(c, b, k) = ... 87 | corr(bSpectra1(:, b), bSpectra2(:, b)); 88 | end 89 | end 90 | end 91 | end 92 | 93 | %% Now spectral samples as a box plot 94 | boxValues = reshape(correlations, numChans*numSpectra, numCombos); 95 | [boxValues, boxLabels] = reformatBoxPlotData(boxValues, comboNames); 96 | theTitle = {'Spectral sample correlation by method:'; eegBaseFile}; 97 | hFig = makeGroupBoxPlot(boxValues, boxLabels, theTitle, comboNames); 98 | if ~isempty(imageDir) 99 | baseFile = [imageDir filesep 'SpectralSampleCorr_' eegBaseFile]; 100 | saveFigures(hFig, baseFile, figureFormats, figureClose); 101 | end 102 | 103 | %% Now plot the spectral bands as a box plot 104 | hFigs = cell(numBands, 1); 105 | for b = 1:numBands 106 | boxValues = squeeze(correlations(:, b, :)); 107 | [boxValues, boxLabels] = reformatBoxPlotData(boxValues, comboNames); 108 | theTitle = {[bandNames{b} ' band correlation by method:']; eegBaseFile}; 109 | hFigs{b} = makeGroupBoxPlot(boxValues, boxLabels, theTitle, comboNames); 110 | if ~isempty(imageDir) 111 | baseFile = [imageDir filesep 'SpectralSampleCorr_' bandNames{b} '_' eegBaseFile]; 112 | saveFigures(hFigs{b}, baseFile, figureFormats, figureClose); 113 | end 114 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EEG-Pipelines 2 | *Various automated MATLAB pipelines for preprocessing EEG* 3 | 4 | This repository holds several pipelines which were used to benchmark how 5 | much differences in EEG preprocessing affect downstream results as reported 6 | in the following paper: 7 | 8 | > How sensitive are EEG results to preprocessing methods: A Benchmarking study 9 | > Kay A. Robbins, Jonathan Touryan, Tim Mullen, Christian Kothe, Nima Bigdely-Shamlo 10 | > bioRxiv 2020.01.20.913327; doi: https://doi.org/10.1101/2020.01.20.913327 11 | 12 | ## Software requirements and setup 13 | The pipelines use MATLAB and [EEGLAB](https://sccn.ucsd.edu/eeglab/index.php "EEGLAB homepage"). Some of the components use the MATLAB statistics toolbox. Computation of the spectral fingerprints and spectral samples assumes the MATLAB wavelet toolbox. The code was tested using MATLAB 2019a and EEGLAB v2019.1. 14 | 15 | ### Setup of EEGLAB 16 | The following EEGLAB plugins were installed in the default configuration: 17 | 1. clean_rawdata2.1 18 | 2. dipfit 19 | 3. firfilt2.3 20 | 4. ICLabel1.1 21 | 22 | We also installed the following EEGLAB plugins: 23 | 1. blinker1.1.2 24 | 2. MARA1.2 25 | 3. PrepPipeline0.55.3 26 | 27 | By installation, we mean that these plugins are unzipped into the EEGLAB/plugin directory. 28 | You should add them to your MATLAB path by running eeglab, not by trying to 29 | add individual directories to your path. We recommend that you always use the latest versions of the plugins. 30 | 31 | ### Additional setup 32 | You will also need go download eye-catch from https://github.com/bigdelys/eye-catch. 33 | Add this directory to your path as well. For convenience we have provided a script to add the project to your MATLAB path: 34 | 35 | runEEGPipelineProjectPaths 36 | 37 | The script assumes that the EEG-pipelines repository and the eye-catch repository are installed at the same level under a common subdirectory. 38 | 39 | ## Data requirements and preparation 40 | 41 | **EEG:** The EEG recording should be in `EEG.set` file format. The recording should be at least 15 minutes in length in order for the pipelines to work effectively, as several of the components (such as PREP and Blinker) use signal statistics to set thresholds. ASR needs data to calibrate. 42 | 43 | **Channel locations:** The input `EEG.set` file MUST have channel locations included. Furthermore, the 44 | `EEG.chanlocs.type` fields must be set so that the EEG channels have the 45 | type 'EEG'. PREP, Blinker, MARA and other tools 46 | rely on being able to reliably distinguish the EEG channels from auxilliary 47 | channels in order to set their defaults. In our implementation, we remove 48 | the non-EEG channels in the first step. If you do not wish to do this, you 49 | must give channels to be excluded in several steps. To simplify the 50 | implementation, this code removes non-EEG channels as the first step in the 51 | pipeline. 52 | 53 | **Reduction of number of channels:** The pipeline implementation reported 54 | in our publication reduces the number of channels to 64 prior to performing 55 | most of the pipeline. To do this, you must provide a channel mapping. We have 56 | provided one channel mapping (for Biosemi 256-channel headsets into closest 57 | channels in Biosemi 64-channel headsets using standard caps). The pipelines 58 | can be run with more than 64 channels, but we have not evaluated the results 59 | for more channels. 60 | 61 | ## Pipeline details 62 | 63 | The original pipelines were implemented using a code infrastructure 64 | that supports large-scale processing. The codes in this repository were 65 | stripped out of that infrastructure and redesigned to run on a single EEG 66 | recording file in .set file format. The code was tested on MATLAB 67 | 2019a and EEGLAB version eeglab2019_1. The paper used cudaica for some of the computations of ICA. This requires special hardware and setup. That version of the code has not been included in the repository. We assume runica from EEGLAB for the ICA decomposition. 68 | 69 | The pipelines are fully automated, once the parameters at the top of 70 | each script are set. The supported pipelines are: 71 | * LARG uses PREP, ICA, Blinker, and eye-catch to identify and remove artifacts. (Note: this implementation allows subtraction of a residual blink signal, but does not currently implement regression out of blinks during preprocessing.) 72 | * MARA uses PREP, ICA and MARA to identify and remove artifacts. 73 | * ASR uses standard application of ASR from raw data (`clean_artifacts`). 74 | * ASRalt (referred to in the paper and in the diagram below as ASR*) uses PREP for line noise, robust referencing, 75 | and bad channel identification and Hamming windows for filtering (as the 76 | LARG and MARA pipelines do). It then uses only the artifact subspace 77 | removal portion of ASR (`clean_asr`) to remove artifacts. (This alternative is not necessarily recommended, but was developed to 78 | make a closer comparison with LARG and MARA.) 79 | 80 | ![Overview of preprocessing pipelines](./metadata/PipelineOverview.png) 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /pipelines/runASRPipeline.m: -------------------------------------------------------------------------------- 1 | %% This script runs the standard ASR pipeline on the specified EEG.set file 2 | 3 | %% Set up the directories and file names 4 | dataDirIn = 'D:\Research\EEGPipelineProject\dataIn'; 5 | dataDirOut = 'D:\Research\EEGPipelineProject\dataOut'; 6 | eegFile = 'basicGuardSession3Subj3202Rec1.set'; 7 | capType = 'Biosemi256'; 8 | %eegFile = 'dasSession16Subj131004Rec1.set'; 9 | %capType = ''; 10 | %eegFile = 'speedControlSession1Subj2015Rec1.set'; 11 | %capType = ''; 12 | %eegFile = 'trafficComplexitySession1Subj2002Rec1.set'; 13 | %capType = ''; 14 | 15 | %% Set up the parameters for the algorithm 16 | algorithm = 'ASR'; 17 | burstCriterion = 5; 18 | maxSamplingRate = 128; 19 | blinkEventsToAdd = {'maxFrame', 'leftZero', 'rightZero', 'leftBase', ... 20 | 'rightBase', 'leftZeroHalfHeight', 'rightZeroHalfHeight'}; 21 | interpolateBadChannels = true; 22 | 23 | %% Make sure output directory exists 24 | if ~exist(dataDirOut, 'dir') 25 | mkdir(dataDirOut); 26 | end 27 | 28 | %% EEG options: no ICA activations, use double precision, and use a single file 29 | pop_editoptions('option_single', false, 'option_savetwofiles', false, ... 30 | 'option_computeica', false); 31 | 32 | %% Load the EEG file 33 | [thePath, theName, theExt] = fileparts(eegFile); 34 | EEG = pop_loadset([dataDirIn filesep eegFile]); 35 | 36 | %% Make sure it has a dataRecordingUuid for identification downstream 37 | if ~isfield(EEG.etc, 'dataRecordingUuid') || isempty(EEG.etc.dataRecordingUuid) 38 | EEG.etc.dataRecordingUuid = getUuid(); 39 | end 40 | 41 | %% Remove the non-EEG channels 42 | chanMask = true(1, length(EEG.chanlocs)); 43 | for m = 1:length(chanMask) 44 | if ~strcmpi(EEG.chanlocs(m).type, 'EEG') || ... 45 | isempty(EEG.chanlocs(m).theta) || isnan(isempty(EEG.chanlocs(m).theta)) 46 | chanMask(m) = false; 47 | end 48 | end 49 | EEG.chanlocs = EEG.chanlocs(chanMask); 50 | EEG.data = EEG.data(chanMask, :); 51 | EEG.nbchan = sum(chanMask); 52 | 53 | %% If a reduction of channels (only Biosemi256 to Biosemi64 is supported) 54 | if strcmpi(capType, 'Biosemi256') 55 | EEG = convertEEGFromBiosemi256ToB64(EEG, false); 56 | warning('Converting from Biosemi256 to Biosemi64'); 57 | elseif size(EEG.data > 64, 1) 58 | warning('The original LARG pipeline remapped to 64 channels in 10-20 config'); 59 | end 60 | 61 | %% Remove channel meanand resample if necessary 62 | EEG = filterAndResample(EEG, [], maxSamplingRate); 63 | 64 | %% Now run Blinker to insert blink events 65 | fprintf('Detecting and adding blink events...\n'); 66 | params = checkBlinkerDefaults(struct(), getBlinkerDefaults(EEG)); 67 | params.fileName = eegFile; 68 | params.signalNumbers = 1:length(EEG.chanlocs); 69 | params.dumpBlinkerStructures = false; 70 | params.dumpBlinkImages = false; 71 | params.dumpBlinkPositions = false; 72 | params.keepSignals = false; % Make true if combining downstream 73 | params.showMaxDistribution = false; 74 | params.verbose = false; 75 | [EEG, com, blinks, fits, props, stats, params] = pop_blinker(EEG, params); 76 | 77 | %% Save the blinkInfo for downstream analysis 78 | blinkInfo = struct(); 79 | blinkInfo.blinks = blinks; 80 | blinkInfo.blinkFits = fits; 81 | blinkInfo.blinkProperties = props; 82 | blinkInfo.blinkStatistics = stats; 83 | blinkInfo.blinkSignal = []; 84 | blinkInfo.custom = []; 85 | blinkInfo.custom.blinkChannel = ''; 86 | blinkInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 87 | if isnan(blinks.usedSignal) 88 | warning('%s: does not have a blink signal', eegFile); 89 | else 90 | blinkInfo.custom.blinkChannel = EEG.chanlocs(abs(blinks.usedSignal)).labels; 91 | if ~isempty(blinkEventsToAdd) 92 | [EEG, blinkSignal] = addBlinkEvents(EEG, blinks, ... 93 | fits, props, blinkEventsToAdd); 94 | blinkInfo.blinkSignal = blinkSignal; 95 | end 96 | end 97 | 98 | %% Compute channel and global amplitudes before ASR 99 | fprintf('Computing channel amplitudes before ASR ...\n'); 100 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 101 | amplitudeInfo = struct(); 102 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 103 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 104 | for i=1:size(EEGLowpassed.data, 1) 105 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 106 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 107 | end 108 | amplitudeInfo.channelRobustStd = channelRobustStd; 109 | EEG.etc.amplitudeInfoBefore = amplitudeInfo; 110 | clear EEGLowpassed; 111 | 112 | %% Now compute ASR 113 | fprintf('Computing ASR ...\n'); 114 | chanlocsOriginal = EEG.chanlocs; 115 | EEG = clean_artifacts(EEG, 'FlatlineCriterion', 5, ... 116 | 'ChannelCriterion', 0.8, 'LineNoiseCriterion', 4, ... 117 | 'Highpass', [0.25 0.75],'BurstCriterion', burstCriterion, ... 118 | 'WindowCriterion','off','BurstRejection','off'); 119 | 120 | %% Check to make sure that all of the channels are there 121 | if interpolateBadChannels && length(EEG.chanlocs) ~= length(chanlocsOriginal) 122 | oldLabels = {chanlocsOriginal.labels}; 123 | newLabels = {EEG.chanlocs.labels}; 124 | interpolatedChannels = setdiff(oldLabels, newLabels); 125 | fprintf('%s: %d channels interpolated', eegFile, length(interpolatedChannels)); 126 | EEG = eeg_interp(EEG, chanlocsOriginal, 'spherical'); 127 | EEG.etc.interpolatedChannels = interpolatedChannels; 128 | end 129 | 130 | %% Average reference the data 131 | EEG.data = bsxfun(@minus, EEG.data, mean(EEG.data, 2)); 132 | 133 | %% Now compute the amplitude vectors after ASR for future reference 134 | fprintf('Computing channel amplitudes after ASR ...\n'); 135 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 136 | amplitudeInfo = struct(); 137 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 138 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 139 | for i = 1:size(EEGLowpassed.data, 1) 140 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 141 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 142 | end 143 | amplitudeInfo.channelRobustStd = channelRobustStd; 144 | amplitudeInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 145 | EEG.etc.amplitudeInfoAfter = amplitudeInfo; 146 | 147 | %% Now save the files 148 | outName = [dataDirOut filesep theName '_' algorithm '_' num2str(burstCriterion)]; 149 | pop_saveset(EEG, 'filename', [outName '.set'], 'version', '7.3'); 150 | save([outName '_blinkInfo.mat'], 'blinkInfo', '-v7.3'); 151 | -------------------------------------------------------------------------------- /pipelines/runMARAPipeline.m: -------------------------------------------------------------------------------- 1 | %% This script runs the Asr pipeline on available studies to remove artifacts 2 | 3 | %% Set up the general processing parameters 4 | dataDirIn = 'D:\Research\EEGPipelineProject\dataIn'; 5 | dataDirOut = 'D:\Research\EEGPipelineProject\dataOut'; 6 | eegFile = 'basicGuardSession3Subj3202Rec1.set'; 7 | capType = 'Biosemi256'; 8 | %eegFile = 'dasSession16Subj131004Rec1.set'; 9 | %capType = ''; 10 | %eegFile = 'speedControlSession1Subj2015Rec1.set'; 11 | %capType = ''; 12 | %eegFile = 'trafficComplexitySession1Subj2002Rec1.set'; 13 | %capType = ''; 14 | algorithm = 'MARA'; 15 | maxSamplingRate = 128; 16 | highPassFrequency = 1.0; 17 | interpolateBadChannels = true; 18 | excludeChannels = {}; % List channel names of mastoids or other non-scalp channels 19 | blinkEventsToAdd = {'maxFrame', 'leftZero', 'rightZero', 'leftBase', ... 20 | 'rightBase', 'leftZeroHalfHeight', 'rightZeroHalfHeight'}; 21 | icaType = 'runica'; 22 | 23 | %% Make sure output directory exists 24 | if ~exist(dataDirOut, 'dir') 25 | mkdir(dataDirOut); 26 | end 27 | 28 | %% EEG options: no ICA activations, use double precision, and use a single file 29 | pop_editoptions('option_single', false, 'option_savetwofiles', false, ... 30 | 'option_computeica', false); 31 | 32 | %% Load the EEG file 33 | [thePath, theName, theExt] = fileparts(eegFile); 34 | EEG = pop_loadset([dataDirIn filesep eegFile]); 35 | 36 | %% Make sure it has a dataRecordingUuid for identification downstream 37 | if ~isfield(EEG.etc, 'dataRecordingUuid') || isempty(EEG.etc.dataRecordingUuid) 38 | EEG.etc.dataRecordingUuid = getUuid(); 39 | end 40 | 41 | %% Remove the non-EEG channels 42 | chanMask = true(1, length(EEG.chanlocs)); 43 | for m = 1:length(chanMask) 44 | if ~strcmpi(EEG.chanlocs(m).type, 'EEG') || ... 45 | isempty(EEG.chanlocs(m).theta) || isnan(isempty(EEG.chanlocs(m).theta)) 46 | chanMask(m) = false; 47 | end 48 | end 49 | EEG.chanlocs = EEG.chanlocs(chanMask); 50 | EEG.data = EEG.data(chanMask, :); 51 | EEG.nbchan = sum(chanMask); 52 | 53 | %% Perform PREP to remove line noise, robust ref, and interpolate bad channels 54 | chanlocs = EEG.chanlocs; 55 | allEEGLabels = {chanlocs.labels}; 56 | allEEGChannels = 1:length(allEEGLabels); 57 | allScalpChannels = allEEGChannels; 58 | if ~isempty(excludeChannels) 59 | [commonLabels, indAll] = intersect(allEEGChannels, excludeChannels); 60 | allScalpChannels(indAll) = []; 61 | end 62 | params = struct(); 63 | params.referenceChannels = allScalpChannels; 64 | params.evaluationChannels = allScalpChannels; 65 | params.rereferencedChannels = allEEGChannels; 66 | params.detrendChannels = params.rereferencedChannels; 67 | params.lineNoiseChannels = params.rereferencedChannels; 68 | params.name = theName; 69 | params.ignoreBoundaryEvents = true; % ignore boundary events 70 | EEG = prepPipeline(EEG, params); 71 | 72 | %% If a reduction of channels (only Biosemi256 to Biosemi64 is supported) 73 | if strcmpi(capType, 'Biosemi256') 74 | EEG = convertEEGFromBiosemi256ToB64(EEG, false); 75 | warning('Converting from Biosemi256 to Biosemi64'); 76 | elseif size(EEG.data > 64, 1) 77 | warning('The original LARG pipeline remapped to 64 channels in 10-20 config'); 78 | end 79 | 80 | %% Remove channel mean, filter, and resample if necessary 81 | EEG = filterAndResample(EEG, highPassFrequency, maxSamplingRate); 82 | 83 | %% Now run Blinker to insert blink events 84 | params = checkBlinkerDefaults(struct(), getBlinkerDefaults(EEG)); 85 | params.fileName = eegFile; 86 | params.signalNumbers = 1:length(EEG.chanlocs); 87 | params.dumpBlinkerStructures = false; 88 | params.dumpBlinkImages = false; 89 | params.dumpBlinkPositions = false; 90 | params.keepSignals = false; % Make true if combining downstream 91 | params.showMaxDistribution = false; 92 | params.verbose = false; 93 | [EEG, com, blinks, fits, props, stats, params] = pop_blinker(EEG, params); 94 | 95 | %% Save the blinkInfo for downstream analysis 96 | blinkInfo = struct(); 97 | blinkInfo.blinks = blinks; 98 | blinkInfo.blinkFits = fits; 99 | blinkInfo.blinkProperties = props; 100 | blinkInfo.blinkStatistics = stats; 101 | blinkInfo.blinkSignal = []; 102 | blinkInfo.custom = []; 103 | blinkInfo.custom.blinkChannel = ''; 104 | blinkInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 105 | if isnan(blinks.usedSignal) 106 | warning('%s: does not have a blink signal', filename); 107 | else 108 | blinkInfo.custom.blinkChannel = EEG.chanlocs(abs(blinks.usedSignal)).labels; 109 | if ~isempty(blinkEventsToAdd) 110 | [EEG, blinkSignal] = addBlinkEvents(EEG, blinks, ... 111 | fits, props, blinkEventsToAdd); 112 | blinkInfo.blinkSignal = blinkSignal; 113 | end 114 | end 115 | 116 | %% Compute channel and global amplitudes before 117 | fprintf('Computing channel amplitudes before MARA ...\n'); 118 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 119 | amplitudeInfo = struct(); 120 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 121 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 122 | for i=1:size(EEGLowpassed.data, 1) 123 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 124 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 125 | end 126 | amplitudeInfo.channelRobustStd = channelRobustStd; 127 | EEG.etc.amplitudeInfoBefore = amplitudeInfo; 128 | clear EEGLowpassed; 129 | 130 | %% Remove eye artifact and blink activity from time-domain using MARA 131 | fprintf('Removing artifacts using MARA....'); 132 | [EEG, removalInfo] = removeArtifactsMARA(EEG, icaType); 133 | EEG.icaact = []; 134 | 135 | %% Now compute the amplitude vectors after MARA 136 | fprintf('Computing channel amplitudes after MARA ...\n'); 137 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 138 | amplitudeInfo = struct(); 139 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 140 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 141 | for i = 1:size(EEGLowpassed.data, 1) 142 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 143 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 144 | end 145 | amplitudeInfo.channelRobustStd = channelRobustStd; 146 | amplitudeInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 147 | EEG.etc.amplitudeInfoAfter = amplitudeInfo; 148 | 149 | %% Now save the files 150 | outName = [dataDirOut filesep theName '_' algorithm]; 151 | pop_saveset(EEG, 'filename', [outName '.set'], 'version', '7.3'); 152 | save([outName '_blinkInfo.mat'], 'blinkInfo', '-v7.3'); 153 | save([outName '_artifactRemovalInfo.mat'], 'removalInfo', '-v7.3'); -------------------------------------------------------------------------------- /pipelines/runASRaltPipeline.m: -------------------------------------------------------------------------------- 1 | %% This script runs the underlying subspace removal mechanism of ASR to remove 2 | 3 | %% Set up the general processing parameters 4 | dataDirIn = 'D:\Research\EEGPipelineProject\dataIn'; 5 | dataDirOut = 'D:\Research\EEGPipelineProject\dataOut'; 6 | eegFile = 'basicGuardSession3Subj3202Rec1.set'; 7 | capType = 'Biosemi256'; 8 | %eegFile = 'dasSession16Subj131004Rec1.set'; 9 | %capType = ''; 10 | %eegFile = 'speedControlSession1Subj2015Rec1.set'; 11 | %capType = ''; 12 | %eegFile = 'trafficComplexitySession1Subj2002Rec1.set'; 13 | %capType = ''; 14 | algorithm = 'ASRalt'; 15 | maxSamplingRate = 128; 16 | highPassFrequency = 1.5; 17 | interpolateBadChannels = true; 18 | excludeChannels = {}; % List channel names of mastoids or other non-scalp channels 19 | blinkEventsToAdd = {'maxFrame', 'leftZero', 'rightZero', 'leftBase', ... 20 | 'rightBase', 'leftZeroHalfHeight', 'rightZeroHalfHeight'}; 21 | 22 | %% Parameter settings specific for ASR 23 | burstCriterion = 10; 24 | 25 | %% Make sure output directory exists 26 | if ~exist(dataDirOut, 'dir') 27 | mkdir(dataDirOut); 28 | end 29 | 30 | %% EEG options: no ICA activations, use double precision, and use a single file 31 | pop_editoptions('option_single', false, 'option_savetwofiles', false, ... 32 | 'option_computeica', false); 33 | 34 | %% Load the EEG file 35 | [thePath, theName, theExt] = fileparts(eegFile); 36 | EEG = pop_loadset([dataDirIn filesep eegFile]); 37 | 38 | %% Make sure it has a dataRecordingUuid for identification downstream 39 | if ~isfield(EEG.etc, 'dataRecordingUuid') || isempty(EEG.etc.dataRecordingUuid) 40 | EEG.etc.dataRecordingUuid = getUuid(); 41 | end 42 | 43 | %% Remove the non-EEG channels 44 | chanMask = true(1, length(EEG.chanlocs)); 45 | for m = 1:length(chanMask) 46 | if ~strcmpi(EEG.chanlocs(m).type, 'EEG') || ... 47 | isempty(EEG.chanlocs(m).theta) || isnan(isempty(EEG.chanlocs(m).theta)) 48 | chanMask(m) = false; 49 | end 50 | end 51 | EEG.chanlocs = EEG.chanlocs(chanMask); 52 | EEG.data = EEG.data(chanMask, :); 53 | EEG.nbchan = sum(chanMask); 54 | 55 | %% Perform PREP to remove line noise, robust ref, and interpolate bad channels 56 | chanlocs = EEG.chanlocs; 57 | allEEGLabels = {chanlocs.labels}; 58 | allEEGChannels = 1:length(allEEGLabels); 59 | allScalpChannels = allEEGChannels; 60 | if ~isempty(excludeChannels) 61 | [commonLabels, indAll] = intersect(allEEGChannels, excludeChannels); 62 | allScalpChannels(indAll) = []; 63 | end 64 | params = struct(); 65 | paramsInit.detrendType = 'high pass'; 66 | paramsInit.detrendCutoff = 1; 67 | paramsInit.referenceType = 'robust'; 68 | paramsInit.meanEstimateType = 'median'; 69 | paramsInit.interpolationOrder = 'post-reference'; 70 | paramsInit.removeInterpolatedChannels = false; 71 | paramsInit.keepFiltered = false; 72 | params.referenceChannels = allScalpChannels; 73 | params.evaluationChannels = allScalpChannels; 74 | params.rereferencedChannels = allEEGChannels; 75 | params.detrendChannels = params.rereferencedChannels; 76 | params.lineNoiseChannels = params.rereferencedChannels; 77 | params.name = theName; 78 | params.ignoreBoundaryEvents = true; % ignore boundary events 79 | EEG = prepPipeline(EEG, params); 80 | 81 | %% If a reduction of channels (only Biosemi256 to Biosemi64 is supported) 82 | if strcmpi(capType, 'Biosemi256') 83 | EEG = convertEEGFromBiosemi256ToB64(EEG, false); 84 | warning('Converting from Biosemi256 to Biosemi64'); 85 | elseif size(EEG.data > 64, 1) 86 | warning('The original LARG pipeline remapped to 64 channels in 10-20 config'); 87 | end 88 | 89 | %% Remove channel mean, filter, and resample if necessary 90 | EEG = filterAndResample(EEG, highPassFrequency, maxSamplingRate); 91 | 92 | %% Now run Blinker to insert blink events 93 | params = checkBlinkerDefaults(struct(), getBlinkerDefaults(EEG)); 94 | params.fileName = eegFile; 95 | params.signalNumbers = 1:length(EEG.chanlocs); 96 | params.dumpBlinkerStructures = false; 97 | params.dumpBlinkImages = false; 98 | params.dumpBlinkPositions = false; 99 | params.keepSignals = false; % Make true if combining downstream 100 | params.showMaxDistribution = false; 101 | params.verbose = false; 102 | [EEG, com, blinks, fits, props, stats, params] = pop_blinker(EEG, params); 103 | 104 | %% Save the blinkInfo for downstream analysis 105 | blinkInfo = struct(); 106 | blinkInfo.blinks = blinks; 107 | blinkInfo.blinkFits = fits; 108 | blinkInfo.blinkProperties = props; 109 | blinkInfo.blinkStatistics = stats; 110 | blinkInfo.blinkSignal = []; 111 | blinkInfo.custom = []; 112 | blinkInfo.custom.blinkChannel = ''; 113 | blinkInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 114 | if isnan(blinks.usedSignal) 115 | warning('%s: does not have a blink signal', filename); 116 | else 117 | blinkInfo.custom.blinkChannel = EEG.chanlocs(abs(blinks.usedSignal)).labels; 118 | if ~isempty(blinkEventsToAdd) 119 | [EEG, blinkSignal] = addBlinkEvents(EEG, blinks, ... 120 | fits, props, blinkEventsToAdd); 121 | blinkInfo.blinkSignal = blinkSignal; 122 | end 123 | end 124 | 125 | %% Compute channel and global amplitudes before 126 | fprintf('Computing channel amplitudes before ASR ...\n'); 127 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 128 | amplitudeInfoBefore.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 129 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 130 | for i=1:size(EEGLowpassed.data, 1) 131 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 132 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 133 | end 134 | amplitudeInfoBefore.channelRobustStd = channelRobustStd; 135 | EEG.etc.amplitudeInfoBefore = amplitudeInfoBefore; 136 | clear EEGLowPassed; 137 | 138 | %% Use subspace removal to remove the artifacts 139 | EEG = clean_asr(EEG, burstCriterion); 140 | 141 | %% Now compute the amplitude vectors after ASR 142 | fprintf('Computing channel amplitudes after LARG ...\n'); 143 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 144 | amplitudeInfo = struct(); 145 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 146 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 147 | for i = 1:size(EEGLowpassed.data, 1) 148 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 149 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 150 | end 151 | amplitudeInfo.channelRobustStd = channelRobustStd; 152 | amplitudeInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 153 | EEG.etc.amplitudeInfoAfter = amplitudeInfo; 154 | clear EEGLowpassed; 155 | 156 | %% Now save the files 157 | outName = [dataDirOut filesep theName '_' algorithm '_' num2str(burstCriterion)]; 158 | pop_saveset(EEG, 'filename', [outName '.set'], 'version', '7.3'); 159 | save([outName '_blinkInfo.mat'], 'blinkInfo', '-v7.3'); 160 | -------------------------------------------------------------------------------- /pipelines/runLARGipeline.m: -------------------------------------------------------------------------------- 1 | %% This script runs the LARG pipeline on an EEG.set file. The full blink 2 | % regression is not available at this time. 3 | 4 | %% Set up the directories and file names 5 | dataDirIn = 'D:\Research\EEGPipelineProject\dataIn'; 6 | dataDirOut = 'D:\Research\EEGPipelineProject\dataOut'; 7 | eegFile = 'basicGuardSession3Subj3202Rec1.set'; 8 | capType = 'Biosemi256'; 9 | %eegFile = 'dasSession16Subj131004Rec1.set'; 10 | %capType = ''; 11 | %eegFile = 'speedControlSession1Subj2015Rec1.set'; 12 | %capType = ''; 13 | %eegFile = 'trafficComplexitySession1Subj2002Rec1.set'; 14 | %capType = ''; 15 | 16 | %% Set up the parameters for the algorithm 17 | algorithm = 'LARG'; 18 | maxSamplingRate = 128; 19 | highPassFrequency = 1.0; 20 | 21 | interpolateBadChannels = true; 22 | excludeChannels = {}; % List channel names of mastoids or other non-scalp channels 23 | blinkEventsToAdd = {'maxFrame', 'leftZero', 'rightZero', 'leftBase', ... 24 | 'rightBase', 'leftZeroHalfHeight', 'rightZeroHalfHeight'}; 25 | 26 | %% Parameter settings specific for LARG 27 | icaType = 'runica'; % If 'none', no eye-catch is performed. 28 | regressBlinkEvents = false; 29 | regressBlinkSignal = false; 30 | 31 | %% Make sure output directory exists 32 | if ~exist(dataDirOut, 'dir') 33 | mkdir(dataDirOut); 34 | end 35 | 36 | %% EEG options: no ICA activations, use double precision, and use a single file 37 | pop_editoptions('option_single', false, 'option_savetwofiles', false, ... 38 | 'option_computeica', false); 39 | 40 | %% Load the EEG file 41 | [thePath, theName, theExt] = fileparts(eegFile); 42 | EEG = pop_loadset([dataDirIn filesep eegFile]); 43 | 44 | %% Make sure it has a dataRecordingUuid for identification downstream 45 | if ~isfield(EEG.etc, 'dataRecordingUuid') || isempty(EEG.etc.dataRecordingUuid) 46 | EEG.etc.dataRecordingUuid = getUuid(); 47 | end 48 | 49 | %% Remove the non-EEG channels 50 | chanMask = true(1, length(EEG.chanlocs)); 51 | for m = 1:length(chanMask) 52 | if ~strcmpi(EEG.chanlocs(m).type, 'EEG') || ... 53 | isempty(EEG.chanlocs(m).theta) || isnan(isempty(EEG.chanlocs(m).theta)) 54 | chanMask(m) = false; 55 | end 56 | end 57 | EEG.chanlocs = EEG.chanlocs(chanMask); 58 | EEG.data = EEG.data(chanMask, :); 59 | EEG.nbchan = sum(chanMask); 60 | 61 | %% Perform PREP to remove line noise, robust ref, and interpolate bad channels 62 | chanlocs = EEG.chanlocs; 63 | allEEGLabels = {chanlocs.labels}; 64 | allEEGChannels = 1:length(allEEGLabels); 65 | allScalpChannels = allEEGChannels; 66 | if ~isempty(excludeChannels) 67 | [commonLabels, indAll] = intersect(allEEGChannels, excludeChannels); 68 | allScalpChannels(indAll) = []; 69 | end 70 | params = struct(); 71 | params.referenceChannels = allScalpChannels; 72 | params.evaluationChannels = allScalpChannels; 73 | params.rereferencedChannels = allEEGChannels; 74 | params.detrendChannels = params.rereferencedChannels; 75 | params.lineNoiseChannels = params.rereferencedChannels; 76 | params.name = theName; 77 | params.ignoreBoundaryEvents = true; % ignore boundary events 78 | EEG = prepPipeline(EEG, params); 79 | 80 | %% If a reduction of channels (only Biosemi256 to Biosemi64 is supported) 81 | if strcmpi(capType, 'Biosemi256') 82 | EEG = convertEEGFromBiosemi256ToB64(EEG, false); 83 | warning('Converting from Biosemi256 to Biosemi64'); 84 | elseif size(EEG.data > 64, 1) 85 | warning('The original LARG pipeline remapped to 64 channels in 10-20 config'); 86 | end 87 | 88 | %% Remove channel mean, filter, and resample if necessary 89 | EEG = filterAndResample(EEG, highPassFrequency, maxSamplingRate); 90 | 91 | %% Now run Blinker to insert blink events 92 | params = checkBlinkerDefaults(struct(), getBlinkerDefaults(EEG)); 93 | params.fileName = eegFile; 94 | params.signalNumbers = 1:length(EEG.chanlocs); 95 | params.dumpBlinkerStructures = false; 96 | params.dumpBlinkImages = false; 97 | params.dumpBlinkPositions = false; 98 | params.keepSignals = false; % Make true if combining downstream 99 | params.showMaxDistribution = false; 100 | params.verbose = false; 101 | [EEG, com, blinks, fits, props, stats, params] = pop_blinker(EEG, params); 102 | 103 | %% Save the blinkInfo for downstream analysis 104 | blinkInfo = struct(); 105 | blinkInfo.blinks = blinks; 106 | blinkInfo.blinkFits = fits; 107 | blinkInfo.blinkProperties = props; 108 | blinkInfo.blinkStatistics = stats; 109 | blinkInfo.blinkSignal = []; 110 | blinkInfo.custom = []; 111 | blinkInfo.custom.blinkChannel = ''; 112 | blinkInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 113 | if isnan(blinks.usedSignal) 114 | warning('%s: does not have a blink signal', filename); 115 | else 116 | blinkInfo.custom.blinkChannel = EEG.chanlocs(abs(blinks.usedSignal)).labels; 117 | if ~isempty(blinkEventsToAdd) 118 | [EEG, blinkSignal] = addBlinkEvents(EEG, blinks, ... 119 | fits, props, blinkEventsToAdd); 120 | blinkInfo.blinkSignal = blinkSignal; 121 | end 122 | end 123 | 124 | %% Compute channel and global amplitudes before 125 | fprintf('Computing channel amplitudes before LARG ...\n'); 126 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 127 | amplitudeInfoBefore.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 128 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 129 | for i=1:size(EEGLowpassed.data, 1) 130 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 131 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 132 | end 133 | amplitudeInfoBefore.channelRobustStd = channelRobustStd; 134 | EEG.etc.amplitudeInfoBefore = amplitudeInfoBefore; 135 | clear EEGLowPassed; 136 | 137 | %% Remove eye artifact and blink activity from time-domain (uses EyeCatch) 138 | [EEG, removalInfo] = removeEyeArtifactsLARG(EEG, blinkInfo, ... 139 | icaType, regressBlinkEvents, regressBlinkSignal); 140 | EEG.icaact = []; 141 | 142 | %% Now compute the amplitude vectors after LARG 143 | fprintf('Computing channel amplitudes after LARG ...\n'); 144 | EEGLowpassed = pop_eegfiltnew(EEG, [], 20); % lowpassed at 20 Hz 145 | amplitudeInfo = struct(); 146 | amplitudeInfo.allDataRobustStd = stdFromMad(vec(EEGLowpassed.data)); 147 | channelRobustStd = zeros(size(EEGLowpassed.data, 1), 1); 148 | for i = 1:size(EEGLowpassed.data, 1) 149 | channelRobustStd(i) = median(abs(EEGLowpassed.data(i,:)' ... 150 | - median(EEGLowpassed.data(i,:), 2))) * 1.4826; 151 | end 152 | amplitudeInfo.channelRobustStd = channelRobustStd; 153 | amplitudeInfo.custom.sourceDataRecordingId = EEG.etc.dataRecordingUuid; 154 | EEG.etc.amplitudeInfoAfter = amplitudeInfo; 155 | clear EEGLowpassed; 156 | 157 | %% Now save the files 158 | outName = [dataDirOut filesep theName '_' algorithm]; 159 | pop_saveset(EEG, 'filename', [outName '.set'], 'version', '7.3'); 160 | save([outName '_blinkInfo.mat'], 'blinkInfo', '-v7.3'); 161 | save([outName '_EyeRemovalInfo.mat'], 'removalInfo', '-v7.3'); -------------------------------------------------------------------------------- /analysis/runEventBlinkRatios.m: -------------------------------------------------------------------------------- 1 | %% Calculate blink event ratios for a dataset for different preprocessing methods 2 | 3 | %% Set up the data 4 | dataDir = 'D:\Research\EEGPipelineProject\dataOut'; 5 | imageDir = 'D:\Research\EEGPipelineProject\dataImages'; 6 | eegBaseFile = 'basicGuardSession3Subj3202Rec1'; 7 | %eegBaseFile = 'dasSession16Subj131004Rec1'; 8 | %eegBaseFile = 'speedControlSession1Subj2015Rec1'; 9 | %eegBaseFile = 'trafficComplexitySession1Subj2002Rec1'; 10 | methodNames = {'LARG', 'MARA', 'ASR_10', 'ASRalt_10', 'ASR_5', 'ASRalt_5'}; 11 | numMethods = length(methodNames); 12 | useLogSpectra = false; 13 | inRange = [-1, 1]; 14 | outRange = [-2, -1; 1, 2]; 15 | 16 | %% Specify the formats in which to save the data 17 | %figureFormats = {'.png', 'png'; '.fig', 'fig'; '.pdf' 'pdf'; '.eps', 'epsc'}; 18 | figureFormats = {'.png', 'png'}; 19 | figureClose = false; 20 | 21 | %% Make sure that image directory exists 22 | if ~isempty(imageDir) && ~exist(imageDir, 'dir') 23 | mkdir(imageDir); 24 | end 25 | 26 | %% Read in the files 27 | eegs = cell(numMethods, 1); 28 | for m = 1:numMethods 29 | fileName = [dataDir filesep eegBaseFile '_' methodNames{m} '.set']; 30 | eegs{m} = pop_loadset(fileName); 31 | end 32 | channelLabels = getCommonChannelLabels(); 33 | numChans = length(channelLabels); 34 | 35 | %% Calculate the event blink and non-blink ratios 36 | blinkPowerRatios = cell(numMethods, 1); 37 | nonBlinkPowerRatios = cell(numMethods, 1); 38 | blinkAmpRatios = cell(numMethods, 1); 39 | nonBlinkAmpRatios = cell(numMethods, 1); 40 | erpBlinkPowerRatio = cell(numMethods, 1); 41 | nonBlinkErpPowerRatio = cell(numMethods, 1); 42 | erpBlinkAmpRatio = cell(numMethods, 1); 43 | nonBlinkErpAmpRatio = cell(numMethods, 1); 44 | for m = 1:numMethods 45 | [EEG, missing, selectMask] = selectEEGChannels(eegs{m}, channelLabels); 46 | if ~isempty(missing) 47 | warning('EEG is missing channels %s\n-- can not compute spectral fingerprints', ... 48 | getListString(missing, ',')); 49 | continue; 50 | end 51 | 52 | %% Now calculate the ratios 53 | [blinkPowerRatios{m}, nonBlinkPowerRatios{m}, blinkAmpRatios{m}, ... 54 | nonBlinkAmpRatios{m}, numBlinks, numOverlaps,... 55 | erpBlinkPowerRatio{m}, nonBlinkErpPowerRatio{m}, ... 56 | erpBlinkAmpRatio{m}, nonBlinkErpAmpRatio{m}] = ... 57 | getEEGBlinkRatios(EEG, inRange, outRange); 58 | end 59 | 60 | %% Plot box plots of individual blink power ratios 61 | dataLimits = [0, 5]; 62 | theTitle = {'Blink power ratio for preprocessing methods'; eegBaseFile}; 63 | theType = 'Power ratio'; 64 | f = []; 65 | hFig1 = makeBlinkRatioBoxPlot(blinkPowerRatios, methodNames, ... 66 | channelLabels, theTitle, theType, dataLimits, f); 67 | if ~isempty(imageDir) 68 | baseFile = [imageDir filesep 'blinkPowerRatios_' eegBaseFile]; 69 | saveFigures(hFig1, baseFile, figureFormats, figureClose); 70 | end 71 | 72 | %% Plot box plots of individual non-blink power ratios 73 | dataLimits = [0, 5]; 74 | theTitle = {'Non blink power ratio for preprocessing methods'; eegBaseFile}; 75 | theType = 'Power ratio'; 76 | hFig2 = makeBlinkRatioBoxPlot(nonBlinkPowerRatios, methodNames, ... 77 | channelLabels, theTitle, theType, dataLimits, f); 78 | if ~isempty(imageDir) 79 | baseFile = [imageDir filesep 'nonBlinkPowerRatios_' eegBaseFile]; 80 | saveFigures(hFig2, baseFile, figureFormats, figureClose); 81 | end 82 | 83 | %% Plot box plots on blink amplitude ratios 84 | dataLimits = [0, 5]; 85 | theTitle = {'Blink amplitude ratio for preprocessing methods'; eegBaseFile}; 86 | theType = 'Amplitude ratio'; 87 | f = []; 88 | hFig3 = makeBlinkRatioBoxPlot(blinkAmpRatios, methodNames, ... 89 | channelLabels, theTitle, theType, dataLimits, f); 90 | if ~isempty(imageDir) 91 | baseFile = [imageDir filesep 'blinkAmplitudeRatios_' eegBaseFile]; 92 | saveFigures(hFig3, baseFile, figureFormats, figureClose); 93 | end 94 | 95 | %% Plot box plots of non-blink amplitude ratios 96 | dataLimits = [0, 5]; 97 | theTitle = {'Non blink amplitude ratio for preprocessing methods'; eegBaseFile}; 98 | theType = 'Amplitude ratio'; 99 | hFig4 = makeBlinkRatioBoxPlot(nonBlinkAmpRatios, methodNames, ... 100 | channelLabels, theTitle, theType, dataLimits, f); 101 | if ~isempty(imageDir) 102 | baseFile = [imageDir filesep 'nonBlinkAmplitudeRatios_' eegBaseFile]; 103 | saveFigures(hFig4, baseFile, figureFormats, figureClose); 104 | end 105 | 106 | %% Plot box plots of sqrt blink power ratio 107 | dataLimits = [0, 5]; 108 | theTitle = {'Blink power ratio for preprocessing methods'; eegBaseFile}; 109 | theType = 'Sqrt power ratio'; 110 | f = @sqrt; 111 | hFig5 = makeBlinkRatioBoxPlot(blinkPowerRatios, methodNames, ... 112 | channelLabels, theTitle, theType, dataLimits, f); 113 | if ~isempty(imageDir) 114 | baseFile = [imageDir filesep 'blinkSqrtPowerRatios_' eegBaseFile]; 115 | saveFigures(hFig5, baseFile, figureFormats, figureClose); 116 | end 117 | 118 | %% Plot box plots of sqrt non blink power ratio 119 | dataLimits = [0, 5]; 120 | theTitle = {'Non blink power ratio for preprocessing methods'; eegBaseFile}; 121 | theType = 'Sqrt power ratio'; 122 | hFig6 = makeBlinkRatioBoxPlot(nonBlinkPowerRatios, methodNames, ... 123 | channelLabels, theTitle, theType, dataLimits, f); 124 | if ~isempty(imageDir) 125 | baseFile = [imageDir filesep 'nonBlinkSqrtPowerRatios_' eegBaseFile]; 126 | saveFigures(hFig6, baseFile, figureFormats, figureClose); 127 | end 128 | 129 | %% Now plot boxplots of ERP blink power ratios 130 | dataLimits = [0, 20]; 131 | theTitle = {'Blink ERP power ratio for preprocessing methods'; eegBaseFile}; 132 | theType = 'ERP power ratio'; 133 | f = []; 134 | hFig7 = makeBlinkRatioBoxPlot(erpBlinkPowerRatio, methodNames, ... 135 | channelLabels, theTitle, theType, dataLimits, f); 136 | if ~isempty(imageDir) 137 | baseFile = [imageDir filesep 'blinkERPPowerRatios_' eegBaseFile]; 138 | saveFigures(hFig7, baseFile, figureFormats, figureClose); 139 | end 140 | 141 | %% Plot box plots of non-blink ERP power ratios 142 | dataLimits = [0, 20]; 143 | theTitle = {'Non blink ERP power ratio for preprocessing methods'; eegBaseFile}; 144 | theType = 'ERP power ratio'; 145 | hFig8 = makeBlinkRatioBoxPlot(nonBlinkErpPowerRatio, methodNames, ... 146 | channelLabels, theTitle, theType, dataLimits, f); 147 | if ~isempty(imageDir) 148 | baseFile = [imageDir filesep 'nonBlinkERPPowerRatios_' eegBaseFile]; 149 | saveFigures(hFig8, baseFile, figureFormats, figureClose); 150 | end 151 | 152 | %% Plot box plots of blink ERP amplitude ratios 153 | dataLimits = [0, 5]; 154 | theTitle = {'Blink ERP amplitude ratio for preprocessing methods'; eegBaseFile}; 155 | theType = 'Amplitude ratio'; 156 | f = []; 157 | hFig9 = makeBlinkRatioBoxPlot(erpBlinkAmpRatio, methodNames, ... 158 | channelLabels, theTitle, theType, dataLimits, f); 159 | if ~isempty(imageDir) 160 | baseFile = [imageDir filesep 'blinkERPAmplitudeRatios_' eegBaseFile]; 161 | saveFigures(hFig9, baseFile, figureFormats, figureClose); 162 | end 163 | 164 | %% Plot box plots of nonblink ERP amplitude ratios 165 | dataLimits = [0, 5]; 166 | theTitle = {'Non blink ERP amplitude ratio for preprocessing methods'; eegBaseFile}; 167 | theType = 'Amplitude ratio'; 168 | hFig10 = makeBlinkRatioBoxPlot(nonBlinkErpAmpRatio, methodNames, ... 169 | channelLabels, theTitle, theType, dataLimits, f); 170 | if ~isempty(imageDir) 171 | baseFile = [imageDir filesep 'nonBlinkERPAmplitudeRatios_' eegBaseFile]; 172 | saveFigures(hFig10, baseFile, figureFormats, figureClose); 173 | end 174 | -------------------------------------------------------------------------------- /utilities/cleanWindows.m: -------------------------------------------------------------------------------- 1 | function [signal, sample_mask] = cleanWindows(signal,max_bad_channels,zthresholds,window_len,censor_cutoffs,censor_dropouts,censor_dropout_maxreject) 2 | % Remove periods with abnormally high-power content from continuous data. 3 | % [Signal,Mask] = clean_windows(Signal,MaxBadChannels,BadTolerances,) 4 | % 5 | % This function cuts segments from the data which contain high-power artifacts. Specifically, 6 | % only windows are retained which have less than a certain fraction of "bad" channels, where a channel 7 | % is bad in a window if its power is above or below a given upper/lower threshold (in standard 8 | % deviations from a robust estimate of the EEG power distribution in the channel). 9 | % 10 | % In: 11 | % Signal : Continuous data set, assumed to be appropriately high-passed (e.g. >1Hz or 12 | % 0.5Hz - 2.0Hz transition band) 13 | % 14 | % MaxBadChannels : The maximum number or fraction of bad channels that a retained window may still 15 | % contain (more than this and it is removed). Reasonable range is 0.05 (very clean 16 | % output) to 0.3 (very lax cleaning of only coarse artifacts). Default: 0.1. 17 | % 18 | % PowerTolerances: The minimum and maximum standard deviations within which the power of a channel 19 | % must lie (relative to a robust estimate of the clean EEG power distribution in 20 | % the channel) for it to be considered "not bad". Default: [-5 5]. 21 | % 22 | % 23 | % The following are "specialty" parameters that usually do not have to be tuned. If you can't get 24 | % the function to do what you want, you might consider adapting these to your data. 25 | % 26 | % WindowLength : Window length that is used to check the data for artifact content. This is 27 | % ideally as long as the expected time scale of the artifacts but not shorter 28 | % than half a cycle of the high-pass filter that was used. Default: 1. 29 | % 30 | % CensorCutoffs : These are robust data censoring cutoffs, in robust z scores per channel, for 31 | % estimating the EEG power distribution per channel. This does not need to be 32 | % tuned unless the artifact content is very unusual, e.g. lots of flat channel 33 | % drop-outs or a very uncommon type of noise. Default: [-5 4]. 34 | % 35 | % CensorDropouts : This is an extra censoring cutoff, in robust z scores across all channels, 36 | % to handle data where the majority of samples in a channel (but not all) drop out. 37 | % Default: -3. 38 | % 39 | % CensorDropoutMaxReject : This is the maximum fraction of windows rejected for which the dropout 40 | % censoring is considered applicable (otherwise the criterion is ignored, 41 | % e.g. for very low-amplitude channels). Default: 0.75 42 | % 43 | % Out: 44 | % Signal : data set with bad time periods removed. 45 | % 46 | % Mask : mask of retained samples (logical array) 47 | % 48 | % Christian Kothe, Swartz Center for Computational Neuroscience, UCSD 49 | % 2010-07-06 50 | 51 | warning off stats:gevfit:IterLimit 52 | warning off stats:gamfit:IterLimit 53 | 54 | if ~exist('max_bad_channels','var') || isempty(max_bad_channels) max_bad_channels = 0.15; end 55 | if ~exist('zthresholds','var') || isempty(zthresholds) zthresholds = [-5 5]; end 56 | if ~exist('window_len','var') || isempty(window_len) window_len = 1; end 57 | if ~exist('censor_cutoffs','var') || isempty(censor_cutoffs) censor_cutoffs = [-5 4]; end 58 | if ~exist('censor_dropouts','var') || isempty(censor_dropouts) censor_dropouts = -3; end 59 | if ~exist('censor_dropout_maxreject','var') || isempty(censor_dropout_maxreject) censor_dropout_maxreject = 0.75; end 60 | 61 | coeff = [0.1314,-0.2877,-0.0104]; % coefficients that model the degrees of freedom in the EEG covariance chi^2 distribution 62 | % depending on the window length over which the covariances are calculated (these numbers 63 | % come from a fit to clean EEG) 64 | [C,S] = size(signal.data); 65 | N = window_len*signal.srate; 66 | wnd = 0:N-1; 67 | offsets = round(1:N/2:S-N); 68 | W = length(offsets); 69 | 70 | if max_bad_channels < 1 71 | max_bad_channels = round(max_bad_channels*C); end 72 | 73 | % compute windowed power across all channels 74 | wp = zeros(C,W); 75 | for c = 1:C 76 | for o=1:W 77 | x = signal.data(c,offsets(o) + wnd); 78 | wp(c,o) = sqrt(sum(x.*x)/N); 79 | end 80 | end 81 | 82 | % find the median and median absolute deviation for that 83 | mu_all = median(wp(:)); 84 | st_all = mad(wp(:),1); 85 | 86 | wz = zeros(C,W); 87 | for c = 1:C 88 | tmp = wp(c,:); 89 | % censor values that are likely temporary channel drop-outs using information from all channels 90 | tmp = tmp(tmp>(mu_all+st_all*censor_dropouts)); 91 | if length(tmp)/W < (1-censor_dropout_maxreject) 92 | % if some channel is extremely low-amplitude, we cannot apply this criterion 93 | tmp = wp(c,:); 94 | end 95 | % estimate the robust mean and std. deviation for this channel 96 | mu_robust = median(tmp); 97 | st_robust = mad(tmp,1); 98 | % censor values that are extreme outliers for this particular channel 99 | tmp = tmp(tmp<(mu_robust+st_robust*censor_cutoffs(2)) & tmp>(mu_robust+st_robust*censor_cutoffs(1))); 100 | % fit a generalized extreme value distribution and a gamma distribution and compute the modes 101 | params_gam = gamfit(tmp); 102 | mode_gam = (params_gam(1) - 1)*params_gam(2); 103 | params_gev = gevfit(tmp); 104 | if params_gev(1) ~= 0 105 | mode_gev = params_gev(3) + params_gev(2)*((1+params_gev(1))^-params_gev(1) - 1)/params_gev(1); 106 | else 107 | mode_gev = params_gev(3); 108 | end 109 | % we take the smaller mode as the mean of the underlying EEG distribution (assuming that it is a Gaussian component of that distribution) 110 | % this is because the GEV allows right-skewed fits, which are non-physiological (in this case we fall back to the Gamma fit) 111 | mu_fine = min([mode_gam,mode_gev]); 112 | % we estimate the standard deviation dependent on the pure EEG mode -- under the assumption of a scaled Chi^2 distribution of EEG variance 113 | % (this depends parametrically on the window length, using a fit on clean reference EEG data) 114 | st_fine = mu_fine .* (coeff(1)*window_len^coeff(2) + coeff(3)); 115 | % calculate the channel power in z scores relative to the estimated underlying EEG distribution 116 | wz(c,:) = (wp(c,:) - mu_fine)/st_fine; 117 | end 118 | 119 | % sort z scores into quantiles 120 | swz = sort(wz); 121 | 122 | % determine which windows to retain 123 | retained_mask = true(1,W); 124 | if max(zthresholds)>0 125 | retained_mask(swz(end-max_bad_channels,:) > max(zthresholds)) = false; end 126 | if min(zthresholds)<0 127 | retained_mask(swz(1+max_bad_channels,:) < min(zthresholds)) = false; end 128 | retained_windows = find(retained_mask); 129 | 130 | % find retained samples 131 | retained_samples = repmat(offsets(retained_windows)',1,length(wnd))+repmat(wnd,length(retained_windows),1); 132 | 133 | % mask them out 134 | sample_mask = false(1,S); sample_mask(retained_samples(:)) = true; 135 | fprintf('Keeping %.1f%% (%.0f seconds) of the data.\n',100*(mean(sample_mask)),nnz(sample_mask)/signal.srate); 136 | % collect into intervals 137 | retain_data_intervals = reshape(find(diff([false sample_mask false])),2,[])'; 138 | retain_data_intervals(:,2) = retain_data_intervals(:,2)-1; 139 | 140 | % apply selection 141 | signal = pop_select(signal, 'point', retain_data_intervals); 142 | if isfield(signal.etc,'clean_sample_mask') 143 | signal.etc.clean_sample_mask(signal.etc.clean_sample_mask) = sample_mask; 144 | else 145 | signal.etc.clean_sample_mask = sample_mask; 146 | end 147 | -------------------------------------------------------------------------------- /utilities/runSpectralCorrelationBoxPlots.m: -------------------------------------------------------------------------------- 1 | %% Performs a boxplot of spectral summary 2 | saveFolder = 'D:\Papers\Current\LARGIIDataCatalogInfo\spectralRelationships'; 3 | suffix = '_spectralCorrelationsAcrossMethods.mat'; 4 | imageFolder = 'D:\Papers\Current\LARGIIFigures\spectraRelationships'; 5 | figureVisibility = 'on'; 6 | figureClose = false; 7 | figureFormats = {'.png', 'png'; '.fig', 'fig'; '.pdf' 'pdf'; '.eps', 'epsc'}; 8 | %% 9 | if ~exist(imageFolder, 'dir') 10 | mkdir(imageFolder); 11 | end 12 | 13 | %% Study info: dir inst site, shortName, studyTitle 14 | removedStudies = {'RSVP', 'RWN_VDE', 'FLERP', 'VEP'}; 15 | [studies, meanings] = getStudies(removedStudies, false); 16 | numStudies = size(studies, 1); 17 | studyRootPos = find(strcmpi(meanings, 'studyRoot'), 1, 'first'); 18 | studyNamePos = find(strcmpi(meanings, 'studyName'), 1, 'first'); 19 | shortNamePos = find(strcmpi(meanings, 'shortName'), 1, 'first'); 20 | comboLabels = {'Larg vs Mara', 'Larg vs Asr_10', 'Larg vs Asr_5', ... 21 | 'Mara vs Asr_10', 'Mara vs Asr_5', 'Asr_10 vs Asr_5'}; 22 | studyPlotOrder = {'Cue', 'GuardA', 'GuardB', 'LKBase', 'LKCal',... 23 | 'LKSp', 'LKTraf', 'Mind', 'RsvpB', 'RsvpC', ... 24 | 'RsvpE', 'RsvpI', 'Das', 'DD', ... 25 | 'LKwAF', 'ACC', 'RsvpUA'}; 26 | bandOrder = {'Delta', 'Theta', 'Alpha', 'Beta', 'Gamma'}; 27 | % template = struct('uuid', NaN, 'site', NaN, 'study', NaN, 'labId', NaN, ... 28 | % 'srate', NaN, 'frequencies', NaN, 'scales', NaN, ... 29 | % 'correlations', NaN, 'bandCorrelations', NaN); 30 | % 31 | % save([saveFolder filesep studyName '_spectrogramCorrelationsAcrossMethods.mat'], ... 32 | % 'spectrogramRecs', 'studies', 'typeNames', 'commonChannels', ... 33 | % 'freqs', 'scales', 'freqBands', 'freqBandNames', ... 34 | % 'freqBandNames'); 35 | 36 | %% Set up the channels and covariance template 37 | commonChannels = getCommonLabels(); 38 | numChans = length(commonChannels); 39 | studyCorrs = []; 40 | studyLabels = {}; 41 | studyBandCorrs = []; 42 | typeLabels = {}; 43 | bandTypeLabels = {}; 44 | bandLabels = {}; 45 | bandStudyLabels = {}; 46 | for n = 1:numStudies 47 | %% Setup up the folders and see if the results have already been calculated 48 | studyName = studies{n, studyNamePos}; 49 | shortName = studies{n, shortNamePos}; 50 | fprintf('\nStarting %s\n', studyName); 51 | temp = load([saveFolder filesep studyName suffix]); 52 | sRecs = temp.spectralRecs; 53 | 54 | typeNames = temp.typeNames; 55 | freqBandNames = temp.freqBandNames; 56 | freqBandNames = freqBandNames(:)'; 57 | numBands = length(freqBandNames); 58 | numTypes = length(typeNames); 59 | numCombos = numTypes*(numTypes - 1)/2; 60 | 61 | %% Now make sure that all the values are there 62 | uuids = {sRecs.uuid}; 63 | uuidMask = false(length(uuids), 1); 64 | for m = 1:length(uuids) 65 | if isnan(uuids{m}) 66 | uuidMask(m) = true; 67 | end 68 | end 69 | sRecs(uuidMask) = []; 70 | numRecs = length(sRecs); 71 | [numChans, numSamples] = size(sRecs(1).randomNums); 72 | sCorrs = []; 73 | sBandCorrs = []; 74 | tLabels = {}; 75 | bLabels = {}; 76 | bTypeLabels = {}; 77 | comboNames = cell(numCombos, 1); 78 | c = 0; 79 | for m1 = 1:numTypes - 1 80 | for m2 = m1+1:numTypes 81 | tName = [typeNames{m1} '_' typeNames{m2}]; 82 | c = c + 1; 83 | comboNames{c} = tName; 84 | for k = 1:numRecs 85 | theseCorrs = sRecs(k).correlations(:, :, m1, m2); 86 | 87 | theseCorrs = theseCorrs(:); 88 | sCorrs = [sCorrs; theseCorrs]; %#ok<*AGROW> 89 | tLabels = [tLabels; repmat({tName}, length(theseCorrs), 1)]; 90 | theseBandCorrs = sRecs(k).bandCorrelations(:, :, m1, m2); 91 | theseBandCorrs = theseBandCorrs(:); 92 | theseBTypeLabels = repmat({tName}, length(theseBandCorrs), 1); 93 | theseBLabels = repmat(freqBandNames, numChans, 1); 94 | theseBLabels = theseBLabels(:); 95 | sBandCorrs = [sBandCorrs; theseBandCorrs]; 96 | bLabels = [bLabels; theseBLabels]; 97 | bTypeLabels = [bTypeLabels; theseBTypeLabels]; 98 | end 99 | end 100 | end 101 | studyLabels = [studyLabels; repmat({shortName}, length(sCorrs), 1)]; 102 | studyCorrs = [studyCorrs; sCorrs]; 103 | studyBandCorrs = [studyBandCorrs; sBandCorrs]; 104 | typeLabels = [typeLabels; tLabels]; 105 | bandLabels = [bandLabels; bLabels]; 106 | bandTypeLabels = [bandTypeLabels; bTypeLabels]; 107 | bandStudyLabels = [bandStudyLabels; repmat({shortName}, length(sBandCorrs), 1)]; 108 | end 109 | 110 | %% Boxplot of different type combinations 111 | dataLimits = [0, 1]; 112 | theTitle = 'Correlation of spectral vectors for preprocessing methods'; 113 | h1Fig = figure('Name', theTitle, 'Visible', figureVisibility); 114 | hold on 115 | bxs = boxplot(studyCorrs, typeLabels, 'orientation', 'horizontal', ... 116 | 'DataLim', dataLimits, 'GroupOrder', comboNames); 117 | ylabel('Type') 118 | xlabel('Correlation') 119 | title(theTitle, 'Interpreter', 'none'); 120 | set(gca, 'XLim', dataLimits, 'XLimMode', 'manual', 'YTickLabelMode', 'manual', ... 121 | 'YTickLabel', comboLabels); 122 | yLimits = get(gca, 'YLim'); 123 | line([0.5, 0.5], yLimits, 'Color', [0.2, 0.2, 0.2]); 124 | box on 125 | axis ij 126 | [~, cols] = size(bxs); 127 | colors = [0.8, 0.8, 0.8]; 128 | for j1 = 1:cols 129 | set(bxs(5, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 130 | patch(get(bxs(5, j1),'XData'),get(bxs(5, j1),'YData'), ... 131 | colors, 'FaceAlpha', 0.5); 132 | set(bxs(6, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 133 | set(bxs(7, j1), 'MarkerEdgeColor', [0.4, 0.4, 0.4], 'LineWidth', 0.5); 134 | end 135 | hold off 136 | baseFile = [imageFolder filesep 'spectralCorrelationByType']; 137 | saveFigures(h1Fig, baseFile, figureFormats, figureClose); 138 | 139 | %% Plot the different combinations 140 | for c = 1:length(comboLabels) 141 | figureVisibility = 'on'; 142 | dataLimits = [0, 1]; 143 | theTitle = ['Correlation by band of ' comboLabels{c}]; 144 | h2Fig = figure('Name', theTitle, 'Visible', figureVisibility); 145 | hold on 146 | labelMask = strcmpi(bandTypeLabels, comboNames{c}); 147 | bxs = boxplot(studyBandCorrs(labelMask), bandLabels(labelMask), ... 148 | 'orientation', 'horizontal', ... 149 | 'DataLim', dataLimits, 'GroupOrder', bandOrder); 150 | ylabel('Type') 151 | xlabel('Correlation') 152 | title(theTitle, 'Interpreter', 'none'); 153 | yLimits = get(gca, 'YLim'); 154 | line([0.5, 0.5], yLimits, 'Color', [0.2, 0.2, 0.2]); 155 | set(gca, 'XLim', dataLimits, 'XLimMode', 'manual', 'YTickLabelMode', 'manual', ... 156 | 'YTickLabel', bandOrder); 157 | box on 158 | axis ij 159 | [~, cols] = size(bxs); 160 | colors = lines(5); 161 | for j1 = 1:cols 162 | jP = mod(j1, 5) + 1; 163 | patch(get(bxs(5, j1),'XData'),get(bxs(5, j1),'YData'), ... 164 | colors(6-jP, :), 'FaceAlpha', 0.5); 165 | set(bxs(6, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 166 | set(bxs(7, j1), 'MarkerEdgeColor', [0.4, 0.4, 0.4], 'LineWidth', 0.5); 167 | end 168 | hold off 169 | baseFile = [imageFolder filesep 'spectralCorrelation_ ' comboNames{c}]; 170 | saveFigures(h2Fig, baseFile, figureFormats, figureClose); 171 | end 172 | %% %% Plot the different combinations 173 | [newLabels, newOrder, newYTickLabels] = makeCombinedLabels(bandTypeLabels, ... 174 | bandLabels, comboNames, bandOrder, comboLabels); 175 | 176 | 177 | dataLimits = [0, 1]; 178 | theTitle = 'Correlation by band of spectral vectors for preprocessing methods'; 179 | h3Fig = figure('Name', theTitle, 'Visible', figureVisibility); 180 | hold on 181 | bxs = boxplot(studyBandCorrs, newLabels, 'orientation', 'horizontal', ... 182 | 'DataLim', dataLimits, 'groupOrder', newOrder); 183 | ylabel('Type') 184 | xlabel('Correlation') 185 | title(theTitle, 'Interpreter', 'none'); 186 | yLimits = get(gca, 'YLim'); 187 | yTicks = get(gca, 'YTick'); 188 | set(gca, 'XLim', dataLimits, 'XLimMode', 'manual', 'YTickLabelMode', 'manual', ... 189 | 'YTickLabel', newYTickLabels); 190 | box on 191 | axis ij 192 | line([0.5, 0.5], yLimits, 'Color', [0.2, 0.2, 0.2]); 193 | base = 5.5; 194 | for j1 = 1:6 195 | line([0, 1], [base, base], 'LineStyle', '-', 'Color', [0.2, 0.2, 0.2]); 196 | base = base + 5; 197 | end 198 | 199 | [~, cols] = size(bxs); 200 | colors = lines(5); 201 | %colors = [0.4, 0.4, 0.4; colors]; 202 | patchColors = [0.8, 0.8, 0.8; 0.8, 1, 0.8]; 203 | for j1 = 1:cols 204 | jP = mod(j1, 5) + 1; 205 | %set(bxs(5, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 206 | patch(get(bxs(5, j1),'XData'),get(bxs(5, j1),'YData'), ... 207 | colors(6-jP, :), 'FaceAlpha', 0.5); 208 | set(bxs(6, j1), 'Color', [0, 0, 0], 'LineWidth', 1); 209 | set(bxs(7, j1), 'MarkerEdgeColor', [0.4, 0.4, 0.4], 'LineWidth', 0.5); 210 | end 211 | hold off 212 | baseFile = [imageFolder filesep 'spectralBandCorrelationSummary']; 213 | saveFigures(h3Fig, baseFile, figureFormats, figureClose); 214 | 215 | -------------------------------------------------------------------------------- /utilities/runRandomSampleCorrelations.m: -------------------------------------------------------------------------------- 1 | %% Calculate random spectral sample correlations and display in boxplot 2 | 3 | %% Set up the data 4 | dataDir = 'D:\Research\EEGPipelineProject\dataOut'; 5 | EEGBaseFile = 'speedControlSession1Subj2015Rec1'; 6 | methodNames = {'LARG', 'MARA', 'ASR_10', 'ASRalt_10', 'ASR_5', 'ASRalt_5'}; 7 | numMethods = length(methodNames); 8 | 9 | %% Read in the files 10 | eegs = cell(numMethods, 1); 11 | for m = 1:numMethods 12 | fileName = [dataDir filesep EEGBaseFile '_' methodNames{m} '.set']; 13 | eegs{m} = pop_loadset(fileName); 14 | end 15 | 16 | %% Spectral parameters 17 | spectraTime = 4; 18 | numSpectra = 100; 19 | freqRange = [1, 50]; 20 | freqResolution = 256; 21 | fBins = linspace(freqRange(1), freqRange(2), freqResolution); 22 | numFreqs = length(fBins); 23 | freqBands = [2, 4; 4, 7; 7, 12; 12, 30; 30, 50]; 24 | freqBandNames = {'Delta'; 'Theta'; 'Alpha'; 'Beta'; 'Gamma'}; 25 | numFreqBands = size(freqBands, 1); 26 | 27 | 28 | %% Set up the channels and covariance template 29 | commonChannels = getCommonLabels(); 30 | numChans = length(commonChannels); 31 | for n = 1:numStudies 32 | %% Setup up the folders and see if the results have already been calculated 33 | studyName = studies{n, studyNamePos}; 34 | studyRoot = studies{n, studyRootPos}; 35 | fprintf('\nStarting %s\n', studyName); 36 | catalogs = cell(numTypes, 1); 37 | studyFolders = cell(numTypes, 1); 38 | for m = 1:numTypes 39 | runFolder = [baseFolders{m} filesep studyRoot filesep runName]; 40 | studyFolders{m} = [runFolder filesep studyName]; 41 | catalogFolder = [runFolder filesep 'catalog']; 42 | if ~exist(runFolder, 'dir') || ~exist(studyFolders{m}, 'dir') || ... 43 | ~exist(catalogFolder, 'dir') 44 | error(['run folder [%s]\n studyOutput folder [%s]\n and ' ... 45 | 'catalog [%s] must exist from stage 1'], ... 46 | runFolder, studyFolders{m}, catalogFolder); 47 | end 48 | 49 | %% Open the catalog and get the file information 50 | catalogs{m} = EntityCatalog([catalogFolder filesep 'database_catalog.sto']); 51 | end 52 | 53 | %% Allocate space for the study structures 54 | EEGFiles = filesAndFolders_to_list(studyFolders{m}, {'EEG.set'}, true); 55 | numRecordings = length(EEGFiles); 56 | spectralRecs = template; 57 | spectralRecs(numRecordings) = template; 58 | 59 | for k = 1:numRecordings 60 | %% Read the EEG file and the amplitude file fill in the template 61 | spectralRecs(k) = template; 62 | fprintf('Processing recording %d:%s\n', k, EEGFiles{k}); 63 | [thePath, theName, theExt] = fileparts(EEGFiles{k}); 64 | if exist([thePath filesep 'channel' filesep 'skipMe.txt'], 'file') 65 | warning('%d %s: should be skipped', k, thePath); 66 | continue; 67 | end 68 | if ~exist([thePath filesep filesep amplitudeName], 'file') 69 | warning('%d %s: has no amplitude file', k, thePath); 70 | continue; 71 | end 72 | %% Make sure that the dataID is in all types 73 | temp = load([thePath filesep amplitudeName]); 74 | amplitudeInfo = temp.amplitudeInfo; 75 | dataID = amplitudeInfo.custom.sourceDataRecordingId; 76 | badMask = false; 77 | for m = 1:numTypes 78 | if ~isKey(uuidMaps{m}, dataID) 79 | warning('%s: not in type %s\n', EEGFiles{k}, typeNames{m}); 80 | badMask = true; 81 | continue; 82 | end 83 | end 84 | if badMask 85 | continue; 86 | end 87 | EEGs = cell(numTypes, 1); 88 | huberMean = nan(numTypes, 1); 89 | for m = 1:numTypes 90 | uuidRec = uuidMaps{m}(dataID); 91 | eegFolder = [studyFolders{m} filesep 'recording_' ... 92 | num2str(uuidRec.recording)]; 93 | EEGFile = [eegFolder filesep 'EEG.set']; 94 | EEGs{m} = pop_loadset(EEGFile); 95 | EEGs{m}= reorderChannels(EEGs{m}, commonChannels); 96 | EEGs{m} = pop_reref(EEGs{m}, []); 97 | temp = load([eegFolder filesep amplitudeName]); 98 | amplitudeInfo = temp.amplitudeInfo; 99 | huberMean(m) = huber_mean(amplitudeInfo.channelRobustStd.tensor); 100 | if uuidRec.srate ~= EEGs{m}.srate || ... 101 | EEGs{1}.srate ~= EEGs{m}.srate || ... 102 | uuidRec.frames ~= size(EEGs{m}.data, 2) || ... 103 | size(EEGs{1}.data, 2) ~= size(EEGs{m}.data, 2) 104 | warning('%s %s does not have matching srate or frames', ... 105 | EEGFile, typeNames{m}); 106 | badMask = true; 107 | break; 108 | end 109 | end 110 | if badMask 111 | continue; 112 | end 113 | uuidRec = uuidMaps{1}(dataID); 114 | spectralRecs(k).uuid = dataID; 115 | spectralRecs(k).srate = uuidRec.srate; 116 | spectralRecs(k).frames = uuidRec.frames; 117 | spectralRecs(k).randomNums = rand(numChans, numSpectra); 118 | spectralRecs(k).labId = uuidRec.labId; 119 | spectralRecs(k).site = uuidRec.site; 120 | spectralRecs(k).study = uuidRec.study; 121 | spectralRecs(k).huberMean = huberMean; 122 | %% Now compute the spectra for each 123 | startingFracs = spectralRecs(k).randomNums; 124 | sFrames = spectraTime*uuidRec.srate; 125 | frames = uuidRec.frames; 126 | actualFrames = frames - sFrames; 127 | startFrames = ceil(startingFracs*actualFrames); 128 | endFrames = startFrames + sFrames - 1; 129 | chanNums = repmat(1:numChans, 1, 100); 130 | pSpectra = zeros(numChans, numFreqs, numSpectra, numTypes); 131 | frequencies = zeros(numFreqs, 1); 132 | for m = 1:numTypes 133 | EEGs{m}.data = EEGs{m}.data/spectralRecs(k).huberMean(m); 134 | for j = 1:numSpectra 135 | data = zeros(numChans, sFrames); 136 | for c = 1:numChans 137 | data(c, :) = EEGs{m}.data(c, startFrames(c,j):endFrames(c, j)); 138 | end 139 | [x, f] = pmtm(data', 4, fBins, 128); 140 | pSpectra(:, :, j, m) = x'; 141 | end 142 | end 143 | %% Compute the average spectra 144 | spectralRecs(k).frequencies = fBins; 145 | spectralRecs(k).meanPowerSpectra = squeeze(mean(pSpectra, 3)); 146 | pBandSpectra = zeros(numChans, numFreqBands, numSpectra, numTypes); 147 | for b = 1:numFreqBands 148 | freqMask = freqBands(b, 1) <= fBins & fBins < freqBands(b, 2); 149 | for m = 1:numTypes 150 | for j = 1:numSpectra 151 | pBandSpectra(:, b, j, m) = mean(pSpectra(:, freqMask, j, m), 2); 152 | end 153 | end 154 | end 155 | %% Compute the correlations 156 | logPSpectra = 10*log10(pSpectra); 157 | logPBandSpectra = 10*log10(pBandSpectra); 158 | correlations = zeros(numChans, numSpectra, numTypes, numTypes); 159 | logCorrelations = zeros(numChans, numSpectra, numTypes, numTypes); 160 | logSpectralDist = zeros(numChans, numSpectra, numTypes, numTypes); 161 | bandCorrelations = zeros(numChans, numFreqBands, numTypes, numTypes); 162 | logBandCorrelations = zeros(numChans, numFreqBands, numTypes, numTypes); 163 | for m1 = 1:numTypes 164 | for m2 = 1:numTypes 165 | for c = 1:numChans 166 | spectra1 = squeeze(pSpectra(c, :, :, m1)); 167 | spectra2 = squeeze(pSpectra(c, :, :, m2)); 168 | logSpectra1 = squeeze(logPSpectra(c, :, :, m1)); 169 | logSpectra2 = squeeze(logPSpectra(c, :, :, m2)); 170 | for j = 1:numSpectra 171 | logSpectralDist(c, j, m1, m2) = ... 172 | mean(10*log10(spectra1(:, j)./spectra2(:, j))); 173 | correlations(c, j, m1, m2) = ... 174 | corr(spectra1(:, j), spectra2(:, j)); 175 | end 176 | 177 | bSpectra1 = squeeze(pBandSpectra(c, :, :, m1))'; 178 | bSpectra2 = squeeze(pBandSpectra(c, :, :, m2))'; 179 | logBSpectra1 = squeeze(logPBandSpectra(c, :, :, m1))'; 180 | logBSpectra2 = squeeze(logPBandSpectra(c, :, :, m2))'; 181 | for b = 1:numFreqBands 182 | bandCorrelations(c, b, m1, m2) = ... 183 | corr(bSpectra1(:, b), bSpectra2(:, b)); 184 | logBandCorrelations(c, b, m1, m2) = ... 185 | corr(logBSpectra1(:, b), logBSpectra2(:, b)); 186 | end 187 | end 188 | end 189 | end 190 | spectralRecs(k).correlations = correlations; 191 | spectralRecs(k).logCorrelations = logCorrelations; 192 | spectralRecs(k).bandCorrelations = bandCorrelations; 193 | spectralRecs(k).logBandCorrelations = logBandCorrelations; 194 | spectralRecs(k).logSpectralDist = logSpectralDist; 195 | end 196 | save([saveFolder filesep studyName '_spectralCorrelationsAcrossMethods.mat'], ... 197 | 'spectralRecs', 'studies', 'typeNames', 'commonChannels', ... 198 | 'freqBands', 'freqBandNames', 'fBins') 199 | end 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /utilities/runRandomSpectraCorrelations.m: -------------------------------------------------------------------------------- 1 | %% Create the random spectral points for the common channels 2 | 3 | %% Set up the folders 4 | uuidFolder = 'D:\Papers\Current\LARGIIDataCatalogInfo\uuidMaps'; 5 | saveFolder = 'D:\Papers\Current\LARGIIDataCatalogInfo\spectralRelationships'; 6 | baseFolders = {'O:\LARGDataCorrected', 'H:\LARGDataMaraCorrected', ... 7 | 'H:\LARGDataAsr_10_AggressiveCorrected', ... 8 | 'H:\LARGDataAsr_5_AggressiveCorrected'}; 9 | typeNames = {'Larg', 'Mara', 'Asr_10', 'Asr_5'}; 10 | numTypes = length(typeNames); 11 | runName = 'run_12'; 12 | recalculate = true; 13 | amplitudeName = ['channel' filesep 'amplitudeInfo.mat']; 14 | 15 | %% Make sure save directory 16 | if ~exist(saveFolder, 'dir') 17 | mkdir(saveFolder); 18 | end 19 | 20 | %% Parameters 21 | spectraTime = 4; 22 | numSpectra = 100; 23 | fBins = linspace(1, 50, 256); 24 | numFreqs = length(fBins); 25 | freqBands = [2, 4; 4, 7; 7, 12; 12, 30; 30, 50]; 26 | freqBandNames = {'Delta'; 'Theta'; 'Alpha'; 'Beta'; 'Gamma'}; 27 | numFreqBands = size(freqBands, 1); 28 | %% Make sure covariance folder exists 29 | uuidMaps = cell(numTypes, 1); 30 | for m = 1:numTypes 31 | uuidName = [uuidFolder filesep typeNames{m} '_UUIDMap.mat']; 32 | if ~exist(uuidName, 'file') 33 | error('%s uuid map does not exist', typeNames{m}); 34 | end 35 | temp = load(uuidName); 36 | uuidMaps{m} = temp.uuidMap; 37 | end 38 | 39 | %% Study info: dir inst site, shortName, studyTitle 40 | removedStudies = {'RSVP', 'RWN_VDE', 'FLERP', 'VEP'}; 41 | [studies, meanings] = getStudies(removedStudies, false); 42 | numStudies = size(studies, 1); 43 | studyRootPos = find(strcmpi(meanings, 'studyRoot'), 1, 'first'); 44 | studyNamePos = find(strcmpi(meanings, 'studyName'), 1, 'first'); 45 | commonLabels = getCommonLabels(); 46 | 47 | %% Setup the recording map. 48 | template = struct('uuid', NaN, 'site', NaN, 'study', NaN, 'labId', NaN, ... 49 | 'frames', NaN, 'srate', NaN, 'huberMean', NaN, 'randomNums', NaN, ... 50 | 'frequencies', NaN, 'meanPowerSpectra', NaN, 'logSpectralDist', NaN, ... 51 | 'correlations', NaN, 'bandCorrelations', NaN, ... 52 | 'logCorrelations', NaN, 'logBandCorrelations', NaN); 53 | 54 | %% Set up the channels and covariance template 55 | commonChannels = getCommonLabels(); 56 | numChans = length(commonChannels); 57 | for n = 1:numStudies 58 | %% Setup up the folders and see if the results have already been calculated 59 | studyName = studies{n, studyNamePos}; 60 | studyRoot = studies{n, studyRootPos}; 61 | fprintf('\nStarting %s\n', studyName); 62 | catalogs = cell(numTypes, 1); 63 | studyFolders = cell(numTypes, 1); 64 | for m = 1:numTypes 65 | runFolder = [baseFolders{m} filesep studyRoot filesep runName]; 66 | studyFolders{m} = [runFolder filesep studyName]; 67 | catalogFolder = [runFolder filesep 'catalog']; 68 | if ~exist(runFolder, 'dir') || ~exist(studyFolders{m}, 'dir') || ... 69 | ~exist(catalogFolder, 'dir') 70 | error(['run folder [%s]\n studyOutput folder [%s]\n and ' ... 71 | 'catalog [%s] must exist from stage 1'], ... 72 | runFolder, studyFolders{m}, catalogFolder); 73 | end 74 | 75 | %% Open the catalog and get the file information 76 | catalogs{m} = EntityCatalog([catalogFolder filesep 'database_catalog.sto']); 77 | end 78 | 79 | %% Allocate space for the study structures 80 | EEGFiles = filesAndFolders_to_list(studyFolders{m}, {'EEG.set'}, true); 81 | numRecordings = length(EEGFiles); 82 | spectralRecs = template; 83 | spectralRecs(numRecordings) = template; 84 | 85 | for k = 1:numRecordings 86 | %% Read the EEG file and the amplitude file fill in the template 87 | spectralRecs(k) = template; 88 | fprintf('Processing recording %d:%s\n', k, EEGFiles{k}); 89 | [thePath, theName, theExt] = fileparts(EEGFiles{k}); 90 | if exist([thePath filesep 'channel' filesep 'skipMe.txt'], 'file') 91 | warning('%d %s: should be skipped', k, thePath); 92 | continue; 93 | end 94 | if ~exist([thePath filesep filesep amplitudeName], 'file') 95 | warning('%d %s: has no amplitude file', k, thePath); 96 | continue; 97 | end 98 | %% Make sure that the dataID is in all types 99 | temp = load([thePath filesep amplitudeName]); 100 | amplitudeInfo = temp.amplitudeInfo; 101 | dataID = amplitudeInfo.custom.sourceDataRecordingId; 102 | badMask = false; 103 | for m = 1:numTypes 104 | if ~isKey(uuidMaps{m}, dataID) 105 | warning('%s: not in type %s\n', EEGFiles{k}, typeNames{m}); 106 | badMask = true; 107 | continue; 108 | end 109 | end 110 | if badMask 111 | continue; 112 | end 113 | EEGs = cell(numTypes, 1); 114 | huberMean = nan(numTypes, 1); 115 | for m = 1:numTypes 116 | uuidRec = uuidMaps{m}(dataID); 117 | eegFolder = [studyFolders{m} filesep 'recording_' ... 118 | num2str(uuidRec.recording)]; 119 | EEGFile = [eegFolder filesep 'EEG.set']; 120 | EEGs{m} = pop_loadset(EEGFile); 121 | EEGs{m}= reorderChannels(EEGs{m}, commonChannels); 122 | EEGs{m} = pop_reref(EEGs{m}, []); 123 | temp = load([eegFolder filesep amplitudeName]); 124 | amplitudeInfo = temp.amplitudeInfo; 125 | huberMean(m) = huber_mean(amplitudeInfo.channelRobustStd.tensor); 126 | if uuidRec.srate ~= EEGs{m}.srate || ... 127 | EEGs{1}.srate ~= EEGs{m}.srate || ... 128 | uuidRec.frames ~= size(EEGs{m}.data, 2) || ... 129 | size(EEGs{1}.data, 2) ~= size(EEGs{m}.data, 2) 130 | warning('%s %s does not have matching srate or frames', ... 131 | EEGFile, typeNames{m}); 132 | badMask = true; 133 | break; 134 | end 135 | end 136 | if badMask 137 | continue; 138 | end 139 | uuidRec = uuidMaps{1}(dataID); 140 | spectralRecs(k).uuid = dataID; 141 | spectralRecs(k).srate = uuidRec.srate; 142 | spectralRecs(k).frames = uuidRec.frames; 143 | spectralRecs(k).randomNums = rand(numChans, numSpectra); 144 | spectralRecs(k).labId = uuidRec.labId; 145 | spectralRecs(k).site = uuidRec.site; 146 | spectralRecs(k).study = uuidRec.study; 147 | spectralRecs(k).huberMean = huberMean; 148 | %% Now compute the spectra for each 149 | startingFracs = spectralRecs(k).randomNums; 150 | sFrames = spectraTime*uuidRec.srate; 151 | frames = uuidRec.frames; 152 | actualFrames = frames - sFrames; 153 | startFrames = ceil(startingFracs*actualFrames); 154 | endFrames = startFrames + sFrames - 1; 155 | chanNums = repmat(1:numChans, 1, 100); 156 | pSpectra = zeros(numChans, numFreqs, numSpectra, numTypes); 157 | frequencies = zeros(numFreqs, 1); 158 | for m = 1:numTypes 159 | EEGs{m}.data = EEGs{m}.data/spectralRecs(k).huberMean(m); 160 | for j = 1:numSpectra 161 | data = zeros(numChans, sFrames); 162 | for c = 1:numChans 163 | data(c, :) = EEGs{m}.data(c, startFrames(c,j):endFrames(c, j)); 164 | end 165 | [x, f] = pmtm(data', 4, fBins, 128); 166 | pSpectra(:, :, j, m) = x'; 167 | end 168 | end 169 | %% Compute the average spectra 170 | spectralRecs(k).frequencies = fBins; 171 | spectralRecs(k).meanPowerSpectra = squeeze(mean(pSpectra, 3)); 172 | pBandSpectra = zeros(numChans, numFreqBands, numSpectra, numTypes); 173 | for b = 1:numFreqBands 174 | freqMask = freqBands(b, 1) <= fBins & fBins < freqBands(b, 2); 175 | for m = 1:numTypes 176 | for j = 1:numSpectra 177 | pBandSpectra(:, b, j, m) = mean(pSpectra(:, freqMask, j, m), 2); 178 | end 179 | end 180 | end 181 | %% Compute the correlations 182 | logPSpectra = 10*log10(pSpectra); 183 | logPBandSpectra = 10*log10(pBandSpectra); 184 | correlations = zeros(numChans, numSpectra, numTypes, numTypes); 185 | logCorrelations = zeros(numChans, numSpectra, numTypes, numTypes); 186 | logSpectralDist = zeros(numChans, numSpectra, numTypes, numTypes); 187 | bandCorrelations = zeros(numChans, numFreqBands, numTypes, numTypes); 188 | logBandCorrelations = zeros(numChans, numFreqBands, numTypes, numTypes); 189 | for m1 = 1:numTypes 190 | for m2 = 1:numTypes 191 | for c = 1:numChans 192 | spectra1 = squeeze(pSpectra(c, :, :, m1)); 193 | spectra2 = squeeze(pSpectra(c, :, :, m2)); 194 | logSpectra1 = squeeze(logPSpectra(c, :, :, m1)); 195 | logSpectra2 = squeeze(logPSpectra(c, :, :, m2)); 196 | for j = 1:numSpectra 197 | logSpectralDist(c, j, m1, m2) = ... 198 | mean(10*log10(spectra1(:, j)./spectra2(:, j))); 199 | correlations(c, j, m1, m2) = ... 200 | corr(spectra1(:, j), spectra2(:, j)); 201 | end 202 | 203 | bSpectra1 = squeeze(pBandSpectra(c, :, :, m1))'; 204 | bSpectra2 = squeeze(pBandSpectra(c, :, :, m2))'; 205 | logBSpectra1 = squeeze(logPBandSpectra(c, :, :, m1))'; 206 | logBSpectra2 = squeeze(logPBandSpectra(c, :, :, m2))'; 207 | for b = 1:numFreqBands 208 | bandCorrelations(c, b, m1, m2) = ... 209 | corr(bSpectra1(:, b), bSpectra2(:, b)); 210 | logBandCorrelations(c, b, m1, m2) = ... 211 | corr(logBSpectra1(:, b), logBSpectra2(:, b)); 212 | end 213 | end 214 | end 215 | end 216 | spectralRecs(k).correlations = correlations; 217 | spectralRecs(k).logCorrelations = logCorrelations; 218 | spectralRecs(k).bandCorrelations = bandCorrelations; 219 | spectralRecs(k).logBandCorrelations = logBandCorrelations; 220 | spectralRecs(k).logSpectralDist = logSpectralDist; 221 | end 222 | save([saveFolder filesep studyName '_spectralCorrelationsAcrossMethods.mat'], ... 223 | 'spectralRecs', 'studies', 'typeNames', 'commonChannels', ... 224 | 'freqBands', 'freqBandNames', 'fBins') 225 | end 226 | 227 | 228 | 229 | --------------------------------------------------------------------------------