Skip to content

Commit 50fb621

Browse files
committed
Updated blinker to include Eye-catch in the distribution
1 parent 16b6ea0 commit 50fb621

16 files changed

+1227
-3
lines changed

EEGLABPlugin/blinkerv1.1.2.zip

-114 KB
Binary file not shown.

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@ Contains tools for the BLINKER pipeline for automatically extracting blinks and
55
ocular indices from EEG. You can find user documention at:
66
http://vislab.github.io/EEG-Blinks/
77

8-
**Note:** For convenience, EEGLABPlugin directory contains the latest released version of BLINKER
9-
that can be unzipped into your EEGLAB plugins directory.
8+
9+
This version has Eye-catch embedded. The original code
10+
for Eye-catch is available at [https://github.com/bigdelys/eye-catch](https://github.com/bigdelys/eye-catch).
11+
12+
### Installing as a plugin in EEGLAB
13+
14+
You can install blinker as a plugin from EEGLAB plugin manager.
15+
However, if you want to install from GitHub:
16+
1. Download this repository.
17+
2. Rename the blinker directory blinkerv1.2.0.
18+
3. Copy the blinkerv1.2.0 into the EEGLAB/plugins directory.
1019

1120
### Citing the BLINKER pipeline
1221
BLINKER is freely available under the GNU General Public License.
@@ -27,6 +36,7 @@ independent components that are not eye-related. Kay Robbins of UTSA maintains B
2736
1.0.2 3/9/2017 Implemented insertion of blink events into EEG structure
2837
1.1.0 3/17/2017 Implemented insertion of additional blink features as events and constructed zeroed blink signal
2938
1.1.1 9/6/2018 Modified getBlinkEvents to not include leading slashes in HED tags
39+
1.2.0 8/28/2022 Incorporated eyeCatch since not easily available elsewhere.
3040

3141
### Support
3242
This research was sponsored by the Army Research Laboratory and was

blinker/changelog.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
blinker 1.2.0 Change log 8/28/2023
2+
1. Included eyeCatch in the utilities directory as it does not seem to
3+
be easily available elsewhere. It makes the distribution very large.
4+
Because of GitHub limitations, this repository cannot be zipped into a
5+
single file to be installed as an EEGLAB Plugin. Thus, if you don't
6+
install through the EEGLAB interface, you must download this repository
7+
into EEGLAB/plugins/blinkerv1.2.0.
8+
19
blinker 1.1.2 Change log 9/7/2018
210
1. Modified getBlinkPositions to calculate robustStdDev using median
311
absolute deviation rather than mean absolute deviation

blinker/eegplugin_blinker.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
%% Adds blinker as a plugin under the tools menu on EEGLAB
2626

2727
%% Set the version number
28-
vers = 'blinker1.1.2';
28+
vers = 'blinker1.2.0';
2929

3030
%% Add subfolders
3131
tmp = which('getBlinkerDefaults');

blinker/utilities/+pr/LICENSE

Lines changed: 340 additions & 0 deletions
Large diffs are not rendered by default.

blinker/utilities/+pr/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<div align="center">
2+
<img src="/logo.svg" width="50%">
3+
</div>
4+
5+
# EyeCatch
6+
A fully automatic algorithm, implemented in MATLAB, for finding Eye-Related ICA components based on their scalpmaps and spectrum signatures (new). It has a performance comparable to CORRMAP while not requiring any user intervention.
7+
8+
Here is a sample of eye-related ICA scalpmaps used in EyeCatch:
9+
10+
<div align="center">
11+
<img src="/eye_ic_scalpmaps.jpg" width="60%">
12+
</div>
13+
14+
15+
[Measure Projection Toolbox (MPT)](http://sccn.ucsd.edu/wiki/MPT) includes EyeCatch software (as pr.eyeCatch class), if you have not installed MPT you can download EyeCatch stand-alone from this repository.
16+
17+
## How to Reference
18+
19+
If you used EyeCatch, please include a reference to EyeCatch paper (below) in your publication :
20+
21+
[Bigdely-Shamlo, Nima, Kenneth Kreutz-Delgado, Christian Kothe, and Scott Makeig. "EyeCatch: Data-mining over half a million EEG independent components to construct a fully-automated eye-component detector." In Engineering in Medicine and Biology Society (EMBC), 2013 35th Annual International Conference of the IEEE, pp. 5845-5848. IEEE, 2013.](http://www.ncbi.nlm.nih.gov/pmc/articles/PMC4136453)
22+
23+
## Usage
24+
25+
Note: if you have already installed Measure Projection software, please use `pr.eyeCatch` instead of `eyeCatch` in the examples below.
26+
Example 1: Finding eye ICs in the EEG structure ().
27+
```matlab
28+
>> eyeDetector = eyeCatch; % create an object from the class. Once you made an object it can
29+
% be used for multiple detections (much faster than creating an
30+
% object each time).
31+
```
32+
then
33+
```matlab
34+
>> [eyeIC similarity scalpmapObj] = eyeDetector.detectFromEEG(EEG); % detect eye ICs
35+
>> eyeIC % display the IC numbers for eye ICs.
36+
>> scalpmapObj.plot(eyeIC) % plot eye ICs
37+
```
38+
Example 2: (application on a study)
39+
```matlab
40+
>> eyeDetector = eyeCatch; % create an object from the class. Once you made an object it can
41+
% be used for multiple detections (much faster than creating an
42+
% object each time).
43+
% read data from a loaded study
44+
>> [isEye similarity scalpmapObj] = eyeDetector.detectFromStudy(STUDY, ALLEEG);
45+
>> find(isEye) % display the IC numbers for eye ICs (since isEye is a logical array). The order of ICs is same order as in STUDY.cluster(1).comps .
46+
>> scalpmapObj.plot(isEye) % plot eye ICs
47+
```
48+
49+
Created by Nima Bigdely-Shamlo, PhD.

blinker/utilities/+pr/eyeCatch.m

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
classdef eyeCatch
2+
% Detetcs eye ICs based solely based on on their scalpmaps.
3+
% The input can be an EEG structure, an array of scalpmaps (channwel weights and location, e.g.
4+
% from EEG.chanlocs and EEG.icawinv) or an scalpmap object from MPT.
5+
%
6+
% Example 1: (finding eye ICs in the EEG structure)
7+
%
8+
% >> eyeDetector = eyeCatch; % create an object from the class
9+
% >> [isEye similarity scalpmapObj] = eyeDetector.detectFromEEG(EEG); % detect eye ICs
10+
% >> find(isEye) % display the IC numbers for eye ICs (since isEye is a logical array)
11+
% >> scalpmapObj.plot(isEye) % plot eye ICs
12+
%
13+
% Example 2:
14+
%
15+
% >> [isEye similarity scalpmapObj] = eyeDetector.detectFromStudy(STUDY, ALLEEG); % read data from a loaded study
16+
% >> find(isEye) % display the IC numbers for eye ICs (since isEye is a logical array). The
17+
% % order of ICs is same order as in STUDY.cluster(1).comps .
18+
% >> scalpmapObj.plot(isEye) % plot eye ICs
19+
%
20+
% Written by Nima Bigdely-Shamlo, Swartz Center, INC, UCSD.
21+
% Copyright � 2012 University Of California San Diego. Distributed under BSD License.
22+
23+
properties
24+
eyeScalpmapDatabase
25+
eyeChannelWeightNormalized % precomputed normalized eye scalpmaps channel weights on interpolated 2D scalp.
26+
similarityThreshold = 0.94
27+
end;
28+
29+
methods
30+
function obj = eyeCatch(similarityThreshold, varargin)
31+
% obj = eyeCatch(similarityThreshold)
32+
if nargin > 0
33+
obj.similarityThreshold = similarityThreshold;
34+
end;
35+
36+
load('pooledEyeScalpmap.mat'); % load pooledEyeScalpmap varaible
37+
load('eyeChannelWeightNormalizedPart1.mat'); % load eyeChannelWeightNormalizedPart1 variable
38+
load('eyeChannelWeightNormalizedPart2.mat'); % load eyeChannelWeightNormalizedPart1 variable
39+
eyeChannelWeightNormalized = cat(1, eyeChannelWeightNormalizedPart1, eyeChannelWeightNormalizedPart2);
40+
clear eyeChannelWeightNormalizedPart1 eyeChannelWeightNormalizedPart2
41+
42+
obj.eyeScalpmapDatabase = pooledEyeScalpmap;
43+
obj.eyeChannelWeightNormalized = eyeChannelWeightNormalized;
44+
if size(obj.eyeChannelWeightNormalized,1) ~= obj.eyeScalpmapDatabase.numberOfScalpmaps
45+
fprintf('Precomputed weights are being recomputed.\n');
46+
% to do compute weights
47+
channelWeight = obj.eyeScalpmapDatabase.originalChannelWeight(:,:);
48+
channelWeight(isnan(channelWeight)) = 0;
49+
channelWeight = channelWeight';
50+
51+
channelWeightNormalized = bsxfun(@minus, channelWeight, mean(channelWeight));
52+
channelWeightNormalized = bsxfun(@rdivide, channelWeightNormalized, std(channelWeightNormalized));
53+
eyeChannelWeightNormalized = channelWeightNormalized';
54+
55+
eyeChannelWeightNormalizedPart1 = eyeChannelWeightNormalized(1:2000,:);
56+
eyeChannelWeightNormalizedPart2 = eyeChannelWeightNormalized(2001:end,:);
57+
58+
% save the file (overwrite)
59+
pooledEyeScalpmap = obj.eyeScalpmapDatabase;
60+
fullPath = which('eyeChannelWeightNormalizedPart1.mat');
61+
try
62+
save(fullPath, 'eyeChannelWeightNormalizedPart1');
63+
fprintf('New precomputed weights saved.\n');
64+
65+
fullPath = which('eyeChannelWeightNormalizedPart2.mat');
66+
save(fullPath, 'eyeChannelWeightNormalizedPart2');
67+
catch
68+
fprintf('Could not save newly precomputed weights, please check if you have write permission.\n');
69+
end;
70+
71+
72+
73+
end;
74+
end;
75+
76+
function [isEye similarity] = detectFromInterpolatedChannelWeight(obj, channelWeight, similarityThreshold, varargin)
77+
% [isEye similarity] = detectFromInterpolatedChannelWeight(obj, channelWeight, similarityThreshold)
78+
79+
if nargin < 3
80+
similarityThreshold = obj.similarityThreshold;
81+
end;
82+
83+
channelWeight(isnan(channelWeight)) = 0;
84+
channelWeight = channelWeight';
85+
86+
channelWeightNormalized = bsxfun(@minus, channelWeight, mean(channelWeight));
87+
channelWeightNormalized = bsxfun(@rdivide, channelWeightNormalized, std(channelWeightNormalized));
88+
channelWeightNormalized = channelWeightNormalized';
89+
90+
similarity = max(abs(obj.eyeChannelWeightNormalized * channelWeightNormalized')) / (size(obj.eyeChannelWeightNormalized,2));
91+
isEye = similarity > similarityThreshold;
92+
end;
93+
94+
function [isEye similarity] = detectFromScalpmapObj(obj, scalpmapObj, similarityThreshold, varargin)
95+
% [isEye similarity] = detectFromScalpmapObj(obj, scalpmapObj, similarityThreshold)
96+
97+
if nargin < 3
98+
similarityThreshold = obj.similarityThreshold;
99+
end;
100+
101+
[isEye similarity] = detectFromInterpolatedChannelWeight(obj, scalpmapObj.originalChannelWeight(:,:), similarityThreshold);
102+
end;
103+
104+
function [isEye similarity scalpmapObj] = detectFromEEG(obj, EEG, similarityThreshold, varargin)
105+
% [isEye similarity scalpmapObj] = detectFromEEG(obj, EEG, similarityThreshold)
106+
% EEG data MUST have been highpassed at 1 Hz.
107+
108+
if nargin < 3
109+
similarityThreshold = obj.similarityThreshold;
110+
end;
111+
112+
if isempty(EEG.icachansind)
113+
EEG.icachansind= 1:length(EEG.chanlocs);
114+
end;
115+
116+
[isEye, similarity, scalpmapObj] = detectFromChannelWeight(obj, EEG.icawinv, EEG.chanlocs(EEG.icachansind), similarityThreshold);
117+
isEye = findEyeMovementICsByPowerRatio(obj, EEG, isEye, similarity);
118+
end;
119+
120+
function [isEye similarity scalpmapObj] = detectFromChannelWeight(obj, channelWeight, channelLocation, similarityThreshold, varargin)
121+
% [isEye similarity] = detectFromChannelWeight(obj, channelWeight, channelLocation, similarityThreshold)
122+
if nargin < 4
123+
similarityThreshold = obj.similarityThreshold;
124+
end;
125+
126+
scalpmapObj = scalpmap;
127+
scalpmapObj = scalpmapObj.addFromChannels(channelWeight, channelLocation);
128+
[isEye similarity] = detectFromScalpmapObj(obj, scalpmapObj, similarityThreshold);
129+
end;
130+
131+
function [isEye similarity scalpmapObj] = detectFromStudy(obj, STUDY, ALLEEG, similarityThreshold, varargin)
132+
scalpmapObj = scalpmapOfStudy(STUDY, ALLEEG, [], 'normalizePolarity', false);
133+
[isEye similarity] = detectFromScalpmapObj(obj, scalpmapObj, similarityThreshold);
134+
end;
135+
136+
function isEye = findEyeMovementICsByPowerRatio(obj, EEG, isEye, eyeCatchSimilarity)
137+
% isEye = findEyeMovementICsByPowerRatio(EEG, isEye, eyeCatchSimilarity)
138+
% EEG data MUST have been highpassed at 1 Hz.
139+
140+
criticalFreq = 3;
141+
powerRatioThreshold = 100;
142+
timeRatioOfPowerRatioTooHigh = 0.01;
143+
144+
icNumbers = find(eyeCatchSimilarity > 0.85 & ~isEye);
145+
146+
wname = 'cmor1-1.5';
147+
T = 1/EEG.srate;
148+
frequencyRange = [1 15];
149+
numberOfFrequencies = 20;
150+
[scales, freqs] = freq2scales(frequencyRange(1), frequencyRange(2), numberOfFrequencies, wname, T);
151+
152+
if isempty(EEG.icaact)
153+
if isempty(EEG.icachansind)
154+
EEG.icachansind = 1:size(EEG.data,1);
155+
end;
156+
EEG.icaact = EEG.icaweights * EEG.icasphere * EEG.data(EEG.icachansind,:);
157+
end;
158+
159+
powerRatioTooHigh = false(length(icNumbers), size(EEG.data,2));
160+
161+
for i=1:length(icNumbers)
162+
163+
tfdecomposition = cwt(EEG.icaact(icNumbers(i),:)',scales, wname);
164+
165+
tfdecomposition = abs(tfdecomposition);
166+
powerRatio = sum(tfdecomposition(freqs < criticalFreq,:).^2) ./ sum(tfdecomposition(freqs >= criticalFreq,:).^2);
167+
powerRatioTooHigh(i,:) = powerRatio > powerRatioThreshold;
168+
end;
169+
170+
eyeMovementICs = mean(powerRatioTooHigh,2) > timeRatioOfPowerRatioTooHigh; % more than 1% of time have a very high low-frequency activity
171+
if any(eyeMovementICs)
172+
fprintf('Found %d additional eye movement ICs: %s\n', sum(eyeMovementICs), ...
173+
strjoin_adjoiner_first(', ', arrayfun(@num2str, find(eyeMovementICs), 'UniformOutput', false)));
174+
end;
175+
isEye(icNumbers(eyeMovementICs)) = true;
176+
end;
177+
end
178+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
function icSubjectAndGroup = get_ic_subject_and_group(STUDY, icId)
2+
3+
if nargin < 2
4+
icId = 1:size(STUDY.cluster(1).sets,2);
5+
end;
6+
7+
icDatasetId = STUDY.cluster(1).sets(1,icId);
8+
icDatasetIdAllConditions = STUDY.cluster(1).sets(:,icId);
9+
10+
% find subject names and groups
11+
icSubjectName = {};
12+
icGroupName = {};
13+
for i=1:length(icDatasetId)
14+
icSubjectName{i} = STUDY.datasetinfo(icDatasetId(i)).subject;
15+
icGroupName{i} = STUDY.datasetinfo(icDatasetId(i)).group;
16+
end;
17+
18+
uniqueSubjectName = unique(icSubjectName);
19+
icSubjectNumber = zeros(1, length(icSubjectName));
20+
for i=1:length(icSubjectName)
21+
icSubjectNumber(i) = find(strcmp(uniqueSubjectName, icSubjectName{i}));
22+
end;
23+
24+
25+
% group names should be strings, if they are numerical they are converted
26+
% here to strings
27+
for i=1:length(icGroupName)
28+
if isnumeric(icGroupName{i})
29+
icGroupName{i} = num2str(icGroupName{i});
30+
end;
31+
end;
32+
33+
uniqueSubjectGroup = unique(icGroupName);
34+
icGroupNumber = zeros(1, length(icGroupName));
35+
for i=1:length(icGroupName)
36+
icGroupNumber(i) = find(strcmp(uniqueSubjectGroup, icGroupName{i}));
37+
end;
38+
39+
icSubjectAndGroup.icDatasetId = icDatasetId;
40+
icSubjectAndGroup.icDatasetIdAllConditions = icDatasetIdAllConditions;
41+
icSubjectAndGroup.icSubjectName = icSubjectName;
42+
icSubjectAndGroup.icGroupName = icGroupName;
43+
icSubjectAndGroup.icGroupNumber = icGroupNumber;
44+
icSubjectAndGroup.icSubjectNumber = icSubjectNumber;
45+
46+
icSubjectAndGroup.uniqueSubjectName = uniqueSubjectName;
47+
icSubjectAndGroup.uniqueSubjectGroup = uniqueSubjectGroup;
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)