Skip to content

Commit

Permalink
Merge pull request #1977 from effigies/feat/subjects
Browse files Browse the repository at this point in the history
feat(schema): Load subject data
  • Loading branch information
effigies authored May 24, 2024
2 parents 6e14422 + 0aa1665 commit ba937d6
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 9 deletions.
15 changes: 14 additions & 1 deletion bids-validator/src/schema/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ rootFileTree.files = [
]
rootFileTree.directories = [subjectFileTree]

const context = new BIDSContext(anatFileTree, dataFile, issues)
let context = new BIDSContext(anatFileTree, dataFile, issues)

Deno.test('test context LoadSidecar', async (t) => {
await context.loadSidecar(rootFileTree)
Expand All @@ -106,3 +106,16 @@ Deno.test('test context LoadSidecar', async (t) => {
assert(anatValue, 'anat')
})
})

context = new BIDSContext(rootFileTree, dataFile, issues)

Deno.test('test context loadSubjects', async (t) => {
await context.loadSubjects()
await t.step('context produces correct subjects object', () => {
assert(context.dataset.subjects, "subjects object exists")
assert(context.dataset.subjects.sub_dirs.length == 1, "there is only one sub dir found")
assert(context.dataset.subjects.sub_dirs[0] == 'sub-01', "that sub dir is sub-01")
// no participants.tsv so this should be empty
assert(context.dataset.subjects.participant_id == undefined, "no participant_id is populated")
})
})
65 changes: 63 additions & 2 deletions bids-validator/src/schema/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ export class BIDSContextDataset implements ContextDataset {
tree: object
ignored: any[]
modalities: any[]
subjects: ContextDatasetSubjects[]
subjects?: ContextDatasetSubjects

constructor(options?: ValidatorOptions, description = {}) {
this.dataset_description = description
this.files = []
this.tree = {}
this.ignored = []
this.modalities = []
this.subjects = [] as ContextDatasetSubjects[]
if (options) {
this.options = options
}
Expand All @@ -47,6 +46,22 @@ export class BIDSContextDataset implements ContextDataset {
}
}

export class BIDSContextDatasetSubjects implements ContextDatasetSubjects {
sub_dirs: string[]
participant_id?: string[]
phenotype?: string[]

constructor(
sub_dirs?: string[],
participant_id?: string[],
phenotype?: string[],
) {
this.sub_dirs = sub_dirs ? sub_dirs : []
this.participant_id = participant_id
this.phenotype = phenotype
}
}

const defaultDsContext = new BIDSContextDataset()

export class BIDSContext implements Context {
Expand Down Expand Up @@ -200,8 +215,54 @@ export class BIDSContext implements Context {
.catch((error) => {})
}

// This is currently done for every file. It should be done once for the dataset.
async loadSubjects(): Promise<void> {
if (this.dataset.subjects != null) {
return
}
this.dataset.subjects = new BIDSContextDatasetSubjects()
// Load subject dirs from the file tree
this.dataset.subjects.sub_dirs = this.fileTree.directories
.filter((dir) => dir.name.startsWith('sub-'))
.map((dir) => dir.name)

// Load participants from participants.tsv
const participants_tsv = this.fileTree.files.find(
(file) => file.name === 'participants.tsv',
)
if (participants_tsv) {
const participantsText = await participants_tsv.text()
const participantsData = parseTSV(participantsText)
this.dataset.subjects.participant_id = participantsData[
'participant_id'
] as string[]
}

// Load phenotype from phenotype/*.tsv
const phenotype_dir = this.fileTree.directories.find(
(dir) => dir.name === 'phenotype',
)
if (phenotype_dir) {
const phenotypeFiles = phenotype_dir.files.filter((file) =>
file.name.endsWith('.tsv'),
)
// Collect observed participant_ids
const seen = new Set() as Set<string>
for (const file of phenotypeFiles) {
const phenotypeText = await file.text()
const phenotypeData = parseTSV(phenotypeText)
const participant_id = phenotypeData['participant_id'] as string[]
if (participant_id) {
participant_id.forEach((id) => seen.add(id))
}
}
this.dataset.subjects.phenotype = Array.from(seen)
}
}

async asyncLoads() {
await Promise.allSettled([
this.loadSubjects(),
this.loadSidecar(),
this.loadColumns(),
this.loadAssociations(),
Expand Down
7 changes: 5 additions & 2 deletions bids-validator/src/schema/expressionLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ export const expressionFunctions = {
return arg.substr(start, end - start)
},
sorted: <T>(list: T[]): T[] => {
list.sort()
return list
// Copy, sort, return
return list.slice().sort()
},
allequal: <T>(a: T[], b: T[]): boolean => {
return (a != null && b != null) && a.length === b.length && a.every((v, i) => v === b[i])
},
}
6 changes: 3 additions & 3 deletions bids-validator/src/types/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { ValidatorOptions } from '../setup/options.ts'

export interface ContextDatasetSubjects {
sub_dirs: string[]
participant_id: string[]
phenotype: string[]
participant_id?: string[]
phenotype?: string[]
}
export interface ContextDataset {
dataset_description: Record<string, unknown>
files: any[]
tree: object
ignored: any[]
modalities: any[]
subjects: ContextDatasetSubjects[]
subjects?: ContextDatasetSubjects
options?: ValidatorOptions
}
export interface ContextSubjectSessions {
Expand Down

0 comments on commit ba937d6

Please sign in to comment.