larvitar 1.5.14 → 2.0.2

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 (107) hide show
  1. package/.vscode/settings.json +4 -0
  2. package/README.md +78 -48
  3. package/bundler/webpack.common.js +27 -0
  4. package/bundler/webpack.dev.js +23 -0
  5. package/bundler/webpack.prod.js +19 -0
  6. package/decs.d.ts +12 -0
  7. package/dist/imaging/MetaDataReadable.d.ts +40 -0
  8. package/dist/imaging/MetaDataTypes.d.ts +3489 -0
  9. package/dist/imaging/imageAnonymization.d.ts +12 -0
  10. package/dist/imaging/imageColormaps.d.ts +47 -0
  11. package/dist/imaging/imageContours.d.ts +18 -0
  12. package/dist/imaging/imageIo.d.ts +42 -0
  13. package/dist/imaging/imageLayers.d.ts +56 -0
  14. package/dist/imaging/imageLoading.d.ts +65 -0
  15. package/dist/imaging/imageParsing.d.ts +46 -0
  16. package/dist/imaging/imagePresets.d.ts +43 -0
  17. package/dist/imaging/imageRendering.d.ts +238 -0
  18. package/dist/imaging/imageReslice.d.ts +14 -0
  19. package/dist/imaging/imageStore.d.ts +121 -0
  20. package/dist/imaging/imageTags.d.ts +22 -0
  21. package/dist/imaging/imageTools.d.ts +20 -0
  22. package/dist/imaging/imageUtils.d.ts +165 -0
  23. package/dist/imaging/loaders/commonLoader.d.ts +103 -0
  24. package/dist/imaging/loaders/dicomLoader.d.ts +29 -0
  25. package/dist/imaging/loaders/fileLoader.d.ts +33 -0
  26. package/dist/imaging/loaders/multiframeLoader.d.ts +37 -0
  27. package/dist/imaging/loaders/nrrdLoader.d.ts +113 -0
  28. package/dist/imaging/loaders/resliceLoader.d.ts +15 -0
  29. package/dist/imaging/monitors/memory.d.ts +41 -0
  30. package/dist/imaging/monitors/performance.d.ts +23 -0
  31. package/dist/imaging/parsers/ecg.d.ts +15 -0
  32. package/dist/imaging/parsers/nrrd.d.ts +3 -0
  33. package/dist/imaging/tools/custom/4dSliceScrollTool.d.ts +12 -0
  34. package/dist/imaging/tools/custom/BorderMagnifyTool.d.ts +18 -0
  35. package/dist/imaging/tools/custom/contourTool.d.ts +409 -0
  36. package/dist/imaging/tools/custom/diameterTool.d.ts +18 -0
  37. package/dist/imaging/tools/custom/editMaskTool.d.ts +22 -0
  38. package/dist/imaging/tools/custom/ellipticalRoiOverlayTool.d.ts +45 -0
  39. package/dist/imaging/tools/custom/polygonSegmentationMixin.d.ts +54 -0
  40. package/dist/imaging/tools/custom/polylineScissorsTool.d.ts +11 -0
  41. package/dist/imaging/tools/custom/rectangleRoiOverlayTool.d.ts +45 -0
  42. package/dist/imaging/tools/custom/seedTool.d.ts +0 -0
  43. package/dist/imaging/tools/custom/setLabelMap3D.d.ts +39 -0
  44. package/dist/imaging/tools/custom/thresholdsBrushTool.d.ts +19 -0
  45. package/dist/imaging/tools/default.d.ts +53 -0
  46. package/dist/imaging/tools/interaction.d.ts +30 -0
  47. package/dist/imaging/tools/io.d.ts +38 -0
  48. package/dist/imaging/tools/main.d.ts +81 -0
  49. package/dist/imaging/tools/segmentation.d.ts +125 -0
  50. package/dist/imaging/tools/state.d.ts +17 -0
  51. package/dist/imaging/tools/strategies/eraseFreehand.d.ts +16 -0
  52. package/dist/imaging/tools/strategies/fillFreehand.d.ts +16 -0
  53. package/dist/imaging/tools/strategies/index.d.ts +2 -0
  54. package/dist/imaging/waveforms/ecg.d.ts +39 -0
  55. package/dist/index.d.ts +35 -0
  56. package/dist/larvitar.js +90104 -0
  57. package/dist/larvitar.js.map +1 -0
  58. package/imaging/MetaDataReadable.ts +41 -0
  59. package/imaging/MetaDataTypes.ts +3491 -0
  60. package/imaging/dataDictionary.json +5328 -5328
  61. package/imaging/{imageAnonymization.js → imageAnonymization.ts} +41 -13
  62. package/imaging/{imageColormaps.js → imageColormaps.ts} +48 -30
  63. package/imaging/{imageContours.js → imageContours.ts} +24 -22
  64. package/imaging/{imageIo.js → imageIo.ts} +89 -52
  65. package/imaging/{imageLayers.js → imageLayers.ts} +31 -14
  66. package/imaging/{imageLoading.js → imageLoading.ts} +107 -43
  67. package/imaging/{imageParsing.js → imageParsing.ts} +160 -80
  68. package/imaging/{imagePresets.js → imagePresets.ts} +44 -11
  69. package/imaging/imageRendering.ts +1091 -0
  70. package/imaging/{imageReslice.js → imageReslice.ts} +18 -9
  71. package/imaging/imageStore.ts +487 -0
  72. package/imaging/imageTags.ts +609 -0
  73. package/imaging/imageTools.js +2 -1
  74. package/imaging/{imageUtils.js → imageUtils.ts} +211 -701
  75. package/imaging/loaders/{commonLoader.js → commonLoader.ts} +73 -24
  76. package/imaging/loaders/{dicomLoader.js → dicomLoader.ts} +25 -5
  77. package/imaging/loaders/{fileLoader.js → fileLoader.ts} +5 -5
  78. package/imaging/loaders/{multiframeLoader.js → multiframeLoader.ts} +145 -90
  79. package/imaging/loaders/{nrrdLoader.js → nrrdLoader.ts} +231 -64
  80. package/imaging/loaders/{resliceLoader.js → resliceLoader.ts} +51 -20
  81. package/imaging/monitors/{memory.js → memory.ts} +54 -8
  82. package/imaging/monitors/performance.ts +34 -0
  83. package/imaging/parsers/ecg.ts +54 -0
  84. package/imaging/tools/README.md +27 -0
  85. package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
  86. package/imaging/tools/custom/BorderMagnifyTool.js +99 -0
  87. package/imaging/tools/custom/ellipticalRoiOverlayTool.js +534 -0
  88. package/imaging/tools/custom/polylineScissorsTool.js +1 -1
  89. package/imaging/tools/custom/rectangleRoiOverlayTool.js +564 -0
  90. package/imaging/tools/{setLabelMap3D.js → custom/setLabelMap3D.ts} +19 -25
  91. package/imaging/tools/{default.js → default.ts} +119 -33
  92. package/imaging/tools/{interaction.js → interaction.ts} +42 -23
  93. package/imaging/tools/{io.js → io.ts} +47 -31
  94. package/imaging/tools/{main.js → main.ts} +105 -40
  95. package/imaging/tools/{segmentation.js → segmentation.ts} +95 -68
  96. package/imaging/tools/{state.js → state.ts} +7 -12
  97. package/imaging/tools/types.d.ts +243 -0
  98. package/imaging/types.d.ts +200 -0
  99. package/imaging/waveforms/ecg.ts +191 -0
  100. package/{index.js → index.ts} +53 -14
  101. package/jsdoc.json +1 -1
  102. package/package.json +35 -14
  103. package/tsconfig.json +102 -0
  104. package/imaging/imageRendering.js +0 -860
  105. package/imaging/imageStore.js +0 -322
  106. package/modules/vuex/larvitar.js +0 -187
  107. /package/imaging/tools/{polygonSegmentationMixin.js → custom/polygonSegmentationMixin.js} +0 -0
@@ -13,7 +13,8 @@ import {
13
13
  getLarvitarImageTracker,
14
14
  getLarvitarManager
15
15
  } from "./loaders/commonLoader";
16
- import { larvitar_store } from "./imageStore";
16
+ import store from "./imageStore";
17
+ import { Series } from "./types";
17
18
 
18
19
  /*
19
20
  * This module provides the following functions to be exported:
@@ -26,12 +27,14 @@ import { larvitar_store } from "./imageStore";
26
27
  * @function resliceSeries
27
28
  * @param {Object} seriesData the original series data
28
29
  * @param {String} orientation the reslice orientation [coronal or sagittal]
29
- * @param {String} seriesId the series id
30
30
  * @returns {Promise} - Return a promise which will resolve when data is available
31
31
  */
32
- export function resliceSeries(seriesData, orientation) {
32
+ export function resliceSeries(
33
+ seriesData: Series,
34
+ orientation: "axial" | "coronal" | "sagittal"
35
+ ) {
33
36
  let reslicePromise = new Promise(resolve => {
34
- let reslicedSeries = {};
37
+ let reslicedSeries: Partial<Series> = {};
35
38
  let reslicedSeriesId = uuidv4();
36
39
  let reslicedMetaData = getReslicedMetadata(
37
40
  reslicedSeriesId,
@@ -42,37 +45,43 @@ export function resliceSeries(seriesData, orientation) {
42
45
  );
43
46
 
44
47
  reslicedSeries.imageIds = reslicedMetaData.imageIds;
48
+ // @ts-ignore fix incompatibilities between these types
45
49
  reslicedSeries.instances = reslicedMetaData.instances;
46
50
 
47
51
  reslicedSeries.currentImageIdIndex = Math.floor(
48
52
  reslicedSeries.imageIds.length / 2
49
53
  );
50
54
 
51
- function computeReslice(seriesData, reslicedSeriesId, reslicedSeries) {
55
+ function computeReslice(
56
+ seriesData: Series,
57
+ reslicedSeriesId: string,
58
+ reslicedSeries: Series
59
+ ) {
52
60
  let t0 = performance.now();
53
61
  let imageTracker = getLarvitarImageTracker();
54
62
  let manager = getLarvitarManager();
55
- each(reslicedSeries.imageIds, function (imageId) {
63
+ each(reslicedSeries.imageIds, function (imageId: string) {
56
64
  reslicedSeries.instances[imageId].pixelData = getReslicedPixeldata(
57
65
  imageId,
58
66
  seriesData,
59
67
  reslicedSeries
60
- );
68
+ ) as Uint16Array;
61
69
  imageTracker[imageId] = reslicedSeriesId;
62
70
  });
63
- larvitar_store.addSeriesIds(reslicedSeriesId, reslicedSeries.imageIds);
71
+ store.addSeriesId(reslicedSeriesId, reslicedSeries.imageIds);
64
72
  reslicedSeries.numberOfImages = reslicedSeries.imageIds.length;
65
73
  reslicedSeries.seriesUID = reslicedSeriesId;
66
74
  reslicedSeries.seriesDescription = seriesData.seriesDescription;
67
75
  reslicedSeries.orientation = orientation;
68
76
  manager[reslicedSeriesId] = reslicedSeries;
77
+ //@ts-ignore deprecated
69
78
  manager[seriesData.seriesUID][orientation] = reslicedSeriesId;
70
79
  let t1 = performance.now();
71
80
  console.log(`Call to resliceSeries took ${t1 - t0} milliseconds.`);
72
81
  resolve(reslicedSeries);
73
82
  }
74
83
  // reslice the data
75
- computeReslice(seriesData, reslicedSeriesId, reslicedSeries);
84
+ computeReslice(seriesData, reslicedSeriesId, reslicedSeries as Series);
76
85
  });
77
86
  return reslicePromise;
78
87
  }
@@ -0,0 +1,487 @@
1
+ /** @module imaging/imageStore
2
+ * @desc This file provides functionalities
3
+ * for data config store.
4
+ */
5
+
6
+ // external libraries
7
+ import { get as _get, cloneDeep as _cloneDeep } from "lodash";
8
+
9
+ type StoreSeries = { imageIds: string[]; progress: number };
10
+
11
+ type Store = {
12
+ colormapId: string;
13
+ errorLog: string; // TODO review this, should be an array?
14
+ leftActiveTool?: string;
15
+ rightActiveTool?: string;
16
+ series: { [seriesUID: string]: StoreSeries };
17
+ viewports: { [key: string]: typeof DEFAULT_VIEWPORT };
18
+ // fallback for any other field
19
+ [key: string]: any;
20
+ };
21
+
22
+ type SetPayload =
23
+ | ["errorLog" | "leftActiveTool" | "rightActiveTool", string]
24
+ | [
25
+ "isColor" | "isMultiframe" | "isPDF" | "isTimeserie" | "ready",
26
+ string,
27
+ boolean
28
+ ]
29
+ | [
30
+ (
31
+ | "progress"
32
+ | "loading"
33
+ | "minPixelValue"
34
+ | "maxPixelValue"
35
+ | "minSliceId"
36
+ | "maxSliceId"
37
+ | "minTimeId"
38
+ | "maxTimeId"
39
+ | "rotation"
40
+ | "scale"
41
+ | "sliceId"
42
+ | "timeId"
43
+ | "thickness"
44
+ ),
45
+ string,
46
+ number
47
+ ]
48
+ | ["timestamp", string, number | undefined]
49
+ | ["pendingSliceId", string, number | undefined]
50
+ | ["timestamps" | "timeIds", string, number[]]
51
+ | [
52
+ "contrast" | "dimensions" | "spacing" | "translation",
53
+ string,
54
+ number,
55
+ number
56
+ ]
57
+ | [
58
+ "defaultViewport",
59
+ string,
60
+ number,
61
+ number,
62
+ number,
63
+ number,
64
+ number,
65
+ number,
66
+ boolean
67
+ ];
68
+
69
+ // Larvitar store object
70
+ let STORE: Store;
71
+
72
+ // Data listeners
73
+ let storeListener: ((data: Store) => {}) | undefined = undefined;
74
+ const seriesListeners = {} as {
75
+ [seriesId: string]: (data: StoreSeries) => {};
76
+ };
77
+ const viewportsListeners = {} as {
78
+ [elementId: string]: (data: typeof DEFAULT_VIEWPORT) => {};
79
+ };
80
+
81
+ // default initial store object
82
+ const INITIAL_STORE_DATA: Store = {
83
+ colormapId: "gray",
84
+ errorLog: "",
85
+ leftActiveTool: undefined,
86
+ rightActiveTool: undefined,
87
+ series: {},
88
+ viewports: {}
89
+ };
90
+
91
+ // default viewport object
92
+ export const DEFAULT_VIEWPORT: {
93
+ loading: number;
94
+ ready: boolean;
95
+ minSliceId: number;
96
+ maxSliceId: number;
97
+ sliceId: number;
98
+ pendingSliceId?: number;
99
+ minTimeId: number;
100
+ maxTimeId: number;
101
+ timeId: number;
102
+ timestamp: number;
103
+ timestamps: number[];
104
+ timeIds: number[];
105
+ rows: number;
106
+ cols: number;
107
+ spacing_x: number;
108
+ spacing_y: number;
109
+ thickness: number;
110
+ minPixelValue: number;
111
+ maxPixelValue: number;
112
+ isColor: boolean;
113
+ isMultiframe: boolean;
114
+ isTimeserie: boolean;
115
+ isPDF: boolean;
116
+ viewport: {
117
+ scale: number;
118
+ rotation: number;
119
+ translation: {
120
+ x: number;
121
+ y: number;
122
+ };
123
+ voi: {
124
+ windowCenter: number;
125
+ windowWidth: number;
126
+ };
127
+ // redundant fields ?
128
+ rows: number;
129
+ cols: number;
130
+ spacing_x: number;
131
+ spacing_y: number;
132
+ thickness: number;
133
+ };
134
+ default: {
135
+ scale: number;
136
+ rotation: number;
137
+ translation: {
138
+ x: number;
139
+ y: number;
140
+ };
141
+ voi: {
142
+ windowCenter: number;
143
+ windowWidth: number;
144
+ invert: boolean;
145
+ };
146
+ };
147
+ } = {
148
+ loading: 0, // from 0 to 100 (%)
149
+ ready: false, // true when currentImageId is rendered
150
+ minSliceId: 0,
151
+ maxSliceId: 0,
152
+ sliceId: 0,
153
+ pendingSliceId: undefined,
154
+ minTimeId: 0,
155
+ maxTimeId: 0,
156
+ timeId: 0,
157
+ timestamp: 0,
158
+ timestamps: [],
159
+ timeIds: [],
160
+ rows: 0,
161
+ cols: 0,
162
+ spacing_x: 0.0,
163
+ spacing_y: 0.0,
164
+ thickness: 0.0,
165
+ minPixelValue: 0,
166
+ maxPixelValue: 0,
167
+ isColor: false,
168
+ isMultiframe: false,
169
+ isTimeserie: false,
170
+ isPDF: false,
171
+ viewport: {
172
+ scale: 0.0,
173
+ rotation: 0.0,
174
+ translation: {
175
+ x: 0.0,
176
+ y: 0.0
177
+ },
178
+ voi: {
179
+ windowCenter: 0.0,
180
+ windowWidth: 0.0
181
+ },
182
+ // redundant fields ?
183
+ rows: 0,
184
+ cols: 0,
185
+ spacing_x: 0.0,
186
+ spacing_y: 0.0,
187
+ thickness: 0.0
188
+ },
189
+ default: {
190
+ scale: 0.0,
191
+ rotation: 0.0,
192
+ translation: {
193
+ x: 0.0,
194
+ y: 0.0
195
+ },
196
+ voi: {
197
+ windowCenter: 0.0,
198
+ windowWidth: 0.0,
199
+ invert: false
200
+ }
201
+ }
202
+ };
203
+
204
+ export type Viewport = typeof DEFAULT_VIEWPORT;
205
+
206
+ // Trigger store listeners
207
+ const triggerStoreListener = (data: Store) =>
208
+ storeListener ? storeListener(data) : undefined;
209
+
210
+ const triggerViewportListener = (elementId: string) => {
211
+ if (viewportsListeners[elementId] && STORE?.viewports[elementId]) {
212
+ viewportsListeners[elementId](STORE.viewports[elementId]);
213
+ }
214
+ };
215
+
216
+ const triggerSeriesListener = (seriesId: string) => {
217
+ if (seriesListeners[seriesId] && STORE?.series[seriesId]) {
218
+ seriesListeners[seriesId](STORE.series[seriesId]);
219
+ }
220
+ };
221
+
222
+ /**
223
+ * Set a value into store
224
+ * @function setValue
225
+ * @param {Object} data - The data object
226
+ */
227
+ const setValue = (store: Store, data: SetPayload) => {
228
+ let field = data[0];
229
+ const k = data[1];
230
+ let [_1, _2, ...v] = data;
231
+
232
+ const viewport = store.viewports[k];
233
+
234
+ // assign values
235
+ switch (field) {
236
+ case "progress":
237
+ if (!store.series[k]) {
238
+ return;
239
+ }
240
+ store.series[k][field] = (v as [number])[0];
241
+ triggerSeriesListener(k);
242
+ break;
243
+
244
+ case "isColor":
245
+ case "isMultiframe":
246
+ case "isPDF":
247
+ case "isTimeserie":
248
+ case "ready":
249
+ if (!viewport) {
250
+ return;
251
+ }
252
+ viewport[field] = (v as [boolean])[0];
253
+ triggerViewportListener(k);
254
+ break;
255
+
256
+ case "loading":
257
+ case "minPixelValue":
258
+ case "maxPixelValue":
259
+ case "minSliceId":
260
+ case "maxSliceId":
261
+ case "minTimeId":
262
+ case "maxTimeId":
263
+ case "sliceId":
264
+ case "pendingSliceId":
265
+ case "timeId":
266
+ case "timestamp":
267
+ if (!viewport) {
268
+ return;
269
+ }
270
+ viewport[field] = (v as [number])[0];
271
+ triggerViewportListener(k);
272
+ break;
273
+
274
+ case "timestamps":
275
+ case "timeIds":
276
+ if (!viewport) {
277
+ return;
278
+ }
279
+ viewport[field] = (v as [[number]])[0];
280
+ triggerViewportListener(k);
281
+ break;
282
+
283
+ case "rotation":
284
+ case "scale":
285
+ if (!viewport) {
286
+ return;
287
+ }
288
+ viewport.viewport[field] = (v as [number])[0];
289
+ triggerViewportListener(k);
290
+ break;
291
+
292
+ case "thickness":
293
+ if (!viewport) {
294
+ return;
295
+ }
296
+ viewport[field] = (v as [number])[0];
297
+ viewport.viewport[field] = (v as [number])[0];
298
+ triggerViewportListener(k);
299
+ break;
300
+
301
+ case "translation":
302
+ if (!viewport) {
303
+ return;
304
+ }
305
+ v = v as [number, number];
306
+ viewport.viewport[field] = { x: v[0], y: v[1] };
307
+ triggerViewportListener(k);
308
+ break;
309
+
310
+ case "contrast":
311
+ if (!viewport) {
312
+ return;
313
+ }
314
+ v = v as [number, number];
315
+ viewport.viewport.voi.windowWidth = v[0];
316
+ viewport.viewport.voi.windowCenter = v[1];
317
+ triggerViewportListener(k);
318
+ break;
319
+
320
+ case "dimensions":
321
+ if (!viewport) {
322
+ return;
323
+ }
324
+ v = v as [number, number];
325
+ viewport.rows = v[0];
326
+ viewport.cols = v[1];
327
+ viewport.viewport.rows = v[0];
328
+ viewport.viewport.cols = v[1];
329
+ triggerViewportListener(k);
330
+ break;
331
+
332
+ case "spacing":
333
+ if (!viewport) {
334
+ return;
335
+ }
336
+ v = v as [number, number];
337
+ viewport.spacing_x = v[0];
338
+ viewport.spacing_y = v[1];
339
+ viewport.viewport.spacing_x = v[0];
340
+ viewport.viewport.spacing_y = v[1];
341
+ triggerViewportListener(k);
342
+ break;
343
+
344
+ case "defaultViewport":
345
+ if (!viewport) {
346
+ return;
347
+ }
348
+ v = v as [number, number, number, number, number, number, boolean];
349
+ viewport.default.scale = v[0];
350
+ viewport.default.rotation = v[1];
351
+ viewport.default.translation.x = v[2];
352
+ viewport.default.translation.y = v[3];
353
+ viewport.default.voi.windowWidth = v[4];
354
+ viewport.default.voi.windowCenter = v[5];
355
+ viewport.default.voi.invert = v[6];
356
+ triggerViewportListener(k);
357
+ break;
358
+
359
+ default:
360
+ store[field] = k;
361
+ break;
362
+ }
363
+ };
364
+
365
+ /**
366
+ * Instancing the store
367
+ */
368
+ const setup = (data = _cloneDeep(INITIAL_STORE_DATA)) => {
369
+ /**
370
+ * Create the Proxy handler object
371
+ * @param {String} name The namespace
372
+ * @param {Object} data The data object
373
+ * @return {Object} The Proxy handler
374
+ */
375
+ const handler: ProxyHandler<Store> = {
376
+ get: (obj, prop: string) => {
377
+ if (prop === "_isProxy") return true;
378
+ if (
379
+ ["object", "array"].includes(
380
+ Object.prototype.toString.call(obj[prop]).slice(8, -1).toLowerCase()
381
+ ) &&
382
+ !obj[prop]._isProxy
383
+ ) {
384
+ obj[prop] = new Proxy<Store>(obj[prop], handler);
385
+ }
386
+ return obj[prop];
387
+ },
388
+ set: (obj, prop: string, value) => {
389
+ // console.warn("SET", obj, prop, value);
390
+ if (obj[prop] === value) return true;
391
+ obj[prop] = value;
392
+ triggerStoreListener(data);
393
+ return true;
394
+ },
395
+ deleteProperty: (obj, prop: string) => {
396
+ delete obj[prop];
397
+ triggerStoreListener(data);
398
+ return true;
399
+ }
400
+ };
401
+
402
+ return new Proxy<Store>(data, handler);
403
+ };
404
+
405
+ const initializeStore = () => {
406
+ STORE = setup();
407
+ };
408
+
409
+ const validateStore = () => {
410
+ if (!STORE) {
411
+ throw "Larvitar store does not exists. Initialize it with the 'initializeStore' function.";
412
+ }
413
+ };
414
+
415
+ export const set = (payload: SetPayload) => {
416
+ validateStore();
417
+ setValue(STORE!, payload);
418
+ };
419
+
420
+ export default {
421
+ initialize: initializeStore,
422
+ // add/remove viewports
423
+ addViewport: (name: string) => {
424
+ validateStore();
425
+ STORE!.viewports[name] = _cloneDeep(DEFAULT_VIEWPORT);
426
+ },
427
+ deleteViewport: (name: string) => {
428
+ validateStore();
429
+ delete STORE!.viewports[name];
430
+ },
431
+ // add/remove series instances ids
432
+ addSeriesId: (seriesId: string, imageIds: string[]) => {
433
+ validateStore();
434
+ if (!STORE!.series[seriesId]) {
435
+ STORE!.series[seriesId] = {} as StoreSeries;
436
+ }
437
+ STORE!.series[seriesId].imageIds = imageIds;
438
+ triggerSeriesListener(seriesId);
439
+ },
440
+ removeSeriesId: (seriesId: string) => {
441
+ validateStore();
442
+ delete STORE!.series[seriesId];
443
+ },
444
+ resetSeriesIds: () => {
445
+ validateStore();
446
+ STORE!.series = {};
447
+ },
448
+ // expose useful sets
449
+ setSliceId: (elementId: string, imageIndex: number) => {
450
+ set(["sliceId", elementId, imageIndex]);
451
+ },
452
+ setPendingSliceId: (elementId: string, imageIndex: number) => {
453
+ set(["pendingSliceId", elementId, imageIndex]);
454
+ },
455
+ setMaxSliceId: (elementId: string, imageIndex: number) => {
456
+ set(["maxSliceId", elementId, imageIndex]);
457
+ },
458
+ // get
459
+ get: (props: string | string[]) => {
460
+ validateStore();
461
+ return _get(STORE, props);
462
+ },
463
+ // watch store
464
+ addStoreListener: (listener: (data: Store) => {}) =>
465
+ (storeListener = listener),
466
+ removeStoreListener: () => (storeListener = undefined),
467
+ // watch single viewport
468
+ addViewportListener: (
469
+ elementId: string,
470
+ listener: (data: typeof DEFAULT_VIEWPORT) => {}
471
+ ) => {
472
+ viewportsListeners[elementId] = listener;
473
+ },
474
+ removeViewportListener: (elementId: string) => {
475
+ delete viewportsListeners[elementId];
476
+ },
477
+ // watch single series
478
+ addSeriesListener: (
479
+ seriesId: string,
480
+ listener: (data: StoreSeries) => {}
481
+ ) => {
482
+ seriesListeners[seriesId] = listener;
483
+ },
484
+ removeSeriesListener: (seriesId: string) => {
485
+ delete seriesListeners[seriesId];
486
+ }
487
+ };