index.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import fetch from 'cross-fetch';
  2. class npyjs {
  3. constructor(opts) {
  4. if (opts && !('convertFloat16' in opts)) {
  5. console.warn([
  6. "npyjs constructor now accepts {convertFloat16?: boolean}.",
  7. "For usage, go to https://github.com/jhuapl-boss/npyjs."
  8. ].join(" "));
  9. }
  10. this.convertFloat16 = opts?.convertFloat16 ?? true;
  11. this.dtypes = {
  12. "<u1": {
  13. name: "uint8",
  14. size: 8,
  15. arrayConstructor: Uint8Array,
  16. },
  17. "|u1": {
  18. name: "uint8",
  19. size: 8,
  20. arrayConstructor: Uint8Array,
  21. },
  22. "<u2": {
  23. name: "uint16",
  24. size: 16,
  25. arrayConstructor: Uint16Array,
  26. },
  27. "|i1": {
  28. name: "int8",
  29. size: 8,
  30. arrayConstructor: Int8Array,
  31. },
  32. "<i2": {
  33. name: "int16",
  34. size: 16,
  35. arrayConstructor: Int16Array,
  36. },
  37. "<u4": {
  38. name: "uint32",
  39. size: 32,
  40. arrayConstructor: Uint32Array,
  41. },
  42. "<i4": {
  43. name: "int32",
  44. size: 32,
  45. arrayConstructor: Int32Array,
  46. },
  47. "<u8": {
  48. name: "uint64",
  49. size: 64,
  50. arrayConstructor: BigUint64Array,
  51. },
  52. "<i8": {
  53. name: "int64",
  54. size: 64,
  55. arrayConstructor: BigInt64Array,
  56. },
  57. "<f4": {
  58. name: "float32",
  59. size: 32,
  60. arrayConstructor: Float32Array
  61. },
  62. "<f8": {
  63. name: "float64",
  64. size: 64,
  65. arrayConstructor: Float64Array
  66. },
  67. "<f2": {
  68. name: "float16",
  69. size: 16,
  70. arrayConstructor: Uint16Array,
  71. converter: this.convertFloat16 ? this.float16ToFloat32Array : undefined
  72. },
  73. };
  74. }
  75. float16ToFloat32Array(float16Array) {
  76. const length = float16Array.length;
  77. const float32Array = new Float32Array(length);
  78. for (let i = 0; i < length; i++) {
  79. float32Array[i] = npyjs.float16ToFloat32(float16Array[i]);
  80. }
  81. return float32Array;
  82. }
  83. static float16ToFloat32(float16) {
  84. // Extract the parts of the float16
  85. const sign = (float16 >> 15) & 0x1;
  86. const exponent = (float16 >> 10) & 0x1f;
  87. const fraction = float16 & 0x3ff;
  88. // Handle special cases
  89. if (exponent === 0) {
  90. if (fraction === 0) {
  91. // Zero
  92. return sign ? -0 : 0;
  93. }
  94. // Denormalized number
  95. return (sign ? -1 : 1) * Math.pow(2, -14) * (fraction / 0x400);
  96. } else if (exponent === 0x1f) {
  97. if (fraction === 0) {
  98. // Infinity
  99. return sign ? -Infinity : Infinity;
  100. }
  101. // NaN
  102. return NaN;
  103. }
  104. // Normalized number
  105. return (sign ? -1 : 1) * Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
  106. }
  107. parse(arrayBufferContents) {
  108. // const version = arrayBufferContents.slice(6, 8); // Uint8-encoded
  109. const headerLength = new DataView(arrayBufferContents.slice(8, 10)).getUint8(0);
  110. const offsetBytes = 10 + headerLength;
  111. const hcontents = new TextDecoder("utf-8").decode(
  112. new Uint8Array(arrayBufferContents.slice(10, 10 + headerLength))
  113. );
  114. const header = JSON.parse(
  115. hcontents
  116. .toLowerCase() // True -> true
  117. .replace(/'/g, '"')
  118. .replace("(", "[")
  119. .replace(/,*\),*/g, "]")
  120. );
  121. const shape = header.shape;
  122. const dtype = this.dtypes[header.descr];
  123. if (!dtype) {
  124. console.error(`Unsupported dtype: ${header.descr}`);
  125. return null;
  126. }
  127. const nums = new dtype.arrayConstructor(
  128. arrayBufferContents,
  129. offsetBytes
  130. );
  131. // Convert float16 to float32 if converter exists
  132. const data = dtype.converter ? dtype.converter.call(this, nums) : nums;
  133. return {
  134. dtype: dtype.name,
  135. data: data,
  136. shape,
  137. fortranOrder: header.fortran_order
  138. };
  139. }
  140. async load(filename, callback, fetchArgs) {
  141. /*
  142. Loads an array from a stream of bytes.
  143. */
  144. fetchArgs = fetchArgs || {};
  145. let arrayBuf;
  146. // If filename is ArrayBuffer
  147. if (filename instanceof ArrayBuffer) {
  148. arrayBuf = filename;
  149. }
  150. // If filename is a file path
  151. else {
  152. const resp = await fetch(filename, { ...fetchArgs });
  153. arrayBuf = await resp.arrayBuffer();
  154. }
  155. const result = this.parse(arrayBuf);
  156. if (callback) {
  157. return callback(result);
  158. }
  159. return result;
  160. }
  161. }
  162. export default npyjs;