Plugins

Authors: Francois Tadel

Brainstorm connects with features from many third-party libraries of methods. The external software can be downloaded or updated automatically by Brainstorm when needed. This tutorial presents the API to register and manage plugins.

Interactive management

The Brainstorm interface offers menus to Install/Update/Uninstall plugins.

example1.gif

Install: Downloads and unzips the package and all its dependencies in the Brainstorm user folder:
$HOME/.brainstorm/plugins/

Uninstall: Deletes the plugin folder, all its subfolders, and all the other plugins that depend on it.

Load: Adds all the subfolders needed by the plugin to the Matlab path, plus other optional taks. Loading a plugin causes the recursive load of all the other plugins required by this plugin.

Unload: Removes all the plugin folders from the Matlab path, and all the plugins that depend on it.

Update: Some plugins are designed to update themselves automatically whenever a new version is available online, or requested by Brainstorm. Others plugins must be updated manually. Updating is equivalent to uninstalling without removing the dependencies, and then nstalling again.

Manual install: If you already have a given plugin installed on your computer (eg. FieldTrip, SPM12) and don't want Brainstorm to manage the download/update or the Matlab path for you, reference it with the menu: Custom install > Set installation folder.

List: You can list all the installed plugins with the menu List:

list.gif

Command-line management

The calls to install or manage plugins are all documented in the header of bst_plugin.m:

1 function [varargout] = bst_plugin(varargin) 2 % BST_PLUGIN: Manages Brainstorm plugins 3 % 4 % USAGE: PlugDesc = bst_plugin('GetSupported') % List all the plugins supported by Brainstorm 5 % PlugDesc = bst_plugin('GetSupported', PlugName/PlugDesc) % Get only one specific supported plugin 6 % PlugDesc = bst_plugin('GetInstalled') % Get all the installed plugins 7 % PlugDesc = bst_plugin('GetInstalled', PlugName/PlugDesc) % Get a specific installed plugin 8 % [PlugDesc, errMsg] = bst_plugin('GetDescription', PlugName/PlugDesc) % Get a full structure representing a plugin 9 % [Version, URLzip] = bst_plugin('GetVersionOnline', PlugName, URLzip, isCache) % Get the latest online version of some plugins 10 % sha = bst_plugin('GetGithubCommit', URLzip) % Get SHA of the last commit of a GitHub repository from a master.zip url 11 % ReadmeFile = bst_plugin('GetReadmeFile', PlugDesc) % Get full path to plugin readme file 12 % LogoFile = bst_plugin('GetLogoFile', PlugDesc) % Get full path to plugin logo file 13 % Version = bst_plugin('CompareVersions', v1, v2) % Compare two version strings 14 % [isOk, errMsg, PlugDesc] = bst_plugin('Load', PlugName/PlugDesc, isVerbose=1) 15 % [isOk, errMsg, PlugDesc] = bst_plugin('LoadInteractive', PlugName/PlugDesc) 16 % [isOk, errMsg, PlugDesc] = bst_plugin('Unload', PlugName/PlugDesc, isVerbose=1) 17 % [isOk, errMsg, PlugDesc] = bst_plugin('UnloadInteractive', PlugName/PlugDesc) 18 % [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 19 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice',PlugNames, isInteractive=0) % Install at least one of the input plugins 20 % [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 21 % [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 22 % [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 23 % bst_plugin('Configure', PlugDesc) % Execute some additional tasks after loading or installation 24 % bst_plugin('SetCustomPath', PlugName, PlugPath) 25 % bst_plugin('List', Target='installed') % Target={'supported','installed'} 26 % bst_plugin('Archive', OutputFile=[ask]) % Archive software environment 27 % bst_plugin('MenuCreate', jMenu) 28 % bst_plugin('MenuUpdate', jMenu) 29 % bst_plugin('LinkCatSpm', Action) % 0=Delete/1=Create/2=Check a symbolic link for CAT12 in SPM12 toolbox folder 30 % bst_plugin('UpdateDescription', PlugDesc, doDelete=0) % Update plugin description after load 31 % 32 % 33 % PLUGIN DEFINITION 34 % ================= 35 % 36 % The plugins registered in Brainstorm are listed in function GetSupported(). 37 % Each one is an entry in the PlugDesc array, following the structure defined in db_template('plugdesc'). 38 % The fields allowed are described below. 39 % 40 % Mandatory fields 41 % ================ 42 % - Name : String: Plugin name = subfolder in the Brainstorm user folder 43 % - Version : String: Version of the plugin (eg. '1.2', '21a', 'github-master', 'latest') 44 % - URLzip : String: Download URL, zip or tgz file accessible over HTTP/HTTPS/FTP 45 % - URLinfo : String: Information URL = Software website 46 % 47 % Optional fields 48 % =============== 49 % - AutoUpdate : Boolean: If true, the plugin is updated automatically when there is a new version available (default: true). 50 % - AutoLoad : Boolean: If true, the plugin is loaded automatically at Brainstorm startup 51 % - Category : String: Sub-menu in which the plugin is listed 52 % - ExtraMenus : Cell matrix {Nx2}: List of entries to add to the plugins menu 53 % | ExtraMenus{i,1}: String: Label of the menu 54 % | ExtraMenus{i,2}: String: Matlab code to eval when the menu is clicked 55 % - TestFile : String: Name of a file that should be located in one of the loaded folders of the plugin (eg. 'spm.m' for SPM12). 56 % | This is used to test whether the plugin was correctly installed, or whether it is available somewhere else in the Matlab path. 57 % - ReadmeFile : String: Name of the text file to display after installing the plugin (must be in the plugin folder). 58 % | If empty, it tries using brainstorm3/doc/plugin/plugname_readme.txt 59 % - LogoFile : String: Name of the image file to display during the plugin download, installation, and associated computations (must be in the plugin folder). 60 % | Supported extensions: gif, png. If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png] 61 % - MinMatlabVer : Integer: Minimum Matlab version required for using this plugin, as returned by bst_get('MatlabVersion') 62 % - CompiledStatus : Integer: Behavior of this plugin in the compiled version of Brainstorm: 63 % | 0: Plugin is not available in the compiled distribution of Brainstorm 64 % | 1: Plugin is available for download (only for plugins based on native compiled code) 65 % | 2: Plugin is included in the compiled distribution of Brainstorm 66 % - RequiredPlugs : Cell-array: Additional plugins required by this plugin, that must be installed/loaded beforehand. 67 % | {Nx2} => {'plugname','version'; ...} or 68 % | {Nx1} => {'plugname'; ...} 69 % - UnloadPlugs : Cell-array of names of incompatible plugin, to unload before loaing this one 70 % - LoadFolders : Cell-array of subfolders to add to the Matlab path when setting up the plugin. Use {'*'} to add all the plugin subfolders. 71 % - GetVersionFcn : String to eval or function handle to call to get the version after installation 72 % - InstalledFcn : String to eval or function handle to call after installing the plugin 73 % - UninstalledFcn : String to eval or function handle to call after uninstalling the plugin 74 % - LoadedFcn : String to eval or function handle to call after loading the plugin 75 % - UnloadedFcn : String to eval or function handle to call after unloading the plugin 76 % - DeleteFiles : List of files to delete after installation 77 % 78 % Fields set when installing the plugin 79 % ===================================== 80 % - Processes : List of process functions to be added to the pipeline manager 81 % 82 % Fields set when loading the plugin 83 % ================================== 84 % - Path : Installation path (eg. /home/username/.brainstorm/plugins/fieldtrip) 85 % - SubFolder : If all the code is in a single subfolder (eg. /plugins/fieldtrip/fieldtrip-20210304), 86 % this is detected and the full path to the TestFile would be typically fullfile(Path, SubFolder). 87 % - isLoaded : 0=Not loaded, 1=Loaded 88 % - isManaged : 0=Installed manually by the user, 1=Installed automatically by Brainstorm 89 % 90 91 % @============================================================================= 92 % This function is part of the Brainstorm software: 93 % https://neuroimage.usc.edu/brainstorm 94 % 95 % Copyright (c) University of Southern California & McGill University 96 % This software is distributed under the terms of the GNU General Public License 97 % as published by the Free Software Foundation. Further details on the GPLv3 98 % license can be found at http://www.gnu.org/copyleft/gpl.html. 99 % 100 % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 101 % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 102 % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 103 % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 104 % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 105 % 106 % For more information type "brainstorm license" at command prompt. 107 % =============================================================================@ 108 % 109 % Authors: Francois Tadel, 2021-2023 110 111 eval(macro_method); 112 end 113 114 115 %% ===== GET SUPPORTED PLUGINS ===== 116 % USAGE: PlugDesc = bst_plugin('GetSupported') % List all the plugins supported by Brainstorm 117 % PlugDesc = bst_plugin('GetSupported', PlugName/PlugDesc) % Get only one specific supported plugin 118 function PlugDesc = GetSupported(SelPlug) 119 % Parse inputs 120 if (nargin < 1) || isempty(SelPlug) 121 SelPlug = []; 122 end 123 % Initialized returned structure 124 PlugDesc = repmat(db_template('PlugDesc'), 0); 125 % Get OS 126 OsType = bst_get('OsType', 0); 127 128 % ================================================================================================================ 129 % === ANATOMY: BRAIN2MESH === 130 PlugDesc(end+1) = GetStruct('brain2mesh'); 131 PlugDesc(end).Version = 'github-master'; 132 PlugDesc(end).Category = 'Anatomy'; 133 PlugDesc(end).URLzip = 'https://github.com/fangq/brain2mesh/archive/master.zip'; 134 PlugDesc(end).URLinfo = 'http://mcx.space/brain2mesh/'; 135 PlugDesc(end).TestFile = 'brain2mesh.m'; 136 PlugDesc(end).ReadmeFile = 'README.md'; 137 PlugDesc(end).CompiledStatus = 2; 138 PlugDesc(end).RequiredPlugs = {'spm12'; 'iso2mesh'}; 139 PlugDesc(end).DeleteFiles = {'examples', 'brain1020.m', 'closestnode.m', 'label2tpm.m', 'slicesurf.m', 'slicesurf3.m', 'tpm2label.m', 'polylineinterp.m', 'polylinelen.m', 'polylinesimplify.m'}; 140 141 % === ANATOMY: CAT12 === 142 PlugDesc(end+1) = GetStruct('cat12'); 143 PlugDesc(end).Version = 'latest'; 144 PlugDesc(end).Category = 'Anatomy'; 145 PlugDesc(end).AutoUpdate = 1; 146 PlugDesc(end).URLzip = 'http://www.neuro.uni-jena.de/cat12/cat12_latest.zip'; 147 PlugDesc(end).URLinfo = 'http://www.neuro.uni-jena.de/cat/'; 148 PlugDesc(end).TestFile = 'cat_version.m'; 149 PlugDesc(end).ReadmeFile = 'Contents.txt'; 150 PlugDesc(end).CompiledStatus = 0; 151 PlugDesc(end).RequiredPlugs = {'spm12'}; 152 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @cat_version)'; 153 PlugDesc(end).InstalledFcn = 'LinkCatSpm(1);'; 154 PlugDesc(end).UninstalledFcn = 'LinkCatSpm(0);'; 155 PlugDesc(end).LoadedFcn = 'LinkCatSpm(2);'; 156 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/SegCAT12'', ''-browser'')'}; 157 158 % === ANATOMY: ISO2MESH === 159 PlugDesc(end+1) = GetStruct('iso2mesh'); 160 PlugDesc(end).Version = '1.9.6'; 161 PlugDesc(end).Category = 'Anatomy'; 162 PlugDesc(end).AutoUpdate = 1; 163 PlugDesc(end).URLzip = 'https://github.com/fangq/iso2mesh/releases/download/v1.9.6/iso2mesh-1.9.6-allinone.zip'; 164 PlugDesc(end).URLinfo = 'http://iso2mesh.sourceforge.net'; 165 PlugDesc(end).TestFile = 'iso2meshver.m'; 166 PlugDesc(end).ReadmeFile = 'README.txt'; 167 PlugDesc(end).CompiledStatus = 2; 168 PlugDesc(end).LoadedFcn = 'assignin(''base'', ''ISO2MESH_TEMP'', bst_get(''BrainstormTmpDir''));'; 169 PlugDesc(end).DeleteFiles = {'doc', 'tools', '.git_filters', 'sample', ... 170 'bin/cgalmesh.exe', 'bin/cgalmesh.mexglx', 'bin/cgalmesh.mexmaci', ... 171 'bin/cgalpoly.exe', 'bin/cgalpoly.mexglx', 'bin/cgalpoly.mexmaci', 'bin/cgalpoly.mexa64', 'bin/cgalpoly.mexmaci64', 'bin/cgalpoly_x86-64.exe', ... % Removing cgalpoly completely (not used) 172 'bin/cgalsurf.exe', 'bin/cgalsurf.mexglx', 'bin/cgalsurf.mexmaci', ... 173 'bin/cork.exe', ... 174 'bin/gtsrefine.mexglx', 'bin/gtsrefine.mexmaci', 'bin/gtsrefine.mexarmhf', 'bin/gtsrefine.exe', 'bin/gtsrefine.mexmaci64', ... % Removing gtsrefine completely (not used) 175 'bin/jmeshlib.exe', 'bin/jmeshlib.mexglx', 'bin/jmeshlib.mexmaci', 'bin/jmeshlib.mexmac', 'bin/jmeshlib.mexarmhf', ... 176 'bin/meshfix.exe', 'bin/meshfix.mexglx', 'bin/meshfix.mexmaci', 'bin/meshfix.mexarmhf', ... 177 'bin/tetgen.mexglx', 'bin/tetgen.mexmac', 'bin/tetgen.mexarmhf', ... 178 'bin/tetgen1.5.mexglx'}; 179 PlugDesc(end).DeleteFilesBin = {'bin/tetgen.exe', 'bin/tetgen.mexa64', 'bin/tetgen.mexmaci', 'bin/tetgen.mexmaci64', 'bin/tetgen_x86-64.exe', ... % Removing older tetgen completely (very sparsely used) 180 'bin/tetgen1.5.exe'}; 181 182 % === ANATOMY: ROAST === 183 PlugDesc(end+1) = GetStruct('roast'); 184 PlugDesc(end).Version = '3.0'; 185 PlugDesc(end).Category = 'Anatomy'; 186 PlugDesc(end).AutoUpdate = 1; 187 PlugDesc(end).URLzip = 'https://www.parralab.org/roast/roast-3.0.zip'; 188 PlugDesc(end).URLinfo = 'https://www.parralab.org/roast/'; 189 PlugDesc(end).TestFile = 'roast.m'; 190 PlugDesc(end).ReadmeFile = 'README.md'; 191 PlugDesc(end).CompiledStatus = 0; 192 PlugDesc(end).UnloadPlugs = {'spm12', 'iso2mesh'}; 193 PlugDesc(end).LoadFolders = {'lib/spm12', 'lib/iso2mesh', 'lib/cvx', 'lib/ncs2daprox', 'lib/NIFTI_20110921'}; 194 195 % === ANATOMY: CT2MRIREG === 196 % this plugin is used for performing CT to MRI co-registration 197 PlugDesc(end+1) = GetStruct('ct2mrireg'); 198 PlugDesc(end).Version = 'github-master'; 199 PlugDesc(end).Category = 'Anatomy'; 200 PlugDesc(end).AutoUpdate = 1; 201 PlugDesc(end).URLzip = 'https://github.com/ajoshiusc/USCCleveland/archive/master.zip'; 202 PlugDesc(end).URLinfo = 'https://github.com/ajoshiusc/USCCleveland/tree/master/ct2mrireg'; 203 PlugDesc(end).TestFile = 'ct2mrireg.m'; 204 PlugDesc(end).ReadmeFile = 'ct2mrireg/README.md'; 205 PlugDesc(end).CompiledStatus = 2; 206 PlugDesc(end).LoadFolders = {'ct2mrireg'}; 207 PlugDesc(end).DeleteFiles = {'fmri_analysis', 'for_clio', 'mixed_atlas', 'process_script', 'reg_prepost', 'visualize_channels', '.gitignore', 'README.md'}; 208 209 % === FORWARD: OPENMEEG === 210 PlugDesc(end+1) = GetStruct('openmeeg'); 211 PlugDesc(end).Version = '2.4.1'; 212 PlugDesc(end).Category = 'Forward'; 213 PlugDesc(end).AutoUpdate = 1; 214 switch(OsType) 215 case 'linux64' 216 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Linux.tar.gz'; 217 PlugDesc(end).TestFile = 'libOpenMEEG.so'; 218 case 'mac64' 219 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-MacOSX.tar.gz'; 220 PlugDesc(end).TestFile = 'libOpenMEEG.1.1.0.dylib'; 221 case 'win32' 222 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/release-2.2/OpenMEEG-2.2.0-win32-x86-cl-OpenMP-shared.tar.gz'; 223 PlugDesc(end).TestFile = 'om_assemble.exe'; 224 case 'win64' 225 PlugDesc(end).URLzip = 'https://files.inria.fr/OpenMEEG/download/OpenMEEG-2.4.1-Win64.tar.gz'; 226 PlugDesc(end).TestFile = 'om_assemble.exe'; 227 end 228 PlugDesc(end).URLinfo = 'https://openmeeg.github.io/'; 229 PlugDesc(end).ExtraMenus = {'Alternate versions', 'web(''https://files.inria.fr/OpenMEEG/download/'', ''-browser'')'; ... 230 'Download Visual C++', 'web(''http://www.microsoft.com/en-us/download/details.aspx?id=14632'', ''-browser'')'; ... 231 'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/TutBem'', ''-browser'')'}; 232 PlugDesc(end).CompiledStatus = 1; 233 PlugDesc(end).LoadFolders = {'bin', 'lib'}; 234 235 % === FORWARD: DUNEURO === 236 PlugDesc(end+1) = GetStruct('duneuro'); 237 PlugDesc(end).Version = 'latest'; 238 PlugDesc(end).Category = 'Forward'; 239 PlugDesc(end).AutoUpdate = 1; 240 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=bst_duneuro.zip'; 241 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Duneuro'; 242 PlugDesc(end).TestFile = 'bst_duneuro_meeg_win64.exe'; 243 PlugDesc(end).CompiledStatus = 1; 244 PlugDesc(end).LoadFolders = {'bin'}; 245 246 % === INVERSE: BRAINENTROPY === 247 PlugDesc(end+1) = GetStruct('brainentropy'); 248 PlugDesc(end).Version = 'github-master'; 249 PlugDesc(end).Category = 'Inverse'; 250 PlugDesc(end).AutoUpdate = 1; 251 PlugDesc(end).URLzip = 'https://github.com/multi-funkim/best-brainstorm/archive/master.zip'; 252 PlugDesc(end).URLinfo = 'https://neuroimage.usc.edu/brainstorm/Tutorials/TutBEst'; 253 PlugDesc(end).TestFile = 'process_inverse_mem.m'; 254 PlugDesc(end).AutoLoad = 1; 255 PlugDesc(end).CompiledStatus = 2; 256 PlugDesc(end).LoadFolders = {'*'}; 257 PlugDesc(end).GetVersionFcn = @be_versions; 258 PlugDesc(end).DeleteFiles = {'docs', '.github'}; 259 260 % === I/O: ADI-SDK === ADInstrument SDK for reading LabChart files 261 PlugDesc(end+1) = GetStruct('adi-sdk'); 262 PlugDesc(end).Version = 'github-master'; 263 PlugDesc(end).Category = 'I/O'; 264 switch (OsType) 265 case 'win64', PlugDesc(end).URLzip = 'https://github.com/JimHokanson/adinstruments_sdk_matlab/archive/master.zip'; 266 end 267 PlugDesc(end).URLinfo = 'https://github.com/JimHokanson/adinstruments_sdk_matlab'; 268 PlugDesc(end).TestFile = 'adi.m'; 269 PlugDesc(end).CompiledStatus = 0; 270 271 % === I/O: AXION === 272 PlugDesc(end+1) = GetStruct('axion'); 273 PlugDesc(end).Version = '1.0'; 274 PlugDesc(end).Category = 'I/O'; 275 PlugDesc(end).URLzip = 'http://neuroimage.usc.edu/bst/getupdate.php?d=AxionBioSystems.zip'; 276 PlugDesc(end).URLinfo = 'https://www.axionbiosystems.com/products/software/neural-module'; 277 PlugDesc(end).TestFile = 'AxisFile.m'; 278 % PlugDesc(end).ReadmeFile = 'README.md'; 279 PlugDesc(end).CompiledStatus = 0; 280 281 % === I/O: BCI2000 === 282 PlugDesc(end+1) = GetStruct('bci2000'); 283 PlugDesc(end).Version = 'latest'; 284 PlugDesc(end).Category = 'I/O'; 285 PlugDesc(end).URLzip = 'https://bci2000.org/downloads/mex.zip'; 286 PlugDesc(end).URLinfo = 'https://www.bci2000.org/mediawiki/index.php/User_Reference:Matlab_MEX_Files'; 287 PlugDesc(end).TestFile = 'load_bcidat.m'; 288 PlugDesc(end).CompiledStatus = 0; 289 290 % === I/O: BLACKROCK === 291 PlugDesc(end+1) = GetStruct('blackrock'); 292 PlugDesc(end).Version = 'github-master'; 293 PlugDesc(end).Category = 'I/O'; 294 PlugDesc(end).URLzip = 'https://github.com/BlackrockMicrosystems/NPMK/archive/master.zip'; 295 PlugDesc(end).URLinfo = 'https://github.com/BlackrockMicrosystems/NPMK/blob/master/NPMK/Users%20Guide.pdf'; 296 PlugDesc(end).TestFile = 'openNSx.m'; 297 PlugDesc(end).CompiledStatus = 2; 298 PlugDesc(end).LoadFolders = {'*'}; 299 PlugDesc(end).DeleteFiles = {'NPMK/installNPMK.m', 'NPMK/Users Guide.pdf', 'NPMK/Versions.txt', ... 300 'NPMK/@KTUEAImpedanceFile', 'NPMK/@KTNSPOnline', 'NPMK/@KTNEVComments', 'NPMK/@KTFigureAxis', 'NPMK/@KTFigure', 'NPMK/@KTUEAMapFile/.svn', ... 301 'NPMK/openNSxSync.m', 'NPMK/NTrode Utilities', 'NPMK/NSx Utilities', 'NPMK/NEV Utilities', 'NPMK/LoadingEngines', ... 302 'NPMK/Other tools/.svn', 'NPMK/Other tools/edgeDetect.m', 'NPMK/Other tools/kshuffle.m', 'NPMK/Other tools/openCCF.m', 'NPMK/Other tools/parseCCF.m', ... 303 'NPMK/Other tools/periEventPlot.asv', 'NPMK/Other tools/periEventPlot.m', 'NPMK/Other tools/playSound.m', ... 304 'NPMK/Dependent Functions/.svn', 'NPMK/Dependent Functions/.DS_Store', 'NPMK/Dependent Functions/bnsx.dat', 'NPMK/Dependent Functions/syncPatternDetectNEV.m', ... 305 'NPMK/Dependent Functions/syncPatternDetectNSx.m', 'NPMK/Dependent Functions/syncPatternFinderNSx.m'}; 306 307 % === I/O: EASYH5 === 308 PlugDesc(end+1) = GetStruct('easyh5'); 309 PlugDesc(end).Version = 'github-master'; 310 PlugDesc(end).Category = 'I/O'; 311 PlugDesc(end).URLzip = 'https://github.com/NeuroJSON/easyh5/archive/master.zip'; 312 PlugDesc(end).URLinfo = 'https://github.com/NeuroJSON/easyh5'; 313 PlugDesc(end).TestFile = 'loadh5.m'; 314 PlugDesc(end).CompiledStatus = 2; 315 PlugDesc(end).LoadFolders = {'*'}; 316 PlugDesc(end).DeleteFiles = {'examples'}; 317 PlugDesc(end).ReadmeFile = 'README.md'; 318 319 % === I/O: MFF === 320 PlugDesc(end+1) = GetStruct('mff'); 321 PlugDesc(end).Version = 'github-master'; 322 PlugDesc(end).Category = 'I/O'; 323 PlugDesc(end).AutoUpdate = 0; 324 PlugDesc(end).URLzip = 'https://github.com/arnodelorme/mffmatlabio/archive/master.zip'; 325 PlugDesc(end).URLinfo = 'https://github.com/arnodelorme/mffmatlabio'; 326 PlugDesc(end).TestFile = 'eegplugin_mffmatlabio.m'; 327 PlugDesc(end).ReadmeFile = 'README.md'; 328 PlugDesc(end).MinMatlabVer = 803; % 2014a 329 PlugDesc(end).CompiledStatus = 0; 330 PlugDesc(end).LoadedFcn = @Configure; 331 % Stable version: http://neuroimage.usc.edu/bst/getupdate.php?d='mffmatlabio-3.5.zip' 332 333 % === I/O: NEUROELECTRICS === 334 PlugDesc(end+1) = GetStruct('neuroelectrics'); 335 PlugDesc(end).Version = '1.8'; 336 PlugDesc(end).Category = 'I/O'; 337 PlugDesc(end).AutoUpdate = 0; 338 PlugDesc(end).URLzip = 'https://sccn.ucsd.edu/eeglab/plugins/Neuroelectrics1.8.zip'; 339 PlugDesc(end).URLinfo = 'https://www.neuroelectrics.com/wiki/index.php/EEGLAB'; 340 PlugDesc(end).TestFile = 'pop_nedf.m'; 341 PlugDesc(end).ReadmeFile = 'README.txt'; 342 PlugDesc(end).CompiledStatus = 2; 343 PlugDesc(end).InstalledFcn = ['d=pwd; cd(fileparts(which(''pop_nedf''))); mkdir(''private''); ' ... 344 'f=fopen(''private' filesep 'eeg_emptyset.m'',''wt''); fprintf(f,''function EEG=eeg_emptyset()\nEEG=struct();''); fclose(f);' ... 345 'f=fopen(''private' filesep 'eeg_checkset.m'',''wt''); fprintf(f,''function EEG=eeg_checkset(EEG)''); fclose(f);' ... 346 'cd(d);']; 347 348 % === I/O: JSNIRF === 349 PlugDesc(end+1) = GetStruct('jsnirfy'); 350 PlugDesc(end).Version = 'github-master'; 351 PlugDesc(end).Category = 'I/O'; 352 PlugDesc(end).URLzip = 'https://github.com/NeuroJSON/jsnirfy/archive/master.zip'; 353 PlugDesc(end).URLinfo = 'https://github.com/NeuroJSON/jsnirfy'; 354 PlugDesc(end).TestFile = 'loadsnirf.m'; 355 PlugDesc(end).CompiledStatus = 2; 356 PlugDesc(end).LoadFolders = {'*'}; 357 PlugDesc(end).DeleteFiles = {'loadjsnirf.m', 'savejsnirf.m'}; 358 PlugDesc(end).ReadmeFile = 'README.md'; 359 PlugDesc(end).RequiredPlugs = {'easyh5'}; 360 361 % === I/O: NWB === 362 PlugDesc(end+1) = GetStruct('nwb'); 363 PlugDesc(end).Version = 'github-master'; 364 PlugDesc(end).Category = 'I/O'; 365 PlugDesc(end).URLzip = 'https://github.com/NeurodataWithoutBorders/matnwb/archive/master.zip'; 366 PlugDesc(end).URLinfo = 'https://github.com/NeurodataWithoutBorders/matnwb'; 367 PlugDesc(end).TestFile = 'nwbRead.m'; 368 PlugDesc(end).ReadmeFile = 'README.md'; 369 PlugDesc(end).MinMatlabVer = 901; % 2016b 370 PlugDesc(end).CompiledStatus = 0; 371 PlugDesc(end).LoadFolders = {'*'}; 372 PlugDesc(end).LoadedFcn = @Configure; 373 374 % === I/O: PLEXON === 375 PlugDesc(end+1) = GetStruct('plexon'); 376 PlugDesc(end).Version = '1.8.4'; 377 PlugDesc(end).Category = 'I/O'; 378 PlugDesc(end).URLzip = 'https://plexon.com/wp-content/uploads/2017/08/OmniPlex-and-MAP-Offline-SDK-Bundle_0.zip'; 379 PlugDesc(end).URLinfo = 'https://plexon.com/software-downloads/#software-downloads-SDKs'; 380 PlugDesc(end).TestFile = 'plx_info.m'; 381 PlugDesc(end).ReadmeFile = 'Change Log.txt'; 382 PlugDesc(end).CompiledStatus = 0; 383 PlugDesc(end).DownloadedFcn = ['d = fullfile(PlugDesc.Path, ''OmniPlex and MAP Offline SDK Bundle'');' ... 384 'unzip(fullfile(d, ''Matlab Offline Files SDK.zip''), PlugDesc.Path);' ... 385 'file_delete(d,1,3);']; 386 PlugDesc(end).InstalledFcn = ['if (exist(''mexPlex'', ''file'') ~= 3), d = pwd;' ... 387 'cd(fullfile(fileparts(which(''plx_info'')), ''mexPlex''));', ... 388 'build_and_verify_mexPlex; cd(d); end']; 389 390 % === I/O: PLOTLY === 391 PlugDesc(end+1) = GetStruct('plotly'); 392 PlugDesc(end).Version = 'github-master'; 393 PlugDesc(end).Category = 'I/O'; 394 PlugDesc(end).URLzip = 'https://github.com/plotly/plotly_matlab/archive/master.zip'; 395 PlugDesc(end).URLinfo = 'https://plotly.com/matlab/'; 396 PlugDesc(end).TestFile = 'plotlysetup_online.m'; 397 PlugDesc(end).ReadmeFile = 'README.mkdn'; 398 PlugDesc(end).CompiledStatus = 0; 399 PlugDesc(end).LoadFolders = {'*'}; 400 PlugDesc(end).ExtraMenus = {'Online tutorial', 'web(''https://neuroimage.usc.edu/brainstorm/Tutorials/Plotly'', ''-browser'')'}; 401 402 % === I/O: TDT-SDK === Tucker-Davis Technologies Matlab SDK 403 PlugDesc(end+1) = GetStruct('tdt-sdk'); 404 PlugDesc(end).Version = 'latest'; 405 PlugDesc(end).Category = 'I/O'; 406 PlugDesc(end).URLzip = 'https://www.tdt.com/files/examples/TDTMatlabSDK.zip'; 407 PlugDesc(end).URLinfo = 'https://www.tdt.com/support/matlab-sdk/'; 408 PlugDesc(end).TestFile = 'TDT_Matlab_Tools.pdf'; 409 PlugDesc(end).CompiledStatus = 0; 410 PlugDesc(end).LoadFolders = {'*'}; 411 412 % === I/O: XDF === 413 PlugDesc(end+1) = GetStruct('xdf'); 414 PlugDesc(end).Version = 'github-master'; 415 PlugDesc(end).Category = 'I/O'; 416 PlugDesc(end).AutoUpdate = 0; 417 PlugDesc(end).URLzip = 'https://github.com/xdf-modules/xdf-Matlab/archive/refs/heads/master.zip'; 418 PlugDesc(end).URLinfo = 'https://github.com/xdf-modules/xdf-Matlab'; 419 PlugDesc(end).TestFile = 'load_xdf.m'; 420 PlugDesc(end).ReadmeFile = 'readme.md'; 421 PlugDesc(end).CompiledStatus = 2; 422 423 % === SIMULATION: SIMMEEG === 424 PlugDesc(end+1) = GetStruct('simmeeg'); 425 PlugDesc(end).Version = 'github-master'; 426 PlugDesc(end).Category = 'Simulation'; 427 PlugDesc(end).AutoUpdate = 1; 428 PlugDesc(end).URLzip = 'https://github.com/branelab/SimMEEG/archive/master.zip'; 429 PlugDesc(end).URLinfo = 'https://audiospeech.ubc.ca/research/brane/brane-lab-software/'; 430 PlugDesc(end).TestFile = 'SimMEEG_GUI.m'; 431 PlugDesc(end).ReadmeFile = 'SIMMEEG_TERMS_OF_USE.txt'; 432 PlugDesc(end).CompiledStatus = 0; 433 PlugDesc(end).RequiredPlugs = {'fieldtrip', '20200911'}; 434 435 % === STATISTICS: LIBSVM === 436 PlugDesc(end+1) = GetStruct('libsvm'); 437 PlugDesc(end).Version = 'github-master'; 438 PlugDesc(end).Category = 'Statistics'; 439 PlugDesc(end).URLzip = 'https://github.com/cjlin1/libsvm/archive/master.zip'; 440 PlugDesc(end).URLinfo = 'https://www.csie.ntu.edu.tw/~cjlin/libsvm/'; 441 PlugDesc(end).TestFile = 'svm.cpp'; 442 PlugDesc(end).ReadmeFile = 'README'; 443 PlugDesc(end).MinMatlabVer = 803; % 2014a 444 PlugDesc(end).CompiledStatus = 2; 445 PlugDesc(end).LoadFolders = {'*'}; 446 PlugDesc(end).InstalledFcn = 'd=pwd; cd(fileparts(which(''make''))); make; cd(d);'; 447 448 % === STATISTICS: FASTICA === 449 PlugDesc(end+1) = GetStruct('fastica'); 450 PlugDesc(end).Version = '2.5'; 451 PlugDesc(end).Category = 'Statistics'; 452 PlugDesc(end).URLzip = 'https://research.ics.aalto.fi/ica/fastica/code/FastICA_2.5.zip'; 453 PlugDesc(end).URLinfo = 'https://research.ics.aalto.fi/ica/fastica/'; 454 PlugDesc(end).TestFile = 'fastica.m'; 455 PlugDesc(end).ReadmeFile = 'Contents.m'; 456 PlugDesc(end).CompiledStatus = 2; 457 458 % === STATISTICS: PICARD === 459 PlugDesc(end+1) = GetStruct('picard'); 460 PlugDesc(end).Version = 'github-master'; 461 PlugDesc(end).Category = 'Statistics'; 462 PlugDesc(end).URLzip = 'https://github.com/pierreablin/picard/archive/refs/heads/master.zip'; 463 PlugDesc(end).URLinfo = 'https://github.com/pierreablin/picard'; 464 PlugDesc(end).TestFile = 'picard.m'; 465 PlugDesc(end).ReadmeFile = 'README.rst'; 466 PlugDesc(end).CompiledStatus = 2; 467 PlugDesc(end).LoadFolders = {'matlab_octave'}; 468 469 % === ELECTROPHYSIOLOGY: DERIVELFP === 470 PlugDesc(end+1) = GetStruct('derivelfp'); 471 PlugDesc(end).Version = '1.0'; 472 PlugDesc(end).Category = 'e-phys'; 473 PlugDesc(end).AutoUpdate = 0; 474 PlugDesc(end).URLzip = 'http://packlab.mcgill.ca/despikingtoolbox.zip'; 475 PlugDesc(end).URLinfo = 'https://journals.physiology.org/doi/full/10.1152/jn.00642.2010'; 476 PlugDesc(end).TestFile = 'despikeLFP.m'; 477 PlugDesc(end).ReadmeFile = 'readme.txt'; 478 PlugDesc(end).CompiledStatus = 2; 479 PlugDesc(end).LoadFolders = {'toolbox'}; 480 PlugDesc(end).DeleteFiles = {'ExampleDespiking.m', 'appendixpaper.pdf', 'downsample2x.m', 'examplelfpdespiking.mat', 'sta.m', ... 481 'toolbox/delineSignal.m', 'toolbox/despikeLFPbyChunks.asv', 'toolbox/despikeLFPbyChunks.m'}; 482 483 % === ELECTROPHYSIOLOGY: Kilosort === 484 PlugDesc(end+1) = GetStruct('kilosort'); 485 PlugDesc(end).Version = 'github-master'; 486 PlugDesc(end).Category = 'e-phys'; 487 PlugDesc(end).URLzip = 'https://github.com/cortex-lab/KiloSort/archive/refs/heads/master.zip'; 488 PlugDesc(end).URLinfo = 'https://papers.nips.cc/paper/2016/hash/1145a30ff80745b56fb0cecf65305017-Abstract.html'; 489 PlugDesc(end).TestFile = 'fitTemplates.m'; 490 PlugDesc(end).ReadmeFile = 'readme.md'; 491 PlugDesc(end).CompiledStatus = 0; 492 PlugDesc(end).LoadFolders = {'*'}; 493 PlugDesc(end).RequiredPlugs = {'kilosort-wrapper'; 'phy'; 'npy-matlab'}; 494 PlugDesc(end).InstalledFcn = 'process_spikesorting_kilosort(''copyKilosortConfig'', bst_fullfile(bst_get(''UserPluginsDir''), ''kilosort'', ''KiloSort-master'', ''configFiles'', ''StandardConfig_MOVEME.m''), bst_fullfile(bst_get(''UserPluginsDir''), ''kilosort'', ''KiloSort-master'', ''KilosortStandardConfig.m''));'; 495 496 497 % === ELECTROPHYSIOLOGY: Kilosort Wrapper === 498 PlugDesc(end+1) = GetStruct('kilosort-wrapper'); 499 PlugDesc(end).Version = 'github-master'; 500 PlugDesc(end).Category = 'e-phys'; 501 PlugDesc(end).URLzip = 'https://github.com/brendonw1/KilosortWrapper/archive/refs/heads/master.zip'; 502 PlugDesc(end).URLinfo = 'https://zenodo.org/record/3604165'; 503 PlugDesc(end).TestFile = 'Kilosort2Neurosuite.m'; 504 PlugDesc(end).ReadmeFile = 'README.md'; 505 PlugDesc(end).CompiledStatus = 0; 506 507 % === ELECTROPHYSIOLOGY: phy === 508 PlugDesc(end+1) = GetStruct('phy'); 509 PlugDesc(end).Version = 'github-master'; 510 PlugDesc(end).Category = 'e-phys'; 511 PlugDesc(end).URLzip = 'https://github.com/cortex-lab/phy/archive/refs/heads/master.zip'; 512 PlugDesc(end).URLinfo = 'https://phy.readthedocs.io/en/latest/'; 513 PlugDesc(end).TestFile = 'feature_view_custom_grid.py'; 514 PlugDesc(end).LoadFolders = {'*'}; 515 PlugDesc(end).ReadmeFile = 'README.md'; 516 PlugDesc(end).CompiledStatus = 0; 517 PlugDesc(end).RequiredPlugs = {'npy-matlab'}; 518 519 % === ELECTROPHYSIOLOGY: npy-matlab === 520 PlugDesc(end+1) = GetStruct('npy-matlab'); 521 PlugDesc(end).Version = 'github-master'; 522 PlugDesc(end).Category = 'e-phys'; 523 PlugDesc(end).URLzip = 'https://github.com/kwikteam/npy-matlab/archive/refs/heads/master.zip'; 524 PlugDesc(end).URLinfo = 'https://github.com/kwikteam/npy-matlab'; 525 PlugDesc(end).TestFile = 'constructNPYheader.m'; 526 PlugDesc(end).LoadFolders = {'*'}; 527 PlugDesc(end).ReadmeFile = 'README.md'; 528 PlugDesc(end).CompiledStatus = 0; 529 530 % === ELECTROPHYSIOLOGY: ultramegasort2000 === 531 PlugDesc(end+1) = GetStruct('ultramegasort2000'); 532 PlugDesc(end).Version = 'github-master'; 533 PlugDesc(end).Category = 'e-phys'; 534 PlugDesc(end).URLzip = 'https://github.com/danamics/UMS2K/archive/refs/heads/master.zip'; 535 PlugDesc(end).URLinfo = 'https://github.com/danamics/UMS2K/blob/master/UltraMegaSort2000%20Manual.pdf'; 536 PlugDesc(end).TestFile = 'UltraMegaSort2000 Manual.pdf'; 537 PlugDesc(end).LoadFolders = {'*'}; 538 PlugDesc(end).ReadmeFile = 'README.md'; 539 PlugDesc(end).CompiledStatus = 0; 540 541 % === ELECTROPHYSIOLOGY: waveclus === 542 PlugDesc(end+1) = GetStruct('waveclus'); 543 PlugDesc(end).Version = 'github-master'; 544 PlugDesc(end).Category = 'e-phys'; 545 PlugDesc(end).URLzip = 'https://github.com/csn-le/wave_clus/archive/refs/heads/master.zip'; 546 PlugDesc(end).URLinfo = 'https://journals.physiology.org/doi/full/10.1152/jn.00339.2018'; 547 PlugDesc(end).TestFile = 'wave_clus.m'; 548 PlugDesc(end).LoadFolders = {'*'}; 549 PlugDesc(end).ReadmeFile = 'README.md'; 550 PlugDesc(end).CompiledStatus = 0; 551 552 % === NIRSTORM === 553 PlugDesc(end+1) = GetStruct('nirstorm'); 554 PlugDesc(end).Version = 'github-master'; 555 PlugDesc(end).Category = 'fNIRS'; 556 PlugDesc(end).AutoUpdate = 0; 557 PlugDesc(end).AutoLoad = 1; 558 PlugDesc(end).CompiledStatus = 2; 559 PlugDesc(end).URLzip = 'https://github.com/Nirstorm/nirstorm/archive/master.zip'; 560 PlugDesc(end).URLinfo = 'https://github.com/Nirstorm/nirstorm'; 561 PlugDesc(end).LoadFolders = {'bst_plugin/core','bst_plugin/forward','bst_plugin/GLM', 'bst_plugin/inverse' , 'bst_plugin/io','bst_plugin/math' ,'bst_plugin/mbll' ,'bst_plugin/misc', 'bst_plugin/OM', 'bst_plugin/preprocessing', 'bst_plugin/ppl'}; 562 PlugDesc(end).TestFile = 'process_nst_mbll.m'; 563 PlugDesc(end).ReadmeFile = 'README.md'; 564 PlugDesc(end).GetVersionFcn = 'nst_get_version'; 565 PlugDesc(end).RequiredPlugs = {'brainentropy'}; 566 PlugDesc(end).MinMatlabVer = 803; % 2014a 567 PlugDesc(end).DeleteFiles = {'scripts', 'test', 'run_tests.m', 'test_suite_bak.m', '.gitignore'}; 568 569 % === MCXLAB CUDA === 570 PlugDesc(end+1) = GetStruct('mcxlab-cuda'); 571 PlugDesc(end).Version = '2021.12.04'; 572 PlugDesc(end).Category = 'fNIRS'; 573 PlugDesc(end).AutoUpdate = 1; 574 PlugDesc(end).URLzip = 'http://mcx.space/nightly/release/v2020/lite/mcxlab-allinone-x86_64-v2020.zip'; 575 PlugDesc(end).TestFile = 'mcxlab.m'; 576 PlugDesc(end).URLinfo = 'http://mcx.space/wiki/'; 577 PlugDesc(end).CompiledStatus = 0; 578 PlugDesc(end).LoadFolders = {'*'}; 579 PlugDesc(end).UnloadPlugs = {'mcxlab-cl'}; 580 581 % === MCXLAB CL === 582 PlugDesc(end+1) = GetStruct('mcxlab-cl'); 583 PlugDesc(end).Version = '2020'; 584 PlugDesc(end).Category = 'fNIRS'; 585 PlugDesc(end).AutoUpdate = 0; 586 PlugDesc(end).URLzip = 'http://mcx.space/nightly/release/v2020/lite/mcxlabcl-allinone-x86_64-v2020.zip'; 587 PlugDesc(end).TestFile = 'mcxlabcl.m'; 588 PlugDesc(end).URLinfo = 'http://mcx.space/wiki/'; 589 PlugDesc(end).CompiledStatus = 2; 590 PlugDesc(end).LoadFolders = {'*'}; 591 PlugDesc(end).UnloadPlugs = {'mcxlab-cuda'}; 592 593 % === MIA === 594 PlugDesc(end+1) = GetStruct('mia'); 595 PlugDesc(end).Version = 'github-master'; 596 PlugDesc(end).Category = 'sEEG'; 597 PlugDesc(end).AutoUpdate = 0; 598 PlugDesc(end).AutoLoad = 1; 599 PlugDesc(end).CompiledStatus = 2; 600 PlugDesc(end).URLzip = 'https://github.com/MIA-iEEG/mia/archive/refs/heads/master.zip'; 601 PlugDesc(end).URLinfo = 'http://www.neurotrack.fr/mia/'; 602 PlugDesc(end).ReadmeFile = 'README.md'; 603 PlugDesc(end).MinMatlabVer = 803; % 2014a 604 PlugDesc(end).LoadFolders = {'*'}; 605 PlugDesc(end).TestFile = 'process_mia_export_db.m'; 606 PlugDesc(end).ExtraMenus = {'Start MIA', 'mia', 'loaded'}; 607 608 % === FIELDTRIP === 609 PlugDesc(end+1) = GetStruct('fieldtrip'); 610 PlugDesc(end).Version = 'latest'; 611 PlugDesc(end).AutoUpdate = 0; 612 PlugDesc(end).URLzip = 'https://download.fieldtriptoolbox.org/fieldtrip-lite-20220228.zip'; 613 PlugDesc(end).URLinfo = 'http://www.fieldtriptoolbox.org'; 614 PlugDesc(end).TestFile = 'ft_defaults.m'; 615 PlugDesc(end).ReadmeFile = 'README'; 616 PlugDesc(end).CompiledStatus = 2; 617 PlugDesc(end).UnloadPlugs = {'spm12', 'roast'}; 618 PlugDesc(end).LoadFolders = {'specest', 'preproc', 'forward', 'src', 'utilities'}; 619 PlugDesc(end).GetVersionFcn = 'ft_version'; 620 PlugDesc(end).LoadedFcn = ['global ft_default; ' ... 621 'ft_default = []; ' ... 622 'if exist(''filtfilt'', ''file''), ft_default.toolbox.signal=''matlab''; end; ' ... 623 'if exist(''nansum'', ''file''), ft_default.toolbox.stats=''matlab''; end; ' ... 624 'if exist(''rgb2hsv'', ''file''), ft_default.toolbox.images=''matlab''; end; ' ... 625 'ft_defaults;']; 626 627 % === SPM12 === 628 PlugDesc(end+1) = GetStruct('spm12'); 629 PlugDesc(end).Version = 'latest'; 630 PlugDesc(end).AutoUpdate = 0; 631 switch(OsType) 632 case 'mac64arm' 633 PlugDesc(end).URLzip = 'https://github.com/spm/spm12/archive/refs/heads/maint.zip'; 634 PlugDesc(end).Version = 'github-maint'; 635 otherwise 636 PlugDesc(end).Version = 'latest'; 637 PlugDesc(end).URLzip = 'https://www.fil.ion.ucl.ac.uk/spm/download/restricted/eldorado/spm12.zip'; 638 end 639 PlugDesc(end).URLinfo = 'https://www.fil.ion.ucl.ac.uk/spm/'; 640 PlugDesc(end).TestFile = 'spm.m'; 641 PlugDesc(end).ReadmeFile = 'README.md'; 642 PlugDesc(end).CompiledStatus = 2; 643 PlugDesc(end).UnloadPlugs = {'fieldtrip', 'roast'}; 644 PlugDesc(end).LoadFolders = {'matlabbatch'}; 645 PlugDesc(end).GetVersionFcn = 'bst_getoutvar(2, @spm, ''Ver'')'; 646 PlugDesc(end).LoadedFcn = 'spm(''defaults'',''EEG'');'; 647 % ================================================================================================================ 648 649 % Select only one plugin 650 if ~isempty(SelPlug) 651 % Get plugin name 652 if ischar(SelPlug) 653 PlugName = SelPlug; 654 else 655 PlugName = SelPlug.Name; 656 end 657 % Find in the list of plugins 658 iPlug = find(strcmpi({PlugDesc.Name}, PlugName)); 659 if ~isempty(iPlug) 660 PlugDesc = PlugDesc(iPlug); 661 else 662 PlugDesc = []; 663 end 664 end 665 end 666 667 668 %% ===== PLUGIN STRUCT ===== 669 function s = GetStruct(PlugName) 670 s = db_template('PlugDesc'); 671 s.Name = PlugName; 672 end 673 674 675 %% ===== CONFIGURE PLUGIN ===== 676 function Configure(PlugDesc) 677 switch (PlugDesc.Name) 678 case 'mff' 679 % Add .jar file to static classpath 680 if ~exist('com.egi.services.mff.api.MFFFactory', 'class') 681 jarList = dir(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'MFF-*.jar')); 682 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, jarList(1).name); 683 disp(['BST> Adding to Java classpath: ' jarPath]); 684 warning off 685 javaaddpathstatic(jarPath); 686 javaaddpath(jarPath); 687 warning on 688 end 689 690 case 'nwb' 691 % Add .jar file to static classpath 692 if ~exist('Schema', 'class') 693 jarPath = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, 'jar', 'schema.jar'); 694 disp(['BST> Adding to Java classpath: ' jarPath]); 695 warning off 696 javaaddpathstatic(jarPath); 697 javaaddpath(jarPath); 698 warning on 699 schema = Schema(); 700 end 701 % Go to NWB folder 702 curDir = pwd; 703 cd(bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder)); 704 % Generate the NWB Schema (must be executed from the NWB folder) 705 generateCore(); 706 % Restore current directory 707 cd(curDir); 708 end 709 end 710 711 712 %% ===== GET ONLINE VERSION ===== 713 % Get the latest online version of some plugins 714 function [Version, URLzip] = GetVersionOnline(PlugName, URLzip, isCache) 715 global GlobalData; 716 Version = []; 717 % Parse inputs 718 if (nargin < 2) || isempty(URLzip) 719 URLzip = []; 720 end 721 % Use cache by default, to avoid fetching online too many times the same info 722 if (nargin < 3) || isempty(isCache) 723 isCache = 1; 724 end 725 % No internet: skip 726 if ~GlobalData.Program.isInternet 727 return; 728 end 729 % Check for existing plugin cache 730 strCache = [PlugName, '_online_', strrep(date,'-','')]; 731 if isCache && isfield(GlobalData.Program.PluginCache, strCache) && isfield(GlobalData.Program.PluginCache.(strCache), 'Version') 732 Version = GlobalData.Program.PluginCache.(strCache).Version; 733 URLzip = GlobalData.Program.PluginCache.(strCache).URLzip; 734 return; 735 end 736 % Get version online 737 try 738 switch (PlugName) 739 case 'spm12' 740 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 741 disp(['BST> Checking latest online version for ' PlugName '...']); 742 s = bst_webread('http://www.fil.ion.ucl.ac.uk/spm/download/spm12_updates/'); 743 if ~isempty(s) 744 n = regexp(s,'spm12_updates_r(\d.*?)\.zip','tokens','once'); 745 if ~isempty(n) && ~isempty(n{1}) 746 Version = n{1}; 747 end 748 end 749 case 'cat12' 750 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 751 disp(['BST> Checking latest online version for ' PlugName '...']); 752 s = bst_webread('http://www.neuro.uni-jena.de/cat12/'); 753 if ~isempty(s) 754 n = regexp(s,'cat12_r(\d.*?)\.zip','tokens'); 755 if ~isempty(n) 756 Version = max(cellfun(@str2double, [n{:}])); 757 Version = num2str(Version); 758 end 759 end 760 case 'fieldtrip' 761 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 762 disp(['BST> Checking latest online version for ' PlugName '...']); 763 s = bst_webread('https://download.fieldtriptoolbox.org'); 764 if ~isempty(s) 765 n = regexp(s,'fieldtrip-lite-(\d.*?)\.zip','tokens'); 766 if ~isempty(n) 767 Version = max(cellfun(@str2double, [n{:}])); 768 Version = num2str(Version); 769 URLzip = ['https://download.fieldtriptoolbox.org/fieldtrip-lite-' Version '.zip']; 770 end 771 end 772 case 'duneuro' 773 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 774 disp(['BST> Checking latest online version for ' PlugName '...']); 775 str = bst_webread('http://neuroimage.usc.edu/bst/getversion_duneuro.php'); 776 Version = str(1:6); 777 case 'nirstorm' 778 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 779 disp(['BST> Checking latest online version for ' PlugName '...']); 780 str = bst_webread('https://raw.githubusercontent.com/Nirstorm/nirstorm/master/bst_plugin/VERSION'); 781 Version = strtrim(str(9:end)); 782 case 'brainentropy' 783 bst_progress('text', ['Checking latest online version for ' PlugName '...']); 784 disp(['BST> Checking latest online version for ' PlugName '...']); 785 str = bst_webread('https://raw.githubusercontent.com/multifunkim/best-brainstorm/master/best/VERSION.txt'); 786 str = strsplit(str,'\n'); 787 Version = strtrim(str{1}); 788 otherwise 789 % If downloading from github: Get last GitHub commit SHA 790 if isGithubMaster(URLzip) 791 Version = GetGithubCommit(URLzip); 792 else 793 return; 794 end 795 end 796 % Executed only if the version was fetched successfully: Keep cached version 797 GlobalData.Program.PluginCache.(strCache).Version = Version; 798 GlobalData.Program.PluginCache.(strCache).URLzip = URLzip; 799 catch 800 disp(['BST> Error: Could not get online version for plugin: ' PlugName]); 801 end 802 end 803 804 805 %% ===== IS GITHUB MASTER ====== 806 % Returns 1 if the URL is a github master/main branch 807 function isMaster = isGithubMaster(URLzip) 808 isMaster = ~isempty(strfind(URLzip, 'https://github.com/')) && (~isempty(strfind(URLzip, 'master.zip')) || ~isempty(strfind(URLzip, 'main.zip'))); 809 end 810 811 812 %% ===== GET GITHUB COMMIT ===== 813 % Get SHA of the GitHub HEAD commit 814 function sha = GetGithubCommit(URLzip) 815 zipUri = matlab.net.URI(URLzip); 816 % Primary branch name: master or main 817 [~, primaryBranch] = bst_fileparts(char(zipUri.Path(end))); 818 % Default result 819 sha = ['github-', primaryBranch]; 820 % Only available after Matlab 2016b (because of matlab.net.http.RequestMessage) 821 if (bst_get('MatlabVersion') < 901) 822 return; 823 end 824 % Try getting the SHA from the GitHub API 825 try 826 % Get GitHub repository path 827 zipUri = matlab.net.URI(URLzip); 828 gitUser = char(zipUri.Path(2)); 829 gitRepo = char(zipUri.Path(3)); 830 % Request last commit SHA with GitHub API 831 apiUri = matlab.net.URI(['https://api.github.com/repos/' gitUser '/' gitRepo '/commits/' primaryBranch]); 832 request = matlab.net.http.RequestMessage; 833 request = request.addFields(matlab.net.http.HeaderField('Accept', 'application/vnd.github.VERSION.sha')); 834 r = send(request, apiUri); 835 sha = char(r.Body.Data); 836 catch 837 disp(['BST> Warning: Could not get GitHub version for URL: ' zipUrl]); 838 end 839 end 840 841 842 %% ===== COMPARE VERSIONS ===== 843 % Returns: 0: v1==v2 844 % -1: v1<v2 845 % 1: v1>v2 846 function res = CompareVersions(v1, v2) 847 % Get numbers 848 iNum1 = find(ismember(v1, '0123456789')); 849 iNum2 = find(ismember(v2, '0123456789')); 850 iDot1 = find(v1 == '.'); 851 iDot2 = find(v2 == '.'); 852 % Equality (or one input empty) 853 if isequal(v1,v2) || isempty(v1) || isempty(v2) 854 res = 0; 855 % Only numbers 856 elseif (length(iNum1) == length(v1)) && (length(iNum2) == length(v2)) 857 n1 = str2double(v1); 858 n2 = str2double(v2); 859 if (n1 > n2) 860 res = 1; 861 elseif (n1 < n2) 862 res = -1; 863 else 864 res = 0; 865 end 866 % Format '1.2.3' 867 elseif (~isempty(iDot1) || ~isempty(iDot2)) && ~isempty(iNum1) && ~isempty(iNum2) 868 % Get subversions 1 869 split1 = str_split(v1, '.'); 870 sub1 = []; 871 for i = 1:length(split1) 872 t = str2num(split1{i}(ismember(split1{i},'0123456789'))); 873 if ~isempty(t) 874 sub1(end+1) = t; 875 else 876 break; 877 end 878 end 879 % Get subversions 1 880 split2 = str_split(v2, '.'); 881 sub2 = []; 882 for i = 1:length(split2) 883 t = str2num(split2{i}(ismember(split2{i},'0123456789'))); 884 if ~isempty(t) 885 sub2(end+1) = t; 886 else 887 break; 888 end 889 end 890 % Add extra zeros to the shortest (so that "1.2" is higher than "1") 891 if (length(sub1) < length(sub2)) 892 tmp = sub1; 893 sub1 = zeros(size(sub2)); 894 sub1(1:length(tmp)) = tmp; 895 elseif (length(sub1) > length(sub2)) 896 tmp = sub2; 897 sub2 = zeros(size(sub1)); 898 sub2(1:length(tmp)) = tmp; 899 end 900 % Compare number by number 901 for i = 1:length(sub1) 902 if (sub1(i) > sub2(i)) 903 res = 1; 904 return; 905 elseif (sub1(i) < sub2(i)) 906 res = -1; 907 return; 908 else 909 res = 0; 910 end 911 end 912 % Mixture of numbers and digits: natural sorting of strings 913 else 914 [s,I] = sort_nat({v1, v2}); 915 if (I(1) == 1) 916 res = -1; 917 else 918 res = 1; 919 end 920 end 921 end 922 923 924 %% ===== EXECUTE CALLBACK ===== 925 function [isOk, errMsg] = ExecuteCallback(PlugDesc, f) 926 isOk = 0; 927 errMsg = ''; 928 if ~isempty(PlugDesc.(f)) 929 try 930 if ischar(PlugDesc.(f)) 931 disp(['BST> Executing callback ' f ': ' PlugDesc.(f)]); 932 eval(PlugDesc.(f)); 933 elseif isa(PlugDesc.(f), 'function_handle') 934 disp(['BST> Executing callback ' f ': ' func2str(PlugDesc.(f))]); 935 feval(PlugDesc.(f), PlugDesc); 936 end 937 catch 938 errMsg = ['Error executing callback ' f ': ' 10 lasterr]; 939 return; 940 end 941 end 942 isOk = 1; 943 end 944 945 946 %% ===== GET INSTALLED PLUGINS ===== 947 % USAGE: [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled', PlugName/PlugDesc) % Get one installed plugin 948 % [PlugDesc, SearchPlugs] = bst_plugin('GetInstalled') % Get all installed plugins 949 function [PlugDesc, SearchPlugs] = GetInstalled(SelPlug) 950 % Parse inputs 951 if (nargin < 1) || isempty(SelPlug) 952 SelPlug = []; 953 end 954 955 % === DEFINE SEARCH LIST === 956 % Looking for a single plugin 957 if ~isempty(SelPlug) 958 SearchPlugs = GetSupported(SelPlug); 959 % Looking for all supported plugins 960 else 961 SearchPlugs = GetSupported(); 962 end 963 % Brainstorm plugin folder 964 UserPluginsDir = bst_get('UserPluginsDir'); 965 % Custom plugin paths 966 PluginCustomPath = bst_get('PluginCustomPath'); 967 % Matlab path 968 matlabPath = str_split(path, pathsep); 969 % Compiled distribution 970 isCompiled = bst_iscompiled(); 971 972 % === LOOK FOR SUPPORTED PLUGINS === 973 % Empty plugin structure 974 PlugDesc = repmat(db_template('PlugDesc'), 0); 975 % Look for each plugin in the search list 976 for iSearch = 1:length(SearchPlugs) 977 % Compiled: skip plugins that are not available 978 if isCompiled && (SearchPlugs(iSearch).CompiledStatus == 0) 979 continue; 980 end 981 % Theoretical plugin path 982 PlugName = SearchPlugs(iSearch).Name; 983 PlugPath = bst_fullfile(UserPluginsDir, PlugName); 984 % Check if test function is available in the Matlab path 985 TestFilePath = GetTestFilePath(SearchPlugs(iSearch)); 986 % If installed software found in Matlab path 987 if ~isempty(TestFilePath) 988 % Register loaded plugin 989 iPlug = length(PlugDesc) + 1; 990 PlugDesc(iPlug) = SearchPlugs(iSearch); 991 PlugDesc(iPlug).isLoaded = 1; 992 % Check if the file is inside the Brainstorm user folder (where it is supposed to be) => Managed plugin 993 if ~isempty(strfind(TestFilePath, PlugPath)) 994 PlugDesc(iPlug).isManaged = 1; 995 % Process compiled together with Brainstorm 996 elseif isCompiled && ~isempty(strfind(TestFilePath, ['.brainstorm' filesep 'plugins' filesep PlugName])) 997 compiledDir = ['.brainstorm' filesep 'plugins' filesep PlugName]; 998 iPath = strfind(TestFilePath, compiledDir); 999 PlugPath = [TestFilePath(1:iPath-2), filesep, compiledDir]; 1000 % Otherwise: Custom installation 1001 else 1002 % If the test file was found in a defined subfolder: remove the subfolder from the plugin path 1003 PlugPath = TestFilePath; 1004 for iSub = 1:length(PlugDesc(iPlug).LoadFolders) 1005 subDir = strrep(PlugDesc(iPlug).LoadFolders{iSub}, '/', filesep); 1006 if (length(PlugPath) > length(subDir)) && isequal(PlugPath(end-length(subDir)+1:end), subDir) 1007 PlugPath = PlugPath(1:end - length(subDir) - 1); 1008 break; 1009 end 1010 end 1011 PlugDesc(iPlug).isManaged = 0; 1012 end 1013 PlugDesc(iPlug).Path = PlugPath; 1014 % Plugin installed: Managed by Brainstorm 1015 elseif isdir(PlugPath) && file_exist(bst_fullfile(PlugPath, 'plugin.mat')) 1016 iPlug = length(PlugDesc) + 1; 1017 PlugDesc(iPlug) = SearchPlugs(iSearch); 1018 PlugDesc(iPlug).Path = PlugPath; 1019 PlugDesc(iPlug).isLoaded = 0; 1020 PlugDesc(iPlug).isManaged = 1; 1021 % Plugin installed: Custom path 1022 elseif isfield(PluginCustomPath, PlugName) && ~isempty(PluginCustomPath.(PlugName)) && file_exist(PluginCustomPath.(PlugName)) 1023 iPlug = length(PlugDesc) + 1; 1024 PlugDesc(iPlug) = SearchPlugs(iSearch); 1025 PlugDesc(iPlug).Path = PluginCustomPath.(PlugName); 1026 PlugDesc(iPlug).isLoaded = 0; 1027 PlugDesc(iPlug).isManaged = 0; 1028 end 1029 end 1030 1031 % === LOOK FOR UNREFERENCED PLUGINS === 1032 % Compiled: do not look for unreferenced plugins 1033 if isCompiled 1034 PlugList = []; 1035 % Get a specific unlisted plugin 1036 elseif ~isempty(SelPlug) 1037 % Get plugin name 1038 if ischar(SelPlug) 1039 PlugName = lower(SelPlug); 1040 else 1041 PlugName = SelPlug.Name; 1042 end 1043 % If plugin is already referenced: skip 1044 if ismember(PlugName, {PlugDesc.Name}) 1045 PlugList = []; 1046 % Else: Try to get target plugin as unreferenced 1047 else 1048 PlugList = struct('name', PlugName); 1049 end 1050 % Get all folders in Brainstorm plugins folder 1051 else 1052 PlugList = dir(UserPluginsDir); 1053 end 1054 % Process folders containing a plugin.mat file 1055 for iDir = 1:length(PlugList) 1056 % Ignore entry if plugin name is already in list of documented plugins 1057 PlugName = PlugList(iDir).name; 1058 if ismember(PlugName, {PlugDesc.Name}) 1059 continue; 1060 end 1061 % Process only folders 1062 PlugDir = bst_fullfile(UserPluginsDir, PlugName); 1063 if ~isdir(PlugDir) || (PlugName(1) == '.') 1064 continue; 1065 end 1066 % Process only folders containing a 'plugin.mat' file 1067 PlugMatFile = bst_fullfile(PlugDir, 'plugin.mat'); 1068 if ~file_exist(PlugMatFile) 1069 continue; 1070 end 1071 % If selecting only one plugin 1072 if ~isempty(SelPlug) && ischar(SelPlug) && ~strcmpi(PlugName, SelPlug) 1073 continue; 1074 end 1075 % Add plugin to list 1076 iPlug = length(PlugDesc) + 1; 1077 PlugDesc(iPlug) = GetStruct(PlugList(iDir).name); 1078 PlugDesc(iPlug).Path = PlugDir; 1079 PlugDesc(iPlug).isManaged = 1; 1080 PlugDesc(iPlug).isLoaded = ismember(PlugDir, matlabPath); 1081 end 1082 1083 % === READ PLUGIN.MAT === 1084 for iPlug = 1:length(PlugDesc) 1085 % Try to load the plugin.mat file in the plugin folder 1086 PlugMatFile = bst_fullfile(PlugDesc(iPlug).Path, 'plugin.mat'); 1087 if file_exist(PlugMatFile) 1088 try 1089 PlugMat = load(PlugMatFile); 1090 catch 1091 PlugMat = struct(); 1092 end 1093 % Copy fields 1094 excludedFields = {'Name', 'Path', 'isLoaded', 'isManaged', 'LoadedFcn', 'UnloadedFcn', 'DownloadedFcn', 'InstalledFcn', 'UninstalledFcn'}; 1095 loadFields = setdiff(fieldnames(db_template('PlugDesc')), excludedFields); 1096 for iField = 1:length(loadFields) 1097 if isfield(PlugMat, loadFields{iField}) && ~isempty(PlugMat.(loadFields{iField})) 1098 PlugDesc(iPlug).(loadFields{iField}) = PlugMat.(loadFields{iField}); 1099 end 1100 end 1101 else 1102 PlugDesc(iPlug).URLzip = []; 1103 end 1104 end 1105 end 1106 1107 1108 %% ===== GET DESCRIPTION ===== 1109 % USAGE: [PlugDesc, errMsg] = GetDescription(PlugName/PlugDesc) 1110 function [PlugDesc, errMsg] = GetDescription(PlugName) 1111 % Initialize returned values 1112 errMsg = ''; 1113 PlugDesc = []; 1114 % CALL: GetDescription(PlugDesc) 1115 if isstruct(PlugName) 1116 % Add the missing fields 1117 PlugDesc = struct_copy_fields(PlugName, db_template('PlugDesc'), 0); 1118 % CALL: GetDescription(PlugName) 1119 elseif ischar(PlugName) 1120 % Get supported plugins 1121 AllPlugs = GetSupported(); 1122 % Find plugin in supported plugins 1123 iPlug = find(strcmpi({AllPlugs.Name}, PlugName)); 1124 if isempty(iPlug) 1125 errMsg = ['Unknown plugin: ' PlugName]; 1126 return; 1127 end 1128 % Return found plugin 1129 PlugDesc = AllPlugs(iPlug); 1130 else 1131 errMsg = 'Invalid call to GetDescription().'; 1132 end 1133 end 1134 1135 1136 %% ===== GET TEST FILE PATH ===== 1137 function TestFilePath = GetTestFilePath(PlugDesc) 1138 % If a test file is defined 1139 if ~isempty(PlugDesc.TestFile) 1140 % Try to find the test function in the path 1141 whichTest = which(PlugDesc.TestFile); 1142 % If it was found: use the parent folder 1143 if ~isempty(whichTest) 1144 % Get the test file path 1145 TestFilePath = bst_fileparts(whichTest); 1146 % FieldTrip: Ignore if found embedded in SPM12 1147 if strcmpi(PlugDesc.Name, 'fieldtrip') 1148 p = which('spm.m'); 1149 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1150 TestFilePath = []; 1151 end 1152 % SPM12: Ignore if found embedded in ROAST 1153 elseif strcmpi(PlugDesc.Name, 'spm12') 1154 p = which('roast.m'); 1155 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1156 TestFilePath = []; 1157 end 1158 % Iso2mesh: Ignore if found embedded in ROAST 1159 elseif strcmpi(PlugDesc.Name, 'iso2mesh') 1160 p = which('roast.m'); 1161 if ~isempty(p) && ~isempty(strfind(TestFilePath, bst_fileparts(p))) 1162 TestFilePath = []; 1163 end 1164 end 1165 else 1166 TestFilePath = []; 1167 end 1168 else 1169 TestFilePath = []; 1170 end 1171 end 1172 1173 1174 %% ===== GET README FILE ==== 1175 % Get full path to the readme file 1176 function ReadmeFile = GetReadmeFile(PlugDesc) 1177 ReadmeFile = []; 1178 % If readme file is defined in the plugin structure 1179 if ~isempty(PlugDesc.ReadmeFile) 1180 % If full path already set: use it 1181 if file_exist(PlugDesc.ReadmeFile) 1182 ReadmeFile = PlugDesc.ReadmeFile; 1183 % Else: check in the plugin Path/SubFolder 1184 else 1185 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.ReadmeFile); 1186 if file_exist(tmpFile) 1187 ReadmeFile = tmpFile; 1188 elseif ~isempty(PlugDesc.SubFolder) 1189 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.ReadmeFile); 1190 if file_exist(tmpFile) 1191 ReadmeFile = tmpFile; 1192 end 1193 end 1194 end 1195 end 1196 % Search for default readme 1197 if isempty(ReadmeFile) 1198 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_readme.txt']); 1199 if file_exist(tmpFile) 1200 ReadmeFile = tmpFile; 1201 end 1202 end 1203 end 1204 1205 1206 %% ===== GET LOGO FILE ==== 1207 % Get full path to the logo file 1208 function LogoFile = GetLogoFile(PlugDesc) 1209 LogoFile = []; 1210 % If logo file is defined in the plugin structure 1211 if ~isempty(PlugDesc.LogoFile) 1212 % If full path already set: use it 1213 if file_exist(PlugDesc.LogoFile) 1214 LogoFile = PlugDesc.LogoFile; 1215 % Else: check in the plugin Path/SubFolder 1216 else 1217 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.LogoFile); 1218 if file_exist(tmpFile) 1219 LogoFile = tmpFile; 1220 elseif ~isempty(PlugDesc.SubFolder) 1221 tmpFile = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.LogoFile); 1222 if file_exist(tmpFile) 1223 LogoFile = tmpFile; 1224 end 1225 end 1226 end 1227 end 1228 % Search for default logo 1229 if isempty(LogoFile) 1230 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.gif']); 1231 if file_exist(tmpFile) 1232 LogoFile = tmpFile; 1233 end 1234 end 1235 if isempty(LogoFile) 1236 tmpFile = bst_fullfile(bst_get('BrainstormDocDir'), 'plugins', [PlugDesc.Name '_logo.png']); 1237 if file_exist(tmpFile) 1238 LogoFile = tmpFile; 1239 end 1240 end 1241 end 1242 1243 1244 %% ===== INSTALL ===== 1245 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('Install', PlugName, isInteractive=0, minVersion=[]) 1246 function [isOk, errMsg, PlugDesc] = Install(PlugName, isInteractive, minVersion) 1247 % Returned variables 1248 isOk = 0; 1249 % Parse inputs 1250 if (nargin < 3) || isempty(minVersion) 1251 minVersion = []; 1252 elseif isnumeric(minVersion) 1253 minVersion = num2str(minVersion); 1254 end 1255 if (nargin < 2) || isempty(isInteractive) 1256 isInteractive = 0; 1257 end 1258 if ~ischar(PlugName) 1259 errMsg = 'Invalid call to Install()'; 1260 PlugDesc = []; 1261 return; 1262 end 1263 % Get plugin structure from name 1264 [PlugDesc, errMsg] = GetDescription(PlugName); 1265 if ~isempty(errMsg) 1266 return; 1267 end 1268 % Check if plugin is supported on Apple silicon 1269 OsType = bst_get('OsType', 0); 1270 if strcmpi(OsType, 'mac64arm') && ismember(PlugName, PluginsNotSupportAppleSilicon()) 1271 errMsg = ['Plugin ', PlugName ' is not supported on Apple silicon yet.']; 1272 PlugDesc = []; 1273 return; 1274 end 1275 % Check if there is a URL to download 1276 if isempty(PlugDesc.URLzip) 1277 errMsg = ['No download URL for ', OsType, ': ', PlugName '']; 1278 return; 1279 end 1280 % Compiled version 1281 isCompiled = bst_iscompiled(); 1282 if isCompiled && (PlugDesc.CompiledStatus == 0) 1283 errMsg = ['Plugin ', PlugName ' is not available in the compiled version of Brainstorm.']; 1284 return; 1285 end 1286 % Minimum Matlab version 1287 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 1288 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 1289 errMsg = ['Plugin ', PlugName ' is not supported for versions of Matlab <= ' strMinVer]; 1290 return; 1291 end 1292 % Get online update (use existing cache) 1293 [newVersion, newURLzip] = GetVersionOnline(PlugName, PlugDesc.URLzip, 1); 1294 if ~isempty(newVersion) 1295 PlugDesc.Version = newVersion; 1296 end 1297 if ~isempty(newURLzip) 1298 PlugDesc.URLzip = newURLzip; 1299 end 1300 1301 % === PROCESS DEPENDENCIES === 1302 % Check required plugins 1303 if ~isempty(PlugDesc.RequiredPlugs) 1304 bst_progress('text', ['Processing dependencies for ' PlugName '...']); 1305 disp(['BST> Processing dependencies: ' PlugName ' requires: ' sprintf('%s ', PlugDesc.RequiredPlugs{:,1})]); 1306 % Get the list of plugins that need to be installed 1307 installPlugs = {}; 1308 installVer = {}; 1309 strInstall = ''; 1310 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 1311 PlugCheck = GetInstalled(PlugDesc.RequiredPlugs{iPlug,1}); 1312 % Plugin not install: Install it 1313 if isempty(PlugCheck) 1314 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 1315 installVer{end+1} = []; 1316 strInstall = [strInstall, '<B>' installPlugs{end} '</B> ']; 1317 % Plugin installed: check version 1318 elseif (size(PlugDesc.RequiredPlugs,2) == 2) 1319 minVerDep = PlugDesc.RequiredPlugs{iPlug,2}; 1320 if ~isempty(minVerDep) && (CompareVersions(minVerDep, PlugCheck.Version) > 0) 1321 installPlugs{end+1} = PlugDesc.RequiredPlugs{iPlug,1}; 1322 installVer{end+1} = PlugDesc.RequiredPlugs{iPlug,2}; 1323 strInstall = [strInstall, '<B>' installPlugs{end} '</B>(' installVer{end} ') ']; 1324 end 1325 end 1326 end 1327 % If there are plugins to install 1328 if ~isempty(installPlugs) 1329 if isInteractive 1330 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> requires: ' strInstall ... 1331 '<BR><BR>Brainstorm will now install these plugins.' 10 10], 'Plugin manager'); 1332 end 1333 for iPlug = 1:length(installPlugs) 1334 [isInstalled, errMsg] = Install(installPlugs{iPlug}, isInteractive, installPlugs{iPlug}); 1335 if ~isInstalled 1336 errMsg = ['Error processing dependency: ' PlugDesc.RequiredPlugs{iPlug,1} 10 errMsg]; 1337 return; 1338 end 1339 end 1340 end 1341 end 1342 1343 % === UPDATE: CHECK PREVIOUS INSTALL === 1344 % Check if installed 1345 OldPlugDesc = GetInstalled(PlugName); 1346 % If already installed 1347 if ~isempty(OldPlugDesc) 1348 % If the plugin is not managed by Brainstorm: do not check versions 1349 if ~OldPlugDesc.isManaged 1350 isUpdate = 0; 1351 % If the requested version is higher 1352 elseif ~isempty(minVersion) && (CompareVersions(minVersion, OldPlugDesc.Version) > 0) 1353 isUpdate = 1; 1354 strUpdate = ['the installed version is outdated.<BR>Minimum version required: <I>' minVersion '</I>']; 1355 % If an update is available and auto-updates are requested 1356 elseif (PlugDesc.AutoUpdate == 1) && bst_get('AutoUpdates') && ... % If updates are enabled 1357 ((isGithubMaster(PlugDesc.URLzip) && ~strcmpi(PlugDesc.Version, OldPlugDesc.Version)) || ... % GitHub-master: update if different commit SHA strings 1358 (~isGithubMaster(PlugDesc.URLzip) && (CompareVersions(PlugDesc.Version, OldPlugDesc.Version) > 0))) % Regular stable version: update if online version is newer 1359 isUpdate = 1; 1360 strUpdate = 'an update is available online.'; 1361 else 1362 isUpdate = 0; 1363 end 1364 % Update plugin 1365 if isUpdate 1366 % Compare versions 1367 strCompare = ['<FONT color="#707070">' ... 1368 'Old version :     <I>' OldPlugDesc.Version '</I><BR>' ... 1369 'New version :   <I>' PlugDesc.Version '</I></FONT><BR><BR>']; 1370 % Ask user for updating 1371 if isInteractive 1372 isConfirm = java_dialog('confirm', ... 1373 ['<HTML>Plugin <B>' PlugName '</B>: ' strUpdate '<BR>' ... 1374 'Download and install the latest version?<BR><BR>' strCompare], 'Plugin manager'); 1375 % If update not confirmed: simply load the existing plugin 1376 if ~isConfirm 1377 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1378 return; 1379 end 1380 end 1381 disp(['BST> Plugin ' PlugName ' is outdated and will be updated.']); 1382 % Uninstall existing plugin 1383 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1384 if ~isOk 1385 errMsg = ['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10]; 1386 return; 1387 end 1388 1389 % No update: Load existing plugin and return 1390 else 1391 % Load plugin 1392 if ~OldPlugDesc.isLoaded 1393 [isLoaded, errMsg, PlugDesc] = Load(OldPlugDesc); 1394 if ~isLoaded 1395 errMsg = ['Could not load plugin ' PlugName ':' 10 errMsg]; 1396 return; 1397 end 1398 else 1399 disp(['BST> Plugin ' PlugName ' already loaded: ' OldPlugDesc.Path]); 1400 end 1401 % Return old plugin 1402 PlugDesc = OldPlugDesc; 1403 isOk = 1; 1404 return; 1405 end 1406 else 1407 % Get user confirmation 1408 if isInteractive 1409 if ~isempty(PlugDesc.Version) && ~isequal(PlugDesc.Version, 'github-master') && ~isequal(PlugDesc.Version, 'latest') 1410 strVer = ['<FONT color="#707070">Latest version: ' PlugDesc.Version '</FONT><BR><BR>']; 1411 else 1412 strVer = ''; 1413 end 1414 isConfirm = java_dialog('confirm', ... 1415 ['<HTML>Plugin <B>' PlugName '</B> is not installed on your computer.<BR>' ... 1416 '<B>Download</B> the latest version of ' PlugName ' now?<BR><BR>' ... 1417 strVer, ... 1418 '<FONT color="#707070">If this program is available on your computer,<BR>' ... 1419 'cancel this installation and use the menu: Plugins > <BR>' ... 1420 PlugName ' > Custom install > Set installation folder.</FONT><BR><BR>'], 'Plugin manager'); 1421 if ~isConfirm 1422 errMsg = 'Installation aborted by user.'; 1423 return; 1424 end 1425 end 1426 end 1427 1428 % === INSTALL PLUGIN === 1429 bst_progress('text', ['Installing plugin ' PlugName '...']); 1430 % Managed plugin folder 1431 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1432 % Delete existing folder 1433 if isdir(PlugPath) 1434 file_delete(PlugPath, 1, 3); 1435 end 1436 % Create folder 1437 if ~isdir(PlugPath) 1438 res = mkdir(PlugPath); 1439 if ~res 1440 errMsg = ['Error: Cannot create folder' 10 PlugPath]; 1441 return 1442 end 1443 end 1444 % Setting progressbar image 1445 LogoFile = GetLogoFile(PlugDesc); 1446 if ~isempty(LogoFile) 1447 bst_progress('setimage', LogoFile); 1448 end 1449 % Get package file format 1450 if strcmpi(PlugDesc.URLzip(end-3:end), '.zip') 1451 pkgFormat = 'zip'; 1452 elseif strcmpi(PlugDesc.URLzip(end-6:end), '.tar.gz') || strcmpi(PlugDesc.URLzip(end-3:end), '.tgz') 1453 pkgFormat = 'tgz'; 1454 else 1455 disp('BST> Could not guess file format, trying ZIP...'); 1456 pkgFormat = 'zip'; 1457 end 1458 % Download file 1459 pkgFile = bst_fullfile(PlugPath, ['plugin.' pkgFormat]); 1460 disp(['BST> Downloading URL : ' PlugDesc.URLzip]); 1461 disp(['BST> Saving to file : ' pkgFile]); 1462 errMsg = gui_brainstorm('DownloadFile', PlugDesc.URLzip, pkgFile, ['Download plugin: ' PlugName], LogoFile); 1463 % If file was not downloaded correctly 1464 if ~isempty(errMsg) 1465 errMsg = ['Impossible to download ' PlugName ' automatically:' 10 errMsg]; 1466 if ~isCompiled 1467 errMsg = [errMsg 10 10 ... 1468 'Alternative download solution:' 10 ... 1469 '1) Copy the URL below from the Matlab command window: ' 10 ... 1470 ' ' PlugDesc.URLzip 10 ... 1471 '2) Paste it in a web browser' 10 ... 1472 '3) Save the file and unzip it' 10 ... 1473 '4) Add to the Matlab path the folder containing ' PlugDesc.TestFile '.']; 1474 end 1475 bst_progress('removeimage'); 1476 return; 1477 end 1478 % Update progress bar 1479 bst_progress('text', ['Installing plugin: ' PlugName '...']); 1480 if ~isempty(LogoFile) 1481 bst_progress('setimage', LogoFile); 1482 end 1483 % Unzip file 1484 switch (pkgFormat) 1485 case 'zip' 1486 bst_unzip(pkgFile, PlugPath); 1487 case 'tgz' 1488 if ispc 1489 untar(pkgFile, PlugPath); 1490 else 1491 curdir = pwd; 1492 cd(PlugPath); 1493 system(['tar -xf ' pkgFile]); 1494 cd(curdir); 1495 end 1496 end 1497 file_delete(pkgFile, 1, 3); 1498 1499 % === SAVE PLUGIN.MAT === 1500 PlugDesc.Path = PlugPath; 1501 PlugMatFile = bst_fullfile(PlugDesc.Path, 'plugin.mat'); 1502 excludedFields = {'LoadedFcn', 'UnloadedFcn', 'DownloadedFcn', 'InstalledFcn', 'UninstalledFcn', 'Path', 'isLoaded', 'isManaged'}; 1503 PlugDescSave = rmfield(PlugDesc, excludedFields); 1504 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1505 1506 % === CALLBACK: POST-DOWNLOADED === 1507 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'DownloadedFcn'); 1508 if ~isOk 1509 return; 1510 end 1511 1512 % === LOAD PLUGIN === 1513 % Load plugin 1514 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 1515 if ~isOk 1516 bst_progress('removeimage'); 1517 return; 1518 end 1519 % Update plugin description after first load, and delete unwanted files 1520 [isOk, errMsg, PlugDesc] = UpdateDescription(PlugDesc, 1); 1521 if ~isOk 1522 return; 1523 end 1524 1525 % === SHOW PLUGIN INFO === 1526 % Log install 1527 bst_webread(['http://neuroimage.usc.edu/bst/pluglog.php?c=K8Yda7B&plugname=' PlugDesc.Name '&action=install']); 1528 % Show plugin information (interactive mode only) 1529 if isInteractive 1530 % Hide progress bar 1531 isProgress = bst_progress('isVisible'); 1532 if isProgress 1533 bst_progress('hide'); 1534 end 1535 % Message box: aknowledgements 1536 java_dialog('msgbox', ['<HTML>Plugin <B>' PlugName '</B> was sucessfully installed.<BR><BR>' ... 1537 'This software is not distributed by the Brainstorm developers.<BR>' ... 1538 'Please take a few minutes to read the license information,<BR>' ... 1539 'check the authors'' website and register online if recommended.<BR><BR>' ... 1540 '<B>Cite the authors</B> in your publications if you are using their software.<BR><BR>'], 'Plugin manager'); 1541 % Show the readme file 1542 if ~isempty(PlugDesc.ReadmeFile) 1543 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1544 end 1545 % Open the website 1546 if ~isempty(PlugDesc.URLinfo) 1547 web(PlugDesc.URLinfo, '-browser') 1548 end 1549 % Restore progress bar 1550 if isProgress 1551 bst_progress('show'); 1552 end 1553 end 1554 % Remove logo 1555 bst_progress('removeimage'); 1556 % Return success 1557 isOk = 1; 1558 end 1559 1560 1561 %% ===== UPDATE DESCRIPTION ===== 1562 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('UpdateDescription', PlugDesc, doDelete=0) 1563 function [isOk, errMsg, PlugDesc] = UpdateDescription(PlugDesc, doDelete) 1564 isOk = 1; 1565 errMsg = ''; 1566 PlugPath = PlugDesc.Path; 1567 PlugName = PlugDesc.Name; 1568 1569 if nargin < 2 1570 doDelete = 0; 1571 end 1572 1573 % Plug in needs to be installed 1574 if isempty(bst_plugin('GetInstalled', PlugDesc.Name)) 1575 isOk = 0; 1576 errMsg = ['Cannot update description, plugin ''' PlugDesc.Name ''' needs to be installed']; 1577 return 1578 end 1579 1580 % === DELETE UNWANTED FILES === 1581 if doDelete && ~isempty(PlugDesc.DeleteFiles) && iscell(PlugDesc.DeleteFiles) 1582 warning('off', 'MATLAB:RMDIR:RemovedFromPath'); 1583 for iDel = 1:length(PlugDesc.DeleteFiles) 1584 if ~isempty(PlugDesc.SubFolder) 1585 fileDel = bst_fullfile(PlugDesc.Path, PlugDesc.SubFolder, PlugDesc.DeleteFiles{iDel}); 1586 else 1587 fileDel = bst_fullfile(PlugDesc.Path, PlugDesc.DeleteFiles{iDel}); 1588 end 1589 if file_exist(fileDel) 1590 try 1591 file_delete(fileDel, 1, 3); 1592 catch 1593 disp(['BST> Plugin ' PlugName ': Could not delete file: ' PlugDesc.DeleteFiles{iDel}]); 1594 end 1595 else 1596 disp(['BST> Plugin ' PlugName ': Missing file: ' PlugDesc.DeleteFiles{iDel}]); 1597 end 1598 end 1599 warning('on', 'MATLAB:RMDIR:RemovedFromPath'); 1600 end 1601 1602 % === SEARCH PROCESSES === 1603 % Look for process_* functions in the process folder 1604 PlugProc = file_find(PlugPath, 'process_*.m', Inf, 0); 1605 if ~isempty(PlugProc) 1606 % Remove absolute path: use only path relative to the plugin Path 1607 PlugDesc.Processes = cellfun(@(c)file_win2unix(strrep(c, [PlugPath, filesep], '')), PlugProc, 'UniformOutput', 0); 1608 end 1609 1610 % === SAVE PLUGIN.MAT === 1611 % Save installation date 1612 c = clock(); 1613 PlugDesc.InstallDate = datestr(datenum(c(1), c(2), c(3), c(4), c(5), c(6)), 'dd-mmm-yyyy HH:MM:SS'); 1614 % Get readme and logo 1615 PlugDesc.ReadmeFile = GetReadmeFile(PlugDesc); 1616 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 1617 % Update plugin.mat 1618 excludedFields = {'LoadedFcn', 'UnloadedFcn', 'DownloadedFcn', 'InstalledFcn', 'UninstalledFcn', 'Path', 'isLoaded', 'isManaged'}; 1619 PlugDescSave = rmfield(PlugDesc, excludedFields); 1620 PlugMatFile = bst_fullfile(PlugDesc.Path, 'plugin.mat'); 1621 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1622 1623 % === CALLBACK: POST-INSTALL === 1624 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'InstalledFcn'); 1625 if ~isOk 1626 return; 1627 end 1628 1629 % === GET INSTALLED VERSION === 1630 % Get installed version 1631 if ~isempty(PlugDesc.GetVersionFcn) 1632 testVer = []; 1633 try 1634 if ischar(PlugDesc.GetVersionFcn) 1635 testVer = eval(PlugDesc.GetVersionFcn); 1636 elseif isa(PlugDesc.GetVersionFcn, 'function_handle') 1637 testVer = feval(PlugDesc.GetVersionFcn); 1638 end 1639 catch 1640 disp(['BST> Could not get installed version with callback: ' PlugDesc.GetVersionFcn]); 1641 end 1642 if ~isempty(testVer) 1643 PlugDesc.Version = testVer; 1644 % Update plugin.mat 1645 PlugDescSave.Version = testVer; 1646 bst_save(PlugMatFile, PlugDescSave, 'v6'); 1647 end 1648 end 1649 end 1650 1651 %% ===== INSTALL INTERACTIVE ===== 1652 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallInteractive', PlugName) 1653 function [isOk, errMsg, PlugDesc] = InstallInteractive(PlugName) 1654 % Open progress bar 1655 isProgress = bst_progress('isVisible'); 1656 if ~isProgress 1657 bst_progress('start', 'Plugin manager', 'Initialization...'); 1658 end 1659 % Call silent function 1660 [isOk, errMsg, PlugDesc] = Install(PlugName, 1); 1661 % Handle errors 1662 if ~isOk 1663 bst_error(['Installation error:' 10 10 errMsg 10], 'Plugin manager', 0); 1664 elseif ~isempty(errMsg) 1665 java_dialog('msgbox', ['Installation message:' 10 10 errMsg 10], 'Plugin manager'); 1666 end 1667 % Close progress bar 1668 if ~isProgress 1669 bst_progress('stop'); 1670 end 1671 end 1672 1673 1674 %% ===== INSTALL MULTIPLE CHOICE ===== 1675 % If multiple plugins provide the same functions (eg. FieldTrip and SPM): make sure at least one is installed 1676 % USAGE: [isOk, errMsg, PlugDesc] = bst_plugin('InstallMultipleChoice', PlugNames, isInteractive) 1677 function [isOk, errMsg, PlugDesc] = InstallMultipleChoice(PlugNames, isInteractive) 1678 % Check if one of the plugins is loaded 1679 for iPlug = 1:length(PlugNames) 1680 PlugInst = GetInstalled(PlugNames{iPlug}); 1681 if ~isempty(PlugInst) 1682 [isOk, errMsg, PlugDesc] = Load(PlugNames{iPlug}); 1683 if isOk 1684 return; 1685 end 1686 end 1687 end 1688 % If no plugin is loaded: Install the first in the list 1689 [isOk, errMsg, PlugDesc] = Install(PlugNames{1}, isInteractive); 1690 end 1691 1692 1693 %% ===== UNINSTALL ===== 1694 % USAGE: [isOk, errMsg] = bst_plugin('Uninstall', PlugName, isInteractive=0, isDependencies=1) 1695 function [isOk, errMsg] = Uninstall(PlugName, isInteractive, isDependencies) 1696 % Returned variables 1697 isOk = 0; 1698 errMsg = ''; 1699 % Parse inputs 1700 if (nargin < 3) || isempty(isDependencies) 1701 isDependencies = 1; 1702 end 1703 if (nargin < 2) || isempty(isInteractive) 1704 isInteractive = 0; 1705 end 1706 if ~ischar(PlugName) 1707 errMsg = 'Invalid call to Uninstall()'; 1708 return; 1709 end 1710 1711 % === CHECK INSTALLATION === 1712 % Get installation 1713 PlugDesc = GetInstalled(PlugName); 1714 % External plugin 1715 if ~isempty(PlugDesc) && ~isequal(PlugDesc.isManaged, 1) 1716 errMsg = ['<HTML>Plugin <B>' PlugName '</B> is not managed by Brainstorm.' 10 'Delete folder manually:' 10 PlugDesc.Path]; 1717 return; 1718 % Plugin not installed: check if folder exists 1719 elseif isempty(PlugDesc) || isempty(PlugDesc.Path) 1720 % Get plugin structure from name 1721 [PlugDesc, errMsg] = GetDescription(PlugName); 1722 if ~isempty(errMsg) 1723 return; 1724 end 1725 % Managed plugin folder 1726 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugName); 1727 else 1728 PlugPath = PlugDesc.Path; 1729 end 1730 % Plugin not installed 1731 if ~file_exist(PlugPath) 1732 errMsg = ['Plugin ' PlugName ' is not installed.']; 1733 return; 1734 end 1735 1736 % === USER CONFIRMATION === 1737 if isInteractive 1738 isConfirm = java_dialog('confirm', ['<HTML>Delete permanently plugin <B>' PlugName '</B>?' 10 10 PlugPath 10 10], 'Plugin manager'); 1739 if ~isConfirm 1740 errMsg = 'Uninstall aborted by user.'; 1741 return; 1742 end 1743 end 1744 1745 % === PROCESS DEPENDENCIES === 1746 % Uninstall dependent plugins 1747 if isDependencies 1748 AllPlugs = GetSupported(); 1749 for iPlug = 1:length(AllPlugs) 1750 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 1751 disp(['BST> Uninstalling dependent plugin: ' AllPlugs(iPlug).Name]); 1752 Uninstall(AllPlugs(iPlug).Name, isInteractive); 1753 end 1754 end 1755 end 1756 1757 % === UNLOAD === 1758 if isequal(PlugDesc.isLoaded, 1) 1759 [isUnloaded, errMsgUnload] = Unload(PlugDesc); 1760 if ~isempty(errMsgUnload) 1761 disp(['BST> Error unloading plugin ' PlugName ': ' errMsgUnload]); 1762 end 1763 end 1764 1765 % === UNINSTALL === 1766 disp(['BST> Deleting plugin ' PlugName ': ' PlugPath]); 1767 % Delete plugin folder 1768 isDeleted = file_delete(PlugPath, 1, 3); 1769 if (isDeleted ~= 1) 1770 errMsg = ['Could not delete plugin folder: ' 10 PlugPath 10 10 ... 1771 'There is probably a file in that folder that is currently ' 10 ... 1772 'loaded in Matlab, but that cannot be unloaded dynamically.' 10 10 ... 1773 'Brainstorm will now close Matlab.' 10 ... 1774 'Restart Matlab and install again the plugin.' 10 10]; 1775 if isInteractive 1776 java_dialog('error', errMsg, 'Restart Matlab'); 1777 else 1778 disp([10 10 'BST> ' errMsg]); 1779 end 1780 quit('force'); 1781 end 1782 1783 % === CALLBACK: POST-UNINSTALL === 1784 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UninstalledFcn'); 1785 if ~isOk 1786 return; 1787 end 1788 1789 % Return success 1790 isOk = 1; 1791 end 1792 1793 1794 %% ===== UNINSTALL INTERACTIVE ===== 1795 % USAGE: [isOk, errMsg] = bst_plugin('UninstallInteractive', PlugName) 1796 function [isOk, errMsg] = UninstallInteractive(PlugName) 1797 % Open progress bar 1798 isProgress = bst_progress('isVisible'); 1799 if ~isProgress 1800 bst_progress('start', 'Plugin manager', 'Initialization...'); 1801 end 1802 % Call silent function 1803 [isOk, errMsg] = Uninstall(PlugName, 1); 1804 % Handle errors 1805 if ~isOk 1806 bst_error(['An error occurred while uninstalling plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1807 elseif ~isempty(errMsg) 1808 java_dialog('msgbox', ['Uninstall message:' 10 10 errMsg 10], 'Plugin manager'); 1809 end 1810 % Close progress bar 1811 if ~isProgress 1812 bst_progress('stop'); 1813 end 1814 end 1815 1816 1817 %% ===== UPDATE INTERACTIVE ===== 1818 % USAGE: [isOk, errMsg] = bst_plugin('UpdateInteractive', PlugName) 1819 function [isOk, errMsg] = UpdateInteractive(PlugName) 1820 % Open progress bar 1821 isProgress = bst_progress('isVisible'); 1822 if ~isProgress 1823 bst_progress('start', 'Plugin manager', 'Initialization...'); 1824 end 1825 % Get new plugin 1826 [PlugRef, errMsg] = GetDescription(PlugName); 1827 isOk = isempty(errMsg); 1828 % Get installed plugin 1829 if isOk 1830 PlugInst = GetInstalled(PlugName); 1831 if isempty(PlugInst) || ~PlugInst.isManaged 1832 isOk = 0; 1833 errMsg = ['Plugin ' PlugName ' is not installed or not managed by Brainstorm.']; 1834 end 1835 end 1836 % Get online update (use cache when available) 1837 [newVersion, newURLzip] = GetVersionOnline(PlugName, PlugRef.URLzip, 1); 1838 if ~isempty(newVersion) 1839 PlugRef.Version = newVersion; 1840 end 1841 if ~isempty(newURLzip) 1842 PlugRef.URLzip = newURLzip; 1843 end 1844 % User confirmation 1845 if isOk 1846 isOk = java_dialog('confirm', ['<HTML>Update plugin <B>' PlugName '</B> ?<BR><BR><FONT color="#707070">' ... 1847 'Old version :     <I>' PlugInst.Version '</I><BR>' ... 1848 'New version :   <I>' PlugRef.Version '</I><BR><BR></FONT>'], 'Plugin manager'); 1849 if ~isOk 1850 errMsg = 'Update aborted by user.'; 1851 end 1852 end 1853 % Uninstall old 1854 if isOk 1855 [isOk, errMsg] = Uninstall(PlugName, 0, 0); 1856 end 1857 % Install new 1858 if isOk 1859 [isOk, errMsg, PlugDesc] = Install(PlugName, 0); 1860 else 1861 PlugDesc = []; 1862 end 1863 % Handle errors 1864 if ~isOk 1865 bst_error(['An error occurred while updating plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 1866 elseif ~isempty(errMsg) 1867 java_dialog('msgbox', ['Update message:' 10 10 errMsg 10], 'Plugin manager'); 1868 end 1869 % Close progress bar 1870 if ~isProgress 1871 bst_progress('stop'); 1872 end 1873 % Plugin was updated successfully 1874 if ~isempty(PlugDesc) 1875 % Show the readme file 1876 if ~isempty(PlugDesc.ReadmeFile) 1877 view_text(PlugDesc.ReadmeFile, ['Installed plugin: ' PlugName], 1, 1); 1878 end 1879 % Open the website 1880 if ~isempty(PlugDesc.URLinfo) 1881 web(PlugDesc.URLinfo, '-browser') 1882 end 1883 end 1884 end 1885 1886 1887 %% ===== LOAD ===== 1888 % USAGE: [isOk, errMsg, PlugDesc] = Load(PlugDesc, isVerbose=1) 1889 function [isOk, errMsg, PlugDesc] = Load(PlugDesc, isVerbose) 1890 % Parse inputs 1891 if (nargin < 2) || isempty(isVerbose) 1892 isVerbose = 1; 1893 end 1894 % Initialize returned variables 1895 isOk = 0; 1896 % Get plugin structure from name 1897 [PlugDesc, errMsg] = GetDescription(PlugDesc); 1898 if ~isempty(errMsg) 1899 return; 1900 end 1901 % Check if plugin is supported on Apple silicon 1902 OsType = bst_get('OsType', 0); 1903 if strcmpi(OsType, 'mac64arm') && ismember(PlugDesc.Name, PluginsNotSupportAppleSilicon()) 1904 errMsg = ['Plugin ', PlugDesc.Name ' is not supported on Apple silicon yet.']; 1905 return; 1906 end 1907 % Minimum Matlab version 1908 if ~isempty(PlugDesc.MinMatlabVer) && (PlugDesc.MinMatlabVer > 0) && (bst_get('MatlabVersion') < PlugDesc.MinMatlabVer) 1909 strMinVer = sprintf('%d.%d', ceil(PlugDesc.MinMatlabVer / 100), mod(PlugDesc.MinMatlabVer, 100)); 1910 errMsg = ['Plugin ', PlugDesc.Name ' is not supported for versions of Matlab <= ' strMinVer]; 1911 return; 1912 end 1913 1914 % === ALREADY LOADED === 1915 % If plugin is already full loaded 1916 if isequal(PlugDesc.isLoaded, 1) && ~isempty(PlugDesc.Path) 1917 if isVerbose 1918 errMsg = ['Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]; 1919 end 1920 return; 1921 end 1922 % Managed plugin path 1923 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 1924 if file_exist(PlugPath) 1925 PlugDesc.isManaged = 1; 1926 % Custom installation 1927 else 1928 PluginCustomPath = bst_get('PluginCustomPath'); 1929 if isfield(PluginCustomPath, PlugDesc.Name) && ~isempty(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) && file_exist(bst_fullfile(PluginCustomPath.(PlugDesc.Name))) 1930 PlugPath = PluginCustomPath.(PlugDesc.Name); 1931 end 1932 PlugDesc.isManaged = 0; 1933 end 1934 % Managed install: Detect if there is a single subfolder containing all the files 1935 if PlugDesc.isManaged && ~isempty(PlugDesc.TestFile) && ~file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 1936 dirList = dir(PlugPath); 1937 for iDir = 1:length(dirList) 1938 % Not folder or . : skip 1939 if (dirList(iDir).name(1) == '.') || ~dirList(iDir).isdir 1940 continue; 1941 end 1942 % Check if test file is in the folder 1943 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.TestFile)) 1944 PlugDesc.SubFolder = dirList(iDir).name; 1945 break; 1946 % Otherwise, check in any of the subfolders 1947 elseif ~isempty(PlugDesc.LoadFolders) 1948 for iSubDir = 1:length(PlugDesc.LoadFolders) 1949 if file_exist(bst_fullfile(PlugPath, dirList(iDir).name, PlugDesc.LoadFolders{iSubDir}, PlugDesc.TestFile)) 1950 PlugDesc.SubFolder = dirList(iDir).name; 1951 break; 1952 end 1953 end 1954 end 1955 end 1956 end 1957 % Check if test function already available in the path 1958 TestFilePath = GetTestFilePath(PlugDesc); 1959 if ~isempty(TestFilePath) 1960 PlugDesc.isLoaded = 1; 1961 PlugDesc.isManaged = ~isempty(strfind(which(PlugDesc.TestFile), PlugPath)); 1962 if PlugDesc.isManaged 1963 PlugDesc.Path = PlugPath; 1964 else 1965 PlugDesc.Path = TestFilePath; 1966 end 1967 if isVerbose 1968 disp(['BST> Plugin ' PlugDesc.Name ' already loaded: ' PlugDesc.Path]); 1969 end 1970 isOk = 1; 1971 return; 1972 end 1973 1974 % === CHECK LOADABILITY === 1975 PlugDesc.Path = PlugPath; 1976 if ~file_exist(PlugDesc.Path) 1977 errMsg = ['Plugin ' PlugDesc.Name ' not installed.' 10 'Missing folder: ' PlugDesc.Path]; 1978 return; 1979 end 1980 % Set logo 1981 LogoFile = GetLogoFile(PlugDesc); 1982 if ~isempty(LogoFile) 1983 bst_progress('setimage', LogoFile); 1984 end 1985 1986 % === PROCESS DEPENDENCIES === 1987 % Unload incompatible plugins 1988 if ~isempty(PlugDesc.UnloadPlugs) 1989 for iPlug = 1:length(PlugDesc.UnloadPlugs) 1990 % disp(['BST> Unloading incompatible plugin: ' PlugDesc.UnloadPlugs{iPlug}]); 1991 Unload(PlugDesc.UnloadPlugs{iPlug}, isVerbose); 1992 end 1993 end 1994 % Load required plugins 1995 if ~isempty(PlugDesc.RequiredPlugs) 1996 for iPlug = 1:size(PlugDesc.RequiredPlugs,1) 1997 % disp(['BST> Loading required plugin: ' PlugDesc.RequiredPlugs{iPlug,1}]); 1998 [isOk, errMsg] = Load(PlugDesc.RequiredPlugs{iPlug,1}, isVerbose); 1999 if ~isOk 2000 errMsg = ['Error processing dependencies: ', PlugDesc.Name, 10, errMsg]; 2001 bst_progress('removeimage'); 2002 return; 2003 end 2004 end 2005 end 2006 2007 % === LOAD PLUGIN === 2008 % Add plugin folder to path 2009 if ~isempty(PlugDesc.SubFolder) 2010 PlugHomeDir = bst_fullfile(PlugPath, PlugDesc.SubFolder); 2011 else 2012 PlugHomeDir = PlugPath; 2013 end 2014 % Do not modify path in compiled mode 2015 isCompiled = bst_iscompiled(); 2016 if ~isCompiled 2017 addpath(PlugHomeDir); 2018 if isVerbose 2019 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ' PlugHomeDir]); 2020 end 2021 % Add specific subfolders to path 2022 if ~isempty(PlugDesc.LoadFolders) 2023 % Load all all subfolders 2024 if isequal(PlugDesc.LoadFolders, '*') || isequal(PlugDesc.LoadFolders, {'*'}) 2025 if isVerbose 2026 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, '*']); 2027 end 2028 addpath(genpath(PlugHomeDir)); 2029 % Load specific subfolders 2030 else 2031 for i = 1:length(PlugDesc.LoadFolders) 2032 subDir = PlugDesc.LoadFolders{i}; 2033 if isequal(filesep, '\') 2034 subDir = strrep(subDir, '/', '\'); 2035 end 2036 if isdir([PlugHomeDir, filesep, subDir]) 2037 if isVerbose 2038 disp(['BST> Adding plugin ' PlugDesc.Name ' to path: ', PlugHomeDir, filesep, subDir]); 2039 end 2040 addpath([PlugHomeDir, filesep, subDir]); 2041 end 2042 end 2043 end 2044 end 2045 end 2046 2047 % === TEST FUNCTION === 2048 % Check if test function is available on path 2049 if ~isCompiled && ~isempty(PlugDesc.TestFile) && (exist(PlugDesc.TestFile, 'file') == 0) 2050 errMsg = ['Plugin ' PlugDesc.Name ' successfully loaded from:' 10 PlugHomeDir 10 10 ... 2051 'However, the function ' PlugDesc.TestFile ' is not accessible in the Matlab path.' 10 ... 2052 'Try restarting Matlab and Brainstorm.']; 2053 bst_progress('removeimage') 2054 return; 2055 end 2056 2057 % === CALLBACK: POST-LOAD === 2058 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'LoadedFcn'); 2059 2060 % Remove logo 2061 bst_progress('removeimage'); 2062 % Return success 2063 PlugDesc.isLoaded = isOk; 2064 end 2065 2066 2067 %% ===== LOAD INTERACTIVE ===== 2068 % USAGE: [isOk, errMsg, PlugDesc] = LoadInteractive(PlugName/PlugDesc) 2069 function [isOk, errMsg, PlugDesc] = LoadInteractive(PlugDesc) 2070 % Open progress bar 2071 isProgress = bst_progress('isVisible'); 2072 if ~isProgress 2073 bst_progress('start', 'Plugin manager', 'Loading plugin...'); 2074 end 2075 % Call silent function 2076 [isOk, errMsg, PlugDesc] = Load(PlugDesc); 2077 % Handle errors 2078 if ~isOk 2079 bst_error(['Load error:' 10 10 errMsg 10], 'Plugin manager', 0); 2080 elseif ~isempty(errMsg) 2081 java_dialog('msgbox', ['Load message:' 10 10 errMsg 10], 'Plugin manager'); 2082 end 2083 % Close progress bar 2084 if ~isProgress 2085 bst_progress('stop'); 2086 end 2087 end 2088 2089 2090 %% ===== UNLOAD ===== 2091 % USAGE: [isOk, errMsg, PlugDesc] = Unload(PlugName/PlugDesc, isVerbose) 2092 function [isOk, errMsg, PlugDesc] = Unload(PlugDesc, isVerbose) 2093 % Parse inputs 2094 if (nargin < 2) || isempty(isVerbose) 2095 isVerbose = 1; 2096 end 2097 % Initialize returned variables 2098 isOk = 0; 2099 errMsg = ''; 2100 % Get installation 2101 InstPlugDesc = GetInstalled(PlugDesc); 2102 % Plugin not installed: check if folder exists 2103 if isempty(InstPlugDesc) || isempty(InstPlugDesc.Path) 2104 % Get plugin structure from name 2105 [PlugDesc, errMsg] = GetDescription(PlugDesc); 2106 if ~isempty(errMsg) 2107 return; 2108 end 2109 % Managed plugin folder 2110 PlugPath = bst_fullfile(bst_get('UserPluginsDir'), PlugDesc.Name); 2111 else 2112 PlugDesc = InstPlugDesc; 2113 PlugPath = PlugDesc.Path; 2114 end 2115 % Plugin not installed 2116 if ~file_exist(PlugPath) 2117 errMsg = ['Plugin ' PlugDesc.Name ' is not installed.' 10 'Missing folder: ' PlugPath]; 2118 return; 2119 end 2120 % Get plugin structure from name 2121 [PlugDesc, errMsg] = GetDescription(PlugDesc); 2122 if ~isempty(errMsg) 2123 return; 2124 end 2125 2126 % === PROCESS DEPENDENCIES === 2127 % Unload dependent plugins 2128 AllPlugs = GetSupported(); 2129 for iPlug = 1:length(AllPlugs) 2130 if ~isempty(AllPlugs(iPlug).RequiredPlugs) && ismember(PlugDesc.Name, AllPlugs(iPlug).RequiredPlugs(:,1)) 2131 Unload(AllPlugs(iPlug)); 2132 end 2133 end 2134 2135 % === UNLOAD PLUGIN === 2136 % Do not modify path in compiled mode 2137 if ~bst_iscompiled() 2138 matlabPath = str_split(path, pathsep); 2139 % Remove plugin folder and subfolders from path 2140 allSubFolders = str_split(genpath(PlugPath), pathsep); 2141 for i = 1:length(allSubFolders) 2142 if ismember(allSubFolders{i}, matlabPath) 2143 rmpath(allSubFolders{i}); 2144 if isVerbose 2145 disp(['BST> Removing plugin ' PlugDesc.Name ' from path: ' allSubFolders{i}]); 2146 end 2147 end 2148 end 2149 end 2150 2151 % === TEST FUNCTION === 2152 % Check if test function is still available on path 2153 if ~isempty(PlugDesc.TestFile) && ~isempty(which(PlugDesc.TestFile)) 2154 errMsg = ['Plugin ' PlugDesc.Name ' successfully unloaded from: ' 10 PlugPath 10 10 ... 2155 'However, another version is still accessible on the Matlab path:' 10 which(PlugDesc.TestFile) 10 10 ... 2156 'Please remove this folder from the Matlab path.']; 2157 return; 2158 end 2159 2160 % === CALLBACK: POST-UNLOAD === 2161 [isOk, errMsg] = ExecuteCallback(PlugDesc, 'UnloadedFcn'); 2162 if ~isOk 2163 return; 2164 end 2165 2166 % Return success 2167 PlugDesc.isLoaded = 0; 2168 isOk = 1; 2169 end 2170 2171 2172 %% ===== UNLOAD INTERACTIVE ===== 2173 % USAGE: [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugName/PlugDesc) 2174 function [isOk, errMsg, PlugDesc] = UnloadInteractive(PlugDesc) 2175 % Open progress bar 2176 isProgress = bst_progress('isVisible'); 2177 if ~isProgress 2178 bst_progress('start', 'Plugin manager', 'Unloading plugin...'); 2179 end 2180 % Call silent function 2181 [isOk, errMsg, PlugDesc] = Unload(PlugDesc); 2182 % Handle errors 2183 if ~isOk 2184 bst_error(['Unload error:' 10 10 errMsg 10], 'Plugin manager', 0); 2185 elseif ~isempty(errMsg) 2186 java_dialog('msgbox', ['Unload message:' 10 10 errMsg 10], 'Plugin manager'); 2187 end 2188 % Close progress bar 2189 if ~isProgress 2190 bst_progress('stop'); 2191 end 2192 end 2193 2194 2195 %% ===== LIST ===== 2196 % USAGE: strList = bst_plugin('List', Target='installed', isGui=0) % Target={'supported','installed', 'loaded'} 2197 function strList = List(Target, isGui) 2198 % Parse inputs 2199 if (nargin < 2) || isempty(isGui) 2200 isGui = 0; 2201 end 2202 if (nargin < 1) || isempty(Target) 2203 Target = 'Installed'; 2204 else 2205 Target = [upper(Target(1)), lower(Target(2:end))]; 2206 end 2207 % Get plugins to list 2208 strTitle = sprintf('%s plugins', Target); 2209 switch (Target) 2210 case 'Supported' 2211 PlugDesc = GetSupported(); 2212 isInstalled = 0; 2213 case 'Installed' 2214 strTitle = [strTitle ' (*=Loaded)']; 2215 PlugDesc = GetInstalled(); 2216 isInstalled = 1; 2217 case 'Loaded' 2218 PlugDesc = GetInstalled(); 2219 PlugDesc = PlugDesc([PlugDesc.isLoaded] == 1); 2220 isInstalled = 1; 2221 otherwise 2222 error(['Invalid target: ' Target]); 2223 end 2224 if isempty(PlugDesc) 2225 return; 2226 end 2227 % Sort by plugin names 2228 [tmp,I] = sort({PlugDesc.Name}); 2229 PlugDesc = PlugDesc(I); 2230 2231 % Get Brainstorm info 2232 bstVer = bst_get('Version'); 2233 bstDir = bst_get('BrainstormHomeDir'); 2234 % Cut version string (short github SHA) 2235 if (length(bstVer.Commit) > 13) 2236 bstGit = ['git @', bstVer.Commit(1:7)]; 2237 bstURL = ['https://github.com/brainstorm-tools/brainstorm3/archive/' bstVer.Commit '.zip']; 2238 structVer = bstGit; 2239 else 2240 bstGit = ''; 2241 bstURL = ''; 2242 structVer = bstVer.Version; 2243 end 2244 2245 % Max lengths 2246 headerName = ' Name'; 2247 headerVersion = 'Version'; 2248 headerPath = 'Install path'; 2249 headerUrl = 'Downloaded from'; 2250 headerDate = 'Install date'; 2251 maxName = max(cellfun(@length, {PlugDesc.Name, headerName, 'brainstorm'})); 2252 maxVer = min(13, max(cellfun(@length, {PlugDesc.Version, headerVersion, bstGit}))); 2253 maxUrl = max(cellfun(@length, {PlugDesc.URLzip, headerUrl, bstURL})); 2254 maxDate = 12; 2255 if isInstalled 2256 strDate = [' | ', headerDate, repmat(' ', 1, maxDate-length(headerDate))]; 2257 strDateSep = ['-|-', repmat('-',1,maxDate)]; 2258 maxPath = max(cellfun(@length, {PlugDesc.Path, headerPath})); 2259 strPath = [' | ', headerPath, repmat(' ', 1, maxPath-length(headerPath))]; 2260 strPathSep = ['-|-', repmat('-',1,maxPath)]; 2261 strBstVer = [' | ', bstVer.Date, repmat(' ', 1, maxDate-length(bstVer.Date))]; 2262 strBstDir = [' | ', bstDir, repmat(' ', 1, maxPath-length(bstDir))]; 2263 else 2264 strDate = ''; 2265 strDateSep = ''; 2266 strPath = ''; 2267 strPathSep = ''; 2268 strBstVer = ''; 2269 strBstDir = ''; 2270 end 2271 % Print column headers 2272 strList = [headerName, repmat(' ', 1, maxName-length(headerName) + 2) ... 2273 ' | ', headerVersion, repmat(' ', 1, maxVer-length(headerVersion)), ... 2274 strDate, strPath, ... 2275 ' | ' headerUrl 10 ... 2276 repmat('-',1,maxName + 2), '-|-', repmat('-',1,maxVer), strDateSep, strPathSep, '-|-', repmat('-',1,maxUrl) 10]; 2277 2278 % Print Brainstorm information 2279 strList = [strList '* ', ... 2280 'brainstorm', repmat(' ', 1, maxName-length('brainstorm')) ... 2281 ' | ', bstGit, repmat(' ', 1, maxVer-length(bstGit)), ... 2282 strBstVer, strBstDir, ... 2283 ' | ' bstURL 10]; 2284 2285 % Print installed plugins to standard output 2286 for iPlug = 1:length(PlugDesc) 2287 % Loaded plugin 2288 if PlugDesc(iPlug).isLoaded 2289 strLoaded = '* '; 2290 else 2291 strLoaded = ' '; 2292 end 2293 % Cut installation date: Only date, no time 2294 if (length(PlugDesc(iPlug).InstallDate) > 11) 2295 plugDate = PlugDesc(iPlug).InstallDate(1:11); 2296 else 2297 plugDate = PlugDesc(iPlug).InstallDate; 2298 end 2299 % Installed listing 2300 if isInstalled 2301 strDate = [' | ', plugDate, repmat(' ', 1, maxDate-length(plugDate))]; 2302 strPath = [' | ', PlugDesc(iPlug).Path, repmat(' ', 1, maxPath-length(PlugDesc(iPlug).Path))]; 2303 else 2304 strDate = ''; 2305 strPath = ''; 2306 end 2307 % Get installed version 2308 if (length(PlugDesc(iPlug).Version) > 13) % Cut version string (short github SHA) 2309 plugVer = ['git @', PlugDesc(iPlug).Version(1:7)]; 2310 else 2311 plugVer = PlugDesc(iPlug).Version; 2312 end 2313 % Get installed version with GetVersionFcn 2314 if isempty(plugVer) && isfield(PlugDesc(iPlug),'GetVersionFcn') && ~isempty(PlugDesc(iPlug).GetVersionFcn) 2315 % Load plugin if needed 2316 tmpLoad = 0; 2317 if ~PlugDesc(iPlug).isLoaded 2318 tmpLoad = 1; 2319 Load(PlugDesc(iPlug), 0); 2320 end 2321 try 2322 if ischar(PlugDesc(iPlug).GetVersionFcn) 2323 plugVer = eval(PlugDesc(iPlug).GetVersionFcn); 2324 elseif isa(PlugDesc(iPlug).GetVersionFcn, 'function_handle') 2325 plugVer = feval(PlugDesc(iPlug).GetVersionFcn); 2326 end 2327 catch 2328 disp(['BST> Could not get installed version with callback: ' PlugDesc(iPlug).GetVersionFcn]); 2329 end 2330 % Unload plugin 2331 if tmpLoad 2332 Unload(PlugDesc(iPlug), 0); 2333 end 2334 end 2335 % Assemble plugin text row 2336 strList = [strList strLoaded, ... 2337 PlugDesc(iPlug).Name, repmat(' ', 1, maxName-length(PlugDesc(iPlug).Name)) ... 2338 ' | ', plugVer, repmat(' ', 1, maxVer-length(plugVer)), ... 2339 strDate, strPath, ... 2340 ' | ' PlugDesc(iPlug).URLzip 10]; 2341 end 2342 % Display output 2343 if isGui 2344 view_text(strList, strTitle); 2345 % No string returned: display it in the command window 2346 elseif (nargout == 0) 2347 disp([10 strTitle 10 10 strList]); 2348 end 2349 end 2350 2351 2352 %% ===== MENUS: CREATE ===== 2353 function j = MenuCreate(jMenu, fontSize) 2354 import org.brainstorm.icon.*; 2355 % Get all the supported plugins 2356 PlugDesc = GetSupported(); 2357 % Get Matlab version 2358 MatlabVersion = bst_get('MatlabVersion'); 2359 isCompiled = bst_iscompiled(); 2360 % Submenus 2361 jSub = {}; 2362 % Process each plugin 2363 j = repmat(struct(), 0); 2364 for iPlug = 1:length(PlugDesc) 2365 Plug = PlugDesc(iPlug); 2366 % Skip if Matlab is too old 2367 if ~isempty(Plug.MinMatlabVer) && (Plug.MinMatlabVer > 0) && (MatlabVersion < Plug.MinMatlabVer) 2368 continue; 2369 end 2370 % Skip if not supported in compiled version 2371 if isCompiled && (Plug.CompiledStatus == 0) 2372 continue; 2373 end 2374 % Category=submenu 2375 if ~isempty(Plug.Category) 2376 if isempty(jSub) || ~ismember(Plug.Category, jSub(:,1)) 2377 jParent = gui_component('Menu', jMenu, [], Plug.Category, IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 2378 jSub(end+1,1:2) = {Plug.Category, jParent}; 2379 else 2380 iSub = find(strcmpi(jSub(:,1), Plug.Category)); 2381 jParent = jSub{iSub,2}; 2382 end 2383 else 2384 jParent = jMenu; 2385 end 2386 % One menu per plugin 2387 ij = length(j) + 1; 2388 j(ij).name = Plug.Name; 2389 % Compiled and included: Simple static menu 2390 if isCompiled && (Plug.CompiledStatus == 2) 2391 j(ij).menu = gui_component('MenuItem', jParent, [], Plug.Name, [], [], [], fontSize); 2392 % Do not create submenus for compiled version 2393 else 2394 % Main menu 2395 j(ij).menu = gui_component('Menu', jParent, [], Plug.Name, [], [], [], fontSize); 2396 % Version 2397 j(ij).version = gui_component('MenuItem', j(ij).menu, [], 'Version', [], [], [], fontSize); 2398 j(ij).versep = java_create('javax.swing.JSeparator'); 2399 j(ij).menu.add(j(ij).versep); 2400 % Install 2401 j(ij).install = gui_component('MenuItem', j(ij).menu, [], 'Install', IconLoader.ICON_DOWNLOAD, [], @(h,ev)InstallInteractive(Plug.Name), fontSize); 2402 % Update 2403 j(ij).update = gui_component('MenuItem', j(ij).menu, [], 'Update', IconLoader.ICON_RELOAD, [], @(h,ev)UpdateInteractive(Plug.Name), fontSize); 2404 % Uninstall 2405 j(ij).uninstall = gui_component('MenuItem', j(ij).menu, [], 'Uninstall', IconLoader.ICON_DELETE, [], @(h,ev)UninstallInteractive(Plug.Name), fontSize); 2406 j(ij).menu.addSeparator(); 2407 % Custom install 2408 j(ij).custom = gui_component('Menu', j(ij).menu, [], 'Custom install', IconLoader.ICON_FOLDER_OPEN, [], [], fontSize); 2409 j(ij).customset = gui_component('MenuItem', j(ij).custom, [], 'Select installation folder', [], [], @(h,ev)SetCustomPath(Plug.Name), fontSize); 2410 j(ij).custompath = gui_component('MenuItem', j(ij).custom, [], 'Path not set', [], [], [], fontSize); 2411 j(ij).custompath.setEnabled(0); 2412 j(ij).custom.addSeparator(); 2413 j(ij).customdel = gui_component('MenuItem', j(ij).custom, [], 'Ignore local installation', [], [], @(h,ev)SetCustomPath(Plug.Name, 0), fontSize); 2414 j(ij).menu.addSeparator(); 2415 % Load 2416 j(ij).load = gui_component('MenuItem', j(ij).menu, [], 'Load', IconLoader.ICON_GOOD, [], @(h,ev)LoadInteractive(Plug.Name), fontSize); 2417 j(ij).unload = gui_component('MenuItem', j(ij).menu, [], 'Unload', IconLoader.ICON_BAD, [], @(h,ev)UnloadInteractive(Plug.Name), fontSize); 2418 j(ij).menu.addSeparator(); 2419 % Website 2420 j(ij).web = gui_component('MenuItem', j(ij).menu, [], 'Website', IconLoader.ICON_EXPLORER, [], @(h,ev)web(Plug.URLinfo, '-browser'), fontSize); 2421 j(ij).usage = gui_component('MenuItem', j(ij).menu, [], 'Usage statistics', IconLoader.ICON_TS_DISPLAY, [], @(h,ev)bst_userstat(0,Plug.Name), fontSize); 2422 % Extra menus 2423 if ~isempty(Plug.ExtraMenus) 2424 j(ij).menu.addSeparator(); 2425 for iMenu = 1:size(Plug.ExtraMenus,1) 2426 j(ij).extra(iMenu) = gui_component('MenuItem', j(ij).menu, [], Plug.ExtraMenus{iMenu,1}, IconLoader.ICON_EXPLORER, [], @(h,ev)bst_call(@eval, Plug.ExtraMenus{iMenu,2}), fontSize); 2427 end 2428 end 2429 end 2430 end 2431 % List 2432 if ~isCompiled 2433 jMenu.addSeparator(); 2434 gui_component('MenuItem', jMenu, [], 'List', IconLoader.ICON_EDIT, [], @(h,ev)List('Installed', 1), fontSize); 2435 end 2436 end 2437 2438 2439 %% ===== MENUS: UPDATE ===== 2440 function MenuUpdate(jPlugs) 2441 import org.brainstorm.icon.*; 2442 % If compiled: disable most menus 2443 isCompiled = bst_iscompiled(); 2444 % Interface scaling 2445 InterfaceScaling = bst_get('InterfaceScaling'); 2446 % Update all the plugins 2447 for iPlug = 1:length(jPlugs) 2448 j = jPlugs(iPlug); 2449 PlugName = j.name; 2450 % Is installed? 2451 PlugRef = GetSupported(PlugName); 2452 Plug = GetInstalled(PlugName); 2453 if ~isempty(Plug) 2454 isInstalled = 1; 2455 elseif ~isempty(PlugRef) 2456 Plug = PlugRef; 2457 isInstalled = 0; 2458 else 2459 disp(['BST> Error: Description not found for plugin: ' PlugName]); 2460 continue; 2461 end 2462 isLoaded = isInstalled && Plug.isLoaded; 2463 isManaged = isInstalled && Plug.isManaged; 2464 % Compiled included: no submenus 2465 if isCompiled && (PlugRef.CompiledStatus == 2) 2466 j.menu.setEnabled(1); 2467 if (InterfaceScaling ~= 100) 2468 j.menu.setIcon(IconLoader.scaleIcon(IconLoader.ICON_GOOD, InterfaceScaling / 100)); 2469 else 2470 j.menu.setIcon(IconLoader.ICON_GOOD); 2471 end 2472 % Otherwise: all available 2473 else 2474 % Main menu: Available/Not available 2475 j.menu.setEnabled(isInstalled || ~isempty(Plug.URLzip)); 2476 % Current version 2477 if ~isInstalled 2478 j.version.setText('<HTML><FONT color="#707070"><I>Not installed</I></FONT>'); 2479 elseif ~isManaged && ~isempty(Plug.Path) 2480 j.version.setText('<HTML><FONT color="#707070"><I>Custom install</I></FONT>') 2481 elseif ~isempty(Plug.Version) && ischar(Plug.Version) 2482 strVer = Plug.Version; 2483 % If downloading from github 2484 if isGithubMaster(Plug.URLzip) 2485 % Show installation date, if available 2486 if ~isempty(Plug.InstallDate) 2487 strVer = Plug.InstallDate(1:11); 2488 % Show only the short SHA (7 chars) 2489 elseif (length(Plug.Version) >= 30) 2490 strVer = Plug.Version(1:7); 2491 end 2492 end 2493 j.version.setText(['<HTML><FONT color="#707070"><I>Installed version: ' strVer '</I></FONT>']) 2494 elseif isInstalled 2495 j.version.setText('<HTML><FONT color="#707070"><I>Installed</I></FONT>'); 2496 end 2497 % Main menu: Icon 2498 if isCompiled && isInstalled 2499 menuIcon = IconLoader.ICON_GOOD; 2500 elseif isLoaded % Loaded 2501 menuIcon = IconLoader.ICON_GOOD; 2502 elseif isInstalled % Not loaded 2503 menuIcon = IconLoader.ICON_BAD; 2504 else 2505 menuIcon = IconLoader.ICON_NEUTRAL; 2506 end 2507 if (InterfaceScaling ~= 100) 2508 j.menu.setIcon(IconLoader.scaleIcon(menuIcon, InterfaceScaling / 100)); 2509 else 2510 j.menu.setIcon(menuIcon); 2511 end 2512 % Install 2513 j.install.setEnabled(~isInstalled); 2514 if ~isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2515 j.install.setText(['<HTML>Install    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2516 else 2517 j.install.setText('Install'); 2518 end 2519 % Update 2520 j.update.setEnabled(isManaged); 2521 if isInstalled && ~isempty(PlugRef.Version) && ischar(PlugRef.Version) 2522 j.update.setText(['<HTML>Update    <FONT color="#707070"><I>(' PlugRef.Version ')</I></FONT>']) 2523 else 2524 j.update.setText('Update'); 2525 end 2526 % Uninstall 2527 j.uninstall.setEnabled(isManaged); 2528 % Custom install 2529 j.custom.setEnabled(~isManaged); 2530 if ~isempty(Plug.Path) 2531 j.custompath.setText(Plug.Path); 2532 else 2533 j.custompath.setText('Path not set'); 2534 end 2535 % Load/Unload 2536 j.load.setEnabled(isInstalled && ~isLoaded && ~isCompiled); 2537 j.unload.setEnabled(isLoaded && ~isCompiled); 2538 % Web 2539 j.web.setEnabled(~isempty(Plug.URLinfo)); 2540 % Extra menus: Update availability 2541 if ~isempty(Plug.ExtraMenus) 2542 for iMenu = 1:size(Plug.ExtraMenus,1) 2543 if (size(Plug.ExtraMenus,2) == 3) && ~isempty(Plug.ExtraMenus{3}) 2544 if (strcmpi(Plug.ExtraMenus{3}, 'loaded') && isLoaded) ... 2545 || (strcmpi(Plug.ExtraMenus{3}, 'installed') && isInstalled) ... 2546 || (strcmpi(Plug.ExtraMenus{3}, 'always')) 2547 j.extra(iMenu).setEnabled(1); 2548 else 2549 j.extra(iMenu).setEnabled(0); 2550 end 2551 end 2552 end 2553 end 2554 end 2555 end 2556 j.menu.repaint() 2557 j.menu.getParent().repaint() 2558 end 2559 2560 2561 %% ===== SET CUSTOM PATH ===== 2562 function SetCustomPath(PlugName, PlugPath) 2563 % Parse inputs 2564 if (nargin < 2) || isempty(PlugPath) 2565 PlugPath = []; 2566 end 2567 % Custom plugin paths 2568 PluginCustomPath = bst_get('PluginCustomPath'); 2569 % Get plugin description 2570 PlugDesc = GetSupported(PlugName); 2571 if isempty(PlugDesc) 2572 return; 2573 end 2574 % Get installed plugin 2575 PlugInst = GetInstalled(PlugName); 2576 isInstalled = ~isempty(PlugInst); 2577 isManaged = isInstalled && PlugInst.isManaged; 2578 if isManaged 2579 bst_error(['Plugin ' PlugName ' is already installed by Brainstorm, uninstall it first.'], 0); 2580 return; 2581 end 2582 % Ask install path to user 2583 isWarning = 1; 2584 if isempty(PlugPath) 2585 PlugPath = uigetdir(PlugInst.Path, ['Select ' PlugName ' directory.']); 2586 if isequal(PlugPath, 0) 2587 PlugPath = []; 2588 end 2589 % If removal is requested 2590 elseif isequal(PlugPath, 0) 2591 PlugPath = []; 2592 isWarning = 0; 2593 end 2594 % If the directory did not change: nothing to do 2595 if (isInstalled && isequal(PlugInst.Path, PlugPath)) || (~isInstalled && isempty(PlugPath)) 2596 return; 2597 end 2598 % Unload previous version 2599 if isInstalled && ~isempty(PlugInst.Path) && PlugInst.isLoaded 2600 Unload(PlugName); 2601 end 2602 % Check if this is a valid plugin folder 2603 if isempty(PlugPath) || ~file_exist(PlugPath) 2604 PlugPath = []; 2605 end 2606 if ~isempty(PlugPath) && ~isempty(PlugDesc.TestFile) 2607 isValid = 0; 2608 if file_exist(bst_fullfile(PlugPath, PlugDesc.TestFile)) 2609 isValid = 1; 2610 elseif ~isempty(PlugDesc.LoadFolders) 2611 for iFolder = 1:length(PlugDesc.LoadFolders) 2612 if file_exist(bst_fullfile(PlugPath, PlugDesc.LoadFolders{iFolder}, PlugDesc.TestFile)) 2613 isValid = 1; 2614 end 2615 end 2616 end 2617 if ~isValid 2618 PlugPath = []; 2619 end 2620 end 2621 % Save path 2622 PluginCustomPath.(PlugName) = PlugPath; 2623 bst_set('PluginCustomPath', PluginCustomPath); 2624 % Load plugin 2625 if ~isempty(PlugPath) 2626 [isOk, errMsg, PlugDesc] = Load(PlugName); 2627 % Ignored warnings 2628 elseif ~isWarning 2629 isOk = 1; 2630 errMsg = []; 2631 % Invalid path 2632 else 2633 isOk = 0; 2634 if ~isempty(PlugDesc.TestFile) 2635 errMsg = ['The file ' PlugDesc.TestFile ' could not be found in selected folder.']; 2636 else 2637 errMsg = 'No valid folder was found.'; 2638 end 2639 end 2640 % Handle errors 2641 if ~isOk 2642 bst_error(['An error occurred while configuring plugin ' PlugName ':' 10 10 errMsg 10], 'Plugin manager', 0); 2643 elseif ~isempty(errMsg) 2644 java_dialog('msgbox', ['Configuration message:' 10 10 errMsg 10], 'Plugin manager'); 2645 elseif isWarning 2646 java_dialog('msgbox', ['Plugin ' PlugName ' successfully loaded.']); 2647 end 2648 end 2649 2650 2651 %% ===== ARCHIVE SOFTWARE ENVIRONMENT ===== 2652 % USAGE: Archive(OutputFile=[ask]) 2653 function Archive(OutputFile) 2654 % Parse inputs 2655 if (nargin < 1) || isempty(OutputFile) 2656 OutputFile = []; 2657 end 2658 % Get date string 2659 c = clock(); 2660 strDate = sprintf('%02d%02d%02d', c(1)-2000, c(2), c(3)); 2661 % Get output filename 2662 if isempty(OutputFile) 2663 % Get default directories 2664 LastUsedDirs = bst_get('LastUsedDirs'); 2665 % Default output filename 2666 OutputFile = bst_fullfile(LastUsedDirs.ExportScript, ['bst_env_' strDate '.zip']); 2667 % File selection 2668 OutputFile = java_getfile('save', 'Export environment', OutputFile, 'single', 'files', ... 2669 {{'.zip'}, 'Zip files (*.zip)', 'ZIP'}, 1); 2670 if isempty(OutputFile) 2671 return 2672 end 2673 % Save new default export path 2674 LastUsedDirs.ExportScript = bst_fileparts(OutputFile); 2675 bst_set('LastUsedDirs', LastUsedDirs); 2676 end 2677 2678 % ===== TEMP FOLDER ===== 2679 bst_progress('start', 'Export environment', 'Creating temporary folder...'); 2680 2681 % ===== COPY BRAINSTORM ===== 2682 bst_progress('text', 'Copying: brainstorm...'); 2683 % Get Brainstorm path and version 2684 bstVer = bst_get('Version'); 2685 bstDir = bst_get('BrainstormHomeDir'); 2686 % Create temporary folder for storing all the files to package 2687 TmpDir = bst_get('BrainstormTmpDir', 0, 'bstenv'); 2688 % Get brainstorm3 destination folder: add version number 2689 if ~isempty(bstVer.Version) && ~any(bstVer.Version == '?') 2690 envBst = bst_fullfile(TmpDir, ['brainstorm', bstVer.Version]); 2691 else 2692 [tmp, bstName] = bst_fileparts(bstDir); 2693 envBst = bst_fullfile(TmpDir, bstName); 2694 end 2695 % Add git commit hash 2696 if (length(bstVer.Commit) >= 30) 2697 envBst = [envBst, '_', bstVer.Commit(1:7)]; 2698 end 2699 % Copy brainstorm3 folder 2700 isOk = file_copy(bstDir, envBst); 2701 if ~isOk 2702 error(['Cannot copy folder: "' bstDir '" to "' envBst '"']); 2703 end 2704 2705 % ===== COPY DEFAULTS ===== 2706 bst_progress('text', 'Copying: user defaults...'); 2707 % Get user defaults folder 2708 userDef = bst_get('UserDefaultsDir'); 2709 envDef = bst_fullfile(envBst, 'defaults'); 2710 isOk = file_copy(userDef, envDef); 2711 if ~isOk 2712 error(['Cannot merge folder: "' userDef '" into "' envDef '"']); 2713 end 2714 2715 % ===== COPY USER PROCESSES ===== 2716 bst_progress('text', 'Copying: user processes...'); 2717 % Get user process folder 2718 userProc = bst_get('UserProcessDir'); 2719 envProc = bst_fullfile(envBst, 'toolbox', 'process', 'functions'); 2720 isOk = file_copy(userProc, envProc); 2721 if ~isOk 2722 error(['Cannot merge folder: "' userProc '" into "' envProc '"']); 2723 end 2724 2725 % ===== COPY PLUGINS ====== 2726 % Get list of plugins to package 2727 PlugDesc = GetInstalled(); 2728 % Destination plugin directory 2729 envPlugins = bst_fullfile(envBst, 'plugins'); 2730 % Copy each installed plugin 2731 for iPlug = 1:length(PlugDesc) 2732 bst_progress('text', ['Copying plugin: ' PlugDesc(iPlug).Name '...']); 2733 envPlug = bst_fullfile(envPlugins, PlugDesc(iPlug).Name); 2734 isOk = file_copy(PlugDesc(iPlug).Path, envPlug); 2735 if ~isOk 2736 error(['Cannot copy folder: "' userProc '" into "' envProc '"']); 2737 end 2738 end 2739 2740 % ===== SAVE LIST OF VERSIONS ===== 2741 strList = bst_plugin('List', 'installed', 0); 2742 % Open file versions.txt 2743 VersionFile = bst_fullfile(TmpDir, 'versions.txt'); 2744 fid = fopen(VersionFile, 'wt'); 2745 if (fid < 0) 2746 error(['Cannot save file: ' VersionFile]); 2747 end 2748 % Save Brainstorm plugins list 2749 fwrite(fid, strList); 2750 % Save Matlab ver command 2751 strMatlab = evalc('ver'); 2752 fwrite(fid, [10 10 strMatlab]); 2753 % Close file 2754 fclose(fid); 2755 2756 % ===== ZIP FILES ===== 2757 bst_progress('text', 'Zipping environment...'); 2758 % Zip files with bst_env_* being the first level 2759 zip(OutputFile, TmpDir, bst_fileparts(TmpDir)); 2760 % Delete the temporary files 2761 file_delete(TmpDir, 1, 1); 2762 % Close progress bar 2763 bst_progress('stop'); 2764 end 2765 2766 2767 %% ============================================================================ 2768 % ===== PLUGIN-SPECIFIC FUNCTIONS ============================================ 2769 % ============================================================================ 2770 2771 %% ===== LINK CAT-SPM ===== 2772 % USAGE: bst_plugin('LinkCatSpm', Action) 2773 % 0=Delete/1=Create/2=Check a symbolic link for CAT12 in SPM12 toolbox folder 2774 function LinkCatSpm(Action) 2775 % Get SPM12 plugin 2776 PlugSpm = GetInstalled('spm12'); 2777 if isempty(PlugSpm) 2778 error('Plugin SPM12 is not loaded.'); 2779 elseif ~PlugSpm.isLoaded 2780 [isOk, errMsg, PlugSpm] = Load('spm12'); 2781 if ~isOk 2782 error('Plugin SPM12 cannot be loaded.'); 2783 end 2784 end 2785 % Get SPM plugin path 2786 if ~isempty(PlugSpm.SubFolder) 2787 spmToolboxDir = bst_fullfile(PlugSpm.Path, PlugSpm.SubFolder, 'toolbox'); 2788 else 2789 spmToolboxDir = bst_fullfile(PlugSpm.Path, 'toolbox'); 2790 end 2791 if ~file_exist(spmToolboxDir) 2792 error(['Could not find SPM12 toolbox folder: ' spmToolboxDir]); 2793 end 2794 % CAT12 plugin path 2795 spmCatDir = bst_fullfile(spmToolboxDir, 'cat12'); 2796 % Check link 2797 if (Action == 2) 2798 % Link exists and works: return here 2799 if file_exist(bst_fullfile(spmCatDir, 'cat12.m')) 2800 return; 2801 % Link doesn't exist: Create it 2802 else 2803 Action = 1; 2804 end 2805 end 2806 % If folder already exists 2807 if file_exist(spmCatDir) 2808 % If setting install and SPM is not managed by Brainstorm: do not risk deleting user's install of CAT12 2809 if (Action == 1) && ~PlugSpm.isManaged 2810 error(['CAT12 seems already set up: ' spmCatDir]); 2811 end 2812 % All the other cases: delete existing CAT12 folder 2813 if ispc 2814 rmCall = ['rmdir /q /s "' spmCatDir '"']; 2815 else 2816 rmCall = ['rm -rf "' spmCatDir '"']; 2817 end 2818 disp(['BST> Deleting existing SPM12 toolbox: ' rmCall]); 2819 [status,result] = system(rmCall); 2820 if (status ~= 0) 2821 error(['Error deleting link: ' result]); 2822 end 2823 end 2824 % Create new link 2825 if (Action == 1) 2826 % Get CAT12 plugin 2827 PlugCat = GetInstalled('cat12'); 2828 if isempty(PlugCat) || ~PlugCat.isLoaded 2829 error('Plugin CAT12 is not loaded.'); 2830 end 2831 % Return if installation is not complete yet (first load before installation ends) 2832 if isempty(PlugCat.InstallDate) 2833 return 2834 end 2835 % Define source and target for the link 2836 if ~isempty(PlugCat.SubFolder) 2837 linkTarget = bst_fullfile(PlugCat.Path, PlugCat.SubFolder); 2838 else 2839 linkTarget = PlugCat.Path; 2840 end 2841 linkFile = spmCatDir; 2842 % Create link 2843 if ispc 2844 linkCall = ['mklink /D "' linkFile '" "' linkTarget '"']; 2845 else 2846 linkCall = ['ln -s "' linkTarget '" "' linkFile '"']; 2847 end 2848 disp(['BST> Creating symbolic link: ' linkCall]); 2849 [status,result] = system(linkCall); 2850 if (status ~= 0) 2851 error(['Error creating link: ' result]); 2852 end 2853 end 2854 end 2855 2856 2857 %% ===== SET PROGRESS LOGO ===== 2858 % USAGE: SetProgressLogo(PlugDesc/PlugName) % Set progress bar image 2859 % SetProgressLogo([]) % Remove progress bar image 2860 function SetProgressLogo(PlugDesc) 2861 % Remove image 2862 if (nargin < 1) || isempty(PlugDesc) 2863 bst_progress('removeimage'); 2864 bst_progress('removelink'); 2865 % Set image 2866 else 2867 % Get plugin description 2868 if ischar(PlugDesc) 2869 PlugDesc = GetSupported(PlugDesc); 2870 end 2871 % Set logo file 2872 if isempty(PlugDesc.LogoFile) 2873 PlugDesc.LogoFile = GetLogoFile(PlugDesc); 2874 end 2875 if ~isempty(PlugDesc.LogoFile) 2876 bst_progress('setimage', PlugDesc.LogoFile); 2877 end 2878 % Set link 2879 if ~isempty(PlugDesc.URLinfo) 2880 bst_progress('setlink', PlugDesc.URLinfo); 2881 end 2882 end 2883 end 2884 2885 2886 %% ===== NOT SUPPORTED APPLE SILICON ===== 2887 % Return list of plugins not supported on Apple silicon 2888 function pluginNames = PluginsNotSupportAppleSilicon() 2889 pluginNames = {'brain2mesh', 'ct2mrireg', 'duneuro', 'iso2mesh', 'mcxlab-cl', 'mcxlab-cuda', ... 2890 'openmeeg', 'xdf'}; 2891 end

Plugin definition

The plugins registered in Brainstorm are listed in bst_plugin.m / GetSupported. Each one is an entry in the PlugDesc array, following the structure defined in db_template('plugdesc'). The fields allowed are described below.

Mandatory fields:

Optional fields:

Text filename, (relative to the plugin path) - If empty, try using brainstorm3/doc/plugin/<Name>_readme.txt

Fields set when installing or loading the plugin:

Tutorials/Plugins (last edited 2021-03-10 18:12:12 by FrancoisTadel)