Plugins

Authors: Francois Tadel, Raymundo Cassani

Brainstorm connects with features from many third-party libraries of methods. In Brainstorm jargon, a plugin is an external software that is 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/

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 installing again.

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

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.

Load: Adds all the subfolders needed by the plugin to the Matlab path, plus other optional tasks. 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.

Website: Opens the website documenting the plugin.

Usage statistics: Display the number of downloads of the plugin, month by month.

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

list.gif

Brainstorm plugins by category

By default Brainstorms offers a curated set of plugins organized in following categories:

Generic toolboxes:

Anatomy processing:

Forward modeling:

Inverse modeling:

I/O Libraries for specific file formats:

Simulation:

Statistics:

e-phys:

fNIRS:

sEEG:

If there is a third-party software that you consider useful to the Brainstorm community, please make a post about it in the Brainstorm Forum to start the discussion.

Meanwhile, if you want to add a third-party software that is not available as a plugin, you can register such software as an user-defined plugin, see this section below: User-defined plugins.

Example: FieldTrip

We have the possibility to call some of the FieldTrip toolbox functions from the Brainstorm environment. If you are running the compiled version of Brainstorm these functions are already packaged with Brainstorm, otherwise you need to install FieldTrip on your computer, either manually or as a Brainstorm plugin.

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:

Fields set when installing the plugin:

Fields set when loading the plugin:

User-defined plugins

It is possible for users to register their own plugins. Once the user-defined plugin is registered, the new plugin will appear under the category User defined, and it can be installed. When a plugin is registered by the user, there will be a JSON file with the plugin definition in the Brainstorm plugin folder: $HOME/.brainstorm/plugins/

Registration of user-defined plugins can be done in three different ways:

  1. Manually: Provide the mandatory fields of the plugin definition. All other fields will be set to their default values.

  2. From file: Provide the path to a JSON file with the plugin definition.

  3. From URL: Provide the URL to a JSON file with the plugin definition.

Providing the JSON file (by file path or URL) allows more flexibility in the plugin definition.

User-defined plugins can be removed with the Remove plugin option.

user_def_menu.gif

Example of JSON file

This is an example of a JSON file to add the processes of the bst-users repository as an user-defined plugin.

{ "Name": "bst-users", "Version": "latest", "URLzip": "https://github.com/brainstorm-tools/bst-users/archive/refs/heads/master.zip", "URLinfo": "https://github.com/brainstorm-tools/bst-users/", "TestFile": "processes/examples/process_beamformer_test.m", "LoadFolders": [ "processes/*" ], "ExtraMenus": [["Plugin tutorial","web('https://neuroimage.usc.edu/brainstorm/Tutorials/Plugins', '-browser')"], ["Show Brainstorm license","bst_license"], ["Print cat in Matlab", "disp('=^_^=')"]], "CompiledStatus": 0 }

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

Tutorials/Plugins (last edited 2025-10-13 19:08:20 by ChinmayChinara)