@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.
- package/dist/{220.bundle.f7e1c96c94245e70f2be.js → 109.bundle.b4fee2a22b622839baf5.js} +4466 -3715
- package/dist/{471.bundle.49c8d281adbae4a2c4df.js → 121.bundle.47f05840a5b3cdf75543.js} +94 -113
- package/dist/141.bundle.556b4c1e4cab770417ac.js +8620 -0
- package/dist/{687.bundle.9065db35c01823286f08.js → 164.bundle.fadc7c5d634402c73b5f.js} +22 -38
- package/dist/17dd54813d5acc10bf8f.wasm +0 -0
- package/dist/183.bundle.a3e238998be71c4b2af8.js +30410 -0
- package/dist/{506.bundle.5731bb4349e266491225.js → 188.bundle.51dc4b37920f45594393.js} +23 -28
- package/dist/{342.bundle.e7c3d500f86fdfcc62b5.js → 206.bundle.fcaa081a0d1f68095c31.js} +1991 -1145
- package/dist/20fc4c659b85ccd2a9c0.wasm +0 -0
- package/dist/217.bundle.d44bbaa50b6fa563fe15.js +115126 -0
- package/dist/{451.bundle.57c21db5d003c75e9d61.js → 295.bundle.5ace95771ced62bdcab8.js} +111 -128
- package/dist/{125.bundle.253395f320b72180da63.js → 297.bundle.194d8985ab974839b5b6.js} +7 -8
- package/dist/{19.bundle.f77c5787b6d8ac0b638b.js → 325.bundle.fd8e0c18db4708d03a91.js} +477 -373
- package/dist/335.bundle.8400aa5a88697a6b9d53.js +2590 -0
- package/dist/{202.bundle.d3490836f71e001dd30f.js → 342.bundle.e6d0bba29351b5650a8c.js} +566 -868
- package/dist/{776.bundle.a2dedb405a12ffd7699b.js → 41.bundle.0905b258a90a7c6437bb.js} +7453 -3624
- package/dist/422.bundle.c6fd037b075dd54f1ba7.js +865 -0
- package/dist/{957.bundle.9ea4506963ef8b2d84ba.js → 433.bundle.e0018820758f5a86fa7f.js} +14797 -27561
- package/dist/445.bundle.38c6d2af64e41cd7c614.js +7835 -0
- package/dist/{126.bundle.6e7111d58bcc937ffd80.js → 448.bundle.5e6da31477887bf53016.js} +356 -430
- package/dist/487.bundle.89d973049defb3ba6cb7.js +1876 -0
- package/dist/{886.bundle.c8dd3ecc42a4253de278.js → 530.bundle.207b38c15c4c01e4db0e.js} +104 -121
- package/dist/{250.bundle.aea3335667054bdefe36.js → 544.bundle.1c1f57118560046649c1.js} +37 -62
- package/dist/574.bundle.d648fea691d6709bf2b4.js +2652 -0
- package/dist/{181.css → 574.css} +1 -1
- package/dist/{410.bundle.15c855b0ff4a1a674fb8.js → 594.bundle.84076375b127b9c7f673.js} +183 -221
- package/dist/{221.bundle.aef554202c58483cb34e.js → 633.bundle.acab89baaa06a299d679.js} +365 -553
- package/dist/{774.bundle.4b2dc46a35012b898e1a.js → 644.bundle.1e77691d2eeb96a423b0.js} +1852 -8945
- package/dist/{663.bundle.d7be28450db14266cdd0.js → 669.bundle.b17e8a621e38d92c653f.js} +310 -265
- package/dist/699.bundle.9367d7ef9f7615b2e733.js +772 -0
- package/dist/702.bundle.963481fbf871984b646f.js +8426 -0
- package/dist/722.bundle.afab1fe6bfcd569130ac.js +1083 -0
- package/dist/{359.bundle.45ecb3d28e8c22142606.js → 724.bundle.55f9f49816de931af91a.js} +165 -260
- package/dist/{757.bundle.ec8301d8e70d2b990f65.js → 726.bundle.0b3d9277d22fe7e15b89.js} +512 -879
- package/dist/{530.bundle.a03b6f942ace3e1baa1e.js → 835.bundle.15aff0b7433bb0dd6d6d.js} +37 -30
- package/dist/{822.bundle.82cdc418f8f56da6060b.js → 862.bundle.d32ab08e64806b2e964d.js} +81 -97
- package/dist/{236.bundle.4e9924934a747afac132.js → 889.bundle.8ef8b723d0163d5d135c.js} +207 -199
- package/dist/{281.bundle.deb7492d143e7768d8bf.js → 905.bundle.8a96e1a75b7cfe5ec093.js} +157 -124
- package/dist/{814.bundle.c8c951d20039b63b865a.js → 907.bundle.5c88ed911bed18582da4.js} +16 -30
- package/dist/{417.bundle.af0a207c29b109f84159.js → 931.bundle.d270a1fda9a2836c3cc5.js} +26 -26
- package/dist/{686.bundle.dccef1f36e4bc79bcc48.js → 939.bundle.9d93b2e47c52338747a2.js} +7 -8
- package/dist/94.bundle.f5f2479c214180d05d42.js +778 -0
- package/dist/{12.bundle.b5ca13e5363f170ecb3b.js → 961.bundle.f4e52bc76d3044d05372.js} +20 -33
- package/dist/app-config.js +1 -0
- package/dist/app.bundle.css +16 -13
- package/dist/{app.bundle.a978edc59b9d82f2eb22.js → app.bundle.ed937512f7d19d61c411.js} +183396 -87682
- package/dist/assets/images/CT-AAA.png +0 -0
- package/dist/assets/images/CT-AAA2.png +0 -0
- package/dist/assets/images/CT-Air.png +0 -0
- package/dist/assets/images/CT-Bone.png +0 -0
- package/dist/assets/images/CT-Bones.png +0 -0
- package/dist/assets/images/CT-Cardiac.png +0 -0
- package/dist/assets/images/CT-Cardiac2.png +0 -0
- package/dist/assets/images/CT-Cardiac3.png +0 -0
- package/dist/assets/images/CT-Chest-Contrast-Enhanced.png +0 -0
- package/dist/assets/images/CT-Chest-Vessels.png +0 -0
- package/dist/assets/images/CT-Coronary-Arteries-2.png +0 -0
- package/dist/assets/images/CT-Coronary-Arteries-3.png +0 -0
- package/dist/assets/images/CT-Coronary-Arteries.png +0 -0
- package/dist/assets/images/CT-Cropped-Volume-Bone.png +0 -0
- package/dist/assets/images/CT-Fat.png +0 -0
- package/dist/assets/images/CT-Liver-Vasculature.png +0 -0
- package/dist/assets/images/CT-Lung.png +0 -0
- package/dist/assets/images/CT-MIP.png +0 -0
- package/dist/assets/images/CT-Muscle.png +0 -0
- package/dist/assets/images/CT-Pulmonary-Arteries.png +0 -0
- package/dist/assets/images/CT-Soft-Tissue.png +0 -0
- package/dist/assets/images/DTI-FA-Brain.png +0 -0
- package/dist/assets/images/MR-Angio.png +0 -0
- package/dist/assets/images/MR-Default.png +0 -0
- package/dist/assets/images/MR-MIP.png +0 -0
- package/dist/assets/images/MR-T2-Brain.png +0 -0
- package/dist/assets/images/VolumeRendering.png +0 -0
- package/dist/cornerstoneDICOMImageLoader.min.js +1 -1
- package/dist/cornerstoneDICOMImageLoader.min.js.map +1 -1
- package/dist/{dicom-microscopy-viewer.bundle.2c146384eb9466d02ff8.js → dicom-microscopy-viewer.bundle.d3a56dc9f62df5e11019.js} +3 -3
- package/dist/histogram-worker.bundle.829e14ec12c2b41a4323.js +359 -0
- package/dist/index.html +1 -1
- package/dist/{index.worker.e62ecca63f1a2e124230.worker.js → index.worker.64c896c4316fcd506666.worker.js} +2 -2
- package/dist/index.worker.64c896c4316fcd506666.worker.js.map +1 -0
- package/dist/polySeg.bundle.f1a6ece1396dc1385155.js +249 -0
- package/dist/serve.json +12 -0
- package/dist/sw.js +1 -1
- package/package.json +26 -22
- package/dist/181.bundle.a62b9f0ec692299acb35.js +0 -1527
- package/dist/23.bundle.e008ad788170f2ed5569.js +0 -900
- package/dist/604.bundle.a51f83e64004bca5f497.js +0 -1848
- package/dist/613.bundle.9e7072e5b575354fe51e.js +0 -532
- package/dist/743.bundle.489f7df3a089d4d374e1.js +0 -78007
- package/dist/75788f12450d4c5ed494.wasm +0 -0
- package/dist/775.bundle.2285e7e0e67878948c0d.js +0 -1009
- package/dist/788.bundle.207ac23c0dfa70cbe3fb.js +0 -2682
- package/dist/82.bundle.d6fdcca0f67540bb226a.js +0 -1049
- package/dist/index.worker.e62ecca63f1a2e124230.worker.js.map +0 -1
- /package/dist/{19.css → 325.css} +0 -0
- /package/dist/{776.css → 41.css} +0 -0
- /package/dist/{579.css → 481.css} +0 -0
- /package/dist/{250.css → 544.css} +0 -0
- /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
|
+
}]);
|