├── .gitignore ├── test ├── minimal.nii └── test.js ├── .travis.yml ├── niinfo.js ├── package.json ├── LICENSE ├── README.md └── nifti.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *~ 3 | -------------------------------------------------------------------------------- /test/minimal.nii: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scijs/nifti-js/HEAD/test/minimal.nii -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.12" 6 | before_install: 7 | - npm install -g npm@">=1.4.6" 8 | sudo: false 9 | -------------------------------------------------------------------------------- /niinfo.js: -------------------------------------------------------------------------------- 1 | var nifti = require('./nifti.js') 2 | var fs = require('fs') 3 | var opts = require('yargs').usage("Usage: node niinfo NII_FILE").demand(1).argv 4 | 5 | var file = nifti.parseHeader(fs.readFileSync(opts._[0])) 6 | console.log(file) 7 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var nifti = require('../nifti.js') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | test("load minimal.nii", function (t) { 7 | // Test file is one of the example data files supplied here: http://nifti.nimh.nih.gov/nifti-1/data 8 | var file = nifti.parse(fs.readFileSync(path.join(path.dirname(module.filename),'minimal.nii'))) 9 | 10 | file.buffer = file.buffer.byteLength + " bytes" 11 | file.data = file.data.length + " items" 12 | console.log(file) 13 | 14 | t.end() 15 | }) 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nifti-js", 3 | "version": "1.0.0", 4 | "description": "NIfTI support for Javascript", 5 | "main": "nifti.js", 6 | "scripts": { 7 | "test": "tape test/*.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/scijs/nifti-js.git" 12 | }, 13 | "keywords": [ 14 | "scijs", 15 | "nifti" 16 | ], 17 | "author": "Jasper van de Gronde", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/scijs/nifti-js/issues" 21 | }, 22 | "homepage": "https://github.com/scijs/nifti-js", 23 | "devDependencies": { 24 | "tape": "^4.2.2" 25 | }, 26 | "optionalDependencies": { 27 | "yargs": "^3.29.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jasper van de Gronde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NIfTI support for Javascript 2 | =========================== 3 | 4 | This can parse files in the [Neuroimaging Informatics Technology Initiative format](http://nifti.nimh.nih.gov/) (version 1). Currently, data (as opposed to the header) can only be read from ".nii" files (files that include both the header and the data in one file), you can still read the header from a header-only file though. 5 | 6 | [![build status](https://secure.travis-ci.org/scijs/nifti-js.png)](http://travis-ci.org/scijs/nifti-js) 7 | 8 | Note that the output is compatible with what is output by [nrrd-js](https://github.com/jaspervdg/nrrd-js). The main area where this breaks down (so far) is encoding orientation information. The NIfTI file format allows for two different transformations in one file (with various options for specifying the transformations). In contrast, the NRRD format only supports a very simple scheme whereby the "data axes" can be mapped to basis vectors in the physical space (in addition to another scheme more or less corresponding to "method 1" in the NIfTI format). Currently, nifti-js simply discards one type of transformation (the sform transformation). 9 | 10 | Also note that for now quite a lot of what is read from the NIfTI file is discarded. The idea is that eventually most of what is now discarded should be mapped to NRRD attributes as much as possible, with the rest mapping to key/value pairs (for example). 11 | 12 | ## Example 13 | To use with [ndarray](https://github.com/mikolalysenko/ndarray), proceed as follows: 14 | 15 | ```javascript 16 | var file = nifti.parse(...); 17 | var array = ndarray(file.data, file.sizes.slice().reverse()); 18 | ``` 19 | 20 | ## Install 21 | Install using [npm](https://www.npmjs.com/): 22 | 23 | npm install nifti-js 24 | 25 | ## API 26 | 27 | #### `require("nifti-js").parse(buffer)` 28 | Parses header and data from `buffer`, returning an object with NRRD-compatible properties. Expects an `ArrayBuffer` or `Buffer` object. 29 | 30 | #### `require("nifti-js").parseHeader(buffer)` 31 | Parses just the header from `buffer`, returning an object with NRRD-compatible properties. Expects an `ArrayBuffer` or `Buffer` object. 32 | 33 | #### `require("nifti-js").parseNIfTIHeader(buffer)` 34 | Parses just the header from `buffer`, returning an object with the raw NIfTI information. Expects an `ArrayBuffer` or `Buffer` object. 35 | 36 | ## License 37 | (c) 2015 Jasper van de Gronde. MIT License 38 | -------------------------------------------------------------------------------- /nifti.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | var assert = require('assert') 3 | 4 | var systemEndianness = (function() { 5 | var buf = new ArrayBuffer(4), 6 | intArr = new Uint32Array(buf), 7 | byteArr = new Uint8Array(buf) 8 | intArr[0] = 0x01020304 9 | if (byteArr[0]==1 && byteArr[1]==2 && byteArr[2]==3 && byteArr[3]==4) { 10 | return 'big' 11 | } else if (byteArr[0]==4 && byteArr[1]==3 && byteArr[2]==2 && byteArr[3]==1) { 12 | return 'little' 13 | } 14 | console.warn("Unrecognized system endianness!") 15 | return undefined 16 | })() 17 | 18 | // Parses a NIfTI header 19 | module.exports.parseNIfTIHeader = parseNIfTIHeader 20 | function parseNIfTIHeader(buffer_org) { 21 | var buf8 = new Uint8Array(buffer_org) 22 | var buffer = buf8.buffer // Make sure we have an ArrayBuffer 23 | var view = new DataView(buffer) 24 | if (buffer.byteLength<348) { 25 | throw new Error("The buffer is not even large enough to contain the minimal header I would expect from a NIfTI file!") 26 | } 27 | 28 | // First read dim[0], to determine byte order 29 | var littleEndian = true 30 | var dim = new Array(8) 31 | dim[0] = view.getInt16(40, littleEndian) 32 | if (1>dim[0] || dim[0]>7) { 33 | littleEndian = !littleEndian 34 | dim[0] = view.getInt16(40, littleEndian) 35 | } 36 | if (1>dim[0] || dim[0]>7) { 37 | // Even if there were other /byte/ orders, we wouldn't be able to detect them using a short (16 bits, so only two bytes). 38 | console.warn("dim[0] is out-of-range, we'll simply try continuing to read the file, but this will most likely fail horribly.") 39 | } 40 | 41 | // Now check header size and magic 42 | var sizeof_hdr = view.getInt32(0, littleEndian) 43 | if (sizeof_hdr !== 348 && (1>dim[0] || dim[0]>7)) { 44 | // Try to recover from weird dim info 45 | littleEndian = !littleEndian 46 | dim[0] = view.getInt16(40, littleEndian) 47 | sizeof_hdr = view.getInt32(0, littleEndian) 48 | if (sizeof_hdr !== 348) { 49 | throw new Error("I'm sorry, but I really cannot determine the byte order of the (NIfTI) file at all.") 50 | } 51 | } else if (sizeof_hdr < 348) { 52 | throw new Error("Header of file is smaller than expected, I cannot deal with this.") 53 | } else if (sizeof_hdr !== 348) { 54 | console.warn("Size of NIfTI header different from what I expect, but I'll try to do my best anyway (might cause trouble).") 55 | } 56 | var magic = String.fromCharCode.apply(null, buf8.subarray(344, 348)) 57 | if (magic !== "ni1\0" && magic !== "n+1\0") { 58 | throw new Error("Sorry, but this does not appear to be a NIfTI-1 file. Maybe Analyze 7.5 format? or NIfTI-2?") 59 | } 60 | 61 | // Read some more structured header fields 62 | var dim_info = view.getInt8(39) 63 | dim.length = 1+Math.min(7, dim[0]) 64 | for(var i=1; i 0) { // "method 2" 166 | // TODO: Figure out exactly what to do with the different qform codes. 167 | ret.space = "right-anterior-superior" // Any method for orientation (except for "method 1") uses this, apparently. 168 | var qfac = niftiHeader.pixdim[0] === 0.0 ? 1 : niftiHeader.pixdim[0] 169 | var a = Math.sqrt(Math.max(0.0,1.0-(niftiHeader.quatern_b*niftiHeader.quatern_b + niftiHeader.quatern_c*niftiHeader.quatern_c + niftiHeader.quatern_d*niftiHeader.quatern_d))) 170 | var b = niftiHeader.quatern_b 171 | var c = niftiHeader.quatern_c 172 | var d = niftiHeader.quatern_d 173 | ret.spaceDirections = [ 174 | [niftiHeader.pixdim[1]*(a*a+b*b-c*c-d*d),niftiHeader.pixdim[1]*(2*b*c+2*a*d),niftiHeader.pixdim[1]*(2*b*d-2*a*c)], 175 | [niftiHeader.pixdim[2]*(2*b*c-2*a*d),niftiHeader.pixdim[2]*(a*a+c*c-b*b-d*d),niftiHeader.pixdim[2]*(2*c*d+2*a*b)], 176 | [qfac*niftiHeader.pixdim[3]*(2*b*d+2*a*c),qfac*niftiHeader.pixdim[3]*(2*c*d-2*a*b),qfac*niftiHeader.pixdim[3]*(a*a+d*d-c*c-b*b)]] 177 | ret.spaceOrigin = [niftiHeader.qoffset_x,niftiHeader.qoffset_y,niftiHeader.qoffset_z] 178 | } else { 179 | console.warn("Invalid qform_code: " + niftiHeader.qform_code + ", orientation is probably messed up.") 180 | } 181 | // TODO: Here we run into trouble, because in NRRD we cannot expose two DIFFERENT (not complementary, different!) transformations. Even more frustrating is that sform transformations are actually more compatible with NRRD than the qform methods. 182 | if (niftiHeader.sform_code > 0) { 183 | console.warn("sform transformation are currently ignored.") 184 | } 185 | /*if (niftiHeader.sform_code > 0) { // "method 3" 186 | ret.space = "right-anterior-superior" // Any method for orientation (except for "method 1") uses this, apparently. 187 | ret.spaceDirections = [ 188 | [niftiHeader.srow[0*4 + 0],niftiHeader.srow[1*4 + 0],niftiHeader.srow[2*4 + 0]], 189 | [niftiHeader.srow[0*4 + 1],niftiHeader.srow[1*4 + 1],niftiHeader.srow[2*4 + 1]], 190 | [niftiHeader.srow[0*4 + 2],niftiHeader.srow[1*4 + 2],niftiHeader.srow[2*4 + 2]]] 191 | ret.spaceOrigin = [niftiHeader.srow[0*4 + 3],niftiHeader.srow[1*4 + 3],niftiHeader.srow[2*4 + 3]] 192 | }*/ 193 | // TODO: Enforce that spaceDirections and so on have the correct size. 194 | 195 | // TODO: We're still missing an awful lot of info! 196 | 197 | return ret 198 | } 199 | 200 | // Just parses the header 201 | // This expects an ArrayBuffer or (Node.js) Buffer 202 | module.exports.parseHeader = function (buffer_org) { 203 | var niftiHeader = parseNIfTIHeader(buffer_org) 204 | var ret = NIfTIToNRRD(niftiHeader) 205 | return ret 206 | } 207 | 208 | // Parses both header and data 209 | // This expects an ArrayBuffer or (Node.js) Buffer 210 | module.exports.parse = function (buffer_org) { 211 | var niftiHeader = parseNIfTIHeader(buffer_org) 212 | var ret = NIfTIToNRRD(niftiHeader) 213 | 214 | if (niftiHeader.extension[0] !== 0) { 215 | console.warn("Looks like there are extensions in use in this NIfTI file, which will all be ignored!") 216 | } 217 | 218 | // Read data if it is here 219 | if (niftiHeader.magic === "n+1\0") { 220 | var buf8 = new Uint8Array(buffer_org) 221 | var buffer = buf8.buffer // Make sure we have an ArrayBuffer 222 | if (niftiHeader.vox_offset<352 || niftiHeader.vox_offset>buffer.byteLength) { 223 | throw new Error("Illegal vox_offset!") 224 | } 225 | ret.buffer = buffer.slice(Math.floor(niftiHeader.vox_offset)) 226 | if (niftiHeader.datatype !== 0) { 227 | // TODO: It MIGHT make sense to equate DT_UNKNOWN (0) to 'block', with bitpix giving the block size in bits 228 | ret.data = parseNIfTIRawData(ret.buffer, niftiHeader.datatype, niftiHeader.dim, {endianFlag: niftiHeader.littleEndian}) 229 | } 230 | } 231 | 232 | return ret 233 | } 234 | 235 | function parseNIfTIRawData(buffer, type, dim, options) { 236 | var i, arr, view, totalLen = 1, endianFlag = options.endianFlag, endianness = endianFlag ? 'little' : 'big' 237 | for(i=1; i