@ohif/app 3.8.0-beta.8 → 3.8.0-beta.80

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 (99) hide show
  1. package/dist/{220.bundle.f7e1c96c94245e70f2be.js → 109.bundle.b4fee2a22b622839baf5.js} +4466 -3715
  2. package/dist/{471.bundle.49c8d281adbae4a2c4df.js → 121.bundle.787f5a848ed632a4d5fc.js} +90 -112
  3. package/dist/141.bundle.556b4c1e4cab770417ac.js +8620 -0
  4. package/dist/{687.bundle.9065db35c01823286f08.js → 164.bundle.d4598e491783753a8b6b.js} +22 -38
  5. package/dist/17dd54813d5acc10bf8f.wasm +0 -0
  6. package/dist/183.bundle.72bf18ad23ee6624986d.js +30394 -0
  7. package/dist/{506.bundle.5731bb4349e266491225.js → 188.bundle.b80554ec7df7dcd435a5.js} +23 -28
  8. package/dist/{342.bundle.e7c3d500f86fdfcc62b5.js → 206.bundle.f957e0d1cdff66dbac69.js} +1963 -1142
  9. package/dist/20fc4c659b85ccd2a9c0.wasm +0 -0
  10. package/dist/217.bundle.be1cc412f8e26be87d21.js +115079 -0
  11. package/dist/{451.bundle.57c21db5d003c75e9d61.js → 295.bundle.6f734abf8fa85b1a310d.js} +107 -127
  12. package/dist/{125.bundle.253395f320b72180da63.js → 297.bundle.194d8985ab974839b5b6.js} +7 -8
  13. package/dist/{19.bundle.f77c5787b6d8ac0b638b.js → 325.bundle.84909a08305556e9f924.js} +479 -371
  14. package/dist/335.bundle.c39d4aefe33aecab958f.js +2590 -0
  15. package/dist/{202.bundle.d3490836f71e001dd30f.js → 342.bundle.e6d0bba29351b5650a8c.js} +566 -868
  16. package/dist/{776.bundle.a2dedb405a12ffd7699b.js → 41.bundle.7c943bb857ed37831905.js} +7295 -3536
  17. package/dist/422.bundle.bd6529c536f59807fbee.js +881 -0
  18. package/dist/{957.bundle.9ea4506963ef8b2d84ba.js → 433.bundle.4c77c1fe8fc90ac14218.js} +14737 -27555
  19. package/dist/445.bundle.38c6d2af64e41cd7c614.js +7835 -0
  20. package/dist/{126.bundle.6e7111d58bcc937ffd80.js → 448.bundle.deedeff5744e77510734.js} +362 -430
  21. package/dist/487.bundle.7890ca42826941ebcd60.js +1875 -0
  22. package/dist/{886.bundle.c8dd3ecc42a4253de278.js → 530.bundle.7c94543955552475c56a.js} +98 -127
  23. package/dist/{663.bundle.d7be28450db14266cdd0.js → 540.bundle.079d43a6717e95c24392.js} +225 -188
  24. package/dist/{250.bundle.aea3335667054bdefe36.js → 544.bundle.1c1f57118560046649c1.js} +37 -62
  25. package/dist/574.bundle.be075ac52fb52b442a8b.js +2641 -0
  26. package/dist/{181.css → 574.css} +1 -1
  27. package/dist/{410.bundle.15c855b0ff4a1a674fb8.js → 594.bundle.0b1165661dd638820082.js} +183 -221
  28. package/dist/{221.bundle.aef554202c58483cb34e.js → 633.bundle.c1658e76f104cbd14cab.js} +349 -552
  29. package/dist/{774.bundle.4b2dc46a35012b898e1a.js → 644.bundle.1e77691d2eeb96a423b0.js} +1852 -8945
  30. package/dist/699.bundle.4f01772e7ce6637de339.js +768 -0
  31. package/dist/702.bundle.963481fbf871984b646f.js +8426 -0
  32. package/dist/722.bundle.afab1fe6bfcd569130ac.js +1083 -0
  33. package/dist/{359.bundle.45ecb3d28e8c22142606.js → 724.bundle.e5794460c391ee9cba2c.js} +164 -259
  34. package/dist/{757.bundle.ec8301d8e70d2b990f65.js → 726.bundle.0b3d9277d22fe7e15b89.js} +512 -879
  35. package/dist/{530.bundle.a03b6f942ace3e1baa1e.js → 835.bundle.15aff0b7433bb0dd6d6d.js} +37 -30
  36. package/dist/{822.bundle.82cdc418f8f56da6060b.js → 862.bundle.c0ee6e1d4d97e1353213.js} +77 -96
  37. package/dist/{236.bundle.4e9924934a747afac132.js → 889.bundle.7858e4b7ca1a2b12b64f.js} +207 -199
  38. package/dist/{281.bundle.deb7492d143e7768d8bf.js → 905.bundle.170908fe660fc6b40649.js} +157 -124
  39. package/dist/{814.bundle.c8c951d20039b63b865a.js → 907.bundle.dee4e30420caf07caea6.js} +16 -30
  40. package/dist/{417.bundle.af0a207c29b109f84159.js → 931.bundle.d270a1fda9a2836c3cc5.js} +26 -26
  41. package/dist/{686.bundle.dccef1f36e4bc79bcc48.js → 939.bundle.9d93b2e47c52338747a2.js} +7 -8
  42. package/dist/94.bundle.c452d9b0645277c2cf4e.js +784 -0
  43. package/dist/{12.bundle.b5ca13e5363f170ecb3b.js → 961.bundle.aaaaaba0ec015a3b85d8.js} +20 -33
  44. package/dist/app-config.js +1 -0
  45. package/dist/{app.bundle.a978edc59b9d82f2eb22.js → app.bundle.6c090a2d6d3ccc97a81d.js} +183240 -87650
  46. package/dist/app.bundle.css +16 -13
  47. package/dist/assets/images/CT-AAA.png +0 -0
  48. package/dist/assets/images/CT-AAA2.png +0 -0
  49. package/dist/assets/images/CT-Air.png +0 -0
  50. package/dist/assets/images/CT-Bone.png +0 -0
  51. package/dist/assets/images/CT-Bones.png +0 -0
  52. package/dist/assets/images/CT-Cardiac.png +0 -0
  53. package/dist/assets/images/CT-Cardiac2.png +0 -0
  54. package/dist/assets/images/CT-Cardiac3.png +0 -0
  55. package/dist/assets/images/CT-Chest-Contrast-Enhanced.png +0 -0
  56. package/dist/assets/images/CT-Chest-Vessels.png +0 -0
  57. package/dist/assets/images/CT-Coronary-Arteries-2.png +0 -0
  58. package/dist/assets/images/CT-Coronary-Arteries-3.png +0 -0
  59. package/dist/assets/images/CT-Coronary-Arteries.png +0 -0
  60. package/dist/assets/images/CT-Cropped-Volume-Bone.png +0 -0
  61. package/dist/assets/images/CT-Fat.png +0 -0
  62. package/dist/assets/images/CT-Liver-Vasculature.png +0 -0
  63. package/dist/assets/images/CT-Lung.png +0 -0
  64. package/dist/assets/images/CT-MIP.png +0 -0
  65. package/dist/assets/images/CT-Muscle.png +0 -0
  66. package/dist/assets/images/CT-Pulmonary-Arteries.png +0 -0
  67. package/dist/assets/images/CT-Soft-Tissue.png +0 -0
  68. package/dist/assets/images/DTI-FA-Brain.png +0 -0
  69. package/dist/assets/images/MR-Angio.png +0 -0
  70. package/dist/assets/images/MR-Default.png +0 -0
  71. package/dist/assets/images/MR-MIP.png +0 -0
  72. package/dist/assets/images/MR-T2-Brain.png +0 -0
  73. package/dist/assets/images/VolumeRendering.png +0 -0
  74. package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
  75. package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
  76. package/dist/{dicom-microscopy-viewer.bundle.2c146384eb9466d02ff8.js → dicom-microscopy-viewer.bundle.d3a56dc9f62df5e11019.js} +3 -3
  77. package/dist/histogram-worker.bundle.829e14ec12c2b41a4323.js +359 -0
  78. package/dist/index.html +1 -1
  79. package/dist/{index.worker.e62ecca63f1a2e124230.worker.js → index.worker.64c896c4316fcd506666.worker.js} +2 -2
  80. package/dist/index.worker.64c896c4316fcd506666.worker.js.map +1 -0
  81. package/dist/polySeg.bundle.63011312c3c79e717ea9.js +249 -0
  82. package/dist/serve.json +12 -0
  83. package/dist/sw.js +1 -1
  84. package/package.json +26 -22
  85. package/dist/181.bundle.a62b9f0ec692299acb35.js +0 -1527
  86. package/dist/23.bundle.e008ad788170f2ed5569.js +0 -900
  87. package/dist/604.bundle.a51f83e64004bca5f497.js +0 -1848
  88. package/dist/613.bundle.9e7072e5b575354fe51e.js +0 -532
  89. package/dist/743.bundle.489f7df3a089d4d374e1.js +0 -78007
  90. package/dist/75788f12450d4c5ed494.wasm +0 -0
  91. package/dist/775.bundle.2285e7e0e67878948c0d.js +0 -1009
  92. package/dist/788.bundle.207ac23c0dfa70cbe3fb.js +0 -2682
  93. package/dist/82.bundle.d6fdcca0f67540bb226a.js +0 -1049
  94. package/dist/index.worker.e62ecca63f1a2e124230.worker.js.map +0 -1
  95. /package/dist/{19.css → 325.css} +0 -0
  96. /package/dist/{776.css → 41.css} +0 -0
  97. /package/dist/{579.css → 481.css} +0 -0
  98. /package/dist/{250.css → 544.css} +0 -0
  99. /package/dist/{221.css → 633.css} +0 -0
@@ -0,0 +1,2641 @@
1
+ "use strict";
2
+ (globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[574],{
3
+
4
+ /***/ 71574:
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 */ Viewport_OHIFCornerstoneViewport)
13
+ });
14
+
15
+ // EXTERNAL MODULE: ../../../node_modules/react/index.js
16
+ var react = __webpack_require__(41766);
17
+ // EXTERNAL MODULE: ../../../node_modules/react-resize-detector/build/index.esm.js
18
+ var index_esm = __webpack_require__(78668);
19
+ // EXTERNAL MODULE: ../../../node_modules/prop-types/index.js
20
+ var prop_types = __webpack_require__(11374);
21
+ var prop_types_default = /*#__PURE__*/__webpack_require__.n(prop_types);
22
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/tools/dist/esm/index.js + 18 modules
23
+ var esm = __webpack_require__(24542);
24
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/core/dist/esm/index.js + 327 modules
25
+ var dist_esm = __webpack_require__(44656);
26
+ // EXTERNAL MODULE: ../../core/src/index.ts + 70 modules
27
+ var src = __webpack_require__(55411);
28
+ // EXTERNAL MODULE: ../../ui/src/index.js + 785 modules
29
+ var ui_src = __webpack_require__(5085);
30
+ // EXTERNAL MODULE: ../../../extensions/cornerstone/src/state.ts
31
+ var state = __webpack_require__(71353);
32
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.css
33
+ // extracted by mini-css-extract-plugin
34
+
35
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx
36
+
37
+
38
+
39
+
40
+
41
+ function CornerstoneImageScrollbar({
42
+ viewportData,
43
+ viewportId,
44
+ element,
45
+ imageSliceData,
46
+ setImageSliceData,
47
+ scrollbarHeight,
48
+ servicesManager
49
+ }) {
50
+ const {
51
+ cineService,
52
+ cornerstoneViewportService
53
+ } = servicesManager.services;
54
+ const onImageScrollbarChange = (imageIndex, viewportId) => {
55
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
56
+ const {
57
+ isCineEnabled
58
+ } = cineService.getState();
59
+ if (isCineEnabled) {
60
+ // on image scrollbar change, stop the CINE if it is playing
61
+ cineService.stopClip(element);
62
+ cineService.setCine({
63
+ id: viewportId,
64
+ isPlaying: false
65
+ });
66
+ }
67
+ esm.utilities.jumpToSlice(viewport.element, {
68
+ imageIndex,
69
+ debounceLoading: true
70
+ });
71
+ };
72
+ (0,react.useEffect)(() => {
73
+ if (!viewportData) {
74
+ return;
75
+ }
76
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
77
+ if (!viewport) {
78
+ return;
79
+ }
80
+ if (viewportData.viewportType === dist_esm.Enums.ViewportType.STACK) {
81
+ const imageIndex = viewport.getCurrentImageIdIndex();
82
+ setImageSliceData({
83
+ imageIndex: imageIndex,
84
+ numberOfSlices: viewportData.data.imageIds.length
85
+ });
86
+ return;
87
+ }
88
+ if (viewportData.viewportType === dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
89
+ const sliceData = dist_esm.utilities.getImageSliceDataForVolumeViewport(viewport);
90
+ if (!sliceData) {
91
+ return;
92
+ }
93
+ const {
94
+ imageIndex,
95
+ numberOfSlices
96
+ } = sliceData;
97
+ setImageSliceData({
98
+ imageIndex,
99
+ numberOfSlices
100
+ });
101
+ }
102
+ }, [viewportId, viewportData]);
103
+ (0,react.useEffect)(() => {
104
+ if (viewportData?.viewportType !== dist_esm.Enums.ViewportType.STACK) {
105
+ return;
106
+ }
107
+ const updateStackIndex = event => {
108
+ const {
109
+ newImageIdIndex
110
+ } = event.detail;
111
+ // find the index of imageId in the imageIds
112
+ setImageSliceData({
113
+ imageIndex: newImageIdIndex,
114
+ numberOfSlices: viewportData.data.imageIds.length
115
+ });
116
+ };
117
+ element.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
118
+ return () => {
119
+ element.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex);
120
+ };
121
+ }, [viewportData, element]);
122
+ (0,react.useEffect)(() => {
123
+ if (viewportData?.viewportType !== dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
124
+ return;
125
+ }
126
+ const updateVolumeIndex = event => {
127
+ const {
128
+ imageIndex,
129
+ numberOfSlices
130
+ } = event.detail;
131
+ // find the index of imageId in the imageIds
132
+ setImageSliceData({
133
+ imageIndex,
134
+ numberOfSlices
135
+ });
136
+ };
137
+ element.addEventListener(dist_esm.Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
138
+ return () => {
139
+ element.removeEventListener(dist_esm.Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex);
140
+ };
141
+ }, [viewportData, element]);
142
+ return /*#__PURE__*/react.createElement(ui_src/* ImageScrollbar */.uq, {
143
+ onChange: evt => onImageScrollbarChange(evt, viewportId),
144
+ max: imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0,
145
+ height: scrollbarHeight,
146
+ value: imageSliceData.imageIndex
147
+ });
148
+ }
149
+ CornerstoneImageScrollbar.propTypes = {
150
+ viewportData: (prop_types_default()).object,
151
+ viewportId: (prop_types_default()).string.isRequired,
152
+ element: prop_types_default().instanceOf(Element),
153
+ scrollbarHeight: (prop_types_default()).string,
154
+ imageSliceData: (prop_types_default()).object.isRequired,
155
+ setImageSliceData: (prop_types_default()).func.isRequired,
156
+ servicesManager: (prop_types_default()).object.isRequired
157
+ };
158
+ /* harmony default export */ const ViewportImageScrollbar = (CornerstoneImageScrollbar);
159
+ // EXTERNAL MODULE: ../../../node_modules/gl-matrix/esm/index.js + 1 modules
160
+ var gl_matrix_esm = __webpack_require__(44753);
161
+ // EXTERNAL MODULE: ../../../node_modules/moment/moment.js
162
+ var moment = __webpack_require__(8291);
163
+ var moment_default = /*#__PURE__*/__webpack_require__.n(moment);
164
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/utils.ts
165
+
166
+
167
+
168
+ /**
169
+ * Checks if value is valid.
170
+ *
171
+ * @param {number} value
172
+ * @returns {boolean} is valid.
173
+ */
174
+ function isValidNumber(value) {
175
+ return typeof value === 'number' && !isNaN(value);
176
+ }
177
+
178
+ /**
179
+ * Formats number precision.
180
+ *
181
+ * @param {number} number
182
+ * @param {number} precision
183
+ * @returns {number} formatted number.
184
+ */
185
+ function formatNumberPrecision(number, precision = 0) {
186
+ if (number !== null) {
187
+ return parseFloat(number).toFixed(precision);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Formats DICOM date.
193
+ *
194
+ * @param {string} date
195
+ * @param {string} strFormat
196
+ * @returns {string} formatted date.
197
+ */
198
+ function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
199
+ return moment_default()(date, 'YYYYMMDD').format(strFormat);
200
+ }
201
+
202
+ /**
203
+ * DICOM Time is stored as HHmmss.SSS, where:
204
+ * HH 24 hour time:
205
+ * m mm 0..59 Minutes
206
+ * s ss 0..59 Seconds
207
+ * S SS SSS 0..999 Fractional seconds
208
+ *
209
+ * Goal: '24:12:12'
210
+ *
211
+ * @param {*} time
212
+ * @param {string} strFormat
213
+ * @returns {string} formatted name.
214
+ */
215
+ function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
216
+ return moment_default()(time, 'HH:mm:ss').format(strFormat);
217
+ }
218
+
219
+ /**
220
+ * Formats a patient name for display purposes
221
+ *
222
+ * @param {string} name
223
+ * @returns {string} formatted name.
224
+ */
225
+ function formatPN(name) {
226
+ if (!name) {
227
+ return '';
228
+ }
229
+ const cleaned = name.split('^').filter(s => !!s).join(', ').trim();
230
+ return cleaned === ',' || cleaned === '' ? '' : cleaned;
231
+ }
232
+
233
+ /**
234
+ * Gets compression type
235
+ *
236
+ * @param {number} imageId
237
+ * @returns {string} compression type.
238
+ */
239
+ function getCompression(imageId) {
240
+ const generalImageModule = metaData.get('generalImageModule', imageId) || {};
241
+ const {
242
+ lossyImageCompression,
243
+ lossyImageCompressionRatio,
244
+ lossyImageCompressionMethod
245
+ } = generalImageModule;
246
+ if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
247
+ const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
248
+ const compressionRatio = formatNumberPrecision(lossyImageCompressionRatio, 2);
249
+ return compressionMethod + compressionRatio + ' : 1';
250
+ }
251
+ return 'Lossless / Uncompressed';
252
+ }
253
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.css
254
+ // extracted by mini-css-extract-plugin
255
+
256
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+ const EPSILON = 1e-4;
265
+ const OverlayItemComponents = {
266
+ 'ohif.overlayItem.windowLevel': VOIOverlayItem,
267
+ 'ohif.overlayItem.zoomLevel': ZoomOverlayItem,
268
+ 'ohif.overlayItem.instanceNumber': InstanceNumberOverlayItem
269
+ };
270
+
271
+ /**
272
+ * Customizable Viewport Overlay
273
+ */
274
+ function CustomizableViewportOverlay({
275
+ element,
276
+ viewportData,
277
+ imageSliceData,
278
+ viewportId,
279
+ servicesManager
280
+ }) {
281
+ const {
282
+ cornerstoneViewportService,
283
+ customizationService,
284
+ toolGroupService
285
+ } = servicesManager.services;
286
+ const [voi, setVOI] = (0,react.useState)({
287
+ windowCenter: null,
288
+ windowWidth: null
289
+ });
290
+ const [scale, setScale] = (0,react.useState)(1);
291
+ const {
292
+ imageIndex
293
+ } = imageSliceData;
294
+ const topLeftCustomization = customizationService.getModeCustomization('cornerstoneOverlayTopLeft');
295
+ const topRightCustomization = customizationService.getModeCustomization('cornerstoneOverlayTopRight');
296
+ const bottomLeftCustomization = customizationService.getModeCustomization('cornerstoneOverlayBottomLeft');
297
+ const bottomRightCustomization = customizationService.getModeCustomization('cornerstoneOverlayBottomRight');
298
+ const instances = (0,react.useMemo)(() => {
299
+ if (viewportData != null) {
300
+ return _getViewportInstances(viewportData);
301
+ } else {
302
+ return null;
303
+ }
304
+ }, [viewportData, imageIndex]);
305
+ const instanceNumber = (0,react.useMemo)(() => viewportData ? getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) : null, [viewportData, viewportId, imageIndex, cornerstoneViewportService]);
306
+
307
+ /**
308
+ * Updating the VOI when the viewport changes its voi
309
+ */
310
+ (0,react.useEffect)(() => {
311
+ const updateVOI = eventDetail => {
312
+ const {
313
+ range
314
+ } = eventDetail.detail;
315
+ if (!range) {
316
+ return;
317
+ }
318
+ const {
319
+ lower,
320
+ upper
321
+ } = range;
322
+ const {
323
+ windowWidth,
324
+ windowCenter
325
+ } = dist_esm.utilities.windowLevel.toWindowLevel(lower, upper);
326
+ setVOI({
327
+ windowCenter,
328
+ windowWidth
329
+ });
330
+ };
331
+ element.addEventListener(dist_esm.Enums.Events.VOI_MODIFIED, updateVOI);
332
+ return () => {
333
+ element.removeEventListener(dist_esm.Enums.Events.VOI_MODIFIED, updateVOI);
334
+ };
335
+ }, [viewportId, viewportData, voi, element]);
336
+
337
+ /**
338
+ * Updating the scale when the viewport changes its zoom
339
+ */
340
+ (0,react.useEffect)(() => {
341
+ const updateScale = eventDetail => {
342
+ const {
343
+ previousCamera,
344
+ camera
345
+ } = eventDetail.detail;
346
+ if (previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale) {
347
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
348
+ if (!viewport) {
349
+ return;
350
+ }
351
+ const imageData = viewport.getImageData();
352
+ if (!imageData) {
353
+ return;
354
+ }
355
+ if (camera.scale) {
356
+ setScale(camera.scale);
357
+ return;
358
+ }
359
+ const {
360
+ spacing
361
+ } = imageData;
362
+ // convert parallel scale to scale
363
+ const scale = element.clientHeight * spacing[0] * 0.5 / camera.parallelScale;
364
+ setScale(scale);
365
+ }
366
+ };
367
+ element.addEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, updateScale);
368
+ return () => {
369
+ element.removeEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, updateScale);
370
+ };
371
+ }, [viewportId, viewportData, cornerstoneViewportService, element]);
372
+ const _renderOverlayItem = (0,react.useCallback)(item => {
373
+ const overlayItemProps = {
374
+ element,
375
+ viewportData,
376
+ imageSliceData,
377
+ viewportId,
378
+ servicesManager,
379
+ customization: item,
380
+ formatters: {
381
+ formatPN: formatPN,
382
+ formatDate: formatDICOMDate,
383
+ formatTime: formatDICOMTime,
384
+ formatNumberPrecision: formatNumberPrecision
385
+ },
386
+ instance: instances ? instances[item?.instanceIndex] : null,
387
+ voi,
388
+ scale,
389
+ instanceNumber
390
+ };
391
+ if (!item) {
392
+ return null;
393
+ }
394
+ const {
395
+ customizationType
396
+ } = item;
397
+ const OverlayItemComponent = OverlayItemComponents[customizationType];
398
+ if (OverlayItemComponent) {
399
+ return /*#__PURE__*/react.createElement(OverlayItemComponent, overlayItemProps);
400
+ } else {
401
+ const renderItem = customizationService.transform(item);
402
+ if (typeof renderItem.content === 'function') {
403
+ return renderItem.content(overlayItemProps);
404
+ }
405
+ }
406
+ }, [element, viewportData, imageSliceData, viewportId, servicesManager, customizationService, instances, voi, scale, instanceNumber]);
407
+ const getContent = (0,react.useCallback)((customization, defaultItems, keyPrefix) => {
408
+ const items = customization?.items ?? defaultItems;
409
+ return /*#__PURE__*/react.createElement(react.Fragment, null, items.map((item, index) => /*#__PURE__*/react.createElement("div", {
410
+ key: `${keyPrefix}_${index}`
411
+ }, item?.condition ? item.condition({
412
+ instance: instances ? instances[item?.instanceIndex] : null,
413
+ formatters: {
414
+ formatDate: formatDICOMDate
415
+ }
416
+ }) ? _renderOverlayItem(item) : null : _renderOverlayItem(item))));
417
+ }, [_renderOverlayItem]);
418
+ const studyDateItem = {
419
+ id: 'StudyDate',
420
+ customizationType: 'ohif.overlayItem',
421
+ label: '',
422
+ title: 'Study date',
423
+ condition: ({
424
+ instance
425
+ }) => instance && instance.StudyDate,
426
+ contentF: ({
427
+ instance,
428
+ formatters: {
429
+ formatDate
430
+ }
431
+ }) => formatDate(instance.StudyDate)
432
+ };
433
+ const seriesDescriptionItem = {
434
+ id: 'SeriesDescription',
435
+ customizationType: 'ohif.overlayItem',
436
+ label: '',
437
+ title: 'Series description',
438
+ attribute: 'SeriesDescription',
439
+ condition: ({
440
+ instance
441
+ }) => {
442
+ return instance && instance.SeriesDescription;
443
+ }
444
+ };
445
+ const topLeftItems = instances ? instances.map((instance, index) => {
446
+ return [{
447
+ ...studyDateItem,
448
+ instanceIndex: index
449
+ }, {
450
+ ...seriesDescriptionItem,
451
+ instanceIndex: index
452
+ }];
453
+ }).flat() : [];
454
+ return /*#__PURE__*/react.createElement(ui_src/* ViewportOverlay */.pU, {
455
+ topLeft:
456
+ /**
457
+ * Inline default overlay items for a more standard expansion
458
+ */
459
+ getContent(topLeftCustomization, [...topLeftItems], 'topLeftOverlayItem'),
460
+ topRight: getContent(topRightCustomization, [], 'topRightOverlayItem'),
461
+ bottomLeft: getContent(bottomLeftCustomization, [{
462
+ id: 'WindowLevel',
463
+ customizationType: 'ohif.overlayItem.windowLevel'
464
+ }, {
465
+ id: 'ZoomLevel',
466
+ customizationType: 'ohif.overlayItem.zoomLevel',
467
+ condition: () => {
468
+ const activeToolName = toolGroupService.getActiveToolForViewport(viewportId);
469
+ return activeToolName === 'Zoom';
470
+ }
471
+ }], 'bottomLeftOverlayItem'),
472
+ bottomRight: getContent(bottomRightCustomization, [{
473
+ id: 'InstanceNumber',
474
+ customizationType: 'ohif.overlayItem.instanceNumber'
475
+ }], 'bottomRightOverlayItem')
476
+ });
477
+ }
478
+ function _getViewportInstances(viewportData) {
479
+ const imageIds = [];
480
+ if (viewportData.viewportType === dist_esm.Enums.ViewportType.STACK) {
481
+ imageIds.push(viewportData.data.imageIds[0]);
482
+ } else if (viewportData.viewportType === dist_esm.Enums.ViewportType.ORTHOGRAPHIC) {
483
+ const volumes = viewportData.data;
484
+ volumes.forEach(volume => {
485
+ if (!volume?.imageIds || volume.imageIds.length === 0) {
486
+ return;
487
+ }
488
+ imageIds.push(volume.imageIds[0]);
489
+ });
490
+ }
491
+ const instances = [];
492
+ imageIds.forEach(imageId => {
493
+ const instance = dist_esm.metaData.get('instance', imageId) || {};
494
+ instances.push(instance);
495
+ });
496
+ return instances;
497
+ }
498
+ const getInstanceNumber = (viewportData, viewportId, imageIndex, cornerstoneViewportService) => {
499
+ let instanceNumber;
500
+ switch (viewportData.viewportType) {
501
+ case dist_esm.Enums.ViewportType.STACK:
502
+ instanceNumber = _getInstanceNumberFromStack(viewportData, imageIndex);
503
+ break;
504
+ case dist_esm.Enums.ViewportType.ORTHOGRAPHIC:
505
+ instanceNumber = _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService, imageIndex);
506
+ break;
507
+ }
508
+ return instanceNumber ?? null;
509
+ };
510
+ function _getInstanceNumberFromStack(viewportData, imageIndex) {
511
+ const imageIds = viewportData.data.imageIds;
512
+ const imageId = imageIds[imageIndex];
513
+ if (!imageId) {
514
+ return;
515
+ }
516
+ const generalImageModule = dist_esm.metaData.get('generalImageModule', imageId) || {};
517
+ const {
518
+ instanceNumber
519
+ } = generalImageModule;
520
+ const stackSize = imageIds.length;
521
+ if (stackSize <= 1) {
522
+ return;
523
+ }
524
+ return parseInt(instanceNumber);
525
+ }
526
+
527
+ // Since volume viewports can be in any view direction, they can render
528
+ // a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber
529
+ // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber
530
+ function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService, imageIndex) {
531
+ const volumes = viewportData.data;
532
+ if (!volumes) {
533
+ return;
534
+ }
535
+
536
+ // Todo: support fusion of acquisition plane which has instanceNumber
537
+ const {
538
+ volume
539
+ } = volumes[0];
540
+ const {
541
+ direction,
542
+ imageIds
543
+ } = volume;
544
+ const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
545
+ if (!cornerstoneViewport) {
546
+ return;
547
+ }
548
+ const camera = cornerstoneViewport.getCamera();
549
+ const {
550
+ viewPlaneNormal
551
+ } = camera;
552
+ // checking if camera is looking at the acquisition plane (defined by the direction on the volume)
553
+
554
+ const scanAxisNormal = direction.slice(6, 9);
555
+
556
+ // check if viewPlaneNormal is parallel to scanAxisNormal
557
+ const cross = gl_matrix_esm/* vec3.cross */.eR.cross(gl_matrix_esm/* vec3.create */.eR.create(), viewPlaneNormal, scanAxisNormal);
558
+ const isAcquisitionPlane = gl_matrix_esm/* vec3.length */.eR.length(cross) < EPSILON;
559
+ if (isAcquisitionPlane) {
560
+ const imageId = imageIds[imageIndex];
561
+ if (!imageId) {
562
+ return {};
563
+ }
564
+ const {
565
+ instanceNumber
566
+ } = dist_esm.metaData.get('generalImageModule', imageId) || {};
567
+ return parseInt(instanceNumber);
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Window Level / Center Overlay item
573
+ */
574
+ function VOIOverlayItem({
575
+ voi,
576
+ customization
577
+ }) {
578
+ const {
579
+ windowWidth,
580
+ windowCenter
581
+ } = voi;
582
+ if (typeof windowCenter !== 'number' || typeof windowWidth !== 'number') {
583
+ return null;
584
+ }
585
+ return /*#__PURE__*/react.createElement("div", {
586
+ className: "overlay-item flex flex-row",
587
+ style: {
588
+ color: customization && customization.color || undefined
589
+ }
590
+ }, /*#__PURE__*/react.createElement("span", {
591
+ className: "mr-1 shrink-0"
592
+ }, "W:"), /*#__PURE__*/react.createElement("span", {
593
+ className: "ml-1 mr-2 shrink-0"
594
+ }, windowWidth.toFixed(0)), /*#__PURE__*/react.createElement("span", {
595
+ className: "mr-1 shrink-0"
596
+ }, "L:"), /*#__PURE__*/react.createElement("span", {
597
+ className: "ml-1 shrink-0"
598
+ }, windowCenter.toFixed(0)));
599
+ }
600
+
601
+ /**
602
+ * Zoom Level Overlay item
603
+ */
604
+ function ZoomOverlayItem({
605
+ scale,
606
+ customization
607
+ }) {
608
+ return /*#__PURE__*/react.createElement("div", {
609
+ className: "overlay-item flex flex-row",
610
+ style: {
611
+ color: customization && customization.color || undefined
612
+ }
613
+ }, /*#__PURE__*/react.createElement("span", {
614
+ className: "mr-1 shrink-0"
615
+ }, "Zoom:"), /*#__PURE__*/react.createElement("span", null, scale.toFixed(2), "x"));
616
+ }
617
+
618
+ /**
619
+ * Instance Number Overlay Item
620
+ */
621
+ function InstanceNumberOverlayItem({
622
+ instanceNumber,
623
+ imageSliceData,
624
+ customization
625
+ }) {
626
+ const {
627
+ imageIndex,
628
+ numberOfSlices
629
+ } = imageSliceData;
630
+ return /*#__PURE__*/react.createElement("div", {
631
+ className: "overlay-item flex flex-row",
632
+ style: {
633
+ color: customization && customization.color || undefined
634
+ }
635
+ }, /*#__PURE__*/react.createElement("span", null, instanceNumber !== undefined && instanceNumber !== null ? /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("span", {
636
+ className: "mr-1 shrink-0"
637
+ }, "I:"), /*#__PURE__*/react.createElement("span", null, `${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`)) : `${imageIndex + 1}/${numberOfSlices}`));
638
+ }
639
+ CustomizableViewportOverlay.propTypes = {
640
+ viewportData: (prop_types_default()).object,
641
+ imageIndex: (prop_types_default()).number,
642
+ viewportId: (prop_types_default()).string
643
+ };
644
+ /* harmony default export */ const Overlays_CustomizableViewportOverlay = (CustomizableViewportOverlay);
645
+ // EXTERNAL MODULE: ../../../node_modules/classnames/index.js
646
+ var classnames = __webpack_require__(61466);
647
+ var classnames_default = /*#__PURE__*/__webpack_require__.n(classnames);
648
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.css
649
+ // extracted by mini-css-extract-plugin
650
+
651
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx
652
+
653
+
654
+
655
+
656
+
657
+
658
+
659
+ const {
660
+ getOrientationStringLPS,
661
+ invertOrientationStringLPS
662
+ } = esm.utilities.orientation;
663
+ function ViewportOrientationMarkers({
664
+ element,
665
+ viewportData,
666
+ imageSliceData,
667
+ viewportId,
668
+ servicesManager,
669
+ orientationMarkers = ['top', 'left']
670
+ }) {
671
+ // Rotation is in degrees
672
+ const [rotation, setRotation] = (0,react.useState)(0);
673
+ const [flipHorizontal, setFlipHorizontal] = (0,react.useState)(false);
674
+ const [flipVertical, setFlipVertical] = (0,react.useState)(false);
675
+ const {
676
+ cornerstoneViewportService
677
+ } = servicesManager.services;
678
+ (0,react.useEffect)(() => {
679
+ const cameraModifiedListener = evt => {
680
+ const {
681
+ rotation,
682
+ previousCamera,
683
+ camera
684
+ } = evt.detail;
685
+ if (rotation !== undefined) {
686
+ setRotation(rotation);
687
+ }
688
+ if (camera.flipHorizontal !== undefined && previousCamera.flipHorizontal !== camera.flipHorizontal) {
689
+ setFlipHorizontal(camera.flipHorizontal);
690
+ }
691
+ if (camera.flipVertical !== undefined && previousCamera.flipVertical !== camera.flipVertical) {
692
+ setFlipVertical(camera.flipVertical);
693
+ }
694
+ };
695
+ element.addEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
696
+ return () => {
697
+ element.removeEventListener(dist_esm.Enums.Events.CAMERA_MODIFIED, cameraModifiedListener);
698
+ };
699
+ }, []);
700
+ const markers = (0,react.useMemo)(() => {
701
+ if (!viewportData) {
702
+ return '';
703
+ }
704
+ let rowCosines, columnCosines;
705
+ if (viewportData.viewportType === 'stack') {
706
+ const imageIndex = imageSliceData.imageIndex;
707
+ const imageId = viewportData.data.imageIds?.[imageIndex];
708
+
709
+ // Workaround for below TODO stub
710
+ if (!imageId) {
711
+ return false;
712
+ }
713
+ ({
714
+ rowCosines,
715
+ columnCosines
716
+ } = dist_esm.metaData.get('imagePlaneModule', imageId) || {});
717
+ } else {
718
+ if (!element || !(0,dist_esm.getEnabledElement)(element)) {
719
+ return '';
720
+ }
721
+ const {
722
+ viewport
723
+ } = (0,dist_esm.getEnabledElement)(element);
724
+ const {
725
+ viewUp,
726
+ viewPlaneNormal
727
+ } = viewport.getCamera();
728
+ const viewRight = gl_matrix_esm/* vec3.create */.eR.create();
729
+ gl_matrix_esm/* vec3.cross */.eR.cross(viewRight, viewUp, viewPlaneNormal);
730
+ columnCosines = [-viewUp[0], -viewUp[1], -viewUp[2]];
731
+ rowCosines = viewRight;
732
+ }
733
+ if (!rowCosines || !columnCosines || rotation === undefined) {
734
+ return '';
735
+ }
736
+ const markers = _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal);
737
+ const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId);
738
+ if (!ohifViewport) {
739
+ console.log('ViewportOrientationMarkers::No viewport');
740
+ return null;
741
+ }
742
+ return orientationMarkers.map((m, index) => /*#__PURE__*/react.createElement("div", {
743
+ className: classnames_default()('overlay-text', `${m}-mid orientation-marker`, 'text-aqua-pale', 'text-[13px]', 'leading-5'),
744
+ key: `${m}-mid orientation-marker`
745
+ }, /*#__PURE__*/react.createElement("div", {
746
+ className: "orientation-marker-value"
747
+ }, markers[m])));
748
+ }, [viewportData, imageSliceData, rotation, flipVertical, flipHorizontal, orientationMarkers, element]);
749
+ return /*#__PURE__*/react.createElement("div", {
750
+ className: "ViewportOrientationMarkers select-none"
751
+ }, markers);
752
+ }
753
+ ViewportOrientationMarkers.propTypes = {
754
+ percentComplete: (prop_types_default()).number,
755
+ error: (prop_types_default()).object
756
+ };
757
+ ViewportOrientationMarkers.defaultProps = {
758
+ percentComplete: 0,
759
+ error: null
760
+ };
761
+
762
+ /**
763
+ *
764
+ * Computes the orientation labels on a Cornerstone-enabled Viewport element
765
+ * when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
766
+ *
767
+ * @param {*} rowCosines
768
+ * @param {*} columnCosines
769
+ * @param {*} rotation in degrees
770
+ * @returns
771
+ */
772
+ function _getOrientationMarkers(rowCosines, columnCosines, rotation, flipVertical, flipHorizontal) {
773
+ const rowString = getOrientationStringLPS(rowCosines);
774
+ const columnString = getOrientationStringLPS(columnCosines);
775
+ const oppositeRowString = invertOrientationStringLPS(rowString);
776
+ const oppositeColumnString = invertOrientationStringLPS(columnString);
777
+ const markers = {
778
+ top: oppositeColumnString,
779
+ left: oppositeRowString,
780
+ right: rowString,
781
+ bottom: columnString
782
+ };
783
+
784
+ // If any vertical or horizontal flips are applied, change the orientation strings ahead of
785
+ // the rotation applications
786
+ if (flipVertical) {
787
+ markers.top = invertOrientationStringLPS(markers.top);
788
+ markers.bottom = invertOrientationStringLPS(markers.bottom);
789
+ }
790
+ if (flipHorizontal) {
791
+ markers.left = invertOrientationStringLPS(markers.left);
792
+ markers.right = invertOrientationStringLPS(markers.right);
793
+ }
794
+
795
+ // Swap the labels accordingly if the viewport has been rotated
796
+ // This could be done in a more complex way for intermediate rotation values (e.g. 45 degrees)
797
+ if (rotation === 90 || rotation === -270) {
798
+ return {
799
+ top: markers.left,
800
+ left: invertOrientationStringLPS(markers.top),
801
+ right: invertOrientationStringLPS(markers.bottom),
802
+ bottom: markers.right // left
803
+ };
804
+ } else if (rotation === -90 || rotation === 270) {
805
+ return {
806
+ top: invertOrientationStringLPS(markers.left),
807
+ left: markers.top,
808
+ bottom: markers.left,
809
+ right: markers.bottom
810
+ };
811
+ } else if (rotation === 180 || rotation === -180) {
812
+ return {
813
+ top: invertOrientationStringLPS(markers.top),
814
+ left: invertOrientationStringLPS(markers.left),
815
+ bottom: invertOrientationStringLPS(markers.bottom),
816
+ right: invertOrientationStringLPS(markers.right)
817
+ };
818
+ }
819
+ return markers;
820
+ }
821
+ /* harmony default export */ const Overlays_ViewportOrientationMarkers = (ViewportOrientationMarkers);
822
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx
823
+
824
+
825
+
826
+ function ViewportImageSliceLoadingIndicator({
827
+ viewportData,
828
+ element
829
+ }) {
830
+ const [loading, setLoading] = (0,react.useState)(false);
831
+ const [error, setError] = (0,react.useState)(false);
832
+ const loadIndicatorRef = (0,react.useRef)(null);
833
+ const imageIdToBeLoaded = (0,react.useRef)(null);
834
+ const setLoadingState = evt => {
835
+ clearTimeout(loadIndicatorRef.current);
836
+ loadIndicatorRef.current = setTimeout(() => {
837
+ setLoading(true);
838
+ }, 50);
839
+ };
840
+ const setFinishLoadingState = evt => {
841
+ clearTimeout(loadIndicatorRef.current);
842
+ setLoading(false);
843
+ };
844
+ const setErrorState = evt => {
845
+ clearTimeout(loadIndicatorRef.current);
846
+ if (imageIdToBeLoaded.current === evt.detail.imageId) {
847
+ setError(evt.detail.error);
848
+ imageIdToBeLoaded.current = null;
849
+ }
850
+ };
851
+ (0,react.useEffect)(() => {
852
+ element.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
853
+ element.addEventListener(dist_esm.Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
854
+ element.addEventListener(dist_esm.Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
855
+ return () => {
856
+ element.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState);
857
+ element.removeEventListener(dist_esm.Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState);
858
+ element.removeEventListener(dist_esm.Enums.Events.IMAGE_LOAD_ERROR, setErrorState);
859
+ };
860
+ }, [element, viewportData]);
861
+ if (error) {
862
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
863
+ className: "absolute top-0 left-0 h-full w-full bg-black opacity-50"
864
+ }, /*#__PURE__*/react.createElement("div", {
865
+ className: "transparent flex h-full w-full items-center justify-center"
866
+ }, /*#__PURE__*/react.createElement("p", {
867
+ className: "text-primary-light text-xl font-light"
868
+ }, /*#__PURE__*/react.createElement("h4", null, "Error Loading Image"), /*#__PURE__*/react.createElement("p", null, "An error has occurred."), /*#__PURE__*/react.createElement("p", null, error)))));
869
+ }
870
+ if (loading) {
871
+ return (
872
+ /*#__PURE__*/
873
+ // IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from
874
+ // interacting with the mouse, since scrolling should propagate to the viewport underneath
875
+ react.createElement("div", {
876
+ className: "pointer-events-none absolute top-0 left-0 h-full w-full bg-black opacity-50"
877
+ }, /*#__PURE__*/react.createElement("div", {
878
+ className: "transparent flex h-full w-full items-center justify-center"
879
+ }, /*#__PURE__*/react.createElement("p", {
880
+ className: "text-primary-light text-xl font-light"
881
+ }, "Loading...")))
882
+ );
883
+ }
884
+ return null;
885
+ }
886
+ ViewportImageSliceLoadingIndicator.propTypes = {
887
+ percentComplete: (prop_types_default()).number,
888
+ error: (prop_types_default()).object,
889
+ element: (prop_types_default()).object
890
+ };
891
+ ViewportImageSliceLoadingIndicator.defaultProps = {
892
+ percentComplete: 0,
893
+ error: null
894
+ };
895
+ /* harmony default export */ const Overlays_ViewportImageSliceLoadingIndicator = (ViewportImageSliceLoadingIndicator);
896
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx
897
+
898
+
899
+
900
+
901
+
902
+ function CornerstoneOverlays(props) {
903
+ const {
904
+ viewportId,
905
+ element,
906
+ scrollbarHeight,
907
+ servicesManager
908
+ } = props;
909
+ const {
910
+ cornerstoneViewportService
911
+ } = servicesManager.services;
912
+ const [imageSliceData, setImageSliceData] = (0,react.useState)({
913
+ imageIndex: 0,
914
+ numberOfSlices: 0
915
+ });
916
+ const [viewportData, setViewportData] = (0,react.useState)(null);
917
+ (0,react.useEffect)(() => {
918
+ const {
919
+ unsubscribe
920
+ } = cornerstoneViewportService.subscribe(cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => {
921
+ if (props.viewportId !== viewportId) {
922
+ return;
923
+ }
924
+ setViewportData(props.viewportData);
925
+ });
926
+ return () => {
927
+ unsubscribe();
928
+ };
929
+ }, [viewportId]);
930
+ if (!element) {
931
+ return null;
932
+ }
933
+ if (viewportData) {
934
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
935
+ if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
936
+ return null;
937
+ }
938
+ }
939
+ return /*#__PURE__*/react.createElement("div", {
940
+ className: "noselect"
941
+ }, /*#__PURE__*/react.createElement(ViewportImageScrollbar, {
942
+ viewportId: viewportId,
943
+ viewportData: viewportData,
944
+ element: element,
945
+ imageSliceData: imageSliceData,
946
+ setImageSliceData: setImageSliceData,
947
+ scrollbarHeight: scrollbarHeight,
948
+ servicesManager: servicesManager
949
+ }), /*#__PURE__*/react.createElement(Overlays_CustomizableViewportOverlay, {
950
+ imageSliceData: imageSliceData,
951
+ viewportData: viewportData,
952
+ viewportId: viewportId,
953
+ servicesManager: servicesManager,
954
+ element: element
955
+ }), /*#__PURE__*/react.createElement(Overlays_ViewportImageSliceLoadingIndicator, {
956
+ viewportData: viewportData,
957
+ element: element
958
+ }), /*#__PURE__*/react.createElement(Overlays_ViewportOrientationMarkers, {
959
+ imageSliceData: imageSliceData,
960
+ element: element,
961
+ viewportData: viewportData,
962
+ servicesManager: servicesManager,
963
+ viewportId: viewportId
964
+ }));
965
+ }
966
+ /* harmony default export */ const Overlays_CornerstoneOverlays = (CornerstoneOverlays);
967
+ // EXTERNAL MODULE: ../../../extensions/cornerstone/src/utils/measurementServiceMappings/utils/getSOPInstanceAttributes.js
968
+ var getSOPInstanceAttributes = __webpack_require__(1663);
969
+ // EXTERNAL MODULE: ../../../node_modules/@cornerstonejs/streaming-image-volume-loader/dist/esm/index.js + 13 modules
970
+ var streaming_image_volume_loader_dist_esm = __webpack_require__(23722);
971
+ // EXTERNAL MODULE: ./state/index.js + 1 modules
972
+ var state_0 = __webpack_require__(15575);
973
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx
974
+
975
+
976
+
977
+
978
+
979
+ function WrappedCinePlayer({
980
+ enabledVPElement,
981
+ viewportId,
982
+ servicesManager
983
+ }) {
984
+ const {
985
+ customizationService,
986
+ displaySetService,
987
+ viewportGridService
988
+ } = servicesManager.services;
989
+ const [{
990
+ isCineEnabled,
991
+ cines
992
+ }, cineService] = (0,ui_src/* useCine */.tq)();
993
+ const [newStackFrameRate, setNewStackFrameRate] = (0,react.useState)(24);
994
+ const [dynamicInfo, setDynamicInfo] = (0,react.useState)(null);
995
+ const [appConfig] = (0,state_0/* useAppConfig */.r)();
996
+ const isMountedRef = (0,react.useRef)(null);
997
+ const cineHandler = () => {
998
+ if (!cines?.[viewportId] || !enabledVPElement) {
999
+ return;
1000
+ }
1001
+ const {
1002
+ isPlaying = false,
1003
+ frameRate = 24
1004
+ } = cines[viewportId];
1005
+ const validFrameRate = Math.max(frameRate, 1);
1006
+ return isPlaying ? cineService.playClip(enabledVPElement, {
1007
+ framesPerSecond: validFrameRate
1008
+ }) : cineService.stopClip(enabledVPElement);
1009
+ };
1010
+ const newDisplaySetHandler = (0,react.useCallback)(() => {
1011
+ if (!enabledVPElement || !isCineEnabled) {
1012
+ return;
1013
+ }
1014
+ const {
1015
+ viewports
1016
+ } = viewportGridService.getState();
1017
+ const {
1018
+ displaySetInstanceUIDs
1019
+ } = viewports.get(viewportId);
1020
+ let frameRate = 24;
1021
+ let isPlaying = cines[viewportId]?.isPlaying || false;
1022
+ displaySetInstanceUIDs.forEach(displaySetInstanceUID => {
1023
+ const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
1024
+ if (displaySet.FrameRate) {
1025
+ // displaySet.FrameRate corresponds to DICOM tag (0018,1063) which is defined as the the frame time in milliseconds
1026
+ // So a bit of math to get the actual frame rate.
1027
+ frameRate = Math.round(1000 / displaySet.FrameRate);
1028
+ isPlaying ||= !!appConfig.autoPlayCine;
1029
+ }
1030
+
1031
+ // check if the displaySet is dynamic and set the dynamic info
1032
+ if (displaySet.isDynamicVolume) {
1033
+ const {
1034
+ dynamicVolumeInfo
1035
+ } = displaySet;
1036
+ const numTimePoints = dynamicVolumeInfo.timePoints.length;
1037
+ const label = dynamicVolumeInfo.splittingTag;
1038
+ const timePointIndex = dynamicVolumeInfo.timePointIndex || 0;
1039
+ setDynamicInfo({
1040
+ volumeId: displaySet.displaySetInstanceUID,
1041
+ timePointIndex,
1042
+ numTimePoints,
1043
+ label
1044
+ });
1045
+ } else {
1046
+ setDynamicInfo(null);
1047
+ }
1048
+ });
1049
+ if (isPlaying) {
1050
+ cineService.setIsCineEnabled(isPlaying);
1051
+ }
1052
+ cineService.setCine({
1053
+ id: viewportId,
1054
+ isPlaying,
1055
+ frameRate
1056
+ });
1057
+ setNewStackFrameRate(frameRate);
1058
+ }, [displaySetService, viewportId, viewportGridService, cines, isCineEnabled]);
1059
+ (0,react.useEffect)(() => {
1060
+ isMountedRef.current = true;
1061
+ newDisplaySetHandler();
1062
+ return () => {
1063
+ isMountedRef.current = false;
1064
+ };
1065
+ }, [isCineEnabled, newDisplaySetHandler]);
1066
+
1067
+ /**
1068
+ * Use effect for handling new display set
1069
+ */
1070
+ (0,react.useEffect)(() => {
1071
+ if (!enabledVPElement) {
1072
+ return;
1073
+ }
1074
+ dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
1075
+ // this doesn't makes sense that we are listening to this event on viewport element
1076
+ enabledVPElement.addEventListener(dist_esm.Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, newDisplaySetHandler);
1077
+ return () => {
1078
+ cineService.setCine({
1079
+ id: viewportId,
1080
+ isPlaying: false
1081
+ });
1082
+ dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
1083
+ enabledVPElement.removeEventListener(dist_esm.Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, newDisplaySetHandler);
1084
+ };
1085
+ }, [enabledVPElement, newDisplaySetHandler, viewportId]);
1086
+ (0,react.useEffect)(() => {
1087
+ if (!cines || !cines[viewportId] || !enabledVPElement || !isMountedRef.current) {
1088
+ return;
1089
+ }
1090
+ cineHandler();
1091
+ return () => {
1092
+ cineService.stopClip(enabledVPElement);
1093
+ };
1094
+ }, [cines, viewportId, cineService, enabledVPElement, cineHandler]);
1095
+ if (!isCineEnabled) {
1096
+ return null;
1097
+ }
1098
+ const cine = cines[viewportId];
1099
+ const isPlaying = cine?.isPlaying || false;
1100
+ return /*#__PURE__*/react.createElement(RenderCinePlayer, {
1101
+ viewportId: viewportId,
1102
+ cineService: cineService,
1103
+ newStackFrameRate: newStackFrameRate,
1104
+ isPlaying: isPlaying,
1105
+ dynamicInfo: dynamicInfo,
1106
+ customizationService: customizationService
1107
+ });
1108
+ }
1109
+ function RenderCinePlayer({
1110
+ viewportId,
1111
+ cineService,
1112
+ newStackFrameRate,
1113
+ isPlaying,
1114
+ dynamicInfo: dynamicInfoProp,
1115
+ customizationService
1116
+ }) {
1117
+ const {
1118
+ component: CinePlayerComponent = ui_src/* CinePlayer */.F0
1119
+ } = customizationService.get('cinePlayer') ?? {};
1120
+ const [dynamicInfo, setDynamicInfo] = (0,react.useState)(dynamicInfoProp);
1121
+ (0,react.useEffect)(() => {
1122
+ setDynamicInfo(dynamicInfoProp);
1123
+ }, [dynamicInfoProp]);
1124
+
1125
+ /**
1126
+ * Use effect for handling 4D time index changed
1127
+ */
1128
+ (0,react.useEffect)(() => {
1129
+ if (!dynamicInfo) {
1130
+ return;
1131
+ }
1132
+ const handleTimePointIndexChange = evt => {
1133
+ const {
1134
+ volumeId,
1135
+ timePointIndex,
1136
+ numTimePoints,
1137
+ splittingTag
1138
+ } = evt.detail;
1139
+ setDynamicInfo({
1140
+ volumeId,
1141
+ timePointIndex,
1142
+ numTimePoints,
1143
+ label: splittingTag
1144
+ });
1145
+ };
1146
+ dist_esm.eventTarget.addEventListener(streaming_image_volume_loader_dist_esm/* Enums.Events */.fX.s.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED, handleTimePointIndexChange);
1147
+ return () => {
1148
+ dist_esm.eventTarget.removeEventListener(streaming_image_volume_loader_dist_esm/* Enums.Events */.fX.s.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED, handleTimePointIndexChange);
1149
+ };
1150
+ }, [dynamicInfo]);
1151
+ (0,react.useEffect)(() => {
1152
+ if (!dynamicInfo) {
1153
+ return;
1154
+ }
1155
+ const {
1156
+ volumeId,
1157
+ timePointIndex,
1158
+ numTimePoints,
1159
+ splittingTag
1160
+ } = dynamicInfo || {};
1161
+ const volume = dist_esm.cache.getVolume(volumeId);
1162
+ volume.timePointIndex = timePointIndex;
1163
+ setDynamicInfo({
1164
+ volumeId,
1165
+ timePointIndex,
1166
+ numTimePoints,
1167
+ label: splittingTag
1168
+ });
1169
+ }, []);
1170
+ const updateDynamicInfo = (0,react.useCallback)(props => {
1171
+ const {
1172
+ volumeId,
1173
+ timePointIndex
1174
+ } = props;
1175
+ const volume = dist_esm.cache.getVolume(volumeId);
1176
+ volume.timePointIndex = timePointIndex;
1177
+ }, []);
1178
+ return /*#__PURE__*/react.createElement(CinePlayerComponent, {
1179
+ className: "absolute left-1/2 bottom-3 -translate-x-1/2",
1180
+ frameRate: newStackFrameRate,
1181
+ isPlaying: isPlaying,
1182
+ onClose: () => {
1183
+ // also stop the clip
1184
+ cineService.setCine({
1185
+ id: viewportId,
1186
+ isPlaying: false
1187
+ });
1188
+ cineService.setIsCineEnabled(false);
1189
+ },
1190
+ onPlayPauseChange: isPlaying => {
1191
+ cineService.setCine({
1192
+ id: viewportId,
1193
+ isPlaying
1194
+ });
1195
+ },
1196
+ onFrameRateChange: frameRate => cineService.setCine({
1197
+ id: viewportId,
1198
+ frameRate
1199
+ }),
1200
+ dynamicInfo: dynamicInfo,
1201
+ updateDynamicInfo: updateDynamicInfo
1202
+ });
1203
+ }
1204
+ /* harmony default export */ const CinePlayer = (WrappedCinePlayer);
1205
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/CinePlayer/index.ts
1206
+
1207
+ /* harmony default export */ const components_CinePlayer = (CinePlayer);
1208
+ // EXTERNAL MODULE: ../../../extensions/cornerstone/src/contextProviders/ViewportActionCornersProvider.tsx
1209
+ var ViewportActionCornersProvider = __webpack_require__(76255);
1210
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/OHIFViewportActionCorners.tsx
1211
+
1212
+
1213
+
1214
+ function OHIFViewportActionCorners({
1215
+ viewportId
1216
+ }) {
1217
+ const [viewportActionCornersState] = (0,ViewportActionCornersProvider/* useViewportActionCornersContext */.R4)();
1218
+ if (!viewportActionCornersState[viewportId]) {
1219
+ return null;
1220
+ }
1221
+ return /*#__PURE__*/react.createElement(ui_src/* ViewportActionCorners */.R2, {
1222
+ cornerComponents: viewportActionCornersState[viewportId]
1223
+ });
1224
+ }
1225
+ /* harmony default export */ const components_OHIFViewportActionCorners = (OHIFViewportActionCorners);
1226
+ // EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
1227
+ var es = __webpack_require__(80619);
1228
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsx
1229
+
1230
+
1231
+
1232
+ function Colormap({
1233
+ colormaps,
1234
+ viewportId,
1235
+ displaySets,
1236
+ commandsManager,
1237
+ serviceManager
1238
+ }) {
1239
+ const {
1240
+ cornerstoneViewportService
1241
+ } = serviceManager.services;
1242
+ const [activeDisplaySet, setActiveDisplaySet] = (0,react.useState)(displaySets[0]);
1243
+ const [showPreview, setShowPreview] = (0,react.useState)(false);
1244
+ const [prePreviewColormap, setPrePreviewColormap] = (0,react.useState)(null);
1245
+ const showPreviewRef = (0,react.useRef)(showPreview);
1246
+ showPreviewRef.current = showPreview;
1247
+ const prePreviewColormapRef = (0,react.useRef)(prePreviewColormap);
1248
+ prePreviewColormapRef.current = prePreviewColormap;
1249
+ const activeDisplaySetRef = (0,react.useRef)(activeDisplaySet);
1250
+ activeDisplaySetRef.current = activeDisplaySet;
1251
+ const onSetColorLUT = (0,react.useCallback)(props => {
1252
+ // TODO: Better way to check if it's a fusion
1253
+ const oneOpacityColormaps = ['Grayscale', 'X Ray'];
1254
+ const opacity = displaySets.length > 1 && !oneOpacityColormaps.includes(props.colormap.name) ? 0.5 : 1;
1255
+ commandsManager.run({
1256
+ commandName: 'setViewportColormap',
1257
+ commandOptions: {
1258
+ ...props,
1259
+ opacity,
1260
+ immediate: true
1261
+ },
1262
+ context: 'CORNERSTONE'
1263
+ });
1264
+ }, [commandsManager]);
1265
+ const getViewportColormap = (viewportId, displaySet) => {
1266
+ const {
1267
+ displaySetInstanceUID
1268
+ } = displaySet;
1269
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1270
+ if (viewport instanceof dist_esm.StackViewport) {
1271
+ const {
1272
+ colormap
1273
+ } = viewport.getProperties();
1274
+ if (!colormap) {
1275
+ return colormaps.find(c => c.Name === 'Grayscale') || colormaps[0];
1276
+ }
1277
+ return colormap;
1278
+ }
1279
+ const actorEntries = viewport.getActors();
1280
+ const actorEntry = actorEntries.find(entry => entry.uid.includes(displaySetInstanceUID));
1281
+ const {
1282
+ colormap
1283
+ } = viewport.getProperties(actorEntry.uid);
1284
+ if (!colormap) {
1285
+ return colormaps.find(c => c.Name === 'Grayscale') || colormaps[0];
1286
+ }
1287
+ return colormap;
1288
+ };
1289
+ const buttons = (0,react.useMemo)(() => {
1290
+ return displaySets.map((displaySet, index) => ({
1291
+ children: displaySet.Modality,
1292
+ key: index,
1293
+ style: {
1294
+ minWidth: `calc(100% / ${displaySets.length})`,
1295
+ fontSize: '0.8rem',
1296
+ textAlign: 'center',
1297
+ display: 'flex',
1298
+ justifyContent: 'center',
1299
+ alignItems: 'center'
1300
+ }
1301
+ }));
1302
+ }, [displaySets]);
1303
+ (0,react.useEffect)(() => {
1304
+ setActiveDisplaySet(displaySets[displaySets.length - 1]);
1305
+ }, [displaySets]);
1306
+ return /*#__PURE__*/react.createElement(react.Fragment, null, buttons.length > 1 && /*#__PURE__*/react.createElement("div", {
1307
+ className: "all-in-one-menu-item flex w-full justify-center"
1308
+ }, /*#__PURE__*/react.createElement(ui_src/* ButtonGroup */.e2, {
1309
+ onActiveIndexChange: index => {
1310
+ setActiveDisplaySet(displaySets[index]);
1311
+ setPrePreviewColormap(null);
1312
+ },
1313
+ activeIndex: displaySets.findIndex(ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID) || 1,
1314
+ className: "w-[70%] text-[10px]"
1315
+ }, buttons.map(({
1316
+ children,
1317
+ key,
1318
+ style
1319
+ }) => /*#__PURE__*/react.createElement("div", {
1320
+ key: key,
1321
+ style: style
1322
+ }, children)))), /*#__PURE__*/react.createElement("div", {
1323
+ className: "all-in-one-menu-item flex w-full justify-center"
1324
+ }, /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1325
+ label: "Preview in viewport",
1326
+ checked: showPreview,
1327
+ onChange: checked => {
1328
+ setShowPreview(checked);
1329
+ }
1330
+ })), /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.DividerItem */.se.VG, null), /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, colormaps.map((colormap, index) => /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.Item */.se.q7, {
1331
+ key: index,
1332
+ label: colormap.description,
1333
+ onClick: () => {
1334
+ onSetColorLUT({
1335
+ viewportId,
1336
+ colormap,
1337
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1338
+ });
1339
+ setPrePreviewColormap(null);
1340
+ },
1341
+ onMouseEnter: () => {
1342
+ if (showPreviewRef.current) {
1343
+ setPrePreviewColormap(getViewportColormap(viewportId, activeDisplaySetRef.current));
1344
+ onSetColorLUT({
1345
+ viewportId,
1346
+ colormap,
1347
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1348
+ });
1349
+ }
1350
+ },
1351
+ onMouseLeave: () => {
1352
+ if (showPreviewRef.current && prePreviewColormapRef.current) {
1353
+ onSetColorLUT({
1354
+ viewportId,
1355
+ colormap: prePreviewColormapRef.current,
1356
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1357
+ });
1358
+ }
1359
+ }
1360
+ }))));
1361
+ }
1362
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsx
1363
+
1364
+
1365
+
1366
+
1367
+ function setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, colorbarOptions) {
1368
+ const {
1369
+ cornerstoneViewportService
1370
+ } = serviceManager.services;
1371
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1372
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
1373
+ const backgroundColor = viewportInfo.getViewportOptions().background;
1374
+ const isLight = backgroundColor ? dist_esm.utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
1375
+ if (isLight) {
1376
+ colorbarOptions.ticks = {
1377
+ position: 'left',
1378
+ style: {
1379
+ font: '12px Arial',
1380
+ color: '#000000',
1381
+ maxNumTicks: 8,
1382
+ tickSize: 5,
1383
+ tickWidth: 1,
1384
+ labelMargin: 3
1385
+ }
1386
+ };
1387
+ }
1388
+ const displaySetInstanceUIDs = [];
1389
+ if (viewport instanceof dist_esm.StackViewport) {
1390
+ displaySetInstanceUIDs.push(viewportId);
1391
+ }
1392
+ if (viewport instanceof dist_esm.VolumeViewport) {
1393
+ displaySets.forEach(ds => {
1394
+ displaySetInstanceUIDs.push(ds.displaySetInstanceUID);
1395
+ });
1396
+ }
1397
+ commandsManager.run({
1398
+ commandName: 'toggleViewportColorbar',
1399
+ commandOptions: {
1400
+ viewportId,
1401
+ options: colorbarOptions,
1402
+ displaySetInstanceUIDs
1403
+ },
1404
+ context: 'CORNERSTONE'
1405
+ });
1406
+ }
1407
+ function Colorbar({
1408
+ viewportId,
1409
+ displaySets,
1410
+ commandsManager,
1411
+ serviceManager,
1412
+ colorbarProperties
1413
+ }) {
1414
+ const {
1415
+ colorbarService
1416
+ } = serviceManager.services;
1417
+ const {
1418
+ width: colorbarWidth,
1419
+ colorbarTickPosition,
1420
+ colorbarContainerPosition,
1421
+ colormaps,
1422
+ colorbarInitialColormap
1423
+ } = colorbarProperties;
1424
+ const [showColorbar, setShowColorbar] = (0,react.useState)(colorbarService.hasColorbar(viewportId));
1425
+ const onSetColorbar = (0,react.useCallback)(() => {
1426
+ setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, {
1427
+ viewportId,
1428
+ colormaps,
1429
+ ticks: {
1430
+ position: colorbarTickPosition
1431
+ },
1432
+ width: colorbarWidth,
1433
+ position: colorbarContainerPosition,
1434
+ activeColormapName: colorbarInitialColormap
1435
+ });
1436
+ }, [commandsManager]);
1437
+ (0,react.useEffect)(() => {
1438
+ const updateColorbarState = () => {
1439
+ setShowColorbar(colorbarService.hasColorbar(viewportId));
1440
+ };
1441
+ const {
1442
+ unsubscribe
1443
+ } = colorbarService.subscribe(colorbarService.EVENTS.STATE_CHANGED, updateColorbarState);
1444
+ return () => {
1445
+ unsubscribe();
1446
+ };
1447
+ }, [viewportId]);
1448
+ return /*#__PURE__*/react.createElement("div", {
1449
+ className: "all-in-one-menu-item flex w-full justify-center"
1450
+ }, /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1451
+ label: "Display Color bar",
1452
+ checked: showColorbar,
1453
+ onChange: () => {
1454
+ onSetColorbar();
1455
+ }
1456
+ }));
1457
+ }
1458
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx
1459
+
1460
+
1461
+
1462
+ function WindowLevel({
1463
+ viewportId,
1464
+ commandsManager,
1465
+ presets
1466
+ }) {
1467
+ const {
1468
+ t
1469
+ } = (0,es/* useTranslation */.Bd)('WindowLevelActionMenu');
1470
+ const onSetWindowLevel = (0,react.useCallback)(props => {
1471
+ commandsManager.run({
1472
+ commandName: 'setViewportWindowLevel',
1473
+ commandOptions: {
1474
+ ...props,
1475
+ viewportId
1476
+ },
1477
+ context: 'CORNERSTONE'
1478
+ });
1479
+ }, [commandsManager, viewportId]);
1480
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, presets.map((modalityPresets, modalityIndex) => /*#__PURE__*/react.createElement(react.Fragment, {
1481
+ key: modalityIndex
1482
+ }, Object.entries(modalityPresets).map(([modality, presetsArray]) => /*#__PURE__*/react.createElement(react.Fragment, {
1483
+ key: modality
1484
+ }, /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.HeaderItem */.se.N5, null, t('Modality Presets', {
1485
+ modality
1486
+ })), presetsArray.map((preset, index) => /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.Item */.se.q7, {
1487
+ key: `${modality}-${index}`,
1488
+ label: preset.description,
1489
+ secondaryLabel: `${preset.window} / ${preset.level}`,
1490
+ onClick: () => onSetWindowLevel(preset)
1491
+ })))))));
1492
+ }
1493
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx
1494
+
1495
+
1496
+
1497
+
1498
+ function VolumeRenderingPresetsContent({
1499
+ presets,
1500
+ viewportId,
1501
+ commandsManager,
1502
+ onClose
1503
+ }) {
1504
+ const [filteredPresets, setFilteredPresets] = (0,react.useState)(presets);
1505
+ const [searchValue, setSearchValue] = (0,react.useState)('');
1506
+ const [selectedPreset, setSelectedPreset] = (0,react.useState)(null);
1507
+ const handleSearchChange = (0,react.useCallback)(value => {
1508
+ setSearchValue(value);
1509
+ const filtered = value ? presets.filter(preset => preset.name.toLowerCase().includes(value.toLowerCase())) : presets;
1510
+ setFilteredPresets(filtered);
1511
+ }, [presets]);
1512
+ const handleApply = (0,react.useCallback)(props => {
1513
+ commandsManager.runCommand('setViewportPreset', {
1514
+ ...props
1515
+ });
1516
+ }, [commandsManager]);
1517
+ const formatLabel = (label, maxChars) => {
1518
+ return label.length > maxChars ? `${label.slice(0, maxChars)}...` : label;
1519
+ };
1520
+ return /*#__PURE__*/react.createElement("div", {
1521
+ className: "flex min-h-full w-full flex-col justify-between"
1522
+ }, /*#__PURE__*/react.createElement("div", {
1523
+ className: "border-secondary-light h-[433px] w-full overflow-hidden rounded border bg-black px-2.5"
1524
+ }, /*#__PURE__*/react.createElement("div", {
1525
+ className: "flex h-[46px] w-full items-center justify-start"
1526
+ }, /*#__PURE__*/react.createElement("div", {
1527
+ className: "h-[26px] w-[200px]"
1528
+ }, /*#__PURE__*/react.createElement(ui_src/* InputFilterText */.Cv, {
1529
+ value: searchValue,
1530
+ onDebounceChange: handleSearchChange,
1531
+ placeholder: 'Search all'
1532
+ }))), /*#__PURE__*/react.createElement("div", {
1533
+ className: "ohif-scrollbar overflow h-[385px] w-full overflow-y-auto"
1534
+ }, /*#__PURE__*/react.createElement("div", {
1535
+ className: "grid grid-cols-4 gap-3 pt-2 pr-3"
1536
+ }, filteredPresets.map((preset, index) => /*#__PURE__*/react.createElement("div", {
1537
+ key: index,
1538
+ className: "flex cursor-pointer flex-col items-start",
1539
+ onClick: () => {
1540
+ setSelectedPreset(preset);
1541
+ handleApply({
1542
+ preset: preset.name,
1543
+ viewportId
1544
+ });
1545
+ }
1546
+ }, /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1547
+ name: preset.name,
1548
+ className: selectedPreset?.name === preset.name ? 'border-primary-light h-[75px] w-[95px] max-w-none rounded border-2' : 'hover:border-primary-light h-[75px] w-[95px] max-w-none rounded border-2 border-black'
1549
+ }), /*#__PURE__*/react.createElement("label", {
1550
+ className: "text-aqua-pale mt-2 text-left text-xs"
1551
+ }, formatLabel(preset.name, 11))))))), /*#__PURE__*/react.createElement("footer", {
1552
+ className: "flex h-[60px] w-full items-center justify-end"
1553
+ }, /*#__PURE__*/react.createElement("div", {
1554
+ className: "flex"
1555
+ }, /*#__PURE__*/react.createElement(ui_src/* Button */.$n, {
1556
+ name: "Cancel",
1557
+ size: ui_src/* ButtonEnums.size */.Ny.Ej.medium,
1558
+ type: ui_src/* ButtonEnums.type */.Ny.NW.secondary,
1559
+ onClick: onClose
1560
+ }, ' ', "Cancel", ' '))));
1561
+ }
1562
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx
1563
+
1564
+
1565
+
1566
+ function VolumeRenderingPresets({
1567
+ viewportId,
1568
+ serviceManager,
1569
+ commandsManager,
1570
+ volumeRenderingPresets
1571
+ }) {
1572
+ const {
1573
+ uiModalService
1574
+ } = serviceManager.services;
1575
+ const onClickPresets = () => {
1576
+ uiModalService.show({
1577
+ content: VolumeRenderingPresetsContent,
1578
+ title: 'Rendering Presets',
1579
+ movable: true,
1580
+ contentProps: {
1581
+ onClose: uiModalService.hide,
1582
+ presets: volumeRenderingPresets,
1583
+ viewportId,
1584
+ commandsManager
1585
+ },
1586
+ containerDimensions: 'h-[543px] w-[460px]',
1587
+ contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]'
1588
+ });
1589
+ };
1590
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.Item */.se.q7, {
1591
+ label: "Rendering Presets",
1592
+ icon: /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1593
+ name: "VolumeRendering"
1594
+ }),
1595
+ rightIcon: /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1596
+ name: "action-new-dialog"
1597
+ }),
1598
+ onClick: onClickPresets
1599
+ });
1600
+ }
1601
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx
1602
+
1603
+ function VolumeRenderingQuality({
1604
+ volumeRenderingQualityRange,
1605
+ commandsManager,
1606
+ serviceManager,
1607
+ viewportId
1608
+ }) {
1609
+ const {
1610
+ cornerstoneViewportService
1611
+ } = serviceManager.services;
1612
+ const {
1613
+ min,
1614
+ max,
1615
+ step
1616
+ } = volumeRenderingQualityRange;
1617
+ const [quality, setQuality] = (0,react.useState)(null);
1618
+ const onChange = (0,react.useCallback)(value => {
1619
+ commandsManager.runCommand('setVolumeRenderingQulaity', {
1620
+ viewportId,
1621
+ volumeQuality: value
1622
+ });
1623
+ setQuality(value);
1624
+ }, [commandsManager, viewportId]);
1625
+ const calculateBackground = value => {
1626
+ const percentage = (value - 0) / (1 - 0) * 100;
1627
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1628
+ };
1629
+ (0,react.useEffect)(() => {
1630
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1631
+ const {
1632
+ actor
1633
+ } = viewport.getActors()[0];
1634
+ const mapper = actor.getMapper();
1635
+ const image = mapper.getInputData();
1636
+ const spacing = image.getSpacing();
1637
+ const sampleDistance = mapper.getSampleDistance();
1638
+ const averageSpacing = spacing.reduce((a, b) => a + b) / 3.0;
1639
+ if (sampleDistance === averageSpacing) {
1640
+ setQuality(1);
1641
+ } else {
1642
+ setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5)));
1643
+ }
1644
+ }, [cornerstoneViewportService, viewportId]);
1645
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1646
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1647
+ }, /*#__PURE__*/react.createElement("label", {
1648
+ className: "block text-white",
1649
+ htmlFor: "volume"
1650
+ }, "Quality"), quality !== null && /*#__PURE__*/react.createElement("input", {
1651
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1652
+ value: quality,
1653
+ id: "volume",
1654
+ max: max,
1655
+ min: min,
1656
+ type: "range",
1657
+ step: step,
1658
+ onChange: e => onChange(parseInt(e.target.value, 10)),
1659
+ style: {
1660
+ background: calculateBackground((quality - min) / (max - min)),
1661
+ '--thumb-inner-color': '#5acce6',
1662
+ '--thumb-outer-color': '#090c29'
1663
+ }
1664
+ })));
1665
+ }
1666
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx
1667
+
1668
+ function VolumeShift({
1669
+ viewportId,
1670
+ commandsManager,
1671
+ serviceManager
1672
+ }) {
1673
+ const {
1674
+ cornerstoneViewportService
1675
+ } = serviceManager.services;
1676
+ const [minShift, setMinShift] = (0,react.useState)(null);
1677
+ const [maxShift, setMaxShift] = (0,react.useState)(null);
1678
+ const [shift, setShift] = (0,react.useState)(cornerstoneViewportService.getCornerstoneViewport(viewportId)?.shiftedBy || 0);
1679
+ const [step, setStep] = (0,react.useState)(null);
1680
+ const [isBlocking, setIsBlocking] = (0,react.useState)(false);
1681
+ const prevShiftRef = (0,react.useRef)(shift);
1682
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1683
+ const {
1684
+ actor
1685
+ } = viewport.getActors()[0];
1686
+ const ofun = actor.getProperty().getScalarOpacity(0);
1687
+ (0,react.useEffect)(() => {
1688
+ if (isBlocking) {
1689
+ return;
1690
+ }
1691
+ const range = ofun.getRange();
1692
+ const transferFunctionWidth = range[1] - range[0];
1693
+ const minShift = -transferFunctionWidth;
1694
+ const maxShift = transferFunctionWidth;
1695
+ setMinShift(minShift);
1696
+ setMaxShift(maxShift);
1697
+ setStep(Math.pow(10, Math.floor(Math.log10(transferFunctionWidth / 500))));
1698
+ }, [cornerstoneViewportService, viewportId, actor, ofun, isBlocking]);
1699
+ const onChangeRange = (0,react.useCallback)(newShift => {
1700
+ const shiftDifference = newShift - prevShiftRef.current;
1701
+ prevShiftRef.current = newShift;
1702
+ viewport.shiftedBy = newShift;
1703
+ commandsManager.runCommand('shiftVolumeOpacityPoints', {
1704
+ viewportId,
1705
+ shift: shiftDifference
1706
+ });
1707
+ }, [commandsManager, viewportId, viewport]);
1708
+ const calculateBackground = value => {
1709
+ const percentage = (value - 0) / (1 - 0) * 100;
1710
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1711
+ };
1712
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1713
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1714
+ }, /*#__PURE__*/react.createElement("label", {
1715
+ className: "block text-white",
1716
+ htmlFor: "shift"
1717
+ }, "Shift"), step !== null && /*#__PURE__*/react.createElement("input", {
1718
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1719
+ value: shift,
1720
+ onChange: e => {
1721
+ const shiftValue = parseInt(e.target.value, 10);
1722
+ setShift(shiftValue);
1723
+ onChangeRange(shiftValue);
1724
+ },
1725
+ id: "shift",
1726
+ onMouseDown: () => setIsBlocking(true),
1727
+ onMouseUp: () => setIsBlocking(false),
1728
+ max: maxShift,
1729
+ min: minShift,
1730
+ type: "range",
1731
+ step: step,
1732
+ style: {
1733
+ background: calculateBackground((shift - minShift) / (maxShift - minShift)),
1734
+ '--thumb-inner-color': '#5acce6',
1735
+ '--thumb-outer-color': '#090c29'
1736
+ }
1737
+ })));
1738
+ }
1739
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsx
1740
+
1741
+ function VolumeLighting({
1742
+ serviceManager,
1743
+ commandsManager,
1744
+ viewportId
1745
+ }) {
1746
+ const {
1747
+ cornerstoneViewportService
1748
+ } = serviceManager.services;
1749
+ const [ambient, setAmbient] = (0,react.useState)(null);
1750
+ const [diffuse, setDiffuse] = (0,react.useState)(null);
1751
+ const [specular, setSpecular] = (0,react.useState)(null);
1752
+ const onAmbientChange = (0,react.useCallback)(() => {
1753
+ commandsManager.runCommand('setVolumeLighting', {
1754
+ viewportId,
1755
+ options: {
1756
+ ambient
1757
+ }
1758
+ });
1759
+ }, [ambient, commandsManager, viewportId]);
1760
+ const onDiffuseChange = (0,react.useCallback)(() => {
1761
+ commandsManager.runCommand('setVolumeLighting', {
1762
+ viewportId,
1763
+ options: {
1764
+ diffuse
1765
+ }
1766
+ });
1767
+ }, [diffuse, commandsManager, viewportId]);
1768
+ const onSpecularChange = (0,react.useCallback)(() => {
1769
+ commandsManager.runCommand('setVolumeLighting', {
1770
+ viewportId,
1771
+ options: {
1772
+ specular
1773
+ }
1774
+ });
1775
+ }, [specular, commandsManager, viewportId]);
1776
+ const calculateBackground = value => {
1777
+ const percentage = (value - 0) / (1 - 0) * 100;
1778
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1779
+ };
1780
+ (0,react.useEffect)(() => {
1781
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1782
+ const {
1783
+ actor
1784
+ } = viewport.getActors()[0];
1785
+ const ambient = actor.getProperty().getAmbient();
1786
+ const diffuse = actor.getProperty().getDiffuse();
1787
+ const specular = actor.getProperty().getSpecular();
1788
+ setAmbient(ambient);
1789
+ setDiffuse(diffuse);
1790
+ setSpecular(specular);
1791
+ }, [viewportId, cornerstoneViewportService]);
1792
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1793
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1794
+ }, /*#__PURE__*/react.createElement("label", {
1795
+ className: "block text-white",
1796
+ htmlFor: "ambient"
1797
+ }, "Ambient"), ambient !== null && /*#__PURE__*/react.createElement("input", {
1798
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1799
+ value: ambient,
1800
+ onChange: e => {
1801
+ setAmbient(e.target.value);
1802
+ onAmbientChange();
1803
+ },
1804
+ id: "ambient",
1805
+ max: 1,
1806
+ min: 0,
1807
+ type: "range",
1808
+ step: 0.1,
1809
+ style: {
1810
+ background: calculateBackground(ambient),
1811
+ '--thumb-inner-color': '#5acce6',
1812
+ '--thumb-outer-color': '#090c29'
1813
+ }
1814
+ })), /*#__PURE__*/react.createElement("div", {
1815
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1816
+ }, /*#__PURE__*/react.createElement("label", {
1817
+ className: "block text-white",
1818
+ htmlFor: "diffuse"
1819
+ }, "Diffuse"), diffuse !== null && /*#__PURE__*/react.createElement("input", {
1820
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1821
+ value: diffuse,
1822
+ onChange: e => {
1823
+ setDiffuse(e.target.value);
1824
+ onDiffuseChange();
1825
+ },
1826
+ id: "diffuse",
1827
+ max: 1,
1828
+ min: 0,
1829
+ type: "range",
1830
+ step: 0.1,
1831
+ style: {
1832
+ background: calculateBackground(diffuse),
1833
+ '--thumb-inner-color': '#5acce6',
1834
+ '--thumb-outer-color': '#090c29'
1835
+ }
1836
+ })), /*#__PURE__*/react.createElement("div", {
1837
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1838
+ }, /*#__PURE__*/react.createElement("label", {
1839
+ className: "block text-white",
1840
+ htmlFor: "specular"
1841
+ }, "Specular"), specular !== null && /*#__PURE__*/react.createElement("input", {
1842
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1843
+ value: specular,
1844
+ onChange: e => {
1845
+ setSpecular(e.target.value);
1846
+ onSpecularChange();
1847
+ },
1848
+ id: "specular",
1849
+ max: 1,
1850
+ min: 0,
1851
+ type: "range",
1852
+ step: 0.1,
1853
+ style: {
1854
+ background: calculateBackground(specular),
1855
+ '--thumb-inner-color': '#5acce6',
1856
+ '--thumb-outer-color': '#090c29'
1857
+ }
1858
+ })));
1859
+ }
1860
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx
1861
+
1862
+
1863
+ function VolumeShade({
1864
+ commandsManager,
1865
+ viewportId,
1866
+ serviceManager
1867
+ }) {
1868
+ const {
1869
+ cornerstoneViewportService
1870
+ } = serviceManager.services;
1871
+ const [shade, setShade] = (0,react.useState)(true);
1872
+ const [key, setKey] = (0,react.useState)(0);
1873
+ const onShadeChange = (0,react.useCallback)(checked => {
1874
+ commandsManager.runCommand('setVolumeLighting', {
1875
+ viewportId,
1876
+ options: {
1877
+ shade: checked
1878
+ }
1879
+ });
1880
+ }, [commandsManager, viewportId]);
1881
+ (0,react.useEffect)(() => {
1882
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1883
+ const {
1884
+ actor
1885
+ } = viewport.getActors()[0];
1886
+ const shade = actor.getProperty().getShade();
1887
+ setShade(shade);
1888
+ setKey(key + 1);
1889
+ }, [viewportId, cornerstoneViewportService]);
1890
+ return /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1891
+ key: key,
1892
+ label: "Shade",
1893
+ checked: shade,
1894
+ onChange: () => {
1895
+ setShade(!shade);
1896
+ onShadeChange(!shade);
1897
+ }
1898
+ });
1899
+ }
1900
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx
1901
+
1902
+
1903
+
1904
+
1905
+
1906
+
1907
+ function VolumeRenderingOptions({
1908
+ viewportId,
1909
+ commandsManager,
1910
+ volumeRenderingQualityRange,
1911
+ serviceManager
1912
+ }) {
1913
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, /*#__PURE__*/react.createElement(VolumeRenderingQuality, {
1914
+ viewportId: viewportId,
1915
+ commandsManager: commandsManager,
1916
+ serviceManager: serviceManager,
1917
+ volumeRenderingQualityRange: volumeRenderingQualityRange
1918
+ }), /*#__PURE__*/react.createElement(VolumeShift, {
1919
+ viewportId: viewportId,
1920
+ commandsManager: commandsManager,
1921
+ serviceManager: serviceManager
1922
+ }), /*#__PURE__*/react.createElement("div", {
1923
+ className: "all-in-one-menu-item flex !h-[20px] w-full justify-start"
1924
+ }, /*#__PURE__*/react.createElement("div", {
1925
+ className: "text-aqua-pale text-[13px]"
1926
+ }, "LIGHTING")), /*#__PURE__*/react.createElement("div", {
1927
+ className: "bg-primary-dark mt-1 mb-1 h-[2px] w-full"
1928
+ }), /*#__PURE__*/react.createElement("div", {
1929
+ className: "all-in-one-menu-item flex w-full justify-center"
1930
+ }, /*#__PURE__*/react.createElement(VolumeShade, {
1931
+ commandsManager: commandsManager,
1932
+ serviceManager: serviceManager,
1933
+ viewportId: viewportId
1934
+ })), /*#__PURE__*/react.createElement(VolumeLighting, {
1935
+ viewportId: viewportId,
1936
+ commandsManager: commandsManager,
1937
+ serviceManager: serviceManager
1938
+ }));
1939
+ }
1940
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx
1941
+
1942
+
1943
+
1944
+
1945
+
1946
+
1947
+
1948
+
1949
+
1950
+
1951
+
1952
+
1953
+ function WindowLevelActionMenu({
1954
+ viewportId,
1955
+ element,
1956
+ presets,
1957
+ verticalDirection,
1958
+ horizontalDirection,
1959
+ commandsManager,
1960
+ serviceManager,
1961
+ colorbarProperties,
1962
+ displaySets,
1963
+ volumeRenderingPresets,
1964
+ volumeRenderingQualityRange
1965
+ }) {
1966
+ const {
1967
+ colormaps,
1968
+ colorbarContainerPosition,
1969
+ colorbarInitialColormap,
1970
+ colorbarTickPosition,
1971
+ width: colorbarWidth
1972
+ } = colorbarProperties;
1973
+ const {
1974
+ colorbarService,
1975
+ cornerstoneViewportService
1976
+ } = serviceManager.services;
1977
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
1978
+ const backgroundColor = viewportInfo.getViewportOptions().background;
1979
+ const isLight = backgroundColor ? dist_esm.utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
1980
+ const nonImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE'];
1981
+ const {
1982
+ t
1983
+ } = (0,es/* useTranslation */.Bd)('WindowLevelActionMenu');
1984
+ const [viewportGrid] = (0,ui_src/* useViewportGrid */.ih)();
1985
+ const {
1986
+ activeViewportId
1987
+ } = viewportGrid;
1988
+ const [vpHeight, setVpHeight] = (0,react.useState)(element?.clientHeight);
1989
+ const [menuKey, setMenuKey] = (0,react.useState)(0);
1990
+ const [is3DVolume, setIs3DVolume] = (0,react.useState)(false);
1991
+ const onSetColorbar = (0,react.useCallback)(() => {
1992
+ setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, {
1993
+ colormaps,
1994
+ ticks: {
1995
+ position: colorbarTickPosition
1996
+ },
1997
+ width: colorbarWidth,
1998
+ position: colorbarContainerPosition,
1999
+ activeColormapName: colorbarInitialColormap
2000
+ });
2001
+ }, [commandsManager]);
2002
+ (0,react.useEffect)(() => {
2003
+ const newVpHeight = element?.clientHeight;
2004
+ if (vpHeight !== newVpHeight) {
2005
+ setVpHeight(newVpHeight);
2006
+ }
2007
+ }, [element, vpHeight]);
2008
+ (0,react.useEffect)(() => {
2009
+ if (!colorbarService.hasColorbar(viewportId)) {
2010
+ return;
2011
+ }
2012
+ window.setTimeout(() => {
2013
+ colorbarService.removeColorbar(viewportId);
2014
+ onSetColorbar();
2015
+ }, 0);
2016
+ }, [viewportId]);
2017
+ (0,react.useEffect)(() => {
2018
+ if (colorbarService.hasColorbar(viewportId)) {
2019
+ colorbarService.removeColorbar(viewportId);
2020
+ }
2021
+ }, [displaySets]);
2022
+ (0,react.useEffect)(() => {
2023
+ setMenuKey(menuKey + 1);
2024
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
2025
+ if (viewport instanceof dist_esm.VolumeViewport3D) {
2026
+ setIs3DVolume(true);
2027
+ } else {
2028
+ setIs3DVolume(false);
2029
+ }
2030
+ }, [displaySets, viewportId, presets, volumeRenderingQualityRange, volumeRenderingPresets, colorbarProperties, activeViewportId, viewportGrid]);
2031
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.IconMenu */.se.dd, {
2032
+ icon: "viewport-window-level",
2033
+ verticalDirection: verticalDirection,
2034
+ horizontalDirection: horizontalDirection,
2035
+ iconClassName: classnames_default()(
2036
+ // Visible on hover and for the active viewport
2037
+ activeViewportId === viewportId ? 'visible' : 'invisible group-hover:visible', 'flex shrink-0 cursor-pointer rounded active:text-white text-primary-light', isLight ? ' hover:bg-secondary-dark' : 'hover:bg-secondary-light/60'),
2038
+ menuStyle: {
2039
+ maxHeight: vpHeight - 32,
2040
+ minWidth: 218
2041
+ },
2042
+ onVisibilityChange: () => {
2043
+ setVpHeight(element.clientHeight);
2044
+ },
2045
+ menuKey: menuKey
2046
+ }, /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, !is3DVolume && /*#__PURE__*/react.createElement(Colorbar, {
2047
+ viewportId: viewportId,
2048
+ displaySets: displaySets.filter(ds => !nonImageModalities.includes(ds.Modality)),
2049
+ commandsManager: commandsManager,
2050
+ serviceManager: serviceManager,
2051
+ colorbarProperties: colorbarProperties
2052
+ }), colormaps && !is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2053
+ key: "colorLUTPresets",
2054
+ itemLabel: "Color LUT",
2055
+ itemIcon: "icon-color-lut"
2056
+ }, /*#__PURE__*/react.createElement(Colormap, {
2057
+ colormaps: colormaps,
2058
+ viewportId: viewportId,
2059
+ displaySets: displaySets.filter(ds => !nonImageModalities.includes(ds.Modality)),
2060
+ commandsManager: commandsManager,
2061
+ serviceManager: serviceManager
2062
+ })), presets && presets.length > 0 && !is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2063
+ key: "windowLevelPresets",
2064
+ itemLabel: t('Modality Window Presets'),
2065
+ itemIcon: "viewport-window-level"
2066
+ }, /*#__PURE__*/react.createElement(WindowLevel, {
2067
+ viewportId: viewportId,
2068
+ commandsManager: commandsManager,
2069
+ presets: presets
2070
+ })), volumeRenderingPresets && is3DVolume && /*#__PURE__*/react.createElement(VolumeRenderingPresets, {
2071
+ serviceManager: serviceManager,
2072
+ viewportId: viewportId,
2073
+ commandsManager: commandsManager,
2074
+ volumeRenderingPresets: volumeRenderingPresets
2075
+ }), volumeRenderingQualityRange && is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2076
+ itemLabel: "Rendering Options"
2077
+ }, /*#__PURE__*/react.createElement(VolumeRenderingOptions, {
2078
+ viewportId: viewportId,
2079
+ commandsManager: commandsManager,
2080
+ volumeRenderingQualityRange: volumeRenderingQualityRange,
2081
+ serviceManager: serviceManager
2082
+ }))));
2083
+ }
2084
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/getWindowLevelActionMenu.tsx
2085
+
2086
+
2087
+ function getWindowLevelActionMenu({
2088
+ viewportId,
2089
+ element,
2090
+ displaySets,
2091
+ servicesManager,
2092
+ commandsManager,
2093
+ verticalDirection,
2094
+ horizontalDirection
2095
+ }) {
2096
+ const {
2097
+ customizationService
2098
+ } = servicesManager.services;
2099
+ const {
2100
+ presets
2101
+ } = customizationService.get('cornerstone.windowLevelPresets');
2102
+ const colorbarProperties = customizationService.get('cornerstone.colorbar');
2103
+ const {
2104
+ volumeRenderingPresets,
2105
+ volumeRenderingQualityRange
2106
+ } = customizationService.get('cornerstone.3dVolumeRendering');
2107
+ const displaySetPresets = displaySets.filter(displaySet => presets[displaySet.Modality]).map(displaySet => {
2108
+ return {
2109
+ [displaySet.Modality]: presets[displaySet.Modality]
2110
+ };
2111
+ });
2112
+ return /*#__PURE__*/react.createElement(WindowLevelActionMenu, {
2113
+ viewportId: viewportId,
2114
+ element: element,
2115
+ presets: displaySetPresets,
2116
+ verticalDirection: verticalDirection,
2117
+ horizontalDirection: horizontalDirection,
2118
+ commandsManager: commandsManager,
2119
+ serviceManager: servicesManager,
2120
+ colorbarProperties: colorbarProperties,
2121
+ displaySets: displaySets,
2122
+ volumeRenderingPresets: volumeRenderingPresets,
2123
+ volumeRenderingQualityRange: volumeRenderingQualityRange
2124
+ });
2125
+ }
2126
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
2127
+
2128
+
2129
+
2130
+
2131
+
2132
+
2133
+
2134
+
2135
+
2136
+
2137
+
2138
+
2139
+
2140
+
2141
+
2142
+ const STACK = 'stack';
2143
+
2144
+ /**
2145
+ * Caches the jump to measurement operation, so that if display set is shown,
2146
+ * it can jump to the measurement.
2147
+ */
2148
+ let cacheJumpToMeasurementEvent;
2149
+ function areEqual(prevProps, nextProps) {
2150
+ if (nextProps.needsRerendering) {
2151
+ return false;
2152
+ }
2153
+ if (prevProps.displaySets.length !== nextProps.displaySets.length) {
2154
+ return false;
2155
+ }
2156
+ if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) {
2157
+ return false;
2158
+ }
2159
+ if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) {
2160
+ return false;
2161
+ }
2162
+ if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) {
2163
+ return false;
2164
+ }
2165
+ if (nextProps.viewportOptions.needsRerendering) {
2166
+ return false;
2167
+ }
2168
+ const prevDisplaySets = prevProps.displaySets;
2169
+ const nextDisplaySets = nextProps.displaySets;
2170
+ if (prevDisplaySets.length !== nextDisplaySets.length) {
2171
+ return false;
2172
+ }
2173
+ for (let i = 0; i < prevDisplaySets.length; i++) {
2174
+ const prevDisplaySet = prevDisplaySets[i];
2175
+ const foundDisplaySet = nextDisplaySets.find(nextDisplaySet => nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID);
2176
+ if (!foundDisplaySet) {
2177
+ return false;
2178
+ }
2179
+
2180
+ // check they contain the same image
2181
+ if (foundDisplaySet.images?.length !== prevDisplaySet.images?.length) {
2182
+ return false;
2183
+ }
2184
+
2185
+ // check if their imageIds are the same
2186
+ if (foundDisplaySet.images?.length) {
2187
+ for (let j = 0; j < foundDisplaySet.images.length; j++) {
2188
+ if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) {
2189
+ return false;
2190
+ }
2191
+ }
2192
+ }
2193
+ }
2194
+ return true;
2195
+ }
2196
+
2197
+ // Todo: This should be done with expose of internal API similar to react-vtkjs-viewport
2198
+ // Then we don't need to worry about the re-renders if the props change.
2199
+ const OHIFCornerstoneViewport = /*#__PURE__*/react.memo(props => {
2200
+ const {
2201
+ displaySets,
2202
+ dataSource,
2203
+ viewportOptions,
2204
+ displaySetOptions,
2205
+ servicesManager,
2206
+ onElementEnabled,
2207
+ onElementDisabled,
2208
+ isJumpToMeasurementDisabled,
2209
+ // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
2210
+ // of the imageData in the OHIFCornerstoneViewport. This prop is used
2211
+ // to set the initial state of the viewport's first image to render
2212
+ initialImageIndex
2213
+ } = props;
2214
+ const viewportId = viewportOptions.viewportId;
2215
+
2216
+ // Since we only have support for dynamic data in volume viewports, we should
2217
+ // handle this case here and set the viewportType to volume if any of the
2218
+ // displaySets are dynamic volumes
2219
+ viewportOptions.viewportType = displaySets.some(ds => ds.isDynamicVolume) ? 'volume' : viewportOptions.viewportType;
2220
+ const [scrollbarHeight, setScrollbarHeight] = (0,react.useState)('100px');
2221
+ const [enabledVPElement, setEnabledVPElement] = (0,react.useState)(null);
2222
+ const elementRef = (0,react.useRef)();
2223
+ const [appConfig] = (0,state_0/* useAppConfig */.r)();
2224
+ const {
2225
+ measurementService,
2226
+ displaySetService,
2227
+ toolbarService,
2228
+ toolGroupService,
2229
+ syncGroupService,
2230
+ cornerstoneViewportService,
2231
+ cornerstoneCacheService,
2232
+ viewportGridService,
2233
+ stateSyncService,
2234
+ viewportActionCornersService
2235
+ } = servicesManager.services;
2236
+ const [viewportDialogState] = (0,ui_src/* useViewportDialog */.OR)();
2237
+ // useCallback for scroll bar height calculation
2238
+ const setImageScrollBarHeight = (0,react.useCallback)(() => {
2239
+ const scrollbarHeight = `${elementRef.current.clientHeight - 40}px`;
2240
+ setScrollbarHeight(scrollbarHeight);
2241
+ }, [elementRef]);
2242
+
2243
+ // useCallback for onResize
2244
+ const onResize = (0,react.useCallback)(() => {
2245
+ if (elementRef.current) {
2246
+ cornerstoneViewportService.resize();
2247
+ setImageScrollBarHeight();
2248
+ }
2249
+ }, [elementRef]);
2250
+ const cleanUpServices = (0,react.useCallback)(viewportInfo => {
2251
+ const renderingEngineId = viewportInfo.getRenderingEngineId();
2252
+ const syncGroups = viewportInfo.getSyncGroups();
2253
+ toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
2254
+ syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups);
2255
+ viewportActionCornersService.clear(viewportId);
2256
+ }, [viewportId]);
2257
+ const elementEnabledHandler = (0,react.useCallback)(evt => {
2258
+ // check this is this element reference and return early if doesn't match
2259
+ if (evt.detail.element !== elementRef.current) {
2260
+ return;
2261
+ }
2262
+ const {
2263
+ viewportId,
2264
+ element
2265
+ } = evt.detail;
2266
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2267
+ (0,state/* setEnabledElement */.ye)(viewportId, element);
2268
+ setEnabledVPElement(element);
2269
+ const renderingEngineId = viewportInfo.getRenderingEngineId();
2270
+ const toolGroupId = viewportInfo.getToolGroupId();
2271
+ const syncGroups = viewportInfo.getSyncGroups();
2272
+ toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId);
2273
+ syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
2274
+ const synchronizersStore = stateSyncService.getState().synchronizersStore;
2275
+ if (synchronizersStore?.[viewportId]?.length) {
2276
+ // If the viewport used to have a synchronizer, re apply it again
2277
+ _rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService);
2278
+ }
2279
+ if (onElementEnabled) {
2280
+ onElementEnabled(evt);
2281
+ }
2282
+ }, [viewportId, onElementEnabled, toolGroupService]);
2283
+
2284
+ // disable the element upon unmounting
2285
+ (0,react.useEffect)(() => {
2286
+ cornerstoneViewportService.enableViewport(viewportId, elementRef.current);
2287
+ dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
2288
+ setImageScrollBarHeight();
2289
+ return () => {
2290
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2291
+ if (!viewportInfo) {
2292
+ return;
2293
+ }
2294
+ cornerstoneViewportService.storePresentation({
2295
+ viewportId
2296
+ });
2297
+
2298
+ // This should be done after the store presentation since synchronizers
2299
+ // will get cleaned up and they need the viewportInfo to be present
2300
+ cleanUpServices(viewportInfo);
2301
+ if (onElementDisabled) {
2302
+ onElementDisabled(viewportInfo);
2303
+ }
2304
+ cornerstoneViewportService.disableElement(viewportId);
2305
+ dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
2306
+ };
2307
+ }, []);
2308
+
2309
+ // subscribe to displaySet metadata invalidation (updates)
2310
+ // Currently, if the metadata changes we need to re-render the display set
2311
+ // for it to take effect in the viewport. As we deal with scaling in the loading,
2312
+ // we need to remove the old volume from the cache, and let the
2313
+ // viewport to re-add it which will use the new metadata. Otherwise, the
2314
+ // viewport will use the cached volume and the new metadata will not be used.
2315
+ // Note: this approach does not actually end of sending network requests
2316
+ // and it uses the network cache
2317
+ (0,react.useEffect)(() => {
2318
+ const {
2319
+ unsubscribe
2320
+ } = displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, async ({
2321
+ displaySetInstanceUID: invalidatedDisplaySetInstanceUID,
2322
+ invalidateData
2323
+ }) => {
2324
+ if (!invalidateData) {
2325
+ return;
2326
+ }
2327
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2328
+ if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
2329
+ const viewportData = viewportInfo.getViewportData();
2330
+ const newViewportData = await cornerstoneCacheService.invalidateViewportData(viewportData, invalidatedDisplaySetInstanceUID, dataSource, displaySetService);
2331
+ const keepCamera = true;
2332
+ cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera);
2333
+ }
2334
+ });
2335
+ return () => {
2336
+ unsubscribe();
2337
+ };
2338
+ }, [viewportId]);
2339
+ (0,react.useEffect)(() => {
2340
+ // handle the default viewportType to be stack
2341
+ if (!viewportOptions.viewportType) {
2342
+ viewportOptions.viewportType = STACK;
2343
+ }
2344
+ const loadViewportData = async () => {
2345
+ const viewportData = await cornerstoneCacheService.createViewportData(displaySets, viewportOptions, dataSource, initialImageIndex);
2346
+
2347
+ // The presentation state will have been stored previously by closing
2348
+ // a viewport. Otherwise, this viewport will be unchanged and the
2349
+ // presentation information will be directly carried over.
2350
+ const state = stateSyncService.getState();
2351
+ const lutPresentationStore = state.lutPresentationStore;
2352
+ const positionPresentationStore = state.positionPresentationStore;
2353
+ const {
2354
+ presentationIds
2355
+ } = viewportOptions;
2356
+ const presentations = {
2357
+ positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId],
2358
+ lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId]
2359
+ };
2360
+ let measurement;
2361
+ if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
2362
+ measurement = cacheJumpToMeasurementEvent.measurement;
2363
+ // Delete the position presentation so that viewport navigates direct
2364
+ presentations.positionPresentation = null;
2365
+ cacheJumpToMeasurementEvent = null;
2366
+ }
2367
+
2368
+ // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
2369
+ // Used for segmentation hydration right now, since the logic to decide whether
2370
+ // a viewport needs to render a segmentation lives inside the CornerstoneViewportService
2371
+ // so we need to re-render (force update via change of the needsRerendering) so that React
2372
+ // does the diffing and decides we should render this again (although the id and element has not changed)
2373
+ // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off.
2374
+ if (viewportOptions.needsRerendering) {
2375
+ viewportOptions.needsRerendering = false;
2376
+ }
2377
+ cornerstoneViewportService.setViewportData(viewportId, viewportData, viewportOptions, displaySetOptions, presentations);
2378
+ if (measurement) {
2379
+ esm.annotation.selection.setAnnotationSelected(measurement.uid);
2380
+ }
2381
+ };
2382
+ loadViewportData();
2383
+ }, [viewportOptions, displaySets, dataSource]);
2384
+
2385
+ /**
2386
+ * There are two scenarios for jump to click
2387
+ * 1. Current viewports contain the displaySet that the annotation was drawn on
2388
+ * 2. Current viewports don't contain the displaySet that the annotation was drawn on
2389
+ * and we need to change the viewports displaySet for jumping.
2390
+ * Since measurement_jump happens via events and listeners, the former case is handled
2391
+ * by the measurement_jump direct callback, but the latter case is handled first by
2392
+ * the viewportGrid to set the correct displaySet on the viewport, AND THEN we check
2393
+ * the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
2394
+ */
2395
+ (0,react.useEffect)(() => {
2396
+ if (isJumpToMeasurementDisabled) {
2397
+ return;
2398
+ }
2399
+ const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService);
2400
+ _checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService);
2401
+ return () => {
2402
+ unsubscribeFromJumpToMeasurementEvents();
2403
+ };
2404
+ }, [displaySets, elementRef, viewportId]);
2405
+
2406
+ // Set up the window level action menu in the viewport action corners.
2407
+ (0,react.useEffect)(() => {
2408
+ // Doing an === check here because the default config value when not set is true
2409
+ if (appConfig.addWindowLevelActionMenu === false) {
2410
+ return;
2411
+ }
2412
+
2413
+ // TODO: In the future we should consider using the customization service
2414
+ // to determine if and in which corner various action components should go.
2415
+ const wlActionMenu = getWindowLevelActionMenu({
2416
+ viewportId,
2417
+ element: elementRef.current,
2418
+ displaySets,
2419
+ servicesManager,
2420
+ commandsManager,
2421
+ verticalDirection: ui_src/* AllInOneMenu.VerticalDirection */.se.mq.TopToBottom,
2422
+ horizontalDirection: ui_src/* AllInOneMenu.HorizontalDirection */.se.Iu.RightToLeft
2423
+ });
2424
+ viewportActionCornersService.setComponent({
2425
+ viewportId,
2426
+ id: 'windowLevelActionMenu',
2427
+ component: wlActionMenu,
2428
+ location: viewportActionCornersService.LOCATIONS.topRight,
2429
+ indexPriority: -100
2430
+ });
2431
+ }, [displaySets, viewportId, viewportActionCornersService, servicesManager, commandsManager]);
2432
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
2433
+ className: "viewport-wrapper"
2434
+ }, /*#__PURE__*/react.createElement(index_esm/* default */.Ay, {
2435
+ onResize: onResize,
2436
+ targetRef: elementRef.current
2437
+ }), /*#__PURE__*/react.createElement("div", {
2438
+ className: "cornerstone-viewport-element",
2439
+ style: {
2440
+ height: '100%',
2441
+ width: '100%'
2442
+ },
2443
+ onContextMenu: e => e.preventDefault(),
2444
+ onMouseDown: e => e.preventDefault(),
2445
+ ref: elementRef
2446
+ }), /*#__PURE__*/react.createElement(Overlays_CornerstoneOverlays, {
2447
+ viewportId: viewportId,
2448
+ toolBarService: toolbarService,
2449
+ element: elementRef.current,
2450
+ scrollbarHeight: scrollbarHeight,
2451
+ servicesManager: servicesManager
2452
+ }), /*#__PURE__*/react.createElement(components_CinePlayer, {
2453
+ enabledVPElement: enabledVPElement,
2454
+ viewportId: viewportId,
2455
+ servicesManager: servicesManager
2456
+ })), /*#__PURE__*/react.createElement("div", {
2457
+ className: "absolute top-[24px] w-full"
2458
+ }, viewportDialogState.viewportId === viewportId && /*#__PURE__*/react.createElement(ui_src/* Notification */.Eg, {
2459
+ id: "viewport-notification",
2460
+ message: viewportDialogState.message,
2461
+ type: viewportDialogState.type,
2462
+ actions: viewportDialogState.actions,
2463
+ onSubmit: viewportDialogState.onSubmit,
2464
+ onOutsideClick: viewportDialogState.onOutsideClick,
2465
+ onKeyPress: viewportDialogState.onKeyPress
2466
+ })), /*#__PURE__*/react.createElement(components_OHIFViewportActionCorners, {
2467
+ viewportId: viewportId
2468
+ }));
2469
+ }, areEqual);
2470
+ function _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService) {
2471
+ const {
2472
+ unsubscribe
2473
+ } = measurementService.subscribe(src.MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => {
2474
+ cacheJumpToMeasurementEvent = props;
2475
+ const {
2476
+ viewportId: jumpId,
2477
+ measurement,
2478
+ isConsumed
2479
+ } = props;
2480
+ if (!measurement || isConsumed) {
2481
+ return;
2482
+ }
2483
+ if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
2484
+ // Decide on which viewport should handle this
2485
+ cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIdToJump(jumpId, measurement.displaySetInstanceUID, {
2486
+ referencedImageId: measurement.referencedImageId
2487
+ });
2488
+ }
2489
+ if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
2490
+ return;
2491
+ }
2492
+ _jumpToMeasurement(measurement, elementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
2493
+ });
2494
+ return unsubscribe;
2495
+ }
2496
+
2497
+ // Check if there is a queued jumpToMeasurement event
2498
+ function _checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService) {
2499
+ if (!cacheJumpToMeasurementEvent) {
2500
+ return;
2501
+ }
2502
+ if (cacheJumpToMeasurementEvent.isConsumed) {
2503
+ cacheJumpToMeasurementEvent = null;
2504
+ return;
2505
+ }
2506
+ const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
2507
+ if (!displaysUIDs?.length) {
2508
+ return;
2509
+ }
2510
+
2511
+ // Jump to measurement if the measurement exists
2512
+ const {
2513
+ measurement
2514
+ } = cacheJumpToMeasurementEvent;
2515
+ if (measurement && elementRef) {
2516
+ if (displaysUIDs.includes(measurement?.displaySetInstanceUID)) {
2517
+ _jumpToMeasurement(measurement, elementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
2518
+ }
2519
+ }
2520
+ }
2521
+ function _jumpToMeasurement(measurement, targetElementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService) {
2522
+ const targetElement = targetElementRef.current;
2523
+ const {
2524
+ displaySetInstanceUID,
2525
+ SOPInstanceUID,
2526
+ frameNumber
2527
+ } = measurement;
2528
+ if (!SOPInstanceUID) {
2529
+ console.warn('cannot jump in a non-acquisition plane measurements yet');
2530
+ return;
2531
+ }
2532
+ const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
2533
+
2534
+ // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
2535
+ // to set it properly
2536
+ // setCornerstoneMeasurementActive(measurement);
2537
+
2538
+ viewportGridService.setActiveViewportId(viewportId);
2539
+ const enabledElement = (0,dist_esm.getEnabledElement)(targetElement);
2540
+ if (enabledElement) {
2541
+ // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
2542
+ const viewport = enabledElement.viewport;
2543
+ let imageIdIndex = 0;
2544
+ let viewportCameraDirectionMatch = true;
2545
+ if (viewport instanceof dist_esm.StackViewport) {
2546
+ const imageIds = viewport.getImageIds();
2547
+ imageIdIndex = imageIds.findIndex(imageId => {
2548
+ const {
2549
+ SOPInstanceUID: aSOPInstanceUID,
2550
+ frameNumber: aFrameNumber
2551
+ } = (0,getSOPInstanceAttributes/* default */.A)(imageId);
2552
+ return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber);
2553
+ });
2554
+ } else {
2555
+ // for volume viewport we can't rely on the imageIdIndex since it can be
2556
+ // a reconstructed view that doesn't match the original slice numbers etc.
2557
+ const {
2558
+ viewPlaneNormal: measurementViewPlane
2559
+ } = measurement.metadata;
2560
+ imageIdIndex = referencedDisplaySet.images.findIndex(i => i.SOPInstanceUID === SOPInstanceUID);
2561
+ const {
2562
+ viewPlaneNormal: viewportViewPlane
2563
+ } = viewport.getCamera();
2564
+
2565
+ // should compare abs for both planes since the direction can be flipped
2566
+ if (measurementViewPlane && !dist_esm.utilities.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs))) {
2567
+ viewportCameraDirectionMatch = false;
2568
+ }
2569
+ }
2570
+ if (!viewportCameraDirectionMatch || imageIdIndex === -1) {
2571
+ return;
2572
+ }
2573
+ esm.utilities.jumpToSlice(targetElement, {
2574
+ imageIndex: imageIdIndex
2575
+ });
2576
+ esm.annotation.selection.setAnnotationSelected(measurement.uid);
2577
+ // Jump to measurement consumed, remove.
2578
+ cacheJumpToMeasurementEvent?.consume?.();
2579
+ cacheJumpToMeasurementEvent = null;
2580
+ }
2581
+ }
2582
+ function _rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService) {
2583
+ synchronizersStore[viewportId].forEach(synchronizerObj => {
2584
+ if (!synchronizerObj.id) {
2585
+ return;
2586
+ }
2587
+ const {
2588
+ id,
2589
+ sourceViewports,
2590
+ targetViewports
2591
+ } = synchronizerObj;
2592
+ const synchronizer = syncGroupService.getSynchronizer(id);
2593
+ if (!synchronizer) {
2594
+ return;
2595
+ }
2596
+ const sourceViewportInfo = sourceViewports.find(sourceViewport => sourceViewport.viewportId === viewportId);
2597
+ const targetViewportInfo = targetViewports.find(targetViewport => targetViewport.viewportId === viewportId);
2598
+ const isSourceViewportInSynchronizer = synchronizer.getSourceViewports().find(sourceViewport => sourceViewport.viewportId === viewportId);
2599
+ const isTargetViewportInSynchronizer = synchronizer.getTargetViewports().find(targetViewport => targetViewport.viewportId === viewportId);
2600
+
2601
+ // if the viewport was previously a source viewport, add it again
2602
+ if (sourceViewportInfo && !isSourceViewportInSynchronizer) {
2603
+ synchronizer.addSource({
2604
+ viewportId: sourceViewportInfo.viewportId,
2605
+ renderingEngineId: sourceViewportInfo.renderingEngineId
2606
+ });
2607
+ }
2608
+
2609
+ // if the viewport was previously a target viewport, add it again
2610
+ if (targetViewportInfo && !isTargetViewportInSynchronizer) {
2611
+ synchronizer.addTarget({
2612
+ viewportId: targetViewportInfo.viewportId,
2613
+ renderingEngineId: targetViewportInfo.renderingEngineId
2614
+ });
2615
+ }
2616
+ });
2617
+ }
2618
+
2619
+ // Component displayName
2620
+ OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
2621
+ OHIFCornerstoneViewport.defaultProps = {
2622
+ isJumpToMeasurementDisabled: false
2623
+ };
2624
+ OHIFCornerstoneViewport.propTypes = {
2625
+ displaySets: (prop_types_default()).array.isRequired,
2626
+ dataSource: (prop_types_default()).object.isRequired,
2627
+ viewportOptions: (prop_types_default()).object,
2628
+ displaySetOptions: prop_types_default().arrayOf((prop_types_default()).any),
2629
+ servicesManager: (prop_types_default()).object.isRequired,
2630
+ onElementEnabled: (prop_types_default()).func,
2631
+ isJumpToMeasurementDisabled: (prop_types_default()).bool,
2632
+ // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
2633
+ // of the imageData in the OHIFCornerstoneViewport. This prop is used
2634
+ // to set the initial state of the viewport's first image to render
2635
+ initialImageIdOrIndex: prop_types_default().oneOfType([(prop_types_default()).string, (prop_types_default()).number])
2636
+ };
2637
+ /* harmony default export */ const Viewport_OHIFCornerstoneViewport = (OHIFCornerstoneViewport);
2638
+
2639
+ /***/ })
2640
+
2641
+ }]);