diff --git a/doc/updates.txt b/doc/updates.txt index 816c9414f..29dd5b5a2 100644 --- a/doc/updates.txt +++ b/doc/updates.txt @@ -4,6 +4,7 @@ January 2023 - Distrib: Enable compilation on Linux and MacOS - IO: Export matrix files as EDF+ - Inverse: Display scouts on source-level PSD as PSD figures +- Clusters: Save clusters in channel file, load automatically -------------------------------------------------------------- November 2022 - Forward: Display leadfield sensitivity (surface, MRI, isosurface) diff --git a/toolbox/core/bst_figures.m b/toolbox/core/bst_figures.m index 7c4d2d485..e15686672 100644 --- a/toolbox/core/bst_figures.m +++ b/toolbox/core/bst_figures.m @@ -53,7 +53,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2021 +% Authors: Francois Tadel, 2008-2023 % Martin Cousineau, 2017 eval(macro_method); @@ -1073,10 +1073,12 @@ function SetCurrentFigure(hFig, Type) panel_record('CurrentFigureChanged_Callback', hFig); % Update tab: Display (for raster plots/erpimage) panel_display('UpdatePanel', hFig); -% FigureId = getappdata(hFig, 'FigureId'); -% if ~isempty(FigureId) && isequal(FigureId.SubType, 'erpimage') -% panel_display('UpdatePanel', hFig); -% end + % Update list of clusters + if ~isempty(hFig) && ~isequal(oldFigType, hFig) + if gui_brainstorm('isTabVisible', 'Cluster') + panel_cluster('CurrentFigureChanged_Callback', hFig); + end + end case 'Type3D' % Only when figure changed (within the figure type) if ~isempty(hFig) && ~isequal(oldFigType, hFig) @@ -1094,6 +1096,9 @@ function SetCurrentFigure(hFig, Type) if gui_brainstorm('isTabVisible', 'iEEG') panel_ieeg('CurrentFigureChanged_Callback', hFig); end + if gui_brainstorm('isTabVisible', 'Cluster') + panel_cluster('CurrentFigureChanged_Callback', hFig); + end end case 'TypeTF' % Only when figure changed (whatever the type of the figure is) diff --git a/toolbox/core/bst_memory.m b/toolbox/core/bst_memory.m index 6ffa4c5a0..f3bbab89f 100644 --- a/toolbox/core/bst_memory.m +++ b/toolbox/core/bst_memory.m @@ -67,7 +67,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2022 +% Authors: Francois Tadel, 2008-2023 % Martin Cousineau, 2019 eval(macro_method); @@ -556,6 +556,7 @@ function LoadChannelFile(iDS, ChannelFile) % Save in DataSet structure GlobalData.DataSet(iDS).ChannelFile = file_win2unix(ChannelFile); GlobalData.DataSet(iDS).Channel = ChannelMat.Channel; + GlobalData.DataSet(iDS).Clusters = ChannelMat.Clusters; GlobalData.DataSet(iDS).IntraElectrodes = ChannelMat.IntraElectrodes; GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; GlobalData.DataSet(iDS).Projector = ChannelMat.Projector; @@ -582,8 +583,9 @@ function LoadChannelFile(iDS, ChannelFile) GlobalData.DataSet(iDS).Channel(i).Loc = [0;0;0]; GlobalData.DataSet(iDS).Channel(i).Type = 'EEG'; end - GlobalData.DataSet(iDS).MegRefCoef = []; - GlobalData.DataSet(iDS).Projector = []; + GlobalData.DataSet(iDS).MegRefCoef = []; + GlobalData.DataSet(iDS).Projector = []; + GlobalData.DataSet(iDS).Clusters = []; GlobalData.DataSet(iDS).IntraElectrodes = []; end end @@ -3204,8 +3206,6 @@ function CheckFrequencies() % Get next surface panel_scout('SetCurrentSurface', CurrentSurface); end - % Unload clusters - panel_cluster('RemoveAllClusters'); end % Empty the clipboard bst_set('Clipboard', []); @@ -3264,6 +3264,7 @@ function CheckFrequencies() gui_hide('Stat'); gui_hide('iEEG'); gui_hide('Spikes'); + gui_hide('Cluster'); end if isNewProgress bst_progress('stop'); @@ -3529,6 +3530,7 @@ function SaveChannelFile(iDS) % Get modified fields ChannelMat.Channel = GlobalData.DataSet(iDS).Channel; ChannelMat.IntraElectrodes = GlobalData.DataSet(iDS).IntraElectrodes; + ChannelMat.Clusters = GlobalData.DataSet(iDS).Clusters; % History: Edit channel file ChannelMat = bst_history('add', ChannelMat, 'edit', 'Edited manually'); % Save file diff --git a/toolbox/core/bst_navigator.m b/toolbox/core/bst_navigator.m index 0fbf621c8..40931d49d 100644 --- a/toolbox/core/bst_navigator.m +++ b/toolbox/core/bst_navigator.m @@ -395,8 +395,9 @@ function DbNavigation( action, iDataSets ) % Load new channel file ChannelMat = in_bst_channel(newChannelFile); GlobalData.DataSet(iDS).Channel = ChannelMat.Channel; - GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; + GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; GlobalData.DataSet(iDS).Projector = ChannelMat.Projector; + GlobalData.DataSet(iDS).Clusters = ChannelMat.Clusters; GlobalData.DataSet(iDS).IntraElectrodes = ChannelMat.IntraElectrodes; end end diff --git a/toolbox/db/db_template.m b/toolbox/db/db_template.m index b23e8073f..d5e39d12d 100644 --- a/toolbox/db/db_template.m +++ b/toolbox/db/db_template.m @@ -21,7 +21,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2022 +% Authors: Francois Tadel, 2008-2023 switch lower(structureName) @@ -235,6 +235,7 @@ 'Label', [], ... 'Type', []), ... 'Channel', [], ... % [nChannels] Structure array, one structure per sensor + 'Clusters', [], ... 'IntraElectrodes', [], ... 'History', []); @@ -751,6 +752,7 @@ 'Channel', repmat(db_template('ChannelDesc'), 0), ... 'MegRefCoef', [], ... 'Projector', repmat(db_template('Projector'), 0), ... + 'Clusters', repmat(db_template('Cluster'), 0), ... 'IntraElectrodes', repmat(db_template('IntraElectrode'), 0), ... 'isChannelModified', 0, ... 'HeadPoints', [], ... @@ -867,6 +869,7 @@ template = struct(... 'Sensors', '', ... % File on which the scout is defined 'Label', '', ... % Comment + 'Color', [], ... % [1x3] RGB values between 0 and 1 'Function', 'Mean'); % Cluster function: PCA, FastPCA, Mean, Max, Power, All case 'globaldata' @@ -951,7 +954,6 @@ 'hButtonTransY', [], ... 'hButtonTransZ', [], ... 'hButtonResize', [])), ... - 'Clusters', repmat(db_template('Cluster'), 0), ... 'CurrentFigure', struct(... 'Type3D', [], ... 'Type2D', [], ... diff --git a/toolbox/gui/gui_brainstorm.m b/toolbox/gui/gui_brainstorm.m index b63a1f003..552ada4b1 100644 --- a/toolbox/gui/gui_brainstorm.m +++ b/toolbox/gui/gui_brainstorm.m @@ -1190,6 +1190,8 @@ function ShowToolTab(tabTitle) panel_surface('UpdatePanel'); case 'iEEG' panel_ieeg('UpdatePanel'); + case 'Cluster' + panel_cluster('UpdatePanel'); end % Select tab SetSelectedTab(tabTitle, 0); diff --git a/toolbox/gui/panel_cluster.m b/toolbox/gui/panel_cluster.m index b1cdbdf64..3dc2e38bd 100644 --- a/toolbox/gui/panel_cluster.m +++ b/toolbox/gui/panel_cluster.m @@ -3,6 +3,7 @@ % % USAGE: bstPanelNew = panel_cluster('CreatePanel') % panel_cluster('UpdatePanel') +% panel_cluster('CurrentFigureChanged_Callback') % % @============================================================================= % This function is part of the Brainstorm software: @@ -22,7 +23,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2009-2017 +% Authors: Francois Tadel, 2009-2023 eval(macro_method); end @@ -50,18 +51,14 @@ gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_NEW_SEL, 'Create new cluster: mouse selection.
Use sensors selected in any time series or topography figure.', @(h,ev)CreateNewCluster('Selection')); gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_NEW_IND, 'Create new cluster: list of indices/names.
Type the list of sensors indices, names or types you want in the new cluster.', @(h,ev)CreateNewCluster('Indices')); gui_component('ToolbarButton', jToolbar,[],[], IconLoader.ICON_TS_DISPLAY, 'Display cluster time series    [ENTER]', @DisplayClusters); - -% % === MENU NEW === -% jMenu = gui_component('Menu', jMenuBar, [], 'New', IconLoader.ICON_MENU, [], [], 11); -% jMenu.setBorder(BorderFactory.createEmptyBorder(0,2,0,2)); -% gui_component('MenuItem', jMenu, [], 'New cluster: Use selected sensors', IconLoader.ICON_NEW_SEL, [], @(h,ev)CreateNewCluster('Selection')); -% gui_component('MenuItem', jMenu, [], 'New cluster: List of indices or names', IconLoader.ICON_NEW_IND, [], @(h,ev)CreateNewCluster('Indices')); % === MENU EDIT === jMenu = gui_component('Menu', jMenuBar, [], 'Edit', IconLoader.ICON_MENU, [], [], 11); jMenu.setBorder(BorderFactory.createEmptyBorder(0,2,0,2)); + % Set color + gui_component('MenuItem', jMenu, [], 'Set color', IconLoader.ICON_COLOR_SELECTION, [], @(h,ev)bst_call(@EditClusterColor)); % Menu: Set cluster function - jMenuTs = gui_component('Menu', jMenu, [], 'Set cluster function', IconLoader.ICON_PROPERTIES, [], []); + jMenuTs = gui_component('Menu', jMenu, [], 'Set function', IconLoader.ICON_PROPERTIES, [], []); gui_component('MenuItem', jMenuTs, [], 'Mean', [], [], @(h,ev)SetClusterFunction('Mean')); gui_component('MenuItem', jMenuTs, [], 'Mean+Std', [], [], @(h,ev)SetClusterFunction('Mean+Std')); gui_component('MenuItem', jMenuTs, [], 'Mean+StdErr', [], [], @(h,ev)SetClusterFunction('Mean+StdErr')); @@ -75,11 +72,6 @@ gui_component('MenuItem', jMenu, [], 'Remove [DEL]', IconLoader.ICON_DELETE, [], @(h,ev)RemoveClusters); gui_component('MenuItem', jMenu, [], 'Deselect all [ESC]', IconLoader.ICON_RELOAD, [], @(h,ev)SetSelectedClusters(0)); -% % === MENU VIEW === -% jMenu = gui_component('Menu', jMenuBar, [], 'View', IconLoader.ICON_MENU, [], [], 11); -% jMenu.setBorder(BorderFactory.createEmptyBorder(0,2,0,2)); -% gui_component('MenuItem', jMenu, [], 'View time series', IconLoader.ICON_TS_DISPLAY, [], @DisplayClusters); - % ===== PANEL MAIN ===== jPanelMain = java_create('javax.swing.JPanel'); jPanelMain.setLayout(BoxLayout(jPanelMain, BoxLayout.Y_AXIS)); @@ -188,52 +180,82 @@ function ListClick_Callback(h, ev) %% ================================================================================= % === EXTERNAL PANEL CALLBACKS =================================================== % ================================================================================= +%% ===== CURRENT FIGURE CHANGED ===== +function CurrentFigureChanged_Callback(hFig) + UpdatePanel(); +end + %% ===== UPDATE CALLBACK ===== -function UpdatePanel() %#ok +function UpdatePanel() % Get panel controls ctrl = bst_get('PanelControls', 'Cluster'); if isempty(ctrl) return; end -% % No raw time or no events structure: exit -% if isempty(GlobalData.DataSet) -% gui_enable([ctrl.jPanelList, ctrl.jPanelOptions, ctrl.jMenuBar, ctrl.jToolbar, ctrl.jToolbar2], 0, 1); -% return -% else -% gui_enable([ctrl.jPanelList, ctrl.jPanelOptions, ctrl.jMenuBar, ctrl.jToolbar, ctrl.jToolbar2], 1, 1); -% end - % Get current clusters - sClusters = GetClusters(); -% % If some clusters are available -% isEnable = ~isempty(sClusters); -% gui_enable([ctrl.jPanelList, ctrl.jPanelOptions, ctrl.jToolbar2], isEnable, 1); % Update clusters JList - UpdateClustersList(sClusters); + UpdateClustersList(); end %% ===== UPDATE CLUSTERS LIST ===== -function UpdateClustersList(sClusters) +function UpdateClustersList() import org.brainstorm.list.*; % Get "Cluster" panel controls ctrl = bst_get('PanelControls', 'Cluster'); if isempty(ctrl) return; end - % If clusters list was not defined : get it - if (nargin < 1) - sClusters = GetClusters(); + % Get clusters + sClusters = GetClusters(); + + % Remove temporarily the list callback + callbackBak = java_getcb(ctrl.jListClusters, 'ValueChangedCallback'); + java_setcb(ctrl.jListClusters, 'ValueChangedCallback', []); + % Get selected clusters + iSelClusters = ctrl.jListClusters.getSelectedIndex() + 1; + SelName = char(ctrl.jListClusters.getSelectedValue()); + if (iSelClusters == 0) || (iSelClusters > length(sClusters)) || ~strcmpi(sClusters(iSelClusters).Label, SelName) + SelName = []; end + % Create a new empty list listModel = java_create('javax.swing.DefaultListModel'); - % Add an item in list for each cluster found for target figure - for iCluster = 1:length(sClusters) - listModel.addElement(BstListItem(sClusters(iCluster).Function, '', sClusters(iCluster).Label, iCluster)); + % Get font with which the list is rendered + fontSize = round(11 * bst_get('InterfaceScaling') / 100); + jFont = java.awt.Font('Dialog', java.awt.Font.PLAIN, fontSize); + tk = java.awt.Toolkit.getDefaultToolkit(); + % Add an item in list for each cluster + Wmax = 0; + iSelClustersNew = []; + for i = 1:length(sClusters) + itemType = sClusters(i).Function; + itemText = sClusters(i).Label; + itemColor = sClusters(i).Color; + listModel.addElement(BstListItem(itemType, [], itemText, i, itemColor(1), itemColor(2), itemColor(3))); + % Get longest string + W = tk.getFontMetrics(jFont).stringWidth(sClusters(i).Label); + if (W > Wmax) + Wmax = W; + end + % Check if selected + if ~isempty(SelName) && strcmpi(sClusters(i).Label, SelName) + iSelClustersNew = i; + end end % Update list model ctrl.jListClusters.setModel(listModel); + % Update cell rederer based on longest channel name + ctrl.jListClusters.setCellRenderer(java_create('org.brainstorm.list.BstClusterListRenderer', 'II', fontSize, Wmax + 28)); + % Select previously selected clusters + if ~isempty(iSelClustersNew) + ctrl.jListClusters.setSelectedIndex(iSelClustersNew - 1); + end % Reset cluster comments ctrl.jLabelClusterSize.setText(''); + + % Restore callback + drawnow; + java_setcb(ctrl.jListClusters, 'ValueChangedCallback', callbackBak); end @@ -276,31 +298,97 @@ function UpdateChannelSelection() end -%% ===== GET ALL CLUSTERS ===== -% USAGE: panel_cluster('GetClusters', iClusters) -% panel_cluster('GetClusters', labels) -function [sClusters, iClusters] = GetClusters(iClusters) +%% ===== GET CLUSTERS ===== +% USAGE: [sClusters, iDSall, iFigall, hFigall] = panel_cluster('GetClusters') +function [sClusters, iDSall, iFigall, hFigall] = GetClusters() global GlobalData; - if (nargin < 1) - iClusters = 1:length(GlobalData.Clusters); - elseif ischar(iClusters) || iscell(iClusters) - if ischar(iClusters) - labels = {iClusters}; + % Get current figure + [hFigall,iFigall,iDSall] = bst_figures('GetCurrentFigure'); + % Check if there are electrodes defined for this file + if isempty(hFigall) || isempty(GlobalData.DataSet(iDSall).Clusters) || isempty(GlobalData.DataSet(iDSall).Clusters) + sClusters = []; + return; + end + % Return all the available electrodes + sClusters = GlobalData.DataSet(iDSall).Clusters; + ChannelFile = GlobalData.DataSet(iDSall).ChannelFile; + % Get all the figures that share this channel file + for iDS = 1:length(GlobalData.DataSet) + % Skip if not the correct channel file + if ~file_compare(GlobalData.DataSet(iDS).ChannelFile, ChannelFile) + continue; + end + % Get all the figures + for iFig = 1:length(GlobalData.DataSet(iDS).Figure) + if ((iDS ~= iDSall(1)) || (iFig ~= iFigall(1))) && ismember(GlobalData.DataSet(iDS).Figure(iFig).Id.Type, {'DataTimeSeries', '3DViz', 'Topography'}) + iDSall(end+1) = iDS; + iFigall(end+1) = iFig; + hFigall(end+1) = GlobalData.DataSet(iDS).Figure(iFig).hFigure; + end + end + end +end + + +%% ===== SET CLUSTER ===== +% USAGE: iClusters = SetClusters(iClusters=[], sClusters) +% iClusters = SetClusters('Add', sClusters) +function iClusters = SetClusters(iClusters, sClusters) + global GlobalData; + % Parse input + isAdd = ~isempty(iClusters) && ischar(iClusters) && strcmpi(iClusters, 'Add'); + % Get dataset + [sClustersOld, iDSall] = GetClusters(); + % If there is no selected dataset + if isempty(iDSall) + return; + end + % Perform operations only once per dataset + iDSall = unique(iDSall); + for iDS = iDSall + % Reset clusters list + if isempty(sClusters) + GlobalData.DataSet(iDS).Clusters(iClusters) = []; + % Set clusters else - labels = iClusters; + % Add new clusters + if isAdd + iClusters = length(GlobalData.DataSet(iDS).Clusters) + (1:length(sClusters)); + % Add clusters + for i = 1:length(sClusters) + % Default cluster name + if isempty(sClusters(i).Label) + sClusters(i).Label = sprintf('c%d', iClusters(i)); + end + % Make new cluster names unique + if ~isempty(GlobalData.DataSet(iDS).Clusters) + sClusters(i).Label = file_unique(sClusters(i).Label, {GlobalData.DataSet(iDS).Clusters.Label, sClusters(1:i-1).Label}); + end + end + end + % Set clusters in global structure + if isempty(GlobalData.DataSet(iDS).Clusters) + GlobalData.DataSet(iDS).Clusters = sClusters; + else + GlobalData.DataSet(iDS).Clusters(iClusters) = sClusters; + end end - iClusters = []; - for i = 1:length(labels) - ind = find(strcmpi({GlobalData.Clusters.Label}, labels{i})); - iClusters = [iClusters, ind]; + % Add color if not defined yet + for i = 1:length(GlobalData.DataSet(iDS).Clusters) + if isempty(GlobalData.DataSet(iDS).Clusters(i).Color) + ColorTable = panel_scout('GetScoutsColorTable'); + iColor = mod(i-1, length(ColorTable)) + 1; + GlobalData.DataSet(iDS).Clusters(i).Color = ColorTable(iColor,:); + end end end - sClusters = GlobalData.Clusters(iClusters); + % Mark channel file as modified (only in first dataset) + GlobalData.DataSet(iDSall(1)).isChannelModified = 1; end %% ===== GET SELECTED CLUSTERS ===== -% NB: Returned indices are indices in GlobalData.Clusters array +% NB: Returned indices are indices in Clusters array function [sSelClusters, iSelClusters] = GetSelectedClusters() sSelClusters = []; iSelClusters = []; @@ -310,22 +398,21 @@ function UpdateChannelSelection() return; end % Get current clusters - [sClusters, iClusters] = GetClusters(); + sClusters = GetClusters(); if isempty(sClusters) return end % Get JList selected indices iSelClusters = uint16(ctrl.jListClusters.getSelectedIndices())' + 1; - if isempty(iClusters) + if isempty(iSelClusters) return end sSelClusters = sClusters(iSelClusters); - iSelClusters = iClusters(iSelClusters); end %% ===== SET SELECTED CLUSTERS ===== -% WARNING: Input indices are references in the GlobalData.Clusters array, not in the JList +% WARNING: Input indices are references in the Clusters array, not in the JList function SetSelectedClusters(iSelClusters, isUpdateMouseSel) if (nargin < 2) || isempty(isUpdateMouseSel) isUpdateMouseSel = 1; @@ -403,23 +490,17 @@ function SetClusterOptions(overlayClusters, overlayConditions) %#ok %% ===== SET CLUSTER FUNCTION ===== -% USAGE: SetClusterFunction(Function, iClusters) -% SetClusterFunction(Function) : Set the function for the selected clusters -function SetClusterFunction(Function, iClusters) - global GlobalData; - % Get clusters - if (nargin < 2) || isempty(iClusters) - [sClusters, iClusters] = GetSelectedClusters(); - if isempty(iClusters) - return - end - else - sClusters = GetClusters(iClusters); +% USAGE: SetClusterFunction(Function) +function SetClusterFunction(Function) + % Get select clusters + [sClusters, iClusters] = GetSelectedClusters(); + if isempty(iClusters) + return end % Set function [sClusters.Function] = deal(Function); % Save clusters - GlobalData.Clusters(iClusters) = sClusters; + SetClusters(iClusters, sClusters); % Update JList UpdateClustersList(); % Select edited clusters (selection was lost during update) @@ -428,7 +509,7 @@ function SetClusterFunction(Function, iClusters) %% ===== GET CHANNELS IN CLUSTER ===== -function [iChannel, Modality] = GetChannelsInCluster(sCluster, Channel, ChannelFlag) %#ok +function [iChannel, Modality] = GetChannelsInCluster(sCluster, Channel, ChannelFlag) % Get channels iChannel = zeros(1, length(sCluster.Sensors)); for ic = 1:length(iChannel) @@ -523,31 +604,11 @@ function SetClusterFunction(Function, iClusters) % === NEW CLUSTER === % New cluster structure sCluster = db_template('Cluster'); - iCluster = length(GlobalData.Clusters) + 1; % Store current cluster sensors list sCluster.Sensors = Sensors; - - % === CLUSTER LABEL === - % Get other clusters with same surface file - sOtherClusters = GetClusters(); - % Define clusters labels (Label=index) - iDisplayIndice = length(sOtherClusters) + 1; - clusterLabel = ['c', int2str(iDisplayIndice)]; - % Check that the cluster name does not exist yet (else, add a ') - sCluster.Label = UniqueClusterLabel(clusterLabel); - - % === CHECK CLUSTER UNICITY === - for i = 1:length(sOtherClusters) - if isequal(sort(Sensors), sort(sOtherClusters(i).Sensors)) - bst_error('Cluster already exists.', 'Create new cluster', 0); - sCluster = sOtherClusters(i); - iCluster = i; - return - end - end - - % === Register new cluster === - GlobalData.Clusters(iCluster) = sCluster; + % Add clusters + iCluster = SetClusters('Add', sCluster); + % Update clusters list UpdateClustersList(); % Select new cluster @@ -555,19 +616,6 @@ function SetClusterFunction(Function, iClusters) end -%% ===== UNIQUE CLUSTER LABEL ===== -function label = UniqueClusterLabel(label) - % Get other clusters with same surface file - sOtherClusters = GetClusters(); - % Check that the scout name does not exist yet (else, add a ') - if ~isempty(sOtherClusters) - while ismember(label, {sOtherClusters.Label}) - label = [label, '''']; - end - end -end - - %% ===== VIEW CLUSTERS ===== function DisplayClusters(varargin) % Display clusters @@ -578,83 +626,69 @@ function DisplayClusters(varargin) % ====== CLUSTERS OPERATIONS ==================================================== % =============================================================================== %% ===== REMOVE CLUSTERS ===== -% Usage : RemoveClusters(iClusters) : remove a list of clusters -% RemoveClusters() : remove the clusters selected in the JList -function RemoveClusters(varargin) +% Usage : RemoveClusters() : remove the clusters selected in the JList +function RemoveClusters() global GlobalData; - % If clusters list is not defined - if (nargin == 0) - % Get selected clusters - [sClusters, iClusters] = GetSelectedClusters(); - % Check whether a cluster is selected - if isempty(sClusters) - java_dialog('warning', 'No cluster selected.', 'Remove cluster'); - return - end - elseif (nargin == 1) - iClusters = varargin{1}; - sClusters = GlobalData.Clusters(iClusters); - else - bst_error('Invalid call to RemoveClusters.', 'Clusters', 0); + % Get dataset + [sClusters, iDSall, iFigall] = GetClusters(); + if isempty(iDSall) + return; + end + % Get selected clusters + [sClusters, iClusters] = GetSelectedClusters(); + % Check whether a cluster is selected + if isempty(sClusters) + java_dialog('warning', 'No cluster selected.', 'Remove cluster'); return end - + % Ask for confirmation + if (length(sClusters) == 1) + strConfirm = ['Delete cluster "' sClusters(1).Label '"?']; + else + strConfirm = ['Delete ' num2str(length(sClusters)) ' clusters?']; + end + if ~java_dialog('confirm', strConfirm) + return; + end % Remove clusters definitions from global data structure - GlobalData.Clusters(iClusters) = []; + for iDS = unique(iDSall) + GlobalData.DataSet(iDS).Clusters(iClusters) = []; + end % Update Clusters list UpdateClustersList(); end -%% ===== REMOVE ALL CLUSTERS ===== -function RemoveAllClusters() - global GlobalData; - if ~isempty(GlobalData.Clusters) - RemoveClusters(1:length(GlobalData.Clusters)); - end -end %% ===== EDIT CLUSTER LABEL ===== -% Usage : EditClusterLabel() : Interactive edition of cluster name -% EditClusterLabel(newLabel, iCluster) : Update label of iCluster to newLabel, if newLabel is unique -function EditClusterLabel(newLabel, iCluster) - global GlobalData; - % If newLabel and iCluster are provided - if (nargin == 2) - % Get input cluster - sCluster = GetClusters(iCluster); - if isempty(sCluster) - error('Invalid cluster index.'); - elseif strcmpi(newLabel, sCluster.Label) - return; - elseif any(strcmpi({GlobalData.Clusters.Label}, newLabel)) - error('Label already exists.'); - end - % Interactive edit - else - % Get selected clusters - [sCluster, iCluster] = GetSelectedClusters(); - % Warning message if no cluster selected - if isempty(sCluster) - java_dialog('warning', 'No cluster selected.', 'Rename selected cluster'); - return; - % If more than one cluster selected: keep only the first one - elseif (length(sCluster) > 1) - iCluster = iCluster(1); - sCluster = sCluster(1); - SetSelectedClusters(iCluster); - end - % Ask user for a new Cluster Label - newLabel = java_dialog('input', sprintf('Please enter a new label for cluster "%s":', sCluster.Label), ... - 'Rename selected cluster', [], sCluster.Label); - if isempty(newLabel) || strcmpi(newLabel, sCluster.Label) - return - elseif any(strcmpi({GlobalData.Clusters.Label}, newLabel)) - java_dialog('warning', 'Cluster name already exists.', 'Rename selected cluster'); - return; - end +% Usage : EditClusterLabel() : Interactive edition of cluster name +function EditClusterLabel() + % Get all clusters + sClustersAll = GetClusters(); + % Get selected clusters + [sCluster, iCluster] = GetSelectedClusters(); + % Warning message if no cluster selected + if isempty(sCluster) + java_dialog('warning', 'No cluster selected.', 'Rename selected cluster'); + return; + % If more than one cluster selected: keep only the first one + elseif (length(sCluster) > 1) + iCluster = iCluster(1); + sCluster = sCluster(1); + SetSelectedClusters(iCluster); + end + % Ask user for a new Cluster Label + newLabel = java_dialog('input', sprintf('Please enter a new label for cluster "%s":', sCluster.Label), ... + 'Rename selected cluster', [], sCluster.Label); + if isempty(newLabel) || strcmpi(newLabel, sCluster.Label) + return + elseif ~isempty(sClustersAll) && any(strcmpi({sClustersAll.Label}, newLabel)) + java_dialog('warning', 'Cluster name already exists.', 'Rename selected cluster'); + return; end + % Update cluster definition - GlobalData.Clusters(iCluster).Label = newLabel; + sCluster.Label = newLabel; + SetClusters(iCluster, sCluster); % Update JList UpdateClustersList(); % Select edited clusters (selection was lost during update) @@ -662,19 +696,43 @@ function EditClusterLabel(newLabel, iCluster) end +%% ===== EDIT CLUSTER COLOR ===== +function EditClusterColor(newColor) + % Get selected scouts + [sSelClusters, iSelClusters] = GetSelectedClusters(); + if isempty(iSelClusters) + java_dialog('warning', 'No cluster selected.', 'Edit cluster color'); + return + end + % If color is not specified in argument : ask it to user + if (nargin < 1) + newColor = java_dialog('color'); + if (length(newColor) ~= 3) || all(sSelClusters(1).Color(:) == newColor(:)) + return + end + end + % Update scouts color + for i = 1:length(sSelClusters) + sSelClusters(i).Color = newColor; + end + % Save scouts + SetClusters(iSelClusters, sSelClusters); + % Update scouts list + UpdateClustersList(); +end + + %% ===== SAVE CLUSTERS ===== function SaveClusters(varargin) - global GlobalData; - % Get protocol description - ProtocolInfo = bst_get('ProtocolInfo'); % Get selected clusters sClusters = GetSelectedClusters(); if isempty(sClusters) return end + % Default folder name + ClusterDir = GetDefaultClusterDir(); % Build a default file name - ClusterFile = bst_fullfile(ProtocolInfo.SUBJECTS, bst_fileparts(GlobalData.CurrentScoutsSurface), ... - ['cluster', sprintf('_%s', sClusters.Label), '.mat']); + ClusterFile = bst_fullfile(ClusterDir, ['cluster', sprintf('_%s', sClusters.Label), '.mat']); % Get filename where to store the filename ClusterFile = java_getfile( 'save', 'Save selected clusters', ClusterFile, ... 'single', 'files', ... @@ -687,40 +745,45 @@ function SaveClusters(varargin) [filePath, fileBase, fileExt] = bst_fileparts(ClusterFile); ClusterFile = bst_fullfile(filePath, ['cluster_' fileBase fileExt]); end - % Save file FileMat.Clusters = sClusters; bst_save(ClusterFile, FileMat, 'v7'); end -%% ===== LOAD CLUSTER ===== -function LoadClusters(varargin) +%% ===== GET DEFAULT CLUSTER DIR ===== +function ClusterDir = GetDefaultClusterDir() global GlobalData; - % === SELECT FILES TO LOAD === - % Get protocol description - ProtocolInfo = bst_get('ProtocolInfo'); - % Get current subject directory - sSubject = bst_get('Subject'); - % If no current subject (no recordings were loaded yet) - curFig = bst_figures('GetCurrentFigure'); - if isempty(sSubject) && ~isempty(curFig) - % Get subject of current figure - curFig = bst_figures('GetCurrentFigure'); - SubjectFile = getappdata(curFig, 'SubjectFile'); - if ~isempty(SubjectFile) - sSubject = bst_get('Subject', SubjectFile); + % Get subject of current figure + [hFig,iFig,iDS] = bst_figures('GetCurrentFigure'); + if ~isempty(iDS) && ~isempty(GlobalData.DataSet(iDS).SubjectFile) + SubjectFile = GlobalData.DataSet(iDS).SubjectFile; + % Get current subject in the database + else + sSubject = bst_get('Subject'); + if ~isempty(sSubject) + SubjectFile = sSubject.FileName; + else + SubjectFile = []; end end - if isempty(sSubject) - return; + % Get subject anatomy folder + if ~isempty(SubjectFile) + ClusterDir = bst_fileparts(file_fullpath(SubjectFile)); + else + ClusterDir = ''; end - clusterSubDir = bst_fullfile(ProtocolInfo.SUBJECTS, bst_fileparts(sSubject.FileName)); +end + +%% ===== LOAD CLUSTER ===== +function LoadClusters(varargin) + % Get default cluster folder + ClusterDir = GetDefaultClusterDir(); % Ask user which are the files to be loaded - ClusterFiles = java_getfile('open', 'Import clusters', clusterSubDir, ... + ClusterFiles = java_getfile('open', 'Import clusters', ClusterDir, ... 'multiple', 'files', ... - {{'_cluster'}, 'Sensor clusters (*cluster*.mat)', 'BST'}, 1); + {{'_cluster'}, 'Sensor clusters (*cluster*.mat)', 'BST'}, 1); if isempty(ClusterFiles) return end @@ -732,24 +795,33 @@ function LoadClusters(varargin) for iFile = 1:length(ClusterFiles) % Try to load cluster file ClusterMat = load(ClusterFiles{iFile}); + if ~isfield(ClusterMat, 'Clusters') || isempty(ClusterMat.Clusters) + continue; + end + % Create standardized structure + sClustersNew = repmat(db_template('cluster'), 1, length(ClusterMat.Clusters)); % Loop on all the new clusters for i = 1:length(ClusterMat.Clusters) - % Make all clusters names unique - ClusterMat.Clusters(i).Label = UniqueClusterLabel(ClusterMat.Clusters(i).Label); - % Add "Function" field if is doesnt exist - if ~isfield(ClusterMat.Clusters, 'Function') - defCluster = db_template('cluster'); - ClusterMat.Clusters(i).Function = defCluster.Function; + sClustersNew(i).Sensors = ClusterMat.Clusters(i).Sensors; + if isfield(ClusterMat.Clusters(i), 'Label') && ~isempty(ClusterMat.Clusters(i).Label) + sClustersNew(i).Label = ClusterMat.Clusters(i).Label; + end + if isfield(ClusterMat.Clusters(i), 'Color') && ~isempty(ClusterMat.Clusters(i).Color) + sClustersNew(i).Color = ClusterMat.Clusters(i).Color; + end + if isfield(ClusterMat.Clusters(i), 'Function') && ~isempty(ClusterMat.Clusters(i).Function) + sClustersNew(i).Function = ClusterMat.Clusters(i).Function; end end % Add to current clusters - iNewClustersList = [iNewClustersList, (1:length(ClusterMat.Clusters))+length(GlobalData.Clusters)]; - GlobalData.Clusters = [GlobalData.Clusters, ClusterMat.Clusters]; + iNewClustersList = [iNewClustersList, SetClusters('Add', sClustersNew)]; end % Update cluster list UpdateClustersList(); % Select first cluster - SetSelectedClusters(iNewClustersList(1)); + if isempty(iNewClustersList) + SetSelectedClusters(iNewClustersList(1)); + end bst_progress('stop'); end diff --git a/toolbox/gui/panel_record.m b/toolbox/gui/panel_record.m index d2edc9fee..c003b32f5 100644 --- a/toolbox/gui/panel_record.m +++ b/toolbox/gui/panel_record.m @@ -2711,6 +2711,7 @@ function CallProcessOnRaw(ProcessName) GlobalData.DataSet(iDS).Channel = ChannelMat.Channel; GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; GlobalData.DataSet(iDS).Projector = ChannelMat.Projector; + GlobalData.DataSet(iDS).Clusters = ChannelMat.Clusters; GlobalData.DataSet(iDS).IntraElectrodes = ChannelMat.IntraElectrodes; else DataMat = in_bst_data(DataFile, 'Events'); diff --git a/toolbox/gui/view_channels.m b/toolbox/gui/view_channels.m index a2cbf55ef..074b8fdf8 100644 --- a/toolbox/gui/view_channels.m +++ b/toolbox/gui/view_channels.m @@ -26,7 +26,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2019 +% Authors: Francois Tadel, 2008-2023 global GlobalData; % Parse inputs @@ -137,6 +137,7 @@ GlobalData.DataSet(iDS).Channel = ChannelMat.Channel; GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; GlobalData.DataSet(iDS).Projector = ChannelMat.Projector; + GlobalData.DataSet(iDS).Clusters = ChannelMat.Clusters; GlobalData.DataSet(iDS).IntraElectrodes = ChannelMat.IntraElectrodes; GlobalData.DataSet(iDS).HeadPoints = ChannelMat.HeadPoints; end @@ -211,6 +212,10 @@ isMesh = ~isequal(Modality, 'SEEG'); figure_3d('ViewSensors', hFig, isMarkers, isLabels, isMesh, Modality); end +% Open Cluster tab +if ~isempty(GlobalData.DataSet(iDS).Clusters) + gui_brainstorm('ShowToolTab', 'Cluster'); +end % Update lights camlight(findobj(hFig, 'Tag', 'FrontLight'), 'headlight'); % Update figure name diff --git a/toolbox/gui/view_clusters.m b/toolbox/gui/view_clusters.m index f8c8d819f..3911b4979 100644 --- a/toolbox/gui/view_clusters.m +++ b/toolbox/gui/view_clusters.m @@ -30,7 +30,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2009-2019 +% Authors: Francois Tadel, 2009-2023 global GlobalData; @@ -49,7 +49,8 @@ [sClusters, iClusters] = panel_cluster('GetSelectedClusters'); else % Get clusters - sClusters = panel_cluster('GetClusters', iClusters); + sClusters = panel_cluster('GetClusters'); + sClusters = sClusters(iClusters); end % Warning message if no cluster selected if isempty(sClusters) @@ -103,6 +104,7 @@ clustersActivity = cell(length(DataFiles), length(iClusters)); clustersStd = cell(length(DataFiles), length(iClusters)); clustersLabels = cell(length(DataFiles), length(iClusters)); +clustersColors = cell(length(DataFiles), length(iClusters)); axesLabels = cell(length(DataFiles), length(iClusters)); % Process each Data file for iFile = 1:length(DataFiles) @@ -201,8 +203,9 @@ end axesLabels{iFile,k} = strAxes; - % === CLUSTERS LABELS === + % === CLUSTERS LABELS/COLORS === clustersLabels{iFile,k} = sClusters(k).Label; + clustersColors{iFile,k} = sClusters(k).Color; end end @@ -233,8 +236,7 @@ axesLabels = axesLabels(:)'; % Clusters labels = cell-array of strings {1, Ngraph} clustersLabels = clustersLabels(:)'; - % clustersColors = repmat({.2*[1,1,1]}, size(clustersLabels)); - clustersColors = []; + clustersColors = clustersColors(:)'; % === OVERLAY CLUSTERS AND CONDITIONS === elseif (ClustersOptions.overlayClusters && ClustersOptions.overlayConditions) @@ -273,7 +275,7 @@ axesLabels = axesLabels(:,1)'; % Clusters labels = cell-array of strings {Ncluster, Ncond} clustersLabels = clustersLabels'; - clustersColors = []; + clustersColors = clustersColors'; % === OVERLAY CONDITIONS ONLY === elseif ClustersOptions.overlayConditions diff --git a/toolbox/gui/view_headpoints.m b/toolbox/gui/view_headpoints.m index 99bdb5c0a..b5776c1f9 100644 --- a/toolbox/gui/view_headpoints.m +++ b/toolbox/gui/view_headpoints.m @@ -78,6 +78,7 @@ GlobalData.DataSet(iDS).Channel = ChannelMat.Channel; GlobalData.DataSet(iDS).MegRefCoef = ChannelMat.MegRefCoef; GlobalData.DataSet(iDS).Projector = ChannelMat.Projector; +GlobalData.DataSet(iDS).Clusters = ChannelMat.Clusters; GlobalData.DataSet(iDS).IntraElectrodes = ChannelMat.IntraElectrodes; GlobalData.DataSet(iDS).HeadPoints = ChannelMat.HeadPoints; diff --git a/toolbox/gui/view_timeseries.m b/toolbox/gui/view_timeseries.m index b49a7a761..6f281c509 100644 --- a/toolbox/gui/view_timeseries.m +++ b/toolbox/gui/view_timeseries.m @@ -36,7 +36,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2022 +% Authors: Francois Tadel, 2008-2023 %% ===== INITIALIZATION ===== global GlobalData; @@ -313,6 +313,10 @@ % Update figure name bst_figures('UpdateFigureName', hFig); end +% Open Cluster tab +if ~isempty(GlobalData.DataSet(iDS).Clusters) + gui_brainstorm('ShowToolTab', 'Cluster'); +end % Close progress bar drawnow; bst_progress('stop'); diff --git a/toolbox/io/import_channel.m b/toolbox/io/import_channel.m index 14e5d7e3e..6a940d4d9 100644 --- a/toolbox/io/import_channel.m +++ b/toolbox/io/import_channel.m @@ -44,7 +44,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2008-2022 +% Authors: Francois Tadel, 2008-2023 %% ===== PARSE INPUTS ===== Output = []; diff --git a/toolbox/math/bst_project_channel.m b/toolbox/math/bst_project_channel.m index f56731d5b..001b078c2 100644 --- a/toolbox/math/bst_project_channel.m +++ b/toolbox/math/bst_project_channel.m @@ -126,7 +126,8 @@ ChannelMatDest.IntraElectrodes(i).Loc = proj(ChannelMatSrc.IntraElectrodes(i).Loc); end end - +% Copy clusters +ChannelMatDest.Clusters = ChannelMatSrc.Clusters; % ===== SAVE NEW FILE ===== bst_progress('text', 'Saving results...'); diff --git a/toolbox/process/functions/process_extract_cluster.m b/toolbox/process/functions/process_extract_cluster.m index 37674fb40..7a577fb4e 100644 --- a/toolbox/process/functions/process_extract_cluster.m +++ b/toolbox/process/functions/process_extract_cluster.m @@ -1,9 +1,6 @@ function varargout = process_extract_cluster( varargin ) % PROCESS_EXTRACT_CLUSTER: Extract clusters values. % -% THIS FUNCTION IS NOW DEPRECATED FOR EXTRACTING SCOUT TIME SERIES -% PLEASE USE PROCESS_EXTRACT_SCOUT INSTEAD - % @============================================================================= % This function is part of the Brainstorm software: % https://neuroimage.usc.edu/brainstorm @@ -22,7 +19,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2010-2021 +% Authors: Francois Tadel, 2010-2023 eval(macro_method); end @@ -64,24 +61,28 @@ %% ===== FORMAT COMMENT ===== function Comment = FormatComment(sProcess) % Only accept data files in input - Comment = 'Extract clusters time series:'; + Comment = 'Extract clusters: '; % Get selected clusters - sClusters = sProcess.options.clusters.Value; + if isstruct(sProcess.options.clusters.Value) + ClusterLabels = {sProcess.options.clusters.Value.Label}; + else + ClusterLabels = sProcess.options.clusters.Value; + end % Format comment - if isempty(sClusters) + if isempty(ClusterLabels) Comment = [Comment, '[no selection]']; - elseif (length(sClusters) > 15) - Comment = [Comment, sprintf('[%d clusters]', length(sClusters))]; + elseif (length(ClusterLabels) > 15) + Comment = [Comment, sprintf('[%d clusters]', length(ClusterLabels))]; else - for i = 1:length(sClusters) - Comment = [Comment, ' ', sClusters(i).Label]; - end + Comment = [Comment, sprintf('%s ', ClusterLabels{:})]; end end %% ===== RUN ===== function OutputFiles = Run(sProcess, sInputs) %#ok + % Initialize returned variable + OutputFiles = {}; % REDIRECTING SCOUT CALLS TO PROCESS_EXTRACT_SCOUTS if ismember(sInputs(1).FileType, {'results', 'timefreq'}) % Issue redirection warning @@ -97,6 +98,18 @@ return; end + % Get clusters: Old version (full structures) or new version (cell list of strings) + if isempty(sProcess.options.clusters.Value) + bst_report('Error', sProcess, [], 'No cluster selected.'); + return; + elseif isstruct(sProcess.options.clusters.Value) + sClusters = sProcess.options.clusters.Value; + ClusterLabels = []; + % New version: passing only the name of the clusters + else + ClusterLabels = sProcess.options.clusters.Value; + end + % Concatenate values ? isConcatenate = sProcess.options.concatenate.Value && (length(sInputs) > 1); isSave = sProcess.options.save.Value; @@ -106,17 +119,11 @@ else TimeWindow = []; end - OutputFiles = {}; - % Get clusters - sClusters = sProcess.options.clusters.Value; - if isempty(sClusters) - bst_report('Error', sProcess, [], 'No cluster/scout selected.'); - return; - end % ===== LOOP ON THE FILES ===== for iInput = 1:length(sInputs) sResults = []; + % === READ FILES === % Load recordings sMat = in_bst_data(sInputs(iInput).FileName); @@ -131,6 +138,10 @@ end % Get channel file ChannelMat = in_bst_channel(sInputs(iInput).ChannelFile); + if ~isfield(ChannelMat, 'Clusters') || isempty(ChannelMat.Clusters) + bst_report('Error', sProcess, [], ['No clusters available in channel file: ' sInputs(iInput).ChannelFile]); + return; + end % Nothing loaded if isempty(sMat) || (isempty(matValues) && (isempty(sResults) || ~isfield(sResults, 'ImagingKernel') || isempty(sResults.ImagingKernel))) @@ -167,7 +178,21 @@ bst_report('Error', sProcess, sInputs(iInput), 'Time bands are not supported yet by this process.'); continue; end - + + % === GET CLUSTERS === + if ~isempty(ClusterLabels) + iClusters = zeros(1, length(ClusterLabels)); + for iClust = 1:length(ClusterLabels) + iFound = find(strcmp(ClusterLabels{iClust}, {ChannelMat.Clusters.Label})); + if isempty(iFound) + bst_report('Error', sProcess, [], ['Requested cluster is not available in channel file: ' ClusterLabels{iClust}]); + return; + end + iClusters(iClust) = iFound(1); + end + sClusters = ChannelMat.Clusters(iClusters); + end + % === TIME === % Check time vectors if (iInput == 1) diff --git a/toolbox/process/panel_process_select.m b/toolbox/process/panel_process_select.m index 9f21f34b9..f90e2608b 100644 --- a/toolbox/process/panel_process_select.m +++ b/toolbox/process/panel_process_select.m @@ -27,7 +27,7 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Francois Tadel, 2010-2022 +% Authors: Francois Tadel, 2010-2023 eval(macro_method); end @@ -1186,10 +1186,10 @@ function UpdateProcessOptions() case {'cluster', 'cluster_confirm'} % Get available and selected clusters - [jList, sClusters] = GetClusterList(iProcess, optNames{iOpt}); + jList = GetClusterList(sProcess, optNames{iOpt}); % If no clusters if isempty(jList) - gui_component('label', jPanelOpt, [], 'No cluster available.'); + gui_component('label', jPanelOpt, [], 'Error: No clusters available in channel file.'); else % Confirm selection if strcmpi(option.Type, 'cluster_confirm') @@ -1199,7 +1199,7 @@ function UpdateProcessOptions() strCheck = 'Use cluster time series:'; end jCheckCluster = gui_component('checkbox', jPanelOpt, [], strCheck); - java_setcb(jCheckCluster, 'ActionPerformedCallback', @(h,ev)Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, sClusters, jList, jCheckCluster, [])); + java_setcb(jCheckCluster, 'ActionPerformedCallback', @(h,ev)Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, jList, jCheckCluster, [])); jCheckCluster.setSelected(1) jList.setEnabled(1); else @@ -1207,8 +1207,8 @@ function UpdateProcessOptions() gui_component('label', jPanelOpt, [], ' Select cluster:'); end % Set callbacks - java_setcb(jList, 'ValueChangedCallback', @(h,ev)Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, sClusters, jList, jCheckCluster, ev)); - Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, sClusters, jList, jCheckCluster, []); + java_setcb(jList, 'ValueChangedCallback', @(h,ev)Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, jList, jCheckCluster, ev)); + Cluster_ValueChangedCallback(iProcess, optNames{iOpt}, jList, jCheckCluster, []); % Create scroll panel jScroll = javax.swing.JScrollPane(jList); jPanelOpt.add('br hfill vfill', jScroll); @@ -1830,22 +1830,32 @@ function OptionRadio_Callback(iProcess, optName, iRadio, isSelected) end %% ===== OPTIONS: GET CLUSTER LIST ===== - function [jList, sClusters] = GetClusterList(iProcess, optName) + function jList = GetClusterList(sProcess, optName) import org.brainstorm.list.*; - % Get process - sCurProcess = GlobalData.Processes.Current(iProcess); - % Get available clusters - sClusters = panel_cluster('GetClusters'); - if isempty(sClusters) - jList = []; - return + % Initialize returned values + jList = []; + + % Get the current channel file + if isfield(sProcess.options.(optName), 'InputTypesB') && ~isempty(sFiles2) + ChannelFile = sFiles2(1).ChannelFile; + else + ChannelFile = sFiles(1).ChannelFile; end + if isempty(ChannelFile) + return; + end + % Load clusters from channel file + ChannelMat = in_bst_channel(ChannelFile, 'Clusters'); + if isempty(ChannelMat.Clusters) + return; + end + % Get all clusters labels - allLabels = {sClusters.Label}; + allLabels = {ChannelMat.Clusters.Label}; % Create a list mode of the existing clusters/scouts listModel = javax.swing.DefaultListModel(); - for iClust = 1:length(sClusters) - listModel.addElement(BstListItem(sClusters(iClust).Label, '', [' ' allLabels{iClust} ' '], iClust)); + for iClust = 1:length(ChannelMat.Clusters) + listModel.addElement(BstListItem(ChannelMat.Clusters(iClust).Label, '', [' ' allLabels{iClust} ' '], iClust)); end % Create list @@ -1854,33 +1864,11 @@ function OptionRadio_Callback(iProcess, optName, iRadio, isSelected) jList.setLayoutOrientation(jList.HORIZONTAL_WRAP); jList.setVisibleRowCount(-1); jList.setCellRenderer(BstStringListRenderer(fontSize)); - - % Get selected clusters - [tmp__, iSelClusters] = panel_cluster('GetSelectedClusters'); - % Get selected indices - if ~isempty(sCurProcess.options.(optName).Value) - labels = {sCurProcess.options.(optName).Value.Label}; - else - labels = {}; - end - % If a selection has already been made for this option - if ~isempty(labels) - % Get the selected clusters indices - iSelClusters = []; - for i = 1:length(labels) - iSelClusters = [iSelClusters, find(strcmpi(labels{i}, {sClusters.Label}))]; - end - elseif ~isempty(iSelClusters) - sCurProcess.options.(optName).Value = sClusters(iSelClusters); - end - if ~isempty(iSelClusters) - jList.setSelectedIndices(iSelClusters - 1); - end end %% ===== OPTIONS: CLUSTER CALLBACK ===== - function Cluster_ValueChangedCallback(iProcess, optName, sClusters, jList, jCheck, ev) + function Cluster_ValueChangedCallback(iProcess, optName, jList, jCheck, ev) % Enable/disable jList if ~isempty(jCheck) isChecked = jCheck.isSelected(); @@ -1894,9 +1882,17 @@ function Cluster_ValueChangedCallback(iProcess, optName, sClusters, jList, jChec % If not currently editing elseif isempty(ev) || ~ev.getValueIsAdjusting() % Get selected clusters - iSel = jList.getSelectedIndices() + 1; + selObj = jList.getSelectedValues(); + if (length(selObj) == 0) + strList = []; + else + strList = cell(1, length(selObj)); + for iObj = 1:length(selObj) + strList{iObj} = char(selObj(iObj).getType()); + end + end % Set value - SetOptionValue(iProcess, optName, sClusters(iSel)); + SetOptionValue(iProcess, optName, strList); end end diff --git a/toolbox/tree/tree_callbacks.m b/toolbox/tree/tree_callbacks.m index 4a2a2befe..f15511abc 100644 --- a/toolbox/tree/tree_callbacks.m +++ b/toolbox/tree/tree_callbacks.m @@ -2772,7 +2772,8 @@ function fcnPopupProjectSources(isSeparator) %% ===== MENU: CLUSTERS TIME SERIES ===== function fcnPopupClusterTimeSeries() import org.brainstorm.icon.*; - if ~isempty(GlobalData.Clusters) + sClusters = panel_cluster('GetClusters'); + if ~isempty(sClusters) gui_component('MenuItem', jPopup, [], 'Clusters time series', IconLoader.ICON_TS_DISPLAY, [], @(h,ev)tree_view_clusters(bstNodes)); end end