@vitessce/spatial-accelerated 3.8.9 → 3.8.13
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 +10480 -1992
- package/dist-tsc/SpatialWrapper.js +2 -2
- package/dist-tsc/VolumeDataManager.d.ts +22 -0
- package/dist-tsc/VolumeDataManager.d.ts.map +1 -1
- package/dist-tsc/VolumeDataManager.js +76 -18
- package/dist-tsc/VolumeRenderManager.d.ts.map +1 -1
- package/dist-tsc/VolumeRenderManager.js +11 -4
- package/dist-tsc/VolumeShader.js +1 -0
- package/dist-tsc/VolumeView.d.ts.map +1 -1
- package/dist-tsc/VolumeView.js +101 -61
- package/dist-tsc/shaders/VolumeFragmentShader.d.ts +1 -1
- package/dist-tsc/shaders/VolumeFragmentShader.d.ts.map +1 -1
- package/dist-tsc/shaders/VolumeFragmentShader.js +22 -4
- package/package.json +11 -11
- package/src/SpatialWrapper.js +6 -6
- package/src/VolumeDataManager.js +91 -21
- package/src/VolumeRenderManager.js +16 -5
- package/src/VolumeShader.js +1 -0
- package/src/VolumeView.js +115 -61
- package/src/shaders/VolumeFragmentShader.js +22 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import React from 'react';
|
|
2
|
+
import React, { Suspense } from 'react';
|
|
3
3
|
// Note: need to be careful about versions of RTF, Three, React, and React DOM in package.json
|
|
4
4
|
// to avoid multiple copies of RTF, since the Canvas depends on a React Context.
|
|
5
5
|
// Otherwise you may see errors such as "useThree can only be used inside the Canvas component!".
|
|
@@ -41,5 +41,5 @@ export function SpatialWrapper(props) {
|
|
|
41
41
|
logarithmicDepthBuffer: false,
|
|
42
42
|
preserveDrawingBuffer: false,
|
|
43
43
|
autoClear: false,
|
|
44
|
-
}, children: _jsx(VolumeView, { ...props }) }));
|
|
44
|
+
}, children: _jsx(Suspense, { fallback: "Initializing Volume View...", children: _jsx(VolumeView, { ...props }) }) }));
|
|
45
45
|
}
|
|
@@ -35,6 +35,12 @@ export function _requestBufferToRequestObjects(buffer: any, k: any): {
|
|
|
35
35
|
}[];
|
|
36
36
|
origRequestCount: number;
|
|
37
37
|
};
|
|
38
|
+
export namespace INIT_STATUS {
|
|
39
|
+
let NOT_STARTED: string;
|
|
40
|
+
let IN_PROGRESS: string;
|
|
41
|
+
let COMPLETE: string;
|
|
42
|
+
let FAILED: string;
|
|
43
|
+
}
|
|
38
44
|
export class VolumeDataManager {
|
|
39
45
|
constructor(glParam: any);
|
|
40
46
|
gl: any;
|
|
@@ -93,6 +99,7 @@ export class VolumeDataManager {
|
|
|
93
99
|
timeStamp: number;
|
|
94
100
|
k: number;
|
|
95
101
|
noNewRequests: boolean;
|
|
102
|
+
manuallyStopped: boolean;
|
|
96
103
|
needsBailout: boolean;
|
|
97
104
|
initStatus: string;
|
|
98
105
|
initError: any;
|
|
@@ -107,6 +114,21 @@ export class VolumeDataManager {
|
|
|
107
114
|
downsampleMin: never[];
|
|
108
115
|
downsampleMax: never[];
|
|
109
116
|
} | null;
|
|
117
|
+
currentRequestCount: number;
|
|
118
|
+
totalBricksRequested: number;
|
|
119
|
+
/**
|
|
120
|
+
* Get loading progress information
|
|
121
|
+
* @returns {Object} Loading progress with bricksLoaded, totalBricks, isLoading, and percentage
|
|
122
|
+
*/
|
|
123
|
+
getLoadingProgress(): Object;
|
|
124
|
+
/**
|
|
125
|
+
* Manually stop loading and render at highest resolution
|
|
126
|
+
*/
|
|
127
|
+
stopLoading(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Manually restart loading
|
|
130
|
+
*/
|
|
131
|
+
restartLoading(): void;
|
|
110
132
|
/**
|
|
111
133
|
* Handle WebGL context loss
|
|
112
134
|
*/
|
|
@@ -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
|
|
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"}
|
|
@@ -30,7 +30,7 @@ const MAX_RESOLUTION_LEVELS = 10; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
|
30
30
|
const MAX_CHANNELS = 7; // [0, 1, 2, 3, 4, 5, 6]
|
|
31
31
|
// const manualChannelSelection = 0;
|
|
32
32
|
// Add a constant for initialization status
|
|
33
|
-
const INIT_STATUS = {
|
|
33
|
+
export const INIT_STATUS = {
|
|
34
34
|
NOT_STARTED: 'not_started',
|
|
35
35
|
IN_PROGRESS: 'in_progress',
|
|
36
36
|
COMPLETE: 'complete',
|
|
@@ -437,14 +437,62 @@ export class VolumeDataManager {
|
|
|
437
437
|
this.timeStamp = 0;
|
|
438
438
|
this.k = 40;
|
|
439
439
|
this.noNewRequests = false;
|
|
440
|
+
this.manuallyStopped = false; // Flag to track if user manually stopped loading
|
|
440
441
|
this.needsBailout = false; // Flag to indicate if we need to bail out of processing requests
|
|
441
442
|
// Add initialization status
|
|
442
443
|
this.initStatus = INIT_STATUS.NOT_STARTED;
|
|
443
444
|
this.initError = null;
|
|
444
445
|
// Store the last known configuration for context restoration
|
|
445
446
|
this._lastChannelConfig = null;
|
|
447
|
+
// Track loading progress
|
|
448
|
+
this.currentRequestCount = 0; // Number of requests currently being processed
|
|
449
|
+
this.totalBricksRequested = 0; // Total number of bricks requested in current batch
|
|
446
450
|
logWithColor('VolumeDataManager constructor complete');
|
|
447
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Get loading progress information
|
|
454
|
+
* @returns {Object} Loading progress with bricksLoaded, totalBricks, isLoading, and percentage
|
|
455
|
+
*/
|
|
456
|
+
getLoadingProgress() {
|
|
457
|
+
const bricksLoaded = this.bricksEverLoaded.size;
|
|
458
|
+
const isLoading = !this.noNewRequests && this.currentRequestCount > 0;
|
|
459
|
+
const percentage = this.totalBricksRequested > 0
|
|
460
|
+
? ((this.totalBricksRequested - this.currentRequestCount) / this.totalBricksRequested) * 100
|
|
461
|
+
: 0;
|
|
462
|
+
return {
|
|
463
|
+
bricksLoaded,
|
|
464
|
+
currentRequestCount: this.currentRequestCount,
|
|
465
|
+
totalBricksRequested: this.totalBricksRequested,
|
|
466
|
+
isLoading,
|
|
467
|
+
percentage,
|
|
468
|
+
noNewRequests: this.noNewRequests,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Manually stop loading and render at highest resolution
|
|
473
|
+
*/
|
|
474
|
+
stopLoading() {
|
|
475
|
+
log.debug('Manually stopping data loading');
|
|
476
|
+
this.noNewRequests = true;
|
|
477
|
+
this.manuallyStopped = true;
|
|
478
|
+
this.needsBailout = true;
|
|
479
|
+
// Reset progress counters to reflect stopped state
|
|
480
|
+
this.currentRequestCount = 0;
|
|
481
|
+
this.totalBricksRequested = 0;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Manually restart loading
|
|
485
|
+
*/
|
|
486
|
+
restartLoading() {
|
|
487
|
+
log.debug('Manually restarting data loading');
|
|
488
|
+
this.noNewRequests = false;
|
|
489
|
+
this.manuallyStopped = false;
|
|
490
|
+
this.needsBailout = false;
|
|
491
|
+
this.triggerUsage = true;
|
|
492
|
+
// Reset progress counters
|
|
493
|
+
this.currentRequestCount = 0;
|
|
494
|
+
this.totalBricksRequested = 0;
|
|
495
|
+
}
|
|
448
496
|
/**
|
|
449
497
|
* Handle WebGL context loss
|
|
450
498
|
*/
|
|
@@ -803,20 +851,13 @@ export class VolumeDataManager {
|
|
|
803
851
|
this.PT = PT;
|
|
804
852
|
this.ptTHREE = ptTHREE;
|
|
805
853
|
this.bcTHREE = bcTHREE;
|
|
806
|
-
// log.debug('gl', this.gl);
|
|
807
|
-
// log.debug('renderer', this.renderer);
|
|
808
854
|
logWithColor('initMRMCPT() COMPLETE');
|
|
809
855
|
}
|
|
810
|
-
/*
|
|
811
|
-
testTexture() {
|
|
812
|
-
log.debug('testTexture pt', this.ptTHREE);
|
|
813
|
-
log.debug('testTexture bc', this.bcTHREE);
|
|
814
|
-
}
|
|
815
|
-
*/
|
|
816
856
|
async initTexture() {
|
|
817
857
|
const requests = [
|
|
818
858
|
{ x: 0, y: 0, z: 1 },
|
|
819
859
|
];
|
|
860
|
+
logWithColor('initTexture - loading first brick');
|
|
820
861
|
await this.handleBrickRequests(requests);
|
|
821
862
|
}
|
|
822
863
|
updateChannels(channelProps) {
|
|
@@ -1015,7 +1056,7 @@ export class VolumeDataManager {
|
|
|
1015
1056
|
if (!this.zarrStore || !this.zarrStore.arrays[resolution]) {
|
|
1016
1057
|
throw new Error('Zarr store or resolution not initialized');
|
|
1017
1058
|
}
|
|
1018
|
-
|
|
1059
|
+
log.debug('loadZarrChunk', { t, c, z, y, x, resolution });
|
|
1019
1060
|
const array = this.zarrStore.arrays[resolution];
|
|
1020
1061
|
// TODO: Use zarrita's get(arr, slice) method (rather than arr.getChunk),
|
|
1021
1062
|
// which will enable reading from arrays with other chunk shapes
|
|
@@ -1024,7 +1065,6 @@ export class VolumeDataManager {
|
|
|
1024
1065
|
// to each "assumed 32x32x32 chunk key".
|
|
1025
1066
|
// Reference: https://zarrita.dev/slicing.html
|
|
1026
1067
|
const chunkEntry = await array.getChunk([t, c, z, y, x]);
|
|
1027
|
-
// log.debug('chunkEntry', chunkEntry);
|
|
1028
1068
|
if (!chunkEntry) {
|
|
1029
1069
|
throw new Error(`No chunk found at coordinates [${t},${c},${z},${y},${x}]`);
|
|
1030
1070
|
}
|
|
@@ -1039,6 +1079,11 @@ export class VolumeDataManager {
|
|
|
1039
1079
|
log.debug('processRequestData: already busy, skipping');
|
|
1040
1080
|
return;
|
|
1041
1081
|
}
|
|
1082
|
+
// Check if loading has been manually stopped
|
|
1083
|
+
if (this.noNewRequests) {
|
|
1084
|
+
log.debug('processRequestData: loading stopped by user, skipping');
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1042
1087
|
// Check for context loss
|
|
1043
1088
|
if (this.isContextLost()) {
|
|
1044
1089
|
log.debug('processRequestData: WebGL context is lost, skipping');
|
|
@@ -1287,7 +1332,7 @@ export class VolumeDataManager {
|
|
|
1287
1332
|
}
|
|
1288
1333
|
log.debug('starting to load zarr chunk', { resolution, z, y, x, zarrChannel });
|
|
1289
1334
|
let chunk = await this.loadZarrChunk(0, zarrChannel, z, y, x, resolution);
|
|
1290
|
-
|
|
1335
|
+
log.debug('chunk', chunk);
|
|
1291
1336
|
// TODO(mark): is there a better way than iterating over all of the chunk values?
|
|
1292
1337
|
if (chunk instanceof Uint16Array) {
|
|
1293
1338
|
log.debug('chunk is Uint16Array, converting to Uint8Array');
|
|
@@ -1301,12 +1346,18 @@ export class VolumeDataManager {
|
|
|
1301
1346
|
log.debug('this.channels.downsampleMin[channel]', this.channels.downsampleMin[channel]);
|
|
1302
1347
|
log.debug('this.channels.downsampleMax[channel]', this.channels.downsampleMax[channel]);
|
|
1303
1348
|
}
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1349
|
+
// Use TypedArray view and bitwise operations to reduce memory allocations
|
|
1350
|
+
// Shift right by 8 to convert uint16 to uint8 range
|
|
1351
|
+
const uint16View = new Uint16Array(chunk.buffer);
|
|
1306
1352
|
const uint8Chunk = new Uint8Array(chunk.length);
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1353
|
+
const minVal = this.channels.downsampleMin[channel];
|
|
1354
|
+
const maxVal = this.channels.downsampleMax[channel];
|
|
1355
|
+
const range = maxVal - minVal;
|
|
1356
|
+
for (let i = 0; i < uint16View.length; i++) {
|
|
1357
|
+
// Clamp to min/max range, then scale to 0-255
|
|
1358
|
+
const val = uint16View[i];
|
|
1359
|
+
const clamped = Math.max(minVal, Math.min(maxVal, val));
|
|
1360
|
+
uint8Chunk[i] = ((clamped - minVal) * 255 / range) | 0; // Use bitwise OR for fast floor
|
|
1310
1361
|
}
|
|
1311
1362
|
chunk = uint8Chunk;
|
|
1312
1363
|
}
|
|
@@ -1377,6 +1428,9 @@ export class VolumeDataManager {
|
|
|
1377
1428
|
// log.debug('handleBrickRequests');
|
|
1378
1429
|
if (ptRequests.length === 0)
|
|
1379
1430
|
return;
|
|
1431
|
+
// Track loading progress
|
|
1432
|
+
this.totalBricksRequested = ptRequests.length;
|
|
1433
|
+
this.currentRequestCount = ptRequests.length;
|
|
1380
1434
|
// log.debug('ptRequests', ptRequests);
|
|
1381
1435
|
// log.debug('this.BCFull', this.BCFull);
|
|
1382
1436
|
// log.debug('this.BCUnusedIndex', this.BCUnusedIndex);
|
|
@@ -1389,9 +1443,10 @@ export class VolumeDataManager {
|
|
|
1389
1443
|
/* upload each (sequentially or Promise.all if you prefer IO overlap) */
|
|
1390
1444
|
log.debug('handleBrickRequests: starting for loop');
|
|
1391
1445
|
for (let i = 0; i < ptRequests.length; ++i) {
|
|
1392
|
-
log.debug('uploading brick', ptRequests[i], slots[i]);
|
|
1393
1446
|
// eslint-disable-next-line no-await-in-loop
|
|
1394
1447
|
await this._uploadBrick(ptRequests[i], slots[i]);
|
|
1448
|
+
// Update progress
|
|
1449
|
+
this.currentRequestCount = ptRequests.length - i - 1;
|
|
1395
1450
|
// this.bricksAllocated++;
|
|
1396
1451
|
const rlength = this.bricksEverLoaded.size;
|
|
1397
1452
|
this.bricksEverLoaded.add(`${ptRequests[i].x},${ptRequests[i].y},${ptRequests[i].z}`);
|
|
@@ -1401,10 +1456,13 @@ export class VolumeDataManager {
|
|
|
1401
1456
|
if (this.needsBailout) {
|
|
1402
1457
|
log.debug('Bailing out of handleBrickRequests early due to needsBailout flag');
|
|
1403
1458
|
this.needsBailout = false; // Reset the flag
|
|
1459
|
+
this.currentRequestCount = 0; // Reset progress
|
|
1404
1460
|
break; // Exit the loop early
|
|
1405
1461
|
// TODO(mark): do something with the allocated slots that were not used?
|
|
1406
1462
|
}
|
|
1407
1463
|
}
|
|
1464
|
+
// Reset progress when complete
|
|
1465
|
+
this.currentRequestCount = 0;
|
|
1408
1466
|
// log.debug('this.bricksAllocated', this.bricksAllocated);
|
|
1409
1467
|
log.debug('this.bricksEverLoaded', this.bricksEverLoaded);
|
|
1410
1468
|
// log.debug('uploaded bricks');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VolumeRenderManager.d.ts","sourceRoot":"","sources":["../src/VolumeRenderManager.js"],"names":[],"mappings":"AA4BA;;;GAGG;AACH;IAII,cAAoB;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAAkB;IAClB,oBAA0B;IAC1B,uBAA6B;IAC7B,kBAAwB;IACxB,kBAAqB;IAGrB,uBAAyB;IACzB,sBAAwB;IACxB,6BAAmC;IACnC,iCAAmC;IACnC,cAAgB;IAChB,sBAAwB;IACxB,0BAA4B;IAC5B,gBAAqC;IACrC,gBAAqC;IACrC,gBAAqC;IACrC,wBAA8B;IAC9B,6BAAmC;IACnC,wBAA8B;IAE9B,iBAAmB;IACnB,qBAAwB;IAS1B;;OAEG;IACH,yBAIC;IAED;;;;OAIG;IACH,yCAHW,MAAM,GACJ,MAAM,
|
|
1
|
+
{"version":3,"file":"VolumeRenderManager.d.ts","sourceRoot":"","sources":["../src/VolumeRenderManager.js"],"names":[],"mappings":"AA4BA;;;GAGG;AACH;IAII,cAAoB;IACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAAkB;IAClB,oBAA0B;IAC1B,uBAA6B;IAC7B,kBAAwB;IACxB,kBAAqB;IAGrB,uBAAyB;IACzB,sBAAwB;IACxB,6BAAmC;IACnC,iCAAmC;IACnC,cAAgB;IAChB,sBAAwB;IACxB,0BAA4B;IAC5B,gBAAqC;IACrC,gBAAqC;IACrC,gBAAqC;IACrC,wBAA8B;IAC9B,6BAAmC;IACnC,wBAA8B;IAE9B,iBAAmB;IACnB,qBAAwB;IAS1B;;OAEG;IACH,yBAIC;IAED;;;;OAIG;IACH,yCAHW,MAAM,GACJ,MAAM,CA0IlB;IAED;;;;OAIG;IACH,uBAHW,MAAM,GACJ,OAAO,CA6BnB;IAbC,mBAA2C;IAe7C;;;;OAIG;IACH,yJAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAsIvB;IAzCG,qBAAyC;IA2C7C;;;;;;;;;;;OAWG;IACH,4CATW,MAAM,eACN,MAAM,2DAGN,MAAM,UACN,OAAO,UACP,OAAO,UACP,OAAO,uDA4HjB;IAED;;;OAGG;IAQH,6CAIC;IAID,+CAkDC;CACF;wBAnlBM,OAAO"}
|
|
@@ -66,12 +66,10 @@ export class VolumeRenderManager {
|
|
|
66
66
|
* @returns {Object} Extracted rendering settings
|
|
67
67
|
*/
|
|
68
68
|
extractRenderingSettingsFromProps(props) {
|
|
69
|
-
logWithColor('Extracting rendering settings from props');
|
|
70
|
-
// log.debug('props', props);
|
|
71
|
-
// log.debug('props.imageChannelCoordination', props.imageChannelCoordination[0]['A']);
|
|
72
69
|
const { images = {}, imageLayerScopes = [], imageLayerCoordination = [{}], imageChannelScopesByLayer = {}, imageChannelCoordination = [{}], spatialRenderingMode, } = props;
|
|
73
70
|
const layerScope = imageLayerScopes[0];
|
|
74
71
|
if (!layerScope) {
|
|
72
|
+
logWithColor('Extracting rendering settings from props - no layer scope');
|
|
75
73
|
return {
|
|
76
74
|
valid: false,
|
|
77
75
|
};
|
|
@@ -81,6 +79,7 @@ export class VolumeRenderManager {
|
|
|
81
79
|
const channelCoordination = imageChannelCoordination[0][layerScope];
|
|
82
80
|
const data = images[layerScope]?.image?.instance?.getData();
|
|
83
81
|
if (!data) {
|
|
82
|
+
logWithColor('Extracting rendering settings from props - no image data');
|
|
84
83
|
return {
|
|
85
84
|
valid: false,
|
|
86
85
|
};
|
|
@@ -93,6 +92,7 @@ export class VolumeRenderManager {
|
|
|
93
92
|
// TODO(mark): the defaults should be set based on the image dtype.
|
|
94
93
|
// TODO(mark): wait for all channel IQR ranges to be computed before initializing,
|
|
95
94
|
// as we do not want this.maxRange to be incorrect.
|
|
95
|
+
logWithColor('Extracting rendering settings from props - no channel window set');
|
|
96
96
|
return {
|
|
97
97
|
valid: false,
|
|
98
98
|
};
|
|
@@ -141,10 +141,12 @@ export class VolumeRenderManager {
|
|
|
141
141
|
let xSlice = layerCoordination[CoordinationType.SPATIAL_SLICE_X];
|
|
142
142
|
let ySlice = layerCoordination[CoordinationType.SPATIAL_SLICE_Y];
|
|
143
143
|
let zSlice = layerCoordination[CoordinationType.SPATIAL_SLICE_Z];
|
|
144
|
+
const lodFactor = layerCoordination[CoordinationType.SPATIAL_LOD_FACTOR] ?? 1.0;
|
|
144
145
|
xSlice = xSlice !== null ? xSlice : new Vector2(-1, 100000);
|
|
145
146
|
ySlice = ySlice !== null ? ySlice : new Vector2(-1, 100000);
|
|
146
147
|
zSlice = zSlice !== null ? zSlice : new Vector2(-1, 100000);
|
|
147
148
|
const allChannels = images[layerScope].image.loaders[0].channels;
|
|
149
|
+
logWithColor('Extracting rendering settings from props - success');
|
|
148
150
|
return {
|
|
149
151
|
valid: true,
|
|
150
152
|
channelsVisible,
|
|
@@ -159,6 +161,7 @@ export class VolumeRenderManager {
|
|
|
159
161
|
xSlice,
|
|
160
162
|
ySlice,
|
|
161
163
|
zSlice,
|
|
164
|
+
lodFactor,
|
|
162
165
|
};
|
|
163
166
|
}
|
|
164
167
|
/**
|
|
@@ -167,9 +170,9 @@ export class VolumeRenderManager {
|
|
|
167
170
|
* @returns {boolean} True if settings were successfully updated
|
|
168
171
|
*/
|
|
169
172
|
updateFromProps(props) {
|
|
170
|
-
logWithColor('Updating from props');
|
|
171
173
|
const settings = this.extractRenderingSettingsFromProps(props);
|
|
172
174
|
if (!settings.valid) {
|
|
175
|
+
logWithColor('Updating from props - invalid settings');
|
|
173
176
|
return false;
|
|
174
177
|
}
|
|
175
178
|
// Store the extracted settings
|
|
@@ -183,6 +186,10 @@ export class VolumeRenderManager {
|
|
|
183
186
|
this.xSlice = settings.xSlice;
|
|
184
187
|
this.ySlice = settings.ySlice;
|
|
185
188
|
this.zSlice = settings.zSlice;
|
|
189
|
+
this.uniforms.lodFactor.value = settings.lodFactor;
|
|
190
|
+
logWithColor(`lodFactor ${settings.lodFactor}`);
|
|
191
|
+
this.shader.uniforms.lodFactor.value = settings.lodFactor;
|
|
192
|
+
logWithColor('Updating from props - success');
|
|
186
193
|
return true;
|
|
187
194
|
}
|
|
188
195
|
/**
|
package/dist-tsc/VolumeShader.js
CHANGED
|
@@ -17,6 +17,7 @@ export const VolumeShader = {
|
|
|
17
17
|
zClip: { value: new Vector2(0, 1000000.0) },
|
|
18
18
|
u_window_size: { value: new Vector2(1, 1) },
|
|
19
19
|
u_vol_scale: { value: new Vector3(1, 1, 1) },
|
|
20
|
+
// Set renderstyle to 3 or 4 for debugging modes
|
|
20
21
|
u_renderstyle: { value: 2 },
|
|
21
22
|
brickCacheTex: { type: 'sampler3D', value: null },
|
|
22
23
|
pageTableTex: { type: 'usampler3D', value: null },
|
|
@@ -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":"AAuLA,2DA6gBC"}
|
package/dist-tsc/VolumeView.js
CHANGED
|
@@ -6,13 +6,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
/**
|
|
7
7
|
* VolumeView.js
|
|
8
8
|
*/
|
|
9
|
-
import React, { useRef, useState, useEffect,
|
|
9
|
+
import React, { useRef, useState, useEffect, useMemo } from 'react';
|
|
10
10
|
import { useThree, useFrame } from '@react-three/fiber';
|
|
11
11
|
import { OrbitControls } from '@react-three/drei';
|
|
12
12
|
import * as THREE from 'three';
|
|
13
13
|
import { WebGLMultipleRenderTargets } from 'three';
|
|
14
14
|
import { log, atLeastLogLevel, LogLevel } from '@vitessce/globals';
|
|
15
|
-
import {
|
|
15
|
+
import { useEventCallback } from '@vitessce/styles';
|
|
16
|
+
import { VolumeDataManager, INIT_STATUS } from './VolumeDataManager.js';
|
|
16
17
|
import { VolumeRenderManager } from './VolumeRenderManager.js';
|
|
17
18
|
import { gaussianVertexShader } from './shaders/GaussianVertexShader.js';
|
|
18
19
|
import { gaussianFragmentShader } from './shaders/GaussianFragmentShader.js';
|
|
@@ -27,13 +28,13 @@ function logWithColor(msg) {
|
|
|
27
28
|
}
|
|
28
29
|
// Functions called with useFrame
|
|
29
30
|
function performGeometryPass(_gl, _camera, _scene, { mrtRef }) {
|
|
30
|
-
// logWithColor('performGeometryPass');
|
|
31
|
+
// logWithColor('performGeometryPass', 'blue');
|
|
31
32
|
_gl.setRenderTarget(mrtRef.current);
|
|
32
33
|
_gl.clear(true, true, true);
|
|
33
34
|
_gl.render(_scene, _camera);
|
|
34
35
|
}
|
|
35
36
|
function performBlitPass(_gl, { screenSceneRef, screenCameraRef }) {
|
|
36
|
-
// logWithColor('performBlitPass');
|
|
37
|
+
// logWithColor('performBlitPass', 'red');
|
|
37
38
|
_gl.setRenderTarget(null);
|
|
38
39
|
_gl.clear(true, true, true);
|
|
39
40
|
_gl.render(screenSceneRef.current, screenCameraRef.current);
|
|
@@ -43,7 +44,8 @@ function handleRequests(_gl, { frameRef, dataManager, mrtRef, bufRequest, bufUsa
|
|
|
43
44
|
// log.debug('handleRequests', frameRef.current);
|
|
44
45
|
const ctx = _gl.getContext();
|
|
45
46
|
const f = frameRef.current;
|
|
46
|
-
|
|
47
|
+
// Only allow automatic restart if not manually stopped by user
|
|
48
|
+
if (dataManager.noNewRequests === true && !dataManager.manuallyStopped && f % 100 === 0 && f < 500) {
|
|
47
49
|
// Every 100th frame of the first 500 frames.
|
|
48
50
|
// eslint-disable-next-line no-param-reassign
|
|
49
51
|
dataManager.noNewRequests = false;
|
|
@@ -89,8 +91,11 @@ function handleAdaptiveQuality(clock, params) {
|
|
|
89
91
|
// While the user is interacting, we want to ensure that the next frame's handleRequests call
|
|
90
92
|
// will trigger the processUsageData call, and subsequently the processRequestData call
|
|
91
93
|
// (since processUsageData will set triggerRequest to true its conclusion).
|
|
92
|
-
|
|
93
|
-
dataManager.
|
|
94
|
+
// But only if not manually stopped by the user
|
|
95
|
+
if (!dataManager.manuallyStopped) {
|
|
96
|
+
dataManager.noNewRequests = false;
|
|
97
|
+
dataManager.triggerUsage = true;
|
|
98
|
+
}
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
96
101
|
if (spatialRenderingModeChanging)
|
|
@@ -145,7 +150,7 @@ function handleAdaptiveQuality(clock, params) {
|
|
|
145
150
|
export function VolumeView(props) {
|
|
146
151
|
const { images, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination,
|
|
147
152
|
// onInitComplete,
|
|
148
|
-
spatialRenderingMode, spatialRenderingModeChanging, } = props;
|
|
153
|
+
spatialRenderingMode, spatialRenderingModeChanging, onVolumeLoadingUpdate, } = props;
|
|
149
154
|
const { gl,
|
|
150
155
|
// scene,
|
|
151
156
|
// camera
|
|
@@ -166,13 +171,17 @@ export function VolumeView(props) {
|
|
|
166
171
|
});
|
|
167
172
|
// const [lastRes, setLastRes] = useState(null);
|
|
168
173
|
// const [lastChannels, setLastChannels] = useState([]);
|
|
169
|
-
const
|
|
174
|
+
const is3D = spatialRenderingMode === '3D';
|
|
170
175
|
// const [loading, setLoading] = useState(false);
|
|
171
176
|
// Add new refs for screen quad setup
|
|
172
177
|
const screenSceneRef = useRef(null);
|
|
173
178
|
const screenCameraRef = useRef(null);
|
|
174
179
|
const screenQuadRef = useRef(null);
|
|
175
|
-
|
|
180
|
+
// The `_setIsInteracting` function is wrapped by the `setIsInteracting` useEventCallback below
|
|
181
|
+
// This allows us to apply the effects related to interaction state synchronously,
|
|
182
|
+
// rather than relying waiting for a React state update with `useEffect`.
|
|
183
|
+
// Updates to `isInteracting` should be done via `setIsInteracting`.
|
|
184
|
+
const [isInteracting, _setIsInteracting] = useState(false);
|
|
176
185
|
const interactionTimeoutRef = useRef(null);
|
|
177
186
|
// Track interaction-triggering props
|
|
178
187
|
// const prevInteractionChannels = useRef();
|
|
@@ -245,6 +254,7 @@ export function VolumeView(props) {
|
|
|
245
254
|
const firstImageLayerChannelCoordination = imageChannelCoordination?.[0]?.[firstImageLayerScope];
|
|
246
255
|
useEffect(() => {
|
|
247
256
|
logWithColor('useEffect INIT');
|
|
257
|
+
let hasRerun = false;
|
|
248
258
|
if (!dataManager || !renderManager) {
|
|
249
259
|
log.debug('dataManager or renderManager not initialized yet');
|
|
250
260
|
return;
|
|
@@ -257,12 +267,20 @@ export function VolumeView(props) {
|
|
|
257
267
|
log.debug('no firstImageLayerChannelCoordination yet');
|
|
258
268
|
return;
|
|
259
269
|
}
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
const initializeDataManager = async () => {
|
|
271
|
+
if (dataManager.initStatus === INIT_STATUS.COMPLETE) {
|
|
272
|
+
log.debug('dataManager already initialized, skipping');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
262
275
|
// TODO(mark): separate the initialization which depends on gl, from the initialization which depends on images, from the dm.init(firstImageLayer)
|
|
263
276
|
dataManager.initImages(images, imageLayerScopes);
|
|
264
277
|
// TODO: generalize to more than one image layer.
|
|
265
278
|
await dataManager.init(firstImageLayerChannelCoordination); // device limits, zarr meta
|
|
279
|
+
if (hasRerun) {
|
|
280
|
+
log.debug('Initialization useEffect has rerun, aborting remaining initialization');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
log.debug('dataManager initialized');
|
|
266
284
|
// log.debug('dm.physicalScale', dataManager.physicalScale);
|
|
267
285
|
renderManager.setZarrUniforms(dataManager.zarrStore, dataManager.PT);
|
|
268
286
|
renderManager.setChannelMapping(dataManager.channels.colorMappings);
|
|
@@ -271,16 +289,21 @@ export function VolumeView(props) {
|
|
|
271
289
|
// if (onInitComplete) {
|
|
272
290
|
// onInitComplete({ zarrStoreInfo: dm.zarrStore, deviceLimits: dm.deviceLimits });
|
|
273
291
|
// }
|
|
274
|
-
|
|
292
|
+
invalidate();
|
|
293
|
+
};
|
|
294
|
+
initializeDataManager();
|
|
295
|
+
// avoid setting state on unmounted component
|
|
296
|
+
// eslint-disable-next-line consistent-return
|
|
297
|
+
return () => {
|
|
298
|
+
hasRerun = true;
|
|
299
|
+
};
|
|
275
300
|
// The data manager does not have a clearCache method, should it?
|
|
276
301
|
// return () => {
|
|
277
302
|
// log.debug('VolumeView useEffect cleanup: running dataManager.clearCache()');
|
|
278
303
|
// dataManager?.clearCache?.();
|
|
279
304
|
// };
|
|
280
305
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
281
|
-
}, [dataManager, renderManager,
|
|
282
|
-
images, imageLayerScopes,
|
|
283
|
-
]);
|
|
306
|
+
}, [dataManager, renderManager, images, imageLayerScopes]);
|
|
284
307
|
/*
|
|
285
308
|
useEffect(() => {
|
|
286
309
|
logWithColor('useEffect GL');
|
|
@@ -315,10 +338,9 @@ export function VolumeView(props) {
|
|
|
315
338
|
*/
|
|
316
339
|
// TODO(mark): convert to a useMemo?
|
|
317
340
|
useEffect(() => {
|
|
318
|
-
logWithColor('useEffect spatialRenderingMode');
|
|
319
341
|
const on3D = spatialRenderingMode === '3D';
|
|
320
|
-
setIs3D(on3D);
|
|
321
342
|
if (on3D && dataManager && renderManager) {
|
|
343
|
+
logWithColor('useEffect spatialRenderingMode');
|
|
322
344
|
// Direct call, no callbacks needed
|
|
323
345
|
const propsForRenderManager = {
|
|
324
346
|
images,
|
|
@@ -359,42 +381,7 @@ export function VolumeView(props) {
|
|
|
359
381
|
}
|
|
360
382
|
}
|
|
361
383
|
}
|
|
362
|
-
}, [dataManager, renderManager, images, imageLayerScopes, imageLayerCoordination,
|
|
363
|
-
imageChannelScopesByLayer, imageChannelCoordination, spatialRenderingMode]);
|
|
364
|
-
// TODO(mark): can this logic be moved into useFrame?
|
|
365
|
-
/*
|
|
366
|
-
useEffect(() => {
|
|
367
|
-
logWithColor('useEffect stillRef');
|
|
368
|
-
// log.debug('useEffect stillRef');
|
|
369
|
-
// log.debug('stillRef', stillRef.current);
|
|
370
|
-
if (!screenQuadRef.current) {
|
|
371
|
-
log.debug('no screen quad yet');
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (!stillRef.current) {
|
|
375
|
-
// log.debug('stillRef is false');
|
|
376
|
-
screenQuadRef.current.material.uniforms.gaussian.value = 7;
|
|
377
|
-
} else {
|
|
378
|
-
//log.debug('stillRef is true');
|
|
379
|
-
screenQuadRef.current.material.uniforms.gaussian.value = 0;
|
|
380
|
-
}
|
|
381
|
-
}, [stillRef.current]);
|
|
382
|
-
*/
|
|
383
|
-
// TODO(mark): can this logic be moved into useFrame?
|
|
384
|
-
// But need to be careful, since currently, it only runs upon change of isInteracting,
|
|
385
|
-
// and we do not want to execute more `invalidate` calls than necessary/currently.
|
|
386
|
-
useEffect(() => {
|
|
387
|
-
logWithColor('useEffect isInteracting');
|
|
388
|
-
if (isInteracting) {
|
|
389
|
-
const meshRefUniforms = meshRef.current?.material?.uniforms;
|
|
390
|
-
if (meshRefUniforms) {
|
|
391
|
-
meshRefUniforms.renderRes.value = dataManager.PT.lowestDataRes;
|
|
392
|
-
}
|
|
393
|
-
// setRenderSpeed(dataManager.PT.lowestDataRes);
|
|
394
|
-
stillRef.current = false;
|
|
395
|
-
invalidate();
|
|
396
|
-
}
|
|
397
|
-
}, [invalidate, isInteracting]);
|
|
384
|
+
}, [dataManager, renderManager, images, imageLayerScopes, imageLayerCoordination, imageChannelScopesByLayer, imageChannelCoordination, spatialRenderingMode]);
|
|
398
385
|
// Execute code on every rendered frame.
|
|
399
386
|
// A numerical renderPriority value will cause React Three Fiber
|
|
400
387
|
// to disable automatic rendering altogether.
|
|
@@ -405,12 +392,14 @@ export function VolumeView(props) {
|
|
|
405
392
|
useFrame((state, delta, xrFrame) => {
|
|
406
393
|
// NOTE: Do not update React state inside useFrame.
|
|
407
394
|
// Reference: https://r3f.docs.pmnd.rs/advanced/pitfalls#%E2%9D%8C-setstate-in-useframe-is-bad
|
|
408
|
-
if (!mrtRef.current || !dataManager || !renderManager)
|
|
395
|
+
if (!mrtRef.current || !dataManager || !renderManager) {
|
|
409
396
|
return;
|
|
397
|
+
}
|
|
410
398
|
// Don't render to MRT if shader is not ready - this prevents WebGL warnings
|
|
411
399
|
// about missing fragment shader outputs for multiple render targets
|
|
412
|
-
if (!renderState.shader)
|
|
400
|
+
if (!renderState.shader) {
|
|
413
401
|
return;
|
|
402
|
+
}
|
|
414
403
|
// Receive the same state as the useThree hook.
|
|
415
404
|
const { gl: frameGl, camera: frameCamera, scene: frameScene, clock, } = state;
|
|
416
405
|
if (!stillRef.current) {
|
|
@@ -488,15 +477,33 @@ export function VolumeView(props) {
|
|
|
488
477
|
};
|
|
489
478
|
}, [mainOrbitControlsRef.current, setIsInteracting]);
|
|
490
479
|
*/
|
|
491
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Handle interaction state changes in event callbacks.
|
|
482
|
+
* This avoids the need for a useEffect hook which
|
|
483
|
+
* would run on every render frame.
|
|
484
|
+
*/
|
|
485
|
+
const setIsInteracting = useEventCallback((interacting) => {
|
|
486
|
+
logWithColor('invalidateOnInteraction callback');
|
|
487
|
+
_setIsInteracting(interacting);
|
|
488
|
+
if (interacting) {
|
|
489
|
+
const meshRefUniforms = meshRef.current?.material?.uniforms;
|
|
490
|
+
if (meshRefUniforms) {
|
|
491
|
+
meshRefUniforms.renderRes.value = dataManager.PT.lowestDataRes;
|
|
492
|
+
}
|
|
493
|
+
// setRenderSpeed(dataManager.PT.lowestDataRes);
|
|
494
|
+
stillRef.current = false;
|
|
495
|
+
invalidate();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
const onOrbitControlsStart = useEventCallback((e) => {
|
|
492
499
|
setIsInteracting(true);
|
|
493
|
-
}
|
|
494
|
-
const onOrbitControlsEnd =
|
|
500
|
+
});
|
|
501
|
+
const onOrbitControlsEnd = useEventCallback((e) => {
|
|
495
502
|
clearTimeout(interactionTimeoutRef.current);
|
|
496
503
|
interactionTimeoutRef.current = setTimeout(() => {
|
|
497
504
|
setIsInteracting(false);
|
|
498
505
|
}, 300);
|
|
499
|
-
}
|
|
506
|
+
});
|
|
500
507
|
// Set isInteracting while the user is sliding the channel slider
|
|
501
508
|
// for smooth updates.
|
|
502
509
|
useEffect(() => {
|
|
@@ -509,7 +516,40 @@ export function VolumeView(props) {
|
|
|
509
516
|
interactionTimeoutRef.current = setTimeout(() => {
|
|
510
517
|
setIsInteracting(false);
|
|
511
518
|
}, 300);
|
|
512
|
-
}, [firstImageLayerChannelCoordination]);
|
|
519
|
+
}, [dataManager, firstImageLayerChannelCoordination, renderManager, setIsInteracting]);
|
|
520
|
+
const stopLoading = useEventCallback(() => {
|
|
521
|
+
if (dataManager) {
|
|
522
|
+
dataManager.stopLoading();
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
const restartLoading = useEventCallback(() => {
|
|
526
|
+
if (dataManager) {
|
|
527
|
+
dataManager.restartLoading();
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
const getLoadingProgress = useEventCallback(() => {
|
|
531
|
+
if (dataManager) {
|
|
532
|
+
return dataManager.getLoadingProgress();
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
});
|
|
536
|
+
// Notify parent component about loading updates
|
|
537
|
+
useEffect(() => {
|
|
538
|
+
if (!onVolumeLoadingUpdate)
|
|
539
|
+
return undefined;
|
|
540
|
+
const intervalId = setInterval(() => {
|
|
541
|
+
const loadingProgress = getLoadingProgress();
|
|
542
|
+
onVolumeLoadingUpdate({
|
|
543
|
+
loadingProgress,
|
|
544
|
+
stillRef,
|
|
545
|
+
onStopLoading: stopLoading,
|
|
546
|
+
onRestartLoading: restartLoading,
|
|
547
|
+
});
|
|
548
|
+
}, 1000); // Update every 1000ms (1s)
|
|
549
|
+
return () => clearInterval(intervalId);
|
|
550
|
+
// `useEventCallback` ensures stable references
|
|
551
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
552
|
+
}, [onVolumeLoadingUpdate, stillRef]);
|
|
513
553
|
// Render nothing during initialization.
|
|
514
554
|
if (!is3D || !dataManager || !renderManager)
|
|
515
555
|
return null;
|