cabana/src/utils/loadnpy.js

135 lines
4.0 KiB
JavaScript

// Client-side parser for .npy files
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
const NumpyLoader = (function NumpyLoader() {
function asciiDecode(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function readUint16LE(buffer) {
const view = new DataView(buffer);
let val = view.getUint8(0);
val |= view.getUint8(1) << 8;
return val;
}
function fromArrayBuffer(buf) {
// Check the magic number
const magic = asciiDecode(buf.slice(0, 6));
if (magic !== '\x93NUMPY') {
throw new Error('Bad magic number');
}
const version = new Uint8Array(buf.slice(6, 8));
const headerLength = readUint16LE(buf.slice(8, 10));
const headerStr = asciiDecode(buf.slice(10, 10 + headerLength));
const offsetBytes = 10 + headerLength;
// rest = buf.slice(10+headerLength); XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5
// Hacky conversion of dict literal string to JS Object
const info = JSON.parse(
headerStr
.toLowerCase()
.replace('(', '[')
.replace('),', ']')
.replace(/'/g, '"')
.replace(',]', ']')
);
// Intepret the bytes according to the specified dtype
let data;
if (info.descr === '|u1') {
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '|i1') {
data = new Int8Array(buf, offsetBytes);
} else if (info.descr === '<u2') {
data = new Uint16Array(buf, offsetBytes);
} else if (info.descr === '<i2') {
data = new Int16Array(buf, offsetBytes);
} else if (info.descr === '<u4') {
data = new Uint32Array(buf, offsetBytes);
} else if (info.descr === '<i4') {
data = new Int32Array(buf, offsetBytes);
} else if (info.descr === '<f4') {
data = new Float32Array(buf, offsetBytes);
} else if (info.descr === '<f8') {
data = new Float64Array(buf, offsetBytes);
} else if (info.descr === '<u8') {
// 8 byte uint64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '<i8') {
// 8 byte int64s
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '|s5') {
// 5 byte string
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '|s8') {
// 8 byte strings
data = new Uint8Array(buf, offsetBytes);
} else if (info.descr === '|s15') {
// 15 byte strings?
data = new Uint8Array(buf, offsetBytes);
} else {
throw new Error(`unknown numeric dtype ${info.descr}`);
}
return {
shape: info.shape,
fortran_order: info.fortran_order,
data
};
}
function open(file, callback) {
const reader = new FileReader();
reader.onload = function () {
// the file contents have been read as an array buffer
const buf = reader.result;
const ndarray = fromArrayBuffer(buf);
callback(ndarray);
};
reader.readAsArrayBuffer(file);
}
function promise(url) {
return new Promise((resolve, reject) => {
// Do the usual XHR stuff
const req = new XMLHttpRequest();
req.onload = function () {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
const buf = req.response; // not responseText
const ndarray = fromArrayBuffer(buf);
resolve(ndarray);
} else if (req.status == 404) {
console.log('yup');
reject({ is404: true });
} else {
// Otherwise reject with the status text
// which will hopefully be a meaningful error
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function () {
reject(Error('Network Error'));
};
// Make the request
req.open('GET', url, true);
req.responseType = 'arraybuffer';
req.send(null);
});
}
return {
open,
promise,
fromArrayBuffer
};
}());
module.exports = NumpyLoader;