Skip to content

Commit c82f006

Browse files
authored
Merge pull request #313 from suyashdb/SubSesIDMismatch
Checks if Sub and Ses ID in path and filename mismatch
2 parents 61d78c9 + be91371 commit c82f006

File tree

5 files changed

+162
-26
lines changed

5 files changed

+162
-26
lines changed

tests/bids.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ var suite = describe('BIDS example datasets ', function() {
113113
var options = {ignoreNiftiHeaders: false};
114114
validate.BIDS("tests/data/valid_filenames", options, function (issues) {
115115
var errors = issues.errors;
116-
assert(errors[1].code ==='62');
116+
assert(errors[2].code ==='64');
117117
isdone();
118118
});
119119
});

tests/type.spec.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var assert = require('chai').assert;
22
var utils = require('../utils');
33
var Test = require("mocha/lib/test");
4+
var BIDS = require('../validators/bids');
45

56

67
var suiteAnat = describe('utils.type.isAnat', function(){
@@ -242,4 +243,42 @@ describe('utils.type.getPathValues', function () {
242243
assert.equal(utils.type.getPathValues('/sub-22/func/sub-22_task-rest_acq-prefrontal_physio.tsv.gz').sub, 22);
243244
assert.equal(utils.type.getPathValues('/sub-22/func/sub-22_task-rest_acq-prefrontal_physio.tsv.gz').ses, null);
244245
});
245-
});
246+
});
247+
248+
describe('BIDS.subIDsesIDmismatchtest', function () {
249+
var code64_seen = false;
250+
var code65_seen = false;
251+
it('should return if sub and ses doesnt match', function () {
252+
253+
var files = { '0':
254+
{ name: 'sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
255+
path: 'tests/data/BIDS-examples-1.0.0-rc3u5/ds001/sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
256+
relativePath: 'ds001/sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz' },
257+
'1':
258+
{ name: '/sub-22/ses-1/func/sub-23_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
259+
path: 'tests/data/BIDS-examples-1.0.0-rc3u5/ds001/sub-22/ses-1/func/sub-23_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
260+
relativePath: 'ds001/sub-22/ses-1/func/sub-23_ses-1_task-rest_acq-prefrontal_physio.tsv.gz' },
261+
'2':
262+
{ name: '/sub-22/ses-1/func/sub-22_ses-2_task-rest_acq-prefrontal_physio.tsv.gz',
263+
path: 'tests/data/BIDS-examples-1.0.0-rc3u5/ds001/sub-22/ses-1/func/sub-22_ses-2_task-rest_acq-prefrontal_physio.tsv.gz',
264+
relativePath: '/sub-22/ses-1/func/sub-22_ses-2_task-rest_acq-prefrontal_physio.tsv.gz' },
265+
'3':
266+
{ name: '/sub-25/ses-2/func/sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
267+
path: 'tests/data/BIDS-examples-1.0.0-rc3u5/ds001/sub-25/ses-2/func/sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz',
268+
relativePath: 'ds001//sub-25/ses-2/func/sub-22_ses-1_task-rest_acq-prefrontal_physio.tsv.gz' }},
269+
270+
callback = function (issues) {
271+
for (var i in issues) {
272+
if (issues[i]['code'] === 64) {
273+
code64_seen = true;
274+
}
275+
else if (issues[i]['code'] === 65) {
276+
code65_seen = false;
277+
}
278+
}
279+
assert(code64_seen);
280+
assert(code65_seen);
281+
};
282+
assert.equal(BIDS.subIDsesIDmismatchtest(files, callback), true);
283+
});
284+
});

utils/issues/list.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,5 +316,15 @@ module.exports = {
316316
key: 'SESSION_VALUE_CONTAINS_ILLEGAL_CHARECTER',
317317
severity: 'error',
318318
reason: 'Ses label contain an Illegal Character hyphen or underscore. Please edit the filename as per BIDS spec.',
319+
},
320+
64: {
321+
key: 'SUBJECT_LABEL_IN_FILENAME_DOESNOT_MATCH_DIRECTORY',
322+
severity: 'error',
323+
reason: 'Subject label in the filename doesn\'t match with the path of the file. File seems to be saved in incorrect subject directory.'
324+
},
325+
65: {
326+
key: 'SESSION_LABEL_IN_FILENAME_DOESNOT_MATCH_DIRECTORY',
327+
severity: 'error',
328+
reason: 'Session label in the filename doesn\'t match with the path of the file. File seems to be saved in incorrect session directory.'
319329
}
320330
};

utils/type.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,28 @@ module.exports = {
1616
* Check if a given path is valid within the
1717
* bids spec.
1818
*/
19-
isBIDS: function(path) {
19+
isBIDS: function (path) {
2020
return (
21-
this.isTopLevel(path) ||
21+
this.isTopLevel(path) ||
2222
this.isAssociatedData(path) ||
23-
this.isSessionLevel(path) ||
24-
this.isSubjectLevel(path) ||
25-
this.isAnat(path) ||
26-
this.isDWI(path) ||
27-
this.isFunc(path) ||
28-
this.isBehavioral(path) ||
29-
this.isCont(path) ||
30-
this.isFieldMap(path) ||
23+
this.isSessionLevel(path) ||
24+
this.isSubjectLevel(path) ||
25+
this.isAnat(path) ||
26+
this.isDWI(path) ||
27+
this.isFunc(path) ||
28+
this.isBehavioral(path) ||
29+
this.isCont(path) ||
30+
this.isFieldMap(path) ||
3131
this.isPhenotypic(path)
3232
);
3333
},
3434

3535
/**
3636
* Check if the file has appropriate name for a top level file
3737
*/
38-
isTopLevel: function(path) {
38+
isTopLevel: function (path) {
3939
var fixedTopLevelNames = ["/README", "/CHANGES", "/dataset_description.json", "/participants.tsv",
40-
"/participants.json", "/phasediff.json", "/phase1.json", "/phase2.json" ,"/fieldmap.json"];
40+
"/participants.json", "/phasediff.json", "/phase1.json", "/phase2.json", "/fieldmap.json"];
4141

4242
var funcTopRe = new RegExp('^\\/(?:ses-[a-zA-Z0-9]+_)?(?:recording-[a-zA-Z0-9]+_)?task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
4343
+ '(_bold.json|_sbref.json|_events.json|_events.tsv|_physio.json|_stim.json|_beh.json)$');
@@ -60,23 +60,23 @@ module.exports = {
6060
/**
6161
* Check if file is appropriate associated data.
6262
*/
63-
isAssociatedData: function(path) {
63+
isAssociatedData: function (path) {
6464
var associatedData = new RegExp('^\\/(?:code|derivatives|sourcedata|stimuli|[.]git)\\/(?:.*)$');
6565
return associatedData.test(path);
6666
},
6767

6868
/**
6969
* Check if file is phenotypic data.
7070
*/
71-
isPhenotypic: function(path) {
71+
isPhenotypic: function (path) {
7272
var phenotypicData = new RegExp('^\\/(?:phenotype)\\/(?:.*.tsv|.*.json)$');
7373
return phenotypicData.test(path);
7474
},
7575

7676
/**
7777
* Check if the file has appropriate name for a session level
7878
*/
79-
isSessionLevel: function(path) {
79+
isSessionLevel: function (path) {
8080
var scansRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
8181
'\\/(?:(ses-[a-zA-Z0-9]+)' +
8282
'\\/)?\\1(_\\2)?(_scans.tsv|_scans.json)$');
@@ -103,7 +103,7 @@ module.exports = {
103103
/**
104104
* Check if the file has appropriate name for a subject level
105105
*/
106-
isSubjectLevel: function(path) {
106+
isSubjectLevel: function (path) {
107107
var scansRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
108108
'\\/\\1(_sessions.tsv|_sessions.json)$');
109109
return scansRe.test(path);
@@ -112,7 +112,7 @@ module.exports = {
112112
/**
113113
* Check if the file has a name appropriate for an anatomical scan
114114
*/
115-
isAnat: function(path) {
115+
isAnat: function (path) {
116116
var anatRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
117117
'\\/(?:(ses-[a-zA-Z0-9]+)' +
118118
'\\/)?anat' +
@@ -125,7 +125,7 @@ module.exports = {
125125
/**
126126
* Check if the file has a name appropriate for a diffusion scan
127127
*/
128-
isDWI: function(path) {
128+
isDWI: function (path) {
129129
var suffixes = ["dwi", "sbref"];
130130
var anatRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
131131
'\\/(?:(ses-[a-zA-Z0-9]+)' +
@@ -139,7 +139,7 @@ module.exports = {
139139
/**
140140
* Check if the file has a name appropriate for a fieldmap scan
141141
*/
142-
isFieldMap: function(path) {
142+
isFieldMap: function (path) {
143143
var suffixes = ["phasediff", "phase1", "phase2", "magnitude1", "magnitude2", "magnitude", "fieldmap", "epi"];
144144
var anatRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
145145
'\\/(?:(ses-[a-zA-Z0-9]+)' +
@@ -153,7 +153,7 @@ module.exports = {
153153
/**
154154
* Check if the file has a name appropriate for a functional scan
155155
*/
156-
isFunc: function(path) {
156+
isFunc: function (path) {
157157
var funcRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
158158
'\\/(?:(ses-[a-zA-Z0-9]+)' +
159159
'\\/)?func' +
@@ -162,7 +162,7 @@ module.exports = {
162162
return conditionalMatch(funcRe, path);
163163
},
164164

165-
isBehavioral: function(path) {
165+
isBehavioral: function (path) {
166166
var funcBeh = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
167167
'\\/(?:(ses-[a-zA-Z0-9]+)' +
168168
'\\/)?beh' +
@@ -171,7 +171,7 @@ module.exports = {
171171
return conditionalMatch(funcBeh, path);
172172
},
173173

174-
isFuncBold: function(path) {
174+
isFuncBold: function (path) {
175175
var funcRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
176176
'\\/(?:(ses-[a-zA-Z0-9]+)' +
177177
'\\/)?func' +
@@ -180,7 +180,7 @@ module.exports = {
180180
return conditionalMatch(funcRe, path);
181181
},
182182

183-
isCont: function(path) {
183+
isCont: function (path) {
184184
var contRe = new RegExp('^\\/(sub-[a-zA-Z0-9]+)' +
185185
'\\/(?:(ses-[a-zA-Z0-9]+)' +
186186
'\\/)?(?:func|beh)' +
@@ -190,7 +190,7 @@ module.exports = {
190190
return conditionalMatch(contRe, path);
191191
},
192192

193-
isNumber: function(n) {
193+
isNumber: function (n) {
194194
return !isNaN(parseFloat(n)) && isFinite(n);
195195
},
196196

@@ -229,3 +229,6 @@ function conditionalMatch (expression, path) {
229229
}
230230
return false;
231231
}
232+
233+
234+

validators/bids.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ BIDS = {
160160
size: 0
161161
};
162162

163+
// var subses_mismatch = false;
164+
self.subIDsesIDmismatchtest(fileList);
165+
163166

164167
// check for illegal character in task name and acq name
165168

@@ -441,6 +444,87 @@ BIDS = {
441444
});
442445
},
443446

447+
/**
448+
* subid and sesid mismatch test. Generates error if ses-id and sub-id are different for any file, Takes a file list and return issues
449+
*/
450+
subIDsesIDmismatchtest: function(fileList){
451+
var self = this;
452+
var subses_mismatch = false;
453+
454+
/**
455+
* getPathandFileValues
456+
* Takes a file path and returns values
457+
* found following keys for both path and file keys.
458+
* sub-
459+
* ses-
460+
*
461+
*
462+
*/
463+
function getPathandFileValues(path){
464+
var values = {}, match;
465+
var file_name ={}, unmat;
466+
467+
// capture subject
468+
match = (/^\/sub-([a-zA-Z0-9]+)/).exec(path);
469+
values.sub = match && match[1] ? match[1] : null;
470+
471+
// capture session
472+
match = (/^\/sub-[a-zA-Z0-9]+\/ses-([a-zA-Z0-9]+)/).exec(path);
473+
values.ses = match && match[1] ? match[1] : null;
474+
475+
//capture session and subject id from filename to find if files are in
476+
// correct sub/ses directory
477+
var filename = path.replace(/^.*[\\\/]/, '');
478+
479+
// capture sub from file name
480+
unmat = (/^sub-([a-zA-Z0-9]+)/).exec(filename);
481+
file_name.sub = unmat && unmat[1] ? unmat[1] : null;
482+
483+
484+
// capture session from file name
485+
unmat = (/^sub-[a-zA-Z0-9]+_ses-([a-zA-Z0-9]+)/).exec(filename);
486+
file_name.ses = unmat && unmat[1] ? unmat[1] : null;
487+
488+
return [values,file_name];
489+
}
490+
491+
492+
// validates if sub/ses-id in filename matches with ses/sub directory file is saved
493+
async.eachOfLimit(fileList, 200, function (file) {
494+
var path = utils.files.relativePath(file);
495+
file.relativePath = path;
496+
var values = getPathandFileValues(file.relativePath);
497+
498+
var pathValues = values[0];
499+
var fileValues = values[1];
500+
// console.log(path, '/n' ,values);
501+
502+
if (fileValues.sub !== null || fileValues.ses !== null) {
503+
if (fileValues.sub !== pathValues.sub) {
504+
// console.log("before call ",subses_mismatch);
505+
subses_mismatch = true;
506+
self.issues.push(new Issue({
507+
code: 64,
508+
evidence: "File: " + file.relativePath + " is saved in incorrect subject directory as per sub-id in filename.",
509+
file: file
510+
}));
511+
}
512+
513+
if (fileValues.ses !== pathValues.ses) {
514+
subses_mismatch = true;
515+
self.issues.push(new Issue({
516+
code: 65,
517+
evidence: "File: " + file.relativePath + " is saved in incorrect session directory as per ses-id in filename.",
518+
file: file
519+
}));
520+
}
521+
}
522+
523+
});
524+
return subses_mismatch;
525+
526+
},
527+
444528
/**
445529
* Reset
446530
*

0 commit comments

Comments
 (0)