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 47 as of 2024-05-24 15:41:40
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, Raymundo Cassani

Brainstorm connects with features from many third-party libraries of methods. In Brainstorm jargon, a plugin is an external software that is can be downloaded or updated automatically by Brainstorm when needed. This tutorial presents the API to register and manage plugins.

Contents

  1. Interactive management
  2. Brainstorm plugins by category
  3. Example: FieldTrip
  4. Plugin definition
  5. User-defined plugins
  6. 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

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

NOTE

  • If you want to add a third-party software that is not available as a plugin, you can register such software as an user-defined plugin.

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', 'github-main', '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

User-defined plugins

It is possible for users to register their own plugins. Once the user-defined plugin is registered, there will be a JSON file with the plugin definition in the PLUGIN DIR.

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

  1. **Manually**, it is necessary to provide the **mandatory fields** of the plugin definition (LINK0), all other fields are set to their default values.
  2. **JSON file**, provide the location to a JSON file with the plugin definition.
  3. **URL to JSON file**, provide the URL for the JSON file with the plugin definition.

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

User-defined plugins that have been registered in Brainstorm can be removed with the option:

IMAGE

NOTE. All user-defined plugins will be added in the **User defined** plugin list

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