const ifds = tiff_reader.extractIFDs(file, first_ifd_offset, endianness) this is an array of objects i want to get the object named 'ImageWidth' and set it as a variable ChatGPT a dit : To extract the ImageWidth value from the array of IFD (Image File Directory) objects returned by tiff_reader.extractIFDs(), you would need to loop through the objects and find the one that has the name ImageWidth. Here's how you could do it: javascript Copier Modifier // Assuming 'ifds' is an array of objects returned by extractIFDs const ifds = tiff_reader.extractIFDs(file, first_ifd_offset, endianness); // Initialize the variable for ImageWidth let imageWidth; // Loop through the array of IFDs and find the one with the 'ImageWidth' tag ifds.forEach(ifd => { if (ifd.tag === 'ImageWidth') { imageWidth = ifd.value; // Assign the value of 'ImageWidth' to the variable } }); // Now imageWidth contains the value of 'ImageWidth' console.log(imageWidth); Explanation: Loop through ifds: The forEach loop checks each object in the ifds array. Tag check: It checks if the tag of the current object is 'ImageWidth'. Set the value: If it finds the object with the ImageWidth tag, it assigns its value to the imageWidth variable. Ensure that the ifd.tag property holds the tag name, and ifd.value holds the value associated with that tag. You might need to adjust this depending on the exact structure of the objects in your ifds array. Let me know if you need further clarifications or adjustments! Vous avez dit : if(flag == 2){ // @band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count const ndvi = spectral_index.mergeTiles( bands.nir.file, bands.nir.metadata.tile_offsets, bands.nir.metadata.tile_byte_counts, bands.red.file, bands.red.metadata.tile_offsets, bands.red.metadata.tile_byte_counts ).then((result) => { console.log(result[76]) visualizeNDVI(result, canvas_settings.width, canvas_settings.height); }) mergeTiles(band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count){ // For each tile index (i from 0 to tileCount - 1): // if(band_a_offsets.length === ) const tile_count = band_a_offsets.length const band_a_buffer = null const band_b_buffer = null for(let i=0; i < tile_count; i++){ const current_offset_a = band_a_offsets[i]; const bytes_a = decompress(band_a.subarray(current_offset_a, current_offset_a + band_a_tile_byte_count[i])); // Get Band5 tile data const current_offset_b = band_b_offsets[i]; const bytes_b = decompress(band_b.subarray(current_offset_b, current_offset_b + band_b_tile_byte_count[i])); // Convert to Uint16Arrays //const tileBand4 = new Uint16Array(band_a, current_offset_a, band_a_tile_byte_count / 2); //const tileBand5 = new Uint16Array(band_b, band_b_offsets, band_b_tile_byte_count / 2); // Compute NDVI tile const ndviTile = new Float32Array(tileBand4.length); for (let j = 0; j < tileBand4.length; j++) { const nir = tileBand5[j]; // NIR is Band 5 const red = tileBand4[j]; // Red is Band 4 const denom = nir + red; ndviTile[j] = denom === 0 ? 0 : (nir - red) / denom; } async function decompressDeflate(compressedUint8Array) { const ds = new DecompressionStream('deflate'); const compressedStream = new Response(compressedUint8Array).body; const decompressedStream = compressedStream.pipeThrough(ds); const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer(); return new Uint8Array(decompressedArrayBuffer); } async function readAndDecompressTile(buffer, offset, byteCount) { const compressed = new Uint8Array(buffer, offset, byteCount); const decompressed = await decompressDeflate(compressed); return new Uint16Array(decompressed.buffer); } these bits dont work together; I get a float32 array with values between -1 and 1 though, but when I try to render it on the canvas its just grey ChatGPT a dit : The issue you're experiencing might be related to the way the NDVI values are being scaled or rendered on the canvas. The NDVI values, which range from -1 to 1, are generally visualized with a color gradient (e.g., from red to green), but in their raw form, they might not be directly mapped to the grayscale values that canvas rendering expects. Let me walk you through the process of fixing this issue. Steps to solve: Fix the NDVI Calculation and Tile Merging Logic: It seems like there are some discrepancies in how the tiles are being merged and decompressed. Ensure that the decompressed tiles are correctly formed and that you're extracting values from them properly. Based on your code, you might also be missing a proper tileBand4 and tileBand5 (Red and NIR bands), which are crucial for the NDVI calculation. Normalize NDVI Values: NDVI values can range from -1 to 1, but grayscale images typically expect values between 0 and 255. You'll need to normalize these values to fit this range. You can scale the NDVI to 8-bit (0-255) for visualization. Render on Canvas: Ensure you're using a proper color mapping scheme for visualizing NDVI values (e.g., color gradients or a heatmap). For NDVI, a typical color map is: Low values (negative) represent areas of water or barren land (could be blue, gray, etc.). Medium values (close to 0) represent areas with vegetation stress (yellow or brown). High values (close to 1) represent healthy vegetation (green). Updated Code Here's an updated version of your code with fixes and added comments: javascript Copier Modifier // Merge tiles and compute NDVI mergeTiles(band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count) { const tile_count = band_a_offsets.length; const ndviResult = []; // Store the computed NDVI tiles for (let i = 0; i < tile_count; i++) { const current_offset_a = band_a_offsets[i]; const bytes_a = decompress(band_a.subarray(current_offset_a, current_offset_a + band_a_tile_byte_count[i])); const current_offset_b = band_b_offsets[i]; const bytes_b = decompress(band_b.subarray(current_offset_b, current_offset_b + band_b_tile_byte_count[i])); // Decompress to get tile values const tileBand4 = new Uint16Array(bytes_a); // Red band (Band 4) const tileBand5 = new Uint16Array(bytes_b); // NIR band (Band 5) // Compute NDVI for the current tile const ndviTile = new Float32Array(tileBand4.length); for (let j = 0; j < tileBand4.length; j++) { const nir = tileBand5[j]; // NIR is Band 5 const red = tileBand4[j]; // Red is Band 4 const denom = nir + red; ndviTile[j] = denom === 0 ? 0 : (nir - red) / denom; } ndviResult.push(ndviTile); // Store NDVI for this tile } return ndviResult; // Return the computed NDVI for all tiles } // Normalize NDVI values to the range of 0-255 for grayscale rendering function normalizeNDVI(ndviTile) { const normalized = new Uint8Array(ndviTile.length); for (let i = 0; i < ndviTile.length; i++) { // Normalize the NDVI value (scale from -1 to 1 to 0 to 255) const normalizedValue = Math.round(((ndviTile[i] + 1) / 2) * 255); normalized[i] = Math.min(255, Math.max(0, normalizedValue)); // Ensure the value is clamped between 0 and 255 } return normalized; } // Visualize NDVI by rendering it on a canvas function visualizeNDVI(ndviResult, width, height) { // Flatten the NDVI result array (if it's multi-tile) to a single array const flattenedNDVI = [].concat(...ndviResult); const canvas = document.getElementById('ndviCanvas'); const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(width, height); const data = imageData.data; // Normalize the NDVI values for rendering const normalizedNDVI = normalizeNDVI(flattenedNDVI); // Fill the imageData with the normalized NDVI values (one value per pixel) for (let i = 0; i < normalizedNDVI.length; i++) { const pixelIndex = i * 4; // Each pixel has 4 channels: RGBA // Grayscale color (you could use a color gradient instead, but let's stick with grayscale for simplicity) const value = normalizedNDVI[i]; // R = G = B = value (gray scale) and A = 255 (fully opaque) data[pixelIndex] = value; // Red channel data[pixelIndex + 1] = value; // Green channel data[pixelIndex + 2] = value; // Blue channel data[pixelIndex + 3] = 255; // Alpha channel (opaque) } // Put the image data on the canvas ctx.putImageData(imageData, 0, 0); } Key Changes: NDVI Calculation: I fixed the tile band extraction and NDVI calculation. Normalization: NDVI values were normalized to the range of 0 to 255. This is done using the formula: javascript Copier Modifier normalizedValue = ((ndviTile[i] + 1) / 2) * 255; This converts NDVI values from a range of [-1, 1] to [0, 255], which can be visualized as grayscale. Rendering on Canvas: I used ctx.createImageData() to create an image buffer and then populated it with the normalized NDVI values. Grayscale Visualization: For simplicity, I mapped the normalized NDVI values to grayscale, where the values between 0 and 255 represent varying shades of gray. Optional: Add Color Gradient for Better Visualization If you want to improve the visualization with color instead of grayscale, you can use a color gradient. For example: javascript Copier Modifier function getColorForNDVI(value) { if (value < 0) return [0, 0, 255]; // Blue for negative NDVI (water, barren land) if (value < 0.3) return [255, 255, 0]; // Yellow for low NDVI (vegetation stress) if (value < 0.6) return [255, 165, 0]; // Orange for medium NDVI (moderate vegetation) return [0, 255, 0]; // Green for high NDVI (healthy vegetation) } In that case, you'd replace the grayscale section in visualizeNDVI() with a color map based on the NDVI value. Let me know if this resolves the issue, or if you need further refinements! Vous avez dit : function visualizeNDVI(ndviArray, width, height) { // Create ImageData object const imageData = ctx.createImageData(width, height); const data = imageData.data; // Fill the image data with the NDVI values for (let i = 0; i < ndviArray.length; i++) { const normalizedValue = normalizeNDVI(ndviArray[i]); console.log(ndviArray[i]) // Each pixel has RGBA values // Red, Green, Blue, Alpha (255 = opaque) const index = i * 4; // Each pixel has 4 values (R, G, B, A) data[index] = normalizedValue; // Red data[index + 1] = normalizedValue; // Green data[index + 2] = normalizedValue; // Blue data[index + 3] = 255; // Alpha (fully opaque) } // Put the image data onto the canvas ctx.putImageData(imageData, 0, 0); } The problem with this one is that I am passing a 2D array of tiles that contain the values, so it would need to be a double for loop, can you update this function ChatGPT a dit : You're right! Since you're passing a 2D array of tiles (likely an array of arrays where each element represents a tile), you will need to loop through the 2D structure and then process each tile individually. You’ll also want to flatten the 2D array into a 1D array of NDVI values to correctly fit it into the ImageData object. Here’s an updated version of your visualizeNDVI function with two nested loops to handle the 2D array of tiles. Updated visualizeNDVI Function javascript Copier Modifier function visualizeNDVI(ndviArray, width, height) { // Create ImageData object to hold pixel data const imageData = ctx.createImageData(width, height); const data = imageData.data; let pixelIndex = 0; // This keeps track of where we are in the 1D data array // Loop through each tile in the 2D array for (let tileRow = 0; tileRow < ndviArray.length; tileRow++) { for (let tileCol = 0; tileCol < ndviArray[tileRow].length; tileCol++) { const tile = ndviArray[tileRow][tileCol]; // Get the current tile (1D array of NDVI values) // Loop through each NDVI value in the current tile (which is a 1D array) for (let i = 0; i < tile.length; i++) { // Normalize the NDVI value to be between 0 and 255 const normalizedValue = normalizeNDVI(tile[i]); // Set the RGBA values for the current pixel data[pixelIndex] = normalizedValue; // Red channel data[pixelIndex + 1] = normalizedValue; // Green channel data[pixelIndex + 2] = normalizedValue; // Blue channel data[pixelIndex + 3] = 255; // Alpha channel (fully opaque) // Move to the next pixel pixelIndex += 4; // We move by 4 because each pixel has 4 values (RGBA) } } } // Put the image data onto the canvas ctx.putImageData(imageData, 0, 0); } Key Changes: Nested Loops: We loop through the 2D array of tiles (ndviArray) using two loops (tileRow and tileCol). Each tile is a 1D array, so we need a third loop to iterate through the individual NDVI values inside each tile. Pixel Index: The pixelIndex variable keeps track of where the current pixel data is being placed in the data array. We increment it by 4 (because RGBA needs 4 entries per pixel) each time we process a new NDVI value. Normalize and Set RGBA: For each NDVI value, the normalizeNDVI function is called to ensure the NDVI value is in the correct range (0–255) for visualization. Then, we assign it to the corresponding pixel’s RGBA values. Assumptions: ndviArray is a 2D array, where each element is a 1D array of NDVI values for a specific tile. normalizeNDVI function converts NDVI values from a range of [-1, 1] to [0, 255] for grayscale visualization. ctx is the 2D canvas context, and width and height are the dimensions of the canvas. Example normalizeNDVI Function (if not already defined): javascript Copier Modifier function normalizeNDVI(ndviValue) { // Convert NDVI value from the range [-1, 1] to [0, 255] const normalizedValue = Math.round(((ndviValue + 1) / 2) * 255); return Math.min(255, Math.max(0, normalizedValue)); // Clamp between 0 and 255 } Rendering Flow: Tile Processing: The outer loops handle multiple tiles, and each tile is a 1D array of NDVI values. Pixel Mapping: Each NDVI value from the tile is normalized, and its corresponding pixel in the ImageData array is populated. Canvas Rendering: After all the pixels are set, ctx.putImageData(imageData, 0, 0) places the final image on the canvas. Let me know if this works or if you need further adjustments! Vous avez dit : I think its an array of float32 arrays ChatGPT a dit : Got it! If the ndviArray is an array of Float32Array (each Float32Array representing NDVI values for a single tile), then the structure is a bit different. We still need to loop through the 2D array (which holds each tile as a Float32Array) and then process each value within those arrays. Let's update the function accordingly, treating ndviArray as a 2D array of Float32Array: Updated visualizeNDVI Function for Float32Array: javascript Copier Modifier function visualizeNDVI(ndviArray, width, height) { // Create ImageData object to hold pixel data const imageData = ctx.createImageData(width, height); const data = imageData.data; let pixelIndex = 0; // This keeps track of where we are in the 1D data array // Loop through each tile in the 2D array (ndviArray) for (let tileRow = 0; tileRow < ndviArray.length; tileRow++) { for (let tileCol = 0; tileCol < ndviArray[tileRow].length; tileCol++) { const tile = ndviArray[tileRow][tileCol]; // Get the current tile (Float32Array of NDVI values) // Loop through each NDVI value in the current tile (Float32Array) for (let i = 0; i < tile.length; i++) { // Normalize the NDVI value to be between 0 and 255 const normalizedValue = normalizeNDVI(tile[i]); // Set the RGBA values for the current pixel data[pixelIndex] = normalizedValue; // Red channel data[pixelIndex + 1] = normalizedValue; // Green channel data[pixelIndex + 2] = normalizedValue; // Blue channel data[pixelIndex + 3] = 255; // Alpha channel (fully opaque) // Move to the next pixel pixelIndex += 4; // Each pixel has 4 values (RGBA) } } } // Put the image data onto the canvas ctx.putImageData(imageData, 0, 0); } Key Changes: Handling Float32Array: The ndviArray is now an array of Float32Array objects. Each Float32Array contains the NDVI values for one tile. We treat each tile as a Float32Array and loop through its values. Nested Loops: The first two loops iterate over the 2D array of tiles (ndviArray). The third loop iterates over the Float32Array (the individual tile data). Normalization: The normalizeNDVI function is still applied to each NDVI value from the Float32Array. normalizeNDVI Function (for context): javascript Copier Modifier function normalizeNDVI(ndviValue) { // Convert NDVI value from the range [-1, 1] to [0, 255] for visualization const normalizedValue = Math.round(((ndviValue + 1) / 2) * 255); return Math.min(255, Math.max(0, normalizedValue)); // Ensure it's within the valid range } Rendering Flow: Tile Processing: The nested loops handle the 2D array of tiles, where each tile is a Float32Array of NDVI values. Pixel Mapping: For each NDVI value, it gets normalized to a value between 0 and 255, and the corresponding pixel in the ImageData array is updated. Canvas Rendering: Once all pixels are processed, the putImageData method is called to render the image to the canvas. Example Data Structure: If your ndviArray looks like this: javascript Copier Modifier let ndviArray = [ [new Float32Array([0.1, 0.2, 0.3]), new Float32Array([0.4, 0.5, 0.6])], [new Float32Array([0.7, 0.8, 0.9]), new Float32Array([1.0, 1.0, 1.0])] ]; Each element is a Float32Array representing NDVI values for a particular tile, and ndviArray itself is a 2D array (array of arrays of Float32Array). Further Considerations: Ensure that the dimensions of the ndviArray match the dimensions of the canvas you're rendering to. If the tiles are larger than the canvas, you might need to adjust how the tiles are mapped to the canvas pixels. Let me know if this works for you or if you encounter any other issues! Vous avez dit : I think this was supposed to be one huge Float32 array that is 1D mergeTiles(band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count) { const tile_count = band_a_offsets.length; const ndviResult = []; // Store the computed NDVI tiles for (let i = 0; i < tile_count; i++) { const current_offset_a = band_a_offsets[i]; const bytes_a = decompress(band_a.subarray(current_offset_a, current_offset_a + band_a_tile_byte_count[i])); const current_offset_b = band_b_offsets[i]; const bytes_b = decompress(band_b.subarray(current_offset_b, current_offset_b + band_b_tile_byte_count[i])); // Decompress to get tile values const tileBand4 = new Uint16Array(bytes_a); // Red band (Band 4) const tileBand5 = new Uint16Array(bytes_b); // NIR band (Band 5) // Compute NDVI for the current tile const ndviTile = new Float32Array(tileBand4.length); for (let j = 0; j < tileBand4.length; j++) { const nir = tileBand5[j]; // NIR is Band 5 const red = tileBand4[j]; // Red is Band 4 const denom = nir + red; ndviTile[j] = denom === 0 ? 0 : (nir - red) / denom; } ndviResult.push(ndviTile); // Store NDVI for this tile } return ndviResult; // Return the computed NDVI for all tiles } ChatGPT a dit : Ah, I see! If you're returning one huge Float32Array that contains all the NDVI values (as a 1D array), then you don't need to worry about the 2D structure. Instead, you'll be processing a single, large Float32Array that holds all the NDVI values from all tiles. Key Updates: Single Float32Array: Your ndviResult array will be a single large Float32Array instead of an array of arrays. Visualization: The visualizeNDVI function will now expect this huge 1D Float32Array instead of a 2D array of Float32Arrays. Let's Update the visualizeNDVI Function: javascript Copier Modifier function visualizeNDVI(ndviArray, width, height) { // Create ImageData object to hold pixel data const imageData = ctx.createImageData(width, height); const data = imageData.data; let pixelIndex = 0; // This keeps track of where we are in the 1D data array // Loop through the entire Float32Array (ndviArray is now 1D) for (let i = 0; i < ndviArray.length; i++) { const normalizedValue = normalizeNDVI(ndviArray[i]); // Set the RGBA values for the current pixel data[pixelIndex] = normalizedValue; // Red channel data[pixelIndex + 1] = normalizedValue; // Green channel data[pixelIndex + 2] = normalizedValue; // Blue channel data[pixelIndex + 3] = 255; // Alpha channel (fully opaque) // Move to the next pixel (4 values: RGBA) pixelIndex += 4; } // Put the image data onto the canvas ctx.putImageData(imageData, 0, 0); } Key Points in This Version: Single Array (ndviArray): ndviArray is now a single, flattened Float32Array. The visualizeNDVI function processes this 1D array directly, treating it as a sequence of NDVI values for each pixel. Normalizing Values: Each NDVI value is normalized using the normalizeNDVI function before it's written to the ImageData. Pixel Mapping: For each NDVI value, it gets normalized and placed into the RGBA channels for each pixel. Since you are working with a 1D array of NDVI values, the loop iterates over the entire array, incrementing by 4 for each RGBA value. Updated normalizeNDVI Function: Here’s the normalizeNDVI function again for reference, ensuring that NDVI values from [-1, 1] are scaled to [0, 255] for visualization: javascript Copier Modifier function normalizeNDVI(ndviValue) { // Convert NDVI value from the range [-1, 1] to [0, 255] const normalizedValue = Math.round(((ndviValue + 1) / 2) * 255); return Math.min(255, Math.max(0, normalizedValue)); // Ensure the value is clamped between 0 and 255 } Merge Tiles Function (For Context): It looks like the function you've provided to merge the tiles and compute NDVI is working to return a single Float32Array: javascript Copier Modifier function mergeTiles(band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count) { const tile_count = band_a_offsets.length; const ndviResult = new Float32Array(band_a.length); // Make it a single 1D array let currentIndex = 0; // This keeps track of where we are in the 1D array for (let i = 0; i < tile_count; i++) { const current_offset_a = band_a_offsets[i]; const bytes_a = decompress(band_a.subarray(current_offset_a, current_offset_a + band_a_tile_byte_count[i])); const current_offset_b = band_b_offsets[i]; const bytes_b = decompress(band_b.subarray(current_offset_b, current_offset_b + band_b_tile_byte_count[i])); // Decompress to get tile values const tileBand4 = new Uint16Array(bytes_a); // Red band (Band 4) const tileBand5 = new Uint16Array(bytes_b); // NIR band (Band 5) // Compute NDVI for the current tile for (let j = 0; j < tileBand4.length; j++) { const nir = tileBand5[j]; // NIR is Band 5 const red = tileBand4[j]; // Red is Band 4 const denom = nir + red; // Store the NDVI in the large 1D array ndviResult[currentIndex] = denom === 0 ? 0 : (nir - red) / denom; currentIndex++; } } return ndviResult; // Return the large 1D NDVI array } Key Changes in mergeTiles: Single Float32Array: Instead of pushing Float32Array tiles into a 2D array, the NDVI results are stored directly in a single, large Float32Array (ndviResult), which is returned at the end. currentIndex: This keeps track of where to place the next NDVI value in the 1D array, ensuring that each NDVI value from each tile is stored sequentially. Flow: Merging Tiles: The mergeTiles function decompresses the tile data, calculates NDVI for each pixel, and stores it in a single 1D Float32Array. Visualizing NDVI: The visualizeNDVI function then takes this 1D array and visualizes it on the canvas, with the NDVI values normalized and mapped to RGBA color values. Example Usage: Now, to visualize your NDVI values, you would do something like this: javascript Copier Modifier const ndvi = mergeTiles(bands.nir.file, bands.red.file, bands.nir.metadata.tile_offsets, bands.red.metadata.tile_offsets, bands.nir.metadata.tile_byte_counts, bands.red.metadata.tile_byte_counts); visualizeNDVI(ndvi, canvas.width, canvas.height); This should now correctly visualize the large 1D Float32Array of NDVI values onto the canvas. Let me know if this works or if you need any further adjustments! Vous avez dit : async convertToTiledImage(file, image_profile) { const { image_width, image_length, samples_per_pixel } = image_profile; const { tile_length, tile_width, tile_offsets, tile_byte_counts } = image_profile; const tiles_across = Math.ceil(image_width.value / tile_width.value); const tiles_down = Math.ceil(image_length.value / tile_length.value); const image_data = new Uint16Array(image_width.value * image_length.value * samples_per_pixel.value); let tile_index = 0; for (let row = 0; row < tiles_down; row++) { for (let col = 0; col < tiles_across; col++) { // Decompress tile data const decompressed = await readAndDecompressTile(file, tile_offsets[tile_index], tile_byte_counts[tile_index]); // Copy tile pixels into final image buffer for (let y = 0; y < tile_length.value; y++) { const global_y = row * tile_length.value + y; if (global_y >= image_length.value) break; for (let x = 0; x < tile_width.value; x++) { const global_x = col * tile_width.value + x; if (global_x >= image_width.value) break; for (let s = 0; s < samples_per_pixel.value; s++) { // Index in decompressed tile data const tile_pixel_index = (y * tile_width.value + x) * samples_per_pixel.value + s; // Index in final image buffer const image_pixel_index = (global_y * image_width.value + global_x) * samples_per_pixel.value + s; image_data[image_pixel_index] = decompressed[tile_pixel_index]; } } } tile_index++; } } return image_data; } Im sure mergetiles should work like this ChatGPT a dit : Yes, you're absolutely right! The logic behind your convertToTiledImage function makes perfect sense, and it looks like your mergeTiles function should indeed follow a similar approach to correctly handle the data and tiles. Let's break it down: Decompression: For each tile, the data is decompressed using readAndDecompressTile, which will return a Uint16Array. This will provide the raw pixel values for each tile. Copying Pixels: After decompressing each tile, you correctly map the tile's pixel data into the global image buffer (image_data). You calculate the global x/y position using the tile's position (row, col), and then iterate over the pixels inside the tile to copy them into the image_data buffer. Multi-sample Data: For each sample (since samples_per_pixel is likely more than one, like RGB bands), you ensure the corresponding sample data gets correctly assigned. Adapting the Logic for mergeTiles: To make sure the mergeTiles function aligns with this structure, we'll follow a similar pattern. Specifically: Decompress each tile's data. Loop through the tile's pixels. For each pixel, calculate its global position in the merged image and assign its value. Updated mergeTiles Function: javascript Copier Modifier async function mergeTiles(band_a, band_b, band_a_offsets, band_b_offsets, band_a_tile_byte_count, band_b_tile_byte_count, tile_width, tile_length, samples_per_pixel) { const tile_count = band_a_offsets.length; // Initialize a single large array for the merged NDVI values const mergedNDVI = new Float32Array(band_a.length); let tile_index = 0; // Keeps track of which tile we're processing let currentIndex = 0; // Keeps track of where to store in merged NDVI array for (let row = 0; row < Math.ceil(band_a.length / tile_width); row++) { for (let col = 0; col < Math.ceil(band_a[0].length / tile_length); col++) { // Read and decompress Band 4 (Red) and Band 5 (NIR) tiles const tileBand4 = await readAndDecompressTile(band_a, band_a_offsets[tile_index], band_a_tile_byte_count[tile_index]); const tileBand5 = await readAndDecompressTile(band_b, band_b_offsets[tile_index], band_b_tile_byte_count[tile_index]); // Process each tile's pixels for (let y = 0; y < tile_length.value; y++) { const global_y = row * tile_length.value + y; if (global_y >= image_length.value) break; for (let x = 0; x < tile_width.value; x++) { const global_x = col * tile_width.value + x; if (global_x >= image_width.value) break; // Compute NDVI for this pixel const nir = tileBand5[(y * tile_width.value) + x]; // NIR Band 5 const red = tileBand4[(y * tile_width.value) + x]; // Red Band 4 const denom = nir + red; const ndvi = denom === 0 ? 0 : (nir - red) / denom; // Store the NDVI value in the merged array mergedNDVI[currentIndex] = ndvi; // Move to the next pixel in the merged NDVI array currentIndex++; } } tile_index++; // Move to the next tile } } return mergedNDVI; // Return the merged NDVI data } Key Considerations for mergeTiles: Decompression: We handle the decompression for both the band_a (Red) and band_b (NIR) tiles using the readAndDecompressTile function for each tile offset and byte count. Pixel Processing: For each pixel in the tile, we compute the NDVI value. NDVI is calculated for each pixel using the formula: NDVI = ( NIR − Red ) ( NIR + Red ) NDVI= (NIR+Red) (NIR−Red) ​ Global Position Calculation: The global pixel position (global_x and global_y) is calculated using the tile's position and the pixel's position inside the tile. Merging NDVI: The NDVI values are stored in the mergedNDVI array, which will be a large 1D array containing all the NDVI values for the entire image. Tile Iteration: The tile iteration works similarly to your convertToTiledImage function, but instead of copying pixel data directly, we calculate and store NDVI values. Adjustments You May Need: Global Image Dimensions: Ensure that image_width.value and image_length.value are properly set to the total width and height of the full image. This is important for correctly calculating the global pixel positions. Samples per Pixel: Since NDVI is calculated per pixel, samples_per_pixel should be adjusted accordingly. In the mergeTiles function, we’re only calculating one NDVI value per pixel, so you might not need samples_per_pixel directly unless you are handling multi-band data (like RGB). Next Steps: Call the mergeTiles Function: After calling the mergeTiles function, you should get a large 1D Float32Array containing the NDVI values. You can then pass this array to your visualizeNDVI function to render the result. Visualization: Use the updated visualizeNDVI function (from earlier) to take the mergedNDVI array and render it to the canvas. javascript Copier Modifier const ndvi = await mergeTiles(bands.nir.file, bands.red.file, bands.nir.metadata.tile_offsets, bands.red.metadata.tile_offsets, bands.nir.metadata.tile_byte_counts, bands.red.metadata.tile_byte_counts, tile_width, tile_length, samples_per_pixel); visualizeNDVI(ndvi, canvas.width, canvas.height); This should now correctly process and visualize your NDVI data! Let me know if anything else needs tweaking or if you have further questions!