@cornerstonejs/core 1.30.0 → 1.31.0

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.
Files changed (55) hide show
  1. package/dist/cjs/RenderingEngine/StackViewport.js +1 -0
  2. package/dist/cjs/RenderingEngine/StackViewport.js.map +1 -1
  3. package/dist/cjs/RenderingEngine/Viewport.js +1 -1
  4. package/dist/cjs/RenderingEngine/Viewport.js.map +1 -1
  5. package/dist/cjs/enums/RequestType.d.ts +2 -1
  6. package/dist/cjs/enums/RequestType.js +1 -0
  7. package/dist/cjs/enums/RequestType.js.map +1 -1
  8. package/dist/cjs/index.d.ts +2 -2
  9. package/dist/cjs/index.js +2 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/init.d.ts +2 -1
  12. package/dist/cjs/init.js +16 -1
  13. package/dist/cjs/init.js.map +1 -1
  14. package/dist/cjs/requestPool/requestPoolManager.d.ts +1 -2
  15. package/dist/cjs/requestPool/requestPoolManager.js +8 -10
  16. package/dist/cjs/requestPool/requestPoolManager.js.map +1 -1
  17. package/dist/cjs/webWorkerManager/webWorkerManager.d.ts +21 -0
  18. package/dist/cjs/webWorkerManager/webWorkerManager.js +167 -0
  19. package/dist/cjs/webWorkerManager/webWorkerManager.js.map +1 -0
  20. package/dist/esm/RenderingEngine/StackViewport.js +1 -0
  21. package/dist/esm/RenderingEngine/StackViewport.js.map +1 -1
  22. package/dist/esm/RenderingEngine/Viewport.js +1 -1
  23. package/dist/esm/RenderingEngine/Viewport.js.map +1 -1
  24. package/dist/esm/enums/RequestType.js +1 -0
  25. package/dist/esm/enums/RequestType.js.map +1 -1
  26. package/dist/esm/index.js +2 -2
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/init.js +12 -1
  29. package/dist/esm/init.js.map +1 -1
  30. package/dist/esm/requestPool/requestPoolManager.js +8 -10
  31. package/dist/esm/requestPool/requestPoolManager.js.map +1 -1
  32. package/dist/esm/webWorkerManager/webWorkerManager.js +132 -0
  33. package/dist/esm/webWorkerManager/webWorkerManager.js.map +1 -0
  34. package/dist/types/RenderingEngine/StackViewport.d.ts.map +1 -1
  35. package/dist/types/enums/RequestType.d.ts +2 -1
  36. package/dist/types/enums/RequestType.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +2 -2
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/types/init.d.ts +2 -1
  40. package/dist/types/init.d.ts.map +1 -1
  41. package/dist/types/requestPool/requestPoolManager.d.ts +1 -2
  42. package/dist/types/requestPool/requestPoolManager.d.ts.map +1 -1
  43. package/dist/types/webWorkerManager/webWorkerManager.d.ts +22 -0
  44. package/dist/types/webWorkerManager/webWorkerManager.d.ts.map +1 -0
  45. package/dist/umd/index.js +1 -2
  46. package/dist/umd/index.js.map +1 -1
  47. package/package.json +3 -2
  48. package/src/RenderingEngine/StackViewport.ts +1 -0
  49. package/src/RenderingEngine/Viewport.ts +1 -1
  50. package/src/enums/RequestType.ts +3 -1
  51. package/src/index.ts +2 -0
  52. package/src/init.ts +18 -0
  53. package/src/requestPool/requestPoolManager.ts +9 -13
  54. package/src/webWorkerManager/webWorkerManager.js +218 -0
  55. package/dist/umd/index.js.LICENSE.txt +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cornerstonejs/core",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "types": "dist/types/index.d.ts",
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@kitware/vtk.js": "27.3.1",
34
+ "comlink": "^4.4.1",
34
35
  "detect-gpu": "^5.0.22",
35
36
  "gl-matrix": "^3.4.3",
36
37
  "lodash.clonedeep": "4.5.0"
@@ -46,5 +47,5 @@
46
47
  "type": "individual",
47
48
  "url": "https://ohif.org/donate"
48
49
  },
49
- "gitHead": "9098e604b0099ed3e779d85faa6a1d0e2a4cc4c7"
50
+ "gitHead": "280c27226cb94a0b422f28656e69dbc059f95323"
50
51
  }
@@ -1936,6 +1936,7 @@ class StackViewport extends Viewport implements IStackViewport, IImagesLoader {
1936
1936
  enabled: true,
1937
1937
  },
1938
1938
  useRGBA: true,
1939
+ requestType,
1939
1940
  };
1940
1941
 
1941
1942
  const eventDetail: EventTypes.PreStackNewImageEventDetail = {
@@ -1113,7 +1113,7 @@ class Viewport implements IViewport {
1113
1113
  }
1114
1114
 
1115
1115
  if (viewUp) {
1116
- viewUpHasChanged = isEqual(currentViewUp, prevViewUp);
1116
+ viewUpHasChanged = !isEqual(currentViewUp, prevViewUp);
1117
1117
  }
1118
1118
 
1119
1119
  // only modify the clipping planes if the camera is modified out of plane
@@ -6,8 +6,10 @@ enum RequestType {
6
6
  Interaction = 'interaction',
7
7
  /** Second highest priority for loading*/
8
8
  Thumbnail = 'thumbnail',
9
- /** Lowest priority for loading*/
9
+ /** Third highest priority for loading, usually used for image loading in the background*/
10
10
  Prefetch = 'prefetch',
11
+ /** Lower priority, often used for background computations in the worker */
12
+ Compute = 'compute',
11
13
  }
12
14
 
13
15
  export default RequestType;
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  resetUseSharedArrayBuffer,
41
41
  getConfiguration,
42
42
  setConfiguration,
43
+ getWebWorkerManager,
43
44
  } from './init';
44
45
 
45
46
  // Classes
@@ -85,6 +86,7 @@ export {
85
86
  // configs
86
87
  getConfiguration,
87
88
  setConfiguration,
89
+ getWebWorkerManager,
88
90
  // enums
89
91
  Enums,
90
92
  CONSTANTS,
package/src/init.ts CHANGED
@@ -6,6 +6,8 @@ let useSharedArrayBuffer = true;
6
6
  let sharedArrayBufferMode = SharedArrayBufferModes.TRUE;
7
7
  import { deepMerge } from './utilities';
8
8
  import { Cornerstone3DConfig } from './types';
9
+ import CentralizedWebWorkerManager from './webWorkerManager/webWorkerManager';
10
+
9
11
  // TODO: move sharedArrayBuffer into config.
10
12
  // TODO: change config into a class with methods to better control get/set
11
13
  const defaultConfig: Cornerstone3DConfig = {
@@ -36,6 +38,8 @@ let config: Cornerstone3DConfig = {
36
38
  // ...
37
39
  };
38
40
 
41
+ let webWorkerManager = null;
42
+
39
43
  function _getGLContext(): RenderingContext {
40
44
  // Create canvas element. The canvas is not added to the
41
45
  // document itself, so it is never displayed in the
@@ -133,6 +137,11 @@ async function init(configuration = config): Promise<boolean> {
133
137
  setUseSharedArrayBuffer(sharedArrayBufferMode);
134
138
 
135
139
  csRenderInitialized = true;
140
+
141
+ if (!webWorkerManager) {
142
+ webWorkerManager = new CentralizedWebWorkerManager();
143
+ }
144
+
136
145
  return csRenderInitialized;
137
146
  }
138
147
 
@@ -256,6 +265,14 @@ function _updateRenderingPipelinesForAllViewports(): void {
256
265
  );
257
266
  }
258
267
 
268
+ function getWebWorkerManager() {
269
+ if (!webWorkerManager) {
270
+ webWorkerManager = new CentralizedWebWorkerManager();
271
+ }
272
+
273
+ return webWorkerManager;
274
+ }
275
+
259
276
  export {
260
277
  init,
261
278
  getShouldUseCPURendering,
@@ -268,4 +285,5 @@ export {
268
285
  resetUseSharedArrayBuffer,
269
286
  getConfiguration,
270
287
  setConfiguration,
288
+ getWebWorkerManager,
271
289
  };
@@ -78,12 +78,14 @@ class RequestPoolManager {
78
78
  interaction: 0,
79
79
  thumbnail: 0,
80
80
  prefetch: 0,
81
+ compute: 0,
81
82
  };
82
83
  /* maximum number of requests of each type. */
83
84
  public maxNumRequests: {
84
85
  interaction: number;
85
86
  thumbnail: number;
86
87
  prefetch: number;
88
+ compute: number;
87
89
  };
88
90
  /* A public property that is used to set the delay between requests. */
89
91
  public grabDelay: number;
@@ -101,6 +103,7 @@ class RequestPoolManager {
101
103
  interaction: { 0: [] },
102
104
  thumbnail: { 0: [] },
103
105
  prefetch: { 0: [] },
106
+ compute: { 0: [] },
104
107
  };
105
108
 
106
109
  this.grabDelay = 5;
@@ -110,12 +113,14 @@ class RequestPoolManager {
110
113
  interaction: 0,
111
114
  thumbnail: 0,
112
115
  prefetch: 0,
116
+ compute: 0,
113
117
  };
114
118
 
115
119
  this.maxNumRequests = {
116
120
  interaction: 6,
117
121
  thumbnail: 6,
118
122
  prefetch: 5,
123
+ compute: 15,
119
124
  };
120
125
  }
121
126
 
@@ -185,15 +190,7 @@ class RequestPoolManager {
185
190
  // Adding the request to the correct priority group of the request type
186
191
  this.requestPool[type][priority].push(requestDetails);
187
192
 
188
- // Wake up
189
- if (!this.awake) {
190
- this.awake = true;
191
- this.startGrabbing();
192
- } else if (type === RequestType.Interaction) {
193
- // Todo: this is a hack for interaction right now, we should separate
194
- // the grabbing from the adding requests
195
- this.startGrabbing();
196
- }
193
+ this.startGrabbing();
197
194
  }
198
195
 
199
196
  /**
@@ -273,11 +270,13 @@ class RequestPoolManager {
273
270
  const hasRemainingPrefetchRequests = this.sendRequests(
274
271
  RequestType.Prefetch
275
272
  );
273
+ const hasRemainingComputeRequests = this.sendRequests(RequestType.Compute);
276
274
 
277
275
  if (
278
276
  !hasRemainingInteractionRequests &&
279
277
  !hasRemainingThumbnailRequests &&
280
- !hasRemainingPrefetchRequests
278
+ !hasRemainingPrefetchRequests &&
279
+ !hasRemainingComputeRequests
281
280
  ) {
282
281
  this.awake = false;
283
282
  }
@@ -323,7 +322,4 @@ class RequestPoolManager {
323
322
  }
324
323
  }
325
324
 
326
- const requestPoolManager = new RequestPoolManager();
327
-
328
325
  export { RequestPoolManager };
329
- export default requestPoolManager;
@@ -0,0 +1,218 @@
1
+ import * as Comlink from 'comlink';
2
+ import { RequestType } from '../enums/';
3
+ import { RequestPoolManager } from '../requestPool/requestPoolManager';
4
+
5
+ class CentralizedWorkerManager {
6
+ constructor() {
7
+ this.workerRegistry = {};
8
+ this.workerPoolManager = new RequestPoolManager('webworker');
9
+ this.checkIntervalForIdleWorkers = 1000;
10
+ }
11
+
12
+ setCheckIntervalForIdleWorkers(value) {
13
+ this.checkIntervalForIdleWorkers = value;
14
+ }
15
+
16
+ /**
17
+ * Registers a new worker, it doesn't mean that the function will get executed.
18
+ *
19
+ * @param workerName - The name of the worker.
20
+ * @param workerFn - The function that creates a new instance of the worker.
21
+ * @param options - Optional parameters.
22
+ * @param options.maxWorkerInstances - The maximum number of instances of this worker that can be created.
23
+ * For instance if you create a worker with maxWorkerInstances = 2, then only 2 instances of this worker will be created
24
+ * and in case there are 10 tasks that need to be executed, each will get assigned 5 tasks.
25
+ * @param options.overwrite - Whether to overwrite the worker if it's already registered.
26
+ * @param options.autoTerminateOnIdle - Whether to automatically terminate idle workers.
27
+ */
28
+ registerWorker(workerName, workerFn, options = {}) {
29
+ const {
30
+ maxWorkerInstances = 1,
31
+ overwrite = false,
32
+ autoTerminateOnIdle = false,
33
+ } = options;
34
+
35
+ if (this.workerRegistry[workerName] && !overwrite) {
36
+ console.warn(`Worker type '${workerName}' is already registered...`);
37
+ return;
38
+ }
39
+
40
+ if (overwrite && this.workerRegistry[workerName]?.idleCheckIntervalId) {
41
+ clearInterval(this.workerRegistry[workerName].idleCheckIntervalId);
42
+ }
43
+
44
+ const workerProperties = {
45
+ workerFn: null,
46
+ idleCheckIntervalId: null,
47
+ instances: [],
48
+ loadCounters: [],
49
+ lastActiveTime: [],
50
+ // used for termination
51
+ nativeWorkers: [],
52
+ };
53
+
54
+ if (
55
+ (autoTerminateOnIdle && !workerProperties.idleCheckIntervalId) ||
56
+ overwrite
57
+ ) {
58
+ const idleCheckIntervalId = setInterval(() => {
59
+ this.terminateIdleWorkers(workerName, autoTerminateOnIdle);
60
+ }, this.checkIntervalForIdleWorkers);
61
+
62
+ workerProperties.idleCheckIntervalId = idleCheckIntervalId;
63
+ }
64
+
65
+ workerProperties.loadCounters = Array(maxWorkerInstances).fill(0);
66
+ workerProperties.lastActiveTime = Array(maxWorkerInstances).fill(null);
67
+
68
+ for (let i = 0; i < maxWorkerInstances; i++) {
69
+ const worker = workerFn();
70
+ workerProperties.instances.push(Comlink.wrap(worker));
71
+ workerProperties.nativeWorkers.push(worker);
72
+ workerProperties.workerFn = workerFn;
73
+ }
74
+
75
+ this.workerRegistry[workerName] = workerProperties;
76
+ }
77
+
78
+ getNextWorkerAPI(workerName) {
79
+ const workerProperties = this.workerRegistry[workerName];
80
+
81
+ if (!workerProperties) {
82
+ console.error(`Worker type '${workerName}' is not registered.`);
83
+ return null;
84
+ }
85
+
86
+ // Find the worker with the minimum load.
87
+ const workerInstances = workerProperties.instances.filter(
88
+ (instance) => instance !== null
89
+ );
90
+
91
+ let minLoadIndex = 0;
92
+ let minLoadValue = workerProperties.loadCounters[0] || 0;
93
+
94
+ for (let i = 1; i < workerInstances.length; i++) {
95
+ const currentLoadValue = workerProperties.loadCounters[i] || 0;
96
+ if (currentLoadValue < minLoadValue) {
97
+ minLoadIndex = i;
98
+ minLoadValue = currentLoadValue;
99
+ }
100
+ }
101
+
102
+ // Check and recreate the worker if it was terminated.
103
+ if (workerProperties.instances[minLoadIndex] === null) {
104
+ const worker = workerProperties.workerFn();
105
+ workerProperties.instances[minLoadIndex] = Comlink.wrap(worker);
106
+ workerProperties.nativeWorkers[minLoadIndex] = worker;
107
+ }
108
+
109
+ // Update the load counter.
110
+ workerProperties.loadCounters[minLoadIndex] += 1;
111
+
112
+ // return the worker that has the minimum load.
113
+ return {
114
+ api: workerProperties.instances[minLoadIndex],
115
+ index: minLoadIndex,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Executes a task on a worker.
121
+ *
122
+ * @param workerName - The name of the worker to execute the task on.
123
+ * @param methodName - The name of the method to execute on the worker.
124
+ * @param args - The arguments to pass to the method. Default is an empty object.
125
+ * @param options - An object containing options for the request. Default is an empty object.
126
+ * @param options.requestType - The type of the request. Default is RequestType.Compute.
127
+ * @param options.priority - The priority of the request. Default is 0.
128
+ * @param options.options - Additional options for the request. Default is an empty object.
129
+ *
130
+ * @returns A promise that resolves with the result of the task.
131
+ */
132
+ executeTask(
133
+ workerName,
134
+ methodName,
135
+ args = {},
136
+ { requestType = RequestType.Compute, priority = 0, options = {} } = {}
137
+ ) {
138
+ return new Promise((resolve, reject) => {
139
+ const requestFn = async () => {
140
+ const { api, index } = this.getNextWorkerAPI(workerName);
141
+ if (!api) {
142
+ const error = new Error(
143
+ `No available worker instance for '${workerName}'`
144
+ );
145
+ console.error(error);
146
+ reject(error);
147
+ return;
148
+ }
149
+
150
+ try {
151
+ const results = await api[methodName](args);
152
+
153
+ const workerProperties = this.workerRegistry[workerName];
154
+ workerProperties.lastActiveTime[index] = Date.now();
155
+
156
+ resolve(results);
157
+ } catch (err) {
158
+ console.error(
159
+ `Error executing method '${methodName}' on worker '${workerName}':`,
160
+ err
161
+ );
162
+ reject(err);
163
+ } finally {
164
+ this.workerRegistry[workerName].loadCounters[index]--;
165
+ }
166
+ };
167
+
168
+ this.workerPoolManager.addRequest(
169
+ requestFn,
170
+ requestType,
171
+ options,
172
+ priority
173
+ );
174
+ });
175
+ }
176
+
177
+ terminateIdleWorkers(workerName, idleTimeThreshold) {
178
+ const workerProperties = this.workerRegistry[workerName];
179
+
180
+ const now = Date.now();
181
+
182
+ workerProperties.instances.forEach((workerInstance, index) => {
183
+ // If the worker has not yet executed any task, skip this iteration
184
+ if (workerProperties.lastActiveTime[index] == null) {
185
+ return;
186
+ }
187
+
188
+ const idleTime = now - workerProperties.lastActiveTime[index];
189
+
190
+ // If the worker has been idle for longer than the threshold and it exists
191
+ if (idleTime > idleTimeThreshold && workerInstance !== null) {
192
+ workerInstance[Comlink.releaseProxy]();
193
+ workerProperties.nativeWorkers[index].terminate();
194
+
195
+ workerProperties.instances[index] = null;
196
+ workerProperties.lastActiveTime[index] = null;
197
+ }
198
+ });
199
+ }
200
+
201
+ terminate(workerName) {
202
+ const workerProperties = this.workerRegistry[workerName];
203
+ if (!workerProperties) {
204
+ console.error(`Worker type '${workerName}' is not registered.`);
205
+ return;
206
+ }
207
+
208
+ workerProperties.instances.forEach((workerInstance) => {
209
+ workerInstance[Comlink.releaseProxy]();
210
+ });
211
+
212
+ workerProperties.nativeWorkers.forEach((worker) => {
213
+ worker.terminate();
214
+ });
215
+ }
216
+ }
217
+
218
+ export default CentralizedWorkerManager;
@@ -1 +0,0 @@
1
- /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */