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