@vitessce/spatial-accelerated 3.9.4 → 3.9.6
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/dist/index.js +43 -6
- package/dist-tsc/VolumeDataManager.d.ts +19 -2
- package/dist-tsc/VolumeDataManager.d.ts.map +1 -1
- package/dist-tsc/VolumeDataManager.js +41 -8
- package/dist-tsc/VolumeDataManager.test.js +23 -2
- package/dist-tsc/VolumeView.d.ts.map +1 -1
- package/dist-tsc/VolumeView.js +10 -3
- package/package.json +11 -11
- package/src/VolumeDataManager.js +52 -8
- package/src/VolumeDataManager.test.js +23 -2
- package/src/VolumeView.js +14 -3
package/dist/index.js
CHANGED
|
@@ -59468,9 +59468,17 @@ function _ptToZarr(ptx, pty, ptz, ptInfo) {
|
|
|
59468
59468
|
z
|
|
59469
59469
|
};
|
|
59470
59470
|
}
|
|
59471
|
-
|
|
59471
|
+
const DEFAULT_SIGMA_NORMALIZED = 0.25;
|
|
59472
|
+
function _requestBufferToRequestObjects(buffer, k, optsForWeighting) {
|
|
59472
59473
|
const counts = /* @__PURE__ */ new Map();
|
|
59473
|
-
|
|
59474
|
+
const { width: width2, height: height2, sigmaNormalized } = optsForWeighting;
|
|
59475
|
+
const useWeighting = Number.isInteger(width2) && Number.isInteger(height2) && width2 > 0 && height2 > 0 && typeof sigmaNormalized === "number";
|
|
59476
|
+
if (!useWeighting) {
|
|
59477
|
+
log.warn("_requestBufferToRequestObjects: proceeding without weighting");
|
|
59478
|
+
}
|
|
59479
|
+
const maxPixels = Math.floor(buffer.length / 4);
|
|
59480
|
+
for (let pix = 0; pix < maxPixels; pix += 1) {
|
|
59481
|
+
const i2 = pix * 4;
|
|
59474
59482
|
const r2 = buffer[i2];
|
|
59475
59483
|
const g = buffer[i2 + 1];
|
|
59476
59484
|
const b = buffer[i2 + 2];
|
|
@@ -59479,7 +59487,17 @@ function _requestBufferToRequestObjects(buffer, k) {
|
|
|
59479
59487
|
continue;
|
|
59480
59488
|
}
|
|
59481
59489
|
const packed = (r2 << 24 | g << 16 | b << 8 | a) >>> 0;
|
|
59482
|
-
|
|
59490
|
+
let weight = 1;
|
|
59491
|
+
if (useWeighting) {
|
|
59492
|
+
const x = pix % width2;
|
|
59493
|
+
const y = Math.floor(pix / width2);
|
|
59494
|
+
const dx = x - width2 / 2;
|
|
59495
|
+
const dy = y - height2 / 2;
|
|
59496
|
+
const nd = Math.sqrt(dx * dx / (width2 * width2) + dy * dy / (height2 * height2));
|
|
59497
|
+
const norm = nd / sigmaNormalized;
|
|
59498
|
+
weight = Math.exp(-0.5 * norm * norm);
|
|
59499
|
+
}
|
|
59500
|
+
counts.set(packed, (counts.get(packed) || 0) + weight);
|
|
59483
59501
|
}
|
|
59484
59502
|
const requests = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, k).map(([packed]) => ({
|
|
59485
59503
|
x: packed >> 22 & 1023,
|
|
@@ -60140,7 +60158,19 @@ class VolumeDataManager {
|
|
|
60140
60158
|
}
|
|
60141
60159
|
return chunkEntry.data;
|
|
60142
60160
|
}
|
|
60143
|
-
|
|
60161
|
+
/**
|
|
60162
|
+
* Process the buffer of brick requests from the shader, turning them into
|
|
60163
|
+
* actual Promises for Zarr chunks on the JS side.
|
|
60164
|
+
* @param {Uint8Array} buffer The bufRequest (of length width*height*4)
|
|
60165
|
+
* containing the brick requests from the shader.
|
|
60166
|
+
* @param {object} optsForWeighting
|
|
60167
|
+
* @param {number} optsForWeighting.width The width of the render target.
|
|
60168
|
+
* @param {number} optsForWeighting.height The height of the render target.
|
|
60169
|
+
* @param {number} optsForWeighting.sigmaNormalized The normalized sigma value
|
|
60170
|
+
* to use for weighting the brick requests based on their distance from
|
|
60171
|
+
* the center of the render target.
|
|
60172
|
+
*/
|
|
60173
|
+
async processRequestData(buffer, optsForWeighting) {
|
|
60144
60174
|
if (this.isBusy) {
|
|
60145
60175
|
log.debug("processRequestData: already busy, skipping");
|
|
60146
60176
|
return;
|
|
@@ -60155,7 +60185,7 @@ class VolumeDataManager {
|
|
|
60155
60185
|
}
|
|
60156
60186
|
this.isBusy = true;
|
|
60157
60187
|
this.triggerRequest = false;
|
|
60158
|
-
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k);
|
|
60188
|
+
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k, optsForWeighting);
|
|
60159
60189
|
if (requests.length === 0) {
|
|
60160
60190
|
this.noNewRequests = true;
|
|
60161
60191
|
}
|
|
@@ -62902,7 +62932,14 @@ function handleRequests(_gl, { frameRef, dataManager, mrtRef, bufRequest, bufUsa
|
|
|
62902
62932
|
ctx.UNSIGNED_BYTE,
|
|
62903
62933
|
bufRequest.current
|
|
62904
62934
|
);
|
|
62905
|
-
dataManager.processRequestData(bufRequest.current
|
|
62935
|
+
dataManager.processRequestData(bufRequest.current, {
|
|
62936
|
+
// If width/height are not yet available,
|
|
62937
|
+
// processRequestData will proceed without weighting.
|
|
62938
|
+
width: mrtRef?.current?.width,
|
|
62939
|
+
height: mrtRef?.current?.height,
|
|
62940
|
+
// Default sigmaNormalized = 0.25; can be tuned
|
|
62941
|
+
sigmaNormalized: DEFAULT_SIGMA_NORMALIZED
|
|
62942
|
+
});
|
|
62906
62943
|
} else if (dataManager.triggerUsage === true && dataManager.noNewRequests === false) {
|
|
62907
62944
|
ctx.bindFramebuffer(ctx.READ_FRAMEBUFFER, framebufferFor(_gl, mrtRef.current));
|
|
62908
62945
|
ctx.readBuffer(ctx.COLOR_ATTACHMENT2);
|
|
@@ -27,7 +27,7 @@ export function _ptToZarr(ptx: any, pty: any, ptz: any, ptInfo: any): {
|
|
|
27
27
|
y: number;
|
|
28
28
|
z: number;
|
|
29
29
|
};
|
|
30
|
-
export function _requestBufferToRequestObjects(buffer: any, k: any): {
|
|
30
|
+
export function _requestBufferToRequestObjects(buffer: any, k: any, optsForWeighting: any): {
|
|
31
31
|
requests: {
|
|
32
32
|
x: number;
|
|
33
33
|
y: number;
|
|
@@ -41,6 +41,7 @@ export namespace INIT_STATUS {
|
|
|
41
41
|
let COMPLETE: string;
|
|
42
42
|
let FAILED: string;
|
|
43
43
|
}
|
|
44
|
+
export const DEFAULT_SIGMA_NORMALIZED: 0.25;
|
|
44
45
|
export class VolumeDataManager {
|
|
45
46
|
constructor(glParam: any);
|
|
46
47
|
gl: any;
|
|
@@ -198,7 +199,23 @@ export class VolumeDataManager {
|
|
|
198
199
|
* @returns {Promise<Uint8Array>} 32x32x32 chunk data
|
|
199
200
|
*/
|
|
200
201
|
loadZarrChunk(t: number | undefined, c: number | undefined, z: number, y: number, x: number, resolution: number): Promise<Uint8Array>;
|
|
201
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Process the buffer of brick requests from the shader, turning them into
|
|
204
|
+
* actual Promises for Zarr chunks on the JS side.
|
|
205
|
+
* @param {Uint8Array} buffer The bufRequest (of length width*height*4)
|
|
206
|
+
* containing the brick requests from the shader.
|
|
207
|
+
* @param {object} optsForWeighting
|
|
208
|
+
* @param {number} optsForWeighting.width The width of the render target.
|
|
209
|
+
* @param {number} optsForWeighting.height The height of the render target.
|
|
210
|
+
* @param {number} optsForWeighting.sigmaNormalized The normalized sigma value
|
|
211
|
+
* to use for weighting the brick requests based on their distance from
|
|
212
|
+
* the center of the render target.
|
|
213
|
+
*/
|
|
214
|
+
processRequestData(buffer: Uint8Array, optsForWeighting: {
|
|
215
|
+
width: number;
|
|
216
|
+
height: number;
|
|
217
|
+
sigmaNormalized: number;
|
|
218
|
+
}): Promise<void>;
|
|
202
219
|
processUsageData(buffer: any): Promise<void>;
|
|
203
220
|
_evictBrick(bcIndex: any): void;
|
|
204
221
|
_purgeChannel(ptChannelIndex: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeDataManager.d.ts","sourceRoot":"","sources":["../src/VolumeDataManager.js"],"names":[],"mappings":"AA8DA,yEAaC;AACD,8EAeC;AAED;;;;GAIG;AACH,kDAHW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,8BAC1B,MAAM;;;;;;;;;;;;;EAyHhB;AAMD,kFAaC;AAqBD;;;;;;EA2DC;
|
|
1
|
+
{"version":3,"file":"VolumeDataManager.d.ts","sourceRoot":"","sources":["../src/VolumeDataManager.js"],"names":[],"mappings":"AA8DA,yEAaC;AACD,8EAeC;AAED;;;;GAIG;AACH,kDAHW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,8BAC1B,MAAM;;;;;;;;;;;;;EAyHhB;AAMD,kFAaC;AAqBD;;;;;;EA2DC;AAKD;;;;;;;EAuDC;;;;;;;AAzDD,4CAA6C;AA6D7C;IACE,0BAgLC;IAjKG,QAAyB;IAa3B,cAAwB;IAyBxB,sBAA+B;IAC/B,wBAA2B;IAC3B,iCAAmC;IAenC;;;;;MAKC;IAED;;;;;;;;;;;;MAcC;IAED,8BAAmB;IACnB,8BAAmB;IAEnB;;;;;;MAMC;IAED;;;;;;;;;MAiBC;IAQD,2BAAiC;IAYjC,gBAAmB;IAGnB,oBAAuD;IACvD,gBAAwD;IACxD,gBAAmB;IACnB,sBAAsB;IAGtB,aAAmD;IAGnD,gBAAkB;IAElB,sBAAwB;IACxB,wBAA2B;IAC3B,kBAAkB;IAClB,UAAW;IACX,uBAA0B;IAC1B,yBAA4B;IAE5B,sBAAyB;IAGzB,mBAAyC;IACzC,eAAqB;IAGrB;;;;;;;;;;aAA8B;IAG9B,4BAA4B;IAC5B,6BAA6B;IAK/B;;;OAGG;IACH,sBAFa,MAAM,CAiBlB;IAED;;OAEG;IACH,oBAQC;IAED;;OAEG;IACH,uBASC;IAED;;OAEG;IACH,qCAeC;IAED;;OAEG;IACH,yCAqCC;IAED;;OAEG;IACH,qBAMC;IAED;;OAEG;IACH,uCAIC;IAED,qDAIC;IAFC,YAAoB;IACpB,sBAAwC;IAG1C;;;;;OAKG;IACH,mBAFa,OAAO,CAAC,MAAM,CAAC,CA8S3B;IAjQG,kBAAmD;IAmQvD;;;;;;;;OAQG;IACH,mBAkBC;IAGD,6BAMC;IAED,wCAqHC;IAED;;;;;OAKG;IAqBH;;;OAGG;IACH,kCAQC;IAED;;;OAGG;IACH,uBAFa,MAAM,CAUlB;IAED,+BAQC;IAED,kCASC;IAED,gCASC;IAED;;;;;;;;;OASG;IACH,iBARW,MAAM,iBACN,MAAM,iBACN,MAAM,KACN,MAAM,KACN,MAAM,cACN,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CA+B/B;IAED;;;;;;;;;;;OAWG;IACH,2BATW,UAAU,oBAGlB;QAAiC,KAAK,EAA9B,MAAM;QACmB,MAAM,EAA/B,MAAM;QACmB,eAAe,EAAxC,MAAM;KAGhB,iBAiCA;IAGD,6CA0DC;IAGD,gCAcC;IAED,yCAmDC;IAGD,+DAqBC;IAKD;;;;OAIG;IACH,oBAHW,MAAM,GACJ;QAAE,OAAO,MAAC;QAAC,CAAC,MAAC;QAAC,CAAC,MAAC;QAAC,CAAC,MAAA;KAAE,EAAE,CAyClC;IAKD,uDA6LC;IAKD,oDAwDC;IAKD,kBAWC;CACF;8BAlwDM,OAAO"}
|
|
@@ -268,9 +268,21 @@ export function _ptToZarr(ptx, pty, ptz, ptInfo) {
|
|
|
268
268
|
z,
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
|
-
|
|
271
|
+
// sigmaNormalized: fraction of half-dimension that determines Gaussian spread
|
|
272
|
+
export const DEFAULT_SIGMA_NORMALIZED = 0.25;
|
|
273
|
+
export function _requestBufferToRequestObjects(buffer, k, optsForWeighting) {
|
|
272
274
|
const counts = new Map();
|
|
273
|
-
|
|
275
|
+
const { width, height, sigmaNormalized } = optsForWeighting;
|
|
276
|
+
const useWeighting = (Number.isInteger(width) && Number.isInteger(height)
|
|
277
|
+
&& width > 0 && height > 0
|
|
278
|
+
&& typeof sigmaNormalized === 'number');
|
|
279
|
+
if (!useWeighting) {
|
|
280
|
+
log.warn('_requestBufferToRequestObjects: proceeding without weighting');
|
|
281
|
+
}
|
|
282
|
+
// Number of pixels present in buffer
|
|
283
|
+
const maxPixels = Math.floor(buffer.length / 4);
|
|
284
|
+
for (let pix = 0; pix < maxPixels; pix += 1) {
|
|
285
|
+
const i = pix * 4;
|
|
274
286
|
const r = buffer[i];
|
|
275
287
|
const g = buffer[i + 1];
|
|
276
288
|
const b = buffer[i + 2];
|
|
@@ -280,11 +292,20 @@ export function _requestBufferToRequestObjects(buffer, k) {
|
|
|
280
292
|
continue;
|
|
281
293
|
}
|
|
282
294
|
const packed = ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
|
|
283
|
-
|
|
295
|
+
let weight = 1.0;
|
|
296
|
+
if (useWeighting) {
|
|
297
|
+
const x = pix % width;
|
|
298
|
+
const y = Math.floor(pix / width);
|
|
299
|
+
const dx = x - (width / 2);
|
|
300
|
+
const dy = y - (height / 2);
|
|
301
|
+
// normalized distance relative to full width/height to be resolution-independent
|
|
302
|
+
const nd = Math.sqrt((dx * dx) / (width * width) + (dy * dy) / (height * height));
|
|
303
|
+
const norm = nd / sigmaNormalized;
|
|
304
|
+
weight = Math.exp(-0.5 * norm * norm);
|
|
305
|
+
}
|
|
306
|
+
counts.set(packed, (counts.get(packed) || 0) + weight);
|
|
284
307
|
}
|
|
285
|
-
//
|
|
286
|
-
// log.debug('this.k', this.k);
|
|
287
|
-
// Get the top K (potentially fewer than K) page table requests.
|
|
308
|
+
// Get the top K (potentially fewer than K) page table requests by weighted count.
|
|
288
309
|
const requests = [...counts.entries()]
|
|
289
310
|
.sort((a, b) => b[1] - a[1])
|
|
290
311
|
.slice(0, k)
|
|
@@ -1074,7 +1095,19 @@ export class VolumeDataManager {
|
|
|
1074
1095
|
}
|
|
1075
1096
|
return chunkEntry.data;
|
|
1076
1097
|
}
|
|
1077
|
-
|
|
1098
|
+
/**
|
|
1099
|
+
* Process the buffer of brick requests from the shader, turning them into
|
|
1100
|
+
* actual Promises for Zarr chunks on the JS side.
|
|
1101
|
+
* @param {Uint8Array} buffer The bufRequest (of length width*height*4)
|
|
1102
|
+
* containing the brick requests from the shader.
|
|
1103
|
+
* @param {object} optsForWeighting
|
|
1104
|
+
* @param {number} optsForWeighting.width The width of the render target.
|
|
1105
|
+
* @param {number} optsForWeighting.height The height of the render target.
|
|
1106
|
+
* @param {number} optsForWeighting.sigmaNormalized The normalized sigma value
|
|
1107
|
+
* to use for weighting the brick requests based on their distance from
|
|
1108
|
+
* the center of the render target.
|
|
1109
|
+
*/
|
|
1110
|
+
async processRequestData(buffer, optsForWeighting) {
|
|
1078
1111
|
if (this.isBusy) {
|
|
1079
1112
|
log.debug('processRequestData: already busy, skipping');
|
|
1080
1113
|
return;
|
|
@@ -1091,7 +1124,7 @@ export class VolumeDataManager {
|
|
|
1091
1124
|
}
|
|
1092
1125
|
this.isBusy = true;
|
|
1093
1126
|
this.triggerRequest = false;
|
|
1094
|
-
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k);
|
|
1127
|
+
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k, optsForWeighting);
|
|
1095
1128
|
if (requests.length === 0) {
|
|
1096
1129
|
this.noNewRequests = true;
|
|
1097
1130
|
}
|
|
@@ -147,7 +147,7 @@ describe('spatial-accelerated data utils', () => {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
describe('_requestBufferToRequestObjects', () => {
|
|
150
|
-
it('correctly converts a buffer to request objects', () => {
|
|
150
|
+
it('correctly converts a buffer to request objects without optsForWeighting', () => {
|
|
151
151
|
const buffer = new Uint8Array([
|
|
152
152
|
0, 0, 0, 0,
|
|
153
153
|
0, 0, 0, 0,
|
|
@@ -155,7 +155,28 @@ describe('spatial-accelerated data utils', () => {
|
|
|
155
155
|
0, 0, 0, 0,
|
|
156
156
|
]);
|
|
157
157
|
const k = 40;
|
|
158
|
-
const { requests: result } = _requestBufferToRequestObjects(buffer, k
|
|
158
|
+
const { requests: result } = _requestBufferToRequestObjects(buffer, k, {
|
|
159
|
+
width: undefined,
|
|
160
|
+
height: undefined,
|
|
161
|
+
sigmaNormalized: undefined,
|
|
162
|
+
});
|
|
163
|
+
expect(result).toEqual([
|
|
164
|
+
{ x: 2, y: 1, z: 13 },
|
|
165
|
+
]);
|
|
166
|
+
});
|
|
167
|
+
it('correctly converts a buffer to request objects with optsForWeighting', () => {
|
|
168
|
+
const buffer = new Uint8Array([
|
|
169
|
+
0, 0, 0, 0,
|
|
170
|
+
0, 0, 0, 0,
|
|
171
|
+
0, 128, 16, 13,
|
|
172
|
+
0, 0, 0, 0,
|
|
173
|
+
]);
|
|
174
|
+
const k = 40;
|
|
175
|
+
const { requests: result } = _requestBufferToRequestObjects(buffer, k, {
|
|
176
|
+
width: 1,
|
|
177
|
+
height: 1,
|
|
178
|
+
sigmaNormalized: 0.25,
|
|
179
|
+
});
|
|
159
180
|
expect(result).toEqual([
|
|
160
181
|
{ x: 2, y: 1, z: 13 },
|
|
161
182
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeView.d.ts","sourceRoot":"","sources":["../src/VolumeView.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VolumeView.d.ts","sourceRoot":"","sources":["../src/VolumeView.js"],"names":[],"mappings":"AAkMA,2DA6gBC"}
|
package/dist-tsc/VolumeView.js
CHANGED
|
@@ -13,7 +13,7 @@ import * as THREE from 'three';
|
|
|
13
13
|
import { WebGLMultipleRenderTargets } from 'three';
|
|
14
14
|
import { log, atLeastLogLevel, LogLevel } from '@vitessce/globals';
|
|
15
15
|
import { useEventCallback } from '@vitessce/styles';
|
|
16
|
-
import { VolumeDataManager, INIT_STATUS } from './VolumeDataManager.js';
|
|
16
|
+
import { VolumeDataManager, INIT_STATUS, DEFAULT_SIGMA_NORMALIZED, } from './VolumeDataManager.js';
|
|
17
17
|
import { VolumeRenderManager } from './VolumeRenderManager.js';
|
|
18
18
|
import { gaussianVertexShader } from './shaders/GaussianVertexShader.js';
|
|
19
19
|
import { gaussianFragmentShader } from './shaders/GaussianFragmentShader.js';
|
|
@@ -57,8 +57,15 @@ function handleRequests(_gl, { frameRef, dataManager, mrtRef, bufRequest, bufUsa
|
|
|
57
57
|
ctx.readPixels(0, 0, mrtRef.current.width, mrtRef.current.height, ctx.RGBA, ctx.UNSIGNED_BYTE, bufRequest.current);
|
|
58
58
|
// Based on the request buffer contents, process the requests
|
|
59
59
|
// (e.g., start loading the brick data and upload to the brick cache).
|
|
60
|
-
//
|
|
61
|
-
dataManager.processRequestData(bufRequest.current
|
|
60
|
+
// Pass width/height so the data manager can compute Gaussian weighting.
|
|
61
|
+
dataManager.processRequestData(bufRequest.current, {
|
|
62
|
+
// If width/height are not yet available,
|
|
63
|
+
// processRequestData will proceed without weighting.
|
|
64
|
+
width: mrtRef?.current?.width,
|
|
65
|
+
height: mrtRef?.current?.height,
|
|
66
|
+
// Default sigmaNormalized = 0.25; can be tuned
|
|
67
|
+
sigmaNormalized: DEFAULT_SIGMA_NORMALIZED,
|
|
68
|
+
});
|
|
62
69
|
}
|
|
63
70
|
else if (dataManager.triggerUsage === true && dataManager.noNewRequests === false) {
|
|
64
71
|
// Read the pixels of the usage buffer into the width*height*RGBA bufUsage.current array.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitessce/spatial-accelerated",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.6",
|
|
4
4
|
"author": "HIDIVE Lab at HMS",
|
|
5
5
|
"homepage": "http://vitessce.io",
|
|
6
6
|
"repository": {
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
"short-number": "^1.0.6",
|
|
29
29
|
"three": "^0.154.0",
|
|
30
30
|
"zarrita": "0.6.1",
|
|
31
|
-
"@vitessce/constants-internal": "3.9.
|
|
32
|
-
"@vitessce/gl": "3.9.
|
|
33
|
-
"@vitessce/legend": "3.9.
|
|
34
|
-
"@vitessce/sets-utils": "3.9.
|
|
35
|
-
"@vitessce/spatial-utils": "3.9.
|
|
36
|
-
"@vitessce/
|
|
37
|
-
"@vitessce/
|
|
38
|
-
"@vitessce/
|
|
39
|
-
"@vitessce/globals": "3.9.
|
|
40
|
-
"@vitessce/
|
|
31
|
+
"@vitessce/constants-internal": "3.9.6",
|
|
32
|
+
"@vitessce/gl": "3.9.6",
|
|
33
|
+
"@vitessce/legend": "3.9.6",
|
|
34
|
+
"@vitessce/sets-utils": "3.9.6",
|
|
35
|
+
"@vitessce/spatial-utils": "3.9.6",
|
|
36
|
+
"@vitessce/utils": "3.9.6",
|
|
37
|
+
"@vitessce/vit-s": "3.9.6",
|
|
38
|
+
"@vitessce/tooltip": "3.9.6",
|
|
39
|
+
"@vitessce/globals": "3.9.6",
|
|
40
|
+
"@vitessce/styles": "3.9.6"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/three": "^0.154.0",
|
package/src/VolumeDataManager.js
CHANGED
|
@@ -316,9 +316,28 @@ export function _ptToZarr(ptx, pty, ptz, ptInfo) {
|
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
// sigmaNormalized: fraction of half-dimension that determines Gaussian spread
|
|
320
|
+
export const DEFAULT_SIGMA_NORMALIZED = 0.25;
|
|
321
|
+
|
|
322
|
+
export function _requestBufferToRequestObjects(buffer, k, optsForWeighting) {
|
|
320
323
|
const counts = new Map();
|
|
321
|
-
|
|
324
|
+
|
|
325
|
+
const { width, height, sigmaNormalized } = optsForWeighting;
|
|
326
|
+
|
|
327
|
+
const useWeighting = (
|
|
328
|
+
Number.isInteger(width) && Number.isInteger(height)
|
|
329
|
+
&& width > 0 && height > 0
|
|
330
|
+
&& typeof sigmaNormalized === 'number'
|
|
331
|
+
);
|
|
332
|
+
if (!useWeighting) {
|
|
333
|
+
log.warn('_requestBufferToRequestObjects: proceeding without weighting');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Number of pixels present in buffer
|
|
337
|
+
const maxPixels = Math.floor(buffer.length / 4);
|
|
338
|
+
|
|
339
|
+
for (let pix = 0; pix < maxPixels; pix += 1) {
|
|
340
|
+
const i = pix * 4;
|
|
322
341
|
const r = buffer[i];
|
|
323
342
|
const g = buffer[i + 1];
|
|
324
343
|
const b = buffer[i + 2];
|
|
@@ -328,11 +347,23 @@ export function _requestBufferToRequestObjects(buffer, k) {
|
|
|
328
347
|
continue;
|
|
329
348
|
}
|
|
330
349
|
const packed = ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
|
|
331
|
-
|
|
350
|
+
|
|
351
|
+
let weight = 1.0;
|
|
352
|
+
if (useWeighting) {
|
|
353
|
+
const x = pix % width;
|
|
354
|
+
const y = Math.floor(pix / width);
|
|
355
|
+
const dx = x - (width / 2);
|
|
356
|
+
const dy = y - (height / 2);
|
|
357
|
+
// normalized distance relative to full width/height to be resolution-independent
|
|
358
|
+
const nd = Math.sqrt((dx * dx) / (width * width) + (dy * dy) / (height * height));
|
|
359
|
+
const norm = nd / sigmaNormalized;
|
|
360
|
+
weight = Math.exp(-0.5 * norm * norm);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
counts.set(packed, (counts.get(packed) || 0) + weight);
|
|
332
364
|
}
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
// Get the top K (potentially fewer than K) page table requests.
|
|
365
|
+
|
|
366
|
+
// Get the top K (potentially fewer than K) page table requests by weighted count.
|
|
336
367
|
const requests = [...counts.entries()]
|
|
337
368
|
.sort((a, b) => b[1] - a[1])
|
|
338
369
|
.slice(0, k)
|
|
@@ -341,6 +372,7 @@ export function _requestBufferToRequestObjects(buffer, k) {
|
|
|
341
372
|
y: (packed >> 12) & 0x3FF,
|
|
342
373
|
z: packed & 0xFFF,
|
|
343
374
|
}));
|
|
375
|
+
|
|
344
376
|
return { requests, origRequestCount: counts.size };
|
|
345
377
|
}
|
|
346
378
|
|
|
@@ -1253,7 +1285,19 @@ export class VolumeDataManager {
|
|
|
1253
1285
|
return chunkEntry.data;
|
|
1254
1286
|
}
|
|
1255
1287
|
|
|
1256
|
-
|
|
1288
|
+
/**
|
|
1289
|
+
* Process the buffer of brick requests from the shader, turning them into
|
|
1290
|
+
* actual Promises for Zarr chunks on the JS side.
|
|
1291
|
+
* @param {Uint8Array} buffer The bufRequest (of length width*height*4)
|
|
1292
|
+
* containing the brick requests from the shader.
|
|
1293
|
+
* @param {object} optsForWeighting
|
|
1294
|
+
* @param {number} optsForWeighting.width The width of the render target.
|
|
1295
|
+
* @param {number} optsForWeighting.height The height of the render target.
|
|
1296
|
+
* @param {number} optsForWeighting.sigmaNormalized The normalized sigma value
|
|
1297
|
+
* to use for weighting the brick requests based on their distance from
|
|
1298
|
+
* the center of the render target.
|
|
1299
|
+
*/
|
|
1300
|
+
async processRequestData(buffer, optsForWeighting) {
|
|
1257
1301
|
if (this.isBusy) {
|
|
1258
1302
|
log.debug('processRequestData: already busy, skipping');
|
|
1259
1303
|
return;
|
|
@@ -1274,7 +1318,7 @@ export class VolumeDataManager {
|
|
|
1274
1318
|
this.isBusy = true;
|
|
1275
1319
|
this.triggerRequest = false;
|
|
1276
1320
|
|
|
1277
|
-
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k);
|
|
1321
|
+
const { requests, origRequestCount } = _requestBufferToRequestObjects(buffer, this.k, optsForWeighting);
|
|
1278
1322
|
|
|
1279
1323
|
if (requests.length === 0) {
|
|
1280
1324
|
this.noNewRequests = true;
|
|
@@ -163,7 +163,7 @@ describe('spatial-accelerated data utils', () => {
|
|
|
163
163
|
});
|
|
164
164
|
});
|
|
165
165
|
describe('_requestBufferToRequestObjects', () => {
|
|
166
|
-
it('correctly converts a buffer to request objects', () => {
|
|
166
|
+
it('correctly converts a buffer to request objects without optsForWeighting', () => {
|
|
167
167
|
const buffer = new Uint8Array([
|
|
168
168
|
0, 0, 0, 0,
|
|
169
169
|
0, 0, 0, 0,
|
|
@@ -171,7 +171,28 @@ describe('spatial-accelerated data utils', () => {
|
|
|
171
171
|
0, 0, 0, 0,
|
|
172
172
|
]);
|
|
173
173
|
const k = 40;
|
|
174
|
-
const { requests: result } = _requestBufferToRequestObjects(buffer, k
|
|
174
|
+
const { requests: result } = _requestBufferToRequestObjects(buffer, k, {
|
|
175
|
+
width: undefined,
|
|
176
|
+
height: undefined,
|
|
177
|
+
sigmaNormalized: undefined,
|
|
178
|
+
});
|
|
179
|
+
expect(result).toEqual([
|
|
180
|
+
{ x: 2, y: 1, z: 13 },
|
|
181
|
+
]);
|
|
182
|
+
});
|
|
183
|
+
it('correctly converts a buffer to request objects with optsForWeighting', () => {
|
|
184
|
+
const buffer = new Uint8Array([
|
|
185
|
+
0, 0, 0, 0,
|
|
186
|
+
0, 0, 0, 0,
|
|
187
|
+
0, 128, 16, 13,
|
|
188
|
+
0, 0, 0, 0,
|
|
189
|
+
]);
|
|
190
|
+
const k = 40;
|
|
191
|
+
const { requests: result } = _requestBufferToRequestObjects(buffer, k, {
|
|
192
|
+
width: 1,
|
|
193
|
+
height: 1,
|
|
194
|
+
sigmaNormalized: 0.25,
|
|
195
|
+
});
|
|
175
196
|
expect(result).toEqual([
|
|
176
197
|
{ x: 2, y: 1, z: 13 },
|
|
177
198
|
]);
|
package/src/VolumeView.js
CHANGED
|
@@ -14,7 +14,11 @@ import { WebGLMultipleRenderTargets } from 'three';
|
|
|
14
14
|
import { log, atLeastLogLevel, LogLevel } from '@vitessce/globals';
|
|
15
15
|
import { useEventCallback } from '@vitessce/styles';
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
VolumeDataManager,
|
|
19
|
+
INIT_STATUS,
|
|
20
|
+
DEFAULT_SIGMA_NORMALIZED,
|
|
21
|
+
} from './VolumeDataManager.js';
|
|
18
22
|
import { VolumeRenderManager } from './VolumeRenderManager.js';
|
|
19
23
|
|
|
20
24
|
import { gaussianVertexShader } from './shaders/GaussianVertexShader.js';
|
|
@@ -69,8 +73,15 @@ function handleRequests(_gl, { frameRef, dataManager, mrtRef, bufRequest, bufUsa
|
|
|
69
73
|
ctx.RGBA, ctx.UNSIGNED_BYTE, bufRequest.current);
|
|
70
74
|
// Based on the request buffer contents, process the requests
|
|
71
75
|
// (e.g., start loading the brick data and upload to the brick cache).
|
|
72
|
-
//
|
|
73
|
-
dataManager.processRequestData(bufRequest.current
|
|
76
|
+
// Pass width/height so the data manager can compute Gaussian weighting.
|
|
77
|
+
dataManager.processRequestData(bufRequest.current, {
|
|
78
|
+
// If width/height are not yet available,
|
|
79
|
+
// processRequestData will proceed without weighting.
|
|
80
|
+
width: mrtRef?.current?.width,
|
|
81
|
+
height: mrtRef?.current?.height,
|
|
82
|
+
// Default sigmaNormalized = 0.25; can be tuned
|
|
83
|
+
sigmaNormalized: DEFAULT_SIGMA_NORMALIZED,
|
|
84
|
+
});
|
|
74
85
|
} else if (dataManager.triggerUsage === true && dataManager.noNewRequests === false) {
|
|
75
86
|
// Read the pixels of the usage buffer into the width*height*RGBA bufUsage.current array.
|
|
76
87
|
ctx.bindFramebuffer(ctx.READ_FRAMEBUFFER, framebufferFor(_gl, mrtRef.current));
|