Skip to content

Commit 45d7c1d

Browse files
authored
Enhance the LF viewer and adding more options from the keyboard (#343)
1 parent 4e99bd5 commit 45d7c1d

1 file changed

Lines changed: 200 additions & 49 deletions

File tree

toolbox/gui/view_leadfields.m

Lines changed: 200 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
% =============================================================================@
2727
%
2828
% Authors: John Mosher, Takfarinas Medani, Francois Tadel, 2020
29-
29+
% Juan Garcia-Prieto : add logarithmic scale for LF vectors
3030

3131
%% ===== PARSE INPUTS =====
3232
if ischar(HeadmodelFiles)
@@ -149,12 +149,17 @@
149149

150150
%% ===== DISPLAY LEADFIELD =====
151151
% Current sensor
152+
useLogScale = false;
153+
useLogScaleLegendMsg = 'Off';
152154
iChannel = 1;
155+
% initial value for the quiver display
156+
quiverSize = 1;
157+
quiverWidth = 1;
158+
thresholdAmplitude = 1; % ratio of the amplitude
159+
thresholdBalance = 0; % orientation of the threshold "<" or " >"
153160
DrawArrows();
154161
bst_progress('stop');
155162

156-
157-
158163
%% =================================================================================
159164
% === INTERNAL CALLBACKS ==========================================================
160165
% =================================================================================
@@ -163,34 +168,90 @@
163168
function KeyPress_Callback(hFig, keyEvent)
164169
switch (keyEvent.Key)
165170
% === LEFT, RIGHT, PAGEUP, PAGEDOWN : Processed by TimeWindow ===
166-
case {'leftarrow', 'space', 'uparrow'}
167-
iChannel = iChannel - 1;
168-
case 'pagedown'
169-
iChannel = iChannel - 10;
170-
case {'rightarrow', 'downarrow'}
171-
iChannel = iChannel + 1;
172-
case 'pageup'
173-
iChannel = iChannel + 10;
171+
case {'leftarrow',}
172+
if ismember('shift', keyEvent.Modifier)
173+
quiverSize = quiverSize /1.2;
174+
elseif ismember('control', keyEvent.Modifier)
175+
quiverWidth = quiverWidth /1.2;
176+
elseif ismember('alt', keyEvent.Modifier)
177+
thresholdAmplitude = thresholdAmplitude - 0.01;
178+
else
179+
iChannel = iChannel - 1;
180+
end
181+
case {'rightarrow'}
182+
if ismember('shift', keyEvent.Modifier)
183+
quiverSize = quiverSize * 1.2;
184+
elseif ismember('control', keyEvent.Modifier)
185+
quiverWidth = quiverWidth * 1.2;
186+
elseif ismember('alt', keyEvent.Modifier)
187+
thresholdAmplitude = thresholdAmplitude + 0.01;
188+
else
189+
iChannel = iChannel + 1;
190+
end
191+
case 'uparrow'
192+
if ismember('shift', keyEvent.Modifier)
193+
quiverSize = quiverSize * 1.2;
194+
elseif ismember('control', keyEvent.Modifier)
195+
quiverWidth = quiverWidth * 1.2;
196+
elseif ismember('alt', keyEvent.Modifier)
197+
thresholdAmplitude = thresholdAmplitude + 0.01;
198+
else
199+
if ~isempty(iRef)
200+
iRef = iRef + 1;
201+
end
202+
end
203+
case 'downarrow'
204+
if ismember('shift', keyEvent.Modifier)
205+
quiverSize = quiverSize / 1.2;
206+
elseif ismember('control', keyEvent.Modifier)
207+
quiverWidth = quiverWidth / 1.2;
208+
elseif ismember('alt', keyEvent.Modifier)
209+
thresholdAmplitude = thresholdAmplitude - 0.01;
210+
else
211+
if ~isempty(iRef)
212+
iRef = iRef - 1;
213+
end
214+
end
174215
case 'r' %% not for MEG
175216
SelectReference();
217+
case 't' %% not for MEG
218+
SelectTarget();
176219
case 's'
177220
if ~isempty(findobj(hFig, 'Tag', 'SetVertices'))
178221
delete(findobj(hFig, 'Tag', 'SetVertices'))
179222
else
180-
hold on;
181223
plot3(HeadmodelMat{1}.GridLoc(:,1), HeadmodelMat{1}.GridLoc(:,2), HeadmodelMat{1}.GridLoc(:,3), 'r.', ...
182224
'Parent', hAxes, ...
183225
'Tag', 'SetVertices');
184226
end
185227
case 'e'
186-
hold on;
187-
% Plot sensors
188-
if ~isempty(findobj(hAxes, 'Tag', 'allChannel'))
189-
delete(findobj(hAxes, 'Tag', 'allChannel'))
228+
if ~ismember('shift', keyEvent.Modifier)
229+
% Plot sensors
230+
if ~isempty(findobj(hAxes, 'Tag', 'allChannel'))
231+
delete(findobj(hAxes, 'Tag', 'allChannel'))
232+
else
233+
if length(Channels) > 10
234+
hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0);
235+
set(hSensors, 'LineWidth', 1, 'MarkerSize', 5,'Tag','allChannel');
236+
end
237+
end
190238
else
191-
if length(Channels) > 10
192-
hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0);
193-
set(hSensors, 'LineWidth', 1, 'MarkerSize', 5,'Tag','allChannel');
239+
% Plot sensors name
240+
if ~isempty(findobj(hAxes, 'Tag', 'allChannelName'))
241+
delete(findobj(hAxes, 'Tag', 'allChannelName'))
242+
else
243+
if length(Channels) > 10
244+
%hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0);
245+
%set(hSensors,'Tag','allChannelName');
246+
channelAllName = cell(length(Channels),1);
247+
for iChan = 1 : length(Channels)
248+
channelAllName{iChan} = Channels(iChan).Name;
249+
end
250+
text(markersLocs(:,1), markersLocs(:,2), markersLocs(:,3),channelAllName,...
251+
'color','y',...
252+
'Parent', hAxes, ...
253+
'Tag', 'allChannelName');
254+
end
194255
end
195256
end
196257
case 'm'
@@ -201,18 +262,54 @@ function KeyPress_Callback(hFig, keyEvent)
201262
if ~isempty(findobj(hAxes, 'Tag', 'allChannel'))
202263
delete(findobj(hAxes, 'Tag', 'allChannel'))
203264
end
265+
266+
case 'l'
267+
if ismember('shift', keyEvent.Modifier)
268+
useLogScale = ~useLogScale;
269+
if (useLogScale)
270+
useLogScaleLegendMsg = 'On';
271+
else
272+
useLogScaleLegendMsg = 'Off';
273+
end
274+
end
275+
276+
case 'return'
277+
if ismember('shift', keyEvent.Modifier)
278+
useLogScale = ~useLogScale;
279+
if (useLogScale)
280+
useLogScaleLegendMsg = 'On';
281+
else
282+
useLogScaleLegendMsg = 'Off';
283+
end
284+
elseif ismember('alt', keyEvent.Modifier)
285+
thresholdBalance = ~thresholdBalance;
286+
else
287+
return;
288+
end
204289
case 'h'
205290
java_dialog('msgbox', ['<HTML><TABLE>' ...
206-
'<TR><TD><B>Left arrow</B></TD><TD>Previous channel</TD></TR>' ....
207-
'<TR><TD><B>Right arrow</B></TD><TD>Next channel</TD></TR>'....
208-
'<TR><TD><B>Page up</B></TD><TD>Previous 10th channel</TD></TR>'....
209-
'<TR><TD><B>Page down</B></TD><TD>Next 10th channel</TD></TR>'....
291+
'<TR><TD><B>Left arrow</B></TD><TD>Previous target channel (red color)</TD></TR>' ....
292+
'<TR><TD><B>Right arrow</B></TD><TD>Next target channel (red color)</TD></TR>'....
293+
'<TR><TD><B>Up arrow</B></TD><TD>Previous ref channel (green color)</TD></TR>'....
294+
'<TR><TD><B>Down arrow</B></TD><TD>Next ref channel (green color)</TD></TR>'....
295+
'<TR><TD><B>Shift + uparrow</B></TD><TD>Increase the vector length</TD></TR>'...
296+
'<TR><TD><B>Shift + downarrow</B></TD><TD>Decrease the vector length</TD></TR>'...
297+
'<TR><TD><B>Shift + L</B></TD><TD>Toggle on/off logarithmic scale</TD></TR>'...
298+
'<TR><TD><B>Control + uparrow</B></TD><TD>Increase the vector width</TD></TR>'...
299+
'<TR><TD><B>Control + downarrow</B></TD><TD>Decrease the vector width</TD></TR>'...
300+
'<TR><TD><B>Alt + Enter </B></TD><TD>Toggle to superior/inferior for LF threshold</TD></TR>'...
301+
'<TR><TD><B>Alt + uparrow </B></TD><TD>Increase Amplitude threshold</TD></TR>'...
302+
'<TR><TD><B>Alt + downarrow </B></TD><TD>Decrease Amplitude threshold</TD></TR>'...
210303
'<TR><TD><B>M</B></TD><TD>Change the <B>M</B>odality (MEG, EEG, SEEG, ECOG)</TD></TR>'....
211-
'<TR><TD><B>R</B></TD><TD>Change the <B>R</B>eference electrode</TD></TR>'....
304+
'<TR><TD><B>R</B></TD><TD>Select the <B>R</B>eference channel</TD></TR>'....
305+
'<TR><TD><B>T</B></TD><TD>Select the <B>T</B>arget channel</TD></TR>'....
212306
'<TR><TD><B>S</B></TD><TD>Show/hide the source grid</TR>'....
213-
'<TR><TD><B>E</B></TD><TD>Show/hide the sensors</TD></TR></TABLE>'], 'Keyboard shortcuts');
307+
'<TR><TD><B>E</B></TD><TD>Show/hide the sensors</TD></TR>'...
308+
'<TR><TD><B>Shift + E</B></TD><TD>Show/hide the sensors labels</TD></TR>'...
309+
'<TR><TD><B>0 to 9</B></TD><TD>Change view</TD></TR>'...
310+
'</TABLE>'], 'Keyboard shortcuts');
214311
otherwise
215-
KeyPressFcn_bak(hQuiver, keyEvent);
312+
KeyPressFcn_bak(hFig, keyEvent);
216313
return;
217314
end
218315
% Redraw arrows
@@ -222,10 +319,24 @@ function KeyPress_Callback(hFig, keyEvent)
222319
if (iChannel > length(Channels))
223320
iChannel = 1;
224321
end
322+
% Redraw arrows
323+
if (iRef <= 0)
324+
iRef = length(Channels);
325+
end
326+
if (iRef > length(Channels))
327+
iRef = 1;
328+
end
329+
330+
if thresholdAmplitude <= 0
331+
thresholdAmplitude = 0;
332+
end
333+
if thresholdAmplitude >= 1
334+
thresholdAmplitude = 1;
335+
end
336+
225337
DrawArrows();
226338
end
227339

228-
229340
%% ===== DRAW CURRENT CHANNEL =====
230341
function DrawArrows()
231342
% Delete previous Channels and sensors
@@ -255,17 +366,39 @@ function DrawArrows()
255366
end
256367
% Display arrows
257368
LeadField = reshape(LeadField,3,[])'; % each column is a vector
369+
if(useLogScale)
370+
LeadField = LogScaleLeadfield(LeadField);
371+
end
372+
373+
% thresholding
374+
normLF = sqrt((LeadField(:,1) ).^2 +(LeadField(:,2) ).^2 + (LeadField(:,3)).^2);
375+
[col1, ind] = sort(normLF, 'ascend');
376+
LeadFieldReordered = LeadField(ind,:);
377+
cdf = cumsum(col1); % Compute cdf
378+
cdf = cdf/cdf(end); % Normalize
379+
% Find index bellow or above the thresholding
380+
if thresholdBalance == 0 % 0 ==> inferior and 1 is superior
381+
index = find(cdf <= thresholdAmplitude);
382+
iSymbole = '<=';
383+
else
384+
index = find(cdf > thresholdAmplitude);
385+
iSymbole = '>';
386+
end
387+
dataValue = zeros(size(LeadFieldReordered));
388+
dataValue(index,:) = LeadFieldReordered(index,:);
389+
258390
hQuiver(iLF) = quiver3(...
259-
HeadmodelMat{iLF}.GridLoc(:,1), HeadmodelMat{iLF}.GridLoc(:,2), HeadmodelMat{iLF}.GridLoc(:,3), ...
260-
LeadField(:,1), LeadField(:,2), LeadField(:,3), ...
261-
5, ...
391+
...HeadmodelMat{iLF}.GridLoc(:,1), HeadmodelMat{iLF}.GridLoc(:,2), HeadmodelMat{iLF}.GridLoc(:,3), ... % These two line are remaining in order to check if the thresholding display is correct
392+
...LeadField(:,1), LeadField(:,2), LeadField(:,3), ...
393+
HeadmodelMat{iLF}.GridLoc(ind,1), HeadmodelMat{iLF}.GridLoc(ind,2), HeadmodelMat{iLF}.GridLoc(ind,3), ...
394+
dataValue(:,1), dataValue(:,2), dataValue(:,3), ...
395+
quiverSize, ...
262396
'Parent', hAxes, ...
263-
'LineWidth', 1, ...
397+
'LineWidth', quiverWidth, ...
264398
'Color', ColorOrder(mod(iLF-1, length(ColorOrder)) + 1, :), ...
265399
'Tag', 'lfArrows');
266400
% Arrow legends
267401
strLegend{iLF} = [SubjectName{iLF} ' : ' selectedModality ' ' HeadmodelMat{iLF}.Comment];
268-
hold on
269402
end
270403

271404
% Remove previous selected sensor
@@ -299,18 +432,18 @@ function DrawArrows()
299432
end
300433
% Title bar (channel name)
301434
if isAvgRef
302-
strTitle = sprintf('Channel #%d/%d (%s) | %s ref Channel = AvgRef', iChannel, length(Channels), Channels(iChannel).Name,selectedModality);
435+
strTitle = sprintf('Target channel(red) #%d/%d (%s) | %s Ref Channel(green) = AvgRef | Amp threshold %s %s %%| Log. scale %s', iChannel, length(Channels), Channels(iChannel).Name,selectedModality, iSymbole, num2str(thresholdAmplitude*100),useLogScaleLegendMsg);
303436
else
304-
strTitle = sprintf('Channel #%d/%d (%s) | %s ref Channel = %s', iChannel, length(Channels), Channels(iChannel).Name,selectedModality,Channels(iRef).Name);
437+
strTitle = sprintf('Target channel(red) #%d/%d (%s) | %s Ref Channel(green) = %s| Amp threshold %s %s %%| Log. scale %s', iChannel, length(Channels), Channels(iChannel).Name,selectedModality,Channels(iRef).Name, iSymbole, num2str(thresholdAmplitude*100),useLogScaleLegendMsg);
305438
end
306439
else
307-
strTitle = sprintf('Channel #%d/%d (%s)', iChannel, length(Channels), Channels(iChannel).Name);
440+
strTitle = sprintf('Target channel (red) #%d/%d (%s) | Amp threshold %s %s %%|Log. scale %s', iChannel, length(Channels), Channels(iChannel).Name, iSymbole,num2str(thresholdAmplitude*100),useLogScaleLegendMsg);
308441
end
309442

310443
if (iChannel == 1) && (length(Channels) > 1)
311444
strTitle = [strTitle, ' [Press arrows for next/previous channel (or H for help)]'];
312445
end
313-
set(hLabel, 'String', strTitle, 'Position', [10 1 1200 35]);
446+
set(hLabel, 'String', strTitle, 'Position', [10 1 1600 35],'ForegroundColor', [1 1 1]);
314447
% Arrows legend
315448
legend(hQuiver, strLegend, ...
316449
'TextColor', 'w', ...
@@ -351,26 +484,32 @@ function DrawArrows()
351484
function isOk = SelectReference()
352485
isOk = 1;
353486
if ~strcmp(selectedModality,'MEG')
354-
[isAvgRef, isCancel] = java_dialog('confirm', ...
355-
['<HTML>Do you want to use the <B>average refence</B> for the ' selectedModality ' ?<BR>'...
356-
'Otherwise you will choose one reference electrode.'], [selectedModality ' average reference'], [], ...
357-
{'Yes, use average reference'}, 1);
358-
if isCancel
487+
% Ask for the reference electrode
488+
refChan = java_dialog('combo', '<HTML>Select the reference channel (green color):<BR><BR>', [selectedModality ' reference'], [], {'Average Ref', Channels.Name});
489+
if isempty(refChan)
359490
isOk = 0;
360491
return;
361492
end
362-
if ~isAvgRef
363-
% Ask for the reference electrode
364-
refChan = java_dialog('combo', '<HTML>Select the reference channel:<BR><BR>', [selectedModality ' reference'], [], {Channels.Name});
365-
if isempty(refChan)
366-
isOk = 0;
367-
return;
368-
end
369-
iRef = find(strcmpi({Channels.Name}, refChan));
493+
iRef = find(strcmpi({Channels.Name}, refChan));
494+
if isempty(iRef)
495+
isAvgRef = 1;
496+
else
497+
isAvgRef = 0;
370498
end
371499
end
372500
end
373501

502+
%% ===== SET TARGET =====
503+
function isOk = SelectTarget()
504+
isOk = 1;
505+
% Ask for the target electrode
506+
trgChan = java_dialog('combo', '<HTML>Select the target channel (red color):<BR><BR>', [selectedModality ' Target'], [], {Channels.Name});
507+
if isempty(trgChan)
508+
isOk = 0;
509+
return;
510+
end
511+
iChannel = find(strcmpi({Channels.Name}, trgChan));
512+
end
374513

375514
%% ===== GET LEADFIELD =====
376515
function GetLeadField
@@ -379,4 +518,16 @@ function DrawArrows()
379518
LF_finale{iLF} = HeadmodelMat{iLF}.Gain(iChannels,:);
380519
end
381520
end
521+
522+
%% ===== LEADFIELD TO LOG SPACE =====
523+
function lf_log = LogScaleLeadfield(lf)
524+
lf_2 = lf.^2;
525+
r = sqrt(sum(lf_2,2));
526+
rho = sqrt(lf_2(:,1) + lf_2(:,2));
527+
t = atan2(rho,lf(:,3));
528+
f = atan2(lf(:,2),lf(:,1));
529+
lf_log = [ log10(r) .* sin(t) .* cos(f) ...
530+
log10(r) .* sin(t) .* sin(f) ...
531+
log10(r) .* cos(t)];
532+
end
382533
end

0 commit comments

Comments
 (0)