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 54 as of 2024-05-24 20:31:47
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 following categories:

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

  • CT2MRI: https://github.com/ajoshiusc/USCCleveland/tree/master/ct2mrireg

    • Warning: Requires the Imaging Processing Toolbox and BrainSuite

  • 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

  • EasyH5 Toolbox (.h5): https://github.com/NeuroJSON/easyh5

  • JSNIRF Toolbox (.snirf, .jsnirf, .bnirs): https://github.com/NeuroJSON/jsnirfy

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

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

  • NumPy-Matlab (.npy): https://github.com/kwikteam/npy-matlab

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

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

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

  • FastICA: https://research.ics.aalto.fi/ica/fastica

  • 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

  • KiloSort: https://papers.nips.cc/paper/2016/hash/1145a30ff80745b56fb0cecf65305017-Abstract.html

  • KiloSortWrapper: https://zenodo.org/records/3604165

  • phy: https://phy.readthedocs.io/en/latest/

  • UltraMegaSort2000: https://github.com/danamics/UMS2K/blob/master/UltraMegaSort2000%20Manual.pdf

  • Wave_clus: https://journals.physiology.org/doi/full/10.1152/jn.00339.2018

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/

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

Example: FieldTrip

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

  • 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, the new plugin will appear under the category User defined, and it can be installed. When a plugin is registered by the user, there will be a JSON file with the plugin definition in the Brainstorm plugin folder: $HOME/.brainstorm/plugins/

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

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

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

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

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

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

user_def_menu.gif

Example of JSON file

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

{ "Name": "bst-users", "Version": "latest", "URLzip": "https://github.com/brainstorm-tools/bst-users/archive/refs/heads/master.zip", "URLinfo": "https://github.com/brainstorm-tools/bst-users/", "TestFile": "processes/examples/process_beamformer_test.m", "LoadFolders": [ "processes" ], }

Command-line management

The calls to install or manage plugins are all documented in the header of bst_plugin.m:

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