From 0b811a267c68746350e6deeadb4211df3ef33a33 Mon Sep 17 00:00:00 2001 From: Jeran Date: Wed, 17 Jun 2026 16:57:27 +0200 Subject: [PATCH 1/6] Added component --- src/GlobalStates/GlobalStore.ts | 2 + src/components/ui/Elements/DimensionOrder.tsx | 54 +++++++++++++++++++ src/components/ui/MainPanel/MetaDataInfo.tsx | 17 ++++-- 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 src/components/ui/Elements/DimensionOrder.tsx diff --git a/src/GlobalStates/GlobalStore.ts b/src/GlobalStates/GlobalStore.ts index bea8ab47..fa1f6889 100644 --- a/src/GlobalStates/GlobalStore.ts +++ b/src/GlobalStates/GlobalStore.ts @@ -47,6 +47,7 @@ type StoreState = { textureArrayDepths: number[]; textureData: Uint8Array; clampExtremes: boolean; + permute: number[]; // setters setDataShape: (dataShape: number[]) => void; @@ -116,6 +117,7 @@ export const useGlobalStore = create((set, get) => ({ DPR: 1, scalingFactor: null, clampExtremes: false, + permute: [0, 1, 2], //Dim permutations // setters setDataShape: (dataShape) => set({ dataShape }), diff --git a/src/components/ui/Elements/DimensionOrder.tsx b/src/components/ui/Elements/DimensionOrder.tsx new file mode 100644 index 00000000..d6b34ae6 --- /dev/null +++ b/src/components/ui/Elements/DimensionOrder.tsx @@ -0,0 +1,54 @@ +import React, {useRef, useState} from 'react' +import { useGlobalStore } from '@/GlobalStates/GlobalStore' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +export const DimensionOrder = ({dimNames}: {dimNames: string[]}) => { + const slotCount = Math.min(3, dimNames.length); + const permuteRef = useRef( + Array.from({ length: slotCount }, (_, i) => i) + ); + const [warnDuplicates, setWarnDuplicates] = useState(false); + + const handleChange = (slotIdx: number, dimIdx: number) => { + permuteRef.current[slotIdx] = dimIdx; + useGlobalStore.setState({ permute: [...permuteRef.current] }); + const permuteArray = permuteRef.current; + setWarnDuplicates(permuteArray.length != new Set(permuteArray).size) + }; + + return ( + <> +
+ {Array.from({ length: slotCount }).map((_, slotIdx) => ( + + ))} +
+ {warnDuplicates &&

+ DUPLICATE DIMENSION USED +

} + + ); +}; + + diff --git a/src/components/ui/MainPanel/MetaDataInfo.tsx b/src/components/ui/MainPanel/MetaDataInfo.tsx index 53abdfef..57d2cb19 100644 --- a/src/components/ui/MainPanel/MetaDataInfo.tsx +++ b/src/components/ui/MainPanel/MetaDataInfo.tsx @@ -8,6 +8,7 @@ import { SliderThumbs } from "@/components/ui/Widgets/SliderThumbs" import Metadata, { defaultAttributes, renderAttributes } from "@/components/ui/MetaData" import { BsFillQuestionCircleFill } from "react-icons/bs"; import { parseLoc, HandleCoarselNums } from "@/utils/HelperFuncs" +import { DimensionOrder } from "../Elements/DimensionOrder"; import { Tooltip, TooltipContent, @@ -58,9 +59,9 @@ function HandleCustomSteps(e: string, chunkSize: number){ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSide }: { meta: any, metadata: Record, setShowMeta: React.Dispatch>, setOpenVariables: (open: boolean) => void, popoverSide: string }) => { - const {is4D, idx4D, variable, initStore, setIs4D, setIdx4D, setVariable, setTextureArrayDepths} = useGlobalStore(useShallow(state => ({ + const {is4D, idx4D, variable, initStore, permute, setIs4D, setIdx4D, setVariable, setTextureArrayDepths} = useGlobalStore(useShallow(state => ({ is4D: state.is4D, idx4D: state.idx4D, variable: state.variable, - initStore: state.initStore, + initStore: state.initStore, permute:state.permute, setIs4D: state.setIs4D, setIdx4D: state.setIdx4D, setVariable: state.setVariable, setTextureArrayDepths: state.setTextureArrayDepths, }))) @@ -84,6 +85,11 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi const [displaySpat, setDisplaySpat] = useState(String(kernelSize)) const [displayDepth, setDisplayDepth] = useState(String(kernelDepth)) + function permuteArr(arr: number[]) { + return permute.map(i => arr[i]); + } + + // ---- Meta Info ---- // const {dimArrays, dimNames, dimUnits} = meta.dimInfo const totalSize = meta.totalSize ? meta.totalSize : 0 @@ -190,7 +196,7 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi } },[currentSize, meta]) - const smallCache = cachedSize > cacheSize + const smallCache = cachedSize > cacheSize // If the current cache is too small useEffect(()=>{ const this4D = meta.shape.length == 4; @@ -246,15 +252,16 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi
}
+
Data Shape - {`[${formatArray(dataShape)}]`} + {`[${formatArray(permuteArr(dataShape))}]`}
Chunk Shape - {`[${formatArray(chunkShape)}]`} + {`[${formatArray(permuteArr(chunkShape))}]`}
From da924b5d2911de6d5315bd09cbd20159960dde06 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 18 Jun 2026 10:25:37 +0200 Subject: [PATCH 2/6] Initial plotting with appropriate axis lines working. Only works on 3D datasets I believe --- src/GlobalStates/PlotStore.ts | 2 + src/components/plots/AxisLines.tsx | 16 +++-- src/components/plots/Plot.tsx | 2 +- src/components/ui/Elements/DimensionOrder.tsx | 16 +++-- src/components/ui/MainPanel/MetaDataInfo.tsx | 10 +-- src/components/zarr/GetArray.ts | 56 ++++++++-------- src/hooks/useDataFetcher.tsx | 17 +++-- src/utils/HelperFuncs.ts | 66 ++++++++++++++++++- 8 files changed, 128 insertions(+), 57 deletions(-) diff --git a/src/GlobalStates/PlotStore.ts b/src/GlobalStates/PlotStore.ts index 5f0e34f7..4fb3a0aa 100644 --- a/src/GlobalStates/PlotStore.ts +++ b/src/GlobalStates/PlotStore.ts @@ -64,6 +64,7 @@ type PlotState ={ maskValue: number; cameraPosition: THREE.Vector3; disablePointScale: boolean; + permute: number[]; // Need one in plotStore as well so plot states don't change when permute changes from UI setQuality: (quality: number) => void; setTimeScale: (timeScale : number) =>void; @@ -185,6 +186,7 @@ export const usePlotStore = create((set, get) => ({ borderWidth: 0.05, cameraPosition: new THREE.Vector3(0, 0, 5), disablePointScale: false, + permute: [0,1,2], setVTransferRange: (vTransferRange) => set({ vTransferRange }), setVTransferScale: (vTransferScale) => set({ vTransferScale }), diff --git a/src/components/plots/AxisLines.tsx b/src/components/plots/AxisLines.tsx index b21196bb..aadd2f53 100644 --- a/src/components/plots/AxisLines.tsx +++ b/src/components/plots/AxisLines.tsx @@ -12,7 +12,7 @@ import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js import { LineSegments2 } from 'three/addons/lines/LineSegments2.js'; import { LineMaterial } from 'three-stdlib'; import { useFrame } from '@react-three/fiber'; -import { parseLoc, coarsenFlatArray } from '@/utils/HelperFuncs'; +import { parseLoc, coarsenFlatArray, permuteArr } from '@/utils/HelperFuncs'; import { useCSSVariable } from '../ui'; import * as THREE from 'three' @@ -41,12 +41,12 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli is4D: state.is4D }))) - const {xRange, yRange, zRange, plotType, timeScale, animProg, zSlice, ySlice, xSlice, coarsen} = usePlotStore(useShallow(state => ({ + const {xRange, yRange, zRange, plotType, timeScale, animProg, zSlice, ySlice, xSlice, coarsen, permute} = usePlotStore(useShallow(state => ({ xRange: state.xRange, yRange: state.yRange, zRange: state.zRange, plotType: state.plotType, timeScale: state.timeScale, animProg: state.animProg, zSlice: state.zSlice, ySlice: state.ySlice, - xSlice: state.xSlice, coarsen: state.coarsen, + xSlice: state.xSlice, coarsen: state.coarsen, permute:state.permute }))) const {hideAxis, hideAxisControls} = useImageExportStore(useShallow( state => ({ hideAxis: state.hideAxis, @@ -73,13 +73,15 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const [yResolution, setYResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) const [zResolution, setZResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) + const permuteShape = useMemo(()=>permuteArr(dataShape,permute),[permute]) const isPC = useMemo(()=>plotType == 'point-cloud',[plotType]) - const globalScale = isPC ? dataShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 + const globalScale = isPC ? permuteShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 + - const depthRatio = useMemo(()=>dataShape[0]/dataShape[2]*timeScale,[dataShape, timeScale]); - const shapeRatio = useMemo(()=>dataShape[1]/dataShape[2], [dataShape]) - const timeRatio = Math.max(dataShape[0]/dataShape[2], 2); + const depthRatio = useMemo(()=>permuteShape[0]/permuteShape[2]*timeScale,[permuteShape, timeScale]); + const shapeRatio = useMemo(()=>permuteShape[1]/permuteShape[2], [permuteShape]) + const timeRatio = Math.max(permuteShape[0]/permuteShape[2], 2); const secondaryColor = useCSSVariable('--text-plot') //replace with needed variable const colorHex = useMemo(()=>{ diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index d54e6ab3..f3128d63 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -225,7 +225,7 @@ const Plot = () => { dpr={[DPR,DPR]} > - + {/* */} {show && } {plotType == "volume" && show && diff --git a/src/components/ui/Elements/DimensionOrder.tsx b/src/components/ui/Elements/DimensionOrder.tsx index d6b34ae6..b2bca9d6 100644 --- a/src/components/ui/Elements/DimensionOrder.tsx +++ b/src/components/ui/Elements/DimensionOrder.tsx @@ -1,4 +1,4 @@ -import React, {useRef, useState} from 'react' +import React, {useRef, useState, useMemo, useEffect} from 'react' import { useGlobalStore } from '@/GlobalStates/GlobalStore' import { Select, @@ -9,26 +9,30 @@ import { } from "@/components/ui/select"; export const DimensionOrder = ({dimNames}: {dimNames: string[]}) => { - const slotCount = Math.min(3, dimNames.length); + const {permute} = useGlobalStore.getState() // Just need the value on mount to set initial handleChange(slotIdx, Number(v))} > diff --git a/src/components/ui/MainPanel/MetaDataInfo.tsx b/src/components/ui/MainPanel/MetaDataInfo.tsx index 57d2cb19..69b53f0f 100644 --- a/src/components/ui/MainPanel/MetaDataInfo.tsx +++ b/src/components/ui/MainPanel/MetaDataInfo.tsx @@ -7,7 +7,7 @@ import { useShallow } from 'zustand/shallow' import { SliderThumbs } from "@/components/ui/Widgets/SliderThumbs" import Metadata, { defaultAttributes, renderAttributes } from "@/components/ui/MetaData" import { BsFillQuestionCircleFill } from "react-icons/bs"; -import { parseLoc, HandleCoarselNums } from "@/utils/HelperFuncs" +import { parseLoc, permuteArr } from "@/utils/HelperFuncs" import { DimensionOrder } from "../Elements/DimensionOrder"; import { Tooltip, @@ -85,10 +85,6 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi const [displaySpat, setDisplaySpat] = useState(String(kernelSize)) const [displayDepth, setDisplayDepth] = useState(String(kernelDepth)) - function permuteArr(arr: number[]) { - return permute.map(i => arr[i]); - } - // ---- Meta Info ---- // const {dimArrays, dimNames, dimUnits} = meta.dimInfo @@ -257,11 +253,11 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi
Data Shape - {`[${formatArray(permuteArr(dataShape))}]`} + {`[${formatArray(permuteArr(dataShape, permute))}]`}
Chunk Shape - {`[${formatArray(permuteArr(chunkShape))}]`} + {`[${formatArray(permuteArr(chunkShape, permute))}]`}
diff --git a/src/components/zarr/GetArray.ts b/src/components/zarr/GetArray.ts index c1ded8fa..7ca756b7 100644 --- a/src/components/zarr/GetArray.ts +++ b/src/components/zarr/GetArray.ts @@ -2,7 +2,7 @@ import { useGlobalStore } from "@/GlobalStates/GlobalStore"; import { useZarrStore } from "@/GlobalStates/ZarrStore"; import { useCacheStore } from "@/GlobalStates/CacheStore"; import { useErrorStore } from "@/GlobalStates/ErrorStore"; -import { calculateStrides } from "@/utils/HelperFuncs"; +import { calculateStrides, GetCurrentArray } from "@/utils/HelperFuncs"; import { ToFloat16, CompressArray, DecompressArray, copyChunkToArray, RescaleArray, copyChunkToArray2D } from "./utils"; import { NCFetcher, zarrFetcher } from "./dataFetchers"; import { Convolve } from "../computation/webGPU"; @@ -69,27 +69,27 @@ export async function GetArray(varOveride?: string) { cachedChunk.kernel.kernelDepth === (coarsen ? kernelDepth : undefined); if (isCacheValid) { - const chunkData = cachedChunk.compressed ? DecompressArray(cachedChunk.data) : cachedChunk.data.slice(); - if (hasZ) { - copyChunkToArray( - chunkData, - cachedChunk.shape, - cachedChunk.stride, - typedArray, - outputShape, - destStride as any, [z, y, x], - [zDim.start, yDim.start, xDim.start] - ) - } else { - copyChunkToArray2D( - chunkData, - cachedChunk.shape, - cachedChunk.stride, - typedArray, - outputShape, - destStride as any, [y, x], - [yDim.start, xDim.start]) - } + // const chunkData = cachedChunk.compressed ? DecompressArray(cachedChunk.data) : cachedChunk.data.slice(); + // if (hasZ) { + // copyChunkToArray( + // chunkData, + // cachedChunk.shape, + // cachedChunk.stride, + // typedArray, + // outputShape, + // destStride as any, [z, y, x], + // [zDim.start, yDim.start, xDim.start] + // ) + // } else { + // copyChunkToArray2D( + // chunkData, + // cachedChunk.shape, + // cachedChunk.stride, + // typedArray, + // outputShape, + // destStride as any, [y, x], + // [yDim.start, xDim.start]) + // } } else { const raw = await fetcher.fetchChunk({ variable:targetVariable, rank, shape, chunkShape, x, y, z, xDimIndex, yDimIndex, zDimIndex, idx4D }); @@ -118,11 +118,11 @@ export async function GetArray(varOveride?: string) { } } - if (hasZ) { - copyChunkToArray(chunkF16, thisShape.slice(-3), chunkStride.slice(-3) as any, typedArray, outputShape, destStride as any, [z, y, x], [zDim.start, yDim.start, xDim.start]); - } else { - copyChunkToArray2D(chunkF16, thisShape, chunkStride as any, typedArray, outputShape, destStride as any, [y, x], [yDim.start, xDim.start]); - } + // if (hasZ) { + // copyChunkToArray(chunkF16, thisShape.slice(-3), chunkStride.slice(-3) as any, typedArray, outputShape, destStride as any, [z, y, x], [zDim.start, yDim.start, xDim.start]); + // } else { + // copyChunkToArray2D(chunkF16, thisShape, chunkStride as any, typedArray, outputShape, destStride as any, [y, x], [yDim.start, xDim.start]); + // } cache.set(cacheName, { data: compress ? CompressArray(chunkF16, 7) : chunkF16, @@ -137,5 +137,5 @@ export async function GetArray(varOveride?: string) { } } setProgress(0); - return { data: typedArray, shape: outputShape, dtype, scalingFactor }; + return { data: GetCurrentArray(), shape: outputShape, dtype, scalingFactor }; } \ No newline at end of file diff --git a/src/hooks/useDataFetcher.tsx b/src/hooks/useDataFetcher.tsx index 850a6110..707392d8 100644 --- a/src/hooks/useDataFetcher.tsx +++ b/src/hooks/useDataFetcher.tsx @@ -4,7 +4,7 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useZarrStore } from '@/GlobalStates/ZarrStore'; import { useShallow } from 'zustand/shallow'; -import { ParseExtent, GetDimInfo } from '@/utils/HelperFuncs'; +import { ParseExtent, GetDimInfo, permuteArr } from '@/utils/HelperFuncs'; import { GetAttributes } from '@/components/zarr/ZarrLoaderLRU'; import { GetArray } from '@/components/zarr/GetArray'; import { ArrayToTexture } from '@/components/textures'; @@ -25,10 +25,11 @@ export const useDataFetcher = () => { setPlotOn: state.setPlotOn, setStatus: state.setStatus }))) - const {variable, is4D, setIsFlat} = useGlobalStore( + const {variable, is4D, permute, setIsFlat} = useGlobalStore( useShallow(state=>({ variable: state.variable, is4D: state.is4D, + permute: state.permute, setIsFlat: state.setIsFlat, }))) const {plotType, interpPixels, setPlotType} = usePlotStore( @@ -66,7 +67,7 @@ export const useDataFetcher = () => { } //----- TS Cleanup ----// useGlobalStore.setState({timeSeries:{}, dimCoords:{}}) - //---- Set Plot Slicez ----// + //---- Set Plot Slices ----// const { setZSlice, setYSlice, setXSlice } = usePlotStore.getState(); setZSlice(zSlice); setYSlice(ySlice); @@ -74,7 +75,7 @@ export const useDataFetcher = () => { //---- Main Fetch ----// GetArray().then((result) => { - const shape = result.shape.filter((val) => val != 1); + const shape = permuteArr(result.shape.filter((val) => val != 1),permute); const [tempTexture, scaling] = ArrayToTexture({ data: result.data, shape @@ -104,6 +105,7 @@ export const useDataFetcher = () => { setShow(true); setPlotOn(true); setStatus(null); + usePlotStore.setState({permute}) }); } catch (error) { console.error(error); @@ -125,6 +127,11 @@ export const useDataFetcher = () => { dimUnits = dimUnits.slice(1); dimNames = dimNames.slice(1); } + if (permute.some((v, i) => i > 0 && v < permute[i - 1])){ + dimArrays = permuteArr(dimArrays, permute); + dimNames = permuteArr(dimNames, permute); + dimUnits = permuteArr(dimUnits, permute) + } setDimNames(dimNames); setDimArrays(dimArrays); @@ -133,7 +140,7 @@ export const useDataFetcher = () => { setFlipY(shouldFlip); setDimUnits(dimUnits); - ParseExtent(dimUnits, dimArrays); + // ParseExtent(dimUnits, dimArrays); }); } else { diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index e3df047a..e1e4d70c 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -257,8 +257,63 @@ function DecompressArray(compressed : Uint8Array){ return floatArray } +function permuteArray( + array: Float16Array, + shape: number[], + order: number[] +): { data: number[]; shape: number[] } { + const ndim = shape.length; + + if (order.length !== ndim) { + throw new Error(`order length (${order.length}) must match number of dimensions (${ndim})`); + } + if ([...order].sort((a, b) => a - b).some((v, i) => v !== i)) { + throw new Error(`order must be a permutation of [0, ..., ${ndim - 1}]`); + } + + const newShape = order.map((ax) => shape[ax]); + const totalElements = array.length; + const result = new Array(totalElements); + + // Precompute strides for the original shape (row-major) + const srcStrides = new Array(ndim); + srcStrides[ndim - 1] = 1; + for (let i = ndim - 2; i >= 0; i--) { + srcStrides[i] = srcStrides[i + 1] * shape[i + 1]; + } + + // Precompute strides for the new (permuted) shape (row-major) + const dstStrides = new Array(ndim); + dstStrides[ndim - 1] = 1; + for (let i = ndim - 2; i >= 0; i--) { + dstStrides[i] = dstStrides[i + 1] * newShape[i + 1]; + } + + // Iterate over all elements by their multi-index in the new shape + const multiIdx = new Array(ndim).fill(0); + + for (let dstFlat = 0; dstFlat < totalElements; dstFlat++) { + // Compute source flat index: new axis i corresponds to old axis order[i] + let srcFlat = 0; + for (let i = 0; i < ndim; i++) { + srcFlat += multiIdx[i] * srcStrides[order[i]]; + } + + result[dstFlat] = array[srcFlat]; + + // Increment multi-index (rightmost axis first) + for (let i = ndim - 1; i >= 0; i--) { + multiIdx[i]++; + if (multiIdx[i] < newShape[i]) break; + multiIdx[i] = 0; + } + } + + return { data: result, shape: newShape }; +} + export function GetCurrentArray(overrideStore?:string){ - const { variable, is4D, idx4D, initStore, strides, dataShape, setStatus }= useGlobalStore.getState() + const { variable, is4D, idx4D, initStore, strides, dataShape, permute, setStatus }= useGlobalStore.getState() const { arraySize, currentChunks } = useZarrStore.getState() const {cache} = useCacheStore.getState(); const store = overrideStore ? overrideStore : initStore @@ -298,8 +353,9 @@ export function GetCurrentArray(overrideStore?:string){ } } } + const doPermute = permute.some((v, i) => i > 0 && v < permute[i - 1]); setStatus(null) - return typedArray + return doPermute ? permuteArray(typedArray, dataShape, permute).data : typedArray } } @@ -402,4 +458,8 @@ export function calculateStrides( return shape.reduce((a: number, b: number, i: number) => a * (i > idx ? b : 1), 1) }) return newStrides -} \ No newline at end of file +} + +export function permuteArr(arr: number[], permute:number[]) { + return permute.map(i => arr[i]); + } \ No newline at end of file From 907e4ac8d96999d0894983c2757334fb94b4c6cd Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 18 Jun 2026 10:58:31 +0200 Subject: [PATCH 3/6] Disable Plot when duplicates --- src/components/ui/Elements/DimensionOrder.tsx | 11 ++++++----- src/components/ui/MainPanel/MetaDataInfo.tsx | 6 ++++-- src/components/zarr/GetArray.ts | 1 + src/utils/HelperFuncs.ts | 1 - 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/components/ui/Elements/DimensionOrder.tsx b/src/components/ui/Elements/DimensionOrder.tsx index b2bca9d6..4b58d880 100644 --- a/src/components/ui/Elements/DimensionOrder.tsx +++ b/src/components/ui/Elements/DimensionOrder.tsx @@ -1,4 +1,4 @@ -import React, {useRef, useState, useMemo, useEffect} from 'react' +import React, {useRef, useState, useEffect} from 'react' import { useGlobalStore } from '@/GlobalStates/GlobalStore' import { Select, @@ -8,7 +8,7 @@ import { SelectValue, } from "@/components/ui/select"; -export const DimensionOrder = ({dimNames}: {dimNames: string[]}) => { +export const DimensionOrder = ({dimNames, setDuplicateDims}: {dimNames: string[], setDuplicateDims: React.Dispatch>}) => { const {permute} = useGlobalStore.getState() // Just need the value on mount to set initial handleChange(slotIdx, Number(v))} > diff --git a/src/components/zarr/GetArray.ts b/src/components/zarr/GetArray.ts index 0fe54178..dd5b1ba8 100644 --- a/src/components/zarr/GetArray.ts +++ b/src/components/zarr/GetArray.ts @@ -3,7 +3,7 @@ import { useZarrStore } from "@/GlobalStates/ZarrStore"; import { useCacheStore } from "@/GlobalStates/CacheStore"; import { useErrorStore } from "@/GlobalStates/ErrorStore"; import { calculateStrides, GetCurrentArray } from "@/utils/HelperFuncs"; -import { ToFloat16, CompressArray, DecompressArray, copyChunkToArray, RescaleArray, copyChunkToArray2D } from "./utils"; +import { ToFloat16, CompressArray, RescaleArray } from "./utils"; import { NCFetcher, zarrFetcher } from "./dataFetchers"; import { Convolve } from "../computation/webGPU"; import { coarsen3DArray } from "@/utils/HelperFuncs"; @@ -68,37 +68,12 @@ export async function GetArray(varOveride?: string) { cachedChunk.kernel.kernelSize === (coarsen ? kernelSize : undefined) && cachedChunk.kernel.kernelDepth === (coarsen ? kernelDepth : undefined); - if (isCacheValid) { - // const chunkData = cachedChunk.compressed ? DecompressArray(cachedChunk.data) : cachedChunk.data.slice(); - // if (hasZ) { - // copyChunkToArray( - // chunkData, - // cachedChunk.shape, - // cachedChunk.stride, - // typedArray, - // outputShape, - // destStride as any, [z, y, x], - // [zDim.start, yDim.start, xDim.start] - // ) - // } else { - // copyChunkToArray2D( - // chunkData, - // cachedChunk.shape, - // cachedChunk.stride, - // typedArray, - // outputShape, - // destStride as any, [y, x], - // [yDim.start, xDim.start]) - // } - } else { + if (!isCacheValid) { const raw = await fetcher.fetchChunk({ variable:targetVariable, rank, shape, chunkShape, x, y, z, xDimIndex, yDimIndex, zDimIndex, idx4D }); - const rawData = Number.isFinite(fillValue) ? raw.data.map((v: number) => v === fillValue ? NaN : v) : raw.data; // Don't map if no fillvalue - let [chunkF16, newScalingFactor] = ToFloat16(rawData, scalingFactor); let thisShape = raw.shape; let chunkStride = raw.stride; - if (coarsen) { chunkF16 = await Convolve(chunkF16, { shape: chunkShape, strides: chunkStride }, "Mean3D", { kernelSize, kernelDepth }) as Float16Array; thisShape = thisShape.map((dim, idx) => Math.floor(dim / (idx === 0 ? kernelDepth : kernelSize))); @@ -118,12 +93,6 @@ export async function GetArray(varOveride?: string) { } } - // if (hasZ) { - // copyChunkToArray(chunkF16, thisShape.slice(-3), chunkStride.slice(-3) as any, typedArray, outputShape, destStride as any, [z, y, x], [zDim.start, yDim.start, xDim.start]); - // } else { - // copyChunkToArray2D(chunkF16, thisShape, chunkStride as any, typedArray, outputShape, destStride as any, [y, x], [yDim.start, xDim.start]); - // } - cache.set(cacheName, { data: compress ? CompressArray(chunkF16, 7) : chunkF16, shape: chunkShape, stride: chunkStride, @@ -134,7 +103,7 @@ export async function GetArray(varOveride?: string) { } setProgress(Math.round(iter++ / totalChunks * 100)); } - } + } } useGlobalStore.setState({dataShape:outputShape}) // Needed for initial GetCurrentArray call which needs dataShape if needs to be permuted setProgress(0); diff --git a/src/hooks/useDataFetcher.tsx b/src/hooks/useDataFetcher.tsx index 707392d8..2501e2ef 100644 --- a/src/hooks/useDataFetcher.tsx +++ b/src/hooks/useDataFetcher.tsx @@ -80,7 +80,6 @@ export const useDataFetcher = () => { data: result.data, shape }); - setTextures(tempTexture); setValueScales(scaling as { maxVal: number; minVal: number }); useGlobalStore.getState().setScalingFactor(result.scalingFactor); @@ -127,7 +126,7 @@ export const useDataFetcher = () => { dimUnits = dimUnits.slice(1); dimNames = dimNames.slice(1); } - if (permute.some((v, i) => i > 0 && v < permute[i - 1])){ + if (permute?.some((v, i) => i > 0 && v < permute[i - 1])){ dimArrays = permuteArr(dimArrays, permute); dimNames = permuteArr(dimNames, permute); dimUnits = permuteArr(dimUnits, permute) @@ -138,7 +137,6 @@ export const useDataFetcher = () => { const targetDim = dimArrays.length > 2 ? dimArrays[1] : dimArrays[0]; const shouldFlip = targetDim[1] < targetDim[0]; setFlipY(shouldFlip); - setDimUnits(dimUnits); // ParseExtent(dimUnits, dimArrays); }); diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index dd23d6e0..83235d2e 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -5,7 +5,7 @@ import { useGlobalStore } from '@/GlobalStates/GlobalStore'; import { usePlotStore } from '@/GlobalStates/PlotStore'; import { useZarrStore } from '@/GlobalStates/ZarrStore'; import { decompressSync } from 'fflate'; -import { copyChunkToArray } from '@/components/zarr/utils'; +import { copyChunkToArray, copyChunkToArray2D } from '@/components/zarr/utils'; import { GetNCDims } from '@/components/zarr/NCGetters'; import { GetZarrDims } from '@/components/zarr/ZarrLoaderLRU'; @@ -262,6 +262,7 @@ function permuteArray( shape: number[], order: number[] ): { data: number[]; shape: number[] } { + // This function is completely vibecoded const ndim = shape.length; if (order.length !== ndim) { throw new Error(`order length (${order.length}) must match number of dimensions (${ndim})`); @@ -316,7 +317,6 @@ export function GetCurrentArray(overrideStore?:string){ const { arraySize, currentChunks } = useZarrStore.getState() const {cache} = useCacheStore.getState(); const store = overrideStore ? overrideStore : initStore - if (cache.has(is4D ? `${store}_${idx4D}_${variable}` : `${store}_${variable}`)){ const chunk = cache.get(is4D ? `${store}_${idx4D}_${variable}` : `${store}_${variable}`) const compressed = chunk.compressed @@ -339,22 +339,36 @@ export function GetCurrentArray(overrideStore?:string){ const chunk = cache.get(cacheName) const compressed = chunk.compressed const thisData = compressed ? DecompressArray(chunk.data) : chunk.data - copyChunkToArray( - thisData, - chunk.shape, - chunk.stride, - typedArray, - dataShape, - strides as [number, number, number], - [z, y, x], - [zStartIdx, yStartIdx, xStartIdx] - ) + const is2D = chunk.shape.length == 2; + if (is2D){ + copyChunkToArray2D( + thisData, + chunk.shape, + chunk.stride, + typedArray, + dataShape, + strides as [number, number], + [y, x], + [yStartIdx, xStartIdx] + ) + }else{ + copyChunkToArray( + thisData, + chunk.shape, + chunk.stride, + typedArray, + dataShape, + strides as [number, number, number], + [z, y, x], + [zStartIdx, yStartIdx, xStartIdx] + ) + } } } } - const doPermute = permute.some((v, i) => i > 0 && v < permute[i - 1]); + const doPermute = permute?.some((v, i) => i > 0 && v < permute[i - 1]); setStatus(null) - return doPermute ? permuteArray(typedArray, dataShape, permute).data : typedArray + return doPermute ? permuteArray(typedArray, dataShape, permute as number[]).data : typedArray } } @@ -459,6 +473,6 @@ export function calculateStrides( return newStrides } -export function permuteArr(arr: number[], permute:number[]) { - return permute.map(i => arr[i]); - } \ No newline at end of file +export function permuteArr(arr: number[], permute?:number[]) { + return permute ? permute.map(i => arr[i]) : arr; +} \ No newline at end of file From 2e6dd6d7a606baa33c6bf590acea51163a673967 Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 18 Jun 2026 13:31:38 +0200 Subject: [PATCH 5/6] Reenable Borders --- src/components/plots/Plot.tsx | 2 +- src/components/ui/MainPanel/MetaDataInfo.tsx | 1 - src/utils/HelperFuncs.ts | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/plots/Plot.tsx b/src/components/plots/Plot.tsx index f3128d63..d54e6ab3 100644 --- a/src/components/plots/Plot.tsx +++ b/src/components/plots/Plot.tsx @@ -225,7 +225,7 @@ const Plot = () => { dpr={[DPR,DPR]} > - {/* */} + {show && } {plotType == "volume" && show && diff --git a/src/components/ui/MainPanel/MetaDataInfo.tsx b/src/components/ui/MainPanel/MetaDataInfo.tsx index fe6af23a..81323b76 100644 --- a/src/components/ui/MainPanel/MetaDataInfo.tsx +++ b/src/components/ui/MainPanel/MetaDataInfo.tsx @@ -227,7 +227,6 @@ const MetaDataInfo = ({ meta, metadata, setShowMeta, setOpenVariables, popoverSi setCached(false) } },[meta, chunkIDs]) - return ( <> {`${meta.long_name} `} diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index 83235d2e..0c1f25c1 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -270,7 +270,7 @@ function permuteArray( if ([...order].sort((a, b) => a - b).some((v, i) => v !== i)) { throw new Error(`order must be a permutation of [0, ..., ${ndim - 1}]`); } - + console.log(shape, order) const newShape = order.map((ax) => shape[ax]); const totalElements = array.length; const result = new Array(totalElements); @@ -367,6 +367,7 @@ export function GetCurrentArray(overrideStore?:string){ } } const doPermute = permute?.some((v, i) => i > 0 && v < permute[i - 1]); + console.log(permute, doPermute) setStatus(null) return doPermute ? permuteArray(typedArray, dataShape, permute as number[]).data : typedArray } From 4efb7ed312ac92f0f4bb5d8caaba7ce61058878d Mon Sep 17 00:00:00 2001 From: Jeran Date: Thu, 18 Jun 2026 13:54:28 +0200 Subject: [PATCH 6/6] Gemini suggestions --- src/components/plots/AxisLines.tsx | 2 +- src/utils/HelperFuncs.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/plots/AxisLines.tsx b/src/components/plots/AxisLines.tsx index aadd2f53..4020ae1c 100644 --- a/src/components/plots/AxisLines.tsx +++ b/src/components/plots/AxisLines.tsx @@ -73,7 +73,7 @@ const CubeAxis = ({flipX, flipY, flipDown}: {flipX: boolean, flipY: boolean, fli const [yResolution, setYResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) const [zResolution, setZResolution] = useState(AXIS_CONSTANTS.INITIAL_RESOLUTION) - const permuteShape = useMemo(()=>permuteArr(dataShape,permute),[permute]) + const permuteShape = useMemo(()=>permuteArr(dataShape,permute),[dataShape, permute]) const isPC = useMemo(()=>plotType == 'point-cloud',[plotType]) const globalScale = isPC ? permuteShape[2]/AXIS_CONSTANTS.PC_GLOBAL_SCALE_DIVISOR : 1 diff --git a/src/utils/HelperFuncs.ts b/src/utils/HelperFuncs.ts index 0c1f25c1..b9351bf4 100644 --- a/src/utils/HelperFuncs.ts +++ b/src/utils/HelperFuncs.ts @@ -283,7 +283,7 @@ function permuteArray( } // Precompute strides for the new (permuted) shape (row-major) - const dstStrides = new Array(ndim); + const dstStrides = new Float16Array(ndim); dstStrides[ndim - 1] = 1; for (let i = ndim - 2; i >= 0; i--) { dstStrides[i] = dstStrides[i + 1] * newShape[i + 1];