@ohif/app 3.8.0-beta.66 → 3.8.0-beta.67

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 (63) hide show
  1. package/dist/{121.bundle.fda405f29003c308ce09.js → 121.bundle.d8b3c6b530d6151f251d.js} +52 -75
  2. package/dist/{155.bundle.e9fdaa40010cc784f389.js → 155.bundle.a2b8640977007e407d30.js} +7 -16
  3. package/dist/{188.bundle.67df9790c453b185fe1d.js → 188.bundle.b6a7e833fdf99cee3ee6.js} +2 -2
  4. package/dist/{295.bundle.57700cd41fd87e1521b4.js → 295.bundle.075944a082306d2e432f.js} +75 -86
  5. package/dist/{41.bundle.b7bf03502ac3e2ddca35.js → 41.bundle.1c504116ebea23b157f6.js} +8 -43
  6. package/dist/{448.bundle.0061f5280490e1a1aa88.js → 448.bundle.f284c88c8780233e06b1.js} +5 -5
  7. package/dist/494.bundle.ffd75704a069c0720596.js +2565 -0
  8. package/dist/{530.bundle.72d9812f117036615a38.js → 530.bundle.aaf1c61342805ff32648.js} +41 -67
  9. package/dist/{544.bundle.c3009e245ceb1554c70a.js → 544.bundle.33ed8e4a3eaf16b55af7.js} +4 -4
  10. package/dist/{559.bundle.bb2c52834fb372399002.js → 559.bundle.601f9e285f6b3b4d1ac5.js} +4 -4
  11. package/dist/{889.bundle.edf546d8738c22b94be5.js → 574.bundle.c79d3fa0066f39b76442.js} +1005 -49
  12. package/dist/{594.bundle.483843d38640164a9aca.js → 594.bundle.49d072fb31c8994ae85f.js} +4 -4
  13. package/dist/{701.bundle.285943aebfc0efe2b4f1.js → 595.bundle.7a41a0998ab07dfa0212.js} +944 -52
  14. package/dist/{638.bundle.4d7da6fe507df0000718.js → 638.bundle.4c2972aa5a19f816d94a.js} +4 -4
  15. package/dist/{699.bundle.62990e46c235ab4785db.js → 699.bundle.43997eacac9490c1751c.js} +7 -21
  16. package/dist/{724.bundle.83a4176860f750353c0b.js → 724.bundle.00e619f0960de50c8e19.js} +22 -57
  17. package/dist/{862.bundle.999931264956ced59b33.js → 862.bundle.1f1613561a4d6059a8aa.js} +64 -83
  18. package/dist/{270.bundle.d7d6957c20f95c675b32.js → 889.bundle.2daaac42e278b28b3d01.js} +5 -5
  19. package/dist/{90.bundle.49b20161b4f864100085.js → 90.bundle.ce42cccceaec1135a165.js} +11 -9
  20. package/dist/{905.bundle.88010c612e910657883d.js → 905.bundle.af59bd2bcb04c4978059.js} +2 -2
  21. package/dist/{907.bundle.1206d58ae62d26beaf30.js → 907.bundle.401a07f50863efc6c6c1.js} +2 -2
  22. package/dist/{961.bundle.b4d84dd80e4e1113de27.js → 961.bundle.fdf0e1c0c54cfb2a952a.js} +2 -2
  23. package/dist/{app.bundle.81c01fc2e11fa9b6ccb8.js → app.bundle.aadaff7af8cd415a1eae.js} +1805 -637
  24. package/dist/app.bundle.css +6 -4
  25. package/dist/assets/images/CT-AAA.png +0 -0
  26. package/dist/assets/images/CT-AAA2.png +0 -0
  27. package/dist/assets/images/CT-Air.png +0 -0
  28. package/dist/assets/images/CT-Bone.png +0 -0
  29. package/dist/assets/images/CT-Bones.png +0 -0
  30. package/dist/assets/images/CT-Cardiac.png +0 -0
  31. package/dist/assets/images/CT-Cardiac2.png +0 -0
  32. package/dist/assets/images/CT-Cardiac3.png +0 -0
  33. package/dist/assets/images/CT-Chest-Contrast-Enhanced.png +0 -0
  34. package/dist/assets/images/CT-Chest-Vessels.png +0 -0
  35. package/dist/assets/images/CT-Coronary-Arteries-2.png +0 -0
  36. package/dist/assets/images/CT-Coronary-Arteries-3.png +0 -0
  37. package/dist/assets/images/CT-Coronary-Arteries.png +0 -0
  38. package/dist/assets/images/CT-Cropped-Volume-Bone.png +0 -0
  39. package/dist/assets/images/CT-Fat.png +0 -0
  40. package/dist/assets/images/CT-Liver-Vasculature.png +0 -0
  41. package/dist/assets/images/CT-Lung.png +0 -0
  42. package/dist/assets/images/CT-MIP.png +0 -0
  43. package/dist/assets/images/CT-Muscle.png +0 -0
  44. package/dist/assets/images/CT-Pulmonary-Arteries.png +0 -0
  45. package/dist/assets/images/CT-Soft-Tissue.png +0 -0
  46. package/dist/assets/images/DTI-FA-Brain.png +0 -0
  47. package/dist/assets/images/MR-Angio.png +0 -0
  48. package/dist/assets/images/MR-Default.png +0 -0
  49. package/dist/assets/images/MR-MIP.png +0 -0
  50. package/dist/assets/images/MR-T2-Brain.png +0 -0
  51. package/dist/assets/images/VolumeRendering.png +0 -0
  52. package/dist/index.html +1 -1
  53. package/dist/{polySeg.bundle.a97fc68de7599f9a9fdc.js → polySeg.bundle.e7b4c29fb9173e8567b8.js} +1 -1
  54. package/dist/sw.js +1 -1
  55. package/package.json +17 -17
  56. package/dist/339.bundle.526cede81f0a9bb248e6.js +0 -2591
  57. /package/dist/{164.bundle.3f877a2272b773332317.js → 164.bundle.ce3d1cd75bd8e13791d7.js} +0 -0
  58. /package/dist/{191.bundle.509480b6972209d2567c.js → 191.bundle.7d89c921abefd1140d50.js} +0 -0
  59. /package/dist/{290.bundle.8b4d7dfbc7cfe418a0f1.js → 290.bundle.952de53057f98e2c5ef0.js} +0 -0
  60. /package/dist/{342.bundle.17ec05907f93624fd494.js → 342.bundle.6e49f63ea7cea4645c0a.js} +0 -0
  61. /package/dist/{504.bundle.6d203e80d4bd8a823059.js → 504.bundle.993d7e2dec36257d4ce4.js} +0 -0
  62. /package/dist/{889.css → 574.css} +0 -0
  63. /package/dist/{701.css → 595.css} +0 -0
@@ -0,0 +1,2565 @@
1
+ "use strict";
2
+ (globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[494],{
3
+
4
+ /***/ 2494:
5
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
6
+
7
+ // ESM COMPAT FLAG
8
+ __webpack_require__.r(__webpack_exports__);
9
+
10
+ // EXPORTS
11
+ __webpack_require__.d(__webpack_exports__, {
12
+ "default": () => (/* binding */ tmtv_src)
13
+ });
14
+
15
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/package.json
16
+ const package_namespaceObject = /*#__PURE__*/JSON.parse('{"UU":"@ohif/extension-tmtv"}');
17
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/id.js
18
+
19
+ const id = package_namespaceObject.UU;
20
+
21
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/hpViewports.ts
22
+ const ctAXIAL = {
23
+ viewportOptions: {
24
+ viewportId: 'ctAXIAL',
25
+ viewportType: 'volume',
26
+ orientation: 'axial',
27
+ toolGroupId: 'ctToolGroup',
28
+ initialImageOptions: {
29
+ // index: 5,
30
+ preset: 'first' // 'first', 'last', 'middle'
31
+ },
32
+ syncGroups: [{
33
+ type: 'cameraPosition',
34
+ id: 'axialSync',
35
+ source: true,
36
+ target: true
37
+ }, {
38
+ type: 'voi',
39
+ id: 'ctWLSync',
40
+ source: true,
41
+ target: true,
42
+ options: {
43
+ syncColormap: true
44
+ }
45
+ }]
46
+ },
47
+ displaySets: [{
48
+ id: 'ctDisplaySet'
49
+ }]
50
+ };
51
+ const ctSAGITTAL = {
52
+ viewportOptions: {
53
+ viewportId: 'ctSAGITTAL',
54
+ viewportType: 'volume',
55
+ orientation: 'sagittal',
56
+ toolGroupId: 'ctToolGroup',
57
+ syncGroups: [{
58
+ type: 'cameraPosition',
59
+ id: 'sagittalSync',
60
+ source: true,
61
+ target: true
62
+ }, {
63
+ type: 'voi',
64
+ id: 'ctWLSync',
65
+ source: true,
66
+ target: true,
67
+ options: {
68
+ syncColormap: true
69
+ }
70
+ }]
71
+ },
72
+ displaySets: [{
73
+ id: 'ctDisplaySet'
74
+ }]
75
+ };
76
+ const ctCORONAL = {
77
+ viewportOptions: {
78
+ viewportId: 'ctCORONAL',
79
+ viewportType: 'volume',
80
+ orientation: 'coronal',
81
+ toolGroupId: 'ctToolGroup',
82
+ syncGroups: [{
83
+ type: 'cameraPosition',
84
+ id: 'coronalSync',
85
+ source: true,
86
+ target: true
87
+ }, {
88
+ type: 'voi',
89
+ id: 'ctWLSync',
90
+ source: true,
91
+ target: true,
92
+ options: {
93
+ syncColormap: true
94
+ }
95
+ }]
96
+ },
97
+ displaySets: [{
98
+ id: 'ctDisplaySet'
99
+ }]
100
+ };
101
+ const ptAXIAL = {
102
+ viewportOptions: {
103
+ viewportId: 'ptAXIAL',
104
+ viewportType: 'volume',
105
+ background: [1, 1, 1],
106
+ orientation: 'axial',
107
+ toolGroupId: 'ptToolGroup',
108
+ initialImageOptions: {
109
+ // index: 5,
110
+ preset: 'first' // 'first', 'last', 'middle'
111
+ },
112
+ syncGroups: [{
113
+ type: 'cameraPosition',
114
+ id: 'axialSync',
115
+ source: true,
116
+ target: true
117
+ }, {
118
+ type: 'voi',
119
+ id: 'ptWLSync',
120
+ source: true,
121
+ target: true,
122
+ options: {
123
+ syncColormap: true
124
+ }
125
+ }, {
126
+ type: 'voi',
127
+ id: 'ptFusionWLSync',
128
+ source: true,
129
+ target: false,
130
+ options: {
131
+ syncInvertState: false
132
+ }
133
+ }]
134
+ },
135
+ displaySets: [{
136
+ options: {
137
+ voi: {
138
+ custom: 'getPTVOIRange'
139
+ },
140
+ voiInverted: true
141
+ },
142
+ id: 'ptDisplaySet'
143
+ }]
144
+ };
145
+ const ptSAGITTAL = {
146
+ viewportOptions: {
147
+ viewportId: 'ptSAGITTAL',
148
+ viewportType: 'volume',
149
+ orientation: 'sagittal',
150
+ background: [1, 1, 1],
151
+ toolGroupId: 'ptToolGroup',
152
+ syncGroups: [{
153
+ type: 'cameraPosition',
154
+ id: 'sagittalSync',
155
+ source: true,
156
+ target: true
157
+ }, {
158
+ type: 'voi',
159
+ id: 'ptWLSync',
160
+ source: true,
161
+ target: true,
162
+ options: {
163
+ syncColormap: true
164
+ }
165
+ }, {
166
+ type: 'voi',
167
+ id: 'ptFusionWLSync',
168
+ source: true,
169
+ target: false,
170
+ options: {
171
+ syncInvertState: false
172
+ }
173
+ }]
174
+ },
175
+ displaySets: [{
176
+ options: {
177
+ voi: {
178
+ custom: 'getPTVOIRange'
179
+ },
180
+ voiInverted: true
181
+ },
182
+ id: 'ptDisplaySet'
183
+ }]
184
+ };
185
+ const ptCORONAL = {
186
+ viewportOptions: {
187
+ viewportId: 'ptCORONAL',
188
+ viewportType: 'volume',
189
+ orientation: 'coronal',
190
+ background: [1, 1, 1],
191
+ toolGroupId: 'ptToolGroup',
192
+ syncGroups: [{
193
+ type: 'cameraPosition',
194
+ id: 'coronalSync',
195
+ source: true,
196
+ target: true
197
+ }, {
198
+ type: 'voi',
199
+ id: 'ptWLSync',
200
+ source: true,
201
+ target: true,
202
+ options: {
203
+ syncColormap: true
204
+ }
205
+ }, {
206
+ type: 'voi',
207
+ id: 'ptFusionWLSync',
208
+ source: true,
209
+ target: false,
210
+ options: {
211
+ syncInvertState: false
212
+ }
213
+ }]
214
+ },
215
+ displaySets: [{
216
+ options: {
217
+ voi: {
218
+ custom: 'getPTVOIRange'
219
+ },
220
+ voiInverted: true
221
+ },
222
+ id: 'ptDisplaySet'
223
+ }]
224
+ };
225
+ const fusionAXIAL = {
226
+ viewportOptions: {
227
+ viewportId: 'fusionAXIAL',
228
+ viewportType: 'volume',
229
+ orientation: 'axial',
230
+ toolGroupId: 'fusionToolGroup',
231
+ initialImageOptions: {
232
+ // index: 5,
233
+ preset: 'first' // 'first', 'last', 'middle'
234
+ },
235
+ syncGroups: [{
236
+ type: 'cameraPosition',
237
+ id: 'axialSync',
238
+ source: true,
239
+ target: true
240
+ }, {
241
+ type: 'voi',
242
+ id: 'ctWLSync',
243
+ source: false,
244
+ target: true
245
+ }, {
246
+ type: 'voi',
247
+ id: 'fusionWLSync',
248
+ source: true,
249
+ target: true,
250
+ options: {
251
+ syncColormap: true
252
+ }
253
+ }, {
254
+ type: 'voi',
255
+ id: 'ptFusionWLSync',
256
+ source: false,
257
+ target: true,
258
+ options: {
259
+ syncInvertState: false
260
+ }
261
+ }]
262
+ },
263
+ displaySets: [{
264
+ id: 'ctDisplaySet'
265
+ }, {
266
+ id: 'ptDisplaySet',
267
+ options: {
268
+ colormap: {
269
+ name: 'hsv',
270
+ opacity: [{
271
+ value: 0,
272
+ opacity: 0
273
+ }, {
274
+ value: 0.1,
275
+ opacity: 0.9
276
+ }, {
277
+ value: 1,
278
+ opacity: 0.95
279
+ }]
280
+ },
281
+ voi: {
282
+ custom: 'getPTVOIRange'
283
+ }
284
+ }
285
+ }]
286
+ };
287
+ const fusionSAGITTAL = {
288
+ viewportOptions: {
289
+ viewportId: 'fusionSAGITTAL',
290
+ viewportType: 'volume',
291
+ orientation: 'sagittal',
292
+ toolGroupId: 'fusionToolGroup',
293
+ // initialImageOptions: {
294
+ // index: 180,
295
+ // preset: 'middle', // 'first', 'last', 'middle'
296
+ // },
297
+ syncGroups: [{
298
+ type: 'cameraPosition',
299
+ id: 'sagittalSync',
300
+ source: true,
301
+ target: true
302
+ }, {
303
+ type: 'voi',
304
+ id: 'ctWLSync',
305
+ source: false,
306
+ target: true
307
+ }, {
308
+ type: 'voi',
309
+ id: 'fusionWLSync',
310
+ source: true,
311
+ target: true,
312
+ options: {
313
+ syncColormap: true
314
+ }
315
+ }, {
316
+ type: 'voi',
317
+ id: 'ptFusionWLSync',
318
+ source: false,
319
+ target: true,
320
+ options: {
321
+ syncInvertState: false
322
+ }
323
+ }]
324
+ },
325
+ displaySets: [{
326
+ id: 'ctDisplaySet'
327
+ }, {
328
+ id: 'ptDisplaySet',
329
+ options: {
330
+ colormap: {
331
+ name: 'hsv',
332
+ opacity: [{
333
+ value: 0,
334
+ opacity: 0
335
+ }, {
336
+ value: 0.1,
337
+ opacity: 0.9
338
+ }, {
339
+ value: 1,
340
+ opacity: 0.95
341
+ }]
342
+ },
343
+ voi: {
344
+ custom: 'getPTVOIRange'
345
+ }
346
+ }
347
+ }]
348
+ };
349
+ const fusionCORONAL = {
350
+ viewportOptions: {
351
+ viewportId: 'fusionCoronal',
352
+ viewportType: 'volume',
353
+ orientation: 'coronal',
354
+ toolGroupId: 'fusionToolGroup',
355
+ // initialImageOptions: {
356
+ // index: 180,
357
+ // preset: 'middle', // 'first', 'last', 'middle'
358
+ // },
359
+ syncGroups: [{
360
+ type: 'cameraPosition',
361
+ id: 'coronalSync',
362
+ source: true,
363
+ target: true
364
+ }, {
365
+ type: 'voi',
366
+ id: 'ctWLSync',
367
+ source: false,
368
+ target: true
369
+ }, {
370
+ type: 'voi',
371
+ id: 'fusionWLSync',
372
+ source: true,
373
+ target: true,
374
+ options: {
375
+ syncColormap: true
376
+ }
377
+ }, {
378
+ type: 'voi',
379
+ id: 'ptFusionWLSync',
380
+ source: false,
381
+ target: true,
382
+ options: {
383
+ syncInvertState: false
384
+ }
385
+ }]
386
+ },
387
+ displaySets: [{
388
+ id: 'ctDisplaySet'
389
+ }, {
390
+ id: 'ptDisplaySet',
391
+ options: {
392
+ colormap: {
393
+ name: 'hsv',
394
+ opacity: [{
395
+ value: 0,
396
+ opacity: 0
397
+ }, {
398
+ value: 0.1,
399
+ opacity: 0.9
400
+ }, {
401
+ value: 1,
402
+ opacity: 0.95
403
+ }]
404
+ },
405
+ voi: {
406
+ custom: 'getPTVOIRange'
407
+ }
408
+ }
409
+ }]
410
+ };
411
+ const mipSAGITTAL = {
412
+ viewportOptions: {
413
+ viewportId: 'mipSagittal',
414
+ viewportType: 'volume',
415
+ orientation: 'sagittal',
416
+ background: [1, 1, 1],
417
+ toolGroupId: 'mipToolGroup',
418
+ syncGroups: [{
419
+ type: 'voi',
420
+ id: 'ptWLSync',
421
+ source: true,
422
+ target: true,
423
+ options: {
424
+ syncColormap: true
425
+ }
426
+ }, {
427
+ type: 'voi',
428
+ id: 'ptFusionWLSync',
429
+ source: true,
430
+ target: false,
431
+ options: {
432
+ syncInvertState: false
433
+ }
434
+ }],
435
+ // Custom props can be used to set custom properties which extensions
436
+ // can react on.
437
+ customViewportProps: {
438
+ // We use viewportDisplay to filter the viewports which are displayed
439
+ // in mip and we set the scrollbar according to their rotation index
440
+ // in the cornerstone extension.
441
+ hideOverlays: true
442
+ }
443
+ },
444
+ displaySets: [{
445
+ options: {
446
+ blendMode: 'MIP',
447
+ slabThickness: 'fullVolume',
448
+ voi: {
449
+ custom: 'getPTVOIRange'
450
+ },
451
+ voiInverted: true
452
+ },
453
+ id: 'ptDisplaySet'
454
+ }]
455
+ };
456
+
457
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/getHangingProtocolModule.js
458
+
459
+
460
+ /**
461
+ * represents a 3x4 viewport layout configuration. The layout displays CT axial, sagittal, and coronal
462
+ * images in the first row, PT axial, sagittal, and coronal images in the second row, and fusion axial,
463
+ * sagittal, and coronal images in the third row. The fourth column is fully spanned by a MIP sagittal
464
+ * image, covering all three rows. It has synchronizers for windowLevel for all CT and PT images, and
465
+ * also camera synchronizer for each orientation
466
+ */
467
+ const stage1 = {
468
+ name: 'default',
469
+ viewportStructure: {
470
+ layoutType: 'grid',
471
+ properties: {
472
+ rows: 3,
473
+ columns: 4,
474
+ layoutOptions: [{
475
+ x: 0,
476
+ y: 0,
477
+ width: 1 / 4,
478
+ height: 1 / 3
479
+ }, {
480
+ x: 1 / 4,
481
+ y: 0,
482
+ width: 1 / 4,
483
+ height: 1 / 3
484
+ }, {
485
+ x: 2 / 4,
486
+ y: 0,
487
+ width: 1 / 4,
488
+ height: 1 / 3
489
+ }, {
490
+ x: 0,
491
+ y: 1 / 3,
492
+ width: 1 / 4,
493
+ height: 1 / 3
494
+ }, {
495
+ x: 1 / 4,
496
+ y: 1 / 3,
497
+ width: 1 / 4,
498
+ height: 1 / 3
499
+ }, {
500
+ x: 2 / 4,
501
+ y: 1 / 3,
502
+ width: 1 / 4,
503
+ height: 1 / 3
504
+ }, {
505
+ x: 0,
506
+ y: 2 / 3,
507
+ width: 1 / 4,
508
+ height: 1 / 3
509
+ }, {
510
+ x: 1 / 4,
511
+ y: 2 / 3,
512
+ width: 1 / 4,
513
+ height: 1 / 3
514
+ }, {
515
+ x: 2 / 4,
516
+ y: 2 / 3,
517
+ width: 1 / 4,
518
+ height: 1 / 3
519
+ }, {
520
+ x: 3 / 4,
521
+ y: 0,
522
+ width: 1 / 4,
523
+ height: 1
524
+ }]
525
+ }
526
+ },
527
+ viewports: [ctAXIAL, ctSAGITTAL, ctCORONAL, ptAXIAL, ptSAGITTAL, ptCORONAL, fusionAXIAL, fusionSAGITTAL, fusionCORONAL, mipSAGITTAL],
528
+ createdDate: '2021-02-23T18:32:42.850Z'
529
+ };
530
+
531
+ /**
532
+ * The layout displays CT axial image in the top-left viewport, fusion axial image
533
+ * in the top-right viewport, PT axial image in the bottom-left viewport, and MIP
534
+ * sagittal image in the bottom-right viewport. The layout follows a simple grid
535
+ * pattern with 2 rows and 2 columns. It includes synchronizers as well.
536
+ */
537
+ const stage2 = {
538
+ name: 'Fusion 2x2',
539
+ viewportStructure: {
540
+ layoutType: 'grid',
541
+ properties: {
542
+ rows: 2,
543
+ columns: 2
544
+ }
545
+ },
546
+ viewports: [ctAXIAL, fusionAXIAL, ptAXIAL, mipSAGITTAL]
547
+ };
548
+
549
+ /**
550
+ * The top row displays CT images in axial, sagittal, and coronal orientations from
551
+ * left to right, respectively. The bottom row displays PT images in axial, sagittal,
552
+ * and coronal orientations from left to right, respectively.
553
+ * The layout follows a simple grid pattern with 2 rows and 3 columns.
554
+ * It includes synchronizers as well.
555
+ */
556
+ const stage3 = {
557
+ name: '2x3-layout',
558
+ viewportStructure: {
559
+ layoutType: 'grid',
560
+ properties: {
561
+ rows: 2,
562
+ columns: 3
563
+ }
564
+ },
565
+ viewports: [ctAXIAL, ctSAGITTAL, ctCORONAL, ptAXIAL, ptSAGITTAL, ptCORONAL]
566
+ };
567
+
568
+ /**
569
+ * In this layout, the top row displays PT images in coronal, sagittal, and axial
570
+ * orientations from left to right, respectively, followed by a MIP sagittal image
571
+ * that spans both rows on the rightmost side. The bottom row displays fusion images
572
+ * in coronal, sagittal, and axial orientations from left to right, respectively.
573
+ * There is no viewport in the bottom row's rightmost position, as the MIP sagittal viewport
574
+ * from the top row spans the full height of both rows.
575
+ * It includes synchronizers as well.
576
+ */
577
+ const stage4 = {
578
+ name: '2x4-layout',
579
+ viewportStructure: {
580
+ layoutType: 'grid',
581
+ properties: {
582
+ rows: 2,
583
+ columns: 4,
584
+ layoutOptions: [{
585
+ x: 0,
586
+ y: 0,
587
+ width: 1 / 4,
588
+ height: 1 / 2
589
+ }, {
590
+ x: 1 / 4,
591
+ y: 0,
592
+ width: 1 / 4,
593
+ height: 1 / 2
594
+ }, {
595
+ x: 2 / 4,
596
+ y: 0,
597
+ width: 1 / 4,
598
+ height: 1 / 2
599
+ }, {
600
+ x: 3 / 4,
601
+ y: 0,
602
+ width: 1 / 4,
603
+ height: 1
604
+ }, {
605
+ x: 0,
606
+ y: 1 / 2,
607
+ width: 1 / 4,
608
+ height: 1 / 2
609
+ }, {
610
+ x: 1 / 4,
611
+ y: 1 / 2,
612
+ width: 1 / 4,
613
+ height: 1 / 2
614
+ }, {
615
+ x: 2 / 4,
616
+ y: 1 / 2,
617
+ width: 1 / 4,
618
+ height: 1 / 2
619
+ }]
620
+ }
621
+ },
622
+ viewports: [ptCORONAL, ptSAGITTAL, ptAXIAL, mipSAGITTAL, fusionCORONAL, fusionSAGITTAL, fusionAXIAL]
623
+ };
624
+ const ptCT = {
625
+ id: '@ohif/extension-tmtv.hangingProtocolModule.ptCT',
626
+ locked: true,
627
+ name: 'Default',
628
+ createdDate: '2021-02-23T19:22:08.894Z',
629
+ modifiedDate: '2022-10-04T19:22:08.894Z',
630
+ availableTo: {},
631
+ editableBy: {},
632
+ imageLoadStrategy: 'interleaveTopToBottom',
633
+ // "default" , "interleaveTopToBottom", "interleaveCenter"
634
+ protocolMatchingRules: [{
635
+ attribute: 'ModalitiesInStudy',
636
+ constraint: {
637
+ contains: ['CT', 'PT']
638
+ }
639
+ }, {
640
+ attribute: 'StudyDescription',
641
+ constraint: {
642
+ contains: 'PETCT'
643
+ }
644
+ }, {
645
+ attribute: 'StudyDescription',
646
+ constraint: {
647
+ contains: 'PET/CT'
648
+ }
649
+ }],
650
+ displaySetSelectors: {
651
+ ctDisplaySet: {
652
+ seriesMatchingRules: [{
653
+ attribute: 'Modality',
654
+ constraint: {
655
+ equals: {
656
+ value: 'CT'
657
+ }
658
+ },
659
+ required: true
660
+ }, {
661
+ attribute: 'isReconstructable',
662
+ constraint: {
663
+ equals: {
664
+ value: true
665
+ }
666
+ },
667
+ required: true
668
+ }, {
669
+ attribute: 'SeriesDescription',
670
+ constraint: {
671
+ contains: 'CT'
672
+ }
673
+ }, {
674
+ attribute: 'SeriesDescription',
675
+ constraint: {
676
+ contains: 'CT WB'
677
+ }
678
+ }]
679
+ },
680
+ ptDisplaySet: {
681
+ seriesMatchingRules: [{
682
+ attribute: 'Modality',
683
+ constraint: {
684
+ equals: 'PT'
685
+ },
686
+ required: true
687
+ }, {
688
+ attribute: 'isReconstructable',
689
+ constraint: {
690
+ equals: {
691
+ value: true
692
+ }
693
+ },
694
+ required: true
695
+ }, {
696
+ attribute: 'SeriesDescription',
697
+ constraint: {
698
+ contains: 'Corrected'
699
+ }
700
+ }, {
701
+ weight: 2,
702
+ attribute: 'SeriesDescription',
703
+ constraint: {
704
+ doesNotContain: {
705
+ value: 'Uncorrected'
706
+ }
707
+ }
708
+ }]
709
+ }
710
+ },
711
+ stages: [stage1, stage2, stage3, stage4],
712
+ numberOfPriorsReferenced: -1
713
+ };
714
+ function getHangingProtocolModule() {
715
+ return [{
716
+ name: ptCT.id,
717
+ protocol: ptCT
718
+ }];
719
+ }
720
+ /* harmony default export */ const src_getHangingProtocolModule = (getHangingProtocolModule);
721
+ // EXTERNAL MODULE: ../../../node_modules/react/index.js
722
+ var react = __webpack_require__(41766);
723
+ // EXTERNAL MODULE: ../../../node_modules/prop-types/index.js
724
+ var prop_types = __webpack_require__(11374);
725
+ var prop_types_default = /*#__PURE__*/__webpack_require__.n(prop_types);
726
+ // EXTERNAL MODULE: ../../ui/src/index.js + 519 modules
727
+ var src = __webpack_require__(3962);
728
+ // EXTERNAL MODULE: ../../core/src/index.ts + 68 modules
729
+ var core_src = __webpack_require__(85073);
730
+ // EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
731
+ var es = __webpack_require__(80619);
732
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelPetSUV.tsx
733
+
734
+
735
+
736
+
737
+
738
+ const DEFAULT_MEATADATA = {
739
+ PatientWeight: null,
740
+ PatientSex: null,
741
+ SeriesTime: null,
742
+ RadiopharmaceuticalInformationSequence: {
743
+ RadionuclideTotalDose: null,
744
+ RadionuclideHalfLife: null,
745
+ RadiopharmaceuticalStartTime: null
746
+ }
747
+ };
748
+
749
+ /*
750
+ * PETSUV panel enables the user to modify the patient related information, such as
751
+ * patient sex, patientWeight. This is allowed since
752
+ * sometimes these metadata are missing or wrong. By changing them
753
+ * @param param0
754
+ * @returns
755
+ */
756
+ function PanelPetSUV({
757
+ servicesManager,
758
+ commandsManager
759
+ }) {
760
+ const {
761
+ t
762
+ } = (0,es/* useTranslation */.Bd)('PanelSUV');
763
+ const {
764
+ displaySetService,
765
+ toolGroupService,
766
+ toolbarService,
767
+ hangingProtocolService
768
+ } = servicesManager.services;
769
+ const [metadata, setMetadata] = (0,react.useState)(DEFAULT_MEATADATA);
770
+ const [ptDisplaySet, setPtDisplaySet] = (0,react.useState)(null);
771
+ const handleMetadataChange = metadata => {
772
+ setMetadata(prevState => {
773
+ const newState = {
774
+ ...prevState
775
+ };
776
+ Object.keys(metadata).forEach(key => {
777
+ if (typeof metadata[key] === 'object') {
778
+ newState[key] = {
779
+ ...prevState[key],
780
+ ...metadata[key]
781
+ };
782
+ } else {
783
+ newState[key] = metadata[key];
784
+ }
785
+ });
786
+ return newState;
787
+ });
788
+ };
789
+ const getMatchingPTDisplaySet = viewportMatchDetails => {
790
+ const ptDisplaySet = commandsManager.runCommand('getMatchingPTDisplaySet', {
791
+ viewportMatchDetails
792
+ });
793
+ if (!ptDisplaySet) {
794
+ return;
795
+ }
796
+ const metadata = commandsManager.runCommand('getPTMetadata', {
797
+ ptDisplaySet
798
+ });
799
+ return {
800
+ ptDisplaySet,
801
+ metadata
802
+ };
803
+ };
804
+ (0,react.useEffect)(() => {
805
+ const displaySets = displaySetService.getActiveDisplaySets();
806
+ const {
807
+ viewportMatchDetails
808
+ } = hangingProtocolService.getMatchDetails();
809
+ if (!displaySets.length) {
810
+ return;
811
+ }
812
+ const displaySetInfo = getMatchingPTDisplaySet(viewportMatchDetails);
813
+ if (!displaySetInfo) {
814
+ return;
815
+ }
816
+ const {
817
+ ptDisplaySet,
818
+ metadata
819
+ } = displaySetInfo;
820
+ setPtDisplaySet(ptDisplaySet);
821
+ setMetadata(metadata);
822
+ }, []);
823
+
824
+ // get the patientMetadata from the StudyInstanceUIDs and update the state
825
+ (0,react.useEffect)(() => {
826
+ const {
827
+ unsubscribe
828
+ } = hangingProtocolService.subscribe(hangingProtocolService.EVENTS.PROTOCOL_CHANGED, ({
829
+ viewportMatchDetails
830
+ }) => {
831
+ const displaySetInfo = getMatchingPTDisplaySet(viewportMatchDetails);
832
+ if (!displaySetInfo) {
833
+ return;
834
+ }
835
+ const {
836
+ ptDisplaySet,
837
+ metadata
838
+ } = displaySetInfo;
839
+ setPtDisplaySet(ptDisplaySet);
840
+ setMetadata(metadata);
841
+ });
842
+ return () => {
843
+ unsubscribe();
844
+ };
845
+ }, []);
846
+ function updateMetadata() {
847
+ if (!ptDisplaySet) {
848
+ throw new Error('No ptDisplaySet found');
849
+ }
850
+
851
+ // metadata should be dcmjs naturalized
852
+ core_src.DicomMetadataStore.updateMetadataForSeries(ptDisplaySet.StudyInstanceUID, ptDisplaySet.SeriesInstanceUID, metadata);
853
+
854
+ // update the displaySets
855
+ displaySetService.setDisplaySetMetadataInvalidated(ptDisplaySet.displaySetInstanceUID);
856
+
857
+ // Crosshair position depends on the metadata values such as the positioning interaction
858
+ // between series, so when the metadata is updated, the crosshairs need to be reset.
859
+ setTimeout(() => {
860
+ commandsManager.runCommand('resetCrosshairs');
861
+ }, 0);
862
+ }
863
+ return /*#__PURE__*/react.createElement("div", {
864
+ className: "invisible-scrollbar overflow-y-auto overflow-x-hidden"
865
+ }, /*#__PURE__*/react.createElement("div", {
866
+ className: "flex flex-col"
867
+ }, /*#__PURE__*/react.createElement("div", {
868
+ className: "bg-primary-dark flex flex-col space-y-4 p-4"
869
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
870
+ label: t('Patient Sex'),
871
+ labelClassName: "text-white mb-2",
872
+ className: "mt-1",
873
+ value: metadata.PatientSex || '',
874
+ onChange: e => {
875
+ handleMetadataChange({
876
+ PatientSex: e.target.value
877
+ });
878
+ }
879
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
880
+ label: t('Patient Weight (kg)'),
881
+ labelClassName: "text-white mb-2",
882
+ className: "mt-1",
883
+ value: metadata.PatientWeight || '',
884
+ onChange: e => {
885
+ handleMetadataChange({
886
+ PatientWeight: e.target.value
887
+ });
888
+ }
889
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
890
+ label: t('Total Dose (bq)'),
891
+ labelClassName: "text-white mb-2",
892
+ className: "mt-1",
893
+ value: metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || '',
894
+ onChange: e => {
895
+ handleMetadataChange({
896
+ RadiopharmaceuticalInformationSequence: {
897
+ RadionuclideTotalDose: e.target.value
898
+ }
899
+ });
900
+ }
901
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
902
+ label: t('Half Life (s)'),
903
+ labelClassName: "text-white mb-2",
904
+ className: "mt-1",
905
+ value: metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || '',
906
+ onChange: e => {
907
+ handleMetadataChange({
908
+ RadiopharmaceuticalInformationSequence: {
909
+ RadionuclideHalfLife: e.target.value
910
+ }
911
+ });
912
+ }
913
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
914
+ label: t('Injection Time (s)'),
915
+ labelClassName: "text-white mb-2",
916
+ className: "mt-1",
917
+ value: metadata.RadiopharmaceuticalInformationSequence.RadiopharmaceuticalStartTime || '',
918
+ onChange: e => {
919
+ handleMetadataChange({
920
+ RadiopharmaceuticalInformationSequence: {
921
+ RadiopharmaceuticalStartTime: e.target.value
922
+ }
923
+ });
924
+ }
925
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
926
+ label: t('Acquisition Time (s)'),
927
+ labelClassName: "text-white mb-2",
928
+ className: "mt-1 mb-2",
929
+ value: metadata.SeriesTime || '',
930
+ onChange: () => {}
931
+ }), /*#__PURE__*/react.createElement(src/* Button */.$n, {
932
+ onClick: updateMetadata
933
+ }, "Reload Data"))));
934
+ }
935
+ PanelPetSUV.propTypes = {
936
+ servicesManager: prop_types_default().shape({
937
+ services: prop_types_default().shape({
938
+ measurementService: prop_types_default().shape({
939
+ getMeasurements: (prop_types_default()).func.isRequired,
940
+ subscribe: (prop_types_default()).func.isRequired,
941
+ EVENTS: (prop_types_default()).object.isRequired,
942
+ VALUE_TYPES: (prop_types_default()).object.isRequired
943
+ }).isRequired
944
+ }).isRequired
945
+ }).isRequired
946
+ };
947
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/segmentationEditHandler.tsx
948
+
949
+
950
+ function segmentationItemEditHandler({
951
+ id,
952
+ servicesManager
953
+ }) {
954
+ const {
955
+ segmentationService,
956
+ uiDialogService
957
+ } = servicesManager.services;
958
+ const segmentation = segmentationService.getSegmentation(id);
959
+ const onSubmitHandler = ({
960
+ action,
961
+ value
962
+ }) => {
963
+ switch (action.id) {
964
+ case 'save':
965
+ {
966
+ segmentationService.addOrUpdateSegmentation({
967
+ ...segmentation,
968
+ ...value
969
+ }, false,
970
+ // don't suppress event
971
+ true // it should update cornerstone
972
+ );
973
+ }
974
+ }
975
+ uiDialogService.dismiss({
976
+ id: 'enter-annotation'
977
+ });
978
+ };
979
+ uiDialogService.create({
980
+ id: 'enter-annotation',
981
+ centralize: true,
982
+ isDraggable: false,
983
+ showOverlay: true,
984
+ content: src/* Dialog */.lG,
985
+ contentProps: {
986
+ title: 'Enter your Segmentation',
987
+ noCloseButton: true,
988
+ value: {
989
+ label: segmentation.label || ''
990
+ },
991
+ body: ({
992
+ value,
993
+ setValue
994
+ }) => {
995
+ const onChangeHandler = event => {
996
+ event.persist();
997
+ setValue(value => ({
998
+ ...value,
999
+ label: event.target.value
1000
+ }));
1001
+ };
1002
+ const onKeyPressHandler = event => {
1003
+ if (event.key === 'Enter') {
1004
+ onSubmitHandler({
1005
+ value,
1006
+ action: {
1007
+ id: 'save'
1008
+ }
1009
+ });
1010
+ }
1011
+ };
1012
+ return /*#__PURE__*/react.createElement(src/* Input */.pd, {
1013
+ autoFocus: true,
1014
+ className: "border-primary-main bg-black",
1015
+ type: "text",
1016
+ containerClassName: "mr-2",
1017
+ value: value.label,
1018
+ onChange: onChangeHandler,
1019
+ onKeyPress: onKeyPressHandler
1020
+ });
1021
+ },
1022
+ actions: [{
1023
+ id: 'cancel',
1024
+ text: 'Cancel',
1025
+ type: src/* ButtonEnums.type */.Ny.NW.secondary
1026
+ }, {
1027
+ id: 'save',
1028
+ text: 'Save',
1029
+ type: src/* ButtonEnums.type */.Ny.NW.primary
1030
+ }],
1031
+ onSubmit: onSubmitHandler
1032
+ }
1033
+ });
1034
+ }
1035
+ /* harmony default export */ const segmentationEditHandler = (segmentationItemEditHandler);
1036
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx
1037
+
1038
+
1039
+
1040
+ function ExportReports({
1041
+ segmentations,
1042
+ tmtvValue,
1043
+ config,
1044
+ commandsManager
1045
+ }) {
1046
+ const {
1047
+ t
1048
+ } = (0,es/* useTranslation */.Bd)('PanelSUVExport');
1049
+ return /*#__PURE__*/react.createElement(react.Fragment, null, segmentations?.length ? /*#__PURE__*/react.createElement("div", {
1050
+ className: "mt-4 flex justify-center space-x-2"
1051
+ }, /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, {
1052
+ color: "black",
1053
+ size: "inherit"
1054
+ }, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1055
+ className: "px-2 py-2 text-base",
1056
+ disabled: tmtvValue === null,
1057
+ onClick: () => {
1058
+ commandsManager.runCommand('exportTMTVReportCSV', {
1059
+ segmentations,
1060
+ tmtv: tmtvValue,
1061
+ config
1062
+ });
1063
+ }
1064
+ }, t('Export CSV'))), /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, {
1065
+ color: "black",
1066
+ size: "inherit"
1067
+ }, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1068
+ className: "px-2 py-2 text-base",
1069
+ onClick: () => {
1070
+ commandsManager.runCommand('createTMTVRTReport');
1071
+ },
1072
+ disabled: tmtvValue === null
1073
+ }, t('Create RT Report')))) : null);
1074
+ }
1075
+ /* harmony default export */ const PanelROIThresholdSegmentation_ExportReports = (ExportReports);
1076
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx
1077
+
1078
+
1079
+
1080
+ const ROI_STAT = 'roi_stat';
1081
+ const RANGE = 'range';
1082
+ const options = [{
1083
+ value: ROI_STAT,
1084
+ label: 'Max',
1085
+ placeHolder: 'Max'
1086
+ }, {
1087
+ value: RANGE,
1088
+ label: 'Range',
1089
+ placeHolder: 'Range'
1090
+ }];
1091
+ function ROIThresholdConfiguration({
1092
+ config,
1093
+ dispatch,
1094
+ runCommand
1095
+ }) {
1096
+ const {
1097
+ t
1098
+ } = (0,es/* useTranslation */.Bd)('ROIThresholdConfiguration');
1099
+ return /*#__PURE__*/react.createElement("div", {
1100
+ className: "bg-primary-dark flex flex-col space-y-4 px-4 py-2"
1101
+ }, /*#__PURE__*/react.createElement("div", {
1102
+ className: "flex items-end space-x-2"
1103
+ }, /*#__PURE__*/react.createElement("div", {
1104
+ className: "flex w-1/2 flex-col"
1105
+ }, /*#__PURE__*/react.createElement(src/* Select */.l6, {
1106
+ label: t('Strategy'),
1107
+ closeMenuOnSelect: true,
1108
+ className: "border-primary-main mr-2 bg-black text-white ",
1109
+ options: options,
1110
+ placeholder: options.find(option => option.value === config.strategy).placeHolder,
1111
+ value: config.strategy,
1112
+ onChange: ({
1113
+ value
1114
+ }) => {
1115
+ dispatch({
1116
+ type: 'setStrategy',
1117
+ payload: {
1118
+ strategy: value
1119
+ }
1120
+ });
1121
+ }
1122
+ })), /*#__PURE__*/react.createElement("div", {
1123
+ className: "w-1/2"
1124
+ }, /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, null, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1125
+ size: "initial",
1126
+ className: "px-2 py-2 text-base text-white",
1127
+ color: "primaryLight",
1128
+ variant: "outlined",
1129
+ onClick: () => runCommand('setStartSliceForROIThresholdTool')
1130
+ }, t('Start')), /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1131
+ size: "initial",
1132
+ color: "primaryLight",
1133
+ variant: "outlined",
1134
+ className: "px-2 py-2 text-base text-white",
1135
+ onClick: () => runCommand('setEndSliceForROIThresholdTool')
1136
+ }, t('End'))))), config.strategy === ROI_STAT && /*#__PURE__*/react.createElement(src/* Input */.pd, {
1137
+ label: t('Percentage of Max SUV'),
1138
+ labelClassName: "text-white",
1139
+ className: "border-primary-main mt-2 bg-black",
1140
+ type: "text",
1141
+ containerClassName: "mr-2",
1142
+ value: config.weight,
1143
+ onChange: e => {
1144
+ dispatch({
1145
+ type: 'setWeight',
1146
+ payload: {
1147
+ weight: e.target.value
1148
+ }
1149
+ });
1150
+ }
1151
+ }), config.strategy !== ROI_STAT && /*#__PURE__*/react.createElement("div", {
1152
+ className: "mr-2 text-sm"
1153
+ }, /*#__PURE__*/react.createElement("table", null, /*#__PURE__*/react.createElement("tbody", null, /*#__PURE__*/react.createElement("tr", {
1154
+ className: "mt-2"
1155
+ }, /*#__PURE__*/react.createElement("td", {
1156
+ className: "pr-4 pt-2",
1157
+ colSpan: "3"
1158
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
1159
+ className: "text-white",
1160
+ text: "Lower & Upper Ranges"
1161
+ }))), /*#__PURE__*/react.createElement("tr", {
1162
+ className: "mt-2"
1163
+ }, /*#__PURE__*/react.createElement("td", {
1164
+ className: "pr-4 pt-2 text-center"
1165
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
1166
+ className: "text-white",
1167
+ text: "CT"
1168
+ })), /*#__PURE__*/react.createElement("td", null, /*#__PURE__*/react.createElement("div", {
1169
+ className: "flex justify-between"
1170
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
1171
+ label: t(''),
1172
+ labelClassName: "text-white",
1173
+ className: "border-primary-main mt-2 bg-black",
1174
+ type: "text",
1175
+ containerClassName: "mr-2",
1176
+ value: config.ctLower,
1177
+ onChange: e => {
1178
+ dispatch({
1179
+ type: 'setThreshold',
1180
+ payload: {
1181
+ ctLower: e.target.value
1182
+ }
1183
+ });
1184
+ }
1185
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
1186
+ label: t(''),
1187
+ labelClassName: "text-white",
1188
+ className: "border-primary-main mt-2 bg-black",
1189
+ type: "text",
1190
+ containerClassName: "mr-2",
1191
+ value: config.ctUpper,
1192
+ onChange: e => {
1193
+ dispatch({
1194
+ type: 'setThreshold',
1195
+ payload: {
1196
+ ctUpper: e.target.value
1197
+ }
1198
+ });
1199
+ }
1200
+ })))), /*#__PURE__*/react.createElement("tr", null, /*#__PURE__*/react.createElement("td", {
1201
+ className: "pr-4 pt-2 text-center"
1202
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
1203
+ className: "text-white",
1204
+ text: "PT"
1205
+ })), /*#__PURE__*/react.createElement("td", null, /*#__PURE__*/react.createElement("div", {
1206
+ className: "flex justify-between"
1207
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
1208
+ label: t(''),
1209
+ labelClassName: "text-white",
1210
+ className: "border-primary-main mt-2 bg-black",
1211
+ type: "text",
1212
+ containerClassName: "mr-2",
1213
+ value: config.ptLower,
1214
+ onChange: e => {
1215
+ dispatch({
1216
+ type: 'setThreshold',
1217
+ payload: {
1218
+ ptLower: e.target.value
1219
+ }
1220
+ });
1221
+ }
1222
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
1223
+ label: t(''),
1224
+ labelClassName: "text-white",
1225
+ className: "border-primary-main mt-2 bg-black",
1226
+ type: "text",
1227
+ containerClassName: "mr-2",
1228
+ value: config.ptUpper,
1229
+ onChange: e => {
1230
+ dispatch({
1231
+ type: 'setThreshold',
1232
+ payload: {
1233
+ ptUpper: e.target.value
1234
+ }
1235
+ });
1236
+ }
1237
+ }))))))));
1238
+ }
1239
+ /* harmony default export */ const PanelROIThresholdSegmentation_ROIThresholdConfiguration = (ROIThresholdConfiguration);
1240
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx
1241
+
1242
+
1243
+
1244
+
1245
+
1246
+
1247
+
1248
+ const LOWER_CT_THRESHOLD_DEFAULT = -1024;
1249
+ const UPPER_CT_THRESHOLD_DEFAULT = 1024;
1250
+ const LOWER_PT_THRESHOLD_DEFAULT = 2.5;
1251
+ const UPPER_PT_THRESHOLD_DEFAULT = 100;
1252
+ const WEIGHT_DEFAULT = 0.41; // a default weight for suv max often used in the literature
1253
+ const DEFAULT_STRATEGY = ROI_STAT;
1254
+ function reducer(state, action) {
1255
+ const {
1256
+ payload
1257
+ } = action;
1258
+ const {
1259
+ strategy,
1260
+ ctLower,
1261
+ ctUpper,
1262
+ ptLower,
1263
+ ptUpper,
1264
+ weight
1265
+ } = payload;
1266
+ switch (action.type) {
1267
+ case 'setStrategy':
1268
+ return {
1269
+ ...state,
1270
+ strategy
1271
+ };
1272
+ case 'setThreshold':
1273
+ return {
1274
+ ...state,
1275
+ ctLower: ctLower ? ctLower : state.ctLower,
1276
+ ctUpper: ctUpper ? ctUpper : state.ctUpper,
1277
+ ptLower: ptLower ? ptLower : state.ptLower,
1278
+ ptUpper: ptUpper ? ptUpper : state.ptUpper
1279
+ };
1280
+ case 'setWeight':
1281
+ return {
1282
+ ...state,
1283
+ weight
1284
+ };
1285
+ default:
1286
+ return state;
1287
+ }
1288
+ }
1289
+ function PanelRoiThresholdSegmentation({
1290
+ servicesManager,
1291
+ commandsManager
1292
+ }) {
1293
+ const {
1294
+ segmentationService
1295
+ } = servicesManager.services;
1296
+ const {
1297
+ t
1298
+ } = (0,es/* useTranslation */.Bd)('PanelSUV');
1299
+ const [showConfig, setShowConfig] = (0,react.useState)(false);
1300
+ const [labelmapLoading, setLabelmapLoading] = (0,react.useState)(false);
1301
+ const [selectedSegmentationId, setSelectedSegmentationId] = (0,react.useState)(null);
1302
+ const [segmentations, setSegmentations] = (0,react.useState)(() => segmentationService.getSegmentations());
1303
+ const [config, dispatch] = (0,react.useReducer)(reducer, {
1304
+ strategy: DEFAULT_STRATEGY,
1305
+ ctLower: LOWER_CT_THRESHOLD_DEFAULT,
1306
+ ctUpper: UPPER_CT_THRESHOLD_DEFAULT,
1307
+ ptLower: LOWER_PT_THRESHOLD_DEFAULT,
1308
+ ptUpper: UPPER_PT_THRESHOLD_DEFAULT,
1309
+ weight: WEIGHT_DEFAULT
1310
+ });
1311
+ const [tmtvValue, setTmtvValue] = (0,react.useState)(null);
1312
+ const runCommand = (0,react.useCallback)((commandName, commandOptions = {}) => {
1313
+ return commandsManager.runCommand(commandName, commandOptions);
1314
+ }, [commandsManager]);
1315
+ const handleTMTVCalculation = (0,react.useCallback)(() => {
1316
+ const tmtv = runCommand('calculateTMTV', {
1317
+ segmentations
1318
+ });
1319
+ if (tmtv !== undefined) {
1320
+ setTmtvValue(tmtv.toFixed(2));
1321
+ }
1322
+ }, [segmentations, runCommand]);
1323
+ const handleROIThresholding = (0,react.useCallback)(() => {
1324
+ const labelmap = runCommand('thresholdSegmentationByRectangleROITool', {
1325
+ segmentationId: selectedSegmentationId,
1326
+ config
1327
+ });
1328
+ const lesionStats = runCommand('getLesionStats', {
1329
+ labelmap
1330
+ });
1331
+ const suvPeak = runCommand('calculateSuvPeak', {
1332
+ labelmap
1333
+ });
1334
+ const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
1335
+
1336
+ // update segDetails with the suv peak for the active segmentation
1337
+ const segmentation = segmentationService.getSegmentation(selectedSegmentationId);
1338
+ const cachedStats = {
1339
+ lesionStats,
1340
+ suvPeak,
1341
+ lesionGlyoclysisStats
1342
+ };
1343
+ const notYetUpdatedAtSource = true;
1344
+ segmentationService.addOrUpdateSegmentation({
1345
+ ...segmentation,
1346
+ ...Object.assign(segmentation.cachedStats, cachedStats),
1347
+ displayText: [`SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`]
1348
+ }, notYetUpdatedAtSource);
1349
+ handleTMTVCalculation();
1350
+ }, [selectedSegmentationId, config]);
1351
+
1352
+ /**
1353
+ * Update UI based on segmentation changes (added, removed, updated)
1354
+ */
1355
+ (0,react.useEffect)(() => {
1356
+ // ~~ Subscription
1357
+ const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
1358
+ const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
1359
+ const subscriptions = [];
1360
+ [added, updated].forEach(evt => {
1361
+ const {
1362
+ unsubscribe
1363
+ } = segmentationService.subscribe(evt, () => {
1364
+ const segmentations = segmentationService.getSegmentations();
1365
+ setSegmentations(segmentations);
1366
+ });
1367
+ subscriptions.push(unsubscribe);
1368
+ });
1369
+ return () => {
1370
+ subscriptions.forEach(unsub => {
1371
+ unsub();
1372
+ });
1373
+ };
1374
+ }, []);
1375
+ (0,react.useEffect)(() => {
1376
+ const {
1377
+ unsubscribe
1378
+ } = segmentationService.subscribe(segmentationService.EVENTS.SEGMENTATION_REMOVED, () => {
1379
+ const segmentations = segmentationService.getSegmentations();
1380
+ setSegmentations(segmentations);
1381
+ if (segmentations.length > 0) {
1382
+ setSelectedSegmentationId(segmentations[0].id);
1383
+ handleTMTVCalculation();
1384
+ } else {
1385
+ setSelectedSegmentationId(null);
1386
+ setTmtvValue(null);
1387
+ }
1388
+ });
1389
+ return () => {
1390
+ unsubscribe();
1391
+ };
1392
+ }, []);
1393
+
1394
+ /**
1395
+ * Whenever the segmentations change, update the TMTV calculations
1396
+ */
1397
+ (0,react.useEffect)(() => {
1398
+ if (!selectedSegmentationId && segmentations.length > 0) {
1399
+ setSelectedSegmentationId(segmentations[0].id);
1400
+ }
1401
+ handleTMTVCalculation();
1402
+ }, [segmentations, selectedSegmentationId]);
1403
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1404
+ className: "flex flex-col"
1405
+ }, /*#__PURE__*/react.createElement("div", {
1406
+ className: "invisible-scrollbar overflow-y-auto overflow-x-hidden"
1407
+ }, /*#__PURE__*/react.createElement("div", {
1408
+ className: "mx-4 my-4 mb-4 flex space-x-4"
1409
+ }, /*#__PURE__*/react.createElement(src/* Button */.$n, {
1410
+ onClick: () => {
1411
+ setLabelmapLoading(true);
1412
+ setTimeout(() => {
1413
+ runCommand('createNewLabelmapFromPT').then(segmentationId => {
1414
+ setLabelmapLoading(false);
1415
+ setSelectedSegmentationId(segmentationId);
1416
+ });
1417
+ });
1418
+ }
1419
+ }, labelmapLoading ? 'loading ...' : 'New Label'), /*#__PURE__*/react.createElement(src/* Button */.$n, {
1420
+ onClick: handleROIThresholding
1421
+ }, "Run")), /*#__PURE__*/react.createElement("div", {
1422
+ className: "bg-secondary-dark border-secondary-light mb-2 flex h-8 cursor-pointer select-none items-center justify-around border-t outline-none first:border-0",
1423
+ onClick: () => {
1424
+ setShowConfig(!showConfig);
1425
+ }
1426
+ }, /*#__PURE__*/react.createElement("div", {
1427
+ className: "px-4 text-base text-white"
1428
+ }, t('ROI Threshold Configuration'))), showConfig && /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation_ROIThresholdConfiguration, {
1429
+ config: config,
1430
+ dispatch: dispatch,
1431
+ runCommand: runCommand
1432
+ }), /*#__PURE__*/react.createElement("div", {
1433
+ className: "mt-4"
1434
+ }, segmentations?.length ? /*#__PURE__*/react.createElement(src/* SegmentationTable */.R4, {
1435
+ title: t('Segmentations'),
1436
+ segmentations: segmentations,
1437
+ activeSegmentationId: selectedSegmentationId,
1438
+ onClick: id => {
1439
+ runCommand('setSegmentationActiveForToolGroups', {
1440
+ segmentationId: id
1441
+ });
1442
+ setSelectedSegmentationId(id);
1443
+ },
1444
+ onToggleVisibility: id => {
1445
+ segmentationService.toggleSegmentationVisibility(id);
1446
+ },
1447
+ onToggleVisibilityAll: ids => {
1448
+ ids.map(id => {
1449
+ segmentationService.toggleSegmentationVisibility(id);
1450
+ });
1451
+ },
1452
+ onDelete: id => {
1453
+ segmentationService.remove(id);
1454
+ },
1455
+ onEdit: id => {
1456
+ segmentationEditHandler({
1457
+ id,
1458
+ servicesManager
1459
+ });
1460
+ }
1461
+ }) : null), tmtvValue !== null ? /*#__PURE__*/react.createElement("div", {
1462
+ className: "bg-secondary-dark mt-4 flex items-baseline justify-between px-2 py-1"
1463
+ }, /*#__PURE__*/react.createElement("span", {
1464
+ className: "text-base font-bold uppercase tracking-widest text-white"
1465
+ }, 'TMTV:'), /*#__PURE__*/react.createElement("div", {
1466
+ className: "text-white"
1467
+ }, `${tmtvValue} mL`)) : null, /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation_ExportReports, {
1468
+ segmentations: segmentations,
1469
+ tmtvValue: tmtvValue,
1470
+ config: config,
1471
+ commandsManager: commandsManager
1472
+ }))), /*#__PURE__*/react.createElement("div", {
1473
+ className: "mt-auto mb-4 flex cursor-pointer items-center justify-center text-blue-400 opacity-50 hover:opacity-80",
1474
+ onClick: () => {
1475
+ // navigate to a url in a new tab
1476
+ window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank');
1477
+ }
1478
+ }, /*#__PURE__*/react.createElement(src/* Icon */.In, {
1479
+ width: "15px",
1480
+ height: "15px",
1481
+ name: 'info',
1482
+ className: 'text-primary-active ml-4 mr-3'
1483
+ }), /*#__PURE__*/react.createElement("span", null, 'User Guide')));
1484
+ }
1485
+ PanelRoiThresholdSegmentation.propTypes = {
1486
+ commandsManager: prop_types_default().shape({
1487
+ runCommand: (prop_types_default()).func.isRequired
1488
+ }),
1489
+ servicesManager: prop_types_default().shape({
1490
+ services: prop_types_default().shape({
1491
+ segmentationService: prop_types_default().shape({
1492
+ getSegmentation: (prop_types_default()).func.isRequired,
1493
+ getSegmentations: (prop_types_default()).func.isRequired,
1494
+ toggleSegmentationVisibility: (prop_types_default()).func.isRequired,
1495
+ subscribe: (prop_types_default()).func.isRequired,
1496
+ EVENTS: (prop_types_default()).object.isRequired
1497
+ }).isRequired
1498
+ }).isRequired
1499
+ }).isRequired
1500
+ };
1501
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/index.ts
1502
+
1503
+ /* harmony default export */ const PanelROIThresholdSegmentation = (PanelRoiThresholdSegmentation);
1504
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/index.tsx
1505
+
1506
+
1507
+
1508
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/getPanelModule.tsx
1509
+
1510
+
1511
+
1512
+ // TODO:
1513
+ // - No loading UI exists yet
1514
+ // - cancel promises when component is destroyed
1515
+ // - show errors in UI for thumbnails if promise fails
1516
+
1517
+ function getPanelModule({
1518
+ commandsManager,
1519
+ extensionManager,
1520
+ servicesManager
1521
+ }) {
1522
+ const wrappedPanelPetSuv = () => {
1523
+ return /*#__PURE__*/react.createElement(PanelPetSUV, {
1524
+ commandsManager: commandsManager,
1525
+ servicesManager: servicesManager,
1526
+ extensionManager: extensionManager
1527
+ });
1528
+ };
1529
+ const wrappedROIThresholdSeg = () => {
1530
+ return /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation, {
1531
+ commandsManager: commandsManager,
1532
+ servicesManager: servicesManager,
1533
+ extensionManager: extensionManager
1534
+ });
1535
+ };
1536
+ return [{
1537
+ name: 'petSUV',
1538
+ iconName: 'tab-patient-info',
1539
+ iconLabel: 'PET SUV',
1540
+ label: 'PET SUV',
1541
+ component: wrappedPanelPetSuv
1542
+ }, {
1543
+ name: 'ROIThresholdSeg',
1544
+ iconName: 'tab-roi-threshold',
1545
+ iconLabel: 'ROI Threshold',
1546
+ label: 'ROI Threshold',
1547
+ component: wrappedROIThresholdSeg
1548
+ }];
1549
+ }
1550
+ /* harmony default export */ const src_getPanelModule = (getPanelModule);
1551
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 16 modules
1552
+ var esm = __webpack_require__(20767);
1553
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/constants/supportedTools.js
1554
+ /* harmony default export */ const supportedTools = (['RectangleROIStartEndThreshold']);
1555
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 383 modules
1556
+ var dist_esm = __webpack_require__(50719);
1557
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js
1558
+
1559
+ function getSOPInstanceAttributes(imageId) {
1560
+ if (imageId) {
1561
+ return _getUIDFromImageID(imageId);
1562
+ }
1563
+ }
1564
+ function _getUIDFromImageID(imageId) {
1565
+ const instance = dist_esm.metaData.get('instance', imageId);
1566
+ return {
1567
+ SOPInstanceUID: instance.SOPInstanceUID,
1568
+ SeriesInstanceUID: instance.SeriesInstanceUID,
1569
+ StudyInstanceUID: instance.StudyInstanceUID,
1570
+ frameNumber: instance.frameNumber || 1
1571
+ };
1572
+ }
1573
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js
1574
+
1575
+
1576
+ const RectangleROIStartEndThreshold = {
1577
+ toAnnotation: (measurement, definition) => {},
1578
+ /**
1579
+ * Maps cornerstone annotation event data to measurement service format.
1580
+ *
1581
+ * @param {Object} cornerstone Cornerstone event data
1582
+ * @return {Measurement} Measurement instance
1583
+ */
1584
+ toMeasurement: (csToolsEventDetail, displaySetService, cornerstoneViewportService) => {
1585
+ const {
1586
+ annotation,
1587
+ viewportId
1588
+ } = csToolsEventDetail;
1589
+ const {
1590
+ metadata,
1591
+ data,
1592
+ annotationUID
1593
+ } = annotation;
1594
+ if (!metadata || !data) {
1595
+ console.warn('Length tool: Missing metadata or data');
1596
+ return null;
1597
+ }
1598
+ const {
1599
+ toolName,
1600
+ referencedImageId,
1601
+ FrameOfReferenceUID
1602
+ } = metadata;
1603
+ const validToolType = supportedTools.includes(toolName);
1604
+ if (!validToolType) {
1605
+ throw new Error('Tool not supported');
1606
+ }
1607
+ const {
1608
+ SOPInstanceUID,
1609
+ SeriesInstanceUID,
1610
+ StudyInstanceUID
1611
+ } = getSOPInstanceAttributes(referencedImageId, cornerstoneViewportService, viewportId);
1612
+ let displaySet;
1613
+ if (SOPInstanceUID) {
1614
+ displaySet = displaySetService.getDisplaySetForSOPInstanceUID(SOPInstanceUID, SeriesInstanceUID);
1615
+ } else {
1616
+ displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
1617
+ }
1618
+ const {
1619
+ cachedStats
1620
+ } = data;
1621
+ return {
1622
+ uid: annotationUID,
1623
+ SOPInstanceUID,
1624
+ FrameOfReferenceUID,
1625
+ // points,
1626
+ metadata,
1627
+ referenceSeriesUID: SeriesInstanceUID,
1628
+ referenceStudyUID: StudyInstanceUID,
1629
+ toolName: metadata.toolName,
1630
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
1631
+ label: metadata.label,
1632
+ // displayText: displayText,
1633
+ data: data.cachedStats,
1634
+ type: 'RectangleROIStartEndThreshold'
1635
+ // getReport,
1636
+ };
1637
+ }
1638
+ };
1639
+ /* harmony default export */ const measurementServiceMappings_RectangleROIStartEndThreshold = (RectangleROIStartEndThreshold);
1640
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js
1641
+
1642
+ const measurementServiceMappingsFactory = (measurementService, displaySetService, cornerstoneViewportService) => {
1643
+ return {
1644
+ RectangleROIStartEndThreshold: {
1645
+ toAnnotation: measurementServiceMappings_RectangleROIStartEndThreshold.toAnnotation,
1646
+ toMeasurement: csToolsAnnotation => measurementServiceMappings_RectangleROIStartEndThreshold.toMeasurement(csToolsAnnotation, displaySetService, cornerstoneViewportService),
1647
+ matchingCriteria: [{
1648
+ valueType: measurementService.VALUE_TYPES.ROI_THRESHOLD_MANUAL
1649
+ }]
1650
+ }
1651
+ };
1652
+ };
1653
+ /* harmony default export */ const measurementServiceMappings_measurementServiceMappingsFactory = (measurementServiceMappingsFactory);
1654
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/init.js
1655
+
1656
+
1657
+ const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
1658
+ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
1659
+ /**
1660
+ *
1661
+ * @param {Object} servicesManager
1662
+ * @param {Object} configuration
1663
+ * @param {Object|Array} configuration.csToolsConfig
1664
+ */
1665
+ function init({
1666
+ servicesManager
1667
+ }) {
1668
+ const {
1669
+ measurementService,
1670
+ displaySetService,
1671
+ cornerstoneViewportService
1672
+ } = servicesManager.services;
1673
+ (0,esm.addTool)(esm.RectangleROIStartEndThresholdTool);
1674
+ const {
1675
+ RectangleROIStartEndThreshold
1676
+ } = measurementServiceMappings_measurementServiceMappingsFactory(measurementService, displaySetService, cornerstoneViewportService);
1677
+ const csTools3DVer1MeasurementSource = measurementService.getSource(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
1678
+ measurementService.addMapping(csTools3DVer1MeasurementSource, 'RectangleROIStartEndThreshold', RectangleROIStartEndThreshold.matchingCriteria, RectangleROIStartEndThreshold.toAnnotation, RectangleROIStartEndThreshold.toMeasurement);
1679
+ }
1680
+ // EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 10 modules
1681
+ var gl_matrix_esm = __webpack_require__(83636);
1682
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/getThresholdValue.ts
1683
+
1684
+ function getRoiStats(referencedVolume, annotations) {
1685
+ // roiStats
1686
+ const {
1687
+ imageData
1688
+ } = referencedVolume;
1689
+ const values = imageData.getPointData().getScalars().getData();
1690
+
1691
+ // Todo: add support for other strategies
1692
+ const {
1693
+ fn,
1694
+ baseValue
1695
+ } = _getStrategyFn('max');
1696
+ let value = baseValue;
1697
+ const boundsIJK = esm.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations(annotations, referencedVolume);
1698
+ const [[iMin, iMax], [jMin, jMax], [kMin, kMax]] = boundsIJK;
1699
+ for (let i = iMin; i <= iMax; i++) {
1700
+ for (let j = jMin; j <= jMax; j++) {
1701
+ for (let k = kMin; k <= kMax; k++) {
1702
+ const offset = imageData.computeOffsetIndex([i, j, k]);
1703
+ value = fn(values[offset], value);
1704
+ }
1705
+ }
1706
+ }
1707
+ return value;
1708
+ }
1709
+ function getThresholdValues(annotationUIDs, referencedVolumes, config) {
1710
+ if (config.strategy === 'range') {
1711
+ return {
1712
+ ptLower: Number(config.ptLower),
1713
+ ptUpper: Number(config.ptUpper),
1714
+ ctLower: Number(config.ctLower),
1715
+ ctUpper: Number(config.ctUpper)
1716
+ };
1717
+ }
1718
+ const {
1719
+ weight
1720
+ } = config;
1721
+ const annotations = annotationUIDs.map(annotationUID => esm.annotation.state.getAnnotation(annotationUID));
1722
+ const ptValue = getRoiStats(referencedVolumes[0], annotations);
1723
+ return {
1724
+ ctLower: -Infinity,
1725
+ ctUpper: +Infinity,
1726
+ ptLower: weight * ptValue,
1727
+ ptUpper: +Infinity
1728
+ };
1729
+ }
1730
+ function _getStrategyFn(statistic) {
1731
+ const baseValue = -Infinity;
1732
+ const fn = (number, maxValue) => {
1733
+ if (number > maxValue) {
1734
+ maxValue = number;
1735
+ }
1736
+ return maxValue;
1737
+ };
1738
+ return {
1739
+ fn,
1740
+ baseValue
1741
+ };
1742
+ }
1743
+ /* harmony default export */ const getThresholdValue = (getThresholdValues);
1744
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/calculateSUVPeak.ts
1745
+
1746
+
1747
+ /**
1748
+ * This method calculates the SUV peak on a segmented ROI from a reference PET
1749
+ * volume. If a rectangle annotation is provided, the peak is calculated within that
1750
+ * rectangle. Otherwise, the calculation is performed on the entire volume which
1751
+ * will be slower but same result.
1752
+ * @param viewport Viewport to use for the calculation
1753
+ * @param labelmap Labelmap from which the mask is taken
1754
+ * @param referenceVolume PET volume to use for SUV calculation
1755
+ * @param toolData [Optional] list of toolData to use for SUV calculation
1756
+ * @param segmentIndex The index of the segment to use for masking
1757
+ * @returns
1758
+ */
1759
+ function calculateSuvPeak(labelmap, referenceVolume, annotations, segmentIndex = 1) {
1760
+ if (referenceVolume.metadata.Modality !== 'PT') {
1761
+ return;
1762
+ }
1763
+ if (labelmap.scalarData.length !== referenceVolume.scalarData.length) {
1764
+ throw new Error('labelmap and referenceVolume must have the same number of pixels');
1765
+ }
1766
+ const {
1767
+ scalarData: labelmapData,
1768
+ dimensions,
1769
+ imageData: labelmapImageData
1770
+ } = labelmap;
1771
+ const {
1772
+ scalarData: referenceVolumeData,
1773
+ imageData: referenceVolumeImageData
1774
+ } = referenceVolume;
1775
+ let boundsIJK;
1776
+ // Todo: using the first annotation for now
1777
+ if (annotations && annotations[0].data?.cachedStats) {
1778
+ const {
1779
+ projectionPoints
1780
+ } = annotations[0].data.cachedStats;
1781
+ const pointsToUse = [].concat(...projectionPoints); // cannot use flat() because of typescript compiler right now
1782
+
1783
+ const rectangleCornersIJK = pointsToUse.map(world => {
1784
+ const ijk = gl_matrix_esm/* vec3.fromValues */.eR.fromValues(0, 0, 0);
1785
+ referenceVolumeImageData.worldToIndex(world, ijk);
1786
+ return ijk;
1787
+ });
1788
+ boundsIJK = esm.utilities.boundingBox.getBoundingBoxAroundShape(rectangleCornersIJK, dimensions);
1789
+ }
1790
+ let max = 0;
1791
+ let maxIJK = [0, 0, 0];
1792
+ let maxLPS = [0, 0, 0];
1793
+ const callback = ({
1794
+ pointIJK,
1795
+ pointLPS
1796
+ }) => {
1797
+ const offset = referenceVolumeImageData.computeOffsetIndex(pointIJK);
1798
+ const value = labelmapData[offset];
1799
+ if (value !== segmentIndex) {
1800
+ return;
1801
+ }
1802
+ const referenceValue = referenceVolumeData[offset];
1803
+ if (referenceValue > max) {
1804
+ max = referenceValue;
1805
+ maxIJK = pointIJK;
1806
+ maxLPS = pointLPS;
1807
+ }
1808
+ };
1809
+ esm.utilities.pointInShapeCallback(labelmapImageData, () => true, callback, boundsIJK);
1810
+ const direction = labelmapImageData.getDirection().slice(0, 3);
1811
+
1812
+ /**
1813
+ * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere)
1814
+ * V = (4/3)πr3
1815
+ */
1816
+ const radius = Math.pow(1 / (4 / 3 * Math.PI), 1 / 3) * 10;
1817
+ const diameter = radius * 2;
1818
+ const secondaryCircleWorld = gl_matrix_esm/* vec3.create */.eR.create();
1819
+ const bottomWorld = gl_matrix_esm/* vec3.create */.eR.create();
1820
+ const topWorld = gl_matrix_esm/* vec3.create */.eR.create();
1821
+ referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld);
1822
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2);
1823
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2);
1824
+ const suvPeakCirclePoints = [bottomWorld, topWorld];
1825
+
1826
+ /**
1827
+ * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous
1828
+ * sphere
1829
+ */
1830
+ let count = 0;
1831
+ let acc = 0;
1832
+ const suvPeakMeanCallback = ({
1833
+ value
1834
+ }) => {
1835
+ acc += value;
1836
+ count += 1;
1837
+ };
1838
+ esm.utilities.pointInSurroundingSphereCallback(referenceVolumeImageData, suvPeakCirclePoints, suvPeakMeanCallback);
1839
+ const mean = acc / count;
1840
+ return {
1841
+ max,
1842
+ maxIJK,
1843
+ maxLPS,
1844
+ mean
1845
+ };
1846
+ }
1847
+ /* harmony default export */ const calculateSUVPeak = (calculateSuvPeak);
1848
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/calculateTMTV.ts
1849
+
1850
+
1851
+ /**
1852
+ * Given a list of labelmaps (with the possibility of overlapping regions),
1853
+ * and a referenceVolume, it calculates the total metabolic tumor volume (TMTV)
1854
+ * by flattening and rasterizing each segment into a single labelmap and summing
1855
+ * the total number of volume voxels. It should be noted that for this calculation
1856
+ * we do not double count voxels that are part of multiple labelmaps.
1857
+ * @param {} labelmaps
1858
+ * @param {number} segmentIndex
1859
+ * @returns {number} TMTV in ml
1860
+ */
1861
+ function calculateTMTV(labelmaps, segmentIndex = 1) {
1862
+ const volumeId = 'mergedLabelmap';
1863
+ const mergedLabelmap = esm.utilities.segmentation.createMergedLabelmapForIndex(labelmaps, segmentIndex, volumeId);
1864
+ const {
1865
+ imageData,
1866
+ spacing
1867
+ } = mergedLabelmap;
1868
+ const values = imageData.getPointData().getScalars().getData();
1869
+
1870
+ // count non-zero values inside the outputData, this would
1871
+ // consider the overlapping regions to be only counted once
1872
+ const numVoxels = values.reduce((acc, curr) => {
1873
+ if (curr > 0) {
1874
+ return acc + 1;
1875
+ }
1876
+ return acc;
1877
+ }, 0);
1878
+ return 1e-3 * numVoxels * spacing[0] * spacing[1] * spacing[2];
1879
+ }
1880
+ /* harmony default export */ const utils_calculateTMTV = (calculateTMTV);
1881
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/createAndDownloadTMTVReport.js
1882
+ function createAndDownloadTMTVReport(segReport, additionalReportRows) {
1883
+ const firstReport = segReport[Object.keys(segReport)[0]];
1884
+ const columns = Object.keys(firstReport);
1885
+ const csv = [columns.join(',')];
1886
+ Object.values(segReport).forEach(segmentation => {
1887
+ const row = [];
1888
+ columns.forEach(column => {
1889
+ // if it is array then we need to replace , with space to avoid csv parsing error
1890
+ row.push(Array.isArray(segmentation[column]) ? segmentation[column].join(' ') : segmentation[column]);
1891
+ });
1892
+ csv.push(row.join(','));
1893
+ });
1894
+ csv.push('');
1895
+ csv.push('');
1896
+ csv.push('');
1897
+ csv.push(`Patient ID,${firstReport.PatientID}`);
1898
+ csv.push(`Study Date,${firstReport.StudyDate}`);
1899
+ csv.push('');
1900
+ additionalReportRows.forEach(({
1901
+ key,
1902
+ value: values
1903
+ }) => {
1904
+ const temp = [];
1905
+ temp.push(`${key}`);
1906
+ Object.keys(values).forEach(k => {
1907
+ temp.push(`${k}`);
1908
+ temp.push(`${values[k]}`);
1909
+ });
1910
+ csv.push(temp.join(','));
1911
+ });
1912
+ const blob = new Blob([csv.join('\n')], {
1913
+ type: 'text/csv;charset=utf-8'
1914
+ });
1915
+ const url = URL.createObjectURL(blob);
1916
+ const a = document.createElement('a');
1917
+ a.href = url;
1918
+ a.download = `${firstReport.PatientID}_tmtv.csv`;
1919
+ a.click();
1920
+ }
1921
+ // EXTERNAL MODULE: ../../../node_modules/dcmjs/build/dcmjs.es.js
1922
+ var dcmjs_es = __webpack_require__(31426);
1923
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
1924
+ var adapters_es = __webpack_require__(83342);
1925
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js
1926
+
1927
+
1928
+
1929
+ const {
1930
+ datasetToBlob
1931
+ } = dcmjs_es/* default.data */.Ay.data;
1932
+ const metadataProvider = core_src.classes.MetadataProvider;
1933
+ function dicomRTAnnotationExport(annotations) {
1934
+ const dataset = adapters_es/* adaptersRT */.f_.Cornerstone3D.RTSS.generateRTSSFromAnnotations(annotations, metadataProvider, core_src.DicomMetadataStore);
1935
+ const reportBlob = datasetToBlob(dataset);
1936
+
1937
+ //Create a URL for the binary.
1938
+ var objectUrl = URL.createObjectURL(reportBlob);
1939
+ window.location.assign(objectUrl);
1940
+ }
1941
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/index.js
1942
+
1943
+ /* harmony default export */ const RTStructureSet = (dicomRTAnnotationExport);
1944
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/commandsModule.js
1945
+
1946
+
1947
+
1948
+
1949
+
1950
+
1951
+
1952
+
1953
+
1954
+
1955
+ const commandsModule_metadataProvider = core_src.classes.MetadataProvider;
1956
+ const RECTANGLE_ROI_THRESHOLD_MANUAL = 'RectangleROIStartEndThreshold';
1957
+ const LABELMAP = esm.Enums.SegmentationRepresentations.Labelmap;
1958
+ const commandsModule = ({
1959
+ servicesManager,
1960
+ commandsManager,
1961
+ extensionManager
1962
+ }) => {
1963
+ const {
1964
+ viewportGridService,
1965
+ uiNotificationService,
1966
+ displaySetService,
1967
+ hangingProtocolService,
1968
+ toolGroupService,
1969
+ cornerstoneViewportService,
1970
+ segmentationService
1971
+ } = servicesManager.services;
1972
+ const utilityModule = extensionManager.getModuleEntry('@ohif/extension-cornerstone.utilityModule.common');
1973
+ const {
1974
+ getEnabledElement
1975
+ } = utilityModule.exports;
1976
+ function _getActiveViewportsEnabledElement() {
1977
+ const {
1978
+ activeViewportId
1979
+ } = viewportGridService.getState();
1980
+ const {
1981
+ element
1982
+ } = getEnabledElement(activeViewportId) || {};
1983
+ const enabledElement = dist_esm.getEnabledElement(element);
1984
+ return enabledElement;
1985
+ }
1986
+ function _getMatchedViewportsToolGroupIds() {
1987
+ const {
1988
+ viewportMatchDetails
1989
+ } = hangingProtocolService.getMatchDetails();
1990
+ const toolGroupIds = [];
1991
+ viewportMatchDetails.forEach(viewport => {
1992
+ const {
1993
+ viewportOptions
1994
+ } = viewport;
1995
+ const {
1996
+ toolGroupId
1997
+ } = viewportOptions;
1998
+ if (toolGroupIds.indexOf(toolGroupId) === -1) {
1999
+ toolGroupIds.push(toolGroupId);
2000
+ }
2001
+ });
2002
+ return toolGroupIds;
2003
+ }
2004
+ const actions = {
2005
+ getMatchingPTDisplaySet: ({
2006
+ viewportMatchDetails
2007
+ }) => {
2008
+ // Todo: this is assuming that the hanging protocol has successfully matched
2009
+ // the correct PT. For future, we should have a way to filter out the PTs
2010
+ // that are in the viewer layout (but then we have the problem of the attenuation
2011
+ // corrected PT vs the non-attenuation correct PT)
2012
+
2013
+ let ptDisplaySet = null;
2014
+ for (const [viewportId, viewportDetails] of viewportMatchDetails) {
2015
+ const {
2016
+ displaySetsInfo
2017
+ } = viewportDetails;
2018
+ const displaySets = displaySetsInfo.map(({
2019
+ displaySetInstanceUID
2020
+ }) => displaySetService.getDisplaySetByUID(displaySetInstanceUID));
2021
+ if (!displaySets || displaySets.length === 0) {
2022
+ continue;
2023
+ }
2024
+ ptDisplaySet = displaySets.find(displaySet => displaySet.Modality === 'PT');
2025
+ if (ptDisplaySet) {
2026
+ break;
2027
+ }
2028
+ }
2029
+ return ptDisplaySet;
2030
+ },
2031
+ getPTMetadata: ({
2032
+ ptDisplaySet
2033
+ }) => {
2034
+ const dataSource = extensionManager.getDataSources()[0];
2035
+ const imageIds = dataSource.getImageIdsForDisplaySet(ptDisplaySet);
2036
+ const firstImageId = imageIds[0];
2037
+ const instance = commandsModule_metadataProvider.get('instance', firstImageId);
2038
+ if (instance.Modality !== 'PT') {
2039
+ return;
2040
+ }
2041
+ const metadata = {
2042
+ SeriesTime: instance.SeriesTime,
2043
+ Modality: instance.Modality,
2044
+ PatientSex: instance.PatientSex,
2045
+ PatientWeight: instance.PatientWeight,
2046
+ RadiopharmaceuticalInformationSequence: {
2047
+ RadionuclideTotalDose: instance.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose,
2048
+ RadionuclideHalfLife: instance.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife,
2049
+ RadiopharmaceuticalStartTime: instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime,
2050
+ RadiopharmaceuticalStartDateTime: instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime
2051
+ }
2052
+ };
2053
+ return metadata;
2054
+ },
2055
+ createNewLabelmapFromPT: async () => {
2056
+ // Create a segmentation of the same resolution as the source data
2057
+ // using volumeLoader.createAndCacheDerivedVolume.
2058
+ const {
2059
+ viewportMatchDetails
2060
+ } = hangingProtocolService.getMatchDetails();
2061
+ const ptDisplaySet = actions.getMatchingPTDisplaySet({
2062
+ viewportMatchDetails
2063
+ });
2064
+ if (!ptDisplaySet) {
2065
+ uiNotificationService.error('No matching PT display set found');
2066
+ return;
2067
+ }
2068
+ const segmentationId = await segmentationService.createSegmentationForDisplaySet(ptDisplaySet.displaySetInstanceUID);
2069
+
2070
+ // Add Segmentation to all toolGroupIds in the viewer
2071
+ const toolGroupIds = _getMatchedViewportsToolGroupIds();
2072
+ const representationType = LABELMAP;
2073
+ for (const toolGroupId of toolGroupIds) {
2074
+ const hydrateSegmentation = true;
2075
+ await segmentationService.addSegmentationRepresentationToToolGroup(toolGroupId, segmentationId, hydrateSegmentation, representationType);
2076
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
2077
+ }
2078
+ return segmentationId;
2079
+ },
2080
+ setSegmentationActiveForToolGroups: ({
2081
+ segmentationId
2082
+ }) => {
2083
+ const toolGroupIds = _getMatchedViewportsToolGroupIds();
2084
+ toolGroupIds.forEach(toolGroupId => {
2085
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
2086
+ });
2087
+ },
2088
+ thresholdSegmentationByRectangleROITool: ({
2089
+ segmentationId,
2090
+ config
2091
+ }) => {
2092
+ const segmentation = esm.segmentation.state.getSegmentation(segmentationId);
2093
+ const {
2094
+ representationData
2095
+ } = segmentation;
2096
+ const {
2097
+ displaySetMatchDetails: matchDetails
2098
+ } = hangingProtocolService.getMatchDetails();
2099
+ const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
2100
+
2101
+ const ctDisplaySet = matchDetails.get('ctDisplaySet');
2102
+ const ctVolumeId = `${volumeLoaderScheme}:${ctDisplaySet.displaySetInstanceUID}`; // VolumeId with loader id + volume id
2103
+
2104
+ const {
2105
+ volumeId: segVolumeId
2106
+ } = representationData[LABELMAP];
2107
+ const {
2108
+ referencedVolumeId
2109
+ } = dist_esm.cache.getVolume(segVolumeId);
2110
+ const labelmapVolume = dist_esm.cache.getVolume(segmentationId);
2111
+ const referencedVolume = dist_esm.cache.getVolume(referencedVolumeId);
2112
+ const ctReferencedVolume = dist_esm.cache.getVolume(ctVolumeId);
2113
+ if (!referencedVolume) {
2114
+ throw new Error('No Reference volume found');
2115
+ }
2116
+ if (!labelmapVolume) {
2117
+ throw new Error('No Reference labelmap found');
2118
+ }
2119
+ const annotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2120
+ if (annotationUIDs.length === 0) {
2121
+ uiNotificationService.show({
2122
+ title: 'Commands Module',
2123
+ message: 'No ROIThreshold Tool is Selected',
2124
+ type: 'error'
2125
+ });
2126
+ return;
2127
+ }
2128
+ const {
2129
+ ptLower,
2130
+ ptUpper,
2131
+ ctLower,
2132
+ ctUpper
2133
+ } = getThresholdValue(annotationUIDs, [referencedVolume, ctReferencedVolume], config);
2134
+ return esm.utilities.segmentation.rectangleROIThresholdVolumeByRange(annotationUIDs, labelmapVolume, [{
2135
+ volume: referencedVolume,
2136
+ lower: ptLower,
2137
+ upper: ptUpper
2138
+ }, {
2139
+ volume: ctReferencedVolume,
2140
+ lower: ctLower,
2141
+ upper: ctUpper
2142
+ }], {
2143
+ overwrite: true
2144
+ });
2145
+ },
2146
+ calculateSuvPeak: ({
2147
+ labelmap
2148
+ }) => {
2149
+ const {
2150
+ referencedVolumeId
2151
+ } = labelmap;
2152
+ const referencedVolume = dist_esm.cache.getVolume(referencedVolumeId);
2153
+ const annotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2154
+ const annotations = annotationUIDs.map(annotationUID => esm.annotation.state.getAnnotation(annotationUID));
2155
+ const suvPeak = calculateSUVPeak(labelmap, referencedVolume, annotations);
2156
+ return {
2157
+ suvPeak: suvPeak.mean,
2158
+ suvMax: suvPeak.max,
2159
+ suvMaxIJK: suvPeak.maxIJK,
2160
+ suvMaxLPS: suvPeak.maxLPS
2161
+ };
2162
+ },
2163
+ getLesionStats: ({
2164
+ labelmap,
2165
+ segmentIndex = 1
2166
+ }) => {
2167
+ const {
2168
+ scalarData,
2169
+ spacing
2170
+ } = labelmap;
2171
+ const {
2172
+ scalarData: referencedScalarData
2173
+ } = dist_esm.cache.getVolume(labelmap.referencedVolumeId);
2174
+ let segmentationMax = -Infinity;
2175
+ let segmentationMin = Infinity;
2176
+ let segmentationValues = [];
2177
+ let voxelCount = 0;
2178
+ for (let i = 0; i < scalarData.length; i++) {
2179
+ if (scalarData[i] === segmentIndex) {
2180
+ const value = referencedScalarData[i];
2181
+ segmentationValues.push(value);
2182
+ if (value > segmentationMax) {
2183
+ segmentationMax = value;
2184
+ }
2185
+ if (value < segmentationMin) {
2186
+ segmentationMin = value;
2187
+ }
2188
+ voxelCount++;
2189
+ }
2190
+ }
2191
+ const stats = {
2192
+ minValue: segmentationMin,
2193
+ maxValue: segmentationMax,
2194
+ meanValue: segmentationValues.reduce((a, b) => a + b, 0) / voxelCount,
2195
+ stdValue: Math.sqrt(segmentationValues.reduce((a, b) => a + b * b, 0) / voxelCount - segmentationValues.reduce((a, b) => a + b, 0) / voxelCount ** 2),
2196
+ volume: voxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3
2197
+ };
2198
+ return stats;
2199
+ },
2200
+ calculateLesionGlycolysis: ({
2201
+ lesionStats
2202
+ }) => {
2203
+ const {
2204
+ meanValue,
2205
+ volume
2206
+ } = lesionStats;
2207
+ return {
2208
+ lesionGlyoclysisStats: volume * meanValue
2209
+ };
2210
+ },
2211
+ calculateTMTV: ({
2212
+ segmentations
2213
+ }) => {
2214
+ const labelmaps = segmentations.map(s => segmentationService.getLabelmapVolume(s.id));
2215
+ if (!labelmaps.length) {
2216
+ return;
2217
+ }
2218
+ return utils_calculateTMTV(labelmaps);
2219
+ },
2220
+ exportTMTVReportCSV: ({
2221
+ segmentations,
2222
+ tmtv,
2223
+ config
2224
+ }) => {
2225
+ const segReport = commandsManager.runCommand('getSegmentationCSVReport', {
2226
+ segmentations
2227
+ });
2228
+ const tlg = actions.getTotalLesionGlycolysis({
2229
+ segmentations
2230
+ });
2231
+ const additionalReportRows = [{
2232
+ key: 'Total Metabolic Tumor Volume',
2233
+ value: {
2234
+ tmtv
2235
+ }
2236
+ }, {
2237
+ key: 'Total Lesion Glycolysis',
2238
+ value: {
2239
+ tlg: tlg.toFixed(4)
2240
+ }
2241
+ }, {
2242
+ key: 'Threshold Configuration',
2243
+ value: {
2244
+ ...config
2245
+ }
2246
+ }];
2247
+ createAndDownloadTMTVReport(segReport, additionalReportRows);
2248
+ },
2249
+ getTotalLesionGlycolysis: ({
2250
+ segmentations
2251
+ }) => {
2252
+ const labelmapVolumes = segmentations.map(s => segmentationService.getLabelmapVolume(s.id));
2253
+ let mergedLabelmap;
2254
+ // merge labelmap will through an error if labels maps are not the same size
2255
+ // or same direction or ....
2256
+ try {
2257
+ mergedLabelmap = esm.utilities.segmentation.createMergedLabelmapForIndex(labelmapVolumes);
2258
+ } catch (e) {
2259
+ console.error('commandsModule::getTotalLesionGlycolysis', e);
2260
+ return;
2261
+ }
2262
+
2263
+ // grabbing the first labelmap referenceVolume since it will be the same for all
2264
+ const {
2265
+ referencedVolumeId,
2266
+ spacing
2267
+ } = labelmapVolumes[0];
2268
+ if (!referencedVolumeId) {
2269
+ console.error('commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found');
2270
+ }
2271
+ const ptVolume = dist_esm.cache.getVolume(referencedVolumeId);
2272
+ const mergedLabelData = mergedLabelmap.scalarData;
2273
+ if (mergedLabelData.length !== ptVolume.scalarData.length) {
2274
+ console.error('commandsModule::getTotalLesionGlycolysis:Labelmap and ptVolume are not the same size');
2275
+ }
2276
+ let suv = 0;
2277
+ let totalLesionVoxelCount = 0;
2278
+ for (let i = 0; i < mergedLabelData.length; i++) {
2279
+ // if not background
2280
+ if (mergedLabelData[i] !== 0) {
2281
+ suv += ptVolume.scalarData[i];
2282
+ totalLesionVoxelCount += 1;
2283
+ }
2284
+ }
2285
+
2286
+ // Average SUV for the merged labelmap
2287
+ const averageSuv = suv / totalLesionVoxelCount;
2288
+
2289
+ // total Lesion Glycolysis [suv * ml]
2290
+ return averageSuv * totalLesionVoxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3;
2291
+ },
2292
+ setStartSliceForROIThresholdTool: () => {
2293
+ const {
2294
+ viewport
2295
+ } = _getActiveViewportsEnabledElement();
2296
+ const {
2297
+ focalPoint,
2298
+ viewPlaneNormal
2299
+ } = viewport.getCamera();
2300
+ const selectedAnnotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2301
+ const annotationUID = selectedAnnotationUIDs[0];
2302
+ const annotation = esm.annotation.state.getAnnotation(annotationUID);
2303
+ const {
2304
+ handles
2305
+ } = annotation.data;
2306
+ const {
2307
+ points
2308
+ } = handles;
2309
+
2310
+ // get the current slice Index
2311
+ const sliceIndex = viewport.getCurrentImageIdIndex();
2312
+ annotation.data.startSlice = sliceIndex;
2313
+
2314
+ // distance between camera focal point and each point on the rectangle
2315
+ const newPoints = points.map(point => {
2316
+ const distance = gl_matrix_esm/* vec3.create */.eR.create();
2317
+ gl_matrix_esm/* vec3.subtract */.eR.subtract(distance, focalPoint, point);
2318
+ // distance in the direction of the viewPlaneNormal
2319
+ const distanceInViewPlane = gl_matrix_esm/* vec3.dot */.eR.dot(distance, viewPlaneNormal);
2320
+ // new point is current point minus distanceInViewPlane
2321
+ const newPoint = gl_matrix_esm/* vec3.create */.eR.create();
2322
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(newPoint, point, viewPlaneNormal, distanceInViewPlane);
2323
+ return newPoint;
2324
+ //
2325
+ });
2326
+ handles.points = newPoints;
2327
+ // IMPORTANT: invalidate the toolData for the cached stat to get updated
2328
+ // and re-calculate the projection points
2329
+ annotation.invalidated = true;
2330
+ viewport.render();
2331
+ },
2332
+ setEndSliceForROIThresholdTool: () => {
2333
+ const {
2334
+ viewport
2335
+ } = _getActiveViewportsEnabledElement();
2336
+ const selectedAnnotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2337
+ const annotationUID = selectedAnnotationUIDs[0];
2338
+ const annotation = esm.annotation.state.getAnnotation(annotationUID);
2339
+
2340
+ // get the current slice Index
2341
+ const sliceIndex = viewport.getCurrentImageIdIndex();
2342
+ annotation.data.endSlice = sliceIndex;
2343
+
2344
+ // IMPORTANT: invalidate the toolData for the cached stat to get updated
2345
+ // and re-calculate the projection points
2346
+ annotation.invalidated = true;
2347
+ viewport.render();
2348
+ },
2349
+ createTMTVRTReport: () => {
2350
+ // get all Rectangle ROI annotation
2351
+ const stateManager = esm.annotation.state.getAnnotationManager();
2352
+ const annotations = [];
2353
+ Object.keys(stateManager.annotations).forEach(frameOfReferenceUID => {
2354
+ const forAnnotations = stateManager.annotations[frameOfReferenceUID];
2355
+ const ROIAnnotations = forAnnotations[RECTANGLE_ROI_THRESHOLD_MANUAL];
2356
+ annotations.push(...ROIAnnotations);
2357
+ });
2358
+ commandsManager.runCommand('exportRTReportForAnnotations', {
2359
+ annotations
2360
+ });
2361
+ },
2362
+ getSegmentationCSVReport: ({
2363
+ segmentations
2364
+ }) => {
2365
+ if (!segmentations || !segmentations.length) {
2366
+ segmentations = segmentationService.getSegmentations();
2367
+ }
2368
+ let report = {};
2369
+ for (const segmentation of segmentations) {
2370
+ const {
2371
+ id,
2372
+ label,
2373
+ cachedStats: data
2374
+ } = segmentation;
2375
+ const segReport = {
2376
+ id,
2377
+ label
2378
+ };
2379
+ if (!data) {
2380
+ report[id] = segReport;
2381
+ continue;
2382
+ }
2383
+ Object.keys(data).forEach(key => {
2384
+ if (typeof data[key] !== 'object') {
2385
+ segReport[key] = data[key];
2386
+ } else {
2387
+ Object.keys(data[key]).forEach(subKey => {
2388
+ const newKey = `${key}_${subKey}`;
2389
+ segReport[newKey] = data[key][subKey];
2390
+ });
2391
+ }
2392
+ });
2393
+ const labelmapVolume = segmentationService.getLabelmapVolume(id);
2394
+ if (!labelmapVolume) {
2395
+ report[id] = segReport;
2396
+ continue;
2397
+ }
2398
+ const referencedVolumeId = labelmapVolume.referencedVolumeId;
2399
+ segReport.referencedVolumeId = referencedVolumeId;
2400
+ const referencedVolume = segmentationService.getLabelmapVolume(referencedVolumeId);
2401
+ if (!referencedVolume) {
2402
+ report[id] = segReport;
2403
+ continue;
2404
+ }
2405
+ if (!referencedVolume.imageIds || !referencedVolume.imageIds.length) {
2406
+ report[id] = segReport;
2407
+ continue;
2408
+ }
2409
+ const firstImageId = referencedVolume.imageIds[0];
2410
+ const instance = core_src/* default.classes */.Ay.classes.MetadataProvider.get('instance', firstImageId);
2411
+ if (!instance) {
2412
+ report[id] = segReport;
2413
+ continue;
2414
+ }
2415
+ report[id] = {
2416
+ ...segReport,
2417
+ PatientID: instance.PatientID,
2418
+ PatientName: instance.PatientName.Alphabetic,
2419
+ StudyInstanceUID: instance.StudyInstanceUID,
2420
+ SeriesInstanceUID: instance.SeriesInstanceUID,
2421
+ StudyDate: instance.StudyDate
2422
+ };
2423
+ }
2424
+ return report;
2425
+ },
2426
+ exportRTReportForAnnotations: ({
2427
+ annotations
2428
+ }) => {
2429
+ RTStructureSet(annotations);
2430
+ },
2431
+ setFusionPTColormap: ({
2432
+ toolGroupId,
2433
+ colormap
2434
+ }) => {
2435
+ const toolGroup = toolGroupService.getToolGroup(toolGroupId);
2436
+ const {
2437
+ viewportMatchDetails
2438
+ } = hangingProtocolService.getMatchDetails();
2439
+ const ptDisplaySet = actions.getMatchingPTDisplaySet({
2440
+ viewportMatchDetails
2441
+ });
2442
+ if (!ptDisplaySet) {
2443
+ return;
2444
+ }
2445
+ const fusionViewportIds = toolGroup.getViewportIds();
2446
+ let viewports = [];
2447
+ fusionViewportIds.forEach(viewportId => {
2448
+ commandsManager.runCommand('setViewportColormap', {
2449
+ viewportId,
2450
+ displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID,
2451
+ colormap: {
2452
+ name: colormap
2453
+ }
2454
+ });
2455
+ viewports.push(cornerstoneViewportService.getCornerstoneViewport(viewportId));
2456
+ });
2457
+ viewports.forEach(viewport => {
2458
+ viewport.render();
2459
+ });
2460
+ }
2461
+ };
2462
+ const definitions = {
2463
+ setEndSliceForROIThresholdTool: {
2464
+ commandFn: actions.setEndSliceForROIThresholdTool
2465
+ },
2466
+ setStartSliceForROIThresholdTool: {
2467
+ commandFn: actions.setStartSliceForROIThresholdTool
2468
+ },
2469
+ getMatchingPTDisplaySet: {
2470
+ commandFn: actions.getMatchingPTDisplaySet
2471
+ },
2472
+ getPTMetadata: {
2473
+ commandFn: actions.getPTMetadata
2474
+ },
2475
+ createNewLabelmapFromPT: {
2476
+ commandFn: actions.createNewLabelmapFromPT
2477
+ },
2478
+ setSegmentationActiveForToolGroups: {
2479
+ commandFn: actions.setSegmentationActiveForToolGroups
2480
+ },
2481
+ thresholdSegmentationByRectangleROITool: {
2482
+ commandFn: actions.thresholdSegmentationByRectangleROITool
2483
+ },
2484
+ getTotalLesionGlycolysis: {
2485
+ commandFn: actions.getTotalLesionGlycolysis
2486
+ },
2487
+ calculateSuvPeak: {
2488
+ commandFn: actions.calculateSuvPeak
2489
+ },
2490
+ getLesionStats: {
2491
+ commandFn: actions.getLesionStats
2492
+ },
2493
+ calculateTMTV: {
2494
+ commandFn: actions.calculateTMTV
2495
+ },
2496
+ exportTMTVReportCSV: {
2497
+ commandFn: actions.exportTMTVReportCSV
2498
+ },
2499
+ createTMTVRTReport: {
2500
+ commandFn: actions.createTMTVRTReport
2501
+ },
2502
+ getSegmentationCSVReport: {
2503
+ commandFn: actions.getSegmentationCSVReport
2504
+ },
2505
+ exportRTReportForAnnotations: {
2506
+ commandFn: actions.exportRTReportForAnnotations
2507
+ },
2508
+ setFusionPTColormap: {
2509
+ commandFn: actions.setFusionPTColormap
2510
+ }
2511
+ };
2512
+ return {
2513
+ actions,
2514
+ definitions,
2515
+ defaultContext: 'TMTV:CORNERSTONE'
2516
+ };
2517
+ };
2518
+ /* harmony default export */ const src_commandsModule = (commandsModule);
2519
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/index.tsx
2520
+
2521
+
2522
+
2523
+
2524
+
2525
+
2526
+ /**
2527
+ *
2528
+ */
2529
+ const tmtvExtension = {
2530
+ /**
2531
+ * Only required property. Should be a unique value across all extensions.
2532
+ */
2533
+ id: id,
2534
+ preRegistration({
2535
+ servicesManager,
2536
+ commandsManager,
2537
+ extensionManager,
2538
+ configuration = {}
2539
+ }) {
2540
+ init({
2541
+ servicesManager,
2542
+ commandsManager,
2543
+ extensionManager,
2544
+ configuration
2545
+ });
2546
+ },
2547
+ getPanelModule: src_getPanelModule,
2548
+ getHangingProtocolModule: src_getHangingProtocolModule,
2549
+ getCommandsModule({
2550
+ servicesManager,
2551
+ commandsManager,
2552
+ extensionManager
2553
+ }) {
2554
+ return src_commandsModule({
2555
+ servicesManager,
2556
+ commandsManager,
2557
+ extensionManager
2558
+ });
2559
+ }
2560
+ };
2561
+ /* harmony default export */ const tmtv_src = (tmtvExtension);
2562
+
2563
+ /***/ })
2564
+
2565
+ }]);