diff --git a/packages/core/entity/FileDetail/RenderZarrThumbnailURL.ts b/packages/core/entity/FileDetail/RenderZarrThumbnailURL.ts index be95fa728..cc6a1d0df 100644 --- a/packages/core/entity/FileDetail/RenderZarrThumbnailURL.ts +++ b/packages/core/entity/FileDetail/RenderZarrThumbnailURL.ts @@ -2,6 +2,14 @@ import * as zarr from "@zarrita/core"; import { FetchStore } from "@zarrita/storage"; import { get as zarrGet } from "@zarrita/indexing"; +// Function to map grayscale value (0-255) to RGB color +function grayscaleToRGB(value: number): [number, number, number] { + const r = value; + const g = 0 + value; + const b = 255 - value; + return [r, g, b]; +} + export async function renderZarrThumbnailURL(zarrUrl: string): Promise { try { const store = new FetchStore(zarrUrl); @@ -18,45 +26,50 @@ export async function renderZarrThumbnailURL(zarrUrl: string): Promise { const lowestResolutionDataset = datasets[datasets.length - 1]; const lowestResolutionLocation = root.resolve(lowestResolutionDataset.path); const lowestResolution = await zarr.open(lowestResolutionLocation, { kind: "array" }); // TODO: check the filesize before slicing - const lowestResolutionView = await zarrGet(lowestResolution, [0, 0, 21, null, null]); // TODO: make this slicing dynamic - const data = lowestResolutionView.data as Uint16Array; + const lowestResolutionView = await zarrGet(lowestResolution, [0, 0, 20, null, null]); // Adjusted slicing for 2D data + console.log("DATA", lowestResolutionView.data); + console.log("SHAPE", lowestResolutionView.shape); + const u16data = lowestResolutionView.data as Uint16Array; + + const min = Math.min(...u16data); + const max = Math.max(...u16data); - // Normalize the Uint16Array data to 8-bit - const maxVal = Math.max(...data); - const minVal = Math.min(...data); - const normalizedData = new Uint8ClampedArray(data.length); - for (let i = 0; i < data.length; i++) { - normalizedData[i] = ((data[i] - minVal) / (maxVal - minVal)) * 255; + const normalizedData = new Uint8Array(u16data.length); + for (let i = 0; i < u16data.length; i++) { + normalizedData[i] = Math.round((255 * (u16data[i] - min)) / (max - min)); } - // Create a canvas to draw the image + const width = lowestResolutionView.shape[1]; + const height = lowestResolutionView.shape[0]; const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - const width = Math.min(100, lowestResolution.shape[3]); // Set to actual data width if less than 100 - const height = Math.min(100, lowestResolution.shape[4]); // Set to actual data height if less than 100 canvas.width = width; canvas.height = height; + const context = canvas.getContext("2d"); - if (context) { - const imageData = context.createImageData(width, height); - const imageDataArray = imageData.data; + if (!context) { + throw new Error("Failed to get canvas context"); + } - // Populate the ImageData object with the normalized data - for (let i = 0; i < normalizedData.length; i++) { - const value = normalizedData[i]; - imageDataArray[i * 4] = value; // Red - imageDataArray[i * 4 + 1] = value; // Green - imageDataArray[i * 4 + 2] = value; // Blue - imageDataArray[i * 4 + 3] = 255; // Alpha - } - context.putImageData(imageData, 0, 0); + const imageData = context.createImageData(width, height); - const dataURL = canvas.toDataURL("image/png"); - return dataURL; - } else { - throw new Error("Unable to get 2D context from canvas"); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 4; + const value = normalizedData[y * width + x]; + const [r, g, b] = grayscaleToRGB(value); + imageData.data[idx] = r; // Red + imageData.data[idx + 1] = g; // Green + imageData.data[idx + 2] = b; // Blue + imageData.data[idx + 3] = 255; // Alpha + } } + + context.putImageData(imageData, 0, 0); + + const dataUrl = canvas.toDataURL("image/png"); + + return dataUrl; } else { throw new Error("Invalid multiscales attribute structure"); }