Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 12 additions & 7 deletions src/schema/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { BIDSFile } from '../types/filetree.ts'
import { FileTree } from '../types/filetree.ts'
import { ColumnsMap } from '../types/columns.ts'
import { readEntities } from './entities.ts'
import { findDatatype } from './datatypes.ts'
import { DatasetIssues } from '../issues/datasetIssues.ts'
import { walkBack } from '../files/inheritance.ts'
import { parseGzip } from '../files/gzip.ts'
Expand Down Expand Up @@ -147,16 +148,20 @@ export class BIDSContext implements Context {
dsContext?: BIDSContextDataset,
fileTree?: FileTree,
) {
this.dataset = dsContext ? dsContext : new BIDSContextDataset({ tree: fileTree })

this.filenameRules = []
this.file = file
const bidsEntities = readEntities(file.name)
this.suffix = bidsEntities.suffix
this.extension = bidsEntities.extension
this.entities = bidsEntities.entities
this.dataset = dsContext ? dsContext : new BIDSContextDataset({ tree: fileTree })

const { entities, suffix, extension } = readEntities(file.name)
const { datatype, modality } = findDatatype(file, this.dataset.schema)
this.entities = entities
this.suffix = suffix
this.extension = extension
this.datatype = datatype
this.modality = modality

this.subject = {} as Subject
this.datatype = ''
this.modality = ''
this.sidecar = {}
this.sidecarKeyOrigin = {}
this.columns = new ColumnsMap() as Record<string, string[]>
Expand Down
57 changes: 57 additions & 0 deletions src/schema/datatypes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { assert, assertObjectMatch } from '@std/assert'
import { loadSchema } from '../setup/loadSchema.ts'
import { BIDSContext, BIDSContextDataset } from '../schema/context.ts'
import { pathsToTree } from '../files/filetree.ts'
import type { BIDSFile } from '../types/filetree.ts'

import { findDatatype, modalityTable } from './datatypes.ts'

const schema = await loadSchema()

// Creates a file object as part of a minimal file tree
const makeFile = (path: string): BIDSFile => pathsToTree([path]).get(path) as BIDSFile

Deno.test('test modalityTable', async (t) => {
await t.step('empty schema', () => {
assertObjectMatch(modalityTable({}), {})
})

await t.step('real schema', async () => {
const table = modalityTable(schema)
// Memoization check
assert(modalityTable(schema) == table)
// spot check
assertObjectMatch(table, {
'anat': 'mri',
'perf': 'mri',
'eeg': 'eeg',
})
})
})

Deno.test('test findDatatype', async (t) => {
await t.step('root files', async () => {
const path = makeFile('/participants.tsv')
assertObjectMatch(findDatatype(path, schema), { datatype: '', modality: '' })
})
await t.step('non-datatype parent', async () => {
const path = makeFile('/stimuli/image.png')
assertObjectMatch(findDatatype(path, schema), { datatype: '', modality: '' })
})
await t.step('phenotype file', async () => {
const path = makeFile('/phenotype/survey.tsv')
assertObjectMatch(findDatatype(path, schema), { datatype: 'phenotype', modality: '' })
})
await t.step('data files', async () => {
for (
const [filename, datatype, modality] of [
['/sub-01/anat/sub-01_T1w.nii.gz', 'anat', 'mri'],
['/sub-01/eeg/sub-01_task-rest_eeg.edf', 'eeg', 'eeg'],
['/sub-01/perf/sub-01_task-rest_bold.nii.gz', 'perf', 'mri'],
]
) {
const path = makeFile(filename)
assertObjectMatch(findDatatype(path, schema), { datatype, modality })
}
})
})
30 changes: 30 additions & 0 deletions src/schema/datatypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { BIDSFile } from '../types/filetree.ts'
import type { Schema } from '../types/schema.ts'
import { memoize } from '../utils/memoize.ts'

function _modalityTable(schema: Schema): Record<string, string> {
const modalities: Record<string, string> = {}
const rules = (schema.rules?.modalities ?? {}) as Record<string, { datatypes: string[] }>
for (const [modality, { datatypes }] of Object.entries(rules)) {
for (const datatype of datatypes) {
modalities[datatype] = modality
}
}
return modalities
}

// Construct once per schema; should only be multiple in tests
export const modalityTable = memoize(_modalityTable)

export function findDatatype(
file: BIDSFile,
schema: Schema,
): { datatype: string; modality: string } {
const lookup = modalityTable(schema)
const datatype = file.parent?.name
if (!schema?.objects?.datatypes[datatype]) {
return { datatype: '', modality: '' }
}
const modality = lookup[datatype] ?? ''
return { datatype, modality }
}
16 changes: 0 additions & 16 deletions src/schema/modalities.ts

This file was deleted.

20 changes: 1 addition & 19 deletions src/validators/filenameIdentify.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { assertEquals } from '@std/assert'
import { SEPARATOR_PATTERN } from '@std/path'
import { BIDSContext } from '../schema/context.ts'
import {
_findRuleMatches,
datatypeFromDirectory,
findDirRuleMatches,
hasMatch,
} from './filenameIdentify.ts'
import { _findRuleMatches, findDirRuleMatches, hasMatch } from './filenameIdentify.ts'
import { BIDSFileDeno } from '../files/deno.ts'
import { FileIgnoreRules } from '../files/ignore.ts'
import { loadSchema } from '../setup/loadSchema.ts'
Expand Down Expand Up @@ -50,19 +45,6 @@ Deno.test('test _findRuleMatches', async (t) => {
)
})

Deno.test('test datatypeFromDirectory', (t) => {
const filesToTest = [
['/sub-01/ses-01/func/sub-01_ses-01_task-nback_run-01_bold.nii', 'func'],
['/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii', 'anat'],
]
filesToTest.map((test) => {
const file = new BIDSFileDeno(PATH, test[0], ignore)
const context = new BIDSContext(file)
datatypeFromDirectory(schema, context)
assertEquals(context.datatype, test[1])
})
})

Deno.test('test hasMatch', async (t) => {
await t.step('hasMatch', async () => {
const fileName = '/sub-01/ses-01/func/sub-01_ses-01_task-nback_run-01_bold.nii'
Expand Down
25 changes: 0 additions & 25 deletions src/validators/filenameIdentify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
import { globToRegExp, SEPARATOR_PATTERN } from '@std/path'
import type { GenericSchema, Schema } from '../types/schema.ts'
import type { BIDSContext } from '../schema/context.ts'
import type { lookupModality } from '../schema/modalities.ts'
import type { CheckFunction } from '../types/check.ts'
import { lookupEntityLiteral } from './filenameValidate.ts'

const CHECKS: CheckFunction[] = [
datatypeFromDirectory,
findRuleMatches,
hasMatch,
cleanContext,
Expand Down Expand Up @@ -126,29 +124,6 @@ function matchStemRule(node, context): boolean {
return true
}

export async function datatypeFromDirectory(schema, context) {
const subEntity = schema.objects.entities.subject.name
const sesEntity = schema.objects.entities.session.name
const parts = context.file.path.split(SEPARATOR_PATTERN)
const datatypeIndex = parts.length - 2
if (datatypeIndex < 1) {
return Promise.resolve()
}
const dirDatatype = parts[datatypeIndex]
if (dirDatatype === 'phenotype') {
// Phenotype is a pseudo-datatype for now.
context.datatype = dirDatatype
return Promise.resolve()
}
for (const key in schema.rules.modalities) {
if (schema.rules.modalities[key].datatypes.includes(dirDatatype)) {
context.modality = key
context.datatype = dirDatatype
return Promise.resolve()
}
}
}

export function hasMatch(schema, context) {
if (
context.filenameRules.length === 0 &&
Expand Down
28 changes: 15 additions & 13 deletions src/validators/validateFiles.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { assert, assertEquals } from '@std/assert'
import { filenameIdentify } from './filenameIdentify.ts'
import { filenameValidate } from './filenameValidate.ts'
import { BIDSContext } from '../schema/context.ts'
import { BIDSContext, BIDSContextDataset } from '../schema/context.ts'
import { loadSchema } from '../setup/loadSchema.ts'
import type { GenericSchema, Schema } from '../types/schema.ts'
import type { DatasetIssues } from '../issues/datasetIssues.ts'
import { pathToFile } from '../files/filetree.ts'
import type { BIDSFile } from '../types/filetree.ts'
import { pathsToTree } from '../files/filetree.ts'

const schema = await loadSchema() as unknown as GenericSchema
const schema = await loadSchema()

function validatePath(path: string): DatasetIssues {
const context = new BIDSContext(pathToFile(path))
filenameIdentify(schema, context)
filenameValidate(schema, context)
return context.dataset.issues
function makeContext(path: string): BIDSContext {
const tree = pathsToTree([path])
const dataset = new BIDSContextDataset({ schema, tree })
return new BIDSContext(tree.get(path) as BIDSFile, dataset)
}

Deno.test('test valid paths', async (t) => {
Expand Down Expand Up @@ -54,11 +54,13 @@ Deno.test('test valid paths', async (t) => {
]
for (const filename of validFiles) {
await t.step(filename, async () => {
const issues = validatePath(filename)
const context = makeContext(filename)
await filenameIdentify(schema, context)
await filenameValidate(schema as unknown as GenericSchema, context)
assertEquals(
issues.get({ location: filename }).length,
context.dataset.issues.get({ location: filename }).length,
0,
Deno.inspect(issues),
Deno.inspect(context.dataset.issues),
)
})
}
Expand Down Expand Up @@ -111,9 +113,9 @@ Deno.test('test invalid paths', async (t) => {
]
for (const filename of invalidFiles) {
await t.step(filename, async () => {
const context = new BIDSContext(pathToFile(filename))
const context = makeContext(filename)
await filenameIdentify(schema, context)
await filenameValidate(schema, context)
await filenameValidate(schema as unknown as GenericSchema, context)
assert(
context.dataset.issues.get({
location: context.file.path,
Expand Down
Loading