Brainstorm
  • Comments
  • Menu
    • Attachments
    • Versions
    • Raw Text
    • Print View
  • Login

Software

  • Introduction

  • Gallery

  • Download

  • Installation

Users

  • Tutorials

  • Forum

  • Courses

  • Community

  • Publications

Development

  • What's new

  • What's next

  • About us

  • Contact us

  • Contribute

Revision 28 as of 2021-08-30 11:48:33
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

Plugins

Authors: Francois Tadel

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

Contents

  1. Interactive management
  2. Brainstorm plugins by category
  3. Example: FieldTrip
  4. Plugin definition
  5. Command-line management

Interactive management

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

example1.gif

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

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

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

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

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

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

list.gif

Brainstorm plugins by category

Generic toolboxes:

  • SPM12: https://www.fil.ion.ucl.ac.uk/spm/

  • FieldTrip: http://www.fieldtriptoolbox.org

Anatomy processing:

  • Brain2Mesh: http://mcx.space/brain2mesh/

    • Warning: Requires the Imaging Processing Toolbox
  • CAT12: https://neuroimage.usc.edu/brainstorm/Tutorials/SegCAT12

  • Iso2Mesh: http://iso2mesh.sourceforge.net

  • ROAST: https://www.parralab.org/roast/

Inverse modeling:

  • BrainEntropy: https://neuroimage.usc.edu/brainstorm/Tutorials/TutBEst

Forward modeling:

  • OpenMEEG: https://neuroimage.usc.edu/brainstorm/Tutorials/TutBem

  • DUNEuro: https://neuroimage.usc.edu/brainstorm/Tutorials/Duneuro

Simulation:

  • SimMEEG: https://audiospeech.ubc.ca/research/brane/brane-lab-software/

Statistics:

  • LibSVM: https://www.csie.ntu.edu.tw/~cjlin/libsvm/

I/O Libraries for specific file formats:

  • Philips-EGI EEG (.mff): https://github.com/arnodelorme/mffmatlabio

  • Blackrock NeuroPort (.nev, .nsx): https://github.com/BlackrockMicrosystems/NPMK

  • AD Instruments SDK: https://github.com/JimHokanson/adinstruments_sdk_matlab

  • Neurodata Without Borders: https://github.com/NeurodataWithoutBorders/matnwb

  • Plotly export: https://plotly.com/matlab/

  • Tucker-Davis Technologies SDK: https://www.tdt.com/support/matlab-sdk/

fNIRS:

  • NIRSTORM: https://github.com/Nirstorm/nirstorm

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.

  • Already in path: If you already have a recent version of FieldTrip set up on your computer and available in your Matlab path, the fieldtrip plugin entry should show a green dot, and everything should work without doing anything special.

    ft_path.gif

  • Custom install: If FieldTrip is somewhere on your computer, but not currently in your Matlab path, you can simply tell Brainstorm where it is with the menu: Plugins > fieldtrip > Custom install > Set installation folder.

    ft_custom.gif

  • Plugin install: Otherwise, the easiest solution is to click on Plugins > fieldtrip > Install, and let Brainstorm manage everything automatically: downloading the latest version, setting the path, managing incompatibilities with other toolboxes (e.g. SPM or ROAST).

    ft_install.gif

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:

  • Name: String: Plugin name = subfolder in the Brainstorm user folder

  • Version: String: Version of the plugin (eg. '1.2', '21a', 'github-master', 'latest')

  • URLzip: String: Download URL, zip or tgz file accessible over HTTP/HTTPS/FTP

  • URLinfo: String: Information URL = Software website

Optional fields:

  • AutoUpdate: Boolean: If true, the plugin is updated automatically when there is a new version available (default: false).

  • AutoLoad: Boolean: If true, the plugin is loaded automatically at Brainstorm startup

  • Category: String: Sub-menu in which the plugin is listed

  • ExtraMenus: Cell matrix {Nx2}: List of entries to add to the plugins menu

    • ExtraMenus{i,1}: String: Label of the menu

    • ExtraMenus{i,2}: String: Matlab code to eval when the menu is clicked

  • TestFile: String: Name of a file that should be located in one of the loaded folders of the plugin (eg. 'spm.m' for SPM12). This is used to test whether the plugin was correctly installed, or whether it is available somewhere else in the Matlab path.

  • ReadmeFile: String: Name of the text file to display after installing the plugin (must be in the plugin folder). If empty, it tries using brainstorm3/doc/plugin/plugname_readme.txt

  • LogoFile: String: Name of the image file to display during the plugin download, installation, and associated computations (must be in the plugin folder). Supported extensions: gif, png. If empty, try using brainstorm3/doc/plugin/<Name>_logo.[gif|png]

  • MinMatlabVer: Integer: Minimum Matlab version required for using this plugin, as returned by bst_get('MatlabVersion')

  • CompiledStatus: Integer: Behavior of this plugin in the compiled version of Brainstorm:

    • 0: Plugin is not available in the compiled distribution of Brainstorm
    • 1: Plugin is available for download (only for plugins based on native compiled code)
    • 2: Plugin is included in the compiled distribution of Brainstorm
  • RequiredPlugs: Cell-array: Additional plugins required by this plugin, that must be installed/loaded beforehand.

    • {Nx2} => {'plugname','version'; ...} or

    • {Nx1} => {'plugname'; ...}

  • UnloadPlugs: Cell-array of names of incompatible plugin, to unload before loaing this one

  • LoadFolders: Cell-array of subfolders to add to the Matlab path when setting up the plugin. Use {'*'} to add all the plugin subfolders.

  • GetVersionFcn: String to eval or function handle to call to get the version after installation

  • InstalledFcn: String to eval or function handle to call after installing the plugin

  • UninstalledFcn: String to eval or function handle to call after uninstalling the plugin

  • LoadedFcn: String to eval or function handle to call after loading the plugin

  • UnloadedFcn: String to eval or function handle to call after unloading the plugin

Fields set when installing the plugin:

  • Processes: List of process functions to be added to the pipeline manager

Fields set when loading the plugin:

  • Path: Installation path (eg. /home/username/.brainstorm/plugins/fieldtrip)

  • SubFolder: If all the code is in a single subfolder (eg. /plugins/fieldtrip/fieldtrip-20210304), this is detected and the full path to the TestFile would be typically fullfile(Path, SubFolder).

  • isLoaded: 0=Not loaded, 1=Loaded

  • isManaged: 0=Installed manually by the user, 1=Installed automatically by Brainstorm

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