@vitessce/spatial-accelerated 3.9.4 → 3.9.5

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 CHANGED
@@ -59468,9 +59468,17 @@ function _ptToZarr(ptx, pty, ptz, ptInfo) {
59468
59468
  z
59469
59469
  };
59470
59470
  }
59471
- function _requestBufferToRequestObjects(buffer, k) {
59471
+ const DEFAULT_SIGMA_NORMALIZED = 0.25;
59472
+ function _requestBufferToRequestObjects(buffer, k, optsForWeighting) {
59472
59473
  const counts = /* @__PURE__ */ new Map();
59473
- for (let i2 = 0; i2 < buffer.length; i2 += 4) {
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
- counts.set(packed, (counts.get(packed) || 0) + 1);
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
- async processRequestData(buffer) {
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
- processRequestData(buffer: any): Promise<void>;
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;AAED;;;;;;;EA0BC;;;;;;;AAID;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,+CAgCC;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;8BAttDM,OAAO"}
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
- export function _requestBufferToRequestObjects(buffer, k) {
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
- for (let i = 0; i < buffer.length; i += 4) {
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
- counts.set(packed, (counts.get(packed) || 0) + 1);
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
- // log.debug('counts', counts);
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
- async processRequestData(buffer) {
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":"AAuLA,2DA6gBC"}
1
+ {"version":3,"file":"VolumeView.d.ts","sourceRoot":"","sources":["../src/VolumeView.js"],"names":[],"mappings":"AAkMA,2DA6gBC"}
@@ -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
- // Finally, it will set triggerUsage to true.
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.4",
3
+ "version": "3.9.5",
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.4",
32
- "@vitessce/gl": "3.9.4",
33
- "@vitessce/legend": "3.9.4",
34
- "@vitessce/sets-utils": "3.9.4",
35
- "@vitessce/spatial-utils": "3.9.4",
36
- "@vitessce/tooltip": "3.9.4",
37
- "@vitessce/utils": "3.9.4",
38
- "@vitessce/styles": "3.9.4",
39
- "@vitessce/globals": "3.9.4",
40
- "@vitessce/vit-s": "3.9.4"
31
+ "@vitessce/constants-internal": "3.9.5",
32
+ "@vitessce/gl": "3.9.5",
33
+ "@vitessce/legend": "3.9.5",
34
+ "@vitessce/tooltip": "3.9.5",
35
+ "@vitessce/spatial-utils": "3.9.5",
36
+ "@vitessce/sets-utils": "3.9.5",
37
+ "@vitessce/styles": "3.9.5",
38
+ "@vitessce/utils": "3.9.5",
39
+ "@vitessce/vit-s": "3.9.5",
40
+ "@vitessce/globals": "3.9.5"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/three": "^0.154.0",
@@ -316,9 +316,28 @@ export function _ptToZarr(ptx, pty, ptz, ptInfo) {
316
316
  };
317
317
  }
318
318
 
319
- export function _requestBufferToRequestObjects(buffer, k) {
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
- for (let i = 0; i < buffer.length; i += 4) {
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
- counts.set(packed, (counts.get(packed) || 0) + 1);
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
- // log.debug('counts', counts);
334
- // log.debug('this.k', this.k);
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
- async processRequestData(buffer) {
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 { VolumeDataManager, INIT_STATUS } from './VolumeDataManager.js';
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
- // Finally, it will set triggerUsage to true.
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));