diff --git a/doc/license.html b/doc/license.html
index 6787fa322..227dc2815 100644
--- a/doc/license.html
+++ b/doc/license.html
@@ -5,7 +5,7 @@
THERE IS NO UNDO BUTTON!
SET UP A BACKUP OF YOUR DATABASE
-Version: 3.230117 (17-Jan-2023)
+Version: 3.230118 (18-Jan-2023)
COPYRIGHT © 2000-2023
USC & McGill University.
diff --git a/doc/updates.txt b/doc/updates.txt
index 8e8583d04..de68caf34 100644
--- a/doc/updates.txt
+++ b/doc/updates.txt
@@ -6,6 +6,7 @@ January 2023
- IO: Export matrix files as EDF+
- Inverse: Display scouts on source-level PSD as PSD figures
- Clusters: Save clusters in channel file, load automatically
+- Clusters: Import from process
--------------------------------------------------------------
November 2022
- Forward: Display leadfield sensitivity (surface, MRI, isosurface)
diff --git a/doc/version.txt b/doc/version.txt
index 9cb4d14c9..d2d86bb6e 100644
--- a/doc/version.txt
+++ b/doc/version.txt
@@ -1,2 +1,2 @@
% Brainstorm
-% v. 3.230117 (17-Jan-2023)
\ No newline at end of file
+% v. 3.230118 (18-Jan-2023)
\ No newline at end of file
diff --git a/toolbox/core/bst_get.m b/toolbox/core/bst_get.m
index 109391747..1a1139f54 100644
--- a/toolbox/core/bst_get.m
+++ b/toolbox/core/bst_get.m
@@ -3801,13 +3801,16 @@
{'.sel'}, 'MNE selection files (*.sel)', 'MNE'; ...
{'.mon'}, 'Text montage files (*.mon)', 'MON'; ...
{'_montage'}, 'Brainstorm montage files (montage_*.mat)', 'BST';
- {'.csv'}, 'Comma-separated montage files (*.csv)', 'CSV'; ...
- {'.xml'}, 'Compumedics ProFusion montages (*.xml)', 'EEG-COMPUMEDICS-PFS'};
+ {'.csv'}, 'Comma-separated montage files (*.csv)', 'CSV'};
case 'montageout'
argout1 = {...
{'.sel'}, 'MNE selection files (*.sel)', 'MNE'; ...
{'.mon'}, 'Text montage files (*.mon)', 'MON'; ...
{'_montage'}, 'Brainstorm montage files (montage_*.mat)', 'BST'};
+ case 'clusterin'
+ argout1 = {...
+ {'_cluster'}, 'Brainstorm clusters file (*cluster*.mat)', 'BST'; ...
+ {'.sel'}, 'MNE selection files (*.sel)', 'MNE'};
case 'fibers'
argout1 = {...
{'.trk'}, 'TrackVis (*.trk)', 'TRK'; ...
diff --git a/toolbox/gui/panel_cluster.m b/toolbox/gui/panel_cluster.m
index 3dc2e38bd..1491f354f 100644
--- a/toolbox/gui/panel_cluster.m
+++ b/toolbox/gui/panel_cluster.m
@@ -781,46 +781,29 @@ function LoadClusters(varargin)
% Get default cluster folder
ClusterDir = GetDefaultClusterDir();
% Ask user which are the files to be loaded
- ClusterFiles = java_getfile('open', 'Import clusters', ClusterDir, ...
- 'multiple', 'files', ...
- {{'_cluster'}, 'Sensor clusters (*cluster*.mat)', 'BST'}, 1);
+ [ClusterFiles, FileFormat] = java_getfile(...
+ 'open', 'Import clusters', ClusterDir, ...
+ 'multiple', 'files', ...
+ bst_get('FileFilters', 'clusterin'), 1);
if isempty(ClusterFiles)
return
end
% ==== CREATE AND DISPLAY ====
- iNewClustersList = [];
+ iNewClusters = [];
bst_progress('start', 'Load clusters', 'Load cluster file');
% Load all files selected by user
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)
- 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
+ % Load clusters
+ sClusters = in_clusters(ClusterFiles{iFile}, FileFormat);
% Add to current clusters
- iNewClustersList = [iNewClustersList, SetClusters('Add', sClustersNew)];
+ iNewClusters = [iNewClusters, SetClusters('Add', sClusters)];
end
% Update cluster list
UpdateClustersList();
% Select first cluster
- if isempty(iNewClustersList)
- SetSelectedClusters(iNewClustersList(1));
+ if isempty(iNewClusters)
+ SetSelectedClusters(iNewClusters(1));
end
bst_progress('stop');
end
diff --git a/toolbox/gui/panel_montage.m b/toolbox/gui/panel_montage.m
index 1eb66f828..f59f4cfd4 100644
--- a/toolbox/gui/panel_montage.m
+++ b/toolbox/gui/panel_montage.m
@@ -1871,8 +1871,6 @@ function CreateMontageMenu(jButton, hFig)
sMon = in_montage_mne(FileNames{iFile});
case 'MON'
sMon = in_montage_mon(FileNames{iFile});
- case 'EEG-COMPUMEDICS-PFS'
- sMon = in_montage_compumedics(FileNames{iFile});
case 'BST'
DataMat = load(FileNames{iFile});
sMon = DataMat.Montages;
diff --git a/toolbox/io/in_clusters.m b/toolbox/io/in_clusters.m
new file mode 100644
index 000000000..c6992f3f6
--- /dev/null
+++ b/toolbox/io/in_clusters.m
@@ -0,0 +1,60 @@
+function sClusters = in_clusters(ClusterFile, FileFormat)
+% IN_CLUSTERS: Read clusters of channels from a file
+
+% @=============================================================================
+% This function is part of the Brainstorm software:
+% https://neuroimage.usc.edu/brainstorm
+%
+% Copyright (c) University of Southern California & McGill University
+% This software is distributed under the terms of the GNU General Public License
+% as published by the Free Software Foundation. Further details on the GPLv3
+% license can be found at http://www.gnu.org/copyleft/gpl.html.
+%
+% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
+% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
+% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
+% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
+% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
+%
+% For more information type "brainstorm license" at command prompt.
+% =============================================================================@
+%
+% Authors: Francois Tadel, 2023
+
+% Read file
+switch (FileFormat)
+ case 'BST'
+ ClusterMat = load(ClusterFile);
+ if ~isfield(ClusterMat, 'Clusters')
+ error('Invalid Brainstorm clusters file: missing field "Clusters".');
+ end
+ case 'MNE'
+ sMontage = in_montage_mne(FileName);
+ for i = 1:length(sMontage)
+ ClusterMat.Clusters(i).Label = sMontage(i).Name;
+ ClusterMat.Clusters(i).Sensors = sMontage(i).ChanNames;
+ end
+ otherwise
+ error('Invalid file format.');
+end
+if isempty(ClusterMat.Clusters)
+ error(['No clusters available in file: ' FileName]);
+end
+
+% Create standardized Brainstorm structure
+sClusters = repmat(db_template('cluster'), 1, length(ClusterMat.Clusters));
+% Loop on all the new clusters
+for i = 1:length(ClusterMat.Clusters)
+ sClusters(i).Sensors = ClusterMat.Clusters(i).Sensors;
+ if isfield(ClusterMat.Clusters(i), 'Label') && ~isempty(ClusterMat.Clusters(i).Label)
+ sClusters(i).Label = ClusterMat.Clusters(i).Label;
+ end
+ if isfield(ClusterMat.Clusters(i), 'Color') && ~isempty(ClusterMat.Clusters(i).Color)
+ sClusters(i).Color = ClusterMat.Clusters(i).Color;
+ end
+ if isfield(ClusterMat.Clusters(i), 'Function') && ~isempty(ClusterMat.Clusters(i).Function)
+ sClusters(i).Function = ClusterMat.Clusters(i).Function;
+ end
+end
+
+
diff --git a/toolbox/process/functions/process_channel_addcluster.m b/toolbox/process/functions/process_channel_addcluster.m
new file mode 100644
index 000000000..be00f36ed
--- /dev/null
+++ b/toolbox/process/functions/process_channel_addcluster.m
@@ -0,0 +1,116 @@
+function varargout = process_channel_addcluster( varargin )
+% PROCESS_CHANNEL_ADDCLUSTER: Import clusters in the selected channel files.
+
+% @=============================================================================
+% This function is part of the Brainstorm software:
+% https://neuroimage.usc.edu/brainstorm
+%
+% Copyright (c) University of Southern California & McGill University
+% This software is distributed under the terms of the GNU General Public License
+% as published by the Free Software Foundation. Further details on the GPLv3
+% license can be found at http://www.gnu.org/copyleft/gpl.html.
+%
+% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
+% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
+% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
+% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
+% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
+%
+% For more information type "brainstorm license" at command prompt.
+% =============================================================================@
+%
+% Authors: Francois Tadel, 2023
+
+eval(macro_method);
+end
+
+
+%% ===== GET DESCRIPTION =====
+function sProcess = GetDescription()
+ % Description the process
+ sProcess.Comment = 'Import clusters of channels';
+ sProcess.Category = 'Custom';
+ sProcess.SubGroup = {'Import', 'Channel file'};
+ sProcess.Index = 90;
+ sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/ChannelClusters';
+ % Definition of the input accepted by this process
+ sProcess.InputTypes = {'data', 'raw'};
+ sProcess.OutputTypes = {'data', 'raw'};
+ sProcess.nInputs = 1;
+ sProcess.nMinFiles = 1;
+ % File selection options
+ SelectOptions = {...
+ '', ... % Filename
+ '', ... % FileFormat
+ 'open', ... % Dialog type: {open,save}
+ 'Import clusters...', ... % Window title
+ 'ImportChannel', ... % LastUsedDir: {ImportData,ImportChannel,ImportAnat,ExportChannel,ExportData,ExportAnat,ExportProtocol,ExportImage,ExportScript}
+ 'single', ... % Selection mode: {single,multiple}
+ 'files', ... % Selection mode: {files,dirs,files_and_dirs}
+ bst_get('FileFilters', 'clusterin'), ... % Available file formats
+ 'ClusterIn'}; % DefaultFormats: {ChannelIn,DataIn,DipolesIn,EventsIn,MriIn,NoiseCovIn,ResultsIn,SspIn,SurfaceIn,TimefreqIn
+ % Option: Event file
+ sProcess.options.clusterfile.Comment = 'Cluster file:';
+ sProcess.options.clusterfile.Type = 'filename';
+ sProcess.options.clusterfile.Value = SelectOptions;
+end
+
+
+%% ===== FORMAT COMMENT =====
+function Comment = FormatComment(sProcess)
+ Comment = sProcess.Comment;
+end
+
+
+%% ===== RUN =====
+function OutputFiles = Run(sProcess, sInputs)
+ % Get options
+ ClusterFile = sProcess.options.clusterfile.Value{1};
+ FileFormat = sProcess.options.clusterfile.Value{2};
+ % Load input cluster file
+ sClusters = in_clusters(ClusterFile, FileFormat);
+ % Get unique channel files
+ AllChannelFiles = unique({sInputs.ChannelFile});
+ % Process each channel file
+ for iFile = 1:length(AllChannelFiles)
+ Compute(AllChannelFiles{iFile}, sClusters);
+ end
+ % Return all the files in input
+ OutputFiles = {sInputs.FileName};
+end
+
+
+%% ===== ADD CLUSTERS TO CHANNEL FILE =====
+function Compute(ChannelFile, sClusters)
+ % Load file
+ ChannelFile = file_fullpath(ChannelFile);
+ ChannelMat = in_bst_channel(ChannelFile);
+ % Add or replace clusters
+ for i = 1:length(sClusters)
+ % If cluster already exists, update it, otherwise create a new entry
+ if ~isfield(ChannelMat, 'Clusters') || isempty(ChannelMat.Clusters)
+ ChannelMat.Clusters = repmat(db_template('cluster'), 0, 1);
+ iCluster = 1;
+ else
+ iCluster = find(strcmp(sClusters(i).Label, {ChannelMat.Clusters.Label}));
+ if isempty(iCluster)
+ iCluster = length(ChannelMat.Clusters) + 1;
+ end
+ end
+ % Copy all the fields
+ ChannelMat.Clusters(iCluster).Sensors = sClusters(i).Sensors;
+ ChannelMat.Clusters(iCluster).Label = sClusters(i).Label;
+ ChannelMat.Clusters(iCluster).Function = sClusters(i).Function;
+ % Add color if not defined yet
+ if ~isempty(sClusters(i).Color)
+ ChannelMat.Clusters(iCluster).Color = sClusters(i).Color;
+ else
+ ColorTable = panel_scout('GetScoutsColorTable');
+ iColor = mod(iCluster-1, length(ColorTable)) + 1;
+ ChannelMat.Clusters(i).Color = ColorTable(iColor,:);
+ end
+ end
+ % Save modified file
+ bst_save(ChannelFile, ChannelMat, 'v7');
+end
+