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