Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NIRS support to BIDS Validator #952

Merged
merged 48 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1c9e6e3
Add fNIRS support
rob-luke May 7, 2020
b954fe9
Fix missing bracket
rob-luke Jan 31, 2022
01f7c19
Add fNIRS error codes
rob-luke Jan 31, 2022
e34b9ca
Remove extra ses type
rob-luke Jan 31, 2022
f6f904c
Lint
rob-luke Jan 31, 2022
601bb13
Use test data
rob-luke Jan 31, 2022
7401bcb
Try and use custom branch for submodule data
rob-luke Jan 31, 2022
335fa56
Revert submodule
rob-luke Feb 17, 2022
11d2436
Add NIRS optodes.tsv to getTsvType definitions
nellh Feb 17, 2022
a829540
only allow optodes files with sub/ses entities at file level. Add nir…
rwblair Mar 14, 2022
f5a305c
fix coordsystem.nirs error, only attempt to display error objects sta…
rwblair Mar 14, 2022
6f570d1
Add fNIRS support
rob-luke May 7, 2020
c83c98a
Fix missing bracket
rob-luke Jan 31, 2022
a8aa997
Add fNIRS error codes
rob-luke Jan 31, 2022
20e9d10
Remove extra ses type
rob-luke Jan 31, 2022
62a3888
Lint
rob-luke Jan 31, 2022
e556979
Use test data
rob-luke Jan 31, 2022
857f704
Try and use custom branch for submodule data
rob-luke Jan 31, 2022
4f50fc1
Revert submodule
rob-luke Feb 17, 2022
e7296de
Add NIRS optodes.tsv to getTsvType definitions
nellh Feb 17, 2022
f7ae2a1
only allow optodes files with sub/ses entities at file level. Add nir…
rwblair Mar 14, 2022
eeb9941
fix coordsystem.nirs error, only attempt to display error objects sta…
rwblair Mar 14, 2022
68799c2
Merge branch 'nirs' of github.com:rob-luke/bids-validator into nirs
rob-luke Jun 12, 2022
d932739
Update file_level_rules.json
rob-luke Jun 12, 2022
f319f37
Update file_level_rules.json
rob-luke Jun 12, 2022
4aef57a
Revert .gitmodules change
rob-luke Jun 12, 2022
29975fe
Update .gitmodules
rob-luke Jun 12, 2022
573ab61
Update file_level_rules.json
rob-luke Jun 12, 2022
1f0bd9a
Update top_level_rules.json
rob-luke Jun 12, 2022
86425a8
Changes required by eslint
rob-luke Jun 12, 2022
7fdd268
Add tests
rob-luke Jun 12, 2022
72767ec
Tests
rob-luke Jun 12, 2022
6d911e5
Tests
rob-luke Jun 12, 2022
feb43ed
Lint
rob-luke Jun 12, 2022
35d350c
Update cli.js
rob-luke Jun 12, 2022
2d76103
Revert package lock changes
rob-luke Jun 20, 2022
31e8320
fixed typo in error message, optodes should be channels
robertoostenveld Jun 20, 2022
0d79e3a
added SourceType metadata field
robertoostenveld Jun 20, 2022
84756ff
Merge pull request #1 from robertoostenveld/rob-luke-nirs
rob-luke Jun 22, 2022
ef677a8
Try and fix regex
rob-luke Jun 22, 2022
dd1f4f2
Update file_level_rules.json
rob-luke Jun 22, 2022
01c9db8
use expanded directory seperator regex in nirs regex
rwblair Jun 27, 2022
09cc8aa
Merge branch 'master' of github.com:bids-standard/bids-validator into…
rwblair Aug 30, 2022
3cae300
add optode fixes to session and top level
rwblair Aug 30, 2022
a50877a
add NIRS to modality summary output
rwblair Aug 30, 2022
e8d3034
fix grouping issue in nirs file_level_rules
rwblair Aug 30, 2022
244fc70
add file rules for optodes json sidecar
rwblair Aug 30, 2022
dfc83d6
fix coordsystem and friends regex
rwblair Aug 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-bids-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:

- name: Get bids-examples data
run: |
git clone --depth 1 https://github.com/bids-standard/bids-examples -b ${{ matrix.bids-examples-branch }}
git clone --depth 1 https://github.com/rob-luke/bids-examples --branch fnirstapping

- name: Display versions and environment information
run: |
Expand Down
19 changes: 19 additions & 0 deletions bids-validator/bids_validator/rules/file_level_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,25 @@
"regexp": "^[\\/\\\\](?:stimuli)[\\/\\\\](?:.*)$"
},

"nirs": {
"regexp": "^\\/(sub-[a-zA-Z0-9]+)\\/(?:(ses-[a-zA-Z0-9]+)\\/)?nirs\\/\\1(_\\2)?@@@_nirs_optodes_@@@|((?:_task-[a-zA-Z0-9]+)?(?:_acq-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_proc-[a-zA-Z0-9]+)?(?:_part-[0-9]+)?(_nirs\\.(@@@_nirs_type_@@@)|(@@@_nirs_ext_@@@)))$",
"tokens": {
"@@@_nirs_type_@@@": ["snirf"],
"@@@_nirs_ext_@@@": [
"_events\\.json",
"_events\\.tsv",
"_channels\\.json",
"_channels\\.tsv",
"_nirs\\.json",
"_photo\\.jpg"
],
"@@@_nirs_optodes_@@@": [
"_optodes\\.tsv",
"_coordsystem\\.json"
]
}
},

"pet": {
"regexp": "^[\\/\\\\](sub-[a-zA-Z0-9]+)[\\/\\\\](?:(ses-[a-zA-Z0-9]+)[\\/\\\\])?pet[\\/\\\\](sub-[a-zA-Z0-9]+)(?:(_ses-[a-zA-Z0-9]+))?(?:_task-[a-zA-Z0-9]+)?(?:_trc-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(@@@_pet_ext_@@@)$",
"tokens": {
Expand Down
14 changes: 14 additions & 0 deletions bids-validator/bids_validator/rules/session_level_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,19 @@
"_SPIM.json"
]
}
},

"nirs_ses": {
"regexp": "^\\/(sub-[a-zA-Z0-9]+)\\/(?:(ses-[a-zA-Z0-9]+)\\/)?\\1(_\\2)?(?:_task-[a-zA-Z0-9]+)?(?:_acq-[a-zA-Z0-9]+)?(?:_proc-[a-zA-Z0-9]+)?(@@@_nirs_ses_type_@@@)$",
"tokens": {
"@@@_nirs_ses_type_@@@": [
"_events.tsv",
"_channels.tsv",
"_optodes.tsv",
rwblair marked this conversation as resolved.
Show resolved Hide resolved
"_nirs.json",
"_coordsystem.json",
"_photo.jpg"
]
}
}
}
12 changes: 12 additions & 0 deletions bids-validator/bids_validator/rules/top_level_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@
]
}
},
"nirs_top": {
"regexp": "^\\/(?:ses-[a-zA-Z0-9]+_)?task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_proc-[a-zA-Z0-9]+)?(?:@@@_nirs_top_ext_@@@)$",
"tokens": {
"@@@_nirs_top_ext_@@@": [
"_nirs\\.json",
"_channels\\.tsv",
"_optodes\\.tsv",
"_photo\\.jpg",
"_coordsystem\\.json"
]
}
},
"multi_dir_fieldmap": {
"regexp": "^[\\/\\\\](?:acq-[a-zA-Z0-9]+_)?(?:dir-[a-zA-Z0-9]+_)epi\\.json$"
},
Expand Down
37 changes: 36 additions & 1 deletion bids-validator/bids_validator/tsv/non_custom_columns.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
"low_cutoff",
"name",
"notch",
"source",
"detector",
"wavelength_actual",
"wavelength_nominal",
"wavelength_emission_actual",
"orientation_component",
"short_channel",
"sampling_frequency",
"software_filters",
"status",
Expand All @@ -28,6 +35,18 @@
"impedance",
"dimension"
],
"optodes": [
"name",
"type",
"x",
"y",
"z",
"template_x",
"template_y",
"template_z",
"source_type",
"detector_type"
rob-luke marked this conversation as resolved.
Show resolved Hide resolved
],
"events": [
"duration",
"HED",
Expand All @@ -44,5 +63,21 @@
"scans": ["acq_time", "filename"],
"sessions": ["acq_time", "session_id"],
"aslcontext": ["volume_type"],
"blood": ["time", "plasma_radioactivity", "whole_blood_radioactivity", "metabolite_parent_fraction", "hplc_recovery_fractions"]
"blood": ["time", "plasma_radioactivity", "whole_blood_radioactivity", "metabolite_parent_fraction", "hplc_recovery_fractions"],
"nirs": [
"name",
"type",
"source",
"detector",
"wavelength_nominal",
"units",
"sampling_frequency",
"orientation_component",
"wavelength_actual",
"description",
"wavelength_emission_actual",
"short_channel",
"status",
"status_description"
]
}
58 changes: 58 additions & 0 deletions bids-validator/tests/json.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,4 +692,62 @@ describe('JSON', function() {
assert(issues.length === 0)
})
})

var nirs_file = {
name: 'sub-01_run-01_nirs.json',
relativePath: '/sub-01_run-01_nirs.json',
}

it('*_nirs.json sidecars should have required key/value pairs', function() {
var jsonObj = {
TaskName: 'Audiovis',
SamplingFrequency: 7,
NIRSChannelCount: 7,
NIRSSourceOptodeCount: 7,
NIRSDetectorOptodeCount: 7,
CapManufacturer: 'EasyCap',
CapManufacturersModelName: 'actiCAP 64 Ch Standard-2',
}
jsonDict[nirs_file.relativePath] = jsonObj
validate.JSON(nirs_file, jsonDict, function(issues) {
assert(issues.length === 0)
})
var jsonObjInval = jsonObj
jsonObjInval['BadKey'] = ''
jsonDict[nirs_file.relativePath] = jsonObjInval
validate.JSON(nirs_file, jsonDict, function(issues) {
assert(issues && issues.length === 1)
})
})
var nirs_coordsystem_file = {
name: 'sub-01/nirs/sub-01_task-testing_coordsystem.json',
relativePath: '/sub-01/nirs/sub-01_task-testing_coordsystem.json',
}

it('NIRS *_coordsystem.json files should have required key/value pairs', function() {
var jsonObj = {
NIRSCoordinateSystem: 'fsaverage',
NIRSCoordinateUnits: 'mm',
}
jsonDict[nirs_coordsystem_file.relativePath] = jsonObj
validate.JSON(nirs_coordsystem_file, jsonDict, function(issues) {
assert(issues.length === 0)
})
})

it('NIRS *_coordsystem.json schema should require *Description if *Coordsystem is "Other"', function() {
var jsonObj = {
NIRSCoordinateSystem: 'Other',
NIRSCoordinateUnits: 'mm',
}
jsonDict[nirs_coordsystem_file.relativePath] = jsonObj
validate.JSON(nirs_coordsystem_file, jsonDict, function(issues) {
assert(issues.length === 2)
assert(
issues[0].evidence ==
" should have required property 'NIRSCoordinateSystemDescription'",
)
assert(issues[1].evidence == ' should match "then" schema')
})
})
})
44 changes: 44 additions & 0 deletions bids-validator/tests/tsv.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,50 @@ describe('TSV', function() {
assert(issues.length === 0)
})
})
var channelsFileNIRS = {
name: 'sub-01_ses-001_task-rest_run-01_channels.tsv',
relativePath:
'/sub-01/ses-001/nirs/sub-01_ses-001_task-rest_run-01_channels.tsv',
}

it('NIRS channels.tsv with correct columns should throw no error', function() {
var tsv =
'name\ttype\tsource\tdetector\twavelength_nominal\tunits\n' +
'testch\tNIRSCWAMPLITUDE\tS1\tD1\t760.0\tV'
validate.TSV.TSV(channelsFileNIRS, tsv, [], function(issues) {
assert(issues.length === 0)
})
})

it('should not allow NIRS channels.tsv files without name column', function() {
var tsv =
'type\tsource\tdetector\twavelength_nominal\tunits\n' +
'NIRSCWAMPLITUDE\tS1\tD1\t760.0\tV'
validate.TSV.TSV(channelsFileNIRS, tsv, [], function(issues) {
assert(issues[0].code === 229)
})
})

// optodes checks ---------------------------------------------------------
var optodesFileNIRS = {
name: 'sub-01_ses-001_task-rest_run-01_optodes.tsv',
relativePath:
'/sub-01/ses-001/nirs/sub-01_ses-001_task-rest_run-01_optodes.tsv',
}

it('should allow NIRS optodes.tsv files with correct columns', function() {
var tsv = 'name\ttype\tx\ty\tz\n' + 'S1\tsource\t-0.04\t0.02\t0.5\n'
validate.TSV.TSV(optodesFileNIRS, tsv, [], function(issues) {
assert(issues.length === 0)
})
})

it('should not allow NIRS optodes.tsv files with out name columns', function() {
var tsv = 'type\tx\ty\tz\n' + 'source\t-0.04\t0.02\t0.5\n'
validate.TSV.TSV(optodesFileNIRS, tsv, [], function(issues) {
assert(issues[0].code === 228)
})
})

// electrodes checks ---------------------------------------------------------
var electrodesFileEEG = {
Expand Down
12 changes: 12 additions & 0 deletions bids-validator/utils/issues/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -1119,4 +1119,16 @@ export default {
severity: 'error',
reason: 'Inconsistent TIFF file type and extension',
},
228: {
key: 'MISSING_TSV_COLUMN_NIRS_OPTODES',
severity: 'error',
reason:
"The column names of the optodes file must begin with ['name', 'type', 'x', 'y', 'z']",
},
229: {
key: 'MISSING_TSV_COLUMN_NIRS_CHANNELS',
severity: 'error',
reason:
"The column names of the channels file must begin with ['name', 'type', 'source', 'detector', 'wavelength_nominal', 'units':]",
},
}
12 changes: 12 additions & 0 deletions bids-validator/utils/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const petBlood = buildRegExp(file_level_rules.pet_blood)
const microscopyData = buildRegExp(file_level_rules.microscopy)
const microscopyPhotoData = buildRegExp(file_level_rules.microscopy_photo)
const microscopyJSON = buildRegExp(file_level_rules.microscopy_json)
const nirsData = buildRegExp(file_level_rules.nirs)
// Phenotypic data
const phenotypicData = buildRegExp(phenotypic_rules.phenotypic_data)
// Session level
Expand All @@ -75,6 +76,7 @@ const megSes = buildRegExp(session_level_rules.meg_ses)
const scansSes = buildRegExp(session_level_rules.scans)
const petSes = buildRegExp(session_level_rules.pet_ses)
const microscopySes = buildRegExp(session_level_rules.microscopy_ses)
const nirsSes = buildRegExp(session_level_rules.nirs_ses)
// Subject level
const subjectLevel = buildRegExp(subject_level_rules.subject_level)
// Top level
Expand All @@ -90,6 +92,7 @@ const otherTopFiles = buildRegExp(top_level_rules.other_top_files)
const megTop = buildRegExp(top_level_rules.meg_top)
const petTop = buildRegExp(top_level_rules.pet_top)
const microscopyTop = buildRegExp(top_level_rules.microscopy_top)
const nirsTop = buildRegExp(top_level_rules.nirs_top)

export default {
/**
Expand All @@ -109,6 +112,7 @@ export default {
this.file.isFunc(path) ||
this.file.isAsl(path) ||
this.file.isMeg(path) ||
this.file.isNIRS(path) ||
this.file.isIEEG(path) ||
this.file.isEEG(path) ||
this.file.isBehavioral(path) ||
Expand Down Expand Up @@ -142,6 +146,7 @@ export default {
eegTop.test(path) ||
ieegTop.test(path) ||
petTop.test(path) ||
nirsTop.test(path) ||
microscopyTop.test(path)
)
} else {
Expand All @@ -157,6 +162,7 @@ export default {
eegTop.test(path) ||
ieegTop.test(path) ||
petTop.test(path) ||
nirsTop.test(path) ||
microscopyTop.test(path)
)
}
Expand Down Expand Up @@ -210,6 +216,7 @@ export default {
conditionalMatch(anatSes, path) ||
conditionalMatch(dwiSes, path) ||
conditionalMatch(megSes, path) ||
conditionalMatch(nirsSes, path) ||
conditionalMatch(eegSes, path) ||
conditionalMatch(ieegSes, path) ||
conditionalMatch(petSes, path) ||
Expand Down Expand Up @@ -333,6 +340,10 @@ export default {
}
},

isNIRS: function(path) {
return conditionalMatch(nirsData, path)
},

isEEG: function(path) {
if (bids_schema) {
return bids_schema.datatypes['eeg'].some(regex => regex.exec(path))
Expand Down Expand Up @@ -381,6 +392,7 @@ export default {
this.isFunc(path) ||
this.isAsl(path) ||
this.isMeg(path) ||
this.isNIRS(path) ||
this.isEEG(path) ||
this.isIEEG(path) ||
this.isBehavioral(path) ||
Expand Down
11 changes: 9 additions & 2 deletions bids-validator/validators/json/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ const selectSchema = file => {
schema = require('./schemas/asl.json')
} else if (file.name.endsWith('pet.json')) {
schema = require('./schemas/pet.json')
} else if (file.name.endsWith('nirs.json')) {
schema = require('./schemas/nirs.json')
} else if (file.relativePath === '/dataset_description.json') {
schema = require('./schemas/dataset_description.json')
} else if (file.name.endsWith('meg.json')) {
Expand Down Expand Up @@ -131,13 +133,18 @@ const selectSchema = file => {
file.name.endsWith('coordsystem.json')
) {
schema = require('./schemas/coordsystem_eeg.json')
} else if (
file.relativePath.includes('/nirs/') &&
file.name.endsWith('coordsystem.json')
) {
schema = require('./schemas/coordsystem_nirs.json')
} else if (file.name.endsWith('genetic_info.json')) {
schema = require('./schemas/genetic_info.json')
} else if (
file.relativePath.includes('/pet/') &&
file.name.endsWith('blood.json')
) {
schema = require('./schemas/pet_blood.json')
} else if (file.name.endsWith('genetic_info.json')) {
schema = require('./schemas/genetic_info.json')
} else if (
file.name.endsWith('physio.json') ||
file.name.endsWith('stim.json')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
"DeviceSerialNumber": { "type": "string" },
"ECGChannelCount": { "type": "integer", "minimum": 0 },
"EEGChannelCount": { "type": "integer", "minimum": 0 },
"NIRSChannelCount": { "type": "integer", "minimum": 0 },
"NIRSSourceOptodeCount": { "type": "integer", "minimum": 0 },
"NIRSDetectorOptodeCount": { "type": "integer", "minimum": 0 },
"EMGChannelCount": { "type": "integer", "minimum": 0 },
"EOGChannelCount": { "type": "integer", "minimum": 0 },
"EpochLength": { "type": "number", "minimum": 0 },
Expand Down
Loading