Skip to content

Commit e5b4104

Browse files
authored
Merge pull request #28 from cpp-lln-lab/meta
[ENH] improve metadata finding heuristics
2 parents 46817f1 + 3231c08 commit e5b4104

File tree

4 files changed

+144
-82
lines changed

4 files changed

+144
-82
lines changed

miss_hit.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ copyright_entity: "spm_2_bids developers"
1717
# metric for code quality
1818
metric "cnest": limit 5
1919
metric "file_length": limit 500
20-
metric "cyc": limit 20
20+
metric "cyc": limit 16
2121
metric "parameters": limit 6

src/utils/identify_sources.m

Lines changed: 101 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,19 @@
2525
% 'wra'
2626
% those will throw warnings
2727

28+
% TODO adapt in case prefixes have been changed from SPM defaults
29+
2830
% TODO
2931
% functional to anatomical coregistration
3032
% anatomical to functional coregistration
3133

3234
default_map = Mapping();
3335
default_map = default_map.default();
3436

35-
sources = '';
37+
sources = {};
3638

3739
prefix_based = true;
3840

39-
add_deformation_field = false;
40-
deformation_field = 'TODO: add deformation field';
41-
4241
args = inputParser;
4342

4443
addOptional(args, 'derivatives', pwd, @ischar);
@@ -72,8 +71,9 @@
7271

7372
bf = bids.File(derivatives, 'verbose', verbose, 'use_schema', false);
7473

74+
% unknown suffix
7575
if ~ismember(bf.suffix, fieldnames(map.cfg.schema.content.objects.suffixes))
76-
sources{1} = 'TODO';
76+
sources{1, 1} = 'TODO';
7777
return
7878
end
7979

@@ -83,94 +83,123 @@
8383
prefix_based = false;
8484
end
8585

86-
% anything prefix based
87-
if prefix_based
86+
% unless this file already contains a derivative entity
87+
% it needs at least 2 characters for this file
88+
% to have some provenance in the derivatives
89+
if length(bf.prefix) == 1 && any(ismember(fieldnames(bf.entities), map.cfg.entity_order))
90+
bf.prefix = '';
91+
sources{1, 1} = fullfile(bf.bids_path, bf.filename);
92+
return
93+
end
8894

89-
if numel(bf.prefix) < 2
95+
sources = add_deformation_field(bf, sources, map, verbose);
9096

91-
% needs at least 2 characters for this file to have some provenance in the
92-
% derivatives
97+
% anything prefix based
98+
if prefix_based
9399

94-
% TODO: files that have been realigned but not resliced have no
95-
% "prefix" so we may miss some transformation
100+
[status, bf] = update_prefix(bf, map);
96101

102+
if status == 0
97103
return
98104

99-
else
100-
% remove the prefix of the last step
101-
102-
if startsWith(bf.prefix, 's')
103-
104-
% in case the prefix includes a number to denotate the FXHM used
105-
% for smoothing
106-
starts_with_fwhm = regexp(bf.prefix, '^s[0-9]*', 'match');
107-
if ~isempty(starts_with_fwhm)
108-
bf.prefix = bf.prefix(length(starts_with_fwhm{1}) + 1:end);
109-
else
110-
bf.prefix = bf.prefix(2:end);
111-
end
112-
113-
elseif ismember(bf.prefix(1:2), {'c1', 'c2', 'c3', 'c4', 'c5'})
114-
% bias corrected image
115-
sources = 'TODO';
116-
return
117-
118-
elseif startsWith(bf.prefix, 'u')
119-
bf.prefix = bf.prefix(2:end);
120-
121-
elseif startsWith(bf.prefix, 'w')
122-
bf.prefix = bf.prefix(2:end);
123-
add_deformation_field = true;
124-
125-
elseif startsWith(bf.prefix, 'rp_a')
126-
bf.prefix = bf.prefix(4:end);
127-
128-
elseif startsWith(bf.prefix, 'mean')
129-
% TODO mean may involve several files from the source (across runs
130-
% and sessions
131-
% prefixes = {
132-
% 'mean'
133-
% 'meanu'
134-
% 'meanua'
135-
% };
136-
sources = 'TODO';
137-
return
138-
139-
else
140-
% no idea
141-
sources = 'TODO';
142-
return
143-
144-
end
105+
elseif status == 1
106+
sources = 'TODO';
107+
return
145108

146109
end
110+
147111
end
148112

149113
% call spm_2_bids what is the filename from the previous step
150114
new_filename = spm_2_bids(bf.filename, map, verbose);
151115

152-
sources{1, 1} = fullfile(bf.bids_path, new_filename);
116+
sources{end + 1, 1} = fullfile(bf.bids_path, new_filename);
153117

154-
% for normalized images
155-
if add_deformation_field
118+
end
156119

157-
% for anatomical data we assume that
158-
% the deformation field comes from the anatomical file itself
159-
if (~isempty(bf.modality) && ismember(bf.modality, {'anat'})) || ...
160-
(~isempty(bf.suffix) && ~isempty(map.cfg.schema.find_suffix_group('anat', bf.suffix)))
120+
function sources = add_deformation_field(bf, sources, map, verbose)
161121

162-
bf.prefix = 'y_';
163-
bf = bf.update;
164-
new_filename = spm_2_bids(bf.filename, map, verbose);
165-
deformation_field = fullfile(bf.bids_path, new_filename);
122+
if ~startsWith(bf.prefix, map.norm)
123+
return
124+
end
166125

167-
% otherwise we can't guess it just from the file name
168-
else
126+
% for anatomical data we assume that
127+
% the deformation field comes from the anatomical file itself
128+
if (~isempty(bf.modality) && ismember(bf.modality, {'anat'})) || ...
129+
(~isempty(bf.suffix) && ~isempty(map.cfg.schema.find_suffix_group('anat', bf.suffix)))
130+
131+
bf.prefix = 'y_';
132+
bf = bf.update;
133+
new_filename = spm_2_bids(bf.filename, map, verbose);
134+
deformation_field = fullfile(bf.bids_path, new_filename);
135+
136+
% otherwise we can't guess it just from the file name
137+
else
138+
deformation_field = 'TODO: add deformation field';
139+
140+
end
141+
142+
sources{end + 1, 1} = deformation_field;
169143

144+
end
145+
146+
function [status, bf] = update_prefix(bf, map)
147+
148+
status = 2;
149+
150+
if length(bf.prefix) < 2
151+
% TODO: files that have been realigned but not resliced have no
152+
% "prefix" so we may miss some transformation
153+
status = 0;
154+
return
155+
end
156+
157+
% remove the prefix of the last step
158+
if startsWith(bf.prefix, map.smooth)
159+
160+
% in case the prefix includes a number to denotate the FXHM used
161+
% for smoothing
162+
starts_with_fwhm = regexp(bf.prefix, '^s[0-9]*', 'match');
163+
if ~isempty(starts_with_fwhm)
164+
bf = shorten_prefix(bf, length(starts_with_fwhm{1}));
165+
else
166+
bf = shorten_prefix(bf, 1);
170167
end
171168

172-
sources{2, 1} = deformation_field;
169+
elseif startsWith(bf.prefix, map.unwarp)
170+
bf = shorten_prefix(bf, 1);
171+
172+
elseif startsWith(bf.prefix, map.norm)
173+
bf = shorten_prefix(bf, 1);
174+
175+
elseif startsWith(bf.prefix, ['rp_' map.stc])
176+
bf = shorten_prefix(bf, 3);
177+
178+
elseif startsWith(bf.prefix, 'mean')
179+
% TODO mean may involve several files from the source (across runs
180+
% and sessions
181+
% prefixes = {
182+
% 'mean'
183+
% 'meanu'
184+
% 'meanua'
185+
% };
186+
status = 1;
187+
return
188+
189+
elseif ismember(bf.prefix(1:2), {'c1', 'c2', 'c3', 'c4', 'c5'})
190+
% bias corrected image
191+
status = 1;
192+
return
193+
194+
else
195+
% no idea
196+
status = 1;
197+
return
173198

174199
end
175200

176201
end
202+
203+
function bf = shorten_prefix(bf, len)
204+
bf.prefix = bf.prefix((len + 1):end);
205+
end

tests/test_identify_sources.m

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@
88
initTestSuite;
99
end
1010

11+
function test_identify_sources_with_non_raw_entity()
12+
13+
file = 'sub-01_task-foo_desc-stc_bold.nii';
14+
15+
prefix_output = {'u', 'sub-01/sub-01_task-foo_desc-stc_bold.nii'};
16+
17+
map = default_mapping();
18+
19+
for i = 1:size(prefix_output, 1)
20+
21+
sources = identify_sources([prefix_output{i, 1} file], map, false);
22+
23+
assertEqual(sources{1}, prefix_output{i, 2});
24+
25+
end
26+
27+
end
28+
1129
function test_identify_sources_surface()
1230

1331
anat_file = 'sub-01_T1w.surf.gii';
@@ -20,7 +38,7 @@ function test_identify_sources_surface()
2038

2139
sources = identify_sources([prefix_output{i, 1} anat_file], map, false);
2240

23-
assertEqual(sources{1}, prefix_output{i, 2});
41+
assertEqual(sources{end}, prefix_output{i, 2});
2442

2543
end
2644

@@ -43,7 +61,7 @@ function test_identify_sources_anat()
4361

4462
sources = identify_sources([prefix_output{i, 1} anat_file], map, false);
4563

46-
assertEqual(sources{1}, prefix_output{i, 2});
64+
assertEqual(sources{end}, prefix_output{i, 2});
4765

4866
end
4967

@@ -73,7 +91,7 @@ function test_identify_sources_func()
7391

7492
sources = identify_sources([prefix_output{i, 1} func_file], map, false);
7593

76-
assertEqual(sources{1}, fullfile('sub-01', prefix_output{i, 2}));
94+
assertEqual(sources{end}, fullfile('sub-01', prefix_output{i, 2}));
7795

7896
end
7997

@@ -95,7 +113,7 @@ function test_identify_sources_mean()
95113

96114
sources = identify_sources([prefix_output{i, 1} func_file], map, false);
97115

98-
assertEqual(sources{1}, fullfile('sub-01', prefix_output{i, 2}));
116+
assertEqual(sources{end}, fullfile('sub-01', prefix_output{i, 2}));
99117

100118
end
101119

tests/test_spm_2_bids_metadata.m

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
initTestSuite;
99
end
1010

11+
function test_spm_2_bids_metadata_with_non_raw_entity()
12+
13+
file = 'usub-01_task-foo_desc-stc_bold.nii';
14+
15+
[~, ~, json] = spm_2_bids(file, [], false);
16+
17+
assertEqual(fieldnames(json), {'filename'; 'content'});
18+
assertEqual(json.content.RawSources{1}, 'sub-01/sub-01_task-foo_bold.nii.gz');
19+
assertEqual(json.content.Sources{1}, ...
20+
'sub-01/sub-01_task-foo_desc-stc_bold.nii');
21+
22+
bids.util.jsonencode(json.filename, json.content);
23+
24+
end
25+
1126
function test_spm_2_bids_metadata_func()
1227

1328
file = 'wuasub-01_task-foo_bold.nii';
@@ -16,9 +31,9 @@ function test_spm_2_bids_metadata_func()
1631

1732
assertEqual(fieldnames(json), {'filename'; 'content'});
1833
assertEqual(json.content.RawSources{1}, 'sub-01/sub-01_task-foo_bold.nii.gz');
19-
assertEqual(json.content.Sources{1}, ...
34+
assertEqual(json.content.Sources{2}, ...
2035
'sub-01/sub-01_task-foo_space-individual_desc-realignUnwarp_bold.nii');
21-
assertEqual(json.content.Sources{2}, 'TODO: add deformation field');
36+
assertEqual(json.content.Sources{1}, 'TODO: add deformation field');
2237

2338
bids.util.jsonencode(json.filename, json.content);
2439

@@ -32,7 +47,7 @@ function test_spm_2_bids_non_raw_suffix()
3247

3348
assertEqual(fieldnames(json), {'filename'; 'content'});
3449
assertEqual(json.content.RawSources{1}, 'TODO');
35-
assertEqual(json.content.Sources{1}, 'TODO');
50+
assertEqual(json.content.Sources{end}, 'TODO');
3651

3752
bids.util.jsonencode(json.filename, json.content);
3853

@@ -46,7 +61,7 @@ function test_spm_2_bids_metadata_surface()
4661

4762
assertEqual(fieldnames(json), {'filename'; 'content'});
4863
assertEqual(json.content.RawSources{1}, 'sub-01/sub-01_T1w.nii.gz');
49-
assertEqual(json.content.Sources{1}, ...
64+
assertEqual(json.content.Sources{end}, ...
5065
'sub-01/sub-01_space-IXI549Space_desc-preproc_T1w.nii');
5166

5267
bids.util.jsonencode(json.filename, json.content);
@@ -75,7 +90,7 @@ function test_spm_2_bids_metadata_smoothed_data()
7590

7691
assertEqual(fieldnames(json), {'filename'; 'content'});
7792
assertEqual(json.content.RawSources{1}, 'sub-01/sub-01_task-auditory_bold.nii.gz');
78-
assertEqual(json.content.Sources{1}, ...
93+
assertEqual(json.content.Sources{end}, ...
7994
'sub-01/sub-01_task-auditory_space-IXI549Space_desc-preproc_bold.nii');
8095

8196
bids.util.jsonencode(json.filename, json.content);

0 commit comments

Comments
 (0)