Skip to content

Commit a24486c

Browse files
authored
Merge pull request #127 from effigies/fix/nifti_headers
fix: Truncate NIfTI header data to avoid extensions
2 parents 9e8da14 + 9963e7d commit a24486c

File tree

5 files changed

+96
-13
lines changed

5 files changed

+96
-13
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
-->
6+
7+
<!--
8+
### Added
9+
10+
- A bullet item for the Added category.
11+
12+
-->
13+
<!--
14+
### Changed
15+
16+
- A bullet item for the Changed category.
17+
18+
-->
19+
### Fixed
20+
21+
- Resolve issue with parsing headers of NIfTI files with large extensions.
22+
Fixes [issue 126].
23+
24+
[issue 126]: https://github.com/bids-standard/bids-validator/issues/126
25+
26+
<!--
27+
### Deprecated
28+
29+
- A bullet item for the Deprecated category.
30+
31+
-->
32+
<!--
33+
### Removed
34+
35+
- A bullet item for the Removed category.
36+
37+
-->
38+
<!--
39+
### Security
40+
41+
- A bullet item for the Security category.
42+
43+
-->
44+
<!--
45+
### Infrastructure
46+
47+
- A bullet item for the Infrastructure category.
48+
49+
-->

src/files/deno.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,17 @@ export class BIDSFileDeno implements BIDSFile {
9393

9494
/**
9595
* Read bytes in a range efficiently from a given file
96+
*
97+
* Reads up to size bytes, starting at offset.
98+
* If EOF is encountered, the resulting array may be smaller.
9699
*/
97100
async readBytes(size: number, offset = 0): Promise<Uint8Array> {
98101
const handle = this.#openHandle()
99102
const buf = new Uint8Array(size)
100103
await handle.seek(offset, Deno.SeekMode.Start)
101-
await handle.read(buf)
104+
const nbytes = await handle.read(buf) ?? 0
102105
handle.close()
103-
return buf
106+
return buf.subarray(0, nbytes)
104107
}
105108

106109
/**

src/files/nifti.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,23 @@ Deno.test('Test loading nifti header', async (t) => {
5353
})
5454
assertObjectMatch(error, { key: 'NIFTI_HEADER_UNREADABLE' })
5555
})
56+
57+
await t.step('Tolerate big headers', async () => {
58+
const path = 'big_header.nii.gz'
59+
const root = './tests/data/'
60+
const file = new BIDSFileDeno(root, path, ignore)
61+
let error: any = undefined
62+
const header = await loadHeader(file)
63+
assert(header !== undefined)
64+
assertObjectMatch(header, {
65+
dim: [3, 1, 1, 1, 1, 1, 1],
66+
pixdim: [1, 1, 1, 1, 1, 1, 1],
67+
shape: [1, 1, 1],
68+
voxel_sizes: [1, 1, 1],
69+
dim_info: { freq: 0, phase: 0, slice: 0 },
70+
xyzt_units: { xyz: 'unknown', t: 'unknown' },
71+
qform_code: 0,
72+
sform_code: 2,
73+
})
74+
})
5675
})

src/files/nifti.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isCompressed, readHeader } from '@mango/nifti'
1+
import { isCompressed, isNIFTI1, isNIFTI2, NIFTI1, NIFTI2 } from '@mango/nifti'
22
import type { BIDSFile } from '../types/filetree.ts'
33
import { logger } from '../utils/logger.ts'
44
import type { NiftiHeader } from '@bids/schema/context'
@@ -11,27 +11,39 @@ async function extract(buffer: Uint8Array, nbytes: number): Promise<Uint8Array>
1111
const stream = new ReadableStream({
1212
start(controller) {
1313
controller.enqueue(buffer)
14+
controller.close()
1415
},
1516
})
1617
const reader = stream.pipeThrough(new DecompressionStream('gzip')).getReader()
1718
let offset = 0
18-
while (offset < nbytes) {
19-
const { value, done } = await reader.read()
20-
if (done) {
21-
break
19+
try {
20+
while (offset < nbytes) {
21+
const { value, done } = await reader.read()
22+
if (done || !value) {
23+
break
24+
}
25+
result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset)
26+
offset += value.length
2227
}
23-
result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset)
24-
offset += value.length
28+
} finally {
29+
await reader.cancel()
2530
}
26-
await reader.cancel()
27-
return result
31+
return result.subarray(0, offset)
2832
}
2933

3034
export async function loadHeader(file: BIDSFile): Promise<NiftiHeader> {
3135
try {
3236
const buf = await file.readBytes(1024)
33-
const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf
34-
const header = readHeader(data.buffer)
37+
const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf.slice(0, 540)
38+
let header
39+
if (isNIFTI1(data.buffer)) {
40+
header = new NIFTI1()
41+
// Truncate to 348 bytes to avoid attempting to parse extensions
42+
header.readHeader(data.buffer.slice(0, 348))
43+
} else if (isNIFTI2(data.buffer)) {
44+
header = new NIFTI2()
45+
header.readHeader(data.buffer)
46+
}
3547
if (!header) {
3648
throw { key: 'NIFTI_HEADER_UNREADABLE' }
3749
}

tests/data/big_header.nii.gz

119 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)