Skip to content

Commit b173d84

Browse files
committed
Initial import of BIDS-MATLAB source code and tests
Add core BIDS-MATLAB source files, internal utilities, transformers, documentation, templates, and a comprehensive test suite. This initial commit establishes the project structure, including internal/private APIs, user-facing functions, example notebooks, and developer documentation.
1 parent b8c1c42 commit b173d84

File tree

391 files changed

+32862
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

391 files changed

+32862
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# BIDS-MATLAB +internal package
2+
3+
This package contains functions for BIDS-MATLAB's internal use only.
4+
5+
Do not call these functions directly from your code! They are undocumented and
6+
subject to change at any time.
7+
If you call these functions from your code, your code may break or produce incorrect results!
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function structure = add_missing_field(structure, field)
2+
%
3+
% USAGE::
4+
%
5+
% structure = add_missing_field(structure, field)
6+
%
7+
8+
% (C) Copyright 2021 BIDS-MATLAB developers
9+
10+
if ~isfield(structure, field)
11+
structure(1).(field) = '';
12+
end
13+
end
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
function [subject, status, previous] = append_to_layout(file, subject, modality, schema, previous)
2+
%
3+
% appends a file to the BIDS layout by parsing it according to the provided schema
4+
%
5+
% USAGE::
6+
%
7+
% subject = append_to_layout(file, subject, modality, schema == [])
8+
%
9+
% :param file:
10+
% :type file: char
11+
%
12+
% :param subject: subject sub-structure from the BIDS layout
13+
% :type subject: structure
14+
%
15+
% :param modality:
16+
% :type modality: char
17+
%
18+
% :param schema:
19+
% :type schema: structure
20+
%
21+
%
22+
23+
% (C) Copyright 2021 BIDS-MATLAB developers
24+
25+
pth = [subject.path, filesep, modality];
26+
27+
% We speed up indexing by checking that the current file has the same basename
28+
% as the previous one.
29+
% In this case we can copy most of the info from the previous file
30+
if same_data(file, previous)
31+
32+
% Skip file in case we are using the schema and faced with a file that
33+
% does have the same basename as the previous file
34+
% but not a recognized extension
35+
% - <match>_events.tsv
36+
% - <match>_events.mat
37+
%
38+
if ~isempty(schema.content) && ...
39+
~any(ismember(file(previous.data.len:end), ...
40+
previous.allowed_ext))
41+
[msg, id] = error_message('unknownExtension', file, file(previous.data.len:end));
42+
bids.internal.error_handling(mfilename, id, msg, true, schema.verbose);
43+
status = 0;
44+
return
45+
end
46+
47+
subject.(modality)(end + 1, 1) = subject.(modality)(end, 1);
48+
subject.(modality)(end, 1).ext = file(previous.data.len:end);
49+
subject.(modality)(end, 1).filename = file;
50+
51+
dep_fname = fullfile(pth, subject.(modality)(end - 1, 1).filename);
52+
subject.(modality)(end).dependencies.data{end + 1, 1} = dep_fname;
53+
status = 1;
54+
return
55+
56+
else
57+
58+
% Parse file fist to identify the suffix group in the template.
59+
% Then reparse the file using the entity-label pairs defined in the schema.
60+
p = bids.internal.parse_filename(file);
61+
if isempty(p)
62+
status = 0;
63+
return
64+
end
65+
66+
if ~isempty(schema.content)
67+
68+
suffix_group = schema.find_suffix_group(modality, p.suffix);
69+
70+
if isempty(suffix_group) || strcmp(suffix_group, '')
71+
[msg, id] = error_message('unknownSuffix', file, p.suffix);
72+
bids.internal.error_handling(mfilename, id, msg, true, schema.verbose);
73+
status = 0;
74+
return
75+
end
76+
77+
datatypes = schema.get_datatypes();
78+
79+
this_suffix_group = datatypes.(modality).(suffix_group);
80+
81+
allowed_extensions = this_suffix_group.extensions;
82+
83+
schema_entities = schema.return_entities_for_suffix_group(this_suffix_group);
84+
required_entities = schema.required_entities_for_suffix_group(this_suffix_group);
85+
86+
present_entities = fieldnames(p.entities);
87+
missing_entities = ~ismember(required_entities, present_entities);
88+
unknown_entity = present_entities(~ismember(present_entities, schema_entities));
89+
90+
extension = p.ext;
91+
% in case we are dealing with a folder
92+
% (can be the case for some MEG formats: .ds)
93+
if exist(fullfile(pth, p.filename), 'dir')
94+
extension = [extension '/'];
95+
end
96+
97+
%% Checks that this file is BIDS compliant
98+
if ~ismember('*', allowed_extensions) && ...
99+
~ismember(extension, allowed_extensions)
100+
[msg, id] = error_message('unknownExtension', file, extension);
101+
end
102+
103+
if ~isempty(unknown_entity)
104+
[msg, id] = error_message('unknownEntity', file, ...
105+
strjoin(cellstr(unknown_entity), ' '));
106+
end
107+
108+
if any(missing_entities)
109+
missing_entities = required_entities(missing_entities);
110+
[msg, id] = error_message('missingRequiredEntity', file, ...
111+
strjoin(cellstr(missing_entities), ' '));
112+
end
113+
114+
if exist('id', 'var')
115+
bids.internal.error_handling(mfilename, id, msg, true, schema.verbose);
116+
status = 0;
117+
return
118+
end
119+
120+
p = bids.internal.parse_filename(file, schema_entities);
121+
if isempty(p)
122+
status = 0;
123+
return
124+
end
125+
126+
previous.allowed_ext = allowed_extensions;
127+
128+
end
129+
130+
p.metafile = bids.internal.get_meta_list(fullfile(subject.path, modality, file));
131+
132+
p.dependencies.explicit = {};
133+
p.dependencies.data = {};
134+
p.dependencies.group = {};
135+
136+
if ~isempty(subject.(modality))
137+
[subject.(modality), p] = bids.internal.match_structure_fields(subject.(modality), p);
138+
139+
end
140+
141+
if isempty(subject.(modality))
142+
subject.(modality) = p;
143+
144+
else
145+
subject.(modality)(end + 1, 1) = p;
146+
147+
end
148+
149+
status = 1;
150+
151+
end
152+
153+
end
154+
155+
function status = same_data(file, previous)
156+
157+
status = strncmp(previous.data.base, file, previous.data.len);
158+
159+
end
160+
161+
function [msg, msg_id] = error_message(msg_id, file, extra)
162+
163+
msg = sprintf('Skipping file: %s.\n', bids.internal.format_path(file));
164+
165+
switch msg_id
166+
167+
case 'unknownExtension'
168+
msg = sprintf('%s Unknown extension: ''%s''', msg, extra);
169+
170+
case 'missingRequiredEntity'
171+
msg = sprintf('%s Missing REQUIRED entity: ''%s''', msg, extra);
172+
173+
case 'unknownEntity'
174+
msg = sprintf('%s Unknown entities: ''%s''', msg, extra);
175+
176+
case 'unknownSuffix'
177+
msg = sprintf('%s Unknown suffix: ''%s''', msg, extra);
178+
179+
end
180+
181+
msg = sprintf('%s\nTry using the parameter: " ''use_schema'', false "\n', msg);
182+
183+
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
function str = camel_case(str)
2+
%
3+
% Removes non alphanumeric characters and uppercase first letter of all
4+
% words but the first
5+
%
6+
% USAGE::
7+
%
8+
% str = camel_case(str)
9+
%
10+
% :param str:
11+
% :type str: char
12+
%
13+
% :returns:
14+
% :str: (char) returns the input with an upper case for first letter
15+
% for all words but the first one (``camelCase``) and
16+
% removes invalid characters (like spaces).
17+
%
18+
19+
% (C) Copyright 2018 BIDS-MATLAB developers
20+
21+
% camel case: upper case for first letter for all words but the first one
22+
spaceIdx = regexp(str, '[a-zA-Z0-9+]*', 'start');
23+
str(spaceIdx(2:end)) = upper(str(spaceIdx(2:end)));
24+
25+
% remove invalid characters
26+
[unvalidCharacters] = regexp(str, '[^a-zA-Z0-9+]');
27+
str(unvalidCharacters) = [];
28+
29+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
function list = create_unordered_list(list)
2+
%
3+
% turns a cell string or a structure into a string
4+
% that is an unordered list to print to the screen
5+
%
6+
% USAGE::
7+
%
8+
% list = bids.internal.create_unordered_list(list)
9+
%
10+
% :param list: obligatory argument.
11+
% :type list: cell string or structure
12+
%
13+
%
14+
15+
% (C) Copyright 2022 Remi Gau
16+
17+
if bids.internal.is_octave
18+
warning('off', 'Octave:mixed-string-concat');
19+
end
20+
21+
prefix = '\n\t- ';
22+
23+
if ischar(list)
24+
list = cellstr(list);
25+
end
26+
27+
if iscell(list)
28+
29+
for i = 1:numel(list)
30+
if isnumeric(list{i})
31+
list{i} = num2str(list{i});
32+
end
33+
end
34+
35+
list = sprintf([prefix, strjoin(list, prefix), '\n']);
36+
37+
elseif isstruct(list)
38+
39+
output = '';
40+
fields = fieldnames(list);
41+
42+
for i = 1:numel(fields)
43+
content = list.(fields{i});
44+
if ~iscell(content)
45+
content = {content};
46+
end
47+
48+
for j = 1:numel(content)
49+
if isnumeric(content{j})
50+
content{j} = num2str(content{j});
51+
end
52+
end
53+
54+
output = [output prefix fields{i} ': {' strjoin(content, ', ') '}'];
55+
end
56+
57+
list = sprintf(output);
58+
59+
end
60+
61+
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
function filename = download(URL, output_dir, verbose)
2+
%
3+
% USAGE::
4+
%
5+
% filename = download(URL, output_dir, verbose)
6+
%
7+
8+
% (C) Copyright 2021 BIDS-MATLAB developers
9+
if nargin < 2
10+
output_dir = pwd;
11+
end
12+
bids.util.mkdir(output_dir);
13+
14+
msg = sprintf('Downloading dataset from:\n %s\n\n', URL);
15+
print_to_screen(msg, verbose);
16+
17+
tokens = regexp(URL, '/', 'split');
18+
protocol = tokens{1};
19+
20+
filename = tokens{end};
21+
if strcmp(filename, '?zip=')
22+
[~, filename] = fileparts(tempname);
23+
filename = [filename '.zip'];
24+
end
25+
26+
if exist(filename, 'file')
27+
delete(filename);
28+
end
29+
30+
if ismember(protocol, {'http:', 'https:'})
31+
32+
try
33+
urlwrite(URL, filename); %#ok<*URLWR>
34+
catch
35+
options = '';
36+
if ~verbose
37+
options = '-q';
38+
end
39+
system(sprintf('wget %s %s', options, URL));
40+
end
41+
42+
% move file in case it was not downloaded in the root dir
43+
if ~exist(fullfile(output_dir, filename), 'file')
44+
print_to_screen([bids.internal.format_path(filename), ...
45+
' --> ', ...
46+
bids.internal.format_path(output_dir)], verbose);
47+
movefile(filename, fullfile(output_dir, filename));
48+
end
49+
filename = fullfile(output_dir, filename);
50+
51+
else
52+
53+
ftp_server = tokens{3};
54+
ftpobj = ftp(ftp_server);
55+
56+
filename = strjoin(tokens(4:end), '/');
57+
filename = mget(ftpobj, filename, output_dir);
58+
59+
end
60+
61+
if iscell(filename)
62+
filename = filename{1};
63+
end
64+
65+
print_to_screen(' Done\n\n', verbose);
66+
67+
end
68+
69+
function print_to_screen(msg, verbose)
70+
if verbose
71+
fprintf(1, msg);
72+
end
73+
end

0 commit comments

Comments
 (0)