Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cca0ec7
dim select components
lazarusA Jun 26, 2026
63efcb3
fix value shown
lazarusA Jun 26, 2026
9e64696
fixes defaults keys
lazarusA Jun 26, 2026
a7493a1
enhanced button
lazarusA Jun 26, 2026
f13f165
more keys
lazarusA Jun 26, 2026
7524860
sugg
lazarusA Jul 4, 2026
dc2b43f
mv over
lazarusA Jul 4, 2026
104de3c
r rules
lazarusA Jul 4, 2026
48d936d
fix vertex, specialize on use case
lazarusA Jul 4, 2026
62372c9
ndSlices mappings
lazarusA Jul 4, 2026
86cf48f
too many
lazarusA Jul 4, 2026
9057e77
fixes axislines
lazarusA Jul 4, 2026
9d841db
more glsl3 updates
lazarusA Jul 4, 2026
1782179
fixes webgl issues in firefox
lazarusA Jul 4, 2026
6536059
all dims
lazarusA Jul 4, 2026
0194816
2d vars
lazarusA Jul 4, 2026
33a7a83
via axismapping
lazarusA Jul 4, 2026
a3cef10
2d
lazarusA Jul 4, 2026
b4cf2f3
get
lazarusA Jul 4, 2026
2f12f5b
isFlat
lazarusA Jul 4, 2026
0167373
fix import
lazarusA Jul 5, 2026
7cb9add
packed chunks
lazarusA Jul 5, 2026
b2fa6e0
fix nan propagation
lazarusA Jul 5, 2026
e874634
rm console
lazarusA Jul 5, 2026
aaa7df3
play any direction
lazarusA Jul 5, 2026
a6b8b80
pass right indices
lazarusA Jul 5, 2026
a61e2c3
conv
lazarusA Jul 5, 2026
d210796
fixes 1 var stats
lazarusA Jul 5, 2026
4534047
fixes nans 2 vars
lazarusA Jul 5, 2026
3a133cc
fixes idx memory strides
lazarusA Jul 5, 2026
0fefd3b
Infity is NaN
lazarusA Jul 5, 2026
f7b0e01
rm DOM lagging
lazarusA Jul 5, 2026
c3ce153
windowing
lazarusA Jul 5, 2026
96d5ac2
good scroll up
lazarusA Jul 5, 2026
e33de18
back hours
lazarusA Jul 5, 2026
8cb110b
flex wrap
lazarusA Jul 5, 2026
30aa9ed
spinner, global styles
lazarusA Jul 5, 2026
86d8754
descriptions
lazarusA Jul 5, 2026
3a7b5dd
keep open
lazarusA Jul 5, 2026
4b4dacb
mv to Dialog
lazarusA Jul 5, 2026
98ee7ce
popover attributes
lazarusA Jul 5, 2026
159b291
fixes width
lazarusA Jul 5, 2026
04d176c
always Dialog for colorbar
lazarusA Jul 5, 2026
f77df9f
new row
lazarusA Jul 6, 2026
ab66d7f
plus one
lazarusA Jul 7, 2026
b173ace
revert
lazarusA Jul 7, 2026
ef1617a
cached
lazarusA Jul 9, 2026
8e681df
lock sliders
lazarusA Jul 9, 2026
69c6c21
fix +1 parsing
lazarusA Jul 10, 2026
d1b6e2c
fixes nan counts
lazarusA Jul 10, 2026
fc84e94
more nan fixes
lazarusA Jul 10, 2026
54a2eec
fixes nan cumsum
lazarusA Jul 10, 2026
3c62445
asChild
lazarusA Jul 10, 2026
61e10a8
test NaN math
lazarusA Jul 10, 2026
3a243df
export parameters
lazarusA Jul 10, 2026
55d5261
export colormap parameters
lazarusA Jul 10, 2026
f546e69
Textures back
TheJeran Jul 11, 2026
fb326ab
usePaddedTextures
lazarusA Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/GlobalStates/GlobalStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ interface Coord {

type StoreState = {
dataShape: number[];
activeIndices: number[];
shape: THREE.Vector3;
valueScales: { maxVal: number; minVal: number };
colormap: THREE.DataTexture;
colormapName: string;
flipColormap: boolean;
timeSeries: Record<string, Record<string, any>>;
strides: number[];
metadata: Record<string, any> | null;
Expand Down Expand Up @@ -50,9 +53,12 @@ type StoreState = {

// setters
setDataShape: (dataShape: number[]) => void;
setActiveIndices: (indices: number[]) => void;
setShape: (shape: THREE.Vector3) => void;
setValueScales: (valueScales: { maxVal: number; minVal: number }) => void;
setColormap: (colormap: THREE.DataTexture) => void;
setColormapName: (colormapName: string) => void;
setFlipColormap: (flipColormap: boolean) => void;
setTimeSeries: (timeSeries: Record<string, Record<string, any>>) => void;
updateTimeSeries: (newEntries: Record<string, Record<string, any>>) => void;
setStrides: (strides: number[]) => void;
Expand Down Expand Up @@ -86,9 +92,12 @@ type StoreState = {

export const useGlobalStore = create<StoreState>((set, get) => ({
dataShape: [1, 1, 1],
activeIndices: [],
shape: new THREE.Vector3(2, 2, 2),
valueScales: { maxVal: 1, minVal: -1 },
colormap: GetColorMapTexture(),
colormapName: "Spectral",
flipColormap: false,
timeSeries: {},
strides: [10368,144,1],
metadata: null,
Expand Down Expand Up @@ -119,9 +128,12 @@ export const useGlobalStore = create<StoreState>((set, get) => ({
// setters

setDataShape: (dataShape) => set({ dataShape }),
setActiveIndices: (indices) => set({ activeIndices: indices }),
setShape: (shape) => set({ shape }),
setValueScales: (valueScales) => set({ valueScales }),
setColormap: (colormap) => set({ colormap }),
setColormapName: (colormapName) => set({ colormapName }),
setFlipColormap: (flipColormap) => set({ flipColormap }),
setTimeSeries: (timeSeries) => set({ timeSeries }),
updateTimeSeries: (newEntries) => {
const merged = { ...newEntries, ...get().timeSeries };
Expand Down
12 changes: 10 additions & 2 deletions src/GlobalStates/ZarrStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { create } from "zustand";
import { GetStore } from "@/components/zarr/ZarrLoaderLRU";
import { FetchStoreOptions, IcechunkStoreOptions } from "@/components/zarr/Interfaces";

const ESDC = 'https://s3.bgc-jena.mpg.de:9000/esdl-esdc-v3.0.2/esdc-16d-2.5deg-46x72x1440-3.0.2.zarr'
Expand All @@ -25,9 +24,14 @@ type ZarrState = {
fetchKey: number;
blobKey: string | undefined; // The key for the stored File blob for a local NC

ndSlices: (number | [number, number | null])[];
axisMapping: { x: number, y: number, z: number };

setZSlice: (zSlice: [number , number | null]) => void;
setYSlice: (ySlice: [number , number | null]) => void;
setXSlice: (xSlice: [number , number | null]) => void;
setNdSlices: (ndSlices: (number | [number, number | null])[]) => void;
setAxisMapping: (mapping: { x: number, y: number, z: number }) => void;
setCompress: (compress: boolean) => void;
setCurrentStore: (currentStore: any) => void;
setReFetch: (reFetch: boolean) => void;
Expand All @@ -49,8 +53,10 @@ export const useZarrStore = create<ZarrState>((set, get) => ({
zSlice: [0, null],
ySlice: [0, null],
xSlice: [0, null],
ndSlices: [],
axisMapping: { x: -1, y: -1, z: -1 },
compress: false,
currentStore: GetStore(ESDC),
currentStore: Promise.resolve(undefined),
reFetch: false,
currentChunks: {x:[], y:[], z:[]},
arraySize: 0,
Expand All @@ -69,6 +75,8 @@ export const useZarrStore = create<ZarrState>((set, get) => ({
setZSlice: (zSlice) => set({ zSlice }),
setYSlice: (ySlice) => set({ ySlice }),
setXSlice: (xSlice) => set({ xSlice }),
setNdSlices: (ndSlices) => set({ ndSlices }),
setAxisMapping: (axisMapping) => set({ axisMapping }),
setCompress: (compress) => set({ compress }),
setCurrentStore: (currentStore) => set({ currentStore }),
setReFetch: (reFetch) => set({ reFetch }),
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ beforeAll(() => {
COPY_DST: 2,
TEXTURE_BINDING: 4,
STORAGE_BINDING: 8,
RENDER_ATTACHMENT: 16
}
RENDER_ATTACHMENT: 16,
TRANSIENT_ATTACHMENT: 32
} as GPUTextureUsage
})

// Export helper to set mock GPU results
Expand Down
280 changes: 280 additions & 0 deletions src/__tests__/webGPU-nan.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
import { describe, expect, test, beforeAll } from 'vitest'
import { DataReduction, Convolve, Multivariate2D, Multivariate3D, CUMSUM3D, Convolve2D } from '../components/computation/webGPU'
import { setMockGPUResult } from './setup'

describe('WebGPU Functions - NaN Statistics', () => {
beforeAll(async () => {
if (!navigator.gpu) {
console.warn('WebGPU is not supported in this environment')
return
}

const adapter = await navigator.gpu.requestAdapter()
if (!adapter) {
console.warn('No WebGPU adapter found')
return
}

const device = await adapter.requestDevice()
if (!device) {
console.warn('No WebGPU device found')
return
}
})

// Array layout (3x3x1) with NaNs:
// [1, NaN, 3]
// [4, 5, NaN]
// [NaN, 8, 9]
const arrayWithNaN = new Float32Array([1, NaN, 3, 4, 5, NaN, NaN, 8, 9])

// All NaN array
const allNaNArray = new Float32Array([NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN])

const shape3D = [3, 3, 1]
const strides3D = [3, 1, 1]
const shape2D = [3, 3]
const strides2D = [3, 1]

describe('DataReduction with NaNs', () => {
test('Mean Reduction - skips NaNs', async () => {
// Reducing along dim 0 (mean of each column):
// Col 0: (1+4)/2 = 2.5 (skips NaN)
// Col 1: (5+8)/2 = 6.5 (skips NaN)
// Col 2: (3+9)/2 = 6 (skips NaN)
setMockGPUResult([2.5, 6.5, 6])
const result = await DataReduction(arrayWithNaN, { shape: shape3D, strides: strides3D }, 0, 'Mean')
expect(result).toBeDefined()
expect(Array.from(result!)).toEqual([2.5, 6.5, 6])
})

test('Min Reduction - skips NaNs', async () => {
// Reducing along dim 0 (min of each column):
// Col 0: min(1, 4) = 1
// Col 1: min(5, 8) = 5
// Col 2: min(3, 9) = 3
setMockGPUResult([1, 5, 3])
const result = await DataReduction(arrayWithNaN, { shape: shape3D, strides: strides3D }, 0, 'Min')
expect(result).toBeDefined()
expect(Array.from(result!)).toEqual([1, 5, 3])
})

test('Max Reduction - skips NaNs', async () => {
// Reducing along dim 0 (max of each column):
// Col 0: max(1, 4) = 4
// Col 1: max(5, 8) = 8
// Col 2: max(3, 9) = 9
setMockGPUResult([4, 8, 9])
const result = await DataReduction(arrayWithNaN, { shape: shape3D, strides: strides3D }, 0, 'Max')
expect(result).toBeDefined()
expect(Array.from(result!)).toEqual([4, 8, 9])
})

test('Mean Reduction - all NaNs returns NaN', async () => {
// If all are NaN, mean should be NaN
setMockGPUResult([NaN, NaN, NaN])
const result = await DataReduction(allNaNArray, { shape: shape3D, strides: strides3D }, 0, 'Mean')
expect(result).toBeDefined()
expect(Number.isNaN(result![0])).toBe(true)
expect(Number.isNaN(result![1])).toBe(true)
expect(Number.isNaN(result![2])).toBe(true)
})
})

describe('Convolution with NaNs', () => {
test('3D Mean Convolution - ignores NaNs', async () => {
// For the arrayWithNaN [1, NaN, 3, 4, 5, NaN, NaN, 8, 9] with 3x3 kernel
// The kernel should only sum valid pixels and divide by valid N.
// E.g., center pixel (5) surrounded by 1, NaN, 3, 4, NaN, NaN, 8, 9
// Sum = 1+3+4+5+8+9 = 30. Valid N = 6. 30/6 = 5.0
// We'll mock out this expected result.
const expectedValues = [1.0, 3.25, 3.0, 2.5, 5.0, 6.0, 4.0, 6.5, 9.0]
setMockGPUResult(expectedValues)
const result = await Convolve(
arrayWithNaN,
{ shape: shape3D, strides: strides3D },
'Mean3D',
{ kernelSize: 3, kernelDepth: 1 }
)
expect(result).toBeDefined()
expect(result?.length).toBe(9)
result!.forEach((val, idx) => {
expect(val).toBeCloseTo(expectedValues[idx], 1)
})
})

test('3D Mean Convolution - all NaNs returns NaN', async () => {
const expectedValues = new Float32Array([NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN])
setMockGPUResult(Array.from(expectedValues))
const result = await Convolve(
allNaNArray,
{ shape: shape3D, strides: strides3D },
'Mean3D',
{ kernelSize: 3, kernelDepth: 1 }
)
expect(result).toBeDefined()
expect(result?.length).toBe(9)
result!.forEach(val => {
expect(Number.isNaN(val)).toBe(true)
})
})

test('2D Mean Convolution - ignores NaNs', async () => {
// Same logic as 3D Convolution
const expectedValues = [1.0, 3.25, 3.0, 2.5, 5.0, 6.0, 4.0, 6.5, 9.0]
setMockGPUResult(expectedValues)
const result = await Convolve2D(
arrayWithNaN,
{ shape: shape2D, strides: strides2D },
'Mean2D',
3
)
expect(result).toBeDefined()
expect(result?.length).toBe(9)
result!.forEach((val, idx) => {
expect(val).toBeCloseTo(expectedValues[idx], 1)
})
})
})

describe('Multivariate with NaNs', () => {
// Array 1 valid elements are at indices: 0, 2, 3, 4, 7, 8
// [1, NaN, 3, 4, 5, NaN, NaN, 8, 9]
// [9, 8, 7, 6, 5, 4, 3, 2, 1]
const secondArray = new Float32Array([9, 8, 7, 6, 5, 4, 3, 2, 1])

test('2D Correlation - computes only on pairwise valid elements', async () => {
// Pairwise valid elements:
// Array 1 valid: [1, 3, 4, 5, 8, 9]
// Array 2 valid: [9, 7, 6, 5, 2, 1]
// This is a perfect negative correlation (-1)
setMockGPUResult([-1])
const result = await Multivariate2D(
arrayWithNaN,
secondArray,
{ shape: shape3D, strides: strides3D },
0,
'Correlation2D'
)
expect(result).toBeDefined()
expect(result?.length).toBe(1)
expect(result![0]).toBeCloseTo(-1, 5)
})

test('3D Correlation - computes only on pairwise valid elements', async () => {
setMockGPUResult([-1])
const result = await Multivariate3D(
arrayWithNaN,
secondArray,
{ shape: shape3D, strides: strides3D },
{ kernelSize: 3, kernelDepth: 1 },
'Correlation3D'
)
expect(result).toBeDefined()
expect(result?.length).toBe(1)
expect(result![0]).toBeCloseTo(-1, 5)
})
})

describe('CUMSUM3D with NaNs', () => {
test('Basic CUMSUM operation along dimension 0', async () => {
// Array layout (3x3x1):
// [1, NaN, 3]
// [4, 5, NaN]
// [NaN, 8, 9]
//
// Cumsum along dim 0 (exclusive accumulation down rows for each column):
// Col 0 [1, 4, NaN]:
// z=0: 0.0 (first valid element)
// z=1: 1.0 (accumulated 1)
// z=2: 5.0 (accumulated 1+4)
// Col 1 [NaN, 5, 8]:
// z=0: NaN (no valid elements seen yet, current is NaN)
// z=1: 0.0 (first valid element)
// z=2: 5.0 (accumulated 5)
// Col 2 [3, NaN, 9]:
// z=0: 0.0 (first valid element)
// z=1: 3.0 (accumulated 3, current is NaN so it outputs accum)
// z=2: 3.0 (accumulated 3)
// Expected: [0, NaN, 0, 1, 0, 3, 5, 5, 3]
const expected = [0, NaN, 0, 1, 0, 3, 5, 5, 3]
setMockGPUResult(expected)
const result = await CUMSUM3D(
arrayWithNaN,
{ shape: shape3D, strides: strides3D },
0,
0
)
expect(result).toBeDefined()
result!.forEach((val, i) => {
if (Number.isNaN(expected[i])) {
expect(Number.isNaN(val)).toBe(true)
} else {
expect(val).toBe(expected[i])
}
})
})

test('Reverse CUMSUM operation along dimension 0', async () => {
// Reverse cumsum along dim 0 (accumulate up rows for each column):
// Col 0 [1, 4, NaN]: [4, NaN, NaN]
// Col 1 [NaN, 5, 8]: [13, 8, 0]
// Col 2 [3, NaN, 9]: [9, 9, 0]
const expected = [4, 13, 9, NaN, 8, 9, NaN, 0, 0]
setMockGPUResult(expected)
const result = await CUMSUM3D(
arrayWithNaN,
{ shape: shape3D, strides: strides3D },
0,
1
)
expect(result).toBeDefined()
result!.forEach((val, i) => {
if (Number.isNaN(expected[i])) {
expect(Number.isNaN(val)).toBe(true)
} else {
expect(val).toBe(expected[i])
}
})
})

test('Basic CUMSUM operation along dimension 1', async () => {
// Cumsum along dim 1 (accumulate across columns within each row):
// Row 0 [1, NaN, 3]: [0, 1, 1]
// Row 1 [4, 5, NaN]: [0, 4, 9]
// Row 2 [NaN, 8, 9]: [NaN, 0, 8]
const expected = [0, 1, 1, 0, 4, 9, NaN, 0, 8]
setMockGPUResult(expected)
const result = await CUMSUM3D(
arrayWithNaN,
{ shape: shape3D, strides: strides3D },
1,
0
)
expect(result).toBeDefined()
result!.forEach((val, i) => {
if (Number.isNaN(expected[i])) {
expect(Number.isNaN(val)).toBe(true)
} else {
expect(val).toBe(expected[i])
}
})
})

test('CUMSUM - all NaNs returns NaN for all', async () => {
// For all NaNs, validCount remains 0 and every element is NaN, so output is all NaNs
const expected = [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN]
setMockGPUResult(expected)
const result = await CUMSUM3D(
allNaNArray,
{ shape: shape3D, strides: strides3D },
0,
0
)
expect(result).toBeDefined()
result!.forEach(val => {
expect(Number.isNaN(val)).toBe(true)
})
})
})
})
Loading
Loading