@ohif/app 3.8.0-beta.7 → 3.8.0-beta.71

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 (93) hide show
  1. package/dist/{220.bundle.f7e1c96c94245e70f2be.js → 109.bundle.b4fee2a22b622839baf5.js} +4466 -3715
  2. package/dist/{471.bundle.49c8d281adbae4a2c4df.js → 121.bundle.21827fec690c01ee9ab3.js} +85 -112
  3. package/dist/{19.bundle.e5579df6d7b74af50b1d.js → 155.bundle.091ace1591aff1f6b679.js} +334 -287
  4. package/dist/{687.bundle.9d0330ea5d61fe3117da.js → 164.bundle.fcc94cd4e142a409769d.js} +22 -38
  5. package/dist/17dd54813d5acc10bf8f.wasm +0 -0
  6. package/dist/{506.bundle.ab8226d3d81abe874544.js → 188.bundle.0081530bd886a18676eb.js} +23 -28
  7. package/dist/191.bundle.ef35ed1f90a988b3952b.js +30360 -0
  8. package/dist/{221.bundle.c2dc03d8fa4235dc1285.js → 2.bundle.ab8937194aad592bc7b4.js} +351 -546
  9. package/dist/20fc4c659b85ccd2a9c0.wasm +0 -0
  10. package/dist/290.bundle.952de53057f98e2c5ef0.js +8883 -0
  11. package/dist/{451.bundle.57c21db5d003c75e9d61.js → 295.bundle.3a0d5062d65296c4bf5d.js} +102 -127
  12. package/dist/{125.bundle.253395f320b72180da63.js → 297.bundle.194d8985ab974839b5b6.js} +7 -8
  13. package/dist/{202.bundle.d3490836f71e001dd30f.js → 342.bundle.9be178d7555a64de203a.js} +544 -860
  14. package/dist/41.bundle.b5a6c70f88cf565cad3e.js +874 -0
  15. package/dist/425.bundle.e44cfce041ba5209a878.js +2957 -0
  16. package/dist/425.css +2 -0
  17. package/dist/{126.bundle.42df2dafc9c0310da188.js → 448.bundle.599d81471e1d7f7962bc.js} +361 -427
  18. package/dist/{957.bundle.9ea4506963ef8b2d84ba.js → 504.bundle.5ccd6d4269fa77a0a7e7.js} +14338 -27291
  19. package/dist/{886.bundle.c8dd3ecc42a4253de278.js → 530.bundle.566bfd08dccb4cf6d98b.js} +75 -105
  20. package/dist/{250.bundle.aea3335667054bdefe36.js → 544.bundle.1110b24e96863d719a95.js} +39 -56
  21. package/dist/{663.bundle.9f359963019cd8ccf8f9.js → 559.bundle.fb8ac10c41eb734e2f3d.js} +151 -147
  22. package/dist/{181.bundle.a62b9f0ec692299acb35.js → 574.bundle.b262cbe9f2afd7275271.js} +1286 -307
  23. package/dist/{181.css → 574.css} +1 -1
  24. package/dist/{410.bundle.38c9d3820e152e89288e.js → 594.bundle.b70ca7a91d85ebd5d8c4.js} +183 -221
  25. package/dist/{776.bundle.004382036bdbd8ee2b95.js → 595.bundle.c25147a450c67defb3d5.js} +3157 -1029
  26. package/dist/{774.bundle.4b2dc46a35012b898e1a.js → 644.bundle.1e77691d2eeb96a423b0.js} +1852 -8945
  27. package/dist/699.bundle.02c15c3cc4c04dbf7f51.js +785 -0
  28. package/dist/{359.bundle.8abe0036a7bf6b5fd115.js → 724.bundle.d50ce9fb0ab01b9378b7.js} +130 -254
  29. package/dist/{757.bundle.ec8301d8e70d2b990f65.js → 726.bundle.c8de818cf1a3ff0cf7d2.js} +512 -879
  30. package/dist/{530.bundle.a03b6f942ace3e1baa1e.js → 835.bundle.15aff0b7433bb0dd6d6d.js} +37 -30
  31. package/dist/{822.bundle.82cdc418f8f56da6060b.js → 862.bundle.809c87a7ba9da6fb29c8.js} +77 -96
  32. package/dist/{236.bundle.c9e70d55e7b2574c1ecd.js → 889.bundle.1c17d0d13e157ac21d38.js} +198 -197
  33. package/dist/{342.bundle.d9668551811e3a88aaa4.js → 90.bundle.27637ef740946d5c8948.js} +1429 -1055
  34. package/dist/{281.bundle.16a2933086a57e60c96c.js → 905.bundle.206e44c3bbd1df1a900b.js} +155 -122
  35. package/dist/{814.bundle.a1aba9c1e3d336008351.js → 907.bundle.11700f7af989b5af8bc3.js} +16 -30
  36. package/dist/{417.bundle.af0a207c29b109f84159.js → 931.bundle.d270a1fda9a2836c3cc5.js} +26 -26
  37. package/dist/{686.bundle.dccef1f36e4bc79bcc48.js → 939.bundle.9d93b2e47c52338747a2.js} +7 -8
  38. package/dist/{12.bundle.37a8b47d2ae587cb9226.js → 961.bundle.a1ffb667eb04cbe07210.js} +16 -31
  39. package/dist/987.bundle.6bdfb3cd8762b8889632.js +122950 -0
  40. package/dist/app-config.js +1 -0
  41. package/dist/app.bundle.css +15 -13
  42. package/dist/{app.bundle.437d085e13599d1e1ced.js → app.bundle.d1c8b09ab30d221fddf0.js} +148259 -61821
  43. package/dist/assets/images/CT-AAA.png +0 -0
  44. package/dist/assets/images/CT-AAA2.png +0 -0
  45. package/dist/assets/images/CT-Air.png +0 -0
  46. package/dist/assets/images/CT-Bone.png +0 -0
  47. package/dist/assets/images/CT-Bones.png +0 -0
  48. package/dist/assets/images/CT-Cardiac.png +0 -0
  49. package/dist/assets/images/CT-Cardiac2.png +0 -0
  50. package/dist/assets/images/CT-Cardiac3.png +0 -0
  51. package/dist/assets/images/CT-Chest-Contrast-Enhanced.png +0 -0
  52. package/dist/assets/images/CT-Chest-Vessels.png +0 -0
  53. package/dist/assets/images/CT-Coronary-Arteries-2.png +0 -0
  54. package/dist/assets/images/CT-Coronary-Arteries-3.png +0 -0
  55. package/dist/assets/images/CT-Coronary-Arteries.png +0 -0
  56. package/dist/assets/images/CT-Cropped-Volume-Bone.png +0 -0
  57. package/dist/assets/images/CT-Fat.png +0 -0
  58. package/dist/assets/images/CT-Liver-Vasculature.png +0 -0
  59. package/dist/assets/images/CT-Lung.png +0 -0
  60. package/dist/assets/images/CT-MIP.png +0 -0
  61. package/dist/assets/images/CT-Muscle.png +0 -0
  62. package/dist/assets/images/CT-Pulmonary-Arteries.png +0 -0
  63. package/dist/assets/images/CT-Soft-Tissue.png +0 -0
  64. package/dist/assets/images/DTI-FA-Brain.png +0 -0
  65. package/dist/assets/images/MR-Angio.png +0 -0
  66. package/dist/assets/images/MR-Default.png +0 -0
  67. package/dist/assets/images/MR-MIP.png +0 -0
  68. package/dist/assets/images/MR-T2-Brain.png +0 -0
  69. package/dist/assets/images/VolumeRendering.png +0 -0
  70. package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
  71. package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
  72. package/dist/{dicom-microscopy-viewer.bundle.2c146384eb9466d02ff8.js → dicom-microscopy-viewer.bundle.d3a56dc9f62df5e11019.js} +3 -3
  73. package/dist/index.html +1 -1
  74. package/dist/{index.worker.e62ecca63f1a2e124230.worker.js → index.worker.64c896c4316fcd506666.worker.js} +2 -2
  75. package/dist/index.worker.64c896c4316fcd506666.worker.js.map +1 -0
  76. package/dist/polySeg.bundle.01449e456b7d4a737d4f.js +252 -0
  77. package/dist/serve.json +12 -0
  78. package/dist/sw.js +1 -1
  79. package/package.json +25 -22
  80. package/dist/23.bundle.e008ad788170f2ed5569.js +0 -900
  81. package/dist/604.bundle.a51f83e64004bca5f497.js +0 -1848
  82. package/dist/613.bundle.aed640a7900dbcb688f5.js +0 -532
  83. package/dist/743.bundle.489f7df3a089d4d374e1.js +0 -78007
  84. package/dist/75788f12450d4c5ed494.wasm +0 -0
  85. package/dist/775.bundle.2285e7e0e67878948c0d.js +0 -1009
  86. package/dist/788.bundle.dcd53828d1bb2ac64d04.js +0 -2682
  87. package/dist/82.bundle.5a94dd7645e5c5476f59.js +0 -1049
  88. package/dist/index.worker.e62ecca63f1a2e124230.worker.js.map +0 -1
  89. /package/dist/{19.css → 155.css} +0 -0
  90. /package/dist/{221.css → 2.css} +0 -0
  91. /package/dist/{579.css → 481.css} +0 -0
  92. /package/dist/{250.css → 544.css} +0 -0
  93. /package/dist/{776.css → 595.css} +0 -0
@@ -0,0 +1,2957 @@
1
+ "use strict";
2
+ (globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[425,481],{
3
+
4
+ /***/ 42048:
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 + 542 modules
727
+ var src = __webpack_require__(48804);
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: "ohif-scrollbar flex min-h-0 flex-auto select-none flex-col justify-between overflow-auto"
865
+ }, /*#__PURE__*/react.createElement("div", {
866
+ className: "flex min-h-0 flex-col bg-black text-[13px] font-[300]"
867
+ }, /*#__PURE__*/react.createElement(src/* PanelSection */.aU, {
868
+ title: t('Patient Information')
869
+ }, /*#__PURE__*/react.createElement("div", {
870
+ className: "flex flex-col"
871
+ }, /*#__PURE__*/react.createElement("div", {
872
+ className: "bg-primary-dark flex flex-col gap-4 p-2"
873
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
874
+ containerClassName: '!flex-row !justify-between items-center',
875
+ label: t('Patient Sex'),
876
+ labelClassName: "text-[13px] font-inter text-white",
877
+ className: "!m-0 !h-[26px] !w-[117px]",
878
+ value: metadata.PatientSex || '',
879
+ onChange: e => {
880
+ handleMetadataChange({
881
+ PatientSex: e.target.value
882
+ });
883
+ }
884
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
885
+ containerClassName: '!flex-row !justify-between items-center',
886
+ label: t('Weight'),
887
+ labelChildren: /*#__PURE__*/react.createElement("span", {
888
+ className: "text-aqua-pale"
889
+ }, " kg"),
890
+ labelClassName: "text-[13px] font-inter text-white",
891
+ className: "!m-0 !h-[26px] !w-[117px]",
892
+ value: metadata.PatientWeight || '',
893
+ onChange: e => {
894
+ handleMetadataChange({
895
+ PatientWeight: e.target.value
896
+ });
897
+ }
898
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
899
+ containerClassName: '!flex-row !justify-between items-center',
900
+ label: t('Total Dose'),
901
+ labelChildren: /*#__PURE__*/react.createElement("span", {
902
+ className: "text-aqua-pale"
903
+ }, " bq"),
904
+ labelClassName: "text-[13px] font-inter text-white",
905
+ className: "!m-0 !h-[26px] !w-[117px]",
906
+ value: metadata.RadiopharmaceuticalInformationSequence.RadionuclideTotalDose || '',
907
+ onChange: e => {
908
+ handleMetadataChange({
909
+ RadiopharmaceuticalInformationSequence: {
910
+ RadionuclideTotalDose: e.target.value
911
+ }
912
+ });
913
+ }
914
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
915
+ containerClassName: '!flex-row !justify-between items-center',
916
+ label: t('Half Life'),
917
+ labelChildren: /*#__PURE__*/react.createElement("span", {
918
+ className: "text-aqua-pale"
919
+ }, " s"),
920
+ labelClassName: "text-[13px] font-inter text-white",
921
+ className: "!m-0 !h-[26px] !w-[117px]",
922
+ value: metadata.RadiopharmaceuticalInformationSequence.RadionuclideHalfLife || '',
923
+ onChange: e => {
924
+ handleMetadataChange({
925
+ RadiopharmaceuticalInformationSequence: {
926
+ RadionuclideHalfLife: e.target.value
927
+ }
928
+ });
929
+ }
930
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
931
+ containerClassName: '!flex-row !justify-between items-center',
932
+ label: t('Injection Time'),
933
+ labelChildren: /*#__PURE__*/react.createElement("span", {
934
+ className: "text-aqua-pale"
935
+ }, " s"),
936
+ labelClassName: "text-[13px] font-inter text-white",
937
+ className: "!m-0 !h-[26px] !w-[117px]",
938
+ value: metadata.RadiopharmaceuticalInformationSequence.RadiopharmaceuticalStartTime || '',
939
+ onChange: e => {
940
+ handleMetadataChange({
941
+ RadiopharmaceuticalInformationSequence: {
942
+ RadiopharmaceuticalStartTime: e.target.value
943
+ }
944
+ });
945
+ }
946
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
947
+ containerClassName: '!flex-row !justify-between items-center',
948
+ label: t('Acquisition Time'),
949
+ labelChildren: /*#__PURE__*/react.createElement("span", {
950
+ className: "text-aqua-pale"
951
+ }, " s"),
952
+ labelClassName: "text-[13px] font-inter text-white",
953
+ className: "!m-0 !h-[26px] !w-[117px]",
954
+ value: metadata.SeriesTime || '',
955
+ onChange: () => {}
956
+ }), /*#__PURE__*/react.createElement(src/* Button */.$n, {
957
+ className: "!h-[26px] !w-[115px] self-end !p-0",
958
+ onClick: updateMetadata
959
+ }, "Reload Data"))))));
960
+ }
961
+ PanelPetSUV.propTypes = {
962
+ servicesManager: prop_types_default().shape({
963
+ services: prop_types_default().shape({
964
+ measurementService: prop_types_default().shape({
965
+ getMeasurements: (prop_types_default()).func.isRequired,
966
+ subscribe: (prop_types_default()).func.isRequired,
967
+ EVENTS: (prop_types_default()).object.isRequired,
968
+ VALUE_TYPES: (prop_types_default()).object.isRequired
969
+ }).isRequired
970
+ }).isRequired
971
+ }).isRequired
972
+ };
973
+ // EXTERNAL MODULE: ../../../extensions/default/src/index.ts + 78 modules
974
+ var default_src = __webpack_require__(54090);
975
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/segmentationEditHandler.tsx
976
+
977
+
978
+ function segmentationItemEditHandler({
979
+ id,
980
+ servicesManager
981
+ }) {
982
+ const {
983
+ segmentationService,
984
+ uiDialogService
985
+ } = servicesManager.services;
986
+ const segmentation = segmentationService.getSegmentation(id);
987
+ const onSubmitHandler = ({
988
+ action,
989
+ value
990
+ }) => {
991
+ switch (action.id) {
992
+ case 'save':
993
+ {
994
+ segmentationService.addOrUpdateSegmentation({
995
+ ...segmentation,
996
+ ...value
997
+ }, false,
998
+ // don't suppress event
999
+ true // it should update cornerstone
1000
+ );
1001
+ }
1002
+ }
1003
+ uiDialogService.dismiss({
1004
+ id: 'enter-annotation'
1005
+ });
1006
+ };
1007
+ uiDialogService.create({
1008
+ id: 'enter-annotation',
1009
+ centralize: true,
1010
+ isDraggable: false,
1011
+ showOverlay: true,
1012
+ content: src/* Dialog */.lG,
1013
+ contentProps: {
1014
+ title: 'Enter your Segmentation',
1015
+ noCloseButton: true,
1016
+ value: {
1017
+ label: segmentation.label || ''
1018
+ },
1019
+ body: ({
1020
+ value,
1021
+ setValue
1022
+ }) => {
1023
+ const onChangeHandler = event => {
1024
+ event.persist();
1025
+ setValue(value => ({
1026
+ ...value,
1027
+ label: event.target.value
1028
+ }));
1029
+ };
1030
+ const onKeyPressHandler = event => {
1031
+ if (event.key === 'Enter') {
1032
+ onSubmitHandler({
1033
+ value,
1034
+ action: {
1035
+ id: 'save'
1036
+ }
1037
+ });
1038
+ }
1039
+ };
1040
+ return /*#__PURE__*/react.createElement(src/* Input */.pd, {
1041
+ autoFocus: true,
1042
+ className: "border-primary-main bg-black",
1043
+ type: "text",
1044
+ containerClassName: "mr-2",
1045
+ value: value.label,
1046
+ onChange: onChangeHandler,
1047
+ onKeyPress: onKeyPressHandler
1048
+ });
1049
+ },
1050
+ actions: [{
1051
+ id: 'cancel',
1052
+ text: 'Cancel',
1053
+ type: src/* ButtonEnums.type */.Ny.NW.secondary
1054
+ }, {
1055
+ id: 'save',
1056
+ text: 'Save',
1057
+ type: src/* ButtonEnums.type */.Ny.NW.primary
1058
+ }],
1059
+ onSubmit: onSubmitHandler
1060
+ }
1061
+ });
1062
+ }
1063
+ /* harmony default export */ const segmentationEditHandler = (segmentationItemEditHandler);
1064
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ExportReports.tsx
1065
+
1066
+
1067
+
1068
+ function ExportReports({
1069
+ segmentations,
1070
+ tmtvValue,
1071
+ config,
1072
+ commandsManager
1073
+ }) {
1074
+ const {
1075
+ t
1076
+ } = (0,es/* useTranslation */.Bd)('PanelSUVExport');
1077
+ return /*#__PURE__*/react.createElement(react.Fragment, null, segmentations?.length ? /*#__PURE__*/react.createElement("div", {
1078
+ className: "mt-4 flex justify-center space-x-2"
1079
+ }, /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, {
1080
+ color: "black",
1081
+ size: "inherit"
1082
+ }, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1083
+ className: "px-2 py-2 text-base",
1084
+ disabled: tmtvValue === null,
1085
+ onClick: () => {
1086
+ commandsManager.runCommand('exportTMTVReportCSV', {
1087
+ segmentations,
1088
+ tmtv: tmtvValue,
1089
+ config
1090
+ });
1091
+ }
1092
+ }, t('Export CSV'))), /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, {
1093
+ color: "black",
1094
+ size: "inherit"
1095
+ }, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
1096
+ className: "px-2 py-2 text-base",
1097
+ onClick: () => {
1098
+ commandsManager.runCommand('createTMTVRTReport');
1099
+ },
1100
+ disabled: tmtvValue === null
1101
+ }, t('Create RT Report')))) : null);
1102
+ }
1103
+ /* harmony default export */ const PanelROIThresholdSegmentation_ExportReports = (ExportReports);
1104
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/callInputDialog.tsx
1105
+
1106
+
1107
+ function callInputDialog(uiDialogService, label, callback) {
1108
+ const dialogId = 'enter-segment-label';
1109
+ const onSubmitHandler = ({
1110
+ action,
1111
+ value
1112
+ }) => {
1113
+ switch (action.id) {
1114
+ case 'save':
1115
+ callback(value.label, action.id);
1116
+ break;
1117
+ case 'cancel':
1118
+ callback('', action.id);
1119
+ break;
1120
+ }
1121
+ uiDialogService.dismiss({
1122
+ id: dialogId
1123
+ });
1124
+ };
1125
+ if (uiDialogService) {
1126
+ uiDialogService.create({
1127
+ id: dialogId,
1128
+ centralize: true,
1129
+ isDraggable: false,
1130
+ showOverlay: true,
1131
+ content: src/* Dialog */.lG,
1132
+ contentProps: {
1133
+ title: 'Segment',
1134
+ value: {
1135
+ label
1136
+ },
1137
+ noCloseButton: true,
1138
+ onClose: () => uiDialogService.dismiss({
1139
+ id: dialogId
1140
+ }),
1141
+ actions: [{
1142
+ id: 'cancel',
1143
+ text: 'Cancel',
1144
+ type: src/* ButtonEnums.type */.Ny.NW.secondary
1145
+ }, {
1146
+ id: 'save',
1147
+ text: 'Confirm',
1148
+ type: src/* ButtonEnums.type */.Ny.NW.primary
1149
+ }],
1150
+ onSubmit: onSubmitHandler,
1151
+ body: ({
1152
+ value,
1153
+ setValue
1154
+ }) => {
1155
+ return /*#__PURE__*/react.createElement(src/* Input */.pd, {
1156
+ label: "Enter the segment label",
1157
+ labelClassName: "text-white text-[14px] leading-[1.2]",
1158
+ autoFocus: true,
1159
+ className: "border-primary-main bg-black",
1160
+ type: "text",
1161
+ value: value.label,
1162
+ onChange: event => {
1163
+ event.persist();
1164
+ setValue(value => ({
1165
+ ...value,
1166
+ label: event.target.value
1167
+ }));
1168
+ },
1169
+ onKeyPress: event => {
1170
+ if (event.key === 'Enter') {
1171
+ onSubmitHandler({
1172
+ value,
1173
+ action: {
1174
+ id: 'save'
1175
+ }
1176
+ });
1177
+ }
1178
+ }
1179
+ });
1180
+ }
1181
+ }
1182
+ });
1183
+ }
1184
+ }
1185
+ /* harmony default export */ const PanelROIThresholdSegmentation_callInputDialog = (callInputDialog);
1186
+ // EXTERNAL MODULE: ../../../node_modules/react-color/es/index.js + 219 modules
1187
+ var react_color_es = __webpack_require__(13726);
1188
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/colorPickerDialog.css
1189
+ // extracted by mini-css-extract-plugin
1190
+
1191
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/colorPickerDialog.tsx
1192
+
1193
+
1194
+
1195
+
1196
+ function callColorPickerDialog(uiDialogService, rgbaColor, callback) {
1197
+ const dialogId = 'pick-color';
1198
+ const onSubmitHandler = ({
1199
+ action,
1200
+ value
1201
+ }) => {
1202
+ switch (action.id) {
1203
+ case 'save':
1204
+ callback(value.rgbaColor, action.id);
1205
+ break;
1206
+ case 'cancel':
1207
+ callback('', action.id);
1208
+ break;
1209
+ }
1210
+ uiDialogService.dismiss({
1211
+ id: dialogId
1212
+ });
1213
+ };
1214
+ if (uiDialogService) {
1215
+ uiDialogService.create({
1216
+ id: dialogId,
1217
+ centralize: true,
1218
+ isDraggable: false,
1219
+ showOverlay: true,
1220
+ content: src/* Dialog */.lG,
1221
+ contentProps: {
1222
+ title: 'Segment Color',
1223
+ value: {
1224
+ rgbaColor
1225
+ },
1226
+ noCloseButton: true,
1227
+ onClose: () => uiDialogService.dismiss({
1228
+ id: dialogId
1229
+ }),
1230
+ actions: [{
1231
+ id: 'cancel',
1232
+ text: 'Cancel',
1233
+ type: 'primary'
1234
+ }, {
1235
+ id: 'save',
1236
+ text: 'Save',
1237
+ type: 'secondary'
1238
+ }],
1239
+ onSubmit: onSubmitHandler,
1240
+ body: ({
1241
+ value,
1242
+ setValue
1243
+ }) => {
1244
+ const handleChange = color => {
1245
+ setValue({
1246
+ rgbaColor: color.rgb
1247
+ });
1248
+ };
1249
+ return /*#__PURE__*/react.createElement(react_color_es/* ChromePicker */.xk, {
1250
+ color: value.rgbaColor,
1251
+ onChange: handleChange,
1252
+ presetColors: [],
1253
+ width: 300
1254
+ });
1255
+ }
1256
+ }
1257
+ });
1258
+ }
1259
+ }
1260
+ /* harmony default export */ const colorPickerDialog = (callColorPickerDialog);
1261
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/PanelROIThresholdSegmentation.tsx
1262
+
1263
+
1264
+
1265
+
1266
+
1267
+
1268
+
1269
+
1270
+ function PanelRoiThresholdSegmentation({
1271
+ servicesManager,
1272
+ commandsManager,
1273
+ extensionManager
1274
+ }) {
1275
+ const {
1276
+ segmentationService,
1277
+ viewportGridService,
1278
+ uiDialogService
1279
+ } = servicesManager.services;
1280
+ const [selectedSegmentationId, setSelectedSegmentationId] = (0,react.useState)(null);
1281
+ const [segmentations, setSegmentations] = (0,react.useState)(() => segmentationService.getSegmentations());
1282
+ const runCommand = (0,react.useCallback)((commandName, commandOptions = {}) => {
1283
+ return commandsManager.runCommand(commandName, commandOptions);
1284
+ }, [commandsManager]);
1285
+
1286
+ /**
1287
+ * Update UI based on segmentation changes (added, removed, updated)
1288
+ */
1289
+ (0,react.useEffect)(() => {
1290
+ // ~~ Subscription
1291
+ const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
1292
+ const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
1293
+ const removed = segmentationService.EVENTS.SEGMENTATION_REMOVED;
1294
+ const subscriptions = [];
1295
+ [added, updated, removed].forEach(evt => {
1296
+ const {
1297
+ unsubscribe
1298
+ } = segmentationService.subscribe(evt, () => {
1299
+ const segmentations = segmentationService.getSegmentations();
1300
+ setSegmentations(segmentations);
1301
+ });
1302
+ subscriptions.push(unsubscribe);
1303
+ });
1304
+ return () => {
1305
+ subscriptions.forEach(unsub => {
1306
+ unsub();
1307
+ });
1308
+ };
1309
+ }, []);
1310
+ const onSegmentationClick = segmentationId => {
1311
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId);
1312
+ setSelectedSegmentationId(segmentationId);
1313
+ };
1314
+ const onSegmentationAdd = async () => {
1315
+ runCommand('createNewLabelmapFromPT').then(segmentationId => {
1316
+ setSelectedSegmentationId(segmentationId);
1317
+ });
1318
+ };
1319
+ const onSegmentAdd = segmentationId => {
1320
+ segmentationService.addSegment(segmentationId);
1321
+ };
1322
+ const getToolGroupIds = segmentationId => {
1323
+ const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId);
1324
+ return toolGroupIds;
1325
+ };
1326
+ const onSegmentClick = (segmentationId, segmentIndex) => {
1327
+ segmentationService.setActiveSegment(segmentationId, segmentIndex);
1328
+ const toolGroupIds = getToolGroupIds(segmentationId);
1329
+ toolGroupIds.forEach(toolGroupId => {
1330
+ // const toolGroupId =
1331
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
1332
+ segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId);
1333
+ });
1334
+ };
1335
+ const _setSegmentationConfiguration = (0,react.useCallback)((segmentationId, key, value) => {
1336
+ segmentationService.setConfiguration({
1337
+ segmentationId,
1338
+ [key]: value
1339
+ });
1340
+ }, [segmentationService]);
1341
+ const onToggleSegmentVisibility = (segmentationId, segmentIndex) => {
1342
+ const segmentation = segmentationService.getSegmentation(segmentationId);
1343
+ const segmentInfo = segmentation.segments[segmentIndex];
1344
+ const isVisible = !segmentInfo.isVisible;
1345
+ const toolGroupIds = getToolGroupIds(segmentationId);
1346
+ toolGroupIds.forEach(toolGroupId => {
1347
+ segmentationService.setSegmentVisibility(segmentationId, segmentIndex, isVisible, toolGroupId);
1348
+ });
1349
+ };
1350
+ const onSegmentDelete = (segmentationId, segmentIndex) => {
1351
+ segmentationService.removeSegment(segmentationId, segmentIndex);
1352
+ };
1353
+ const onSegmentEdit = (segmentationId, segmentIndex) => {
1354
+ const segmentation = segmentationService.getSegmentation(segmentationId);
1355
+ const segment = segmentation.segments[segmentIndex];
1356
+ const {
1357
+ label
1358
+ } = segment;
1359
+ PanelROIThresholdSegmentation_callInputDialog(uiDialogService, label, (label, actionId) => {
1360
+ if (label === '') {
1361
+ return;
1362
+ }
1363
+ segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
1364
+ });
1365
+ };
1366
+ const onToggleSegmentLock = (segmentationId, segmentIndex) => {
1367
+ segmentationService.toggleSegmentLocked(segmentationId, segmentIndex);
1368
+ };
1369
+ const onSegmentColorClick = (segmentationId, segmentIndex) => {
1370
+ const segmentation = segmentationService.getSegmentation(segmentationId);
1371
+ const segment = segmentation.segments[segmentIndex];
1372
+ const {
1373
+ color,
1374
+ opacity
1375
+ } = segment;
1376
+ const rgbaColor = {
1377
+ r: color[0],
1378
+ g: color[1],
1379
+ b: color[2],
1380
+ a: opacity / 255.0
1381
+ };
1382
+ colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
1383
+ if (actionId === 'cancel') {
1384
+ return;
1385
+ }
1386
+ segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0]);
1387
+ });
1388
+ };
1389
+ const storeSegmentation = async segmentationId => {
1390
+ const datasources = extensionManager.getActiveDataSource();
1391
+ const displaySetInstanceUIDs = await (0,default_src.createReportAsync)({
1392
+ servicesManager,
1393
+ getReport: () => commandsManager.runCommand('storeSegmentation', {
1394
+ segmentationId,
1395
+ dataSource: datasources[0]
1396
+ }),
1397
+ reportType: 'Segmentation'
1398
+ });
1399
+
1400
+ // Show the exported report in the active viewport as read only (similar to SR)
1401
+ if (displaySetInstanceUIDs) {
1402
+ // clear the segmentation that we exported, similar to the storeMeasurement
1403
+ // where we remove the measurements and prompt again the user if they would like
1404
+ // to re-read the measurements in a SR read only viewport
1405
+ segmentationService.remove(segmentationId);
1406
+ viewportGridService.setDisplaySetsForViewport({
1407
+ viewportId: viewportGridService.getActiveViewportId(),
1408
+ displaySetInstanceUIDs
1409
+ });
1410
+ }
1411
+ };
1412
+ const onSegmentationDownloadRTSS = segmentationId => {
1413
+ commandsManager.runCommand('downloadRTSS', {
1414
+ segmentationId
1415
+ });
1416
+ };
1417
+ const onSegmentationDownload = segmentationId => {
1418
+ commandsManager.runCommand('downloadSegmentation', {
1419
+ segmentationId
1420
+ });
1421
+ };
1422
+ const tmtvValue = segmentations?.[0]?.cachedStats?.tmtv?.value || null;
1423
+ const config = segmentations?.[0]?.cachedStats?.tmtv?.config || {};
1424
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1425
+ className: "flex flex-col"
1426
+ }, /*#__PURE__*/react.createElement("div", {
1427
+ className: "invisible-scrollbar overflow-y-auto overflow-x-hidden"
1428
+ }, /*#__PURE__*/react.createElement("div", {
1429
+ className: "flex min-h-0 flex-col bg-black text-[13px] font-[300]"
1430
+ }, /*#__PURE__*/react.createElement(src/* SegmentationGroupTableExpanded */.fO, {
1431
+ disableEditing: false,
1432
+ showAddSegmentation: true,
1433
+ showAddSegment: false,
1434
+ showDeleteSegment: true,
1435
+ segmentations: segmentations,
1436
+ onSegmentationAdd: onSegmentationAdd,
1437
+ onSegmentationClick: onSegmentationClick,
1438
+ onToggleSegmentationVisibility: id => {
1439
+ segmentationService.toggleSegmentationVisibility(id);
1440
+ },
1441
+ onToggleSegmentVisibility: onToggleSegmentVisibility,
1442
+ onSegmentationDelete: id => {
1443
+ segmentationService.remove(id);
1444
+ },
1445
+ onSegmentationEdit: id => {
1446
+ segmentationEditHandler({
1447
+ id,
1448
+ servicesManager
1449
+ });
1450
+ },
1451
+ segmentationConfig: {
1452
+ initialConfig: segmentationService.getConfiguration()
1453
+ },
1454
+ onSegmentAdd: onSegmentAdd,
1455
+ onSegmentClick: onSegmentClick,
1456
+ onSegmentDelete: onSegmentDelete,
1457
+ onSegmentEdit: onSegmentEdit,
1458
+ onToggleSegmentLock: onToggleSegmentLock,
1459
+ onSegmentColorClick: onSegmentColorClick,
1460
+ storeSegmentation: storeSegmentation,
1461
+ onSegmentationDownloadRTSS: onSegmentationDownloadRTSS,
1462
+ onSegmentationDownload: onSegmentationDownload,
1463
+ setRenderOutline: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value),
1464
+ setOutlineOpacityActive: value => _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value),
1465
+ setRenderFill: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value),
1466
+ setRenderInactiveSegmentations: value => _setSegmentationConfiguration(selectedSegmentationId, 'renderInactiveSegmentations', value),
1467
+ setOutlineWidthActive: value => _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value),
1468
+ setFillAlpha: value => _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value),
1469
+ setFillAlphaInactive: value => _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value)
1470
+ })), tmtvValue !== null ? /*#__PURE__*/react.createElement("div", {
1471
+ className: "bg-secondary-dark mt-1 flex items-baseline justify-between px-2 py-1"
1472
+ }, /*#__PURE__*/react.createElement("span", {
1473
+ className: "text-base font-bold uppercase tracking-widest text-white"
1474
+ }, 'TMTV:'), /*#__PURE__*/react.createElement("div", {
1475
+ className: "text-white"
1476
+ }, `${tmtvValue} mL`)) : null, /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation_ExportReports, {
1477
+ segmentations: segmentations,
1478
+ tmtvValue: tmtvValue,
1479
+ config: config,
1480
+ commandsManager: commandsManager
1481
+ }))), /*#__PURE__*/react.createElement("div", {
1482
+ className: "absolute bottom-1 flex cursor-pointer items-center justify-center text-blue-400 opacity-50 hover:opacity-80",
1483
+ onClick: () => {
1484
+ // navigate to a url in a new tab
1485
+ window.open('https://github.com/OHIF/Viewers/blob/master/modes/tmtv/README.md', '_blank');
1486
+ }
1487
+ }, /*#__PURE__*/react.createElement(src/* Icon */.In, {
1488
+ width: "15px",
1489
+ height: "15px",
1490
+ name: 'info',
1491
+ className: 'text-primary-active ml-4 mr-3'
1492
+ }), /*#__PURE__*/react.createElement("span", null, 'User Guide')));
1493
+ }
1494
+ PanelRoiThresholdSegmentation.propTypes = {
1495
+ commandsManager: prop_types_default().shape({
1496
+ runCommand: (prop_types_default()).func.isRequired
1497
+ }),
1498
+ servicesManager: prop_types_default().shape({
1499
+ services: prop_types_default().shape({
1500
+ segmentationService: prop_types_default().shape({
1501
+ getSegmentation: (prop_types_default()).func.isRequired,
1502
+ getSegmentations: (prop_types_default()).func.isRequired,
1503
+ toggleSegmentationVisibility: (prop_types_default()).func.isRequired,
1504
+ subscribe: (prop_types_default()).func.isRequired,
1505
+ EVENTS: (prop_types_default()).object.isRequired
1506
+ }).isRequired
1507
+ }).isRequired
1508
+ }).isRequired
1509
+ };
1510
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/index.ts
1511
+
1512
+ /* harmony default export */ const PanelROIThresholdSegmentation = (PanelRoiThresholdSegmentation);
1513
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/index.tsx
1514
+
1515
+
1516
+
1517
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/getPanelModule.tsx
1518
+
1519
+
1520
+
1521
+
1522
+ // TODO:
1523
+ // - No loading UI exists yet
1524
+ // - cancel promises when component is destroyed
1525
+ // - show errors in UI for thumbnails if promise fails
1526
+
1527
+ function getPanelModule({
1528
+ commandsManager,
1529
+ extensionManager,
1530
+ servicesManager
1531
+ }) {
1532
+ const wrappedPanelPetSuv = () => {
1533
+ return /*#__PURE__*/react.createElement(PanelPetSUV, {
1534
+ commandsManager: commandsManager,
1535
+ servicesManager: servicesManager
1536
+ });
1537
+ };
1538
+ const wrappedROIThresholdSeg = () => {
1539
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(src/* Toolbox */.OO, {
1540
+ commandsManager: commandsManager,
1541
+ servicesManager: servicesManager,
1542
+ extensionManager: extensionManager,
1543
+ buttonSectionId: "tmtvToolbox",
1544
+ title: "Threshold Tools"
1545
+ }), /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation, {
1546
+ commandsManager: commandsManager,
1547
+ servicesManager: servicesManager,
1548
+ extensionManager: extensionManager
1549
+ }));
1550
+ };
1551
+ return [{
1552
+ name: 'petSUV',
1553
+ iconName: 'tab-patient-info',
1554
+ iconLabel: 'Patient Info',
1555
+ label: 'Patient Info',
1556
+ component: wrappedPanelPetSuv
1557
+ }, {
1558
+ name: 'ROIThresholdSeg',
1559
+ iconName: 'tab-segmentation',
1560
+ iconLabel: 'Segmentation',
1561
+ label: 'Segmentation',
1562
+ component: wrappedROIThresholdSeg
1563
+ }];
1564
+ }
1565
+ /* harmony default export */ const src_getPanelModule = (getPanelModule);
1566
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 16 modules
1567
+ var esm = __webpack_require__(20767);
1568
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/constants/supportedTools.js
1569
+ /* harmony default export */ const supportedTools = (['RectangleROIStartEndThreshold']);
1570
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 383 modules
1571
+ var dist_esm = __webpack_require__(50719);
1572
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js
1573
+
1574
+ function getSOPInstanceAttributes(imageId) {
1575
+ if (imageId) {
1576
+ return _getUIDFromImageID(imageId);
1577
+ }
1578
+ }
1579
+ function _getUIDFromImageID(imageId) {
1580
+ const instance = dist_esm.metaData.get('instance', imageId);
1581
+ return {
1582
+ SOPInstanceUID: instance.SOPInstanceUID,
1583
+ SeriesInstanceUID: instance.SeriesInstanceUID,
1584
+ StudyInstanceUID: instance.StudyInstanceUID,
1585
+ frameNumber: instance.frameNumber || 1
1586
+ };
1587
+ }
1588
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/RectangleROIStartEndThreshold.js
1589
+
1590
+
1591
+ const RectangleROIStartEndThreshold = {
1592
+ toAnnotation: (measurement, definition) => {},
1593
+ /**
1594
+ * Maps cornerstone annotation event data to measurement service format.
1595
+ *
1596
+ * @param {Object} cornerstone Cornerstone event data
1597
+ * @return {Measurement} Measurement instance
1598
+ */
1599
+ toMeasurement: (csToolsEventDetail, displaySetService, cornerstoneViewportService) => {
1600
+ const {
1601
+ annotation,
1602
+ viewportId
1603
+ } = csToolsEventDetail;
1604
+ const {
1605
+ metadata,
1606
+ data,
1607
+ annotationUID
1608
+ } = annotation;
1609
+ if (!metadata || !data) {
1610
+ console.warn('Length tool: Missing metadata or data');
1611
+ return null;
1612
+ }
1613
+ const {
1614
+ toolName,
1615
+ referencedImageId,
1616
+ FrameOfReferenceUID
1617
+ } = metadata;
1618
+ const validToolType = supportedTools.includes(toolName);
1619
+ if (!validToolType) {
1620
+ throw new Error('Tool not supported');
1621
+ }
1622
+ const {
1623
+ SOPInstanceUID,
1624
+ SeriesInstanceUID,
1625
+ StudyInstanceUID
1626
+ } = getSOPInstanceAttributes(referencedImageId, cornerstoneViewportService, viewportId);
1627
+ let displaySet;
1628
+ if (SOPInstanceUID) {
1629
+ displaySet = displaySetService.getDisplaySetForSOPInstanceUID(SOPInstanceUID, SeriesInstanceUID);
1630
+ } else {
1631
+ displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID);
1632
+ }
1633
+ return {
1634
+ uid: annotationUID,
1635
+ SOPInstanceUID,
1636
+ FrameOfReferenceUID,
1637
+ // points,
1638
+ metadata,
1639
+ referenceSeriesUID: SeriesInstanceUID,
1640
+ referenceStudyUID: StudyInstanceUID,
1641
+ toolName: metadata.toolName,
1642
+ displaySetInstanceUID: displaySet.displaySetInstanceUID,
1643
+ label: metadata.label,
1644
+ data: data.cachedStats,
1645
+ type: 'RectangleROIStartEndThreshold'
1646
+ };
1647
+ }
1648
+ };
1649
+ /* harmony default export */ const measurementServiceMappings_RectangleROIStartEndThreshold = (RectangleROIStartEndThreshold);
1650
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.js
1651
+
1652
+ const measurementServiceMappingsFactory = (measurementService, displaySetService, cornerstoneViewportService) => {
1653
+ return {
1654
+ RectangleROIStartEndThreshold: {
1655
+ toAnnotation: measurementServiceMappings_RectangleROIStartEndThreshold.toAnnotation,
1656
+ toMeasurement: csToolsAnnotation => measurementServiceMappings_RectangleROIStartEndThreshold.toMeasurement(csToolsAnnotation, displaySetService, cornerstoneViewportService),
1657
+ matchingCriteria: [{
1658
+ valueType: measurementService.VALUE_TYPES.ROI_THRESHOLD_MANUAL
1659
+ }]
1660
+ }
1661
+ };
1662
+ };
1663
+ /* harmony default export */ const measurementServiceMappings_measurementServiceMappingsFactory = (measurementServiceMappingsFactory);
1664
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/init.js
1665
+
1666
+
1667
+ const CORNERSTONE_3D_TOOLS_SOURCE_NAME = 'Cornerstone3DTools';
1668
+ const CORNERSTONE_3D_TOOLS_SOURCE_VERSION = '0.1';
1669
+ /**
1670
+ *
1671
+ * @param {Object} servicesManager
1672
+ * @param {Object} configuration
1673
+ * @param {Object|Array} configuration.csToolsConfig
1674
+ */
1675
+ function init({
1676
+ servicesManager
1677
+ }) {
1678
+ const {
1679
+ measurementService,
1680
+ displaySetService,
1681
+ cornerstoneViewportService
1682
+ } = servicesManager.services;
1683
+ (0,esm.addTool)(esm.RectangleROIStartEndThresholdTool);
1684
+ const {
1685
+ RectangleROIStartEndThreshold
1686
+ } = measurementServiceMappings_measurementServiceMappingsFactory(measurementService, displaySetService, cornerstoneViewportService);
1687
+ const csTools3DVer1MeasurementSource = measurementService.getSource(CORNERSTONE_3D_TOOLS_SOURCE_NAME, CORNERSTONE_3D_TOOLS_SOURCE_VERSION);
1688
+ measurementService.addMapping(csTools3DVer1MeasurementSource, 'RectangleROIStartEndThreshold', RectangleROIStartEndThreshold.matchingCriteria, RectangleROIStartEndThreshold.toAnnotation, RectangleROIStartEndThreshold.toMeasurement);
1689
+ }
1690
+ // EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 10 modules
1691
+ var gl_matrix_esm = __webpack_require__(83636);
1692
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/getThresholdValue.ts
1693
+
1694
+ function getRoiStats(referencedVolume, annotations) {
1695
+ // roiStats
1696
+ const {
1697
+ imageData
1698
+ } = referencedVolume;
1699
+ const values = imageData.getPointData().getScalars().getData();
1700
+
1701
+ // Todo: add support for other strategies
1702
+ const {
1703
+ fn,
1704
+ baseValue
1705
+ } = _getStrategyFn('max');
1706
+ let value = baseValue;
1707
+ const boundsIJK = esm.utilities.rectangleROITool.getBoundsIJKFromRectangleAnnotations(annotations, referencedVolume);
1708
+ const [[iMin, iMax], [jMin, jMax], [kMin, kMax]] = boundsIJK;
1709
+ for (let i = iMin; i <= iMax; i++) {
1710
+ for (let j = jMin; j <= jMax; j++) {
1711
+ for (let k = kMin; k <= kMax; k++) {
1712
+ const offset = imageData.computeOffsetIndex([i, j, k]);
1713
+ value = fn(values[offset], value);
1714
+ }
1715
+ }
1716
+ }
1717
+ return value;
1718
+ }
1719
+ function getThresholdValues(annotationUIDs, referencedVolumes, config) {
1720
+ if (config.strategy === 'range') {
1721
+ return {
1722
+ ptLower: Number(config.ptLower),
1723
+ ptUpper: Number(config.ptUpper),
1724
+ ctLower: Number(config.ctLower),
1725
+ ctUpper: Number(config.ctUpper)
1726
+ };
1727
+ }
1728
+ const {
1729
+ weight
1730
+ } = config;
1731
+ const annotations = annotationUIDs.map(annotationUID => esm.annotation.state.getAnnotation(annotationUID));
1732
+ const ptValue = getRoiStats(referencedVolumes[0], annotations);
1733
+ return {
1734
+ ctLower: -Infinity,
1735
+ ctUpper: +Infinity,
1736
+ ptLower: weight * ptValue,
1737
+ ptUpper: +Infinity
1738
+ };
1739
+ }
1740
+ function _getStrategyFn(statistic) {
1741
+ const baseValue = -Infinity;
1742
+ const fn = (number, maxValue) => {
1743
+ if (number > maxValue) {
1744
+ maxValue = number;
1745
+ }
1746
+ return maxValue;
1747
+ };
1748
+ return {
1749
+ fn,
1750
+ baseValue
1751
+ };
1752
+ }
1753
+ /* harmony default export */ const getThresholdValue = (getThresholdValues);
1754
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/calculateSUVPeak.ts
1755
+
1756
+
1757
+ /**
1758
+ * This method calculates the SUV peak on a segmented ROI from a reference PET
1759
+ * volume. If a rectangle annotation is provided, the peak is calculated within that
1760
+ * rectangle. Otherwise, the calculation is performed on the entire volume which
1761
+ * will be slower but same result.
1762
+ * @param viewport Viewport to use for the calculation
1763
+ * @param labelmap Labelmap from which the mask is taken
1764
+ * @param referenceVolume PET volume to use for SUV calculation
1765
+ * @param toolData [Optional] list of toolData to use for SUV calculation
1766
+ * @param segmentIndex The index of the segment to use for masking
1767
+ * @returns
1768
+ */
1769
+ function calculateSuvPeak(labelmap, referenceVolume, annotations, segmentIndex = 1) {
1770
+ if (referenceVolume.metadata.Modality !== 'PT') {
1771
+ return;
1772
+ }
1773
+ if (labelmap.scalarData.length !== referenceVolume.scalarData.length) {
1774
+ throw new Error('labelmap and referenceVolume must have the same number of pixels');
1775
+ }
1776
+ const {
1777
+ scalarData: labelmapData,
1778
+ dimensions,
1779
+ imageData: labelmapImageData
1780
+ } = labelmap;
1781
+ const {
1782
+ scalarData: referenceVolumeData,
1783
+ imageData: referenceVolumeImageData
1784
+ } = referenceVolume;
1785
+ let boundsIJK;
1786
+ // Todo: using the first annotation for now
1787
+ if (annotations && annotations[0].data?.cachedStats) {
1788
+ const {
1789
+ projectionPoints
1790
+ } = annotations[0].data.cachedStats;
1791
+ const pointsToUse = [].concat(...projectionPoints); // cannot use flat() because of typescript compiler right now
1792
+
1793
+ const rectangleCornersIJK = pointsToUse.map(world => {
1794
+ const ijk = gl_matrix_esm/* vec3.fromValues */.eR.fromValues(0, 0, 0);
1795
+ referenceVolumeImageData.worldToIndex(world, ijk);
1796
+ return ijk;
1797
+ });
1798
+ boundsIJK = esm.utilities.boundingBox.getBoundingBoxAroundShape(rectangleCornersIJK, dimensions);
1799
+ }
1800
+ let max = 0;
1801
+ let maxIJK = [0, 0, 0];
1802
+ let maxLPS = [0, 0, 0];
1803
+ const callback = ({
1804
+ pointIJK,
1805
+ pointLPS
1806
+ }) => {
1807
+ const offset = referenceVolumeImageData.computeOffsetIndex(pointIJK);
1808
+ const value = labelmapData[offset];
1809
+ if (value !== segmentIndex) {
1810
+ return;
1811
+ }
1812
+ const referenceValue = referenceVolumeData[offset];
1813
+ if (referenceValue > max) {
1814
+ max = referenceValue;
1815
+ maxIJK = pointIJK;
1816
+ maxLPS = pointLPS;
1817
+ }
1818
+ };
1819
+ esm.utilities.pointInShapeCallback(labelmapImageData, () => true, callback, boundsIJK);
1820
+ const direction = labelmapImageData.getDirection().slice(0, 3);
1821
+
1822
+ /**
1823
+ * 2. Find the bottom and top of the great circle for the second sphere (1cc sphere)
1824
+ * V = (4/3)πr3
1825
+ */
1826
+ const radius = Math.pow(1 / (4 / 3 * Math.PI), 1 / 3) * 10;
1827
+ const diameter = radius * 2;
1828
+ const secondaryCircleWorld = gl_matrix_esm/* vec3.create */.eR.create();
1829
+ const bottomWorld = gl_matrix_esm/* vec3.create */.eR.create();
1830
+ const topWorld = gl_matrix_esm/* vec3.create */.eR.create();
1831
+ referenceVolumeImageData.indexToWorld(maxIJK, secondaryCircleWorld);
1832
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(bottomWorld, secondaryCircleWorld, direction, -diameter / 2);
1833
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(topWorld, secondaryCircleWorld, direction, diameter / 2);
1834
+ const suvPeakCirclePoints = [bottomWorld, topWorld];
1835
+
1836
+ /**
1837
+ * 3. Find the Mean and Max of the 1cc sphere centered on the suv Max of the previous
1838
+ * sphere
1839
+ */
1840
+ let count = 0;
1841
+ let acc = 0;
1842
+ const suvPeakMeanCallback = ({
1843
+ value
1844
+ }) => {
1845
+ acc += value;
1846
+ count += 1;
1847
+ };
1848
+ esm.utilities.pointInSurroundingSphereCallback(referenceVolumeImageData, suvPeakCirclePoints, suvPeakMeanCallback);
1849
+ const mean = acc / count;
1850
+ return {
1851
+ max,
1852
+ maxIJK,
1853
+ maxLPS,
1854
+ mean
1855
+ };
1856
+ }
1857
+ /* harmony default export */ const calculateSUVPeak = (calculateSuvPeak);
1858
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/calculateTMTV.ts
1859
+
1860
+
1861
+ /**
1862
+ * Given a list of labelmaps (with the possibility of overlapping regions),
1863
+ * and a referenceVolume, it calculates the total metabolic tumor volume (TMTV)
1864
+ * by flattening and rasterizing each segment into a single labelmap and summing
1865
+ * the total number of volume voxels. It should be noted that for this calculation
1866
+ * we do not double count voxels that are part of multiple labelmaps.
1867
+ * @param {} labelmaps
1868
+ * @param {number} segmentIndex
1869
+ * @returns {number} TMTV in ml
1870
+ */
1871
+ function calculateTMTV(labelmaps, segmentIndex = 1) {
1872
+ const volumeId = 'mergedLabelmap';
1873
+ const mergedLabelmap = esm.utilities.segmentation.createMergedLabelmapForIndex(labelmaps, segmentIndex, volumeId);
1874
+ const {
1875
+ imageData,
1876
+ spacing
1877
+ } = mergedLabelmap;
1878
+ const values = imageData.getPointData().getScalars().getData();
1879
+
1880
+ // count non-zero values inside the outputData, this would
1881
+ // consider the overlapping regions to be only counted once
1882
+ const numVoxels = values.reduce((acc, curr) => {
1883
+ if (curr > 0) {
1884
+ return acc + 1;
1885
+ }
1886
+ return acc;
1887
+ }, 0);
1888
+ return 1e-3 * numVoxels * spacing[0] * spacing[1] * spacing[2];
1889
+ }
1890
+ /* harmony default export */ const utils_calculateTMTV = (calculateTMTV);
1891
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/createAndDownloadTMTVReport.js
1892
+ function createAndDownloadTMTVReport(segReport, additionalReportRows) {
1893
+ const firstReport = segReport[Object.keys(segReport)[0]];
1894
+ const columns = Object.keys(firstReport);
1895
+ const csv = [columns.join(',')];
1896
+ Object.values(segReport).forEach(segmentation => {
1897
+ const row = [];
1898
+ columns.forEach(column => {
1899
+ // if it is array then we need to replace , with space to avoid csv parsing error
1900
+ row.push(Array.isArray(segmentation[column]) ? segmentation[column].join(' ') : segmentation[column]);
1901
+ });
1902
+ csv.push(row.join(','));
1903
+ });
1904
+ csv.push('');
1905
+ csv.push('');
1906
+ csv.push('');
1907
+ csv.push(`Patient ID,${firstReport.PatientID}`);
1908
+ csv.push(`Study Date,${firstReport.StudyDate}`);
1909
+ csv.push('');
1910
+ additionalReportRows.forEach(({
1911
+ key,
1912
+ value: values
1913
+ }) => {
1914
+ const temp = [];
1915
+ temp.push(`${key}`);
1916
+ Object.keys(values).forEach(k => {
1917
+ temp.push(`${k}`);
1918
+ temp.push(`${values[k]}`);
1919
+ });
1920
+ csv.push(temp.join(','));
1921
+ });
1922
+ const blob = new Blob([csv.join('\n')], {
1923
+ type: 'text/csv;charset=utf-8'
1924
+ });
1925
+ const url = URL.createObjectURL(blob);
1926
+ const a = document.createElement('a');
1927
+ a.href = url;
1928
+ a.download = `${firstReport.PatientID}_tmtv.csv`;
1929
+ a.click();
1930
+ }
1931
+ // EXTERNAL MODULE: ../../../node_modules/dcmjs/build/dcmjs.es.js
1932
+ var dcmjs_es = __webpack_require__(31426);
1933
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/adapters/dist/adapters.es.js
1934
+ var adapters_es = __webpack_require__(83342);
1935
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/dicomRTAnnotationExport.js
1936
+
1937
+
1938
+
1939
+ const {
1940
+ datasetToBlob
1941
+ } = dcmjs_es/* default.data */.Ay.data;
1942
+ const metadataProvider = core_src.classes.MetadataProvider;
1943
+ function dicomRTAnnotationExport(annotations) {
1944
+ const dataset = adapters_es/* adaptersRT */.f_.Cornerstone3D.RTSS.generateRTSSFromAnnotations(annotations, metadataProvider, core_src.DicomMetadataStore);
1945
+ const reportBlob = datasetToBlob(dataset);
1946
+
1947
+ //Create a URL for the binary.
1948
+ var objectUrl = URL.createObjectURL(reportBlob);
1949
+ window.location.assign(objectUrl);
1950
+ }
1951
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/index.js
1952
+
1953
+ /* harmony default export */ const RTStructureSet = (dicomRTAnnotationExport);
1954
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/commandsModule.js
1955
+
1956
+
1957
+
1958
+
1959
+
1960
+
1961
+
1962
+
1963
+
1964
+
1965
+ const commandsModule_metadataProvider = core_src.classes.MetadataProvider;
1966
+ const RECTANGLE_ROI_THRESHOLD_MANUAL = 'RectangleROIStartEndThreshold';
1967
+ const LABELMAP = esm.Enums.SegmentationRepresentations.Labelmap;
1968
+ const commandsModule = ({
1969
+ servicesManager,
1970
+ commandsManager,
1971
+ extensionManager
1972
+ }) => {
1973
+ const {
1974
+ viewportGridService,
1975
+ uiNotificationService,
1976
+ displaySetService,
1977
+ hangingProtocolService,
1978
+ toolGroupService,
1979
+ cornerstoneViewportService,
1980
+ segmentationService
1981
+ } = servicesManager.services;
1982
+ const utilityModule = extensionManager.getModuleEntry('@ohif/extension-cornerstone.utilityModule.common');
1983
+ const {
1984
+ getEnabledElement
1985
+ } = utilityModule.exports;
1986
+ function _getActiveViewportsEnabledElement() {
1987
+ const {
1988
+ activeViewportId
1989
+ } = viewportGridService.getState();
1990
+ const {
1991
+ element
1992
+ } = getEnabledElement(activeViewportId) || {};
1993
+ const enabledElement = dist_esm.getEnabledElement(element);
1994
+ return enabledElement;
1995
+ }
1996
+ function _getMatchedViewportsToolGroupIds() {
1997
+ const {
1998
+ viewportMatchDetails
1999
+ } = hangingProtocolService.getMatchDetails();
2000
+ const toolGroupIds = [];
2001
+ viewportMatchDetails.forEach(viewport => {
2002
+ const {
2003
+ viewportOptions
2004
+ } = viewport;
2005
+ const {
2006
+ toolGroupId
2007
+ } = viewportOptions;
2008
+ if (toolGroupIds.indexOf(toolGroupId) === -1) {
2009
+ toolGroupIds.push(toolGroupId);
2010
+ }
2011
+ });
2012
+ return toolGroupIds;
2013
+ }
2014
+ const actions = {
2015
+ getMatchingPTDisplaySet: ({
2016
+ viewportMatchDetails
2017
+ }) => {
2018
+ // Todo: this is assuming that the hanging protocol has successfully matched
2019
+ // the correct PT. For future, we should have a way to filter out the PTs
2020
+ // that are in the viewer layout (but then we have the problem of the attenuation
2021
+ // corrected PT vs the non-attenuation correct PT)
2022
+
2023
+ let ptDisplaySet = null;
2024
+ for (const [viewportId, viewportDetails] of viewportMatchDetails) {
2025
+ const {
2026
+ displaySetsInfo
2027
+ } = viewportDetails;
2028
+ const displaySets = displaySetsInfo.map(({
2029
+ displaySetInstanceUID
2030
+ }) => displaySetService.getDisplaySetByUID(displaySetInstanceUID));
2031
+ if (!displaySets || displaySets.length === 0) {
2032
+ continue;
2033
+ }
2034
+ ptDisplaySet = displaySets.find(displaySet => displaySet.Modality === 'PT');
2035
+ if (ptDisplaySet) {
2036
+ break;
2037
+ }
2038
+ }
2039
+ return ptDisplaySet;
2040
+ },
2041
+ getPTMetadata: ({
2042
+ ptDisplaySet
2043
+ }) => {
2044
+ const dataSource = extensionManager.getDataSources()[0];
2045
+ const imageIds = dataSource.getImageIdsForDisplaySet(ptDisplaySet);
2046
+ const firstImageId = imageIds[0];
2047
+ const instance = commandsModule_metadataProvider.get('instance', firstImageId);
2048
+ if (instance.Modality !== 'PT') {
2049
+ return;
2050
+ }
2051
+ const metadata = {
2052
+ SeriesTime: instance.SeriesTime,
2053
+ Modality: instance.Modality,
2054
+ PatientSex: instance.PatientSex,
2055
+ PatientWeight: instance.PatientWeight,
2056
+ RadiopharmaceuticalInformationSequence: {
2057
+ RadionuclideTotalDose: instance.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose,
2058
+ RadionuclideHalfLife: instance.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife,
2059
+ RadiopharmaceuticalStartTime: instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime,
2060
+ RadiopharmaceuticalStartDateTime: instance.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime
2061
+ }
2062
+ };
2063
+ return metadata;
2064
+ },
2065
+ createNewLabelmapFromPT: async () => {
2066
+ // Create a segmentation of the same resolution as the source data
2067
+ // using volumeLoader.createAndCacheDerivedVolume.
2068
+ const {
2069
+ viewportMatchDetails
2070
+ } = hangingProtocolService.getMatchDetails();
2071
+ const ptDisplaySet = actions.getMatchingPTDisplaySet({
2072
+ viewportMatchDetails
2073
+ });
2074
+ if (!ptDisplaySet) {
2075
+ uiNotificationService.error('No matching PT display set found');
2076
+ return;
2077
+ }
2078
+ const currentSegmentations = segmentationService.getSegmentations();
2079
+ const segmentationId = await segmentationService.createSegmentationForDisplaySet(ptDisplaySet.displaySetInstanceUID, {
2080
+ label: `Segmentation ${currentSegmentations.length + 1}`
2081
+ });
2082
+
2083
+ // Add Segmentation to all toolGroupIds in the viewer
2084
+ const toolGroupIds = _getMatchedViewportsToolGroupIds();
2085
+ const representationType = LABELMAP;
2086
+ for (const toolGroupId of toolGroupIds) {
2087
+ const hydrateSegmentation = true;
2088
+ await segmentationService.addSegmentationRepresentationToToolGroup(toolGroupId, segmentationId, hydrateSegmentation, representationType);
2089
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
2090
+ }
2091
+ segmentationService.addSegment(segmentationId, {
2092
+ segmentIndex: 1,
2093
+ properties: {
2094
+ label: 'Segment 1'
2095
+ }
2096
+ });
2097
+ return segmentationId;
2098
+ },
2099
+ setSegmentationActiveForToolGroups: ({
2100
+ segmentationId
2101
+ }) => {
2102
+ const toolGroupIds = _getMatchedViewportsToolGroupIds();
2103
+ toolGroupIds.forEach(toolGroupId => {
2104
+ segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId);
2105
+ });
2106
+ },
2107
+ thresholdSegmentationByRectangleROITool: ({
2108
+ segmentationId,
2109
+ config,
2110
+ segmentIndex
2111
+ }) => {
2112
+ const segmentation = esm.segmentation.state.getSegmentation(segmentationId);
2113
+ const {
2114
+ representationData
2115
+ } = segmentation;
2116
+ const {
2117
+ displaySetMatchDetails: matchDetails
2118
+ } = hangingProtocolService.getMatchDetails();
2119
+ const volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to use
2120
+
2121
+ const ctDisplaySet = matchDetails.get('ctDisplaySet');
2122
+ const ctVolumeId = `${volumeLoaderScheme}:${ctDisplaySet.displaySetInstanceUID}`; // VolumeId with loader id + volume id
2123
+
2124
+ const {
2125
+ volumeId: segVolumeId
2126
+ } = representationData[LABELMAP];
2127
+ const {
2128
+ referencedVolumeId
2129
+ } = dist_esm.cache.getVolume(segVolumeId);
2130
+ const labelmapVolume = dist_esm.cache.getVolume(segmentationId);
2131
+ const referencedVolume = dist_esm.cache.getVolume(referencedVolumeId);
2132
+ const ctReferencedVolume = dist_esm.cache.getVolume(ctVolumeId);
2133
+ if (!referencedVolume) {
2134
+ throw new Error('No Reference volume found');
2135
+ }
2136
+ if (!labelmapVolume) {
2137
+ throw new Error('No Reference labelmap found');
2138
+ }
2139
+ const annotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2140
+ if (annotationUIDs.length === 0) {
2141
+ uiNotificationService.show({
2142
+ title: 'Commands Module',
2143
+ message: 'No ROIThreshold Tool is Selected',
2144
+ type: 'error'
2145
+ });
2146
+ return;
2147
+ }
2148
+ const {
2149
+ ptLower,
2150
+ ptUpper,
2151
+ ctLower,
2152
+ ctUpper
2153
+ } = getThresholdValue(annotationUIDs, [referencedVolume, ctReferencedVolume], config);
2154
+ return esm.utilities.segmentation.rectangleROIThresholdVolumeByRange(annotationUIDs, labelmapVolume, [{
2155
+ volume: referencedVolume,
2156
+ lower: ptLower,
2157
+ upper: ptUpper
2158
+ }, {
2159
+ volume: ctReferencedVolume,
2160
+ lower: ctLower,
2161
+ upper: ctUpper
2162
+ }], {
2163
+ overwrite: true,
2164
+ segmentIndex
2165
+ });
2166
+ },
2167
+ calculateSuvPeak: ({
2168
+ labelmap,
2169
+ segmentIndex
2170
+ }) => {
2171
+ const {
2172
+ referencedVolumeId
2173
+ } = labelmap;
2174
+ const referencedVolume = dist_esm.cache.getVolume(referencedVolumeId);
2175
+ const annotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2176
+ const annotations = annotationUIDs.map(annotationUID => esm.annotation.state.getAnnotation(annotationUID));
2177
+ const suvPeak = calculateSUVPeak(labelmap, referencedVolume, annotations, segmentIndex);
2178
+ return {
2179
+ suvPeak: suvPeak.mean,
2180
+ suvMax: suvPeak.max,
2181
+ suvMaxIJK: suvPeak.maxIJK,
2182
+ suvMaxLPS: suvPeak.maxLPS
2183
+ };
2184
+ },
2185
+ getLesionStats: ({
2186
+ labelmap,
2187
+ segmentIndex = 1
2188
+ }) => {
2189
+ const {
2190
+ scalarData,
2191
+ spacing
2192
+ } = labelmap;
2193
+ const {
2194
+ scalarData: referencedScalarData
2195
+ } = dist_esm.cache.getVolume(labelmap.referencedVolumeId);
2196
+ let segmentationMax = -Infinity;
2197
+ let segmentationMin = Infinity;
2198
+ let segmentationValues = [];
2199
+ let voxelCount = 0;
2200
+ for (let i = 0; i < scalarData.length; i++) {
2201
+ if (scalarData[i] === segmentIndex) {
2202
+ const value = referencedScalarData[i];
2203
+ segmentationValues.push(value);
2204
+ if (value > segmentationMax) {
2205
+ segmentationMax = value;
2206
+ }
2207
+ if (value < segmentationMin) {
2208
+ segmentationMin = value;
2209
+ }
2210
+ voxelCount++;
2211
+ }
2212
+ }
2213
+ const stats = {
2214
+ minValue: segmentationMin,
2215
+ maxValue: segmentationMax,
2216
+ meanValue: segmentationValues.reduce((a, b) => a + b, 0) / voxelCount,
2217
+ stdValue: Math.sqrt(segmentationValues.reduce((a, b) => a + b * b, 0) / voxelCount - segmentationValues.reduce((a, b) => a + b, 0) / voxelCount ** 2),
2218
+ volume: voxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3
2219
+ };
2220
+ return stats;
2221
+ },
2222
+ calculateLesionGlycolysis: ({
2223
+ lesionStats
2224
+ }) => {
2225
+ const {
2226
+ meanValue,
2227
+ volume
2228
+ } = lesionStats;
2229
+ return {
2230
+ lesionGlyoclysisStats: volume * meanValue
2231
+ };
2232
+ },
2233
+ calculateTMTV: ({
2234
+ segmentations
2235
+ }) => {
2236
+ const labelmaps = segmentations.map(s => segmentationService.getLabelmapVolume(s.id));
2237
+ if (!labelmaps.length) {
2238
+ return;
2239
+ }
2240
+ return utils_calculateTMTV(labelmaps);
2241
+ },
2242
+ exportTMTVReportCSV: ({
2243
+ segmentations,
2244
+ tmtv,
2245
+ config
2246
+ }) => {
2247
+ const segReport = commandsManager.runCommand('getSegmentationCSVReport', {
2248
+ segmentations
2249
+ });
2250
+ const tlg = actions.getTotalLesionGlycolysis({
2251
+ segmentations
2252
+ });
2253
+ const additionalReportRows = [{
2254
+ key: 'Total Metabolic Tumor Volume',
2255
+ value: {
2256
+ tmtv
2257
+ }
2258
+ }, {
2259
+ key: 'Total Lesion Glycolysis',
2260
+ value: {
2261
+ tlg: tlg.toFixed(4)
2262
+ }
2263
+ }, {
2264
+ key: 'Threshold Configuration',
2265
+ value: {
2266
+ ...config
2267
+ }
2268
+ }];
2269
+ createAndDownloadTMTVReport(segReport, additionalReportRows);
2270
+ },
2271
+ getTotalLesionGlycolysis: ({
2272
+ segmentations
2273
+ }) => {
2274
+ const labelmapVolumes = segmentations.map(s => segmentationService.getLabelmapVolume(s.id));
2275
+ let mergedLabelmap;
2276
+ // merge labelmap will through an error if labels maps are not the same size
2277
+ // or same direction or ....
2278
+ try {
2279
+ mergedLabelmap = esm.utilities.segmentation.createMergedLabelmapForIndex(labelmapVolumes);
2280
+ } catch (e) {
2281
+ console.error('commandsModule::getTotalLesionGlycolysis', e);
2282
+ return;
2283
+ }
2284
+
2285
+ // grabbing the first labelmap referenceVolume since it will be the same for all
2286
+ const {
2287
+ referencedVolumeId,
2288
+ spacing
2289
+ } = labelmapVolumes[0];
2290
+ if (!referencedVolumeId) {
2291
+ console.error('commandsModule::getTotalLesionGlycolysis:No referencedVolumeId found');
2292
+ }
2293
+ const ptVolume = dist_esm.cache.getVolume(referencedVolumeId);
2294
+ const mergedLabelData = mergedLabelmap.scalarData;
2295
+ if (mergedLabelData.length !== ptVolume.scalarData.length) {
2296
+ console.error('commandsModule::getTotalLesionGlycolysis:Labelmap and ptVolume are not the same size');
2297
+ }
2298
+ let suv = 0;
2299
+ let totalLesionVoxelCount = 0;
2300
+ for (let i = 0; i < mergedLabelData.length; i++) {
2301
+ // if not background
2302
+ if (mergedLabelData[i] !== 0) {
2303
+ suv += ptVolume.scalarData[i];
2304
+ totalLesionVoxelCount += 1;
2305
+ }
2306
+ }
2307
+
2308
+ // Average SUV for the merged labelmap
2309
+ const averageSuv = suv / totalLesionVoxelCount;
2310
+
2311
+ // total Lesion Glycolysis [suv * ml]
2312
+ return averageSuv * totalLesionVoxelCount * spacing[0] * spacing[1] * spacing[2] * 1e-3;
2313
+ },
2314
+ setStartSliceForROIThresholdTool: () => {
2315
+ const {
2316
+ viewport
2317
+ } = _getActiveViewportsEnabledElement();
2318
+ const {
2319
+ focalPoint,
2320
+ viewPlaneNormal
2321
+ } = viewport.getCamera();
2322
+ const selectedAnnotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2323
+ const annotationUID = selectedAnnotationUIDs[0];
2324
+ const annotation = esm.annotation.state.getAnnotation(annotationUID);
2325
+ const {
2326
+ handles
2327
+ } = annotation.data;
2328
+ const {
2329
+ points
2330
+ } = handles;
2331
+
2332
+ // get the current slice Index
2333
+ const sliceIndex = viewport.getCurrentImageIdIndex();
2334
+ annotation.data.startSlice = sliceIndex;
2335
+
2336
+ // distance between camera focal point and each point on the rectangle
2337
+ const newPoints = points.map(point => {
2338
+ const distance = gl_matrix_esm/* vec3.create */.eR.create();
2339
+ gl_matrix_esm/* vec3.subtract */.eR.subtract(distance, focalPoint, point);
2340
+ // distance in the direction of the viewPlaneNormal
2341
+ const distanceInViewPlane = gl_matrix_esm/* vec3.dot */.eR.dot(distance, viewPlaneNormal);
2342
+ // new point is current point minus distanceInViewPlane
2343
+ const newPoint = gl_matrix_esm/* vec3.create */.eR.create();
2344
+ gl_matrix_esm/* vec3.scaleAndAdd */.eR.scaleAndAdd(newPoint, point, viewPlaneNormal, distanceInViewPlane);
2345
+ return newPoint;
2346
+ //
2347
+ });
2348
+ handles.points = newPoints;
2349
+ // IMPORTANT: invalidate the toolData for the cached stat to get updated
2350
+ // and re-calculate the projection points
2351
+ annotation.invalidated = true;
2352
+ viewport.render();
2353
+ },
2354
+ setEndSliceForROIThresholdTool: () => {
2355
+ const {
2356
+ viewport
2357
+ } = _getActiveViewportsEnabledElement();
2358
+ const selectedAnnotationUIDs = esm.annotation.selection.getAnnotationsSelectedByToolName(RECTANGLE_ROI_THRESHOLD_MANUAL);
2359
+ const annotationUID = selectedAnnotationUIDs[0];
2360
+ const annotation = esm.annotation.state.getAnnotation(annotationUID);
2361
+
2362
+ // get the current slice Index
2363
+ const sliceIndex = viewport.getCurrentImageIdIndex();
2364
+ annotation.data.endSlice = sliceIndex;
2365
+
2366
+ // IMPORTANT: invalidate the toolData for the cached stat to get updated
2367
+ // and re-calculate the projection points
2368
+ annotation.invalidated = true;
2369
+ viewport.render();
2370
+ },
2371
+ createTMTVRTReport: () => {
2372
+ // get all Rectangle ROI annotation
2373
+ const stateManager = esm.annotation.state.getAnnotationManager();
2374
+ const annotations = [];
2375
+ Object.keys(stateManager.annotations).forEach(frameOfReferenceUID => {
2376
+ const forAnnotations = stateManager.annotations[frameOfReferenceUID];
2377
+ const ROIAnnotations = forAnnotations[RECTANGLE_ROI_THRESHOLD_MANUAL];
2378
+ annotations.push(...ROIAnnotations);
2379
+ });
2380
+ commandsManager.runCommand('exportRTReportForAnnotations', {
2381
+ annotations
2382
+ });
2383
+ },
2384
+ getSegmentationCSVReport: ({
2385
+ segmentations
2386
+ }) => {
2387
+ if (!segmentations || !segmentations.length) {
2388
+ segmentations = segmentationService.getSegmentations();
2389
+ }
2390
+ let report = {};
2391
+ for (const segmentation of segmentations) {
2392
+ const {
2393
+ id,
2394
+ label,
2395
+ cachedStats: data
2396
+ } = segmentation;
2397
+ const segReport = {
2398
+ id,
2399
+ label
2400
+ };
2401
+ if (!data) {
2402
+ report[id] = segReport;
2403
+ continue;
2404
+ }
2405
+ Object.keys(data).forEach(key => {
2406
+ if (typeof data[key] !== 'object') {
2407
+ segReport[key] = data[key];
2408
+ } else {
2409
+ Object.keys(data[key]).forEach(subKey => {
2410
+ const newKey = `${key}_${subKey}`;
2411
+ segReport[newKey] = data[key][subKey];
2412
+ });
2413
+ }
2414
+ });
2415
+ const labelmapVolume = segmentationService.getLabelmapVolume(id);
2416
+ if (!labelmapVolume) {
2417
+ report[id] = segReport;
2418
+ continue;
2419
+ }
2420
+ const referencedVolumeId = labelmapVolume.referencedVolumeId;
2421
+ segReport.referencedVolumeId = referencedVolumeId;
2422
+ const referencedVolume = segmentationService.getLabelmapVolume(referencedVolumeId);
2423
+ if (!referencedVolume) {
2424
+ report[id] = segReport;
2425
+ continue;
2426
+ }
2427
+ if (!referencedVolume.imageIds || !referencedVolume.imageIds.length) {
2428
+ report[id] = segReport;
2429
+ continue;
2430
+ }
2431
+ const firstImageId = referencedVolume.imageIds[0];
2432
+ const instance = core_src/* default.classes */.Ay.classes.MetadataProvider.get('instance', firstImageId);
2433
+ if (!instance) {
2434
+ report[id] = segReport;
2435
+ continue;
2436
+ }
2437
+ report[id] = {
2438
+ ...segReport,
2439
+ PatientID: instance.PatientID,
2440
+ PatientName: instance.PatientName.Alphabetic,
2441
+ StudyInstanceUID: instance.StudyInstanceUID,
2442
+ SeriesInstanceUID: instance.SeriesInstanceUID,
2443
+ StudyDate: instance.StudyDate
2444
+ };
2445
+ }
2446
+ return report;
2447
+ },
2448
+ exportRTReportForAnnotations: ({
2449
+ annotations
2450
+ }) => {
2451
+ RTStructureSet(annotations);
2452
+ },
2453
+ setFusionPTColormap: ({
2454
+ toolGroupId,
2455
+ colormap
2456
+ }) => {
2457
+ const toolGroup = toolGroupService.getToolGroup(toolGroupId);
2458
+ const {
2459
+ viewportMatchDetails
2460
+ } = hangingProtocolService.getMatchDetails();
2461
+ const ptDisplaySet = actions.getMatchingPTDisplaySet({
2462
+ viewportMatchDetails
2463
+ });
2464
+ if (!ptDisplaySet) {
2465
+ return;
2466
+ }
2467
+ const fusionViewportIds = toolGroup.getViewportIds();
2468
+ let viewports = [];
2469
+ fusionViewportIds.forEach(viewportId => {
2470
+ commandsManager.runCommand('setViewportColormap', {
2471
+ viewportId,
2472
+ displaySetInstanceUID: ptDisplaySet.displaySetInstanceUID,
2473
+ colormap: {
2474
+ name: colormap
2475
+ }
2476
+ });
2477
+ viewports.push(cornerstoneViewportService.getCornerstoneViewport(viewportId));
2478
+ });
2479
+ viewports.forEach(viewport => {
2480
+ viewport.render();
2481
+ });
2482
+ }
2483
+ };
2484
+ const definitions = {
2485
+ setEndSliceForROIThresholdTool: {
2486
+ commandFn: actions.setEndSliceForROIThresholdTool
2487
+ },
2488
+ setStartSliceForROIThresholdTool: {
2489
+ commandFn: actions.setStartSliceForROIThresholdTool
2490
+ },
2491
+ getMatchingPTDisplaySet: {
2492
+ commandFn: actions.getMatchingPTDisplaySet
2493
+ },
2494
+ getPTMetadata: {
2495
+ commandFn: actions.getPTMetadata
2496
+ },
2497
+ createNewLabelmapFromPT: {
2498
+ commandFn: actions.createNewLabelmapFromPT
2499
+ },
2500
+ setSegmentationActiveForToolGroups: {
2501
+ commandFn: actions.setSegmentationActiveForToolGroups
2502
+ },
2503
+ thresholdSegmentationByRectangleROITool: {
2504
+ commandFn: actions.thresholdSegmentationByRectangleROITool
2505
+ },
2506
+ getTotalLesionGlycolysis: {
2507
+ commandFn: actions.getTotalLesionGlycolysis
2508
+ },
2509
+ calculateSuvPeak: {
2510
+ commandFn: actions.calculateSuvPeak
2511
+ },
2512
+ getLesionStats: {
2513
+ commandFn: actions.getLesionStats
2514
+ },
2515
+ calculateTMTV: {
2516
+ commandFn: actions.calculateTMTV
2517
+ },
2518
+ exportTMTVReportCSV: {
2519
+ commandFn: actions.exportTMTVReportCSV
2520
+ },
2521
+ createTMTVRTReport: {
2522
+ commandFn: actions.createTMTVRTReport
2523
+ },
2524
+ getSegmentationCSVReport: {
2525
+ commandFn: actions.getSegmentationCSVReport
2526
+ },
2527
+ exportRTReportForAnnotations: {
2528
+ commandFn: actions.exportRTReportForAnnotations
2529
+ },
2530
+ setFusionPTColormap: {
2531
+ commandFn: actions.setFusionPTColormap
2532
+ }
2533
+ };
2534
+ return {
2535
+ actions,
2536
+ definitions,
2537
+ defaultContext: 'TMTV:CORNERSTONE'
2538
+ };
2539
+ };
2540
+ /* harmony default export */ const src_commandsModule = (commandsModule);
2541
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/PanelROIThresholdSegmentation/ROIThresholdConfiguration.tsx
2542
+
2543
+
2544
+
2545
+ const ROI_STAT = 'roi_stat';
2546
+ const RANGE = 'range';
2547
+ const options = [{
2548
+ value: ROI_STAT,
2549
+ label: 'Max',
2550
+ placeHolder: 'Max'
2551
+ }, {
2552
+ value: RANGE,
2553
+ label: 'Range',
2554
+ placeHolder: 'Range'
2555
+ }];
2556
+ function ROIThresholdConfiguration({
2557
+ config,
2558
+ dispatch,
2559
+ runCommand
2560
+ }) {
2561
+ const {
2562
+ t
2563
+ } = (0,es/* useTranslation */.Bd)('ROIThresholdConfiguration');
2564
+ return /*#__PURE__*/react.createElement("div", {
2565
+ className: "bg-primary-dark flex flex-col space-y-4"
2566
+ }, /*#__PURE__*/react.createElement("div", {
2567
+ className: "flex items-end space-x-2"
2568
+ }, /*#__PURE__*/react.createElement("div", {
2569
+ className: "flex w-1/2 flex-col"
2570
+ }, /*#__PURE__*/react.createElement(src/* Select */.l6, {
2571
+ label: t('Strategy'),
2572
+ closeMenuOnSelect: true,
2573
+ className: "border-primary-main mr-2 bg-black text-white ",
2574
+ options: options,
2575
+ placeholder: options.find(option => option.value === config.strategy).placeHolder,
2576
+ value: config.strategy,
2577
+ onChange: ({
2578
+ value
2579
+ }) => {
2580
+ dispatch({
2581
+ type: 'setStrategy',
2582
+ payload: {
2583
+ strategy: value
2584
+ }
2585
+ });
2586
+ }
2587
+ })), /*#__PURE__*/react.createElement("div", {
2588
+ className: "w-1/2"
2589
+ }, /*#__PURE__*/react.createElement(src/* LegacyButtonGroup */.xA, null, /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
2590
+ size: "initial",
2591
+ className: "px-2 py-2 text-base text-white",
2592
+ color: "primaryLight",
2593
+ variant: "outlined",
2594
+ onClick: () => runCommand('setStartSliceForROIThresholdTool')
2595
+ }, t('Start')), /*#__PURE__*/react.createElement(src/* LegacyButton */._H, {
2596
+ size: "initial",
2597
+ color: "primaryLight",
2598
+ variant: "outlined",
2599
+ className: "px-2 py-2 text-base text-white",
2600
+ onClick: () => runCommand('setEndSliceForROIThresholdTool')
2601
+ }, t('End'))))), config.strategy === ROI_STAT && /*#__PURE__*/react.createElement(src/* Input */.pd, {
2602
+ label: t('Percentage of Max SUV'),
2603
+ labelClassName: "text-[13px] font-inter text-white",
2604
+ className: "border-primary-main bg-black",
2605
+ type: "text",
2606
+ containerClassName: "mr-2",
2607
+ value: config.weight,
2608
+ onChange: e => {
2609
+ dispatch({
2610
+ type: 'setWeight',
2611
+ payload: {
2612
+ weight: e.target.value
2613
+ }
2614
+ });
2615
+ }
2616
+ }), config.strategy !== ROI_STAT && /*#__PURE__*/react.createElement("div", {
2617
+ className: "mr-2 text-sm"
2618
+ }, /*#__PURE__*/react.createElement("table", null, /*#__PURE__*/react.createElement("tbody", null, /*#__PURE__*/react.createElement("tr", {
2619
+ className: "mt-2"
2620
+ }, /*#__PURE__*/react.createElement("td", {
2621
+ className: "pr-4",
2622
+ colSpan: "3"
2623
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
2624
+ className: "font-inter text-[13px] text-white",
2625
+ text: "Lower & Upper Ranges"
2626
+ }))), /*#__PURE__*/react.createElement("tr", {
2627
+ className: "mt-2"
2628
+ }, /*#__PURE__*/react.createElement("td", {
2629
+ className: "pr-4 pt-2 text-center"
2630
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
2631
+ className: "text-white",
2632
+ text: "CT"
2633
+ })), /*#__PURE__*/react.createElement("td", null, /*#__PURE__*/react.createElement("div", {
2634
+ className: "flex justify-between"
2635
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
2636
+ label: t(''),
2637
+ labelClassName: "text-white",
2638
+ className: "border-primary-main mt-2 bg-black",
2639
+ type: "text",
2640
+ containerClassName: "mr-2",
2641
+ value: config.ctLower,
2642
+ onChange: e => {
2643
+ dispatch({
2644
+ type: 'setThreshold',
2645
+ payload: {
2646
+ ctLower: e.target.value
2647
+ }
2648
+ });
2649
+ }
2650
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
2651
+ label: t(''),
2652
+ labelClassName: "text-white",
2653
+ className: "border-primary-main mt-2 bg-black",
2654
+ type: "text",
2655
+ containerClassName: "mr-2",
2656
+ value: config.ctUpper,
2657
+ onChange: e => {
2658
+ dispatch({
2659
+ type: 'setThreshold',
2660
+ payload: {
2661
+ ctUpper: e.target.value
2662
+ }
2663
+ });
2664
+ }
2665
+ })))), /*#__PURE__*/react.createElement("tr", null, /*#__PURE__*/react.createElement("td", {
2666
+ className: "pr-4 pt-2 text-center"
2667
+ }, /*#__PURE__*/react.createElement(src/* Label */.JU, {
2668
+ className: "text-white",
2669
+ text: "PT"
2670
+ })), /*#__PURE__*/react.createElement("td", null, /*#__PURE__*/react.createElement("div", {
2671
+ className: "flex justify-between"
2672
+ }, /*#__PURE__*/react.createElement(src/* Input */.pd, {
2673
+ label: t(''),
2674
+ labelClassName: "text-white",
2675
+ className: "border-primary-main mt-2 bg-black",
2676
+ type: "text",
2677
+ containerClassName: "mr-2",
2678
+ value: config.ptLower,
2679
+ onChange: e => {
2680
+ dispatch({
2681
+ type: 'setThreshold',
2682
+ payload: {
2683
+ ptLower: e.target.value
2684
+ }
2685
+ });
2686
+ }
2687
+ }), /*#__PURE__*/react.createElement(src/* Input */.pd, {
2688
+ label: t(''),
2689
+ labelClassName: "text-white",
2690
+ className: "border-primary-main mt-2 bg-black",
2691
+ type: "text",
2692
+ containerClassName: "mr-2",
2693
+ value: config.ptUpper,
2694
+ onChange: e => {
2695
+ dispatch({
2696
+ type: 'setThreshold',
2697
+ payload: {
2698
+ ptUpper: e.target.value
2699
+ }
2700
+ });
2701
+ }
2702
+ }))))))));
2703
+ }
2704
+ /* harmony default export */ const PanelROIThresholdSegmentation_ROIThresholdConfiguration = (ROIThresholdConfiguration);
2705
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/Panels/RectangleROIOptions.tsx
2706
+
2707
+
2708
+
2709
+
2710
+ const LOWER_CT_THRESHOLD_DEFAULT = -1024;
2711
+ const UPPER_CT_THRESHOLD_DEFAULT = 1024;
2712
+ const LOWER_PT_THRESHOLD_DEFAULT = 2.5;
2713
+ const UPPER_PT_THRESHOLD_DEFAULT = 100;
2714
+ const WEIGHT_DEFAULT = 0.41; // a default weight for suv max often used in the literature
2715
+ const DEFAULT_STRATEGY = ROI_STAT;
2716
+ function reducer(state, action) {
2717
+ const {
2718
+ payload
2719
+ } = action;
2720
+ const {
2721
+ strategy,
2722
+ ctLower,
2723
+ ctUpper,
2724
+ ptLower,
2725
+ ptUpper,
2726
+ weight
2727
+ } = payload;
2728
+ switch (action.type) {
2729
+ case 'setStrategy':
2730
+ return {
2731
+ ...state,
2732
+ strategy
2733
+ };
2734
+ case 'setThreshold':
2735
+ return {
2736
+ ...state,
2737
+ ctLower: ctLower ? ctLower : state.ctLower,
2738
+ ctUpper: ctUpper ? ctUpper : state.ctUpper,
2739
+ ptLower: ptLower ? ptLower : state.ptLower,
2740
+ ptUpper: ptUpper ? ptUpper : state.ptUpper
2741
+ };
2742
+ case 'setWeight':
2743
+ return {
2744
+ ...state,
2745
+ weight
2746
+ };
2747
+ default:
2748
+ return state;
2749
+ }
2750
+ }
2751
+ function RectangleROIOptions({
2752
+ servicesManager,
2753
+ commandsManager
2754
+ }) {
2755
+ const {
2756
+ segmentationService
2757
+ } = servicesManager.services;
2758
+ const [selectedSegmentationId, setSelectedSegmentationId] = (0,react.useState)(null);
2759
+ const runCommand = (0,react.useCallback)((commandName, commandOptions = {}) => {
2760
+ return commandsManager.runCommand(commandName, commandOptions);
2761
+ }, [commandsManager]);
2762
+ const [config, dispatch] = (0,react.useReducer)(reducer, {
2763
+ strategy: DEFAULT_STRATEGY,
2764
+ ctLower: LOWER_CT_THRESHOLD_DEFAULT,
2765
+ ctUpper: UPPER_CT_THRESHOLD_DEFAULT,
2766
+ ptLower: LOWER_PT_THRESHOLD_DEFAULT,
2767
+ ptUpper: UPPER_PT_THRESHOLD_DEFAULT,
2768
+ weight: WEIGHT_DEFAULT
2769
+ });
2770
+ const handleROIThresholding = (0,react.useCallback)(() => {
2771
+ const segmentationId = selectedSegmentationId;
2772
+ const segmentation = segmentationService.getSegmentation(segmentationId);
2773
+ const activeSegmentIndex = esm.segmentation.segmentIndex.getActiveSegmentIndex(segmentationId);
2774
+
2775
+ // run the threshold based on the active segment index
2776
+ // Todo: later find a way to associate each rectangle with a segment (e.g., maybe with color?)
2777
+ const labelmap = runCommand('thresholdSegmentationByRectangleROITool', {
2778
+ segmentationId,
2779
+ config,
2780
+ segmentIndex: activeSegmentIndex
2781
+ });
2782
+
2783
+ // re-calculating the cached stats for the active segmentation
2784
+ const updatedPerSegmentCachedStats = {};
2785
+ segmentation.segments = segmentation.segments.map(segment => {
2786
+ if (!segment || !segment.segmentIndex) {
2787
+ return segment;
2788
+ }
2789
+ const segmentIndex = segment.segmentIndex;
2790
+ const lesionStats = runCommand('getLesionStats', {
2791
+ labelmap,
2792
+ segmentIndex
2793
+ });
2794
+ const suvPeak = runCommand('calculateSuvPeak', {
2795
+ labelmap,
2796
+ segmentIndex
2797
+ });
2798
+ const lesionGlyoclysisStats = lesionStats.volume * lesionStats.meanValue;
2799
+
2800
+ // update segDetails with the suv peak for the active segmentation
2801
+ const cachedStats = {
2802
+ lesionStats,
2803
+ suvPeak,
2804
+ lesionGlyoclysisStats
2805
+ };
2806
+ segment.cachedStats = cachedStats;
2807
+ segment.displayText = [`SUV Peak: ${suvPeak.suvPeak.toFixed(2)}`, `Volume: ${lesionStats.volume.toFixed(2)} mm3`];
2808
+ updatedPerSegmentCachedStats[segmentIndex] = cachedStats;
2809
+ return segment;
2810
+ });
2811
+ const notYetUpdatedAtSource = true;
2812
+ const segmentations = segmentationService.getSegmentations();
2813
+ const tmtv = runCommand('calculateTMTV', {
2814
+ segmentations
2815
+ });
2816
+ segmentation.cachedStats = Object.assign(segmentation.cachedStats, updatedPerSegmentCachedStats, {
2817
+ tmtv: {
2818
+ value: tmtv.toFixed(3),
2819
+ config: {
2820
+ ...config
2821
+ }
2822
+ }
2823
+ });
2824
+ segmentationService.addOrUpdateSegmentation({
2825
+ ...segmentation
2826
+ }, false,
2827
+ // don't suppress events
2828
+ notYetUpdatedAtSource);
2829
+ }, [selectedSegmentationId, config]);
2830
+ (0,react.useEffect)(() => {
2831
+ const segmentations = segmentationService.getSegmentations();
2832
+ if (!segmentations.length) {
2833
+ return;
2834
+ }
2835
+ const isActive = segmentations.find(seg => seg.isActive);
2836
+ setSelectedSegmentationId(isActive.id);
2837
+ }, []);
2838
+
2839
+ /**
2840
+ * Update UI based on segmentation changes (added, removed, updated)
2841
+ */
2842
+ (0,react.useEffect)(() => {
2843
+ // ~~ Subscription
2844
+ const added = segmentationService.EVENTS.SEGMENTATION_ADDED;
2845
+ const updated = segmentationService.EVENTS.SEGMENTATION_UPDATED;
2846
+ const subscriptions = [];
2847
+ [added, updated].forEach(evt => {
2848
+ const {
2849
+ unsubscribe
2850
+ } = segmentationService.subscribe(evt, () => {
2851
+ const segmentations = segmentationService.getSegmentations();
2852
+ if (!segmentations.length) {
2853
+ return;
2854
+ }
2855
+ const isActive = segmentations.find(seg => seg.isActive);
2856
+ setSelectedSegmentationId(isActive.id);
2857
+ });
2858
+ subscriptions.push(unsubscribe);
2859
+ });
2860
+ return () => {
2861
+ subscriptions.forEach(unsub => {
2862
+ unsub();
2863
+ });
2864
+ };
2865
+ }, []);
2866
+ (0,react.useEffect)(() => {
2867
+ const {
2868
+ unsubscribe
2869
+ } = segmentationService.subscribe(segmentationService.EVENTS.SEGMENTATION_REMOVED, () => {
2870
+ const segmentations = segmentationService.getSegmentations();
2871
+ if (segmentations.length > 0) {
2872
+ setSelectedSegmentationId(segmentations[0].id);
2873
+ handleROIThresholding();
2874
+ } else {
2875
+ setSelectedSegmentationId(null);
2876
+ handleROIThresholding();
2877
+ }
2878
+ });
2879
+ return () => {
2880
+ unsubscribe();
2881
+ };
2882
+ }, []);
2883
+ return /*#__PURE__*/react.createElement("div", {
2884
+ className: "invisible-scrollbar mb-2 flex flex-col overflow-y-auto overflow-x-hidden"
2885
+ }, /*#__PURE__*/react.createElement(PanelROIThresholdSegmentation_ROIThresholdConfiguration, {
2886
+ config: config,
2887
+ dispatch: dispatch,
2888
+ runCommand: runCommand
2889
+ }), selectedSegmentationId !== null && /*#__PURE__*/react.createElement(src/* Button */.$n, {
2890
+ className: "mt-2 !h-[26px] !w-[75px]",
2891
+ onClick: handleROIThresholding
2892
+ }, "Run"));
2893
+ }
2894
+ /* harmony default export */ const Panels_RectangleROIOptions = (RectangleROIOptions);
2895
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/getToolbarModule.tsx
2896
+
2897
+ function getToolbarModule({
2898
+ commandsManager,
2899
+ servicesManager
2900
+ }) {
2901
+ return [{
2902
+ name: 'tmtv.RectangleROIThresholdOptions',
2903
+ defaultComponent: () => Panels_RectangleROIOptions({
2904
+ commandsManager,
2905
+ servicesManager
2906
+ })
2907
+ }];
2908
+ }
2909
+ ;// CONCATENATED MODULE: ../../../extensions/tmtv/src/index.tsx
2910
+
2911
+
2912
+
2913
+
2914
+
2915
+
2916
+
2917
+ /**
2918
+ *
2919
+ */
2920
+ const tmtvExtension = {
2921
+ /**
2922
+ * Only required property. Should be a unique value across all extensions.
2923
+ */
2924
+ id: id,
2925
+ preRegistration({
2926
+ servicesManager,
2927
+ commandsManager,
2928
+ extensionManager,
2929
+ configuration = {}
2930
+ }) {
2931
+ init({
2932
+ servicesManager,
2933
+ commandsManager,
2934
+ extensionManager,
2935
+ configuration
2936
+ });
2937
+ },
2938
+ getToolbarModule: getToolbarModule,
2939
+ getPanelModule: src_getPanelModule,
2940
+ getHangingProtocolModule: src_getHangingProtocolModule,
2941
+ getCommandsModule({
2942
+ servicesManager,
2943
+ commandsManager,
2944
+ extensionManager
2945
+ }) {
2946
+ return src_commandsModule({
2947
+ servicesManager,
2948
+ commandsManager,
2949
+ extensionManager
2950
+ });
2951
+ }
2952
+ };
2953
+ /* harmony default export */ const tmtv_src = (tmtvExtension);
2954
+
2955
+ /***/ })
2956
+
2957
+ }]);