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 44 as of 2023-02-22 08:52:14
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/

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

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

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

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

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

Website: Opens the website documenting the plugin.

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

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

list.gif

Brainstorm plugins by category

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/

Forward modeling:

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

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

Inverse modeling:

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

I/O Libraries for specific file formats:

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

  • Axion BioSystems (.raw): https://www.axionbiosystems.com/products/software/neural-module

  • BCI2000 (.dat): https://www.bci2000.org/mediawiki/index.php/Technical_Reference:BCI2000_File_Format

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

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

  • Plexon (.plx, .pl2): https://plexon.com/software-downloads/#software-downloads-SDKs

  • Neuroelectrics EEGLAB plugin (.easy, .nedf): https://www.neuroelectrics.com

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

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

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

  • XDF: https://github.com/xdf-modules/xdf-Matlab

Simulation:

  • SimMEEG: https://neuroimage.usc.edu/brainstorm/Tutorials/Simulations#SimMEEG

Statistics:

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

  • Picard ICA: https://github.com/pierreablin/picard

e-phys:

  • DeriveLFP: https://journals.physiology.org/doi/full/10.1152/jn.00642.2010

fNIRS:

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

  • MCXlab-cuda: http://mcx.space/wiki/

  • MCXlab-cl: http://mcx.space/wiki/

sEEG:

  • MIA: http://www.neurotrack.fr/mia/

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: true).

  • 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} or {Nx3}: 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

    • ExtraMenus{i,3}: String, optional: Context in which the menu is enabled {'installed', 'loaded', 'always'}. By default, the menu is always enabled.

  • 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 loading 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

  • DownloadedFcn: String to eval or function handle to call after downloading the plugin

  • 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

  • DeleteFiles: Cell-array of files to delete after unzipping the plugin package (path relative to the plugin folder)

  • DeleteFilesBin: Cell-array of files to delete before compiling Brainstorm, to avoid including them in the binary distribution (path relative to the plugin folder)

Fields set when installing the plugin:

  • InstallDate: Installation date: 'dd-mmm-yyyy HH:MM:SS'

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