diff --git a/src/ImageItem.jsx b/src/ImageItem.jsx
index f34b14f..b76d50f 100644
--- a/src/ImageItem.jsx
+++ b/src/ImageItem.jsx
@@ -1,15 +1,17 @@
import React from "react";
+import Thumbnail from "./Thumbnail";
import Viewer from "./Viewer";
import OpenWith from "./OpenWith";
import CopyButton from "./CopyButton";
import { loadOmeroMultiscales, open, getNgffAxes } from "./util";
+import { openArray } from "zarr";
// DeckGL react component
export default function ImageItem({ source }) {
let config = { source };
- const [layers, setLayers] = React.useState([]);
+ // const [layers, setLayers] = React.useState([]);
const [imgInfo, setImageInfo] = React.useState({});
@@ -45,30 +47,37 @@ export default function ImageItem({ source }) {
const axes = getNgffAxes(attrs.multiscales);
- let layerData = await loadOmeroMultiscales(config, node, attrs);
-
- let shape = layerData.loader[0]._data.meta.shape;
- let chunks = layerData.loader[0]._data.meta.chunks;
- console.log("layerData.loader[0]._data.meta", layerData.loader[0]._data.meta, chunks, chunks.join(","))
-
- let selections = [];
- layerData.channelsVisible.forEach((visible, chIndex) => {
- if (visible) {
- selections.push(
- axes.map((axis, dim) => {
- if (axis.type == "time") return 0;
- if (axis.name == "z") return parseInt(shape[dim] / 2);
- if (axis.name == "c") return chIndex;
- return 0;
- })
- );
- }
- });
+ let path = attrs.multiscales[0].datasets[0].path;
+ const store = await openArray({ store: source + "/" + path, mode: "r" });
+
+ let shape = store.meta.shape;
+ let chunks = store.meta.chunks;
+
+ // let layerData = await loadOmeroMultiscales(config, node, attrs);
+ // let shape = ["TBD"] // layerData.loader[0]._data.meta.shape;
+ // let chunks = ["TBD"] // layerData.loader[0]._data.meta.chunks;
+ // console.log("layerData.loader[0]._data.meta", layerData.loader[0]._data.meta, chunks, chunks.join(","))
- layerData.selections = selections;
+ // let selections = [];
+ // layerData.channelsVisible.forEach((visible, chIndex) => {
+ // if (visible) {
+ // selections.push(
+ // axes.map((axis, dim) => {
+ // if (axis.type == "time") return 0;
+ // if (axis.name == "z") return parseInt(shape[dim] / 2);
+ // if (axis.name == "c") return chIndex;
+ // return 0;
+ // })
+ // );
+ // }
+ // });
+
+ // layerData.selections = selections;
+
+ // setLayers([layerData]);
- setLayers([layerData]);
setImageInfo({
+ attrs,
axes: axes.map((axis) => axis.name).join(""),
version: attrs.multiscales?.[0]?.version,
keywords,
@@ -115,7 +124,9 @@ export default function ImageItem({ source }) {
{imgInfo?.keywords?.join(", ")} |
-
+ {imgInfo.attrs &&
+
+ }
|
diff --git a/src/Thumbnail.jsx b/src/Thumbnail.jsx
new file mode 100644
index 0000000..9aca410
--- /dev/null
+++ b/src/Thumbnail.jsx
@@ -0,0 +1,99 @@
+import React from "react";
+
+import { openArray, slice } from "zarr";
+import {getNgffAxes, renderTo8bitArray, getMinMaxValues, getDefaultVisibilities, hexToRGB} from "./util";
+
+export default function Thumbnail({ source, attrs }) {
+ // const [r, setChunk] = React.useState();
+
+ const [canvasSize, setCanvasSize] = React.useState({width: 100, height: 100});
+
+ const canvas = React.useRef();
+
+
+ React.useEffect(() => {
+ const fn = async function () {
+ let paths = attrs.multiscales[0].datasets.map(d => d.path);
+ let axes = getNgffAxes(attrs.multiscales).map(a => a.name);
+ console.log("paths", paths, "axes", axes);
+
+ let path = paths.at(-1);
+ const store = await openArray({ store: source + "/" + path, mode: "r" });
+
+ let chDim = axes.indexOf('c');
+
+ let shape = store.meta.shape;
+ let dims = shape.length;
+ let ch = store.meta.chunks;
+ console.log("shape", shape, ch);
+
+ let channel_count = shape[chDim];
+ let visibilities;
+ let colors;
+ if (attrs?.omero?.channels) {
+ console.log("omero channels", attrs?.omero?.channels)
+ visibilities = attrs.omero.channels.map(ch => ch.active);
+ colors = attrs.omero.channels.map(ch => hexToRGB(ch.color));
+ } else {
+ visibilities = getDefaultVisibilities(channel_count);
+ colors = getDefaultColors(channel_count, visibilities);
+ }
+ // filter for active channels
+ colors = colors.filter((col, idx) => visibilities[idx]);
+
+
+ let activeChannels = visibilities.reduce((prev, active, index) => {
+ if (active) prev.push(index);
+ return prev;
+ }, []);
+
+ console.log({visibilities, activeChannels, colors});
+
+ let promises = activeChannels.map(chIndex => {
+ let indecies = shape.map((dimSize, index) => {
+ // channel
+ if (index == chDim) return chIndex;
+ // x and y
+ if (index >= dims - 2) {
+ return slice(0, dimSize);
+ }
+ // z
+ if (axes[index] == 'z') {
+ return parseInt(dimSize / 2);
+ }
+ return 0;
+ });
+ console.log('ch indecies', chIndex, indecies);
+ return store.get(indecies);
+ });
+
+
+ let ndChunks = await Promise.all(promises);
+ console.log('ndChunks', ndChunks);
+
+
+
+
+ let minMaxValues = ndChunks.map(ch => getMinMaxValues(ch));
+
+ console.log("minMaxValues", minMaxValues);
+
+ let rbgData = renderTo8bitArray(ndChunks, minMaxValues, colors);
+
+ console.log("rbgData", rbgData);
+ // setChunk(data);
+ let width = shape.at(-1);
+ let height = shape.at(-2);
+ setCanvasSize({width, height });
+
+ const ctx = canvas.current.getContext('2d');
+ ctx.putImageData(new ImageData(rbgData, width, height), 0, 0);
+ };
+
+ fn();
+ }, []);
+
+ return
+
+
;
+}
diff --git a/src/util.js b/src/util.js
index 8ac39f9..1e39901 100644
--- a/src/util.js
+++ b/src/util.js
@@ -297,3 +297,62 @@ export async function loadOmeroMultiscales(config, zarrGroup, attrs) {
name: meta.name ?? name,
};
}
+
+
+export function renderTo8bitArray(ndChunks, minMaxValues, colors) {
+ // Render chunks (array) into 2D 8-bit data for new ImageData(arr)
+ // ndChunks is list of zarr arrays
+
+ // assume all chunks are same shape
+ const shape = ndChunks[0].shape;
+ const height = shape[0];
+ const width = shape[1];
+
+ if (!minMaxValues) {
+ minMaxValues = ndChunks.map(getMinMaxValues);
+ }
+
+ // let rgb = [255, 255, 255];
+
+ const rgba = new Uint8ClampedArray(4 * height * width).fill(0);
+ let offset = 0;
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ for (let p = 0; p < ndChunks.length; p++) {
+ let rgb = colors[p];
+ let data = ndChunks[p].data;
+ let range = minMaxValues[p];
+ let rawValue = data[y][x];
+ let fraction = (rawValue - range[0]) / (range[1] - range[0]);
+ // for red, green, blue,
+ for (let i = 0; i < 3; i++) {
+ // rgb[i] is 0-255...
+ let v = (fraction * rgb[i]) << 0;
+ // increase pixel intensity if value is higher
+ rgba[offset * 4 + i] = Math.max(rgba[offset * 4 + i], v);
+ }
+ }
+ rgba[offset * 4 + 3] = 255; // alpha
+ offset += 1;
+ }
+ }
+ return rgba;
+}
+
+export function getMinMaxValues(chunk2d) {
+ const shape = chunk2d.shape;
+ const height = shape[0];
+ const width = shape[1];
+ const data = chunk2d.data;
+ let maxV = 0;
+ let minV = Infinity;
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ let rawValue = data[y][x];
+ maxV = Math.max(maxV, rawValue);
+ minV = Math.min(minV, rawValue);
+ }
+ }
+ return [minV, maxV];
+}
+