-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathtopoimpedance.m
394 lines (346 loc) · 13.6 KB
/
topoimpedance.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
function figHandle = topoimpedance(Values, loc_file, varargin)
% topoimpedance() - plot electrode locations with impedance circles
% Usage:
% >> topoimpedance(impedance_values, EEG.chanlocs);
% >> topoimpedance(impedance_values, EEG.chanlocs, 'update', figure_handle);
% Inputs:
% Values - vector of impedance values for each electrode
% loc_file - EEG.chanlocs structure or location file
% Optional inputs:
% 'electrodes' - 'on'|'off'|'labels'|'numbers' {default: 'on'}
% 'headrad' - [0.15<=float<=1.0] head radius {default: 0.5}
% 'plotrad' - [0.15<=float<=1.0] plotting radius {default: 0.5}
% 'nosedir' - ['+X'|'-X'|'+Y'|'-Y'] direction of nose {default: '+X'}
% 'verbose' - ['on'|'off'] {default: 'off'}
% 'update' - figure handle for updating existing plot
% Set defaults
ELECTRODES = 'on';
HEADCOLOR = [0 0 0];
rmax = 0.5; % actual head radius
headrad = 0.5; % head radius
CIRCGRID = 201; % number of angles to use in drawing circles
AXHEADFAC = 1.3; % head to axes scaling factor
HLINEWIDTH = 2; % default linewidth for head, nose, ears
DISKSIZE = 0.04; % constant size for impedance disks
DISKBORDER = 1; % border width for disks
THRESHOLD = 0.5; % threshold for red/green coloring
EFSIZE = get(0,'DefaultAxesFontSize'); % use current default fontsize for electrode labels
figHandle = [];
% Make Values persistent for callback functions
persistent CurrentImpedances;
persistent ax;
% UI parameters
persistent UI_PARAMS;
if isempty(UI_PARAMS)
UI_PARAMS = struct();
UI_PARAMS.threshold = 150;
UI_PARAMS.freq_center = 32.1; % Hz
UI_PARAMS.freq_spread = 3; % Hz
UI_PARAMS.current = 6; % nA
UI_PARAMS.show_labels = false;
UI_PARAMS.show_values = true;
end
% Parse remaining optional arguments
if ~isempty(varargin)
for i = 1:2:length(varargin)
Param = varargin{i};
Value = varargin{i+1};
if ~ischar(Param)
error('Flag arguments must be strings')
end
Param = lower(Param);
switch Param
case 'electrodes'
ELECTRODES = lower(Value);
case 'headrad'
headrad = Value;
case 'update'
figHandle = Value;
end
end
end
if isstruct(Values)
% compute tapered FFT
filtered_data = GenericButterBand(UI_PARAMS.freq_center - UI_PARAMS.freq_spread/2, ...
UI_PARAMS.freq_center + UI_PARAMS.freq_spread/2, ...
Values.srate, Values.data');
rms_val = sqrt(mean(filtered_data.^2)); % Calculate RMS
z_calc = (1e-6 * rms_val * sqrt(2) / (UI_PARAMS.current * 1e-9)) - 2200; % formula from https://openbci-stream.readthedocs.io/en/latest/notebooks/A2-electrodes_impedance.html
z_calc = max(z_calc, 0);
CurrentImpedances = z_calc/1000; % kOhm instead of Ohm
if 0
chanlocs = readlocs(loc_file);
for chanIndex = 1:length(CurrentImpedances)
fprintf('Channel %s: %1.1f kOhm\n', chanlocs(chanIndex).labels, CurrentImpedances(chanIndex));
end
fprintf('\n');
end
CurrentImpedances = CurrentImpedances(end:-1:1);
% z_out = max(0, z_calc); % Return 0 if calculated z is negative
if 0
tapered_data = bsxfun(@times, Values.data', hanning(size(Values.data, 2)));
fft_data = fft(tapered_data);
fft_data = fft_data(1:end/2+1, :);
fft_data = abs(fft_data).^2;
fft_data = fft_data / size(fft_data, 1);
freqs = linspace(0, Values.srate/2, size(fft_data, 1));
freqs_indices = find(freqs >= UI_PARAMS.freq_center - UI_PARAMS.freq_spread/2 & freqs <= UI_PARAMS.freq_center + UI_PARAMS.freq_spread/2);
power = fft_data(freqs_indices, :);
for freqIndex = 1:length(freqs_indices)
fprintf('Frequency %2.1f: ', freqs(freqs_indices(freqIndex)));
for chanIndex = 1:size(power, 2)
fprintf('%1.1f ', power(freqIndex, chanIndex));
end
fprintf('\n');
end
fprintf('\n');
end
end
% If this is an update call, just update the disk colors
if ~isempty(figHandle)
if ishandle(ax)
patches = findobj(ax, 'Type', 'patch');
% Update colors based on new values
for i = 1:length(patches)
if CurrentImpedances(i) > UI_PARAMS.threshold
set(patches(i), 'FaceColor', [1 0 0]); % red for high impedance
else
set(patches(i), 'FaceColor', [0 1 0]); % green for low impedance
end
end
% Update text visibility
text_objects = findobj(ax, 'Type', 'text');
for i = 1:length(text_objects)
if ~isempty(text_objects(i).UserData)
% check if values are visible and update them
if strcmp(text_objects(i).UserData, 'value')
text_objects(i).Visible = UI_PARAMS.show_values;
text_objects(i).String = sprintf('%.1f', CurrentImpedances((i+1)/2));
end
end
end
else
figHandle = [];
end
return; % if ax is empty, the figure has been closed
end
% Create figure with UI panel
figHandle = figure('Position', [100 100 900 600], 'MenuBar', 'none', 'ToolBar', 'none', 'Name', 'Impedance', 'NumberTitle', 'off', ...
'CloseRequestFcn', 'delete(gcbf); clear topoimpedance;');
set(figHandle, 'Color', [0.94 0.94 0.94]);
% Create UI panel
panel = uipanel('Title', 'Parameters', 'Position', [0.02 0.02 0.20 0.96]);
% Threshold slider
uicontrol('Parent', panel, 'Style', 'text', 'String', 'Threshold:', ...
'Position', [10 530 150 20]);
uicontrol('Parent', panel, 'Style', 'slider', ...
'Position', [10 510 150 20], ...
'Min', 0, 'Max', 1000, 'Value', UI_PARAMS.threshold, ...
'Callback', @updateThreshold);
threshold_text = uicontrol('Parent', panel, 'Style', 'edit', ...
'String', sprintf('%.2f', UI_PARAMS.threshold), ...
'Position', [10 490 150 20], ...
'Callback', @updateThresholdFromText, 'enable', 'off');
% Frequency center
uicontrol('Parent', panel, 'Style', 'text', 'String', 'Freq Center (Hz):', ...
'Position', [10 450 150 20], 'enable', 'off');
uicontrol('Parent', panel, 'Style', 'edit', ...
'Position', [10 430 150 20], ...
'String', num2str(UI_PARAMS.freq_center), ...
'Callback', @updateFreqCenter, 'enable', 'off');
% Frequency spread
uicontrol('Parent', panel, 'Style', 'text', 'String', 'Freq Spread (Hz):', ...
'Position', [10 400 150 20]);
uicontrol('Parent', panel, 'Style', 'edit', ...
'Position', [10 380 150 20], ...
'String', num2str(UI_PARAMS.freq_spread), ...
'Callback', @updateFreqSpread, 'enable', 'off');
% Current
uicontrol('Parent', panel, 'Style', 'text', 'String', 'Current (nA):', ...
'Position', [10 350 150 20]);
uicontrol('Parent', panel, 'Style', 'edit', ...
'Position', [10 330 150 20], ...
'String', num2str(UI_PARAMS.current), ...
'Callback', @updateCurrent, 'enable', 'off');
% Show labels checkbox
uicontrol('Parent', panel, 'Style', 'checkbox', ...
'Position', [10 300 150 20], ...
'String', 'Show Labels', ...
'Value', UI_PARAMS.show_labels, ...
'Callback', @toggleLabels);
% Show values checkbox
uicontrol('Parent', panel, 'Style', 'checkbox', ...
'Position', [10 270 150 20], ...
'String', 'Show Values', ...
'Value', UI_PARAMS.show_values, ...
'Callback', @toggleValues);
% Create axes for the plot
ax = axes('Position', [0.3 0.1 0.65 0.8]);
% Read channel locations
if ischar(loc_file) || isstruct(loc_file)
[~, labels, Th, Rd] = readlocs(loc_file);
else
error('loc_file must be a EEG.locs struct or locs filename');
end
% Convert to radians and get coordinates
Th = pi/180*Th;
[x,y] = pol2cart(Th,Rd);
% Apply default -90 degree rotation to match topoplot.m
allcoords = (y + x*sqrt(-1))*exp(-sqrt(-1)*pi/2);
x = -imag(allcoords);
y = real(allcoords);
% Set up the plot
axes(ax);
cla
hold on
% Add DISKSIZE to the limits to show full disks
set(gca,'Xlim',[-rmax-DISKSIZE rmax+DISKSIZE]*AXHEADFAC,'Ylim',[-rmax-DISKSIZE rmax+DISKSIZE]*AXHEADFAC);
axis square
axis off
% Draw head outline
circ = linspace(0,2*pi,CIRCGRID);
rx = sin(circ);
ry = cos(circ);
headx = [rx(:)' rx(1)]*headrad;
heady = [ry(:)' ry(1)]*headrad;
plot(headx,heady,'color',HEADCOLOR,'linewidth',HLINEWIDTH);
% Draw nose
base = rmax-.0046;
basex = 0.18*rmax;
tip = 1.15*rmax;
tiphw = .04*rmax;
tipr = .01*rmax;
plot([basex;tiphw;0;-tiphw;-basex],[base;tip-tipr;tip;tip-tipr;base],...
'Color',HEADCOLOR,'LineWidth',HLINEWIDTH);
% Draw ears
q = .04;
EarX = [.497-.005 .510 .518 .5299 .5419 .54 .547 .532 .510 .489-.005];
EarY = [q+.0555 q+.0775 q+.0783 q+.0746 q+.0555 -.0055 -.0932 -.1313 -.1384 -.1199];
plot(EarX,EarY,'color',HEADCOLOR,'LineWidth',HLINEWIDTH);
plot(-EarX,EarY,'color',HEADCOLOR,'LineWidth',HLINEWIDTH);
% Plot impedance circles
if ~isempty(CurrentImpedances)
for i = 1:length(x)
% Draw circle with constant size
circ = linspace(0,2*pi,32);
circle_x = x(i) + DISKSIZE*cos(circ);
circle_y = y(i) + DISKSIZE*sin(circ);
% Choose color based on threshold
if CurrentImpedances(i) > UI_PARAMS.threshold
diskcolor = [1 0 0]; % red for high impedance
else
diskcolor = [0 1 0]; % green for low impedance
end
% Draw filled circle with border
patch(circle_x, circle_y, diskcolor, 'EdgeColor', HEADCOLOR, ...
'LineWidth', DISKBORDER);
% Add electrode label
h = text(x(i),y(i),labels{i},'HorizontalAlignment','center',...
'VerticalAlignment','middle','Color',HEADCOLOR,...
'FontSize',EFSIZE);
set(h, 'UserData', 'label');
set(h, 'Visible', UI_PARAMS.show_labels);
% Add impedance value
value_str = sprintf('%.1f', CurrentImpedances(i));
h = text(x(i),y(i),value_str,...
'HorizontalAlignment','center',...
'VerticalAlignment','middle','Color',HEADCOLOR,...
'FontSize',EFSIZE);
set(h, 'UserData', 'value');
set(h, 'Visible', UI_PARAMS.show_values);
end
end
hold off
% Callback functions
function updateThreshold(source, ~)
UI_PARAMS.threshold = source.Value;
% Update threshold text display
threshold_text = findobj(source.Parent, 'Style', 'edit', 'Position', [10 490 150 20]);
set(threshold_text, 'String', sprintf('%.2f', UI_PARAMS.threshold));
% Update colors
patches = findobj(ax, 'Type', 'patch');
for i = 1:length(patches)
if CurrentImpedances(i) > UI_PARAMS.threshold
set(patches(i), 'FaceColor', [1 0 0]);
else
set(patches(i), 'FaceColor', [0 1 0]);
end
end
end
function updateThresholdFromText(source, ~)
% Get the new value from the text field
new_value = str2double(source.String);
% Validate the input
if isnan(new_value) || new_value < 0 || new_value > 1
% If invalid, revert to previous value
set(source, 'String', sprintf('%.2f', UI_PARAMS.threshold));
return;
end
% Update the threshold value
UI_PARAMS.threshold = new_value;
% Update the slider
slider = findobj(source.Parent, 'Style', 'slider', 'Position', [10 510 150 20]);
set(slider, 'Value', new_value);
% Update colors
patches = findobj(ax, 'Type', 'patch');
for i = 1:length(patches)
if CurrentImpedances(i) > UI_PARAMS.threshold
set(patches(i), 'FaceColor', [1 0 0]);
else
set(patches(i), 'FaceColor', [0 1 0]);
end
end
end
function updateFreqCenter(source, ~)
UI_PARAMS.freq_center = str2double(source.String);
% Add your frequency update logic here
end
function updateFreqSpread(source, ~)
UI_PARAMS.freq_spread = str2double(source.String);
% Add your frequency spread update logic here
end
function updateCurrent(source, ~)
UI_PARAMS.current = str2double(source.String);
% Add your current update logic here
end
function toggleLabels(source, ~)
UI_PARAMS.show_labels = source.Value;
if UI_PARAMS.show_labels
% Uncheck values checkbox
values_checkbox = findobj(source.Parent, 'Style', 'checkbox', 'String', 'Show Values');
set(values_checkbox, 'Value', 0);
UI_PARAMS.show_values = false;
end
% Update labels visibility
text_objects = findobj(ax, 'Type', 'text');
for i = 1:length(text_objects)
if ~isempty(text_objects(i).UserData)
if strcmp(text_objects(i).UserData, 'label')
text_objects(i).Visible = UI_PARAMS.show_labels;
elseif strcmp(text_objects(i).UserData, 'value')
text_objects(i).Visible = UI_PARAMS.show_values;
end
end
end
end
function toggleValues(source, ~)
UI_PARAMS.show_values = source.Value;
if UI_PARAMS.show_values
% Uncheck labels checkbox
labels_checkbox = findobj(source.Parent, 'Style', 'checkbox', 'String', 'Show Labels');
set(labels_checkbox, 'Value', 0);
UI_PARAMS.show_labels = false;
end
% Update values visibility
text_objects = findobj(ax, 'Type', 'text');
for i = 1:length(text_objects)
if ~isempty(text_objects(i).UserData)
if strcmp(text_objects(i).UserData, 'label')
text_objects(i).Visible = UI_PARAMS.show_labels;
elseif strcmp(text_objects(i).UserData, 'value')
text_objects(i).Visible = UI_PARAMS.show_values;
end
end
end
end
end