diff --git a/bids-validator/utils/options.js b/bids-validator/utils/options.js index 77a66f0c7..f428ce6f1 100644 --- a/bids-validator/utils/options.js +++ b/bids-validator/utils/options.js @@ -23,7 +23,6 @@ export default { remoteFiles: Boolean(options.remoteFiles), gitRef: options.gitRef || 'HEAD', config: options.config || {}, - schema: options.schema !== 'disable' ? options.schema : false, } if (options.config && typeof options.config !== 'boolean') { this.parseConfig(dir, options.config, function (issues, config) { diff --git a/bids-validator/utils/type.js b/bids-validator/utils/type.js index 33a6bd268..98de0b88c 100644 --- a/bids-validator/utils/type.js +++ b/bids-validator/utils/type.js @@ -17,13 +17,6 @@ import session_level_rules from '../bids_validator/rules/session_level_rules.jso import subject_level_rules from '../bids_validator/rules/subject_level_rules.json' import top_level_rules from '../bids_validator/rules/top_level_rules.json' -let bids_schema - -// Alternative method of loading from bids-specification schema -export function schemaSetup(schema) { - bids_schema = schema -} - // Associated data const associatedData = buildRegExp(associated_data_rules.associated_data) // File level @@ -146,57 +139,30 @@ export default { * Check if the file has appropriate name for a top level file */ isTopLevel: function (path) { - if (bids_schema) { - return ( - bids_schema.top_level_files.some((regex) => regex.exec(path)) || - funcTop.test(path) || - aslTop.test(path) || - dwiTop.test(path) || - anatTop.test(path) || - vfaTop.test(path) || - megreTop.test(path) || - irt1Top.test(path) || - mpmTop.test(path) || - mtsTop.test(path) || - mtrTop.test(path) || - mp2rageTop.test(path) || - fmapEpiTop.test(path) || - fmapGreTop.test(path) || - otherTopFiles.test(path) || - megTop.test(path) || - eegTop.test(path) || - ieegTop.test(path) || - petTop.test(path) || - motionTop.test(path) || - nirsTop.test(path) || - microscopyTop.test(path) - ) - } else { - return ( - rootTop.test(path) || - funcTop.test(path) || - aslTop.test(path) || - dwiTop.test(path) || - anatTop.test(path) || - vfaTop.test(path) || - megreTop.test(path) || - irt1Top.test(path) || - mpmTop.test(path) || - mtsTop.test(path) || - mtrTop.test(path) || - mp2rageTop.test(path) || - fmapEpiTop.test(path) || - fmapGreTop.test(path) || - otherTopFiles.test(path) || - megTop.test(path) || - eegTop.test(path) || - ieegTop.test(path) || - petTop.test(path) || - motionTop.test(path) || - nirsTop.test(path) || - microscopyTop.test(path) - ) - } + return ( + rootTop.test(path) || + funcTop.test(path) || + aslTop.test(path) || + dwiTop.test(path) || + anatTop.test(path) || + vfaTop.test(path) || + megreTop.test(path) || + irt1Top.test(path) || + mpmTop.test(path) || + mtsTop.test(path) || + mtrTop.test(path) || + mp2rageTop.test(path) || + fmapEpiTop.test(path) || + fmapGreTop.test(path) || + otherTopFiles.test(path) || + megTop.test(path) || + eegTop.test(path) || + ieegTop.test(path) || + petTop.test(path) || + motionTop.test(path) || + nirsTop.test(path) || + microscopyTop.test(path) + ) }, /** @@ -267,51 +233,39 @@ export default { * Check if the file has a name appropriate for an anatomical scan */ isAnat: function (path) { - if (bids_schema) { - return bids_schema.datatypes['anat'].some((regex) => regex.exec(path)) - } else { - return ( - conditionalMatch(anatNonparametric, path) || - conditionalMatch(anatParametric, path) || - conditionalMatch(anatDefacemask, path) || - conditionalMatch(anatMultiEcho, path) || - conditionalMatch(anatMultiFlip, path) || - conditionalMatch(anatMultiInv, path) || - conditionalMatch(anatMP2RAGE, path) || - conditionalMatch(anatVFAMT, path) || - conditionalMatch(anatMTR, path) - ) - } + return ( + conditionalMatch(anatNonparametric, path) || + conditionalMatch(anatParametric, path) || + conditionalMatch(anatDefacemask, path) || + conditionalMatch(anatMultiEcho, path) || + conditionalMatch(anatMultiFlip, path) || + conditionalMatch(anatMultiInv, path) || + conditionalMatch(anatMP2RAGE, path) || + conditionalMatch(anatVFAMT, path) || + conditionalMatch(anatMTR, path) + ) }, /** * Check if the file has a name appropriate for a diffusion scan */ isDWI: function (path) { - if (bids_schema) { - return bids_schema.datatypes['dwi'].some((regex) => regex.exec(path)) - } else { - return conditionalMatch(dwiData, path) - } + return conditionalMatch(dwiData, path) }, /** * Check if the file has a name appropriate for a fieldmap scan */ isFieldMap: function (path) { - if (bids_schema) { - return bids_schema.datatypes['fmap'].some((regex) => regex.exec(path)) - } else { - return ( - conditionalMatch(fmapGre, path) || - conditionalMatch(fmapPepolarAsl, path) || - conditionalMatch(fmapTB1DAM, path) || - conditionalMatch(fmapTB1EPI, path) || - conditionalMatch(fmapTB1SRGE, path) || - conditionalMatch(fmapRF, path) || - conditionalMatch(fmapParametric, path) - ) - } + return ( + conditionalMatch(fmapGre, path) || + conditionalMatch(fmapPepolarAsl, path) || + conditionalMatch(fmapTB1DAM, path) || + conditionalMatch(fmapTB1EPI, path) || + conditionalMatch(fmapTB1SRGE, path) || + conditionalMatch(fmapRF, path) || + conditionalMatch(fmapParametric, path) + ) }, isFieldMapMainNii: function (path) { @@ -332,16 +286,12 @@ export default { * Check if the file has a name appropriate for a functional scan */ isFunc: function (path) { - if (bids_schema) { - return bids_schema.datatypes['func'].some((regex) => regex.exec(path)) - } else { - return ( - conditionalMatch(func, path) || - conditionalMatch(funcPhaseDeprecated, path) || - conditionalMatch(funcEvents, path) || - conditionalMatch(funcTimeseries, path) - ) - } + return ( + conditionalMatch(func, path) || + conditionalMatch(funcPhaseDeprecated, path) || + conditionalMatch(funcEvents, path) || + conditionalMatch(funcTimeseries, path) + ) }, isAsl: function (path) { @@ -349,11 +299,7 @@ export default { }, isPET: function (path) { - if (bids_schema) { - return bids_schema.datatypes['pet'].some((regex) => regex.exec(path)) - } else { - return conditionalMatch(petData, path) - } + return conditionalMatch(petData, path) }, isPETBlood: function (path) { @@ -361,44 +307,26 @@ export default { }, isMeg: function (path) { - if (bids_schema) { - return bids_schema.datatypes['meg'].some((regex) => regex.exec(path)) - } else { - return ( - conditionalMatch(megData, path) || - conditionalMatch(megCalibrationData, path) || - conditionalMatch(megCrosstalkData, path) - ) - } + return ( + conditionalMatch(megData, path) || + conditionalMatch(megCalibrationData, path) || + conditionalMatch(megCrosstalkData, path) + ) }, isNIRS: function (path) { return conditionalMatch(nirsData, path) }, isEEG: function (path) { - if (bids_schema) { - return bids_schema.datatypes['eeg'].some((regex) => regex.exec(path)) - } else { - return conditionalMatch(eegData, path) - } + return conditionalMatch(eegData, path) }, isIEEG: function (path) { - if (bids_schema) { - return bids_schema.datatypes['ieeg'].some((regex) => regex.exec(path)) - } else { - return conditionalMatch(ieegData, path) - } + return conditionalMatch(ieegData, path) }, isMOTION: function (path) { - if (bids_schema) { - // Motion not currently in schema - // return bids_schema.datatypes['motion'].some(regex => regex.exec(path)) - return conditionalMatch(motion, path) - } else { - return conditionalMatch(motion, path) - } + return conditionalMatch(motion, path) }, isMicroscopy: function (path) { @@ -414,11 +342,7 @@ export default { }, isBehavioral: function (path) { - if (bids_schema) { - return bids_schema.datatypes['beh'].some((regex) => regex.exec(path)) - } else { - return conditionalMatch(behavioralData, path) - } + return conditionalMatch(behavioralData, path) }, isFuncBold: function (path) { @@ -479,9 +403,6 @@ export default { return values }, - - // CommonJS default export - schemaSetup, } function conditionalMatch(expression, path) { diff --git a/bids-validator/validators/bids/fullTest.js b/bids-validator/validators/bids/fullTest.js index edf0b4054..e9b433c33 100644 --- a/bids-validator/validators/bids/fullTest.js +++ b/bids-validator/validators/bids/fullTest.js @@ -28,7 +28,7 @@ import collectModalities from '../../utils/summary/collectModalities' * Takes on an array of files, callback, and boolean indicating if git-annex is used. * Starts the validation process for a BIDS package. */ -const fullTest = (fileList, options, annexed, dir, schema, callback) => { +const fullTest = (fileList, options, annexed, dir, callback) => { const self = BIDS self.options = options @@ -64,7 +64,7 @@ const fullTest = (fileList, options, annexed, dir, schema, callback) => { }) } - const summary = utils.collectSummary(fileList, self.options, schema) + const summary = utils.collectSummary(fileList, self.options) // remove size redundancies for (const key in fileList) { diff --git a/bids-validator/validators/bids/start.js b/bids-validator/validators/bids/start.js index 7d4685b75..20463ce65 100644 --- a/bids-validator/validators/bids/start.js +++ b/bids-validator/validators/bids/start.js @@ -5,8 +5,6 @@ import quickTest from './quickTest' import quickTestError from './quickTestError' import fullTest from './fullTest' import utils from '../../utils' -import { schemaRegex } from '../../validators/schemaTypes' -import { schemaSetup } from '../../utils/type' /** * Start @@ -31,17 +29,11 @@ const start = (dir, options, callback) => { } else { BIDS.options = options reset(BIDS) - // Load the bids-spec schema ahead of any validation - let schema - if (options.schema) { - schema = await schemaRegex(options.schema) - schemaSetup(schema) - } const files = await utils.files.readDir(dir, options) if (quickTest(files)) { // Is the dir using git-annex? const annexed = utils.files.remoteFiles.isGitAnnex(dir) - fullTest(files, BIDS.options, annexed, dir, schema, callback) + fullTest(files, BIDS.options, annexed, dir, callback) } else { // Return an error immediately if quickTest fails const issue = quickTestError(dir) diff --git a/bids-validator/validators/options.js b/bids-validator/validators/options.js index e30399171..b1b08d34e 100644 --- a/bids-validator/validators/options.js +++ b/bids-validator/validators/options.js @@ -66,13 +66,6 @@ export function parseOptions(argumentOverride) { 'A less accurate check that reads filenames one per line from stdin.', ) .hide('filenames') - .option('schema', { - alias: 's', - describe: - 'BIDS specification schema version to use for validation, e.g. "v1.6.0" (beta)', - default: 'disable', - choices: ['disable', 'v1.6.0', 'v1.7.0', 'master'], - }) .epilogue( 'This tool checks if a dataset in a given directory is \ compatible with the Brain Imaging Data Structure specification. To learn \ diff --git a/bids-validator/validators/schemaTypes.js b/bids-validator/validators/schemaTypes.js deleted file mode 100644 index 2063dc122..000000000 --- a/bids-validator/validators/schemaTypes.js +++ /dev/null @@ -1,175 +0,0 @@ -import yaml from 'js-yaml' -import isNode from '../utils/isNode' - -// Version implemented by the internal rules or the included schema version -const localVersion = 'v1.6.0' - -const modalities = [ - 'anat', - 'beh', - 'dwi', - 'eeg', - 'fmap', - 'func', - 'ieeg', - 'meg', - 'pet', -] - -async function loadYaml(base, path, local) { - const url = `${base}/${path}` - try { - let text // Loaded raw yaml - if (local) { - throw Error('Defaulting to embedded bids-specification schema') - } else { - const res = await fetch(url) - if (res.status !== 200) { - throw Error( - `Loading remote bids-specification schema failed, falling back to embedded bids-specification@${localVersion}`, - ) - } - text = await res.text() - } - return yaml.safeLoad(text) - } catch (err) { - if (isNode) { - const fs = require('fs') - const text = fs.readFileSync(url, 'utf-8') - return yaml.safeLoad(text) - } else { - // TODO - handle the case where no yaml is available in the browser - throw Error( - `Loading remote bids-specification schema failed, and internal validation rules will be used instead`, - ) - } - } -} - -/** - * Load schema files from network or embedded copies - * @param {string} base Base URL or path - * @param {boolean} local Avoid any network access - */ -async function loadSchema(base, local = false) { - // Define path prefix depending on the BIDS schema version - const prefix_objects = base.includes('v1.6.0') ? '' : 'objects/' - const prefix_rules = base.includes('v1.6.0') ? '' : 'rules/' - const prefix_datatypes = prefix_rules + 'datatypes/' - - // Define schema files for top level files and entities - const top = prefix_rules + 'top_level_files.yaml' - const entities = prefix_objects + 'entities.yaml' - - return { - top_level_files: await loadYaml(base, top, local), - entities: await loadYaml(base, entities, local), - datatypes: { - anat: await loadYaml(base, prefix_datatypes + 'anat.yaml', local), - beh: await loadYaml(base, prefix_datatypes + 'beh.yaml', local), - dwi: await loadYaml(base, prefix_datatypes + 'dwi.yaml', local), - eeg: await loadYaml(base, prefix_datatypes + 'eeg.yaml', local), - fmap: await loadYaml(base, prefix_datatypes + 'fmap.yaml', local), - func: await loadYaml(base, prefix_datatypes + 'func.yaml', local), - ieeg: await loadYaml(base, prefix_datatypes + 'ieeg.yaml', local), - meg: await loadYaml(base, prefix_datatypes + 'meg.yaml', local), - pet: await loadYaml(base, prefix_datatypes + 'pet.yaml', local), - }, - } -} - -/** - * Generate matching regular expressions based on the most recent bids-specification schema - * @param {*} schema Loaded yaml schemas (js-yaml) - * @param {boolean} pythonRegex Boolean flag to enable/disable Python compatible regex generation - * @returns - */ -export async function generateRegex(schema, pythonRegex = false) { - // Python regex needs a 'P' before matching group name - const P = pythonRegex ? 'P' : '' - const regex = { - label: '[a-zA-Z0-9]+', - index: '[0-9]+', - sub_ses_dirs: - '^[\\/\\\\](sub-[a-zA-Z0-9]+)[\\/\\\\](?:(ses-[a-zA-Z0-9]+)[\\/\\\\])?', - type_dir: '[\\/\\\\]', - sub_ses_entity: '\\1(_\\2)?', - optional: '?', - required: '', - } - - const exportRegex = { - top_level_files: [], - datatypes: { - anat: [], - beh: [], - dwi: [], - eeg: [], - fmap: [], - func: [], - ieeg: [], - meg: [], - pet: [], - }, - } - - // Modality agnostic top level files - for (const root of Object.keys(schema.top_level_files)) { - const extensions = schema.top_level_files[root].extensions.join('|') - const root_level = `[\\/\\\\]${root}${ - extensions === 'None' ? '' : `(?${P}${extensions})` - }$` - exportRegex.top_level_files.push(new RegExp(root_level)) - } - - for (const mod of modalities) { - const modality_datatype_schema = schema.datatypes[mod] - for (const datatype of Object.keys(modality_datatype_schema)) { - let file_regex = `${regex.sub_ses_dirs}${mod}${regex.type_dir}${regex.sub_ses_entity}` - let entities = Object.keys(schema.entities) - for (const entity of Object.keys( - modality_datatype_schema[datatype].entities, - )) { - if (entities.includes(entity)) { - const entityDefinion = schema.entities[entity] - // sub and ses entities in file name handled by directory pattern matching groups - if (entity === 'subject' || entity === 'session') { - continue - } - const entityKey = entityDefinion.entity - const format = regex[schema.entities[entity].format] - if (format) { - // Limitation here is that if format is missing an essential entity may be skipped - file_regex += `(?${P}<${entity}>_${entityKey}-${format})${ - regex[modality_datatype_schema[datatype].entities[entity]] - }` - } - } - } - const suffix_regex = `_(?${P}${modality_datatype_schema[ - datatype - ].suffixes.join('|')})` - // Workaround v1.6.0 MEG extension "*" - const wildcard_extensions = modality_datatype_schema[ - datatype - ].extensions.map((ext) => (ext === '*' ? '.*?' : ext)) - const ext_regex = `(?${P}${wildcard_extensions.join('|')})` - exportRegex.datatypes[mod].push( - new RegExp(file_regex + suffix_regex + ext_regex + '$'), - ) - } - } - return exportRegex -} - -export async function schemaRegex(version = localVersion, options = {}) { - let schema - if ('local' in options) { - schema = await loadSchema('./bids-validator/spec/src/schema', true) - } else { - schema = await loadSchema( - `https://raw.githubusercontent.com/bids-standard/bids-specification/${version}/src/schema`, - ) - } - return generateRegex(schema) -}