From b59f865c234a0659a44b9f2121143f4d9935df5b Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 15:42:46 +0100 Subject: [PATCH 01/13] add scrolling on t and y axis when pressing t / z --- main.ts | 2 +- src/components/Viewer.tsx | 202 +++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index e3308a3f..4df6713c 100644 --- a/main.ts +++ b/main.ts @@ -2,7 +2,7 @@ import debounce from "just-debounce-it"; import * as vizarr from "./src/index"; async function main() { - console.log(`vizarr v${vizarr.version}: https://github.com/hms-dbmi/vizarr`); + console.log(`vizarr v${vizarr.version}: https://github.com/BioNGFF/vizarr`); // biome-ignore lint/style/noNonNullAssertion: We know the element exists const viewer = await vizarr.createViewer(document.querySelector("#root")!); const url = new URL(window.location.href); diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index f54a58dd..66bf785a 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -2,21 +2,26 @@ import { ScaleBarLayer } from "@hms-dbmi/viv"; import DeckGL from "deck.gl"; import { OrthographicView } from "deck.gl"; import { useAtom, useAtomValue } from "jotai"; +import { useAtomCallback } from "jotai/utils"; import * as React from "react"; import { useViewState } from "../hooks"; -import { layerAtoms, viewportAtom } from "../state"; +import { layerAtoms, layerFamilyAtom, sourceInfoAtom, viewportAtom } from "../state"; import { fitImageToViewport, getLayerSize, resolveLoaderFromLayerProps } from "../utils"; import type { DeckGLRef, OrthographicViewState, PickingInfo } from "deck.gl"; import type { GrayscaleBitmapLayerPickingInfo } from "../layers/label-layer"; import type { ViewState, VizarrLayer } from "../state"; +const AXIS_SCROLL_STEP_DELTA = 40; + export default function Viewer() { const deckRef = React.useRef(null); const [viewport, setViewport] = useAtom(viewportAtom); const [viewState, setViewState] = useViewState(); const layers = useAtomValue(layerAtoms); const firstLayer = layers[0] as VizarrLayer; + const [axisScrollKey, setAxisScrollKey] = React.useState<"z" | "t" | null>(null); + const axisScrollAccumulatorRef = React.useRef(0); const resetViewState = React.useCallback( (layer: VizarrLayer) => { @@ -78,6 +83,201 @@ export default function Viewer() { preserveDrawingBuffer: true, }; + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const key = event.key.toLowerCase(); + if (key === "z" || key === "t") { + setAxisScrollKey(key as "z" | "t"); + } // set when pressing the key + }; + + const handleKeyUp = (event: KeyboardEvent) => { + const key = event.key.toLowerCase(); + if (key === "z" || key === "t") { + setAxisScrollKey((prev) => (prev === key ? null : prev)); + } // reset when letting go of the key + }; + + const handleBlur = () => { + // reset when switching windows + setAxisScrollKey(null); + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + window.addEventListener("blur", handleBlur); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + window.removeEventListener("blur", handleBlur); + }; + }, []); + + React.useEffect(() => { + // reset accumulator when axis key changes + axisScrollAccumulatorRef.current = 0; + void axisScrollKey; + }, [axisScrollKey]); + + const handleWheel = useAtomCallback( + // handle wheel events for axis scrolling + React.useCallback( + (get, set, event: WheelEvent) => { + if (!axisScrollKey) { + return; // ignore if no axis key is set, fall back to default zoom behavior + } + + const deckInstance = viewport ?? deckRef.current?.deck ?? null; + const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; + if (!deckInstance || !canvas) { + return; // no deck instance or canvas + } + + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + if (x < 0 || y < 0 || x > rect.width || y > rect.height) { + return; // only consider events within the canvas + } + + const picks = (deckInstance.pickMultipleObjects({ x, y, depth: 1 }) ?? []) as PickingInfo[]; + const sources = get(sourceInfoAtom); + if (sources.length === 0) { + return; + } + + const pickedLayerId = (() => { + const pick = picks.find((info: PickingInfo) => info.layer && typeof info.layer.props?.id === "string"); // find the first layer with an id + if (!pick || !pick.layer?.props?.id) { + return undefined; + } + return String(pick.layer.props.id); + })(); + + const targetSource = pickedLayerId + ? (sources.find( + // find source matching picked layer id + (item: (typeof sources)[number]) => + pickedLayerId === item.id || + pickedLayerId.startsWith(`${item.id}_`) || + pickedLayerId.startsWith(`${item.id}-`), + ) ?? sources[0]) + : sources[0]; + + const axisLabels = targetSource.axis_labels ?? []; + const axisIndex = axisLabels.findIndex((axis: string) => axis.toLowerCase() === axisScrollKey); // find the index of the axis to scroll + if (axisIndex === -1) { + return; + } + + // get the max index on this axis + const baseLoader = targetSource.loader?.[0]; + const shape = baseLoader?.shape; + if (!shape || axisIndex >= shape.length) { + return; + } + const maxIndex = shape[axisIndex] - 1; + if (maxIndex <= 0) { + return; + } + + const layerAtom = layerFamilyAtom(targetSource); + const layerState = get(layerAtom); + if (!layerState) { + return; + } + + const { layerProps } = layerState; + const selections = layerProps.selections; + if (selections.length === 0) { + return; + } + + // prevent the default zoom behavior + event.preventDefault(); + event.stopPropagation(); + + // calculate scroll steps + axisScrollAccumulatorRef.current += event.deltaY; + const steps = Math.trunc(axisScrollAccumulatorRef.current / AXIS_SCROLL_STEP_DELTA); + if (steps === 0) { + return; + } + + // keep overflow for next time + axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; + + // keep within bounds + const currentIndex = selections[0]?.[axisIndex] ?? 0; + const nextIndex = Math.min(Math.max(currentIndex + steps, 0), maxIndex); + if (nextIndex === currentIndex) { + return; + } + + // update the selections with the new index + const nextSelections = selections.map((selection: number[]) => { + const next = [...selection]; + next[axisIndex] = nextIndex; + return next; + }); + + set(layerAtom, { + ...layerState, + layerProps: { + ...layerProps, + selections: nextSelections, + }, + }); + + const defaultSelection = nextSelections[0] ? [...nextSelections[0]] : undefined; + if (!defaultSelection) { + return; + } + + set(sourceInfoAtom, (prev: typeof sources) => + prev.map((item: (typeof sources)[number]) => { + if (item.id !== targetSource.id) { + return item; + } + const prevSelection = item.defaults.selection; + const isSame = + prevSelection.length === defaultSelection.length && + prevSelection.every((value: number, index: number) => value === defaultSelection[index]); + if (isSame) { + return item; + } + return { + ...item, + defaults: { + ...item.defaults, + selection: defaultSelection, + }, + }; + }), + ); + }, + [viewport, axisScrollKey], + ), + ); + + React.useEffect(() => { + // attach wheel listener to deck canvas + const deckInstance = (viewport ?? deckRef.current?.deck ?? null) as { canvas?: HTMLCanvasElement } | null; + const element = deckInstance?.canvas; + if (!element) { + return; + } + + const listener = (event: WheelEvent) => { + void handleWheel(event); + }; + + element.addEventListener("wheel", listener, { passive: false }); + return () => { + element.removeEventListener("wheel", listener); + }; + }, [viewport, handleWheel]); + const getTooltip = (info: GrayscaleBitmapLayerPickingInfo | PickingInfo) => { const pickingInfo = info as PickingInfo & { gridCoord?: { row: number; column: number }; From 3614d90a3d0e9aa687fee48cde8083f19c2ff6e8 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 16:20:34 +0100 Subject: [PATCH 02/13] refactor: axis scroll out of viewer --- src/components/Viewer.tsx | 205 +----------------------------------- src/hooks/useAxisScroll.ts | 208 +++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 201 deletions(-) create mode 100644 src/hooks/useAxisScroll.ts diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 66bf785a..8aee5704 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -2,26 +2,24 @@ import { ScaleBarLayer } from "@hms-dbmi/viv"; import DeckGL from "deck.gl"; import { OrthographicView } from "deck.gl"; import { useAtom, useAtomValue } from "jotai"; -import { useAtomCallback } from "jotai/utils"; import * as React from "react"; import { useViewState } from "../hooks"; -import { layerAtoms, layerFamilyAtom, sourceInfoAtom, viewportAtom } from "../state"; +import { useAxisScroll } from "../hooks/useAxisScroll"; +import { layerAtoms, viewportAtom } from "../state"; import { fitImageToViewport, getLayerSize, resolveLoaderFromLayerProps } from "../utils"; import type { DeckGLRef, OrthographicViewState, PickingInfo } from "deck.gl"; import type { GrayscaleBitmapLayerPickingInfo } from "../layers/label-layer"; import type { ViewState, VizarrLayer } from "../state"; -const AXIS_SCROLL_STEP_DELTA = 40; - export default function Viewer() { const deckRef = React.useRef(null); const [viewport, setViewport] = useAtom(viewportAtom); const [viewState, setViewState] = useViewState(); const layers = useAtomValue(layerAtoms); const firstLayer = layers[0] as VizarrLayer; - const [axisScrollKey, setAxisScrollKey] = React.useState<"z" | "t" | null>(null); - const axisScrollAccumulatorRef = React.useRef(0); + + useAxisScroll(deckRef, viewport); const resetViewState = React.useCallback( (layer: VizarrLayer) => { @@ -83,201 +81,6 @@ export default function Viewer() { preserveDrawingBuffer: true, }; - React.useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - const key = event.key.toLowerCase(); - if (key === "z" || key === "t") { - setAxisScrollKey(key as "z" | "t"); - } // set when pressing the key - }; - - const handleKeyUp = (event: KeyboardEvent) => { - const key = event.key.toLowerCase(); - if (key === "z" || key === "t") { - setAxisScrollKey((prev) => (prev === key ? null : prev)); - } // reset when letting go of the key - }; - - const handleBlur = () => { - // reset when switching windows - setAxisScrollKey(null); - }; - - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); - window.addEventListener("blur", handleBlur); - return () => { - window.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("keyup", handleKeyUp); - window.removeEventListener("blur", handleBlur); - }; - }, []); - - React.useEffect(() => { - // reset accumulator when axis key changes - axisScrollAccumulatorRef.current = 0; - void axisScrollKey; - }, [axisScrollKey]); - - const handleWheel = useAtomCallback( - // handle wheel events for axis scrolling - React.useCallback( - (get, set, event: WheelEvent) => { - if (!axisScrollKey) { - return; // ignore if no axis key is set, fall back to default zoom behavior - } - - const deckInstance = viewport ?? deckRef.current?.deck ?? null; - const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; - if (!deckInstance || !canvas) { - return; // no deck instance or canvas - } - - const rect = canvas.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - if (x < 0 || y < 0 || x > rect.width || y > rect.height) { - return; // only consider events within the canvas - } - - const picks = (deckInstance.pickMultipleObjects({ x, y, depth: 1 }) ?? []) as PickingInfo[]; - const sources = get(sourceInfoAtom); - if (sources.length === 0) { - return; - } - - const pickedLayerId = (() => { - const pick = picks.find((info: PickingInfo) => info.layer && typeof info.layer.props?.id === "string"); // find the first layer with an id - if (!pick || !pick.layer?.props?.id) { - return undefined; - } - return String(pick.layer.props.id); - })(); - - const targetSource = pickedLayerId - ? (sources.find( - // find source matching picked layer id - (item: (typeof sources)[number]) => - pickedLayerId === item.id || - pickedLayerId.startsWith(`${item.id}_`) || - pickedLayerId.startsWith(`${item.id}-`), - ) ?? sources[0]) - : sources[0]; - - const axisLabels = targetSource.axis_labels ?? []; - const axisIndex = axisLabels.findIndex((axis: string) => axis.toLowerCase() === axisScrollKey); // find the index of the axis to scroll - if (axisIndex === -1) { - return; - } - - // get the max index on this axis - const baseLoader = targetSource.loader?.[0]; - const shape = baseLoader?.shape; - if (!shape || axisIndex >= shape.length) { - return; - } - const maxIndex = shape[axisIndex] - 1; - if (maxIndex <= 0) { - return; - } - - const layerAtom = layerFamilyAtom(targetSource); - const layerState = get(layerAtom); - if (!layerState) { - return; - } - - const { layerProps } = layerState; - const selections = layerProps.selections; - if (selections.length === 0) { - return; - } - - // prevent the default zoom behavior - event.preventDefault(); - event.stopPropagation(); - - // calculate scroll steps - axisScrollAccumulatorRef.current += event.deltaY; - const steps = Math.trunc(axisScrollAccumulatorRef.current / AXIS_SCROLL_STEP_DELTA); - if (steps === 0) { - return; - } - - // keep overflow for next time - axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; - - // keep within bounds - const currentIndex = selections[0]?.[axisIndex] ?? 0; - const nextIndex = Math.min(Math.max(currentIndex + steps, 0), maxIndex); - if (nextIndex === currentIndex) { - return; - } - - // update the selections with the new index - const nextSelections = selections.map((selection: number[]) => { - const next = [...selection]; - next[axisIndex] = nextIndex; - return next; - }); - - set(layerAtom, { - ...layerState, - layerProps: { - ...layerProps, - selections: nextSelections, - }, - }); - - const defaultSelection = nextSelections[0] ? [...nextSelections[0]] : undefined; - if (!defaultSelection) { - return; - } - - set(sourceInfoAtom, (prev: typeof sources) => - prev.map((item: (typeof sources)[number]) => { - if (item.id !== targetSource.id) { - return item; - } - const prevSelection = item.defaults.selection; - const isSame = - prevSelection.length === defaultSelection.length && - prevSelection.every((value: number, index: number) => value === defaultSelection[index]); - if (isSame) { - return item; - } - return { - ...item, - defaults: { - ...item.defaults, - selection: defaultSelection, - }, - }; - }), - ); - }, - [viewport, axisScrollKey], - ), - ); - - React.useEffect(() => { - // attach wheel listener to deck canvas - const deckInstance = (viewport ?? deckRef.current?.deck ?? null) as { canvas?: HTMLCanvasElement } | null; - const element = deckInstance?.canvas; - if (!element) { - return; - } - - const listener = (event: WheelEvent) => { - void handleWheel(event); - }; - - element.addEventListener("wheel", listener, { passive: false }); - return () => { - element.removeEventListener("wheel", listener); - }; - }, [viewport, handleWheel]); - const getTooltip = (info: GrayscaleBitmapLayerPickingInfo | PickingInfo) => { const pickingInfo = info as PickingInfo & { gridCoord?: { row: number; column: number }; diff --git a/src/hooks/useAxisScroll.ts b/src/hooks/useAxisScroll.ts new file mode 100644 index 00000000..c4a1e7cb --- /dev/null +++ b/src/hooks/useAxisScroll.ts @@ -0,0 +1,208 @@ +import type { DeckGLRef, PickingInfo } from "deck.gl"; +import { useAtomCallback } from "jotai/utils"; +import * as React from "react"; +import { layerFamilyAtom, sourceInfoAtom } from "../state"; + +type DeckInstance = DeckGLRef["deck"] | null; + +const AXIS_SCROLL_STEP_DELTA = 40; + +export function useAxisScroll(deckRef: React.RefObject, viewport: DeckInstance) { + const [axisScrollKey, setAxisScrollKey] = React.useState<"z" | "t" | null>(null); + const axisScrollAccumulatorRef = React.useRef(0); + + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const key = event.key.toLowerCase(); + if (key === "z" || key === "t") { + setAxisScrollKey(key as "z" | "t"); + } // set when pressing the key + }; + + const handleKeyUp = (event: KeyboardEvent) => { + const key = event.key.toLowerCase(); + if (key === "z" || key === "t") { + setAxisScrollKey((prev) => (prev === key ? null : prev)); + } // reset when letting go of the key + }; + + const handleBlur = () => { + // reset when switching windows + setAxisScrollKey(null); + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + window.addEventListener("blur", handleBlur); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + window.removeEventListener("blur", handleBlur); + }; + }, []); + + React.useEffect(() => { + // reset accumulator when axis key changes + axisScrollAccumulatorRef.current = 0; + void axisScrollKey; + }, [axisScrollKey]); + + const handleWheel = useAtomCallback( + // handle wheel events for axis scrolling + React.useCallback( + (get, set, event: WheelEvent) => { + if (!axisScrollKey) { + return; // ignore if no axis key is set, fall back to default zoom behavior + } + + const deckInstance = viewport ?? deckRef.current?.deck ?? null; + const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; + if (!deckInstance || !canvas) { + return; // no deck instance or canvas + } + + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + if (x < 0 || y < 0 || x > rect.width || y > rect.height) { + return; // only consider events within the canvas + } + + const picks = (deckInstance.pickMultipleObjects({ x, y, depth: 1 }) ?? []) as PickingInfo[]; + const sources = get(sourceInfoAtom); + if (sources.length === 0) { + return; + } + + const pickedLayerId = (() => { + const pick = picks.find((info: PickingInfo) => info.layer && typeof info.layer.props?.id === "string"); // find the first layer with an id + if (!pick || !pick.layer?.props?.id) { + return undefined; + } + return String(pick.layer.props.id); + })(); + + const targetSource = pickedLayerId + ? (sources.find( + // find source matching picked layer id + (item: (typeof sources)[number]) => + pickedLayerId === item.id || + pickedLayerId.startsWith(`${item.id}_`) || + pickedLayerId.startsWith(`${item.id}-`), + ) ?? sources[0]) + : sources[0]; + + const axisLabels = targetSource.axis_labels ?? []; + const axisIndex = axisLabels.findIndex((axis: string) => axis.toLowerCase() === axisScrollKey); // find the index of the axis to scroll + if (axisIndex === -1) { + return; + } + + // get the max index on this axis + const baseLoader = targetSource.loader?.[0]; + const shape = baseLoader?.shape; + if (!shape || axisIndex >= shape.length) { + return; + } + const maxIndex = shape[axisIndex] - 1; + if (maxIndex <= 0) { + return; + } + + const layerAtom = layerFamilyAtom(targetSource); + const layerState = get(layerAtom); + if (!layerState) { + return; + } + + const { layerProps } = layerState; + const selections = layerProps.selections; + if (selections.length === 0) { + return; + } + + // prevent the default zoom behavior + event.preventDefault(); + event.stopPropagation(); + + // calculate scroll steps + axisScrollAccumulatorRef.current += event.deltaY; + const steps = Math.trunc(axisScrollAccumulatorRef.current / AXIS_SCROLL_STEP_DELTA); + if (steps === 0) { + return; + } + + // keep overflow for next time + axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; + + // keep within bounds + const currentIndex = selections[0]?.[axisIndex] ?? 0; + const nextIndex = Math.min(Math.max(currentIndex + steps, 0), maxIndex); + if (nextIndex === currentIndex) { + return; + } + + // update the selections with the new index + const nextSelections = selections.map((selection: number[]) => { + const next = [...selection]; + next[axisIndex] = nextIndex; + return next; + }); + + set(layerAtom, { + ...layerState, + layerProps: { + ...layerProps, + selections: nextSelections, + }, + }); + + const defaultSelection = nextSelections[0] ? [...nextSelections[0]] : undefined; + if (!defaultSelection) { + return; + } + + set(sourceInfoAtom, (prev: typeof sources) => + prev.map((item: (typeof sources)[number]) => { + if (item.id !== targetSource.id) { + return item; + } + const prevSelection = item.defaults.selection; + const isSame = + prevSelection.length === defaultSelection.length && + prevSelection.every((value: number, index: number) => value === defaultSelection[index]); + if (isSame) { + return item; + } + return { + ...item, + defaults: { + ...item.defaults, + selection: defaultSelection, + }, + }; + }), + ); + }, + [viewport, axisScrollKey, deckRef], + ), + ); + + React.useEffect(() => { + // attach wheel listener to deck canvas + const deckInstance = (viewport ?? deckRef.current?.deck ?? null) as { canvas?: HTMLCanvasElement } | null; + const element = deckInstance?.canvas; + if (!element) { + return; + } + + const listener = (event: WheelEvent) => { + void handleWheel(event); + }; + + element.addEventListener("wheel", listener, { passive: false }); + return () => { + element.removeEventListener("wheel", listener); + }; + }, [viewport, handleWheel, deckRef]); +} From 7ce79d77d25806888a287383e6ea786f57980a27 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 16:55:25 +0100 Subject: [PATCH 03/13] add: arrow key navigation in t and z --- src/components/Viewer.tsx | 4 +- src/hooks/useAxisNavigation.ts | 276 +++++++++++++++++++++++++++++++++ src/hooks/useAxisScroll.ts | 208 ------------------------- 3 files changed, 278 insertions(+), 210 deletions(-) create mode 100644 src/hooks/useAxisNavigation.ts delete mode 100644 src/hooks/useAxisScroll.ts diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 8aee5704..9e39f737 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -4,7 +4,7 @@ import { OrthographicView } from "deck.gl"; import { useAtom, useAtomValue } from "jotai"; import * as React from "react"; import { useViewState } from "../hooks"; -import { useAxisScroll } from "../hooks/useAxisScroll"; +import { useAxisNavigation } from "../hooks/useAxisNavigation"; import { layerAtoms, viewportAtom } from "../state"; import { fitImageToViewport, getLayerSize, resolveLoaderFromLayerProps } from "../utils"; @@ -19,7 +19,7 @@ export default function Viewer() { const layers = useAtomValue(layerAtoms); const firstLayer = layers[0] as VizarrLayer; - useAxisScroll(deckRef, viewport); + useAxisNavigation(deckRef, viewport); const resetViewState = React.useCallback( (layer: VizarrLayer) => { diff --git a/src/hooks/useAxisNavigation.ts b/src/hooks/useAxisNavigation.ts new file mode 100644 index 00000000..240176b1 --- /dev/null +++ b/src/hooks/useAxisNavigation.ts @@ -0,0 +1,276 @@ +import type { DeckGLRef, PickingInfo } from "deck.gl"; +import { useAtomCallback } from "jotai/utils"; +import * as React from "react"; +import { layerFamilyAtom, sourceInfoAtom } from "../state"; + +type DeckInstance = DeckGLRef["deck"] | null; + +type Axis = "z" | "t"; +type AdjustArgs = { + axis: Axis; + delta: number; + pointer?: { x: number; y: number }; +}; + +const AXIS_SCROLL_STEP_DELTA = 40; + +export function useAxisNavigation(deckRef: React.RefObject, viewport: DeckInstance) { + const [axisScrollKey, setAxisScrollKey] = React.useState(null); + const axisScrollAccumulatorRef = React.useRef(0); + const lastPointerRef = React.useRef<{ x: number; y: number } | undefined>(undefined); + const lastTargetSourceIdRef = React.useRef(undefined); + + const adjustAxis = useAtomCallback( + React.useCallback( + (get, set, { axis, delta, pointer }: AdjustArgs) => { + if (delta === 0) { + return; + } + + const deckInstance = viewport ?? deckRef.current?.deck ?? null; + const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; + if (!deckInstance || !canvas) { + return; // no deck instance or canvas + } + + const rect = canvas.getBoundingClientRect(); + if (pointer) { + lastPointerRef.current = pointer; + } + + const sources = get(sourceInfoAtom); + if (sources.length === 0) { + return; + } + + const getAxisIndex = (source: (typeof sources)[number]) => + (source.axis_labels ?? []).findIndex((label) => label.toLowerCase() === axis); + + const pointerToUse = pointer ?? lastPointerRef.current; + + let targetSource: (typeof sources)[number] | undefined; + let axisIndex = -1; + + if (pointerToUse) { + const { x, y } = pointerToUse; + if (x >= 0 && y >= 0 && x <= rect.width && y <= rect.height) { + const picks = (deckInstance.pickMultipleObjects({ x, y, depth: 1 }) ?? []) as PickingInfo[]; + const pickedLayerId = (() => { + const pick = picks.find((info: PickingInfo) => info.layer && typeof info.layer.props?.id === "string"); + if (!pick || !pick.layer?.props?.id) { + return undefined; + } + return String(pick.layer.props.id); + })(); + + if (pickedLayerId) { + targetSource = sources.find( + (item) => + pickedLayerId === item.id || + pickedLayerId.startsWith(`${item.id}_`) || + pickedLayerId.startsWith(`${item.id}-`), + ); + if (targetSource) { + axisIndex = getAxisIndex(targetSource); + } + } + } + } + + if ((!targetSource || axisIndex === -1) && lastTargetSourceIdRef.current) { + targetSource = sources.find((item) => item.id === lastTargetSourceIdRef.current); + if (targetSource) { + axisIndex = getAxisIndex(targetSource); + } + } + + if (!targetSource) { + targetSource = sources[0]; + axisIndex = targetSource ? getAxisIndex(targetSource) : -1; + } + + if (!targetSource || axisIndex === -1) { + return; + } + + lastTargetSourceIdRef.current = targetSource.id; + + const baseLoader = targetSource.loader?.[0]; + const shape = baseLoader?.shape; + if (!shape || axisIndex >= shape.length) { + return; + } + + const maxIndex = shape[axisIndex] - 1; + if (maxIndex <= 0) { + return; + } + + const layerAtom = layerFamilyAtom(targetSource); + const layerState = get(layerAtom); + if (!layerState) { + return; + } + + const { layerProps } = layerState; + const selections = layerProps.selections; + if (selections.length === 0) { + return; + } + + const currentIndex = selections[0]?.[axisIndex] ?? 0; + const nextIndex = Math.min(Math.max(currentIndex + delta, 0), maxIndex); + if (nextIndex === currentIndex) { + return; + } + + const nextSelections = selections.map((selection: number[]) => { + const next = [...selection]; + next[axisIndex] = nextIndex; + return next; + }); + + set(layerAtom, { + ...layerState, + layerProps: { + ...layerProps, + selections: nextSelections, + }, + }); + + const defaultSelection = nextSelections[0] ? [...nextSelections[0]] : undefined; + if (!defaultSelection) { + return; + } + + const resolvedTarget = targetSource; + + set(sourceInfoAtom, (prev: typeof sources) => + prev.map((item) => { + if (item.id !== resolvedTarget.id) { + return item; + } + const prevSelection = item.defaults.selection; + const isSame = + prevSelection.length === defaultSelection.length && + prevSelection.every((value: number, index: number) => value === defaultSelection[index]); + if (isSame) { + return item; + } + return { + ...item, + defaults: { + ...item.defaults, + selection: defaultSelection, + }, + }; + }), + ); + }, + [viewport, deckRef], + ), + ); + + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const lower = event.key.toLowerCase(); + if (lower === "z" || lower === "t") { + setAxisScrollKey(lower as Axis); + return; // set when pressing the key + } + + if ( + event.key === "ArrowUp" || + event.key === "ArrowDown" || + event.key === "ArrowLeft" || + event.key === "ArrowRight" + ) { + const axis: Axis = event.key === "ArrowUp" || event.key === "ArrowDown" ? "z" : "t"; + const delta = event.key === "ArrowUp" || event.key === "ArrowLeft" ? -1 : 1; + event.preventDefault(); + void adjustAxis({ axis, delta }); + } + }; + + const handleKeyUp = (event: KeyboardEvent) => { + const lower = event.key.toLowerCase(); + if (lower === "z" || lower === "t") { + setAxisScrollKey((prev) => (prev === lower ? null : prev)); + } // reset when letting go of the key + }; + + const handleBlur = () => { + // reset when switching windows + setAxisScrollKey(null); + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + window.addEventListener("blur", handleBlur); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + window.removeEventListener("blur", handleBlur); + }; + }, [adjustAxis]); + + React.useEffect(() => { + // reset accumulator when axis key changes + axisScrollAccumulatorRef.current = 0; + void axisScrollKey; + }, [axisScrollKey]); + + const handleWheel = React.useCallback( + (event: WheelEvent) => { + if (!axisScrollKey) { + return; // ignore if no axis key is set, fall back to default zoom behavior + } + + const deckInstance = viewport ?? deckRef.current?.deck ?? null; + const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; + if (!deckInstance || !canvas) { + return; // no deck instance or canvas + } + + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + if (x < 0 || y < 0 || x > rect.width || y > rect.height) { + return; // only consider events within the canvas + } + + event.preventDefault(); + event.stopPropagation(); + + axisScrollAccumulatorRef.current += event.deltaY; + const steps = Math.trunc(axisScrollAccumulatorRef.current / AXIS_SCROLL_STEP_DELTA); + if (steps === 0) { + return; + } + + axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; + + const pointer = { x, y }; + void adjustAxis({ axis: axisScrollKey, delta: steps, pointer }); + }, + [axisScrollKey, viewport, deckRef, adjustAxis], + ); + + React.useEffect(() => { + // attach wheel listener to deck canvas + const deckInstance = (viewport ?? deckRef.current?.deck ?? null) as { canvas?: HTMLCanvasElement } | null; + const element = deckInstance?.canvas; + if (!element) { + return; + } + + const listener = (event: WheelEvent) => { + handleWheel(event); + }; + + element.addEventListener("wheel", listener, { passive: false }); + return () => { + element.removeEventListener("wheel", listener); + }; + }, [viewport, handleWheel, deckRef]); +} diff --git a/src/hooks/useAxisScroll.ts b/src/hooks/useAxisScroll.ts deleted file mode 100644 index c4a1e7cb..00000000 --- a/src/hooks/useAxisScroll.ts +++ /dev/null @@ -1,208 +0,0 @@ -import type { DeckGLRef, PickingInfo } from "deck.gl"; -import { useAtomCallback } from "jotai/utils"; -import * as React from "react"; -import { layerFamilyAtom, sourceInfoAtom } from "../state"; - -type DeckInstance = DeckGLRef["deck"] | null; - -const AXIS_SCROLL_STEP_DELTA = 40; - -export function useAxisScroll(deckRef: React.RefObject, viewport: DeckInstance) { - const [axisScrollKey, setAxisScrollKey] = React.useState<"z" | "t" | null>(null); - const axisScrollAccumulatorRef = React.useRef(0); - - React.useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - const key = event.key.toLowerCase(); - if (key === "z" || key === "t") { - setAxisScrollKey(key as "z" | "t"); - } // set when pressing the key - }; - - const handleKeyUp = (event: KeyboardEvent) => { - const key = event.key.toLowerCase(); - if (key === "z" || key === "t") { - setAxisScrollKey((prev) => (prev === key ? null : prev)); - } // reset when letting go of the key - }; - - const handleBlur = () => { - // reset when switching windows - setAxisScrollKey(null); - }; - - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); - window.addEventListener("blur", handleBlur); - return () => { - window.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("keyup", handleKeyUp); - window.removeEventListener("blur", handleBlur); - }; - }, []); - - React.useEffect(() => { - // reset accumulator when axis key changes - axisScrollAccumulatorRef.current = 0; - void axisScrollKey; - }, [axisScrollKey]); - - const handleWheel = useAtomCallback( - // handle wheel events for axis scrolling - React.useCallback( - (get, set, event: WheelEvent) => { - if (!axisScrollKey) { - return; // ignore if no axis key is set, fall back to default zoom behavior - } - - const deckInstance = viewport ?? deckRef.current?.deck ?? null; - const canvas = (deckInstance as { canvas?: HTMLCanvasElement } | null)?.canvas; - if (!deckInstance || !canvas) { - return; // no deck instance or canvas - } - - const rect = canvas.getBoundingClientRect(); - const x = event.clientX - rect.left; - const y = event.clientY - rect.top; - if (x < 0 || y < 0 || x > rect.width || y > rect.height) { - return; // only consider events within the canvas - } - - const picks = (deckInstance.pickMultipleObjects({ x, y, depth: 1 }) ?? []) as PickingInfo[]; - const sources = get(sourceInfoAtom); - if (sources.length === 0) { - return; - } - - const pickedLayerId = (() => { - const pick = picks.find((info: PickingInfo) => info.layer && typeof info.layer.props?.id === "string"); // find the first layer with an id - if (!pick || !pick.layer?.props?.id) { - return undefined; - } - return String(pick.layer.props.id); - })(); - - const targetSource = pickedLayerId - ? (sources.find( - // find source matching picked layer id - (item: (typeof sources)[number]) => - pickedLayerId === item.id || - pickedLayerId.startsWith(`${item.id}_`) || - pickedLayerId.startsWith(`${item.id}-`), - ) ?? sources[0]) - : sources[0]; - - const axisLabels = targetSource.axis_labels ?? []; - const axisIndex = axisLabels.findIndex((axis: string) => axis.toLowerCase() === axisScrollKey); // find the index of the axis to scroll - if (axisIndex === -1) { - return; - } - - // get the max index on this axis - const baseLoader = targetSource.loader?.[0]; - const shape = baseLoader?.shape; - if (!shape || axisIndex >= shape.length) { - return; - } - const maxIndex = shape[axisIndex] - 1; - if (maxIndex <= 0) { - return; - } - - const layerAtom = layerFamilyAtom(targetSource); - const layerState = get(layerAtom); - if (!layerState) { - return; - } - - const { layerProps } = layerState; - const selections = layerProps.selections; - if (selections.length === 0) { - return; - } - - // prevent the default zoom behavior - event.preventDefault(); - event.stopPropagation(); - - // calculate scroll steps - axisScrollAccumulatorRef.current += event.deltaY; - const steps = Math.trunc(axisScrollAccumulatorRef.current / AXIS_SCROLL_STEP_DELTA); - if (steps === 0) { - return; - } - - // keep overflow for next time - axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; - - // keep within bounds - const currentIndex = selections[0]?.[axisIndex] ?? 0; - const nextIndex = Math.min(Math.max(currentIndex + steps, 0), maxIndex); - if (nextIndex === currentIndex) { - return; - } - - // update the selections with the new index - const nextSelections = selections.map((selection: number[]) => { - const next = [...selection]; - next[axisIndex] = nextIndex; - return next; - }); - - set(layerAtom, { - ...layerState, - layerProps: { - ...layerProps, - selections: nextSelections, - }, - }); - - const defaultSelection = nextSelections[0] ? [...nextSelections[0]] : undefined; - if (!defaultSelection) { - return; - } - - set(sourceInfoAtom, (prev: typeof sources) => - prev.map((item: (typeof sources)[number]) => { - if (item.id !== targetSource.id) { - return item; - } - const prevSelection = item.defaults.selection; - const isSame = - prevSelection.length === defaultSelection.length && - prevSelection.every((value: number, index: number) => value === defaultSelection[index]); - if (isSame) { - return item; - } - return { - ...item, - defaults: { - ...item.defaults, - selection: defaultSelection, - }, - }; - }), - ); - }, - [viewport, axisScrollKey, deckRef], - ), - ); - - React.useEffect(() => { - // attach wheel listener to deck canvas - const deckInstance = (viewport ?? deckRef.current?.deck ?? null) as { canvas?: HTMLCanvasElement } | null; - const element = deckInstance?.canvas; - if (!element) { - return; - } - - const listener = (event: WheelEvent) => { - void handleWheel(event); - }; - - element.addEventListener("wheel", listener, { passive: false }); - return () => { - element.removeEventListener("wheel", listener); - }; - }, [viewport, handleWheel, deckRef]); -} From cf07ed46d6baf6c16df92bf1d44c1240f13a52e9 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 17:34:19 +0100 Subject: [PATCH 04/13] add: pyramid loading in plate view --- src/layers/grid-layer.ts | 219 +++++++++++++++++++++++++++++++++------ src/ome.ts | 56 +++++----- src/utils.ts | 2 +- 3 files changed, 219 insertions(+), 58 deletions(-) diff --git a/src/layers/grid-layer.ts b/src/layers/grid-layer.ts index eabb7ac3..c3c8026c 100644 --- a/src/layers/grid-layer.ts +++ b/src/layers/grid-layer.ts @@ -1,15 +1,16 @@ import { CompositeLayer, SolidPolygonLayer, TextLayer } from "deck.gl"; +import type { Viewport } from "deck.gl"; import pMap from "p-map"; import { ColorPaletteExtension, XRLayer } from "@hms-dbmi/viv"; -import type { SupportedTypedArray } from "@vivjs/types"; import type { CompositeLayerProps, PickingInfo, SolidPolygonLayerProps, TextLayerProps } from "deck.gl"; +import type { Matrix4 } from "math.gl"; import type { ZarrPixelSource } from "../ZarrPixelSource"; import { assert } from "../utils"; import type { BaseLayerProps } from "./viv-layers"; export interface GridLoader { - loader: ZarrPixelSource; + sources: ZarrPixelSource[]; row: number; col: number; name: string; @@ -30,6 +31,8 @@ export interface GridLayerProps concurrency?: number; } +const MIN_PIXELS_PER_DATA_PIXEL = 0.5; + function scaleBounds(width: number, height: number, translate = [0, 0], scale = 1) { const [left, top] = translate; const right = width * scale + left; @@ -49,7 +52,7 @@ function validateWidthHeight(d: { data: { width: number; height: number } }[]) { return { width, height }; } -function refreshGridData(props: GridLayerProps) { +function refreshGridData(props: GridLayerProps, level: number) { const { loaders, selections = [] } = props; let { concurrency } = props; if (concurrency && selections.length > 0) { @@ -58,14 +61,22 @@ function refreshGridData(props: GridLayerProps) { concurrency = Math.ceil(concurrency / selections.length); } const mapper = async (d: GridLoader) => { - const promises = selections.map((selection) => d.loader.getRaster({ selection })); + const sources = d.sources; + assert(sources.length > 0, "Grid loader is missing pixel sources"); + const index = Math.min(level, sources.length - 1); + const source = sources[index]; + const promises = selections.map((selection) => source.getRaster({ selection })); const tiles = await Promise.all(promises); + const width = tiles[0]?.width ?? 0; + const height = tiles[0]?.height ?? 0; return { ...d, + source, + sourceIndex: index, data: { - data: tiles.map((d) => d.data), - width: tiles[0].width, - height: tiles[0].height, + data: tiles.map((tile) => tile.data), + width, + height, }, }; }; @@ -74,8 +85,9 @@ function refreshGridData(props: GridLayerProps) { type SharedLayerState = { gridData: Awaited>; - width: number; - height: number; + fullWidth: number; + fullHeight: number; + resolutionLevel: number; }; class GridLayer extends CompositeLayer { @@ -105,11 +117,28 @@ class GridLayer extends CompositeLayer { } initializeState() { - this.#state = { gridData: [], width: 0, height: 0 }; - refreshGridData(this.props).then((gridData) => { - const { width, height } = validateWidthHeight(gridData); - this.setState({ gridData, width, height }); - }); + const fullSize = this.#getFullResolutionSize(this.props.loaders); + const initialLevel = this.#getInitialResolutionLevel(this.props.loaders); + this.#state = { + gridData: [], + fullWidth: fullSize.width, + fullHeight: fullSize.height, + resolutionLevel: initialLevel, + }; + this.#refreshAndSetState(this.props, initialLevel); + } + + // biome-ignore lint/suspicious/noExplicitAny: deck.gl typing does not expose narrowed props + shouldUpdateState({ changeFlags, props, oldProps }: any) { + if (changeFlags.viewportChanged) { + return true; + } + const nextProps = props as GridLayerProps; + const prevProps = oldProps as GridLayerProps; + if (nextProps.selections !== prevProps.selections) { + return true; + } + return Boolean(changeFlags.propsChanged || changeFlags.dataChanged); } updateState({ @@ -121,16 +150,29 @@ class GridLayer extends CompositeLayer { oldProps: GridLayerProps; changeFlags: { propsChanged: string | boolean | null; + viewportChanged?: boolean; }; }) { const { propsChanged } = changeFlags; const loaderChanged = typeof propsChanged === "string" && propsChanged.includes("props.loaders"); const loaderSelectionChanged = props.selections !== oldProps.selections; + if (loaderChanged) { + const fullSize = this.#getFullResolutionSize(props.loaders); + this.setState({ fullWidth: fullSize.width, fullHeight: fullSize.height }); + } + if (loaderChanged || loaderSelectionChanged) { // Only fetch new data to render if loader has changed - refreshGridData(this.props).then((gridData) => { - this.setState({ gridData }); - }); + this.#refreshAndSetState(props, this.#state.resolutionLevel); + return; + } + + if (changeFlags.viewportChanged) { + const level = this.#pickResolutionLevel(props.loaders, this.context.viewport); + if (level !== this.#state.resolutionLevel) { + this.setState({ resolutionLevel: level }); + this.#refreshAndSetState(props, level); + } } } @@ -140,13 +182,13 @@ class GridLayer extends CompositeLayer { return info; } const spacer = this.props.spacer || 0; - const { width, height } = this.#state; - if (width === 0 || height === 0) { + const { fullWidth, fullHeight } = this.#state; + if (fullWidth === 0 || fullHeight === 0) { return info; } const [x, y] = info.coordinate; - const row = Math.floor(y / (height + spacer)); - const column = Math.floor(x / (width + spacer)); + const row = Math.floor(y / (fullHeight + spacer)); + const column = Math.floor(x / (fullWidth + spacer)); const { rows, columns, rowLabels, columnLabels } = this.props; if (row < 0 || column < 0 || row >= rows || column >= columns) { return info; @@ -162,19 +204,18 @@ class GridLayer extends CompositeLayer { } renderLayers() { - const { gridData, width, height } = this.#state; - if (width === 0 || height === 0) return null; // early return if no data + const { gridData, fullWidth, fullHeight } = this.#state; + if (fullWidth === 0 || fullHeight === 0) return null; // early return if no data const { rows, columns, spacer = 0, id = "" } = this.props; - type Data = { row: number; col: number; loader: Pick; data: Array }; const layers = gridData.map((d) => { - const y = d.row * (height + spacer); - const x = d.col * (width + spacer); + const y = d.row * (fullHeight + spacer); + const x = d.col * (fullWidth + spacer); const layerProps = { channelData: d.data, // coerce to null if no data - bounds: scaleBounds(width, height, [x, y]), + bounds: scaleBounds(fullWidth, fullHeight, [x, y]), id: `${id}-GridLayer-${d.row}-${d.col}`, - dtype: d.loader.dtype || "Uint16", // fallback if missing, + dtype: d.source?.dtype || d.sources[0]?.dtype || "Uint16", // fallback if missing, pickable: false, extensions: [new ColorPaletteExtension()], }; @@ -184,8 +225,8 @@ class GridLayer extends CompositeLayer { if (this.props.pickable) { type Data = { polygon: Polygon }; - const bottom = rows * (height + spacer); - const right = columns * (width + spacer); + const bottom = rows * (fullHeight + spacer); + const right = columns * (fullWidth + spacer); const polygon = [ [0, 0], [right, 0], @@ -209,7 +250,7 @@ class GridLayer extends CompositeLayer { const layer = new TextLayer>({ id: `${id}-GridLayer-text`, data: gridData, - getPosition: (d) => [d.col * (width + spacer), d.row * (height + spacer)], + getPosition: (d) => [d.col * (fullWidth + spacer), d.row * (fullHeight + spacer)], getText: (d) => d.name, getColor: [255, 255, 255, 255], getSize: 16, @@ -222,6 +263,122 @@ class GridLayer extends CompositeLayer { return layers; } + + #refreshAndSetState(props: GridLayerProps, level: number) { + refreshGridData(props, level) + .then((gridData) => { + if (this.#state.resolutionLevel !== level) { + return; + } + if (gridData.length > 0) { + validateWidthHeight(gridData); + } + this.setState({ gridData }); + }) + .catch(() => { + if (this.#state.resolutionLevel !== level) { + return; + } + this.setState({ gridData: [] }); + }); + } + + #getFullResolutionSize(loaders: GridLoader[]) { + const first = loaders.find((loader) => loader.sources.length > 0); + if (!first) { + return { width: 0, height: 0 }; + } + return getSourceDimensions(first.sources[0]); + } + + #getMaxValidLevel(loaders: GridLoader[]) { + if (loaders.length === 0) { + return 0; + } + const minSources = loaders.reduce((min, loader) => Math.min(min, loader.sources.length), Number.POSITIVE_INFINITY); + if (!Number.isFinite(minSources)) { + return 0; + } + return Math.max(0, minSources - 1); + } + + #getInitialResolutionLevel(loaders: GridLoader[]) { + return this.#getMaxValidLevel(loaders); + } + + #getLevelDimensions(loaders: GridLoader[]) { + const first = loaders.find((loader) => loader.sources.length > 0); + if (!first) { + return [] as Array<{ width: number; height: number }>; + } + return first.sources.map((source) => getSourceDimensions(source)); + } + + #pickResolutionLevel(loaders: GridLoader[], viewport?: Viewport) { + const maxLevel = this.#getMaxValidLevel(loaders); + if (maxLevel <= 0) { + return 0; + } + const dimensions = this.#getLevelDimensions(loaders).slice(0, maxLevel + 1); + if (dimensions.length <= 1) { + return 0; + } + const screenSize = this.#getCellScreenSize(viewport); + if (!screenSize) { + return this.#state.resolutionLevel; + } + + for (let level = 0; level < dimensions.length; level += 1) { + const { width, height } = dimensions[level]; + if (width === 0 || height === 0) { + continue; + } + const ratio = Math.min(screenSize.width / width, screenSize.height / height); + if (ratio >= MIN_PIXELS_PER_DATA_PIXEL) { + return level; + } + } + return dimensions.length - 1; + } + + #getCellScreenSize(viewport?: Viewport) { + if (!viewport) { + return null; + } + const { fullWidth, fullHeight } = this.#state; + if (fullWidth === 0 || fullHeight === 0) { + return null; + } + const topLeft = this.#applyModelMatrix([0, 0, 0]); + const topRight = this.#applyModelMatrix([fullWidth, 0, 0]); + const bottomLeft = this.#applyModelMatrix([0, fullHeight, 0]); + const projectedTopLeft = viewport.project(topLeft); + const projectedTopRight = viewport.project(topRight); + const projectedBottomLeft = viewport.project(bottomLeft); + const width = Math.abs(projectedTopRight[0] - projectedTopLeft[0]); + const height = Math.abs(projectedBottomLeft[1] - projectedTopLeft[1]); + return { width, height }; + } + + #applyModelMatrix(point: [number, number, number]) { + const matrix = this.props.modelMatrix as Matrix4 | undefined; + if (!matrix) { + return point; + } + const transformed = matrix.transformAsPoint(point); + return [transformed[0], transformed[1], transformed[2] ?? 0]; + } } export { GridLayer }; + +function getSourceDimensions(source: ZarrPixelSource) { + const labels = source.labels as unknown as string[]; + const xIndex = labels.indexOf("x"); + const yIndex = labels.indexOf("y"); + assert(xIndex !== -1 && yIndex !== -1, "Expected pixel source with x/y axes"); + return { + width: source.shape[xIndex], + height: source.shape[yIndex], + }; +} diff --git a/src/ome.ts b/src/ome.ts index 3912ef01..d12e9eca 100644 --- a/src/ome.ts +++ b/src/ome.ts @@ -70,7 +70,7 @@ export async function loadWell( name: String(offset), row, col, - loader: new ZarrPixelSource(data[offset], { labels: axis_labels, tileSize }), + sources: [new ZarrPixelSource(data[offset], { labels: axis_labels, tileSize })], }; }); }); @@ -79,16 +79,16 @@ export async function loadWell( if (utils.isOmeMultiscales(imgAttrs)) { meta = parseOmeroMeta(imgAttrs.omero, axes); } else { - const lowres = loaders.at(-1); + const lowres = loaders.at(-1)?.sources.at(-1); utils.assert(lowres, "Expected at least one resolution, found none."); - meta = await defaultMeta(lowres.loader, axis_labels); + meta = await defaultMeta(lowres, axis_labels); } const sourceData: SourceData = { loaders, ...meta, axis_labels, - loader: [loaders[0].loader], + loader: loaders[0].sources, model_matrix: utils.parseMatrix(config.model_matrix), defaults: { selection: meta.defaultSelection, @@ -159,7 +159,7 @@ export async function loadPlate( // Lowest resolution is the 'path' of the last 'dataset' from the first multiscales const { datasets } = imgAttrs.multiscales[0]; - const resolution = datasets[datasets.length - 1].path; + const datasetPaths = datasets.map((dataset) => dataset.path); async function getImgPath(wellPath: string) { const wellAttrs = await utils.getAttrsOnly<{ well: Ome.Well }>(grp, { @@ -172,40 +172,44 @@ export async function loadPlate( const wellImagePaths = await Promise.all(wellPaths.map(getImgPath)); // Create loader for every Well. Some loaders may be undefined if Wells are missing. - const mapper = async ([key, path]: string[]) => { - // @ts-expect-error - we don't need the meta for these arrays - let arr: zarr.Array = await zarr.open(grp.resolve(path), { - kind: "array", - attrs: false, - }); - return [key, arr] as const; - }; - - const promises = await pMap( - wellImagePaths.map((p) => [p, utils.join(p, resolution)]), - mapper, - { concurrency: 10 }, + const data = await pMap( + wellImagePaths, + async (imagePath) => { + const arrays = await Promise.all( + datasetPaths.map(async (datasetPath) => { + // @ts-expect-error - attrs not needed for pixel data arrays + const arr: zarr.Array = await zarr.open( + grp.resolve(utils.join(imagePath, datasetPath)), + { kind: "array", attrs: false }, + ); + return arr; + }), + ); + return [imagePath, arrays] as const; + }, + { concurrency: 5 }, ); - const data = await Promise.all(promises); const axes = utils.getNgffAxes(imgAttrs.multiscales); const axis_labels = utils.getNgffAxisLabels(axes); - const tileSize = utils.guessTileSize(data[0][1]); - const loaders = data.map((d) => { - const [row, col] = d[0].split("/"); + const loaders = data.map(([path, arrays]) => { + const [row, col] = path.split("/"); + const sources = arrays.map( + (arr) => new ZarrPixelSource(arr, { labels: axis_labels, tileSize: utils.guessTileSize(arr) }), + ); return { name: `${row}${col}`, row: rows.indexOf(row), col: columns.indexOf(col), - loader: new ZarrPixelSource(d[1], { labels: axis_labels, tileSize }), + sources, }; }); let meta: Meta; if ("omero" in imgAttrs) { meta = parseOmeroMeta(imgAttrs.omero, axes); } else { - const lowres = loaders.at(-1); + const lowres = loaders.at(-1)?.sources.at(-1); utils.assert(lowres, "Expected at least one resolution, found none."); - meta = await defaultMeta(lowres.loader, axis_labels); + meta = await defaultMeta(lowres, axis_labels); } // Load Image to use for channel names, rendering settings, sizeZ, sizeT etc. @@ -213,7 +217,7 @@ export async function loadPlate( loaders, ...meta, axis_labels, - loader: [loaders[0].loader], + loader: loaders[0].sources, model_matrix: utils.parseMatrix(config.model_matrix), defaults: { selection: meta.defaultSelection, diff --git a/src/utils.ts b/src/utils.ts index 01ba260d..7709bd6d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -524,7 +524,7 @@ export function isGridLayerProps( export function resolveLoaderFromLayerProps( layerProps: GridLayerProps | ImageLayerProps | MultiscaleImageLayerProps | LabelLayerProps, ) { - return isGridLayerProps(layerProps) ? layerProps.loaders[0].loader : layerProps.loader; + return isGridLayerProps(layerProps) ? layerProps.loaders[0].sources : layerProps.loader; } /** From 3042b45b330d03dacfd9049a2459a16ff2825bf6 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 17:46:24 +0100 Subject: [PATCH 05/13] fix: only update pyramid in wells visible on screen --- src/layers/grid-layer.ts | 55 +++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/layers/grid-layer.ts b/src/layers/grid-layer.ts index c3c8026c..aa832adb 100644 --- a/src/layers/grid-layer.ts +++ b/src/layers/grid-layer.ts @@ -1,10 +1,10 @@ import { CompositeLayer, SolidPolygonLayer, TextLayer } from "deck.gl"; import type { Viewport } from "deck.gl"; +import { Matrix4 } from "math.gl"; import pMap from "p-map"; import { ColorPaletteExtension, XRLayer } from "@hms-dbmi/viv"; import type { CompositeLayerProps, PickingInfo, SolidPolygonLayerProps, TextLayerProps } from "deck.gl"; -import type { Matrix4 } from "math.gl"; import type { ZarrPixelSource } from "../ZarrPixelSource"; import { assert } from "../utils"; import type { BaseLayerProps } from "./viv-layers"; @@ -52,14 +52,15 @@ function validateWidthHeight(d: { data: { width: number; height: number } }[]) { return { width, height }; } -function refreshGridData(props: GridLayerProps, level: number) { - const { loaders, selections = [] } = props; +function refreshGridData(props: GridLayerProps, level: number, viewport?: Viewport) { + const { loaders, selections = [], modelMatrix } = props; let { concurrency } = props; if (concurrency && selections.length > 0) { // There are `loaderSelection.length` requests per loader. This block scales // the provided concurrency to map to the number of actual requests. concurrency = Math.ceil(concurrency / selections.length); } + const visible = viewport ? getVisibleGridCells(loaders, viewport, modelMatrix) : loaders; const mapper = async (d: GridLoader) => { const sources = d.sources; assert(sources.length > 0, "Grid loader is missing pixel sources"); @@ -80,7 +81,7 @@ function refreshGridData(props: GridLayerProps, level: number) { }, }; }; - return pMap(loaders, mapper, { concurrency }); + return pMap(visible, mapper, { concurrency }); } type SharedLayerState = { @@ -125,7 +126,7 @@ class GridLayer extends CompositeLayer { fullHeight: fullSize.height, resolutionLevel: initialLevel, }; - this.#refreshAndSetState(this.props, initialLevel); + this.#refreshAndSetState(this.props, initialLevel, this.context.viewport); } // biome-ignore lint/suspicious/noExplicitAny: deck.gl typing does not expose narrowed props @@ -163,7 +164,7 @@ class GridLayer extends CompositeLayer { if (loaderChanged || loaderSelectionChanged) { // Only fetch new data to render if loader has changed - this.#refreshAndSetState(props, this.#state.resolutionLevel); + this.#refreshAndSetState(props, this.#state.resolutionLevel, this.context.viewport); return; } @@ -171,7 +172,9 @@ class GridLayer extends CompositeLayer { const level = this.#pickResolutionLevel(props.loaders, this.context.viewport); if (level !== this.#state.resolutionLevel) { this.setState({ resolutionLevel: level }); - this.#refreshAndSetState(props, level); + this.#refreshAndSetState(props, level, this.context.viewport); + } else { + this.#refreshAndSetState(props, level, this.context.viewport); } } } @@ -264,8 +267,8 @@ class GridLayer extends CompositeLayer { return layers; } - #refreshAndSetState(props: GridLayerProps, level: number) { - refreshGridData(props, level) + #refreshAndSetState(props: GridLayerProps, level: number, viewport?: Viewport) { + refreshGridData(props, level, viewport) .then((gridData) => { if (this.#state.resolutionLevel !== level) { return; @@ -382,3 +385,37 @@ function getSourceDimensions(source: ZarrPixelSource) { height: source.shape[yIndex], }; } + +function getVisibleGridCells(loaders: GridLoader[], viewport: Viewport, modelMatrix?: Matrix4) { + if (loaders.length === 0) { + return loaders; + } + const first = loaders.find((loader) => loader.sources.length > 0); + if (!first) { + return loaders; + } + const { width, height } = getSourceDimensions(first.sources[0]); + const spacer = 5; + const inverse = modelMatrix ? new Matrix4(modelMatrix).invert() : null; + const corners = [ + viewport.unproject([0, 0, 0]), + viewport.unproject([viewport.width, 0, 0]), + viewport.unproject([viewport.width, viewport.height, 0]), + viewport.unproject([0, viewport.height, 0]), + ]; + const transformed = inverse ? corners.map((corner) => inverse.transformAsPoint(corner)) : corners; + const xs = transformed.map((p) => p[0]); + const ys = transformed.map((p) => p[1]); + const minX = Math.min(...xs); + const maxX = Math.max(...xs); + const minY = Math.min(...ys); + const maxY = Math.max(...ys); + return loaders.filter((loader) => { + const x = loader.col * (width + spacer); + const y = loader.row * (height + spacer); + const right = x + width; + const bottom = y + height; + const intersects = right >= minX && x <= maxX && bottom >= minY && y <= maxY; + return intersects; + }); +} From 11e84054edc36e03d3b5fc748b3f58c25db64e21 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 19:57:32 +0100 Subject: [PATCH 06/13] fix: arrow keybindings in plate view --- src/components/Viewer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 9e39f737..ea5344fe 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -142,6 +142,7 @@ export default function Viewer() { ref={deckRef} layers={deckLayers} viewState={viewState && { ortho: viewState }} + controller={{ keyboard: false }} onViewStateChange={(e: { viewState: OrthographicViewState }) => // @ts-expect-error - deck doesn't know this should be ok setViewState(e.viewState) From 9bce28c61a7bebf403e725f941c92f922c29823d Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Thu, 13 Nov 2025 22:54:43 +0100 Subject: [PATCH 07/13] fix: smoother scrolling --- src/components/Viewer.tsx | 144 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index ea5344fe..fecf9104 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -12,15 +12,114 @@ import type { DeckGLRef, OrthographicViewState, PickingInfo } from "deck.gl"; import type { GrayscaleBitmapLayerPickingInfo } from "../layers/label-layer"; import type { ViewState, VizarrLayer } from "../state"; +const VIEWSTATE_EPSILON = 1e-3; +const VIEWSTATE_COMMIT_DELAY_MS = 50; + +function normalizeTarget(state: OrthographicViewState | ViewState): number[] { + const candidate = (state as OrthographicViewState).target ?? (state as ViewState).target; + return Array.isArray(candidate) ? candidate.map((value) => Number(value)) : []; +} + +function normalizeZoom(state: OrthographicViewState | ViewState): number { + const candidate = (state as OrthographicViewState).zoom ?? (state as ViewState).zoom; + if (Array.isArray(candidate)) { + return candidate[0] ?? 0; + } + return typeof candidate === "number" ? candidate : 0; +} + +function hasViewportDimensions(state: unknown): state is ViewState & { width: number; height: number } { + if (!state || typeof state !== "object") { + return false; + } + const maybe = state as { width?: unknown; height?: unknown }; + return typeof maybe.width === "number" && typeof maybe.height === "number"; +} + +function viewStatesEqual(a: OrthographicViewState | null, b: (OrthographicViewState | ViewState) | null): boolean { + if (!a || !b) { + return a === (b as OrthographicViewState | null); + } + const targetA = normalizeTarget(a); + const targetB = normalizeTarget(b as OrthographicViewState | ViewState); + const length = Math.min(targetA.length, targetB.length); + for (let i = 0; i < length; i += 1) { + if (Math.abs(targetA[i] - targetB[i]) > VIEWSTATE_EPSILON) { + return false; + } + } + const zoomA = normalizeZoom(a); + const zoomB = normalizeZoom(b as OrthographicViewState | ViewState); + return Math.abs(zoomA - zoomB) <= VIEWSTATE_EPSILON; +} + +function mapDeckToViewState(next: OrthographicViewState, prev?: ViewState | null): ViewState { + const target = normalizeTarget(next); + const resolvedTarget = (target.length >= 2 ? [target[0], target[1]] : (prev?.target ?? [0, 0])) as [number, number]; + const zoom = normalizeZoom(next); + const width = + typeof (next as { width?: unknown }).width === "number" ? (next as { width: number }).width : prev?.width; + const height = + typeof (next as { height?: unknown }).height === "number" ? (next as { height: number }).height : prev?.height; + return { + zoom: Number.isFinite(zoom) ? zoom : (prev?.zoom ?? 0), + target: resolvedTarget, + width, + height, + }; +} + export default function Viewer() { const deckRef = React.useRef(null); const [viewport, setViewport] = useAtom(viewportAtom); const [viewState, setViewState] = useViewState(); + const [deckViewState, setDeckViewState] = React.useState(null); const layers = useAtomValue(layerAtoms); const firstLayer = layers[0] as VizarrLayer; useAxisNavigation(deckRef, viewport); + const pendingViewStateRef = React.useRef(null); + const pendingTimeoutRef = React.useRef(null); + const interactionStateRef = React.useRef({ isActive: false }); + + const flushPendingViewState = React.useCallback(() => { + if (pendingTimeoutRef.current !== null) { + window.clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + const next = pendingViewStateRef.current; + pendingViewStateRef.current = null; + if (next) { + setViewState((prev) => mapDeckToViewState(next, prev)); + } + }, [setViewState]); + + const scheduleViewStateCommit = React.useCallback( + (next: OrthographicViewState, immediate = false) => { + pendingViewStateRef.current = next; + if (pendingTimeoutRef.current !== null) { + window.clearTimeout(pendingTimeoutRef.current); + } + if (immediate) { + flushPendingViewState(); + } else { + pendingTimeoutRef.current = window.setTimeout(flushPendingViewState, VIEWSTATE_COMMIT_DELAY_MS); + } + }, + [flushPendingViewState], + ); + + React.useEffect(() => { + return () => { + if (pendingTimeoutRef.current !== null) { + window.clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + pendingViewStateRef.current = null; + }; + }, []); + const resetViewState = React.useCallback( (layer: VizarrLayer) => { const { deck } = deckRef.current || {}; @@ -57,8 +156,28 @@ export default function Viewer() { } }, [viewport, setViewport, firstLayer, resetViewState, viewState, setViewState]); + React.useEffect(() => { + if (!viewState) { + setDeckViewState(null); + if (pendingTimeoutRef.current !== null) { + window.clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + pendingViewStateRef.current = null; + return; + } + if (!viewStatesEqual(pendingViewStateRef.current, viewState)) { + if (pendingTimeoutRef.current !== null) { + window.clearTimeout(pendingTimeoutRef.current); + pendingTimeoutRef.current = null; + } + pendingViewStateRef.current = null; + } + setDeckViewState((prev) => (viewStatesEqual(prev, viewState) ? prev : (viewState as OrthographicViewState))); + }, [viewState]); + const deckLayers = React.useMemo(() => { - if (!firstLayer || !(viewState?.width && viewState?.height)) { + if (!firstLayer || !hasViewportDimensions(viewState)) { return layers; } const loader = resolveLoaderFromLayerProps(firstLayer.props); @@ -68,7 +187,7 @@ export default function Viewer() { id: "scalebar", size: size / firstLayer.props.modelMatrix[0], unit: unit, - viewState: viewState, + viewState: viewState as unknown as OrthographicViewState, snap: false, }); return [...layers, scalebar]; @@ -141,12 +260,23 @@ export default function Viewer() { { + const effective = deckViewState ?? (viewState as OrthographicViewState | null); + return effective ? { ortho: effective } : undefined; + })()} controller={{ keyboard: false }} - onViewStateChange={(e: { viewState: OrthographicViewState }) => - // @ts-expect-error - deck doesn't know this should be ok - setViewState(e.viewState) - } + onViewStateChange={(e: { viewState: OrthographicViewState; interactionState?: { inTransition?: boolean } }) => { + setDeckViewState((prev) => (viewStatesEqual(prev, e.viewState) ? prev : e.viewState)); + const inTransition = e.interactionState?.inTransition ?? false; + scheduleViewStateCommit(e.viewState, !inTransition); + }} + onInteractionStateChange={(state) => { + const isActive = Boolean(state.isDragging || state.isZooming || state.isRotating || state.isPanning); + if (interactionStateRef.current.isActive && !isActive) { + flushPendingViewState(); + } + interactionStateRef.current = { isActive }; + }} views={[new OrthographicView({ id: "ortho", controller: true, near, far })]} glOptions={glOptions} getTooltip={getTooltip} From 9a6b03c174ea5a61f2c7963e515031f8160a55a1 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 14:33:33 +0100 Subject: [PATCH 08/13] add: only load tiles in screen --- .vscode/launch.json | 16 +++ assets/favicon.ico | Bin 359318 -> 88246 bytes assets/favicon.png | Bin 67325 -> 10083 bytes assets/logo.png | Bin 5822 -> 6401 bytes index.html | 11 +- src/ZarrPixelSource.ts | 25 +++- src/layers/grid-layer.ts | 263 ++++++++++++++++++++++++++++++++------- 7 files changed, 259 insertions(+), 56 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..beabd871 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + // "url": "http://localhost:5173/?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0001A/2551.zarr", + "url": "http://localhost:5173", //?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/6001253.zarr", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/assets/favicon.ico b/assets/favicon.ico index 02a653ce42efadccf844ed76512b0a9942cc6db3..b60bdfe90077d1f974a1f54bf491b2cfe891ab62 100644 GIT binary patch literal 88246 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%0ANQp% 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 K37{MCum0a)Lusr4 diff --git a/assets/favicon.png b/assets/favicon.png index 5e793a23cb92a86fd64c29080731e6637942362a..a986115dfb652be3f275484331ec404438b4bea6 100644 GIT binary patch literal 10083 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 BGtvM6 literal 67325 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≪4tcDW<65 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 diff --git a/assets/logo.png b/assets/logo.png index 2d07b8cfce868d0ebc0a8dc4c293c3e01d38c85f..8c72fe723a1334bffd999c31cd1849b5c5a8a44d 100644 GIT binary patch literal 6401 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 literal 5822 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 w@uI5|VC{Jsn0c1We( - - + - + vizarr diff --git a/src/ZarrPixelSource.ts b/src/ZarrPixelSource.ts index 4445dc08..44b3193c 100644 --- a/src/ZarrPixelSource.ts +++ b/src/ZarrPixelSource.ts @@ -83,12 +83,15 @@ export class ZarrPixelSource implements viv.PixelSource> { async getRaster(options: { selection: viv.PixelSourceSelection> | Array; signal?: AbortSignal; + window?: { x: [number, number]; y: [number, number] }; }): Promise { - const { selection, signal } = options; + const { selection, signal, window } = options; + const xSlice = makeSlice(window?.x, this.#width); + const ySlice = makeSlice(window?.y, this.#height); return this.#fetchData({ selection: buildZarrSelection(selection, { labels: this.labels, - slices: { x: zarr.slice(null), y: zarr.slice(null) }, + slices: { x: xSlice, y: ySlice }, }), signal, }); @@ -180,3 +183,21 @@ function capitalize(s: T): Capitalize { // @ts-expect-error - TypeScript can't verify that the return type is correct return s[0].toUpperCase() + s.slice(1); } + +function makeSlice(range: [number, number] | undefined, limit: number) { + if (!range) { + return zarr.slice(null); + } + const [rawStart, rawStop] = range; + if (!Number.isFinite(rawStart) || !Number.isFinite(rawStop)) { + return zarr.slice(null); + } + if (limit <= 0) { + return zarr.slice(null); + } + const maxStart = Math.max(0, limit - 1); + const start = Math.min(Math.max(0, Math.floor(rawStart)), maxStart); + const roundedStop = Math.ceil(rawStop); + const stop = Math.min(limit, Math.max(start + 1, roundedStop)); + return zarr.slice(start, stop); +} diff --git a/src/layers/grid-layer.ts b/src/layers/grid-layer.ts index aa832adb..62b830bf 100644 --- a/src/layers/grid-layer.ts +++ b/src/layers/grid-layer.ts @@ -33,11 +33,147 @@ export interface GridLayerProps const MIN_PIXELS_PER_DATA_PIXEL = 0.5; -function scaleBounds(width: number, height: number, translate = [0, 0], scale = 1) { - const [left, top] = translate; - const right = width * scale + left; - const bottom = height * scale + top; - return [left, bottom, right, top]; +type DeckBounds = [left: number, bottom: number, right: number, top: number]; + +type CellBounds = { + left: number; + right: number; + top: number; + bottom: number; +}; + +type Dimensions = { + width: number; + height: number; +}; + +type VisibleGridCell = { + loader: GridLoader; + viewportBounds: CellBounds; +}; + +type ComputeWindowResult = { + window?: { x: [number, number]; y: [number, number] }; + renderBounds: CellBounds; + coversWholeCell: boolean; +}; + +function clamp(value: number, min: number, max: number) { + if (value < min) { + return min; + } + if (value > max) { + return max; + } + return value; +} + +function getCellBounds(loader: GridLoader, width: number, height: number, spacer: number): CellBounds { + const left = loader.col * (width + spacer); + const top = loader.row * (height + spacer); + const right = left + width; + const bottom = top + height; + return { left, top, right, bottom }; +} + +function toDeckBounds(bounds: CellBounds): DeckBounds { + return [bounds.left, bounds.bottom, bounds.right, bounds.top]; +} + +function intersectBounds(a: CellBounds, b: CellBounds): CellBounds | null { + const left = Math.max(a.left, b.left); + const right = Math.min(a.right, b.right); + const top = Math.max(a.top, b.top); + const bottom = Math.min(a.bottom, b.bottom); + if (right <= left || bottom <= top) { + return null; + } + return { left, right, top, bottom }; +} + +function getViewportBounds(viewport: Viewport, modelMatrix?: Matrix4): CellBounds { + let inverse: Matrix4 | null = null; + if (modelMatrix) { + try { + inverse = new Matrix4(modelMatrix).invert(); + } catch { + inverse = null; + } + } + const corners = [ + viewport.unproject([0, 0, 0]), + viewport.unproject([viewport.width, 0, 0]), + viewport.unproject([viewport.width, viewport.height, 0]), + viewport.unproject([0, viewport.height, 0]), + ]; + const inv = inverse; + const transformed = inv ? corners.map((corner) => inv.transformAsPoint(corner)) : corners; + const xs = transformed.map((p) => p[0]); + const ys = transformed.map((p) => p[1]); + const left = Math.min(...xs); + const right = Math.max(...xs); + const top = Math.min(...ys); + const bottom = Math.max(...ys); + return { left, right, top, bottom }; +} + +function getAllGridCells(loaders: GridLoader[], fullSize: Dimensions, spacer: number): VisibleGridCell[] { + const { width, height } = fullSize; + if (width === 0 || height === 0) { + return []; + } + return loaders + .filter((loader) => loader.sources.length > 0) + .map((loader) => ({ + loader, + viewportBounds: getCellBounds(loader, width, height, spacer), + })); +} + +function computeWindowForSource(options: { + viewportBounds: CellBounds; + cellBounds: CellBounds; + fullSize: Dimensions; + levelSize: Dimensions; +}): ComputeWindowResult { + const { viewportBounds, cellBounds, fullSize, levelSize } = options; + const { width: levelWidth, height: levelHeight } = levelSize; + if (levelWidth === 0 || levelHeight === 0) { + return { window: undefined, renderBounds: cellBounds, coversWholeCell: true }; + } + + const pixelSizeX = fullSize.width / levelWidth; + const pixelSizeY = fullSize.height / levelHeight; + + const localLeft = clamp(viewportBounds.left - cellBounds.left, 0, fullSize.width); + const localRight = clamp(viewportBounds.right - cellBounds.left, 0, fullSize.width); + const localTop = clamp(viewportBounds.top - cellBounds.top, 0, fullSize.height); + const localBottom = clamp(viewportBounds.bottom - cellBounds.top, 0, fullSize.height); + + const xStart = Math.max(0, Math.floor(localLeft / pixelSizeX)); + const xEnd = Math.min(levelWidth, Math.max(xStart + 1, Math.ceil(localRight / pixelSizeX))); + const yStart = Math.max(0, Math.floor(localTop / pixelSizeY)); + const yEnd = Math.min(levelHeight, Math.max(yStart + 1, Math.ceil(localBottom / pixelSizeY))); + + const coversWholeCell = xStart === 0 && xEnd === levelWidth && yStart === 0 && yEnd === levelHeight; + + const renderBounds: CellBounds = coversWholeCell + ? cellBounds + : { + left: cellBounds.left + xStart * pixelSizeX, + right: cellBounds.left + xEnd * pixelSizeX, + top: cellBounds.top + yStart * pixelSizeY, + bottom: cellBounds.top + yEnd * pixelSizeY, + }; + + const window = coversWholeCell + ? undefined + : { + x: [xStart, xEnd] as [number, number], + y: [yStart, yEnd] as [number, number], + }; + + return { window, renderBounds, coversWholeCell }; } function validateWidthHeight(d: { data: { width: number; height: number } }[]) { @@ -60,20 +196,54 @@ function refreshGridData(props: GridLayerProps, level: number, viewport?: Viewpo // the provided concurrency to map to the number of actual requests. concurrency = Math.ceil(concurrency / selections.length); } - const visible = viewport ? getVisibleGridCells(loaders, viewport, modelMatrix) : loaders; - const mapper = async (d: GridLoader) => { - const sources = d.sources; + + if (loaders.length === 0) { + return Promise.resolve([]); + } + + const baseLoader = loaders.find((loader) => loader.sources.length > 0); + if (!baseLoader) { + return Promise.resolve([]); + } + + const fullSize = getSourceDimensions(baseLoader.sources[0]); + if (fullSize.width === 0 || fullSize.height === 0) { + return Promise.resolve([]); + } + + const spacer = props.spacer ?? 0; + + const visibleCells = viewport + ? getVisibleGridCells(loaders, viewport, fullSize, spacer, modelMatrix) + : getAllGridCells(loaders, fullSize, spacer); + + if (visibleCells.length === 0) { + return Promise.resolve([]); + } + + const mapper = async ({ loader, viewportBounds }: VisibleGridCell) => { + const { sources } = loader; assert(sources.length > 0, "Grid loader is missing pixel sources"); - const index = Math.min(level, sources.length - 1); - const source = sources[index]; - const promises = selections.map((selection) => source.getRaster({ selection })); + const sourceIndex = Math.min(level, sources.length - 1); + const source = sources[sourceIndex]; + const levelSize = getSourceDimensions(source); + const cellBounds = getCellBounds(loader, fullSize.width, fullSize.height, spacer); + const { window, renderBounds, coversWholeCell } = computeWindowForSource({ + viewportBounds, + cellBounds, + fullSize, + levelSize, + }); + const promises = selections.map((selection) => source.getRaster({ selection, window })); const tiles = await Promise.all(promises); const width = tiles[0]?.width ?? 0; const height = tiles[0]?.height ?? 0; return { - ...d, + ...loader, + bounds: toDeckBounds(renderBounds), + coversWholeCell, source, - sourceIndex: index, + sourceIndex, data: { data: tiles.map((tile) => tile.data), width, @@ -81,7 +251,8 @@ function refreshGridData(props: GridLayerProps, level: number, viewport?: Viewpo }, }; }; - return pMap(visible, mapper, { concurrency }); + + return pMap(visibleCells, mapper, { concurrency }); } type SharedLayerState = { @@ -212,11 +383,11 @@ class GridLayer extends CompositeLayer { const { rows, columns, spacer = 0, id = "" } = this.props; const layers = gridData.map((d) => { - const y = d.row * (fullHeight + spacer); - const x = d.col * (fullWidth + spacer); + const fallbackBounds = toDeckBounds(getCellBounds(d, fullWidth, fullHeight, spacer)); + const bounds = d.bounds ?? fallbackBounds; const layerProps = { channelData: d.data, // coerce to null if no data - bounds: scaleBounds(fullWidth, fullHeight, [x, y]), + bounds, id: `${id}-GridLayer-${d.row}-${d.col}`, dtype: d.source?.dtype || d.sources[0]?.dtype || "Uint16", // fallback if missing, pickable: false, @@ -274,7 +445,10 @@ class GridLayer extends CompositeLayer { return; } if (gridData.length > 0) { - validateWidthHeight(gridData); + const shouldValidate = gridData.every((entry) => entry.coversWholeCell); + if (shouldValidate) { + validateWidthHeight(gridData); + } } this.setState({ gridData }); }) @@ -386,36 +560,29 @@ function getSourceDimensions(source: ZarrPixelSource) { }; } -function getVisibleGridCells(loaders: GridLoader[], viewport: Viewport, modelMatrix?: Matrix4) { - if (loaders.length === 0) { - return loaders; +function getVisibleGridCells( + loaders: GridLoader[], + viewport: Viewport, + fullSize: Dimensions, + spacer: number, + modelMatrix?: Matrix4, +): VisibleGridCell[] { + const { width, height } = fullSize; + if (loaders.length === 0 || width === 0 || height === 0) { + return []; } - const first = loaders.find((loader) => loader.sources.length > 0); - if (!first) { - return loaders; + + const viewportBounds = getViewportBounds(viewport, modelMatrix); + const visible: VisibleGridCell[] = []; + for (const loader of loaders) { + if (loader.sources.length === 0) { + continue; + } + const cellBounds = getCellBounds(loader, width, height, spacer); + const intersection = intersectBounds(cellBounds, viewportBounds); + if (intersection) { + visible.push({ loader, viewportBounds: intersection }); + } } - const { width, height } = getSourceDimensions(first.sources[0]); - const spacer = 5; - const inverse = modelMatrix ? new Matrix4(modelMatrix).invert() : null; - const corners = [ - viewport.unproject([0, 0, 0]), - viewport.unproject([viewport.width, 0, 0]), - viewport.unproject([viewport.width, viewport.height, 0]), - viewport.unproject([0, viewport.height, 0]), - ]; - const transformed = inverse ? corners.map((corner) => inverse.transformAsPoint(corner)) : corners; - const xs = transformed.map((p) => p[0]); - const ys = transformed.map((p) => p[1]); - const minX = Math.min(...xs); - const maxX = Math.max(...xs); - const minY = Math.min(...ys); - const maxY = Math.max(...ys); - return loaders.filter((loader) => { - const x = loader.col * (width + spacer); - const y = loader.row * (height + spacer); - const right = x + width; - const bottom = y + height; - const intersects = right >= minX && x <= maxX && bottom >= minY && y <= maxY; - return intersects; - }); + return visible; } From 13ed46002d5e3a0825af56ea55e7f1da6cab9439 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 15:02:51 +0100 Subject: [PATCH 09/13] fix: only use arrow keys for z / t axis scrolling when z / t keys are pressed --- src/components/Viewer.tsx | 1 + src/hooks/useAxisNavigation.ts | 51 +++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 9e39f737..db8bc94a 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -142,6 +142,7 @@ export default function Viewer() { ref={deckRef} layers={deckLayers} viewState={viewState && { ortho: viewState }} + controller={{ keyboard: true }} onViewStateChange={(e: { viewState: OrthographicViewState }) => // @ts-expect-error - deck doesn't know this should be ok setViewState(e.viewState) diff --git a/src/hooks/useAxisNavigation.ts b/src/hooks/useAxisNavigation.ts index 240176b1..8211b64a 100644 --- a/src/hooks/useAxisNavigation.ts +++ b/src/hooks/useAxisNavigation.ts @@ -16,10 +16,19 @@ const AXIS_SCROLL_STEP_DELTA = 40; export function useAxisNavigation(deckRef: React.RefObject, viewport: DeckInstance) { const [axisScrollKey, setAxisScrollKey] = React.useState(null); + const axisScrollKeyRef = React.useRef(null); const axisScrollAccumulatorRef = React.useRef(0); const lastPointerRef = React.useRef<{ x: number; y: number } | undefined>(undefined); const lastTargetSourceIdRef = React.useRef(undefined); + const updateAxisScrollKey = React.useCallback( + (nextKey: Axis | null) => { + axisScrollKeyRef.current = nextKey; + setAxisScrollKey(nextKey); + }, + [setAxisScrollKey], + ); + const adjustAxis = useAtomCallback( React.useCallback( (get, set, { axis, delta, pointer }: AdjustArgs) => { @@ -175,19 +184,25 @@ export function useAxisNavigation(deckRef: React.RefObject, viewport: const handleKeyDown = (event: KeyboardEvent) => { const lower = event.key.toLowerCase(); if (lower === "z" || lower === "t") { - setAxisScrollKey(lower as Axis); + event.preventDefault(); + event.stopPropagation(); + updateAxisScrollKey(lower as Axis); return; // set when pressing the key } - if ( - event.key === "ArrowUp" || - event.key === "ArrowDown" || - event.key === "ArrowLeft" || - event.key === "ArrowRight" - ) { - const axis: Axis = event.key === "ArrowUp" || event.key === "ArrowDown" ? "z" : "t"; - const delta = event.key === "ArrowUp" || event.key === "ArrowLeft" ? -1 : 1; + if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "ArrowLeft" || event.key === "ArrowRight") { + const axis = axisScrollKeyRef.current; + if (!axis) { + return; // only respond when an axis key is active + } + if (event.key === "ArrowUp" || event.key === "ArrowDown") { + event.preventDefault(); + event.stopPropagation(); + return; // suppress vertical arrows when an axis key is active + } + const delta = event.key === "ArrowLeft" ? -1 : 1; event.preventDefault(); + event.stopPropagation(); void adjustAxis({ axis, delta }); } }; @@ -195,24 +210,28 @@ export function useAxisNavigation(deckRef: React.RefObject, viewport: const handleKeyUp = (event: KeyboardEvent) => { const lower = event.key.toLowerCase(); if (lower === "z" || lower === "t") { - setAxisScrollKey((prev) => (prev === lower ? null : prev)); + event.preventDefault(); + event.stopPropagation(); + if (axisScrollKeyRef.current === lower) { + updateAxisScrollKey(null); + } } // reset when letting go of the key }; const handleBlur = () => { // reset when switching windows - setAxisScrollKey(null); + updateAxisScrollKey(null); }; - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); + window.addEventListener("keydown", handleKeyDown, true); + window.addEventListener("keyup", handleKeyUp, true); window.addEventListener("blur", handleBlur); return () => { - window.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("keyup", handleKeyUp); + window.removeEventListener("keydown", handleKeyDown, true); + window.removeEventListener("keyup", handleKeyUp, true); window.removeEventListener("blur", handleBlur); }; - }, [adjustAxis]); + }, [adjustAxis, updateAxisScrollKey]); React.useEffect(() => { // reset accumulator when axis key changes From c052cf8c19b8107bb0b34321553659eef21c168e Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 17:27:37 +0100 Subject: [PATCH 10/13] fix: removed launch.json --- .vscode/launch.json | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index beabd871..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - // "url": "http://localhost:5173/?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0001A/2551.zarr", - "url": "http://localhost:5173", //?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/6001253.zarr", - "webRoot": "${workspaceFolder}" - } - ] -} From b6d36a52ad40932583ac0f3a0d9a5ea8926abd50 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 18:19:10 +0100 Subject: [PATCH 11/13] cleanup --- src/components/Viewer.tsx | 144 ++++++++-------- src/layers/grid-layer.ts | 347 ++++++++++++++++++++++---------------- 2 files changed, 266 insertions(+), 225 deletions(-) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index fecf9104..88121b03 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -13,19 +13,27 @@ import type { GrayscaleBitmapLayerPickingInfo } from "../layers/label-layer"; import type { ViewState, VizarrLayer } from "../state"; const VIEWSTATE_EPSILON = 1e-3; -const VIEWSTATE_COMMIT_DELAY_MS = 50; -function normalizeTarget(state: OrthographicViewState | ViewState): number[] { - const candidate = (state as OrthographicViewState).target ?? (state as ViewState).target; - return Array.isArray(candidate) ? candidate.map((value) => Number(value)) : []; -} - -function normalizeZoom(state: OrthographicViewState | ViewState): number { - const candidate = (state as OrthographicViewState).zoom ?? (state as ViewState).zoom; - if (Array.isArray(candidate)) { - return candidate[0] ?? 0; - } - return typeof candidate === "number" ? candidate : 0; +function mapDeckToViewState(next: OrthographicViewState, prev?: ViewState | null): ViewState { + const targetCandidate = (Array.isArray(next.target) ? next.target : prev?.target ?? []) as number[]; + const resolvedTarget: [number, number] = targetCandidate.length >= 2 + ? [Number(targetCandidate[0] ?? 0), Number(targetCandidate[1] ?? 0)] + : prev?.target ?? [0, 0]; + const zoom = typeof next.zoom === "number" ? next.zoom : prev?.zoom ?? 0; + const width = + typeof (next as { width?: unknown }).width === "number" + ? (next as { width: number }).width + : prev?.width; + const height = + typeof (next as { height?: unknown }).height === "number" + ? (next as { height: number }).height + : prev?.height; + return { + zoom, + target: resolvedTarget, + width, + height, + }; } function hasViewportDimensions(state: unknown): state is ViewState & { width: number; height: number } { @@ -36,89 +44,82 @@ function hasViewportDimensions(state: unknown): state is ViewState & { width: nu return typeof maybe.width === "number" && typeof maybe.height === "number"; } -function viewStatesEqual(a: OrthographicViewState | null, b: (OrthographicViewState | ViewState) | null): boolean { +function viewStatesApproximatelyEqual( + a: OrthographicViewState | null, + b: (OrthographicViewState | ViewState) | null, +): boolean { if (!a || !b) { return a === (b as OrthographicViewState | null); } - const targetA = normalizeTarget(a); - const targetB = normalizeTarget(b as OrthographicViewState | ViewState); - const length = Math.min(targetA.length, targetB.length); + const nextTarget = Array.isArray(a.target) ? a.target.map((value) => Number(value)) : []; + const rawPrevTarget = Array.isArray((b as OrthographicViewState).target) + ? (b as OrthographicViewState).target + : ((b as ViewState).target ?? []); + const prevTarget = (rawPrevTarget as number[]).map((value) => Number(value)); + const length = Math.min(nextTarget.length, prevTarget.length); for (let i = 0; i < length; i += 1) { - if (Math.abs(targetA[i] - targetB[i]) > VIEWSTATE_EPSILON) { + if (Math.abs(nextTarget[i] - prevTarget[i]) > VIEWSTATE_EPSILON) { return false; } } - const zoomA = normalizeZoom(a); - const zoomB = normalizeZoom(b as OrthographicViewState | ViewState); + const zoomA = typeof a.zoom === "number" ? a.zoom : 0; + const zoomCandidate = (b as OrthographicViewState).zoom ?? (b as ViewState).zoom; + const zoomB = typeof zoomCandidate === "number" ? zoomCandidate : 0; return Math.abs(zoomA - zoomB) <= VIEWSTATE_EPSILON; } -function mapDeckToViewState(next: OrthographicViewState, prev?: ViewState | null): ViewState { - const target = normalizeTarget(next); - const resolvedTarget = (target.length >= 2 ? [target[0], target[1]] : (prev?.target ?? [0, 0])) as [number, number]; - const zoom = normalizeZoom(next); - const width = - typeof (next as { width?: unknown }).width === "number" ? (next as { width: number }).width : prev?.width; - const height = - typeof (next as { height?: unknown }).height === "number" ? (next as { height: number }).height : prev?.height; - return { - zoom: Number.isFinite(zoom) ? zoom : (prev?.zoom ?? 0), - target: resolvedTarget, - width, - height, - }; -} - export default function Viewer() { const deckRef = React.useRef(null); const [viewport, setViewport] = useAtom(viewportAtom); const [viewState, setViewState] = useViewState(); - const [deckViewState, setDeckViewState] = React.useState(null); + const [localViewState, setLocalViewState] = React.useState(null); const layers = useAtomValue(layerAtoms); const firstLayer = layers[0] as VizarrLayer; useAxisNavigation(deckRef, viewport); const pendingViewStateRef = React.useRef(null); - const pendingTimeoutRef = React.useRef(null); + const pendingFrameRef = React.useRef(); const interactionStateRef = React.useRef({ isActive: false }); - const flushPendingViewState = React.useCallback(() => { - if (pendingTimeoutRef.current !== null) { - window.clearTimeout(pendingTimeoutRef.current); - pendingTimeoutRef.current = null; + const cancelPendingFrame = React.useCallback(() => { + if (pendingFrameRef.current !== undefined) { + window.cancelAnimationFrame(pendingFrameRef.current); + pendingFrameRef.current = undefined; } + }, []); + + const flushPendingViewState = React.useCallback(() => { + cancelPendingFrame(); const next = pendingViewStateRef.current; pendingViewStateRef.current = null; if (next) { setViewState((prev) => mapDeckToViewState(next, prev)); } - }, [setViewState]); + }, [cancelPendingFrame, setViewState]); const scheduleViewStateCommit = React.useCallback( (next: OrthographicViewState, immediate = false) => { pendingViewStateRef.current = next; - if (pendingTimeoutRef.current !== null) { - window.clearTimeout(pendingTimeoutRef.current); - } if (immediate) { flushPendingViewState(); - } else { - pendingTimeoutRef.current = window.setTimeout(flushPendingViewState, VIEWSTATE_COMMIT_DELAY_MS); + return; } + if (pendingFrameRef.current !== undefined) { + return; + } + pendingFrameRef.current = window.requestAnimationFrame(() => { + pendingFrameRef.current = undefined; + flushPendingViewState(); + }); }, [flushPendingViewState], ); - React.useEffect(() => { - return () => { - if (pendingTimeoutRef.current !== null) { - window.clearTimeout(pendingTimeoutRef.current); - pendingTimeoutRef.current = null; - } - pendingViewStateRef.current = null; - }; - }, []); + React.useEffect(() => () => { + cancelPendingFrame(); + pendingViewStateRef.current = null; + }, [cancelPendingFrame]); const resetViewState = React.useCallback( (layer: VizarrLayer) => { @@ -158,23 +159,16 @@ export default function Viewer() { React.useEffect(() => { if (!viewState) { - setDeckViewState(null); - if (pendingTimeoutRef.current !== null) { - window.clearTimeout(pendingTimeoutRef.current); - pendingTimeoutRef.current = null; - } + cancelPendingFrame(); pendingViewStateRef.current = null; + setLocalViewState(null); return; } - if (!viewStatesEqual(pendingViewStateRef.current, viewState)) { - if (pendingTimeoutRef.current !== null) { - window.clearTimeout(pendingTimeoutRef.current); - pendingTimeoutRef.current = null; - } + if (!viewStatesApproximatelyEqual(pendingViewStateRef.current, viewState)) { pendingViewStateRef.current = null; } - setDeckViewState((prev) => (viewStatesEqual(prev, viewState) ? prev : (viewState as OrthographicViewState))); - }, [viewState]); + setLocalViewState((prev) => (viewStatesApproximatelyEqual(prev, viewState) ? prev : (viewState as OrthographicViewState))); + }, [cancelPendingFrame, viewState]); const deckLayers = React.useMemo(() => { if (!firstLayer || !hasViewportDimensions(viewState)) { @@ -260,15 +254,13 @@ export default function Viewer() { { - const effective = deckViewState ?? (viewState as OrthographicViewState | null); - return effective ? { ortho: effective } : undefined; - })()} + viewState={localViewState ? { ortho: localViewState } : undefined} controller={{ keyboard: false }} - onViewStateChange={(e: { viewState: OrthographicViewState; interactionState?: { inTransition?: boolean } }) => { - setDeckViewState((prev) => (viewStatesEqual(prev, e.viewState) ? prev : e.viewState)); - const inTransition = e.interactionState?.inTransition ?? false; - scheduleViewStateCommit(e.viewState, !inTransition); + onViewStateChange={(event: { viewState: OrthographicViewState; interactionState?: { inTransition?: boolean } }) => { + const { viewState: next, interactionState } = event; + setLocalViewState((prev) => (viewStatesApproximatelyEqual(prev, next) ? prev : next)); + const immediate = !(interactionState?.inTransition ?? false); + scheduleViewStateCommit(next, immediate); }} onInteractionStateChange={(state) => { const isActive = Boolean(state.isDragging || state.isZooming || state.isRotating || state.isPanning); diff --git a/src/layers/grid-layer.ts b/src/layers/grid-layer.ts index 62b830bf..fb79fee6 100644 --- a/src/layers/grid-layer.ts +++ b/src/layers/grid-layer.ts @@ -4,6 +4,7 @@ import { Matrix4 } from "math.gl"; import pMap from "p-map"; import { ColorPaletteExtension, XRLayer } from "@hms-dbmi/viv"; +import type { SupportedTypedArray } from "@vivjs/types"; import type { CompositeLayerProps, PickingInfo, SolidPolygonLayerProps, TextLayerProps } from "deck.gl"; import type { ZarrPixelSource } from "../ZarrPixelSource"; import { assert } from "../utils"; @@ -49,13 +50,26 @@ type Dimensions = { type VisibleGridCell = { loader: GridLoader; + cellBounds: CellBounds; viewportBounds: CellBounds; }; -type ComputeWindowResult = { - window?: { x: [number, number]; y: [number, number] }; - renderBounds: CellBounds; +type GridContext = { + fullSize: Dimensions; + spacer: number; + visibleCells: VisibleGridCell[]; +}; + +type GridDataEntry = GridLoader & { + bounds: DeckBounds; coversWholeCell: boolean; + source: ZarrPixelSource; + sourceIndex: number; + data: { + data: SupportedTypedArray[]; + width: number; + height: number; + }; }; function clamp(value: number, min: number, max: number) { @@ -106,28 +120,58 @@ function getViewportBounds(viewport: Viewport, modelMatrix?: Matrix4): CellBound viewport.unproject([viewport.width, viewport.height, 0]), viewport.unproject([0, viewport.height, 0]), ]; - const inv = inverse; - const transformed = inv ? corners.map((corner) => inv.transformAsPoint(corner)) : corners; + const transformed = inverse ? corners.map((corner) => inverse.transformAsPoint(corner)) : corners; const xs = transformed.map((p) => p[0]); const ys = transformed.map((p) => p[1]); - const left = Math.min(...xs); - const right = Math.max(...xs); - const top = Math.min(...ys); - const bottom = Math.max(...ys); - return { left, right, top, bottom }; + return { + left: Math.min(...xs), + right: Math.max(...xs), + top: Math.min(...ys), + bottom: Math.max(...ys), + }; } -function getAllGridCells(loaders: GridLoader[], fullSize: Dimensions, spacer: number): VisibleGridCell[] { - const { width, height } = fullSize; +function getAllGridCells(loaders: GridLoader[], cellSize: Dimensions, spacer: number): VisibleGridCell[] { + const { width, height } = cellSize; if (width === 0 || height === 0) { return []; } return loaders .filter((loader) => loader.sources.length > 0) - .map((loader) => ({ - loader, - viewportBounds: getCellBounds(loader, width, height, spacer), - })); + .map((loader) => { + const cellBounds = getCellBounds(loader, width, height, spacer); + return { + loader, + cellBounds, + viewportBounds: cellBounds, + }; + }); +} + +function getVisibleGridCells( + loaders: GridLoader[], + viewport: Viewport, + cellSize: Dimensions, + spacer: number, + modelMatrix?: Matrix4, +): VisibleGridCell[] { + const { width, height } = cellSize; + if (width === 0 || height === 0) { + return []; + } + const viewportBounds = getViewportBounds(viewport, modelMatrix); + const visible: VisibleGridCell[] = []; + for (const loader of loaders) { + if (loader.sources.length === 0) { + continue; + } + const cellBounds = getCellBounds(loader, width, height, spacer); + const intersection = intersectBounds(cellBounds, viewportBounds); + if (intersection) { + visible.push({ loader, cellBounds, viewportBounds: intersection }); + } + } + return visible; } function computeWindowForSource(options: { @@ -135,7 +179,11 @@ function computeWindowForSource(options: { cellBounds: CellBounds; fullSize: Dimensions; levelSize: Dimensions; -}): ComputeWindowResult { +}): { + window?: { x: [number, number]; y: [number, number] }; + renderBounds: CellBounds; + coversWholeCell: boolean; +} { const { viewportBounds, cellBounds, fullSize, levelSize } = options; const { width: levelWidth, height: levelHeight } = levelSize; if (levelWidth === 0 || levelHeight === 0) { @@ -176,87 +224,111 @@ function computeWindowForSource(options: { return { window, renderBounds, coversWholeCell }; } -function validateWidthHeight(d: { data: { width: number; height: number } }[]) { - const [first] = d; - // Return early if no grid data. Maybe throw an error? - const { width, height } = first.data; - // Verify that all grid data is same shape (ignoring undefined) - for (const { data } of d) { - if (!data) continue; - assert(data.width === width && data.height === height, "Grid data is not same shape."); - } - return { width, height }; -} - -function refreshGridData(props: GridLayerProps, level: number, viewport?: Viewport) { - const { loaders, selections = [], modelMatrix } = props; - let { concurrency } = props; - if (concurrency && selections.length > 0) { - // There are `loaderSelection.length` requests per loader. This block scales - // the provided concurrency to map to the number of actual requests. - concurrency = Math.ceil(concurrency / selections.length); - } - +function buildGridContext(props: GridLayerProps, viewport?: Viewport): GridContext | null { + const { loaders, spacer = 0 } = props; if (loaders.length === 0) { - return Promise.resolve([]); + return null; } - const baseLoader = loaders.find((loader) => loader.sources.length > 0); if (!baseLoader) { - return Promise.resolve([]); + return null; } - const fullSize = getSourceDimensions(baseLoader.sources[0]); if (fullSize.width === 0 || fullSize.height === 0) { - return Promise.resolve([]); + return null; } - - const spacer = props.spacer ?? 0; - const visibleCells = viewport - ? getVisibleGridCells(loaders, viewport, fullSize, spacer, modelMatrix) + ? getVisibleGridCells(loaders, viewport, fullSize, spacer, props.modelMatrix as Matrix4 | undefined) : getAllGridCells(loaders, fullSize, spacer); + return { fullSize, spacer, visibleCells }; +} - if (visibleCells.length === 0) { - return Promise.resolve([]); +function getEffectiveConcurrency(concurrency: number | undefined, selectionCount: number) { + if (!concurrency) { + return undefined; + } + if (selectionCount <= 0) { + return concurrency; } + return Math.max(1, Math.ceil(concurrency / selectionCount)); +} - const mapper = async ({ loader, viewportBounds }: VisibleGridCell) => { - const { sources } = loader; - assert(sources.length > 0, "Grid loader is missing pixel sources"); - const sourceIndex = Math.min(level, sources.length - 1); - const source = sources[sourceIndex]; - const levelSize = getSourceDimensions(source); - const cellBounds = getCellBounds(loader, fullSize.width, fullSize.height, spacer); - const { window, renderBounds, coversWholeCell } = computeWindowForSource({ - viewportBounds, - cellBounds, - fullSize, - levelSize, - }); - const promises = selections.map((selection) => source.getRaster({ selection, window })); - const tiles = await Promise.all(promises); - const width = tiles[0]?.width ?? 0; - const height = tiles[0]?.height ?? 0; - return { - ...loader, - bounds: toDeckBounds(renderBounds), - coversWholeCell, - source, - sourceIndex, - data: { - data: tiles.map((tile) => tile.data), - width, - height, - }, - }; +async function loadVisibleCell( + cell: VisibleGridCell, + level: number, + selections: number[][], + context: GridContext, +): Promise { + const { loader } = cell; + assert(loader.sources.length > 0, "Grid loader is missing pixel sources"); + const sourceIndex = Math.min(level, loader.sources.length - 1); + const source = loader.sources[sourceIndex]; + const levelSize = getSourceDimensions(source); + const { window, renderBounds, coversWholeCell } = computeWindowForSource({ + viewportBounds: cell.viewportBounds, + cellBounds: cell.cellBounds, + fullSize: context.fullSize, + levelSize, + }); + const tiles = await Promise.all(selections.map((selection) => source.getRaster({ selection, window }))); + const firstTile = tiles[0]; + const width = firstTile?.width ?? 0; + const height = firstTile?.height ?? 0; + return { + ...loader, + bounds: toDeckBounds(renderBounds), + coversWholeCell, + source, + sourceIndex, + data: { + data: tiles.map((tile) => tile.data) as SupportedTypedArray[], + width, + height, + }, }; +} + +function refreshGridData( + context: GridContext, + level: number, + selections: number[][], + concurrency?: number, +): Promise { + if (context.visibleCells.length === 0) { + return Promise.resolve([]); + } + const effectiveConcurrency = getEffectiveConcurrency(concurrency, selections.length); + return pMap(context.visibleCells, (cell) => loadVisibleCell(cell, level, selections, context), { + concurrency: effectiveConcurrency, + }); +} + +function validateWidthHeight(data: GridDataEntry[]) { + const [first] = data; + const { width, height } = first.data; + for (const entry of data) { + const current = entry.data; + if (!current) { + continue; + } + assert(current.width === width && current.height === height, "Grid data is not same shape."); + } + return { width, height }; +} - return pMap(visibleCells, mapper, { concurrency }); +function getSourceDimensions(source: ZarrPixelSource) { + const labels = source.labels as unknown as string[]; + const xIndex = labels.indexOf("x"); + const yIndex = labels.indexOf("y"); + assert(xIndex !== -1 && yIndex !== -1, "Expected pixel source with x/y axes"); + return { + width: source.shape[xIndex], + height: source.shape[yIndex], + }; } type SharedLayerState = { - gridData: Awaited>; + gridData: GridDataEntry[]; fullWidth: number; fullHeight: number; resolutionLevel: number; @@ -267,14 +339,12 @@ class GridLayer extends CompositeLayer { static defaultProps = { // @ts-expect-error - XRLayer props are not typed ...XRLayer.defaultProps, - // Special grid props loaders: { type: "array", value: [], compare: true }, spacer: { type: "number", value: 5, compare: true }, rows: { type: "number", value: 0, compare: true }, columns: { type: "number", value: 0, compare: true }, - concurrency: { type: "number", value: 10, compare: false }, // set concurrency for queue + concurrency: { type: "number", value: 10, compare: false }, text: { type: "boolean", value: false, compare: true }, - // Deck.gl onClick: { type: "function", value: null, compare: true }, onHover: { type: "function", value: null, compare: true }, }; @@ -289,15 +359,17 @@ class GridLayer extends CompositeLayer { } initializeState() { - const fullSize = this.#getFullResolutionSize(this.props.loaders); const initialLevel = this.#getInitialResolutionLevel(this.props.loaders); + const context = buildGridContext(this.props, this.context.viewport); this.#state = { gridData: [], - fullWidth: fullSize.width, - fullHeight: fullSize.height, + fullWidth: context?.fullSize.width ?? 0, + fullHeight: context?.fullSize.height ?? 0, resolutionLevel: initialLevel, }; - this.#refreshAndSetState(this.props, initialLevel, this.context.viewport); + if (context) { + this.#refreshAndSetState(this.props, initialLevel, this.context.viewport, context); + } } // biome-ignore lint/suspicious/noExplicitAny: deck.gl typing does not expose narrowed props @@ -327,15 +399,18 @@ class GridLayer extends CompositeLayer { }) { const { propsChanged } = changeFlags; const loaderChanged = typeof propsChanged === "string" && propsChanged.includes("props.loaders"); - const loaderSelectionChanged = props.selections !== oldProps.selections; + const selectionChanged = props.selections !== oldProps.selections; + const context = buildGridContext(props, this.context.viewport); + if (loaderChanged) { - const fullSize = this.#getFullResolutionSize(props.loaders); - this.setState({ fullWidth: fullSize.width, fullHeight: fullSize.height }); + this.setState({ + fullWidth: context?.fullSize.width ?? 0, + fullHeight: context?.fullSize.height ?? 0, + }); } - if (loaderChanged || loaderSelectionChanged) { - // Only fetch new data to render if loader has changed - this.#refreshAndSetState(props, this.#state.resolutionLevel, this.context.viewport); + if (loaderChanged || selectionChanged) { + this.#refreshAndSetState(props, this.#state.resolutionLevel, this.context.viewport, context ?? undefined); return; } @@ -343,15 +418,12 @@ class GridLayer extends CompositeLayer { const level = this.#pickResolutionLevel(props.loaders, this.context.viewport); if (level !== this.#state.resolutionLevel) { this.setState({ resolutionLevel: level }); - this.#refreshAndSetState(props, level, this.context.viewport); - } else { - this.#refreshAndSetState(props, level, this.context.viewport); } + this.#refreshAndSetState(props, level, this.context.viewport, context ?? undefined); } } getPickingInfo({ info }: { info: PickingInfo }) { - // provide Grid row and column info for mouse events (hover & click) if (!info.coordinate) { return info; } @@ -379,17 +451,17 @@ class GridLayer extends CompositeLayer { renderLayers() { const { gridData, fullWidth, fullHeight } = this.#state; - if (fullWidth === 0 || fullHeight === 0) return null; // early return if no data + if (fullWidth === 0 || fullHeight === 0) { + return null; + } const { rows, columns, spacer = 0, id = "" } = this.props; - const layers = gridData.map((d) => { - const fallbackBounds = toDeckBounds(getCellBounds(d, fullWidth, fullHeight, spacer)); - const bounds = d.bounds ?? fallbackBounds; + const layers = gridData.map((entry) => { const layerProps = { - channelData: d.data, // coerce to null if no data - bounds, - id: `${id}-GridLayer-${d.row}-${d.col}`, - dtype: d.source?.dtype || d.sources[0]?.dtype || "Uint16", // fallback if missing, + channelData: entry.data, + bounds: entry.bounds, + id: `${id}-GridLayer-${entry.row}-${entry.col}`, + dtype: entry.source?.dtype || entry.sources[0]?.dtype || "Uint16", pickable: false, extensions: [new ColorPaletteExtension()], }; @@ -409,10 +481,10 @@ class GridLayer extends CompositeLayer { ] satisfies Polygon; const layerProps = { data: [{ polygon }], - getPolygon: (d) => d.polygon, - getFillColor: [0, 0, 0, 0], // transparent + getPolygon: (d: Data) => d.polygon, + getFillColor: [0, 0, 0, 0], getLineColor: [0, 0, 0, 0], - pickable: true, // enable picking + pickable: true, id: `${id}-GridLayer-picking`, } satisfies SolidPolygonLayerProps; const layer = new SolidPolygonLayer>({ ...this.props, ...layerProps }); @@ -438,8 +510,19 @@ class GridLayer extends CompositeLayer { return layers; } - #refreshAndSetState(props: GridLayerProps, level: number, viewport?: Viewport) { - refreshGridData(props, level, viewport) + #refreshAndSetState( + props: GridLayerProps, + level: number, + viewport?: Viewport, + context?: GridContext | null, + ) { + const resolvedContext = context ?? buildGridContext(props, viewport); + if (!resolvedContext) { + this.setState({ gridData: [] }); + return; + } + const selections = props.selections ?? []; + refreshGridData(resolvedContext, level, selections, props.concurrency) .then((gridData) => { if (this.#state.resolutionLevel !== level) { return; @@ -450,7 +533,11 @@ class GridLayer extends CompositeLayer { validateWidthHeight(gridData); } } - this.setState({ gridData }); + this.setState({ + gridData, + fullWidth: resolvedContext.fullSize.width, + fullHeight: resolvedContext.fullSize.height, + }); }) .catch(() => { if (this.#state.resolutionLevel !== level) { @@ -548,41 +635,3 @@ class GridLayer extends CompositeLayer { } export { GridLayer }; - -function getSourceDimensions(source: ZarrPixelSource) { - const labels = source.labels as unknown as string[]; - const xIndex = labels.indexOf("x"); - const yIndex = labels.indexOf("y"); - assert(xIndex !== -1 && yIndex !== -1, "Expected pixel source with x/y axes"); - return { - width: source.shape[xIndex], - height: source.shape[yIndex], - }; -} - -function getVisibleGridCells( - loaders: GridLoader[], - viewport: Viewport, - fullSize: Dimensions, - spacer: number, - modelMatrix?: Matrix4, -): VisibleGridCell[] { - const { width, height } = fullSize; - if (loaders.length === 0 || width === 0 || height === 0) { - return []; - } - - const viewportBounds = getViewportBounds(viewport, modelMatrix); - const visible: VisibleGridCell[] = []; - for (const loader of loaders) { - if (loader.sources.length === 0) { - continue; - } - const cellBounds = getCellBounds(loader, width, height, spacer); - const intersection = intersectBounds(cellBounds, viewportBounds); - if (intersection) { - visible.push({ loader, viewportBounds: intersection }); - } - } - return visible; -} From c2050193fbf5871c23b92a7221c2c1d86841cb76 Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 18:38:04 +0100 Subject: [PATCH 12/13] cleanup / linting --- src/components/Viewer.tsx | 39 ++++++++++++++++++++++----------------- src/layers/grid-layer.ts | 15 +-------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/components/Viewer.tsx b/src/components/Viewer.tsx index 88121b03..5d584a43 100644 --- a/src/components/Viewer.tsx +++ b/src/components/Viewer.tsx @@ -15,19 +15,16 @@ import type { ViewState, VizarrLayer } from "../state"; const VIEWSTATE_EPSILON = 1e-3; function mapDeckToViewState(next: OrthographicViewState, prev?: ViewState | null): ViewState { - const targetCandidate = (Array.isArray(next.target) ? next.target : prev?.target ?? []) as number[]; - const resolvedTarget: [number, number] = targetCandidate.length >= 2 - ? [Number(targetCandidate[0] ?? 0), Number(targetCandidate[1] ?? 0)] - : prev?.target ?? [0, 0]; - const zoom = typeof next.zoom === "number" ? next.zoom : prev?.zoom ?? 0; + const targetCandidate = (Array.isArray(next.target) ? next.target : (prev?.target ?? [])) as number[]; + const resolvedTarget: [number, number] = + targetCandidate.length >= 2 + ? [Number(targetCandidate[0] ?? 0), Number(targetCandidate[1] ?? 0)] + : (prev?.target ?? [0, 0]); + const zoom = typeof next.zoom === "number" ? next.zoom : (prev?.zoom ?? 0); const width = - typeof (next as { width?: unknown }).width === "number" - ? (next as { width: number }).width - : prev?.width; + typeof (next as { width?: unknown }).width === "number" ? (next as { width: number }).width : prev?.width; const height = - typeof (next as { height?: unknown }).height === "number" - ? (next as { height: number }).height - : prev?.height; + typeof (next as { height?: unknown }).height === "number" ? (next as { height: number }).height : prev?.height; return { zoom, target: resolvedTarget, @@ -116,10 +113,13 @@ export default function Viewer() { [flushPendingViewState], ); - React.useEffect(() => () => { - cancelPendingFrame(); - pendingViewStateRef.current = null; - }, [cancelPendingFrame]); + React.useEffect( + () => () => { + cancelPendingFrame(); + pendingViewStateRef.current = null; + }, + [cancelPendingFrame], + ); const resetViewState = React.useCallback( (layer: VizarrLayer) => { @@ -167,7 +167,9 @@ export default function Viewer() { if (!viewStatesApproximatelyEqual(pendingViewStateRef.current, viewState)) { pendingViewStateRef.current = null; } - setLocalViewState((prev) => (viewStatesApproximatelyEqual(prev, viewState) ? prev : (viewState as OrthographicViewState))); + setLocalViewState((prev) => + viewStatesApproximatelyEqual(prev, viewState) ? prev : (viewState as OrthographicViewState), + ); }, [cancelPendingFrame, viewState]); const deckLayers = React.useMemo(() => { @@ -256,7 +258,10 @@ export default function Viewer() { layers={deckLayers} viewState={localViewState ? { ortho: localViewState } : undefined} controller={{ keyboard: false }} - onViewStateChange={(event: { viewState: OrthographicViewState; interactionState?: { inTransition?: boolean } }) => { + onViewStateChange={(event: { + viewState: OrthographicViewState; + interactionState?: { inTransition?: boolean }; + }) => { const { viewState: next, interactionState } = event; setLocalViewState((prev) => (viewStatesApproximatelyEqual(prev, next) ? prev : next)); const immediate = !(interactionState?.inTransition ?? false); diff --git a/src/layers/grid-layer.ts b/src/layers/grid-layer.ts index fb79fee6..b6337f51 100644 --- a/src/layers/grid-layer.ts +++ b/src/layers/grid-layer.ts @@ -510,12 +510,7 @@ class GridLayer extends CompositeLayer { return layers; } - #refreshAndSetState( - props: GridLayerProps, - level: number, - viewport?: Viewport, - context?: GridContext | null, - ) { + #refreshAndSetState(props: GridLayerProps, level: number, viewport?: Viewport, context?: GridContext | null) { const resolvedContext = context ?? buildGridContext(props, viewport); if (!resolvedContext) { this.setState({ gridData: [] }); @@ -547,14 +542,6 @@ class GridLayer extends CompositeLayer { }); } - #getFullResolutionSize(loaders: GridLoader[]) { - const first = loaders.find((loader) => loader.sources.length > 0); - if (!first) { - return { width: 0, height: 0 }; - } - return getSourceDimensions(first.sources[0]); - } - #getMaxValidLevel(loaders: GridLoader[]) { if (loaders.length === 0) { return 0; From 160a75c41e413790038351e6bbe21eb6d5d2ff3c Mon Sep 17 00:00:00 2001 From: Joris Gentinetta Date: Fri, 14 Nov 2025 20:23:33 +0100 Subject: [PATCH 13/13] fix: invert scroll direction for z / t --- src/hooks/useAxisNavigation.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/hooks/useAxisNavigation.ts b/src/hooks/useAxisNavigation.ts index 8211b64a..ab5c5a7a 100644 --- a/src/hooks/useAxisNavigation.ts +++ b/src/hooks/useAxisNavigation.ts @@ -21,13 +21,10 @@ export function useAxisNavigation(deckRef: React.RefObject, viewport: const lastPointerRef = React.useRef<{ x: number; y: number } | undefined>(undefined); const lastTargetSourceIdRef = React.useRef(undefined); - const updateAxisScrollKey = React.useCallback( - (nextKey: Axis | null) => { - axisScrollKeyRef.current = nextKey; - setAxisScrollKey(nextKey); - }, - [setAxisScrollKey], - ); + const updateAxisScrollKey = React.useCallback((nextKey: Axis | null) => { + axisScrollKeyRef.current = nextKey; + setAxisScrollKey(nextKey); + }, []); const adjustAxis = useAtomCallback( React.useCallback( @@ -190,7 +187,12 @@ export function useAxisNavigation(deckRef: React.RefObject, viewport: return; // set when pressing the key } - if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "ArrowLeft" || event.key === "ArrowRight") { + if ( + event.key === "ArrowUp" || + event.key === "ArrowDown" || + event.key === "ArrowLeft" || + event.key === "ArrowRight" + ) { const axis = axisScrollKeyRef.current; if (!axis) { return; // only respond when an axis key is active @@ -270,7 +272,7 @@ export function useAxisNavigation(deckRef: React.RefObject, viewport: axisScrollAccumulatorRef.current -= steps * AXIS_SCROLL_STEP_DELTA; const pointer = { x, y }; - void adjustAxis({ axis: axisScrollKey, delta: steps, pointer }); + void adjustAxis({ axis: axisScrollKey, delta: -steps, pointer }); }, [axisScrollKey, viewport, deckRef, adjustAxis], );