diff --git a/toolbox/gui/view_leadfields.m b/toolbox/gui/view_leadfields.m index 07aaea2ad..6849d45d1 100644 --- a/toolbox/gui/view_leadfields.m +++ b/toolbox/gui/view_leadfields.m @@ -26,7 +26,7 @@ % =============================================================================@ % % Authors: John Mosher, Takfarinas Medani, Francois Tadel, 2020 - +% Juan Garcia-Prieto : add logarithmic scale for LF vectors %% ===== PARSE INPUTS ===== if ischar(HeadmodelFiles) @@ -149,12 +149,17 @@ %% ===== DISPLAY LEADFIELD ===== % Current sensor +useLogScale = false; +useLogScaleLegendMsg = 'Off'; iChannel = 1; +% initial value for the quiver display +quiverSize = 1; +quiverWidth = 1; +thresholdAmplitude = 1; % ratio of the amplitude +thresholdBalance = 0; % orientation of the threshold "<" or " >" DrawArrows(); bst_progress('stop'); - - %% ================================================================================= % === INTERNAL CALLBACKS ========================================================== % ================================================================================= @@ -163,34 +168,90 @@ function KeyPress_Callback(hFig, keyEvent) switch (keyEvent.Key) % === LEFT, RIGHT, PAGEUP, PAGEDOWN : Processed by TimeWindow === - case {'leftarrow', 'space', 'uparrow'} - iChannel = iChannel - 1; - case 'pagedown' - iChannel = iChannel - 10; - case {'rightarrow', 'downarrow'} - iChannel = iChannel + 1; - case 'pageup' - iChannel = iChannel + 10; + case {'leftarrow',} + if ismember('shift', keyEvent.Modifier) + quiverSize = quiverSize /1.2; + elseif ismember('control', keyEvent.Modifier) + quiverWidth = quiverWidth /1.2; + elseif ismember('alt', keyEvent.Modifier) + thresholdAmplitude = thresholdAmplitude - 0.01; + else + iChannel = iChannel - 1; + end + case {'rightarrow'} + if ismember('shift', keyEvent.Modifier) + quiverSize = quiverSize * 1.2; + elseif ismember('control', keyEvent.Modifier) + quiverWidth = quiverWidth * 1.2; + elseif ismember('alt', keyEvent.Modifier) + thresholdAmplitude = thresholdAmplitude + 0.01; + else + iChannel = iChannel + 1; + end + case 'uparrow' + if ismember('shift', keyEvent.Modifier) + quiverSize = quiverSize * 1.2; + elseif ismember('control', keyEvent.Modifier) + quiverWidth = quiverWidth * 1.2; + elseif ismember('alt', keyEvent.Modifier) + thresholdAmplitude = thresholdAmplitude + 0.01; + else + if ~isempty(iRef) + iRef = iRef + 1; + end + end + case 'downarrow' + if ismember('shift', keyEvent.Modifier) + quiverSize = quiverSize / 1.2; + elseif ismember('control', keyEvent.Modifier) + quiverWidth = quiverWidth / 1.2; + elseif ismember('alt', keyEvent.Modifier) + thresholdAmplitude = thresholdAmplitude - 0.01; + else + if ~isempty(iRef) + iRef = iRef - 1; + end + end case 'r' %% not for MEG SelectReference(); + case 't' %% not for MEG + SelectTarget(); case 's' if ~isempty(findobj(hFig, 'Tag', 'SetVertices')) delete(findobj(hFig, 'Tag', 'SetVertices')) else - hold on; plot3(HeadmodelMat{1}.GridLoc(:,1), HeadmodelMat{1}.GridLoc(:,2), HeadmodelMat{1}.GridLoc(:,3), 'r.', ... 'Parent', hAxes, ... 'Tag', 'SetVertices'); end case 'e' - hold on; - % Plot sensors - if ~isempty(findobj(hAxes, 'Tag', 'allChannel')) - delete(findobj(hAxes, 'Tag', 'allChannel')) + if ~ismember('shift', keyEvent.Modifier) + % Plot sensors + if ~isempty(findobj(hAxes, 'Tag', 'allChannel')) + delete(findobj(hAxes, 'Tag', 'allChannel')) + else + if length(Channels) > 10 + hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0); + set(hSensors, 'LineWidth', 1, 'MarkerSize', 5,'Tag','allChannel'); + end + end else - if length(Channels) > 10 - hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0); - set(hSensors, 'LineWidth', 1, 'MarkerSize', 5,'Tag','allChannel'); + % Plot sensors name + if ~isempty(findobj(hAxes, 'Tag', 'allChannelName')) + delete(findobj(hAxes, 'Tag', 'allChannelName')) + else + if length(Channels) > 10 + %hSensors = figure_3d('PlotSensorsNet', hAxes, markersLocs, 0, 0); + %set(hSensors,'Tag','allChannelName'); + channelAllName = cell(length(Channels),1); + for iChan = 1 : length(Channels) + channelAllName{iChan} = Channels(iChan).Name; + end + text(markersLocs(:,1), markersLocs(:,2), markersLocs(:,3),channelAllName,... + 'color','y',... + 'Parent', hAxes, ... + 'Tag', 'allChannelName'); + end end end case 'm' @@ -201,18 +262,54 @@ function KeyPress_Callback(hFig, keyEvent) if ~isempty(findobj(hAxes, 'Tag', 'allChannel')) delete(findobj(hAxes, 'Tag', 'allChannel')) end + + case 'l' + if ismember('shift', keyEvent.Modifier) + useLogScale = ~useLogScale; + if (useLogScale) + useLogScaleLegendMsg = 'On'; + else + useLogScaleLegendMsg = 'Off'; + end + end + + case 'return' + if ismember('shift', keyEvent.Modifier) + useLogScale = ~useLogScale; + if (useLogScale) + useLogScaleLegendMsg = 'On'; + else + useLogScaleLegendMsg = 'Off'; + end + elseif ismember('alt', keyEvent.Modifier) + thresholdBalance = ~thresholdBalance; + else + return; + end case 'h' java_dialog('msgbox', ['' ... - '' .... - ''.... - ''.... - ''.... + '' .... + ''.... + ''.... + ''.... + ''... + ''... + ''... + ''... + ''... + ''... + ''... + ''... ''.... - ''.... + ''.... + ''.... ''.... - '
Left arrowPrevious channel
Right arrowNext channel
Page upPrevious 10th channel
Page downNext 10th channel
Left arrowPrevious target channel (red color)
Right arrowNext target channel (red color)
Up arrowPrevious ref channel (green color)
Down arrowNext ref channel (green color)
Shift + uparrowIncrease the vector length
Shift + downarrowDecrease the vector length
Shift + LToggle on/off logarithmic scale
Control + uparrowIncrease the vector width
Control + downarrowDecrease the vector width
Alt + Enter Toggle to superior/inferior for LF threshold
Alt + uparrow Increase Amplitude threshold
Alt + downarrow Decrease Amplitude threshold
MChange the Modality (MEG, EEG, SEEG, ECOG)
RChange the Reference electrode
RSelect the Reference channel
TSelect the Target channel
SShow/hide the source grid
EShow/hide the sensors
'], 'Keyboard shortcuts'); + 'EShow/hide the sensors'... + 'Shift + EShow/hide the sensors labels'... + '0 to 9Change view'... + ''], 'Keyboard shortcuts'); otherwise - KeyPressFcn_bak(hQuiver, keyEvent); + KeyPressFcn_bak(hFig, keyEvent); return; end % Redraw arrows @@ -222,10 +319,24 @@ function KeyPress_Callback(hFig, keyEvent) if (iChannel > length(Channels)) iChannel = 1; end + % Redraw arrows + if (iRef <= 0) + iRef = length(Channels); + end + if (iRef > length(Channels)) + iRef = 1; + end + + if thresholdAmplitude <= 0 + thresholdAmplitude = 0; + end + if thresholdAmplitude >= 1 + thresholdAmplitude = 1; + end + DrawArrows(); end - %% ===== DRAW CURRENT CHANNEL ===== function DrawArrows() % Delete previous Channels and sensors @@ -255,17 +366,39 @@ function DrawArrows() end % Display arrows LeadField = reshape(LeadField,3,[])'; % each column is a vector + if(useLogScale) + LeadField = LogScaleLeadfield(LeadField); + end + + % thresholding + normLF = sqrt((LeadField(:,1) ).^2 +(LeadField(:,2) ).^2 + (LeadField(:,3)).^2); + [col1, ind] = sort(normLF, 'ascend'); + LeadFieldReordered = LeadField(ind,:); + cdf = cumsum(col1); % Compute cdf + cdf = cdf/cdf(end); % Normalize + % Find index bellow or above the thresholding + if thresholdBalance == 0 % 0 ==> inferior and 1 is superior + index = find(cdf <= thresholdAmplitude); + iSymbole = '<='; + else + index = find(cdf > thresholdAmplitude); + iSymbole = '>'; + end + dataValue = zeros(size(LeadFieldReordered)); + dataValue(index,:) = LeadFieldReordered(index,:); + hQuiver(iLF) = quiver3(... - HeadmodelMat{iLF}.GridLoc(:,1), HeadmodelMat{iLF}.GridLoc(:,2), HeadmodelMat{iLF}.GridLoc(:,3), ... - LeadField(:,1), LeadField(:,2), LeadField(:,3), ... - 5, ... + ...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 + ...LeadField(:,1), LeadField(:,2), LeadField(:,3), ... + HeadmodelMat{iLF}.GridLoc(ind,1), HeadmodelMat{iLF}.GridLoc(ind,2), HeadmodelMat{iLF}.GridLoc(ind,3), ... + dataValue(:,1), dataValue(:,2), dataValue(:,3), ... + quiverSize, ... 'Parent', hAxes, ... - 'LineWidth', 1, ... + 'LineWidth', quiverWidth, ... 'Color', ColorOrder(mod(iLF-1, length(ColorOrder)) + 1, :), ... 'Tag', 'lfArrows'); % Arrow legends strLegend{iLF} = [SubjectName{iLF} ' : ' selectedModality ' ' HeadmodelMat{iLF}.Comment]; - hold on end % Remove previous selected sensor @@ -299,18 +432,18 @@ function DrawArrows() end % Title bar (channel name) if isAvgRef - strTitle = sprintf('Channel #%d/%d (%s) | %s ref Channel = AvgRef', iChannel, length(Channels), Channels(iChannel).Name,selectedModality); + 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); else - strTitle = sprintf('Channel #%d/%d (%s) | %s ref Channel = %s', iChannel, length(Channels), Channels(iChannel).Name,selectedModality,Channels(iRef).Name); + 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); end else - strTitle = sprintf('Channel #%d/%d (%s)', iChannel, length(Channels), Channels(iChannel).Name); + 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); end if (iChannel == 1) && (length(Channels) > 1) strTitle = [strTitle, ' [Press arrows for next/previous channel (or H for help)]']; end - set(hLabel, 'String', strTitle, 'Position', [10 1 1200 35]); + set(hLabel, 'String', strTitle, 'Position', [10 1 1600 35],'ForegroundColor', [1 1 1]); % Arrows legend legend(hQuiver, strLegend, ... 'TextColor', 'w', ... @@ -351,26 +484,32 @@ function DrawArrows() function isOk = SelectReference() isOk = 1; if ~strcmp(selectedModality,'MEG') - [isAvgRef, isCancel] = java_dialog('confirm', ... - ['Do you want to use the average refence for the ' selectedModality ' ?
'... - 'Otherwise you will choose one reference electrode.'], [selectedModality ' average reference'], [], ... - {'Yes, use average reference'}, 1); - if isCancel + % Ask for the reference electrode + refChan = java_dialog('combo', 'Select the reference channel (green color):

', [selectedModality ' reference'], [], {'Average Ref', Channels.Name}); + if isempty(refChan) isOk = 0; return; end - if ~isAvgRef - % Ask for the reference electrode - refChan = java_dialog('combo', 'Select the reference channel:

', [selectedModality ' reference'], [], {Channels.Name}); - if isempty(refChan) - isOk = 0; - return; - end - iRef = find(strcmpi({Channels.Name}, refChan)); + iRef = find(strcmpi({Channels.Name}, refChan)); + if isempty(iRef) + isAvgRef = 1; + else + isAvgRef = 0; end end end +%% ===== SET TARGET ===== + function isOk = SelectTarget() + isOk = 1; + % Ask for the target electrode + trgChan = java_dialog('combo', 'Select the target channel (red color):

', [selectedModality ' Target'], [], {Channels.Name}); + if isempty(trgChan) + isOk = 0; + return; + end + iChannel = find(strcmpi({Channels.Name}, trgChan)); + end %% ===== GET LEADFIELD ===== function GetLeadField @@ -379,4 +518,16 @@ function DrawArrows() LF_finale{iLF} = HeadmodelMat{iLF}.Gain(iChannels,:); end end + +%% ===== LEADFIELD TO LOG SPACE ===== + function lf_log = LogScaleLeadfield(lf) + lf_2 = lf.^2; + r = sqrt(sum(lf_2,2)); + rho = sqrt(lf_2(:,1) + lf_2(:,2)); + t = atan2(rho,lf(:,3)); + f = atan2(lf(:,2),lf(:,1)); + lf_log = [ log10(r) .* sin(t) .* cos(f) ... + log10(r) .* sin(t) .* sin(f) ... + log10(r) .* cos(t)]; + end end