Skip to content

Commit

Permalink
Merge pull request #2036 from effigies/fix/nifti_reading
Browse files Browse the repository at this point in the history
fix(nifti): Load NIfTI headers
  • Loading branch information
rwblair authored Jul 31, 2024
2 parents 63fba42 + 54a9129 commit cb1f701
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
1 change: 1 addition & 0 deletions bids-validator/src/deps/nifti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { isCompressed, readHeader } from 'https://esm.sh/[email protected]'
26 changes: 26 additions & 0 deletions bids-validator/src/files/nifti.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { assert } from '../deps/asserts.ts'
import { FileIgnoreRules } from './ignore.ts'
import { BIDSFileDeno } from './deno.ts'

import { loadHeader } from './nifti.ts'

Deno.test('Test loading nifti header', async (t) => {
const ignore = new FileIgnoreRules([])
await t.step('Load header from compressed file', async () => {
const path = "sub-01/func/sub-01_task-rhymejudgment_bold.nii.gz"
const root = "./tests/data/valid_headers"
const file = new BIDSFileDeno(root, path, ignore)
const header = await loadHeader(file)
assert(header !== undefined)
assert(header['pixdim'].length === 8)
})
await t.step('Fail on non-nifti file', async () => {
const path = "sub-01/func/sub-01_task-rhymejudgment_events.tsv"
const root = "./tests/data/valid_headers"
const file = new BIDSFileDeno(root, path, ignore)
const header = await loadHeader(file)
assert(header !== undefined)
assert(header === null)
})

})
37 changes: 32 additions & 5 deletions bids-validator/src/files/nifti.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import 'https://raw.githubusercontent.com/rii-mango/NIFTI-Reader-JS/v0.6.4/release/current/nifti-reader-min.js'
import { isCompressed, readHeader } from '../deps/nifti.ts'
import { BIDSFile } from '../types/filetree.ts'
import { logger } from '../utils/logger.ts'
import { ContextNiftiHeader } from '../types/context.ts'

export async function loadHeader(file: BIDSFile) {
async function extract(buffer: Uint8Array, nbytes: number): Promise<Uint8Array> {
// The fflate decompression that is used in nifti-reader does not like
// truncated data, so pretend that we have a stream and stop reading
// when we have enough bytes.
const result = new Uint8Array(nbytes)
const stream = new ReadableStream({
start(controller) {
controller.enqueue(buffer)
},
})
const reader = stream.pipeThrough(new DecompressionStream('gzip')).getReader()
let offset = 0
while (offset < nbytes) {
const { value, done } = await reader.read()
if (done) {
break
}
result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset)
offset += value.length
}
await reader.cancel()
return result
}

export async function loadHeader(file: BIDSFile): Promise<ContextNiftiHeader | undefined> {
try {
const buf = await file.readBytes(1024)
// @ts-expect-error NIFTI-Reader-JS required mangling globals
const header = globalThis.nifti.readHeader(buf.buffer)
const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf
const header = readHeader(data.buffer)
// normalize between nifti-reader and spec schema
// https://github.com/bids-standard/bids-specification/blob/master/src/schema/meta/context.yaml#L200
if (header) {
// @ts-expect-error
header.pixdim = header.pixDims
// @ts-expect-error
header.dim = header.dims
}
return header
return header as unknown as ContextNiftiHeader
} catch (err) {
logger.warning(`NIfTI file could not be opened or read ${file.path}`)
logger.debug(err)
Expand Down

0 comments on commit cb1f701

Please sign in to comment.