diff options
Diffstat (limited to 'assets/npyjs.js')
| -rw-r--r-- | assets/npyjs.js | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/assets/npyjs.js b/assets/npyjs.js new file mode 100644 index 0000000..b474575 --- /dev/null +++ b/assets/npyjs.js @@ -0,0 +1,176 @@ +class npyjs { + + constructor(opts) { + if (opts && !('convertFloat16' in opts)) { + console.warn([ + "npyjs constructor now accepts {convertFloat16?: boolean}.", + "For usage, go to https://github.com/jhuapl-boss/npyjs." + ].join(" ")); + } + + this.convertFloat16 = opts?.convertFloat16 ?? true; + + this.dtypes = { + "<u1": { + name: "uint8", + size: 8, + arrayConstructor: Uint8Array, + }, + "|u1": { + name: "uint8", + size: 8, + arrayConstructor: Uint8Array, + }, + "<u2": { + name: "uint16", + size: 16, + arrayConstructor: Uint16Array, + }, + "|i1": { + name: "int8", + size: 8, + arrayConstructor: Int8Array, + }, + "<i2": { + name: "int16", + size: 16, + arrayConstructor: Int16Array, + }, + "<u4": { + name: "uint32", + size: 32, + arrayConstructor: Uint32Array, + }, + "<i4": { + name: "int32", + size: 32, + arrayConstructor: Int32Array, + }, + "<u8": { + name: "uint64", + size: 64, + arrayConstructor: BigUint64Array, + }, + "<i8": { + name: "int64", + size: 64, + arrayConstructor: BigInt64Array, + }, + "<f4": { + name: "float32", + size: 32, + arrayConstructor: Float32Array + }, + "<f8": { + name: "float64", + size: 64, + arrayConstructor: Float64Array + }, + "<f2": { + name: "float16", + size: 16, + arrayConstructor: Uint16Array, + converter: this.convertFloat16 ? this.float16ToFloat32Array : undefined + }, + }; + } + + float16ToFloat32Array(float16Array) { + const length = float16Array.length; + const float32Array = new Float32Array(length); + + for (let i = 0; i < length; i++) { + float32Array[i] = npyjs.float16ToFloat32(float16Array[i]); + } + + return float32Array; + } + + static float16ToFloat32(float16) { + // Extract the parts of the float16 + const sign = (float16 >> 15) & 0x1; + const exponent = (float16 >> 10) & 0x1f; + const fraction = float16 & 0x3ff; + + // Handle special cases + if (exponent === 0) { + if (fraction === 0) { + // Zero + return sign ? -0 : 0; + } + // Denormalized number + return (sign ? -1 : 1) * Math.pow(2, -14) * (fraction / 0x400); + } else if (exponent === 0x1f) { + if (fraction === 0) { + // Infinity + return sign ? -Infinity : Infinity; + } + // NaN + return NaN; + } + + // Normalized number + return (sign ? -1 : 1) * Math.pow(2, exponent - 15) * (1 + fraction / 0x400); + } + + parse(arrayBufferContents) { + // const version = arrayBufferContents.slice(6, 8); // Uint8-encoded + const headerLength = new DataView(arrayBufferContents.slice(8, 10)).getUint8(0); + const offsetBytes = 10 + headerLength; + + const hcontents = new TextDecoder("utf-8").decode( + new Uint8Array(arrayBufferContents.slice(10, 10 + headerLength)) + ); + const header = JSON.parse( + hcontents + .toLowerCase() // True -> true + .replace(/'/g, '"') + .replace("(", "[") + .replace(/,*\),*/g, "]") + ); + const shape = header.shape; + const dtype = this.dtypes[header.descr]; + + if (!dtype) { + console.error(`Unsupported dtype: ${header.descr}`); + return null; + } + + const nums = new dtype.arrayConstructor( + arrayBufferContents, + offsetBytes + ); + + // Convert float16 to float32 if converter exists + const data = dtype.converter ? dtype.converter.call(this, nums) : nums; + + return { + dtype: dtype.name, + data: data, + shape, + fortranOrder: header.fortran_order + }; + } + + async load(filename, callback, fetchArgs) { + /* + Loads an array from a stream of bytes. + */ + fetchArgs = fetchArgs || {}; + let arrayBuf; + // If filename is ArrayBuffer + if (filename instanceof ArrayBuffer) { + arrayBuf = filename; + } + // If filename is a file path + else { + const resp = await fetch(filename, { ...fetchArgs }); + arrayBuf = await resp.arrayBuffer(); + } + const result = this.parse(arrayBuf); + if (callback) { + return callback(result); + } + return result; + } +} |
