May 2023
class DataViewReader { constructor(dataViewOrBuffer) { if (dataViewOrBuffer instanceof DataView) { this.dataView = dataViewOrBuffer; } else if (dataViewOrBuffer instanceof ArrayBuffer) { this.dataView = new DataView(dataViewOrBuffer); } this.offset = 0; } /* Variable length accessors */ readBytes(length) { const buffer = new DataView(this.dataView.buffer, this.offset, length) this.offset += length; return buffer; } readAndASCIIDecodeBytes(length) { const array = new Uint8Array(this.dataView.buffer, this.offset, length) this.offset += length; return this._decodeASCIIByteArray(array); } /* Fixed length accessors */ readUint8(littleEndian = false) { const value = this.dataView.getUint8(this.offset, littleEndian); this.offset += Uint8Array.BYTES_PER_ELEMENT; return value; } readUint16(littleEndian = false) { const value = this.dataView.getUint16(this.offset, littleEndian); this.offset += Uint16Array.BYTES_PER_ELEMENT; return value; } readUint32(littleEndian = false) { const value = this.dataView.getUint32(this.offset, littleEndian); this.offset += Uint32Array.BYTES_PER_ELEMENT; return value; } /* Helpers */ _decodeASCIIByteArray(array) { const characters = [] for (const byte of array) { const char = String.fromCharCode(byte); characters.push(char); } return characters.join(''); } } function fromArrayBuffer(buffer) { if (!buffer instanceof ArrayBuffer) { throw new Error('Argument must be an ArrayBuffer.'); } const reader = new DataViewReader(buffer); // comments are taken from https://docs.scipy.org/doc/numpy-1.14.1/neps/npy-format.html#format-specification-version-1-0 // "The first 6 bytes are a magic string: exactly "x93NUMPY"" const magicByte = reader.readUint8(); const magicWord = reader.readAndASCIIDecodeBytes(5); if (magicByte != 0x93 || magicWord != 'NUMPY') { throw new Error(`unknown file type: "${magicByte}${magicWord}"`); } // "The next 1 byte is an unsigned byte: the major version number of the file format, e.g. x01."" const versionMajor = reader.readUint8(); // "The next 1 byte is an unsigned byte: the minor version number of the file format, e.g. x00." const versionMinor = reader.readUint8(); // Parse header length. This depends on the major file format version as follows: let headerLength; if (versionMajor <= 1) { // "The next 2 bytes form a little-endian unsigned short int: the length of the header data HEADER_LEN." headerLength = reader.readUint16(true); } else { // "The next 4 bytes form a little-endian unsigned int: the length of the header data HEADER_LEN." headerLength = reader.readUint32(true); } /* "The next HEADER_LEN bytes form the header data describing the array’s format. It is an ASCII string which contains a Python literal expression of a dictionary. It is terminated by a newline (‘n’) and padded with spaces (‘x20’) to make the total length of the magic string + 4 + HEADER_LEN be evenly divisible by 16." */ const preludeLength = 6 + 4 + headerLength; if (preludeLength % 16 != 0) { console.warn(`NPY file header is incorrectly padded. (${preludeLength} is not evenly divisible by 16.)`) } const headerStr = reader.readAndASCIIDecodeBytes(headerLength); const header = parseHeaderStr(headerStr); if (header.fortran_order) { throw new Error('NPY file is written in Fortran byte order, support for this byte order is not yet implemented.') } // Intepret the bytes according to the specified dtype const constructor = typedArrayConstructorForDescription(header.descr); const data = new constructor(buffer, reader.offset); // Return object with same signature as NDArray expects: {data, shape} return { data: data, shape: header.shape }; } function parseHeaderStr(headerStr) { const jsonHeader = headerStr .toLowerCase() // boolean literals: False -> false .replace('(','[').replace('),',']') // Python tuple to JS array: (10,) -> [10,] .replace('[,','[1,]').replace(',]',',1]') // implicit dimensions: [10,] -> [10,1] .replace(/'/g, '"'); // single quotes -> double quotes return JSON.parse(jsonHeader); } function typedArrayConstructorForDescription(dtypeDescription) { /* 'dtype' description strings consist of three characters, indicating one of three properties each: byte order, data type, and byte length. Byte order: '<' (little-endian), '>' (big-endian), or '|' (not applicable) Data type: 'u' (unsigned), 'i' (signed integer), 'f' (floating) Byte Length: 1, 2, 4 or 8 bytes Note that for 1 byte dtypes there is no byte order, thus the use of '|' (not applicable). Data types are specified in numpy source: https://github.com/numpy/numpy/blob/8aa121415760cc6839a546c3f84e238d1dfa1aa6/numpy/core/_dtype.py#L13 */ switch (dtypeDescription) { // Unsigned Integers case '|u1': return Uint8Array; case ' { // console.log('DATA!', imageData.njArray); // displayImage('myCanvas', imageData.njArray); // }) // .catch((error) => console.error('Error loading the TIFF image:', error)); async function loadNumpyArray(url) { const response = await fetch(url); const arrayBuffer = await response.arrayBuffer(); // const response = await fetch(url); // const arrayBuffer = await response.arrayBuffer(); console.log('-- 1. ') const { data, shape } = fromArrayBuffer(arrayBuffer); // Parse the ArrayBuffer into a JavaScript array // const numpyParser = new NumpyParser(); // const jsArray = numpyParser.parse(arrayBuffer); console.log('data', data, shape) return nj.array(data).reshape(shape); } async function main() { const depth = await loadNumpyArray('depth_1.npy'); console.log(depth, depth.shape, depth.selection.data[0]); let normed = depth; normed = normed.subtract(normed.min()); // normed = normed.divide(normed.max()); console.log('---', normed.max()); displayImage('myCanvas', normed); // console.log('JavaScript array shape:', jsArray.shape); // console.log('JavaScript array data:', jsArray.data); } main(); // Fetch the binary NumPy array data from a file (using the Fetch API, for example) // fetch('geltip-sim/numpy_array_bin.npy') // .then((response) => response.arrayBuffer()) // .then((arrayBuffer) => { // console.log(window); // // Convert the binary data to a JavaScript array // const jsArray = parse(arrayBuffer); // // // Use the JavaScript array as needed // console.log(jsArray); // }) // .catch((error) => console.error('Error loading the binary NumPy array:', error)); const a = nj.array([2, 3, 4]); console.log('A', a); // Create the scene and camera const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.z = 5; // Create the renderer // renderer.setSize(window.innerWidth, window.innerHeight); const renderer = THREE.WebGL1Renderer ? new THREE.WebGL1Renderer() : new THREE.WebGLRenderer; renderer.setSize(640, 480, false); const params = { format: THREE.DepthFormat, type: THREE.UnsignedShortType }; const formats = {DepthFormat: THREE.DepthFormat, DepthStencilFormat: THREE.DepthStencilFormat}; const types = { UnsignedShortType: THREE.UnsignedShortType, UnsignedIntType: THREE.UnsignedIntType, UnsignedInt248Type: THREE.UnsignedInt248Type }; const camera2 = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera2.position.z = 1; const renderer2 = THREE.WebGL1Renderer ? new THREE.WebGL1Renderer() : new THREE.WebGLRenderer; renderer2.setPixelRatio(window.devicePixelRatio); renderer2.setSize(640, 480, false); const format = parseFloat(params.format); const type = parseFloat(params.type); target = new THREE.WebGLRenderTarget(640, 480); target.texture.minFilter = THREE.NearestFilter; target.texture.magFilter = THREE.NearestFilter; target.stencilBuffer = (format === THREE.DepthStencilFormat) ? true : false; target.depthTexture = new THREE.DepthTexture(); target.depthTexture.format = format; target.depthTexture.type = type; console.log('---', type, format); const dpr = renderer2.getPixelRatio(); target.setSize(640 * dpr, 480 * dpr); document.querySelector('#geltip-demo-scene').appendChild(renderer.domElement); document.querySelector('#geltip-demo-depth').appendChild(renderer2.domElement); const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({color: 0x00ff00}); const cube = new THREE.Mesh(geometry, material); // scene.add(cube); // Load the STL file const loader = new THREE.STLLoader(); let meshObject; loader.load("assets/meshes/elastomer.stl", function (geometry) { const material = new THREE.MeshBasicMaterial({color: 'white'}); const mesh = new THREE.Mesh(geometry, material); mesh.scale.set(0.01, 0.01, 0.01); scene.add(mesh); meshObject = mesh; }); // Render the scene function animate() { requestAnimationFrame(animate); if (meshObject) { meshObject.rotation.x += 0.01; meshObject.rotation.y += 0.01; } renderer.render(scene, camera); renderer2.setRenderTarget(target); renderer2.render(scene, camera2); } animate();