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

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.47f05840a5b3cdf75543.js} +94 -113
  3. package/dist/141.bundle.556b4c1e4cab770417ac.js +8620 -0
  4. package/dist/{687.bundle.9065db35c01823286f08.js → 164.bundle.fadc7c5d634402c73b5f.js} +22 -38
  5. package/dist/17dd54813d5acc10bf8f.wasm +0 -0
  6. package/dist/183.bundle.a3e238998be71c4b2af8.js +30410 -0
  7. package/dist/{506.bundle.5731bb4349e266491225.js → 188.bundle.51dc4b37920f45594393.js} +23 -28
  8. package/dist/{342.bundle.e7c3d500f86fdfcc62b5.js → 206.bundle.fcaa081a0d1f68095c31.js} +1991 -1145
  9. package/dist/20fc4c659b85ccd2a9c0.wasm +0 -0
  10. package/dist/217.bundle.d44bbaa50b6fa563fe15.js +115126 -0
  11. package/dist/{451.bundle.57c21db5d003c75e9d61.js → 295.bundle.5ace95771ced62bdcab8.js} +111 -128
  12. package/dist/{125.bundle.253395f320b72180da63.js → 297.bundle.194d8985ab974839b5b6.js} +7 -8
  13. package/dist/{19.bundle.f77c5787b6d8ac0b638b.js → 325.bundle.fd8e0c18db4708d03a91.js} +477 -373
  14. package/dist/335.bundle.8400aa5a88697a6b9d53.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.0905b258a90a7c6437bb.js} +7453 -3624
  17. package/dist/422.bundle.c6fd037b075dd54f1ba7.js +865 -0
  18. package/dist/{957.bundle.9ea4506963ef8b2d84ba.js → 433.bundle.e0018820758f5a86fa7f.js} +14797 -27561
  19. package/dist/445.bundle.38c6d2af64e41cd7c614.js +7835 -0
  20. package/dist/{126.bundle.6e7111d58bcc937ffd80.js → 448.bundle.5e6da31477887bf53016.js} +356 -430
  21. package/dist/487.bundle.89d973049defb3ba6cb7.js +1876 -0
  22. package/dist/{886.bundle.c8dd3ecc42a4253de278.js → 530.bundle.207b38c15c4c01e4db0e.js} +104 -121
  23. package/dist/{250.bundle.aea3335667054bdefe36.js → 544.bundle.1c1f57118560046649c1.js} +37 -62
  24. package/dist/574.bundle.d648fea691d6709bf2b4.js +2652 -0
  25. package/dist/{181.css → 574.css} +1 -1
  26. package/dist/{410.bundle.15c855b0ff4a1a674fb8.js → 594.bundle.84076375b127b9c7f673.js} +183 -221
  27. package/dist/{221.bundle.aef554202c58483cb34e.js → 633.bundle.acab89baaa06a299d679.js} +365 -553
  28. package/dist/{774.bundle.4b2dc46a35012b898e1a.js → 644.bundle.1e77691d2eeb96a423b0.js} +1852 -8945
  29. package/dist/{663.bundle.d7be28450db14266cdd0.js → 669.bundle.b17e8a621e38d92c653f.js} +310 -265
  30. package/dist/699.bundle.9367d7ef9f7615b2e733.js +772 -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.55f9f49816de931af91a.js} +165 -260
  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.d32ab08e64806b2e964d.js} +81 -97
  37. package/dist/{236.bundle.4e9924934a747afac132.js → 889.bundle.8ef8b723d0163d5d135c.js} +207 -199
  38. package/dist/{281.bundle.deb7492d143e7768d8bf.js → 905.bundle.8a96e1a75b7cfe5ec093.js} +157 -124
  39. package/dist/{814.bundle.c8c951d20039b63b865a.js → 907.bundle.5c88ed911bed18582da4.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.f5f2479c214180d05d42.js +778 -0
  43. package/dist/{12.bundle.b5ca13e5363f170ecb3b.js → 961.bundle.f4e52bc76d3044d05372.js} +20 -33
  44. package/dist/app-config.js +1 -0
  45. package/dist/app.bundle.css +16 -13
  46. package/dist/{app.bundle.a978edc59b9d82f2eb22.js → app.bundle.ed937512f7d19d61c411.js} +183396 -87682
  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.f1a6ece1396dc1385155.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,2652 @@
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
+ viewportId
1009
+ }) : cineService.stopClip(enabledVPElement);
1010
+ };
1011
+ const newDisplaySetHandler = (0,react.useCallback)(() => {
1012
+ if (!enabledVPElement || !isCineEnabled) {
1013
+ return;
1014
+ }
1015
+ const {
1016
+ viewports
1017
+ } = viewportGridService.getState();
1018
+ const {
1019
+ displaySetInstanceUIDs
1020
+ } = viewports.get(viewportId);
1021
+ let frameRate = 24;
1022
+ let isPlaying = cines[viewportId]?.isPlaying || false;
1023
+ displaySetInstanceUIDs.forEach(displaySetInstanceUID => {
1024
+ const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
1025
+ if (displaySet.FrameRate) {
1026
+ // displaySet.FrameRate corresponds to DICOM tag (0018,1063) which is defined as the the frame time in milliseconds
1027
+ // So a bit of math to get the actual frame rate.
1028
+ frameRate = Math.round(1000 / displaySet.FrameRate);
1029
+ isPlaying ||= !!appConfig.autoPlayCine;
1030
+ }
1031
+
1032
+ // check if the displaySet is dynamic and set the dynamic info
1033
+ if (displaySet.isDynamicVolume) {
1034
+ const {
1035
+ dynamicVolumeInfo
1036
+ } = displaySet;
1037
+ const numTimePoints = dynamicVolumeInfo.timePoints.length;
1038
+ const label = dynamicVolumeInfo.splittingTag;
1039
+ const timePointIndex = dynamicVolumeInfo.timePointIndex || 0;
1040
+ setDynamicInfo({
1041
+ volumeId: displaySet.displaySetInstanceUID,
1042
+ timePointIndex,
1043
+ numTimePoints,
1044
+ label
1045
+ });
1046
+ } else {
1047
+ setDynamicInfo(null);
1048
+ }
1049
+ });
1050
+ if (isPlaying) {
1051
+ cineService.setIsCineEnabled(isPlaying);
1052
+ }
1053
+ cineService.setCine({
1054
+ id: viewportId,
1055
+ isPlaying,
1056
+ frameRate
1057
+ });
1058
+ setNewStackFrameRate(frameRate);
1059
+ }, [displaySetService, viewportId, viewportGridService, cines, isCineEnabled]);
1060
+ (0,react.useEffect)(() => {
1061
+ isMountedRef.current = true;
1062
+ newDisplaySetHandler();
1063
+ return () => {
1064
+ isMountedRef.current = false;
1065
+ };
1066
+ }, [isCineEnabled, newDisplaySetHandler]);
1067
+
1068
+ /**
1069
+ * Use effect for handling new display set
1070
+ */
1071
+ (0,react.useEffect)(() => {
1072
+ if (!enabledVPElement) {
1073
+ return;
1074
+ }
1075
+ dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
1076
+ // this doesn't makes sense that we are listening to this event on viewport element
1077
+ enabledVPElement.addEventListener(dist_esm.Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, newDisplaySetHandler);
1078
+ return () => {
1079
+ cineService.setCine({
1080
+ id: viewportId,
1081
+ isPlaying: false
1082
+ });
1083
+ dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.STACK_VIEWPORT_NEW_STACK, newDisplaySetHandler);
1084
+ enabledVPElement.removeEventListener(dist_esm.Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, newDisplaySetHandler);
1085
+ };
1086
+ }, [enabledVPElement, newDisplaySetHandler, viewportId]);
1087
+ (0,react.useEffect)(() => {
1088
+ if (!cines || !cines[viewportId] || !enabledVPElement || !isMountedRef.current) {
1089
+ return;
1090
+ }
1091
+ cineHandler();
1092
+ return () => {
1093
+ cineService.stopClip(enabledVPElement);
1094
+ };
1095
+ }, [cines, viewportId, cineService, enabledVPElement, cineHandler]);
1096
+ if (!isCineEnabled) {
1097
+ return null;
1098
+ }
1099
+ const cine = cines[viewportId];
1100
+ const isPlaying = cine?.isPlaying || false;
1101
+ return /*#__PURE__*/react.createElement(RenderCinePlayer, {
1102
+ viewportId: viewportId,
1103
+ cineService: cineService,
1104
+ newStackFrameRate: newStackFrameRate,
1105
+ isPlaying: isPlaying,
1106
+ dynamicInfo: dynamicInfo,
1107
+ customizationService: customizationService
1108
+ });
1109
+ }
1110
+ function RenderCinePlayer({
1111
+ viewportId,
1112
+ cineService,
1113
+ newStackFrameRate,
1114
+ isPlaying,
1115
+ dynamicInfo: dynamicInfoProp,
1116
+ customizationService
1117
+ }) {
1118
+ const {
1119
+ component: CinePlayerComponent = ui_src/* CinePlayer */.F0
1120
+ } = customizationService.get('cinePlayer') ?? {};
1121
+ const [dynamicInfo, setDynamicInfo] = (0,react.useState)(dynamicInfoProp);
1122
+ (0,react.useEffect)(() => {
1123
+ setDynamicInfo(dynamicInfoProp);
1124
+ }, [dynamicInfoProp]);
1125
+
1126
+ /**
1127
+ * Use effect for handling 4D time index changed
1128
+ */
1129
+ (0,react.useEffect)(() => {
1130
+ if (!dynamicInfo) {
1131
+ return;
1132
+ }
1133
+ const handleTimePointIndexChange = evt => {
1134
+ const {
1135
+ volumeId,
1136
+ timePointIndex,
1137
+ numTimePoints,
1138
+ splittingTag
1139
+ } = evt.detail;
1140
+ setDynamicInfo({
1141
+ volumeId,
1142
+ timePointIndex,
1143
+ numTimePoints,
1144
+ label: splittingTag
1145
+ });
1146
+ };
1147
+ dist_esm.eventTarget.addEventListener(streaming_image_volume_loader_dist_esm/* Enums.Events */.fX.s.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED, handleTimePointIndexChange);
1148
+ return () => {
1149
+ dist_esm.eventTarget.removeEventListener(streaming_image_volume_loader_dist_esm/* Enums.Events */.fX.s.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED, handleTimePointIndexChange);
1150
+ };
1151
+ }, [dynamicInfo]);
1152
+ (0,react.useEffect)(() => {
1153
+ if (!dynamicInfo) {
1154
+ return;
1155
+ }
1156
+ const {
1157
+ volumeId,
1158
+ timePointIndex,
1159
+ numTimePoints,
1160
+ splittingTag
1161
+ } = dynamicInfo || {};
1162
+ const volume = dist_esm.cache.getVolume(volumeId);
1163
+ volume.timePointIndex = timePointIndex;
1164
+ setDynamicInfo({
1165
+ volumeId,
1166
+ timePointIndex,
1167
+ numTimePoints,
1168
+ label: splittingTag
1169
+ });
1170
+ }, []);
1171
+ const updateDynamicInfo = (0,react.useCallback)(props => {
1172
+ const {
1173
+ volumeId,
1174
+ timePointIndex
1175
+ } = props;
1176
+ const volume = dist_esm.cache.getVolume(volumeId);
1177
+ volume.timePointIndex = timePointIndex;
1178
+ }, []);
1179
+ return /*#__PURE__*/react.createElement(CinePlayerComponent, {
1180
+ className: "absolute left-1/2 bottom-3 -translate-x-1/2",
1181
+ frameRate: newStackFrameRate,
1182
+ isPlaying: isPlaying,
1183
+ onClose: () => {
1184
+ // also stop the clip
1185
+ cineService.setCine({
1186
+ id: viewportId,
1187
+ isPlaying: false
1188
+ });
1189
+ cineService.setIsCineEnabled(false);
1190
+ },
1191
+ onPlayPauseChange: isPlaying => {
1192
+ cineService.setCine({
1193
+ id: viewportId,
1194
+ isPlaying
1195
+ });
1196
+ },
1197
+ onFrameRateChange: frameRate => cineService.setCine({
1198
+ id: viewportId,
1199
+ frameRate
1200
+ }),
1201
+ dynamicInfo: dynamicInfo,
1202
+ updateDynamicInfo: updateDynamicInfo
1203
+ });
1204
+ }
1205
+ /* harmony default export */ const CinePlayer = (WrappedCinePlayer);
1206
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/CinePlayer/index.ts
1207
+
1208
+ /* harmony default export */ const components_CinePlayer = (CinePlayer);
1209
+ // EXTERNAL MODULE: ../../../extensions/cornerstone/src/contextProviders/ViewportActionCornersProvider.tsx
1210
+ var ViewportActionCornersProvider = __webpack_require__(76255);
1211
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/OHIFViewportActionCorners.tsx
1212
+
1213
+
1214
+
1215
+ function OHIFViewportActionCorners({
1216
+ viewportId
1217
+ }) {
1218
+ const [viewportActionCornersState] = (0,ViewportActionCornersProvider/* useViewportActionCornersContext */.R4)();
1219
+ if (!viewportActionCornersState[viewportId]) {
1220
+ return null;
1221
+ }
1222
+ return /*#__PURE__*/react.createElement(ui_src/* ViewportActionCorners */.R2, {
1223
+ cornerComponents: viewportActionCornersState[viewportId]
1224
+ });
1225
+ }
1226
+ /* harmony default export */ const components_OHIFViewportActionCorners = (OHIFViewportActionCorners);
1227
+ // EXTERNAL MODULE: ../../../node_modules/react-i18next/dist/es/index.js + 15 modules
1228
+ var es = __webpack_require__(80619);
1229
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/Colormap.tsx
1230
+
1231
+
1232
+
1233
+ function Colormap({
1234
+ colormaps,
1235
+ viewportId,
1236
+ displaySets,
1237
+ commandsManager,
1238
+ serviceManager
1239
+ }) {
1240
+ const {
1241
+ cornerstoneViewportService
1242
+ } = serviceManager.services;
1243
+ const [activeDisplaySet, setActiveDisplaySet] = (0,react.useState)(displaySets[0]);
1244
+ const [showPreview, setShowPreview] = (0,react.useState)(false);
1245
+ const [prePreviewColormap, setPrePreviewColormap] = (0,react.useState)(null);
1246
+ const showPreviewRef = (0,react.useRef)(showPreview);
1247
+ showPreviewRef.current = showPreview;
1248
+ const prePreviewColormapRef = (0,react.useRef)(prePreviewColormap);
1249
+ prePreviewColormapRef.current = prePreviewColormap;
1250
+ const activeDisplaySetRef = (0,react.useRef)(activeDisplaySet);
1251
+ activeDisplaySetRef.current = activeDisplaySet;
1252
+ const onSetColorLUT = (0,react.useCallback)(props => {
1253
+ // TODO: Better way to check if it's a fusion
1254
+ const oneOpacityColormaps = ['Grayscale', 'X Ray'];
1255
+ const opacity = displaySets.length > 1 && !oneOpacityColormaps.includes(props.colormap.name) ? 0.5 : 1;
1256
+ commandsManager.run({
1257
+ commandName: 'setViewportColormap',
1258
+ commandOptions: {
1259
+ ...props,
1260
+ opacity,
1261
+ immediate: true
1262
+ },
1263
+ context: 'CORNERSTONE'
1264
+ });
1265
+ }, [commandsManager]);
1266
+ const getViewportColormap = (viewportId, displaySet) => {
1267
+ const {
1268
+ displaySetInstanceUID
1269
+ } = displaySet;
1270
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1271
+ if (viewport instanceof dist_esm.StackViewport) {
1272
+ const {
1273
+ colormap
1274
+ } = viewport.getProperties();
1275
+ if (!colormap) {
1276
+ return colormaps.find(c => c.Name === 'Grayscale') || colormaps[0];
1277
+ }
1278
+ return colormap;
1279
+ }
1280
+ const actorEntries = viewport.getActors();
1281
+ const actorEntry = actorEntries.find(entry => entry.uid.includes(displaySetInstanceUID));
1282
+ const {
1283
+ colormap
1284
+ } = viewport.getProperties(actorEntry.uid);
1285
+ if (!colormap) {
1286
+ return colormaps.find(c => c.Name === 'Grayscale') || colormaps[0];
1287
+ }
1288
+ return colormap;
1289
+ };
1290
+ const buttons = (0,react.useMemo)(() => {
1291
+ return displaySets.map((displaySet, index) => ({
1292
+ children: displaySet.Modality,
1293
+ key: index,
1294
+ style: {
1295
+ minWidth: `calc(100% / ${displaySets.length})`,
1296
+ fontSize: '0.8rem',
1297
+ textAlign: 'center',
1298
+ display: 'flex',
1299
+ justifyContent: 'center',
1300
+ alignItems: 'center'
1301
+ }
1302
+ }));
1303
+ }, [displaySets]);
1304
+ (0,react.useEffect)(() => {
1305
+ setActiveDisplaySet(displaySets[displaySets.length - 1]);
1306
+ }, [displaySets]);
1307
+ return /*#__PURE__*/react.createElement(react.Fragment, null, buttons.length > 1 && /*#__PURE__*/react.createElement("div", {
1308
+ className: "all-in-one-menu-item flex w-full justify-center"
1309
+ }, /*#__PURE__*/react.createElement(ui_src/* ButtonGroup */.e2, {
1310
+ onActiveIndexChange: index => {
1311
+ setActiveDisplaySet(displaySets[index]);
1312
+ setPrePreviewColormap(null);
1313
+ },
1314
+ activeIndex: displaySets.findIndex(ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID) || 1,
1315
+ className: "w-[70%] text-[10px]"
1316
+ }, buttons.map(({
1317
+ children,
1318
+ key,
1319
+ style
1320
+ }) => /*#__PURE__*/react.createElement("div", {
1321
+ key: key,
1322
+ style: style
1323
+ }, children)))), /*#__PURE__*/react.createElement("div", {
1324
+ className: "all-in-one-menu-item flex w-full justify-center"
1325
+ }, /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1326
+ label: "Preview in viewport",
1327
+ checked: showPreview,
1328
+ onChange: checked => {
1329
+ setShowPreview(checked);
1330
+ }
1331
+ })), /*#__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, {
1332
+ key: index,
1333
+ label: colormap.description,
1334
+ onClick: () => {
1335
+ onSetColorLUT({
1336
+ viewportId,
1337
+ colormap,
1338
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1339
+ });
1340
+ setPrePreviewColormap(null);
1341
+ },
1342
+ onMouseEnter: () => {
1343
+ if (showPreviewRef.current) {
1344
+ setPrePreviewColormap(getViewportColormap(viewportId, activeDisplaySetRef.current));
1345
+ onSetColorLUT({
1346
+ viewportId,
1347
+ colormap,
1348
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1349
+ });
1350
+ }
1351
+ },
1352
+ onMouseLeave: () => {
1353
+ if (showPreviewRef.current && prePreviewColormapRef.current) {
1354
+ onSetColorLUT({
1355
+ viewportId,
1356
+ colormap: prePreviewColormapRef.current,
1357
+ displaySetInstanceUID: activeDisplaySetRef.current.displaySetInstanceUID
1358
+ });
1359
+ }
1360
+ }
1361
+ }))));
1362
+ }
1363
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/Colorbar.tsx
1364
+
1365
+
1366
+
1367
+
1368
+ function setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, colorbarOptions) {
1369
+ const {
1370
+ cornerstoneViewportService
1371
+ } = serviceManager.services;
1372
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1373
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
1374
+ const backgroundColor = viewportInfo.getViewportOptions().background;
1375
+ const isLight = backgroundColor ? dist_esm.utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
1376
+ if (isLight) {
1377
+ colorbarOptions.ticks = {
1378
+ position: 'left',
1379
+ style: {
1380
+ font: '12px Arial',
1381
+ color: '#000000',
1382
+ maxNumTicks: 8,
1383
+ tickSize: 5,
1384
+ tickWidth: 1,
1385
+ labelMargin: 3
1386
+ }
1387
+ };
1388
+ }
1389
+ const displaySetInstanceUIDs = [];
1390
+ if (viewport instanceof dist_esm.StackViewport) {
1391
+ displaySetInstanceUIDs.push(viewportId);
1392
+ }
1393
+ if (viewport instanceof dist_esm.VolumeViewport) {
1394
+ displaySets.forEach(ds => {
1395
+ displaySetInstanceUIDs.push(ds.displaySetInstanceUID);
1396
+ });
1397
+ }
1398
+ commandsManager.run({
1399
+ commandName: 'toggleViewportColorbar',
1400
+ commandOptions: {
1401
+ viewportId,
1402
+ options: colorbarOptions,
1403
+ displaySetInstanceUIDs
1404
+ },
1405
+ context: 'CORNERSTONE'
1406
+ });
1407
+ }
1408
+ function Colorbar({
1409
+ viewportId,
1410
+ displaySets,
1411
+ commandsManager,
1412
+ serviceManager,
1413
+ colorbarProperties
1414
+ }) {
1415
+ const {
1416
+ colorbarService
1417
+ } = serviceManager.services;
1418
+ const {
1419
+ width: colorbarWidth,
1420
+ colorbarTickPosition,
1421
+ colorbarContainerPosition,
1422
+ colormaps,
1423
+ colorbarInitialColormap
1424
+ } = colorbarProperties;
1425
+ const [showColorbar, setShowColorbar] = (0,react.useState)(colorbarService.hasColorbar(viewportId));
1426
+ const onSetColorbar = (0,react.useCallback)(() => {
1427
+ setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, {
1428
+ viewportId,
1429
+ colormaps,
1430
+ ticks: {
1431
+ position: colorbarTickPosition
1432
+ },
1433
+ width: colorbarWidth,
1434
+ position: colorbarContainerPosition,
1435
+ activeColormapName: colorbarInitialColormap
1436
+ });
1437
+ }, [commandsManager]);
1438
+ (0,react.useEffect)(() => {
1439
+ const updateColorbarState = () => {
1440
+ setShowColorbar(colorbarService.hasColorbar(viewportId));
1441
+ };
1442
+ const {
1443
+ unsubscribe
1444
+ } = colorbarService.subscribe(colorbarService.EVENTS.STATE_CHANGED, updateColorbarState);
1445
+ return () => {
1446
+ unsubscribe();
1447
+ };
1448
+ }, [viewportId]);
1449
+ return /*#__PURE__*/react.createElement("div", {
1450
+ className: "all-in-one-menu-item flex w-full justify-center"
1451
+ }, /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1452
+ label: "Display Color bar",
1453
+ checked: showColorbar,
1454
+ onChange: () => {
1455
+ onSetColorbar();
1456
+ }
1457
+ }));
1458
+ }
1459
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevel.tsx
1460
+
1461
+
1462
+
1463
+ function WindowLevel({
1464
+ viewportId,
1465
+ commandsManager,
1466
+ presets
1467
+ }) {
1468
+ const {
1469
+ t
1470
+ } = (0,es/* useTranslation */.Bd)('WindowLevelActionMenu');
1471
+ const onSetWindowLevel = (0,react.useCallback)(props => {
1472
+ commandsManager.run({
1473
+ commandName: 'setViewportWindowLevel',
1474
+ commandOptions: {
1475
+ ...props,
1476
+ viewportId
1477
+ },
1478
+ context: 'CORNERSTONE'
1479
+ });
1480
+ }, [commandsManager, viewportId]);
1481
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, presets.map((modalityPresets, modalityIndex) => /*#__PURE__*/react.createElement(react.Fragment, {
1482
+ key: modalityIndex
1483
+ }, Object.entries(modalityPresets).map(([modality, presetsArray]) => /*#__PURE__*/react.createElement(react.Fragment, {
1484
+ key: modality
1485
+ }, /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.HeaderItem */.se.N5, null, t('Modality Presets', {
1486
+ modality
1487
+ })), presetsArray.map((preset, index) => /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.Item */.se.q7, {
1488
+ key: `${modality}-${index}`,
1489
+ label: preset.description,
1490
+ secondaryLabel: `${preset.window} / ${preset.level}`,
1491
+ onClick: () => onSetWindowLevel(preset)
1492
+ })))))));
1493
+ }
1494
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresetsContent.tsx
1495
+
1496
+
1497
+
1498
+
1499
+ function VolumeRenderingPresetsContent({
1500
+ presets,
1501
+ viewportId,
1502
+ commandsManager,
1503
+ onClose
1504
+ }) {
1505
+ const [filteredPresets, setFilteredPresets] = (0,react.useState)(presets);
1506
+ const [searchValue, setSearchValue] = (0,react.useState)('');
1507
+ const [selectedPreset, setSelectedPreset] = (0,react.useState)(null);
1508
+ const handleSearchChange = (0,react.useCallback)(value => {
1509
+ setSearchValue(value);
1510
+ const filtered = value ? presets.filter(preset => preset.name.toLowerCase().includes(value.toLowerCase())) : presets;
1511
+ setFilteredPresets(filtered);
1512
+ }, [presets]);
1513
+ const handleApply = (0,react.useCallback)(props => {
1514
+ commandsManager.runCommand('setViewportPreset', {
1515
+ ...props
1516
+ });
1517
+ }, [commandsManager]);
1518
+ const formatLabel = (label, maxChars) => {
1519
+ return label.length > maxChars ? `${label.slice(0, maxChars)}...` : label;
1520
+ };
1521
+ return /*#__PURE__*/react.createElement("div", {
1522
+ className: "flex min-h-full w-full flex-col justify-between"
1523
+ }, /*#__PURE__*/react.createElement("div", {
1524
+ className: "border-secondary-light h-[433px] w-full overflow-hidden rounded border bg-black px-2.5"
1525
+ }, /*#__PURE__*/react.createElement("div", {
1526
+ className: "flex h-[46px] w-full items-center justify-start"
1527
+ }, /*#__PURE__*/react.createElement("div", {
1528
+ className: "h-[26px] w-[200px]"
1529
+ }, /*#__PURE__*/react.createElement(ui_src/* InputFilterText */.Cv, {
1530
+ value: searchValue,
1531
+ onDebounceChange: handleSearchChange,
1532
+ placeholder: 'Search all'
1533
+ }))), /*#__PURE__*/react.createElement("div", {
1534
+ className: "ohif-scrollbar overflow h-[385px] w-full overflow-y-auto"
1535
+ }, /*#__PURE__*/react.createElement("div", {
1536
+ className: "grid grid-cols-4 gap-3 pt-2 pr-3"
1537
+ }, filteredPresets.map((preset, index) => /*#__PURE__*/react.createElement("div", {
1538
+ key: index,
1539
+ className: "flex cursor-pointer flex-col items-start",
1540
+ onClick: () => {
1541
+ setSelectedPreset(preset);
1542
+ handleApply({
1543
+ preset: preset.name,
1544
+ viewportId
1545
+ });
1546
+ }
1547
+ }, /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1548
+ name: preset.name,
1549
+ 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'
1550
+ }), /*#__PURE__*/react.createElement("label", {
1551
+ className: "text-aqua-pale mt-2 text-left text-xs"
1552
+ }, formatLabel(preset.name, 11))))))), /*#__PURE__*/react.createElement("footer", {
1553
+ className: "flex h-[60px] w-full items-center justify-end"
1554
+ }, /*#__PURE__*/react.createElement("div", {
1555
+ className: "flex"
1556
+ }, /*#__PURE__*/react.createElement(ui_src/* Button */.$n, {
1557
+ name: "Cancel",
1558
+ size: ui_src/* ButtonEnums.size */.Ny.Ej.medium,
1559
+ type: ui_src/* ButtonEnums.type */.Ny.NW.secondary,
1560
+ onClick: onClose
1561
+ }, ' ', "Cancel", ' '))));
1562
+ }
1563
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingPresets.tsx
1564
+
1565
+
1566
+
1567
+ function VolumeRenderingPresets({
1568
+ viewportId,
1569
+ serviceManager,
1570
+ commandsManager,
1571
+ volumeRenderingPresets
1572
+ }) {
1573
+ const {
1574
+ uiModalService
1575
+ } = serviceManager.services;
1576
+ const onClickPresets = () => {
1577
+ uiModalService.show({
1578
+ content: VolumeRenderingPresetsContent,
1579
+ title: 'Rendering Presets',
1580
+ movable: true,
1581
+ contentProps: {
1582
+ onClose: uiModalService.hide,
1583
+ presets: volumeRenderingPresets,
1584
+ viewportId,
1585
+ commandsManager
1586
+ },
1587
+ containerDimensions: 'h-[543px] w-[460px]',
1588
+ contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]'
1589
+ });
1590
+ };
1591
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.Item */.se.q7, {
1592
+ label: "Rendering Presets",
1593
+ icon: /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1594
+ name: "VolumeRendering"
1595
+ }),
1596
+ rightIcon: /*#__PURE__*/react.createElement(ui_src/* Icon */.In, {
1597
+ name: "action-new-dialog"
1598
+ }),
1599
+ onClick: onClickPresets
1600
+ });
1601
+ }
1602
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingQuality.tsx
1603
+
1604
+ function VolumeRenderingQuality({
1605
+ volumeRenderingQualityRange,
1606
+ commandsManager,
1607
+ serviceManager,
1608
+ viewportId
1609
+ }) {
1610
+ const {
1611
+ cornerstoneViewportService
1612
+ } = serviceManager.services;
1613
+ const {
1614
+ min,
1615
+ max,
1616
+ step
1617
+ } = volumeRenderingQualityRange;
1618
+ const [quality, setQuality] = (0,react.useState)(null);
1619
+ const onChange = (0,react.useCallback)(value => {
1620
+ commandsManager.runCommand('setVolumeRenderingQulaity', {
1621
+ viewportId,
1622
+ volumeQuality: value
1623
+ });
1624
+ setQuality(value);
1625
+ }, [commandsManager, viewportId]);
1626
+ const calculateBackground = value => {
1627
+ const percentage = (value - 0) / (1 - 0) * 100;
1628
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1629
+ };
1630
+ (0,react.useEffect)(() => {
1631
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1632
+ const {
1633
+ actor
1634
+ } = viewport.getActors()[0];
1635
+ const mapper = actor.getMapper();
1636
+ const image = mapper.getInputData();
1637
+ const spacing = image.getSpacing();
1638
+ const sampleDistance = mapper.getSampleDistance();
1639
+ const averageSpacing = spacing.reduce((a, b) => a + b) / 3.0;
1640
+ if (sampleDistance === averageSpacing) {
1641
+ setQuality(1);
1642
+ } else {
1643
+ setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5)));
1644
+ }
1645
+ }, [cornerstoneViewportService, viewportId]);
1646
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1647
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1648
+ }, /*#__PURE__*/react.createElement("label", {
1649
+ className: "block text-white",
1650
+ htmlFor: "volume"
1651
+ }, "Quality"), quality !== null && /*#__PURE__*/react.createElement("input", {
1652
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1653
+ value: quality,
1654
+ id: "volume",
1655
+ max: max,
1656
+ min: min,
1657
+ type: "range",
1658
+ step: step,
1659
+ onChange: e => onChange(parseInt(e.target.value, 10)),
1660
+ style: {
1661
+ background: calculateBackground((quality - min) / (max - min)),
1662
+ '--thumb-inner-color': '#5acce6',
1663
+ '--thumb-outer-color': '#090c29'
1664
+ }
1665
+ })));
1666
+ }
1667
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShift.tsx
1668
+
1669
+ function VolumeShift({
1670
+ viewportId,
1671
+ commandsManager,
1672
+ serviceManager
1673
+ }) {
1674
+ const {
1675
+ cornerstoneViewportService
1676
+ } = serviceManager.services;
1677
+ const [minShift, setMinShift] = (0,react.useState)(null);
1678
+ const [maxShift, setMaxShift] = (0,react.useState)(null);
1679
+ const [shift, setShift] = (0,react.useState)(cornerstoneViewportService.getCornerstoneViewport(viewportId)?.shiftedBy || 0);
1680
+ const [step, setStep] = (0,react.useState)(null);
1681
+ const [isBlocking, setIsBlocking] = (0,react.useState)(false);
1682
+ const prevShiftRef = (0,react.useRef)(shift);
1683
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1684
+ const {
1685
+ actor
1686
+ } = viewport.getActors()[0];
1687
+ const ofun = actor.getProperty().getScalarOpacity(0);
1688
+ (0,react.useEffect)(() => {
1689
+ if (isBlocking) {
1690
+ return;
1691
+ }
1692
+ const range = ofun.getRange();
1693
+ const transferFunctionWidth = range[1] - range[0];
1694
+ const minShift = -transferFunctionWidth;
1695
+ const maxShift = transferFunctionWidth;
1696
+ setMinShift(minShift);
1697
+ setMaxShift(maxShift);
1698
+ setStep(Math.pow(10, Math.floor(Math.log10(transferFunctionWidth / 500))));
1699
+ }, [cornerstoneViewportService, viewportId, actor, ofun, isBlocking]);
1700
+ const onChangeRange = (0,react.useCallback)(newShift => {
1701
+ const shiftDifference = newShift - prevShiftRef.current;
1702
+ prevShiftRef.current = newShift;
1703
+ viewport.shiftedBy = newShift;
1704
+ commandsManager.runCommand('shiftVolumeOpacityPoints', {
1705
+ viewportId,
1706
+ shift: shiftDifference
1707
+ });
1708
+ }, [commandsManager, viewportId, viewport]);
1709
+ const calculateBackground = value => {
1710
+ const percentage = (value - 0) / (1 - 0) * 100;
1711
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1712
+ };
1713
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1714
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1715
+ }, /*#__PURE__*/react.createElement("label", {
1716
+ className: "block text-white",
1717
+ htmlFor: "shift"
1718
+ }, "Shift"), step !== null && /*#__PURE__*/react.createElement("input", {
1719
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1720
+ value: shift,
1721
+ onChange: e => {
1722
+ const shiftValue = parseInt(e.target.value, 10);
1723
+ setShift(shiftValue);
1724
+ onChangeRange(shiftValue);
1725
+ },
1726
+ id: "shift",
1727
+ onMouseDown: () => setIsBlocking(true),
1728
+ onMouseUp: () => setIsBlocking(false),
1729
+ max: maxShift,
1730
+ min: minShift,
1731
+ type: "range",
1732
+ step: step,
1733
+ style: {
1734
+ background: calculateBackground((shift - minShift) / (maxShift - minShift)),
1735
+ '--thumb-inner-color': '#5acce6',
1736
+ '--thumb-outer-color': '#090c29'
1737
+ }
1738
+ })));
1739
+ }
1740
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeLighting.tsx
1741
+
1742
+ function VolumeLighting({
1743
+ serviceManager,
1744
+ commandsManager,
1745
+ viewportId
1746
+ }) {
1747
+ const {
1748
+ cornerstoneViewportService
1749
+ } = serviceManager.services;
1750
+ const [ambient, setAmbient] = (0,react.useState)(null);
1751
+ const [diffuse, setDiffuse] = (0,react.useState)(null);
1752
+ const [specular, setSpecular] = (0,react.useState)(null);
1753
+ const onAmbientChange = (0,react.useCallback)(() => {
1754
+ commandsManager.runCommand('setVolumeLighting', {
1755
+ viewportId,
1756
+ options: {
1757
+ ambient
1758
+ }
1759
+ });
1760
+ }, [ambient, commandsManager, viewportId]);
1761
+ const onDiffuseChange = (0,react.useCallback)(() => {
1762
+ commandsManager.runCommand('setVolumeLighting', {
1763
+ viewportId,
1764
+ options: {
1765
+ diffuse
1766
+ }
1767
+ });
1768
+ }, [diffuse, commandsManager, viewportId]);
1769
+ const onSpecularChange = (0,react.useCallback)(() => {
1770
+ commandsManager.runCommand('setVolumeLighting', {
1771
+ viewportId,
1772
+ options: {
1773
+ specular
1774
+ }
1775
+ });
1776
+ }, [specular, commandsManager, viewportId]);
1777
+ const calculateBackground = value => {
1778
+ const percentage = (value - 0) / (1 - 0) * 100;
1779
+ return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
1780
+ };
1781
+ (0,react.useEffect)(() => {
1782
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1783
+ const {
1784
+ actor
1785
+ } = viewport.getActors()[0];
1786
+ const ambient = actor.getProperty().getAmbient();
1787
+ const diffuse = actor.getProperty().getDiffuse();
1788
+ const specular = actor.getProperty().getSpecular();
1789
+ setAmbient(ambient);
1790
+ setDiffuse(diffuse);
1791
+ setSpecular(specular);
1792
+ }, [viewportId, cornerstoneViewportService]);
1793
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
1794
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1795
+ }, /*#__PURE__*/react.createElement("label", {
1796
+ className: "block text-white",
1797
+ htmlFor: "ambient"
1798
+ }, "Ambient"), ambient !== null && /*#__PURE__*/react.createElement("input", {
1799
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1800
+ value: ambient,
1801
+ onChange: e => {
1802
+ setAmbient(e.target.value);
1803
+ onAmbientChange();
1804
+ },
1805
+ id: "ambient",
1806
+ max: 1,
1807
+ min: 0,
1808
+ type: "range",
1809
+ step: 0.1,
1810
+ style: {
1811
+ background: calculateBackground(ambient),
1812
+ '--thumb-inner-color': '#5acce6',
1813
+ '--thumb-outer-color': '#090c29'
1814
+ }
1815
+ })), /*#__PURE__*/react.createElement("div", {
1816
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1817
+ }, /*#__PURE__*/react.createElement("label", {
1818
+ className: "block text-white",
1819
+ htmlFor: "diffuse"
1820
+ }, "Diffuse"), diffuse !== null && /*#__PURE__*/react.createElement("input", {
1821
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1822
+ value: diffuse,
1823
+ onChange: e => {
1824
+ setDiffuse(e.target.value);
1825
+ onDiffuseChange();
1826
+ },
1827
+ id: "diffuse",
1828
+ max: 1,
1829
+ min: 0,
1830
+ type: "range",
1831
+ step: 0.1,
1832
+ style: {
1833
+ background: calculateBackground(diffuse),
1834
+ '--thumb-inner-color': '#5acce6',
1835
+ '--thumb-outer-color': '#090c29'
1836
+ }
1837
+ })), /*#__PURE__*/react.createElement("div", {
1838
+ className: "all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]"
1839
+ }, /*#__PURE__*/react.createElement("label", {
1840
+ className: "block text-white",
1841
+ htmlFor: "specular"
1842
+ }, "Specular"), specular !== null && /*#__PURE__*/react.createElement("input", {
1843
+ className: "bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg",
1844
+ value: specular,
1845
+ onChange: e => {
1846
+ setSpecular(e.target.value);
1847
+ onSpecularChange();
1848
+ },
1849
+ id: "specular",
1850
+ max: 1,
1851
+ min: 0,
1852
+ type: "range",
1853
+ step: 0.1,
1854
+ style: {
1855
+ background: calculateBackground(specular),
1856
+ '--thumb-inner-color': '#5acce6',
1857
+ '--thumb-outer-color': '#090c29'
1858
+ }
1859
+ })));
1860
+ }
1861
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeShade.tsx
1862
+
1863
+
1864
+ function VolumeShade({
1865
+ commandsManager,
1866
+ viewportId,
1867
+ serviceManager
1868
+ }) {
1869
+ const {
1870
+ cornerstoneViewportService
1871
+ } = serviceManager.services;
1872
+ const [shade, setShade] = (0,react.useState)(true);
1873
+ const [key, setKey] = (0,react.useState)(0);
1874
+ const onShadeChange = (0,react.useCallback)(checked => {
1875
+ commandsManager.runCommand('setVolumeLighting', {
1876
+ viewportId,
1877
+ options: {
1878
+ shade: checked
1879
+ }
1880
+ });
1881
+ }, [commandsManager, viewportId]);
1882
+ (0,react.useEffect)(() => {
1883
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
1884
+ const {
1885
+ actor
1886
+ } = viewport.getActors()[0];
1887
+ const shade = actor.getProperty().getShade();
1888
+ setShade(shade);
1889
+ setKey(key + 1);
1890
+ }, [viewportId, cornerstoneViewportService]);
1891
+ return /*#__PURE__*/react.createElement(ui_src/* SwitchButton */.L$, {
1892
+ key: key,
1893
+ label: "Shade",
1894
+ checked: shade,
1895
+ onChange: () => {
1896
+ setShade(!shade);
1897
+ onShadeChange(!shade);
1898
+ }
1899
+ });
1900
+ }
1901
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/VolumeRenderingOptions.tsx
1902
+
1903
+
1904
+
1905
+
1906
+
1907
+
1908
+ function VolumeRenderingOptions({
1909
+ viewportId,
1910
+ commandsManager,
1911
+ volumeRenderingQualityRange,
1912
+ serviceManager
1913
+ }) {
1914
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, /*#__PURE__*/react.createElement(VolumeRenderingQuality, {
1915
+ viewportId: viewportId,
1916
+ commandsManager: commandsManager,
1917
+ serviceManager: serviceManager,
1918
+ volumeRenderingQualityRange: volumeRenderingQualityRange
1919
+ }), /*#__PURE__*/react.createElement(VolumeShift, {
1920
+ viewportId: viewportId,
1921
+ commandsManager: commandsManager,
1922
+ serviceManager: serviceManager
1923
+ }), /*#__PURE__*/react.createElement("div", {
1924
+ className: "all-in-one-menu-item flex !h-[20px] w-full justify-start"
1925
+ }, /*#__PURE__*/react.createElement("div", {
1926
+ className: "text-aqua-pale text-[13px]"
1927
+ }, "LIGHTING")), /*#__PURE__*/react.createElement("div", {
1928
+ className: "bg-primary-dark mt-1 mb-1 h-[2px] w-full"
1929
+ }), /*#__PURE__*/react.createElement("div", {
1930
+ className: "all-in-one-menu-item flex w-full justify-center"
1931
+ }, /*#__PURE__*/react.createElement(VolumeShade, {
1932
+ commandsManager: commandsManager,
1933
+ serviceManager: serviceManager,
1934
+ viewportId: viewportId
1935
+ })), /*#__PURE__*/react.createElement(VolumeLighting, {
1936
+ viewportId: viewportId,
1937
+ commandsManager: commandsManager,
1938
+ serviceManager: serviceManager
1939
+ }));
1940
+ }
1941
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/WindowLevelActionMenu.tsx
1942
+
1943
+
1944
+
1945
+
1946
+
1947
+
1948
+
1949
+
1950
+
1951
+
1952
+
1953
+
1954
+ function WindowLevelActionMenu({
1955
+ viewportId,
1956
+ element,
1957
+ presets,
1958
+ verticalDirection,
1959
+ horizontalDirection,
1960
+ commandsManager,
1961
+ serviceManager,
1962
+ colorbarProperties,
1963
+ displaySets,
1964
+ volumeRenderingPresets,
1965
+ volumeRenderingQualityRange
1966
+ }) {
1967
+ const {
1968
+ colormaps,
1969
+ colorbarContainerPosition,
1970
+ colorbarInitialColormap,
1971
+ colorbarTickPosition,
1972
+ width: colorbarWidth
1973
+ } = colorbarProperties;
1974
+ const {
1975
+ colorbarService,
1976
+ cornerstoneViewportService
1977
+ } = serviceManager.services;
1978
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
1979
+ const backgroundColor = viewportInfo.getViewportOptions().background;
1980
+ const isLight = backgroundColor ? dist_esm.utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
1981
+ const nonImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE'];
1982
+ const {
1983
+ t
1984
+ } = (0,es/* useTranslation */.Bd)('WindowLevelActionMenu');
1985
+ const [viewportGrid] = (0,ui_src/* useViewportGrid */.ih)();
1986
+ const {
1987
+ activeViewportId
1988
+ } = viewportGrid;
1989
+ const [vpHeight, setVpHeight] = (0,react.useState)(element?.clientHeight);
1990
+ const [menuKey, setMenuKey] = (0,react.useState)(0);
1991
+ const [is3DVolume, setIs3DVolume] = (0,react.useState)(false);
1992
+ const onSetColorbar = (0,react.useCallback)(() => {
1993
+ setViewportColorbar(viewportId, displaySets, commandsManager, serviceManager, {
1994
+ colormaps,
1995
+ ticks: {
1996
+ position: colorbarTickPosition
1997
+ },
1998
+ width: colorbarWidth,
1999
+ position: colorbarContainerPosition,
2000
+ activeColormapName: colorbarInitialColormap
2001
+ });
2002
+ }, [commandsManager]);
2003
+ (0,react.useEffect)(() => {
2004
+ const newVpHeight = element?.clientHeight;
2005
+ if (vpHeight !== newVpHeight) {
2006
+ setVpHeight(newVpHeight);
2007
+ }
2008
+ }, [element, vpHeight]);
2009
+ (0,react.useEffect)(() => {
2010
+ if (!colorbarService.hasColorbar(viewportId)) {
2011
+ return;
2012
+ }
2013
+ window.setTimeout(() => {
2014
+ colorbarService.removeColorbar(viewportId);
2015
+ onSetColorbar();
2016
+ }, 0);
2017
+ }, [viewportId]);
2018
+ (0,react.useEffect)(() => {
2019
+ if (colorbarService.hasColorbar(viewportId)) {
2020
+ colorbarService.removeColorbar(viewportId);
2021
+ }
2022
+ }, [displaySets]);
2023
+ (0,react.useEffect)(() => {
2024
+ setMenuKey(menuKey + 1);
2025
+ const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
2026
+ if (viewport instanceof dist_esm.VolumeViewport3D) {
2027
+ setIs3DVolume(true);
2028
+ } else {
2029
+ setIs3DVolume(false);
2030
+ }
2031
+ }, [displaySets, viewportId, presets, volumeRenderingQualityRange, volumeRenderingPresets, colorbarProperties, activeViewportId, viewportGrid]);
2032
+ return /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.IconMenu */.se.dd, {
2033
+ icon: "viewport-window-level",
2034
+ verticalDirection: verticalDirection,
2035
+ horizontalDirection: horizontalDirection,
2036
+ iconClassName: classnames_default()(
2037
+ // Visible on hover and for the active viewport
2038
+ 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'),
2039
+ menuStyle: {
2040
+ maxHeight: vpHeight - 32,
2041
+ minWidth: 218
2042
+ },
2043
+ onVisibilityChange: () => {
2044
+ setVpHeight(element.clientHeight);
2045
+ },
2046
+ menuKey: menuKey
2047
+ }, /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.ItemPanel */.se.cV, null, !is3DVolume && /*#__PURE__*/react.createElement(Colorbar, {
2048
+ viewportId: viewportId,
2049
+ displaySets: displaySets.filter(ds => !nonImageModalities.includes(ds.Modality)),
2050
+ commandsManager: commandsManager,
2051
+ serviceManager: serviceManager,
2052
+ colorbarProperties: colorbarProperties
2053
+ }), colormaps && !is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2054
+ key: "colorLUTPresets",
2055
+ itemLabel: "Color LUT",
2056
+ itemIcon: "icon-color-lut"
2057
+ }, /*#__PURE__*/react.createElement(Colormap, {
2058
+ colormaps: colormaps,
2059
+ viewportId: viewportId,
2060
+ displaySets: displaySets.filter(ds => !nonImageModalities.includes(ds.Modality)),
2061
+ commandsManager: commandsManager,
2062
+ serviceManager: serviceManager
2063
+ })), presets && presets.length > 0 && !is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2064
+ key: "windowLevelPresets",
2065
+ itemLabel: t('Modality Window Presets'),
2066
+ itemIcon: "viewport-window-level"
2067
+ }, /*#__PURE__*/react.createElement(WindowLevel, {
2068
+ viewportId: viewportId,
2069
+ commandsManager: commandsManager,
2070
+ presets: presets
2071
+ })), volumeRenderingPresets && is3DVolume && /*#__PURE__*/react.createElement(VolumeRenderingPresets, {
2072
+ serviceManager: serviceManager,
2073
+ viewportId: viewportId,
2074
+ commandsManager: commandsManager,
2075
+ volumeRenderingPresets: volumeRenderingPresets
2076
+ }), volumeRenderingQualityRange && is3DVolume && /*#__PURE__*/react.createElement(ui_src/* AllInOneMenu.SubMenu */.se.g8, {
2077
+ itemLabel: "Rendering Options"
2078
+ }, /*#__PURE__*/react.createElement(VolumeRenderingOptions, {
2079
+ viewportId: viewportId,
2080
+ commandsManager: commandsManager,
2081
+ volumeRenderingQualityRange: volumeRenderingQualityRange,
2082
+ serviceManager: serviceManager
2083
+ }))));
2084
+ }
2085
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/components/WindowLevelActionMenu/getWindowLevelActionMenu.tsx
2086
+
2087
+
2088
+ function getWindowLevelActionMenu({
2089
+ viewportId,
2090
+ element,
2091
+ displaySets,
2092
+ servicesManager,
2093
+ commandsManager,
2094
+ verticalDirection,
2095
+ horizontalDirection
2096
+ }) {
2097
+ const {
2098
+ customizationService
2099
+ } = servicesManager.services;
2100
+ const {
2101
+ presets
2102
+ } = customizationService.get('cornerstone.windowLevelPresets');
2103
+ const colorbarProperties = customizationService.get('cornerstone.colorbar');
2104
+ const {
2105
+ volumeRenderingPresets,
2106
+ volumeRenderingQualityRange
2107
+ } = customizationService.get('cornerstone.3dVolumeRendering');
2108
+ const displaySetPresets = displaySets.filter(displaySet => presets[displaySet.Modality]).map(displaySet => {
2109
+ return {
2110
+ [displaySet.Modality]: presets[displaySet.Modality]
2111
+ };
2112
+ });
2113
+ return /*#__PURE__*/react.createElement(WindowLevelActionMenu, {
2114
+ viewportId: viewportId,
2115
+ element: element,
2116
+ presets: displaySetPresets,
2117
+ verticalDirection: verticalDirection,
2118
+ horizontalDirection: horizontalDirection,
2119
+ commandsManager: commandsManager,
2120
+ serviceManager: servicesManager,
2121
+ colorbarProperties: colorbarProperties,
2122
+ displaySets: displaySets,
2123
+ volumeRenderingPresets: volumeRenderingPresets,
2124
+ volumeRenderingQualityRange: volumeRenderingQualityRange
2125
+ });
2126
+ }
2127
+ ;// CONCATENATED MODULE: ../../../extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx
2128
+
2129
+
2130
+
2131
+
2132
+
2133
+
2134
+
2135
+
2136
+
2137
+
2138
+
2139
+
2140
+
2141
+
2142
+
2143
+ const STACK = 'stack';
2144
+
2145
+ /**
2146
+ * Caches the jump to measurement operation, so that if display set is shown,
2147
+ * it can jump to the measurement.
2148
+ */
2149
+ let cacheJumpToMeasurementEvent;
2150
+ function areEqual(prevProps, nextProps) {
2151
+ if (nextProps.needsRerendering) {
2152
+ return false;
2153
+ }
2154
+ if (prevProps.displaySets.length !== nextProps.displaySets.length) {
2155
+ return false;
2156
+ }
2157
+ if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) {
2158
+ return false;
2159
+ }
2160
+ if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) {
2161
+ return false;
2162
+ }
2163
+ if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) {
2164
+ return false;
2165
+ }
2166
+ if (nextProps.viewportOptions.needsRerendering) {
2167
+ return false;
2168
+ }
2169
+ const prevDisplaySets = prevProps.displaySets;
2170
+ const nextDisplaySets = nextProps.displaySets;
2171
+ if (prevDisplaySets.length !== nextDisplaySets.length) {
2172
+ return false;
2173
+ }
2174
+ for (let i = 0; i < prevDisplaySets.length; i++) {
2175
+ const prevDisplaySet = prevDisplaySets[i];
2176
+ const foundDisplaySet = nextDisplaySets.find(nextDisplaySet => nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID);
2177
+ if (!foundDisplaySet) {
2178
+ return false;
2179
+ }
2180
+
2181
+ // check they contain the same image
2182
+ if (foundDisplaySet.images?.length !== prevDisplaySet.images?.length) {
2183
+ return false;
2184
+ }
2185
+
2186
+ // check if their imageIds are the same
2187
+ if (foundDisplaySet.images?.length) {
2188
+ for (let j = 0; j < foundDisplaySet.images.length; j++) {
2189
+ if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) {
2190
+ return false;
2191
+ }
2192
+ }
2193
+ }
2194
+ }
2195
+ return true;
2196
+ }
2197
+
2198
+ // Todo: This should be done with expose of internal API similar to react-vtkjs-viewport
2199
+ // Then we don't need to worry about the re-renders if the props change.
2200
+ const OHIFCornerstoneViewport = /*#__PURE__*/react.memo(props => {
2201
+ const {
2202
+ displaySets,
2203
+ dataSource,
2204
+ viewportOptions,
2205
+ displaySetOptions,
2206
+ servicesManager,
2207
+ onElementEnabled,
2208
+ onElementDisabled,
2209
+ isJumpToMeasurementDisabled,
2210
+ // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
2211
+ // of the imageData in the OHIFCornerstoneViewport. This prop is used
2212
+ // to set the initial state of the viewport's first image to render
2213
+ initialImageIndex,
2214
+ // if the viewport is part of a hanging protocol layout
2215
+ // we should not really rely on the old synchronizers and
2216
+ // you see below we only rehydrate the synchronizers if the viewport
2217
+ // is not part of the hanging protocol layout. HPs should
2218
+ // define their own synchronizers. Since the synchronizers are
2219
+ // viewportId dependent and
2220
+ isHangingProtocolLayout
2221
+ } = props;
2222
+ const viewportId = viewportOptions.viewportId;
2223
+ if (!viewportId) {
2224
+ throw new Error('Viewport ID is required');
2225
+ }
2226
+
2227
+ // Since we only have support for dynamic data in volume viewports, we should
2228
+ // handle this case here and set the viewportType to volume if any of the
2229
+ // displaySets are dynamic volumes
2230
+ viewportOptions.viewportType = displaySets.some(ds => ds.isDynamicVolume) ? 'volume' : viewportOptions.viewportType;
2231
+ const [scrollbarHeight, setScrollbarHeight] = (0,react.useState)('100px');
2232
+ const [enabledVPElement, setEnabledVPElement] = (0,react.useState)(null);
2233
+ const elementRef = (0,react.useRef)();
2234
+ const [appConfig] = (0,state_0/* useAppConfig */.r)();
2235
+ const {
2236
+ measurementService,
2237
+ displaySetService,
2238
+ toolbarService,
2239
+ toolGroupService,
2240
+ syncGroupService,
2241
+ cornerstoneViewportService,
2242
+ cornerstoneCacheService,
2243
+ viewportGridService,
2244
+ stateSyncService,
2245
+ viewportActionCornersService
2246
+ } = servicesManager.services;
2247
+ const [viewportDialogState] = (0,ui_src/* useViewportDialog */.OR)();
2248
+ // useCallback for scroll bar height calculation
2249
+ const setImageScrollBarHeight = (0,react.useCallback)(() => {
2250
+ const scrollbarHeight = `${elementRef.current.clientHeight - 40}px`;
2251
+ setScrollbarHeight(scrollbarHeight);
2252
+ }, [elementRef]);
2253
+
2254
+ // useCallback for onResize
2255
+ const onResize = (0,react.useCallback)(() => {
2256
+ if (elementRef.current) {
2257
+ cornerstoneViewportService.resize();
2258
+ setImageScrollBarHeight();
2259
+ }
2260
+ }, [elementRef]);
2261
+ const cleanUpServices = (0,react.useCallback)(viewportInfo => {
2262
+ const renderingEngineId = viewportInfo.getRenderingEngineId();
2263
+ const syncGroups = viewportInfo.getSyncGroups();
2264
+ toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId);
2265
+ syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups);
2266
+ viewportActionCornersService.clear(viewportId);
2267
+ }, [viewportId]);
2268
+ const elementEnabledHandler = (0,react.useCallback)(evt => {
2269
+ // check this is this element reference and return early if doesn't match
2270
+ if (evt.detail.element !== elementRef.current) {
2271
+ return;
2272
+ }
2273
+ const {
2274
+ viewportId,
2275
+ element
2276
+ } = evt.detail;
2277
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2278
+ (0,state/* setEnabledElement */.ye)(viewportId, element);
2279
+ setEnabledVPElement(element);
2280
+ const renderingEngineId = viewportInfo.getRenderingEngineId();
2281
+ const toolGroupId = viewportInfo.getToolGroupId();
2282
+ const syncGroups = viewportInfo.getSyncGroups();
2283
+ toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId);
2284
+ syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups);
2285
+ const synchronizersStore = stateSyncService.getState().synchronizersStore;
2286
+ if (synchronizersStore?.[viewportId]?.length && !isHangingProtocolLayout) {
2287
+ // If the viewport used to have a synchronizer, re apply it again
2288
+ _rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService);
2289
+ }
2290
+ if (onElementEnabled) {
2291
+ onElementEnabled(evt);
2292
+ }
2293
+ }, [viewportId, onElementEnabled, toolGroupService]);
2294
+
2295
+ // disable the element upon unmounting
2296
+ (0,react.useEffect)(() => {
2297
+ cornerstoneViewportService.enableViewport(viewportId, elementRef.current);
2298
+ dist_esm.eventTarget.addEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
2299
+ setImageScrollBarHeight();
2300
+ return () => {
2301
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2302
+ if (!viewportInfo) {
2303
+ return;
2304
+ }
2305
+ cornerstoneViewportService.storePresentation({
2306
+ viewportId
2307
+ });
2308
+
2309
+ // This should be done after the store presentation since synchronizers
2310
+ // will get cleaned up and they need the viewportInfo to be present
2311
+ cleanUpServices(viewportInfo);
2312
+ if (onElementDisabled) {
2313
+ onElementDisabled(viewportInfo);
2314
+ }
2315
+ cornerstoneViewportService.disableElement(viewportId);
2316
+ dist_esm.eventTarget.removeEventListener(dist_esm.Enums.Events.ELEMENT_ENABLED, elementEnabledHandler);
2317
+ };
2318
+ }, []);
2319
+
2320
+ // subscribe to displaySet metadata invalidation (updates)
2321
+ // Currently, if the metadata changes we need to re-render the display set
2322
+ // for it to take effect in the viewport. As we deal with scaling in the loading,
2323
+ // we need to remove the old volume from the cache, and let the
2324
+ // viewport to re-add it which will use the new metadata. Otherwise, the
2325
+ // viewport will use the cached volume and the new metadata will not be used.
2326
+ // Note: this approach does not actually end of sending network requests
2327
+ // and it uses the network cache
2328
+ (0,react.useEffect)(() => {
2329
+ const {
2330
+ unsubscribe
2331
+ } = displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, async ({
2332
+ displaySetInstanceUID: invalidatedDisplaySetInstanceUID,
2333
+ invalidateData
2334
+ }) => {
2335
+ if (!invalidateData) {
2336
+ return;
2337
+ }
2338
+ const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
2339
+ if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) {
2340
+ const viewportData = viewportInfo.getViewportData();
2341
+ const newViewportData = await cornerstoneCacheService.invalidateViewportData(viewportData, invalidatedDisplaySetInstanceUID, dataSource, displaySetService);
2342
+ const keepCamera = true;
2343
+ cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera);
2344
+ }
2345
+ });
2346
+ return () => {
2347
+ unsubscribe();
2348
+ };
2349
+ }, [viewportId]);
2350
+ (0,react.useEffect)(() => {
2351
+ // handle the default viewportType to be stack
2352
+ if (!viewportOptions.viewportType) {
2353
+ viewportOptions.viewportType = STACK;
2354
+ }
2355
+ const loadViewportData = async () => {
2356
+ const viewportData = await cornerstoneCacheService.createViewportData(displaySets, viewportOptions, dataSource, initialImageIndex);
2357
+
2358
+ // The presentation state will have been stored previously by closing
2359
+ // a viewport. Otherwise, this viewport will be unchanged and the
2360
+ // presentation information will be directly carried over.
2361
+ const state = stateSyncService.getState();
2362
+ const lutPresentationStore = state.lutPresentationStore;
2363
+ const positionPresentationStore = state.positionPresentationStore;
2364
+ const {
2365
+ presentationIds
2366
+ } = viewportOptions;
2367
+ const presentations = {
2368
+ positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId],
2369
+ lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId]
2370
+ };
2371
+ let measurement;
2372
+ if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
2373
+ measurement = cacheJumpToMeasurementEvent.measurement;
2374
+ // Delete the position presentation so that viewport navigates direct
2375
+ presentations.positionPresentation = null;
2376
+ cacheJumpToMeasurementEvent = null;
2377
+ }
2378
+
2379
+ // Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
2380
+ // Used for segmentation hydration right now, since the logic to decide whether
2381
+ // a viewport needs to render a segmentation lives inside the CornerstoneViewportService
2382
+ // so we need to re-render (force update via change of the needsRerendering) so that React
2383
+ // does the diffing and decides we should render this again (although the id and element has not changed)
2384
+ // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off.
2385
+ if (viewportOptions.needsRerendering) {
2386
+ viewportOptions.needsRerendering = false;
2387
+ }
2388
+ cornerstoneViewportService.setViewportData(viewportId, viewportData, viewportOptions, displaySetOptions, presentations);
2389
+ if (measurement) {
2390
+ esm.annotation.selection.setAnnotationSelected(measurement.uid);
2391
+ }
2392
+ };
2393
+ loadViewportData();
2394
+ }, [viewportOptions, displaySets, dataSource]);
2395
+
2396
+ /**
2397
+ * There are two scenarios for jump to click
2398
+ * 1. Current viewports contain the displaySet that the annotation was drawn on
2399
+ * 2. Current viewports don't contain the displaySet that the annotation was drawn on
2400
+ * and we need to change the viewports displaySet for jumping.
2401
+ * Since measurement_jump happens via events and listeners, the former case is handled
2402
+ * by the measurement_jump direct callback, but the latter case is handled first by
2403
+ * the viewportGrid to set the correct displaySet on the viewport, AND THEN we check
2404
+ * the cache for jumping to see if there is any jump queued, then we jump to the correct slice.
2405
+ */
2406
+ (0,react.useEffect)(() => {
2407
+ if (isJumpToMeasurementDisabled) {
2408
+ return;
2409
+ }
2410
+ const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService);
2411
+ _checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService);
2412
+ return () => {
2413
+ unsubscribeFromJumpToMeasurementEvents();
2414
+ };
2415
+ }, [displaySets, elementRef, viewportId]);
2416
+
2417
+ // Set up the window level action menu in the viewport action corners.
2418
+ (0,react.useEffect)(() => {
2419
+ // Doing an === check here because the default config value when not set is true
2420
+ if (appConfig.addWindowLevelActionMenu === false) {
2421
+ return;
2422
+ }
2423
+
2424
+ // TODO: In the future we should consider using the customization service
2425
+ // to determine if and in which corner various action components should go.
2426
+ const wlActionMenu = getWindowLevelActionMenu({
2427
+ viewportId,
2428
+ element: elementRef.current,
2429
+ displaySets,
2430
+ servicesManager,
2431
+ commandsManager,
2432
+ verticalDirection: ui_src/* AllInOneMenu.VerticalDirection */.se.mq.TopToBottom,
2433
+ horizontalDirection: ui_src/* AllInOneMenu.HorizontalDirection */.se.Iu.RightToLeft
2434
+ });
2435
+ viewportActionCornersService.setComponent({
2436
+ viewportId,
2437
+ id: 'windowLevelActionMenu',
2438
+ component: wlActionMenu,
2439
+ location: viewportActionCornersService.LOCATIONS.topRight,
2440
+ indexPriority: -100
2441
+ });
2442
+ }, [displaySets, viewportId, viewportActionCornersService, servicesManager, commandsManager]);
2443
+ return /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", {
2444
+ className: "viewport-wrapper"
2445
+ }, /*#__PURE__*/react.createElement(index_esm/* default */.Ay, {
2446
+ onResize: onResize,
2447
+ targetRef: elementRef.current
2448
+ }), /*#__PURE__*/react.createElement("div", {
2449
+ className: "cornerstone-viewport-element",
2450
+ style: {
2451
+ height: '100%',
2452
+ width: '100%'
2453
+ },
2454
+ onContextMenu: e => e.preventDefault(),
2455
+ onMouseDown: e => e.preventDefault(),
2456
+ ref: elementRef
2457
+ }), /*#__PURE__*/react.createElement(Overlays_CornerstoneOverlays, {
2458
+ viewportId: viewportId,
2459
+ toolBarService: toolbarService,
2460
+ element: elementRef.current,
2461
+ scrollbarHeight: scrollbarHeight,
2462
+ servicesManager: servicesManager
2463
+ }), /*#__PURE__*/react.createElement(components_CinePlayer, {
2464
+ enabledVPElement: enabledVPElement,
2465
+ viewportId: viewportId,
2466
+ servicesManager: servicesManager
2467
+ })), /*#__PURE__*/react.createElement("div", {
2468
+ className: "absolute top-[24px] w-full"
2469
+ }, viewportDialogState.viewportId === viewportId && /*#__PURE__*/react.createElement(ui_src/* Notification */.Eg, {
2470
+ id: "viewport-notification",
2471
+ message: viewportDialogState.message,
2472
+ type: viewportDialogState.type,
2473
+ actions: viewportDialogState.actions,
2474
+ onSubmit: viewportDialogState.onSubmit,
2475
+ onOutsideClick: viewportDialogState.onOutsideClick,
2476
+ onKeyPress: viewportDialogState.onKeyPress
2477
+ })), /*#__PURE__*/react.createElement(components_OHIFViewportActionCorners, {
2478
+ viewportId: viewportId
2479
+ }));
2480
+ }, areEqual);
2481
+ function _subscribeToJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService) {
2482
+ const {
2483
+ unsubscribe
2484
+ } = measurementService.subscribe(src.MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => {
2485
+ cacheJumpToMeasurementEvent = props;
2486
+ const {
2487
+ viewportId: jumpId,
2488
+ measurement,
2489
+ isConsumed
2490
+ } = props;
2491
+ if (!measurement || isConsumed) {
2492
+ return;
2493
+ }
2494
+ if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
2495
+ // Decide on which viewport should handle this
2496
+ cacheJumpToMeasurementEvent.cornerstoneViewport = cornerstoneViewportService.getViewportIdToJump(jumpId, measurement.displaySetInstanceUID, {
2497
+ referencedImageId: measurement.referencedImageId
2498
+ });
2499
+ }
2500
+ if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
2501
+ return;
2502
+ }
2503
+ _jumpToMeasurement(measurement, elementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
2504
+ });
2505
+ return unsubscribe;
2506
+ }
2507
+
2508
+ // Check if there is a queued jumpToMeasurement event
2509
+ function _checkForCachedJumpToMeasurementEvents(measurementService, displaySetService, elementRef, viewportId, displaySets, viewportGridService, cornerstoneViewportService) {
2510
+ if (!cacheJumpToMeasurementEvent) {
2511
+ return;
2512
+ }
2513
+ if (cacheJumpToMeasurementEvent.isConsumed) {
2514
+ cacheJumpToMeasurementEvent = null;
2515
+ return;
2516
+ }
2517
+ const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
2518
+ if (!displaysUIDs?.length) {
2519
+ return;
2520
+ }
2521
+
2522
+ // Jump to measurement if the measurement exists
2523
+ const {
2524
+ measurement
2525
+ } = cacheJumpToMeasurementEvent;
2526
+ if (measurement && elementRef) {
2527
+ if (displaysUIDs.includes(measurement?.displaySetInstanceUID)) {
2528
+ _jumpToMeasurement(measurement, elementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService);
2529
+ }
2530
+ }
2531
+ }
2532
+ function _jumpToMeasurement(measurement, targetElementRef, viewportId, measurementService, displaySetService, viewportGridService, cornerstoneViewportService) {
2533
+ const targetElement = targetElementRef.current;
2534
+ const {
2535
+ displaySetInstanceUID,
2536
+ SOPInstanceUID,
2537
+ frameNumber
2538
+ } = measurement;
2539
+ if (!SOPInstanceUID) {
2540
+ console.warn('cannot jump in a non-acquisition plane measurements yet');
2541
+ return;
2542
+ }
2543
+ const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
2544
+
2545
+ // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
2546
+ // to set it properly
2547
+ // setCornerstoneMeasurementActive(measurement);
2548
+
2549
+ viewportGridService.setActiveViewportId(viewportId);
2550
+ const enabledElement = (0,dist_esm.getEnabledElement)(targetElement);
2551
+ if (enabledElement) {
2552
+ // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
2553
+ const viewport = enabledElement.viewport;
2554
+ let imageIdIndex = 0;
2555
+ let viewportCameraDirectionMatch = true;
2556
+ if (viewport instanceof dist_esm.StackViewport) {
2557
+ const imageIds = viewport.getImageIds();
2558
+ imageIdIndex = imageIds.findIndex(imageId => {
2559
+ const {
2560
+ SOPInstanceUID: aSOPInstanceUID,
2561
+ frameNumber: aFrameNumber
2562
+ } = (0,getSOPInstanceAttributes/* default */.A)(imageId);
2563
+ return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber);
2564
+ });
2565
+ } else {
2566
+ // for volume viewport we can't rely on the imageIdIndex since it can be
2567
+ // a reconstructed view that doesn't match the original slice numbers etc.
2568
+ const {
2569
+ viewPlaneNormal: measurementViewPlane
2570
+ } = measurement.metadata;
2571
+ imageIdIndex = referencedDisplaySet.images.findIndex(i => i.SOPInstanceUID === SOPInstanceUID);
2572
+ const {
2573
+ viewPlaneNormal: viewportViewPlane
2574
+ } = viewport.getCamera();
2575
+
2576
+ // should compare abs for both planes since the direction can be flipped
2577
+ if (measurementViewPlane && !dist_esm.utilities.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs))) {
2578
+ viewportCameraDirectionMatch = false;
2579
+ }
2580
+ }
2581
+ if (!viewportCameraDirectionMatch || imageIdIndex === -1) {
2582
+ return;
2583
+ }
2584
+ esm.utilities.jumpToSlice(targetElement, {
2585
+ imageIndex: imageIdIndex
2586
+ });
2587
+ esm.annotation.selection.setAnnotationSelected(measurement.uid);
2588
+ // Jump to measurement consumed, remove.
2589
+ cacheJumpToMeasurementEvent?.consume?.();
2590
+ cacheJumpToMeasurementEvent = null;
2591
+ }
2592
+ }
2593
+ function _rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService) {
2594
+ synchronizersStore[viewportId].forEach(synchronizerObj => {
2595
+ if (!synchronizerObj.id) {
2596
+ return;
2597
+ }
2598
+ const {
2599
+ id,
2600
+ sourceViewports,
2601
+ targetViewports
2602
+ } = synchronizerObj;
2603
+ const synchronizer = syncGroupService.getSynchronizer(id);
2604
+ if (!synchronizer) {
2605
+ return;
2606
+ }
2607
+ const sourceViewportInfo = sourceViewports.find(sourceViewport => sourceViewport.viewportId === viewportId);
2608
+ const targetViewportInfo = targetViewports.find(targetViewport => targetViewport.viewportId === viewportId);
2609
+ const isSourceViewportInSynchronizer = synchronizer.getSourceViewports().find(sourceViewport => sourceViewport.viewportId === viewportId);
2610
+ const isTargetViewportInSynchronizer = synchronizer.getTargetViewports().find(targetViewport => targetViewport.viewportId === viewportId);
2611
+
2612
+ // if the viewport was previously a source viewport, add it again
2613
+ if (sourceViewportInfo && !isSourceViewportInSynchronizer) {
2614
+ synchronizer.addSource({
2615
+ viewportId: sourceViewportInfo.viewportId,
2616
+ renderingEngineId: sourceViewportInfo.renderingEngineId
2617
+ });
2618
+ }
2619
+
2620
+ // if the viewport was previously a target viewport, add it again
2621
+ if (targetViewportInfo && !isTargetViewportInSynchronizer) {
2622
+ synchronizer.addTarget({
2623
+ viewportId: targetViewportInfo.viewportId,
2624
+ renderingEngineId: targetViewportInfo.renderingEngineId
2625
+ });
2626
+ }
2627
+ });
2628
+ }
2629
+
2630
+ // Component displayName
2631
+ OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport';
2632
+ OHIFCornerstoneViewport.defaultProps = {
2633
+ isJumpToMeasurementDisabled: false
2634
+ };
2635
+ OHIFCornerstoneViewport.propTypes = {
2636
+ displaySets: (prop_types_default()).array.isRequired,
2637
+ dataSource: (prop_types_default()).object.isRequired,
2638
+ viewportOptions: (prop_types_default()).object,
2639
+ displaySetOptions: prop_types_default().arrayOf((prop_types_default()).any),
2640
+ servicesManager: (prop_types_default()).object.isRequired,
2641
+ onElementEnabled: (prop_types_default()).func,
2642
+ isJumpToMeasurementDisabled: (prop_types_default()).bool,
2643
+ // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation
2644
+ // of the imageData in the OHIFCornerstoneViewport. This prop is used
2645
+ // to set the initial state of the viewport's first image to render
2646
+ initialImageIdOrIndex: prop_types_default().oneOfType([(prop_types_default()).string, (prop_types_default()).number])
2647
+ };
2648
+ /* harmony default export */ const Viewport_OHIFCornerstoneViewport = (OHIFCornerstoneViewport);
2649
+
2650
+ /***/ })
2651
+
2652
+ }]);