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 +