gladly-plot 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/package.json +10 -11
- package/src/axes/Axis.js +320 -172
- package/src/axes/AxisLink.js +6 -2
- package/src/axes/AxisRegistry.js +116 -39
- package/src/axes/Camera.js +47 -0
- package/src/axes/ColorAxisRegistry.js +10 -2
- package/src/axes/FilterAxisRegistry.js +1 -1
- package/src/axes/TickLabelAtlas.js +99 -0
- package/src/axes/ZoomController.js +446 -124
- package/src/colorscales/ColorscaleRegistry.js +30 -10
- package/src/compute/ComputationRegistry.js +126 -184
- package/src/compute/axisFilter.js +21 -9
- package/src/compute/conv.js +64 -8
- package/src/compute/elementwise.js +72 -0
- package/src/compute/fft.js +106 -20
- package/src/compute/filter.js +105 -103
- package/src/compute/hist.js +247 -142
- package/src/compute/kde.js +64 -46
- package/src/compute/scatter2dInterpolate.js +277 -0
- package/src/compute/util.js +196 -0
- package/src/core/ComputePipeline.js +153 -0
- package/src/core/GlBase.js +141 -0
- package/src/core/Layer.js +22 -8
- package/src/core/LayerType.js +253 -92
- package/src/core/Plot.js +644 -162
- package/src/core/PlotGroup.js +204 -0
- package/src/core/ShaderQueue.js +73 -0
- package/src/data/ColumnData.js +269 -0
- package/src/data/Computation.js +95 -0
- package/src/data/Data.js +270 -0
- package/src/floats/Float.js +56 -0
- package/src/index.js +16 -4
- package/src/layers/BarsLayer.js +168 -0
- package/src/layers/ColorbarLayer.js +10 -14
- package/src/layers/ColorbarLayer2d.js +13 -24
- package/src/layers/FilterbarLayer.js +4 -3
- package/src/layers/LinesLayer.js +108 -122
- package/src/layers/PointsLayer.js +73 -69
- package/src/layers/ScatterShared.js +62 -106
- package/src/layers/TileLayer.js +20 -16
- package/src/math/mat4.js +100 -0
- package/src/core/Data.js +0 -67
- package/src/layers/HistogramLayer.js +0 -212
package/src/compute/fft.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { registerTextureComputation,
|
|
1
|
+
import { registerTextureComputation, registerComputedData, EXPRESSION_REF, resolveQuantityKind } from "./ComputationRegistry.js"
|
|
2
|
+
import { TextureComputation, ComputedData } from "../data/Computation.js"
|
|
3
|
+
import { ArrayColumn } from "../data/ColumnData.js"
|
|
2
4
|
|
|
3
5
|
/* ============================================================
|
|
4
6
|
Utilities
|
|
@@ -8,6 +10,8 @@ function nextPow2(n) {
|
|
|
8
10
|
return 1 << Math.ceil(Math.log2(n));
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
// Internal: complex texture (R=real, G=imag per frequency bin), 1 element per texel.
|
|
14
|
+
// Not exposed via sampleColumn — only used as intermediate within this module.
|
|
11
15
|
function makeComplexTexture(regl, data, N) {
|
|
12
16
|
// data: Float32Array (real), imag assumed 0
|
|
13
17
|
const texData = new Float32Array(N * 4);
|
|
@@ -39,7 +43,7 @@ function makeEmptyComplexTexture(regl, N) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
function makeFBO(regl, tex) {
|
|
42
|
-
return regl.framebuffer({ color: tex });
|
|
46
|
+
return regl.framebuffer({ color: tex, depth: false, stencil: false });
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/* ============================================================
|
|
@@ -166,11 +170,15 @@ function scalePass(regl, N) {
|
|
|
166
170
|
});
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
// If a single batch of FFT stages takes longer than this, yield before the next batch.
|
|
174
|
+
const TDR_STEP_MS = 500
|
|
175
|
+
|
|
169
176
|
/* ============================================================
|
|
170
|
-
|
|
177
|
+
Internal GPU FFT — returns a complex texture (R=real, G=imag)
|
|
178
|
+
with 1 frequency bin per texel. NOT for direct use with sampleColumn.
|
|
171
179
|
============================================================ */
|
|
172
180
|
|
|
173
|
-
export function fft1d(regl, realArray, inverse = false) {
|
|
181
|
+
export async function fft1d(regl, realArray, inverse = false) {
|
|
174
182
|
const N = nextPow2(realArray.length);
|
|
175
183
|
|
|
176
184
|
let texA = makeComplexTexture(regl, realArray, N);
|
|
@@ -185,8 +193,9 @@ export function fft1d(regl, realArray, inverse = false) {
|
|
|
185
193
|
});
|
|
186
194
|
[fboA, fboB] = [fboB, fboA];
|
|
187
195
|
|
|
188
|
-
// FFT stages
|
|
196
|
+
// FFT stages — yield between batches to avoid triggering the Windows TDR watchdog.
|
|
189
197
|
const stages = Math.log2(N);
|
|
198
|
+
let batchStart = performance.now();
|
|
190
199
|
for (let s = 1; s <= stages; s++) {
|
|
191
200
|
fftStagePass(regl, 1 << s, inverse)({
|
|
192
201
|
input: fboA,
|
|
@@ -194,6 +203,10 @@ export function fft1d(regl, realArray, inverse = false) {
|
|
|
194
203
|
fbo: fboB
|
|
195
204
|
});
|
|
196
205
|
[fboA, fboB] = [fboB, fboA];
|
|
206
|
+
if (performance.now() - batchStart > TDR_STEP_MS) {
|
|
207
|
+
await new Promise(r => requestAnimationFrame(r));
|
|
208
|
+
batchStart = performance.now();
|
|
209
|
+
}
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
// scale for inverse
|
|
@@ -209,14 +222,14 @@ export function fft1d(regl, realArray, inverse = false) {
|
|
|
209
222
|
}
|
|
210
223
|
|
|
211
224
|
/* ============================================================
|
|
212
|
-
FFT-based convolution
|
|
225
|
+
FFT-based convolution (internal)
|
|
213
226
|
============================================================ */
|
|
214
227
|
|
|
215
|
-
export function fftConvolution(regl, signal, kernel) {
|
|
228
|
+
export async function fftConvolution(regl, signal, kernel) {
|
|
216
229
|
const N = nextPow2(signal.length + kernel.length);
|
|
217
230
|
|
|
218
|
-
const sigTex = fft1d(regl, signal, false);
|
|
219
|
-
const kerTex = fft1d(regl, kernel, false);
|
|
231
|
+
const sigTex = await fft1d(regl, signal, false);
|
|
232
|
+
const kerTex = await fft1d(regl, kernel, false);
|
|
220
233
|
|
|
221
234
|
const outTex = makeEmptyComplexTexture(regl, N);
|
|
222
235
|
const outFBO = makeFBO(regl, outTex);
|
|
@@ -251,32 +264,108 @@ export function fftConvolution(regl, signal, kernel) {
|
|
|
251
264
|
})();
|
|
252
265
|
|
|
253
266
|
// inverse FFT
|
|
254
|
-
return fft1d(regl, new Float32Array(N), true);
|
|
267
|
+
return await fft1d(regl, new Float32Array(N), true);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* ============================================================
|
|
271
|
+
extractAndRepack: extract one channel from a complex texture
|
|
272
|
+
(1 element per texel, R=real/G=imag) into a 4-packed RGBA texture.
|
|
273
|
+
channelSwizzle: 'r' for real part, 'g' for imaginary part.
|
|
274
|
+
============================================================ */
|
|
275
|
+
|
|
276
|
+
function extractAndRepack(regl, complexTex, channelSwizzle, N) {
|
|
277
|
+
const nTexels = Math.ceil(N / 4)
|
|
278
|
+
const w = Math.min(nTexels, regl.limits.maxTextureSize)
|
|
279
|
+
const h = Math.ceil(nTexels / w)
|
|
280
|
+
const outputTex = regl.texture({ width: w, height: h, type: 'float', format: 'rgba' })
|
|
281
|
+
const outputFBO = regl.framebuffer({ color: outputTex, depth: false, stencil: false })
|
|
282
|
+
|
|
283
|
+
regl({
|
|
284
|
+
framebuffer: outputFBO,
|
|
285
|
+
vert: `#version 300 es
|
|
286
|
+
in vec2 position;
|
|
287
|
+
void main() { gl_Position = vec4(position, 0.0, 1.0); }`,
|
|
288
|
+
frag: `#version 300 es
|
|
289
|
+
precision highp float;
|
|
290
|
+
uniform sampler2D complexTex;
|
|
291
|
+
uniform int totalLength;
|
|
292
|
+
out vec4 fragColor;
|
|
293
|
+
void main() {
|
|
294
|
+
int texelI = int(gl_FragCoord.y) * ${w} + int(gl_FragCoord.x);
|
|
295
|
+
int base = texelI * 4;
|
|
296
|
+
float v0 = base + 0 < totalLength ? texelFetch(complexTex, ivec2(base+0, 0), 0).${channelSwizzle} : 0.0;
|
|
297
|
+
float v1 = base + 1 < totalLength ? texelFetch(complexTex, ivec2(base+1, 0), 0).${channelSwizzle} : 0.0;
|
|
298
|
+
float v2 = base + 2 < totalLength ? texelFetch(complexTex, ivec2(base+2, 0), 0).${channelSwizzle} : 0.0;
|
|
299
|
+
float v3 = base + 3 < totalLength ? texelFetch(complexTex, ivec2(base+3, 0), 0).${channelSwizzle} : 0.0;
|
|
300
|
+
fragColor = vec4(v0, v1, v2, v3);
|
|
301
|
+
}`,
|
|
302
|
+
attributes: { position: [[-1,-1],[1,-1],[-1,1],[1,1]] },
|
|
303
|
+
uniforms: { complexTex, totalLength: N },
|
|
304
|
+
count: 4,
|
|
305
|
+
primitive: 'triangle strip'
|
|
306
|
+
})()
|
|
307
|
+
|
|
308
|
+
outputTex._dataLength = N
|
|
309
|
+
return outputTex
|
|
255
310
|
}
|
|
256
311
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
312
|
+
/* ============================================================
|
|
313
|
+
FftData — ComputedData producing 'real' and 'imag' columns
|
|
314
|
+
============================================================ */
|
|
315
|
+
|
|
316
|
+
class FftData extends ComputedData {
|
|
317
|
+
columns() { return ['real', 'imag'] }
|
|
318
|
+
|
|
319
|
+
async compute(regl, params, data, getAxisDomain) {
|
|
320
|
+
const inputCol = data.getData(params.input)
|
|
321
|
+
if (!(inputCol instanceof ArrayColumn)) {
|
|
322
|
+
throw new Error(`FftData: input '${params.input}' must be a plain data column`)
|
|
323
|
+
}
|
|
324
|
+
const N = nextPow2(inputCol.array.length)
|
|
325
|
+
const complexTex = await fft1d(regl, inputCol.array, params.inverse ?? false)
|
|
326
|
+
return {
|
|
327
|
+
real: extractAndRepack(regl, complexTex, 'r', N),
|
|
328
|
+
imag: extractAndRepack(regl, complexTex, 'g', N),
|
|
329
|
+
_meta: {
|
|
330
|
+
domains: { real: null, imag: null },
|
|
331
|
+
quantityKinds: { real: null, imag: null }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
260
334
|
}
|
|
335
|
+
|
|
261
336
|
schema(data) {
|
|
337
|
+
const cols = data ? data.columns() : []
|
|
262
338
|
return {
|
|
263
339
|
type: 'object',
|
|
340
|
+
title: 'FftData',
|
|
264
341
|
properties: {
|
|
265
|
-
input:
|
|
266
|
-
inverse: { type: 'boolean' }
|
|
342
|
+
input: { type: 'string', enum: cols, description: 'Input signal column' },
|
|
343
|
+
inverse: { type: 'boolean', default: false, description: 'Inverse FFT' }
|
|
267
344
|
},
|
|
268
345
|
required: ['input']
|
|
269
346
|
}
|
|
270
347
|
}
|
|
271
348
|
}
|
|
272
349
|
|
|
350
|
+
registerComputedData('FftData', new FftData())
|
|
351
|
+
|
|
352
|
+
/* ============================================================
|
|
353
|
+
FftConvolutionComputation — TextureComputation (real output)
|
|
354
|
+
============================================================ */
|
|
355
|
+
|
|
273
356
|
class FftConvolutionComputation extends TextureComputation {
|
|
274
|
-
|
|
275
|
-
|
|
357
|
+
getQuantityKind(params, data) { return resolveQuantityKind(params.signal, data) }
|
|
358
|
+
async compute(regl, params, data, getAxisDomain) {
|
|
359
|
+
const signal = params.signal instanceof ArrayColumn ? params.signal.array : params.signal
|
|
360
|
+
const kernel = params.kernel instanceof ArrayColumn ? params.kernel.array : params.kernel
|
|
361
|
+
const N = nextPow2(signal.length + kernel.length)
|
|
362
|
+
const complexResult = await fftConvolution(regl, signal, kernel)
|
|
363
|
+
return extractAndRepack(regl, complexResult, 'r', N)
|
|
276
364
|
}
|
|
277
365
|
schema(data) {
|
|
278
366
|
return {
|
|
279
367
|
type: 'object',
|
|
368
|
+
title: 'fftConvolution',
|
|
280
369
|
properties: {
|
|
281
370
|
signal: EXPRESSION_REF,
|
|
282
371
|
kernel: EXPRESSION_REF
|
|
@@ -286,7 +375,4 @@ class FftConvolutionComputation extends TextureComputation {
|
|
|
286
375
|
}
|
|
287
376
|
}
|
|
288
377
|
|
|
289
|
-
// fft1d: output is a complex texture — R = real part, G = imaginary part.
|
|
290
|
-
// Use a downstream computation (e.g. magnitude) to get a single scalar per bin.
|
|
291
|
-
registerTextureComputation('fft1d', new Fft1dComputation())
|
|
292
378
|
registerTextureComputation('fftConvolution', new FftConvolutionComputation())
|
package/src/compute/filter.js
CHANGED
|
@@ -1,166 +1,162 @@
|
|
|
1
|
-
import { registerTextureComputation,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (input instanceof Float32Array) {
|
|
5
|
-
return regl.texture({ data: input, shape: [length, 1], type: 'float', format: 'rgba' });
|
|
6
|
-
}
|
|
7
|
-
return input; // already a texture
|
|
8
|
-
}
|
|
1
|
+
import { registerTextureComputation, EXPRESSION_REF, resolveQuantityKind } from "./ComputationRegistry.js"
|
|
2
|
+
import { TextureComputation } from "../data/Computation.js"
|
|
3
|
+
import { ArrayColumn, SAMPLE_COLUMN_GLSL } from "../data/ColumnData.js"
|
|
9
4
|
|
|
10
5
|
function subtractTextures(regl, texA, texB) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
6
|
+
const w = texA.width
|
|
7
|
+
const h = texA.height
|
|
8
|
+
const outputTex = regl.texture({ width: w, height: h, type: 'float', format: 'rgba' })
|
|
9
|
+
const outputFBO = regl.framebuffer({ color: outputTex, depth: false, stencil: false })
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
regl({
|
|
16
12
|
framebuffer: outputFBO,
|
|
17
|
-
vert:
|
|
13
|
+
vert: `#version 300 es
|
|
18
14
|
precision highp float;
|
|
19
|
-
|
|
20
|
-
void main() {
|
|
21
|
-
float x = (bin + 0.5)/${length}.0*2.0 - 1.0;
|
|
22
|
-
gl_Position = vec4(x,0.0,0.0,1.0);
|
|
23
|
-
}
|
|
15
|
+
in vec2 position;
|
|
16
|
+
void main() { gl_Position = vec4(position, 0.0, 1.0); }
|
|
24
17
|
`,
|
|
25
|
-
frag:
|
|
18
|
+
frag: `#version 300 es
|
|
26
19
|
precision highp float;
|
|
27
20
|
uniform sampler2D texA;
|
|
28
21
|
uniform sampler2D texB;
|
|
29
22
|
out vec4 fragColor;
|
|
30
23
|
void main() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
float b = texelFetch(texB, ivec2(idx,0),0).r;
|
|
34
|
-
fragColor = vec4(a-b,0.0,0.0,1.0);
|
|
24
|
+
ivec2 coord = ivec2(gl_FragCoord.xy);
|
|
25
|
+
fragColor = texelFetch(texA, coord, 0) - texelFetch(texB, coord, 0);
|
|
35
26
|
}
|
|
36
27
|
`,
|
|
37
|
-
attributes: {
|
|
28
|
+
attributes: { position: [[-1, -1], [1, -1], [-1, 1], [1, 1]] },
|
|
38
29
|
uniforms: { texA, texB },
|
|
39
|
-
count:
|
|
40
|
-
primitive: '
|
|
41
|
-
})
|
|
30
|
+
count: 4,
|
|
31
|
+
primitive: 'triangle strip'
|
|
32
|
+
})()
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
return outputTex
|
|
34
|
+
if (texA._dataLength !== undefined) outputTex._dataLength = texA._dataLength
|
|
35
|
+
return outputTex
|
|
45
36
|
}
|
|
46
37
|
|
|
47
38
|
/**
|
|
48
39
|
* Generic 1D convolution filter
|
|
49
40
|
* @param {regl} regl - regl context
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {Float32Array} kernel - 1D kernel
|
|
52
|
-
* @returns {Texture} - filtered output texture
|
|
41
|
+
* @param {Texture} inputTex - 4-packed GPU texture, _dataLength set
|
|
42
|
+
* @param {Float32Array} kernel - 1D kernel weights
|
|
43
|
+
* @returns {Texture} - filtered output texture (4-packed, same dimensions as input)
|
|
53
44
|
*/
|
|
54
|
-
function filter1D(regl,
|
|
55
|
-
const length =
|
|
56
|
-
const
|
|
45
|
+
function filter1D(regl, inputTex, kernel) {
|
|
46
|
+
const length = inputTex._dataLength ?? inputTex.width * inputTex.height * 4
|
|
47
|
+
const w = inputTex.width
|
|
48
|
+
const h = inputTex.height
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
// Kernel texture stays R-channel (internal, not exposed via sampleColumn)
|
|
51
|
+
const kernelData = new Float32Array(kernel.length * 4)
|
|
52
|
+
for (let i = 0; i < kernel.length; i++) kernelData[i * 4] = kernel[i]
|
|
53
|
+
const kernelTex = regl.texture({ data: kernelData, shape: [kernel.length, 1], type: 'float', format: 'rgba' })
|
|
54
|
+
const outputTex = regl.texture({ width: w, height: h, type: 'float', format: 'rgba' })
|
|
55
|
+
const outputFBO = regl.framebuffer({ color: outputTex, depth: false, stencil: false })
|
|
59
56
|
|
|
60
|
-
const
|
|
61
|
-
const outputFBO = regl.framebuffer({ color: outputTex });
|
|
57
|
+
const radius = Math.floor(kernel.length / 2)
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const drawFilter = regl({
|
|
59
|
+
regl({
|
|
66
60
|
framebuffer: outputFBO,
|
|
67
|
-
vert:
|
|
61
|
+
vert: `#version 300 es
|
|
68
62
|
precision highp float;
|
|
69
|
-
|
|
70
|
-
void main() {
|
|
71
|
-
float x = (bin + 0.5)/${length}.0*2.0 - 1.0;
|
|
72
|
-
gl_Position = vec4(x, 0.0, 0.0, 1.0);
|
|
73
|
-
}
|
|
63
|
+
in vec2 position;
|
|
64
|
+
void main() { gl_Position = vec4(position, 0.0, 1.0); }
|
|
74
65
|
`,
|
|
75
|
-
frag:
|
|
76
|
-
#version 300 es
|
|
66
|
+
frag: `#version 300 es
|
|
77
67
|
precision highp float;
|
|
68
|
+
precision highp sampler2D;
|
|
78
69
|
uniform sampler2D inputTex;
|
|
79
70
|
uniform sampler2D kernelTex;
|
|
80
71
|
uniform int radius;
|
|
81
|
-
uniform int
|
|
72
|
+
uniform int totalLength;
|
|
82
73
|
out vec4 fragColor;
|
|
83
|
-
|
|
74
|
+
${SAMPLE_COLUMN_GLSL}
|
|
84
75
|
void main() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
float
|
|
92
|
-
|
|
76
|
+
ivec2 sz = textureSize(inputTex, 0);
|
|
77
|
+
int texelI = int(gl_FragCoord.y) * sz.x + int(gl_FragCoord.x);
|
|
78
|
+
int base = texelI * 4;
|
|
79
|
+
float s0 = 0.0, s1 = 0.0, s2 = 0.0, s3 = 0.0;
|
|
80
|
+
for (int i = -16; i <= 16; i++) {
|
|
81
|
+
if (i + 16 >= radius * 2 + 1) break;
|
|
82
|
+
float kw = texelFetch(kernelTex, ivec2(i + radius, 0), 0).r;
|
|
83
|
+
s0 += sampleColumn(inputTex, float(clamp(base + 0 + i, 0, totalLength - 1))) * kw;
|
|
84
|
+
s1 += sampleColumn(inputTex, float(clamp(base + 1 + i, 0, totalLength - 1))) * kw;
|
|
85
|
+
s2 += sampleColumn(inputTex, float(clamp(base + 2 + i, 0, totalLength - 1))) * kw;
|
|
86
|
+
s3 += sampleColumn(inputTex, float(clamp(base + 3 + i, 0, totalLength - 1))) * kw;
|
|
93
87
|
}
|
|
94
|
-
fragColor = vec4(
|
|
88
|
+
fragColor = vec4(s0, s1, s2, s3);
|
|
95
89
|
}
|
|
96
90
|
`,
|
|
97
|
-
attributes: {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
},
|
|
106
|
-
count: length,
|
|
107
|
-
primitive: 'points'
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
drawFilter();
|
|
111
|
-
return outputTex;
|
|
91
|
+
attributes: { position: [[-1, -1], [1, -1], [-1, 1], [1, 1]] },
|
|
92
|
+
uniforms: { inputTex, kernelTex, radius, totalLength: length },
|
|
93
|
+
count: 4,
|
|
94
|
+
primitive: 'triangle strip'
|
|
95
|
+
})()
|
|
96
|
+
|
|
97
|
+
outputTex._dataLength = length
|
|
98
|
+
return outputTex
|
|
112
99
|
}
|
|
113
100
|
|
|
114
101
|
// Gaussian kernel helper
|
|
115
102
|
function gaussianKernel(size, sigma) {
|
|
116
|
-
const radius = Math.floor(size / 2)
|
|
117
|
-
const kernel = new Float32Array(size)
|
|
118
|
-
let sum = 0
|
|
103
|
+
const radius = Math.floor(size / 2)
|
|
104
|
+
const kernel = new Float32Array(size)
|
|
105
|
+
let sum = 0
|
|
119
106
|
for (let i = -radius; i <= radius; i++) {
|
|
120
|
-
const v = Math.exp(-0.5 * (i / sigma) ** 2)
|
|
121
|
-
kernel[i + radius] = v
|
|
122
|
-
sum += v
|
|
107
|
+
const v = Math.exp(-0.5 * (i / sigma) ** 2)
|
|
108
|
+
kernel[i + radius] = v
|
|
109
|
+
sum += v
|
|
123
110
|
}
|
|
124
|
-
for (let i = 0; i < size; i++) kernel[i] /= sum
|
|
125
|
-
return kernel
|
|
111
|
+
for (let i = 0; i < size; i++) kernel[i] /= sum
|
|
112
|
+
return kernel
|
|
126
113
|
}
|
|
127
114
|
|
|
128
115
|
/**
|
|
129
116
|
* Low-pass filter
|
|
117
|
+
* @param {regl} regl
|
|
118
|
+
* @param {Texture} inputTex - 4-packed GPU texture
|
|
130
119
|
*/
|
|
131
|
-
function lowPass(regl,
|
|
132
|
-
const size = kernelSize || Math.ceil(sigma*6)|1
|
|
133
|
-
const kernel = gaussianKernel(size, sigma)
|
|
134
|
-
return filter1D(regl,
|
|
120
|
+
function lowPass(regl, inputTex, sigma = 3, kernelSize = null) {
|
|
121
|
+
const size = kernelSize || (Math.ceil(sigma * 6) | 1) // ensure odd
|
|
122
|
+
const kernel = gaussianKernel(size, sigma)
|
|
123
|
+
return filter1D(regl, inputTex, kernel)
|
|
135
124
|
}
|
|
136
125
|
|
|
137
126
|
/**
|
|
138
127
|
* High-pass filter: subtract low-pass
|
|
128
|
+
* @param {regl} regl
|
|
129
|
+
* @param {Texture} inputTex - 4-packed GPU texture
|
|
139
130
|
*/
|
|
140
|
-
function highPass(regl,
|
|
141
|
-
const low = lowPass(regl,
|
|
142
|
-
|
|
143
|
-
return subtractTextures(regl, input, low);
|
|
131
|
+
function highPass(regl, inputTex, sigma = 3, kernelSize = null) {
|
|
132
|
+
const low = lowPass(regl, inputTex, sigma, kernelSize)
|
|
133
|
+
return subtractTextures(regl, inputTex, low)
|
|
144
134
|
}
|
|
145
135
|
|
|
146
136
|
/**
|
|
147
137
|
* Band-pass filter: difference of low-pass filters
|
|
138
|
+
* @param {regl} regl
|
|
139
|
+
* @param {Texture} inputTex - 4-packed GPU texture
|
|
148
140
|
*/
|
|
149
|
-
function bandPass(regl,
|
|
150
|
-
const lowHigh = lowPass(regl,
|
|
151
|
-
const lowLow
|
|
152
|
-
return subtractTextures(regl, lowHigh, lowLow)
|
|
141
|
+
function bandPass(regl, inputTex, sigmaLow, sigmaHigh) {
|
|
142
|
+
const lowHigh = lowPass(regl, inputTex, sigmaHigh)
|
|
143
|
+
const lowLow = lowPass(regl, inputTex, sigmaLow)
|
|
144
|
+
return subtractTextures(regl, lowHigh, lowLow)
|
|
153
145
|
}
|
|
154
146
|
|
|
155
147
|
export { filter1D, gaussianKernel, lowPass, highPass, bandPass }
|
|
156
148
|
|
|
157
149
|
class Filter1DComputation extends TextureComputation {
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
getQuantityKind(params, data) { return resolveQuantityKind(params.input, data) }
|
|
151
|
+
compute(regl, inputs, getAxisDomain) {
|
|
152
|
+
const inputTex = inputs.input.toTexture(regl)
|
|
153
|
+
const kernelArr = inputs.kernel instanceof ArrayColumn ? inputs.kernel.array : inputs.kernel
|
|
154
|
+
return filter1D(regl, inputTex, kernelArr)
|
|
160
155
|
}
|
|
161
156
|
schema(data) {
|
|
162
157
|
return {
|
|
163
158
|
type: 'object',
|
|
159
|
+
title: 'filter1D',
|
|
164
160
|
properties: {
|
|
165
161
|
input: EXPRESSION_REF,
|
|
166
162
|
kernel: EXPRESSION_REF
|
|
@@ -171,12 +167,14 @@ class Filter1DComputation extends TextureComputation {
|
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
class LowPassComputation extends TextureComputation {
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
getQuantityKind(params, data) { return resolveQuantityKind(params.input, data) }
|
|
171
|
+
compute(regl, inputs, getAxisDomain) {
|
|
172
|
+
return lowPass(regl, inputs.input.toTexture(regl), inputs.sigma, inputs.kernelSize)
|
|
176
173
|
}
|
|
177
174
|
schema(data) {
|
|
178
175
|
return {
|
|
179
176
|
type: 'object',
|
|
177
|
+
title: 'lowPass',
|
|
180
178
|
properties: {
|
|
181
179
|
input: EXPRESSION_REF,
|
|
182
180
|
sigma: { type: 'number' },
|
|
@@ -188,12 +186,14 @@ class LowPassComputation extends TextureComputation {
|
|
|
188
186
|
}
|
|
189
187
|
|
|
190
188
|
class HighPassComputation extends TextureComputation {
|
|
191
|
-
|
|
192
|
-
|
|
189
|
+
getQuantityKind(params, data) { return resolveQuantityKind(params.input, data) }
|
|
190
|
+
compute(regl, inputs, getAxisDomain) {
|
|
191
|
+
return highPass(regl, inputs.input.toTexture(regl), inputs.sigma, inputs.kernelSize)
|
|
193
192
|
}
|
|
194
193
|
schema(data) {
|
|
195
194
|
return {
|
|
196
195
|
type: 'object',
|
|
196
|
+
title: 'highPass',
|
|
197
197
|
properties: {
|
|
198
198
|
input: EXPRESSION_REF,
|
|
199
199
|
sigma: { type: 'number' },
|
|
@@ -205,12 +205,14 @@ class HighPassComputation extends TextureComputation {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
class BandPassComputation extends TextureComputation {
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
getQuantityKind(params, data) { return resolveQuantityKind(params.input, data) }
|
|
209
|
+
compute(regl, inputs, getAxisDomain) {
|
|
210
|
+
return bandPass(regl, inputs.input.toTexture(regl), inputs.sigmaLow, inputs.sigmaHigh)
|
|
210
211
|
}
|
|
211
212
|
schema(data) {
|
|
212
213
|
return {
|
|
213
214
|
type: 'object',
|
|
215
|
+
title: 'bandPass',
|
|
214
216
|
properties: {
|
|
215
217
|
input: EXPRESSION_REF,
|
|
216
218
|
sigmaLow: { type: 'number' },
|