├── bst_plugin ├── VERSION ├── math │ ├── dg_voronoi.mexa64 │ ├── dg_voronoi.mexglx │ ├── dg_voronoi.mexw64 │ ├── dg_voronoi.mexmaca64 │ ├── dg_voronoi.mexmaci64 │ ├── nst_math_fit_AR.m │ ├── nst_math_dctmtx.m │ ├── nst_pdist.m │ ├── dg_voronoi.m │ ├── nst_mbll_source.m │ ├── nst_tddr_correction.m │ ├── nst_knnsearch.m │ ├── nst_math_build_basis_dct.m │ ├── nst_mne_lcurve_MAP.m │ └── nst_mne_lcurve.m ├── io │ ├── nst_get_repository_url.m │ ├── nst_get_local_user_dir.m │ ├── nst_get_formats.m │ ├── nst_split_ftp.m │ ├── nst_format_pval.m │ ├── nst_parse_bst_item_name.m │ ├── nst_save_figure.m │ ├── nst_download.m │ ├── nst_save_table_in_bst.m │ └── nst_io_fetch_sample_data.m ├── core │ ├── nst_core_get_available_templates.m │ ├── nst_get_version.m │ ├── nst_measure_types.m │ ├── nst_format_channel.m │ ├── nst_unformat_channel.m │ ├── nst_bst_set_template_anatomy.m │ ├── nst_get_pair_indexes_from_names.m │ ├── nst_get_bst_func_files.m │ ├── nst_bst_add_surf_data.m │ └── nst_unformat_channels.m ├── GLM │ ├── nst_glm_initialize_model.m │ ├── nst_make_basic_contrasts.m │ ├── nst_glm_fit_B.m │ ├── nst_make_event_regressors.m │ ├── nst_make_event_toeplitz_mtx.m │ ├── process_nst_glm_fit1.m │ ├── nst_glm_display_model.m │ ├── nst_glm_apply_filter.m │ ├── nst_glm_fit.m │ ├── process_nst_mask_from_atlas.m │ └── process_nst_glm_contrast_ttest.m ├── misc │ ├── nst_protect_fn_str.m │ ├── nst_misc_unpack_glm_result.m │ ├── nst_misc_convert_to_mumol.m │ └── nst_misc_FOV_to_cortex.m ├── forward │ ├── nst_headmodel_get_gains.m │ ├── nst_headmodel_get_FOV.m │ ├── nst_get_tissues_optical_properties.m │ ├── tissues_property.json │ └── process_nst_cpt_cortex_to_head_distance.m └── preprocessing │ ├── process_nst_deglitch.m │ ├── process_nst_detrend.m │ ├── process_nst_extract_ssc.m │ └── process_nst_separations.m ├── test ├── IGNORE ├── utest_request_data.m ├── all_close.m ├── utest_reset_bst.m ├── bst_create_test_protocol.m ├── utest_clean_bst.m ├── utest_bst_setup.m ├── ScriptTest.m ├── bst_create_test_subject.m ├── utest_get_test_bst_events.m ├── ArFitTest.m ├── bst_create_scout.m ├── PrefixMatrixTest.m ├── utest_import_nirs_in_bst.m ├── WorkShopPerform2018Test.m ├── SeparationTest.m ├── cpt_spherical_fluences.m ├── DeglitchTest.m ├── RemoteDataTest.m ├── SpreeTest.m ├── run_tests.m ├── EVTImportTest.m ├── SciTest.m ├── OptimalMontageTest.m └── MbllTest.m ├── .gitignore ├── scripts ├── process_memo.m ├── deglitch_example.m ├── generate_colormap_matlab.py ├── source_space_full_group_pipeline_V1.m ├── surface_template_full_group_pipeline_V1.m ├── surface_group_pipeline_V1.m ├── anatomy_based_full_group_pipeline_V1.m └── surface_template_full_group_pipeline_V1_all_opts.m └── README.md /bst_plugin/VERSION: -------------------------------------------------------------------------------- 1 | stable_v0.9.11 2 | -------------------------------------------------------------------------------- /test/IGNORE: -------------------------------------------------------------------------------- 1 | WorkShopPerform2018Test.m 2 | EVTImportTest.m 3 | -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.mexa64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nirstorm/nirstorm/HEAD/bst_plugin/math/dg_voronoi.mexa64 -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.mexglx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nirstorm/nirstorm/HEAD/bst_plugin/math/dg_voronoi.mexglx -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.mexw64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nirstorm/nirstorm/HEAD/bst_plugin/math/dg_voronoi.mexw64 -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.mexmaca64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nirstorm/nirstorm/HEAD/bst_plugin/math/dg_voronoi.mexmaca64 -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.mexmaci64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nirstorm/nirstorm/HEAD/bst_plugin/math/dg_voronoi.mexmaci64 -------------------------------------------------------------------------------- /bst_plugin/io/nst_get_repository_url.m: -------------------------------------------------------------------------------- 1 | function repo_url = nst_get_repository_url() 2 | repo_url = 'https://neuroimage.usc.edu/resources/nst_data'; 3 | end 4 | -------------------------------------------------------------------------------- /bst_plugin/io/nst_get_local_user_dir.m: -------------------------------------------------------------------------------- 1 | function local_nst_dir = nst_get_local_user_dir() 2 | local_nst_dir = fullfile(bst_get('BrainstormUserDir'), 'defaults', 'nirstorm'); 3 | end -------------------------------------------------------------------------------- /test/utest_request_data.m: -------------------------------------------------------------------------------- 1 | function data_fn = utest_request_data(data_bfn_parts) 2 | local_fns = nst_request_files({['unittest' data_bfn_parts]}, 0); 3 | data_fn = local_fns{1}; 4 | end -------------------------------------------------------------------------------- /bst_plugin/core/nst_core_get_available_templates.m: -------------------------------------------------------------------------------- 1 | function template_names = nst_core_get_available_templates() 2 | %TODO: add descriptions 3 | template_names = {'Colin27_4NIRS', ... 4 | 'Colin27_4NIRS_lowres', ... 5 | 'Colin27_4NIRS_Jan19'}; 6 | end -------------------------------------------------------------------------------- /bst_plugin/io/nst_get_formats.m: -------------------------------------------------------------------------------- 1 | function formats = nst_get_formats() 2 | formats.pair_re = 'S(?[0-9]{1,2})D(?[0-9]{1,2})'; 3 | formats.pair_fmt = 'S%dD%d'; 4 | formats.chan_re = [formats.pair_re '(?WL\d+|HbO|HbR|HbT)']; 5 | formats.pair_fmt = 'S%dD%d'; 6 | end -------------------------------------------------------------------------------- /bst_plugin/io/nst_split_ftp.m: -------------------------------------------------------------------------------- 1 | function [ftp_site, rfn] = nst_split_ftp(ftp) 2 | 3 | ftp = strrep(ftp, 'ftp://', ''); 4 | toks = strsplit(ftp, '/'); 5 | ftp_site = toks{1}; 6 | if length(toks) > 1 7 | rfn = strjoin(toks(2:end), '/'); 8 | else 9 | rfn = ''; 10 | end 11 | 12 | end -------------------------------------------------------------------------------- /bst_plugin/io/nst_format_pval.m: -------------------------------------------------------------------------------- 1 | function s_pval = nst_format_pval(pval) 2 | 3 | if pval < 0.001 4 | s_pval = sprintf('%1.1e', pval); 5 | elseif pval < 0.01 6 | s_pval = sprintf('%1.3f', pval); 7 | elseif pval < 0.1 8 | s_pval = sprintf('%1.2f', pval); 9 | else 10 | s_pval = sprintf('%1.1f', pval); 11 | end 12 | 13 | end -------------------------------------------------------------------------------- /bst_plugin/math/nst_math_fit_AR.m: -------------------------------------------------------------------------------- 1 | function W = nst_math_fit_AR(x,M) 2 | % Autoregressive all-pole model parameters using Yule-Walker equation 3 | % W = aryule(x,M); 4 | 5 | r = xcorr(x,'biased')'; 6 | r(1:length(x)-1) = []; 7 | 8 | R=toeplitz(r(1:M)); 9 | 10 | W=R\(-r(2:M+1)); 11 | W=[1 W']; 12 | end -------------------------------------------------------------------------------- /bst_plugin/math/nst_math_dctmtx.m: -------------------------------------------------------------------------------- 1 | function c = nst_math_dctmtx(n) 2 | % Create a DCT matrix, see 3 | % https://www.mathworks.com/help/images/ref/dctmtx.html 4 | % Simimar output as: %cmat = dctmtx(nsamples)'; 5 | 6 | [cc,rr] = meshgrid(0:n-1); 7 | c = sqrt(2 / n) * cos(pi * (2*cc + 1) .* rr / (2 * n)); 8 | c(1,:) = c(1,:) / sqrt(2); 9 | -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_glm_initialize_model.m: -------------------------------------------------------------------------------- 1 | function model = nst_glm_initialize_model(time) 2 | model=struct('X',[],'reg_names',{},'n_roi',0,'time',[],'hrf',[],'accept_filter',[], 'ntime',0,'fs',0); 3 | 4 | %inizialize the structure 5 | model(1).time=time; 6 | model(1).ntime=length(time); 7 | model(1).fs=1/(time(2)-time(1)); 8 | end 9 | 10 | -------------------------------------------------------------------------------- /bst_plugin/core/nst_get_version.m: -------------------------------------------------------------------------------- 1 | function version = nst_get_version() 2 | %NST_GET_VERSION Return the current version of NIRSTORN 3 | version = 'github-master'; 4 | major=0; 5 | minor=0; 6 | patches=0; 7 | 8 | path = fileparts(which('process_nst_mbll.m')); 9 | 10 | id = fopen(fullfile(path, '..','VERSION')); 11 | str=fread(id,'*char' )'; 12 | 13 | version = str(9:end-1); 14 | 15 | end 16 | 17 | -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_make_basic_contrasts.m: -------------------------------------------------------------------------------- 1 | function contrast = nst_make_basic_contrasts(events_order) 2 | 3 | contrast = struct(); 4 | for ievent=1:length(events_order) 5 | contrast(ievent).label = events_order{ievent}; 6 | con_vec = zeros(1, length(events_order)); 7 | con_vec(ievent) = 1; 8 | contrast(ievent).vector = sprintf('[%s]', strip(sprintf('%d ', con_vec))); 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /bst_plugin/misc/nst_protect_fn_str.m: -------------------------------------------------------------------------------- 1 | function s = nst_protect_fn_str(s) 2 | 3 | s = strrep(s, '|', '_'); 4 | s = strrep(s, '"', ''); 5 | s = strrep(s, ':', '_'); 6 | s = strrep(s, '(', '_'); 7 | s = strrep(s, ')', '_'); 8 | s = strrep(s, '[', '_'); 9 | s = strrep(s, ']', '_'); 10 | s = strrep(s, '!', '_'); 11 | s = strrep(s, '__','_'); 12 | s = strrep(s, ' ', '_'); 13 | 14 | end 15 | -------------------------------------------------------------------------------- /bst_plugin/math/nst_pdist.m: -------------------------------------------------------------------------------- 1 | function d = nst_pdist(x,y) 2 | % D = nst_pdist( X,Y) returns the distance between each pair of 3 | % observations in X and Y using the euclidean distance 4 | % Equivalent to pdist2(x,y); Might be slower 5 | % see also pdist2 6 | 7 | d = zeros(size(x,1), size(y,1)); 8 | for i = 1:size(x,1) 9 | for j = 1:size(y,1) 10 | d(i,j) = sqrt(sum( (x(i,:) - y(j,:)).^2)); 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/all_close.m: -------------------------------------------------------------------------------- 1 | function flag = all_close(v1, v2, rtol, atol) 2 | 3 | if nargin < 3 4 | rtol = 1e-5; %default relative tolerance 5 | end 6 | 7 | if nargin < 4 8 | atol = 1e-5; %default absolute tolerance 9 | end 10 | 11 | % Convert to vectors if needed: 12 | if ndims(v1) == 2 13 | v1 = v1(:)'; 14 | end 15 | 16 | if ndims(v2) == 2 17 | v2 = v2(:)'; 18 | end 19 | 20 | flag = all(abs(v1 - v2) <= (atol + rtol * max(abs(v1), abs(v2)))); 21 | 22 | end -------------------------------------------------------------------------------- /bst_plugin/core/nst_measure_types.m: -------------------------------------------------------------------------------- 1 | function measure_types = nst_measure_types() 2 | % NST_MEASURE_TYPES return an enumeration of measure types (eg wavelength, Hb) 3 | % 4 | % MEASURE_TYPES = NST_MEASURE_TYPES() 5 | % MEASURE_TYPES: struct with numerical fields listing all available 6 | % channel types: 7 | % - MEASURE_TYPES.WAVELENGTH 8 | % - MEASURE_TYPES.Hb 9 | 10 | measure_types.WAVELENGTH = 1; 11 | measure_types.HB = 2; 12 | end -------------------------------------------------------------------------------- /bst_plugin/misc/nst_misc_unpack_glm_result.m: -------------------------------------------------------------------------------- 1 | function results = nst_misc_unpack_glm_result(results, model,method_name,mask) 2 | 3 | 4 | results.B(:,mask)=model.B; 5 | if strcmp(method_name,'OLS_prewhitening') 6 | results.covB(:,:,mask)=model.covB; 7 | else 8 | results.covB(:,:,mask)=repmat(model.covB,1,1,length(mask)); 9 | end 10 | 11 | results.residuals(:,mask)=model.residuals; 12 | results.mse_residuals(:,mask)=model.mse_residuals; 13 | results.dfe(mask) = model.dfe; 14 | 15 | end 16 | 17 | -------------------------------------------------------------------------------- /test/utest_reset_bst.m: -------------------------------------------------------------------------------- 1 | function utest_reset_bst() 2 | % Remove unit test protocol from brainstorm DB 3 | % Reset stored messages. 4 | 5 | global GlobalData; 6 | 7 | % Delete unit test protocol 8 | ProtocolName = 'nst_utest'; 9 | gui_brainstorm('DeleteProtocol', ProtocolName); 10 | db_dir = bst_get('BrainstormDbDir'); 11 | nst_protocol_dir = fullfile(db_dir, ProtocolName); 12 | if exist(nst_protocol_dir, 'dir') 13 | rmdir(nst_protocol_dir, 's'); 14 | end 15 | 16 | % Flush latest error message 17 | GlobalData.lastestFullErrMsg = ''; 18 | GlobalData.lastestConsoleMsg = ''; 19 | end 20 | -------------------------------------------------------------------------------- /bst_plugin/io/nst_parse_bst_item_name.m: -------------------------------------------------------------------------------- 1 | function item = nst_parse_bst_item_name(item_name) 2 | 3 | %TODO: handle error scenarios 4 | 5 | item.comment = ''; 6 | item.subject_name = ''; 7 | item.condition = ''; 8 | 9 | split_re = '(?.*/)?(?[^/]+)'; 10 | toks = regexp(item_name, split_re, 'names'); 11 | 12 | if ~isempty(toks) 13 | item.comment = toks.item; 14 | if ~isempty(toks.root) 15 | toks = regexp(toks.root(1:end-1), split_re, 'names'); 16 | item.subject_name = toks.root(1:end-1); 17 | item.condition = toks.item; 18 | end 19 | end 20 | 21 | end -------------------------------------------------------------------------------- /test/bst_create_test_protocol.m: -------------------------------------------------------------------------------- 1 | function bst_create_test_protocol(use_default_anatomy) 2 | 3 | if nargin < 1 4 | use_default_anatomy = 0; 5 | end 6 | 7 | %% Ensure that nst_utest protocol exists 8 | ProtocolName = 'nst_utest'; 9 | % Delete existing protocol 10 | db_dir = bst_get('BrainstormDbDir'); 11 | gui_brainstorm('DeleteProtocol', ProtocolName); 12 | 13 | nst_protocol_dir = fullfile(db_dir, ProtocolName); 14 | if exist(nst_protocol_dir, 'dir') 15 | rmdir(nst_protocol_dir, 's'); 16 | end 17 | 18 | % Create new protocol 19 | gui_brainstorm('CreateProtocol', ProtocolName, use_default_anatomy, 0); 20 | 21 | end -------------------------------------------------------------------------------- /test/utest_clean_bst.m: -------------------------------------------------------------------------------- 1 | function utest_clean_bst() 2 | % Clean brainstorm from anything done during unit test 3 | global GlobalData; 4 | GlobalData.Program.isServer = 0; 5 | GlobalData.Program.HandleExceptionWithBst = 1; 6 | 7 | % Delete unit test protocol 8 | ProtocolName = 'nst_utest'; 9 | gui_brainstorm('DeleteProtocol', ProtocolName); 10 | db_dir = bst_get('BrainstormDbDir'); 11 | nst_protocol_dir = fullfile(db_dir, ProtocolName); 12 | if exist(nst_protocol_dir, 'dir') 13 | rmdir(nst_protocol_dir, 's'); 14 | end 15 | 16 | % Flush latest error message 17 | GlobalData.lastestFullErrMsg = ''; 18 | GlobalData.lastestConsoleMsg = ''; 19 | end 20 | -------------------------------------------------------------------------------- /bst_plugin/misc/nst_misc_convert_to_mumol.m: -------------------------------------------------------------------------------- 1 | function Y = nst_misc_convert_to_mumol(Y,DisplayUnits) 2 | %NST_MISC_CONVERT_TO_MUMOL Convert Y from DisplayUnits to mumol.l-1 3 | if strcmp(DisplayUnits, 'mol.l-1') 4 | Y = Y * 1e6; 5 | elseif strcmp(DisplayUnits, 'mmol.l-1') 6 | Y = Y * 1e3; 7 | elseif strcmp(DisplayUnits, 'mumol.l-1') || strcmp(DisplayUnits, '\mumol.l-1') 8 | Y = Y * 1; 9 | else 10 | if ~isempty(DisplayUnits) 11 | warning('Cannot interpret data unit: %s.',DisplayUnits); 12 | else 13 | warning('Unspecified data unit.'); 14 | end 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /bst_plugin/io/nst_save_figure.m: -------------------------------------------------------------------------------- 1 | function nst_save_figure(fig_fn, options, hfig) 2 | 3 | if nargin < 3 4 | hfig = gcf; 5 | end 6 | 7 | switch options.save_fig_method 8 | case 'export_fig' 9 | if ~isfield(options, 'export_fig_dpi') 10 | options.export_fig_dpi = 90; 11 | end 12 | export_fig(fig_fn, '-transparent', sprintf('-r%d', options.export_fig_dpi), hfig); 13 | case 'saveas' 14 | saveas(hfig, fig_fn); %TODO: handle hfig not being a figure but Axes -> for colorbar saving (works with export_fig) 15 | otherwise 16 | error('Unknown figure saving method: %s', options.save_fig_method); 17 | end 18 | 19 | end -------------------------------------------------------------------------------- /bst_plugin/io/nst_download.m: -------------------------------------------------------------------------------- 1 | function success = nst_download(source_fn, dest_fn, download_msg, bst_interactive) 2 | 3 | if nargin < 3 4 | download_msg = sprintf('Downloading "%s" to "%s"...\n', source_fn, dest_fn); 5 | end 6 | 7 | fprintf('%s -- ', download_msg) 8 | 9 | 10 | success = 1; 11 | tstart = tic(); 12 | errMsg = bst_websave(dest_fn, source_fn); 13 | tend = toc(tstart); 14 | 15 | % Error message 16 | if ~isempty(errMsg) 17 | errMsg = ['Download data failed from:' 10 source_fn]; 18 | bst_error(errMsg); 19 | success = 0; 20 | end 21 | 22 | fprintf('done in %1.1f sec.\n', tend); 23 | end 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # Compiled MEX binaries (all platforms) 4 | *.mex* 5 | 6 | # Simulink Code Generation 7 | slprj/ 8 | 9 | # Windows default autosave extension 10 | *.asv 11 | 12 | # Session info 13 | octave-workspace 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Packages 19 | *.egg 20 | *.egg-info 21 | dist 22 | build 23 | eggs 24 | parts 25 | var 26 | sdist 27 | develop-eggs 28 | .installed.cfg 29 | lib 30 | lib64 31 | __pycache__ 32 | 33 | # Installer logs 34 | pip-log.txt 35 | 36 | # Unit test / coverage reports 37 | .coverage 38 | .tox 39 | nosetests.xml 40 | .coveralls.yml 41 | 42 | # Translations 43 | *.mo 44 | 45 | # Mr Developer 46 | .mr.developer.cfg 47 | .project 48 | .pydevproject 49 | 50 | # Temporary file and backup of editors 51 | *~ 52 | *.bak 53 | *.swp 54 | \#* 55 | .\#* 56 | 57 | # configuration files 58 | .ackrc 59 | -------------------------------------------------------------------------------- /bst_plugin/misc/nst_misc_FOV_to_cortex.m: -------------------------------------------------------------------------------- 1 | function sResults = nst_misc_FOV_to_cortex(sResults, nVertex, valid_vertex, isSaveFactor) 2 | 3 | 4 | mapping = zeros(nVertex, length(valid_vertex)); 5 | for iNode = 1:length(valid_vertex) 6 | mapping(valid_vertex(iNode), iNode) = 1; 7 | end 8 | mapping = sparse(mapping); 9 | 10 | 11 | for iMap = 1:length(sResults) 12 | if iscell(sResults(iMap).ImageGridAmp) 13 | sResults(iMap).ImageGridAmp = [ {mapping} sResults(iMap).ImageGridAmp ]; 14 | else 15 | if isSaveFactor 16 | sResults(iMap).ImageGridAmp = {mapping , sResults(iMap).ImageGridAmp}; 17 | else 18 | sResults(iMap).ImageGridAmp = mapping * sResults(iMap).ImageGridAmp; 19 | end 20 | end 21 | end 22 | 23 | 24 | end -------------------------------------------------------------------------------- /bst_plugin/forward/nst_headmodel_get_gains.m: -------------------------------------------------------------------------------- 1 | function gains = nst_headmodel_get_gains(HeadModel, unused_iWL, sChannel, selected_channels) 2 | 3 | warning('This function is depracted. For more information, consult https://github.com/Nirstorm/nirstorm/pull/266') 4 | 5 | % Old head model 6 | if ndims(HeadModel.Gain) == 3 7 | HeadModel = process_nst_import_head_model('convert_head_model', sChannel, HeadModel, 0); 8 | else 9 | % New Head model. Check that the orientation is applied, or apply 10 | % it 11 | 12 | if size(HeadModel.Gain,2) == 3 * length(HeadModel.GridOrient) 13 | % Apply the fixed orientation to the Gain matrix (normal to the cortex) 14 | HeadModel.Gain = bst_gain_orient(HeadModel.Gain, HeadModel.GridOrient); 15 | end 16 | end 17 | 18 | gains = HeadModel.Gain(selected_channels, :); 19 | 20 | end -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_glm_fit_B.m: -------------------------------------------------------------------------------- 1 | function [B,proj_X] = nst_glm_fit_B(model,y, method) 2 | % nst_glm_model_fit_B - fit the model to compute B using either SVD or the 3 | % pseudo-inverse 4 | if nargin < 3 5 | method='SVD'; 6 | end 7 | 8 | X=model.X; 9 | switch method 10 | case 'SVD' 11 | [X_u, X_s, X_v] = svd(X,0); 12 | X_diag_s = diag(X_s); 13 | X_rank_tol = max(size(X)) * max(abs(X_diag_s)) * eps; 14 | X_rank = sum(X_diag_s > X_rank_tol); 15 | 16 | % Compute projector based on X to solve Y = X*B 17 | proj_X = X_v(:,1:X_rank) * diag(1./X_diag_s(1:X_rank)) * X_u(:,1:X_rank)'; 18 | 19 | % Fit to data 20 | B = proj_X * y; 21 | case 'pinv' 22 | proj_X= (X'*X)\X'; % compute inv(X'X)*X' 23 | B=proj_X*y; 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /test/utest_bst_setup.m: -------------------------------------------------------------------------------- 1 | function utest_bst_setup(gui, server_mode) 2 | % Run brainstorm with no user interface, if not already running. 3 | % Force brainstorm to run in server mode (no user interaction). 4 | % Activate exception bypassing. It requires to override bst_error, bst_call 5 | % and bst_process with the ones from nirstorm 6 | % -> use nst_install('link', 'debug') or nst_install('copy', 'debug') 7 | % 8 | global GlobalData; 9 | if nargin < 1 10 | gui = 0; 11 | end 12 | 13 | if nargin < 2 14 | server_mode = 1; 15 | end 16 | 17 | if ~brainstorm('status') 18 | if ~gui 19 | brainstorm nogui; 20 | else 21 | brainstorm; 22 | end 23 | end 24 | GlobalData.Program.isServer = server_mode; 25 | GlobalData.Program.HandleExceptionWithBst = 0; 26 | GlobalData.lastestFullErrMsg = ''; 27 | GlobalData.lastestConsoleMsg = ''; 28 | panel_process_select('ParseProcessFolder', 1); 29 | end 30 | -------------------------------------------------------------------------------- /test/ScriptTest.m: -------------------------------------------------------------------------------- 1 | classdef ScriptTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function create_tmp_dir(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | end 13 | end 14 | 15 | methods(TestMethodTeardown) 16 | function closeFigure(testCase) 17 | rmdir(testCase.tmp_dir, 's'); 18 | end 19 | end 20 | 21 | methods(Test) 22 | function test_tutorial_tapping(testCase) 23 | addpath('../scripts'); 24 | zip_fn = nst_request_files({{'tutorials', 'sample_nirs.zip'}}, ... 25 | 1, 'ftp://neuroimage.usc.edu/pub/'); 26 | assert(~isempty(zip_fn)); 27 | unzip(zip_fn{1}, testCase.tmp_dir); 28 | nst_tutorial_tapping(testCase.tmp_dir, testCase.tmp_dir); 29 | end 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /test/bst_create_test_subject.m: -------------------------------------------------------------------------------- 1 | function [subject_name, sSubject, iSubject] = bst_create_test_subject(template_name, use_default_anatomy) 2 | 3 | if nargin < 1 4 | template_name = 'Colin27_4NIRS_lowres'; 5 | end 6 | 7 | if nargin < 2 8 | use_default_anatomy = 0; 9 | end 10 | 11 | bst_create_test_protocol(use_default_anatomy); 12 | 13 | % Add test subject 14 | subject_name = 'test_subject'; 15 | [sSubject, iSubject] = db_add_subject(subject_name); 16 | 17 | if ~isempty(template_name) 18 | % Set anatomy to template Colin27 4NIRS - low res version 19 | sTemplates = bst_get('AnatomyDefaults'); 20 | iTemplate = strcmpi(template_name, {sTemplates.Name}); 21 | if ~any(iTemplate) 22 | error('Template %s not found', template_name); 23 | end 24 | db_set_template(iSubject, sTemplates(iTemplate), 0); 25 | db_save(); 26 | end 27 | % Set default cortical surface to low resolution mid 28 | sSubject = bst_get('Subject', subject_name); 29 | db_surface_default( iSubject, 'Cortex', find(strcmp({sSubject.Surface.Comment}, 'mid_lowres'))); 30 | panel_protocols('RepaintTree'); 31 | end 32 | -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_make_event_regressors.m: -------------------------------------------------------------------------------- 1 | function events_mat = nst_make_event_regressors(events, filter, time) 2 | % NST_MAKE_EVENT_REGRESSORS build event design matrix from given events and filter 3 | % 4 | % EVENTS_MAT = NST_MAKE_EVENT_REGRESSORS(EVENTS, FILTER, TIME 5 | % 6 | % EVENTS (struct array): 7 | % events as brainstorm structure (see DB_TEMPLATE('event')) 8 | % FILTER (1D array of double): column vector 9 | % filter for convolution 10 | % TIME (array of float): reference time axis 11 | % 12 | % Output: 13 | % 14 | % EVENTS_MAT (matrix of double): 15 | % Event-induced design matrix built by convolving the events with 16 | % the given filter. 17 | % Size is nb_samples x nb_event_groups 18 | % 19 | % See also DB_TEMPLATE('event'), NST_MAKE_EVENT_TOEPLITZ_MTX 20 | 21 | assert(size(filter, 2) == 1); % column vector 22 | 23 | events_tpz = nst_make_event_toeplitz_mtx(events, time, size(filter, 1)); 24 | regressors = cellfun(@(m) m*filter, events_tpz, 'UniformOutput', false); 25 | events_mat = horzcat(regressors{:}); 26 | end -------------------------------------------------------------------------------- /bst_plugin/forward/nst_headmodel_get_FOV.m: -------------------------------------------------------------------------------- 1 | function [valid_nodes,dis2cortex] = nst_headmodel_get_FOV(ChannelMat, cortex, thresh_dis2cortex, ChannelFlag) 2 | %% define the reconstruction FOV 3 | 4 | if nargin < 4 5 | ChannelFlag = ones(1, length(ChannelMat.Channel)); 6 | end 7 | 8 | montage_info = nst_montage_info_from_bst_channels(ChannelMat.Channel, ChannelFlag); 9 | src_locs = montage_info.src_pos; 10 | det_locs = montage_info.det_pos; 11 | 12 | optodes_pos = [src_locs;det_locs]; 13 | % inflate surface 100% to calculate distances to optodes (see BST folder figure_3d.m line 2595) 14 | iVertices = 1:length(cortex.Vertices); 15 | % Smoothing factor 16 | SurfSmoothIterations = ceil(300 * 1 * length(iVertices) / 100000); 17 | % Calculate smoothed vertices locations 18 | Vertices_sm = cortex.Vertices; 19 | Vertices_sm(iVertices,:) = tess_smooth(cortex.Vertices(iVertices,:), 1, SurfSmoothIterations, cortex.VertConn(iVertices,iVertices), 1); 20 | 21 | dis2cortex = nst_pdist(Vertices_sm,optodes_pos); 22 | dis2cortex = min(dis2cortex,[],2); 23 | 24 | valid_nodes = find(dis2cortex < thresh_dis2cortex); 25 | end -------------------------------------------------------------------------------- /bst_plugin/forward/nst_get_tissues_optical_properties.m: -------------------------------------------------------------------------------- 1 | function prop = nst_get_tissues_optical_properties(tissues,wavelength) 2 | 3 | 4 | txt = fileread( fullfile(fileparts(which('nst_get_tissues_optical_properties')), 'tissues_property.json')); 5 | data = jsondecode(txt); 6 | 7 | 8 | % [mua, mus, g, n] 9 | prop = nan(size(tissues,1), 4 ); 10 | 11 | for iTissue = 1:size(tissues,1) 12 | 13 | tissues_name = tissues{iTissue,2}; 14 | tissue_idx = tissues{iTissue,1}; 15 | 16 | if ~isfield(data,tissues_name) 17 | error('Unknown tissue type %s', tissues_name); 18 | end 19 | 20 | data_tissue = data.(tissues_name); 21 | iWavelength = find(data_tissue.wavelength == wavelength); 22 | if isempty(iWavelength) 23 | error('No optical property for wavelength %d for tissue type %s', wavelength, tissues_name); 24 | end 25 | 26 | prop(1+tissue_idx, 1) = data_tissue.mua(iWavelength); 27 | prop(1+tissue_idx, 2) = data_tissue.mus(iWavelength); 28 | prop(1+tissue_idx, 3) = data_tissue.g(iWavelength); 29 | prop(1+tissue_idx, 4) = data_tissue.n(iWavelength); 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /test/utest_get_test_bst_events.m: -------------------------------------------------------------------------------- 1 | function bst_events = utest_get_test_bst_events() 2 | % Return a BST event struct with corner cases: 3 | % - multiple conditions 4 | % - some have empty notes / channels tags 5 | % - some have non-empty notes with weird chars 6 | % - some selected, some not 7 | % 8 | % Maintain this function everytime bst event structure changes 9 | % -> to add new fields use expected default values so that backward compatibility 10 | % is guarenteed. test_load_save_backward_compatibility should yell at 11 | % you if not. 12 | bst_events = db_template('event'); 13 | bst_events(1).label = 'condition1'; 14 | bst_events(1).color = [0., 0., 0.]; 15 | bst_events(1).epochs = [1]; 16 | bst_events(1).times = [10.3;10.9]; 17 | bst_events(1).reactTimes = []; 18 | bst_events(1).select = 1; 19 | bst_events(1).channels = {{}}; 20 | bst_events(1).notes = {''}; 21 | 22 | bst_events(2).label = 'condition2'; 23 | bst_events(2).color = [0., 1., 0.3]; 24 | bst_events(2).epochs = [1 1 1]; 25 | bst_events(2).times = [1.3 7.5 12.6]; 26 | bst_events(2).reactTimes = []; 27 | bst_events(2).select = 0; 28 | bst_events(2).channels = {{'S1D1', 'S2D3'}, {}, {'S121', 'S4D3'}}; 29 | bst_events(2).notes = {sprintf('\t!:/\\,#%%*+{}[]wazaéàè?`''&$'), ... 30 | '', '12345678910-=;,1,2||'}; 31 | 32 | end -------------------------------------------------------------------------------- /bst_plugin/math/dg_voronoi.m: -------------------------------------------------------------------------------- 1 | function [vor, dist] = dg_voronoi(img, voxsize, seeds, distance, aniso) 2 | % Geodesic Discrete Voronoi Diagram 3 | % FORMAT [vor dist] = dg_voronoi(img, voxsize, seeds, distance, aniso) 4 | % 5 | % img - binary image (2D or 3D) with : > 0 : inside 6 | % <= 0 : outside 7 | % voxsize - size of the voxels in each dimension 8 | % (default is [1.0 1.0 1.0]) 9 | % seeds - n x 3 array with the positions of the n seeds 10 | % distance - type of distance to use 11 | % (default is 'd34') 12 | % aniso - anisotropic map 13 | % 14 | % vor - Geodesic Discrete Voronoi diagram 15 | % (label is equal to the position of the seed in 'seeds') 16 | % dist - Geodesic Distance map of img with seeds as objects 17 | %_______________________________________________________________________ 18 | % 19 | % Compute the geodesic discrete Voronoi Diagram of an image of labelled 20 | % objects using front propagation). The distance map is also available 21 | % on output. 22 | % 23 | % This is a cmex-compiled algorithm (dg_voronoi.c) 24 | %_______________________________________________________________________ 25 | % @(#)dg_voronoi.m Guillaume Flandin 01/10/11 26 | 27 | error(sprintf('Missing MEX-file: %s', mfilename)); 28 | -------------------------------------------------------------------------------- /bst_plugin/core/nst_format_channel.m: -------------------------------------------------------------------------------- 1 | function [channel_label, measure] = nst_format_channel(isrc, idet, measure) 2 | % NST_FORMAT_CHANNEL make channel label from source, dectector and measure information. 3 | % 4 | % CHANNEL_LABEL = NST_FORMAT_CHANNEL(ISRC, IDET, MEAS) 5 | % 6 | % ISRC (int >= 0): source index 7 | % IDET (int >= 0): extracted detector index 8 | % MEAS (int | str): measure value. 9 | % Either wavelength (int) or Hb type (str) 10 | % -> 'HbO', 'HbR', 'HbT' 11 | % 12 | % CHANNEL_LABEL (str): 13 | % formatted as 'SxDyWLz' or 'SxDyHbt', where: 14 | % x: source index 15 | % y: detector index 16 | % z: wavelength 17 | % t: Hb type (O, R, T). 18 | % Examples: S1D2WL685, S3D01HbR 19 | % 20 | % See also NST_UNFORMAT_CHANNEL 21 | 22 | % stub: 23 | assert(isrc >= 0); 24 | assert(idet >= 0); 25 | 26 | if nargin >= 3 27 | assert(isnumeric(measure) || (ischar(measure) && ... 28 | ismember(measure, {'HbO', 'HbR', 'HbT'}))); 29 | 30 | if isnumeric(measure) 31 | assert(measure >= 0); 32 | assert(round(measure) == measure); 33 | measure = sprintf('WL%d', measure); 34 | end 35 | else 36 | measure = ''; 37 | end 38 | 39 | channel_label = sprintf('S%dD%d%s', isrc, idet, measure); 40 | end -------------------------------------------------------------------------------- /test/ArFitTest.m: -------------------------------------------------------------------------------- 1 | classdef ArFitTest < matlab.unittest.TestCase 2 | 3 | properties 4 | sigma 5 | N 6 | end 7 | 8 | methods(TestMethodSetup) 9 | function setup(testCase) 10 | testCase.sigma = sqrt(0.2); 11 | testCase.N = 1024; 12 | end 13 | end 14 | 15 | methods(Test) 16 | 17 | function test_AR1(testCase) 18 | 19 | ar_coeffs=linspace(0.02,0.99,20); 20 | 21 | for coeff=ar_coeffs 22 | A=[1 coeff]; 23 | x= testCase.sigma*randn(1,testCase.N); 24 | y = filter(1,A,x); 25 | 26 | W=nst_math_fit_AR(y,1); 27 | assert( all( abs(W-A) < 0.1)) 28 | end 29 | 30 | end 31 | 32 | function test_ARP(testCase) 33 | 34 | for order = 1:7 35 | for ntrials=1:100 36 | l=cumsum(rand(1,order+1)) ; 37 | l=l/l(end); 38 | 39 | A= l(end:-1:1); 40 | 41 | x= testCase.sigma*randn(1,testCase.N*5); 42 | y = filter(1,A,x); 43 | 44 | W=nst_math_fit_AR(y,order); 45 | assert( all( abs(W-A) < 0.1),'Failed to estimate order %s',num2str(order)) 46 | end 47 | end 48 | 49 | end 50 | 51 | end 52 | 53 | end -------------------------------------------------------------------------------- /scripts/process_memo.m: -------------------------------------------------------------------------------- 1 | function varargout = process_memo( varargin ) 2 | eval(macro_method); 3 | end 4 | 5 | 6 | function sProcess = GetDescription() %#ok 7 | 8 | sProcess.options.text.Comment = 'Text: '; 9 | sProcess.options.text.Type = 'text'; 10 | sProcess.options.text.Value = 'the text'; 11 | 12 | sProcess.options.flag.Comment = 'Check box'; 13 | sProcess.options.flag.Type = 'checkbox'; 14 | sProcess.options.flag.Value = 0; 15 | 16 | sProcess.options.float.Comment = 'Value with unit'; 17 | sProcess.options.float.Type = 'value'; 18 | sProcess.options.float.Value = {50, 'unit', 0}; % last if nb of subunit digits, use 0 for integer 19 | 20 | sProcess.options.choice.Comment = 'Choice'; 21 | sProcess.options.choice.Type = 'combobox'; 22 | choices = choice_enum(); 23 | sProcess.options.choice.Value = {choices.Choice1,... 24 | fieldnames(choices)}; 25 | 26 | end 27 | 28 | %% ===== RUN ===== 29 | function OutputFile = Run(sProcess, sInputs) %#ok 30 | OutputFile = {}; 31 | 32 | %% Retrieve options 33 | fag = sProcess.options.flag.Value; 34 | 35 | float_val = sProcess.options.float.Value{1}; 36 | 37 | choices = choice_enum(); 38 | choice_int = sProcess.options.choice.Value{1}; 39 | choice_str = sProcess.options.choice.Value{2}{sProcess.options.choice.Value{1}}; 40 | 41 | switch choice_int 42 | case choices.Choice1 43 | case choices.Choice2 44 | otherwise 45 | error('Invalid choice value'); 46 | end 47 | end 48 | 49 | function enum = choice_enum() 50 | enum.Choice1 = 1; 51 | enum.Choice2 = 2; 52 | end 53 | -------------------------------------------------------------------------------- /test/bst_create_scout.m: -------------------------------------------------------------------------------- 1 | function new_scout_sel = bst_create_scout(subject_name, surface_type, scout_name, scout_seed, scout_size, atlas_name) 2 | assert(isvarname(scout_name)); 3 | 4 | if strcmp(surface_type, 'scalp') 5 | s_isurf = 'iScalp'; 6 | elseif strcmp(surface_type, 'cortex') 7 | s_isurf = 'iCortex'; 8 | else 9 | disp('ERROR: surf_type must either be "scalp" or "cortex"'); 10 | bst_error('surf_type must either be "scalp" or "cortex"'); 11 | end 12 | 13 | if nargin < 6 14 | atlas_name = 'User scouts'; 15 | end 16 | 17 | [sSubject, iSubject] = bst_get('Subject', subject_name); 18 | 19 | sSurf = in_tess_bst(sSubject.Surface(sSubject.(s_isurf)).FileName); 20 | iatlas = strcmp(atlas_name, {sSurf.Atlas.Name}); 21 | if isempty(iatlas) 22 | error(['Atlas not found:' atlas_name]); 23 | end 24 | 25 | assert(scout_seed <= size(sSurf.Vertices, 1)); 26 | scout_vertices = [scout_seed]; 27 | for isize=1:scout_size 28 | cur_vertices = scout_vertices; 29 | for ivertex=1:length(cur_vertices) 30 | vidx = cur_vertices(ivertex); 31 | scout_vertices = unique([scout_vertices find(sSurf.VertConn(vidx, :))]); 32 | end 33 | end 34 | scout_idx = size(sSurf.Atlas(iatlas).Scouts,2) + 1; 35 | new_scout = db_template('Scout'); 36 | new_scout.Vertices = scout_vertices; 37 | new_scout.Seed = scout_seed; 38 | new_scout.Color = [0,0,0]; 39 | new_scout.Label = scout_name; 40 | sSurf.Atlas(iatlas).Scouts(scout_idx) = new_scout; 41 | bst_save(file_fullpath(sSubject.Surface(sSubject.(s_isurf)).FileName), sSurf, 'v7'); 42 | db_save(); 43 | 44 | new_scout_sel.sSubject = bst_get('Subject', subject_name); 45 | new_scout_sel.isurface = sSubject.(s_isurf); 46 | new_scout_sel.iatlas = iatlas; 47 | new_scout_sel.iscout = scout_idx; 48 | new_scout_sel.sScout = new_scout; 49 | end 50 | -------------------------------------------------------------------------------- /bst_plugin/forward/tissues_property.json: -------------------------------------------------------------------------------- 1 | { 2 | "Background":{ 3 | "wavelength": [685, 690, 830 ], 4 | "mua": [ 0, 0, 0], 5 | "mus": [ 0, 0, 0], 6 | "g": [ 1, 1, 1], 7 | "n": [ 1, 1, 1] 8 | }, 9 | "Scalp": { 10 | "wavelength": [685, 690, 830 ], 11 | "mua": [ 0.0159, 0.0159, 0.0191 ], 12 | "mus": [ 10, 10, 8.25 ], 13 | "g": [ 0.92, 0.92, 0.92 ], 14 | "n": [ 1.37, 1.37, 1.37 ] 15 | }, 16 | "Skull": { 17 | "wavelength": [ 685, 690, 830 ], 18 | "mua": [ 0.0101, 0.0101, 0.0136 ], 19 | "mus": [ 12.5, 12.5, 10.75 ], 20 | "g": [ 0.92, 0.92, 0.92 ], 21 | "n": [ 1.37, 1.37, 1.37 ] 22 | }, 23 | "CSF": { 24 | "wavelength": [ 685, 690, 830 ], 25 | "mua": [ 0.0004, 0.0004, 0.0026 ], 26 | "mus": [ 0.125, 0.125, 0.125 ], 27 | "g": [ 0.92, 0.92, 0.92 ], 28 | "n": [ 1.37, 1.37, 1.37 ] 29 | }, 30 | "Gray": { 31 | "wavelength": [ 685, 690, 830 ], 32 | "mua": [ 0.0178, 0.0178, 0.0186 ], 33 | "mus": [ 15.625, 15.625, 13.875 ], 34 | "g": [ 0.92, 0.92, 0.92 ], 35 | "n": [ 1.37, 1.37, 1.37 ] 36 | }, 37 | "White": { 38 | "wavelength": [ 685, 690, 830 ], 39 | "mua": [ 0.0178, 0.0178, 0.0186 ], 40 | "mus": [ 15.625, 15.625, 13.875 ], 41 | "g": [ 0.92, 0.92, 0.92 ], 42 | "n": [ 1.37, 1.37, 1.37 ] 43 | } 44 | } -------------------------------------------------------------------------------- /bst_plugin/core/nst_unformat_channel.m: -------------------------------------------------------------------------------- 1 | function [isrc, idet, measure, channel_type] = nst_unformat_channel(channel_label, warn_bad_channel) 2 | % NST_UNFORMAT_CHANNEL extract source, dectector and measure information from channel label. 3 | % 4 | % [ISRC, IDET, MEAS, CHAN_TYPE] = NST_UNFORMAT_CHANNEL(CHANNEL_LABEL) 5 | % CHANNEL_LABEL (str): 6 | % formatted as 'SxDyWLz' or 'SxDyHbt', where: 7 | % x: source index 8 | % y: detector index 9 | % z: wavelength 10 | % t: Hb type (O, R, T). 11 | % Examples: S1D2WL685, S01D7WL830, S3D01HbR 12 | % 13 | % ISRC (int): extracted source index 14 | % IDET (int): extracted detector index 15 | % MEAS (int | str): extracted measure value 16 | % CHAN_TYPE (int): extracted channel type. 17 | % 18 | % If channel cannot be unformatted then all returned values are nan 19 | % 20 | % See also NST_UNFORMAT_CHANNELS, NST_CHANNEL_TYPES, NST_FORMAT_CHANNEL 21 | assert(ischar(channel_label)); 22 | 23 | if nargin < 2 24 | warn_bad_channel = 0; 25 | end 26 | 27 | CHAN_RE = '^S([0-9]+)D([0-9]+)(WL\d+|HbO|HbR|HbT)$'; 28 | toks = regexp(channel_label, CHAN_RE, 'tokens'); 29 | if isempty(toks) 30 | if warn_bad_channel 31 | warning('NIRSTORM:MalformedChannelLabel', ... 32 | ['Malformed channel label:', channel_label, ... 33 | '. Should be SxDyWLz or SxDyHb(O|R|T)']); 34 | end 35 | isrc = nan; 36 | idet = nan; 37 | measure = nan; 38 | channel_type = nan; 39 | else 40 | isrc = str2double(toks{1}{1}); 41 | idet = str2double(toks{1}{2}); 42 | measure = toks{1}{3}; 43 | 44 | measure_types = nst_measure_types(); 45 | if ~isempty(strfind(measure, 'WL')) 46 | channel_type = measure_types.WAVELENGTH; 47 | measure = str2double(measure(3:end)); 48 | else 49 | channel_type = measure_types.HB; 50 | end 51 | end 52 | 53 | end -------------------------------------------------------------------------------- /bst_plugin/core/nst_bst_set_template_anatomy.m: -------------------------------------------------------------------------------- 1 | function nst_bst_set_template_anatomy(template_name, iSubject, confirm_download) 2 | %NST_BST_SET_TEMPLATE_ANATOMY Use given template to set the anatomy. 3 | % Set the anatomy of the given subject (default is 0) to the given 4 | % Nirstorm template, for the currently loaded brainstorm protocol. 5 | % 6 | % WARNING: It does not handle Brainstorm templates. To do that, use: 7 | % sTemplates = bst_get('AnatomyDefaults'); 8 | % iTemplate = find(strcmpi(template_name, {sTemplates.Name})); 9 | % db_set_template(iSubject, sTemplates(iTemplate), 0); 10 | % 11 | % TEMPLATE_NAME (str): name of the nirstorm template 12 | % See NST_CORE_GET_AVAILABLE_TEMPLATES for the description of 13 | % the available templates and their string identifiers 14 | % [ISUBJECT (int):] subject ID 15 | % Default is 0 (default subject) 16 | % [CONFIRM_DOWNLOAD (bool):] 17 | % Flag to ask the user to confirm download if the template is not 18 | % available locally. Default is 1. 19 | % 20 | 21 | if nargin < 2 22 | iSubject = 0; 23 | end 24 | 25 | if nargin < 3 26 | confirm_download = 1; 27 | end 28 | 29 | sTemplates = bst_get('AnatomyDefaults'); 30 | iTemplate = strcmpi(template_name, {sTemplates.Name}); 31 | if ~any(iTemplate) 32 | template_bfn = [template_name '.zip']; 33 | template_tmp_fn = nst_request_files({{'template', template_bfn}}, confirm_download, ... 34 | nst_get_repository_url(), 18e6); 35 | % Copy to .brainstorm/defaults/anatomy 36 | copyfile(template_tmp_fn{1}, fullfile(bst_get('BrainstormUserDir'), 'defaults', 'anatomy')); 37 | % Remove temporary file 38 | delete(template_tmp_fn{1}); 39 | end 40 | sTemplates = bst_get('AnatomyDefaults'); 41 | iTemplate = strcmpi(template_name, {sTemplates.Name}); 42 | db_set_template(iSubject, sTemplates(iTemplate), 0); 43 | db_save(); 44 | end 45 | 46 | -------------------------------------------------------------------------------- /bst_plugin/core/nst_get_pair_indexes_from_names.m: -------------------------------------------------------------------------------- 1 | function [pair_chan_indexes, pair_sd_ids] = nst_get_pair_indexes_from_names(channel_labels) 2 | % NST_GET_PAIR_INDEXES_FROM_NAMES groups channels by optode pair 3 | % 4 | % [PAIR_CHAN_INDEXES, PAIR_SD_IDS] = NST_GET_PAIR_INDEXES_FROM_NAMES(CHANNEL_LABELS) 5 | % CHANNEL_LABELS (cell array of str): 6 | % each str is formatted as 'SxDyWLz' or 'SxDyHbt', where: 7 | % x: source index 8 | % y: detector index 9 | % z: wavelength 10 | % t: Hb type (O, R, T). 11 | % Consistency is asserted so that: 12 | % - channels are unique 13 | % - channel type is homogeneous 14 | % Warning is issued if: 15 | % - the number of measures per pair is not homogeneous 16 | % eg one pair has only one wavelength 17 | % 18 | % Examples: S1D2WL685, S01D7WL830, S3D01HbR 19 | % 20 | % 21 | % PAIR_CHAN_INDEXES (2D array of int): channel indexes for each pair 22 | % size: (nb_pairs, nb_measures) 23 | % PAIR_SD_IDS (2D array of int): source and detector ids 24 | % size: (nb_pairs, 2) 25 | % 26 | % See also NST_UNFORMAT_CHANNELS 27 | 28 | [isrcs, idets, measures, channel_type] = nst_unformat_channels(channel_labels); 29 | 30 | pair_to_chans = containers.Map(); 31 | pair_to_sd = containers.Map(); 32 | 33 | for ichan=1:length(isrcs) 34 | src_id = isrcs(ichan); 35 | det_id = idets(ichan); 36 | pair_name = sprintf('S%dD%d', src_id, det_id); 37 | pair_to_sd(pair_name) = [src_id, det_id]; 38 | if pair_to_chans.isKey(pair_name) 39 | pair_to_chans(pair_name) = [pair_to_chans(pair_name) ichan]; 40 | else 41 | pair_to_chans(pair_name) = ichan; 42 | end 43 | end 44 | 45 | pair_chan_indexes = cell2mat(pair_to_chans.values'); 46 | pair_sd_ids = cell2mat(pair_to_sd.values'); 47 | end -------------------------------------------------------------------------------- /test/PrefixMatrixTest.m: -------------------------------------------------------------------------------- 1 | classdef PrefixMatrixTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_add_row_col_prefixes(testCase) 26 | 27 | ncols = 6; 28 | t1_col_names = arrayfun(@(n) sprintf('c%d', n), 1:ncols, 'UniformOutput', 0); 29 | t1_row_names = {'t1_r1', 't1_r2', 't1_r3'}; 30 | t1 = array2table(randi(100, 3, ncols), 'VariableNames', t1_col_names, ... 31 | 'RowNames', t1_row_names); 32 | 33 | subject_name = bst_create_test_subject(''); 34 | 35 | sFile_t1 = nst_save_table_in_bst(t1, subject_name, 'condition', 'table1'); 36 | 37 | sFile_rt1 = bst_process('CallProcess', 'process_nst_prefix_matrix', ... 38 | sFile_t1, [], ... 39 | 'row_prefixes', 'rp1_, rp2_, rp3_', 'col_prefixes', 'cp_'); 40 | prefixed_mat = in_bst_matrix(sFile_rt1.FileName); 41 | for icol=1:length(prefixed_mat.ColNames) 42 | testCase.assertMatches(prefixed_mat.ColNames{icol}, ['cp_' t1_col_names{icol}]); 43 | end 44 | testCase.assertMatches(prefixed_mat.RowNames{1}, ['rp1_' t1_row_names{1}]); 45 | testCase.assertMatches(prefixed_mat.RowNames{2}, ['rp2_' t1_row_names{2}]); 46 | testCase.assertMatches(prefixed_mat.RowNames{3}, ['rp3_' t1_row_names{3}]) 47 | end 48 | % TODO: test errors 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /bst_plugin/core/nst_get_bst_func_files.m: -------------------------------------------------------------------------------- 1 | function [file_names, file_data_types] = nst_get_bst_func_files(subject_name, condition_name, item_name, data_types, protocol_name, sStudy) 2 | 3 | file_names = {}; 4 | file_data_types = {}; 5 | 6 | if nargin < 4 7 | data_types = nst_get_bst_data_fields(); 8 | else 9 | if ischar(data_types) 10 | data_types = {data_types}; 11 | end 12 | end 13 | 14 | if nargin >= 5 && ~isempty(protocol_name) 15 | gui_brainstorm('SetCurrentProtocol', bst_get('Protocol', protocol_name)); 16 | end 17 | 18 | if nargin < 6 19 | sSubject = bst_get('Subject', subject_name, 1); 20 | if isempty(sSubject) 21 | return; 22 | end 23 | sStudy = bst_get('StudyWithSubject', sSubject.FileName); 24 | end 25 | 26 | for i_study=1:length(sStudy) 27 | for i_cond=1:length(sStudy(i_study).Condition) 28 | if strcmp(sStudy(i_study).Condition{i_cond}, condition_name) 29 | for i_data_type=1:length(data_types) 30 | data_type = data_types{i_data_type}; 31 | for i_data=1:length(sStudy(i_study).(data_type)) 32 | if strcmp(sStudy(i_study).(data_type)(i_data).Comment, item_name) 33 | file_names{end+1} = sStudy(i_study).(data_type)(i_data).FileName; 34 | file_data_types{end+1} = data_type; 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | 42 | % if isempty(iData) 43 | % disp(['Cannot find data "' item_name ... 44 | % '" for subject "' acq_tag '" and condition "' condition_name '"']); 45 | % return 46 | % end 47 | % if ~isempty(iData) 48 | % sFile = bst_process('GetInputStruct', sStudy(iStudy).(data_type)(iData).FileName); 49 | % end 50 | 51 | if length(file_names) == 1 52 | file_names = file_names{1}; 53 | file_data_types = file_data_types{1}; 54 | end 55 | 56 | end 57 | 58 | 59 | function file_types = nst_get_bst_data_fields() 60 | 61 | file_types = {'HeadModel', 'Channel', 'Data', 'Stat', 'Image', 'Matrix', 'Result', 'Timefreq'}; 62 | 63 | end -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_make_event_toeplitz_mtx.m: -------------------------------------------------------------------------------- 1 | function events_tpz = nst_make_event_toeplitz_mtx(events, time_ref, nb_coeffs) 2 | % NST_MAKE_EVENT_TOEPLITZ_MTX build binary toeplitz matrices from given events, 3 | % ready for convolution. 4 | % 5 | % EVENTS_TPZ = NST_MAKE_EVENT_TOEPLITZ_MTX(EVENTS, DT, NB_SAMPLES, NB_COEFFS) 6 | % 7 | % EVENTS (struct array): 8 | % events as brainstorm structure (see DB_TEMPLATE('event')) 9 | % TIME_REF (array of float): reference temporal axis. 10 | % NB_COEFFS (int): number of filter coefficients 11 | % 12 | % EVENTS_TPZ (cell array of matrix of double): 13 | % Each matrix in the returned cell array is a binary toeplitz matrix 14 | % built from one event group, with size nb_samples x nb_coeffs 15 | % 16 | % See also DB_TEMPLATE('event'), NST_MAKE_EVENT_REGRESSORS 17 | 18 | assert(isscalar(nb_coeffs)); 19 | assert(round(nb_coeffs) == nb_coeffs); 20 | assert(isnumeric(time_ref)); 21 | nb_samples = length(time_ref); 22 | 23 | events_tpz = cell(1, length(events)); 24 | for icondition=1:length(events) 25 | binary_seq = zeros(nb_samples, 1); 26 | for ievent=1:size(events(icondition).times, 2) 27 | i_smpl = time_to_sample_idx(events(icondition).times(:, ievent), time_ref); 28 | if length(i_smpl) == 2 29 | binary_seq(i_smpl(1):i_smpl(2)) = 1; 30 | else 31 | binary_seq(i_smpl(1)) = 1; 32 | end 33 | end 34 | events_tpz{icondition} = toeplitz(binary_seq, [binary_seq(1) zeros(1, nb_coeffs-1)]); 35 | if size(events_tpz{icondition}, 1) > nb_samples 36 | warning('Truncate event regressor for %s, from %d to nb_samples=%d', ... 37 | events(icondition).label, size(events_tpz{icondition}, 1), nb_samples); 38 | events_tpz{icondition} = events_tpz{icondition}(1:nb_samples, :); 39 | end 40 | end 41 | end 42 | 43 | function samples = time_to_sample_idx(time, ref_time) 44 | if nargin < 2 45 | assert(all(diff(diff(time))==0)); 46 | ref_time = time; 47 | end 48 | samples = round((time - ref_time(1)) / diff(ref_time(1:2))) + 1; 49 | end 50 | -------------------------------------------------------------------------------- /bst_plugin/io/nst_save_table_in_bst.m: -------------------------------------------------------------------------------- 1 | function sFile = nst_save_table_in_bst(t, subject_name, condition, comment, extra, displayUnits) 2 | % Save a table as a matrix brainstorm item: 3 | % Field Std is not used 4 | % Fields Time and Description are not used because their interpretation by 5 | % brainstorm is not straightfoward. 6 | % Description sometimes refer to row names and sometimes to column names 7 | % depending on the matrix dimension and if Time is available. 8 | % Here new fields RowNames and ColNames are added to store them. 9 | % 10 | % 11 | 12 | if nargin < 5 13 | extra = struct(); 14 | end 15 | 16 | if nargin < 6 17 | displayUnits = ''; 18 | end 19 | 20 | MatNew = db_template('matrix'); 21 | MatNew.Value = table2array(t); 22 | nrows = size(MatNew.Value, 1); 23 | MatNew.Std = []; 24 | MatNew.Time = []; 25 | MatNew.Description = {}; 26 | MatNew.DisplayUnits = displayUnits; 27 | MatNew.Comment = comment; 28 | 29 | if ~isempty(t.Row) 30 | MatNew.RowNames = t.Row; 31 | else 32 | MatNew.RowNames = arrayfun(@(n) sprintf('row_%d', n), 1:nrows, 'UniformOutput', 0); 33 | end 34 | MatNew.RowNames = line_vector(MatNew.RowNames); 35 | MatNew.Time = 1:length(MatNew.RowNames); 36 | MatNew.ColNames = line_vector(t.Properties.VariableNames); 37 | MatNew.Description = MatNew.ColNames; 38 | 39 | sSubject = bst_get('Subject', subject_name, 1); 40 | if isempty(sSubject) 41 | db_add_subject(subject_name, []); 42 | end 43 | [sStudy, iStudy] = bst_get('StudyWithCondition', [subject_name '/' condition]); 44 | if isempty(sStudy) 45 | iStudy = db_add_condition(subject_name, condition); 46 | sStudy = bst_get('Study', iStudy); 47 | end 48 | 49 | % Add extra fields 50 | extra_fields = fieldnames(extra); 51 | for ifield = 1:length(extra_fields) 52 | % assert(~isfield(MatNew, extra_fields{ifield})); 53 | MatNew.(extra_fields{ifield}) = extra.(extra_fields{ifield}); 54 | end 55 | 56 | sFile = bst_process('GetNewFilename', bst_fileparts(sStudy.FileName), 'matrix_table'); 57 | % Save file 58 | bst_save(sFile, MatNew, 'v6'); 59 | % Register in database 60 | db_add_data(iStudy, sFile, MatNew); 61 | end 62 | 63 | function v = line_vector(v) 64 | if size(v, 2) == 1 && size(v, 1) > 1 65 | v = v'; 66 | end 67 | end -------------------------------------------------------------------------------- /scripts/deglitch_example.m: -------------------------------------------------------------------------------- 1 | function deglitch_example() 2 | %% Generate signal with glitches 3 | nb_samples = 500; 4 | nb_glitches = 10; 5 | nb_draws = 2; 6 | signal = randn(nb_samples, nb_draws); 7 | gen_spikes = [ones(1, nb_draws) ; randi([3,nb_samples-2], nb_glitches-2, nb_draws) ; zeros(1, nb_draws) + nb_samples]; 8 | for idraw=1:nb_draws 9 | signal(gen_spikes(:, idraw), idraw) = rand(nb_glitches, 1) * 5 + 5; 10 | end 11 | 12 | %% Detect glitches 13 | std_factor_thresh = 2.5; 14 | signal_tmp = [signal(2, :) ; signal ; signal(end-1, :)]; % mirror edges 15 | grad = diff(signal_tmp); 16 | abs_grad = abs(grad); 17 | std_agrad = std(abs_grad); 18 | glitch_canditates = [zeros(1, nb_draws) ; (abs_grad > std_factor_thresh * std_agrad) .* sign(grad)]; 19 | glitch_flags = [abs(diff(glitch_canditates))==2 ; false(1, nb_draws)] & glitch_canditates; 20 | glitch_flags = glitch_flags(2:end-1, :); % remove mirrored edges 21 | 22 | %% Clean glitches 23 | signal_cleaned = signal; 24 | for idraw=1:nb_draws 25 | signal_cleaned(glitch_flags(:, idraw), idraw) = repmat(mean(signal(:,idraw)), sum(glitch_flags(:, idraw)), 1); 26 | if glitch_flags(end, idraw) 27 | signal_cleaned(end, idraw) = mean(signal(nb_samples-2:nb_samples-1, idraw)); 28 | glitch_flags(end, idraw) = 0; 29 | end 30 | if glitch_flags(1, idraw) 31 | signal_cleaned(1, idraw) = mean(signal(2:3, idraw)); 32 | glitch_flags(1, idraw) = 0; 33 | end 34 | % Maybe the following can be vectorized but there was smthg strange with 35 | % find on 2D array 36 | glitch_i = find(glitch_flags(:, idraw)); 37 | signal_cleaned(glitch_i, idraw) = (signal(glitch_i-1, idraw) + signal(glitch_i+1, idraw)) ./ 2; 38 | end 39 | 40 | %% Plot results 41 | figure(); hold on; 42 | plot(signal(:, 1), 'b', 'Linewidth', 3); 43 | plot(find(glitch_flags(:,1)), signal(glitch_flags(:,1), 1), 'Xg', 'MarkerSize', 10); 44 | plot(signal_cleaned(:, 1), 'r', 'Linewidth', 2); 45 | plot(gen_spikes(:, 1), signal(gen_spikes(:, 1), 1), 'ok', 'MarkerSize', 10); 46 | 47 | figure(); hold on; 48 | plot(signal(:, 2), 'b', 'Linewidth', 3); 49 | plot(find(glitch_flags(:,2)), signal(glitch_flags(:,2), 2), 'Xg', 'MarkerSize', 10); 50 | plot(signal_cleaned(:, 2), 'r', 'Linewidth', 2); 51 | plot(gen_spikes(:, 2), signal(gen_spikes(:, 2), 2), 'ok', 'MarkerSize', 10); 52 | end 53 | -------------------------------------------------------------------------------- /test/utest_import_nirs_in_bst.m: -------------------------------------------------------------------------------- 1 | function sFile = utest_import_nirs_in_bst(nirs_fn, clean, raw_importation) 2 | 3 | % RAW=1 -> import as link to raw file 4 | % RAW=0 -> import data into brainstorm DB. 5 | 6 | if nargin < 2 7 | clean = 1; % always clean by default 8 | end 9 | 10 | if nargin < 3 11 | raw_importation = 1; 12 | end 13 | 14 | ProtocolName = 'nst_utest'; 15 | 16 | %% Clean nst_utest protocol if needed 17 | if clean && ~isempty(bst_get('Protocol', ProtocolName)) 18 | % Delete existing protocol 19 | gui_brainstorm('DeleteProtocol', ProtocolName); 20 | 21 | db_dir = bst_get('BrainstormDbDir'); 22 | nst_protocol_dir = fullfile(db_dir, ProtocolName); 23 | if exist(nst_protocol_dir, 'dir') 24 | rmdir(nst_protocol_dir, 's'); 25 | end 26 | end 27 | 28 | %% Ensure that nst_utest protocol exists 29 | if isempty(bst_get('Protocol', ProtocolName)) 30 | % Create new protocol 31 | gui_brainstorm('CreateProtocol', ProtocolName, 0, 0); 32 | end 33 | 34 | %% Resolve file format 35 | [rr, bfn, ext] = fileparts(nirs_fn); 36 | switch(ext) 37 | case '.nirs' %Homer 38 | format = 'NIRS-BRS'; 39 | case '.txt' %Artinis TODO: enable importation in Brainstorm 40 | format = 'NIRS-ARTINIS'; 41 | end 42 | 43 | %% Import data as raw or data file 44 | subject_name = 'test_subject'; 45 | if raw_importation 46 | sFile = bst_process('CallProcess', 'process_import_data_raw', [], [], ... 47 | 'subjectname', subject_name, ... 48 | 'datafile', {nirs_fn, format}, ... 49 | 'channelreplace', 1, ... 50 | 'channelalign', 0); 51 | else 52 | sFile = bst_process('CallProcess', 'process_import_data_time', [], [], ... 53 | 'subjectname', subject_name, ... 54 | 'condition', 'test', ... 55 | 'datafile', {nirs_fn, format}, ... 56 | 'timewindow', [], ... 57 | 'split', 0, ... 58 | 'ignoreshort', 1, ... 59 | 'channelalign', 0, ... 60 | 'usectfcomp', 0, ... 61 | 'usessp', 0, ... 62 | 'freq', [], ... 63 | 'baseline', []); 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /bst_plugin/GLM/process_nst_glm_fit1.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_glm_fit1( varargin ) 2 | % process_compute_glm: compute the glm : find B such as Y = XB +e with X 3 | % 4 | % OlS_fit use an ordinary least square algorithm to find B : B= ( X^{T}X)^{-1} X^{T} Y 5 | % AR-IRLS : Details about AR-IRLS algorithm can be found here : 6 | % http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3756568/ 7 | % 8 | % 9 | % Further update : use more sophisticated method to fit the B 10 | % @============================================================================= 11 | % This function is part of the Brainstorm software: 12 | % http://neuroimage.usc.edu/brainstorm 13 | % 14 | % Copyright (c)2000-2017 University of Southern California & McGill University 15 | % This software is distributed under the terms of the GNU General Public License 16 | % as published by the Free Software Foundation. Further details on the GPLv3 17 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 18 | % 19 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 20 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 21 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 22 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 23 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 24 | % 25 | % For more information type "brainstorm license" at command prompt. 26 | % =============================================================================@ 27 | % 28 | % Authors: Edouard Delaire, Thomas Vincent 2018-2020 29 | % 30 | % TODO: use different output types for channel-space and surface analyses 31 | % -> more convenient for contrast computation afterwards, to be able to map 32 | % input to output types 33 | % 34 | eval(macro_method); 35 | end 36 | 37 | 38 | %% ===== GET DESCRIPTION ===== 39 | function sProcess = GetDescription() 40 | 41 | 42 | sProcess = process_nst_glm_fit('GetDescription'); 43 | 44 | sProcess.Category = 'File'; 45 | sProcess.nInputs = 1; 46 | sProcess.nMinFiles = 1; 47 | 48 | end 49 | 50 | 51 | 52 | %% ===== FORMAT COMMENT ===== 53 | function Comment = FormatComment(sProcess) 54 | Comment = sProcess.Comment; 55 | end 56 | 57 | function OutputFiles = Run(sProcess, sInput) 58 | OutputFiles = process_nst_glm_fit('Run',sProcess,sInput,[]); 59 | end 60 | -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_glm_display_model.m: -------------------------------------------------------------------------------- 1 | function nst_glm_display_model(model,mode) 2 | n_regressor=size(model.X,2); 3 | if strcmp(mode,'matrix') 4 | Tag='DesignModelMatrix'; 5 | else 6 | Tag='DesignModelTime'; 7 | end 8 | hFig = findobj(0, 'Type', 'Figure', 'Tag', Tag); 9 | if isempty(hFig) 10 | hFig = figure(... 11 | 'MenuBar', 'none', ... 12 | ... 'Toolbar', 'none', ... 13 | 'Toolbar', 'figure', ... 14 | 'NumberTitle', 'off', ... 15 | 'Name', sprintf('Design Matrix'), ... 16 | 'Tag', Tag, ... 17 | 'Units', 'Pixels'); 18 | % Figure already exists: re-use it 19 | else 20 | clf(hFig); 21 | figure(hFig); 22 | end 23 | 24 | % Plot frequency response 25 | hAxes = axes('Units', 'pixels', 'Parent', hFig, 'Tag', 'AxesImpz'); 26 | 27 | switch mode 28 | case 'matrix' 29 | h = imagesc(1:n_regressor,model.time(end:-1:1),model.X, ... 30 | 'Parent', hAxes); 31 | colormap('gray'); 32 | set(gca,'xtick',[]); 33 | 34 | % Enable zooming by default 35 | zoom(hFig, 'on'); 36 | set(hFig, bst_get('ResizeFunction'), @ResizeCallback); 37 | ResizeCallback(hFig); 38 | 39 | case 'timecourse' 40 | for i_reg=1:n_regressor 41 | subplot(n_regressor,1,i_reg,'Parent',hFig) 42 | plot(model.time,model.X(:,i_reg)) 43 | xlim([model.time(1) model.time(end)]) 44 | title(model.reg_names{i_reg},'interpreter', 'none') 45 | end 46 | end 47 | 48 | function ResizeCallback(hFig, ev) 49 | % Get figure position 50 | figpos = get(hFig, 'Position'); 51 | textH = 110; % Text Height 52 | marginL = 70; 53 | marginR = 30; 54 | marginT = 30; 55 | marginB = 50; 56 | axesH = round((figpos(4) - textH) ./ 2); 57 | % Position axes 58 | set(hAxes, 'Position', max(1, [marginL, textH + marginB + axesH, figpos(3) - marginL - marginR, axesH - marginB - marginT])); 59 | %set(hLabel1, 'Position', max(1, [marginL, textH + marginB, figpos(3) - marginL - marginR, axesH - marginB - marginT])); 60 | %set(hLabel1, 'Position', max(1, [40, 1, round((figpos(3)-40)/2), textH])); 61 | %set(hLabel2, 'Position', max(1, [round(figpos(3)/2), 1, round(figpos(3)/2), textH])); 62 | end 63 | 64 | end 65 | 66 | -------------------------------------------------------------------------------- /test/WorkShopPerform2018Test.m: -------------------------------------------------------------------------------- 1 | classdef WorkShopPerform2018Test < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_downloader(testCase) 26 | global GlobalData; 27 | tmp_dir = testCase.tmp_dir; 28 | workshop_data_path = fullfile(nst_get_local_user_dir(), 'tutorials', 'Nirstorm_workshop_PERFORM_2018'); 29 | if ~exist(workshop_data_path, 'dir') 30 | warning('Skipping test setup of workshop PERFORM 2018. Sample data directory not found in %s', ... 31 | workshop_data_path); 32 | return; 33 | end 34 | fluence_uncompress_dir = fullfile(workshop_data_path, 'fluences_for_OM'); 35 | if exist(fluence_uncompress_dir , 'dir') 36 | warning('Uncompressed fluence folder exists, deleting it...'); 37 | rmdir(fluence_uncompress_dir, 's'); 38 | end 39 | 40 | if ~exist(workshop_data_path, 'dir') 41 | % TODO: check that all data files are there 42 | % data_fns = process_nst_get_data_perform_2018('get_data_file_names'); 43 | warning('Skip WorkshopPerform2018.test_downloader because data not found in %s', workshop_data_path); 44 | end 45 | 46 | % Test behaviour when all is fine 47 | bst_dir = fullfile(tmpdir, 'bst_home'); 48 | bst_process('CallProcess', ... 49 | 'process_nst_get_data_perform_2018', [], [], ... 50 | 'inputdir', {workshop_data_path, 'DIR'}, ... 51 | 'bst_dir', bst_dir); 52 | 53 | installed_fns = [process_nst_get_data_perform_2018('get_colin27_installed_fn', bst_dir) ... 54 | process_nst_get_data_perform_2018('get_OM_fluence_fns', bst_dir)]; 55 | 56 | fluence_uncompress_dir = fullfile(workshop_data_path, 'fluences_for_OM'); 57 | testCase.assertTrue(exist(fluence_uncompress_dir, 'dir')==0); 58 | 59 | testCase.assertEmpty(GlobalData.lastestFullErrMsg); 60 | testCase.assertTrue(files_exist(installed_fns)); 61 | end 62 | 63 | end 64 | end 65 | 66 | function flags = files_exist(fns) 67 | flags = all(cellfun(@(fn) exist(fn, 'file')>0, fns)); 68 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NIRSTORM 2 | 3 | ![NIRSTORM - a Brainstorm plugin for fNIRS data analysis.](https://user-images.githubusercontent.com/24530402/98286213-a4b4a600-1f71-11eb-8a76-8eff3f820e3a.png) 4 | 5 | Current features include: 6 | - classical within-subject analysis comprising motion correction, MBLL, and window-averaging. 7 | - statistical analysis using GLM 8 | - optimal montages that optimize the sensitivity to a given cortical region of interest 9 | - source reconstruction (cMEM, MNE) 10 | - precomputed fluence template based on Colin27 11 | 12 | ## Main developers: 13 | * Edouard Delaire, PERFORM Centre and Physics dpt., Concordia University, Montreal, Canada 14 | * Thomas Vincent, EPIC Center, Montreal Heart Institute, Montreal, Canada 15 | * Zhengchen Cai, PERFORM Centre, and Physics dpt., Concordia University, Montreal, Canada 16 | * Robert Stojan, Sportpsychology, Chemnitz University of Technology, Germany 17 | * Alexis Machado, Multimodal Functional Imaging Lab., Biomedical Engineering Dpt, McGill University, Montreal, Canada 18 | 19 | ## Collaborators: 20 | * Amanda Spilkin, PERFORM Centre and Physics dpt., Concordia University, Montreal, Canada 21 | * Jean-Marc Lina, Electrical Engineering Dpt, Ecole de Technologie Supérieure, Montréal, Canada 22 | * Christophe Grova, PERFORM Centre and physics dpt., Concordia University, Montreal, Canada 23 | * Louis Bherer, EPIC Center, Montreal Heart Institute, Montreal, Canada 24 | 25 | ## Usage 26 | 27 | The primary documentation is available on the [website of Brainstorm](https://neuroimage.usc.edu/brainstorm/Tutorials). 28 | 29 | ## Citation 30 | If you are using NIRSTORM, please cite: 31 | 32 | Édouard Delaire, Thomas Vincent, Zhengchen Cai, Alexis Machado, Laurent Hugueville, Denis Schwartz, Francois Tadel, Raymundo Cassani, Louis Bherer, Jean-Marc Lina, Mélanie Pélégrini-Issac, Christophe Grova, NIRSTORM: A brainstorm extension dedicated to fNIRS data analysis, advanced 3D reconstructions, and optimal probe design, Neurophoton. 12(2), 025011 (2025), doi: 10.1117/1.NPh.12.2.025011. 33 | 34 | ## Installation 35 | 36 | Nirstorm is available in two forms: an open-source Matlab plugin for Brainstorm (Matlab license required) and is included in the Brainstorm standalone version (Java executable) 37 | 38 | Matlab version can be installed using the Brainstorm plugin system : 39 | ![Nirs_installation](https://neuroimage.usc.edu/brainstorm/Tutorials/Plugins?action=AttachFile&do=get&target=example1.gif) 40 | 41 | This required the Brainstorm version 3.210414 (14 April 2021) or higher. To use nirstorm with a previous version of Brainstorm, refer to this tutorial https://github.com/Nirstorm/nirstorm/wiki/Installation (Note: We recommend you to always use the latest version of Brainstorm) 42 | 43 | A standalone version is directly included in Brainstorm standalone version. Follow Brainstorm tutorials for more information: 44 | https://neuroimage.usc.edu/brainstorm/Installation 45 | 46 | 47 | -------------------------------------------------------------------------------- /bst_plugin/math/nst_mbll_source.m: -------------------------------------------------------------------------------- 1 | function sResults_hb = nst_mbll_source(sResults, wavelentghts) 2 | %NST_MBLL_SOURCE Apply the MBLL on the data after source reconstruction 3 | 4 | assert(length(sResults) == length(wavelentghts), 'Provide the wavelength associated with each map') 5 | assert(length(sResults) > 1, 'Unable to compute the MNLL with only one wavelentght ') 6 | 7 | 8 | 9 | bst_progress('text', 'Calculating HbO/HbR/HbT in source space...'); 10 | 11 | % Compute dHb 12 | hb_extinctions = nst_get_hb_extinctions(wavelentghts); 13 | hb_extinctions = hb_extinctions ./10;% mm-1.mole-1.L 14 | 15 | if ~iscell(sResults(1).ImageGridAmp) 16 | dOD_sources = permute( cat(3, sResults.ImageGridAmp), [1 3 2]); 17 | else 18 | isConsistent = 1; 19 | for iResult = 2:length(sResults) 20 | isConsistent = isConsistent && isequal( sResults(1).ImageGridAmp{2}, sResults(iResult).ImageGridAmp{2}); 21 | end 22 | 23 | if isConsistent 24 | dOD_sources = zeros(size(sResults(1).ImageGridAmp{1}, 1) ,length(sResults),size(sResults(1).ImageGridAmp{1}, 2)); 25 | for iResult = 1:length(sResults) 26 | dOD_sources(:, iResult, :) = sResults(iResult).ImageGridAmp{1}; 27 | end 28 | else 29 | % If not consistent, go back to full time-course 30 | dOD_sources = zeros(size(sResults(1).ImageGridAmp{1}, 1) ,length(sResults),size(sResults(1).ImageGridAmp{2}, 2)); 31 | for iResult = 1:length(sResults) 32 | sResults(iResult).ImageGridAmp = sResults(iResult).ImageGridAmp{1} * sResults(iResult).ImageGridAmp{2}; 33 | dOD_sources(:, iResult, :) = sResults(iResult).ImageGridAmp; 34 | end 35 | end 36 | end 37 | 38 | Hb_sources = zeros(size(dOD_sources,1), 3, size(dOD_sources,3)); 39 | for inode=1:size(dOD_sources,1) 40 | Hb_sources(inode, 1:2, :) = pinv(hb_extinctions) * ... 41 | squeeze(dOD_sources(inode, :, :)); 42 | 43 | end 44 | Hb_sources(:,3,:) = squeeze(sum(Hb_sources, 2)); 45 | 46 | hb_unit_factor = 1e6; 47 | hb_unit = '\mumol.l-1'; 48 | hb_types = {'HbO', 'HbR','HbT'}; 49 | 50 | sResults_hb = repmat(sResults(1), 1, 3); 51 | for iHb = 1:3 52 | 53 | tmp = strsplit(sResults(end).Comment, '|'); 54 | 55 | sResults_hb(iHb).Comment = strjoin( [{strjoin(tmp(1:end-1), '|')} , '|' , hb_types{iHb}]); 56 | sResults_hb(iHb).History = sResults(end).History; 57 | sResults_hb(iHb).DisplayUnits = hb_unit; 58 | sResults_hb(iHb) = bst_history('add', sResults_hb(iHb), 'compute', 'Estimate concentration change'); 59 | 60 | if iscell(sResults_hb(iHb).ImageGridAmp ) 61 | sResults_hb(iHb).ImageGridAmp{1} = squeeze(Hb_sources(:,iHb,:)) .* hb_unit_factor; 62 | else 63 | sResults_hb(iHb).ImageGridAmp = squeeze(Hb_sources(:,iHb,:)) .* hb_unit_factor; 64 | end 65 | end 66 | 67 | end 68 | 69 | -------------------------------------------------------------------------------- /test/SeparationTest.m: -------------------------------------------------------------------------------- 1 | classdef SeparationTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | 26 | function test_forged_data_by_pairs(testCase) 27 | bst_create_test_subject(); 28 | ref_point = [-0.0789 ; -0.0263 ; 0.0655]; % meter, somewhere at the back of the head when using Colin27_4NIRS 29 | srcs_pos = [ref_point ref_point ref_point ref_point]; 30 | dets_pos = [ref_point+[0.01;0;0] ref_point+[0.01;0;0] ref_point+[0.01;0.01;0.01] ref_point+[0.01;0.01;0.01]]; 31 | nirs_input_file = bst_create_nirs_data('test', [], [], {'S2D2WL650', 'S2D2WL800', 'S2D1WL650', 'S2D1WL800'}, srcs_pos, dets_pos); 32 | 33 | pair_ids = [[2 1];[2 2]]; 34 | sInput = in_bst_data(nirs_input_file); 35 | channel_def = in_bst_channel(sInput.ChannelFile); 36 | separations = process_nst_separations('Compute', channel_def.Channel, pair_ids); 37 | % Note: output unit is same as in channel_def -> meter 38 | testCase.assertTrue(all(size(separations)==[size(pair_ids, 1) 1])); 39 | expected_separation = sqrt(0.01^2 * 3); 40 | testCase.assertTrue(all_close(separations(1), expected_separation)); %S2D1 41 | expected_separation = 0.01; 42 | testCase.assertTrue(all_close(separations(2), expected_separation)); %S2D2 43 | end 44 | 45 | function test_forged_data_by_channels(testCase) 46 | bst_create_test_subject(); 47 | ref_point = [-0.0789 ; -0.0263 ; 0.0655]; % meter, somewhere at the back of the head when using Colin27_4NIRS 48 | srcs_pos = [ref_point ref_point ref_point ref_point]; 49 | dets_pos = [ref_point+[0.01;0;0] ref_point+[0.01;0;0] ref_point+[0.01;0.01;0.01] ref_point+[0.01;0.01;0.01]]; 50 | nirs_input = bst_create_nirs_data('test', [], [], {}, srcs_pos, dets_pos); 51 | output = bst_process('CallProcess', 'process_nst_separations', nirs_input, []); 52 | 53 | %Note: output unit is cm 54 | sDataOut = in_bst_data(output.FileName); 55 | testCase.assertMatches(sDataOut.DisplayUnits, 'cm'); 56 | testCase.assertTrue(all(size(sDataOut.F)==[4 1])); 57 | testCase.assertTrue(all_close(sDataOut.F(1), 0.01 * 100)); %S1D1WL1 58 | testCase.assertTrue(all_close(sDataOut.F(2), 0.01 * 100)); %S1D1WL2 59 | expected_separation = sqrt(0.01^2 * 3) * 100; 60 | testCase.assertTrue(all_close(sDataOut.F(3), expected_separation)); %S1D2WL1 61 | testCase.assertTrue(all_close(sDataOut.F(4), expected_separation)); %S1D2WL2 62 | end 63 | 64 | end 65 | end -------------------------------------------------------------------------------- /bst_plugin/core/nst_bst_add_surf_data.m: -------------------------------------------------------------------------------- 1 | function [sStudy, ResultFile] = nst_bst_add_surf_data(data, time, head_model, file_tag, comment, ... 2 | sInputs, sStudy, history_comment, surface_file, ... 3 | sparse_storage, extra) 4 | 5 | 6 | if nargin < 9 || isempty(surface_file) 7 | if ~isempty(head_model) 8 | surface_file = file_short(head_model.SurfaceFile); 9 | else 10 | error('Surface file not defined'); 11 | end 12 | end 13 | 14 | if nargin < 10 15 | sparse_storage = 0; 16 | end 17 | 18 | if nargin < 11 19 | extra = struct(); 20 | end 21 | 22 | %TODO: check consistency between data and nb of vertices 23 | 24 | %% Save a cortical map to brainstorm with given data 25 | 26 | ResultFile = bst_process('GetNewFilename', bst_fileparts(sStudy.FileName), ... 27 | ['results_' nst_protect_fn_str(file_tag)]); 28 | 29 | % ===== CREATE FILE STRUCTURE ===== 30 | ResultsMat = db_template('resultsmat'); 31 | ResultsMat.Comment = comment; 32 | ResultsMat.Function = ''; 33 | ResultsMat.ImageGridAmp = data; 34 | ResultsMat.Time = time; 35 | 36 | 37 | if ~isempty(sInputs) 38 | if isfield(sInputs, 'DataFile') 39 | ResultsMat.DataFile = sInputs.DataFile; 40 | elseif isfield(sInputs, 'FileName') 41 | ResultsMat.DataFile = sInputs.FileName; 42 | end 43 | end 44 | 45 | if ~isempty(head_model) 46 | if ~isempty(sStudy.iHeadModel) 47 | ResultsMat.HeadModelFile = sStudy.HeadModel(sStudy.iHeadModel).FileName; 48 | end 49 | ResultsMat.HeadModelType = head_model.HeadModelType; 50 | end 51 | 52 | 53 | ResultsMat.ChannelFlag = []; 54 | ResultsMat.GoodChannel = []; 55 | ResultsMat.SurfaceFile = surface_file; 56 | ResultsMat.GridLoc = []; 57 | ResultsMat.GridOrient = []; 58 | ResultsMat.nAvg = 1; 59 | 60 | % Add extra fields 61 | extra_fields = fieldnames(extra); 62 | for ifield = 1:length(extra_fields) 63 | % assert(~isfield(ResultsMat, extra_fields{ifield})); 64 | ResultsMat.(extra_fields{ifield}) = extra.(extra_fields{ifield}); 65 | end 66 | 67 | % History 68 | if ~isempty(sInputs) 69 | sData = in_bst_data(sInputs.FileName,'History'); 70 | if isfield(sData,'History') 71 | ResultsMat.History = sData.History; 72 | end 73 | end 74 | 75 | ResultsMat = bst_history('add', ResultsMat, 'compute', history_comment); 76 | % Save new file structure 77 | bst_save(ResultFile, ResultsMat, 'v6'); 78 | % ===== REGISTER NEW FILE ===== 79 | % Create new results structure 80 | newResult = db_template('results'); 81 | newResult.Comment = comment; 82 | newResult.FileName = file_short(ResultFile); 83 | newResult.isLink = 0; 84 | newResult.HeadModelType = ResultsMat.HeadModelType; 85 | % Add new entry to the database 86 | iResult = length(sStudy.Result) + 1; 87 | sStudy.Result(iResult) = newResult; 88 | % Update Brainstorm database 89 | if ~isempty(sInputs) && isfield(sInputs,'iStudy') 90 | bst_set('Study', sInputs.iStudy, sStudy); 91 | else 92 | [tmp, iStudy] = bst_get('Study', sStudy.FileName); 93 | bst_set('Study', iStudy, sStudy); 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /bst_plugin/math/nst_tddr_correction.m: -------------------------------------------------------------------------------- 1 | function signal_corrected = nst_tddr_correction( signal , sample_rate ) 2 | % This function is the reference implementation for the TDDR algorithm for 3 | % motion correction of fNIRS data, as described in: 4 | % 5 | % Fishburn F.A., Ludlum R.S., Vaidya C.J., & Medvedev A.V. (2019). 6 | % Temporal Derivative Distribution Repair (TDDR): A motion correction 7 | % method for fNIRS. NeuroImage, 184, 171-179. 8 | % https://doi.org/10.1016/j.neuroimage.2018.09.025 9 | % 10 | % Usage: 11 | % signals_corrected = TDDR( signals , sample_rate ); 12 | % 13 | % Inputs: 14 | % signals: A [sample x channel] matrix of uncorrected optical density data 15 | % sample_rate: A scalar reflecting the rate of acquisition in Hz 16 | % 17 | % Outputs: 18 | % signals_corrected: A [sample x channel] matrix of corrected optical density data 19 | % 20 | 21 | %% Iterate over each channel 22 | nch = size(signal,2); 23 | if nch>1 24 | signal_corrected = zeros(size(signal)); 25 | for ch = 1:nch 26 | signal_corrected(:,ch) = nst_tddr_correction( signal(:,ch) , sample_rate ); 27 | end 28 | return 29 | end 30 | 31 | %% Preprocess: Separate high and low frequencies 32 | filter_cutoff = .5; 33 | filter_order = 3; 34 | Fc = filter_cutoff * 2/sample_rate; 35 | signal_mean = mean(signal); 36 | signal = signal - signal_mean; 37 | if Fc<1 38 | if bst_get('UseSigProcToolbox') 39 | [fb,fa] = butter(filter_order,Fc); 40 | signal_low = filtfilt(fb,fa,signal); 41 | else 42 | [fb,fa] = oc_butter(filter_order,Fc); 43 | signal_low = oc_filtfilt(fb,fa,signal); 44 | end 45 | else 46 | signal_low = signal; 47 | end 48 | signal_high = signal - signal_low; 49 | 50 | %% Initialize 51 | tune = 4.685; 52 | D = sqrt(eps(class(signal))); 53 | mu = inf; 54 | iter = 0; 55 | 56 | %% Step 1. Compute temporal derivative of the signal 57 | deriv = diff(signal_low); 58 | 59 | %% Step 2. Initialize observation weights 60 | w = ones(size(deriv)); 61 | 62 | %% Step 3. Iterative estimation of robust weights 63 | while iter < 50 64 | 65 | iter = iter + 1; 66 | mu0 = mu; 67 | 68 | % Step 3a. Estimate weighted mean 69 | mu = sum( w .* deriv ) / sum( w ); 70 | 71 | % Step 3b. Calculate absolute residuals of estimate 72 | dev = abs(deriv - mu); 73 | 74 | % Step 3c. Robust estimate of standard deviation of the residuals 75 | sigma = 1.4826 * median(dev); 76 | 77 | % Step 3d. Scale deviations by standard deviation and tuning parameter 78 | r = dev / (sigma * tune); 79 | 80 | % Step 3e. Calculate new weights accoring to Tukey's biweight function 81 | w = ((1 - r.^2) .* (r < 1)) .^ 2; 82 | 83 | % Step 3f. Terminate if new estimate is within machine-precision of old estimate 84 | if abs(mu-mu0) < D*max(abs(mu),abs(mu0)) 85 | break; 86 | end 87 | 88 | end 89 | 90 | %% Step 4. Apply robust weights to centered derivative 91 | new_deriv = w .* (deriv-mu); 92 | 93 | %% Step 5. Integrate corrected derivative 94 | signal_low_corrected = cumsum([0; new_deriv]); 95 | 96 | %% Postprocess: Center the corrected signal 97 | signal_low_corrected = signal_low_corrected - mean(signal_low_corrected); 98 | 99 | %% Postprocess: Merge back with uncorrected high frequency component 100 | signal_corrected = signal_low_corrected + signal_high + signal_mean; 101 | 102 | end 103 | -------------------------------------------------------------------------------- /test/cpt_spherical_fluences.m: -------------------------------------------------------------------------------- 1 | function fluence_dir = cpt_spherical_fluences(sSubject, head_scout_vertices, wavelengths, dcut) 2 | 3 | fluence_dir = fullfile(nst_get_local_user_dir(), 'fluence', 'MRI__Colin27_4NIRS', 'spherical'); 4 | if ~exist(fluence_dir, 'dir') 5 | mkdir(fluence_dir); 6 | end 7 | 8 | if nargin < 4 9 | dcut = 40; %mm 10 | end 11 | 12 | disp(['Start generating missing spherical fluences for ' num2str(length(head_scout_vertices)) ' vertices...']); 13 | sHead = in_tess_bst(sSubject.Surface(sSubject.iScalp).FileName); 14 | sMri = in_mri_bst(sSubject.Anatomy(sSubject.iAnatomy).FileName); 15 | 16 | % Vertices: SCS->MRI, MRI(mm) 17 | head_vertices_mri = cs_convert(sMri, 'scs', 'mri', sHead.Vertices) * 1000; % m to mm 18 | 19 | [dimx, dimy, dimz] = size(sMri.Cube); 20 | [xx, yy, zz] = meshgrid(1:dimx, 1:dimy, 1:dimz); 21 | all_coords = [xx(:) yy(:) zz(:)] .* repmat(sMri.Voxsize, dimx*dimy*dimz, 1); 22 | fluence_vol = zeros(dimy, dimx, dimz); %TODO: weird order ... to check 23 | 24 | 25 | 26 | % Save MRI to nifti: 27 | % sVol = sMri; 28 | % out_fn = fullfile(fluence_dir, 't1.nii'); 29 | % out_mri_nii(sMri, out_fn, 'float32'); 30 | 31 | for ivertex=1:length(head_scout_vertices) 32 | vertex_id = head_scout_vertices(ivertex); 33 | fluence_wl1_fn = fullfile(fluence_dir, process_nst_import_head_model('get_fluence_fn', vertex_id, wavelengths(1))); 34 | if ~exist(fluence_wl1_fn, 'file') 35 | vertex = head_vertices_mri(vertex_id, :); 36 | rv = repmat(vertex, size(all_coords, 1), 1); 37 | cp = rv - all_coords; 38 | fluence_flat = max(0, 1 - sqrt(sum(cp .* cp, 2)) / dcut); 39 | fluence_vol(:) = fluence_flat; 40 | to_save = permute(fluence_vol, [2 1 3]); 41 | 42 | [vmax, imax] = max(to_save(:)); 43 | [vvox_i, vvox_j, vvox_k] = ind2sub([dimx, dimy, dimz], imax); 44 | vertex_vox = round(vertex ./ sMri.Voxsize); 45 | assert(all([vvox_i, vvox_j, vvox_k] == vertex_vox)); 46 | 47 | if 0 48 | figure(); 49 | 50 | subplot(2,2,1); hold on; 51 | imagesc(double(squeeze(sMri.Cube(:,:,vertex_vox(3)))) .* (squeeze(to_save(:,:,vertex_vox(3)))*10+1)); 52 | 53 | subplot(2,2,2); imagesc(double(squeeze(sMri.Cube(:,vertex_vox(2),:))) .* (squeeze(to_save(:,vertex_vox(2),:))*10+1)); 54 | subplot(2,2,3); imagesc(double(squeeze(sMri.Cube(vertex_vox(1),:,:))) .* (squeeze(to_save(vertex_vox(1),:,:))*10+1)); 55 | colormap gray; 56 | 57 | title(sprintf('vertex %d', vertex_id)); 58 | 59 | sVol = sMri; 60 | sVol.Comment = ''; 61 | sVol.Cube = to_save; 62 | sVol.Histogram = []; 63 | [rr, bfn, ext] = fileparts(fluence_wl1_fn); 64 | out_fn = fullfile(fluence_dir, [bfn '.nii']); 65 | out_mri_nii(sVol, out_fn, 'float32'); 66 | end 67 | fluence_flat_sparse_vol = sparse(double(to_save(:))); 68 | reference_voxel_index = round(vertex ./ sMri.Voxsize); 69 | save(fluence_wl1_fn, 'fluence_flat_sparse_vol','reference_voxel_index'); 70 | end 71 | for iwl=2:length(wavelengths) 72 | wl = wavelengths(iwl); 73 | fluence_fn = fullfile(fluence_dir, process_nst_import_head_model('get_fluence_fn', vertex_id, wl)); 74 | if ~exist(fluence_fn, 'file') 75 | copyfile(fluence_wl1_fn, fluence_fn); 76 | end 77 | end 78 | end 79 | disp('Done generating missing spherical fluences.'); 80 | end 81 | -------------------------------------------------------------------------------- /scripts/generate_colormap_matlab.py: -------------------------------------------------------------------------------- 1 | import os.path as op 2 | 3 | import matplotlib as mpl 4 | import matplotlib.pyplot as plt 5 | from matplotlib.colors import LinearSegmentedColormap 6 | import numpy as np 7 | 8 | 9 | def plot_cmap(cm, norm, img_fn=None): 10 | fig = plt.figure() 11 | ax = fig.add_axes([0.05, 0.2, 0.9, 0.1]) 12 | cb = mpl.colorbar.ColorbarBase(ax, cmap=cm, norm=norm, 13 | orientation='horizontal') 14 | cb.ax.tick_params(labelsize=38) 15 | 16 | if img_fn is not None: 17 | plt.savefig(img_fn, bbox_inches='tight') 18 | #make_white_transparent_in_img(img_fn) 19 | 20 | def cmap_to_matlab_code(cmap, cmap_name, nb_colors=256): 21 | licence_comments = """% @============================================================================= 22 | % This function is part of the Brainstorm software: 23 | % https://neuroimage.usc.edu/brainstorm 24 | % 25 | % Copyright (c)2000-2019 University of Southern California & McGill University 26 | % This software is distributed under the terms of the GNU General Public License 27 | % as published by the Free Software Foundation. Further details on the GPLv3 28 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 29 | % 30 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 31 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 32 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 33 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 34 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 35 | % 36 | % For more information type "brainstorm license" at command prompt. 37 | % =============================================================================@""" 38 | function_pat = 'function CMap = cmap_{}(varargin)\n\n' + \ 39 | licence_comments + '\n\n' + 'CMap = [\n {}];\n' 40 | 41 | clist = cmap(np.linspace(0,1,nb_colors))[:,:3] 42 | return function_pat.format(cmap_name, 43 | '\n '.join([' '.join(['%1.5f'%c for c in rgb]) 44 | for rgb in clist])) 45 | 46 | cdict_hbo_np = {'red': ((0.0, 0.0, 0.0), 47 | (0.25, 45/255., 45/255.), 48 | (0.5, 125/255., 1.0), 49 | (0.75, 1.0, 1.0), 50 | (1.0, 1.0, 1.0)), 51 | 'green': ((0.0, 0.0, 0.0), 52 | (0.25, 183/255., 183./255.), 53 | (0.5, 1.0, 1.0), 54 | (0.75, 190/255., 190/255.), 55 | (1.0, 0.0, 0.0)), 56 | 'blue': ((0.0, 1.0, 1.0), 57 | (0.25, 1.0, 1.0), 58 | (0.5, 226/255., 125/255.), 59 | (0.75, 0.0, 0.0), 60 | (1.0, 0.0, 0.0)) 61 | } 62 | 63 | cmap = LinearSegmentedColormap('HbOEffect', cdict_hbo_np) 64 | vmax = 5 65 | vmin = -5 66 | norm = plt.Normalize(vmin, vmax) #MidpointNormalize(vmin, vmax, midpoint=0) 67 | 68 | 69 | nb_colors = 256 70 | cmap_name = 'ovun' 71 | dest_dir = '/home/tom/Projects/Research/Software/Brainstorm/brainstorm3_trunk/toolbox/misc' 72 | dest_code_fn = op.join(dest_dir, 'cmap_ovun.m') 73 | with open(dest_code_fn, "w") as text_file: 74 | text_file.write(cmap_to_matlab_code(cmap, cmap_name, nb_colors)) 75 | 76 | bounds = np.linspace(0,14,15) 77 | norm_bounds = mpl.colors.BoundaryNorm(bounds, cmap.N) 78 | plot_cmap(cmap, norm_bounds, './cmap_ovun.png') 79 | plt.show() 80 | -------------------------------------------------------------------------------- /test/DeglitchTest.m: -------------------------------------------------------------------------------- 1 | classdef DeglitchTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_detection(testCase) 26 | 27 | % Request utest data -> time-series + ground-truth marking 28 | deglitch_fn = utest_request_data({'artifacts','glitch_data.mat'}); 29 | deglitch_data = load(deglitch_fn); 30 | 31 | glitch_flags = process_nst_deglitch('detect_glitches', deglitch_data.nirs_signal); 32 | 33 | if 0 34 | for ipos=1:size(deglitch_data.nirs_signal, 1) 35 | figure(); hold on; 36 | plot(deglitch_data.nirs_signal(ipos, :), 'g', 'LineWidth', 3); 37 | glitches_idx = find(glitch_flags(ipos, :)); 38 | plot(glitches_idx, deglitch_data.nirs_signal(ipos,glitches_idx), 'or', 'MarkerSize', 8); 39 | end 40 | end 41 | 42 | % Check against ground-truth markings 43 | testCase.assertTrue(all(glitch_flags(:)==deglitch_data.glitch_flags(:))); 44 | end 45 | 46 | function test_deglitch(testCase) 47 | 48 | % Request utest data -> time-series + ground-truth marking 49 | deglitch_fn = utest_request_data({'artifacts','glitch_data.mat'}); 50 | deglitch_data = load(deglitch_fn); 51 | 52 | % Import in brainstorm 53 | % subject_name = bst_create_test_subject(''); 54 | deglitched_signal = process_nst_deglitch('Compute', deglitch_data.nirs_signal); 55 | if 0 56 | for ipos=1:size(deglitch_data.nirs_signal, 1) 57 | figure(); hold on; 58 | plot(deglitch_data.nirs_signal(ipos, :), 'b', 'LineWidth', 3); 59 | plot(deglitched_signal(ipos, :), 'r'); 60 | end 61 | end 62 | 63 | % Check result 64 | nb_samples = size(deglitch_data.nirs_signal, 2); 65 | for ipos=1:size(deglitch_data.nirs_signal, 1) 66 | gflags = deglitch_data.glitch_flags(ipos, :); 67 | gflags([1 end]) = 0; 68 | signal_clean = deglitch_data.nirs_signal(ipos, :); 69 | signal_clean(1) = mean(signal_clean(2:3)); 70 | signal_clean(end) = mean(signal_clean(nb_samples-2:nb_samples-1)); 71 | glitches_idx = find(gflags); 72 | signal_clean(gflags) = (signal_clean(glitches_idx-1) + signal_clean(glitches_idx+1)) / 2; 73 | 74 | if 0 75 | figure(); hold on; 76 | plot(deglitch_data.nirs_signal(ipos, :), 'g', 'LineWidth', 3); 77 | plot(signal_clean, 'b', 'LineWidth', 2); 78 | plot(deglitched_signal(ipos, :), 'r'); 79 | plot(glitches_idx, deglitch_data.nirs_signal(ipos,glitches_idx), 'or', 'MarkerSize', 8); 80 | end 81 | 82 | testCase.assertTrue(all_close(signal_clean, deglitched_signal(ipos, :))); 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /bst_plugin/io/nst_io_fetch_sample_data.m: -------------------------------------------------------------------------------- 1 | function varargout = nst_io_fetch_sample_data(data_label, confirm_download) 2 | % Return file names of sample data. Download them if necessary. 3 | % 4 | % [NIRS_FILES, SUBJECT_NAMES] = NST_IO_FETCH_SAMPLE_DATA('template_group_tapping', CONFIRM_DOWNLOAD) 5 | % 6 | 7 | if nargin < 2 8 | confirm_download = 1; 9 | end 10 | 11 | switch data_label 12 | case 'template_group_tapping' 13 | subject_ids = 1:10; 14 | subject_names = arrayfun(@(ii) sprintf('Subject%02d', ii), subject_ids, 'UniformOutput', 0); 15 | nb_subjects = length(subject_ids); 16 | 17 | data_to_fetch = cell(1, nb_subjects * 2); 18 | % Make list of nirs files to fetch: 19 | % sample_data/template_group_tapping/Subject*/S*_tapping.nirs 20 | for subject_id=subject_ids 21 | data_to_fetch{subject_id} = {'sample_data', 'template_group_tapping', ... 22 | subject_names{subject_id}, ... 23 | sprintf('S%02d_tapping.nirs', subject_id)}; 24 | end 25 | % Make list of optode files to fetch: 26 | % sample_data/template_group_tapping/Subject*/optodes.txt 27 | for subject_id=subject_ids 28 | data_to_fetch{nb_subjects + subject_id} = {'sample_data', 'template_group_tapping', ... 29 | subject_names{subject_id}, 'optodes.txt'}; 30 | end 31 | data_fns = nst_request_files(data_to_fetch, confirm_download, nst_get_repository_url(), 1e6); 32 | varargout{1} = data_fns(1:nb_subjects); 33 | varargout{2} = subject_names; 34 | 35 | case 'group_tapping' 36 | subject_names = {'S01' ,'S02','S03', ... 37 | 'S04', 'S05', ... 38 | 'S07', 'S08', 'S09', ... 39 | 'S10', 'S11'}; 40 | 41 | nb_subjects = length(subject_names); 42 | data_to_fetch{3*nb_subjects}={''}; 43 | 44 | for i=1:nb_subjects 45 | data_to_fetch{i}={'Tapping',subject_names{i}, 'data', ['subject_' subject_names{i}(2:3) '.nirs'] }; 46 | data_to_fetch{i+nb_subjects}={'Tapping',subject_names{i}, 'data', 'optodes.txt' }; 47 | data_to_fetch{i+2*nb_subjects}={'Tapping',subject_names{i}, 'data', 'headpoints' }; 48 | end 49 | 50 | data_fns = nst_request_files(data_to_fetch, confirm_download, nst_get_repository_url(), 1e6); 51 | varargout{1} = data_fns(:); 52 | varargout{2} = subject_names; 53 | case 'group_tapping_with_anatomy' 54 | subject_names = {'S01' ,'S02','S03', ... 55 | 'S04', 'S05', ... 56 | 'S07', 'S08', 'S09', ... 57 | 'S10', 'S11'}; 58 | 59 | nb_subjects = length(subject_names); 60 | data_to_fetch{4*nb_subjects}={''}; 61 | 62 | for i=1:nb_subjects 63 | data_to_fetch{i}={'Tapping',subject_names{i}, 'anatomy' }; 64 | data_to_fetch{i+nb_subjects}={'Tapping',subject_names{i}, 'data', ['subject_' subject_names{i}(2:3) '.nirs'] }; 65 | data_to_fetch{i+2*nb_subjects}={'Tapping',subject_names{i}, 'data', 'optodes.txt' }; 66 | data_to_fetch{i+3*nb_subjects}={'Tapping',subject_names{i}, 'data', 'headpoints' }; 67 | end 68 | 69 | data_fns = nst_request_files(data_to_fetch, confirm_download, nst_get_repository_url(), 1e6); 70 | varargout{1} = data_fns(:); 71 | varargout{2} = subject_names; 72 | 73 | 74 | otherwise 75 | error(['Unknown data set: ' data_label]); 76 | end 77 | 78 | end -------------------------------------------------------------------------------- /bst_plugin/math/nst_knnsearch.m: -------------------------------------------------------------------------------- 1 | function [idx, dist] = nst_knnsearch(X,Y,varargin) 2 | 3 | % For compatibility 4 | try 5 | [idx, dist] = knnsearch(X,Y,varargin{:}); 6 | catch 7 | % Alternate implementation from file exchange 8 | % https://www.mathworks.com/matlabcentral/fileexchange/19345-efficient-k-nearest-neighbor-search-using-jit 9 | % 10 | % Licence: 11 | % Copyright (c) 2009, Yi Cao 12 | % All rights reserved. 13 | % 14 | % Redistribution and use in source and binary forms, with or without 15 | % modification, are permitted provided that the following conditions are met: 16 | % 17 | % * Redistributions of source code must retain the above copyright notice, this 18 | % list of conditions and the following disclaimer. 19 | % 20 | % * Redistributions in binary form must reproduce the above copyright notice, 21 | % this list of conditions and the following disclaimer in the documentation 22 | % and/or other materials provided with the distribution 23 | % THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | % AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | % IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | % DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 27 | % FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | % DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | % SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | % CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | % OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | % OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | % 34 | % By Yi Cao at Cranfield University on 25 March 2008 35 | 36 | % Check inputs 37 | all_varagin = {Y X varargin{:}}; 38 | [Q,R,K,fident] = parseinputs(all_varagin{:}); 39 | 40 | % Check outputs 41 | nargoutchk(0,2); 42 | 43 | % C2 = sum(C.*C,2)'; 44 | [N,M] = size(Q); 45 | L=size(R,1); 46 | idx = zeros(N,K); 47 | D = idx; 48 | 49 | if K==1 50 | % Loop for each query point 51 | for k=1:N 52 | d=zeros(L,1); 53 | for t=1:M 54 | d=d+(R(:,t)-Q(k,t)).^2; 55 | end 56 | if fident 57 | d(k)=inf; 58 | end 59 | [D(k),idx(k)]=min(d); 60 | end 61 | else 62 | for k=1:N 63 | d=zeros(L,1); 64 | for t=1:M 65 | d=d+(R(:,t)-Q(k,t)).^2; 66 | end 67 | if fident 68 | d(k)=inf; 69 | end 70 | [s,t]=sort(d); 71 | idx(k,:)=t(1:K); 72 | D(k,:)=s(1:K); 73 | end 74 | end 75 | 76 | if nargout>1 77 | dist=sqrt(D); 78 | end 79 | 80 | end 81 | 82 | end 83 | 84 | 85 | function [Q,R,K,fident] = parseinputs(varargin) 86 | % Copyright (c) 2009, Yi Cao 87 | % See Licence in main function 88 | 89 | % Check input and output 90 | narginchk(1,3); 91 | 92 | Q=varargin{1}; 93 | 94 | if nargin<2 95 | R=Q; 96 | fident = true; 97 | else 98 | fident = false; 99 | R=varargin{2}; 100 | end 101 | 102 | if isempty(R) 103 | fident = true; 104 | R=Q; 105 | end 106 | 107 | if ~fident 108 | fident = isequal(Q,R); 109 | end 110 | 111 | if nargin<3 112 | K=1; 113 | else 114 | K=varargin{3}; 115 | end 116 | 117 | end -------------------------------------------------------------------------------- /scripts/source_space_full_group_pipeline_V1.m: -------------------------------------------------------------------------------- 1 | function source_space_full_group_pipeline_V1() 2 | 3 | % Example for the template- and surface-based full pipeline, using the 4 | % function nst_ppl_1st_level_channel_V1. 5 | % 6 | % This script downloads some sample data of 10 "dummy" subjects (27 Mb) 7 | % Total max amount of data to download: (TODO) Mb, the user is asked for download 8 | % confirmation. 9 | % 10 | % Data are imported in a dedicated protocol, using specific name convention 11 | % handled by nst_ppl_1st_level_channel_V1. 12 | % Preprocessings and processings are then run until group-level analysis: 13 | % 1) Resampling to 5hz 14 | % 2) Detect bad channels 15 | % 3) Convert to delta optical density 16 | % 4) High pass filter 17 | % 5) Project on the cortical surface using head 18 | % 6) 1st level GLM (pre-coloring) 19 | % 7 level GLM with MFX contrast t-maps 20 | % 21 | % This script illustrates a fully functional analysis pipeline that can 22 | % serve as a basis for another custom study. 23 | % 24 | % For a more detailed description, see the wiki page: 25 | % TODO: add wiki page 26 | 27 | %% Setup brainstorm 28 | if ~brainstorm('status') 29 | % Start brainstorm without the GUI if not already running 30 | brainstorm nogui 31 | end 32 | 33 | %% Check Protocol 34 | 35 | protocol_name = 'Tapping_on_template'; 36 | 37 | if isempty(bst_get('Protocol', protocol_name)) 38 | gui_brainstorm('CreateProtocol', protocol_name, 1, 0); % UseDefaultAnat=1, UseDefaultChannel=0 39 | end 40 | 41 | % Set template for default anatomy 42 | nst_bst_set_template_anatomy('Colin27_4NIRS_Jan19'); 43 | 44 | %% Fetch data 45 | % Get list of local nirs files for the group data. 46 | % The function nst_io_fetch_sample_data takes care of downloading data to 47 | % .brainstorm/defaults/nirstorm/sample_data if necessary 48 | [data_fns, subject_names] = nst_io_fetch_sample_data('group_tapping'); 49 | nb_subjects=length(subject_names); 50 | 51 | %% Import data 52 | options = nst_ppl_1st_level_channel_V1('get_options'); 53 | 54 | options.import.subject(1:nb_subjects)=repmat(options.import.subject,1,nb_subjects); 55 | 56 | for i=1:nb_subjects 57 | options.import.subject{i}.name=subject_names{i}; 58 | options.import.subject{i}.nirs_fn=data_fns{i}; 59 | options.import.subject{i}.additional_headpoints=data_fns{i+2*nb_subjects}; 60 | end 61 | 62 | [sFiles, imported] = nst_ppl_1st_level_channel_V1('import_subjects', options); 63 | 64 | % Read stimulation events from AUX channel 65 | for ifile=1:length(sFiles) 66 | if imported(ifile) 67 | % Read events from aux channel 68 | bst_process('CallProcess', 'process_evt_read', sFiles{ifile}, [], ... 69 | 'stimchan', 'NIRS_AUX', ... 70 | 'trackmode', 3, ... % Value: detect the changes of channel value 71 | 'zero', 0); 72 | % Rename event AUX1 -> motor 73 | bst_process('CallProcess', 'process_evt_rename', sFiles{ifile}, [], ... 74 | 'src', 'AUX1', 'dest', 'motor'); 75 | % Convert to extended event-> add duration of 10 sec to all motor events 76 | bst_process('CallProcess', 'process_evt_extended', sFiles{ifile}, [], ... 77 | 'eventname', 'motor', 'timewindow', [0, 10]); 78 | end 79 | end 80 | 81 | %% Run pipeline 82 | 83 | options.MBLL.timewindow=30; 84 | 85 | 86 | 87 | options.GLM_1st_level.stimulation_events = {'motor'}; 88 | options.GLM_1st_level.contrasts(1).label = 'motor'; 89 | options.GLM_1st_level.contrasts(1).vector = '[1 0]'; % a vector of weights, as a string 90 | options.GLM_1st_level.contrast_tstat.do = 1; 91 | 92 | 93 | % Run the pipeline (and save user markings): 94 | nst_ppl_1st_level_channel_V1('analyse', options, subject_names); % Run the full pipeline 95 | %TODO: full reload of GUI tree at end of pipeline 96 | end -------------------------------------------------------------------------------- /scripts/surface_template_full_group_pipeline_V1.m: -------------------------------------------------------------------------------- 1 | function surface_template_full_group_pipeline_V1() 2 | % Example for the template- and surface-based full pipeline, using the 3 | % function NST_PPL_SURFACE_TEMPLATE_V1. 4 | % 5 | % This script downloads some sample data of 10 "dummy" subjects (27 Mb), 6 | % as well as the Colin27_4NIRS template (19 Mb) if not available. 7 | % For the analysis part, precomputed fluence data are also downloaded. 8 | % Total max amount of data to download: 50 Mb, the user is asked for download 9 | % confirmation. 10 | % All downloaded data will be stored in .brainstorm/defaults/nirstorm 11 | % 12 | % Data are imported in brainstorm in a dedicated protocol, using specific 13 | % naming conventions specific to NST_PPL_SURFACE_TEMPLATE_V1. 14 | % Preprocessings and processings are then run, up to group-level analysis: 15 | % 1) Resampling to 5hz 16 | % 2) Bad channels detection 17 | % 3) Conversion to delta optical density 18 | % 4) High pass filter 19 | % 5) Projection on the cortical surface using head model 20 | % 6) 1st level GLM with pre-coloring 21 | % 7) group-level GLM with MFX contrast t-maps 22 | % 23 | % This script illustrates a minimal fully functional analysis pipeline that can 24 | % serve as a basis for another custom study. 25 | % 26 | % For a more detailed description, see the wiki page: 27 | % https://github.com/Nirstorm/nirstorm/wiki/%5BWIP%5D-GLM-pipeline:-surface-and-template-based 28 | % 29 | % For a more comprehensive example script, see: 30 | % surface_template_full_group_pipeline_V1_all_opts.m 31 | 32 | %% Setup brainstorm 33 | if ~brainstorm('status') 34 | % Start brainstorm without the GUI if not already running 35 | brainstorm nogui 36 | end 37 | 38 | %% Check Protocol 39 | protocol_name = 'TestSurfaceTemplateGroupPipelineV1'; 40 | if isempty(bst_get('Protocol', protocol_name)) 41 | gui_brainstorm('CreateProtocol', protocol_name, 1, 0); % UseDefaultAnat=1, UseDefaultChannel=0 42 | end 43 | 44 | % Set template for default anatomy 45 | nst_bst_set_template_anatomy('Colin27_4NIRS_Jan19'); 46 | 47 | %% Fetch data 48 | % Get list of local nirs files for the group data. 49 | % The function nst_io_fetch_sample_data takes care of downloading data to 50 | % .brainstorm/defaults/nirstorm/sample_data if necessary 51 | [nirs_fns, subject_names] = nst_io_fetch_sample_data('template_group_tapping'); 52 | 53 | %% Import data 54 | options = nst_ppl_surface_template_V1('get_options'); % get default pipeline options 55 | sFiles = nst_ppl_surface_template_V1('import', options, nirs_fns, subject_names); 56 | 57 | % Read stimulation events from AUX channel 58 | for ifile=1:length(sFiles) 59 | evt_data = load(file_fullpath(sFiles{ifile}), 'Events'); 60 | if ~any(strcmp({evt_data.Events.label}, 'motor')) % Insure that events were not already loaded 61 | bst_process('CallProcess', 'process_evt_read', sFiles{ifile}, [], ... 62 | 'stimchan', 'NIRS_AUX', ... 63 | 'trackmode', 3, ... % Value: detect the changes of channel value 64 | 'zero', 0); 65 | % Rename event AUX1 -> motor 66 | bst_process('CallProcess', 'process_evt_rename', sFiles{ifile}, [], ... 67 | 'src', 'AUX1', 'dest', 'motor'); 68 | % Convert to extended event-> add duration of 30 sec to all motor events 69 | bst_process('CallProcess', 'process_evt_extended', sFiles{ifile}, [], ... 70 | 'eventname', 'motor', 'timewindow', [0, 30]); 71 | end 72 | end 73 | 74 | %% Run pipeline 75 | options.GLM_1st_level.stimulation_events = {'motor'}; 76 | options.GLM_1st_level.contrasts(1).label = 'motor'; 77 | options.GLM_1st_level.contrasts(1).vector = '[1 0]'; % a vector of weights, as a string 78 | 79 | % Run the pipeline: 80 | nst_ppl_surface_template_V1('analyse', options, subject_names); % Run the full pipeline 81 | end -------------------------------------------------------------------------------- /bst_plugin/math/nst_math_build_basis_dct.m: -------------------------------------------------------------------------------- 1 | function [cmat, band_indexes] = nst_math_build_basis_dct(nsamples, sampling_rate, freq_ranges, ortho) 2 | % build_basis_cosine - Build a Cosine basis within specific frequency 3 | % ranges (or bands) 4 | % 5 | % Synopsis 6 | % [cmat] = build_basis_cosine(nsamples, sampling_rate, freq_ranges) 7 | % 8 | % Description 9 | % build an orthogonal matrix whose regressors are built from cosine 10 | % functions spanning specified frequency bands. In practice, building 11 | % such matrix consists in 12 | % gathering sub-columns of a Discrete Cosine Transform (DCT) matrix, 13 | % that corresponds to the specified frequencies. 14 | % 15 | % Inputs ([]s are optional) 16 | % (scalar int) nsamples 17 | % number of samples -> number of lines in the basis 18 | % (scalar float) sampling_rate 19 | % sampling rate of the signal 20 | % (matrix) freq_ranges 21 | % F x 2 matrix defining the F frequency-bands (min and max freqs) 22 | % 23 | % Outputs ([]s are optional) 24 | % (matrix) 25 | % nsamples x ncols matrix for the orthogonal 26 | % cosine basis. F is the number of frequency bands as defined by the 27 | % number of lines in freq_ranges. 28 | % 29 | % Examples: signal of 100 sec, 10 Hz sampling, physiological freq band 30 | % (heart beat, respiration, Meyer) 31 | % cmat = build_basis_cosine(1000, 10, [.2 .6; .8 1.5; .01 .1;]); 32 | % 33 | % TODO: assert that given freq ranges are not overlapping 34 | 35 | % cache in tmp to avoid costly recomputations ... 36 | % TODO: expose cached file name to clean properly after 37 | 38 | global nirs_deconv_cache 39 | 40 | cache_dir = '/tmp/nirs_dyna'; 41 | band_indexes = {}; 42 | if nargin < 4 43 | ortho = 0; 44 | end 45 | 46 | sfr = {}; 47 | for ifr=1:size(freq_ranges,1) 48 | sfr{ifr} = sprintf('%1.6f_%1.6f', freq_ranges(ifr, 1), freq_ranges(ifr, 2) ); 49 | end 50 | 51 | idx_ranges = freq_ranges * nsamples / (sampling_rate/2); 52 | 53 | if exist('nirs_deconv_cache', 'var') && isfield(nirs_deconv_cache, 'cmat') && ... 54 | nirs_deconv_cache.cmat.nsamples == nsamples && ... 55 | nirs_deconv_cache.cmat.sampling_rate == sampling_rate && ... 56 | nirs_deconv_cache.cmat.ortho == ortho 57 | cmat = nirs_deconv_cache.cmat.cmat; 58 | band_indexes = nirs_deconv_cache.cmat.band_indexes; 59 | else 60 | cache_fn = fullfile(cache_dir, ... 61 | sprintf('dct_mat_%d_%1.6f_%s_%d.mat', nsamples, ... 62 | sampling_rate, strjoin(sfr, '_'), ortho)); 63 | 64 | warning(['Cache file used for DCT matrix has to be cleaned by user: ' ... 65 | cache_fn]); 66 | 67 | if ~exist(cache_fn, 'file') 68 | %cmat = dctmtx(nsamples)'; 69 | cmat = nst_math_dctmtx(nsamples)'; 70 | sub_idx = []; 71 | cur_idx = 1; 72 | for fr=1:size(freq_ranges,1) % loop over freq ranges 73 | 74 | idx_ranges(2) = min(idx_ranges(2), nsamples); 75 | cur_idx_range = max(1, floor(idx_ranges(fr, 1))):ceil(idx_ranges(fr, 2)); 76 | sub_idx = [sub_idx cur_idx_range]; 77 | band_indexes{fr} = cur_idx:(cur_idx+length(cur_idx_range)-1); 78 | cur_idx = cur_idx + length(cur_idx_range); 79 | end 80 | cmat = cmat(:, sub_idx); 81 | 82 | if ortho 83 | cmat = orth(cmat); 84 | end 85 | if ~exist(cache_dir, 'dir') 86 | mkdir(cache_dir); 87 | end 88 | save(cache_fn, 'cmat', 'band_indexes'); 89 | else 90 | cmat_data = load(cache_fn); 91 | cmat = cmat_data.cmat; 92 | band_indexes = cmat_data.band_indexes; 93 | end 94 | nirs_deconv_cache.cmat.cmat = cmat; 95 | nirs_deconv_cache.cmat.band_indexes = band_indexes; 96 | nirs_deconv_cache.cmat.nsamples = nsamples; 97 | nirs_deconv_cache.cmat.ortho = ortho; 98 | nirs_deconv_cache.cmat.sampling_rate = sampling_rate; 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /test/RemoteDataTest.m: -------------------------------------------------------------------------------- 1 | classdef RemoteDataTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function create_tmp_dir(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | end 13 | end 14 | 15 | methods(TestMethodTeardown) 16 | function rm_tmp_dir(testCase) 17 | rmdir(testCase.tmp_dir, 's'); 18 | end 19 | end 20 | 21 | methods(Test) 22 | function test_file_request(testCase) 23 | %% Test download only when file not available locally 24 | % Prepare local files 25 | utest_dir = fullfile(nst_get_local_user_dir(), 'unittest'); 26 | if ~exist(utest_dir, 'dir') 27 | mkdir(utest_dir); 28 | end 29 | fout = fopen(fullfile(nst_get_local_user_dir(), 'unittest', 'dummy.txt'), 'w'); 30 | fclose(fout); 31 | data_fns = {{'unittest', 'dummy.txt'}, {'unittest', 'motor_data', 'fiducials.txt'}, ... 32 | {'unittest', 'motor_data', 'optodes.txt'}}; 33 | for ifns=2:length(data_fns) % make sure some files are not locally available 34 | fn = fullfile(nst_get_local_user_dir(), strjoin(data_fns{ifns}, filesep)); 35 | if exist(fn, 'file') 36 | delete(fn); 37 | end 38 | end 39 | % Request files 40 | [local_fns, downloaded_fns] = nst_request_files(data_fns, 0); 41 | 42 | % Check results 43 | assert(all(cellfun(@(fn) ~isempty(strfind(fn, nst_get_local_user_dir())), local_fns))); 44 | assert(all(cellfun(@(fn) exist(fn, 'file'), local_fns))); 45 | assert(length(downloaded_fns) == 2); 46 | assert(strcmp(downloaded_fns{1}, strjoin({nst_get_repository_url(), strjoin(data_fns{2}, '/')}, '/'))); 47 | assert(strcmp(downloaded_fns{2}, strjoin({nst_get_repository_url(), strjoin(data_fns{3}, '/')}, '/'))); 48 | 49 | %% Test error when some requested file not available locally nor remotely 50 | data_fns = {{'unittest', 'dummy.txt'}, {'unittest', 'malcolm_crowe'}} ; 51 | try 52 | nst_request_files(data_fns, 0); 53 | catch ME 54 | testCase.assertMatches(ME.identifier, 'NIRSTORM:RemoteFilesNotFound'); 55 | files_not_found = strsplit(ME.message, '\n'); 56 | files_not_found = files_not_found(2:end); 57 | assert(nst_url_equal(files_not_found{1}, ... 58 | strjoin({nst_get_repository_url(), strjoin(data_fns{2}, '/')}, '/'))); 59 | end 60 | 61 | end 62 | 63 | function test_fluence_download(testCase) 64 | utest_bst_setup(); 65 | 66 | data_url = [nst_get_repository_url() '/fluence/']; 67 | vol_shape = [256,256,256]; 68 | [fluence_volumes, fluence_ref] = process_nst_import_head_model('request_fluences', [3000 4002], ... 69 | 'MRI: Colin27 4NIRS', 685, ... 70 | data_url, nan, nan, ... 71 | vol_shape); 72 | assert(length(fluence_volumes) == 2); 73 | assert(length(fluence_ref) == 2); 74 | assert(numel(fluence_volumes{1}{1}) == prod(vol_shape)); 75 | assert(numel(fluence_ref{1}{1}) == 3); 76 | 77 | % TODO: test failed fluence download 78 | % TODO: test with VOI masking 79 | utest_clean_bst(); 80 | end 81 | 82 | end 83 | end 84 | 85 | function flag = nst_url_equal(u1, u2) 86 | flag = strcmp(normalize_url(u1), normalize_url(u2)); 87 | end 88 | 89 | function u = normalize_url(u) 90 | u = regexprep(u,'//+', '/'); 91 | end 92 | -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_glm_apply_filter.m: -------------------------------------------------------------------------------- 1 | function model = nst_glm_apply_filter(model,filter_name, varargin ) 2 | %NST_GLM_APPLY_FILTER Apply filter on the regressor of the column matrix 3 | %but only apply when it's ok to apply it (for exemple, we don't apply a 4 | %high-pass filter on the constant regressor). Condition about what filter 5 | %can be applied can be found in model.accept_filter. For a channel i, if 6 | % model.accept_filter = 0 then no filter can be applied 7 | % model.accept_filter = 1 then high-pass can be applied 8 | % model.accept_filter = 2 then low-pass can be applied 9 | % model.accept_filter = 3 then both can be applied 10 | 11 | 12 | switch filter_name 13 | 14 | case 'lpf' 15 | % Applied the GLM low pass filter; 16 | % Usage : 17 | % lpf = full(lpf_hrf(hrf, size(y, 1))); 18 | % model = nst_glm_apply_filter(model,'lpf', lpf ); 19 | 20 | lpf=varargin{1}; 21 | 22 | ind=find(model.accept_filter >= 2); 23 | 24 | model.X(:,ind)= lpf*model.X(:,ind); 25 | model.X(:,ind) = model.X(:,ind) ./ repmat(max(abs(model.X(:,ind))), size(model.X(:,ind),1) ,1); 26 | 27 | case 'IIR_highpass' 28 | % Applied the GLM IIR high pass filter; 29 | % Usage model = nst_glm_apply_filter(model,'IIR_highpass', hpf_low_cutoff ); 30 | cutoff=varargin{1}; 31 | if cutoff > 0 % Security check :) 32 | ind=find(model.accept_filter == 1 | model.accept_filter == 3); 33 | model.X(:,ind)= process_nst_iir_filter('Compute', model.X(:,ind), model.fs, ... 34 | 'highpass', cutoff, 0, 2, 0); 35 | end 36 | case 'IIR_bp' 37 | % Applied the GLM IIR high pass filter; 38 | % Usage model = nst_glm_apply_filter(model,'IIR_highpass', hpf_low_cutoff ); 39 | low_cutoff = varargin{1}; 40 | high_cutoff = varargin{2}; 41 | if nargin < 5 42 | order = 3; 43 | else 44 | order = varargin{3}; 45 | end 46 | 47 | ind=find(model.accept_filter == 1 | model.accept_filter == 3); 48 | model.X(:,ind)= process_nst_iir_filter('Compute', model.X(:,ind), model.fs, ... 49 | 'bandpass', low_cutoff, high_cutoff, order, 0); 50 | case 'FIR_bp' 51 | low_cutoff = varargin{1}; 52 | high_cutoff = varargin{2}; 53 | 54 | if nargin < 5 55 | is_relax = 1; 56 | else 57 | is_relax = varargin{3}; 58 | end 59 | if nargin < 6 60 | TranBand = min(0.5*low_cutoff, 0.2*high_cutoff); % completely arbritary 61 | else 62 | TranBand = varargin{4}; 63 | end 64 | 65 | ind=find(model.accept_filter == 1 | model.accept_filter == 3); 66 | 67 | [x, FiltSpec, Messages] = process_bandpass('Compute', model.X(:,ind)', model.fs, low_cutoff, high_cutoff, [], 0,is_relax , TranBand); 68 | model.X(:,ind)= x'; 69 | 70 | case 'DCT_filter' 71 | % Applied the GLM IIR DCT filter; 72 | % Usage model = nst_glm_apply_filter(model,'DCT', low_period_cuttoff ); 73 | cutoff=varargin{1}; 74 | 75 | model_dct= nst_glm_initialize_model(model.time); 76 | model_dct=nst_glm_add_regressors(model_dct,'constant'); 77 | model_dct=nst_glm_add_regressors(model_dct,'linear'); 78 | 79 | if cutoff > 0 80 | model_dct=nst_glm_add_regressors(model_dct,'DCT',[1/model.time(end) 1/cutoff],{'LFO'}); 81 | end 82 | 83 | ind=find(model.accept_filter == 1 | model.accept_filter == 3); 84 | [B,proj_X] = nst_glm_fit_B(model_dct,model.X(:,ind), 'SVD'); 85 | 86 | model.X(:,ind) = model.X(:,ind) - model_dct.X*B; 87 | end 88 | 89 | end 90 | 91 | -------------------------------------------------------------------------------- /bst_plugin/math/nst_mne_lcurve_MAP.m: -------------------------------------------------------------------------------- 1 | function J = nst_mne_lcurve_MAP(HM,OPTIONS) 2 | % nst_mne_lcurve - this function solve the inverse probleme using a l-curve 3 | % approach in the MAP formalism. This approach is inefficient and should 4 | % not be used. Consider using nst_mne_lcurve instead. 5 | % Input: HM - struct 6 | % | - HM.Gain : Gain matrix 7 | % OPTIONS - struct 8 | % | - OPTIONS.Data : data matrix (nChannel x nTimes) 9 | % | - OPTIONS.DataTime: Corresponding time (1xnTimes) 10 | % | - OPTIONS.BaselineSegment: Segment used for baseline [start,end] 11 | % | - OPTIONS.NoiseCov_recompute: true if using the baseline to 12 | % estimate the noise covariance, if false, the noise covariance is 13 | % identity 14 | % | - OPTIONS.depth_weigth_MNE: depth-weighting factor between 0 and 1 15 | % Output: J - reconstructed time-course on the cortex (nVertex x nTimes) 16 | 17 | % @============================================================================= 18 | % This function is part of the Brainstorm software: 19 | % https://neuroimage.usc.edu/brainstorm 20 | % 21 | % Copyright (c) University of Southern California & McGill University 22 | % This software is distributed under the terms of the GNU General Public License 23 | % as published by the Free Software Foundation. Further details on the GPLv3 24 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 25 | % 26 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 27 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 28 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 29 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 30 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 31 | % 32 | % For more information type "brainstorm license" at command prompt. 33 | % =============================================================================@ 34 | % 35 | % Authors: Edouard Delaire, 2024 36 | 37 | % selection of the data: 38 | sample_baseline = be_closest(OPTIONS.BaselineSegment([1 end]), OPTIONS.DataTime); 39 | baseline = OPTIONS.Data(:,sample_baseline(1):sample_baseline(2)); 40 | 41 | sample_data = be_closest(OPTIONS.TimeSegment([1 end]), OPTIONS.DataTime); 42 | M = OPTIONS.Data(:,sample_data(1):sample_data(2)); 43 | 44 | % Normalization by using the mean standard deviation of baseline 45 | 46 | SD = std(baseline,[],2); 47 | MSD = mean(SD); 48 | 49 | %Normalize datas, baseline, gain matrix with the mean std dev 50 | M = M./MSD; 51 | baseline = baseline./MSD; 52 | G = HM.Gain./MSD; 53 | 54 | % Compute inverse of noise covariance matrices 55 | if OPTIONS.NoiseCov_recompute 56 | Pd = inv(diag(diag(real(cov(baseline'))))); 57 | else 58 | Pd = eye(size(M,1)); 59 | end 60 | 61 | p = OPTIONS.depth_weigth_MNE; 62 | if OPTIONS.NoiseCov_recompute 63 | Ps = diag(power(diag(G'*Pd*G),p)); 64 | else 65 | Ps = diag(power(diag(G'*G),p)); 66 | end 67 | W = sqrt(Ps); 68 | 69 | % Parameter for l-curve 70 | param1 = [0.1:0.1:1 1:5:100 100:100:1000]; %the param1 list we tested in wMNE_org 71 | 72 | % Scale alpha using trace(G*G')./trace(W'*W) 73 | scale = trace(G*G')./ trace(Ps) ; 74 | alpha = param1.*scale; 75 | 76 | % Pre-compute 77 | GPG = G'*Pd*G; 78 | GP = G'*Pd; 79 | Fit = zeros(1,length(param1)); 80 | Prior = zeros(1,length(param1)); 81 | 82 | bst_progress('start', 'wMNE, solving MNE by L-curve ... ' , 'Solving MNE-MAP by L-curve ... ', 1, length(param1)); 83 | for iAlpha = 1:length(alpha) 84 | Kernel = inv( GPG + alpha(iAlpha) * Ps)*GP; 85 | J = Kernel * M; 86 | 87 | Fit(iAlpha) = norm(M-G*J); % Define Fit as a function of alpha 88 | Prior(iAlpha) = norm(W*J); % Define Prior as a function of alpha 89 | 90 | bst_progress('inc', 1); 91 | end 92 | 93 | % Fid alpha optimal based on l-curve 94 | [~,Index] = min(Fit/max(Fit)+Prior/max(Prior)); 95 | 96 | Kernel = inv( GPG + alpha(Index) * Ps)*GP; 97 | J = Kernel * M; 98 | 99 | bst_progress('text', 'wMNE, solving MNE by L-curve ... done'); 100 | 101 | end -------------------------------------------------------------------------------- /bst_plugin/forward/process_nst_cpt_cortex_to_head_distance.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_cpt_cortex_to_head_distance( varargin ) 2 | % Compute the distance of each vertex of the cortical surface to the head 3 | % in mm 4 | 5 | % @============================================================================= 6 | % This software is part of the Brainstorm software: 7 | % http://neuroimage.usc.edu/brainstorm 8 | % 9 | % Copyright (c)2000-2013 Brainstorm by the University of Southern California 10 | % This software is distributed under the terms of the GNU General Public License 11 | % as published by the Free Software Foundation. Further details on the GPL 12 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 13 | % 14 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 15 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 16 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 17 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 18 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 19 | % 20 | % For more information type "brainstorm license" at command prompt. 21 | % =============================================================================@ 22 | % 23 | % Authors: Edouard Delaire (2023) 24 | 25 | eval(macro_method); 26 | end 27 | 28 | function sProcess = GetDescription() 29 | % Description the process 30 | sProcess.Comment = 'Compute distance from cortical surface to head surface'; 31 | sProcess.Category = 'Custom'; 32 | sProcess.SubGroup = {'NIRS', 'Sources'}; 33 | sProcess.Index = 1406; 34 | sProcess.Description = ''; 35 | % Definition of the input accepted by this process 36 | sProcess.InputTypes = {'import'}; 37 | % Definition of the outputs of this process 38 | sProcess.OutputTypes = {'results'}; 39 | sProcess.nInputs = 1; 40 | sProcess.nMinFiles = 0; 41 | sProcess.isSeparator = 1; 42 | 43 | % Option: Subject name 44 | sProcess.options.subjectname.Comment = 'Subject name:'; 45 | sProcess.options.subjectname.Type = 'subjectname'; 46 | sProcess.options.subjectname.Value = ''; 47 | 48 | end 49 | 50 | %% ===== FORMAT COMMENT ===== 51 | function Comment = FormatComment(sProcess) 52 | Comment = sProcess.Comment; 53 | end 54 | 55 | 56 | %% ===== RUN ===== 57 | function OutputFiles = Run(sProcess, sInputs) 58 | OutputFiles = {}; 59 | 60 | % Get subject name 61 | if isfield(sProcess.options, 'subjectname') && ~isempty(sProcess.options.subjectname.Value) 62 | SubjectName = file_standardize(sProcess.options.subjectname.Value); 63 | else 64 | SubjectName = sInputs.SubjectName; 65 | end 66 | if isempty(SubjectName) 67 | bst_report('Error', sProcess, [], 'Subject name is empty.'); 68 | return; 69 | end 70 | 71 | % ===== GET SUBJECT ===== 72 | % Get subject 73 | [sSubject, iSubject] = bst_get('Subject', SubjectName); 74 | if isempty(iSubject) 75 | bst_report('Error', sProcess, [], ['Subject "' SubjectName '" does not exist.']); 76 | return 77 | end 78 | 79 | % Obtain the cortical surface 80 | sCortex = in_tess_bst(sSubject.Surface(sSubject.iCortex).FileName); 81 | sHead = in_tess_bst(sSubject.Surface(sSubject.iScalp).FileName); 82 | 83 | distance = Compute(sCortex, sHead); 84 | 85 | iStudy = db_add_condition(SubjectName, 'Distance'); 86 | sStudy = bst_get('Study', iStudy); 87 | 88 | 89 | OutputFile = bst_process('GetNewFilename', bst_fileparts(sStudy.FileName), ... 90 | ['results_distance_cortex_to_head']); 91 | 92 | % ===== CREATE FILE STRUCTURE ===== 93 | ResultsMat = db_template('resultsmat'); 94 | ResultsMat.Comment = 'Cortex to Head distance'; 95 | ResultsMat.Function = ''; 96 | ResultsMat.Time = [0]; 97 | ResultsMat.ImageGridAmp = distance*1000; 98 | ResultsMat.ChannelFlag = []; 99 | ResultsMat.GoodChannel = []; 100 | ResultsMat.DisplayUnits = 'mm'; 101 | ResultsMat.SurfaceFile = sSubject.Surface(sSubject.iCortex).FileName; 102 | % History 103 | ResultsMat = bst_history('add', ResultsMat, 'compute', 'Compute distance fron cortex to head'); 104 | % Save new file structure 105 | bst_save(OutputFile, ResultsMat, 'v6'); 106 | % Update database 107 | db_add_data(iStudy, OutputFile, ResultsMat); 108 | OutputFiles = {OutputFile}; 109 | 110 | end 111 | 112 | function distance = Compute(surfaceA, surfaceB) 113 | % For every point i in surfaceA, distance(i) is the minimum distance from 114 | % that point to anypoint in surfaceB 115 | 116 | x = surfaceA.Vertices; 117 | y = surfaceB.Vertices; 118 | d = nst_pdist(x,y); 119 | distance = min(d,[],2); 120 | end 121 | 122 | -------------------------------------------------------------------------------- /bst_plugin/math/nst_mne_lcurve.m: -------------------------------------------------------------------------------- 1 | function J = nst_mne_lcurve(HM,OPTIONS) 2 | % nst_mne_lcurve - this function solve the inverse probleme using a l-curve 3 | % approach 4 | % Input: HM - struct 5 | % | - HM.Gain : Gain matrix 6 | % OPTIONS - struct 7 | % | - OPTIONS.Data : data matrix (nChannel x nTimes) 8 | % | - OPTIONS.DataTime: Corresponding time (1xnTimes) 9 | % | - OPTIONS.BaselineSegment: Segment used for baseline [start,end] 10 | % | - OPTIONS.NoiseCov_recompute: true if using the baseline to 11 | % estimate the noise covariance, if false, the noise covariance is 12 | % identity 13 | % | - OPTIONS.depth_weigth_MNE: depth-weighting factor between 0 and 1 14 | % Output: J - reconstructed time-course on the cortex (nVertex x nTimes) 15 | 16 | % @============================================================================= 17 | % This function is part of the Brainstorm software: 18 | % https://neuroimage.usc.edu/brainstorm 19 | % 20 | % Copyright (c) University of Southern California & McGill University 21 | % This software is distributed under the terms of the GNU General Public License 22 | % as published by the Free Software Foundation. Further details on the GPLv3 23 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 24 | % 25 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 26 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 27 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 28 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 29 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 30 | % 31 | % For more information type "brainstorm license" at command prompt. 32 | % =============================================================================@ 33 | % 34 | % Authors: Edouard Delaire, 2024 35 | 36 | % selection of the data: 37 | sample_baseline = be_closest(OPTIONS.BaselineSegment([1 end]), OPTIONS.DataTime); 38 | baseline = OPTIONS.Data(:,sample_baseline(1):sample_baseline(2)); 39 | 40 | sample_data = be_closest(OPTIONS.TimeSegment([1 end]), OPTIONS.DataTime); 41 | M = OPTIONS.Data(:,sample_data(1):sample_data(2)); 42 | 43 | % Normalization by using the mean standard deviation of baseline 44 | 45 | SD = std(baseline,[],2); 46 | MSD = mean(SD); 47 | 48 | %Normalize datas, baseline, gain matrix with the mean std dev 49 | M = M./MSD; 50 | baseline = baseline./MSD; 51 | G = HM.Gain./MSD; 52 | 53 | % Compute inverse of noise covariance matrices 54 | if OPTIONS.NoiseCov_recompute 55 | Sigma_d = diag(diag(real(cov(baseline')))); 56 | else 57 | Sigma_d = eye(size(M,1)); 58 | end 59 | 60 | p = OPTIONS.depth_weigth_MNE; 61 | if OPTIONS.NoiseCov_recompute 62 | Sigma_s = diag(power(diag(G'*inv(Sigma_d)*G),-p)); 63 | else 64 | Sigma_s = diag(power(diag(G'*G),-p)); 65 | end 66 | 67 | 68 | % Pre-compute matrix 69 | GSG = G * Sigma_s * G'; 70 | SG = Sigma_s * G'; 71 | 72 | % Note W*S*G: W = Sigma_s^-0.5 so W*Sigma_s = Sigma_s^0.5 73 | wSG = sqrt(Sigma_s) * G'; 74 | 75 | % Parameter for l-curve 76 | param1 = [0.1:0.1:1 1:5:100 100:100:1000]; 77 | 78 | % Scale alpha using trace(G*G')./trace(W'*W) 79 | scale = trace(G*G')./ trace(inv(Sigma_s)); 80 | alpha = param1.*scale; 81 | 82 | % Pre-compute data decomposition 83 | [U,S] = svd(M,'econ'); 84 | 85 | 86 | Fit = zeros(1,length(alpha)); 87 | Prior = zeros(1,length(alpha)); 88 | 89 | bst_progress('start', 'wMNE, solving MNE by L-curve ... ' , 'Solving MNE by L-curve ... ', 1, length(param1)); 90 | for iAlpha = 1:length(alpha) 91 | 92 | inv_matrix = inv( GSG + alpha(iAlpha) * Sigma_d ); 93 | 94 | residual_kernal = eye(size(M,1)) - GSG * inv_matrix; 95 | wKernel = wSG*inv_matrix; 96 | 97 | 98 | % Estimate the corresponding norm 99 | R = qr(residual_kernal*U); 100 | R = triu(R); % BUGFIX for old version of matlab (<2022a). 101 | Fit(iAlpha) = norm(R*S); 102 | 103 | R = qr(wKernel*U); 104 | R = triu(R); % BUGFIX for old version of matlab (<2022a). 105 | Prior(iAlpha) = norm(R*S); 106 | 107 | 108 | bst_progress('inc', 1); 109 | end 110 | 111 | % Fid alpha optimal based on l-curve 112 | [~,Index] = min(Fit/max(Fit)+Prior/max(Prior)); 113 | 114 | Kermel = SG * inv( GSG + alpha(Index) * Sigma_d ); 115 | J = Kermel*M; 116 | 117 | 118 | bst_progress('text', 'wMNE, solving MNE by L-curve ... done'); 119 | end 120 | -------------------------------------------------------------------------------- /scripts/surface_group_pipeline_V1.m: -------------------------------------------------------------------------------- 1 | function surface_group_pipeline_V1() 2 | % Example for the template- and surface-based full pipeline, using the 3 | % function NST_PPL_SURFACE_TEMPLATE_V1. 4 | % 5 | % This script downloads some sample data of 10 "dummy" subjects (27 Mb), 6 | % as well as the Colin27_4NIRS template (19 Mb) if not available. 7 | % All downloaded data will be stored in .brainstorm/defaults/nirstorm 8 | % 9 | % Data are imported in brainstorm in a dedicated protocol, using specific 10 | % naming conventions specific to NST_PPL_SURFACE_TEMPLATE_V1. 11 | % Preprocessings and processings are then run up to group-level analysis: 12 | % 1) Resampling to 5hz 13 | % 2) Bad channels detection 14 | % 3) Conversion to delta optical density 15 | % 4) High pass filter 16 | % 5a) Compute head model using fluences 17 | % 5b) Projection on the cortical surface using head model 18 | % 6) 1st level GLM with pre-coloring 19 | % 7) group-level GLM with MFX contrast t-maps 20 | % 21 | % This script illustrates a minimal fully functional analysis pipeline that can 22 | % serve as a basis for another custom study. 23 | % 24 | % For a more detailed description, see the wiki page: 25 | % https://github.com/Nirstorm/nirstorm/wiki/%5BWIP%5D-GLM-pipeline:-surface-and-template-based 26 | % 27 | % For a more comprehensive example script, see: 28 | % surface_template_full_group_pipeline_V1_all_opts.m 29 | 30 | %% Setup brainstorm 31 | if ~brainstorm('status') 32 | % Start brainstorm without the GUI if not already running 33 | brainstorm nogui 34 | end 35 | 36 | %% Check Protocol 37 | 38 | protocol_name = 'SurfaceGroupPipelineV1'; 39 | 40 | if isempty(bst_get('Protocol', protocol_name)) 41 | gui_brainstorm('CreateProtocol', protocol_name, 1, 0); % UseDefaultAnat=1, UseDefaultChannel=0 42 | end 43 | 44 | 45 | % Set template for default anatomy 46 | nst_bst_set_template_anatomy('Colin27_4NIRS_Jan19'); 47 | 48 | %% Fetch data 49 | % Get list of local nirs files for the group data. 50 | % The function nst_io_fetch_sample_data takes care of downloading data to 51 | % .brainstorm/defaults/nirstorm/sample_data if necessary 52 | [data_fns, subject_names] = nst_io_fetch_sample_data('group_tapping_with_anatomy'); 53 | nb_subjects=length(subject_names); 54 | 55 | 56 | mri_folders=data_fns(1:nb_subjects); 57 | nirs_fns = data_fns((1+nb_subjects):2*nb_subjects); 58 | headpoints=data_fns((1+3*nb_subjects):4*nb_subjects); 59 | 60 | 61 | %% Import data 62 | options = nst_ppl_surface_V1('get_options'); 63 | options.import.subject(1:nb_subjects)=repmat(options.import.subject,1,nb_subjects); 64 | 65 | options.import.mri_folder_type='FreeSurfer'; 66 | options.import.nvertices = 25000; 67 | options.import.aseg=1; 68 | 69 | options.head_model.surface = 'mid_25002V'; 70 | 71 | 72 | for i=1:nb_subjects 73 | options.import.subject{i}.name=subject_names{i}; 74 | options.import.subject{i}.nirs_fn=nirs_fns{i}; 75 | options.import.subject{i}.mri_folder=mri_folders{i}; 76 | options.import.subject{i}.additional_headpoints=headpoints{i}; 77 | end 78 | 79 | [sFiles, imported] = nst_ppl_surface_V1('import_subjects', options); 80 | 81 | % Read stimulation events from AUX channel 82 | for ifile=1:length(sFiles) 83 | if imported(ifile) 84 | % Read events from aux channel 85 | bst_process('CallProcess', 'process_evt_read', sFiles{ifile}, [], ... 86 | 'stimchan', 'NIRS_AUX', ... 87 | 'trackmode', 3, ... % Value: detect the changes of channel value 88 | 'zero', 0); 89 | % Rename event AUX1 -> motor 90 | bst_process('CallProcess', 'process_evt_rename', sFiles{ifile}, [], ... 91 | 'src', 'AUX1', 'dest', 'motor'); 92 | % Convert to extended event-> add duration of 30 sec to all motor events 93 | bst_process('CallProcess', 'process_evt_extended', sFiles{ifile}, [], ... 94 | 'eventname', 'motor', 'timewindow', [0, 10]); 95 | end 96 | end 97 | 98 | %% Run pipeline 99 | options.fluences.export_dir='/NAS/home/edelaire/Documents/output_analysis/fluences'; 100 | options.fluences.nphoton=100; 101 | options.fluences.thresh=0; 102 | 103 | 104 | % Recompute the headmodel for each subject 105 | options.head_model.subject_specific=1; 106 | 107 | options.GLM_1st_level.contrast_tstat.do = 1; 108 | 109 | options.GLM_1st_level.stimulation_events = {'motor'}; 110 | options.GLM_1st_level.contrasts(1).label = 'motor'; 111 | options.GLM_1st_level.contrasts(1).vector = '[1 0]'; % a vector of weights, as a string 112 | 113 | % Run the pipeline (and save user markings): 114 | nst_ppl_surface_V1('analyse', options, subject_names); % Run the full pipeline 115 | %TODO: full reload of GUI tree at end of pipeline 116 | end 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /test/SpreeTest.m: -------------------------------------------------------------------------------- 1 | classdef SpreeTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | function test_detection(testCase) 25 | % Load forged data to test activity detection 26 | % activity is a struct array with fields: 27 | % - condition_name (str) 28 | % - activ_chans: cell array of channel indexes (int) that are 29 | % activating for this condition 30 | % - activ_scout: bst scout gathering all cortical vertices 31 | % that are activating for this condition 32 | [nirs_chan_fn, nirs_cortex_hbo, nirs_cortex_hbr, activity] = load_activity_test_subject(); 33 | 34 | 35 | if 0 36 | % Requires figtk 37 | fig_options.DefaultFigureVisible = 'on'; 38 | fig_options.fig_height = 5; 39 | fig_options.fig_width = 6; 40 | fig_options.DefaultAxesFontSize = 22; 41 | fig_options.DefaultTextFontSize = 22; 42 | figtk_setup(fig_options); 43 | 44 | output_fig_dir = '/tmp/nst_spree_utest/'; 45 | else 46 | output_fig_dir = ''; 47 | end 48 | 49 | spree_result_chan = bst_process('CallProcess', 'process_nst_spree', nirs_chan_fn, [], ... 50 | 'stim_events', 'stim1', ... 51 | 'nb_iterations', 200, ... 52 | 'save_evoked_response', 1, ... 53 | 'save_effect', 0, ... 54 | 'save_ppm', 0, ... 55 | 'save_fit', 0, ..., 56 | 'output_fig_dir', output_fig_dir, ... 57 | 'save_full_fitted_model', 1); 58 | 59 | % TODO 60 | % Only check S1D1 61 | % check_response_estimation(spree_result_chan); 62 | % check_chan_activity_detection(spree_result_chan.activ_chans, activity); 63 | 64 | % Only check S2D2 65 | 66 | % TODO: fix issue with input data (data.mat not found...) 67 | % spree_result_cortex_hbo = bst_process('CallProcess', 'process_nst_spree', ... 68 | % nirs_cortex_hbo, [], ... 69 | % 'stim_events', 'stim1'); 70 | 71 | % TODO 72 | % check_cortex_activity_detection(spree_result_cortex_hbo.activ_scouts, activity); 73 | end 74 | end 75 | end 76 | 77 | 78 | 79 | function [chan_fn, cortex_hbo, cortex_hbr, activity] = load_activity_test_subject() 80 | 81 | % Setup utest protocol 82 | use_default_anatomy = 0; 83 | bst_create_test_protocol(use_default_anatomy); 84 | 85 | % Request activity test data 86 | repo_url = nst_get_repository_url(); 87 | %TODO: add ground-truth HRF in forged subject data 88 | data_fns = nst_request_files({{'unittest','activity_test_subject','activity_test_subject.zip'}, ... 89 | {'unittest','activity_test_subject','activity_info.mat'}}, ... 90 | 1, repo_url); 91 | 92 | % Import data in bst 93 | import_subject(data_fns{1}); 94 | 95 | % Retrieve file names 96 | % TODO: rename test_subject to activity_test_subject 97 | % and condition to "evoked_activity_high_SNR" 98 | chan_fn = nst_get_bst_func_files('test_subject', 'test', 'Hb'); 99 | cortex_hbo = nst_get_bst_func_files('test_subject', 'test', 'HbO cortex'); 100 | cortex_hbr = nst_get_bst_func_files('test_subject', 'test', 'HbR cortex'); 101 | 102 | activity_info_data = load(data_fns{2}, '-mat'); 103 | activity = activity_info_data.activity_info; 104 | end 105 | 106 | function check_chan_activity_detection() 107 | 108 | % Sensitivity check: 109 | % All activ channels are actually detected 110 | 111 | 112 | % Specificity check: 113 | % All inactiv channels are not detected 114 | 115 | end 116 | 117 | function check_cortex_activity_detection() 118 | 119 | % Sensitivity check: 120 | % All activ vertices are actually detected 121 | 122 | 123 | % Specificity check: 124 | % All inactiv vertices are not detected 125 | 126 | end 127 | -------------------------------------------------------------------------------- /bst_plugin/core/nst_unformat_channels.m: -------------------------------------------------------------------------------- 1 | function [isrcs, idets, measures, measure_type] = nst_unformat_channels(channel_labels, warn_bad_channels) 2 | % NST_UNFORMAT_CHANNELS extract sources, dectectors and measures information 3 | % from channel labels with *homogeneous* type (eg wavelength or Hb). 4 | % 5 | % [ISRCS, IDETS, MEAS, CHAN_TYPE] = NST_UNFORMAT_CHANNELS(CHANNEL_LABELS) 6 | % CHANNEL_LABELS (cell array of str): 7 | % each str is formatted as 'SxDyWLz' or 'SxDyHbt', where: 8 | % x: source index 9 | % y: detector index 10 | % z: wavelength 11 | % t: Hb type (O, R, T). 12 | % Consistency is asserted so that: 13 | % - channels are unique 14 | % - channel type is homogeneous 15 | % Warning is issued if: 16 | % - the number of measures per pair is not homogeneous 17 | % eg one pair has only one wavelength 18 | % 19 | % Examples: S1D2WL685, S01D7WL830, S3D01HbR 20 | % 21 | % 22 | % ISRCS (array of int): extracted source indexes 23 | % IDETS (array of int): extracted detector indexes 24 | % MEAS (array of int | cell array of str): extracted measure values 25 | % CHAN_TYPE (array of int): channel type (see NST_CHANNEL_TYPES for enum). 26 | % 27 | % See also NST_UNFORMAT_CHANNEL, NST_CHANNEL_TYPES, NST_FORMAT_CHANNEL 28 | 29 | assert(iscellstr(channel_labels)); 30 | 31 | if nargin < 2 32 | warn_bad_channels = 0; 33 | end 34 | 35 | 36 | nb_channels = length(channel_labels); 37 | isrcs = zeros(1, nb_channels); 38 | idets = zeros(1, nb_channels); 39 | measures = cell(1, nb_channels); 40 | mtypes = zeros(1, nb_channels); 41 | reformated_channels = cell(1, nb_channels); 42 | for ichan=1:nb_channels 43 | [isrc, idet, meas, mtype] = nst_unformat_channel(channel_labels{ichan}, warn_bad_channels); 44 | isrcs(ichan) = isrc; 45 | idets(ichan) = idet; 46 | measures{ichan} = meas; 47 | mtypes(ichan) = mtype; 48 | if ~isnan(isrc) && ~isnan(idet) 49 | % reformat channels to make sure formatting is consistent 50 | % -> allow proper duplicate detection after 51 | reformated_channels{ichan} = nst_format_channel(isrc, idet, meas); 52 | else 53 | reformated_channels{ichan} = ''; 54 | end 55 | end 56 | 57 | % check uniqueness 58 | [~, i_unique] = unique(reformated_channels); 59 | duplicates = reformated_channels; 60 | duplicates(i_unique) = []; 61 | duplicates(strcmp(duplicates, '')) = []; %remove unrecognized channels 62 | i_duplicates = ismember(reformated_channels, unique(duplicates)); 63 | if ~isempty(duplicates) 64 | msg = sprintf('Duplicated channels: "%s". Indexes: %s', ... 65 | strjoin(channel_labels(i_duplicates), ', '), num2str(i_duplicates)); 66 | throw(MException('NIRSTORM:NonUniqueChannels', msg)); 67 | end 68 | 69 | % check homogeneity of channel type 70 | mtypes = mtypes(~isnan(mtypes)); 71 | if length(unique(mtypes)) > 1 72 | throw(MException('NIRSTORM:NonHomogeneousMeasure', 'Measure type is not homogeneous.')); 73 | end 74 | 75 | measure_types = nst_measure_types(); 76 | measure_type = mtypes(1); 77 | if measure_type == measure_types.WAVELENGTH 78 | measures = cell2mat(measures); 79 | end 80 | 81 | %% Check number of measures per pair 82 | max_idet = (max(idets)+1); 83 | pairs_hash = isrcs(~isnan(isrcs)) * max_idet + idets(~isnan(isrcs)); 84 | unique_pairs_hash = unique(pairs_hash); 85 | mcounts = sparse(ones(1, length(unique_pairs_hash)), unique_pairs_hash, ... 86 | ones(1, length(unique_pairs_hash)), 1, max(unique_pairs_hash), ... 87 | nb_channels); 88 | if ~iscell(measures) 89 | all_measures = unique(measures(~isnan(measures))); 90 | else 91 | all_measures = unique(measures(cellfun(@(e) ischar(e) || ~isnan(e), measures))); 92 | end 93 | for ichan=1:nb_channels 94 | if ~isnan(isrcs(ichan)) 95 | if measure_type == measure_types.WAVELENGTH 96 | measure_hash = find(measures(ichan)==all_measures); 97 | elseif measure_type == measure_types.HB 98 | measure_hash = find(strcmp(measures(ichan),all_measures)); 99 | end 100 | h_idx = isrcs(ichan) * max_idet + idets(ichan); 101 | mcounts(h_idx) = mcounts(h_idx) + measure_hash; 102 | end 103 | end 104 | 105 | % pairs with too many measures: 106 | i_inconsistant_measures = (mcounts ~= sum(1:length(all_measures)) + 1) & (mcounts ~= 0); 107 | 108 | if nnz(i_inconsistant_measures) > 0 109 | [isrc_incon,idet_incon,counts] = find(i_inconsistant_measures); 110 | inconsistent_pairs = cell(1, length(isrc_incon)); 111 | for ipair=1:length(isrc_incon) 112 | inconsistent_pairs{ipair} = sprintf('S%dD%d', isrc_incon(ipair), idet_incon(ipair)); 113 | end 114 | warning('Inconsistent measure(s) for pair(s): %s', strjoin(inconsistent_pairs, ', ')); 115 | end 116 | 117 | end 118 | 119 | -------------------------------------------------------------------------------- /test/run_tests.m: -------------------------------------------------------------------------------- 1 | function run_tests(to_run, stop_on_error, do_coverage, re_match_filter) 2 | % Run unit test suites from files located in './test' 3 | % Test scenarios to run are specificied by given to_run: 4 | % - package: tests related to functions in bst_plugin 5 | % - source: tests of tools used on package sources (eg dist_tools) 6 | % - scripts: execute tutorial script (download data if not available). 7 | % WARNING: tests of scripts take time!! 8 | % 9 | install_error_msg = sprintf(['To run unit tests, nirstorm debug functions must be installed.\n'... 10 | ' Use nst_install(''copy'', ''debug'') or ' ... 11 | 'nst_install(''link'', ''debug'') (linux only).\n'... 12 | 'WARNING: these functions override brainstorm behavior.']); 13 | try 14 | if ~exist(fullfile(bst_get('BrainstormUserDir'), 'process', 'bst_error'), 'file') 15 | error(install_error_msg); 16 | end 17 | catch 18 | error(install_error_msg); 19 | end 20 | %TODO: check for matlab version > R2013b 21 | %TODO: check that nirstorm is actually installed 22 | 23 | import matlab.unittest.TestRunner 24 | import matlab.unittest.TestSuite 25 | import matlab.unittest.plugins.StopOnFailuresPlugin 26 | import matlab.unittest.plugins.CodeCoveragePlugin 27 | 28 | if nargin < 1 29 | to_run = {'package', 'source'}; 30 | else 31 | if ischar(to_run) 32 | if strcmp(to_run, 'all') 33 | to_run = {'package', 'source', 'scripts'}; 34 | else 35 | to_run = {to_run}; 36 | end 37 | end 38 | 39 | if ~all(ismember(to_run, {'package', 'source', 'scripts'})) 40 | throw(MException('Nirstorm:Unittests:BadScenarios', ... 41 | ['Wrong test scenario(s) must be "package", '... 42 | '"source", or "scripts"'])); 43 | end 44 | end 45 | 46 | if nargin < 2 47 | stop_on_error = 0; 48 | end 49 | 50 | if nargin < 3 51 | do_coverage = 0; 52 | end 53 | 54 | if nargin < 4 55 | re_match_filter = '.*'; 56 | end 57 | 58 | %% Dispatch tests in different test suites: 59 | % - tests for source tools (eg installation) 60 | % - tests for installed package (eg brainstorm processes) 61 | test_scripts = dir(fullfile('test', '*.m')); 62 | iss = 1; 63 | ips = 1; 64 | ics = 1; 65 | 66 | tests_to_ignore = readlines(fullfile('test', 'IGNORE')); 67 | for iscript=1:length(test_scripts) 68 | if ismember(test_scripts(iscript).name, tests_to_ignore) 69 | warning(['Test ignored: ' test_scripts(iscript).name]); 70 | continue 71 | end 72 | test_fn = fullfile('test', test_scripts(iscript).name); 73 | if ~isempty(strfind(test_fn, 'Test.m')) && ~isempty(regexp(test_fn, re_match_filter, 'match')) 74 | 75 | tests = TestSuite.fromFile(test_fn); 76 | if ~isempty(strfind(test_fn, 'SourceTest')) 77 | source_suite(iss:(iss+length(tests)-1)) = tests; 78 | iss = iss + length(tests); 79 | elseif ~isempty(strfind(test_fn, 'ScriptTest')) 80 | script_suite(ics:(ics+length(tests)-1)) = tests; 81 | ics = ics + length(tests); 82 | else 83 | package_suite(ips:(ips+length(tests)-1)) = tests; 84 | ips = ips + length(tests); 85 | end 86 | end 87 | end 88 | 89 | if ismember('package', to_run) 90 | if exist('package_suite', 'var') && ~isempty(package_suite) 91 | %% Configure & run test runner for installed package tools 92 | runner = TestRunner.withTextOutput; 93 | if stop_on_error 94 | runner.addPlugin(StopOnFailuresPlugin('IncludingAssumptionFailures', true)); 95 | end 96 | if do_coverage 97 | runner.addPlugin(CodeCoveragePlugin.forFolder(fullfile(bst_get('BrainstormUserDir'), 'process'))); 98 | end 99 | result = runner.run(package_suite); 100 | else 101 | warning('Package test suite is empty'); 102 | end 103 | end 104 | 105 | if ismember('source', to_run) 106 | %% Configure & run test runner for source tools 107 | runner = TestRunner.withTextOutput; 108 | if stop_on_error 109 | runner.addPlugin(StopOnFailuresPlugin('IncludingAssumptionFailures', true)); 110 | end 111 | addpath(fullfile(pwd, 'dist_tools')); 112 | if do_coverage 113 | runner.addPlugin(CodeCoveragePlugin.forFolder('dist_tools')); 114 | end 115 | result = runner.run(source_suite); 116 | end 117 | 118 | if ismember('scripts', to_run) 119 | %% Configure & run test runner for source tools 120 | runner = TestRunner.withTextOutput; 121 | if stop_on_error 122 | runner.addPlugin(StopOnFailuresPlugin('IncludingAssumptionFailures', true)); 123 | end 124 | result = runner.run(script_suite); 125 | end 126 | 127 | end 128 | 129 | function lines = readlines(fn) 130 | f = fopen(fn); 131 | lines = textscan(f,'%s','delimiter',char(10)); %#ok<*CHARTEN> 132 | lines = lines{1}; 133 | fclose(f); 134 | end -------------------------------------------------------------------------------- /bst_plugin/preprocessing/process_nst_deglitch.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_deglitch( varargin ) 2 | 3 | % @============================================================================= 4 | % This software is part of the Brainstorm software: 5 | % http://neuroimage.usc.edu/brainstorm 6 | % 7 | % Copyright (c)2000-2013 Brainstorm by the University of Southern California 8 | % This software is distributed under the terms of the GNU General Public License 9 | % as published by the Free Software Foundation. Further details on the GPL 10 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 11 | % 12 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 13 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 14 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 15 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 16 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 17 | % 18 | % For more information type "brainstorm license" at command prompt. 19 | % =============================================================================@ 20 | % 21 | % Authors: Thomas Vincent (2019) 22 | 23 | eval(macro_method); 24 | end 25 | 26 | 27 | %% ===== GET DESCRIPTION ===== 28 | function sProcess = GetDescription() 29 | 30 | % Description the process 31 | sProcess.Comment = 'Remove glitches'; 32 | sProcess.Category = 'Filter'; 33 | sProcess.SubGroup = {'NIRS', 'Pre-process'}; 34 | sProcess.Index = 1302; 35 | sProcess.isSeparator = 0; 36 | sProcess.Description = 'https://github.com/Nirstorm/nirstorm/wiki/Remove-glitches'; 37 | 38 | % Definition of the input accepted by this process 39 | sProcess.InputTypes = {'raw', 'data'}; 40 | sProcess.OutputTypes = {'raw', 'data'}; 41 | 42 | sProcess.options.factor_std_grad.Comment = 'Variation threshold: '; 43 | sProcess.options.factor_std_grad.Type = 'value'; 44 | sProcess.options.factor_std_grad.Value = {2.5, '* std(gradient)', 2}; % last if nb of subunit digits, use 0 for integer 45 | 46 | % sProcess.options.tag_only.Comment = 'Only mark glitches as events'; 47 | % sProcess.options.tag_only.Type = 'checkbox'; 48 | % sProcess.options.tag_only.Value = 0; 49 | 50 | %TODO: apply only to NIRS channels 51 | end 52 | 53 | %% ===== FORMAT COMMENT ===== 54 | function Comment = FormatComment(sProcess) 55 | Comment = sProcess.Comment; 56 | end 57 | 58 | %% ===== RUN ===== 59 | function sInputs = Run(sProcess, sInputs) 60 | sInputs.A = Compute(sInputs.A, sProcess.options.factor_std_grad.Value{1}); 61 | end 62 | 63 | %% ===== Compute ===== 64 | function [nirs_deglitched, glitch_flags] = Compute(nirs_sig, std_factor_thresh) 65 | 66 | if nargin < 2 67 | std_factor_thresh = 2.5; 68 | end 69 | 70 | [nb_positions, nb_samples] = size(nirs_sig); 71 | glitch_flags = detect_glitches(nirs_sig, std_factor_thresh); 72 | nirs_deglitched = nirs_sig; 73 | for ipos=1:nb_positions 74 | nirs_deglitched(ipos, glitch_flags(ipos, :)) = repmat(mean(nirs_sig(ipos, :)), ... 75 | 1, sum(glitch_flags(ipos, :))); 76 | if glitch_flags(ipos, end) 77 | nirs_deglitched(ipos, end) = mean(nirs_sig(ipos, nb_samples-2:nb_samples-1)); 78 | glitch_flags(ipos, end) = 0; 79 | end 80 | if glitch_flags(ipos, 1) 81 | nirs_deglitched(ipos, 1) = mean(nirs_sig(ipos, 2:3)); 82 | glitch_flags(ipos, 1) = 0; 83 | end 84 | % Maybe the following can be vectorized but there was something strange with 85 | % find function on 2D array 86 | glitch_i = find(glitch_flags(ipos, :)); 87 | nirs_deglitched(ipos, glitch_i) = (nirs_sig(ipos, glitch_i-1) + nirs_sig(ipos, glitch_i+1)) ./ 2; 88 | end 89 | end 90 | 91 | function glitch_flags = detect_glitches(nirs_sig, std_factor_thresh) 92 | 93 | % nirs_sig: (nb_positions, nb_samples) 94 | 95 | [nb_pos, nb_samples] = size(nirs_sig); 96 | 97 | if nargin < 2 98 | std_factor_thresh = 2.5; 99 | end 100 | 101 | signal_tmp = [nirs_sig(:, 2) nirs_sig nirs_sig(:, end-1)]; % mirror edges 102 | grad = diff(signal_tmp, 1, 2); 103 | abs_grad = abs(grad); 104 | 105 | % Robust standard deviation of absolute gradient 106 | % -> clip values within 1% to 99% of total range, to remove extreme values 107 | % then compute reference std of absolute gradient over these clipped values 108 | 109 | sorted_signal = sort(signal_tmp, 2); 110 | thresh_low = sorted_signal(:, round(nb_samples*0.01)); 111 | thresh_high = sorted_signal(:, round(nb_samples*0.99)); 112 | for ipos=1:size(signal_tmp, 1) 113 | signal_tmp(ipos, signal_tmp(ipos, :) < thresh_low(ipos)) = thresh_low(ipos); 114 | signal_tmp(ipos, signal_tmp(ipos, :) > thresh_high(ipos)) = thresh_high(ipos); 115 | end 116 | std_agrad = std(abs(diff(signal_tmp, 1, 2)), 0, 2); 117 | 118 | glitch_canditates = [zeros(nb_pos, 1) (abs_grad > std_factor_thresh * std_agrad) .* sign(grad)]; 119 | glitch_flags = [abs(diff(glitch_canditates, 1, 2))==2 false(nb_pos, 1)] & glitch_canditates; 120 | glitch_flags = glitch_flags(:, 2:end-1); % remove mirrored edges 121 | end 122 | -------------------------------------------------------------------------------- /bst_plugin/preprocessing/process_nst_detrend.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_detrend( varargin ) 2 | 3 | % @============================================================================= 4 | % This software is part of the Brainstorm software: 5 | % http://neuroimage.usc.edu/brainstorm 6 | % 7 | % Copyright (c)2000-2013 Brainstorm by the University of Southern California 8 | % This software is distributed under the terms of the GNU General Public License 9 | % as published by the Free Software Foundation. Further details on the GPL 10 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 11 | % 12 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 13 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 14 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 15 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 16 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 17 | % 18 | % For more information type "brainstorm license" at command prompt. 19 | % =============================================================================@ 20 | % 21 | % Authors: Delaire Edouard (2022) 22 | % 23 | % 24 | eval(macro_method); 25 | end 26 | 27 | %% ===== GET DESCRIPTION ===== 28 | function sProcess = GetDescription() 29 | % Description the process 30 | sProcess.Comment = 'Remove slow fluctuations'; 31 | sProcess.FileTag = '_detrend'; 32 | sProcess.Category = 'Filter'; 33 | sProcess.SubGroup = {'NIRS', 'Pre-process'}; 34 | sProcess.Index = 1309; 35 | sProcess.isSeparator = 0; 36 | sProcess.Description = ''; 37 | 38 | % Definition of the input accepted by this process 39 | sProcess.InputTypes = {'data','raw'}; 40 | sProcess.OutputTypes = {'data','raw'}; 41 | 42 | 43 | sProcess.options.filter_model.Comment = {'Polynome', 'DCT', 'Trend modelisation: '; ... 44 | 'legendre', 'DCT',''}; 45 | sProcess.options.filter_model.Type = 'radio_linelabel'; 46 | sProcess.options.filter_model.Value = 'DCT'; 47 | sProcess.options.filter_model.Controller = struct('legendre','legendre','DCT','DCT' ); 48 | 49 | 50 | sProcess.options.option_period.Comment = 'Miminum Period (0= only linear detrend):'; 51 | sProcess.options.option_period.Type = 'value'; 52 | sProcess.options.option_period.Value = {200, 's', 0}; 53 | sProcess.options.option_period.Class = 'DCT'; 54 | 55 | 56 | sProcess.options.poly_order.Comment = 'Polynome order:'; 57 | sProcess.options.poly_order.Type = 'value'; 58 | sProcess.options.poly_order.Value = {3, '', 0}; 59 | sProcess.options.poly_order.Class = 'legendre'; 60 | 61 | 62 | sProcess.options.option_keep_mean.Comment = 'Keep the mean'; 63 | sProcess.options.option_keep_mean.Type = 'checkbox'; 64 | sProcess.options.option_keep_mean.Value = 0; 65 | 66 | sProcess.nInputs = 1; 67 | sProcess.nMinFiles = 1; 68 | sProcess.nOutputs = 1; 69 | end 70 | 71 | %% ===== FORMAT COMMENT ===== 72 | function [Comment, fileTag] = FormatComment(sProcess) 73 | if strcmp(sProcess.options.filter_model.Type, 'legendre') 74 | Comment = 'Detend (polynoial)'; 75 | elseif strcmp(sProcess.options.filter_model.Type, 'DCT') 76 | Comment = 'Detend (DCT)'; 77 | else 78 | Comment = 'Detend'; 79 | end 80 | 81 | fileTag = 'detrend'; 82 | end 83 | 84 | function sInput = Run(sProcess, sInput) 85 | 86 | keep_mean = sProcess.options.option_keep_mean.Value; 87 | ChannelMat = in_bst_channel(sInput.ChannelFile); 88 | nirs_ichans = sInput.ChannelFlag ~= -1 & strcmpi({ChannelMat.Channel.Type}, 'NIRS')'; 89 | 90 | 91 | Y = sInput.A(nirs_ichans,:)'; 92 | 93 | model = nst_glm_initialize_model(sInput.TimeVector); 94 | model = nst_glm_add_regressors(model,'constant'); 95 | 96 | switch sProcess.options.filter_model.Value 97 | case 'DCT' 98 | model = nst_glm_add_regressors(model,'linear'); 99 | if sProcess.options.option_period.Value{1} > 0 100 | period = sProcess.options.option_period.Value{1}; 101 | model = nst_glm_add_regressors(model,'DCT',[1/sInput.TimeVector(end) 1/period],{'LFO'}); 102 | end 103 | case 'legendre' 104 | model = nst_glm_add_regressors(model,'legendre', sProcess.options.poly_order.Value{1} ); 105 | end 106 | 107 | 108 | [B,proj_X] = nst_glm_fit_B(model,Y, 'SVD'); 109 | 110 | if keep_mean 111 | Y = Y - model.X(:,2:end)*B(2:end,:); 112 | else 113 | Y = Y - model.X*B; 114 | end 115 | 116 | 117 | sInput.A(nirs_ichans,:) = Y'; 118 | sInput.CommentTag = FormatComment(sProcess); 119 | 120 | History = 'Remove Linear trend'; 121 | if strcmp(sProcess.options.filter_model.Value,'DCT') && sProcess.options.option_period.Value{1} 122 | History = [History sprintf('(using DCT of period > %ds)',period)]; 123 | 124 | elseif strcmp(sProcess.options.filter_model.Value,'legendre') && sProcess.options.option_period.Value{1} 125 | History = [History sprintf('(using %d order polynomial)',sProcess.options.poly_order.Value{1})]; 126 | end 127 | sInput.HistoryComment = History; 128 | 129 | end -------------------------------------------------------------------------------- /bst_plugin/preprocessing/process_nst_extract_ssc.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_extract_ssc( varargin ) 2 | 3 | % @============================================================================= 4 | % This software is part of the Brainstorm software: 5 | % http://neuroimage.usc.edu/brainstorm 6 | % 7 | % Copyright (c)2000-2013 Brainstorm by the University of Southern California 8 | % This software is distributed under the terms of the GNU General Public License 9 | % as published by the Free Software Foundation. Further details on the GPL 10 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 11 | % 12 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 13 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 14 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 15 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 16 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 17 | % 18 | % For more information type "brainstorm license" at command prompt. 19 | % =============================================================================@ 20 | % 21 | % Authors: Thomas Vincent (2019) 22 | % 23 | % 24 | eval(macro_method); 25 | end 26 | 27 | %% ===== GET DESCRIPTION ===== 28 | function sProcess = GetDescription() 29 | % Description the process 30 | sProcess.Comment = 'Split short separation channels'; 31 | sProcess.Category = 'Custom'; 32 | sProcess.SubGroup = {'NIRS', 'Pre-process'}; 33 | sProcess.Index = 1307; 34 | sProcess.isSeparator = 0; 35 | sProcess.Description = ''; 36 | 37 | % Definition of the input accepted by this process 38 | sProcess.InputTypes = {'data','raw'}; 39 | sProcess.OutputTypes = {'data','data'}; 40 | 41 | sProcess.nInputs = 1; 42 | sProcess.nMinFiles = 1; 43 | sProcess.nOutputs = 2; 44 | 45 | sProcess.options.separation_threshold_cm.Comment = 'Separation threshold'; 46 | sProcess.options.separation_threshold_cm.Type = 'value'; 47 | sProcess.options.separation_threshold_cm.Value = {1.5, 'cm', 2}; 48 | 49 | end 50 | 51 | %% ===== FORMAT COMMENT ===== 52 | function Comment = FormatComment(sProcess) 53 | Comment = sProcess.Comment; 54 | end 55 | 56 | function [OutputFiles_SSC] = Run(sProcess, sInputs) 57 | 58 | OutputFiles_SSC = {}; 59 | 60 | separation_threshold_m = sProcess.options.separation_threshold_cm.Value{1} / 100; 61 | 62 | for iInput=1:length(sInputs) 63 | 64 | % Load recordings 65 | if strcmp(sInputs(iInput).FileType, 'data') % Imported data structure 66 | sDataIn = in_bst_data(sInputs(iInput).FileName); 67 | elseif strcmp(sInputs(iInput).FileType, 'raw') % Continuous data file 68 | sDataIn = in_bst(sInputs(iInput).FileName, [], 1, 1, 'no'); 69 | end 70 | 71 | ChannelMat = in_bst_channel(sInputs(iInput).ChannelFile); 72 | % nirs_ichans = channel_find(ChannelMat.Channel, 'NIRS'); 73 | % other_chans = true(size(sDataIn.ChannelFlag)); 74 | % other_chans(nirs_ichans) = false; 75 | 76 | separations = process_nst_separations('Compute', ChannelMat.Channel); 77 | if isempty(separations) 78 | warning(sprintf('Separations could not be computed for %s', sInputs.FileName)); 79 | continue; 80 | end 81 | 82 | lsc_chans = separations > separation_threshold_m; 83 | ssc_chans = ~lsc_chans & ~isnan(separations); 84 | 85 | % Create new condition because channel definition is different from original one 86 | cond_name = sInputs(iInput).Condition; 87 | if length(cond_name)>=4 && strcmp(cond_name(1:4), '@raw') 88 | cond_name = cond_name(5:end); 89 | end 90 | iStudyOut = db_add_condition(sInputs(iInput).SubjectName, [cond_name, '_SSC']); 91 | sStudyOut = bst_get('Study', iStudyOut); 92 | 93 | % Save SSC 94 | selected_chans = ssc_chans; % ssc_chans | other_chans; 95 | 96 | ChannelOut = ChannelMat; 97 | ChannelOut.Channel = ChannelOut.Channel(selected_chans); 98 | ChannelOut.Comment = sprintf('NIRS short channels (%d)', sum(selected_chans)); 99 | % Save channel definition 100 | [tmp, iChannelStudy] = bst_get('ChannelForStudy', iStudyOut); 101 | db_set_channel(iChannelStudy, ChannelOut, 0, 0); 102 | 103 | sDataOut = db_template('data'); 104 | sDataOut.F = sDataIn.F(selected_chans, :); 105 | sDataOut.Comment = [sInputs(iInput).Comment ' SSC']; 106 | sDataOut.ChannelFlag = sDataIn.ChannelFlag(selected_chans); 107 | sDataOut.Time = sDataIn.Time; 108 | sDataOut.DataType = 'recordings'; 109 | sDataOut.nAvg = 1; 110 | if ~isempty(sDataIn.Std) 111 | sDataOut.Std = sDataIn.Std(selected_chans, :); 112 | else 113 | sDataOut.Std = []; 114 | end 115 | sDataOut.ColormapType = []; 116 | sDataOut.Events = sDataIn.Events; 117 | sDataOut.DisplayUnits = sDataIn.DisplayUnits; 118 | 119 | % Generate a new file name in the same folder 120 | OutputFile = bst_process('GetNewFilename', bst_fileparts(sStudyOut.FileName), 'data_lsc'); 121 | sDataOut.FileName = file_short(OutputFile); 122 | bst_save(OutputFile, sDataOut, 'v7'); 123 | % Register in database 124 | db_add_data(iStudyOut, OutputFile, sDataOut); 125 | OutputFiles_SSC{iInput} = OutputFile; 126 | end 127 | 128 | end 129 | -------------------------------------------------------------------------------- /scripts/anatomy_based_full_group_pipeline_V1.m: -------------------------------------------------------------------------------- 1 | function anatomy_based_full_group_pipeline_V1() 2 | 3 | % Example for the template- and surface-based full pipeline, using the 4 | % function NST_PPL_SURFACE_TEMPLATE_V1. 5 | % 6 | % This script downloads some sample data of 10 "dummy" subjects (27 Mb) 7 | % Total max amount of data to download: (TODO) Mb, the user is asked for download 8 | % confirmation. 9 | % 10 | % Data are imported in a dedicated protocol, using specific name convention 11 | % handled by NST_PPL_ANATOMY_BASED_V1. 12 | % Preprocessings and processings are then run until group-level analysis: 13 | % 1) Resampling to 5hz 14 | % 2) Detect bad channels 15 | % 3) Convert to delta optical density 16 | % 4) High pass filter 17 | % 5) Project on the cortical surface using head 18 | % 6) 1st level GLM (pre-coloring) 19 | % 7 level GLM with MFX contrast t-maps 20 | % 21 | % This script illustrates a fully functional analysis pipeline that can 22 | % serve as a basis for another custom study. 23 | % 24 | % For a more detailed description, see the wiki page: 25 | % TODO: add wiki page 26 | 27 | %% Define experiment folder 28 | % where example data will downloaded and result figures will be stored 29 | % Default is to create folder in the system temporary directory. 30 | % Modify root_folder to point to a custom folder. 31 | % WARNING: it is strongly advised not to use a subdirectory of 32 | % the nirstorm source folder. 33 | 34 | tmp_folder = tempdir(); 35 | if ~exist(tmp_folder, 'dir') 36 | error('Cannot locate temporary folder'); 37 | end 38 | root_folder = fullfile(tmp_folder, 'nst_ppl_surface_template_V1_example'); 39 | if ~exist(root_folder, 'dir') 40 | mkdir(root_folder); 41 | end 42 | 43 | %% Setup brainstorm 44 | if ~brainstorm('status') 45 | % Start brainstorm without the GUI if not already running 46 | brainstorm nogui 47 | end 48 | 49 | %% Check Protocol 50 | protocol_name = 'TestAnatomyBasedGroupPipelineV1'; 51 | 52 | if isempty(bst_get('Protocol', protocol_name)) 53 | % Might need a fix 54 | gui_brainstorm('CreateProtocol', protocol_name, 0, 0); 55 | end 56 | db_save(); 57 | %% Fetch data 58 | subject_names = {'S01', ... 59 | 'S04', 'S05', ... 60 | 'S07', 'S08', 'S09', ... 61 | 'S10', 'S11'}; 62 | 63 | nb_subjects = length(subject_names); 64 | % TODO: resolve file names from Subject names 65 | requested_files{4*nb_subjects}={''}; 66 | 67 | for i=1:nb_subjects 68 | requested_files{i}={'Tapping',subject_names{i}, 'anatomy' }; 69 | requested_files{i+nb_subjects}={'Tapping',subject_names{i}, 'data', ['subject_' subject_names{i}(2:3) '.nirs'] }; 70 | requested_files{i+2*nb_subjects}={'Tapping',subject_names{i}, 'data', 'optodes.txt' }; 71 | requested_files{i+3*nb_subjects}={'Tapping',subject_names{i}, 'data', 'headpoints' }; 72 | end 73 | 74 | data_fns = nst_request_files(requested_files, ... 75 | 1, nst_get_repository_url(), 1e6, root_folder); 76 | 77 | mri_folders=data_fns(1:nb_subjects); 78 | nirs_fns = data_fns((1+nb_subjects):2*nb_subjects); 79 | headpoints=data_fns((1+3*nb_subjects):4*nb_subjects); 80 | %% Import data 81 | 82 | options = nst_ppl_surface_template_V1('get_options'); 83 | 84 | options.import.useDefaultAnat=0; 85 | options.import.mri_folder_type='FreeSurfer'; 86 | options.import.nvertices = 2500; 87 | options.import.aseg=1; 88 | 89 | options.import.subject(1:nb_subjects)=repmat(options.import.subject,1,nb_subjects); 90 | 91 | for i=1:nb_subjects 92 | options.import.subject{i}.name=subject_names{i}; 93 | options.import.subject{i}.nirs_fn=nirs_fns{i}; 94 | options.import.subject{i}.mri_folder=mri_folders{i}; 95 | options.import.subject{i}.additional_headpoints=headpoints{i}; 96 | end 97 | 98 | [sFiles, imported] = nst_ppl_surface_template_V1('import_subjects', options); 99 | 100 | % Read stimulation events from AUX channel 101 | for ifile=1:length(sFiles) 102 | if imported(ifile) 103 | % Read events from aux channel 104 | bst_process('CallProcess', 'process_evt_read', sFiles{ifile}, [], ... 105 | 'stimchan', 'NIRS_AUX', ... 106 | 'trackmode', 3, ... % Value: detect the changes of channel value 107 | 'zero', 0); 108 | % Rename event AUX1 -> motor 109 | bst_process('CallProcess', 'process_evt_rename', sFiles{ifile}, [], ... 110 | 'src', 'AUX1', ... 111 | 'dest', 'motor'); 112 | % Convert to extended event-> add duration of 30 sec to all motor events 113 | bst_process('CallProcess', 'process_evt_extended', sFiles{ifile}, [], ... 114 | 'eventname', 'motor', ... 115 | 'timewindow', [0, 30]); 116 | 117 | end 118 | end 119 | 120 | %% Run pipeline 121 | options.GLM_1st_level.stimulation_events = {'motor'}; 122 | options.GLM_1st_level.contrasts(1).label = 'motor'; 123 | options.GLM_1st_level.contrasts(1).vector = '[1 0]'; % a vector of weights, as a string 124 | 125 | % Run the pipeline (and save user markings): 126 | nst_ppl_surface_template_V1('analyse', options, subject_names); % Run the full pipeline 127 | %TODO: full reload of GUI tree at end of pipeline 128 | end -------------------------------------------------------------------------------- /test/EVTImportTest.m: -------------------------------------------------------------------------------- 1 | classdef EVTImportTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | 8 | % This script aimed to test two functionality : 9 | % 10 | % 11 | methods(TestMethodSetup) 12 | function setup(testCase) 13 | tmpd = tempname; 14 | mkdir(tmpd); 15 | testCase.tmp_dir = tmpd; 16 | utest_bst_setup(); 17 | end 18 | end 19 | 20 | methods(TestMethodTeardown) 21 | function tear_down(testCase) 22 | rmdir(testCase.tmp_dir, 's'); 23 | utest_clean_bst(); 24 | end 25 | end 26 | 27 | methods(Test) 28 | function test_evt_event_import_events(testCase) 29 | global GlobalData; 30 | 31 | events=generate_fake_event(10,2,150); 32 | 33 | dlmwrite(fullfile(testCase.tmp_dir,'event.evt'),events,'delimiter','\t') 34 | %bst_save(fullfile(utest_dir,testCase.tmp_dir,'event.evt'),events); 35 | %bst_save(fullfile(utest_dir,'event.evt') ,events); 36 | 37 | 38 | nirs = load(fullfile(utest_dir,'dummy.nirs'), '-mat'); 39 | nirs_dt = diff(nirs.t(1:2)); 40 | 41 | 42 | sFiles_dummy = utest_import_nirs_in_bst(fullfile(utest_dir,'dummy.nirs')); 43 | 44 | sFiles_processed = bst_process('CallProcess', ... 45 | 'process_nst_import_evt_events', ... 46 | sFiles_dummy, [], ... 47 | 'evtfile',{ fullfile(utest_dir,testCase.tmp_dir,'event.evt')}, ... 48 | 'label_cols','ConditionA,ConditionB', ... 49 | 'last_event',0 ,... 50 | 'confirm_importation', 0); 51 | 52 | 53 | assert(~isempty(sFiles_processed)); 54 | events = get_events(sFiles_processed); 55 | 56 | assert(length(events) == 2); 57 | assert(size(events(1).samples, 1) == 2); 58 | 59 | 60 | assert( strcmp( events(1).label,'ConditionB')); 61 | assert( size(events(1).epochs,2) == 9 ); 62 | % The last event is not here because we don't know his duration 63 | % at it is the last of the file 64 | 65 | assert( strcmp( events(2).label,'ConditionA')); 66 | assert( size(events(2).epochs,2) == 10 ); 67 | 68 | 69 | assert(abs( events(strcmp({events.label}, 'ConditionA')).times(1, 1)) - 6.04000000000000 <= nirs_dt); 70 | assert(abs( events(strcmp({events.label}, 'ConditionA')).times(2, 1)) - 6.04000000000000 <= nirs_dt); 71 | 72 | utest_reset_bst(); 73 | end 74 | 75 | 76 | function test_evt_event_merge(testCase) 77 | global GlobalData; 78 | 79 | events=generate_fake_event(10,4,150); 80 | 81 | dlmwrite(fullfile(utest_dir,testCase.tmp_dir,'event.evt'),events,'delimiter','\t') 82 | %bst_save(fullfile(utest_dir,testCase.tmp_dir,'event.evt'),events); 83 | %bst_save(fullfile(utest_dir,'event.evt') ,events); 84 | 85 | 86 | nirs = load(fullfile(utest_dir,'dummy.nirs'), '-mat'); 87 | nirs_dt = diff(nirs.t(1:2)); 88 | 89 | 90 | sFiles_dummy = utest_import_nirs_in_bst(fullfile(utest_dir,'dummy.nirs')); 91 | 92 | sFiles_processed = bst_process('CallProcess', ... 93 | 'process_nst_import_evt_events', ... 94 | sFiles_dummy, [], ... 95 | 'evtfile',{ fullfile(utest_dir,testCase.tmp_dir,'event.evt')}, ... 96 | 'label_cols','ConditionA,ConditionA,ConditionB,ConditionC', ... 97 | 'last_event',0 ,... 98 | 'confirm_importation', 0); 99 | 100 | assert(~isempty(sFiles_processed)); 101 | events = get_events(sFiles_processed); 102 | 103 | assert(length(events) == 3); 104 | assert(events(strcmp({events.label}, 'ConditionA')).times(2, 1) - events(strcmp({events.label}, 'ConditionB')).times(1, 1) - 12 <= nirs_dt); 105 | assert(events(strcmp({events.label}, 'ConditionB')).times(2, 1) - events(strcmp({events.label}, 'ConditionB')).times(1, 1) - 6 <= nirs_dt); 106 | assert(events(strcmp({events.label}, 'ConditionC')).times(2, 1) - events(strcmp({events.label}, 'ConditionC')).times(1, 1) - 6 <= nirs_dt); 107 | 108 | 109 | utest_reset_bst(); 110 | end 111 | 112 | 113 | end 114 | end 115 | 116 | function events = get_events(sFile) 117 | DataMat = in_bst_data(sFile.FileName, 'F'); 118 | events = DataMat.F.events; 119 | end 120 | 121 | 122 | % Generate a file with n_unique events which repete n times. 123 | % Each event last duration sample. 124 | 125 | function fake_event=generate_fake_event(n,unique_event,duration) 126 | 127 | fake_event=zeros(n*unique_event,9); 128 | sample=1; 129 | 130 | for i=1:(n*unique_event) 131 | fake_event(i,1)=sample; 132 | event_code=dec2bin(1+mod(i,unique_event),8); 133 | for j=2:9 134 | fake_event(i,j)=str2num(event_code(j-1)); 135 | end 136 | sample=sample+duration; 137 | end 138 | end -------------------------------------------------------------------------------- /bst_plugin/GLM/nst_glm_fit.m: -------------------------------------------------------------------------------- 1 | function fitted_model = nst_glm_fit(model, y,method) 2 | %NST_GLM_MODEL_FIT Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | if nargin < 4 6 | method='OLS_precoloring'; 7 | end 8 | 9 | switch method 10 | case 'OLS_precoloring' 11 | [B, covB, dfe, residuals, mse_residuals] = OLS_precoloring_fit(model, y); 12 | case 'OLS_prewhitening' 13 | %if one wants to extend to AR(p) then pass p as varagin{1} 14 | [B, covB, dfe, residuals, mse_residuals] = OLS_AR1_fit(model, y); 15 | otherwise 16 | bst_error('This method is not implemented'); 17 | return 18 | end 19 | 20 | fitted_model=struct('B',[],'covB',[],'dfe',[],'residuals',[],'mse_residuals',[]); 21 | fitted_model.B=B; 22 | fitted_model.covB=covB; 23 | fitted_model.dfe=dfe; 24 | fitted_model.residuals=residuals; 25 | fitted_model.mse_residuals=mse_residuals; 26 | end 27 | 28 | 29 | function [B, covB, dfe, residuals, mse_residuals] = OLS_precoloring_fit(model,y) 30 | 31 | % Low pass filter, applied to input data 32 | lpf = full(process_nst_glm_fit('lpf_hrf',model.hrf, size(y, 1))); 33 | % Convert to full mat since operation on sparse matrices are 34 | % single-threaded only but full 35 | y_filtered = lpf * y; 36 | 37 | if any(~isfinite(y_filtered)) 38 | warning('Found non-finite values in filtered input data.'); 39 | end 40 | 41 | % Apply filter to the design matrix 42 | model = nst_glm_apply_filter(model,'lpf', lpf ); 43 | X_filtered=model.X; 44 | 45 | %% Solve y = X*B using SVD as in SPM 46 | [B,proj_X] = nst_glm_fit_B(model,y_filtered, 'SVD'); 47 | 48 | 49 | %% For stat afterwards 50 | res_form_mat = eye(size(lpf)) - X_filtered * proj_X; 51 | lpf_lpf_T = full(lpf * lpf'); 52 | RV = res_form_mat * lpf_lpf_T; 53 | trRV = sum(diag(RV)); 54 | RVRVt = RV .* RV'; 55 | trRVRV = sum(RVRVt(:)); % faster than sum(diag(RV * RV)); 56 | dfe = trRV^2 / trRVRV; 57 | 58 | fit = X_filtered * B; 59 | residuals = y_filtered - fit; 60 | mse_residuals = var(residuals) * (size(y,1)-1) / trRV; 61 | 62 | % figure();plot(y_filtered(:, 161)/max(y_filtered(:,161)), 'b', 'LineWidth', 2); hold on; plot(X_filtered); 63 | % figure(); hold on; plot(y_filtered(:,161), 'b'); plot(residual(:,161), 'g'); plot(fit(:,161), 'r'); 64 | 65 | pXS = proj_X * lpf; 66 | covB = pXS * pXS'; 67 | 68 | end 69 | 70 | 71 | function [B_out, covB_out, dfe_out, residuals_out, mse_residuals_out] = OLS_AR1_fit(model,y ) 72 | 73 | n_chan = size(y,2); 74 | n_cond = size(model.X,2); 75 | n_time = size(y,1); 76 | 77 | B_out = zeros(n_cond,n_chan); 78 | 79 | covB_out = zeros(n_cond,n_cond,n_chan); 80 | dfe_out = zeros(1,n_chan); 81 | residuals_out = zeros(n_time,n_chan); 82 | mse_residuals_out = zeros(1,n_chan); 83 | 84 | [B_init,proj_X] = nst_glm_fit_B(model,y, 'SVD'); 85 | bst_progress('start', 'GLM - Pre-whitenning ' , 'Fitting the GLM', 1, n_chan); 86 | 87 | for i_chan=1:n_chan 88 | % Solve B for chan i_chan. We need to solve B for each channel 89 | % speratly as we are fitting one AR model per channel. 90 | tic 91 | fmodel=model; % keep a track of the model so it doesn't change 92 | 93 | y=y(:,i_chan); 94 | SX=fmodel.X; 95 | SY=y; 96 | 97 | B=B_init(:,i_chan); 98 | % Estimate the AR(1) processe on the residual 99 | res=SY-SX*B; 100 | W=nst_math_fit_AR(res',1); 101 | 102 | % Compute the filtering matrix 103 | a1 = -W(2); 104 | ka = sparse( ( eye(n_time) - diag( ones(1,n_time-1)*a1,-1) ))^(-1); 105 | Va = ka* ka'; 106 | 107 | % Compute corresponding low-pass filter 108 | S= full( inv(Va) )^(0.5); % can't use power .5 on sparse matrix 109 | 110 | % Apply the filtering to the data and the deisgn matrix 111 | SY = S*SY; 112 | fmodel = nst_glm_apply_filter(fmodel,'lpf', S ); 113 | 114 | 115 | % Compute B for Sy = SXB + Se following an iid normal distribution 116 | [B,proj_X] = nst_glm_fit_B(fmodel,SY, 'SVD'); 117 | SX=fmodel.X; 118 | Va=(inv(S))^2; 119 | % Compute stat afterwards 120 | 121 | pSX=pinv(SX); 122 | R = eye(n_time) - SX * pSX; 123 | 124 | S_Va_S_t = S * Va * S'; 125 | RV = R * S_Va_S_t; 126 | trRV = sum(diag(RV)); 127 | RVRVt = RV .* RV'; 128 | trRVRV = sum(RVRVt(:)); % faster than sum(diag(RV * RV)); 129 | 130 | fit = SX * B; 131 | residuals = SY - fit; 132 | sigma2=var(residuals); 133 | 134 | dfe = trRV^2 / trRVRV; 135 | mse_residuals = sigma2* (size(y,1)-1)/trRV; 136 | covB = pSX * pSX'; 137 | 138 | 139 | % Save stats 140 | B_out(:,i_chan)=B; 141 | covB_out(:,:,i_chan)=covB; 142 | dfe_out(i_chan)=dfe; 143 | residuals_out(:,i_chan)=residuals; 144 | mse_residuals_out(i_chan)=mse_residuals; 145 | e = toc; 146 | disp( [ '#' num2str(i_chan) ' analized in ' num2str(iter) ' iteration (' num2str(e) ' sec)']) 147 | bst_progress('inc', 1); 148 | end 149 | 150 | bst_progress('stop'); 151 | 152 | end 153 | -------------------------------------------------------------------------------- /test/SciTest.m: -------------------------------------------------------------------------------- 1 | classdef SciTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_sci_with_simulations(testCase) 26 | 27 | dt = 0.05; %sec (20Hz) 28 | time = (0:8000) * dt; %sec 29 | nb_samples = length(time); 30 | 31 | bst_create_test_subject(); 32 | 33 | %% Only noise -- uncorrelated wavelength signals 34 | signals = [randn(1, nb_samples);randn(1, nb_samples);]; 35 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 36 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 37 | sDataOut = in_bst_data(output.FileName); 38 | 39 | %Check SCI close to 0 40 | testCase.assertTrue(all(sDataOut.F<0.1)); 41 | 42 | %% Correlation outside of cardiac band 43 | freq = 5; % Hz 44 | signals = [cos(4*pi*freq*time) + randn(1, nb_samples);... 45 | cos(4*pi*freq*time) + randn(1, nb_samples)]; 46 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 47 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 48 | sDataOut = in_bst_data(output.FileName); 49 | 50 | %Check SCI close to 0 51 | testCase.assertTrue(all(sDataOut.F<0.1)); 52 | 53 | %% Perfect cardiac consistency - correlated 54 | freq = 1; % Hz 55 | signals = [cos(4*pi*freq*time); cos(4*pi*freq*time)]; 56 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 57 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 58 | sDataOut = in_bst_data(output.FileName); 59 | 60 | %Check SCI high 61 | testCase.assertTrue(all(sDataOut.F>0.99)); 62 | 63 | %% Perfect cardiac consistency - anticorrelated 64 | freq = 1; % Hz 65 | signals = [cos(4*pi*freq*time); -cos(4*pi*freq*time)]; 66 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 67 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 68 | sDataOut = in_bst_data(output.FileName); 69 | 70 | %Check SCI high 71 | testCase.assertTrue(all(sDataOut.F>0.99)); 72 | 73 | %% Consistent cardiac components - high SNR 74 | freq = 1; % Hz 75 | signals = [cos(4*pi*freq*time) + randn(1, nb_samples)*0.2; ... 76 | -cos(4*pi*freq*time) + randn(1, nb_samples)*0.2]; 77 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 78 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 79 | sDataOut = in_bst_data(output.FileName); 80 | 81 | %Check SCI > 0.75 82 | testCase.assertTrue(all(sDataOut.F>0.75)); 83 | 84 | %% Consistent cardiac components - very low SNR 85 | freq = 1; % Hz 86 | signals = [cos(4*pi*freq*time) + randn(1, nb_samples)*50; ... 87 | -cos(4*pi*freq*time) + randn(1, nb_samples)*50]; 88 | nirs_input = bst_create_nirs_data('test_all_noisy', signals, time); 89 | output = bst_process('CallProcess', 'process_nst_sci', nirs_input, []); 90 | sDataOut = in_bst_data(output.FileName); 91 | 92 | %Check SCI < 0.75 93 | testCase.assertTrue(all(sDataOut.F<0.75)); 94 | 95 | 96 | end 97 | 98 | function test_sci_on_tapping_data(testCase) 99 | repo_url = nst_get_repository_url(); 100 | data_fns = nst_request_files({{'unittest','motor_data','motor.nirs'}, ... 101 | {'unittest','motor_data','optodes.txt'}, ... 102 | {'unittest','motor_data','fiducials.txt'},... 103 | {'unittest','motor_data','sci.mat'}}, ... 104 | 1, repo_url); 105 | 106 | 107 | nirs_fn = data_fns{1}; 108 | sFile = utest_import_nirs_in_bst(nirs_fn); 109 | output = bst_process('CallProcess', 'process_nst_sci', sFile, []); 110 | sDataOut = in_bst_data(output.FileName); 111 | 112 | % Non-regression test (sci results manually checked and 113 | % recorded on 17 July 2018) 114 | expected_sci_fn = data_fns{end}; 115 | expected_sci = load(expected_sci_fn); 116 | expected_sci = expected_sci.sci_motor; 117 | 118 | testCase.assertTrue(all_close(sDataOut.F, expected_sci, 0.01, 0.01)); 119 | end 120 | 121 | 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /scripts/surface_template_full_group_pipeline_V1_all_opts.m: -------------------------------------------------------------------------------- 1 | function surface_template_full_group_pipeline_V1_all_opts() 2 | % Example for the template- and surface-based full pipeline, using the 3 | % function NST_PPL_SURFACE_TEMPLATE_V1. 4 | % 5 | % This script downloads some sample data of 10 "dummy" subjects (27 Mb), 6 | % as well as the Colin27_4NIRS template (19 Mb) if not available. 7 | % For the analysis part, precomputed fluence data are also downloaded. 8 | % Total max amount of data to download: 50 Mb, the user is asked for download 9 | % confirmation. 10 | % All downloaded data will be stored in .brainstorm/defaults/nirstorm 11 | % 12 | % Since several files will be written outside of the brainstorm database, 13 | % a temporary folder will be used (see output_dir). 14 | % 15 | % Data are imported in brainstorm in a dedicated protocol, using specific 16 | % naming conventions handled by NST_PPL_SURFACE_TEMPLATE_V1. 17 | % Then, this script emulates user-defined movement artefacts markings as 18 | % well as bad channel taggings. 19 | % These markings will be saved in output_dir. Everytime NST_PPL_SURFACE_TEMPLATE_V1 20 | % is run, markings in the brainstorm DB are saved in the output_dir. 21 | % When some nirs data is not available in the DB and need (re)importation, 22 | % these markings are loaded at the same time. 23 | % 24 | % Preprocessings and processings are then run up to group-level analysis: 25 | % 0) Figure outputs of raw time-series and SCI maps 26 | % 1) Motion-correction 27 | % 2) Resampling to 5hz 28 | % 3) Bad channels detection 29 | % 4) Conversion to delta optical density 30 | % 5) High pass filter 31 | % 6) Figure output of preprocessed time-series 32 | % 5) Projection on the cortical surface using head model 33 | % 6) 1st level GLM with pre-coloring 34 | % 7) Subject-level t-maps with figure outputs 35 | % 8) group-level GLM with MFX contrast t-maps 36 | % 37 | % This script illustrates a minimal fully functional analysis pipeline that can 38 | % serve as a basis for another custom study. 39 | % 40 | % For a more detailed description, see the wiki page: 41 | % https://github.com/Nirstorm/nirstorm/wiki/%5BWIP%5D-GLM-pipeline:-surface-and-template-based#example-script-with-all-options 42 | % 43 | % For a minimal example script, see: 44 | % surface_template_full_group_pipeline_V1.m 45 | % 46 | 47 | %% Setup brainstorm 48 | if ~brainstorm('status') 49 | % Start brainstorm without the GUI if not already running 50 | brainstorm nogui 51 | end 52 | 53 | %% Check Protocol 54 | protocol_name = 'TestSurfaceTemplateGroupPipelineV1'; 55 | if isempty(bst_get('Protocol', protocol_name)) 56 | gui_brainstorm('CreateProtocol', protocol_name, 1, 0); % UseDefaultAnat=1, UseDefaultChannel=0 57 | end 58 | 59 | % Set template for default anatomy 60 | nst_bst_set_template_anatomy('Colin27_4NIRS_Jan19'); 61 | 62 | %% Fetch data 63 | % Get list of local nirs files for the group data. 64 | % The function nst_io_fetch_sample_data takes care of downloading data to 65 | % .brainstorm/defaults/nirstorm/sample_data if necessary 66 | [nirs_fns, subject_names] = nst_io_fetch_sample_data('template_group_tapping'); 67 | 68 | %% Set root output dir (to store markings, figures and results) 69 | tmp_folder = tempdir(); 70 | if ~exist(tmp_folder, 'dir') 71 | error('Cannot locate temporary folder'); 72 | end 73 | output_dir = fullfile(tmp_folder, 'nst_ppl_surface_template_V1_example'); 74 | if ~exist(output_dir, 'dir') 75 | mkdir(output_dir); 76 | end 77 | 78 | %% Get default options 79 | options = nst_ppl_surface_template_V1('get_options'); % get default pipeline options 80 | 81 | %% Set output directories for markings 82 | options.moco.export_dir = fullfile(output_dir, 'markings_moco'); 83 | options.tag_bad_channels.export_dir = fullfile(output_dir, 'markings_bad_channels'); 84 | 85 | %% Import data 86 | sFiles = nst_ppl_surface_template_V1('import', options, nirs_fns, subject_names); 87 | 88 | % Read stimulation events from AUX channel 89 | for ifile=1:length(sFiles) 90 | evt_data = load(file_fullpath(sFiles{ifile}), 'Events'); 91 | if ~any(strcmp({evt_data.Events.label}, 'motor')) % Insure that events were not already loaded 92 | bst_process('CallProcess', 'process_evt_read', sFiles{ifile}, [], ... 93 | 'stimchan', 'NIRS_AUX', ... 94 | 'trackmode', 3, ... % Value: detect the changes of channel value 95 | 'zero', 0); 96 | % Rename event AUX1 -> motor 97 | bst_process('CallProcess', 'process_evt_rename', sFiles{ifile}, [], ... 98 | 'src', 'AUX1', 'dest', 'motor'); 99 | % Convert to extended event-> add duration of 30 sec to all motor events 100 | bst_process('CallProcess', 'process_evt_extended', sFiles{ifile}, [], ... 101 | 'eventname', 'motor', 'timewindow', [0, 30]); 102 | end 103 | end 104 | 105 | %% Simulate user-inputs 106 | % Add tagging of movement artefacts 107 | 108 | % Add bad channel tagging 109 | 110 | %% Run pipeline 111 | options.GLM_1st_level.stimulation_events = {'motor'}; 112 | options.GLM_1st_level.contrasts(1).label = 'motor'; 113 | options.GLM_1st_level.contrasts(1).vector = '[1 0]'; % a vector of weights, as a string 114 | 115 | % Run the pipeline (and save user markings): 116 | nst_ppl_surface_template_V1('analyse', options, subject_names); % Run the full pipeline 117 | %TODO: full reload of GUI tree at end of pipeline 118 | end 119 | -------------------------------------------------------------------------------- /bst_plugin/GLM/process_nst_mask_from_atlas.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_mask_from_atlas( varargin ) 2 | 3 | % @============================================================================= 4 | % This software is part of the Brainstorm software: 5 | % http://neuroimage.usc.edu/brainstorm 6 | % 7 | % Copyright (c)2000-2013 Brainstorm by the University of Southern California 8 | % This software is distributed under the terms of the GNU General Public License 9 | % as published by the Free Software Foundation. Further details on the GPL 10 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 11 | % 12 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 13 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 14 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 15 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 16 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 17 | % 18 | % For more information type "brainstorm license" at command prompt. 19 | % =============================================================================@ 20 | % 21 | % Authors: Thomas Vincent (2019) 22 | 23 | eval(macro_method); 24 | end 25 | 26 | function sProcess = GetDescription() 27 | % Description the process 28 | sProcess.Comment = 'Cortical mask from atlas scouts'; 29 | sProcess.Category = 'Custom'; 30 | sProcess.SubGroup = 'NIRS'; 31 | sProcess.Index = 1901; 32 | sProcess.Description = ''; 33 | % Definition of the input accepted by this process 34 | sProcess.InputTypes = {'data', 'raw', 'results'}; 35 | % Definition of the outputs of this process 36 | sProcess.OutputTypes = {'results', 'results', 'results'}; 37 | sProcess.nInputs = 1; 38 | sProcess.nMinFiles = 1; 39 | 40 | 41 | sProcess.options.surface_name.Comment = 'Surface: '; 42 | sProcess.options.surface_name.Type = 'text'; 43 | sProcess.options.surface_name.Value = ''; 44 | 45 | sProcess.options.atlas.Comment = 'Atlas'; 46 | sProcess.options.atlas.Type = 'atlas'; 47 | sProcess.options.atlas.Value = ''; 48 | 49 | sProcess.options.scout_names.Comment = 'Regions (comma-separated list of scout labels): '; 50 | sProcess.options.scout_names.Type = 'text'; 51 | sProcess.options.scout_names.Value = ''; 52 | 53 | sProcess.options.output_comment.Comment = 'Output label: '; 54 | sProcess.options.output_comment.Type = 'text'; 55 | sProcess.options.output_comment.Value = 'Atlas-based mask'; 56 | 57 | end 58 | 59 | %% ===== FORMAT COMMENT ===== 60 | function Comment = FormatComment(sProcess) 61 | Comment = sProcess.Comment; 62 | end 63 | 64 | %% ===== RUN ===== 65 | function OutputFiles = Run(sProcess, sInputs) 66 | 67 | % Get scout vertices & load head mesh 68 | atlas_name = sProcess.options.atlas.Value; 69 | surface_name = sProcess.options.surface_name.Value; 70 | scout_names = cellfun(@(s) strtrim(s), strsplit(sProcess.options.scout_names.Value, ','), ... 71 | 'UniformOutput', false); 72 | 73 | [sSubject, iSubject] = bst_get('Subject', sInputs(1).SubjectName); 74 | if ~isempty(sSubject.Surface) 75 | i_surface = find(strcmp({sSubject.Surface.Comment}, surface_name)); 76 | if ~isempty(i_surface) 77 | if length(i_surface) > 1 78 | warning('Multiple surfaces with name %s, taking 1st one.', surface_name); 79 | end 80 | surface_file = sSubject.Surface(i_surface(1)).FileName; 81 | sSurf = in_tess_bst(surface_file); 82 | else 83 | error('Surface %s not found.', surface_name); 84 | end 85 | else 86 | error('No surface available'); 87 | end 88 | 89 | [mask, atlas] = Compute(sSubject, sSurf, atlas_name, scout_names); 90 | 91 | DataMat.ImageGridAmp = mask; 92 | DataMat = db_template('resultsmat'); 93 | 94 | DataMat.from_atlas = atlas; 95 | 96 | % DataMat.fov_mask = fov_mask; 97 | % DataMat.fov_roi_indexes = fov_roi_indexes; 98 | 99 | DataMat.ImageGridAmp = mask; 100 | DataMat.ImagingKernel = []; 101 | DataMat.Comment = sProcess.options.output_comment.Value; 102 | DataMat.Function = ''; 103 | DataMat.Time = 1; 104 | DataMat.DataFile = []; 105 | DataMat.HeadModelFile = []; 106 | DataMat.SurfaceFile = surface_file; 107 | 108 | % Output filename 109 | DataFile = bst_process('GetNewFilename', bst_fileparts(sInputs(1).FileName), 'results_mask_'); 110 | % Save on disk 111 | bst_save(DataFile, DataMat, 'v6'); 112 | % Register in database 113 | db_add_data(sInputs(1).iStudy, DataFile, DataMat); 114 | % Return data file 115 | OutputFiles{1} = DataFile; 116 | 117 | end 118 | 119 | function [mask, atlas] = Compute(sSubject, sSurf, atlas_name, scout_names) 120 | 121 | iatlas_selected = find(strcmp({sSurf.Atlas.Name}, atlas_name)); 122 | 123 | if length(iatlas_selected) > 1 124 | warning('Multiple atlas with name %s, taking 1st one.', atlas_name); 125 | end 126 | 127 | if isempty(iatlas_selected) 128 | error('Atlas %s not found', atlas_name); 129 | else 130 | atlas = sSurf.Atlas(iatlas_selected(1)); 131 | scouts = atlas.Scouts; 132 | 133 | scouts_found = ismember(scout_names, {scouts.Label}); 134 | if ~all(scouts_found) 135 | error('Scouts not found: %s', strjoin(scout_names(~scouts_found), ', ')); 136 | end 137 | i_selected_scouts = find(ismember({scouts.Label}, scout_names)); 138 | mask = zeros(size(sSurf.Vertices,1), 1); 139 | for ii=1:length(i_selected_scouts) 140 | i_selected_scout = i_selected_scouts(ii); 141 | mask(scouts(i_selected_scout).Vertices) = i_selected_scout; 142 | end 143 | end 144 | end -------------------------------------------------------------------------------- /test/OptimalMontageTest.m: -------------------------------------------------------------------------------- 1 | classdef OptimalMontageTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_OM_from_cortex(testCase) 26 | global GlobalData; 27 | subject_name = bst_create_test_subject('Colin27_4NIRS'); 28 | 29 | stg_vertex_id = 19552; 30 | roi_scout_selection = bst_create_scout(subject_name, 'cortex', 'roi_temporal', stg_vertex_id, 2, 'User scouts'); 31 | 32 | extent_cm = 3; % centimeter 33 | head_vertices = process_nst_cpt_fluences_from_cortex('proj_cortex_scout_to_scalp', ... 34 | roi_scout_selection, extent_cm * 0.01); 35 | wavelengths = 685; 36 | fluence_dir = cpt_spherical_fluences(roi_scout_selection.sSubject, head_vertices, wavelengths); 37 | bst_process('CallProcess', ... 38 | 'process_nst_OM_from_cortex', [], [], ... 39 | 'scout_sel_roi', roi_scout_selection, ... 40 | 'cortex_to_scalp_extent', extent_cm, ... 41 | 'condition_name', 'OM_test', ... 42 | 'wavelengths', strjoin(arrayfun(@num2str, wavelengths, 'UniformOutput', false), ','), ... 43 | 'data_source', fluence_dir, ... 44 | 'nb_sources', 1, ... 45 | 'nb_detectors', 2, ... 46 | 'nAdjacentDet', 0, ... 47 | 'exist_weight', 0, ... 48 | 'sep_optode', {[0 55],'mm', 0}); 49 | 50 | testCase.assertEmpty(GlobalData.lastestFullErrMsg); 51 | % TODO add checks on nb optodes + separations 52 | end 53 | 54 | 55 | function test_OM_from_head(testCase) 56 | global GlobalData; 57 | subject_name = bst_create_test_subject('Colin27_4NIRS'); 58 | head_vertex_idx = 6465; %ASSUME: valid for Colin27 4NIRS default head mesh 59 | head_scout_selection = bst_create_scout(subject_name, 'scalp', 'OM_temporal', head_vertex_idx, 5, 'User scouts'); 60 | 61 | stg_vertex_id = 19552; %ASSUME: valid for Colin27 4NIRS default mid mesh 62 | roi_scout_selection = bst_create_scout(subject_name, 'cortex', 'roi_temporal', stg_vertex_id, 2, 'User scouts'); 63 | 64 | wavelengths = 685; 65 | fluence_dir = cpt_spherical_fluences(head_scout_selection.sSubject, head_scout_selection.sScout.Vertices, wavelengths); 66 | 67 | bst_process('CallProcess', ... 68 | 'process_nst_OM_from_head', [], [], ... 69 | 'scout_sel_head', head_scout_selection, ... 70 | 'scout_sel_roi', roi_scout_selection, ... 71 | 'condition_name', 'OM_test', ... 72 | 'wavelengths', strjoin(arrayfun(@num2str, wavelengths, 'UniformOutput', false), ','), ... 73 | 'data_source', fluence_dir, ... %TODO 74 | 'nb_sources', 1, ... 75 | 'nb_detectors', 2, ... 76 | 'nAdjacentDet', 0, ... 77 | 'exist_weight', 0, ... 78 | 'sep_optode', {[0 55],'mm', 0}); 79 | testCase.assertEmpty(GlobalData.lastestFullErrMsg); 80 | % TODO add checks on nb optodes + separations 81 | end 82 | 83 | end 84 | end 85 | 86 | 87 | 88 | % function fluence = load_fluence_spherical(vertex_id, sInputs) 89 | % 90 | % ChannelMat = in_bst_channel(sInputs(1).ChannelFile); 91 | % wavelengths = ChannelMat.Nirs.Wavelengths; 92 | % 93 | % fluence_bfn = sprintf('fluence_spherical_%d.mat', vertex_id); 94 | % fluence_fn = bst_fullfile(bst_get('BrainstormUserDir'), 'defaults', ... 95 | % 'nirstorm', fluence_bfn); 96 | % if ~file_exist(fluence_fn) 97 | % % Create folder 98 | % if ~file_exist(bst_fileparts(fluence_fn)) 99 | % mkdir(bst_fileparts(fluence_fn)); 100 | % end 101 | % 102 | % [sSubject, iSubject] = bst_get('Subject', sInputs.SubjectName); 103 | % anatomy_file = sSubject.Anatomy(sSubject.iAnatomy).FileName; 104 | % head_mesh_fn = sSubject.Surface(sSubject.iScalp).FileName; 105 | % 106 | % % Obtain the head mesh 107 | % sHead = in_tess_bst(head_mesh_fn); 108 | % 109 | % % Obtain the anatomical MRI 110 | % sMri = in_mri_bst(anatomy_file); 111 | % [dimx, dimy, dimz] = size(sMri.Cube); 112 | % 113 | % vertex = sHead.Vertices(vertex_id, :); 114 | % % Vertices: SCS->MRI, MRI(MM)->MRI(Voxels) 115 | % vertex = round(cs_convert(sMri, 'scs', 'mri', vertex) * 1000 ./ sMri.Voxsize); 116 | % 117 | % dcut = 30; %mm 118 | % svox = sMri.Voxsize; 119 | % fluence_vol = zeros(dimx, dimy, dimz); 120 | % for i=1:dimx 121 | % for j=1:dimy 122 | % for k=1:dimz 123 | % cp = (vertex - [i,j,k]) .* svox; 124 | % fluence_vol(i,j,k) = max(0, 1 - sqrt(sum(cp .* cp)) / dcut); 125 | % end 126 | % end 127 | % end 128 | % save(fluence_fn, 'fluence_vol'); 129 | % else 130 | % load(fluence_fn, 'fluence_vol'); 131 | % end 132 | % 133 | % for iwl=1:length(wavelengths) 134 | % fluence{iwl}.fluence.data = fluence_vol; 135 | % end 136 | % 137 | % end 138 | % 139 | -------------------------------------------------------------------------------- /bst_plugin/preprocessing/process_nst_separations.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_separations( varargin ) 2 | % process_nst_separation: compute distances between sources and detectors 3 | % 4 | % @============================================================================= 5 | % This function is part of the Brainstorm software: 6 | % http://neuroimage.usc.edu/brainstorm 7 | % 8 | % Copyright (c)2000-2017 University of Southern California & McGill University 9 | % This software is distributed under the terms of the GNU General Public License 10 | % as published by the Free Software Foundation. Further details on the GPLv3 11 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 12 | % 13 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 14 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 15 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 16 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 17 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 18 | % 19 | % For more information type "brainstorm license" at command prompt. 20 | % =============================================================================@ 21 | % 22 | % Authors: Thomas Vincent, 2018 23 | % 24 | eval(macro_method); 25 | end 26 | 27 | 28 | %% ===== GET DESCRIPTION ===== 29 | function sProcess = GetDescription() 30 | % Description the process 31 | sProcess.Comment = 'Compute separations'; 32 | sProcess.Category = 'File'; 33 | sProcess.SubGroup = {'NIRS', 'Pre-process'}; 34 | sProcess.Index = 1202; 35 | sProcess.isSeparator = 1; 36 | sProcess.Description = 'https://github.com/Nirstorm/nirstorm/wiki/Optode-separations'; 37 | 38 | % Definition of the input accepted by this process 39 | sProcess.InputTypes = {'data','raw'}; 40 | sProcess.OutputTypes = {'data','data'}; 41 | 42 | sProcess.nInputs = 1; 43 | sProcess.nMinFiles = 1; 44 | 45 | end 46 | 47 | 48 | 49 | %% ===== FORMAT COMMENT ===== 50 | function Comment = FormatComment(sProcess) 51 | Comment = sProcess.Comment; 52 | end 53 | 54 | function OutputFiles = Run(sProcess, sInput) 55 | 56 | OutputFiles = {}; 57 | 58 | % Load recordings 59 | if strcmp(sInput.FileType, 'data') % Imported data structure 60 | sDataIn = in_bst_data(sInput.FileName); 61 | elseif strcmp(sInput.FileType, 'raw') % Continuous data file 62 | sDataIn = in_bst(sInput.FileName, [], 1, 1, 'no'); 63 | end 64 | 65 | ChannelMat = in_bst_channel(sInput.ChannelFile); 66 | [nirs_ichans, tmp] = channel_find(ChannelMat.Channel, 'NIRS'); 67 | 68 | separations = Compute(ChannelMat.Channel(nirs_ichans)) * 100; %convert to cm 69 | 70 | 71 | % Get the output condition - create it if it doesn't exist 72 | if strcmpi(sInput.FileType, 'raw') 73 | % Create or get the target study 74 | newCondition = strrep(sInput.Condition, '@raw', ''); 75 | iStudy = db_add_condition(sInput.SubjectName, newCondition); 76 | db_set_channel(iStudy, sInput.ChannelFile, 2, 0); 77 | else 78 | iStudy = sInput.iStudy; 79 | end 80 | sStudy = bst_get('Study', iStudy); 81 | 82 | % Save time-series data 83 | data_out = zeros(size(sDataIn.F, 1), 1); 84 | data_out(nirs_ichans,:) = separations; 85 | sDataOut = db_template('data'); 86 | sDataOut.F = data_out; 87 | sDataOut.Comment = 'Separations'; 88 | sDataOut.ChannelFlag = sDataIn.ChannelFlag; 89 | sDataOut.Time = [1]; 90 | sDataOut.DataType = 'recordings'; 91 | sDataOut.nAvg = 1; 92 | sDataOut.DisplayUnits = 'cm'; 93 | 94 | % Generate a new file name in the same folder 95 | OutputFile = bst_process('GetNewFilename', bst_fileparts(sStudy.FileName), 'data_chan_dist'); 96 | sDataOut.FileName = file_short(OutputFile); 97 | bst_save(OutputFile, sDataOut, 'v7'); 98 | % Register in database 99 | db_add_data(iStudy, OutputFile, sDataOut); 100 | OutputFiles{end+1} = OutputFile; 101 | end 102 | 103 | 104 | function separations = Compute(channels, pair_ids) 105 | % Compute distances between sources and detectors for given channels or pairs. 106 | % Note: output unit is the same as the one of the input. 107 | % 108 | % Inputs: 109 | % - channels (struct array): 110 | % Channel brainstorm structure (see db_template('channeldesc')) 111 | % [- pair_ids ] (2d array of int): size(nb_pairs x 2) 112 | % List of pairs for which to compute separations. 113 | % Column 1 contains source ids and column 2 contains detector ids. 114 | % Must be consistent with given channels. 115 | % 116 | % Outputs: 117 | % 1D array of double containing separations in the same unit as 118 | % in given channel. 119 | % If pair_ids is not given, then size(separations) = nb_channels 120 | % If pair_ids is given, then size(separations) = size(pair_indexes, 1); 121 | 122 | if nargin < 2 % pair_indexes not given 123 | separations = zeros(length(channels), 1); 124 | for ichan=1:length(channels) 125 | if strcmp(channels(ichan).Type, 'NIRS') 126 | separations(ichan) = euc_dist(channels(ichan).Loc(:,1), ... 127 | channels(ichan).Loc(:,2)); 128 | else 129 | separations(ichan) = nan; 130 | end 131 | end 132 | else 133 | montage_info = nst_montage_info_from_bst_channels(channels); 134 | separations = zeros(size(pair_ids, 1), 1); 135 | for ipair=1:size(pair_ids, 1) 136 | src_idx = montage_info.src_ids == pair_ids(ipair, 1); 137 | det_idx = montage_info.det_ids == pair_ids(ipair, 2); 138 | separations(ipair) = euc_dist(montage_info.src_pos(src_idx, :), ... 139 | montage_info.det_pos(det_idx, :)); 140 | end 141 | end 142 | end 143 | 144 | function d = euc_dist(p1, p2) 145 | d = sqrt(sum((p1 - p2).^2)); 146 | end 147 | -------------------------------------------------------------------------------- /bst_plugin/GLM/process_nst_glm_contrast_ttest.m: -------------------------------------------------------------------------------- 1 | function varargout = process_nst_glm_contrast_ttest( varargin ) 2 | % process_nst_compute_ttest 3 | % Compute a ttest according to a spm-like constrast vector using 4 | % t = cB / sqrt( c Cov(B) c^T ) 5 | % 6 | % B, cov(B) and the corresponding degrree of freedom are estimated inprocess_nst_compute_glm. 7 | % 8 | % 9 | % @============================================================================= 10 | % This function is part of the Brainstorm software: 11 | % http://neuroimage.usc.edu/brainstorm 12 | % 13 | % Copyright (c)2000-2017 University of Southern California & McGill University 14 | % This software is distributed under the terms of the GNU General Public License 15 | % as published by the Free Software Foundation. Further details on the GPLv3 16 | % license can be found at http://www.gnu.org/copyleft/gpl.html. 17 | % 18 | % FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE 19 | % UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY 20 | % WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF 21 | % MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY 22 | % LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. 23 | % 24 | % For more information type "brainstorm license" at command prompt. 25 | % =============================================================================@ 26 | % 27 | % Authors: Edouard Delaire, Thomas Vincent 2018 28 | % 29 | eval(macro_method); 30 | end 31 | 32 | 33 | 34 | %% ===== GET DESCRIPTION ===== 35 | function sProcess = GetDescription() 36 | % Description the process 37 | sProcess.Comment = 'GLM - contrast t-test'; 38 | sProcess.Category = 'Stat1'; 39 | sProcess.SubGroup = {'NIRS', 'GLM'}; 40 | sProcess.Index = 1604; 41 | sProcess.isSeparator = 0; 42 | sProcess.Description = ''; 43 | 44 | % Definition of the input accepted by this process 45 | sProcess.InputTypes = {'data', 'results'}; 46 | sProcess.OutputTypes = {'pdata', 'presults'}; %TODO sort out output types depending on data type (chan or surf) 47 | 48 | sProcess.nInputs = 1; 49 | sProcess.nMinFiles = 1; 50 | 51 | % === tail for the ttest 52 | sProcess.options.tail.Comment = {'One-tailed (-)', 'Two-tailed', 'One-tailed (+)', ''; ... 53 | 'one-', 'two', 'one+', ''}; 54 | sProcess.options.tail.Type = 'radio_linelabel'; 55 | sProcess.options.tail.Value = 'one+'; 56 | end 57 | 58 | 59 | %% ===== FORMAT COMMENT ===== 60 | function Comment = FormatComment(sProcess) 61 | Comment = sProcess.Comment; 62 | % Comment = [ Comment ' C = ' sProcess.options.Contrast.Value ]; 63 | end 64 | 65 | function sOutput = Run(sProcess, sInputs) 66 | sOutput = []; 67 | 68 | con_data = in_bst_data(sInputs(1).FileName); 69 | 70 | if isfield(con_data, 'SurfaceFile') 71 | surface_data = 1; 72 | 73 | 74 | mask = con_data.edf > 0 ; 75 | con_mat = con_data.ImageGridAmp(mask)'; 76 | edf = con_data.edf(mask); 77 | con_std = real(con_data.Std(mask))'; 78 | 79 | else 80 | surface_data = 0; 81 | 82 | ChannelMat = in_bst_channel(sInputs.ChannelFile); 83 | [nirs_ichans, tmp] = channel_find(ChannelMat.Channel, 'NIRS'); 84 | 85 | con_mat = con_data.F(nirs_ichans)'; 86 | con_std = real(con_data.Std(nirs_ichans))'; 87 | edf = con_data.edf(nirs_ichans); 88 | end 89 | 90 | 91 | t_stat = con_mat ./ con_std ; 92 | t_stat(con_std==0) = 0; 93 | p = process_test_parametric2('ComputePvalues', t_stat, edf, 't', ... 94 | sProcess.options.tail.Value ); 95 | 96 | 97 | if isfield(con_data, 'SurfaceFile') 98 | tmp = zeros(size(con_data.edf)); 99 | tmp(mask)= p; 100 | p=tmp; 101 | 102 | tmp = zeros(size(con_data.edf)); 103 | tmp(mask)= t_stat; 104 | t_stat=tmp; 105 | end 106 | 107 | comment = 't-stat'; 108 | switch sProcess.options.tail.Value 109 | case {'one-'} 110 | comment=[ comment ' < 0 ']; 111 | case {'two'} 112 | comment=[ comment ' <> 0 ']; 113 | case { 'one+'} 114 | comment=[ comment ' > 0 ']; 115 | end 116 | 117 | % iStudy = sInputs.iStudy; 118 | % [tmp, iSubject] = bst_get('Subject', sInputs(1).SubjectName); 119 | % [sStudyIntra, iStudyIntra] = bst_get('AnalysisIntraStudy', iSubject); 120 | % [ChannelFile] = bst_get('ChannelFileForStudy', iStudy); 121 | % [tmp, iChannelStudy] = bst_get('ChannelForStudy', iStudyIntra); 122 | % db_set_channel(iChannelStudy, ChannelFile, 0, 0); 123 | % output_fn = bst_process('GetNewFilename', fileparts(sStudyIntra.FileName), 'pdata_ttest_matrix'); 124 | 125 | % Output of statmap 126 | sOutput = db_template('statmat'); 127 | sOutput.pmap = p'; 128 | sOutput.tmap = t_stat'; 129 | sOutput.contrast_name = con_data.contrast_name; 130 | 131 | df_map = zeros(size(sOutput.tmap)); 132 | if length(edf) > 1 133 | df_map(con_std>0) = edf(con_std>0); 134 | else 135 | df_map(con_std>0) = edf; 136 | end 137 | sOutput.df = df_map; 138 | 139 | sOutput.Correction = 'no'; 140 | if surface_data 141 | sOutput.SurfaceFile = con_data.SurfaceFile; 142 | sOutput.ChannelFlag = []; 143 | else 144 | sOutput.ChannelFlag = con_data.ChannelFlag; %TODO: use current channel flags 145 | sOutput.Options.SensorTypes = 'NIRS'; 146 | end 147 | sOutput.Time = [1]; 148 | sOutput.ColormapType = 'stat2'; 149 | sOutput.DisplayUnits = 't'; 150 | sOutput.nComponents = 1; 151 | 152 | sOutput.Comment = comment; 153 | 154 | sOutput = bst_history('add', sOutput, con_data.History, ''); 155 | sOutput = bst_history('add', sOutput, 'ttest computation', comment); 156 | 157 | end 158 | -------------------------------------------------------------------------------- /test/MbllTest.m: -------------------------------------------------------------------------------- 1 | classdef MbllTest < matlab.unittest.TestCase 2 | 3 | properties 4 | tmp_dir 5 | end 6 | 7 | methods(TestMethodSetup) 8 | function setup(testCase) 9 | tmpd = tempname; 10 | mkdir(tmpd); 11 | testCase.tmp_dir = tmpd; 12 | utest_bst_setup(); 13 | end 14 | end 15 | 16 | methods(TestMethodTeardown) 17 | function tear_down(testCase) 18 | rmdir(testCase.tmp_dir, 's'); 19 | utest_clean_bst(); 20 | end 21 | end 22 | 23 | methods(Test) 24 | 25 | function test_mbll_with_simulations(testCase) 26 | 27 | delta_hb = [ 0.01;... %HbO (mmol.l-1) 28 | -0.005]; %HbR (mmol.l-1) 29 | delta_hb = delta_hb / 1000; % mol.l-1 30 | 31 | age = 25; 32 | % HbT_0 = 1.7 / 1000; % (mol.l-1) -> blood concentration 33 | HbT_0 = 100*10^-6; % (mol.l-1) -> tissue concentration 34 | sat = 0.7; 35 | hb_0 = [HbT_0 * sat; 36 | HbT_0 * (1-sat)]; 37 | 38 | separation = 1; %cm 39 | wavelengths = [690, 832]; 40 | pvf = 1; 41 | hb_extinctions = process_nst_mbll('get_hb_extinctions', wavelengths); 42 | i_light_ref = 1e6; 43 | 44 | y_baseline = mbll_fwd(i_light_ref, hb_0, hb_extinctions, age, separation, pvf); 45 | y_activ = mbll_fwd(i_light_ref, hb_0 + delta_hb, hb_extinctions, age, separation, pvf); 46 | 47 | dt = 0.1; %sec 48 | nb_samples = 1000; 49 | time = (0:(nb_samples-1))*dt; 50 | 51 | y = zeros(2, nb_samples) + repmat(y_baseline, 1, nb_samples); 52 | activ_window_samples = 300:500; 53 | y(:, activ_window_samples) = repmat(y_activ, 1, length(activ_window_samples)); 54 | 55 | bst_create_test_subject(); 56 | sRaw = bst_create_nirs_data('simulated_evoked_raw', y, time, {'S1D1WL690','S1D1WL832'},... 57 | [1 1;1 1;1 1], [1.01 1.01;1 1;1 1]); 58 | 59 | sOutput = bst_process('CallProcess', 'process_nst_mbll', sRaw, [], ... 60 | 'option_age', age, ... 61 | 'option_pvf', pvf, ... 62 | 'option_dpf_method', 2, ... % Ducan 63 | 'option_baseline_method', 1, ... % mean 64 | 'timewindow', [0 (activ_window_samples(1)-2)*dt], ... 65 | 'option_do_plp_corr', 1); 66 | sDataOut = in_bst_data(sOutput.FileName); 67 | 68 | dhb_signal = zeros(3, nb_samples); 69 | dhb_signal(1:2, activ_window_samples) = repmat(delta_hb, 1, length(activ_window_samples)); 70 | dhb_signal(3,:) = sum(dhb_signal(1:2, :)); 71 | 72 | if 0 73 | figure(); hold on; 74 | plot(time,dhb_signal(1,:)*1000, 'r'); 75 | plot(time,dhb_signal(2,:)*1000, 'b'); 76 | 77 | plot(time, sDataOut.F(1,:)*1000, 'r--'); 78 | plot(time, sDataOut.F(2,:)*1000, 'b--'); 79 | end 80 | 81 | testCase.assertTrue(all_close(dhb_signal*1000, sDataOut.F*1000)); 82 | end 83 | 84 | function test_mbll_on_tapping_data(testCase) 85 | repo_url = nst_get_repository_url(); 86 | data_fns = nst_request_files({{'unittest','motor_data','motor.nirs'}, ... 87 | {'unittest','motor_data','optodes.txt'}, ... 88 | {'unittest','motor_data','fiducials.txt'}... 89 | {'unittest','motor_data','mbll.mat'}}, ... 90 | 1, repo_url); 91 | 92 | nirs_fn = data_fns{1}; 93 | sFile = utest_import_nirs_in_bst(nirs_fn); 94 | sFile_bad_tagged = bst_process('CallProcess', 'process_nst_detect_bad', sFile, [], ... 95 | 'option_remove_negative', 1, ... 96 | 'option_invalidate_paired_channels', 1, ... 97 | 'option_max_sat_prop', 1); 98 | sFileHb = bst_process('CallProcess', 'process_nst_mbll', sFile_bad_tagged, [], ... 99 | 'option_age', {25, ''}, ... 100 | 'option_pvf', {50, ''}, ... 101 | 'option_baseline_method', {1, {'mean', 'median'}}, ... 102 | 'option_do_plp_corr', 1); 103 | 104 | sDataOut = in_bst_data(sFileHb.FileName); 105 | 106 | % Non-regression test (on 23rd July 2018) 107 | expected_mbll_fn = data_fns{end}; 108 | expected_mbll = load(expected_mbll_fn); 109 | expected_mbll = expected_mbll.mbll_motor; 110 | 111 | if 0 112 | figure(); hold on; 113 | plot(sDataOut.Time,expected_mbll(1,:), 'r'); 114 | plot(sDataOut.Time,expected_mbll(2,:), 'b'); 115 | 116 | plot(sDataOut.Time, sDataOut.F(1,:), 'r--'); 117 | plot(sDataOut.Time, sDataOut.F(2,:), 'b--'); 118 | end 119 | 120 | testCase.assertTrue(all_close(sDataOut.F*1000, expected_mbll*1000)); 121 | end 122 | 123 | end 124 | 125 | end 126 | 127 | function i_light_output = mbll_fwd(i_light_ref, concentrations, extinctions, age, separation, pvf) 128 | y0 = [5.38;4.67]; 129 | a1 = [0.049;0.062]; 130 | a2 = [0.877;0.819]; 131 | dpf = y0 + a1 .* age.^a2; %checked that dfp was OK 132 | i_light_output = i_light_ref * power(repmat(10,2,1), -separation .* extinctions * concentrations .* dpf / pvf); 133 | end 134 | 135 | --------------------------------------------------------------------------------